From d33f50e760f009abd8ca315fed51a3777456b377 Mon Sep 17 00:00:00 2001 From: dreamer <1185977+dromer@users.noreply.github.com> Date: Fri, 1 Sep 2023 19:28:11 +0200 Subject: [PATCH] Feature/daisy midi (#76) * start midi work for daisy * semi-working usb and uart midi * set delayMs to 0; enable other midi input events * try SendMessage * midi out * make midi optional in the template based on board_info * remove print * disable usb-midi when debug printing in enabled * separate usb and uart midi with extra meta.json flag * use FixedCapStr as the FIFO type * separate midi (usb and uart) and improved debug printing (#117) * disable usb-midi when debug printing in enabled * separate usb and uart midi with extra meta.json flag * use FixedCapStr as the FIFO type * update changelog and docs * use temp wstd2daisy package * update changelog --- CHANGELOG.md | 4 +- docs/03.gen.daisy.md | 42 ++- hvcc/generators/c2daisy/c2daisy.py | 16 ++ .../c2daisy/templates/HeavyDaisy.cpp | 265 +++++++++++++++++- setup.cfg | 2 +- 5 files changed, 325 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e523ac..50b25965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,10 @@ CHANGELOG Next Release ----- -* Daisy: allow for debug printing (off by default, increases program size due to formatting) * Daisy: set bootloader type in Makefile +* Daisy: MIDI i/o for NoteOn/Off, ControlChange, ProgramChange, ChannelPressure, and PitchBend +* Daisy: USB MIDI toggle (disabled by debug printing) +* Daisy: allow for debug printing (off by default, increases program size due to formatting) * DPF bugfixes: broken midi template include; MIDI_RT_CLOCK fails under certain conditions 0.8.0 diff --git a/docs/03.gen.daisy.md b/docs/03.gen.daisy.md index c3ed8f35..3b92c859 100644 --- a/docs/03.gen.daisy.md +++ b/docs/03.gen.daisy.md @@ -22,4 +22,44 @@ Which can be configured using the `-m` metadata.json `daisy.board` setting: } ``` -However one can also create custom board layouts. See the pd2dsy documentation for more information. +However one can also create custom board layouts. See [the Electro-Smith documentation](https://github.com/electro-smith/DaisyWiki/wiki/Pd2dsy-Guide) for more information. + +The custom layout can be passed on via the meta.json as such: + +```json +{ + "daisy": { + "board_file": + } +} +``` + +## MIDI + +Board files that have `OOPSY_TARGET_HAS_MIDI_INPUT` configured will automatically set up UART MIDI on the default USART1 Rx and Tx pins of the Daisy (D13/14). + +Additionally `usb_midi`, running on the onboard micro-usb, can be enabled separately via the meta.json + +```json +{ + "daisy": { + "usb_midi": true + } +} +``` + +At the moment all midi messages will be merged between USB and UART MIDI interfaces. In the future it will likely be possible to assign additional UART pins and group them under a specific PD midi "port". + +## [print] object + +Printing to serial console can be enabled using the `debug_printing` flag in the meta.json: + +```json +{ + "daisy": { + "debug_printing": true + } +} +``` + +This will increase the program size with a few kb and will disable `usb_midi` as we currently do not have composite USB device yet. diff --git a/hvcc/generators/c2daisy/c2daisy.py b/hvcc/generators/c2daisy/c2daisy.py index 0c0fbd5b..7cce9b44 100644 --- a/hvcc/generators/c2daisy/c2daisy.py +++ b/hvcc/generators/c2daisy/c2daisy.py @@ -11,6 +11,16 @@ from . import parameters +hv_midi_messages = { + "__hv_noteout", + "__hv_ctlout", + "__hv_pgmout", + "__hv_touchout", + "__hv_bendout", + "__hv_midiout" +} + + class c2daisy: """ Generates a Daisy wrapper for a given patch. """ @@ -63,6 +73,10 @@ def compile( else: header, board_info = json2daisy.generate_header_from_name(board) + # remove heavy out params from externs + externs['parameters']['out'] = [ + t for t in externs['parameters']['out'] if not any(x == y for x in hv_midi_messages for y in t)] + component_glue = parameters.parse_parameters( externs['parameters'], board_info['components'], board_info['aliases'], 'hardware') component_glue['class_name'] = board_info['name'] @@ -71,6 +85,8 @@ def compile( component_glue['max_channels'] = board_info['channels'] component_glue['num_output_channels'] = num_output_channels component_glue['debug_printing'] = daisy_meta.get('debug_printing', False) + component_glue['usb_midi'] = daisy_meta.get('usb_midi', False) + component_glue['has_midi'] = board_info['has_midi'] component_glue['copyright'] = copyright_c diff --git a/hvcc/generators/c2daisy/templates/HeavyDaisy.cpp b/hvcc/generators/c2daisy/templates/HeavyDaisy.cpp index e9aab001..1c55ffb1 100644 --- a/hvcc/generators/c2daisy/templates/HeavyDaisy.cpp +++ b/hvcc/generators/c2daisy/templates/HeavyDaisy.cpp @@ -6,6 +6,31 @@ #define SAMPLE_RATE 48000.f +{% if has_midi or usb_midi %} +#define HV_HASH_NOTEIN 0x67E37CA3 +#define HV_HASH_CTLIN 0x41BE0f9C +#define HV_HASH_PGMIN 0x2E1EA03D +#define HV_HASH_TOUCHIN 0x553925BD +#define HV_HASH_BENDIN 0x3083F0F7 +#define HV_HASH_MIDIIN 0x149631bE +#define HV_HASH_MIDIREALTIMEIN 0x6FFF0BCF + +#define HV_HASH_NOTEOUT 0xD1D4AC2 +#define HV_HASH_CTLOUT 0xE5e2A040 +#define HV_HASH_PGMOUT 0x8753E39E +#define HV_HASH_TOUCHOUT 0x476D4387 +#define HV_HASH_BENDOUT 0xE8458013 +#define HV_HASH_MIDIOUT 0x6511DE55 +#define HV_HASH_MIDIOUTPORT 0x165707E4 + +#define MIDI_RT_CLOCK 0xF8 +#define MIDI_RT_START 0xFA +#define MIDI_RT_CONTINUE 0xFB +#define MIDI_RT_STOP 0xFC +#define MIDI_RT_ACTIVESENSE 0xFE +#define MIDI_RT_RESET 0xFF +{% endif %} + using namespace daisy; json2daisy::Daisy{{ class_name|capitalize }} hardware; @@ -14,7 +39,13 @@ Heavy_{{patch_name}} hv(SAMPLE_RATE); void audiocallback(daisy::AudioHandle::InputBuffer in, daisy::AudioHandle::OutputBuffer out, size_t size); static void sendHook(HeavyContextInterface *c, const char *receiverName, uint32_t receiverHash, const HvMessage * m); +{% if debug_printing %} static void printHook(HeavyContextInterface *c, const char *printLabel, const char *msgString, const HvMessage *m); +/** FIFO to hold messages as we're ready to print them */ +FIFO, 64> event_log; +{% elif usb_midi %} +daisy::MidiUsbHandler midiusb; +{% endif %} void CallbackWriteIn(Heavy_{{patch_name}}& hv); void LoopWriteIn(Heavy_{{patch_name}}& hv); void CallbackWriteOut(); @@ -60,19 +91,110 @@ DaisyHvParamOut DaisyOutputParameters[DaisyNumOutputParameters] = { }; {% endif %} +{% if has_midi or usb_midi %} +// Typical Switch case for Message Type. +void HandleMidiMessage(MidiEvent m) +{ + switch(m.type) + { + case NoteOff: { + NoteOnEvent p = m.AsNoteOn(); + hv.sendMessageToReceiverV(HV_HASH_NOTEIN, 0, "fff", + (float) p.note, // pitch + (float) 0, // velocity + (float) p.channel); + break; + } + case NoteOn: { + NoteOnEvent p = m.AsNoteOn(); + hv.sendMessageToReceiverV(HV_HASH_NOTEIN, 0, "fff", + (float) p.note, // pitch + (float) p.velocity, // velocity + (float) p.channel); + break; + } + case ControlChange: { + ControlChangeEvent p = m.AsControlChange(); + hv.sendMessageToReceiverV(HV_HASH_CTLIN, 0, "fff", + (float) p.value, // value + (float) p.control_number, // cc number + (float) p.channel); + break; + } + case ProgramChange: { + ProgramChangeEvent p = m.AsProgramChange(); + hv.sendMessageToReceiverV(HV_HASH_PGMIN, 0, "ff", + (float) p.program, + (float) p.channel); + break; + } + case ChannelPressure: { + ChannelPressureEvent p = m.AsChannelPressure(); + hv.sendMessageToReceiverV(HV_HASH_TOUCHIN, 0, "ff", + (float) p.pressure, + (float) p.channel); + break; + } + case PitchBend: { + PitchBendEvent p = m.AsPitchBend(); + // combine 7bit lsb and msb into 32bit int + hv_uint32_t value = (((hv_uint32_t) m.data[1]) << 7) | ((hv_uint32_t) m.data[0]); + hv.sendMessageToReceiverV(HV_HASH_BENDIN, 0, "ff", + (float) value, + (float) p.channel); + break; + } + + default: break; + } +} +{% endif %} + int main(void) { hardware.Init(true); + {% if has_midi %} + MidiUartHandler::Config midi_config; + hardware.midi.Init(midi_config); + hardware.midi.StartReceive(); + {% endif %} + {% if not debug_printing and usb_midi %} + MidiUsbHandler::Config midiusb_config; + midiusb.Init(midiusb_config); + midiusb.StartReceive(); + {% endif %} + hardware.StartAudio(audiocallback); {% if debug_printing %} hardware.som.StartLog(); hv.setPrintHook(printHook); + + uint32_t now = System::GetNow(); + uint32_t log_time = System::GetNow(); {% endif %} hv.setSendHook(sendHook); for(;;) { + {% if debug_printing %} + now = System::GetNow(); + {% endif %} + hardware.LoopProcess(); + {% if has_midi %} + hardware.midi.Listen(); + while(hardware.midi.HasEvents()) + { + HandleMidiMessage(hardware.midi.PopEvent()); + } + {% endif %} + {% if not debug_printing and usb_midi %} + midiusb.Listen(); + while(midiusb.HasEvents()) + { + HandleMidiMessage(midiusb.PopEvent()); + } + {% endif %} Display(); {% if loop_write_in|length > 0 %} LoopWriteIn(hv); @@ -80,6 +202,19 @@ int main(void) {% if output_parameters|length > 0 %} LoopWriteOut(); {% endif %} + + {% if debug_printing %} + /** Now separately, every 5ms we'll print the top message in our queue if there is one */ + if(now - log_time > 5) + { + log_time = now; + if(!event_log.IsEmpty()) + { + auto msg = event_log.PopFront(); + hardware.som.PrintLine(msg); + } + } + {% endif %} } } @@ -107,6 +242,129 @@ void audiocallback(daisy::AudioHandle::InputBuffer in, daisy::AudioHandle::Outpu hardware.PostProcess(); } +{% if has_midi or usb_midi %} +void HandleMidiOut(uint8_t *midiData, const uint8_t numElements) +{ + {% if has_midi %} + hardware.midi.SendMessage(midiData, numElements); + {% endif %} + {% if not debug_printing and usb_midi %} + midiusb.SendMessage(midiData, numElements); + {% endif %} +} + +void HandleMidiSend(uint32_t sendHash, const HvMessage *m) +{ + switch(sendHash){ + case HV_HASH_NOTEOUT: // __hv_noteout + { + uint8_t note = hv_msg_getFloat(m, 0); + uint8_t velocity = hv_msg_getFloat(m, 1); + uint8_t ch = hv_msg_getFloat(m, 2); + ch %= 16; // drop any pd "ports" + + const uint8_t numElements = 3; + uint8_t midiData[numElements]; + + if (velocity > 0){ + midiData[0] = 0x90 | ch; // noteon + } else { + midiData[0] = 0x80 | ch; // noteoff + } + midiData[1] = note; + midiData[2] = velocity; + + HandleMidiOut(midiData, numElements); + break; + } + case HV_HASH_CTLOUT: + { + uint8_t value = hv_msg_getFloat(m, 0); + uint8_t cc = hv_msg_getFloat(m, 1); + uint8_t ch = hv_msg_getFloat(m, 2); + ch %= 16; + + const uint8_t numElements = 3; + uint8_t midiData[numElements]; + midiData[0] = 0xB0 | ch; // send CC + midiData[1] = cc; + midiData[2] = value; + + HandleMidiOut(midiData, numElements); + break; + } + case HV_HASH_PGMOUT: + { + uint8_t pgm = hv_msg_getFloat(m, 0); + uint8_t ch = hv_msg_getFloat(m, 1); + ch %= 16; + + const uint8_t numElements = 2; + uint8_t midiData[numElements]; + midiData[0] = 0xC0 | ch; // send Program Change + midiData[1] = pgm; + + HandleMidiOut(midiData, numElements); + break; + } + case HV_HASH_TOUCHOUT: + { + uint8_t value = hv_msg_getFloat(m, 0); + uint8_t ch = hv_msg_getFloat(m, 1); + ch %= 16; + + const uint8_t numElements = 2; + uint8_t midiData[numElements]; + midiData[0] = 0xD0 | ch; // send Touch + midiData[1] = value; + + HandleMidiOut(midiData, numElements); + break; + } + case HV_HASH_BENDOUT: + { + uint16_t value = hv_msg_getFloat(m, 0); + uint8_t lsb = value & 0x7F; + uint8_t msb = (value >> 7) & 0x7F; + uint8_t ch = hv_msg_getFloat(m, 1); + ch %= 16; + + const uint8_t numElements = 3; + uint8_t midiData[numElements]; + midiData[0] = 0xE0 | ch; // send Bend + midiData[1] = lsb; + midiData[2] = msb; + + HandleMidiOut(midiData, numElements); + break; + } + case HV_HASH_MIDIOUT: // __hv_midiout + { + const uint8_t numElements = m->numElements; + uint8_t midiData[numElements]; + if (numElements <=4 ) + { + for (int i = 0; i < numElements; ++i) + { + midiData[i] = hv_msg_getFloat(m, i); + } + } + else + { + // we do not support sysex yet + break; + } + + HandleMidiOut(midiData, numElements); + break; + } + default: + break; + } +} +{% endif %} + + /** Receives messages from PD and writes them to the appropriate * index in the `output_data` array, to be written later. */ @@ -121,6 +379,9 @@ static void sendHook(HeavyContextInterface *c, const char *receiverName, uint32_ } } {% endif %} + {% if has_midi or usb_midi %} + HandleMidiSend(receiverHash, m); + {% endif %} } {% if debug_printing %} @@ -135,7 +396,9 @@ static void printHook(HeavyContextInterface *c, const char *printLabel, const ch dst = stpncpy(dst, printLabel, len); dst = stpcpy(dst, " "); dst = stpncpy(dst, msgString, 63-len); - hardware.som.PrintLine(buf); + + /** Regardless of message, let's add the message data to our queue to output */ + event_log.PushBack(buf); } {% endif %} diff --git a/setup.cfg b/setup.cfg index 313d67e8..8455381e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ python_requires = >= 3.7 install_requires = Jinja2>=2.11 importlib_resources>=5.1 - json2daisy>=0.4.2 + wstd2daisy>=0.5.0 [options.entry_points] console_scripts =