Files
audio-playground/main.c
T
2026-10-27 22:10:43 +01:00

167 lines
5.1 KiB
C

#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(&note, 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;
}