From 102484e439922088dbaed5511671b3f92fa50dae Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Tue, 7 Jan 2014 23:16:03 -0800 Subject: [PATCH] Tweaking of the sawtooth algorithm This change makes the saw do most of the slices without interpolation. It also uses a fixed frequency for the entire 64 sample buffer, rather than trying to sweep it linearly (the impact on quality is inaudible). The calculation of slices is done with actual freq relative to sample rate, so it should be more robust to sample rate changes. And the strategy to use is lifted up out of the inner loop. The total number of slices computed is reduced on both the low and high ends (from 64 to 36). Lastly, there's a very clever trick: the slice boundary is placed so that the interpolation zone falls evenly between notes in 12tet in 44.1 and 48 kHz sampling rates, so the interpolated path will be very unlikely in practice. --- cpp/src/SynthApp.xcodeproj/project.pbxproj | 6 +- cpp/src/SynthApp/SynthAppDelegate.h | 2 +- cpp/src/core.xcodeproj/project.pbxproj | 68 ++++++----- cpp/src/main.cc | 5 +- cpp/src/sawtooth.cc | 133 ++++++++++++--------- cpp/src/sawtooth.h | 10 +- 6 files changed, 131 insertions(+), 93 deletions(-) diff --git a/cpp/src/SynthApp.xcodeproj/project.pbxproj b/cpp/src/SynthApp.xcodeproj/project.pbxproj index 8fa6c11..3af493a 100644 --- a/cpp/src/SynthApp.xcodeproj/project.pbxproj +++ b/cpp/src/SynthApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 45; objects = { /* Begin PBXBuildFile section */ @@ -182,11 +182,7 @@ }; buildConfigurationList = F2D5F35CD2FF169326ADC8E8 /* Build configuration list for PBXProject "SynthApp" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; hasScannedForEncodings = 1; - knownRegions = ( - en, - ); mainGroup = EEAFDBA4970041AA25C22652; projectDirPath = ""; projectReferences = ( diff --git a/cpp/src/SynthApp/SynthAppDelegate.h b/cpp/src/SynthApp/SynthAppDelegate.h index 20c5d25..2973b96 100644 --- a/cpp/src/SynthApp/SynthAppDelegate.h +++ b/cpp/src/SynthApp/SynthAppDelegate.h @@ -16,7 +16,7 @@ #import -#import +#import "SynthMain.h" @interface SynthAppDelegate : NSObject { NSWindow *window; diff --git a/cpp/src/core.xcodeproj/project.pbxproj b/cpp/src/core.xcodeproj/project.pbxproj index 04cf8c3..822c43b 100644 --- a/cpp/src/core.xcodeproj/project.pbxproj +++ b/cpp/src/core.xcodeproj/project.pbxproj @@ -7,20 +7,22 @@ objects = { /* Begin PBXBuildFile section */ - 2DD71F3E9AE0C4253B8B7747 /* exp2.cc in Sources */ = {isa = PBXBuildFile; fileRef = D5669C7FDAF95F10639CF5AB /* exp2.cc */; }; - 391223C6D7249361C00A14AF /* dx7note.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA00975977E2704F74104728 /* dx7note.cc */; }; - 3DFFE98F73F04D2F66DA9FA4 /* env.cc in Sources */ = {isa = PBXBuildFile; fileRef = B73D485E55EBD9CD5950A375 /* env.cc */; }; - 50C9BC20B9DB9E51A9B852BC /* test_ringbuffer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 521793C71CAA078F5598EFC0 /* test_ringbuffer.cc */; }; - 6E01450F6BFF126216C8164C /* ringbuffer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 68FD17910F296961541A67E4 /* ringbuffer.cc */; }; - 79D1CB0F9A4671DA79729544 /* fm_op_kernel.cc in Sources */ = {isa = PBXBuildFile; fileRef = 509D811344DB98984FD6C126 /* fm_op_kernel.cc */; }; - 97788F06DA1810835D9F0A50 /* lfo.cc in Sources */ = {isa = PBXBuildFile; fileRef = 42D2A2AD8EDACBEF9B884718 /* lfo.cc */; }; - AA0F43347EBDEE6D29032DAF /* sin.cc in Sources */ = {isa = PBXBuildFile; fileRef = D1D8B6FB01C9E7E2D99378F0 /* sin.cc */; }; - AA1CD32CDCFEB4469067D98E /* resofilter.cc in Sources */ = {isa = PBXBuildFile; fileRef = 97A5CBACD479212282D0BFD6 /* resofilter.cc */; }; - B64D26CA7CC8017052D3E963 /* patch.cc in Sources */ = {isa = PBXBuildFile; fileRef = 779AFCD7AFEB1CDEACBC0425 /* patch.cc */; }; - D80FBC17492647DCFDE96F75 /* fm_core.cc in Sources */ = {isa = PBXBuildFile; fileRef = DA7AAEE2AD874001F6B71D52 /* fm_core.cc */; }; - E2649182D734B708D5E72B66 /* synth_unit.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2082841A11DF6E62596265CF /* synth_unit.cc */; }; - E31A9F6965B9E56115BAE59F /* sawtooth.cc in Sources */ = {isa = PBXBuildFile; fileRef = 48B6535400CF3AC8BABB3299 /* sawtooth.cc */; }; - FB78A2E9904695ABCC799CC3 /* freqlut.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9FF02D488CE6FD5017D7D81A /* freqlut.cc */; }; + 15B76856F0421489F3EEA593 /* freqlut.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9FF02D488CE6FD5017D7D81A /* freqlut.cc */; }; + 1A75F75619C78DB07A6085B0 /* exp2.cc in Sources */ = {isa = PBXBuildFile; fileRef = D5669C7FDAF95F10639CF5AB /* exp2.cc */; }; + 20C8EA4478F3F0E2429332F4 /* sawtooth.cc in Sources */ = {isa = PBXBuildFile; fileRef = 48B6535400CF3AC8BABB3299 /* sawtooth.cc */; }; + 2300749E4E3C2F5E2E47B813 /* fm_core.cc in Sources */ = {isa = PBXBuildFile; fileRef = DA7AAEE2AD874001F6B71D52 /* fm_core.cc */; }; + 25F144B38B846E3C5850E7BD /* dx7note.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA00975977E2704F74104728 /* dx7note.cc */; }; + 2A6CCD05E26CB8A8DCB0070C /* log2.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9A44725EA215BBEA625F264E /* log2.cc */; }; + 413968CF2316B4C71CC32B83 /* ringbuffer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 68FD17910F296961541A67E4 /* ringbuffer.cc */; }; + 539ED55158DD0A1FC841C627 /* lfo.cc in Sources */ = {isa = PBXBuildFile; fileRef = 42D2A2AD8EDACBEF9B884718 /* lfo.cc */; }; + 5BFBFDCD915B0AE650CE6798 /* patch.cc in Sources */ = {isa = PBXBuildFile; fileRef = 779AFCD7AFEB1CDEACBC0425 /* patch.cc */; }; + 9F42FBCD78CEF75B6A043433 /* env.cc in Sources */ = {isa = PBXBuildFile; fileRef = B73D485E55EBD9CD5950A375 /* env.cc */; }; + B4D196C5D9E56B0238F25397 /* fm_op_kernel.cc in Sources */ = {isa = PBXBuildFile; fileRef = 509D811344DB98984FD6C126 /* fm_op_kernel.cc */; }; + B9A52CE8D262A4428581D53C /* resofilter.cc in Sources */ = {isa = PBXBuildFile; fileRef = 97A5CBACD479212282D0BFD6 /* resofilter.cc */; }; + D541FD687D3305820E5F8FE2 /* sin.cc in Sources */ = {isa = PBXBuildFile; fileRef = D1D8B6FB01C9E7E2D99378F0 /* sin.cc */; }; + E071B9E5AD56CCD871968387 /* synth_unit.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2082841A11DF6E62596265CF /* synth_unit.cc */; }; + E5B6FED47570BBD53CD55329 /* pitchenv.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB03474DCAA7D2AC7378C51E /* pitchenv.cc */; }; + EB08E41E39595C13C10972F1 /* test_ringbuffer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 521793C71CAA078F5598EFC0 /* test_ringbuffer.cc */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -34,7 +36,9 @@ 779AFCD7AFEB1CDEACBC0425 /* patch.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = patch.cc; sourceTree = ""; }; 8B1FC9FF853D5C32F4771091 /* libcore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libcore.a; sourceTree = BUILT_PRODUCTS_DIR; }; 97A5CBACD479212282D0BFD6 /* resofilter.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = resofilter.cc; sourceTree = ""; }; + 9A44725EA215BBEA625F264E /* log2.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = log2.cc; sourceTree = ""; }; 9FF02D488CE6FD5017D7D81A /* freqlut.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = freqlut.cc; sourceTree = ""; }; + AB03474DCAA7D2AC7378C51E /* pitchenv.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = pitchenv.cc; sourceTree = ""; }; B73D485E55EBD9CD5950A375 /* env.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = env.cc; sourceTree = ""; }; BA00975977E2704F74104728 /* dx7note.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dx7note.cc; sourceTree = ""; }; D1D8B6FB01C9E7E2D99378F0 /* sin.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sin.cc; sourceTree = ""; }; @@ -56,13 +60,13 @@ 19BC3E7E3EDF760717F26DF5 = { isa = PBXGroup; children = ( - 1B875637F4FC04501E9170B8 /* Source */, + 7BF99477CB1A413FA62AFE65 /* Source */, E53EB7DD466ADEEC9F094623 /* Products */, 8512968DD6AB145582824E65 /* Build */, ); sourceTree = ""; }; - 1B875637F4FC04501E9170B8 /* Source */ = { + 7BF99477CB1A413FA62AFE65 /* Source */ = { isa = PBXGroup; children = ( BA00975977E2704F74104728 /* dx7note.cc */, @@ -72,7 +76,9 @@ 509D811344DB98984FD6C126 /* fm_op_kernel.cc */, 9FF02D488CE6FD5017D7D81A /* freqlut.cc */, 42D2A2AD8EDACBEF9B884718 /* lfo.cc */, + 9A44725EA215BBEA625F264E /* log2.cc */, 779AFCD7AFEB1CDEACBC0425 /* patch.cc */, + AB03474DCAA7D2AC7378C51E /* pitchenv.cc */, 97A5CBACD479212282D0BFD6 /* resofilter.cc */, 68FD17910F296961541A67E4 /* ringbuffer.cc */, 48B6535400CF3AC8BABB3299 /* sawtooth.cc */, @@ -143,20 +149,22 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 391223C6D7249361C00A14AF /* dx7note.cc in Sources */, - 3DFFE98F73F04D2F66DA9FA4 /* env.cc in Sources */, - 2DD71F3E9AE0C4253B8B7747 /* exp2.cc in Sources */, - D80FBC17492647DCFDE96F75 /* fm_core.cc in Sources */, - 79D1CB0F9A4671DA79729544 /* fm_op_kernel.cc in Sources */, - FB78A2E9904695ABCC799CC3 /* freqlut.cc in Sources */, - 97788F06DA1810835D9F0A50 /* lfo.cc in Sources */, - B64D26CA7CC8017052D3E963 /* patch.cc in Sources */, - AA1CD32CDCFEB4469067D98E /* resofilter.cc in Sources */, - 6E01450F6BFF126216C8164C /* ringbuffer.cc in Sources */, - E31A9F6965B9E56115BAE59F /* sawtooth.cc in Sources */, - AA0F43347EBDEE6D29032DAF /* sin.cc in Sources */, - E2649182D734B708D5E72B66 /* synth_unit.cc in Sources */, - 50C9BC20B9DB9E51A9B852BC /* test_ringbuffer.cc in Sources */, + 25F144B38B846E3C5850E7BD /* dx7note.cc in Sources */, + 9F42FBCD78CEF75B6A043433 /* env.cc in Sources */, + 1A75F75619C78DB07A6085B0 /* exp2.cc in Sources */, + 2300749E4E3C2F5E2E47B813 /* fm_core.cc in Sources */, + B4D196C5D9E56B0238F25397 /* fm_op_kernel.cc in Sources */, + 15B76856F0421489F3EEA593 /* freqlut.cc in Sources */, + 539ED55158DD0A1FC841C627 /* lfo.cc in Sources */, + 2A6CCD05E26CB8A8DCB0070C /* log2.cc in Sources */, + 5BFBFDCD915B0AE650CE6798 /* patch.cc in Sources */, + E5B6FED47570BBD53CD55329 /* pitchenv.cc in Sources */, + B9A52CE8D262A4428581D53C /* resofilter.cc in Sources */, + 413968CF2316B4C71CC32B83 /* ringbuffer.cc in Sources */, + 20C8EA4478F3F0E2429332F4 /* sawtooth.cc in Sources */, + D541FD687D3305820E5F8FE2 /* sin.cc in Sources */, + E071B9E5AD56CCD871968387 /* synth_unit.cc in Sources */, + EB08E41E39595C13C10972F1 /* test_ringbuffer.cc in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/cpp/src/main.cc b/cpp/src/main.cc index ee45d59..6ff57e5 100644 --- a/cpp/src/main.cc +++ b/cpp/src/main.cc @@ -20,6 +20,7 @@ #include "synth.h" #include "module.h" +#include "aligned_buf.h" #include "freqlut.h" #include "wavout.h" #include "sawtooth.h" @@ -287,8 +288,8 @@ int main(int argc, char **argv) { //int32_t freq = atoi(argv[1]); //cout << "Logfreq(" << freq << ") = " << Freqlut::lookup(freq) << endl; - mkdx7note(sample_rate); - //mksaw(sample_rate); + //mkdx7note(sample_rate); + mksaw(sample_rate); //test_ringbuffer(); test_exp2(); return 0; diff --git a/cpp/src/sawtooth.cc b/cpp/src/sawtooth.cc index aa78018..3ec30bf 100644 --- a/cpp/src/sawtooth.cc +++ b/cpp/src/sawtooth.cc @@ -18,41 +18,39 @@ #include "module.h" #include "sawtooth.h" -#include "freqlut.h" +#include "exp2.h" // There's a fair amount of lookup table and so on that needs to be set before // generating any signal. In Java, this would be done by a separate factory class. // Here, we're just going to do it as globals. -#define HIGH_QUALITY - -#define noLOW_FREQ_HACK - #define FANCY_GOERTZEL_SIN #define noPRINT_ERROR #define R (1 << 29) -#ifdef LOW_FREQ_HACK -#define LG_N_SAMPLES 9 -#else -#define LG_N_SAMPLES 11 -#endif +#define LG_N_SAMPLES 10 #define N_SAMPLES (1 << LG_N_SAMPLES) #define N_PARTIALS_MAX (N_SAMPLES / 2) #define LG_SLICES_PER_OCTAVE 2 #define SLICES_PER_OCTAVE (1 << LG_SLICES_PER_OCTAVE) #define SLICE_SHIFT (24 - LG_SLICES_PER_OCTAVE) +#define SLICE_EXTRA 3 -#define LG_N_SLICES (LG_SLICES_PER_OCTAVE + 4) -#define N_SLICES (1 << LG_N_SLICES) +#define N_SLICES 36 +// 0.5 * (log(440./44100) / log(2) + log(440./48000) / log(2) + 2./12) + 1./64 - 3 in Q24 +#define SLICE_BASE 161217316 +#define LOW_FREQ_LIMIT (-SLICE_BASE) #define NEG2OVERPI -0.63661977236758138 int32_t sawtooth[N_SLICES][N_SAMPLES]; +int32_t sawtooth_freq_off; + void Sawtooth::init(double sample_rate) { + sawtooth_freq_off = -(1 << 24) * log(sample_rate) / log(2); int32_t lut[N_SAMPLES / 2]; for (int i = 0; i < N_SAMPLES / 2; i++) { @@ -60,11 +58,12 @@ void Sawtooth::init(double sample_rate) { } double slice_inc = pow(2.0, 1.0 / SLICES_PER_OCTAVE); - double f_0 = pow(slice_inc, N_SLICES - 1); + double f_0 = pow(slice_inc, N_SLICES - 1) * pow(0.5, SLICE_BASE * 1.0 / (1 << 24)); int n_partials_last = 0; for (int j = N_SLICES - 1; j >= 0; j--) { - int n_partials = floor(0.5 * sample_rate / f_0); + int n_partials = floor(0.5 / f_0); n_partials = n_partials < N_PARTIALS_MAX ? n_partials : N_PARTIALS_MAX; + //printf("slice %d: n_partials=%d\n", j, n_partials); for (int k = n_partials_last + 1; k <= n_partials; k++) { double scale = NEG2OVERPI / k; scale = (N_PARTIALS_MAX - k) > (N_PARTIALS_MAX >> 2) ? scale : @@ -94,7 +93,7 @@ void Sawtooth::init(double sample_rate) { int abs_err = err > 0 ? err : -err; maxerr = abs_err > maxerr ? abs_err : maxerr; #endif - ds += ((int64_t)cm2 * (int64_t)s + R) >> 29; + ds += ((int64_t)cm2 * (int64_t)s + (1 << 28)) >> 29; s += (ds + round) >> dshift; } #else @@ -135,56 +134,82 @@ Sawtooth::Sawtooth() { phase = 0; } -int32_t Sawtooth::lookup(int32_t phase, int32_t log_f) { +int32_t Sawtooth::compute(int32_t phase) { + return phase * 2 - (1 << 24); +} - log_f = log_f < 0 ? 0 : log_f; - int slice = (log_f + (1 << SLICE_SHIFT) - 1) >> SLICE_SHIFT; - int phase_int = (phase >> (24 - LG_N_SAMPLES)) & (N_SAMPLES - 1); - int lowbits = phase & ((1 << (24 - LG_N_SAMPLES)) - 1); - int y0 = sawtooth[slice][phase_int]; - int y1 = sawtooth[slice][(phase_int + 1) & (N_SAMPLES - 1)]; +int32_t Sawtooth::lookup_1(int32_t phase, int slice) { + int32_t phase_int = (phase >> (24 - LG_N_SAMPLES)) & (N_SAMPLES - 1); + int32_t lowbits = phase & ((1 << (24 - LG_N_SAMPLES)) - 1); + int32_t y0 = sawtooth[slice][phase_int]; + int32_t y1 = sawtooth[slice][(phase_int + 1) & (N_SAMPLES - 1)]; - int y4 = y0 + ((((int64_t)(y1 - y0) * (int64_t)lowbits)) >> (24 - LG_N_SAMPLES)); + return y0 + ((((int64_t)(y1 - y0) * (int64_t)lowbits)) >> (24 - LG_N_SAMPLES)); +} - // TODO: lift this out of loop - // TODO: optimal threshold probably depends on sample rate -#ifdef LOW_FREQ_HACK - if (log_f < (8 << 24)) - y4 = phase * 2 - (1 << 24); -#endif +int32_t Sawtooth::lookup_2(int32_t phase, int slice, int32_t slice_lowbits) { + int32_t phase_int = (phase >> (24 - LG_N_SAMPLES)) & (N_SAMPLES - 1); + int32_t lowbits = phase & ((1 << (24 - LG_N_SAMPLES)) - 1); + int32_t y0 = sawtooth[slice][phase_int]; + int32_t y1 = sawtooth[slice][(phase_int + 1) & (N_SAMPLES - 1)]; + int32_t y4 = y0 + ((((int64_t)(y1 - y0) * (int64_t)lowbits)) >> (24 - LG_N_SAMPLES)); -#ifdef HIGH_QUALITY - int y2 = sawtooth[slice + 1][phase_int]; - int y3 = sawtooth[slice + 1][(phase_int + 1) & (N_SAMPLES - 1)]; - int y5 = y2 + ((((int64_t)(y3 - y2) * (int64_t)lowbits)) >> (24 - LG_N_SAMPLES)); -#ifdef LOW_FREQ_HACK - if (log_f < (8 << 24) - (1 << SLICE_SHIFT)) - y5 = phase * 2 - (1 << 24); -#endif - int slice_lowbits = log_f & ((1 << SLICE_SHIFT) - 1); - int y = y4 + ((((int64_t)(y5 - y4) * (int64_t)slice_lowbits)) >> SLICE_SHIFT); - return y; -#else - return y4; -#endif + int32_t y2 = sawtooth[slice + 1][phase_int]; + int32_t y3 = sawtooth[slice + 1][(phase_int + 1) & (N_SAMPLES - 1)]; + int32_t y5 = y2 + ((((int64_t)(y3 - y2) * (int64_t)lowbits)) >> (24 - LG_N_SAMPLES)); + + return y4 + ((((int64_t)(y5 - y4) * (int64_t)slice_lowbits)) >> (SLICE_SHIFT - SLICE_EXTRA)); } void Sawtooth::process(const int32_t **inbufs, const int32_t *control_in, const int32_t *control_last, int32_t **outbufs) { int32_t logf = control_last[0]; - int32_t logf_in = control_in[0]; int32_t *obuf = outbufs[0]; - int32_t delta_logf = (logf_in - logf) >> lg_n; - int f = Freqlut::lookup(logf); - int f_in = Freqlut::lookup(logf_in); - int32_t delta_f = (f_in - f) >> lg_n; + int32_t actual_logf = logf + sawtooth_freq_off; + int f = Exp2::lookup(actual_logf); int32_t p = phase; - for (int i = 0; i < n; i++) { - f += delta_f; - logf += delta_logf; - obuf[i] = lookup(p, logf); - p += f; - p &= (1 << 24) - 1; + // choose a strategy based on the frequency + if (actual_logf < LOW_FREQ_LIMIT - (1 << (SLICE_SHIFT - SLICE_EXTRA))) { + for (int i = 0; i < n; i++) { + obuf[i] = compute(p); + p += f; + p &= (1 << 24) - 1; + } + } else if (actual_logf < LOW_FREQ_LIMIT) { + // interpolate between computed and lookup + int slice = (LOW_FREQ_LIMIT + SLICE_BASE + (1 << SLICE_SHIFT) - 1) >> SLICE_SHIFT; + int slice_lowbits = actual_logf - LOW_FREQ_LIMIT + (1 << (SLICE_SHIFT - SLICE_EXTRA)); + for (int i = 0; i < n; i++) { + int32_t yc = compute(p); + int32_t yl = lookup_1(p, slice + 1); + obuf[i] = yc + ((((int64_t)(yl - yc) * (int64_t)slice_lowbits)) >> (SLICE_SHIFT - SLICE_EXTRA)); + p += f; + p &= (1 << 24) - 1; + } + } else { + int slice = (actual_logf + SLICE_BASE + (1 << SLICE_SHIFT) - 1) >> SLICE_SHIFT; + const int slice_start = (1 << SLICE_SHIFT) - (1 << (SLICE_SHIFT - SLICE_EXTRA)); + int slice_lowbits = ((actual_logf + SLICE_BASE) & ((1 << SLICE_SHIFT) - 1)) - slice_start; + // slice < 0 can't happen because LOW_FREQ_LIMIT kicks in first + if (slice > N_SLICES - 2) { + if (slice > N_SLICES - 1 || slice_lowbits > 0) { + slice = N_SLICES - 1; + slice_lowbits = 0; + } + } + if (slice_lowbits <= 0) { + for (int i = 0; i < n; i++) { + obuf[i] = lookup_1(p, slice); + p += f; + p &= (1 << 24) - 1; + } + } else { + for (int i = 0; i < n; i++) { + obuf[i] = lookup_2(p, slice, slice_lowbits); + p += f; + p &= (1 << 24) - 1; + } + } } phase = p; } diff --git a/cpp/src/sawtooth.h b/cpp/src/sawtooth.h index ac85c59..0530614 100644 --- a/cpp/src/sawtooth.h +++ b/cpp/src/sawtooth.h @@ -19,9 +19,17 @@ class Sawtooth : Module { Sawtooth(); static void init(double sample_rate); - static int32_t lookup(int32_t phase, int32_t log_f); void process(const int32_t **inbufs, const int32_t *control_in, const int32_t *control_last, int32_t **outbufs); private: int32_t phase; + + // Compute mathematical function with no antialiasing + static int32_t compute(int32_t phase); + + // Lookup from a single slice + static int32_t lookup_1(int32_t phase, int slice); + + // Lookup from two slices, interpolate + static int32_t lookup_2(int32_t phase, int slice, int32_t slice_lowbits); };