diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.c | 185 | ||||
| -rw-r--r-- | src/sounds.c | 248 | ||||
| -rw-r--r-- | src/sounds.h | 137 |
3 files changed, 445 insertions, 125 deletions
@@ -1,119 +1,10 @@ #include <stdio.h> +#include "sounds.h" #include <alsa/asoundlib.h> -#include <math.h> #include <raylib.h> #include <pthread.h> #include <stdbool.h> -#define SAMPLE_RATE 48000 -#define PERIOD_SIZE 480 -#define AMPLITUDE 10000 - -#define check(ret) \ - do { \ - int res = (ret); \ - if (res < 0) { \ - fprintf(stderr, "%s:%d ERROR: %s (%d)\n", \ - __FILE__, __LINE__, snd_strerror(res), res); \ - exit(1); \ - } \ - } while (0) - -typedef struct { - snd_pcm_t * pcm; - float *freqs; - size_t freqs_count; - bool should_stop; -} sound_thread_meta; - -int set_hw_params(snd_pcm_t *pcm) { - snd_pcm_hw_params_t *hw_params; - - snd_pcm_hw_params_alloca(&hw_params); - - check(snd_pcm_hw_params_any(pcm, hw_params)); - - unsigned int resample = 1; - check(snd_pcm_hw_params_set_rate_resample(pcm, hw_params, resample)); - check(snd_pcm_hw_params_set_access(pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)); - check(snd_pcm_hw_params_set_format(pcm, hw_params, SND_PCM_FORMAT_S16_LE)); - check(snd_pcm_hw_params_set_channels(pcm, hw_params, 1)); - check(snd_pcm_hw_params_set_rate(pcm, hw_params, SAMPLE_RATE, 0)); - snd_pcm_uframes_t period_size = PERIOD_SIZE; - check(snd_pcm_hw_params_set_period_size_near(pcm, hw_params, &period_size, 0)); - snd_pcm_uframes_t buffer_size = period_size * 3; - check(snd_pcm_hw_params_set_buffer_size_near(pcm, hw_params, &buffer_size)); - check(snd_pcm_hw_params(pcm, hw_params)); - - return 0; -} - -short *gen_sine(short *buffer, size_t sample_count, float freq, float *phase) { - for (size_t i = 0; i < sample_count; i++) { - buffer[i] = AMPLITUDE * sinf(*phase); - *phase += 2 * M_PI * freq / SAMPLE_RATE; - if (*phase >= 2 * M_PI) *phase -= 2 * M_PI; - } - - return buffer; -} - -short *gen_square(short *buffer, size_t sample_count, float freq, float *phase) { - int samples_full_cycle = (float)SAMPLE_RATE / freq; - int samples_half_cycle = samples_full_cycle / 2.0f; - for (size_t i = 0; i < sample_count; i++) { - buffer[i] = *phase < samples_half_cycle ? AMPLITUDE : -AMPLITUDE; - *phase = (((int)*phase + 1) % samples_full_cycle); - } - return buffer; -} - -short *gen_saw(short *buffer, size_t sample_count, float freq, float *phase) { - int samples_full_cycle = (float)SAMPLE_RATE / freq; - int step = AMPLITUDE / samples_full_cycle; - - for (size_t i = 0; i < sample_count; i++) { - buffer[i] = *phase; - - *phase += step; - if (*phase >= AMPLITUDE) { - *phase = -AMPLITUDE; - } - } - - return buffer; -} - -void *sound_thread_generate(void *ptr) { - sound_thread_meta *meta = ptr; - - short buffer[PERIOD_SIZE]; - float phase = 0.0f; - - float prev_freq = meta->freqs[0]; - - while (!meta->should_stop) { - if (meta->freqs_count <= 0) { - continue; - } - - float freq = meta->freqs[0]; - if (freq != prev_freq) { - printf("%f\n", freq); - prev_freq = freq; - } - - gen_saw(buffer, PERIOD_SIZE, freq, &phase); - - snd_pcm_sframes_t written = snd_pcm_writei(meta->pcm, buffer, PERIOD_SIZE); - if (written < 0) { - snd_pcm_prepare(meta->pcm); // recover from xrun - } - } - - return NULL; -} - typedef struct { char key; float freq; @@ -139,25 +30,70 @@ int main() { InitWindow(800, 800, "crynth"); SetTargetFPS(60); - float freqs[10]; - - freqs[0] = 220; + message_queue queue; + mqueue_init(&queue); - sound_thread_meta soundgen = { + sound_thread_meta sound_thread_params = { .pcm = sound_device, - .freqs = freqs, - .freqs_count = 1, - .should_stop = false, + .queue = &queue, }; pthread_t sound_thread; - pthread_create(&sound_thread, NULL, sound_thread_generate, &soundgen); + pthread_create(&sound_thread, NULL, sound_thread_start, &sound_thread_params); + + /* synth_message param_message = { */ + /* .type = MSG_PARAM_CHANGE, */ + /* .param_change = { */ + /* .param_type = PARAM_OSC, */ + /* .value = OSC_SINE, */ + /* }, */ + /* }; */ + /* mqueue_push(&queue, param_message); */ + /* param_message = (synth_message){ */ + /* .type = MSG_PARAM_CHANGE, */ + /* .param_change = { */ + /* .param_type = PARAM_VOLUME, */ + /* .value = 0.2, */ + /* }, */ + /* }; */ + /* mqueue_push(&queue, param_message); */ + + int keys[12] = { + KEY_Z, + KEY_S, + KEY_X, + KEY_D, + KEY_C, + KEY_V, + KEY_G, + KEY_B, + KEY_H, + KEY_N, + KEY_J, + KEY_M, + }; while (!WindowShouldClose()) { - if (IsKeyDown(KEY_SPACE)) { - soundgen.freqs[0] = 900; - } else { - soundgen.freqs[0] = 500; + for (int i = 0; i < 12; i++) { + if (IsKeyPressed(keys[i])) { + synth_message message = { + .type = MSG_NOTE_ON, + .note = { + .note_id = i, + }, + }; + mqueue_push(&queue, message); + } + + if (IsKeyReleased(keys[i])) { + synth_message message = { + .type = MSG_NOTE_OFF, + .note = { + .note_id = i, + }, + }; + mqueue_push(&queue, message); + } } BeginDrawing(); @@ -165,12 +101,11 @@ int main() { EndDrawing(); } - soundgen.should_stop = true; + synth_message stop_msg = {.type = MSG_STOP}; + mqueue_push(&queue, stop_msg); CloseWindow(); pthread_join(sound_thread, NULL); - - check(snd_pcm_drop(sound_device)); check(snd_pcm_close(sound_device)); return 0; diff --git a/src/sounds.c b/src/sounds.c new file mode 100644 index 0000000..fe6b3d3 --- /dev/null +++ b/src/sounds.c @@ -0,0 +1,248 @@ +#include "sounds.h" + +int mqueue_get(message_queue *q, synth_message *msg) { + pthread_mutex_lock(&(q)->lock); + if ((q)->tail == (q)->head) { + pthread_mutex_unlock(&(q)->lock); + return 1; + } + + *(msg) = (q)->buffer[(q)->tail]; + (q)->tail = ((q)->tail + 1) % MESSAGE_QUEUE_SIZE; + + pthread_mutex_unlock(&(q)->lock); + return 0; +} + +/* osc_triangle_gen */ + +void osc_sine_gen(float *buffer, size_t sample_count, float *phase, + float phase_inc) { + for (size_t i = 0; i < sample_count; i++) { + buffer[i] += sinf(*phase); + *phase += phase_inc; + if (*phase >= 2 * M_PI) + *phase -= 2 * M_PI; + } +} + +void osc_square_gen(float *buffer, size_t sample_count, float *phase, + float phase_inc) { + for (size_t i = 0; i < sample_count; i++) { + buffer[i] += sinf(*phase) ? 1 : -1; + *phase += phase_inc; + if (*phase >= 2 * M_PI) + *phase -= 2 * M_PI; + } +} + +void osc_saw_gen(float *buffer, size_t sample_count, float *phase, + float phase_inc) { + for (size_t i = 0; i < sample_count; i++) { + buffer[i] += (*phase / M_PI) - 1; + *phase += phase_inc; + if (*phase >= 2 * M_PI) + *phase -= 2 * M_PI; + } +} + +oscilator_func osc_get(oscilator_type type) { + switch (type) { + case OSC_SINE: + return osc_sine_gen; + case OSC_SAW: + return osc_saw_gen; + case OSC_SQUARE: + return osc_square_gen; + default: + return osc_sine_gen; + } +} + +void set_note_on(synth_voices *voices, size_t note_id) { + if (note_id >= voices->size) { + return; + } + voices->buffer[note_id].active = true; +} + +void set_note_off(synth_voices *voices, size_t note_id) { + if (note_id >= voices->size) { + return; + } + voices->buffer[note_id].active = false; +} + +void set_all_notes_off(synth_voices *voices) { + for (size_t i = 0; i < voices->size; i++) { + if (voices->buffer[i].active) { + voices->buffer[i].active = false; + } + } +} + +void set_param(synth_params *params, param_type type, float value) { + switch (type) { + case PARAM_OSC: { + params->oscilator_type = (int)value; + break; + } + case PARAM_VOLUME: { + params->master_volume = value; + break; + } + } +} + +void generate_voices(synth_voices *voices, synth_params *params, float *buffer, + size_t buffer_size) { + oscilator_func oscilator = osc_get(params->oscilator_type); + for (size_t i = 0; i < voices->size; i++) { + synth_voice *voice = &voices->buffer[i]; + if (!voice->active) { + continue; + } + + if (voice->phase_inc == 0) { + voice->phase_inc = 2 * M_PI * voice->freq / SAMPLE_RATE; + } + oscilator(buffer, buffer_size, &voice->phase, voice->phase_inc); + } +} + +void post_process(synth_params *params, float *scratch_buffer, + size_t buffer_size) { + for (size_t i = 0; i < buffer_size; i++) { + scratch_buffer[i] *= params->master_volume; + } +} + +void prepare_output(float *scratch_buffer, short *output_buffer, + size_t buffer_size) { + for (size_t i = 0; i < buffer_size; i++) { + output_buffer[i] = scratch_buffer[i] * 0.2f * 32767; + } +} + +void sound_loop_start(snd_pcm_t *pcm, message_queue *queue, + synth_voices *voices, synth_params *params) { + bool should_stop = false; + + short output_buffer[PERIOD_SIZE]; + float scratch_buffer[PERIOD_SIZE]; + + while (!should_stop) { + synth_message msg; + while (mqueue_get(queue, &msg) == 0 && !should_stop) { + switch (msg.type) { + case MSG_NOTE_ON: { + size_t note_id = msg.note.note_id; + set_note_on(voices, note_id); + break; + } + case MSG_NOTE_OFF: { + size_t note_id = msg.note.note_id; + set_note_off(voices, note_id); + break; + } + case MSG_ALL_NOTES_OFF: { + set_all_notes_off(voices); + break; + } + case MSG_PARAM_CHANGE: { + param_type type = msg.param_change.param_type; + float value = msg.param_change.value; + set_param(params, type, value); + break; + } + case MSG_STOP: { + should_stop = true; + } + } + } + + memset(&output_buffer, 0, PERIOD_SIZE * sizeof(short)); + memset(&scratch_buffer, 0, PERIOD_SIZE * sizeof(float)); + + generate_voices(voices, params, scratch_buffer, PERIOD_SIZE); + + /* post_process(params, scratch_buffer, PERIOD_SIZE); */ + prepare_output(scratch_buffer, output_buffer, PERIOD_SIZE); + + snd_pcm_sframes_t written = snd_pcm_writei(pcm, output_buffer, PERIOD_SIZE); + if (written < 0) { + printf("xrun\n"); + snd_pcm_prepare(pcm); // recover from xrun + } + } +} + +void fill_voices(synth_voice *voices, float *freqs, size_t freqs_amount) { + for (size_t i = 0; i < freqs_amount; i++) { + voices[i] = (synth_voice){ + .active = false, + .freq = freqs[i], + .phase = 0, + .phase_inc = 0, + }; + } +} + +void *sound_thread_start(void *ptr) { + sound_thread_meta *meta = ptr; + + float freqs[12] = { + 261.63f, // c + 277.18f, // c# + 293.66f, // e + 311.13f, // e# + 329.63f, // d + 349.23f, // f + 369.99f, // f# + 392, // g + 415.3f, // g# + 440, // a + 466.16, // a# + 493.88, // b + }; + + synth_voice buffer[12]; + fill_voices(buffer, freqs, 12); + + synth_voices voices = { + .buffer = buffer, + .size = 12, + }; + synth_params params = { + .oscilator_type = OSC_SINE, + }; + + sound_loop_start(meta->pcm, meta->queue, &voices, ¶ms); + + check(snd_pcm_drop(meta->pcm)); + return NULL; +} + +int set_hw_params(snd_pcm_t *pcm) { + snd_pcm_hw_params_t *hw_params; + + snd_pcm_hw_params_alloca(&hw_params); + + check(snd_pcm_hw_params_any(pcm, hw_params)); + + unsigned int resample = 1; + check(snd_pcm_hw_params_set_rate_resample(pcm, hw_params, resample)); + check(snd_pcm_hw_params_set_access(pcm, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED)); + check(snd_pcm_hw_params_set_format(pcm, hw_params, SND_PCM_FORMAT_S16_LE)); + check(snd_pcm_hw_params_set_channels(pcm, hw_params, 1)); + check(snd_pcm_hw_params_set_rate(pcm, hw_params, SAMPLE_RATE, 0)); + snd_pcm_uframes_t period_size = PERIOD_SIZE; + check( + snd_pcm_hw_params_set_period_size_near(pcm, hw_params, &period_size, 0)); + snd_pcm_uframes_t buffer_size = period_size * 4; + check(snd_pcm_hw_params_set_buffer_size_near(pcm, hw_params, &buffer_size)); + check(snd_pcm_hw_params(pcm, hw_params)); + + return 0; +} diff --git a/src/sounds.h b/src/sounds.h new file mode 100644 index 0000000..e58ae4f --- /dev/null +++ b/src/sounds.h @@ -0,0 +1,137 @@ +#ifndef SOUNDS_H_ +#define SOUNDS_H_ + +#include <alsa/asoundlib.h> +#include <math.h> +#include <pthread.h> +#include <stdbool.h> +#include <limits.h> + +#define check(ret) \ + do { \ + int res = (ret); \ + if (res < 0) { \ + fprintf(stderr, "%s:%d ERROR: %s (%d)\n", \ + __FILE__, __LINE__, snd_strerror(res), res); \ + exit(1); \ + } \ + } while (0) + +#define MESSAGE_QUEUE_SIZE 128 +#define SAMPLE_RATE 48000 +#define PERIOD_SIZE 480 + +typedef enum { + PARAM_OSC, + PARAM_VOLUME, +} param_type; + +typedef enum { + OSC_SINE, + OSC_SAW, + OSC_SQUARE, + OSC_TRIANGLE, +} oscilator_type; + +typedef enum { + MSG_NOTE_ON, + MSG_NOTE_OFF, + MSG_ALL_NOTES_OFF, + MSG_PARAM_CHANGE, + MSG_STOP, +} synth_message_type; + +typedef struct { + synth_message_type type; + + union { + // NOTE_ON / NOTE_OFF + struct { + size_t note_id; + } note; + + // SET_PARAM; + struct { + param_type param_type; + float value; + } param_change; + }; +} synth_message; + + +typedef struct { + synth_message buffer[MESSAGE_QUEUE_SIZE]; + size_t head; + size_t tail; + pthread_mutex_t lock; +} message_queue; + +typedef struct { + snd_pcm_t *pcm; + message_queue *queue; +} sound_thread_meta; + +typedef struct { + bool active; + float freq; + float phase; + float phase_inc; +} synth_voice; + +typedef struct { + synth_voice *buffer; + size_t size; +} synth_voices; + +typedef struct { + oscilator_type oscilator_type; + float master_volume; +} synth_params; + +typedef void (*oscilator_func)(float *buffer, size_t sample_count, float *phase, float phase_inc); + +#define mqueue_init(q) \ + do { \ + (q)->head = (q)->tail = 0; \ + pthread_mutex_init(&(q)->lock, NULL); \ + } while (0) + + +#define mqueue_push(q, msg) \ + do { \ + pthread_mutex_lock(&(q)->lock); \ + size_t next = ((q)->head + 1) % MESSAGE_QUEUE_SIZE; \ + \ + if ((q)->tail == next) { \ + pthread_mutex_unlock(&(q)->lock); \ + return 1; \ + } \ + \ + (q)->buffer[(q)->head] = msg; \ + (q)->head = next; \ + \ + pthread_mutex_unlock(&(q)->lock); \ + } while (0) \ + +/* #define mqueue_get(q, msg, ok) \ */ +/* do { \ */ +/* pthread_mutex_lock(&(q)->lock); \ */ +/* if ((q)->tail == (q)->head) { \ */ +/* pthread_mutex_unlock(&(q)->lock); \ */ +/* *(ok) = false;\ */ +/* break; \ */ +/* } \ */ +/* \ */ +/* *(msg) = (q)->buffer[(q)->tail]; \ */ +/* (q)->tail = ((q)->tail + 1) % MESSAGE_QUEUE_SIZE; \ */ +/* \ */ +/* pthread_mutex_unlock(&(q)->lock); \ */ +/* *(ok) = true; \ */ +/* } while (0) */ + +#define mqueue_empty(q) (q)->head == (q)->tail + +void *sound_thread_start(void *ptr); +int set_hw_params(snd_pcm_t *pcm); + +#endif // SOUNDS_H_ |
