Skip to content

Commit

Permalink
Merge pull request #129 from shorepine/pcmloop
Browse files Browse the repository at this point in the history
  • Loading branch information
bwhitman authored Jun 22, 2024
2 parents cced2d1 + 04b841f commit 9ea1a48
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 23 deletions.
27 changes: 15 additions & 12 deletions src/amy.c
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ void amy_add_event_internal(struct event e, uint16_t base_osc) {
for(uint8_t j=0;j<MAX_BREAKPOINTS;j++) {
d.param=BP_START+(j*2)+(i*MAX_BREAKPOINTS*2); d.data = *(uint32_t *)&t.breakpoint_times[i][j]; add_delta_to_queue(d);
// Stop adding deltas after first UNSET time sent, just to mark the end of the set when overwriting.
if(!AMY_IS_SET(t.breakpoint_times[i][j])) break;
if(AMY_IS_UNSET(t.breakpoint_times[i][j])) break;
d.param=BP_START+(j*2 + 1)+(i*MAX_BREAKPOINTS*2); d.data = *(uint32_t *)&t.breakpoint_values[i][j]; add_delta_to_queue(d);
}
}
Expand Down Expand Up @@ -980,26 +980,27 @@ void play_event(struct delta d) {
#if AMY_KS_OSCS > 0
ks_note_off(d.osc);
#endif
}
else if(synth[d.osc].wave==ALGO) { algo_note_off(d.osc); }
else if(synth[d.osc].wave==PARTIAL) {
} else if(synth[d.osc].wave==ALGO) {
algo_note_off(d.osc);
} else if(synth[d.osc].wave==PARTIAL) {
#if AMY_HAS_PARTIALS == 1
partial_note_off(d.osc);
#endif
}
else if(synth[d.osc].wave==PARTIALS) {
} else if(synth[d.osc].wave==PARTIALS) {
#if AMY_HAS_PARTIALS == 1
partials_note_off(d.osc);
#endif
}
else if(synth[d.osc].wave==PCM) { pcm_note_off(d.osc); }
else if(synth[d.osc].wave==CUSTOM) {
} else if(synth[d.osc].wave==PCM) {
pcm_note_off(d.osc);
} else if(synth[d.osc].wave==CUSTOM) {
#if AMY_HAS_CUSTOM == 1
custom_note_off(d.osc);
#endif
}
else {
} else {
// osc note off, start release
// For now, note_off_clock signals note of BUT ONLY IF IT'S NOT KS, ALGO, PARTIAL, PARTIALS, PCM, or CUSTOM.
// I'm not crazy about this, but if we apply it in those cases, the default bp0 amp envelope immediately zeros-out
// those waves on note-off.
AMY_UNSET(synth[d.osc].note_on_clock);
synth[d.osc].note_off_clock = total_samples;
}
Expand Down Expand Up @@ -1073,7 +1074,9 @@ void hold_and_modify(uint16_t osc) {
msynth[osc].amp = combine_controls_mult(ctrl_inputs, synth[osc].amp_coefs);
if (msynth[osc].amp <= 0.001) msynth[osc].amp = 0;

msynth[osc].feedback = synth[osc].feedback;
// synth[osc].feedback is copied to msynth in pcm_note_on, then used to track note-off for looping PCM.
// For PCM, don't re-copy it every loop, or we'd lose track of that flag. (This means you can't change feedback mid-playback for PCM).
if (synth[osc].wave != PCM) msynth[osc].feedback = synth[osc].feedback;
msynth[osc].resonance = synth[osc].resonance;

if (osc == 999) {
Expand Down
22 changes: 13 additions & 9 deletions src/pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,23 @@ void pcm_note_on(uint16_t osc) {
// This will result in PCM_SAMPLE_RATE when the midi_note == patch->midinote.
synth[osc].logfreq_coefs[COEF_CONST] = PCM_AMY_LOG2_SAMPLE_RATE - logfreq_for_midi_note(pcm_map[synth[osc].patch].midinote);
}
synth[osc].phase = 0; // s16.15 index into the table; as if a PHASOR into a 16 bit sample table.
synth[osc].phase = 0; // s16.15 index into the table; as if a PHASOR into a 16 bit sample table.
// Special case: We use the msynth feedback flag to indicate note-off for looping PCM. As a result, it's explicitly NOT set in amy:hold_and_modify for PCM voices. Set it here.
msynth[osc].feedback = synth[osc].feedback;
}

void pcm_mod_trigger(uint16_t osc) {
pcm_note_on(osc);
}

void pcm_note_off(uint16_t osc) {
// if looping set, disable looping; sample should play through to end.
if(msynth[osc].feedback > 0) {
msynth[osc].feedback = 0;
} else {
// Set phase to the end to cause immediate stop.
if(msynth[osc].feedback == 0) {
// Non-looping note: Set phase to the end to cause immediate stop.
synth[osc].phase = F2P(pcm_map[synth[osc].patch].length / (float)(1 << PCM_INDEX_FRAC_BITS));
} else {
// Looping is requested, disable future looping, sample will play through to end.
// (sending a second note-off will stop it immediately).
msynth[osc].feedback = 0;
}
}

Expand All @@ -47,14 +50,15 @@ SAMPLE render_pcm(SAMPLE* buf, uint16_t osc) {
const pcm_map_t* patch = &pcm_map[synth[osc].patch];
float logfreq = msynth[osc].logfreq;
// If osc[midi_note] is unset, apply patch's default here.
if (!AMY_IS_SET(synth[osc].midi_note)) logfreq += logfreq_for_midi_note(patch->midinote);
if (AMY_IS_UNSET(synth[osc].midi_note)) logfreq += logfreq_for_midi_note(patch->midinote);
float playback_freq = freq_of_logfreq(logfreq); // PCM_SAMPLE_RATE modified by

SAMPLE max_value = 0;
SAMPLE amp = F2S(msynth[osc].amp);
PHASOR step = F2P((playback_freq / (float)AMY_SAMPLE_RATE) / (float)(1 << PCM_INDEX_BITS));
const LUTSAMPLE* table = pcm + patch->offset;
uint32_t base_index = INT_OF_P(synth[osc].phase, PCM_INDEX_BITS);
//fprintf(stderr, "render_pcm: time=%.3f patch=%d base_index=%d length=%d loopstart=%d loopend=%d fb=%f is_unset_note_off %d\n", total_samples / (float)AMY_SAMPLE_RATE, synth[osc].patch, base_index, patch->length, patch->loopstart, patch->loopend, msynth[osc].feedback, AMY_IS_UNSET(synth[osc].note_off_clock));
for(uint16_t i=0; i < AMY_BLOCK_SIZE; i++) {
SAMPLE frac = S_FRAC_OF_P(synth[osc].phase, PCM_INDEX_BITS);
LUTSAMPLE b = table[base_index];
Expand All @@ -67,11 +71,11 @@ SAMPLE render_pcm(SAMPLE* buf, uint16_t osc) {
synth[osc].status = STATUS_OFF;// is this right?
sample = 0;
} else {
if(msynth[osc].feedback > 0) { // loop
if(msynth[osc].feedback) { // still looping. The feedback flag is cleared by pcm_note_off.
if(base_index >= patch->loopend) { // loopend
// back to loopstart
int32_t loop_len = patch->loopend - patch->loopstart;
synth[osc].phase -= F2P(loop_len / (float)(1 << PCM_INDEX_FRAC_BITS));
synth[osc].phase -= F2P(loop_len / (float)(1 << PCM_INDEX_BITS));
base_index -= loop_len;
}
}
Expand Down
13 changes: 11 additions & 2 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ def run(self):
amy.send(time=500, note=70, vel=1)


class TestPcmLoop(AmyTest):

def run(self):
amy.send(time=0, osc=0, wave=amy.PCM, patch=10, feedback=1)
amy.send(time=100, osc=0, vel=1)
amy.send(time=500, osc=0, vel=0)


class TestPartial(AmyTest):

def run(self):
Expand Down Expand Up @@ -427,7 +435,7 @@ class TestFilterReleaseGlitch(AmyTest):

def run(self):
amy.send(time=0, osc=0, wave=amy.SAW_DOWN, filter_type=amy.FILTER_LPF24, filter_freq='100,0,0,6')
amy.send(time=100, note=60, vel=1)
amy.send(time=100, note=64, vel=1)
amy.send(time=500, vel=0)


Expand All @@ -451,13 +459,14 @@ def main(argv):
#TestBrass2().test()
#TestSineEnv().test()
#TestSawDownOsc().test()
TestGuitar().test()
#TestGuitar().test()
#TestFilter().test()
#TestAlgo().test()
#TestBleep().test()
#TestChainedOsc().test()
#TestJunoPatch().test()
#TestJunoTrumpetPatch().test()
TestPcmLoop().test()

amy.send(debug=0)
print("tests done.")
Expand Down
Binary file modified tests/ref/TestFilterReleaseGlitch.wav
Binary file not shown.
Binary file added tests/ref/TestPcmLoop.wav
Binary file not shown.

0 comments on commit 9ea1a48

Please sign in to comment.