diff --git a/README.md b/README.md index f38c1e5..1ddbc99 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,7 @@ Here's the full list: | s | pitch_bend | float | Sets the global pitch bend, by default modifying all note frequencies by (fractional) octaves up or down | | T | bp0_target | uint mask | Which parameter bp0 controls. 1=amp, 2=duty, 4=freq, 8=filter freq, 16=resonance, 32=feedback (can be added together). Can add 64 for linear ramp, otherwise exponential. **Deprecated** for setting targets, subsumbed by ControlCoefficients. | | t | timestamp | uint | ms of expected playback since some fixed start point on your host. you should always give this if you can. | +| u | store_patch | number,string | store up to 32 patches in RAM with ID number (1024-1055) and AMY message after a comma. Must be sent alone | | v | osc | uint 0 to OSCS-1 | which oscillator to control | | V | volume | float 0-10 | volume knob for entire synth, default 1.0 | | w | wave | uint 0-11 | waveform: [0=SINE, PULSE, SAW_DOWN, SAW_UP, TRIANGLE, NOISE, KS, PCM, ALGO, PARTIAL, PARTIALS, OFF]. default: 0/SINE | diff --git a/amy.py b/amy.py index 8315c63..bf147b4 100644 --- a/amy.py +++ b/amy.py @@ -79,11 +79,13 @@ def trunc3(number): # Construct an AMY message def message(osc=0, wave=None, patch=None, note=None, vel=None, amp=None, freq=None, duty=None, feedback=None, time=None, reset=None, phase=None, pan=None, - client=None, retries=None, volume=None, pitch_bend=None, filter_freq = None, resonance = None, bp0=None, bp1=None, bp0_target=None, bp1_target=None, mod_target=None, - debug=None, chained_osc=None, mod_source=None, clone_osc=None, eq_l = None, eq_m = None, eq_h = None, filter_type= None, algorithm=None, ratio = None, latency_ms = None, algo_source=None, - chorus_level=None, chorus_delay=None, chorus_freq=None, chorus_depth=None, reverb_level=None, reverb_liveness=None, reverb_damping=None, reverb_xover=None, load_patch=None, voices=None): + client=None, retries=None, volume=None, pitch_bend=None, filter_freq = None, resonance = None, bp0=None, bp1=None, bp0_target=None, bp1_target=None, + mod_target=None, debug=None, chained_osc=None, mod_source=None, clone_osc=None, eq_l = None, eq_m = None, eq_h = None, filter_type= None, + algorithm=None, ratio = None, latency_ms = None, algo_source=None, chorus_level=None, chorus_delay=None, chorus_freq=None, chorus_depth=None, + reverb_level=None, reverb_liveness=None, reverb_damping=None, reverb_xover=None, load_patch=None, store_patch=None, voices=None): m = "" + if(store_patch is not None): return "u" + str(store_patch) if(time is not None): m = m + "t" + str(time) if(osc is not None): m = m + "v" + str(osc) if(wave is not None): m = m + "w" + str(wave) diff --git a/src/amy.c b/src/amy.c index ee2b97b..ce63adc 100644 --- a/src/amy.c +++ b/src/amy.c @@ -1568,9 +1568,10 @@ struct event amy_parse_message(char * message) { case 'S': e.reset_osc = atoi(message + start); break; case 's': e.pitch_bend = atoff(message + start); break; case 'T': e.bp0_target = atoi(message + start); break; - case 'W': e.bp1_target = atoi(message + start); break; + case 'u': patches_store_patch(message+start); return amy_default_event(); case 'v': e.osc=((atoi(message + start)) % AMY_OSCS); break; // allow osc wraparound case 'V': e.volume = atoff(message + start); break; + case 'W': e.bp1_target = atoi(message + start); break; case 'w': e.wave=atoi(message + start); break; case 'x': e.eq_l = atoff(message+start); break; case 'y': e.eq_m = atoff(message+start); break; diff --git a/src/amy.h b/src/amy.h index 83e537a..0c88992 100644 --- a/src/amy.h +++ b/src/amy.h @@ -484,6 +484,7 @@ extern void partials_note_off(uint16_t osc); extern void patches_load_patch(struct event e); extern void patches_event_has_voices(struct event e); extern void patches_reset(); +extern void patches_store_patch(char * message); extern SAMPLE render_partials(SAMPLE *buf, uint16_t osc); extern SAMPLE render_custom(SAMPLE *buf, uint16_t osc) ; diff --git a/src/patches.c b/src/patches.c index 82157ec..df5de8a 100644 --- a/src/patches.c +++ b/src/patches.c @@ -20,6 +20,9 @@ if you get a osc and a voice, you add the osc to the base_osc lookup and send th */ #define MAX_VOICES 16 +#define MEMORY_PATCHES 32 +char * memory_patch[MEMORY_PATCHES]; +uint16_t memory_patch_oscs[MEMORY_PATCHES]; uint8_t osc_to_voice[AMY_OSCS]; uint16_t voice_to_base_osc[MAX_VOICES]; @@ -33,8 +36,36 @@ void patches_reset() { for(uint16_t i=0;i max_osc) max_osc = patch_event.osc; + start = i+1; + } + } + if(memory_patch[patch_number-1024] != NULL) free(memory_patch[patch_number-1024]); + memory_patch[patch_number-1024] = malloc(strlen(patch)); + memory_patch_oscs[patch_number-1024] = max_osc + 1; + strcpy(memory_patch[patch_number-1024], patch); +} // This is called when i get an event with voices in it, BUT NOT with a load_patch // So i know that the patch / voice alloc already exists and the patch has already been set! @@ -61,8 +92,15 @@ void patches_load_patch(struct event e) { int16_t voices[MAX_VOICES]; uint8_t num_voices = parse_int_list_message(e.voices, voices, MAX_VOICES); - - char*message = (char*)patch_commands[e.load_patch]; + char *message; + uint16_t patch_osc = 0; + if(e.load_patch > 1023) { + message = memory_patch[e.load_patch-1024]; + patch_osc = memory_patch_oscs[e.load_patch-1024]; + } else { + message = (char*)patch_commands[e.load_patch]; + patch_osc = patch_oscs[e.load_patch]; + } for(uint8_t v=0;v