Skip to content

Commit

Permalink
Merge pull request #218 from shorepine/fix_ks
Browse files Browse the repository at this point in the history
Restore Karplus-Strong (KS) osc mode
  • Loading branch information
dpwe authored Sep 20, 2024
2 parents c771c62 + 893437c commit 9c4ef9b
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 52 deletions.
84 changes: 40 additions & 44 deletions src/amy.c
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,10 @@ void osc_note_on(uint16_t osc, float initial_freq) {
//printf("Note on: osc %d wav %d filter_freq_coefs=%f %f %f %f %f %f\n", osc, synth[osc].wave,
// synth[osc].filter_logfreq_coefs[0], synth[osc].filter_logfreq_coefs[1], synth[osc].filter_logfreq_coefs[2],
// synth[osc].filter_logfreq_coefs[3], synth[osc].filter_logfreq_coefs[4], synth[osc].filter_logfreq_coefs[5]);
// take care of fm & ks first -- no special treatment for bp/mod
#if AMY_KS_OSCS > 0
if(synth[osc].wave==KS) ks_note_on(osc);
#endif
if(synth[osc].wave==SINE) sine_note_on(osc, initial_freq);
if(synth[osc].wave==SAW_DOWN) saw_down_note_on(osc, initial_freq);
if(synth[osc].wave==SAW_UP) saw_up_note_on(osc, initial_freq);
Expand Down Expand Up @@ -1049,51 +1053,43 @@ void play_event(struct delta d) {
//synth[d.osc].amp_coefs[COEF_CONST] = *(float *)&d.data; // these could be decoupled, later
synth[d.osc].velocity = *(float *)&d.data;
synth[d.osc].status = AUDIBLE;
// take care of fm & ks first -- no special treatment for bp/mod
if(synth[d.osc].wave==KS) {
#if AMY_KS_OSCS > 0
ks_note_on(d.osc);
#endif
} else {
// an osc came in with a note on.
// start the bp clock
synth[d.osc].note_on_clock = total_samples; //esp_timer_get_time() / 1000;

// if there was a filter active for this voice, reset it
if(synth[d.osc].filter_type != FILTER_NONE) reset_filter(d.osc);
// For repeatability, start at zero phase.
synth[d.osc].phase = 0;
// an osc came in with a note on.
// start the bp clock
synth[d.osc].note_on_clock = total_samples; //esp_timer_get_time() / 1000;

// if there was a filter active for this voice, reset it
if(synth[d.osc].filter_type != FILTER_NONE) reset_filter(d.osc);
// For repeatability, start at zero phase.
synth[d.osc].phase = 0;

// restart the waveforms
// Guess at the initial frequency depending only on const & note. Envelopes not "developed" yet.
float initial_logfreq = synth[d.osc].logfreq_coefs[COEF_CONST];
if (AMY_IS_SET(synth[d.osc].midi_note)) {
// synth[d.osc].logfreq_coefs[COEF_CONST] = 0;
initial_logfreq += synth[d.osc].logfreq_coefs[COEF_NOTE] * logfreq_for_midi_note(synth[d.osc].midi_note);
}
// If we're coming out of note-off, set the freq history for portamento.
//if (AMY_IS_SET(synth[d.osc].note_off_clock))
// msynth[d.osc].last_logfreq = initial_logfreq;
// Now we've tested that, we can reset note-off clocks.
AMY_UNSET(synth[d.osc].note_off_clock); // Most recent note event is not note-off.
AMY_UNSET(synth[d.osc].zero_amp_clock);

float initial_freq = freq_of_logfreq(initial_logfreq);
osc_note_on(d.osc, initial_freq);
// trigger the mod source, if we have one
if(AMY_IS_SET(synth[d.osc].mod_source)) {
synth[synth[d.osc].mod_source].phase = synth[synth[d.osc].mod_source].trigger_phase;

synth[synth[d.osc].mod_source].note_on_clock = total_samples; // Need a note_on_clock to have envelope work correctly.
if(synth[synth[d.osc].mod_source].wave==SINE) sine_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==SAW_DOWN) saw_up_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==SAW_UP) saw_down_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==TRIANGLE) triangle_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==PULSE) pulse_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==PCM) pcm_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==CUSTOM) custom_mod_trigger(synth[d.osc].mod_source);
}

// restart the waveforms
// Guess at the initial frequency depending only on const & note. Envelopes not "developed" yet.
float initial_logfreq = synth[d.osc].logfreq_coefs[COEF_CONST];
if (AMY_IS_SET(synth[d.osc].midi_note)) {
// synth[d.osc].logfreq_coefs[COEF_CONST] = 0;
initial_logfreq += synth[d.osc].logfreq_coefs[COEF_NOTE] * logfreq_for_midi_note(synth[d.osc].midi_note);
}
// If we're coming out of note-off, set the freq history for portamento.
//if (AMY_IS_SET(synth[d.osc].note_off_clock))
// msynth[d.osc].last_logfreq = initial_logfreq;
// Now we've tested that, we can reset note-off clocks.
AMY_UNSET(synth[d.osc].note_off_clock); // Most recent note event is not note-off.
AMY_UNSET(synth[d.osc].zero_amp_clock);

float initial_freq = freq_of_logfreq(initial_logfreq);
osc_note_on(d.osc, initial_freq);
// trigger the mod source, if we have one
if(AMY_IS_SET(synth[d.osc].mod_source)) {
synth[synth[d.osc].mod_source].phase = synth[synth[d.osc].mod_source].trigger_phase;

synth[synth[d.osc].mod_source].note_on_clock = total_samples; // Need a note_on_clock to have envelope work correctly.
if(synth[synth[d.osc].mod_source].wave==SINE) sine_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==SAW_DOWN) saw_up_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==SAW_UP) saw_down_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==TRIANGLE) triangle_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==PULSE) pulse_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==PCM) pcm_mod_trigger(synth[d.osc].mod_source);
if(synth[synth[d.osc].mod_source].wave==CUSTOM) custom_mod_trigger(synth[d.osc].mod_source);
}
} else if(synth[d.osc].velocity > 0 && *(float *)&d.data == 0) { // new note off
// DON'T clear velocity, we still need to reference it in decay.
Expand Down
30 changes: 22 additions & 8 deletions src/oscillators.c
Original file line number Diff line number Diff line change
Expand Up @@ -573,20 +573,25 @@ SAMPLE render_ks(SAMPLE * buf, uint16_t osc) {
SAMPLE max_value = 0;
if(freq >= 55) { // lowest note we can play
uint16_t buflen = (uint16_t)(AMY_SAMPLE_RATE / freq);
for(uint16_t i=0;i<AMY_BLOCK_SIZE;i++) {
for(uint16_t i = 0; i < AMY_BLOCK_SIZE; i++) {
uint16_t index = (uint16_t)(synth[osc].step);
synth[osc].sample = ks_buffer[ks_polyphony_index][index];
ks_buffer[ks_polyphony_index][index] =
MUL4_SS(
SMULR7(
(ks_buffer[ks_polyphony_index][index] + ks_buffer[ks_polyphony_index][(index + 1) % buflen]),
half);
synth[osc].step = (index + 1) % buflen;
SAMPLE value = MUL4_SS(synth[osc].sample, amp);
buf[i] = value;
if (value < 0) value = -value;
if (value > max_value) max_value = value;
SAMPLE value = SMULR7(synth[osc].sample, amp);
buf[i] += value;
if (i == 0) {
max_value = value;
} else {
if (value > max_value) max_value = value;
else if (-value > max_value) max_value = -value;
}
}
}
//fprintf(stderr, "render_ks time %u osc %d freq %.1f amp %.3f maxval %.3f\n", total_samples, osc, freq, S2F(amp), S2F(max_value));
return max_value;
}

Expand All @@ -596,11 +601,20 @@ void ks_note_on(uint16_t osc) {
uint16_t buflen = (uint16_t)(AMY_SAMPLE_RATE / freq);
if(buflen > MAX_KS_BUFFER_LEN) buflen = MAX_KS_BUFFER_LEN;
// init KS buffer with noise up to max
for(uint16_t i=0;i<buflen;i++) {
ks_buffer[ks_polyphony_index][i] = amy_get_random();
SAMPLE sum = 0;
for(uint16_t i = 0; i < buflen; i++) {
SAMPLE val = amy_get_random();
ks_buffer[ks_polyphony_index][i] = val;
sum += val;
}
// Remove dc, to avoid ending up with a dc-offset residual.
SAMPLE mean = sum / buflen;
for(uint16_t i = 0; i < buflen; i++) {
ks_buffer[ks_polyphony_index][i] -= mean;
}
ks_polyphony_index++;
if(ks_polyphony_index == AMY_KS_OSCS) ks_polyphony_index = 0;
//fprintf(stderr, "ks_note_on: osc %d buflen %d poly_index %d\n", osc, buflen, ks_polyphony_index);
}

void ks_note_off(uint16_t osc) {
Expand Down

0 comments on commit 9c4ef9b

Please sign in to comment.