diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dee53c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +*~ diff --git a/controller.c b/controller.c new file mode 100644 index 0000000..76e38b7 --- /dev/null +++ b/controller.c @@ -0,0 +1,110 @@ +#include "pipewire/keys.h" +#include "pipewire/port.h" +#include "spa/pod/iter.h" +#include "spa/utils/defs.h" +#include <signal.h> +#include <stdio.h> + +#include <spa/control/control.h> +#include <spa/param/latency-utils.h> +#include <spa/pod/builder.h> +#include <spa/pod/pod.h> + +#include <pipewire/filter.h> +#include <pipewire/pipewire.h> + +#include "guitfx.h" +#include "delay.h" +#include "sust.h" +#include "gate.h" + +void midi_control( + struct FxData * data, + + uint64_t frame, + uint64_t offset, + unsigned sec, + unsigned midi_size, + uint8_t * midi_data, + struct spa_io_position* position +) { + printf("[%d] MIDI message (%d bytes) : %x, %x, %x\n", sec, midi_size, + midi_data[0], midi_data[1], midi_data[2]); + + switch (midi_data[0] & 0xff) { + case 0xb0: + switch (midi_data[1]) { + case 0x0b: + // expr pedal + float val_f = ((float)midi_data[2]) / 128.0; + + float thres = 0.5; + + if (val_f > thres) { + float expr_mix = (val_f - thres) / (1.0 - thres); + printf("Expr Pedal %f\n", expr_mix); + + data->delay.mix = expr_mix; + } else { + data->delay.mix = 0.0; + } + break; + + case 0x42: + + // noise gate calibration + if (data->prog == 2) { + if (midi_data[2] >= 64) { + data->gate.threshold = data->gate.cur_block_sum * 0.8; + printf("calibrate noise gate: threshold = %f\n", + data->gate.threshold); + } + } + + // sust pedal + if (data->prog == 1) { + if (midi_data[2] >= 64) { + sust_swap(&data->sust); + data->sust.playing = true; + + data->sust.start_idx = data->sust.idx; + data->sust.idx = 0; + } else { + data->sust.playing = false; + } + } + + // tap tempo + if (data->prog == 0) { + uint64_t cur_tap = frame + offset; + uint64_t duration = cur_tap - data->last_tap; + data->last_tap = cur_tap; + if (duration < (4 * position->clock.rate.denom)) { + delay_set_time(&data->delay, duration); + } + + sust_resize(&data->sust, duration); + } + + break; + } + + break; + + case 0xc0: + // program change + if (midi_data[2] >= 64) { + sust_swap(&data->sust); + data->sust.playing = true; + + data->sust.start_idx = data->sust.idx; + data->sust.idx = 0; + } else { + data->sust.playing = false; + } + printf("program change: %u\n", midi_data[1]); + data->prog = midi_data[1]; + break; + } +} + diff --git a/delay.c b/delay.c index c99b818..1723ca9 100644 --- a/delay.c +++ b/delay.c @@ -1,12 +1,12 @@ -#include <stdint.h> -#include <stddef.h> -#include <stdlib.h> -#include <stdio.h> #include "delay.h" +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> void delay_init( - struct delay * delay -) { + struct delay* delay) +{ delay->mix = 0.8; delay->feedback = 0.8; delay->duration = 0; @@ -16,39 +16,38 @@ void delay_init( } void delay_set_time( - struct delay * delay, - uint64_t new_duration -) { + struct delay* delay, + uint64_t new_duration) +{ printf("set delay duration to %lu samples\n", new_duration); - if( new_duration > delay->buf_capacity ) { + if (new_duration > delay->buf_capacity) { delay->buf_capacity = new_duration; - delay->buf = realloc( delay->buf, sizeof(float) * new_duration ); + delay->buf = realloc(delay->buf, sizeof(float) * new_duration); } - for( int i = delay->duration; i < new_duration; ++i ) { + for (int i = delay->duration; i < new_duration; ++i) { delay->buf[i] = delay->buf[i - delay->duration]; } delay->duration = new_duration; - if( new_duration > 0 ) { + if (new_duration > 0) { delay->buf_idx %= new_duration; } } void delay_process( - struct delay * delay, + struct delay* delay, size_t frame_size, - float const * in, - float * out -) { - for( size_t i = 0; i < frame_size; ++i ) { - if( delay->duration > 0 ) { - out[i] = - 0.5 * in[i] - + 0.5 * delay->mix * delay->buf[ delay->buf_idx ]; + float const* in, + float* out) +{ + for (size_t i = 0; i < frame_size; ++i) { + if (delay->duration > 0) { + out[i] = in[i] + + delay->mix * delay->buf[delay->buf_idx]; - delay->buf[ delay->buf_idx ] *= delay->feedback; - delay->buf[ delay->buf_idx ] += (1.0 - delay->feedback) * in[i]; + delay->buf[delay->buf_idx] *= delay->feedback; + delay->buf[delay->buf_idx] += (1.0 - delay->feedback) * in[i]; delay->buf_idx = (delay->buf_idx + 1) % delay->duration; } else { out[i] = 0.5 * in[i]; diff --git a/delay.h b/delay.h index 4ed2cf5..499b1cc 100644 --- a/delay.h +++ b/delay.h @@ -1,7 +1,7 @@ #pragma once -#include <stdint.h> #include <stddef.h> +#include <stdint.h> struct delay { uint64_t duration; @@ -10,21 +10,18 @@ struct delay { uint64_t buf_idx; size_t buf_capacity; - float * buf; + float* buf; }; void delay_init( - struct delay * delay -); + struct delay* delay); void delay_set_time( - struct delay * delay, - uint64_t new_duration -); + struct delay* delay, + uint64_t new_duration); void delay_process( - struct delay * delay, + struct delay* delay, size_t frame_size, - float const * in, - float * out -); + float const* in, + float* out); diff --git a/gate.c b/gate.c new file mode 100644 index 0000000..3f13c6a --- /dev/null +++ b/gate.c @@ -0,0 +1,64 @@ +#include "gate.h" +#include <math.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +void gate_init( + struct gate* gate) +{ + gate->threshold = 0.0; + gate->enable_calibration = false; + + gate->block_size = 1024; + gate->cur_block_count = 0; + gate->cur_block_sum = 0.0; + + gate->is_active = false; + gate->cur_gain = 1.0; + gate->attack = 1.0 / 128.0; + gate->release = 1.0 / 8192.0; +} + +void gate_process( + struct gate* gate, + size_t frame_size, + float const* in, + float* out) +{ + + bool act = false; + + for (size_t i = 0; i < frame_size; ++i) { + gate->cur_block_sum += fabs(in[i]); + gate->cur_block_count += 1; + + if (gate->cur_block_count >= gate->block_size) { + // printf("new block sum %f, gain = %f\n", gate->cur_block_sum, gate->cur_gain); + + gate->last_block_sum = gate->cur_block_sum; + gate->cur_block_sum = 0.0; + + if (gate->last_block_sum > 0.03) { + act = true; + } else { + act = false; + } + } + + if (act) { + gate->cur_gain += gate->attack; + if (gate->cur_gain > 1.0) { + gate->cur_gain = 1.0; + } + } else { + gate->cur_gain -= gate->release; + if (gate->cur_gain < 0.0) { + gate->cur_gain = 0.0; + } + } + + out[i] = in[i] * gate->cur_gain; + } +} diff --git a/gate.h b/gate.h new file mode 100644 index 0000000..3773e5c --- /dev/null +++ b/gate.h @@ -0,0 +1,28 @@ +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +struct gate { + float threshold; + bool enable_calibration; + + unsigned block_size; + unsigned cur_block_count; + float cur_block_sum; + float last_block_sum; + + bool is_active; + float cur_gain; + + float attack; + float release; +}; + +void gate_init(); +void gate_process( + struct gate* gate, + size_t frame_size, + float const* in, + float* out); diff --git a/guitfx.c b/guitfx.c index 02f8a12..82b53ca 100644 --- a/guitfx.c +++ b/guitfx.c @@ -2,223 +2,186 @@ #include "pipewire/port.h" #include "spa/pod/iter.h" #include "spa/utils/defs.h" -#include <stdio.h> #include <signal.h> +#include <stdio.h> -#include <spa/pod/pod.h> -#include <spa/pod/builder.h> #include <spa/control/control.h> #include <spa/param/latency-utils.h> +#include <spa/pod/builder.h> +#include <spa/pod/pod.h> -#include <pipewire/pipewire.h> #include <pipewire/filter.h> +#include <pipewire/pipewire.h> #include "delay.h" +#include "gate.h" +#include "sust.h" +#include "guitfx.h" + +float envelope(float x); struct data; struct port { - struct data *data; + struct data* data; }; +void midi_control( + struct FxData * data, + + uint64_t frame, + uint64_t offset, + unsigned sec, + unsigned midi_size, + uint8_t * midi_data, + struct spa_io_position* position); + + struct data { - struct pw_main_loop * loop; - struct pw_filter * filter; - struct port * guit_in_port; - struct port * midi_in_port; - struct port * out_port; - - //! effect data - uint64_t last_tap; - struct delay delay; - - //! elapsed time in number of samples - uint64_t time; + struct pw_main_loop* loop; + struct pw_filter* filter; + struct port* guit_in_port; + struct port* midi_in_port; + struct port* out_port; + struct FxData fx_data; }; -static void on_process(void *userdata, struct spa_io_position *position) +static void on_process(void* userdata, struct spa_io_position* position) { - struct data *data = userdata; + struct data* data = userdata; - uint32_t n_samples = position->clock.duration; - uint64_t frame = data->time; - data->time += n_samples; + uint32_t n_samples = position->clock.duration; + uint64_t frame = data->fx_data.time; + data->fx_data.time += n_samples; - struct pw_buffer * b = pw_filter_dequeue_buffer(data->midi_in_port); - if( b == NULL ) { - fprintf(stderr, "on_process(): no buffer for midi_in_port\n"); - return; - } + struct pw_buffer* b = pw_filter_dequeue_buffer(data->midi_in_port); + if (b == NULL) { + fprintf(stderr, "on_process(): no buffer for midi_in_port\n"); + return; + } - struct spa_buffer * buf = b->buffer; - spa_assert(buf->n_datas == 1); - struct spa_data * d = &buf->datas[0]; + struct spa_buffer* buf = b->buffer; + spa_assert(buf->n_datas == 1); + struct spa_data* d = &buf->datas[0]; - if( d->data ) { - struct spa_pod * pod = - spa_pod_from_data( - d->data, - d->maxsize, - d->chunk->offset, - d->chunk->size - ); + if (d->data) { + struct spa_pod* pod = spa_pod_from_data(d->data, d->maxsize, + d->chunk->offset, d->chunk->size); - if( pod ) { - if( spa_pod_is_sequence(pod) ) { - struct spa_pod_sequence * pod_seq = (struct spa_pod_sequence*) pod; - struct spa_pod_control * c; - SPA_POD_SEQUENCE_FOREACH(pod_seq, c) { - if( c->type == SPA_CONTROL_Midi ) { - unsigned sec = - (frame + c->offset) - / (float) position->clock.rate.denom; - char * midi_data = SPA_POD_BODY(&c->value); - unsigned size = SPA_POD_BODY_SIZE(&c->value); + if (pod) { + if (spa_pod_is_sequence(pod)) { + struct spa_pod_sequence* pod_seq = (struct spa_pod_sequence*)pod; + struct spa_pod_control* c; + SPA_POD_SEQUENCE_FOREACH(pod_seq, c) + { + if (c->type == SPA_CONTROL_Midi) { + unsigned sec = (frame + c->offset) / (float)position->clock.rate.denom; + char* midi_data = SPA_POD_BODY(&c->value); + unsigned midi_size = SPA_POD_BODY_SIZE(&c->value); - printf("[%d] MIDI message (%d bytes) : %x, %x, %x\n", sec, size, midi_data[0], midi_data[1], midi_data[2]); + midi_control( &data->fx_data, frame, c->offset, sec, midi_size, midi_data, position ); + } else { - switch( midi_data[0] & 0xff ) { - case 0xb0: - switch( midi_data[1] ) { - case 0x0b: - // expr pedal - float val_f = ((float)midi_data[2]) / 128.0; - printf("Expr Pedal %f\n", val_f); + printf("on_process(): non midi-control\n"); + } + } + } else { + fprintf(stderr, "on_process(): unexpected POD that is not a sequence " + "(midi_in_port)\n"); + } + } else { + fprintf(stderr, "on_process(): pod is NULL\n"); + } + } else { + fprintf(stderr, "on_process(): no data in buffer of midi_in_port\n"); + } - data->delay.mix = val_f; - break; + pw_filter_queue_buffer(data->midi_in_port, b); - case 0x42: - // sust pedal - - uint64_t cur_tap = frame + c->offset; - uint64_t duration = cur_tap - data->last_tap; - data->last_tap = cur_tap; - if( duration < (4*position->clock.rate.denom) ) { - delay_set_time( &data->delay, duration/4 ); - } - break; - } - break; - - case 0xc0: - // program change - printf("program change: %u\n", midi_data[1]); - break; - } - } else { - printf("on_process(): non midi-control\n"); - } - } - } else { - fprintf(stderr, "on_process(): unexpected POD that is not a sequence (midi_in_port)\n"); - } - } else { - fprintf(stderr, "on_process(): pod is NULL\n"); - } - } else { - fprintf(stderr, "on_process(): no data in buffer of midi_in_port\n"); - } - - pw_filter_queue_buffer(data->midi_in_port, b); - - - float * const in = pw_filter_get_dsp_buffer(data->guit_in_port, n_samples); - float * out = pw_filter_get_dsp_buffer(data->out_port, n_samples); - if( in && out ) { - delay_process( &data->delay, n_samples, in, out ); - } + float* const in = pw_filter_get_dsp_buffer(data->guit_in_port, n_samples); + float tmp1[n_samples]; + float tmp2[n_samples]; + float* out = pw_filter_get_dsp_buffer(data->out_port, n_samples); + if (in && out) { + gate_process(&data->fx_data.gate, n_samples, in, tmp1); + sust_process(&data->fx_data.sust, n_samples, tmp1, tmp2); + delay_process(&data->fx_data.delay, n_samples, tmp2, out); + } } static const struct pw_filter_events filter_events = { - PW_VERSION_FILTER_EVENTS, - .process = on_process, + PW_VERSION_FILTER_EVENTS, + .process = on_process, }; -static void do_quit(void *userdata, int signal_number) +static void do_quit(void* userdata, int signal_number) { - struct data *data = userdata; - pw_main_loop_quit(data->loop); + struct data* data = userdata; + pw_main_loop_quit(data->loop); } -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { - struct data data = { 0, }; + struct data data = { + 0, + }; - delay_init( &data.delay ); + gate_init(&data.fx_data.gate); + delay_init(&data.fx_data.delay); + sust_init(&data.fx_data.sust); + data.fx_data.prog = 0; - const struct spa_pod *params[1]; - uint8_t buffer[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod* params[1]; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - pw_init(&argc, &argv); + pw_init(&argc, &argv); - data.loop = pw_main_loop_new(NULL); + data.loop = pw_main_loop_new(NULL); - pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); - pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); - data.filter = pw_filter_new_simple( - pw_main_loop_get_loop(data.loop), - "Guitar FX", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Audio", - PW_KEY_MEDIA_CATEGORY, "Filter", - PW_KEY_MEDIA_ROLE, "DSP", - NULL), - &filter_events, - &data); + data.filter = pw_filter_new_simple( + pw_main_loop_get_loop(data.loop), "Guitar FX", + pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, + "Filter", PW_KEY_MEDIA_ROLE, "DSP", NULL), + &filter_events, &data); - data.midi_in_port = pw_filter_add_port(data.filter, - PW_DIRECTION_INPUT, - PW_FILTER_PORT_FLAG_MAP_BUFFERS, - sizeof(struct port), - pw_properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", - PW_KEY_PORT_NAME, "midi in", - NULL - ), - NULL, 0 - ); + data.midi_in_port = pw_filter_add_port(data.filter, PW_DIRECTION_INPUT, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), + pw_properties_new(PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_PORT_NAME, "midi in", NULL), + NULL, 0); - data.guit_in_port = pw_filter_add_port(data.filter, - PW_DIRECTION_INPUT, - PW_FILTER_PORT_FLAG_MAP_BUFFERS, - sizeof(struct port), - pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit float mono audio", - PW_KEY_PORT_NAME, "guitar in", - NULL), - NULL, 0); + data.guit_in_port = pw_filter_add_port( + data.filter, PW_DIRECTION_INPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + pw_properties_new(PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_NAME, "guitar in", NULL), + NULL, 0); - data.out_port = pw_filter_add_port(data.filter, - PW_DIRECTION_OUTPUT, - PW_FILTER_PORT_FLAG_MAP_BUFFERS, - sizeof(struct port), - pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit float mono audio", - PW_KEY_PORT_NAME, "fx out", - NULL), - NULL, 0); + data.out_port = pw_filter_add_port( + data.filter, PW_DIRECTION_OUTPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + pw_properties_new(PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_NAME, "fx out", NULL), + NULL, 0); - params[0] = spa_process_latency_build(&b, - SPA_PARAM_ProcessLatency, - &SPA_PROCESS_LATENCY_INFO_INIT( - .ns = 10 * SPA_NSEC_PER_MSEC - )); + params[0] = spa_process_latency_build( + &b, SPA_PARAM_ProcessLatency, + &SPA_PROCESS_LATENCY_INFO_INIT(.ns = 10 * SPA_NSEC_PER_MSEC)); - if (pw_filter_connect(data.filter, - PW_FILTER_FLAG_RT_PROCESS, - params, 1) < 0) { - fprintf(stderr, "can't connect\n"); - return -1; - } + if (pw_filter_connect(data.filter, PW_FILTER_FLAG_RT_PROCESS, params, 1) < 0) { + fprintf(stderr, "can't connect\n"); + return -1; + } - pw_main_loop_run(data.loop); + pw_main_loop_run(data.loop); - pw_filter_destroy(data.filter); - pw_main_loop_destroy(data.loop); - pw_deinit(); + pw_filter_destroy(data.filter); + pw_main_loop_destroy(data.loop); + pw_deinit(); - return 0; + return 0; } diff --git a/guitfx.h b/guitfx.h new file mode 100644 index 0000000..4dedcdd --- /dev/null +++ b/guitfx.h @@ -0,0 +1,16 @@ + +#include "delay.h" +#include "sust.h" +#include "gate.h" + +struct FxData { + uint64_t last_tap; + struct delay delay; + struct sust sust; + struct gate gate; + + unsigned prog; + + //! elapsed time in number of samples + uint64_t time; +}; diff --git a/meson.build b/meson.build index 5f61c36..ab53a04 100644 --- a/meson.build +++ b/meson.build @@ -1,8 +1,15 @@ project('guitfx', 'c') pipewire_dep = dependency('libpipewire-0.3') + +cc = meson.get_compiler('c') +m_dep = cc.find_library('m', required : false) + executable( 'guitfx', 'guitfx.c', 'delay.c', - dependencies : [pipewire_dep], + 'sust.c', + 'gate.c', + 'controller.c', + dependencies : [pipewire_dep, m_dep], ) diff --git a/sust.c b/sust.c new file mode 100644 index 0000000..f9d31a2 --- /dev/null +++ b/sust.c @@ -0,0 +1,91 @@ +#include "sust.h" +#include <math.h> +#include <stdio.h> +#include <stdlib.h> + +void sust_init(struct sust* sust) +{ + sust->mode = MODE_Sostenuto; + + sust->playing = false; + + sust->start_idx = 0; + sust->idx = 0; + + sust->buf_len = 51200; + sust->record_buf = malloc(sizeof(float) * sust->buf_len); + sust->play_buf = malloc(sizeof(float) * sust->buf_len); +} + +float envelope(float x) +{ + if (x < 0.4) + return (x / 0.4) * (x / 0.4); + if (x < 0.6) + return 1.0; + + float v = 1.0 - ((x - 0.6) / 0.4); + return v * v; +} + +float softcos(float x) +{ + return sin(x * (3.141 / 2.0)); +} + +void sust_resize(struct sust* sust, size_t new_len) +{ + sust->buf_len = new_len; + sust->play_buf = realloc(sust->play_buf, sizeof(float) * new_len); + sust->record_buf = realloc(sust->record_buf, sizeof(float) * new_len); + sust->idx %= new_len; +} + +void sust_swap( + struct sust* sust) +{ + float* tmp = sust->play_buf; + sust->play_buf = sust->record_buf; + sust->record_buf = tmp; + + for (int i = 0; i < sust->buf_len; ++i) { + sust->record_buf[i] = sust->play_buf[i]; + } +} + +void sust_process( + struct sust* sust, + size_t frame_size, + float const* in, + float* out) +{ + for (size_t i = 0; i < frame_size; ++i) { + + if (sust->mode == MODE_Sustain && sust->idx == 0) { + sust_swap(sust); + } + + float out_value = in[i]; + + if (sust->playing) { + int n_voices = 5; + + for (int v = 0; v < n_voices; ++v) { + size_t play_idx = (sust->start_idx + sust->idx + ((v * sust->buf_len) / n_voices)) % sust->buf_len; + float gain = envelope(((float)play_idx) / ((float)sust->buf_len)); + // printf("gain = %f\n", gain); + out_value += 0.5 * gain * sust->play_buf[play_idx]; + } + } + + if (sust->mode == MODE_Sostenuto) { + sust->record_buf[sust->idx] = in[i]; + } + if (sust->mode == MODE_Sustain) { + sust->record_buf[sust->idx] = 0.5 * out_value; + } + + sust->idx = (sust->idx + 1) % sust->buf_len; + out[i] = out_value; + } +} diff --git a/sust.h b/sust.h new file mode 100644 index 0000000..a0472b1 --- /dev/null +++ b/sust.h @@ -0,0 +1,30 @@ +#pragma once + +#include <stdbool.h> +#include <stddef.h> + +enum sust_mode { + MODE_Sustain = 0, + MODE_Sostenuto = 1 +}; + +struct sust { + enum sust_mode mode; + bool playing; + + size_t start_idx; + size_t idx; + size_t buf_len; + float* record_buf; + float* play_buf; +}; + +void sust_init(struct sust* sust); +void sust_resize(struct sust* sust, size_t new_len); +void sust_swap(struct sust* sust); + +void sust_process( + struct sust* sust, + size_t frame_size, + float const* in, + float* out);