|
|
|
@ -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 <cstdio> |
|
|
|
#include <cstdio> |
|
|
|
#include <cstdlib> |
|
|
|
#include <cstdlib> |
|
|
|
|
|
|
|
|
|
|
|
#include <unistd.h> |
|
|
|
#include <unistd.h> |
|
|
|
#include <linux/spi/spi.h> |
|
|
|
#include <linux/spi/spi.h> |
|
|
|
|
|
|
|
|
|
|
|
#include "spi.h" |
|
|
|
#include "spi.h" // FIXME: should rename to gpio.h |
|
|
|
#include "midi.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_SPEED 1000000 |
|
|
|
#define SPI_BUFFER_LENGTH 2 |
|
|
|
#define SPI_BUFFER_LENGTH 2 |
|
|
|
#define SPI_CHANNEL 0 |
|
|
|
#define SPI_CHANNEL 0 |
|
|
|
@ -17,7 +27,13 @@ |
|
|
|
#define MIDI_NOTE_OFFSET 24 |
|
|
|
#define MIDI_NOTE_OFFSET 24 |
|
|
|
#define MIDI_NOTE_LENGTH 61 |
|
|
|
#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 MAX_DAC_INPUT 4095 // 2^12 - 1
|
|
|
|
|
|
|
|
#define TEST_DELAY 5 // seconds to sleep in the test loop
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void |
|
|
|
void |
|
|
|
@ -26,8 +42,8 @@ showUsage(const char* prog_name) |
|
|
|
printf("Usage: %s [options]\n", prog_name); |
|
|
|
printf("Usage: %s [options]\n", prog_name); |
|
|
|
printf(" options:\n"); |
|
|
|
printf(" options:\n"); |
|
|
|
printf(" -t: cycle test voltages on DAC\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(" -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(" -h: show this help \n"); |
|
|
|
printf(" example:\n"); |
|
|
|
printf(" example:\n"); |
|
|
|
printf(" %s -k 20:0\n", prog_name); |
|
|
|
printf(" %s -k 20:0\n", prog_name); |
|
|
|
@ -37,32 +53,92 @@ showUsage(const char* prog_name) |
|
|
|
u32 |
|
|
|
u32 |
|
|
|
convertNoteToDACInput(u8 midi_note) |
|
|
|
convertNoteToDACInput(u8 midi_note) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
#if USE_10V_MAX_CV |
|
|
|
|
|
|
|
u32 dac = midi_note * 68 / 2; |
|
|
|
|
|
|
|
#else |
|
|
|
// NOTE: assuming
|
|
|
|
// NOTE: assuming
|
|
|
|
// DAC: 5V = 4080 / 4095 * Vref, Vref ~= 5.0184
|
|
|
|
// DAC: 5V = 4080 / 4095 * Vref, Vref ~= 5.0184
|
|
|
|
// 1 half step/note = 1/12V = dac 68
|
|
|
|
// 1 half step/note = 1/12V = dac 68
|
|
|
|
// 1 octave = 1V = dac 816
|
|
|
|
// 1 octave = 1V = dac 816
|
|
|
|
u32 dac = midi_note * 68; |
|
|
|
u32 dac = midi_note * 68; |
|
|
|
|
|
|
|
#endif |
|
|
|
(dac > MAX_DAC_INPUT) ? dac = MAX_DAC_INPUT : dac; |
|
|
|
(dac > MAX_DAC_INPUT) ? dac = MAX_DAC_INPUT : dac; |
|
|
|
|
|
|
|
|
|
|
|
return dac; |
|
|
|
return dac; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void |
|
|
|
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) |
|
|
|
while(1) |
|
|
|
{ |
|
|
|
{ |
|
|
|
printf("\n--------------------\n"); |
|
|
|
printf("\n--------------------\n"); |
|
|
|
snd_seq_event_t *ev = nullptr; |
|
|
|
snd_seq_event_t *ev = nullptr; |
|
|
|
snd_seq_event_input(seq->seq_handle, &ev); |
|
|
|
snd_seq_event_input(seq->seq_handle, &ev); |
|
|
|
i32 note = midiProcess(ev); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (note >= 0) { |
|
|
|
// FIXME: we should only be processing notes from the first channel
|
|
|
|
i32 dac_in = convertNoteToDACInput(note); |
|
|
|
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
|
|
|
|
// TODO: could check return value here for SPI error
|
|
|
|
spiSendVoltage(spi, dac_in); |
|
|
|
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}; |
|
|
|
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); |
|
|
|
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) { |
|
|
|
if (spi.fd < 0) { |
|
|
|
printf("error opening SPI device\n"); |
|
|
|
printf("error opening SPI device\n"); |
|
|
|
return 1; |
|
|
|
return 1; |
|
|
|
@ -87,10 +168,10 @@ main(i32 argc, char* argv[]) |
|
|
|
if (!midiOpen(&seq)) |
|
|
|
if (!midiOpen(&seq)) |
|
|
|
return 1; |
|
|
|
return 1; |
|
|
|
|
|
|
|
|
|
|
|
while ((opt = getopt(argc, argv, "tk:f:h")) != -1) { |
|
|
|
while ((opt = getopt(argc, argv, "tlk:h")) != -1) { |
|
|
|
switch (opt) { |
|
|
|
switch (opt) { |
|
|
|
case 'f': |
|
|
|
case 'l': |
|
|
|
printf("TODO: parse and play sequence from midi file\n"); |
|
|
|
loopMIDI(&seq, &spi, &gpio); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'k': |
|
|
|
case 'k': |
|
|
|
if (!midiConnect(&seq, optarg)) { |
|
|
|
if (!midiConnect(&seq, optarg)) { |
|
|
|
@ -98,10 +179,10 @@ main(i32 argc, char* argv[]) |
|
|
|
exit(1); |
|
|
|
exit(1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
loopMIDI(&seq, &spi); |
|
|
|
loopMIDI(&seq, &spi, &gpio); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case 't': |
|
|
|
case 't': |
|
|
|
printf("TODO: cycle test voltages to SPI DAC\n"); |
|
|
|
loopTestVoltages(&spi); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'h': |
|
|
|
case 'h': |
|
|
|
default: |
|
|
|
default: |
|
|
|
|