#include #include #include #include #include #include "synth.h" #define ALSA_CHK(stmt, msg) do { \ int alsa_chk_err = (stmt); \ if (alsa_chk_err < 0) { \ fprintf(stderr, "%s%s\n", msg, snd_strerror(alsa_chk_err)); \ return 1; \ } \ } while (0) #define N_CHANNELS 1 #define SAMPLE_RATE 44100 #define PLAYBACK_LATENCY 50000 // 50 ms #define PLAYBACK_BUFFER_SIZE 512 static int write_audio(snd_pcm_t *handle, const int16_t *buffer) { snd_pcm_uframes_t offset = 0; while (offset < PLAYBACK_BUFFER_SIZE) { snd_pcm_sframes_t written = snd_pcm_writei( handle, buffer + offset * N_CHANNELS, PLAYBACK_BUFFER_SIZE - offset); if (written < 0) { int err = snd_pcm_recover(handle, (int)written, 1); ALSA_CHK(err, "Could not write to alsa device: "); continue; } if (written == 0) { int err = snd_pcm_wait(handle, -1); if (err < 0) { err = snd_pcm_recover(handle, err, 1); ALSA_CHK(err, "Could not write to alsa device: "); } continue; } offset += (snd_pcm_uframes_t)written; } return 0; } static int midi_open(snd_seq_t **seq_handle, int src_client, int src_port) { int err = snd_seq_open(seq_handle, "default", SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK); ALSA_CHK(err, "Error opening ALSA sequencer: "); err = snd_seq_set_client_name(*seq_handle, "Midi Listener"); ALSA_CHK(err, "Error setting sequencer client name: "); uint32_t caps = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; int in_port = snd_seq_create_simple_port(*seq_handle, "listen:in", caps, SND_SEQ_PORT_TYPE_APPLICATION); ALSA_CHK(in_port, "Error creating sequencer port: "); err = snd_seq_connect_from(*seq_handle, in_port, src_client, src_port); ALSA_CHK(err, "Error connecting MIDI source: "); return 0; } static float midi_note_to_freq(int midi) { int note_offset = midi - 69; return 440.0f * exp2f((float) note_offset / 12.0f); } static Waveform cycle_waveform(Waveform form) { Waveform new_form = form + 1; if (new_form > WAVE_TRIANGLE) { new_form = WAVE_SINE; } const char *waveform_names[] = {"NONE", "SINE", "SQUARE", "SAW", "TRIANGLE"}; printf("Waveform changed to: %s\n", waveform_names[new_form]); return new_form; } static int parse_port(int *src_client, int *src_port, const char *str) { return sscanf(str, "%d:%d", src_client, src_port) == 2; } int main(int argc, const char** argv) { if (argc < 2) { fprintf(stderr, "Usage: %s 20:0\n", argv[0]); return 1; } int src_client = 0; int src_port = 0; if (!parse_port(&src_client, &src_port, argv[1])) { fprintf(stderr, "Unable to parse midi port %s\n", argv[1]); return 1; } int err = 0; snd_pcm_t *handle = 0; // Init alsa audio err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0); ALSA_CHK(err, "Could not open alsa device: "); err = snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, N_CHANNELS, SAMPLE_RATE, 1, PLAYBACK_LATENCY); ALSA_CHK(err, "Could not set alsa params: "); // Init alsa midi snd_seq_t *seq_handle = 0; err = midi_open(&seq_handle, src_client, src_port); if (err) { printf("Error initializing MIDI: %d\n", err); return err; } // Main loop Note note = { .form = WAVE_SAW, .attack_ms = 100.0f, .release_ms = 50.0f }; int16_t buf[PLAYBACK_BUFFER_SIZE] = {0}; for (;;) { // Drain queued MIDI events for (;;) { snd_seq_event_t *ev = NULL; int ret = snd_seq_event_input(seq_handle, &ev); if (ret == -EAGAIN) { break; } ALSA_CHK(ret, "Failed to read midi event: "); if (!ev) { continue; } switch (ev->type) { case SND_SEQ_EVENT_NOTEON: { note.is_playing = ev->data.note.velocity != 0; note.freq = midi_note_to_freq(ev->data.note.note); } break; case SND_SEQ_EVENT_NOTEOFF: { note.is_playing = false; } break; case SND_SEQ_EVENT_CONTROLLER: { note.filter_cutoff = (float) ev->data.control.value / 64.0f; printf("filter cutoff: %f\n", note.filter_cutoff); } break; case SND_SEQ_EVENT_PGMCHANGE: { note.form = cycle_waveform(note.form); } break; default: printf("%d\n", ev->type); break; } snd_seq_free_event(ev); } play_note(¬e, buf, PLAYBACK_BUFFER_SIZE); if (write_audio(handle, buf) < 0) { return 1; } } snd_pcm_drain(handle); // wait for playback to finish (not stricty needed depending on requested latency) snd_pcm_close(handle); return 0; }