From 9a8659f9ef5732514948b01fc893e1d4ecdac679 Mon Sep 17 00:00:00 2001 From: Dan Ellis Date: Sat, 18 Apr 2026 00:13:21 -0400 Subject: [PATCH 1/8] First step in adding buses: amy_global has an array bus_state *bus[AMY_NUM_BUSES], compiles, still works - except echo. --- amy/constants.py | 6 +- src/amy.c | 303 +++++++++++++++++++++++++++-------------------- src/amy.h | 82 +++++++++---- src/api.c | 2 + src/delay.c | 122 ++++++++++--------- src/delay.h | 9 +- src/examples.c | 6 +- src/filters.c | 122 +++++-------------- src/instrument.c | 24 +++- src/parse.c | 27 +++-- src/patches.c | 41 ++++--- 11 files changed, 407 insertions(+), 337 deletions(-) diff --git a/amy/constants.py b/amy/constants.py index e1be32b5..a803c124 100644 --- a/amy/constants.py +++ b/amy/constants.py @@ -15,8 +15,10 @@ AMY_PCM_TYPE_FILE=1 AMY_PCM_TYPE_MEMORY=2 PCM_FILE_BUFFER_MULT=8 -AMY_BUS_OUTPUT=1 -AMY_BUS_AUDIO_IN=2 +AMY_TRANSFER_OUTPUT=1 +AMY_TRANSFER_AUDIO_IN=2 +AMY_NUM_BUSES=4 +AMY_DEFAULT_BUS=0 AMY_MAX_CORES=2 AMY_MAX_CHANNELS=2 AMY_NCHANS=2 diff --git a/src/amy.c b/src/amy.c index e0ef97e7..1e84a270 100644 --- a/src/amy.c +++ b/src/amy.c @@ -2,6 +2,7 @@ // brian@variogr.am / dan.ellis@gmail.com #include "amy.h" +#include "delay.h" #ifdef AMY_DEBUG @@ -79,11 +80,6 @@ void amy_profiles_print() {} #include "clipping_lookup_table.h" -// Final output delay lines. -delay_line_t *chorus_delay_lines[AMY_MAX_CHANNELS] = {NULL, NULL}; -delay_line_t *echo_delay_lines[AMY_MAX_CHANNELS] = {NULL, NULL}; -SAMPLE *delay_mod = NULL; - // Set up the mutex for accessing the queue during rendering (for multicore) @@ -197,19 +193,20 @@ output_sample_type * output_block; #endif -void dealloc_echo_delay_lines(void) { +void dealloc_echo_delay_lines(uint8_t bus) { for (int c = AMY_NCHANS - 1; c >= 0; --c) { - if (echo_delay_lines[c]) free_delay_line(echo_delay_lines[c]); - echo_delay_lines[c] = NULL; + if (amy_global.bus[bus]->echo.echo_delay_lines[c]) + free_delay_line(amy_global.bus[bus]->echo.echo_delay_lines[c]); + amy_global.bus[bus]->echo.echo_delay_lines[c] = NULL; } } -bool alloc_echo_delay_lines(uint32_t max_delay_samples) { +bool alloc_echo_delay_lines(uint8_t bus, uint32_t max_delay_samples) { bool success = true; for (int c = 0; c < AMY_NCHANS; ++c) { delay_line_t *delay_line = new_delay_line(max_delay_samples, 0, amy_global.config.ram_caps_delay); if (delay_line) { - echo_delay_lines[c] = delay_line; + amy_global.bus[bus]->echo.echo_delay_lines[c] = delay_line; } else { success = false; break; @@ -217,7 +214,7 @@ bool alloc_echo_delay_lines(uint32_t max_delay_samples) { } if (!success) { fprintf(stderr, "unable to alloc echo of %d samples\n", (int)max_delay_samples); - dealloc_echo_delay_lines(); + dealloc_echo_delay_lines(bus); return false; } return true; @@ -229,57 +226,57 @@ uint32_t enclosing_power_of_2(uint32_t n) { return result; } -void config_echo(float level, float delay_ms, float max_delay_ms, float feedback, float filter_coef) { - if (AMY_IS_UNSET(level)) level = S2F(amy_global.echo.level); - if (AMY_IS_UNSET(delay_ms)) delay_ms = (amy_global.echo.delay_samples + 0.5f) / (AMY_SAMPLE_RATE / 1000.f); - if (AMY_IS_UNSET(max_delay_ms)) max_delay_ms = (amy_global.echo.max_delay_samples + 0.5f) / (AMY_SAMPLE_RATE / 1000.f); - if (AMY_IS_UNSET(feedback)) feedback = S2F(amy_global.echo.feedback); - if (AMY_IS_UNSET(filter_coef)) filter_coef = S2F(amy_global.echo.filter_coef); +void config_echo(uint8_t bus, float level, float delay_ms, float max_delay_ms, float feedback, float filter_coef) { + if (AMY_IS_UNSET(level)) level = S2F(amy_global.bus[bus]->echo.level); + if (AMY_IS_UNSET(delay_ms)) delay_ms = (amy_global.bus[bus]->echo.delay_samples + 0.5f) / (AMY_SAMPLE_RATE / 1000.f); + if (AMY_IS_UNSET(max_delay_ms)) max_delay_ms = (amy_global.bus[bus]->echo.max_delay_samples + 0.5f) / (AMY_SAMPLE_RATE / 1000.f); + if (AMY_IS_UNSET(feedback)) feedback = S2F(amy_global.bus[bus]->echo.feedback); + if (AMY_IS_UNSET(filter_coef)) filter_coef = S2F(amy_global.bus[bus]->echo.filter_coef); uint32_t delay_samples = (uint32_t)(delay_ms / 1000.f * AMY_SAMPLE_RATE); //fprintf(stderr, "config_echo: delay_ms=%.3f max_delay_ms=%.3f delay_samples=%d echo.max_delay_samples=%d\n", delay_ms, max_delay_ms, delay_samples, echo.max_delay_samples); if (level > 0) { - if (echo_delay_lines[0] == NULL) { + if (amy_global.bus[bus]->echo.echo_delay_lines[0] == NULL) { // Delay line len must be power of 2. uint32_t max_delay_samples = enclosing_power_of_2((uint32_t)(max_delay_ms / 1000.f * AMY_SAMPLE_RATE)); - if (!alloc_echo_delay_lines(max_delay_samples)) return; - amy_global.echo.max_delay_samples = max_delay_samples; + if (!alloc_echo_delay_lines(bus, max_delay_samples)) return; + amy_global.bus[bus]->echo.max_delay_samples = max_delay_samples; //fprintf(stderr, "config_echo: max_delay_samples=%d\n", max_delay_samples); } // Apply delay. We have to stay 1 sample less than delay line length for FIR EQ delay. - if (delay_samples > amy_global.echo.max_delay_samples - 1) delay_samples = amy_global.echo.max_delay_samples - 1; + if (delay_samples > amy_global.bus[bus]->echo.max_delay_samples - 1) delay_samples = amy_global.bus[bus]->echo.max_delay_samples - 1; for (int c = 0; c < AMY_NCHANS; ++c) { - echo_delay_lines[c]->fixed_delay = delay_samples; + amy_global.bus[bus]->echo.echo_delay_lines[c]->fixed_delay = delay_samples; } } - amy_global.echo.level = F2S(level); - amy_global.echo.delay_samples = delay_samples; + amy_global.bus[bus]->echo.level = F2S(level); + amy_global.bus[bus]->echo.delay_samples = delay_samples; // Filter is IIR [1, filter_coef] normalized for filter_coef > 0 (LPF), or FIR [1, filter_coef] normalized for filter_coef < 0 (HPF). if (filter_coef > 0.99) filter_coef = 0.99; // Avoid unstable filters. - amy_global.echo.filter_coef = F2S(filter_coef); + amy_global.bus[bus]->echo.filter_coef = F2S(filter_coef); // FIR filter potentially has gain > 1 for high frequencies, so discount the loop feedback to stop things exploding. if (filter_coef < 0) feedback /= 1.f - filter_coef; - amy_global.echo.feedback = F2S(feedback); + amy_global.bus[bus]->echo.feedback = F2S(feedback); //fprintf(stderr, "config_echo: delay_samples=%d level=%.3f feedback=%.3f filter_coef=%.3f fc0=%.3f\n", delay_samples, level, feedback, filter_coef, S2F(echo.filter_coef)); } -void dealloc_chorus_delay_lines(void) { +void dealloc_chorus_delay_lines(uint8_t bus) { for(int c = AMY_NCHANS - 1; c >= 0; --c) { - if (chorus_delay_lines[c]) free_delay_line(chorus_delay_lines[c]); - chorus_delay_lines[c] = NULL; + if (amy_global.bus[bus]->chorus.chorus_delay_lines[c]) free_delay_line(amy_global.bus[bus]->chorus.chorus_delay_lines[c]); + amy_global.bus[bus]->chorus.chorus_delay_lines[c] = NULL; } - free(delay_mod); - delay_mod = NULL; + free(amy_global.bus[bus]->chorus.delay_mod); + amy_global.bus[bus]->chorus.delay_mod = NULL; } -void alloc_chorus_delay_lines(void) { - delay_mod = (SAMPLE *)malloc_caps(sizeof(SAMPLE) * AMY_BLOCK_SIZE, amy_global.config.ram_caps_delay); +void alloc_chorus_delay_lines(uint8_t bus) { + amy_global.bus[bus]->chorus.delay_mod = (SAMPLE *)malloc_caps(sizeof(SAMPLE) * AMY_BLOCK_SIZE, amy_global.config.ram_caps_delay); bool success = true; for(int c = 0; c < AMY_NCHANS; ++c) { delay_line_t *delay_line = new_delay_line(DELAY_LINE_LEN, DELAY_LINE_LEN / 2, amy_global.config.ram_caps_delay); if (delay_line) { - chorus_delay_lines[c] = delay_line; + amy_global.bus[bus]->chorus.chorus_delay_lines[c] = delay_line; } else { success = false; break; @@ -287,22 +284,22 @@ void alloc_chorus_delay_lines(void) { } if (!success) { fprintf(stderr, "unable to alloc chorus of %d samples\n", (int)DELAY_LINE_LEN); - dealloc_chorus_delay_lines(); + dealloc_chorus_delay_lines(bus); } } -void config_chorus(float level, uint16_t max_delay, float lfo_freq, float depth) { - if (AMY_IS_UNSET(level)) level = S2F(amy_global.chorus.level); - if (AMY_IS_UNSET(max_delay)) max_delay = amy_global.chorus.max_delay; - if (AMY_IS_UNSET(lfo_freq)) lfo_freq = amy_global.chorus.lfo_freq; - if (AMY_IS_UNSET(depth)) depth = amy_global.chorus.depth; +void config_chorus(uint8_t bus, float level, uint16_t max_delay, float lfo_freq, float depth) { + if (AMY_IS_UNSET(level)) level = S2F(amy_global.bus[bus]->chorus.level); + if (AMY_IS_UNSET(max_delay)) max_delay = amy_global.bus[bus]->chorus.max_delay; + if (AMY_IS_UNSET(lfo_freq)) lfo_freq = amy_global.bus[bus]->chorus.lfo_freq; + if (AMY_IS_UNSET(depth)) depth = amy_global.bus[bus]->chorus.depth; //fprintf(stderr, "config_chorus: osc %d level %.3f max_del %d lfo_freq %.3f depth %.3f\n", // CHORUS_MOD_SOURCE, level, max_delay, lfo_freq, depth); if (level > 0) { ensure_osc_allocd(CHORUS_MOD_SOURCE, NULL); // only allocate delay lines if chorus is more than inaudible. - if (chorus_delay_lines[0] == NULL) { - alloc_chorus_delay_lines(); + if (amy_global.bus[bus]->chorus.chorus_delay_lines[0] == NULL) { + alloc_chorus_delay_lines(bus); } // if we're turning on for the first time, start the oscillator. if (synth[CHORUS_MOD_SOURCE]->status == SYNTH_OFF) { //chorus.level == 0) { @@ -321,32 +318,45 @@ void config_chorus(float level, uint16_t max_delay, float lfo_freq, float depth) // apply max_delay. for (int chan=0; chanmax_delay = max_delay; - chorus_delay_lines[chan]->fixed_delay = (int)max_delay / 2; + amy_global.bus[bus]->chorus.chorus_delay_lines[chan]->fixed_delay = (int)max_delay / 2; } } - amy_global.chorus.max_delay = max_delay; - amy_global.chorus.level = F2S(level); - amy_global.chorus.lfo_freq = lfo_freq; - amy_global.chorus.depth = depth; + amy_global.bus[bus]->chorus.max_delay = max_delay; + amy_global.bus[bus]->chorus.level = F2S(level); + amy_global.bus[bus]->chorus.lfo_freq = lfo_freq; + amy_global.bus[bus]->chorus.depth = depth; +} + +void alloc_reverb_delay_lines(uint8_t bus) { + if (amy_global.bus[bus]->reverb.rev == NULL) + amy_global.bus[bus]->reverb.rev = new_reverb(); + init_stereo_reverb(amy_global.bus[bus]->reverb.rev); +} + +void dealloc_reverb_delay_lines(uint8_t bus) { + if (amy_global.bus[bus]->reverb.rev != NULL) { + deinit_stereo_reverb(amy_global.bus[bus]->reverb.rev); + delete_reverb(amy_global.bus[bus]->reverb.rev); + } } -void config_reverb(float level, float liveness, float damping, float xover_hz) { - if (AMY_IS_UNSET(level)) level = S2F(amy_global.reverb.level); - if (AMY_IS_UNSET(liveness)) liveness = amy_global.reverb.liveness; - if (AMY_IS_UNSET(damping)) damping = amy_global.reverb.damping; - if (AMY_IS_UNSET(xover_hz)) xover_hz = amy_global.reverb.xover_hz; +void config_reverb(uint8_t bus, float level, float liveness, float damping, float xover_hz) { + if (AMY_IS_UNSET(level)) level = S2F(amy_global.bus[bus]->reverb.level); + if (AMY_IS_UNSET(liveness)) liveness = amy_global.bus[bus]->reverb.liveness; + if (AMY_IS_UNSET(damping)) damping = amy_global.bus[bus]->reverb.damping; + if (AMY_IS_UNSET(xover_hz)) xover_hz = amy_global.bus[bus]->reverb.xover_hz; if (level > 0) { //printf("config_reverb: level %f liveness %f xover %f damping %f\n", // level, liveness, xover_hz, damping); - if (amy_global.reverb.level == 0) { - init_stereo_reverb(); // In case it's the first time + if (amy_global.bus[bus]->reverb.level == 0) { + alloc_reverb_delay_lines(bus); } - config_stereo_reverb(liveness, xover_hz, damping); + config_stereo_reverb(amy_global.bus[bus]->reverb.rev, liveness, xover_hz, damping); } - amy_global.reverb.level = F2S(level); - amy_global.reverb.liveness = liveness; - amy_global.reverb.damping = damping; - amy_global.reverb.xover_hz = xover_hz; + amy_global.bus[bus]->reverb.level = F2S(level); + amy_global.bus[bus]->reverb.liveness = liveness; + amy_global.bus[bus]->reverb.damping = damping; + amy_global.bus[bus]->reverb.xover_hz = xover_hz; } @@ -386,6 +396,33 @@ int peek_stack(char *tag) { return 0; } #endif + +void config_eq(uint8_t bus, SAMPLE eq_l, SAMPLE eq_m, SAMPLE eq_h) { + amy_global.bus[bus]->eq.eq[0] = eq_l; + amy_global.bus[bus]->eq.eq[1] = eq_m; + amy_global.bus[bus]->eq.eq[2] = eq_h; +} + + +void bus_reset(uint8_t bus) { + struct bus_state *bus_st = amy_global.bus[bus]; + bus_st->hpf_state = 0; + + config_eq(bus, F2S(1.0f), F2S(1.0f), F2S(1.0f)); + filters_init(bus); + reset_parametric(bus); + + if (AMY_HAS_CHORUS) config_chorus(bus, CHORUS_DEFAULT_LEVEL, CHORUS_DEFAULT_MAX_DELAY, CHORUS_DEFAULT_LFO_FREQ, CHORUS_DEFAULT_MOD_DEPTH); + if (AMY_HAS_REVERB) config_reverb(bus, REVERB_DEFAULT_LEVEL, REVERB_DEFAULT_LIVENESS, REVERB_DEFAULT_DAMPING, REVERB_DEFAULT_XOVER_HZ); + if (AMY_HAS_ECHO) config_echo(bus, S2F(ECHO_DEFAULT_LEVEL), ECHO_DEFAULT_DELAY_MS, ECHO_DEFAULT_MAX_DELAY_MS, S2F(ECHO_DEFAULT_FEEDBACK), S2F(ECHO_DEFAULT_FILTER_COEF)); +} + +void buses_reset() { + for (int i = 0; i < AMY_NUM_BUSES; ++i) { + bus_reset(i); + } +} + int8_t global_init(amy_config_t c) { peek_stack("init"); amy_global.config = c; @@ -396,10 +433,7 @@ int8_t global_init(amy_config_t c) { amy_global.pitch_bend = 0; amy_global.latency_ms = 0; amy_global.tempo = 108.0; - amy_global.eq[0] = F2S(1.0f); - amy_global.eq[1] = F2S(1.0f); - amy_global.eq[2] = F2S(1.0f); - amy_global.hpf_state = 0; + amy_global.pitch_bend = 0; amy_global.transfer_flag = AMY_TRANSFER_TYPE_NONE; amy_global.transfer_storage = NULL; amy_global.transfer_length_bytes = 0; @@ -411,28 +445,26 @@ int8_t global_init(amy_config_t c) { amy_global.next_amy_tick_us = 0; amy_global.us_per_tick = 0; amy_global.sequence_entry_ll_start = NULL; - - amy_global.reverb.level = F2S(REVERB_DEFAULT_LEVEL); - amy_global.reverb.liveness= REVERB_DEFAULT_LIVENESS; - amy_global.reverb.damping = REVERB_DEFAULT_DAMPING; - amy_global.reverb.xover_hz = REVERB_DEFAULT_XOVER_HZ; - - amy_global.chorus.level = F2S(CHORUS_DEFAULT_LEVEL); - amy_global.chorus.max_delay = CHORUS_DEFAULT_MAX_DELAY; - amy_global.chorus.lfo_freq = CHORUS_DEFAULT_LFO_FREQ; - amy_global.chorus.depth = CHORUS_DEFAULT_MOD_DEPTH; - - amy_global.echo.level = F2S(ECHO_DEFAULT_LEVEL); - amy_global.echo.delay_samples = (uint32_t)(ECHO_DEFAULT_DELAY_MS / 1000.f * AMY_SAMPLE_RATE); - amy_global.echo.max_delay_samples = 65536; - amy_global.echo.feedback = F2S(ECHO_DEFAULT_FEEDBACK); - amy_global.echo.filter_coef = ECHO_DEFAULT_FILTER_COEF; + struct bus_state *bus_configs = malloc_caps(sizeof(struct bus_state) * AMY_NUM_BUSES, + amy_global.config.ram_caps_synth); + bzero(bus_configs, sizeof(struct bus_state) * AMY_NUM_BUSES); + for (int i = 0; i < AMY_NUM_BUSES; ++i) { + amy_global.bus[i] = bus_configs; + ++bus_configs; + } + buses_reset(); + amy_init_lock(); return 0; } +void global_deinit(void) { + for (int bus = 0; bus < AMY_NUM_BUSES; ++bus) filters_deinit(bus); + free(amy_global.bus[0]); +} + // Convert to and from the log-frequency scale. // A log-frequency scale is good for summing control inputs. float logfreq_of_freq(float freq) { @@ -532,11 +564,23 @@ void amy_event_to_deltas_queue(amy_event *e, uint16_t base_osc, struct delta **q if(AMY_IS_UNSET(e->osc)) { d.osc = 0; } if(AMY_IS_UNSET(e->time)) { d.time = 0; } - // First, adapt the osc in this event with base_osc offsets for voices - d.osc += base_osc; - - // Ensure this osc has its synthinfo allocated. - ensure_osc_allocd(d.osc, NULL); + // If this is a bus-directed event, use d->osc to store the bus number instead. + bool bus_directed_command = false; + if (AMY_IS_SET(e->eq_l) || AMY_IS_SET(e->eq_m) || AMY_IS_SET(e->eq_h) + || AMY_IS_SET(e->echo_level) || AMY_IS_SET(e->echo_delay_ms) || AMY_IS_SET(e->echo_max_delay_ms) || AMY_IS_SET(e->echo_feedback) || AMY_IS_SET(e->echo_filter_coef) + || AMY_IS_SET(e->chorus_level) || AMY_IS_SET(e->chorus_max_delay) || AMY_IS_SET(e->chorus_lfo_freq) || AMY_IS_SET(e->chorus_depth) + || AMY_IS_SET(e->reverb_level) || AMY_IS_SET(e->reverb_liveness) || AMY_IS_SET(e->reverb_damping) || AMY_IS_SET(e->reverb_xover_hz)) { + if (AMY_IS_SET(e->osc)) fprintf(stderr, "** osc %d specific for bus-directed command, ignoring\n", e->osc); // Can't at this moment be more specific about which command. + // Store the target bus in d.osc. + d.osc = AMY_IS_SET(e->bus) ? e->bus : AMY_DEFAULT_BUS; + bus_directed_command = true; + } else { + // d.osc refers to an osc + // First, adapt the osc in this event with base_osc offsets for voices + d.osc += base_osc; + // Ensure this osc has its synthinfo allocated. + ensure_osc_allocd(d.osc, NULL); + } // Voices / patches gets set up here // you must set both voices & load_patch together to load a patch @@ -563,6 +607,7 @@ void amy_event_to_deltas_queue(amy_event *e, uint16_t base_osc, struct delta **q } // Everything else only added to queue if set + if (!bus_directed_command) EVENT_TO_DELTA_I(bus, BUS) EVENT_TO_DELTA_I(wave, WAVE) EVENT_TO_DELTA_I(preset, PRESET) EVENT_TO_DELTA_F(midi_note, MIDI_NOTE) @@ -700,6 +745,7 @@ void reset_modosc(struct mod_synthinfo *pmsynth) { void reset_osc_params(struct synthinfo *psynth) { // osc params are the things set through the amy_event API // Event-derived config + psynth->bus = AMY_DEFAULT_BUS; psynth->wave = SINE; AMY_UNSET(psynth->preset); AMY_UNSET(psynth->note_source); @@ -788,16 +834,8 @@ void amy_reset_oscs() { //for(uint16_t i=0;i0) ks_init(); - filters_init(); algo_init(); patches_init(amy_global.config.max_memory_patches); instruments_init(amy_global.config.max_synths); @@ -1015,7 +1052,7 @@ void show_debug(uint8_t type) { if(type>1) { // print out all the osc data //printf("global: filter %f resonance %f volume %f bend %f status %d\n", amy_global.filter_freq, amy_global.resonance, amy_global.volume, amy_global.pitch_bend, amy_global.status); - fprintf(stderr,"global: volume %f bend %f eq: %f %f %f \n", amy_global.volume, amy_global.pitch_bend, S2F(amy_global.eq[0]), S2F(amy_global.eq[1]), S2F(amy_global.eq[2])); + fprintf(stderr,"global: volume %f bend %f bus 0 eq: %f %f %f \n", amy_global.volume, amy_global.pitch_bend, S2F(amy_global.bus[0]->eq.eq[0]), S2F(amy_global.bus[0]->eq.eq[1]), S2F(amy_global.bus[0]->eq.eq[2])); for(uint16_t i=0;i<10 /* AMY_OSCS */;i++) { print_osc_debug(i, (type > 3) /* show_eg */); } @@ -1047,8 +1084,11 @@ void show_debug(uint8_t type) { void oscs_deinit() { midi_mappings_deinit(); - dealloc_chorus_delay_lines(); - dealloc_echo_delay_lines(); + for (int bus = 0; bus < AMY_NUM_BUSES; ++bus) { + dealloc_chorus_delay_lines(bus); + dealloc_echo_delay_lines(bus); + dealloc_reverb_delay_lines(bus); + } for(int core = 0; core < AMY_CORES; ++core) { free(fbl[core]); free(per_osc_fb[core]); @@ -1068,7 +1108,6 @@ void oscs_deinit() { instruments_deinit(); patches_deinit(); algo_deinit(); - filters_deinit(); if(amy_global.config.ks_oscs > 0) ks_deinit(); } @@ -1164,6 +1203,7 @@ void play_delta(struct delta *d) { sine_note_on(d->osc, freq_of_logfreq(synth[d->osc]->logfreq_coefs[COEF_CONST])); } } + DELTA_TO_SYNTH_I(BUS, bus) DELTA_TO_SYNTH_F(FEEDBACK, feedback) DELTA_TO_SYNTH_F(RATIO, logratio) DELTA_TO_SYNTH_F(RESONANCE, resonance) @@ -1279,26 +1319,27 @@ void play_delta(struct delta *d) { } } // for global changes, just make the change, no need to update the per-osc synth + uint8_t bus = d->osc; // We assume d.osc was hijacked in amy_event_to_deltas_queue if(d->param == VOLUME) amy_global.volume = d->data.f; if(d->param == PITCH_BEND) amy_global.pitch_bend = d->data.f; if(d->param == LATENCY) amy_global.latency_ms = d->data.i; if(d->param == TEMPO) { amy_global.tempo = d->data.f; sequencer_recompute(); } - if(d->param == EQ_L) amy_global.eq[0] = F2S(powf(10, d->data.f / 20.0)); - if(d->param == EQ_M) amy_global.eq[1] = F2S(powf(10, d->data.f / 20.0)); - if(d->param == EQ_H) amy_global.eq[2] = F2S(powf(10, d->data.f / 20.0)); - if(d->param == ECHO_LEVEL) config_echo(d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); - if(d->param == ECHO_DELAY_MS) config_echo(AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); - if(d->param == ECHO_MAX_DELAY_MS) config_echo(AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); - if(d->param == ECHO_FEEDBACK) config_echo(AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT); - if(d->param == ECHO_FILTER_COEF) config_echo(AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f); - if(d->param == CHORUS_LEVEL) config_chorus(d->data.f, UINT16_MAX, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); - if(d->param == CHORUS_MAX_DELAY) config_chorus(AMY_UNSET_FLOAT, AMY_IS_UNSET(d->data.f)?UINT16_MAX : (uint16_t)roundf(d->data.f), AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); - if(d->param == CHORUS_LFO_FREQ) config_chorus(AMY_UNSET_FLOAT, UINT16_MAX, d->data.f, AMY_UNSET_FLOAT); - if(d->param == CHORUS_DEPTH) config_chorus(AMY_UNSET_FLOAT, UINT16_MAX, AMY_UNSET_FLOAT, d->data.f); - if(d->param == REVERB_LEVEL) config_reverb(d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); - if(d->param == REVERB_LIVENESS) config_reverb(AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); - if(d->param == REVERB_DAMPING) config_reverb(AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT); - if(d->param == REVERB_XOVER_HZ) config_reverb(AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f); + if(d->param == EQ_L) amy_global.bus[bus]->eq.eq[0] = F2S(powf(10, d->data.f / 20.0)); + if(d->param == EQ_M) amy_global.bus[bus]->eq.eq[1] = F2S(powf(10, d->data.f / 20.0)); + if(d->param == EQ_H) amy_global.bus[bus]->eq.eq[2] = F2S(powf(10, d->data.f / 20.0)); + if(d->param == ECHO_LEVEL) config_echo(bus, d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); + if(d->param == ECHO_DELAY_MS) config_echo(bus, AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); + if(d->param == ECHO_MAX_DELAY_MS) config_echo(bus, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); + if(d->param == ECHO_FEEDBACK) config_echo(bus, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT); + if(d->param == ECHO_FILTER_COEF) config_echo(bus, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f); + if(d->param == CHORUS_LEVEL) config_chorus(bus, d->data.f, UINT16_MAX, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); + if(d->param == CHORUS_MAX_DELAY) config_chorus(bus, AMY_UNSET_FLOAT, AMY_IS_UNSET(d->data.f)?UINT16_MAX : (uint16_t)roundf(d->data.f), AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); + if(d->param == CHORUS_LFO_FREQ) config_chorus(bus, AMY_UNSET_FLOAT, UINT16_MAX, d->data.f, AMY_UNSET_FLOAT); + if(d->param == CHORUS_DEPTH) config_chorus(bus, AMY_UNSET_FLOAT, UINT16_MAX, AMY_UNSET_FLOAT, d->data.f); + if(d->param == REVERB_LEVEL) config_reverb(bus, d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); + if(d->param == REVERB_LIVENESS) config_reverb(bus, AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT); + if(d->param == REVERB_DAMPING) config_reverb(bus, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f, AMY_UNSET_FLOAT); + if(d->param == REVERB_XOVER_HZ) config_reverb(bus, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, AMY_UNSET_FLOAT, d->data.f); // triggers / envelopes // the only way a sound is made is if velocity (note on) is >0. @@ -1707,6 +1748,8 @@ SAMPLE render_osc_wave(uint16_t osc, uint8_t core, SAMPLE* buf) { void amy_render(uint16_t start, uint16_t end, uint8_t core) { AMY_PROFILE_START(AMY_RENDER) + uint8_t bus = 0; // FIXME: Render per bus + for(uint16_t i=0;ichorus.level!=0) { + bzero(amy_global.bus[bus]->chorus.delay_mod, AMY_BLOCK_SIZE * sizeof(SAMPLE)); + render_osc_wave(CHORUS_MOD_SOURCE, 0 /* core */, amy_global.bus[bus]->chorus.delay_mod); } } @@ -1852,6 +1896,7 @@ void amy_process_event(amy_event *e) { int16_t * amy_fill_buffer() { AMY_PROFILE_START(AMY_FILL_BUFFER) + uint8_t bus = 0; // FIXME: handle buses. #ifdef __EMSCRIPTEN__ // post a message to the main thread of the audioworklet (amy main, in this case) that a block has been finished //emscripten_audio_worklet_post_function_v(0, amy_block_processed); @@ -1872,18 +1917,18 @@ int16_t * amy_fill_buffer() { // Apply global processing only if there is some signal. //if (max_val > 0) { // NO - see #629 // apply the eq filters if there is some signal and EQ is non-default. - if (amy_global.eq[0] != F2S(1.0f) || amy_global.eq[1] != F2S(1.0f) || amy_global.eq[2] != F2S(1.0f)) { - parametric_eq_process(fbl[0]); + if (amy_global.bus[bus]->eq.eq[0] != F2S(1.0f) || amy_global.bus[bus]->eq.eq[1] != F2S(1.0f) || amy_global.bus[bus]->eq.eq[2] != F2S(1.0f)) { + parametric_eq_process(bus, fbl[0]); } if(AMY_HAS_CHORUS) { // apply chorus. - if(amy_global.chorus.level > 0 && chorus_delay_lines[0] != NULL) { + if(amy_global.bus[bus]->chorus.level > 0 && amy_global.bus[bus]->chorus.chorus_delay_lines[0] != NULL) { // apply time-varying delays to both chans. // delay_mod_val, the modulated delay amount, is set up before calling render_*. SAMPLE scale = F2S(1.0f); for (int16_t c=0; c < AMY_NCHANS; ++c) { - apply_variable_delay(fbl[0] + c * AMY_BLOCK_SIZE, chorus_delay_lines[c], - delay_mod, scale, amy_global.chorus.level, 0); + apply_variable_delay(fbl[0] + c * AMY_BLOCK_SIZE, amy_global.bus[bus]->chorus.chorus_delay_lines[c], + amy_global.bus[bus]->chorus.delay_mod, scale, amy_global.bus[bus]->chorus.level, 0); // flip delay direction for alternating channels. scale = -scale; } @@ -1892,19 +1937,19 @@ int16_t * amy_fill_buffer() { //} if (AMY_HAS_ECHO) { // Apply echo. - if (amy_global.echo.level > 0 && echo_delay_lines[0] != NULL ) { + if (amy_global.bus[bus]->echo.level > 0 && amy_global.bus[bus]->echo.echo_delay_lines[0] != NULL ) { for (int16_t c=0; c < AMY_NCHANS; ++c) { - apply_fixed_delay(fbl[0] + c * AMY_BLOCK_SIZE, echo_delay_lines[c], amy_global.echo.delay_samples, amy_global.echo.level, amy_global.echo.feedback, amy_global.echo.filter_coef); + apply_fixed_delay(fbl[0] + c * AMY_BLOCK_SIZE, amy_global.bus[bus]->echo.echo_delay_lines[c], amy_global.bus[bus]->echo.delay_samples, amy_global.bus[bus]->echo.level, amy_global.bus[bus]->echo.feedback, amy_global.bus[bus]->echo.filter_coef); } } } if(AMY_HAS_REVERB) { // apply reverb. - if(amy_global.reverb.level > 0) { + if(amy_global.bus[bus]->reverb.level > 0) { if(AMY_NCHANS == 1) { - stereo_reverb(fbl[0], NULL, fbl[0], NULL, AMY_BLOCK_SIZE, amy_global.reverb.level); + stereo_reverb(amy_global.bus[bus]->reverb.rev, fbl[0], NULL, fbl[0], NULL, AMY_BLOCK_SIZE, amy_global.bus[bus]->reverb.level); } else { - stereo_reverb(fbl[0], fbl[0] + AMY_BLOCK_SIZE, fbl[0], fbl[0] + AMY_BLOCK_SIZE, AMY_BLOCK_SIZE, amy_global.reverb.level); + stereo_reverb(amy_global.bus[bus]->reverb.rev, fbl[0], fbl[0] + AMY_BLOCK_SIZE, fbl[0], fbl[0] + AMY_BLOCK_SIZE, AMY_BLOCK_SIZE, amy_global.bus[bus]->reverb.level); } } } @@ -1976,10 +2021,10 @@ int16_t * amy_fill_buffer() { if(byte_offset + bytes_to_copy >= amy_global.transfer_length_bytes) { bytes_to_copy = amy_global.transfer_length_bytes - byte_offset; } - if(amy_global.transfer_file_handle==AMY_BUS_OUTPUT) { + if(amy_global.transfer_file_handle==AMY_TRANSFER_OUTPUT) { // copy block[] to amy_global.transfer_storage memcpy(amy_global.transfer_storage + byte_offset, output_block, bytes_to_copy); - } else if(amy_global.transfer_file_handle==AMY_BUS_AUDIO_IN) { + } else if(amy_global.transfer_file_handle==AMY_TRANSFER_AUDIO_IN) { // copy audio input buffer to storage memcpy(amy_global.transfer_storage + byte_offset, amy_in_block, bytes_to_copy); } diff --git a/src/amy.h b/src/amy.h index 71305a9f..1cd57f56 100644 --- a/src/amy.h +++ b/src/amy.h @@ -93,9 +93,13 @@ extern const uint32_t pcm_wavetable_len; // File-streaming buffer size multiplier (in blocks). #define PCM_FILE_BUFFER_MULT 8 +// Values used to indicate nature of transfer. +#define AMY_TRANSFER_OUTPUT 1 +#define AMY_TRANSFER_AUDIO_IN 2 -#define AMY_BUS_OUTPUT 1 -#define AMY_BUS_AUDIO_IN 2 +// Each bus has separate FX (EQ, chorus, reverb, echo) +#define AMY_NUM_BUSES 4 +#define AMY_DEFAULT_BUS 0 // Always use fixed point. You can remove this if you want float #define AMY_USE_FIXEDPOINT @@ -108,7 +112,6 @@ extern const uint32_t pcm_wavetable_len; // Always use 2 channels. Clients that want mono can deinterleave #define AMY_NCHANS 2 - // Use dual cores on supported platforms #if (defined (ESP_PLATFORM) || defined (ARDUINO_ARCH_RP2040) ||defined(ARDUINO_ARCH_RP2350)) #define AMY_DUALCORE @@ -356,7 +359,8 @@ enum params{ REVERB_LIVENESS, REVERB_DAMPING, REVERB_XOVER_HZ, - NO_PARAM // 209 + BUS, + NO_PARAM // 210 }; /////////////////////////////////////// @@ -515,12 +519,15 @@ typedef struct amy_event { uint8_t grab_midi_notes; // To enable/disable automatic MIDI note-on/off generating note-on/off. uint8_t pedal; // MIDI pedal value. uint16_t num_voices; + uint8_t oscs_per_voice; // Used when initializing a synth without a patch. + // uint32_t sequence[3]; // tick, period, tag // uint8_t status; uint8_t note_source; // .. to mark note on/offs that come from MIDI so we don't send them back out again. uint32_t reset_osc; // Global effects + uint8_t bus; // Which bus this osc ends up on / Prefix for global FX params float echo_level; float echo_delay_ms; float echo_max_delay_ms; @@ -534,13 +541,13 @@ typedef struct amy_event { float reverb_liveness; float reverb_damping; float reverb_xover_hz; - uint8_t oscs_per_voice; // Used when initializing a synth without a patch. } amy_event; // This is the state of each oscillator, set by the sequencer from deltas struct synthinfo { uint16_t osc; // self-reference // Configuration (can be fixed during oscillation) + uint8_t bus; // Which bus this osc ends up on uint16_t wave; int16_t preset; // Negative preset is voice count for build-your-own PARTIALS uint8_t note_source; // Was the most recent note on/off received e.g. from MIDI? @@ -622,7 +629,7 @@ typedef struct delay_line { } delay_line_t; -#include "delay.h" +//#include "delay.h" #include "sequencer.h" #include "amy_midi.h" #include "transfer.h" @@ -723,11 +730,27 @@ typedef struct { } amy_config_t; +typedef struct eq_state { + SAMPLE eq[3]; + SAMPLE ** eq_coeffs; + SAMPLE *** eq_delay; +} eq_state_t; + +typedef struct reverb_params { + SAMPLE f1state, f2state, f3state, f4state; + delay_line_t *delay_1, *delay_2, *delay_3, *delay_4; + delay_line_t *ref_1, *ref_2, *ref_3, *ref_4, *ref_5, *ref_6; + SAMPLE lpfcoef; + SAMPLE lpfgain; + SAMPLE liveness; +} reverb_params_t; + typedef struct reverb_state { SAMPLE level; float liveness; float damping; float xover_hz; + reverb_params_t *rev; } reverb_state_t; typedef struct chorus_config { @@ -735,6 +758,8 @@ typedef struct chorus_config { int32_t max_delay; // Max delay when modulating. Must be <= DELAY_LINE_LEN float lfo_freq; float depth; + delay_line_t *chorus_delay_lines[AMY_MAX_CHANNELS]; + SAMPLE *delay_mod; } chorus_config_t; typedef struct echo_config { @@ -743,19 +768,28 @@ typedef struct echo_config { uint32_t max_delay_samples; // Maximum delay, i.e. size of allocated delay line. SAMPLE feedback; // Gain applied when feeding back output to input. SAMPLE filter_coef; // Echo is filtered by a two-point normalize IIR. This is the real pole location. + delay_line_t *echo_delay_lines[AMY_MAX_CHANNELS]; } echo_config_t; +// Per-bus parameters +struct bus_state { + // State of fixed dc-blocking HPF + SAMPLE hpf_state; + eq_state_t eq; + reverb_state_t reverb; + chorus_config_t chorus; + echo_config_t echo; +}; + // global synth state struct state { amy_config_t config; uint8_t running; uint8_t i2s_is_in_background; // Flag not to handle I2S in amy_update. float volume; - float pitch_bend; - // State of fixed dc-blocking HPF - SAMPLE hpf_state; - SAMPLE eq[3]; + float pitch_bend; // Legacy global pitch bend, will be subsumed per-synth (instrument). + uint16_t delta_qsize; struct delta * delta_queue; // start of the sorted queue of deltas to execute. int16_t latency_ms; @@ -764,10 +798,6 @@ struct state { float time; uint8_t debug_flag; - reverb_state_t reverb; - chorus_config_t chorus; - echo_config_t echo; - // Transfer uint8_t transfer_flag; uint8_t * transfer_storage; @@ -782,6 +812,11 @@ struct state { uint32_t us_per_tick; sequence_entry_ll_t * sequence_entry_ll_start; + // Buses + struct bus_state *bus[AMY_NUM_BUSES]; + + // Final output mix + float bus_gain[AMY_NUM_BUSES]; }; @@ -808,6 +843,7 @@ extern output_sample_type * amy_in_block; extern output_sample_type * amy_external_in_block; int8_t global_init(amy_config_t c); +void global_deinit(); void amy_deltas_reset(); void add_delta_to_queue(struct delta *d, struct delta **queue); void amy_add_event_internal(amy_event *e, uint16_t base_osc); @@ -826,9 +862,9 @@ float portamento_ms_to_alpha(uint16_t portamento_ms); uint16_t alpha_to_portamento_ms(float alpha); int8_t check_init(amy_err_t (*fn)(), char *name); void * malloc_caps(uint32_t size, uint32_t flags); -void config_reverb(float level, float liveness, float damping, float xover_hz); -void config_chorus(float level, uint16_t max_delay, float lfo_freq, float depth); -void config_echo(float level, float delay_ms, float max_delay_ms, float feedback, float filter_coef); +void config_reverb(uint8_t bus, float level, float liveness, float damping, float xover_hz); +void config_chorus(uint8_t bus, float level, uint16_t max_delay, float lfo_freq, float depth); +void config_echo(uint8_t bus, float level, float delay_ms, float max_delay_ms, float feedback, float filter_coef); void osc_note_on(uint16_t osc, float initial_freq); void chorus_note_on(float initial_freq); @@ -968,7 +1004,7 @@ extern int instruments_max_instruments(); extern void instruments_init(int num_instruments); extern void instruments_deinit(); extern void instruments_reset(); -extern void instrument_add_new(int instrument_number, int num_voices, uint16_t *amy_voices, uint16_t patch_number, uint16_t oscs_per_voice, uint32_t flags); +extern void instrument_add_new(int instrument_number, int num_voices, uint16_t *amy_voices, uint16_t patch_number, uint16_t oscs_per_voice, uint8_t bus, uint32_t flags); extern void instrument_release(int instrument_number); extern void instrument_change_number(int old_instrument_number, int new_instrument_number); #define _INSTRUMENT_NO_VOICE (255) @@ -980,6 +1016,8 @@ extern int instrument_sustain(int instrument_number, bool sustain, uint16_t *amy extern int instrument_get_patch_number(int instrument_number); extern int instrument_get_oscs_per_voice(int instrument_number); extern uint32_t instrument_get_flags(int instrument_number); +extern uint8_t instrument_get_bus(int instrument_number); +extern void instrument_set_bus(int instrument_number, uint8_t bus); extern uint16_t instrument_noteon_delay_ms(int instrument_number); extern void instrument_set_noteon_delay_ms(int instrument_number, uint16_t noteon_delay_ms); extern bool instrument_grab_midi_notes(int instrument_number); @@ -1031,12 +1069,12 @@ extern void pcm_unload_preset(uint16_t preset_number); extern void pcm_unload_all_presets(); // filters -extern void filters_init(); -extern void filters_deinit(); +extern void filters_init(uint8_t bus); +extern void filters_deinit(uint8_t bus); extern SAMPLE filter_process(SAMPLE * block, uint16_t osc, SAMPLE max_value); -extern void parametric_eq_process(SAMPLE *block); +extern void parametric_eq_process(uint8_t bus, SAMPLE *block); extern void reset_filter(uint16_t osc); -extern void reset_parametric(void); +extern void reset_parametric(uint8_t bus); extern float dsps_sqrtf_f32_ansi(float f); extern int8_t dsps_biquad_gen_lpf_f32(SAMPLE *coeffs, float f, float qFactor); extern int8_t dsps_biquad_f32_ansi(const SAMPLE *input, SAMPLE *output, int len, SAMPLE *coef, SAMPLE *w); diff --git a/src/api.c b/src/api.c index 9aaa4ea9..bc6b503b 100644 --- a/src/api.c +++ b/src/api.c @@ -110,6 +110,7 @@ void amy_clear_event(amy_event *e) { e->status = EVENT_EMPTY; AMY_UNSET(e->time); AMY_UNSET(e->osc); + AMY_UNSET(e->bus); AMY_UNSET(e->preset); AMY_UNSET(e->wave); AMY_UNSET(e->patch_number); @@ -400,6 +401,7 @@ void amy_stop() { stop_midi(); amy_platform_deinit(); oscs_deinit(); + global_deinit(); } diff --git a/src/delay.c b/src/delay.c index cfe2ed95..c143befc 100644 --- a/src/delay.c +++ b/src/delay.c @@ -193,31 +193,30 @@ void apply_fixed_delay(SAMPLE *block, delay_line_t *delay_line, uint32_t delay_s delay_line_in_out_fixed_delay(block, block, AMY_BLOCK_SIZE, delay_samples, delay_line, mix_level, feedback, filter_coef); } -SAMPLE f1state = 0, f2state = 0, f3state = 0, f4state = 0; - -delay_line_t *delay_1 = NULL, *delay_2 = NULL, *delay_3 = NULL, - *delay_4 = NULL; -delay_line_t *ref_1 = NULL, *ref_2 = NULL, *ref_3= NULL, - *ref_4 = NULL, *ref_5 = NULL, *ref_6 = NULL; - #define INITIAL_XOVER_HZ 3000.0 #define INITIAL_LIVENESS 0.85 #define INITIAL_DAMPING 0.5 -SAMPLE lpfcoef; -SAMPLE lpfgain; -SAMPLE liveness; +reverb_params_t *new_reverb() { + reverb_params_t *rev = malloc_caps(sizeof(reverb_params_t), amy_global.config.ram_caps_synth); + bzero(rev, sizeof(reverb_params_t)); + return rev; +} + +void delete_reverb(reverb_params_t *rev) { + if(rev) free(rev); +} -void config_stereo_reverb(float a_liveness, float crossover_hz, float damping) { +void config_stereo_reverb(reverb_params_t *rev, float a_liveness, float crossover_hz, float damping) { //printf("config_stereo_reverb: liveness %f xover %f damping %f\n", // a_liveness, crossover_hz, damping); // liveness (0..1) controls how much energy is preserved (larger = longer reverb). - liveness = F2S(a_liveness); + rev->liveness = F2S(a_liveness); // crossover_hz is 3dB point of 1-pole lowpass freq. - lpfcoef = F2S(6.2832f * crossover_hz / AMY_SAMPLE_RATE); - if (lpfcoef > F2S(1.f)) lpfcoef = F2S(1.f); - if (lpfcoef < 0) lpfcoef = 0; - lpfgain = F2S(1.f - damping); + rev->lpfcoef = F2S(6.2832f * crossover_hz / AMY_SAMPLE_RATE); + if (rev->lpfcoef > F2S(1.f)) rev->lpfcoef = F2S(1.f); + if (rev->lpfcoef < 0) rev->lpfcoef = 0; + rev->lpfgain = F2S(1.f - damping); } // Delay 1 is 58.6435 ms @@ -241,25 +240,40 @@ void config_stereo_reverb(float a_liveness, float crossover_hz, float damping) { #define REF6SAMPS 602 // 13.645 ms -void init_stereo_reverb(void) { - if (delay_1 == NULL) { - delay_1 = new_delay_line(DELAY_POW2, DELAY1SAMPS, amy_global.config.ram_caps_delay); - delay_2 = new_delay_line(DELAY_POW2, DELAY2SAMPS, amy_global.config.ram_caps_delay); - delay_3 = new_delay_line(DELAY_POW2, DELAY3SAMPS, amy_global.config.ram_caps_delay); - delay_4 = new_delay_line(DELAY_POW2, DELAY4SAMPS, amy_global.config.ram_caps_delay); +void init_stereo_reverb(reverb_params_t *rev) { + if (rev->delay_1 == NULL) { + rev->delay_1 = new_delay_line(DELAY_POW2, DELAY1SAMPS, amy_global.config.ram_caps_delay); + rev->delay_2 = new_delay_line(DELAY_POW2, DELAY2SAMPS, amy_global.config.ram_caps_delay); + rev->delay_3 = new_delay_line(DELAY_POW2, DELAY3SAMPS, amy_global.config.ram_caps_delay); + rev->delay_4 = new_delay_line(DELAY_POW2, DELAY4SAMPS, amy_global.config.ram_caps_delay); - ref_1 = new_delay_line(4096, REF1SAMPS, amy_global.config.ram_caps_delay); - ref_2 = new_delay_line(2048, REF2SAMPS, amy_global.config.ram_caps_delay); - ref_3 = new_delay_line(2048, REF3SAMPS, amy_global.config.ram_caps_delay); - ref_4 = new_delay_line(1024, REF4SAMPS, amy_global.config.ram_caps_delay); - ref_5 = new_delay_line(1024, REF5SAMPS, amy_global.config.ram_caps_delay); - ref_6 = new_delay_line(1024, REF6SAMPS, amy_global.config.ram_caps_delay); + rev->ref_1 = new_delay_line(4096, REF1SAMPS, amy_global.config.ram_caps_delay); + rev->ref_2 = new_delay_line(2048, REF2SAMPS, amy_global.config.ram_caps_delay); + rev->ref_3 = new_delay_line(2048, REF3SAMPS, amy_global.config.ram_caps_delay); + rev->ref_4 = new_delay_line(1024, REF4SAMPS, amy_global.config.ram_caps_delay); + rev->ref_5 = new_delay_line(1024, REF5SAMPS, amy_global.config.ram_caps_delay); + rev->ref_6 = new_delay_line(1024, REF6SAMPS, amy_global.config.ram_caps_delay); - config_stereo_reverb(INITIAL_LIVENESS, INITIAL_XOVER_HZ, INITIAL_DAMPING); + config_stereo_reverb(rev, INITIAL_LIVENESS, INITIAL_XOVER_HZ, INITIAL_DAMPING); + } +} + +void deinit_stereo_reverb(reverb_params_t *rev) { + if (rev->delay_1 != NULL) { + free(rev->delay_1); rev->delay_1 = NULL; + free(rev->delay_2); rev->delay_2 = NULL; + free(rev->delay_3); rev->delay_3 = NULL; + free(rev->delay_4); rev->delay_4 = NULL; + free(rev->ref_1); rev->ref_1 = NULL; + free(rev->ref_2); rev->ref_2 = NULL; + free(rev->ref_3); rev->ref_3 = NULL; + free(rev->ref_4); rev->ref_4 = NULL; + free(rev->ref_5); rev->ref_5 = NULL; + free(rev->ref_6); rev->ref_6 = NULL; } } -void stereo_reverb(SAMPLE *r_in, SAMPLE *l_in, SAMPLE *r_out, SAMPLE *l_out, int n_samples, SAMPLE level) { +void stereo_reverb(reverb_params_t *rev, SAMPLE *r_in, SAMPLE *l_in, SAMPLE *r_out, SAMPLE *l_out, int n_samples, SAMPLE level) { // Stereo reverb. *{r,l}_in each point to n_samples input samples. // n_samples are written to {r,l}_out. // Recreate @@ -276,56 +290,56 @@ void stereo_reverb(SAMPLE *r_in, SAMPLE *l_in, SAMPLE *r_out, SAMPLE *l_out, int r_acc = MUL0_SS(F2S(0.0625f), in_r); l_acc = MUL0_SS(F2S(0.0625f), in_l); - DEL_IN(ref_1, l_acc); - SAMPLE d_out = DEL_OUT(ref_1, 0); + DEL_IN(rev->ref_1, l_acc); + SAMPLE d_out = DEL_OUT(rev->ref_1, 0); l_acc = r_acc - d_out; r_acc += d_out; - DEL_IN(ref_2, l_acc); - d_out = DEL_OUT(ref_2, 0); + DEL_IN(rev->ref_2, l_acc); + d_out = DEL_OUT(rev->ref_2, 0); l_acc = r_acc - d_out; r_acc += d_out; - DEL_IN(ref_3, l_acc); - d_out = DEL_OUT(ref_3, 0); + DEL_IN(rev->ref_3, l_acc); + d_out = DEL_OUT(rev->ref_3, 0); l_acc = r_acc - d_out; r_acc += d_out; - DEL_IN(ref_4, l_acc); - d_out = DEL_OUT(ref_4, 0); + DEL_IN(rev->ref_4, l_acc); + d_out = DEL_OUT(rev->ref_4, 0); l_acc = r_acc - d_out; r_acc += d_out; - DEL_IN(ref_5, l_acc); - d_out = DEL_OUT(ref_5, 0); + DEL_IN(rev->ref_5, l_acc); + d_out = DEL_OUT(rev->ref_5, 0); l_acc = r_acc - d_out; r_acc += d_out; - DEL_IN(ref_6, l_acc); - l_acc = DEL_OUT(ref_6, 0); + DEL_IN(rev->ref_6, l_acc); + l_acc = DEL_OUT(rev->ref_6, 0); // Reverb delays & matrix. - SAMPLE d1 = DEL_OUT(delay_1, 0); - d1 = LPF(d1, f1state, lpfcoef, lpfgain, liveness); + SAMPLE d1 = DEL_OUT(rev->delay_1, 0); + d1 = LPF(d1, rev->f1state, rev->lpfcoef, rev->lpfgain, rev->liveness); d1 += r_acc; *r_out++ = in_r + MUL8_SS(level, d1); - SAMPLE d2 = DEL_OUT(delay_2, 0); - d2 = LPF(d2, f2state, lpfcoef, lpfgain, liveness); + SAMPLE d2 = DEL_OUT(rev->delay_2, 0); + d2 = LPF(d2, rev->f2state, rev->lpfcoef, rev->lpfgain, rev->liveness); d2 += l_acc; if (l_out != NULL) *l_out++ = in_l + MUL8_SS(level, d2); - SAMPLE d3 = DEL_OUT(delay_3, 0); - d3 = LPF(d3, f3state, lpfcoef, lpfgain, liveness); + SAMPLE d3 = DEL_OUT(rev->delay_3, 0); + d3 = LPF(d3, rev->f3state, rev->lpfcoef, rev->lpfgain, rev->liveness); - SAMPLE d4 = DEL_OUT(delay_4, 0); - d4 = LPF(d4, f3state, lpfcoef, lpfgain, liveness); + SAMPLE d4 = DEL_OUT(rev->delay_4, 0); + d4 = LPF(d4, rev->f3state, rev->lpfcoef, rev->lpfgain, rev->liveness); // Mixing and feedback. - DEL_IN(delay_1, d1 + d2 + d3 + d4); - DEL_IN(delay_2, d1 - d2 + d3 - d4); - DEL_IN(delay_3, d1 + d2 - d3 - d4); - DEL_IN(delay_4, d1 - d2 - d3 + d4); + DEL_IN(rev->delay_1, d1 + d2 + d3 + d4); + DEL_IN(rev->delay_2, d1 - d2 + d3 - d4); + DEL_IN(rev->delay_3, d1 + d2 - d3 - d4); + DEL_IN(rev->delay_4, d1 - d2 - d3 + d4); } } diff --git a/src/delay.h b/src/delay.h index 60902088..c519bb31 100644 --- a/src/delay.h +++ b/src/delay.h @@ -13,8 +13,11 @@ void free_delay_line(delay_line_t *d); void apply_variable_delay(SAMPLE *block, delay_line_t *delay_line, SAMPLE *delay_samples, SAMPLE mod_scale, SAMPLE mix_level, SAMPLE feedback_level); void apply_fixed_delay(SAMPLE *block, delay_line_t *delay_line, uint32_t delay_samples, SAMPLE mix_level, SAMPLE feedback, SAMPLE filter_coef); -void config_stereo_reverb(float a_liveness, float crossover_hz, float damping); -void init_stereo_reverb(void); -void stereo_reverb(SAMPLE *r_in, SAMPLE *l_in, SAMPLE *r_out, SAMPLE *l_out, int n_samples, SAMPLE level); +reverb_params_t *new_reverb(); +void delete_reverb(reverb_params_t *rev); +void config_stereo_reverb(reverb_params_t *rev, float a_liveness, float crossover_hz, float damping); +void init_stereo_reverb(reverb_params_t *rev); +void deinit_stereo_reverb(reverb_params_t *rev); +void stereo_reverb(reverb_params_t *rev, SAMPLE *r_in, SAMPLE *l_in, SAMPLE *r_out, SAMPLE *l_out, int n_samples, SAMPLE level); #endif // !_DELAY_H diff --git a/src/examples.c b/src/examples.c index a2a88dea..08e7c25d 100644 --- a/src/examples.c +++ b/src/examples.c @@ -254,13 +254,15 @@ void example_patches() { } void example_reverb() { if(AMY_HAS_REVERB) { - config_reverb(2, REVERB_DEFAULT_LIVENESS, REVERB_DEFAULT_DAMPING, REVERB_DEFAULT_XOVER_HZ); + uint8_t bus = 0; + config_reverb(bus, 2, REVERB_DEFAULT_LIVENESS, REVERB_DEFAULT_DAMPING, REVERB_DEFAULT_XOVER_HZ); } } void example_chorus() { if(AMY_HAS_CHORUS) { - config_chorus(0.8, CHORUS_DEFAULT_MAX_DELAY, CHORUS_DEFAULT_LFO_FREQ, CHORUS_DEFAULT_MOD_DEPTH); + uint8_t bus = 0; + config_chorus(bus, 0.8, CHORUS_DEFAULT_MAX_DELAY, CHORUS_DEFAULT_LFO_FREQ, CHORUS_DEFAULT_MOD_DEPTH); } } diff --git a/src/filters.c b/src/filters.c index da2ab79b..000a8ba7 100644 --- a/src/filters.c +++ b/src/filters.c @@ -10,10 +10,6 @@ #define FILT_NUM_DELAYS 4 // Need 4 memories for DFI filters, if used (only 2 for DFII). -SAMPLE ** eq_coeffs; -SAMPLE *** eq_delay; - - #ifdef __GNUC__ #pragma GCC diagnostic push // save the actual diag context #pragma GCC diagnostic ignored "-Wuninitialized" // disable maybe warnings @@ -465,7 +461,9 @@ int8_t dsps_biquad_f32_ansi_commuted(const SAMPLE *input, SAMPLE *output, int le return 0; } -void filters_deinit() { +void filters_deinit(uint8_t bus) { + SAMPLE **eq_coeffs = amy_global.bus[bus]->eq.eq_coeffs; + SAMPLE ***eq_delay = amy_global.bus[bus]->eq.eq_delay; for(uint16_t i=0;ieq.eq_coeffs = NULL; + amy_global.bus[bus]->eq.eq_delay = NULL; } -void filters_init() { - eq_coeffs = malloc_caps(sizeof(SAMPLE*)*3, amy_global.config.ram_caps_fbl); - eq_delay = malloc_caps(sizeof(SAMPLE**)*AMY_NCHANS, amy_global.config.ram_caps_fbl); +void filters_init(uint8_t bus) { + SAMPLE ***p_eq_coeffs = &amy_global.bus[bus]->eq.eq_coeffs; + SAMPLE ****p_eq_delay = &amy_global.bus[bus]->eq.eq_delay; + *p_eq_coeffs = malloc_caps(sizeof(SAMPLE*)*3, amy_global.config.ram_caps_fbl); + *p_eq_delay = malloc_caps(sizeof(SAMPLE**)*AMY_NCHANS, amy_global.config.ram_caps_fbl); for(uint16_t i=0;i<3;i++) { - eq_coeffs[i] = malloc_caps(sizeof(SAMPLE) * 5, amy_global.config.ram_caps_fbl); + (*p_eq_coeffs)[i] = malloc_caps(sizeof(SAMPLE) * 5, amy_global.config.ram_caps_fbl); } for(uint16_t i=0;ieq.eq_coeffs; + SAMPLE ***eq_delay = amy_global.bus[bus]->eq.eq_delay; + for(int c = 0; c < AMY_NCHANS; ++c) { SAMPLE *cblock = block + c * AMY_BLOCK_SIZE; // Zeros then poles - Direct Form I @@ -609,13 +540,13 @@ void parametric_eq_process_top16block(SAMPLE *block) { int c20bits, c23bits, c24bits; // int c21bits, c22bits, c11bits, c12bits, c01bits, c02bits; // Fold the global EQ parameters into the forward-gains of each stage. - SAMPLE c00 = top16SMUL_a_part(top16SMUL(amy_global.eq[0], eq_coeffs[0][0]), &c00bits); + SAMPLE c00 = top16SMUL_a_part(top16SMUL(amy_global.bus[bus]->eq.eq[0], eq_coeffs[0][0]), &c00bits); SAMPLE c03 = top16SMUL_a_part(eq_coeffs[0][3], &c03bits); SAMPLE c04 = top16SMUL_a_part(eq_coeffs[0][4], &c04bits); - SAMPLE c10 = top16SMUL_a_part(top16SMUL(amy_global.eq[1], eq_coeffs[1][0]), &c10bits); + SAMPLE c10 = top16SMUL_a_part(top16SMUL(amy_global.bus[bus]->eq.eq[1], eq_coeffs[1][0]), &c10bits); SAMPLE c13 = top16SMUL_a_part(eq_coeffs[1][3], &c13bits); SAMPLE c14 = top16SMUL_a_part(eq_coeffs[1][4], &c14bits); - SAMPLE c20 = top16SMUL_a_part(top16SMUL(amy_global.eq[2], eq_coeffs[2][0]), &c20bits); + SAMPLE c20 = top16SMUL_a_part(top16SMUL(amy_global.bus[bus]->eq.eq[2], eq_coeffs[2][0]), &c20bits); SAMPLE c23 = top16SMUL_a_part(eq_coeffs[2][3], &c23bits); SAMPLE c24 = top16SMUL_a_part(eq_coeffs[2][4], &c24bits); for (int i = 0 ; i < AMY_BLOCK_SIZE ; i++) { @@ -841,7 +772,8 @@ void reset_filter(uint16_t osc) { } -void reset_parametric(void) { +void reset_parametric(uint8_t bus) { + SAMPLE ***eq_delay = amy_global.bus[bus]->eq.eq_delay; for (int c = 0; c < AMY_NCHANS; ++c) { for (int b = 0; b < 3; ++b) { for (int d = 0; d < FILT_NUM_DELAYS; ++d) { diff --git a/src/instrument.c b/src/instrument.c index 2b208111..9e0e5d8c 100644 --- a/src/instrument.c +++ b/src/instrument.c @@ -105,6 +105,7 @@ struct instrument_info { uint8_t num_voices; uint8_t oscs_per_voice; // How many oscs each voice uses. Stored for convenience. uint8_t id; // synth number assigned by client. + uint8_t bus; // which bus this instrument ends up on. uint16_t patch_number; // What patch this instrument is currently set to. Stored for convenience. int16_t bank_number; // Optional top-7-bit word of Program, set by MIDI CC 0 (-1 if not set). uint32_t flags; // Bitmask of special instrument properties (for MIDI Drums translation). @@ -124,8 +125,8 @@ struct instrument_info { }; void instrument_debug(struct instrument_info *instrument) { - fprintf(stderr, "**instrument 0x%lx id %d num_voices %d patch %d oscs %d bank %d flags %" PRIu32 " noteon_delay_ms %d in_sustain %d grab_midi %d\n", - (unsigned long)instrument, instrument->id, instrument->num_voices, instrument->patch_number, instrument->oscs_per_voice, instrument->bank_number, instrument->flags, + fprintf(stderr, "**instrument 0x%lx id %d bus %d num_voices %d patch %d oscs %d bank %d flags %" PRIu32 " noteon_delay_ms %d in_sustain %d grab_midi %d\n", + (unsigned long)instrument, instrument->id, instrument->bus, instrument->num_voices, instrument->patch_number, instrument->oscs_per_voice, instrument->bank_number, instrument->flags, instrument->noteon_delay_ms, instrument->in_sustain, instrument->grab_midi_notes); for (int i = 0; i < instrument->num_voices; ++i) fprintf(stderr, "voice %d amy_voice %d note_per_voice %d pending_release %d\n", @@ -147,7 +148,7 @@ void _instrument_reset_forgotten_pool(struct instrument_info *instrument) { } } -struct instrument_info *instrument_init(int id, int num_voices, uint16_t* amy_voices, uint16_t patch_number, uint16_t oscs_per_voice, uint32_t flags) { +struct instrument_info *instrument_init(int id, int num_voices, uint16_t* amy_voices, uint16_t patch_number, uint16_t oscs_per_voice, uint8_t bus, uint32_t flags) { struct instrument_info *instrument = (struct instrument_info *)malloc_caps(sizeof(struct instrument_info), amy_global.config.ram_caps_synth); instrument->id = id; if (num_voices <= 0 || num_voices > MAX_VOICES_PER_INSTRUMENT) { @@ -159,6 +160,7 @@ struct instrument_info *instrument_init(int id, int num_voices, uint16_t* amy_vo instrument->patch_number = patch_number; instrument->oscs_per_voice = oscs_per_voice; instrument->bank_number = -1; + instrument->bus = bus; instrument->flags = flags; instrument->noteon_delay_ms = 0; instrument->in_sustain = false; @@ -358,12 +360,12 @@ bool instrument_number_exists(int instrument_number, char *tag) { return false; } -void instrument_add_new(int instrument_number, int num_voices, uint16_t *amy_voices, uint16_t patch_number, uint16_t oscs_per_voice, uint32_t flags) { +void instrument_add_new(int instrument_number, int num_voices, uint16_t *amy_voices, uint16_t patch_number, uint16_t oscs_per_voice, uint8_t bus, uint32_t flags) { if (!instrument_number_ok(instrument_number, "add_new")) return; if(instruments[instrument_number]) { instrument_free(instruments[instrument_number]); } - instruments[instrument_number] = instrument_init(instrument_number, num_voices, amy_voices, patch_number, oscs_per_voice, flags); + instruments[instrument_number] = instrument_init(instrument_number, num_voices, amy_voices, patch_number, oscs_per_voice, bus, flags); } void instrument_change_number(int old_instrument_number, int new_instrument_number) { @@ -445,6 +447,18 @@ int instrument_sustain(int instrument_number, bool sustain, uint16_t *amy_voices return num_voices_turned_off; } +uint8_t instrument_get_bus(int instrument_number) { + if (!instrument_number_exists(instrument_number, "get_bus")) return -1; + struct instrument_info *instrument = instruments[instrument_number]; + return instrument->bus; +} + +void instrument_set_bus(int instrument_number, uint8_t bus) { + if (!instrument_number_exists(instrument_number, "set_bus")) return; + struct instrument_info *instrument = instruments[instrument_number]; + instrument->bus = bus; +} + int instrument_get_patch_number(int instrument_number) { if (!instrument_number_exists(instrument_number, "get_patch")) return -1; struct instrument_info *instrument = instruments[instrument_number]; diff --git a/src/parse.c b/src/parse.c index 11c752d2..28c17b10 100644 --- a/src/parse.c +++ b/src/parse.c @@ -341,13 +341,14 @@ int amy_parse_synth_layer_message(char *message, amy_event *e) { } char cmd = message[0]; message++; - if (cmd == 'p') e->pedal = atoi(message); + if (cmd == 'd') e->synth_delay_ms = atoi(message); else if (cmd == 'f') e->synth_flags = atoi(message); - else if (cmd == 'v') e->num_voices = atoi(message); - else if (cmd == 't') e->to_synth = atoi(message); else if (cmd == 'm') e->grab_midi_notes = atoi(message); - else if (cmd == 'd') e->synth_delay_ms = atoi(message); else if (cmd == 'n') e->oscs_per_voice = atoi(message); + else if (cmd == 'p') e->pedal = atoi(message); + else if (cmd == 't') e->to_synth = atoi(message); + else if (cmd == 'v') e->num_voices = atoi(message); + else if (cmd == 'y') e->bus = atoi(message); // 'i1iy1' is the same as 'i1y1'. else if (cmd == 'c') { // MIDI CC mapping ic,,,,,, see https://github.com/shorepine/amy/issues/524 // ic255 clears all MIDI CC mappings for this synth (short form, no extra fields needed). @@ -377,6 +378,17 @@ int amy_parse_synth_layer_message(char *message, amy_event *e) { return skip_chars; } +void parse_mix_levels(char *message, amy_event *e) { + float vals[AMY_NUM_BUSES + 1]; + int num_parsed = parse_list_float(message, vals, AMY_NUM_BUSES + 1, + AMY_UNSET_VALUE(vals[0])); + // Clear unspecified values. + for (int i = num_parsed; i < MAX_ALGO_OPS; ++i) { + AMY_UNSET(vals[i]); + } + +} + // Parser for transfer-layer ('z') prefix. Returns how much of a message to skip uint16_t amy_parse_transfer_layer_message(char *message) { @@ -601,7 +613,7 @@ int amy_parse_message(char * message, int length, amy_event *e) { case 'H': parse_list_uint32_t(arg, e->sequence, 3, 0); break; case 'h': if (AMY_HAS_REVERB) { float reverb_params[4]; - parse_list_float(arg, reverb_params, 4, AMY_UNSET_VALUE(amy_global.reverb.liveness)); + parse_list_float(arg, reverb_params, 4, AMY_UNSET_VALUE(e->reverb_level)); e->reverb_level = reverb_params[0]; e->reverb_liveness = reverb_params[1]; e->reverb_damping = reverb_params[2]; @@ -612,7 +624,7 @@ int amy_parse_message(char * message, int length, amy_event *e) { case 'i': pos += amy_parse_synth_layer_message(arg, e); break; // Skip over second cmd letter, if any, or entire MIDI CC code string. case 'I': e->ratio = atoff(arg); break; case 'j': e->tempo = atof(arg); break; - /* j, J available */ + /* J available */ // chorus.level case 'k': if(AMY_HAS_CHORUS) { float chorus_params[4]; @@ -687,11 +699,12 @@ int amy_parse_message(char * message, int length, amy_event *e) { e->eq_h = eq[2]; } break; + case 'y': e->bus = atoi(arg); break; + case 'Y': parse_mix_levels(arg, e); break; case 'z': { pos += amy_parse_transfer_layer_message(arg); break; } - /* Y,y available */ /* Z used for end of message */ case 'Z': ++pos; diff --git a/src/patches.c b/src/patches.c index 81a63dc8..4710aa8f 100644 --- a/src/patches.c +++ b/src/patches.c @@ -568,29 +568,30 @@ float lin_to_db(float lin) { void set_event_for_global_fx(amy_event *event, struct state *state) { // Always emit all FX fields so saved patches are fully self-describing. + uint8_t bus = 0; // FIXME - identify the actual bus. // Volume event->volume = state->volume; // EQ - event->eq_l = lin_to_db(S2F(state->eq[0])); - event->eq_m = lin_to_db(S2F(state->eq[1])); - event->eq_h = lin_to_db(S2F(state->eq[2])); + event->eq_l = lin_to_db(S2F(state->bus[bus]->eq.eq[0])); + event->eq_m = lin_to_db(S2F(state->bus[bus]->eq.eq[1])); + event->eq_h = lin_to_db(S2F(state->bus[bus]->eq.eq[2])); // Reverb - event->reverb_level = S2F(state->reverb.level); - event->reverb_liveness = state->reverb.liveness; - event->reverb_damping = state->reverb.damping; - event->reverb_xover_hz = state->reverb.xover_hz; + event->reverb_level = S2F(state->bus[bus]->reverb.level); + event->reverb_liveness = state->bus[bus]->reverb.liveness; + event->reverb_damping = state->bus[bus]->reverb.damping; + event->reverb_xover_hz = state->bus[bus]->reverb.xover_hz; // Chorus - event->chorus_level = S2F(state->chorus.level); - event->chorus_max_delay = state->chorus.max_delay; - event->chorus_lfo_freq = state->chorus.lfo_freq; - event->chorus_depth = state->chorus.depth; + event->chorus_level = S2F(state->bus[bus]->chorus.level); + event->chorus_max_delay = state->bus[bus]->chorus.max_delay; + event->chorus_lfo_freq = state->bus[bus]->chorus.lfo_freq; + event->chorus_depth = state->bus[bus]->chorus.depth; // Echo - event->echo_level = S2F(state->echo.level); - event->echo_delay_ms = state->echo.delay_samples * 1000.f / AMY_SAMPLE_RATE; - if (state->echo.max_delay_samples != 65536) - event->echo_max_delay_ms = state->echo.max_delay_samples * 1000.f / AMY_SAMPLE_RATE; - event->echo_feedback = S2F(state->echo.feedback); - event->echo_filter_coef = S2F(state->echo.filter_coef); + event->echo_level = S2F(state->bus[bus]->echo.level); + event->echo_delay_ms = state->bus[bus]->echo.delay_samples * 1000.f / AMY_SAMPLE_RATE; + if (state->bus[bus]->echo.max_delay_samples != 65536) + event->echo_max_delay_ms = state->bus[bus]->echo.max_delay_samples * 1000.f / AMY_SAMPLE_RATE; + event->echo_feedback = S2F(state->bus[bus]->echo.feedback); + event->echo_filter_coef = S2F(state->bus[bus]->echo.filter_coef); } @@ -966,6 +967,8 @@ void patches_event_has_voices(amy_event *e, struct delta **queue) { AMY_UNSET(e->patch_number); int32_t instrument = e->synth; AMY_UNSET(e->synth); + // Set the bus for the instrument, but also for each osc of each voice, below. + if (AMY_IS_SET(e->bus)) instrument_set_bus(instrument, e->bus); // for each voice, send the event to the base osc (+ e->osc if given) for(uint8_t i=0;isynth)) { uint32_t flags = 0; if (AMY_IS_SET(e->synth_flags)) flags = e->synth_flags; - instrument_add_new(e->synth, num_voices, voices, patch_number, oscs_per_voice, flags); + uint8_t bus = 0; + if (AMY_IS_SET(e->bus)) bus = e->bus; + instrument_add_new(e->synth, num_voices, voices, patch_number, oscs_per_voice, bus, flags); } } From d1965221a9b579e7a3b488f0a74b44511deefaf2 Mon Sep 17 00:00:00 2001 From: Dan Ellis Date: Sat, 18 Apr 2026 10:49:24 -0400 Subject: [PATCH 2/8] Fix default ECHO_MAX_DELAY_MS, all tests pass. --- amy/constants.py | 2 +- src/amy.c | 7 ++++--- src/amy.h | 2 +- src/patches.c | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/amy/constants.py b/amy/constants.py index a803c124..9022f111 100644 --- a/amy/constants.py +++ b/amy/constants.py @@ -39,7 +39,7 @@ REVERB_DEFAULT_XOVER_HZ=3000.0 ECHO_DEFAULT_LEVEL=0 ECHO_DEFAULT_DELAY_MS=500. -ECHO_DEFAULT_MAX_DELAY_MS=743. +ECHO_DEFAULT_MAX_DELAY_MS=743.039 ECHO_DEFAULT_FEEDBACK=0 ECHO_DEFAULT_FILTER_COEF=0 AMY_SEQUENCER_PPQ=48 diff --git a/src/amy.c b/src/amy.c index 1e84a270..e38f2970 100644 --- a/src/amy.c +++ b/src/amy.c @@ -234,14 +234,15 @@ void config_echo(uint8_t bus, float level, float delay_ms, float max_delay_ms, f if (AMY_IS_UNSET(filter_coef)) filter_coef = S2F(amy_global.bus[bus]->echo.filter_coef); uint32_t delay_samples = (uint32_t)(delay_ms / 1000.f * AMY_SAMPLE_RATE); - //fprintf(stderr, "config_echo: delay_ms=%.3f max_delay_ms=%.3f delay_samples=%d echo.max_delay_samples=%d\n", delay_ms, max_delay_ms, delay_samples, echo.max_delay_samples); + uint32_t max_delay_samples = enclosing_power_of_2((uint32_t)(max_delay_ms / 1000.f * AMY_SAMPLE_RATE)); + // Remember this value for max_delay_samples even if we don't allocate on this call, so a later UNSET call will pick it up. + amy_global.bus[bus]->echo.max_delay_samples = max_delay_samples; + //fprintf(stderr, "config_echo: bus %d delay=%.3f ms / %d samps max_delay=%.3f ms / %d samps echo.max_delay_samples=%d\n", bus, delay_ms, delay_samples, max_delay_ms, max_delay_samples, amy_global.bus[bus]->echo.max_delay_samples); if (level > 0) { if (amy_global.bus[bus]->echo.echo_delay_lines[0] == NULL) { // Delay line len must be power of 2. - uint32_t max_delay_samples = enclosing_power_of_2((uint32_t)(max_delay_ms / 1000.f * AMY_SAMPLE_RATE)); if (!alloc_echo_delay_lines(bus, max_delay_samples)) return; - amy_global.bus[bus]->echo.max_delay_samples = max_delay_samples; //fprintf(stderr, "config_echo: max_delay_samples=%d\n", max_delay_samples); } // Apply delay. We have to stay 1 sample less than delay line length for FIR EQ delay. diff --git a/src/amy.h b/src/amy.h index 1cd57f56..fd829f17 100644 --- a/src/amy.h +++ b/src/amy.h @@ -168,7 +168,7 @@ extern const uint32_t pcm_wavetable_len; #define ECHO_DEFAULT_LEVEL 0 #define ECHO_DEFAULT_DELAY_MS 500.f // Delay line allocates in 2^n samples at 44k; 743ms is just under 32768 samples. -#define ECHO_DEFAULT_MAX_DELAY_MS 743.f +#define ECHO_DEFAULT_MAX_DELAY_MS 743.039f #define ECHO_DEFAULT_FEEDBACK 0 #define ECHO_DEFAULT_FILTER_COEF 0 diff --git a/src/patches.c b/src/patches.c index 4710aa8f..323e7518 100644 --- a/src/patches.c +++ b/src/patches.c @@ -588,7 +588,7 @@ void set_event_for_global_fx(amy_event *event, struct state *state) { // Echo event->echo_level = S2F(state->bus[bus]->echo.level); event->echo_delay_ms = state->bus[bus]->echo.delay_samples * 1000.f / AMY_SAMPLE_RATE; - if (state->bus[bus]->echo.max_delay_samples != 65536) + if (state->bus[bus]->echo.max_delay_samples != (uint32_t)(ECHO_DEFAULT_MAX_DELAY_MS / 1000.f * AMY_SAMPLE_RATE)) event->echo_max_delay_ms = state->bus[bus]->echo.max_delay_samples * 1000.f / AMY_SAMPLE_RATE; event->echo_feedback = S2F(state->bus[bus]->echo.feedback); event->echo_filter_coef = S2F(state->bus[bus]->echo.filter_coef); From 825e7498f178d6ebf31683570b028f43d8ba4ea5 Mon Sep 17 00:00:00 2001 From: Dan Ellis Date: Sat, 18 Apr 2026 13:03:09 -0400 Subject: [PATCH 3/8] Per-bus effects are applied per bus; volume has one value per bus giving mix-down. --- amy/__init__.py | 4 +- amy/test.py | 16 ++++ src/amy-example.c | 5 +- src/amy.c | 191 +++++++++++++++++++++------------------- src/amy.h | 40 +++++---- src/amy_midi.c | 6 +- src/api.c | 3 +- src/parse.c | 15 +--- src/patches.c | 56 ++++++++---- tests/ref/TestBuses.wav | Bin 0 -> 176172 bytes 10 files changed, 189 insertions(+), 147 deletions(-) create mode 100644 tests/ref/TestBuses.wav diff --git a/amy/__init__.py b/amy/__init__.py index aa10274f..4d663b89 100644 --- a/amy/__init__.py +++ b/amy/__init__.py @@ -138,7 +138,7 @@ def str_of_int(arg): _KW_MAP_LIST = [ # Order matters because patch_string must come last. ('osc', 'vI'), ('wave', 'wI'), ('note', 'nF'), ('vel', 'lF'), ('amp', 'aC'), ('freq', 'fC'), ('duty', 'dC'), ('feedback', 'bF'), ('time', 'tI'), ('reset', 'SI'), ('phase', 'PF'), ('pan', 'QC'), ('client', 'gI'), - ('volume', 'VF'), ('pitch_bend', 'sF'), ('filter_freq', 'FC'), ('resonance', 'RF'), + ('volume', 'VL'), ('pitch_bend', 'sF'), ('filter_freq', 'FC'), ('resonance', 'RF'), ('bp0', 'AL'), ('bp1', 'BL'), ('eg0', 'AL'), ('eg1', 'BL'), # Aliases for bp0 and bp1 ('eg0_type', 'TI'), ('eg1_type', 'XI'), ('debug', 'DI'), ('chained_osc', 'cI'), @@ -150,7 +150,7 @@ def str_of_int(arg): ('to_synth', 'itI'), ('grab_midi_notes', 'imI'), ('synth_delay', 'idI'), ('preset', 'pI'), ('num_partials', 'pI'), # note aliasing ('start_sample', 'zSL'), ('stop_sample', 'zOI'), - ('midi_cc', 'icL'), + ('midi_cc', 'icL'), ('bus', 'yI'), ('patch_string', 'uS'), # patch_string MUST be last because we can't identify when it ends except by end-of-message. ] _KW_PRIORITY = {k: i for i, (k, _) in enumerate(_KW_MAP_LIST)} # Maps each key to its index within _KW_MAP_LIST. diff --git a/amy/test.py b/amy/test.py index 0263437d..36f457f5 100644 --- a/amy/test.py +++ b/amy/test.py @@ -1254,6 +1254,22 @@ def run(self): amy.send(time=750, synth=1, note=48, vel=1) amy.send(time=950, synth=1, vel=0) + +class TestBuses(AmyTest): + """You can assign synths to different buses to get independent FX.""" + + def run(self): + amy.send(time=0, synth=1, num_voices=4, patch=22, bus=0, pan=0.2) # A37 Pizzicato + amy.send(time=0, bus=0, reverb=1, echo=0) + amy.send(time=0, synth=2, num_voices=4, patch=22, bus=1, pan=0.8) + amy.send(time=0, bus=1, reverb=0, echo='1,100,,0.5,0.5') + amy.send(time=0, volume='2,0.5') + amy.send(time=100, synth=1, note=60, vel=5) + amy.send(time=300, synth=2, note=63, vel=5) + amy.send(time=500, synth=1, note=67, vel=5) + amy.send(time=700, synth=2, note=70, vel=5) + + def main(argv): if len(argv) > 1 and argv[1] == 'quiet': quiet = True diff --git a/src/amy-example.c b/src/amy-example.c index 98e756f7..ba23a9af 100644 --- a/src/amy-example.c +++ b/src/amy-example.c @@ -62,9 +62,10 @@ void test_patch_set() { e.wave = SINE; amy_add_event(&e); - // Change the global volume. + // Change the global volume for bus 0. e = amy_default_event(); - e.volume = 2.0f; + int bus = 0; + e.volume[bus] = 2.0f; amy_add_event(&e); } diff --git a/src/amy.c b/src/amy.c index e38f2970..d35dfe30 100644 --- a/src/amy.c +++ b/src/amy.c @@ -148,7 +148,7 @@ void amy_init_lock() { // Global state -struct state amy_global; +global_state_t amy_global; // set of deltas for the fifo to be played struct delta * deltas; // state per osc as multi-channel synthesizer that the scheduler renders into @@ -157,8 +157,8 @@ struct synthinfo ** synth; struct mod_synthinfo ** msynth; // Two mixing blocks, one per core of rendering -SAMPLE *fbl[AMY_MAX_CORES]; -SAMPLE *per_osc_fb[AMY_MAX_CORES]; +SAMPLE *fbl[AMY_MAX_CORES][AMY_NUM_BUSES]; +SAMPLE *per_osc_fb[AMY_MAX_CORES][AMY_NUM_BUSES]; SAMPLE core_max[AMY_MAX_CORES]; // Public pointer to recently-emitted waveform block. @@ -295,26 +295,26 @@ void config_chorus(uint8_t bus, float level, uint16_t max_delay, float lfo_freq, if (AMY_IS_UNSET(lfo_freq)) lfo_freq = amy_global.bus[bus]->chorus.lfo_freq; if (AMY_IS_UNSET(depth)) depth = amy_global.bus[bus]->chorus.depth; //fprintf(stderr, "config_chorus: osc %d level %.3f max_del %d lfo_freq %.3f depth %.3f\n", - // CHORUS_MOD_SOURCE, level, max_delay, lfo_freq, depth); + // CHORUS_MOD_SOURCE + bus, level, max_delay, lfo_freq, depth); if (level > 0) { - ensure_osc_allocd(CHORUS_MOD_SOURCE, NULL); + ensure_osc_allocd(CHORUS_MOD_SOURCE + bus, NULL); // only allocate delay lines if chorus is more than inaudible. if (amy_global.bus[bus]->chorus.chorus_delay_lines[0] == NULL) { alloc_chorus_delay_lines(bus); } // if we're turning on for the first time, start the oscillator. - if (synth[CHORUS_MOD_SOURCE]->status == SYNTH_OFF) { //chorus.level == 0) { + if (synth[CHORUS_MOD_SOURCE + bus]->status == SYNTH_OFF) { //chorus.level == 0) { // Setup chorus oscillator. - synth[CHORUS_MOD_SOURCE]->logfreq_coefs[COEF_CONST] = logfreq_of_freq(lfo_freq); - synth[CHORUS_MOD_SOURCE]->logfreq_coefs[COEF_NOTE] = 0; // Turn off default. - synth[CHORUS_MOD_SOURCE]->logfreq_coefs[COEF_BEND] = 0; // Turn off default. - synth[CHORUS_MOD_SOURCE]->amp_coefs[COEF_CONST] = depth; - synth[CHORUS_MOD_SOURCE]->amp_coefs[COEF_VEL] = 0; // Turn off default. - synth[CHORUS_MOD_SOURCE]->amp_coefs[COEF_EG0] = 0; // Turn off default. - synth[CHORUS_MOD_SOURCE]->wave = TRIANGLE; - osc_note_on(CHORUS_MOD_SOURCE, freq_of_logfreq(synth[CHORUS_MOD_SOURCE]->logfreq_coefs[COEF_CONST])); + synth[CHORUS_MOD_SOURCE + bus]->logfreq_coefs[COEF_CONST] = logfreq_of_freq(lfo_freq); + synth[CHORUS_MOD_SOURCE + bus]->logfreq_coefs[COEF_NOTE] = 0; // Turn off default. + synth[CHORUS_MOD_SOURCE + bus]->logfreq_coefs[COEF_BEND] = 0; // Turn off default. + synth[CHORUS_MOD_SOURCE + bus]->amp_coefs[COEF_CONST] = depth; + synth[CHORUS_MOD_SOURCE + bus]->amp_coefs[COEF_VEL] = 0; // Turn off default. + synth[CHORUS_MOD_SOURCE + bus]->amp_coefs[COEF_EG0] = 0; // Turn off default. + synth[CHORUS_MOD_SOURCE + bus]->wave = TRIANGLE; + osc_note_on(CHORUS_MOD_SOURCE + bus, freq_of_logfreq(synth[CHORUS_MOD_SOURCE + bus]->logfreq_coefs[COEF_CONST])); // Stop us from doing this again. - synth[CHORUS_MOD_SOURCE]->status = SYNTH_IS_MOD_SOURCE; + synth[CHORUS_MOD_SOURCE + bus]->status = SYNTH_IS_MOD_SOURCE; } // apply max_delay. for (int chan=0; chanhpf_state = 0; - config_eq(bus, F2S(1.0f), F2S(1.0f), F2S(1.0f)); filters_init(bus); reset_parametric(bus); @@ -430,7 +427,8 @@ int8_t global_init(amy_config_t c) { amy_global.i2s_is_in_background = 0; amy_global.delta_queue = NULL; amy_global.delta_qsize = 0; - amy_global.volume = 1.0f; + for (int bus = 0; bus < AMY_NUM_BUSES; ++bus) + amy_global.volume[bus] = 1.0f; amy_global.pitch_bend = 0; amy_global.latency_ms = 0; amy_global.tempo = 108.0; @@ -442,6 +440,8 @@ int8_t global_init(amy_config_t c) { amy_global.transfer_file_handle = 0; amy_global.transfer_filename[0] = '\0'; amy_global.debug_flag = 0; + amy_global.highest_bus = 0; + amy_global.hpf_state = 0; amy_global.sequencer_tick_count = 0; amy_global.next_amy_tick_us = 0; amy_global.us_per_tick = 0; @@ -534,7 +534,7 @@ float map_01_to_60dBf(float log) { #define EVENT_TO_DELTA_F(FIELD, FLAG) if(AMY_IS_SET(e->FIELD)) { d.param=FLAG; d.data.f = e->FIELD; add_delta_to_queue(&d, queue); } #define EVENT_TO_DELTA_I(FIELD, FLAG) if(AMY_IS_SET(e->FIELD)) { d.param=FLAG; d.data.i = e->FIELD; add_delta_to_queue(&d, queue); } -#define EVENT_TO_DELTA_WITH_BASEOSC(FIELD, FLAG) if(AMY_IS_SET(e->FIELD)) { d.param=FLAG; d.data.i = e->FIELD + base_osc; if (FLAG != RESET_OSC && d.data.i < (uint32_t)AMY_OSCS + 1) ensure_osc_allocd(d.data.i, NULL); add_delta_to_queue(&d, queue);} +#define EVENT_TO_DELTA_WITH_BASEOSC(FIELD, FLAG) if(AMY_IS_SET(e->FIELD)) { d.param=FLAG; d.data.i = e->FIELD + base_osc; if (FLAG != RESET_OSC && d.data.i < (uint32_t)AMY_OSCS + AMY_NUM_BUSES) ensure_osc_allocd(d.data.i, NULL); add_delta_to_queue(&d, queue);} #define EVENT_TO_DELTA_LOG(FIELD, FLAG) if(AMY_IS_SET(e->FIELD)) { d.param=FLAG; d.data.f = log2f(e->FIELD); add_delta_to_queue(&d, queue);} #define EVENT_TO_DELTA_COEFS(FIELD, FLAG) \ for (int i = 0; i < NUM_COMBO_COEFS; ++i) \ @@ -575,6 +575,7 @@ void amy_event_to_deltas_queue(amy_event *e, uint16_t base_osc, struct delta **q // Store the target bus in d.osc. d.osc = AMY_IS_SET(e->bus) ? e->bus : AMY_DEFAULT_BUS; bus_directed_command = true; + if (d.osc > amy_global.highest_bus) amy_global.highest_bus = d.osc; } else { // d.osc refers to an osc // First, adapt the osc in this event with base_osc offsets for voices @@ -608,7 +609,11 @@ void amy_event_to_deltas_queue(amy_event *e, uint16_t base_osc, struct delta **q } // Everything else only added to queue if set - if (!bus_directed_command) EVENT_TO_DELTA_I(bus, BUS) + if (!bus_directed_command) { + EVENT_TO_DELTA_I(bus, BUS) + if (AMY_IS_SET(e->bus) && e->bus > amy_global.highest_bus) + amy_global.highest_bus = e->bus; + } EVENT_TO_DELTA_I(wave, WAVE) EVENT_TO_DELTA_I(preset, PRESET) EVENT_TO_DELTA_F(midi_note, MIDI_NOTE) @@ -619,7 +624,6 @@ void amy_event_to_deltas_queue(amy_event *e, uint16_t base_osc, struct delta **q EVENT_TO_DELTA_COEFS(pan_coefs, PAN) EVENT_TO_DELTA_F(feedback, FEEDBACK) EVENT_TO_DELTA_F(trigger_phase, PHASE) - EVENT_TO_DELTA_F(volume, VOLUME) EVENT_TO_DELTA_F(pitch_bend, PITCH_BEND) EVENT_TO_DELTA_I(latency_ms, LATENCY) EVENT_TO_DELTA_F(tempo, TEMPO) @@ -651,6 +655,9 @@ void amy_event_to_deltas_queue(amy_event *e, uint16_t base_osc, struct delta **q EVENT_TO_DELTA_I(eg_type[0], EG0_TYPE) EVENT_TO_DELTA_I(eg_type[1], EG1_TYPE) + for (int bus = 0; bus < AMY_NUM_BUSES; ++bus) + EVENT_TO_DELTA_F(volume[bus], VOLUME_BASE + bus) + bool algo_ops_set = false; for (int i = 0; i < MAX_ALGO_OPS; ++i) { if(AMY_IS_SET(e->algo_source[i])) { @@ -830,11 +837,12 @@ void amy_reset_oscs() { // Put the noise generator into a known state. srand48(517730); // We reset oscs by freeing them. - // Include chorus osc (osc=AMY_OSCS) - for(uint16_t i=0;i1) { // print out all the osc data //printf("global: filter %f resonance %f volume %f bend %f status %d\n", amy_global.filter_freq, amy_global.resonance, amy_global.volume, amy_global.pitch_bend, amy_global.status); - fprintf(stderr,"global: volume %f bend %f bus 0 eq: %f %f %f \n", amy_global.volume, amy_global.pitch_bend, S2F(amy_global.bus[0]->eq.eq[0]), S2F(amy_global.bus[0]->eq.eq[1]), S2F(amy_global.bus[0]->eq.eq[2])); + fprintf(stderr,"global: volume %.3f %.3f %.3f %.3f bend %f bus 0 eq: %f %f %f \n", amy_global.volume[0], amy_global.volume[1], amy_global.volume[2], amy_global.volume[3], amy_global.pitch_bend, S2F(amy_global.bus[0]->eq.eq[0]), S2F(amy_global.bus[0]->eq.eq[1]), S2F(amy_global.bus[0]->eq.eq[2])); for(uint16_t i=0;i<10 /* AMY_OSCS */;i++) { print_osc_debug(i, (type > 3) /* show_eg */); } @@ -1091,12 +1097,14 @@ void oscs_deinit() { dealloc_reverb_delay_lines(bus); } for(int core = 0; core < AMY_CORES; ++core) { - free(fbl[core]); - free(per_osc_fb[core]); + for (int bus = 0; bus < AMY_NUM_BUSES; ++bus) { + free(fbl[core][bus]); + free(per_osc_fb[core][bus]); + } } deltas_pool_free(); // Include chorus osc (osc=AMY_OSCS) - for (int i = 0; i < AMY_OSCS + 1; ++i) free_osc(i); + for (int i = 0; i < AMY_OSCS + AMY_NUM_BUSES; ++i) free_osc(i); free(amy_external_in_block); free(amy_in_block); free(output_block_1); @@ -1285,7 +1293,7 @@ void play_delta(struct delta *d) { // If we got here, it's a full reset of patches. patches_reset(); } - if(d->data.i < (uint32_t)AMY_OSCS + 1) { + if(d->data.i < (uint32_t)AMY_OSCS + AMY_NUM_BUSES) { reset_osc(d->data.i); } } @@ -1321,7 +1329,7 @@ void play_delta(struct delta *d) { } // for global changes, just make the change, no need to update the per-osc synth uint8_t bus = d->osc; // We assume d.osc was hijacked in amy_event_to_deltas_queue - if(d->param == VOLUME) amy_global.volume = d->data.f; + if(d->param >= VOLUME_BASE && d->param < (VOLUME_BASE + AMY_NUM_BUSES)) amy_global.volume[d->param - VOLUME_BASE] = d->data.f; if(d->param == PITCH_BEND) amy_global.pitch_bend = d->data.f; if(d->param == LATENCY) amy_global.latency_ms = d->data.i; if(d->param == TEMPO) { amy_global.tempo = d->data.f; sequencer_recompute(); } @@ -1655,7 +1663,7 @@ SAMPLE render_osc_wave(uint16_t osc, uint8_t core, SAMPLE* buf) { if (synth[osc]->wave != SILENT) { // apply filter to osc if set if (synth[osc]->filter_type != FILTER_NONE) { - max_val = filter_process(per_osc_fb[core], osc, max_val); + max_val = filter_process(buf, osc, max_val); // Maybe clear filter state here if we've finshed this osc. if (synth[osc]->status != SYNTH_AUDIBLE) { reset_filter(osc); // (f) @@ -1675,7 +1683,7 @@ SAMPLE render_osc_wave(uint16_t osc, uint8_t core, SAMPLE* buf) { max_val = render_envelope(buf, osc); // apply filter to osc if set if (synth[osc]->filter_type != FILTER_NONE) { - max_val = filter_process(per_osc_fb[core], osc, max_val); + max_val = filter_process(buf, osc, max_val); // Maybe clear filter state here if we've finshed this osc. if (synth[osc]->status != SYNTH_AUDIBLE) { reset_filter(osc); // (f) @@ -1749,60 +1757,49 @@ SAMPLE render_osc_wave(uint16_t osc, uint8_t core, SAMPLE* buf) { void amy_render(uint16_t start, uint16_t end, uint8_t core) { AMY_PROFILE_START(AMY_RENDER) - uint8_t bus = 0; // FIXME: Render per bus - for(uint16_t i=0;istatus == SYNTH_AUDIBLE) { // skip oscs that are silent or mod sources from playback - bzero(per_osc_fb[core], AMY_BLOCK_SIZE * sizeof(SAMPLE)); - SAMPLE max_val = render_osc_wave(osc, core, per_osc_fb[core]); - // check it's not off, just in case. todo, why do i care? - // apply filter to osc if set - if(//synth[osc]->status == SYNTH_AUDIBLE && // (e) - synth[osc]->filter_type != FILTER_NONE) { - //fprintf(stderr, "time %.3f osc %d filter_type %d\n", - // (float)amy_global.total_blocks*AMY_BLOCK_SIZE / AMY_SAMPLE_RATE, - // osc, synth[osc]->filter_type); - //max_val = filter_process(per_osc_fb[core], osc, max_val); - //// Maybe clear filter state here if we've finshed this osc. - //if (synth[osc]->status != SYNTH_AUDIBLE) { - // reset_filter(osc); // (f) - //} - } + uint8_t bus = synth[osc]->bus; + bzero(per_osc_fb[core][bus], AMY_BLOCK_SIZE * sizeof(SAMPLE)); + SAMPLE max_val = render_osc_wave(osc, core, per_osc_fb[core][bus]); if (synth[osc]->status != SYNTH_AUDIBLE) { reset_modosc(msynth[osc]); // (g) This makes a difference, but not clicks reset_osc_state(synth[osc]); } uint8_t handled = 0; if(amy_global.config.amy_external_render_hook != NULL) { - handled = amy_global.config.amy_external_render_hook(osc, per_osc_fb[core], AMY_BLOCK_SIZE); + handled = amy_global.config.amy_external_render_hook(osc, per_osc_fb[core][bus], AMY_BLOCK_SIZE); } else { #ifdef __EMSCRIPTEN__ // TODO -- pass the buffer to a JS shim using the new bytes support, we could use this to visualize CV output #endif } // only mix the audio in if the external hook did not handle it - if(!handled) mix_with_pan(fbl[core], per_osc_fb[core], msynth[osc]->last_pan, msynth[osc]->pan); + if(!handled) mix_with_pan(fbl[core][bus], per_osc_fb[core][bus], msynth[osc]->last_pan, msynth[osc]->pan); if (max_val > max_max) max_max = max_val; } // end if audible } core_max[core] = max_max; if(AMY_HAS_CHORUS && core == 0) { - // **FIXME: chorus is per-bus - ensure_osc_allocd(CHORUS_MOD_SOURCE, NULL); - hold_and_modify(CHORUS_MOD_SOURCE); - if(amy_global.bus[bus]->chorus.level!=0) { - bzero(amy_global.bus[bus]->chorus.delay_mod, AMY_BLOCK_SIZE * sizeof(SAMPLE)); - render_osc_wave(CHORUS_MOD_SOURCE, 0 /* core */, amy_global.bus[bus]->chorus.delay_mod); + for(int bus = 0; bus <= amy_global.highest_bus; ++bus) { + ensure_osc_allocd(CHORUS_MOD_SOURCE + bus, NULL); + hold_and_modify(CHORUS_MOD_SOURCE + bus); + if(amy_global.bus[bus]->chorus.level!=0) { + bzero(amy_global.bus[bus]->chorus.delay_mod, AMY_BLOCK_SIZE * sizeof(SAMPLE)); + render_osc_wave(CHORUS_MOD_SOURCE + bus, 0 /* core */, amy_global.bus[bus]->chorus.delay_mod); + } } } if (amy_global.debug_flag) { amy_global.debug_flag = 0; // Only do this once each time debug_flag is set. - SAMPLE smax = scan_max(fbl[core], AMY_BLOCK_SIZE); - fprintf(stderr, "time %" PRIu32 " core %d max_max=%.3f post-eq max=%.3f\n", amy_global.total_blocks*AMY_BLOCK_SIZE, core, S2F(max_max), S2F(smax)); + SAMPLE smax = scan_max(fbl[core][0 /* bus */], AMY_BLOCK_SIZE); + fprintf(stderr, "time %" PRIu32 " core %d bus 0 max_max=%.3f post-eq max=%.3f\n", amy_global.total_blocks*AMY_BLOCK_SIZE, core, S2F(max_max), S2F(smax)); } AMY_PROFILE_STOP(AMY_RENDER) @@ -1897,7 +1894,6 @@ void amy_process_event(amy_event *e) { int16_t * amy_fill_buffer() { AMY_PROFILE_START(AMY_FILL_BUFFER) - uint8_t bus = 0; // FIXME: handle buses. #ifdef __EMSCRIPTEN__ // post a message to the main thread of the audioworklet (amy main, in this case) that a block has been finished //emscripten_audio_worklet_post_function_v(0, amy_block_processed); @@ -1912,55 +1908,64 @@ int16_t * amy_fill_buffer() { // mix results from both cores. //SAMPLE max_val = core_max[0]; #ifdef AMY_DUALCORE - for (int16_t i=0; i < AMY_BLOCK_SIZE * AMY_NCHANS; ++i) fbl[0][i] += fbl[1][i]; + for (int bus = 0; bus <= amy_global.highest_bus; ++bus) + for (int16_t i=0; i < AMY_BLOCK_SIZE * AMY_NCHANS; ++i) fbl[0][bus][i] += fbl[1][bus][i]; // if (core_max[1] > max_val) max_val = core_max[1]; #endif // Apply global processing only if there is some signal. //if (max_val > 0) { // NO - see #629 // apply the eq filters if there is some signal and EQ is non-default. + for (int bus=0; bus <= amy_global.highest_bus; ++bus) { + // Per-bus EQ if (amy_global.bus[bus]->eq.eq[0] != F2S(1.0f) || amy_global.bus[bus]->eq.eq[1] != F2S(1.0f) || amy_global.bus[bus]->eq.eq[2] != F2S(1.0f)) { - parametric_eq_process(bus, fbl[0]); + parametric_eq_process(bus, fbl[0][bus]); } if(AMY_HAS_CHORUS) { - // apply chorus. + // apply per-bus chorus. if(amy_global.bus[bus]->chorus.level > 0 && amy_global.bus[bus]->chorus.chorus_delay_lines[0] != NULL) { // apply time-varying delays to both chans. // delay_mod_val, the modulated delay amount, is set up before calling render_*. SAMPLE scale = F2S(1.0f); for (int16_t c=0; c < AMY_NCHANS; ++c) { - apply_variable_delay(fbl[0] + c * AMY_BLOCK_SIZE, amy_global.bus[bus]->chorus.chorus_delay_lines[c], + apply_variable_delay(fbl[0][bus] + c * AMY_BLOCK_SIZE, amy_global.bus[bus]->chorus.chorus_delay_lines[c], amy_global.bus[bus]->chorus.delay_mod, scale, amy_global.bus[bus]->chorus.level, 0); // flip delay direction for alternating channels. scale = -scale; } } } - //} - if (AMY_HAS_ECHO) { - // Apply echo. - if (amy_global.bus[bus]->echo.level > 0 && amy_global.bus[bus]->echo.echo_delay_lines[0] != NULL ) { - for (int16_t c=0; c < AMY_NCHANS; ++c) { - apply_fixed_delay(fbl[0] + c * AMY_BLOCK_SIZE, amy_global.bus[bus]->echo.echo_delay_lines[c], amy_global.bus[bus]->echo.delay_samples, amy_global.bus[bus]->echo.level, amy_global.bus[bus]->echo.feedback, amy_global.bus[bus]->echo.filter_coef); + //} + if (AMY_HAS_ECHO) { + // Apply per-bus echo. + if (amy_global.bus[bus]->echo.level > 0 && amy_global.bus[bus]->echo.echo_delay_lines[0] != NULL ) { + for (int16_t c=0; c < AMY_NCHANS; ++c) { + apply_fixed_delay(fbl[0][bus] + c * AMY_BLOCK_SIZE, amy_global.bus[bus]->echo.echo_delay_lines[c], amy_global.bus[bus]->echo.delay_samples, amy_global.bus[bus]->echo.level, amy_global.bus[bus]->echo.feedback, amy_global.bus[bus]->echo.filter_coef); + } } } - } - if(AMY_HAS_REVERB) { - // apply reverb. - if(amy_global.bus[bus]->reverb.level > 0) { - if(AMY_NCHANS == 1) { - stereo_reverb(amy_global.bus[bus]->reverb.rev, fbl[0], NULL, fbl[0], NULL, AMY_BLOCK_SIZE, amy_global.bus[bus]->reverb.level); - } else { - stereo_reverb(amy_global.bus[bus]->reverb.rev, fbl[0], fbl[0] + AMY_BLOCK_SIZE, fbl[0], fbl[0] + AMY_BLOCK_SIZE, AMY_BLOCK_SIZE, amy_global.bus[bus]->reverb.level); + if(AMY_HAS_REVERB) { + // apply per-bus reverb. + if(amy_global.bus[bus]->reverb.level > 0) { + if(AMY_NCHANS == 1) { + stereo_reverb(amy_global.bus[bus]->reverb.rev, fbl[0][bus], NULL, fbl[0][bus], NULL, AMY_BLOCK_SIZE, amy_global.bus[bus]->reverb.level); + } else { + stereo_reverb(amy_global.bus[bus]->reverb.rev, fbl[0][bus], fbl[0][bus] + AMY_BLOCK_SIZE, fbl[0][bus], fbl[0][bus] + AMY_BLOCK_SIZE, AMY_BLOCK_SIZE, amy_global.bus[bus]->reverb.level); + } } } - } + } // end of per-bus FX // global volume is supposed to max out at 10, so scale by 0.1. - SAMPLE volume_scale = MUL4_SS(F2S(0.1f), F2S(amy_global.volume)); + SAMPLE volume_scale[AMY_NUM_BUSES]; + for (int bus = 0; bus <= amy_global.highest_bus; ++bus) + volume_scale[bus] = MUL4_SS(F2S(0.1f), F2S(amy_global.volume[bus])); for(int16_t i=0; i < AMY_BLOCK_SIZE; ++i) { for (int16_t c=0; c < AMY_NCHANS; ++c) { - // Convert the mixed sample into the int16 range, applying overall gain. - SAMPLE fsample = MUL8_SS(volume_scale, fbl[0][i + c * AMY_BLOCK_SIZE]); + SAMPLE fsample = 0; + for (int bus = 0; bus <= amy_global.highest_bus; ++bus) { + // Convert the mixed sample into the int16 range, applying overall gain. + fsample += MUL8_SS(volume_scale[bus], fbl[0][bus][i + c * AMY_BLOCK_SIZE]); + } // One-pole high-pass filter to remove large low-frequency excursions from // some FM patches. b = [1 -1]; a = [1 -0.995] diff --git a/src/amy.h b/src/amy.h index fd829f17..cdec3bfd 100644 --- a/src/amy.h +++ b/src/amy.h @@ -150,7 +150,7 @@ extern const uint32_t pcm_wavetable_len; #define CHORUS_DEFAULT_MOD_DEPTH 0.5 #define CHORUS_DEFAULT_LEVEL 0 #define CHORUS_DEFAULT_MAX_DELAY 320 -// Chorus gets is modulator from a special osc one beyond the normal range. +// Per-bus choruses get modulators from a special osc one beyond the normal range. #define CHORUS_MOD_SOURCE AMY_OSCS // center frequencies for the EQ @@ -330,14 +330,16 @@ enum params{ FEEDBACK=DUTY + NUM_COMBO_COEFS, // 21 FREQ, // 22..30 VELOCITY=FREQ + NUM_COMBO_COEFS, // 31 - PHASE, DETUNE, VOLUME, PITCH_BEND, // 32, 33, 34, 35 - PAN, // 36..44 - FILTER_FREQ=PAN + NUM_COMBO_COEFS, // 45..53 - RATIO=FILTER_FREQ + NUM_COMBO_COEFS, // 54 - RESONANCE, PORTAMENTO, CHAINED_OSC, // 55, 56, 57 - MOD_SOURCE, FILTER_TYPE, // 58, 59 - EQ_L, EQ_M, EQ_H, // 60, 61, 62 - ALGORITHM, LATENCY, TEMPO, // 63, 64, 65 + PHASE, DETUNE, PITCH_BEND, // 32, 33, 34 + PAN, // 35..43 + FILTER_FREQ=PAN + NUM_COMBO_COEFS, // 44..52 + RATIO=FILTER_FREQ + NUM_COMBO_COEFS, // 53 + RESONANCE, PORTAMENTO, CHAINED_OSC, // 54, 55, 56 + MOD_SOURCE, FILTER_TYPE, // 57, 58 + EQ_L, EQ_M, EQ_H, // 59, 60, 61 + ALGORITHM, LATENCY, TEMPO, // 62, 63, 64 + VOLUME_BASE, // 65..68 + VOLUME_END=VOLUME_BASE + AMY_NUM_BUSES, // 69 ALGO_SOURCE_START=100, // 100..105 ALGO_SOURCE_END=100+MAX_ALGO_OPS, // 106 BP_START=ALGO_SOURCE_END + 1, // 107..202 @@ -488,7 +490,7 @@ typedef struct amy_event { float feedback; float velocity; float trigger_phase; - float volume; // event_only + float volume[AMY_NUM_BUSES]; // event_only float pitch_bend; // event_only float tempo; // event_only uint16_t latency_ms; // event_only @@ -773,21 +775,20 @@ typedef struct echo_config { // Per-bus parameters -struct bus_state { +typedef struct bus_state { // State of fixed dc-blocking HPF - SAMPLE hpf_state; eq_state_t eq; reverb_state_t reverb; chorus_config_t chorus; echo_config_t echo; -}; +} bus_state_t; // global synth state -struct state { +typedef struct global_state { amy_config_t config; uint8_t running; uint8_t i2s_is_in_background; // Flag not to handle I2S in amy_update. - float volume; + float volume[AMY_NUM_BUSES]; // Volume controls mix of buses into final output. float pitch_bend; // Legacy global pitch bend, will be subsumed per-synth (instrument). uint16_t delta_qsize; @@ -797,6 +798,9 @@ struct state { uint32_t total_blocks; float time; uint8_t debug_flag; + // How many buses do we actually have to process? + uint8_t highest_bus; + SAMPLE hpf_state; // Transfer uint8_t transfer_flag; @@ -813,11 +817,11 @@ struct state { sequence_entry_ll_t * sequence_entry_ll_start; // Buses - struct bus_state *bus[AMY_NUM_BUSES]; + bus_state_t *bus[AMY_NUM_BUSES]; // Final output mix float bus_gain[AMY_NUM_BUSES]; -}; +} global_state_t; // custom oscillator @@ -836,7 +840,7 @@ struct custom_oscillator { // Shared structures extern struct synthinfo** synth; extern struct mod_synthinfo** msynth; -extern struct state amy_global; +extern global_state_t amy_global; extern output_sample_type * amy_out_block; extern output_sample_type * amy_in_block; diff --git a/src/amy_midi.c b/src/amy_midi.c index 18b13026..2b894a5e 100644 --- a/src/amy_midi.c +++ b/src/amy_midi.c @@ -106,7 +106,11 @@ void amy_received_control_change(uint8_t channel, uint8_t control, uint8_t value //e.volume = (float)value/12.7; // Max volume is 10. //e.note_source = NOTE_SOURCE_MIDI; //amy_add_event(&e); - amy_global.volume = (float)value/12.7; // Max volume is 10. + if (instrument_number_exists(channel, NULL)) { + int bus = instrument_get_bus(channel); + if (bus >= 0 && bus < AMY_NUM_BUSES) + amy_global.volume[bus] = (float)value/12.7; // Max volume is 10. + } } } diff --git a/src/api.c b/src/api.c index bc6b503b..0d8baa2c 100644 --- a/src/api.c +++ b/src/api.c @@ -118,7 +118,8 @@ void amy_clear_event(amy_event *e) { AMY_UNSET(e->feedback); AMY_UNSET(e->velocity); AMY_UNSET(e->midi_note); - AMY_UNSET(e->volume); + for (int bus = 0; bus < AMY_NUM_BUSES; ++bus) + AMY_UNSET(e->volume[bus]); AMY_UNSET(e->pitch_bend); AMY_UNSET(e->tempo); AMY_UNSET(e->latency_ms); diff --git a/src/parse.c b/src/parse.c index 28c17b10..b538fe07 100644 --- a/src/parse.c +++ b/src/parse.c @@ -378,17 +378,6 @@ int amy_parse_synth_layer_message(char *message, amy_event *e) { return skip_chars; } -void parse_mix_levels(char *message, amy_event *e) { - float vals[AMY_NUM_BUSES + 1]; - int num_parsed = parse_list_float(message, vals, AMY_NUM_BUSES + 1, - AMY_UNSET_VALUE(vals[0])); - // Clear unspecified values. - for (int i = num_parsed; i < MAX_ALGO_OPS; ++i) { - AMY_UNSET(vals[i]); - } - -} - // Parser for transfer-layer ('z') prefix. Returns how much of a message to skip uint16_t amy_parse_transfer_layer_message(char *message) { @@ -687,7 +676,7 @@ int amy_parse_message(char * message, int length, amy_event *e) { case 'u': patches_store_patch(e, arg); pos = strlen(message) - 1; break; // patches_store_patch processes the patch as all the rest of the message and maybe sets patch. /* U used by Alles for sync */ case 'v': e->osc=((atoi(arg)) % (AMY_OSCS+1)); break; // allow osc wraparound - case 'V': e->volume = atoff(arg); break; + case 'V': parse_list_float(arg, e->volume, AMY_NUM_BUSES, AMY_UNSET_VALUE(e->volume[0])); break; case 'w': e->wave=atoi(arg); break; /* W used by Tulip for CV, external_channel */ case 'X': e->eg_type[1] = atoi(arg); break; @@ -700,7 +689,7 @@ int amy_parse_message(char * message, int length, amy_event *e) { } break; case 'y': e->bus = atoi(arg); break; - case 'Y': parse_mix_levels(arg, e); break; + /* Y still available */ case 'z': { pos += amy_parse_transfer_layer_message(arg); break; diff --git a/src/patches.c b/src/patches.c index 323e7518..63c4fb04 100644 --- a/src/patches.c +++ b/src/patches.c @@ -193,6 +193,23 @@ void add_deltas_to_queue_with_baseosc(struct delta *d, int base_osc, struct delt } \ } \ } +#define _EPRINT_F_SEQ(FIELD, NAME, LEN, WIRECODE) { \ + int last_set = -1; \ + for (int i = 0; i < LEN; ++i) { \ + if (AMY_IS_SET(e->FIELD[i])) last_set = i; \ + } \ + if (last_set >= 0) { \ + sprintf(s, "%s", wirecode ? WIRECODE : " " NAME ": "); \ + s += strlen(s); \ + for (int i = 0; i <= last_set; ++i) { \ + if (i > 0) { sprintf(s, ","); s += strlen(s); } \ + if (AMY_IS_SET(e->FIELD[i])) { \ + sprintf(s, "%.3f", e->FIELD[i]); \ + s += strlen(s); \ + } \ + } \ + } \ +} #define _EPRINT_BP(TFIELD, VFIELD, NAME, WIRECODE) { \ int last_set = -1; \ @@ -265,7 +282,7 @@ int sprint_event(amy_event *e, char *s, size_t len, bool wirecode) { _EPRINT_COEF(pan_coefs, "pan_coefs", "Q"); _EPRINT_F(feedback, "feedback", "b"); _EPRINT_F(trigger_phase, "phase", "P"); - _EPRINT_F(volume, "volume", "V"); // NOT osc-dep + _EPRINT_F_SEQ(volume, "volume", AMY_NUM_BUSES, "V"); // NOT osc-dep _EPRINT_F(pitch_bend, "pitch_bend", "s"); // NOT osc-dep _EPRINT_F(tempo, "tempo", "j"); // NOT osc-dep _EPRINT_I(latency_ms, "latency_ms", "N"); // NOT osc-dep @@ -351,7 +368,6 @@ struct delta *deltas_to_event(struct delta *queue, struct amy_event *event) { _CASE_F(midi_note, MIDI_NOTE) _CASE_F(feedback, FEEDBACK) _CASE_F(trigger_phase, PHASE) - _CASE_F(volume, VOLUME) _CASE_F(pitch_bend, PITCH_BEND) _CASE_I(latency_ms, LATENCY) _CASE_F(tempo, TEMPO) @@ -389,6 +405,10 @@ struct delta *deltas_to_event(struct delta *queue, struct amy_event *event) { _TEST_FREQ_COEFS(filter_freq_coefs, FILTER_FREQ) _TEST_COEFS(duty_coefs, DUTY) _TEST_COEFS(pan_coefs, PAN) + for (int bus = 0; bus < AMY_NUM_BUSES; ++bus) { + if ((int)queue->param == (int)VOLUME_BASE + bus) + event->volume[bus] = queue->data.f; + } for (int i = 0; i < MAX_ALGO_OPS; ++i) { if ((int)queue->param == (int)ALGO_SOURCE_START + i) event->algo_source[i] = queue->data.i; @@ -566,11 +586,12 @@ float lin_to_db(float lin) { return 20.0f * log10f(lin); } -void set_event_for_global_fx(amy_event *event, struct state *state) { +void set_event_for_bus_fx(amy_event *event, uint8_t bus, global_state_t *state) { // Always emit all FX fields so saved patches are fully self-describing. - uint8_t bus = 0; // FIXME - identify the actual bus. - // Volume - event->volume = state->volume; + // Set the bus + event->bus = bus; + // Volume for this bus alone. + event->volume[bus] = state->volume[bus]; // EQ event->eq_l = lin_to_db(S2F(state->bus[bus]->eq.eq[0])); event->eq_m = lin_to_db(S2F(state->bus[bus]->eq.eq[1])); @@ -595,24 +616,24 @@ void set_event_for_global_fx(amy_event *event, struct state *state) { } -void *yield_synth_events(uint8_t synth, struct amy_event *event, bool include_fx, void *state) { +void *yield_synth_events(uint8_t instr_num, struct amy_event *event, bool include_fx, void *state) { // Return a sequence of events defining a synth. // state = NULL on first call and it returns state to be passed on next call. Returns NULL when event sequence is finished. // Find oscs for synth. uint16_t voices[MAX_VOICES_PER_INSTRUMENT]; - int num_voices = instrument_get_num_voices(synth, voices); + int num_voices = instrument_get_num_voices(instr_num, voices); if (num_voices < 1) { - fprintf(stderr, "yield_synth_events: synth %" PRId32" has no voices.\n", (int32_t)synth); + fprintf(stderr, "yield_synth_events: synth %" PRId32" has no voices.\n", (int32_t)instr_num); return NULL; // instrument not allocated. } - uint32_t flags = instrument_get_flags(synth); + uint32_t flags = instrument_get_flags(instr_num); uint16_t voice = voices[0]; uint16_t base_osc = voice_to_base_osc[voice]; int num_oscs = 0; while(osc_to_voice[base_osc + num_oscs] == voice) ++num_oscs; // The "state" indicates which osc within the voice we're going to report for. int state_val = (intptr_t)state; - //fprintf(stderr, "yield_synth_events(%d) voice=%d num_oscs=%d state_val=%d\n", synth, voice, num_oscs, (int)state_val); + //fprintf(stderr, "yield_synth_events(%d) voice=%d num_oscs=%d state_val=%d\n", instr_num, voice, num_oscs, (int)state_val); amy_clear_event(event); int first_osc_state_val = 0; int last_osc_state_val = num_oscs; @@ -629,8 +650,9 @@ void *yield_synth_events(uint8_t synth, struct amy_event *event, bool include_fx // base_osc, event->osc, state_val, first_osc_state_val, last_osc_state_val); set_event_for_osc(base_osc, event->osc, event); } else if (include_fx && (state_val == last_osc_state_val)) { - // optional final event contains the global settings (volume, eq, chorus, echo, reverb). - set_event_for_global_fx(event, &amy_global); + // optional final event contains the global/bus settings (volume, eq, chorus, echo, reverb). + // The bus is determined by the bus value on the base_osc, probably OK. + set_event_for_bus_fx(event, synth[base_osc]->bus, &amy_global); } ++state_val; if (state_val == last_osc_state_val + (include_fx ? 1 : 0)) state_val = 0; // Indicate this is the final event. @@ -638,14 +660,14 @@ void *yield_synth_events(uint8_t synth, struct amy_event *event, bool include_fx } #define STATE_START_OF_MIDI 1024 -void *yield_synth_commands(uint8_t synth, char *s, size_t len, bool include_fx, void *state) { +void *yield_synth_commands(uint8_t instr_num, char *s, size_t len, bool include_fx, void *state) { // Generator to return multiple wirecode strings to reconfigure a synth. int state_val = (intptr_t)state; - //fprintf(stderr, "yield_synth_commands: synth %d state %d\n", synth, state_val); + //fprintf(stderr, "yield_synth_commands: synth %d state %d\n", instr_num, state_val); s[0] = '\0'; // By default, return an empty string. if (state_val < STATE_START_OF_MIDI) { amy_event event = amy_default_event(); - state_val = (intptr_t)yield_synth_events(synth, &event, include_fx, (void *)(intptr_t)state_val); + state_val = (intptr_t)yield_synth_events(instr_num, &event, include_fx, (void *)(intptr_t)state_val); sprint_event(&event, s, len, /* wirecode= */ true); if (state_val == 0) { // Push the state machine on to the MIDI codes @@ -655,7 +677,7 @@ void *yield_synth_commands(uint8_t synth, char *s, size_t len, bool include_fx, // MIDI CC part bool found = false; for (int next_midi_code = state_val - STATE_START_OF_MIDI; next_midi_code < 128; ++next_midi_code) { - if (midi_fetch_control_code_command(synth, next_midi_code, s, len) == true) { + if (midi_fetch_control_code_command(instr_num, next_midi_code, s, len) == true) { state_val = STATE_START_OF_MIDI + next_midi_code + 1; found = true; break; diff --git a/tests/ref/TestBuses.wav b/tests/ref/TestBuses.wav new file mode 100644 index 0000000000000000000000000000000000000000..b823ee01cff5beeecd918405178b9309d4867580 GIT binary patch literal 176172 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

w)}OLwbWNF%_KNb@DQDTwVw@g}Lbb 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