aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.c185
-rw-r--r--src/sounds.c248
-rw-r--r--src/sounds.h137
3 files changed, 445 insertions, 125 deletions
diff --git a/src/main.c b/src/main.c
index 553ee89..f8972c1 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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, &params);
+
+ 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_