diff --git a/src/amy.c b/src/amy.c index ae459241..2c86a3b9 100644 --- a/src/amy.c +++ b/src/amy.c @@ -652,7 +652,11 @@ void reset_osc(uint16_t i ) { } synth[i].eg_type[j] = ENVELOPE_NORMAL; } - for(uint8_t j=0;j= MIN_ZERO_AMP_TIME_SAMPS) { //printf("h&m: time %f osc %d OFF\n", total_samples/(float)AMY_SAMPLE_RATE, osc); synth[osc].status = SYNTH_AUDIBLE_SUSPENDED; // It *could* come back... + // .. but reset osc and filter just in case. + synth[osc].phase = 0; + if(synth[osc].filter_type != FILTER_NONE) reset_filter(osc); } } } else if (max_val == 0) { diff --git a/src/amy.h b/src/amy.h index fa748fba..9e8bd6cf 100644 --- a/src/amy.h +++ b/src/amy.h @@ -347,7 +347,9 @@ struct synthinfo { float breakpoint_values[MAX_BREAKPOINT_SETS][MAX_BREAKPOINTS]; uint8_t eg_type[MAX_BREAKPOINT_SETS]; // one of the ENVELOPE_ values SAMPLE last_scale[MAX_BREAKPOINT_SETS]; // remembers current envelope level, to use as start point in release. - + float seg_start_val[MAX_BREAKPOINT_SETS]; // remembers starting point of current envelope segment. + uint8_t current_seg[MAX_BREAKPOINT_SETS]; // which envelope seg we are currently in. + // State variable for the dc-removal filter. SAMPLE hpf_state[2]; // Constant offset to add to sawtooth before integrating. diff --git a/src/envelope.c b/src/envelope.c index 1e545718..3abfc293 100644 --- a/src/envelope.c +++ b/src/envelope.c @@ -60,10 +60,10 @@ SAMPLE compute_breakpoint_scale(uint16_t osc, uint8_t bp_set, uint16_t sample_of // We have to aim to overshoot to the desired gap so that we hit the target by exponential_rate time. const SAMPLE exponential_rate_overshoot_factor = F2S(1.0f / (1.0f - exp2f(EXP_RATE_VAL))); uint32_t elapsed = 0; - SAMPLE scale = F2S(1.0f); int eg_type = synth[osc].eg_type[bp_set]; uint32_t bp_end_times[MAX_BREAKPOINTS]; uint32_t cumulated_time = 0; + SAMPLE scale = F2S(1.0f); // Scan breakpoints to find which one is release (the last one) bp_r = -1; @@ -78,9 +78,7 @@ SAMPLE compute_breakpoint_scale(uint16_t osc, uint8_t bp_set, uint16_t sample_of if(bp_r < 0) { // no breakpoints, return key gate. if(AMY_IS_SET(synth[osc].note_off_clock)) scale = 0; - synth[osc].last_scale[bp_set] = scale; - //return scale; - goto return_label; + goto return_scale; } // Fix up bp_end_times for release segment to be relative to note-off time. bp_end_times[bp_r] = synth[osc].breakpoint_times[bp_set][bp_r]; @@ -99,10 +97,8 @@ SAMPLE compute_breakpoint_scale(uint16_t osc, uint8_t bp_set, uint16_t sample_of // We didn't find anything, so we are in sustain. found = bp_r - 1; // segment before release defines sustain scale = F2S(synth[osc].breakpoint_values[bp_set][found]); - synth[osc].last_scale[bp_set] = scale; //printf("env: time %lld bpset %d seg %d SUSTAIN %f\n", total_samples, bp_set, found, S2F(scale)); - //return scale; - goto return_label; + goto return_scale; } } else if(AMY_IS_SET(synth[osc].note_off_clock)) { release = 1; @@ -110,8 +106,6 @@ SAMPLE compute_breakpoint_scale(uint16_t osc, uint8_t bp_set, uint16_t sample_of // Get the last t/v pair , for release found = bp_r; t0 = 0; // start the elapsed clock again - // Release starts from wherever we got to - v0 = synth[osc].last_scale[bp_set]; if(elapsed > synth[osc].breakpoint_times[bp_set][bp_r]) { // OK. partials (et al) need a frame to fade out to avoid clicks. This is in conflict with the breakpoint release, // which will set it to the bp end value before the fade out, often 0 so the fadeout never gets to hit. @@ -120,24 +114,25 @@ SAMPLE compute_breakpoint_scale(uint16_t osc, uint8_t bp_set, uint16_t sample_of // to fully respect the actual envelope, else it pops up to full amplitude after the release. if(synth[osc].wave==PARTIAL && synth[osc].patch >= 0) { scale = F2S(1.0f); - synth[osc].last_scale[bp_set] = scale; - //return scale; - goto return_label; + goto return_scale; } //printf("cbp: time %f osc %d amp %f OFF\n", total_samples / (float)AMY_SAMPLE_RATE, osc, msynth[osc].amp); // Synth is now turned off in hold_and_modify, which tracks when the amplitude goes to zero (and waits a bit). //synth[osc].status=SYNTH_OFF; //AMY_UNSET(synth[osc].note_off_clock); scale = F2S(synth[osc].breakpoint_values[bp_set][bp_r]); - synth[osc].last_scale[bp_set] = scale; - //return scale; - goto return_label; + goto return_scale; } } + if (found != synth[osc].current_seg[bp_set]) { + // This is the first time we've been in this segment. + synth[osc].current_seg[bp_set] = found; + // Initialize the segment value from wherever we have gotten to so far. + synth[osc].seg_start_val[bp_set] = synth[osc].last_scale[bp_set]; + } - if(found<0) return scale; - t1 = bp_end_times[found]; + v0 = synth[osc].seg_start_val[bp_set]; v1 = F2S(synth[osc].breakpoint_values[bp_set][found]); if(found>0 && bp_r != found && !release) { t0 = bp_end_times[found-1]; @@ -211,8 +206,8 @@ SAMPLE compute_breakpoint_scale(uint16_t osc, uint8_t bp_set, uint16_t sample_of scale = -scale; } // Keep track of the most-recently returned non-release scale. - return_label: - if (!release) synth[osc].last_scale[bp_set] = scale; + return_scale: + synth[osc].last_scale[bp_set] = scale; //if (osc < AMY_OSCS && found != -1) // fprintf(stderr, "env: time %f osc %d bpset %d seg %d type %d t0 %d t1 %d elapsed %d v0 %f v1 %f scale %f\n", total_samples / (float)AMY_SAMPLE_RATE, osc, bp_set, found, eg_type, t0, t1, elapsed, S2F(v0), S2F(v1), S2F(scale)); AMY_PROFILE_STOP(COMPUTE_BREAKPOINT_SCALE) diff --git a/test.py b/test.py index 5071d6f5..f1c1abba 100644 --- a/test.py +++ b/test.py @@ -222,6 +222,32 @@ def run(self): amy.send(time=100, vel=1) amy.send(time=500, vel=0) +class TestEnvRetrig(AmyTest): + + def run(self): + # Retriggering an envelope that hasn't fully decayed should restart from current value, not zero. + amy.send(time=0, osc=0, wave=amy.SINE, freq=1000) + amy.send(time=0, osc=0, amp='0,0,0.85,1,0,0', bp0='50,1,200,0.1,100,0') + amy.send(time=100, vel=8) + amy.send(time=200, vel=8) # Retrigger during Decay + amy.send(time=300, vel=0) + amy.send(time=350, vel=8) # Retrigger during Release + amy.send(time=500, vel=0) + amy.send(time=600, vel=0) # Repeated note-off + +class TestFiltRetrig(AmyTest): + + def run(self): + # Retriggering with filter state. + amy.send(time=0, osc=0, wave=amy.SINE, freq=1000) + amy.send(time=0, osc=0, amp='0,0,0.85,1,0,0', bp0='50,1,200,0.1,100,0', + filter_type=amy.FILTER_LPF, filter_freq=500, resonance=8) + amy.send(time=100, vel=8) + amy.send(time=200, vel=8) # Retrigger during Decay + amy.send(time=300, vel=0) + amy.send(time=350, vel=8) # Retrigger during Release + amy.send(time=500, vel=0) + amy.send(time=600, vel=0) # Repeated note-off class TestAlgo(AmyTest): @@ -578,7 +604,6 @@ def main(argv): #TestBleep().test() #TestBrass().test() #TestBrass2().test() - #TestSineEnv().test() #TestSawDownOsc().test() #TestGuitar().test() #TestFilter().test() @@ -589,6 +614,7 @@ def main(argv): #TestJunoTrumpetPatch().test() #TestPcmLoop().test() TestBYOPNoteOff().test() + TestSineEnv().test() amy.send(debug=0) print("tests done.") diff --git a/tests/ref/TestBuildYourOwnPartials.wav b/tests/ref/TestBuildYourOwnPartials.wav index 038c70ce..a8538a13 100644 Binary files a/tests/ref/TestBuildYourOwnPartials.wav and b/tests/ref/TestBuildYourOwnPartials.wav differ diff --git a/tests/ref/TestChainedOsc.wav b/tests/ref/TestChainedOsc.wav index b4d548e0..58e92780 100644 Binary files a/tests/ref/TestChainedOsc.wav and b/tests/ref/TestChainedOsc.wav differ diff --git a/tests/ref/TestChorus.wav b/tests/ref/TestChorus.wav index 67288ea2..15f220a3 100644 Binary files a/tests/ref/TestChorus.wav and b/tests/ref/TestChorus.wav differ diff --git a/tests/ref/TestEnvRetrig.wav b/tests/ref/TestEnvRetrig.wav new file mode 100644 index 00000000..2a0384ed Binary files /dev/null and b/tests/ref/TestEnvRetrig.wav differ diff --git a/tests/ref/TestFiltRetrig.wav b/tests/ref/TestFiltRetrig.wav new file mode 100644 index 00000000..555da411 Binary files /dev/null and b/tests/ref/TestFiltRetrig.wav differ diff --git a/tests/ref/TestFilter.wav b/tests/ref/TestFilter.wav index 91939c5d..64db81db 100644 Binary files a/tests/ref/TestFilter.wav and b/tests/ref/TestFilter.wav differ diff --git a/tests/ref/TestFilter24.wav b/tests/ref/TestFilter24.wav index 1875d8d8..cd44b65a 100644 Binary files a/tests/ref/TestFilter24.wav and b/tests/ref/TestFilter24.wav differ diff --git a/tests/ref/TestJunoCheapTrumpetPatch.wav b/tests/ref/TestJunoCheapTrumpetPatch.wav index a7368886..22ef31ac 100644 Binary files a/tests/ref/TestJunoCheapTrumpetPatch.wav and b/tests/ref/TestJunoCheapTrumpetPatch.wav differ diff --git a/tests/ref/TestJunoClip.wav b/tests/ref/TestJunoClip.wav index 76eb01c8..de513c21 100644 Binary files a/tests/ref/TestJunoClip.wav and b/tests/ref/TestJunoClip.wav differ diff --git a/tests/ref/TestJunoTrumpetPatch.wav b/tests/ref/TestJunoTrumpetPatch.wav index 5dab6f66..38efecf4 100644 Binary files a/tests/ref/TestJunoTrumpetPatch.wav and b/tests/ref/TestJunoTrumpetPatch.wav differ diff --git a/tests/ref/TestLowerVcf.wav b/tests/ref/TestLowerVcf.wav index f9c275ec..d5012aa5 100644 Binary files a/tests/ref/TestLowerVcf.wav and b/tests/ref/TestLowerVcf.wav differ diff --git a/tests/ref/TestOscBD.wav b/tests/ref/TestOscBD.wav index 8ad91ad6..729fb890 100644 Binary files a/tests/ref/TestOscBD.wav and b/tests/ref/TestOscBD.wav differ diff --git a/tests/ref/TestOverload.wav b/tests/ref/TestOverload.wav index d606d063..7ff77520 100644 Binary files a/tests/ref/TestOverload.wav and b/tests/ref/TestOverload.wav differ