From be3315e5e3d54d265a0cb41c973da8a016c421b8 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Fri, 17 Mar 2023 15:05:08 -0400 Subject: [PATCH] add cli option to connect to midi device --- src/main.cpp | 80 +++++++++++++++++++++++++++++++++++++++++++--------- src/midi.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/midi.h | 10 +++++-- 3 files changed, 149 insertions(+), 21 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 61750d7..5c8b51c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,8 @@ +#include +#include + +#include #include #include "spi.h" @@ -15,6 +19,21 @@ #define MAX_DAC_INPUT 4095 // 2^12 - 1 + +void +showUsage(const char* prog_name) +{ + printf("Usage: %s [options]\n", prog_name); + printf(" options:\n"); + printf(" -t: cycle test voltages on DAC\n"); + printf(" -k sequencer-port: attempt to connect to port \n"); + printf(" -f midi-file: play the first track, first channel of a MIDI file\n"); + printf(" -h: show this help \n"); + printf(" example:\n"); + printf(" %s -k 20:0\n", prog_name); + printf(" attempt to connect to MIDI input device on channel:port 20:0\n"); +} + u32 convertNoteToDACInput(u8 midi_note) { @@ -28,11 +47,34 @@ convertNoteToDACInput(u8 midi_note) return dac; } -// TODO: add an argument option that slowly cycles between 0-1V for tuning an -// oscillator +void +loopMIDI(alsa_sequencer* seq, spi_connection* spi) +{ + while(1) + { + printf("\n--------------------\n"); + snd_seq_event_t *ev = nullptr; + snd_seq_event_input(seq->seq_handle, &ev); + i32 note = midiProcess(ev); + + if (note >= 0) { + i32 dac_in = convertNoteToDACInput(note); + + // TODO: could check return value here for SPI error + spiSendVoltage(spi, dac_in); + } + } +} + i32 -main() +main(i32 argc, char* argv[]) { + if (argc == 1) { + showUsage(argv[0]); + exit(0); + } + + i32 opt = 0; alsa_sequencer seq = {0}; u8 spi_buf[SPI_BUFFER_LENGTH] = {0}; spi_connection spi = spiInit(SPI_SPEED, MAX_DAC_INPUT, spi_buf, SPI_BUFFER_LENGTH, SPI_FLAGS, SPI_CHANNEL); @@ -45,20 +87,30 @@ main() if (!midiOpen(&seq)) return 1; - while(1) - { - printf("\n--------------------\n"); - snd_seq_event_t *ev = nullptr; - snd_seq_event_input(seq.seq_handle, &ev); - i32 note = midiProcess(ev); + while ((opt = getopt(argc, argv, "tk:f:h")) != -1) { + switch (opt) { + case 'f': + printf("TODO: parse and play sequence from midi file\n"); + break; + case 'k': + if (!midiConnect(&seq, optarg)) { + printf("Error connecting to MIDI device, %s\n", optarg); + exit(1); + } - if (note > 0) { - i32 dac_in = convertNoteToDACInput(note); - - // TODO: could check return value here for SPI error - spiSendVoltage(&spi, dac_in); + loopMIDI(&seq, &spi); + break; + case 't': + printf("TODO: cycle test voltages to SPI DAC\n"); + break; + case 'h': + default: + showUsage(argv[0]); + exit(1); } } + midiClose(&seq); return spiClose(spi.fd); } + diff --git a/src/midi.cpp b/src/midi.cpp index e1c66da..5f4dc33 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -1,9 +1,18 @@ +#include #include +#include #include "midi.h" +// forward declarations + +bool parseDeviceStr(snd_seq_addr_t& src, const char* midi_device); + + +// interface + bool midiOpen(alsa_sequencer* seq) { @@ -12,27 +21,48 @@ midiOpen(alsa_sequencer* seq) return false; } - if (snd_seq_set_client_name(seq->seq_handle, "Midi Listener")) { + if (snd_seq_set_client_name(seq->seq_handle, "midi_to_cv")) { printf("Could not set client name\n"); return false; } - seq->in_port = snd_seq_create_simple_port(seq->seq_handle, + seq->dest.client = snd_seq_client_id(seq->seq_handle); + seq->dest.port = snd_seq_create_simple_port(seq->seq_handle, "listen:in", SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION); - if (seq->in_port < 0) { + if (seq->dest.port < 0) { printf("Could not open port\n"); return false; } - i32 client_id = snd_seq_client_id(seq->seq_handle); - printf("client id: %d, MIDI port: %d\n", client_id, seq->in_port); + printf("host client id: %d, host port: %d\n", seq->dest.client, seq->dest.port); return true; } +bool +midiConnect(alsa_sequencer* seq, const char* midi_device) +{ + if (!parseDeviceStr(seq->src, midi_device)) { + printf("invalid device string\n"); + return false; + } + + snd_seq_port_subscribe_malloc(&seq->subscription); + snd_seq_port_subscribe_set_sender(seq->subscription, &seq->src); + snd_seq_port_subscribe_set_dest(seq->subscription, &seq->dest); + snd_seq_port_subscribe_set_queue(seq->subscription, 1); + snd_seq_port_subscribe_set_time_update(seq->subscription, 1); + snd_seq_port_subscribe_set_time_real(seq->subscription, 1); + + if (snd_seq_subscribe_port(seq->seq_handle, seq->subscription) == 0) + return true; + + return false; +} + i32 midiProcess(const snd_seq_event_t* ev) { @@ -64,3 +94,43 @@ midiProcess(const snd_seq_event_t* ev) return note; } + +i32 +midiClose(alsa_sequencer* seq) +{ + snd_seq_port_subscribe_free(seq->subscription); + return snd_seq_close(seq->seq_handle); +} + + +// internal + +bool +parseDeviceStr(snd_seq_addr_t& src, const char* midi_device) +{ + // NOTE: midi_device should be in the form 'client:port', where each number + // is an unsigned char. So max string length is 'xxx:xxx" = 7 + std::string s(midi_device); + + if (s.size() > 0 && s.size() < 8) { + u32 split = s.find(':'); + + if (split > 0 && split != std::string::npos) { + std::string sub1 = s.substr(0, split); + std::string sub2 = s.substr(split + 1, s.size()); + + u32 client= strtoul(sub1.c_str(), nullptr, 10); + u32 port = strtoul(sub2.c_str(), nullptr, 10); + + if (errno == 0 && client > 0 && client < 256 && port >= 0 && port < 256) + { + src.client = client; + src.port = port; + return true; + } + } + } + + return false; +} + diff --git a/src/midi.h b/src/midi.h index 41d4571..39678fd 100644 --- a/src/midi.h +++ b/src/midi.h @@ -9,10 +9,16 @@ struct alsa_sequencer { snd_seq_t* seq_handle; - i32 in_port; + snd_seq_port_subscribe_t* subscription; + snd_seq_addr_t src; + snd_seq_addr_t dest; }; -bool midiOpen(alsa_sequencer* handles); +bool midiOpen(alsa_sequencer* seq); + +bool midiConnect(alsa_sequencer* seq, const char* midi_device); i32 midiProcess(const snd_seq_event_t* ev); + +i32 midiClose(alsa_sequencer* seq);