Last active
May 30, 2021 10:47
-
-
Save AliceLR/f09d6be8b83eb86d38bc5a6ab530ebe3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ ./mixer.exe | |
Output samples: 4800000 | |
Function: exo_mixer | |
0: 48000Hz <- 48000Hz, vol=256, chn=1, mode=FLAT : 22435 us | |
1: 48000Hz <- 48000Hz, vol=256, chn=2, mode=FLAT : 22965 us | |
2: 48000Hz <- 48000Hz, vol=179, chn=1, mode=FLAT : 26772 us | |
3: 48000Hz <- 48000Hz, vol=179, chn=2, mode=FLAT : 29794 us | |
4: 48000Hz <- 44100Hz, vol=256, chn=1, mode=Nearest : 28849 us | |
5: 48000Hz <- 44100Hz, vol=256, chn=2, mode=Nearest : 27272 us | |
6: 48000Hz <- 44100Hz, vol=179, chn=1, mode=Nearest : 26582 us | |
7: 48000Hz <- 44100Hz, vol=179, chn=2, mode=Nearest : 32344 us | |
8: 48000Hz <- 44100Hz, vol=256, chn=1, mode=Linear : 36516 us | |
9: 48000Hz <- 44100Hz, vol=256, chn=2, mode=Linear : 47827 us | |
10: 48000Hz <- 44100Hz, vol=179, chn=1, mode=Linear : 40985 us | |
11: 48000Hz <- 44100Hz, vol=179, chn=2, mode=Linear : 53354 us | |
12: 48000Hz <- 44100Hz, vol=256, chn=1, mode=Cubic : 103178 us | |
13: 48000Hz <- 44100Hz, vol=256, chn=2, mode=Cubic : 171409 us | |
14: 48000Hz <- 44100Hz, vol=179, chn=1, mode=Cubic : 110741 us | |
15: 48000Hz <- 44100Hz, vol=179, chn=2, mode=Cubic : 184491 us | |
16: 48000Hz <- 22050Hz, vol=256, chn=1, mode=Nearest : 24953 us | |
17: 48000Hz <- 22050Hz, vol=256, chn=2, mode=Nearest : 26652 us | |
18: 48000Hz <- 22050Hz, vol=179, chn=1, mode=Nearest : 26688 us | |
19: 48000Hz <- 22050Hz, vol=179, chn=2, mode=Nearest : 32573 us | |
20: 48000Hz <- 22050Hz, vol=256, chn=1, mode=Linear : 35655 us | |
21: 48000Hz <- 22050Hz, vol=256, chn=2, mode=Linear : 54606 us | |
22: 48000Hz <- 22050Hz, vol=179, chn=1, mode=Linear : 38543 us | |
23: 48000Hz <- 22050Hz, vol=179, chn=2, mode=Linear : 52945 us | |
24: 48000Hz <- 22050Hz, vol=256, chn=1, mode=Cubic : 103621 us | |
25: 48000Hz <- 22050Hz, vol=256, chn=2, mode=Cubic : 166605 us | |
26: 48000Hz <- 22050Hz, vol=179, chn=1, mode=Cubic : 109783 us | |
27: 48000Hz <- 22050Hz, vol=179, chn=2, mode=Cubic : 187311 us | |
28: 48000Hz <- 88200Hz, vol=256, chn=1, mode=Nearest : 24524 us | |
29: 48000Hz <- 88200Hz, vol=256, chn=2, mode=Nearest : 28573 us | |
30: 48000Hz <- 88200Hz, vol=179, chn=1, mode=Nearest : 26770 us | |
31: 48000Hz <- 88200Hz, vol=179, chn=2, mode=Nearest : 33647 us | |
32: 48000Hz <- 88200Hz, vol=256, chn=1, mode=Linear : 36670 us | |
33: 48000Hz <- 88200Hz, vol=256, chn=2, mode=Linear : 52245 us | |
34: 48000Hz <- 88200Hz, vol=179, chn=1, mode=Linear : 38754 us | |
35: 48000Hz <- 88200Hz, vol=179, chn=2, mode=Linear : 53203 us | |
36: 48000Hz <- 88200Hz, vol=256, chn=1, mode=Cubic : 105438 us | |
37: 48000Hz <- 88200Hz, vol=256, chn=2, mode=Cubic : 166757 us | |
38: 48000Hz <- 88200Hz, vol=179, chn=1, mode=Cubic : 110132 us | |
39: 48000Hz <- 88200Hz, vol=179, chn=2, mode=Cubic : 184436 us | |
Function: templ_mixer | |
0: 48000Hz <- 48000Hz, vol=256, chn=1, mode=FLAT : 17131 us | |
1: 48000Hz <- 48000Hz, vol=256, chn=2, mode=FLAT : 23422 us | |
2: 48000Hz <- 48000Hz, vol=179, chn=1, mode=FLAT : 23503 us | |
3: 48000Hz <- 48000Hz, vol=179, chn=2, mode=FLAT : 20326 us | |
4: 48000Hz <- 44100Hz, vol=256, chn=1, mode=Nearest : 20711 us | |
5: 48000Hz <- 44100Hz, vol=256, chn=2, mode=Nearest : 24233 us | |
6: 48000Hz <- 44100Hz, vol=179, chn=1, mode=Nearest : 22659 us | |
7: 48000Hz <- 44100Hz, vol=179, chn=2, mode=Nearest : 29459 us | |
8: 48000Hz <- 44100Hz, vol=256, chn=1, mode=Linear : 33721 us | |
9: 48000Hz <- 44100Hz, vol=256, chn=2, mode=Linear : 46263 us | |
10: 48000Hz <- 44100Hz, vol=179, chn=1, mode=Linear : 36550 us | |
11: 48000Hz <- 44100Hz, vol=179, chn=2, mode=Linear : 54954 us | |
12: 48000Hz <- 44100Hz, vol=256, chn=1, mode=Cubic : 98541 us | |
13: 48000Hz <- 44100Hz, vol=256, chn=2, mode=Cubic : 169011 us | |
14: 48000Hz <- 44100Hz, vol=179, chn=1, mode=Cubic : 107805 us | |
15: 48000Hz <- 44100Hz, vol=179, chn=2, mode=Cubic : 175298 us | |
16: 48000Hz <- 22050Hz, vol=256, chn=1, mode=Nearest : 19606 us | |
17: 48000Hz <- 22050Hz, vol=256, chn=2, mode=Nearest : 23786 us | |
18: 48000Hz <- 22050Hz, vol=179, chn=1, mode=Nearest : 22596 us | |
19: 48000Hz <- 22050Hz, vol=179, chn=2, mode=Nearest : 29747 us | |
20: 48000Hz <- 22050Hz, vol=256, chn=1, mode=Linear : 32246 us | |
21: 48000Hz <- 22050Hz, vol=256, chn=2, mode=Linear : 47347 us | |
22: 48000Hz <- 22050Hz, vol=179, chn=1, mode=Linear : 36646 us | |
23: 48000Hz <- 22050Hz, vol=179, chn=2, mode=Linear : 53825 us | |
24: 48000Hz <- 22050Hz, vol=256, chn=1, mode=Cubic : 98109 us | |
25: 48000Hz <- 22050Hz, vol=256, chn=2, mode=Cubic : 166653 us | |
26: 48000Hz <- 22050Hz, vol=179, chn=1, mode=Cubic : 106044 us | |
27: 48000Hz <- 22050Hz, vol=179, chn=2, mode=Cubic : 177058 us | |
28: 48000Hz <- 88200Hz, vol=256, chn=1, mode=Nearest : 22747 us | |
29: 48000Hz <- 88200Hz, vol=256, chn=2, mode=Nearest : 27709 us | |
30: 48000Hz <- 88200Hz, vol=179, chn=1, mode=Nearest : 22850 us | |
31: 48000Hz <- 88200Hz, vol=179, chn=2, mode=Nearest : 30354 us | |
32: 48000Hz <- 88200Hz, vol=256, chn=1, mode=Linear : 32793 us | |
33: 48000Hz <- 88200Hz, vol=256, chn=2, mode=Linear : 47094 us | |
34: 48000Hz <- 88200Hz, vol=179, chn=1, mode=Linear : 36086 us | |
35: 48000Hz <- 88200Hz, vol=179, chn=2, mode=Linear : 54275 us | |
36: 48000Hz <- 88200Hz, vol=256, chn=1, mode=Cubic : 97572 us | |
37: 48000Hz <- 88200Hz, vol=256, chn=2, mode=Cubic : 167816 us | |
38: 48000Hz <- 88200Hz, vol=179, chn=1, mode=Cubic : 111874 us | |
39: 48000Hz <- 88200Hz, vol=179, chn=2, mode=Cubic : 182448 us | |
Mismatches: 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* MegaZeux | |
* | |
* Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net> | |
* Copyright (C) 2004 madbrain | |
* Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk> | |
* Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com> | |
* | |
* This program is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU General Public License as | |
* published by the Free Software Foundation; either version 2 of | |
* the License, or (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
* General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program; if not, write to the Free Software | |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
*/ | |
#include "mixer.h" | |
#include <math.h> | |
typedef uint8_t Uint8; | |
typedef int16_t Sint16; | |
typedef uint32_t Uint32; | |
typedef int32_t Sint32; | |
typedef int64_t Sint64; | |
#define FP_SHIFT 13 | |
#define FP_AND ((1 << FP_SHIFT) - 1) | |
// Macros are used to generate functions to help reduce redundancy and | |
// maintain some kind of speed. Additional, fixed point is used (again, | |
// for speed purposes to avoid the hits in converting between fixed and | |
// floating point). I'm almost completely sure that fixed point is ideal | |
// for nearest and linear resampling but it might not be so good for | |
// cubic because it has to use 64bit ints and a lot of shifts | |
// (should work great on a 64bit machine though). The cubic resampler can | |
// be further optimized in other ways as well, and might be in due time. | |
// For now, if cubic doesn't give you good speed stick with linear which | |
// should be quite fast. | |
#define FLAT_SETUP_INDEX(channels) | |
#define NEAREST_SETUP_INDEX(channels) | |
#define FRACTIONAL_SETUP_INDEX(channels) \ | |
int_index = (Sint32)(s_index >> FP_SHIFT) * channels; \ | |
frac_index = (Sint32)(s_index & FP_AND); \ | |
#define LINEAR_SETUP_INDEX(channels) \ | |
FRACTIONAL_SETUP_INDEX(channels) \ | |
#define CUBIC_SETUP_INDEX(channels) \ | |
FRACTIONAL_SETUP_INDEX(channels) \ | |
#define FLAT_MIX_SAMPLE(dest, channels, offset) \ | |
dest src_buffer[i2 + offset] \ | |
#define NEAREST_MIX_SAMPLE(dest, channels, offset) \ | |
dest src_buffer[((s_index >> FP_SHIFT) * channels) + offset] \ | |
#define LINEAR_MIX_SAMPLE(dest, channels, offset) \ | |
right_sample = src_buffer[int_index + offset]; \ | |
dest (right_sample + ((frac_index * \ | |
(src_buffer[int_index + channels + offset] - right_sample)) >> \ | |
FP_SHIFT)) \ | |
#define CUBIC_MIX_SAMPLE(dest, channels, offset) \ | |
s0 = src_buffer[int_index - channels + offset] << FP_SHIFT; \ | |
s1 = src_buffer[int_index + offset] << FP_SHIFT; \ | |
s2 = src_buffer[int_index + channels + offset] << FP_SHIFT; \ | |
s3 = src_buffer[int_index + (channels * 2) + offset] << FP_SHIFT; \ | |
\ | |
a = (((3 * (s1 - s2)) - s0 + s3) / 2); \ | |
b = ((2 * s2) + s0 - (((5 * s1) + s3) / 2)); \ | |
c = ((s2 - s0) / 2); \ | |
\ | |
dest ((Sint32)(((((((((a * frac_index) >> FP_SHIFT) + b) * \ | |
frac_index) >> FP_SHIFT) + c) * frac_index) >> FP_SHIFT) + s1) >> \ | |
FP_SHIFT) \ | |
#define RESAMPLE_LOOP_HEADER \ | |
for(i = 0; i < write_len; i += 2, s_index += d) \ | |
{ \ | |
#define FLAT_LOOP_HEADER(channels) \ | |
for(i = 0, i2 = 0; i < write_len; i += 2, i2 += channels) \ | |
{ \ | |
#define NEAREST_LOOP_HEADER(dummy) \ | |
RESAMPLE_LOOP_HEADER \ | |
#define LINEAR_LOOP_HEADER(dummy) \ | |
RESAMPLE_LOOP_HEADER \ | |
#define CUBIC_LOOP_HEADER(dummy) \ | |
RESAMPLE_LOOP_HEADER \ | |
#define SPLIT_HEADER \ | |
Sint32 int_index; \ | |
Sint32 frac_index; \ | |
#define RESAMPLE_HEADER \ | |
Sint64 s_index = sample_index; \ | |
Sint64 d = frequency_delta; \ | |
#define FLAT_HEADER \ | |
Uint32 i2; \ | |
#define NEAREST_HEADER \ | |
RESAMPLE_HEADER \ | |
#define LINEAR_HEADER \ | |
RESAMPLE_HEADER \ | |
SPLIT_HEADER \ | |
Sint32 right_sample; \ | |
#define CUBIC_HEADER \ | |
RESAMPLE_HEADER \ | |
SPLIT_HEADER \ | |
Sint32 s0, s1, s2, s3; \ | |
Sint64 a, b, c; \ | |
#define MIXER_FOOTER(channels) \ | |
} \ | |
s_index -= (data_window_length / (channels * 2)) << FP_SHIFT; \ | |
sample_index = s_index; \ | |
#define FLAT_FOOTER(dummy) \ | |
} \ | |
sample_index = 0; \ | |
#define NEAREST_FOOTER(channels) \ | |
MIXER_FOOTER(channels) \ | |
#define LINEAR_FOOTER(channels) \ | |
MIXER_FOOTER(channels) \ | |
#define CUBIC_FOOTER(channels) \ | |
MIXER_FOOTER(channels) \ | |
/* | |
#define VOL \ | |
* volume / 256 \ | |
*/ | |
#define VOL \ | |
* volume >> 8 \ | |
#define SETUP_MIXER(type, num, mod) \ | |
case num: \ | |
{ \ | |
type##_HEADER \ | |
type##_LOOP_HEADER(2) \ | |
type##_SETUP_INDEX(2) \ | |
type##_MIX_SAMPLE(dest_buffer[i] +=, 2, 0) mod; \ | |
type##_MIX_SAMPLE(dest_buffer[i + 1] +=, 2, 1) mod; \ | |
type##_FOOTER(2) \ | |
break; \ | |
} \ | |
#define SETUP_MIXER_MONO(type, num, mod) \ | |
case num: \ | |
{ \ | |
Sint32 current_sample; \ | |
type##_HEADER \ | |
type##_LOOP_HEADER(1) \ | |
type##_SETUP_INDEX(1) \ | |
type##_MIX_SAMPLE(current_sample =, 1, 0) mod; \ | |
dest_buffer[i] += current_sample; \ | |
dest_buffer[i + 1] += current_sample; \ | |
type##_FOOTER(1) \ | |
break; \ | |
} \ | |
#define SETUP_MIXER_ALL(type, num) \ | |
SETUP_MIXER(type, (num * 4), ) \ | |
SETUP_MIXER_MONO(type, (num * 4) + 1, ) \ | |
SETUP_MIXER(type, (num * 4) + 2, VOL) \ | |
SETUP_MIXER_MONO(type, (num * 4) + 3, VOL) \ | |
void exo_mix_data(int32_t * __restrict__ dest_buffer, size_t len, const int16_t *src, | |
size_t src_len, int volume, unsigned int channels, unsigned int resample_mode, | |
size_t input_frequency, size_t output_frequency) | |
{ | |
Sint16 *src_buffer = (Sint16 *)src; | |
Uint32 write_len = len / 2; | |
Uint32 volume_mode = 1; | |
Uint32 mono_mode = (channels == 1); | |
Uint32 i; | |
Sint64 frequency_delta = ((Sint64)input_frequency << FP_SHIFT) / output_frequency; | |
Sint64 sample_index = 0; | |
Uint32 data_window_length = | |
(Uint32)(ceil((double)write_len / channels * | |
input_frequency / output_frequency) * 2 * channels); | |
if(input_frequency == output_frequency) | |
resample_mode = 0; | |
if(volume == 256) | |
volume_mode = 0; | |
switch((resample_mode << 2) | (volume_mode << 1) | mono_mode) | |
{ | |
SETUP_MIXER_ALL(FLAT, 0) | |
SETUP_MIXER_ALL(NEAREST, 1) | |
SETUP_MIXER_ALL(LINEAR, 2) | |
SETUP_MIXER_ALL(CUBIC, 3) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CFLAGS := -O3 -std=gnu++17 -Wall -Wextra -Wno-unused-parameter -ffast-math ${CFLAGS} | |
LDFLAGS += | |
LDLIBS += | |
OBJS := mixer.o templ_mixer.o exo_mixer.o | |
TARGS := mixer | |
all: ${TARGS} | |
${TARGS}: ${OBJS} | |
${CXX} ${CFLAGS} $^ -o $@ ${LDFLAGS} ${LDLIBS} | |
%.o: %.c | |
${CXX} -MD ${CFLAGS} -c $< -o $@ | |
%.o: %.cpp | |
${CXX} -MD ${CFLAGS} -c $< -o $@ | |
clean: | |
rm -f *.d *.o mixer mixer.exe |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <inttypes.h> | |
#include <stdio.h> | |
#include <time.h> | |
#include <algorithm> | |
#include <cassert> | |
#include <chrono> | |
#include <vector> | |
#include "mixer.h" | |
static uint64_t rng_state; | |
// Seed the RNG from system time on startup | |
static void rng_seed_init(void) | |
{ | |
uint64_t seed = (((uint64_t)time(NULL)) << 32) | clock(); | |
rng_state = seed; | |
} | |
// xorshift* | |
// Implementation from https://en.wikipedia.org/wiki/Xorshift | |
unsigned int Random(uint64_t range) | |
{ | |
uint64_t x = rng_state; | |
if(x == 0) x = 1; | |
x ^= x >> 12; // a | |
x ^= x << 25; // b | |
x ^= x >> 27; // c | |
rng_state = x; | |
return (((x * 0x2545F4914F6CDD1D) >> 32) * range) >> 32; | |
} | |
template<class T, int N> | |
static constexpr size_t ARRAY_SIZE(const T (&ignore)[N]) | |
{ | |
return N; | |
} | |
typedef int16_t sample_t; | |
typedef int32_t sample_output_t; | |
static constexpr size_t output_frequency = 48000; | |
static constexpr size_t input_frequencies[] = | |
{ | |
output_frequency, // Same as output--use flat resampling. | |
44100, // Different--force upsampling. | |
8363, // Different--force upsampling. | |
88200, // Different--force downsampling. | |
}; | |
static constexpr unsigned int resample_modes[] = | |
{ | |
0, // Flat | |
1, // Nearest | |
2, // Linear | |
3, // Cubic | |
}; | |
static constexpr const char *resample_mode_str[] = | |
{ | |
"FLAT", | |
"Nearest", | |
"Linear", | |
"Cubic", | |
"Sinc-L.", | |
}; | |
static constexpr int volumes[] = | |
{ | |
256, // No volume mixing | |
179, // Volume mixing | |
}; | |
static constexpr int channel_counts[] = | |
{ | |
1, // Mono mode | |
2, // Stereo mode | |
}; | |
static constexpr size_t num_tests = | |
((ARRAY_SIZE(input_frequencies) - 1) * (ARRAY_SIZE(resample_modes) - 1) + 1) * | |
ARRAY_SIZE(volumes) * ARRAY_SIZE(channel_counts); | |
static constexpr size_t max_frequency = | |
*std::max_element(std::begin(input_frequencies), std::end(input_frequencies)); | |
static constexpr int max_channels = | |
*std::max_element(std::begin(channel_counts), std::end(channel_counts)); | |
static constexpr size_t multiplier = 10; | |
static constexpr size_t repeat_times = 100; | |
// The destination buffer is always stereo. | |
static constexpr size_t dest_size = output_frequency * multiplier * 2; | |
// Allocate the source buffer with enough space for both the highest input | |
// frequency and channel count. Also, add extra for linear/cubic modes. | |
static constexpr size_t src_size = max_frequency * multiplier * max_channels + 256; | |
void test_function(const mix_data_function mix_data_f, const std::vector<sample_t> &src, | |
std::vector<std::vector<sample_output_t>> &dests) | |
{ | |
size_t dest_num = 0; | |
for(size_t freq : input_frequencies) | |
{ | |
for(unsigned resample_mode : resample_modes) | |
{ | |
// Only use flat copying when the frequencies match. | |
if(freq == output_frequency && resample_mode != 0) | |
continue; | |
// Only use resampling when the frequencies don't match. | |
if(freq != output_frequency && resample_mode == 0) | |
continue; | |
for(int volume : volumes) | |
{ | |
for(int channels : channel_counts) | |
{ | |
printf("%2zu: %zuHz <- %6zuHz, vol=%d, chn=%d, mode=%-8s: ", dest_num, | |
output_frequency, freq, volume, channels, resample_mode_str[resample_mode]); | |
fflush(stdout); | |
assert(dest_num < num_tests); | |
std::vector<sample_output_t> &dest = dests[dest_num++]; | |
auto start_time = std::chrono::steady_clock::now(); | |
for(size_t i = 0; i < repeat_times; i++) | |
{ | |
mix_data_f(dest.data(), dest.size(), src.data(), src.size(), | |
volume, channels, resample_mode, freq, output_frequency); | |
} | |
auto end_time = std::chrono::steady_clock::now(); | |
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time); | |
printf("%8" PRId64 " us\n", (int64_t)duration.count()); | |
fflush(stdout); | |
} | |
} | |
} | |
} | |
} | |
int main(void) | |
{ | |
std::vector<std::vector<sample_output_t>> exo_result(num_tests, std::vector<sample_output_t>(dest_size, 0)); | |
std::vector<std::vector<sample_output_t>> tmpl_result(num_tests, std::vector<sample_output_t>(dest_size, 0)); | |
std::vector<sample_t> src(src_size); | |
rng_seed_init(); | |
for(sample_t &smpl : src) | |
smpl = Random(UINT16_MAX) - (INT16_MAX + 1); | |
printf("Output samples: %zu\n", output_frequency * multiplier); | |
printf("\nFunction: exo_mixer\n"); | |
test_function(exo_mix_data, src, exo_result); | |
printf("\nFunction: templ_mixer\n"); | |
test_function(template_mix_data, src, tmpl_result); | |
printf("\n"); | |
size_t mismatches = 0; | |
for(size_t i = 0; i < num_tests; i++) | |
{ | |
if(exo_result[i] != tmpl_result[i]) | |
{ | |
printf("Mismatch in test %zu\n", i); | |
mismatches++; | |
} | |
} | |
printf("Mismatches: %zu\n", mismatches); | |
fflush(stdout); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stddef.h> | |
#include <stdint.h> | |
typedef bool boolean; | |
typedef void (*mix_data_function)(int32_t * __restrict__ dest_buffer, size_t len, | |
const int16_t *src, size_t src_len, int volume, unsigned channels, | |
unsigned resample_mode, size_t input_frequency, size_t output_frequency); | |
void exo_mix_data(int32_t * __restrict__ dest_buffer, size_t len, const int16_t *src, | |
size_t src_len, int volume, unsigned int channels, unsigned int resample_mode, | |
size_t input_frequency, size_t output_frequency); | |
void template_mix_data(int32_t * __restrict__ dest_buffer, size_t len, const int16_t *src, | |
size_t src_len, int volume, unsigned int channels, unsigned int resample_mode, | |
size_t input_frequency, size_t output_frequency); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* MegaZeux | |
* | |
* Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net> | |
* Copyright (C) 2004 madbrain | |
* Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk> | |
* Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com> | |
* | |
* This program is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU General Public License as | |
* published by the Free Software Foundation; either version 2 of | |
* the License, or (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
* General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program; if not, write to the Free Software | |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
*/ | |
#include "mixer.h" | |
#include <math.h> | |
#include <cassert> | |
#define FP_SHIFT 13 | |
//#define FP_SHIFT ((ssize_t)(sizeof(ssize_t) >= 8 ? 16 : 13)) | |
#define FP_AND ((1 << FP_SHIFT) - 1) | |
// Placeholder. | |
struct sampled_stream | |
{ | |
int64_t sample_index; | |
uint64_t frequency_delta; | |
uint32_t data_window_length; | |
uint32_t channels; | |
}; | |
enum mixer_resample | |
{ | |
FLAT = 0, | |
NEAREST = 1, | |
LINEAR = 2, | |
CUBIC = 3, | |
}; | |
enum mixer_channels | |
{ | |
MONO = 1, | |
STEREO = 2, | |
}; | |
enum mixer_volume | |
{ | |
FIXED = 0, | |
DYNAMIC = 1, | |
}; | |
static void update_sample_index(struct sampled_stream *s, int64_t index) | |
{ | |
s->sample_index = index - ((int64_t)(s->data_window_length / (s->channels * 2)) << FP_SHIFT); | |
} | |
template<mixer_volume VOLUME> | |
static int32_t volume_function(int32_t sample, int volume) | |
{ | |
/** | |
* NOTE: previous versions of MZX did a /256 here. Just do the bitshift--the | |
* rounding difference is more or less irrelevant and it performs better than | |
* GCC's optimization for signed constant division. | |
*/ | |
if(VOLUME) | |
return (sample * volume) >> 8; | |
return sample; | |
} | |
template<mixer_channels CHANNELS, mixer_volume VOLUME> | |
static void flat_mix_loop(struct sampled_stream *s, | |
int32_t * __restrict__ dest, size_t write_len, const int16_t *src, int volume) | |
{ | |
for(size_t i = 0; i < write_len; i += 2) | |
{ | |
if(CHANNELS >= STEREO) | |
{ | |
*(dest++) += volume_function<VOLUME>(*(src++), volume); | |
*(dest++) += volume_function<VOLUME>(*(src++), volume); | |
} | |
else | |
{ | |
int32_t smpl = volume_function<VOLUME>(*(src++), volume); | |
*(dest++) += smpl; | |
*(dest++) += smpl; | |
} | |
} | |
s->sample_index = 0; | |
} | |
typedef int32_t (*mix_function)(const int16_t *src_offset, ssize_t frac_index); | |
template<mixer_channels CHANNELS> | |
static int32_t nearest_mix(const int16_t *src_offset, ssize_t frac_index) | |
{ | |
return src_offset[0]; | |
} | |
template<mixer_channels CHANNELS> | |
static int32_t linear_mix(const int16_t *src_offset, ssize_t frac_index) | |
{ | |
int32_t left = src_offset[0]; | |
int32_t right = src_offset[CHANNELS]; | |
return left + ((right - left) * frac_index >> FP_SHIFT); | |
} | |
template<mixer_channels CHANNELS> | |
static int32_t cubic_mix(const int16_t *src_offset, ssize_t frac_index) | |
{ | |
/** | |
* NOTE: copied mostly verbatim from the old mixer code, with cleanup. | |
* This uses ssize_t instead of int32_t since it seems to be faster for | |
* 64-bit machines in this particular resampler. | |
* | |
* This uses a Hermite spline. This is somewhat faster to compute and | |
* generally considered better quality than a Lagrange cubic. | |
*/ | |
/* | |
int32_t s0 = src_offset[-CHANNELS] << FP_SHIFT; | |
int32_t s1 = src_offset[0] << FP_SHIFT; | |
int32_t s2 = src_offset[CHANNELS] << FP_SHIFT; | |
int32_t s3 = src_offset[CHANNELS * 2] << FP_SHIFT; | |
*/ | |
ssize_t s0 = (ssize_t)src_offset[-CHANNELS] << FP_SHIFT; | |
ssize_t s1 = (ssize_t)src_offset[0] << FP_SHIFT; | |
ssize_t s2 = (ssize_t)src_offset[CHANNELS] << FP_SHIFT; | |
ssize_t s3 = (ssize_t)src_offset[CHANNELS * 2] << FP_SHIFT; | |
int64_t a = (((3 * (s1 - s2)) - s0 + s3) / 2); | |
int64_t b = ((2 * s2) + s0 - (((5 * s1) + s3) / 2)); | |
int64_t c = ((s2 - s0) / 2); | |
a = ((a * frac_index) >> FP_SHIFT) + b; | |
a = ((a * frac_index) >> FP_SHIFT) + c; | |
a = ((a * frac_index) >> FP_SHIFT) + s1; | |
return (a >> FP_SHIFT); | |
} | |
template<mixer_channels CHANNELS, mixer_volume VOLUME, mix_function MIX> | |
static void resample_mix_loop(struct sampled_stream *s, | |
int32_t * __restrict__ dest, size_t write_len, const int16_t *src, int volume) | |
{ | |
int64_t sample_index = s->sample_index; | |
int64_t delta = s->frequency_delta; | |
for(size_t i = 0; i < write_len; i += 2, sample_index += delta) | |
{ | |
ssize_t int_index = (sample_index >> FP_SHIFT) * CHANNELS; | |
ssize_t frac_index = sample_index & FP_AND; | |
if(CHANNELS >= STEREO) | |
{ | |
int32_t mix_a = MIX(src + int_index + 0, frac_index); | |
int32_t mix_b = MIX(src + int_index + 1, frac_index); | |
*(dest++) += volume_function<VOLUME>(mix_a, volume); | |
*(dest++) += volume_function<VOLUME>(mix_b, volume); | |
} | |
else | |
{ | |
int32_t mix = MIX(src + int_index, frac_index); | |
int32_t smpl = volume_function<VOLUME>(mix, volume); | |
*(dest++) += smpl; | |
*(dest++) += smpl; | |
} | |
} | |
update_sample_index(s, sample_index); | |
} | |
template<mixer_channels CHANNELS, mixer_volume VOLUME> | |
static void mixer_function(struct sampled_stream *s, | |
int32_t * __restrict__ dest, size_t write_len, const int16_t *src, int volume, | |
int resample_mode) | |
{ | |
switch((mixer_resample)resample_mode) | |
{ | |
case FLAT: | |
flat_mix_loop<CHANNELS, VOLUME>(s, dest, write_len, src, volume); | |
break; | |
case NEAREST: | |
resample_mix_loop<CHANNELS, VOLUME, nearest_mix<CHANNELS>>(s, | |
dest, write_len, src, volume); | |
break; | |
case LINEAR: | |
resample_mix_loop<CHANNELS, VOLUME, linear_mix<CHANNELS>>(s, | |
dest, write_len, src, volume); | |
break; | |
case CUBIC: | |
resample_mix_loop<CHANNELS, VOLUME, cubic_mix<CHANNELS>>(s, | |
dest, write_len, src, volume); | |
break; | |
} | |
} | |
template<mixer_volume VOLUME> | |
static void mixer_function(struct sampled_stream *s, | |
int32_t * __restrict__ dest, size_t write_len, const int16_t *src, int volume, | |
int resample_mode, mixer_channels channel_mode) | |
{ | |
switch(channel_mode) | |
{ | |
case MONO: | |
mixer_function<MONO, VOLUME>(s, dest, write_len, src, volume, resample_mode); | |
break; | |
case STEREO: | |
mixer_function<STEREO, VOLUME>(s, dest, write_len, src, volume, resample_mode); | |
break; | |
} | |
} | |
static void mixer_function(struct sampled_stream *s, | |
int32_t * __restrict__ dest, size_t write_len, const int16_t *src, int volume, | |
int resample_mode, mixer_channels channel_mode, mixer_volume volume_mode) | |
{ | |
switch(volume_mode) | |
{ | |
case FIXED: | |
mixer_function<FIXED>(s, dest, write_len, src, volume, resample_mode, channel_mode); | |
break; | |
case DYNAMIC: | |
mixer_function<DYNAMIC>(s, dest, write_len, src, volume, resample_mode, channel_mode); | |
break; | |
} | |
} | |
void template_mix_data(int32_t * __restrict__ dest, size_t len, const int16_t *src, | |
size_t src_len, int volume, unsigned int channels, unsigned int resample_mode, | |
size_t input_frequency, size_t output_frequency) | |
{ | |
struct sampled_stream placeholder; | |
size_t write_len = len / 2; | |
enum mixer_volume use_volume = DYNAMIC; | |
enum mixer_channels use_channels = STEREO; | |
placeholder.sample_index = 0; | |
placeholder.frequency_delta = (((uint64_t)input_frequency) << FP_SHIFT) / output_frequency; | |
placeholder.channels = channels; | |
placeholder.data_window_length = | |
(ceil((double)write_len / 8 * input_frequency / output_frequency) * 2 * channels); | |
if(input_frequency == output_frequency) | |
resample_mode = 0; | |
if(volume == 256) | |
use_volume = FIXED; | |
if(channels < 2) | |
use_channels = MONO; | |
mixer_function(&placeholder, dest, write_len, src, volume, | |
resample_mode, use_channels, use_volume); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment