diff --git a/amy/__init__.py b/amy/__init__.py index ef8fbf17..c5ace50b 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. @@ -356,8 +356,8 @@ def b64(b): def b64(b): return ubinascii.b2a_base64(b)[:-1] -def start_sample(preset=0, bus=1, max_frames=0, midinote=60, loopstart=0, loopend=0): - s = "%d,%d,%d,%d,%d,%d" % (preset, bus, max_frames, midinote, loopstart, loopend) +def start_sample(preset=0, source=SAMPLE_FROM_OUTPUT, max_frames=0, midinote=60, loopstart=0, loopend=0): + s = "%d,%d,%d,%d,%d,%d" % (preset, source, max_frames, midinote, loopstart, loopend) send(start_sample=s) def stop_sample(): diff --git a/amy/constants.py b/amy/constants.py index e1be32b5..00572f21 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 +SAMPLE_FROM_OUTPUT=1 +SAMPLE_FROM_AUDIO_IN=2 +AMY_NUM_BUSES=4 +AMY_DEFAULT_BUS=0 AMY_MAX_CORES=2 AMY_MAX_CHANNELS=2 AMY_NCHANS=2 @@ -37,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/amy/fm.py b/amy/fm.py index 27f07c0c..1d6d571b 100644 --- a/amy/fm.py +++ b/amy/fm.py @@ -238,7 +238,7 @@ def send_to_AMY(self, reset=True): # Set up each operator. last_release_time = 0 last_release_value = 0 - # Oscs: 0 is algo, 1 is pitch LFO, 2 is amp LFO, 3-8 are ops 1-6 + # Oscs: 0 is algo, 1 is LFO, 2-7 are ops 1-6 main_osc = 0 lfo_osc = 1 # The osc of op0 (they go up from here) @@ -251,14 +251,14 @@ def send_to_AMY(self, reset=True): amp_times[2], t(amp_levels[2]), amp_times[3], t(amp_levels[3]), amp_times[4], t(amp_levels[4]) ) - oscbpfmt = "%d,%s/%d,%s/%d,%s/%d,%s/%d,%s" % ( - amp_times[0], t(amp_levels[0]), amp_times[1], t(amp_levels[1]), - amp_times[2], t(amp_levels[2]), amp_times[3], t(amp_levels[3]), - amp_times[4], t(amp_levels[4]) - ) if(amp_times[4] > last_release_time): last_release_time = amp_times[4] last_release_value = amp_levels[4] + #oscbpfmt = "%d,%s/%d,%s/%d,%s/%d,%s/%d,%s" % ( + # amp_times[0], t(amp_levels[0]), amp_times[1], t(amp_levels[1]), + # amp_times[2], t(amp_levels[2]), amp_times[3], t(amp_levels[3]), + # amp_times[4], t(amp_levels[4]) + #) #print("osc %d (op %d) freq %.6f ratio %d env %s amp %.6f amp_mod %d" % \ # (osc_num, osc.op_num_from_0, osc.frequency, osc.freq_is_ratio, oscbpfmt, # osc.op_amp, osc.ampmodsens)) @@ -270,7 +270,7 @@ def send_to_AMY(self, reset=True): args["ratio"] = t(osc.frequency) else: args["freq"] = t(osc.frequency) - # TODO: we xignore intensity of amp mod sens, just on/off + # TODO: we ignore intensity of amp mod sens, just on/off args.update({"mod_source": lfo_osc, "amp": "%s,0,0,1,0,%s" % (t(osc.op_amp), t(self.amp_lfo_amp * (osc.ampmodsens > 0)))}) # We are _NOT_ updating operators with pitch bp, per dan tuesday 7/5 morning (but not monday 7/4 morning) diff --git a/amy/test.py b/amy/test.py index 47f99cf3..ae835869 100644 --- a/amy/test.py +++ b/amy/test.py @@ -1063,7 +1063,7 @@ def run(self): class TestSample(AmyTest): def run(self): - amy.start_sample(preset=1024,bus=1,max_frames=22050, midinote=60) + amy.start_sample(preset=1024, source=amy.SAMPLE_FROM_OUTPUT, max_frames=22050, midinote=60) amy.send(time=0, synth=1, num_voices=4, patch=20) amy.send(time=50, synth=1, note=48, vel=1) amy.send(time=150, synth=1, note=60, vel=1) @@ -1265,6 +1265,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') # Mixdown for buses 0 and 1. + 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/docs/api.md b/docs/api.md index 7ea706f4..a26c01e3 100644 --- a/docs/api.md +++ b/docs/api.md @@ -220,6 +220,7 @@ A note on list parameters: When an argument is a list of parameters, you can in | `in` | `oscs_per_voice` | `oscs_per_voice` | >0 | Reserve this many oscs for each voice. Needed when initializing a synth (or voice) withouth an initial patch. Setting `oscs_per_voice` on an existing synth resets all oscs to their default state. | | `im` | `grab_midi_notes` | `grab_midi_notes` | 0/1 | Use `amy.send(synth=CHANNEL, grab_midi_notes=0)` to prevent the default direct forwarding of MIDI note-on/offs to synth CHANNEL. | | `ip` | `pedal` | `pedal` | int | Non-zero means pedal is down (i.e., sustain). Must be used with `synth`. | +| `iy` | `bus` | `bus` | int | Bus onto which the synth outputs are added (synonym for `y`). | | `K` | `patch_number` | `patch` | uint 0-X | Apply a saved or user patch to a specified synth or voice. | | `r` | `voices[]` | `voices` | int[,int] | Comma separated list of voices to send message to, or load patch into. | | `u` | **TODO**| `patch_string` | string | Provide AMY message to define up to 32 patches in RAM with ID numbers (1024-1055) provided via `patch_number`, or directly configure a `synth`. | @@ -249,6 +250,7 @@ A note on list parameters: When an argument is a list of parameters, you can in | `R` | `resonance` | `resonance` | float | Q factor of variable filter, 0.5-16.0. default 0.7 | | `T` | `eg_type[0]` | `eg0_type` | uint 0-3 | Type for Envelope Generator 0 - 0: Normal (RC-like) / 1: Linear / 2: DX7-style / 3: True exponential. | | `X` | `eg_type[1]` | `eg1_type` | uint 0-3 | Type for Envelope Generator 1 - 0: Normal (RC-like) / 1: Linear / 2: DX7-style / 3: True exponential. | +| `y` | bus | `bus` | int | Bus that this osc gets added onto (default 0) | | `l` | `velocity` | `vel` | float | Note on velocity. Use to start an envelope or set amplitude | ### CtrlCoefs @@ -269,7 +271,7 @@ These per-oscillator parameters use [CtrlCoefs](synth.md) notation | ------ | -------- | ---------- | ---------- | ------------------------------------- | | `z` | **TODO**| `load_sample` | uint x 6 | Signal to start loading sample. preset number, length(frames), samplerate, channels, midinote, loopstart, loopend. All subsequent messages are base64 encoded WAVE-style frames of audio until `length` is reached. Set `preset` and `length=0` to unload a sample from RAM. | | `zF` | **TODO**| `disk_sample` | uint,string,uint | Set a PCM preset to play live from a WAV filename on AMY host disk. Params: preset number, filename, midinote. See `hooks` for reading files on host disk. **Only one file sample can be played at once per preset number. Use multiple presets if you want polyphony from a single sample.** | -| `zS` | **TODO**| `start_sample` | uint x 6 | Start sampling to a stereo PCM preset from bus. Params: preset number, bus, max length in frames, midinote, loopstart, loopend. bus = 1 is AMY mixed output. bus = 2 is AUDIO_IN0 + 1. Will sample until max length is reached, `stop_sample` is issued, or a new `start_sample` is issued. | +| `zS` | **TODO**| `start_sample` | uint x 6 | Start sampling to a stereo PCM preset from source. Params: preset number, source, max length in frames, midinote, loopstart, loopend. source = 1 is AMY mixed output. source = 2 is AUDIO_IN0 + 1. Will sample until max length is reached, `stop_sample` is issued, or a new `start_sample` is issued. | | `zO` | **TODO**| `stop_sample` | uint | Stop sampling. Does nothing if no sampling active. param ignored. | ### WAVETABLE wave type @@ -284,20 +286,31 @@ These per-oscillator parameters use [CtrlCoefs](synth.md) notation - Each wavetable preset is expected to be one 64-cycle table (normally `16384` samples total, `256` samples per cycle). - `duty` crossfades across the 64 cycles inside the selected wavetable preset. +### Per-bus Effects + +Each of the `y` buses has separate effects units. You set their parameters with commands such as `amy.send(bus=0, reverb=1)` (or `y0h1`). + +The final mixdown of the buses onto the AMY output is controlled by one value per bus passed to the `volume` (`V`) command. + +Default AMY has 4 buses, 0..3. If the bus (`y`) is not specified for one of these commands, it defaults to 0. + +| Wire code | C `amy_event` | Python / JS | Type-range | Notes | +| ------ | -------- | ---------- | ---------- | ------------------------------------- | +| `h` | `reverb_level, reverb_liveness, reverb_damping, reverb_xover_hz` | `reverb` | float[,float,float,float] | Reverb parameters -- level, liveness, damping, xover: Level is for output mix; +| `k` | `chorus_level, chorus_max_delay, chorus_lfo_freq, chorus_depth` | `chorus` | float[,float,float,float] | Chorus parameters -- level, delay, freq, depth: Level is for output mix (0 to turn off); delay is max in samples (320); freq is LFO rate in Hz (0.5); depth is proportion of max delay (0.5). | +| `M` | `echo_level, echo_delay_ms, echo_max_delay_ms, echo_feedback, echo_filter_coef` | `echo` | float[,int,int,float,float] | Echo parameters -- level, delay_ms, max_delay_ms, feedback, filter_coef (-1 is HPF, 0 is flat, +1 is LPF). | +| `x` | `eq_l, eq_m, eq_h` |`eq` | float,float,float | Equalization in dB low (~800Hz) / med (~2500Hz) / high (~7500Gz) -15 to 15. 0 is off. default 0. | + ### Other | Wire code | C `amy_event` | Python / JS | Type-range | Notes | | ------ | -------- | ---------- | ---------- | ------------------------------------- | | `H` | `sequence[3]` | `sequence` | int,int,int | Tick offset, period, tag for sequencing | -| `h` | `reverb_level, reverb_liveness, reverb_damping, reverb_xover_hz` | `reverb` | float[,float,float,float] | Reverb parameters -- level, liveness, damping, xover: Level is for output mix; liveness controls decay time, 1 = longest, default 0.85; damping is extra decay of high frequencies, default 0.5; xover is damping crossover frequency, default 3000 Hz. | | `j` | `tempo` | `tempo` | float | The tempo (BPM, quarter notes) of the sequencer. Defaults to 108.0. | -| `k` | `chorus_level, chorus_max_delay, chorus_lfo_freq, chorus_depth` | `chorus` | float[,float,float,float] | Chorus parameters -- level, delay, freq, depth: Level is for output mix (0 to turn off); delay is max in samples (320); freq is LFO rate in Hz (0.5); depth is proportion of max delay (0.5). | -| `M` | `echo_level, echo_delay_ms, echo_max_delay_ms, echo_feedback, echo_filter_coef` | `echo` | float[,int,int,float,float] | Echo parameters -- level, delay_ms, max_delay_ms, feedback, filter_coef (-1 is HPF, 0 is flat, +1 is LPF). | | `N` | `latency_ms`| `latency_ms` | uint | Sets latency in ms. default 0 (see LATENCY) | | `s` | `pitch_bend` | `pitch_bend` | float | Sets the global pitch bend, by default modifying all note frequencies by (fractional) octaves up or down | | `t` | `time` | `time` | uint | Request playback time relative to some fixed start point on your host, in ms. Allows precise future scheduling. | -| `V` | `volume`| `volume` | float 0-10 | Volume knob for entire synth, default 1.0 | -| `x` | `eq_l, eq_m, eq_h` |`eq` | float,float,float | Equalization in dB low (~800Hz) / med (~2500Hz) / high (~7500Gz) -15 to 15. 0 is off. default 0. | +| `V` | `volume`| `volume` | float, float, ... | Volume knob for each bus in the final mixdown, default 1.0 | | `g` | `client` | `client` | uint | Client number for Alles distributed synthesis. | | `W` | `external_channel` | `external_channel` | uint | External channel routing (used by Tulip for CV output). | | `D` | **TODO** | `debug` | uint, 2-4 | 2 shows queue sample, 3 shows oscillator data, 4 shows modified oscillator. Will interrupt audio! | diff --git a/docs/synth.md b/docs/synth.md index 922cad92..9bd36dc9 100644 --- a/docs/synth.md +++ b/docs/synth.md @@ -376,12 +376,12 @@ amy.send(osc=1, wave=amy.PCM_RIGHT, preset=1025, pan=1, note=60, vel=1) ### Sampling -AMY can also sample directly into a PCM memory buffer from a `bus`. [A bus in AMY is a work in progress](https://github.com/shorepine/amy/issues/114) but for now we support two stereo buses: `bus=1` is the final AMY output and `bus=2` is just `AUDIO_IN0` and `AUDIO_IN1`. To start sampling to a PCM preset, use `start_sample`: +AMY can also sample directly into a PCM memory buffer. We support two stereo sampling sources: `source=amy.SAMPLE_FROM_OUTPUT` is the final AMY output and `source=amy.SAMPLE_FROM_AUDIO_IN` is just `AUDIO_IN0` and `AUDIO_IN1`. To start sampling to a PCM preset, use `start_sample`: ```python -amy.start_sample(preset=1024, bus=0, max_frames=44100) # sample for one second +amy.start_sample(preset=1024, source=amy.SAMPLE_FROM_AUDIO_IN, max_frames=44100) # sample for one second amy.stop_sample() # stop all sampling, not needed if using max_frames -amy.start_sample(preset=1024, bus=1, max_frames=11025, midinote=60) # set base midi note, looping, too +amy.start_sample(preset=1024, source=amy.SAMPLE_FROM_OUTPUT, max_frames=11025, midinote=60) # set base midi note, looping, too amy.send(osc=0, wave=amy.PCM_LEFT, preset=1024, pan=0, note=72, vel=1) # play back AUDIO_IN sample an octave higher amy.send(osc=1, wave=amy.PCM_RIGHT, preset=1024, pan=1, note=72, vel=1) ``` 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 f9ea2fe5..7d186958 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) @@ -152,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 @@ -161,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. @@ -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,58 @@ 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); + 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 (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; //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,66 +285,81 @@ 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); + // CHORUS_MOD_SOURCE + bus, 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 we're turning on for the first time, start the oscillator. - if (synth[CHORUS_MOD_SOURCE]->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])); - // Stop us from doing this again. - synth[CHORUS_MOD_SOURCE]->status = SYNTH_IS_MOD_SOURCE; + if (amy_global.bus[bus]->chorus.chorus_delay_lines[0] == NULL) { + alloc_chorus_delay_lines(bus); } // 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; + } + // Configure the LFO osc. + ensure_osc_allocd(CHORUS_MOD_SOURCE + bus, NULL); + // if we're turning on for the first time, start the oscillator. + if (synth[CHORUS_MOD_SOURCE + bus]->status == SYNTH_OFF) { //chorus.level == 0) { + // Setup chorus oscillator. + 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_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, lfo_freq); + // Stop us from doing this again. + synth[CHORUS_MOD_SOURCE + bus]->status = SYNTH_IS_MOD_SOURCE; } + // apply depth, lfo_freq + synth[CHORUS_MOD_SOURCE + bus]->amp_coefs[COEF_CONST] = depth; + synth[CHORUS_MOD_SOURCE + bus]->logfreq_coefs[COEF_CONST] = logfreq_of_freq(lfo_freq); + } + 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); } - 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; } -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,20 +399,42 @@ int peek_stack(const 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) { + 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; 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; - 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; @@ -407,32 +442,32 @@ 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; 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) { @@ -501,7 +536,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) \ @@ -532,11 +567,24 @@ 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; + 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 + 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 +611,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 (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) @@ -573,7 +626,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) @@ -605,6 +657,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])) { @@ -700,6 +755,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); @@ -783,21 +839,14 @@ 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;i0) ks_init(); - filters_init(); algo_init(); patches_init(amy_global.config.max_memory_patches); instruments_init(amy_global.config.max_synths); @@ -928,9 +976,9 @@ int8_t oscs_init() { if(pcm_samples) pcm_init(); if(AMY_HAS_CUSTOM) custom_init(); // synth and msynth are now pointers to arrays of pointers to dynamically-allocated synth structures. - synth = (struct synthinfo **) malloc_caps(sizeof(struct synthinfo *) * (AMY_OSCS+1), amy_global.config.ram_caps_synth); - bzero(synth, sizeof(struct synthinfo *) * (AMY_OSCS+1)); - msynth = (struct mod_synthinfo **) malloc_caps(sizeof(struct mod_synthinfo *) * (AMY_OSCS+1), amy_global.config.ram_caps_synth); + synth = (struct synthinfo **) malloc_caps(sizeof(struct synthinfo *) * (AMY_OSCS + AMY_NUM_BUSES), amy_global.config.ram_caps_synth); + bzero(synth, sizeof(struct synthinfo *) * (AMY_OSCS + AMY_NUM_BUSES)); + msynth = (struct mod_synthinfo **) malloc_caps(sizeof(struct mod_synthinfo *) * (AMY_OSCS + AMY_NUM_BUSES), amy_global.config.ram_caps_synth); output_block_0 = (output_sample_type *) malloc_caps(sizeof(output_sample_type) * AMY_BLOCK_SIZE * AMY_NCHANS, amy_global.config.ram_caps_block); output_block_1 = (output_sample_type *) malloc_caps(sizeof(output_sample_type) * AMY_BLOCK_SIZE * AMY_NCHANS, amy_global.config.ram_caps_block); output_block = output_block_0; @@ -946,12 +994,10 @@ int8_t oscs_init() { // clear out both as local mode won't use fbl[1] for(uint16_t core=0;core1) { // 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 %.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 */); } @@ -1047,15 +1093,20 @@ 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]); + 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); @@ -1068,7 +1119,6 @@ void oscs_deinit() { instruments_deinit(); patches_deinit(); algo_deinit(); - filters_deinit(); if(amy_global.config.ks_oscs > 0) ks_deinit(); } @@ -1164,6 +1214,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) @@ -1244,7 +1295,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); } } @@ -1279,26 +1330,27 @@ void play_delta(struct delta *d) { } } // for global changes, just make the change, no need to update the per-osc synth - if(d->param == VOLUME) amy_global.volume = d->data.f; + uint8_t bus = d->osc; // We assume d.osc was hijacked in amy_event_to_deltas_queue + 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(); } - 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. @@ -1613,7 +1665,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) @@ -1633,7 +1685,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) @@ -1707,57 +1759,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) - 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) { - ensure_osc_allocd(CHORUS_MOD_SOURCE, NULL); - hold_and_modify(CHORUS_MOD_SOURCE); - if(amy_global.chorus.level!=0) { - bzero(delay_mod, AMY_BLOCK_SIZE * sizeof(SAMPLE)); - render_osc_wave(CHORUS_MOD_SOURCE, 0 /* core */, 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) @@ -1866,55 +1910,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. - 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]); + 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][bus]); } if(AMY_HAS_CHORUS) { - // apply chorus. - if(amy_global.chorus.level > 0 && chorus_delay_lines[0] != NULL) { + // 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, chorus_delay_lines[c], - delay_mod, scale, amy_global.chorus.level, 0); + 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.echo.level > 0 && 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); + //} + 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.reverb.level > 0) { - if(AMY_NCHANS == 1) { - stereo_reverb(fbl[0], NULL, fbl[0], NULL, AMY_BLOCK_SIZE, amy_global.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); + 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] @@ -1976,10 +2029,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==SAMPLE_FROM_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==SAMPLE_FROM_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 071a280b..1a401f43 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 sampling source +#define SAMPLE_FROM_OUTPUT 1 +#define SAMPLE_FROM_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 @@ -147,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 @@ -165,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 @@ -327,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 @@ -356,7 +361,8 @@ enum params{ REVERB_LIVENESS, REVERB_DAMPING, REVERB_XOVER_HZ, - NO_PARAM // 209 + BUS, + NO_PARAM // 210 }; /////////////////////////////////////// @@ -484,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 @@ -515,12 +521,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 +543,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 +631,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 +732,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 +760,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 +770,27 @@ 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 +typedef struct bus_state { + // State of fixed dc-blocking HPF + 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 pitch_bend; - // State of fixed dc-blocking HPF - SAMPLE hpf_state; - SAMPLE eq[3]; + 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; struct delta * delta_queue; // start of the sorted queue of deltas to execute. int16_t latency_ms; @@ -763,11 +798,10 @@ 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; - reverb_state_t reverb; - chorus_config_t chorus; - echo_config_t echo; - // Transfer uint8_t transfer_flag; uint8_t * transfer_storage; @@ -782,7 +816,12 @@ struct state { uint32_t us_per_tick; sequence_entry_ll_t * sequence_entry_ll_start; -}; + // Buses + bus_state_t *bus[AMY_NUM_BUSES]; + + // Final output mix + float bus_gain[AMY_NUM_BUSES]; +} global_state_t; // custom oscillator @@ -801,13 +840,14 @@ 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; 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 +866,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)(), const 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 +1008,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 +1020,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 +1073,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/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 9aaa4ea9..0d8baa2c 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); @@ -117,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); @@ -400,6 +402,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 d4a479ae..45140d36 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, const 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 bb698d8c..600fa889 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). @@ -607,7 +608,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]; @@ -618,7 +619,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]; @@ -681,7 +682,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; @@ -693,11 +694,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; + /* Y still available */ 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..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,52 +586,54 @@ 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. - // 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->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 != (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); } -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; @@ -628,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. @@ -637,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 @@ -654,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; @@ -966,6 +989,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); } } diff --git a/tests/ref/TestBuses.wav b/tests/ref/TestBuses.wav new file mode 100644 index 00000000..b823ee01 Binary files /dev/null and b/tests/ref/TestBuses.wav differ