diff --git a/.gitignore b/.gitignore index 1a9b966d..92e5ef62 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ src/amy.egg-info/ .python-version amy.egg-info/ build/ +venv/ # Prerequisites diff --git a/amy/__init__.py b/amy/__init__.py index ef8fbf17..221fd6f7 100644 --- a/amy/__init__.py +++ b/amy/__init__.py @@ -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'), ('midi_note_cmd', 'ioL'), ('patch_string', 'uS'), # patch_string MUST be last because we can't identify when it ends except by end-of-message. ] _KW_PRIORITY = {k: i for i, (k, _) in enumerate(_KW_MAP_LIST)} # Maps each key to its index within _KW_MAP_LIST. diff --git a/amy/constants.py b/amy/constants.py index e1be32b5..9adbea77 100644 --- a/amy/constants.py +++ b/amy/constants.py @@ -100,7 +100,7 @@ EVENT_SCHEDULED=1 EVENT_TRANSFER_DATA=2 EVENT_SEQUENCE=3 -NOTE_SOURCE_MIDI=2 +NOTE_SOURCE_MIDI=1 ENVELOPE_NORMAL=0 ENVELOPE_LINEAR=1 ENVELOPE_DX7=2 diff --git a/docs/api.md b/docs/api.md index 7ea706f4..e199eba7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -213,13 +213,14 @@ A note on list parameters: When an argument is a list of parameters, you can in | ------ | -------- | ---------- | ---------- | ------------------------------------- | | `i` | `synth` | `synth` | 0-31 | Define a set of voices for voice management. | | `ic` | **TODO** | `midi_cc` | C,L,N,X,O,CMD | MIDI Control Code command for this synth (1-16). `C`=MIDI CC (0-127), `L`=log mapping (0/1), `N`=min val, `X`=max val, `O`=offset, `CMD`=wire command to execute, where `%i` is replaced by the channel number and `%v` is replaced by the value after min/max/offset/log mapping. Providing `C` with no further args deletes that CC. `C=255` deletes all CC mappings for the specified synth. See [#524](https://github.com/shorepine/amy/issues/524) | -| `if` | `synth_flags` | `synth_flags` | uint | Flags for synth creation: 1 = Use MIDI drum note->preset translation; 2 = Drop note-off events. | | `id` | `synth_delay_ms` | `synth_delay` | uint | Delay (in ms) applied to synth note-ons. Gives time for decay of 'stolen' notes. | -| `it` | `to_synth` | `to_synth` | 0-31 | New synth number, when changing the number (MIDI channel for n=1..16) of an entire synth. | -| `iv` | `num_voices` | `num_voices` | int | The number of voices to allocate when defining a synth, alternative to directly specifying voice numbers with `voices=`. Only valid with `synth=X, patch[_number]=Y`. | +| `if` | `synth_flags` | `synth_flags` | uint | Flags for synth creation: 1 = Use MIDI drum note->preset translation; 2 = Drop note-off events. | | `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. | +| `io` | **TODO** | `midi_note_cmd` | M,L,N,X,O,CMD | MIDI Note on/off command for this synth. M=MIDI note number, or -1 for all notes. Other args map the velocity, as for `ic`. `%n` is substituted with the note number. | | `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`. | +| `it` | `to_synth` | `to_synth` | 0-31 | New synth number, when changing the number (MIDI channel for n=1..16) of an entire synth. | +| `iv` | `num_voices` | `num_voices` | int | The number of voices to allocate when defining a synth, alternative to directly specifying voice numbers with `voices=`. Only valid with `synth=X, patch[_number]=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`. | diff --git a/src/amy.c b/src/amy.c index f9ea2fe5..f702f9f5 100644 --- a/src/amy.c +++ b/src/amy.c @@ -1023,7 +1023,7 @@ void show_debug(uint8_t type) { patches_debug(); } if (type > 5) { - cc_mapping_debug(); + midi_mapping_debug(); } if (type > 6) { for (int synth = 0; synth < 32 /* MAX_INSTRUMENTS */; ++synth) { diff --git a/src/amy.h b/src/amy.h index 071a280b..1565dae6 100644 --- a/src/amy.h +++ b/src/amy.h @@ -272,7 +272,7 @@ enum coefs{ #define EVENT_SEQUENCE 3 // note_source values -#define NOTE_SOURCE_MIDI 2 +#define NOTE_SOURCE_MIDI 1 // Envelope generator types (for synth[osc].env_type[eg]). #define ENVELOPE_NORMAL 0 @@ -901,13 +901,22 @@ extern int parse_int_list_message16(char *message, int16_t *vals, int max_num_va extern void reset_osc_by_pointer(struct synthinfo *psynth, struct mod_synthinfo *pmsynth); extern void reset_osc(uint16_t i ); -extern int midi_store_control_code(int channel, int code, int is_log, float min_val, float max_val, float offset_val, char *message); -extern int midi_clear_control_code(int channel, int code); -extern bool midi_fetch_control_code_command(int channel, int code, char *s, size_t len); -extern void cc_mapping_debug(); +// Values for midi_mapping.type +#define MIDI_MAP_TYPE_ANY (-1) +#define MIDI_MAP_TYPE_CC (0) +#define MIDI_MAP_TYPE_NOTE (1) + +// Value for code (or note) that matches anything +#define MIDI_MAP_CODE_ANY (-1) + +extern int midi_store_mapping(int channel, int type, int code, int is_log, float min_val, float max_val, float offset_val, char *message); +extern int midi_clear_mapping(int channel, int type, int code); +extern bool midi_fetch_mapping_command(int channel, int type, int code, char *s, size_t len); +extern void midi_mapping_debug(); extern void midi_mappings_init(); extern void midi_mappings_deinit(); -extern void midi_clear_channel_mappings(int channel); +extern void midi_clear_channel_mappings(int channel, int type); +extern void midi_msg_handler(uint8_t * bytes, uint16_t len, uint8_t is_sysex, uint32_t time); extern float render_am_lut(float * buf, float step, float skip, float incoming_amp, float ending_amp, const float* lut, int16_t lut_size, float *mod, float bandwidth); extern void ks_init(); diff --git a/src/amy_midi.c b/src/amy_midi.c index 18b13026..3d2b3372 100644 --- a/src/amy_midi.c +++ b/src/amy_midi.c @@ -51,6 +51,8 @@ static void debug_print_midi_hex(const uint8_t *data, uint32_t len, uint8_t syse // Send a MIDI note on OUT void amy_send_midi_note_on(uint16_t osc) { // don't forward on a note coming in through MIDI IN + //fprintf(stderr, "amy_send_midi_note_on: osc %d source %d note %.1f vel %.3f\n", + // osc, synth[osc]->note_source, synth[osc]->midi_note, synth[osc]->velocity); if(synth[osc]->note_source != NOTE_SOURCE_MIDI) { uint8_t bytes[3]; bytes[0] = 0x90; @@ -65,37 +67,14 @@ void amy_send_midi_note_off(uint16_t osc) { // don't forward on a note coming in through MIDI IN if(synth[osc]->note_source != NOTE_SOURCE_MIDI) { uint8_t bytes[3]; - bytes[0] = 0x80; + // Send note-off as a note-on with vel 0. + bytes[0] = 0x90; bytes[1] = (uint8_t)roundf(synth[osc]->midi_note); - bytes[2] = (uint8_t)roundf(synth[osc]->velocity*127.0f); + bytes[2] = 0; midi_out(bytes, 3); } } -// Given a MIDI note on IN, create a AMY message on that instrument and play it -void amy_received_note_on(uint8_t channel, uint8_t note, uint8_t vel, uint32_t time) { - if (!instrument_grab_midi_notes(channel)) return; - amy_event e = amy_default_event(); - e.time = time; - e.synth = channel; - e.note_source = NOTE_SOURCE_MIDI; - e.midi_note = note; - e.velocity = ((float)vel/127.0f); - amy_add_event(&e); -} - -// Given a MIDI note off IN, create a AMY message on that instrument and play it -void amy_received_note_off(uint8_t channel, uint8_t note, uint8_t vel, uint32_t time) { - if (!instrument_grab_midi_notes(channel)) return; - amy_event e = amy_default_event(); - e.time = time; - e.synth = channel; - e.note_source = NOTE_SOURCE_MIDI; - e.midi_note = note; - e.velocity = 0; - amy_add_event(&e); -} - void amy_received_control_change(uint8_t channel, uint8_t control, uint8_t value, uint32_t time) { if (control == 0) { // Bank select coarse. @@ -167,9 +146,8 @@ void amy_event_midi_message_received(uint8_t * data, uint32_t len, uint8_t sysex uint8_t status = status_byte & 0xF0; uint8_t channel = status_byte & 0x0F; // Do the AMY instrument things here - if(status == 0x80) amy_received_note_off(channel+1, data[1], data[2], time); - else if(status == 0x90) amy_received_note_on(channel+1, data[1], data[2], time); - else if(status == 0xB0 && data[1] == 0x40) amy_received_pedal(channel+1, data[2], time); + /* if(status == 0x90) amy_received_note_on(channel+1, data[1], data[2], time); + else */ if(status == 0xB0 && data[1] == 0x40) amy_received_pedal(channel+1, data[2], time); else if(status == 0xB0 && data[1] == 0x7B) amy_received_all_notes_off(channel+1, time); else if(status == 0XB0) amy_received_control_change(channel+1, data[1], data[2], time); else if(status == 0xC0) amy_received_program_change(channel+1, data[1], time); @@ -177,6 +155,7 @@ void amy_event_midi_message_received(uint8_t * data, uint32_t len, uint8_t sysex else if(status_byte == 0xFA) sequencer_midi_start(); else if(status_byte == 0xFC) sequencer_midi_stop(); } + midi_msg_handler(data, len, sysex, time); // Also send the external hooks if set if(amy_global.config.amy_external_midi_input_hook != NULL) { diff --git a/src/api.c b/src/api.c index 9aaa4ea9..f0482deb 100644 --- a/src/api.c +++ b/src/api.c @@ -259,8 +259,6 @@ void amy_add_event(amy_event *e) { // defined in midi_mappings.c extern void juno_filter_midi_handler(uint8_t * bytes, uint16_t len, uint8_t is_sysex); -extern void midi_cc_handler(uint8_t * bytes, uint16_t len, uint8_t is_sysex); - #ifdef __EMSCRIPTEN__ void amy_start_web() { // a shim for web AMY, as it's annoying to build structs in js @@ -268,7 +266,6 @@ void amy_start_web() { amy_config.midi = AMY_MIDI_IS_WEBMIDI; amy_config.features.default_synths = 1; amy_config.features.startup_bleep = 1; - amy_config.amy_external_midi_input_hook = midi_cc_handler; amy_start(amy_config); } @@ -277,7 +274,6 @@ void amy_start_web_no_synths() { amy_config_t amy_config = amy_default_config(); amy_config.midi = AMY_MIDI_IS_WEBMIDI; amy_config.features.default_synths = 0; - amy_config.amy_external_midi_input_hook = midi_cc_handler; amy_start(amy_config); } #endif @@ -387,9 +383,6 @@ void amy_start(amy_config_t c) { if (amy_global.config.audio == AMY_AUDIO_IS_MINIAUDIO) miniaudio_start(); #endif - if (amy_global.config.amy_external_midi_input_hook == NULL) { - amy_global.config.amy_external_midi_input_hook = midi_cc_handler; - } } void amy_stop() { diff --git a/src/midi_mappings.c b/src/midi_mappings.c index dacc955e..05bd2ef7 100644 --- a/src/midi_mappings.c +++ b/src/midi_mappings.c @@ -31,28 +31,46 @@ void juno_filter_midi_handler(uint8_t * bytes, uint16_t len, uint8_t is_sysex) { } } -struct cc_mapping { - struct cc_mapping *next; +struct midi_mapping { + struct midi_mapping *next; int channel; - int code; + int type; // + int code; // For note-ons, note number for note-specific events; MIDI_MAP_CODE_ANY for non-note-specific events. + // Transform of value field int is_log; float min_val; float max_val; float offset_val; + // What we actually do. char *message_template; }; -struct cc_mapping *cc_mapping_root = NULL; +struct midi_mapping *midi_mapping_root = NULL; + +// Built-in default for note commands +struct midi_mapping default_note_mapping = { + .next = NULL, + .channel = 0, + .type = MIDI_MAP_TYPE_NOTE, + .code = MIDI_MAP_CODE_ANY, + .is_log = 0, + .min_val = 0, + .max_val = 1.0f, + .offset_val = 0, + .message_template = "i%iiM1n%nl%v", +}; + -void cc_mapping_print(struct cc_mapping *mapping) { - fprintf(stderr, "mapping 0x%lx chan %d code 0x%x log %d min %.1f max %.1f offs %.1f msg %s\n", - (unsigned long)mapping, mapping->channel, mapping->code, mapping->is_log, mapping->min_val, mapping->max_val, mapping->offset_val, mapping->message_template); +void midi_mapping_print(struct midi_mapping *mapping) { + fprintf(stderr, "mapping 0x%lx chan %d type %d code 0x%x log %d min %.1f max %.1f offs %.1f msg %s\n", + (unsigned long)mapping, mapping->channel, mapping->type, mapping->code, mapping->is_log, mapping->min_val, mapping->max_val, mapping->offset_val, mapping->message_template); } -struct cc_mapping *cc_mapping_init(struct cc_mapping **p_root, int channel, int code, int is_log, float min_val, float max_val, float offset_val, char *message_template) { - struct cc_mapping *result = (struct cc_mapping *)malloc_caps(sizeof(struct cc_mapping) + strlen(message_template) + 1, amy_global.config.ram_caps_synth); - result->message_template = ((char *)result) + sizeof(struct cc_mapping); +struct midi_mapping *midi_mapping_init(struct midi_mapping **p_root, int channel, int type, int code, int is_log, float min_val, float max_val, float offset_val, char *message_template) { + struct midi_mapping *result = (struct midi_mapping *)malloc_caps(sizeof(struct midi_mapping) + strlen(message_template) + 1, amy_global.config.ram_caps_synth); + result->message_template = ((char *)result) + sizeof(struct midi_mapping); result->channel = channel; + result->type = type; result->code = code; result->is_log = is_log; result->min_val = min_val; @@ -65,95 +83,100 @@ struct cc_mapping *cc_mapping_init(struct cc_mapping **p_root, int channel, int return result; } -void cc_mapping_debug(void) { - fprintf(stderr, "cc_mapping_debug:\n"); - struct cc_mapping **p_mapping = &cc_mapping_root; +void midi_mapping_debug(void) { + fprintf(stderr, "midi_mapping_debug:\n"); + struct midi_mapping **p_mapping = &midi_mapping_root; while (*p_mapping != NULL) { - cc_mapping_print(*p_mapping); + midi_mapping_print(*p_mapping); p_mapping = &((*p_mapping)->next); } } -void cc_mapping_free(struct cc_mapping **p_mapping) { +void midi_mapping_free(struct midi_mapping **p_mapping) { // Close up the linked list. - struct cc_mapping *doomed = *p_mapping; + struct midi_mapping *doomed = *p_mapping; *p_mapping = doomed->next; // Return the memory free(doomed); } void midi_mappings_init(void) { - cc_mapping_root = NULL; + midi_mapping_root = NULL; } void midi_mappings_deinit(void) { - struct cc_mapping **p_mapping = &cc_mapping_root; + struct midi_mapping **p_mapping = &midi_mapping_root; while (*p_mapping != NULL) { - cc_mapping_free(p_mapping); + midi_mapping_free(p_mapping); } } -void midi_clear_channel_mappings(int channel) { - struct cc_mapping **p_mapping = &cc_mapping_root; +void midi_clear_channel_mappings(int channel, int type) { + struct midi_mapping **p_mapping = &midi_mapping_root; while (*p_mapping != NULL) { - if ((*p_mapping)->channel == channel) { - cc_mapping_free(p_mapping); + if ((*p_mapping)->channel == channel && ((type == MIDI_MAP_TYPE_ANY) || ((*p_mapping)->type == type))) { + midi_mapping_free(p_mapping); } else { p_mapping = &((*p_mapping)->next); } } } -struct cc_mapping **cc_mapping_find(int channel, int code) { +struct midi_mapping **midi_mapping_find(int channel, int type, int code) { // Retrieve the mapping associated with a midi channel + code, if any. - struct cc_mapping **p_mapping = &cc_mapping_root; + struct midi_mapping **p_mapping = &midi_mapping_root; while (*p_mapping != NULL) { - if ((*p_mapping)->channel == channel && (*p_mapping)->code == code) return p_mapping; + if ((*p_mapping)->channel == channel && ((type == MIDI_MAP_TYPE_ANY) || (*p_mapping)->type == type)) { + if ((code == MIDI_MAP_CODE_ANY) || ((*p_mapping)->code == MIDI_MAP_CODE_ANY) || ((*p_mapping)->code == code)) + return p_mapping; + } p_mapping = &((*p_mapping)->next); } return NULL; } -int midi_clear_control_code(int channel, int code) { - if (code == 255) { +int midi_clear_mapping(int channel, int type, int code) { + // Backwards compatibility + if (code == 255) code = MIDI_MAP_CODE_ANY; + if (code == MIDI_MAP_CODE_ANY) { // Magic value means clear all MIDI CCs for this channel - midi_clear_channel_mappings(channel); + midi_clear_channel_mappings(channel, type); return 1; } - struct cc_mapping **p_mapping = cc_mapping_find(channel, code); - if (p_mapping) { cc_mapping_free(p_mapping); return 1; } + struct midi_mapping **p_mapping = midi_mapping_find(channel, type, code); + if (p_mapping) { midi_mapping_free(p_mapping); return 1; } return 0; // nothing found. } -int midi_store_control_code(int channel, int code, int is_log, float min_val, float max_val, float offset_val, char *message) { - // Register a MIDI control code and mapping and a wire code template. +int midi_store_mapping(int channel, int type, int code, int is_log, float min_val, float max_val, float offset_val, char *message) { + // Register a MIDI mapping and a wire code template. // Strip trailing wire protocol terminator(s) so they don't accumulate on round-trips. size_t mlen = strlen(message); while (mlen > 0 && message[mlen - 1] == 'Z') { message[--mlen] = '\0'; } - struct cc_mapping **p_mapping = cc_mapping_find(channel, code); - if (p_mapping) cc_mapping_free(p_mapping); + struct midi_mapping **p_mapping = midi_mapping_find(channel, type, code); + if (p_mapping) midi_mapping_free(p_mapping); // store with an empty string removes mapping if (mlen) { - /* struct cc_mapping *mapping = */ cc_mapping_init(&cc_mapping_root, channel, code, is_log, min_val, max_val, offset_val, message); - //cc_mapping_debug(); + /* struct midi_mapping *mapping = */ midi_mapping_init(&midi_mapping_root, channel, type, code, is_log, min_val, max_val, offset_val, message); + midi_mapping_debug(); } return 1; } -bool midi_fetch_control_code_command(int channel, int code, char *s, size_t len) { - struct cc_mapping **p_mapping = cc_mapping_find(channel, code); - //fprintf(stderr, "midi_fetch_control_code chan %d code %d mapping 0x%llx\n", channel, code, (uint64_t)p_mapping); +bool midi_fetch_mapping_command(int channel, int type, int code, char *s, size_t len) { + struct midi_mapping **p_mapping = midi_mapping_find(channel, type, code); + //fprintf(stderr, "midi_fetch_mapping_command chan %d type %d code %d mapping 0x%llx\n", channel, type, code, (uint64_t)p_mapping); if (p_mapping == NULL) return false; // Format the control code - ic,,,,, - sprintf(s, "ic%d,%d,%.3f,%.3f,%.3f,%sZ", (*p_mapping)->code, (*p_mapping)->is_log, (*p_mapping)->min_val, (*p_mapping)->max_val, (*p_mapping)->offset_val, (*p_mapping)->message_template); + sprintf(s, "i%c%d,%d,%.3f,%.3f,%.3f,%sZ", (*p_mapping)->type == MIDI_MAP_TYPE_CC? 'c' : 'o', (*p_mapping)->code, (*p_mapping)->is_log, (*p_mapping)->min_val, (*p_mapping)->max_val, (*p_mapping)->offset_val, (*p_mapping)->message_template); assert(strlen(s) < len); return true; } -float map_cc_value(struct cc_mapping *mapping, uint8_t value) { +float map_midi_value(struct midi_mapping *mapping, uint8_t value) { if (mapping->is_log != 0) { return (mapping->min_val + mapping->offset_val) * expf( @@ -171,7 +194,7 @@ float map_cc_value(struct cc_mapping *mapping, uint8_t value) { #define WIRE_COMMAND_LEN 256 -void substitute_cc_special_values(char *dest, const char *src, int channel, float value) { +void substitute_midi_special_values(char *dest, const char *src, int channel, int code, float value) { // Copy src string to dest, but replace "%i" with channel and "%v" with value. const char *s; const char *entry_src = src; @@ -191,8 +214,10 @@ void substitute_cc_special_values(char *dest, const char *src, int channel, floa sprintf(dest, "%d", (int)value); } else if (src[0] == 'i') { sprintf(dest, "%d", channel); + } else if (src[0] == 'n') { // 'n' is for note. + sprintf(dest, "%d", code); } else { - fprintf(stderr, "substitute_cc: unrecognized '%%%c' in %s\n", src[0], entry_src); + fprintf(stderr, "substitute_midi: unrecognized '%%%c' in %s\n", src[0], entry_src); } ++src; // skip over the code char nchars = strlen(dest); @@ -203,15 +228,25 @@ void substitute_cc_special_values(char *dest, const char *src, int channel, floa if (n_remain > (int)strlen(src)) strcpy(dest, src); } -void midi_cc_handler(uint8_t * bytes, uint16_t len, uint8_t is_sysex) { - if ((bytes[0] & 0xF0) == 0xB0) { // CC +void midi_msg_handler(uint8_t * bytes, uint16_t len, uint8_t is_sysex, uint32_t time) { + uint8_t status = bytes[0] & 0xF0; + if (status == 0xB0 || status == 0x90) { // CC or note-on int channel = (bytes[0] & 0x0F) + 1; - int code = bytes[1]; - struct cc_mapping **p_mapping = cc_mapping_find(channel, code); - if (p_mapping != NULL) { - float value = map_cc_value(*p_mapping, bytes[2]); + int type = (status == 0xB0) ? MIDI_MAP_TYPE_CC : MIDI_MAP_TYPE_NOTE; + int code = bytes[1]; // note for note-on events + struct midi_mapping **p_mapping = midi_mapping_find(channel, type, code); + struct midi_mapping *mapping = &default_note_mapping; + if (type == MIDI_MAP_TYPE_NOTE || p_mapping != NULL) { + if (p_mapping != NULL) + mapping = *p_mapping; + float value = map_midi_value(mapping, bytes[2]); char message[WIRE_COMMAND_LEN]; - substitute_cc_special_values(message, (*p_mapping)->message_template, channel, value); + char offset = 0; + if (AMY_IS_SET(time)) { + sprintf(message, "t%d", time); + offset = strlen(message); + } + substitute_midi_special_values(message + offset, mapping->message_template, channel, code, value); //fprintf(stderr, "midi_cc_handler: message %s\n", message); amy_add_message(message); } diff --git a/src/parse.c b/src/parse.c index bb698d8c..fafcb0ab 100644 --- a/src/parse.c +++ b/src/parse.c @@ -316,9 +316,9 @@ void parse_coef_message(char *message, float *coefs) { extern const mp_obj_fun_builtin_var_t tulip_pcm_load_file_obj; #endif -int parse_midi_cc_payload(char *message, int32_t *p_cc_code, int32_t *p_is_log, float *p_min_val, float *p_max_val, float *p_offset_val) { +int parse_midi_mapping_payload(char *message, int32_t *p_code, int32_t *p_is_log, float *p_min_val, float *p_max_val, float *p_offset_val) { char *m = message; - m += parse_val_int32_t(m, p_cc_code); + m += parse_val_int32_t(m, p_code); if (m[0] != ',') goto end; else ++m; m += parse_val_int32_t(m, p_is_log); if (m[0] != ',') goto end; else ++m; @@ -346,28 +346,30 @@ int amy_parse_synth_layer_message(char *message, amy_event *e) { 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 == 'M') e->note_source = atoi(message); // To mark MIDI-in notes. else if (cmd == 'd') e->synth_delay_ms = atoi(message); else if (cmd == 'n') e->oscs_per_voice = atoi(message); - else if (cmd == 'c') { + else if (cmd == 'c' || cmd == 'o') { // 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). - int32_t cc_code, is_log; + int32_t code, is_log; float min_val, max_val, offset_val; - AMY_UNSET(cc_code); + int type = (cmd == 'c') ? MIDI_MAP_TYPE_CC : MIDI_MAP_TYPE_NOTE; + AMY_UNSET(code); AMY_UNSET(is_log); - skip_chars = parse_midi_cc_payload(message, &cc_code, &is_log, &min_val, &max_val, &offset_val); + skip_chars = parse_midi_mapping_payload(message, &code, &is_log, &min_val, &max_val, &offset_val); if (*(message + skip_chars) != ',') { - if (AMY_IS_UNSET(cc_code) || AMY_IS_SET(is_log)) { + if (AMY_IS_UNSET(code) || AMY_IS_SET(is_log)) { // Either parsing bailed without even a CC code, or it got past the is_log, meaning it wasn't a bare ic command. - fprintf(stderr, "synth_layer: midi cc payload didn't parse for %s.\n", message - 1); + fprintf(stderr, "synth_layer: midi mapping payload didn't parse for %s.\n", message - 1); return skip_chars; // maybe the rest will parse? } // Else we got an incomplete message with a valid CC code - clear it - midi_clear_control_code(e->synth, cc_code); // (handles 255 as special case). + midi_clear_mapping(e->synth, type, code); // (handles 255 as special case). return skip_chars; } ++skip_chars; // step over the "," before the wire string template. - midi_store_control_code(e->synth, cc_code, is_log, min_val, max_val, offset_val, message + skip_chars); + midi_store_mapping(e->synth, type, code, is_log, min_val, max_val, offset_val, message + skip_chars); // Consume rest of message but leave the trailing 'Z' for the outer parser. int remainder = strlen(message); if (remainder > 0 && message[remainder - 1] == 'Z') remainder--; diff --git a/src/patches.c b/src/patches.c index 81a63dc8..e7297747 100644 --- a/src/patches.c +++ b/src/patches.c @@ -653,8 +653,9 @@ void *yield_synth_commands(uint8_t synth, char *s, size_t len, bool include_fx, } else { // MIDI CC part bool found = false; + int type = MIDI_MAP_TYPE_CC; 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_mapping_command(synth, type, next_midi_code, s, len) == true) { state_val = STATE_START_OF_MIDI + next_midi_code + 1; found = true; break; @@ -1030,8 +1031,9 @@ uint8_t patches_voices_for_load_synth(amy_event *e, uint16_t voices[]) { instrument_release(e->synth); // Delete the instrument number so we don't forward the 'rest' of the event to it. AMY_UNSET(e->synth); - // Clear all the midi control code mappings. - midi_clear_channel_mappings(e->synth); + // Clear all the midi mappings. + int type = MIDI_MAP_TYPE_ANY; + midi_clear_channel_mappings(e->synth, type); return 0; } //fprintf(stderr, "Allocated %d voices to instrument %d\n", num_voices, e->synth); diff --git a/src/pyamy.c b/src/pyamy.c index ec573b7d..5be74120 100644 --- a/src/pyamy.c +++ b/src/pyamy.c @@ -228,8 +228,8 @@ static PyObject * inject_midi_wrapper(PyObject *self, PyObject *args) { uint8_t byte_data[MAX_MIDI_ARGS]; uint32_t time = AMY_UNSET_VALUE(time); // But for now we accept only exactly 3 or 4 values: [time,] midi_bytes0..2 - if (! PyArg_ParseTuple(args, "iiii", &time, &data[0], &data[1], &data[2])) - if (! PyArg_ParseTuple(args, "iii", &data[0], &data[1], &data[2])) + if (!PyArg_ParseTuple(args, "iiii", &time, &data[0], &data[1], &data[2]) + && !PyArg_ParseTuple(args, "iii", &data[0], &data[1], &data[2])) return NULL; uint8_t sysex = 0; for (int i = 0; i < 3; ++i) byte_data[i] = (uint8_t)data[i]; diff --git a/tests/ref/TestMidiDrums.wav b/tests/ref/TestMidiDrums.wav index 8f2d31c3..0db9d06e 100644 Binary files a/tests/ref/TestMidiDrums.wav and b/tests/ref/TestMidiDrums.wav differ