Initial commit
This commit is contained in:
commit
91a1e2e312
22 files changed
+885
No files matched your search
@@ -0,0 +1,166 @@
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#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;
|
||||
}
|
||||
Reference in new issue
Block a user