parse midi messages with ALSA sequencer API, and output SPI message to MCP4921 DAC for controlling an analog synthesizer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

124 lines
2.7 KiB

// TODO: gate on/off controls, can just use hardware GPIO in digital mode
// 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 <cstdlib>
#include <unistd.h>
#include <linux/spi/spi.h>
#include "spi.h"
#include "midi.h"
#define SPI_SPEED 1000000
#define SPI_BUFFER_LENGTH 2
#define SPI_CHANNEL 0
#define SPI_FLAGS SPI_MODE_3|SPI_CS_HIGH
#define MIDI_NOTE_OFFSET 24
#define MIDI_NOTE_LENGTH 61
#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)
{
// 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;
(dac > MAX_DAC_INPUT) ? dac = MAX_DAC_INPUT : dac;
return dac;
}
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(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);
if (spi.fd < 0) {
printf("error opening SPI device\n");
return 1;
}
if (!midiOpen(&seq))
return 1;
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);
}
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);
}