diff options
| author | spl3g <spleefer6@yandex.ru> | 2025-10-02 18:53:07 +0300 |
|---|---|---|
| committer | spl3g <spleefer6@yandex.ru> | 2025-10-02 19:02:59 +0300 |
| commit | 06aada0bec32d889c0f0ce6f2ecf193bbece3c73 (patch) | |
| tree | 20410d05a7c2d0ee6d05dec52f53976009c9e3ce | |
| parent | 5fad25abc6ab0632b8594c84f59fa5c1eec06d2f (diff) | |
Add envelopes
| -rw-r--r-- | src/sounds.c | 177 | ||||
| -rw-r--r-- | src/sounds.h | 90 |
2 files changed, 180 insertions, 87 deletions
diff --git a/src/sounds.c b/src/sounds.c index fe6b3d3..9ca88c1 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -14,48 +14,113 @@ int mqueue_get(message_queue *q, synth_message *msg) { return 0; } -/* osc_triangle_gen */ +int mqueue_push(message_queue *q, synth_message msg) { + pthread_mutex_lock(&(q)->lock); + size_t next = ((q)->head + 1) % MESSAGE_QUEUE_SIZE; -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; + if ((q)->tail == next) { + pthread_mutex_unlock(&(q)->lock); + return 1; } + + (q)->buffer[(q)->head] = msg; + (q)->head = next; + + pthread_mutex_unlock(&(q)->lock); + return 0; } -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; +float envelope_next(envelope *env) { + float value; + bool next_state = false; + + env->counter++; + + switch (env->state) { + case ENV_OFF: { + return 0; + } + case ENV_ATTACK: { + if (env->counter >= env->attack_time) { + next_state = true; + } + value = env->increases[0] * (float)env->counter; + break; + } + case ENV_DECAY: { + if (env->counter >= env->decay_time) { + next_state = true; + } + value = 1.0 - env->increases[1] * (float)env->counter; + break; + } + case ENV_SUSTAIN: { + value = env->sustain_level; + break; + } + case ENV_RELEASE: { + if (env->counter >= env->release_time) { + env->state = ENV_OFF; + env->counter = 0; + return 0; + } + value = env->sustain_level - env->increases[2] * (float)env->counter; + break; + } } -} -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; + if (next_state) { + env->counter = 0; + env->state++; } + return value; +} + +void envelope_init(envelope *env) { + env->state = ENV_OFF; + env->counter = 0; + env->attack_time = 0.005 * SAMPLE_RATE; + env->decay_time = 0.0010 * SAMPLE_RATE; + env->sustain_level = 0.7; + env->release_time = 1.000 * SAMPLE_RATE; + + env->increases[0] = 1.0 / (float)env->attack_time; + env->increases[1] = (1.0 - env->sustain_level) / (float)env->decay_time; + env->increases[2] = env->sustain_level / (float)env->release_time; +} + +void envelope_note_on(envelope *env) { + env->state = ENV_ATTACK; + env->counter = 0; +} + +void envelope_note_off(envelope *env) { + env->state = ENV_RELEASE; + env->counter = 0; +} + +float osc_sine_get(float phase) { + return sinf(phase); +} + +float osc_square_get(float phase) { + return sinf(phase) > 0 ? 1 : -1; +} + +float osc_saw_get(float phase) { + return (phase / M_PI) - 1; } oscilator_func osc_get(oscilator_type type) { switch (type) { case OSC_SINE: - return osc_sine_gen; + return osc_sine_get; case OSC_SAW: - return osc_saw_gen; + return osc_saw_get; case OSC_SQUARE: - return osc_square_gen; + return osc_square_get; default: - return osc_sine_gen; + return osc_sine_get; } } @@ -63,6 +128,7 @@ void set_note_on(synth_voices *voices, size_t note_id) { if (note_id >= voices->size) { return; } + envelope_note_on(&voices->buffer[note_id].envelope); voices->buffer[note_id].active = true; } @@ -70,7 +136,7 @@ void set_note_off(synth_voices *voices, size_t note_id) { if (note_id >= voices->size) { return; } - voices->buffer[note_id].active = false; + envelope_note_off(&voices->buffer[note_id].envelope); } void set_all_notes_off(synth_voices *voices) { @@ -94,8 +160,7 @@ void set_param(synth_params *params, param_type type, float value) { } } -void generate_voices(synth_voices *voices, synth_params *params, float *buffer, - size_t buffer_size) { +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]; @@ -103,10 +168,23 @@ void generate_voices(synth_voices *voices, synth_params *params, float *buffer, 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); + if (voice->phase_inc == 0) { + voice->phase_inc = 2 * M_PI * voice->freq / SAMPLE_RATE; + } + + for (size_t j = 0; j < buffer_size; j++) { + float env_value = envelope_next(&voice->envelope); + if (env_value == 0) { + voice->active = false; + break; + } + + buffer[j] += oscilator(voice->phase) * env_value; + + voice->phase += voice->phase_inc; + if (voice->phase >= 2 * M_PI) + voice->phase -= 2 * M_PI; + } } } @@ -126,14 +204,12 @@ void prepare_output(float *scratch_buffer, short *output_buffer, 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) { + while (true) { synth_message msg; - while (mqueue_get(queue, &msg) == 0 && !should_stop) { + while (mqueue_get(queue, &msg) == 0) { switch (msg.type) { case MSG_NOTE_ON: { size_t note_id = msg.note.note_id; @@ -156,7 +232,7 @@ void sound_loop_start(snd_pcm_t *pcm, message_queue *queue, break; } case MSG_STOP: { - should_stop = true; + goto stop; } } } @@ -169,21 +245,34 @@ void sound_loop_start(snd_pcm_t *pcm, message_queue *queue, /* 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 - } + int period_size = PERIOD_SIZE; + short *ptr = output_buffer; + + while (period_size > 0) { + snd_pcm_sframes_t written = snd_pcm_writei(pcm, ptr, period_size); + if (written < 0) { + printf("xrun\n"); + snd_pcm_prepare(pcm); // recover from xrun + break; + } + ptr += written; + period_size -= written; + } } +stop: + return; } void fill_voices(synth_voice *voices, float *freqs, size_t freqs_amount) { + envelope env = {0}; + envelope_init(&env); for (size_t i = 0; i < freqs_amount; i++) { voices[i] = (synth_voice){ .active = false, .freq = freqs[i], .phase = 0, .phase_inc = 0, + .envelope = env, }; } } diff --git a/src/sounds.h b/src/sounds.h index e58ae4f..4b2f78f 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -2,20 +2,20 @@ #define SOUNDS_H_ #include <alsa/asoundlib.h> +#include <limits.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 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 @@ -45,20 +45,19 @@ 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; + // 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; @@ -71,11 +70,30 @@ typedef struct { message_queue *queue; } sound_thread_meta; +typedef enum { + ENV_OFF, + ENV_ATTACK, + ENV_DECAY, + ENV_SUSTAIN, + ENV_RELEASE, +} envelope_state; + +typedef struct { + envelope_state state; + int counter; + int attack_time; + int decay_time; + float sustain_level; + int release_time; + float increases[3]; +} envelope; + typedef struct { bool active; float freq; float phase; float phase_inc; + envelope envelope; } synth_voice; typedef struct { @@ -88,30 +106,16 @@ typedef struct { float master_volume; } synth_params; -typedef void (*oscilator_func)(float *buffer, size_t sample_count, float *phase, float phase_inc); +typedef float (*oscilator_func)(float phase); -#define mqueue_init(q) \ - do { \ - (q)->head = (q)->tail = 0; \ - pthread_mutex_init(&(q)->lock, NULL); \ - } while (0) +int mqueue_get(message_queue *q, synth_message *msg); +int mqueue_push(message_queue *q, synth_message msg); - -#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_init(q) \ + do { \ + (q)->head = (q)->tail = 0; \ + pthread_mutex_init(&(q)->lock, NULL); \ + } while (0) /* #define mqueue_get(q, msg, ok) \ */ /* do { \ */ |
