From ca4b657e4b92d1725fff64df286ada92d7a9c666 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Sun, 16 Apr 2023 17:17:00 +0000 Subject: [PATCH] add initial support for GPIO gate pulses --- Makefile | 2 +- src/main.cpp | 103 +++++++++++++++++++++++++++++++++++++++++++++------ src/midi.cpp | 21 +++++++---- src/midi.h | 9 ++++- src/spi.cpp | 52 ++++++++++++++++++++++++++ src/spi.h | 19 +++++++++- 6 files changed, 184 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index cce8093..78abb16 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX=g++ CXXFLAGS= -std=c++11 -g -ggdb3 -Wall -LDFLAGS=-lasound +LDFLAGS=-lasound -lgpiod OBJDIR = build SRCDIR = src diff --git a/src/main.cpp b/src/main.cpp index 5c8b51c..bed3941 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,14 +1,24 @@ +// TODO: update README with wiring instructions for SPI to DAC +// TODO: DAC voltage stepping +// if we use DAC input 4080 = 5V, then we can do steps of 1/12V on exactly +// the correct DAC value. Would then need to set Vref to: +// DAC: 5V = 4080 / 4095 * Vref, Vref ~= 5.0184 + + #include #include #include #include -#include "spi.h" +#include "spi.h" // FIXME: should rename to gpio.h #include "midi.h" +#define GPIO_CHIP_NAME "gpiochip1" +#define GPIO_LINE_NUM 82 // GPIO Pin #38 on 'le-potato' + #define SPI_SPEED 1000000 #define SPI_BUFFER_LENGTH 2 #define SPI_CHANNEL 0 @@ -17,7 +27,13 @@ #define MIDI_NOTE_OFFSET 24 #define MIDI_NOTE_LENGTH 61 +// NOTE: the max output from the MCP4921 DAC is 5.5V, but we have enough DAC +// resolution to use a voltage amplifier with a gain of 2 to extend the range +// to a full 10V. this gives us a range of 121 notes, most of the full MIDI +// range +#define USE_10V_MAX_CV true #define MAX_DAC_INPUT 4095 // 2^12 - 1 +#define TEST_DELAY 5 // seconds to sleep in the test loop void @@ -26,8 +42,8 @@ showUsage(const char* prog_name) printf("Usage: %s [options]\n", prog_name); printf(" options:\n"); printf(" -t: cycle test voltages on DAC\n"); + printf(" -l: listen for midi events\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); @@ -37,32 +53,92 @@ showUsage(const char* prog_name) u32 convertNoteToDACInput(u8 midi_note) { +#if USE_10V_MAX_CV + u32 dac = midi_note * 68 / 2; +#else // NOTE: assuming // DAC: 5V = 4080 / 4095 * Vref, Vref ~= 5.0184 // 1 half step/note = 1/12V = dac 68 // 1 octave = 1V = dac 816 u32 dac = midi_note * 68; +#endif (dac > MAX_DAC_INPUT) ? dac = MAX_DAC_INPUT : dac; return dac; } void -loopMIDI(alsa_sequencer* seq, spi_connection* spi) +loopMIDI(alsa_sequencer* seq, spi_connection* spi, gpio_info* gpio) { + // TODO: there's probably a more elegant way to handle this, but this was + // the first idea that came up, and seems to work + // NOTE: MIDI Note '0' is a vaild note, but 255 is not. So, we'll use 255 + // as a placeholder to represent an unused PLAYING_NOTE + static const u32 MAX_NOTES = 6; + static u8 PLAYING_NOTES[MAX_NOTES] = {255,255,255,255,255,255}; + 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); + // FIXME: we should only be processing notes from the first channel + alsa_note_event nev = midiProcess(ev); + bool note_tracked = false; + + for (u32 i = 0; i < MAX_NOTES; i++) { + if (PLAYING_NOTES[i] == nev.note) { + note_tracked = true; + + if (nev.note_on) { // do nothing + break; + } else { // remove note from tracked list + assert(gpioSetValue(gpio, 0)); // send set gate off + PLAYING_NOTES[i] = 255; + break; + } + } + } + + // send note voltage to DAC, and add note to tracked list + if (!note_tracked && nev.note_on ) { + for (u32 i = 0; i < MAX_NOTES; i++) { + if (PLAYING_NOTES[i] == 255) { + PLAYING_NOTES[i] = nev.note; + assert(gpioSetValue(gpio, 1)); // send set gate on + break; + } + } + i32 dac_in = convertNoteToDACInput(nev.note); // TODO: could check return value here for SPI error spiSendVoltage(spi, dac_in); } + + printf("PLAYING_NOTES: %d,%d,%d,%d,%d,%d\n", + PLAYING_NOTES[0], + PLAYING_NOTES[1], + PLAYING_NOTES[2], + PLAYING_NOTES[3], + PLAYING_NOTES[4], + PLAYING_NOTES[5]); + } +} + +void +loopTestVoltages(spi_connection* spi) +{ + u32 one_volt = 408; + + for (u32 i = 0;; i++) { +#if 0 + u32 volts = (i % 2 == 0) ? 0 : one_volt; + spiSendVoltage(spi, volts); +#else + spiSendVoltage(spi, 10 * one_volt); +#endif + sleep(TEST_DELAY); } } @@ -79,6 +155,11 @@ main(i32 argc, char* argv[]) 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); + gpio_info gpio = {0}; + + if (!gpioInit(&gpio, GPIO_CHIP_NAME, GPIO_LINE_NUM)) + return 1; + if (spi.fd < 0) { printf("error opening SPI device\n"); return 1; @@ -87,10 +168,10 @@ main(i32 argc, char* argv[]) if (!midiOpen(&seq)) return 1; - while ((opt = getopt(argc, argv, "tk:f:h")) != -1) { + while ((opt = getopt(argc, argv, "tlk:h")) != -1) { switch (opt) { - case 'f': - printf("TODO: parse and play sequence from midi file\n"); + case 'l': + loopMIDI(&seq, &spi, &gpio); break; case 'k': if (!midiConnect(&seq, optarg)) { @@ -98,10 +179,10 @@ main(i32 argc, char* argv[]) exit(1); } - loopMIDI(&seq, &spi); + loopMIDI(&seq, &spi, &gpio); break; case 't': - printf("TODO: cycle test voltages to SPI DAC\n"); + loopTestVoltages(&spi); break; case 'h': default: diff --git a/src/midi.cpp b/src/midi.cpp index 5f4dc33..4b773bb 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -63,26 +63,31 @@ midiConnect(alsa_sequencer* seq, const char* midi_device) return false; } -i32 +alsa_note_event midiProcess(const snd_seq_event_t* ev) { assert(ev != nullptr); - i32 note = -1; + alsa_note_event nev = {0}; if((ev->type == SND_SEQ_EVENT_NOTEON) || (ev->type == SND_SEQ_EVENT_NOTEOFF)) { const char *type = (ev->type==SND_SEQ_EVENT_NOTEON) ? "on " : "off"; - printf("[%d] Note %s: %d, 0x%2x vel(%2x)\n", - ev->time.tick, type, + printf("[%d] Note %s: %d, 0x%2x vel(%2x), channel: %d\n", + ev->time.tick, + type, ev->data.note.note, ev->data.note.note, - ev->data.note.velocity); - note = ev->data.note.note; + ev->data.note.velocity, + ev->data.note.channel); + + nev.note = ev->data.note.note; + nev.channel = ev->data.note.channel; + nev.note_on = (ev->type == SND_SEQ_EVENT_NOTEON) ? true : false; } else if(ev->type == SND_SEQ_EVENT_CONTROLLER) { - printf("[%d] Control: %2x val(%2x)\n", + printf("[%d] Control: 0x%2x val(%2x)\n", ev->time.tick, ev->data.control.param, ev->data.control.value); @@ -92,7 +97,7 @@ midiProcess(const snd_seq_event_t* ev) printf("[%d] Unknown: Unhandled Event Received\n", ev->time.tick); } - return note; + return nev; } i32 diff --git a/src/midi.h b/src/midi.h index 39678fd..16fe738 100644 --- a/src/midi.h +++ b/src/midi.h @@ -14,11 +14,18 @@ struct alsa_sequencer snd_seq_addr_t dest; }; +struct alsa_note_event +{ + u8 note; + u8 channel; + bool note_on; +}; + bool midiOpen(alsa_sequencer* seq); bool midiConnect(alsa_sequencer* seq, const char* midi_device); -i32 midiProcess(const snd_seq_event_t* ev); +alsa_note_event midiProcess(const snd_seq_event_t* ev); i32 midiClose(alsa_sequencer* seq); diff --git a/src/spi.cpp b/src/spi.cpp index 80d37a7..0a2520b 100644 --- a/src/spi.cpp +++ b/src/spi.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -9,6 +10,57 @@ #include "spi.h" +// GPIO + +bool +gpioInit(gpio_info* gpio, const char* chip_name, u32 line_num) +{ + strncpy(gpio->chip_name, chip_name, sizeof(gpio->chip_name)); + gpio->line_num = line_num; + gpio->chip = gpiod_chip_open_by_name(gpio->chip_name); + + if (!gpio->chip) { + printf("Error opening chip, %s\n", gpio->chip_name); + return false; + } + + gpio->line = gpiod_chip_get_line(gpio->chip, line_num); + + if (!gpio->line) { + printf("Error opening line, chip: %s, line: %d\n", gpio->chip_name, line_num); + gpiod_chip_close(gpio->chip); + return false; + } + + i32 ret = gpiod_line_request_output(gpio->line, "Consumer", 0); + + if (ret < 0) { + printf("Error setting line output, chip: %s, line: %d\n", gpio->chip_name, line_num); + gpiod_line_release(gpio->line); + gpiod_chip_close(gpio->chip); + return false; + } + + return true; +} + +bool +gpioSetValue(gpio_info* gpio, u32 value) +{ + assert(gpio); + i32 ret = gpiod_line_set_value(gpio->line, value); + + if (ret < 0) { + printf("Error setting line value, chip: %s, line: %d\n", gpio->chip_name, gpio->line_num); + return false; + } + + return true; +} + + +// SPI + i32 open_device(u8 channel, u32 speed, u8 mode_flags); diff --git a/src/spi.h b/src/spi.h index 5678243..268e5d7 100644 --- a/src/spi.h +++ b/src/spi.h @@ -3,10 +3,28 @@ #include #include +#include #include "types.h" +// GPIO + +struct gpio_info +{ + char chip_name[16]; + u32 line_num; + gpiod_chip* chip; + gpiod_line* line; +}; + +bool gpioInit(gpio_info* gpio, const char* chip_name, u32 line_num); + +bool gpioSetValue(gpio_info* gpio, u32 value); + + +// SPI + struct spi_connection { i32 fd; @@ -17,7 +35,6 @@ struct spi_connection u8* buffer; }; - spi_connection spiInit(u32 speed, u32 max_dac_input, u8* buf, u32 buf_len, u8 flags, u8 channel); bool spiWrite(i32 fd, u32 speed, u8* buf, u32 buf_len);