From c03d395f6848fe9b2d1185173a9cf5ec8277394f Mon Sep 17 00:00:00 2001 From: gramanas Date: Tue, 21 Nov 2023 14:07:33 +0200 Subject: Crappy fft for spectrum analysis and initial gtk test --- src/Makefile.am | 10 +++++-- src/generator.c | 70 ++++++++++++++++++++++++++++++++++++++++++++---- src/gtk.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lowpass.c | 2 ++ src/midi.c | 9 ++++--- src/synth.c | 4 +-- src/synth_engine.c | 62 +++++++++++++++++++++++++++++++++++-------- src/synth_engine.h | 3 ++- src/synth_gui.c | 3 +++ 9 files changed, 215 insertions(+), 26 deletions(-) create mode 100644 src/gtk.c diff --git a/src/Makefile.am b/src/Makefile.am index 6e75352..45ac871 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ #bin_PROGRAMS = food cookbook cook synth -bin_PROGRAMS = synth +bin_PROGRAMS = synth gtk common_sources = adsr.c \ adsr.h \ @@ -32,6 +32,8 @@ common_sources = adsr.c \ synth_gui.h \ synth_math.h +gtk_sources = + # -fwhole-program allows cross-file inlining, but only works when you put all # the source files on one gcc command-line. -flto is another way to get the # same effect. (Link-Time Optimization). clang supports -flto but not @@ -45,7 +47,11 @@ common_sources = adsr.c \ AM_CFLAGS = -O3 -march=native -fno-math-errno -funroll-loops -flto -pthread synth_SOURCES = synth.c $(common_sources) -synth_LDADD = -lportaudio -lrt -lm -lasound -lraylib -lportmidi -ljack -lfftw3f -lsndfile +synth_LDADD = -lportaudio -lrt -lm -lasound -lraylib -lportmidi -ljack -lfftw3f -lsndfile -lconfig + +gtk_SOURCES = gtk.c $(common_sources) $(gtk_sources) +gtk_LDADD = -lportaudio -lrt -lm -lasound -lraylib -lportmidi -ljack -lfftw3f -lsndfile -lconfig -lgtk-4 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lgraphene-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 +gtk_CFLAGS = -I/usr/include/gtk-4.0 -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-6 -I/usr/include/harfbuzz -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/fribidi -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/graphene-1.0 -I/usr/lib/graphene-1.0/include -mfpmath=sse -msse -msse2 -pthread #cookbook_SOURCES = cookbook.c $(common_sources) #cookbook_SOURCES = cookbook.c $(common_sources) #cook_SOURCES = cook.c $(common_sources) diff --git a/src/generator.c b/src/generator.c index 4e81297..ab2206f 100644 --- a/src/generator.c +++ b/src/generator.c @@ -1,16 +1,76 @@ +#include + #include "generator.h" float -generate(generator * g, synth_t * s, int i) +generate(generator * g, synth_t * s, int i, sound_node * root) { float sample = 0; - float f = get_frequency(g, s, i);' - - sample = osc_saw(midi_note->wvt_index); - midi_note->wvt_index = osc_saw_next(f, midi_note->wvt_index); + sampe = play_tree(root, s->midi_note[i]); return sample; } + +int +sound_tree_add_node(sound_node * root, const char * cmp) +{ + +} + +int +load_preset(sound_node * tree, const char * path) +{ + config_t cfg; + config_setting_t *setting, *x; + const char *str; + + config_init(&cfg); + if (! config_read_file(&cfg, "../preset.synth")) { + fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg), + config_error_line(&cfg), config_error_text(&cfg)); + config_destroy(&cfg); + return(EXIT_FAILURE); + } + setting = config_lookup(&cfg, "patch"); + if (setting != NULL) { + int count = config_setting_length(setting); + + for(int i = 0; i < count; ++i) { + x = config_setting_get_elem(setting, i); + printf("%s = %f\n", config_setting_name(x), config_setting_get_float(x)); + // TODO + sound_tree_add_node(tree, name); + } + } + + + setting = config_tree_setting(&cfg); + if (setting != NULL) { + int count = config_setting_length(setting); + + for(int i = 0; i < count; ++i) { + chat * name = config_setting_name(x); + x = config_setting_get_elem(setting, i); + printf("%s -- %d\n", config_setting_name(x), config_setting_type(x)); + + if (name[0] == 'o') { // osc + // TODO + configure_osc(tree, id); + } else if (name[0] == 'a') { // adsr + // TODO + configure_adsr(tree, id); + } else if (name[0] == 'l') { // lfo + // TODO + configure_lfo(tree, id); + } else { + printf("%s: wrong!", name); + } + } + } + + config_destroy(&cfg); + +} diff --git a/src/gtk.c b/src/gtk.c new file mode 100644 index 0000000..2b91b94 --- /dev/null +++ b/src/gtk.c @@ -0,0 +1,78 @@ +/* + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* audio */ +#include +#include + +#include + +/* graphics */ +#include + +/* synth */ +#include "synth_engine.h" +#include "midi.h" + +#define NUM_SECONDS (1) + +#define WAVE_SIZE (44100) + +static void +activate (GtkApplication* app, + gpointer user_data) +{ + GtkWidget *window; + + window = gtk_application_window_new (app); + gtk_window_set_title (GTK_WINDOW (window), "Synth"); + gtk_window_set_default_size (GTK_WINDOW (window), WIDTH, HEIGHT); + gtk_window_present (GTK_WINDOW (window)); +} + + +int +main(int argc, char * argv[]) { + synth_t synth; + midi_t midi; + + init_synth(&synth); + init_midi(&midi, &synth); + + GtkApplication *app; + int status; + + app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS); + g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); + status = g_application_run (G_APPLICATION (app), argc, argv); + g_object_unref (app); + + // rayrun(&synth); + + terminate_midi(&midi); + free_synth(&synth); + + return status; +} diff --git a/src/lowpass.c b/src/lowpass.c index ccb41dd..0ec53be 100644 --- a/src/lowpass.c +++ b/src/lowpass.c @@ -181,6 +181,8 @@ signed char lpf_init() void lpf_close() { + if (iir.coef) + free(iir.coef); } diff --git a/src/midi.c b/src/midi.c index c01b803..e711ecb 100644 --- a/src/midi.c +++ b/src/midi.c @@ -14,12 +14,12 @@ void midi_decode(uint32_t msg, synth_t * synth) { switch (message) { case 0x08: - // printf("Note Off: channel=%d, note=%d, velocity=%d\n", channel, data1, data2); + printf("Note Off: channel=%d, note=%d, velocity=%d\n", channel, data1, data2); synth->midi_note[data1].noteOff = Pa_GetStreamTime(synth->stream); synth->midi_note[data1].noteOffSample = synth->midi_note[data1].elapsed; break; case 0x09: - // printf("Note On: channel=%d, note=%d, velocity=%d\n", channel, data1, data2); + printf("Note On: channel=%d, note=%d, velocity=%d\n", channel, data1, data2); //synth->midi_note[i].n = -1; synth->midi_note[data1].freq = notes[data1 % 12][(data1 / 12) % 8]; synth->midi_note[data1].channel = channel; @@ -127,9 +127,10 @@ init_midi(midi_t *m, synth_t *synth) info = Pm_GetDeviceInfo(i); printf("%d: %s [input: %d output: %d opened: %d is_virt:%d] (interf: %s) -- %d\n", i, info->name, info->input, info->output, info->opened, info->is_virtual, info->interf, Pm_GetDefaultInputDeviceID()); //if (!strcmp("MPK225 MIDI", info->name) && !info->input) break; - //if (!strcmp("MPK225 Port A", info->name) && info->input == 1) break; + if (!strcmp("MPK225 Port A", info->name) && info->input == 1) break; //if (!strcmp("CH345 MIDI 1", info->name) && info->input == 1) break; - if (!strcmp("Midi Through Port-0", info->name) && info->input == 1) break; + //if (!strcmp("Midi Through Port-0", info->name) && info->input == 1) break; + //if (!strcmp("DigitalKBD MIDI 1", info->name) && info->input == 1) break; } Pt_Start(1, midiCallback, m); diff --git a/src/synth.c b/src/synth.c index 1113a56..ea4a8f7 100644 --- a/src/synth.c +++ b/src/synth.c @@ -30,18 +30,16 @@ #include /* graphics */ +#include "synth_gui.h" /* synth */ #include "synth_engine.h" -#include "synth_gui.h" #include "midi.h" #define NUM_SECONDS (1) #define WAVE_SIZE (44100) - - int main(void) { synth_t synth; diff --git a/src/synth_engine.c b/src/synth_engine.c index 517f9d8..b47d510 100644 --- a/src/synth_engine.c +++ b/src/synth_engine.c @@ -167,8 +167,9 @@ make_sample(void *synthData, unsigned int sample_rate, int frame) // if rms == 0 we can deduce there are no notes however notes_active handles // stopping any already playing notes and if we remove it we need to make - // sure that notes stop some other way (it laso happens every frame) - rms = sqrt(rms / n); + // sure that notes stop some other way (it also happens every frame) + rms = sqrt(rms / (float)n); + // printf("rms %f\n", rms); for (int i = 0; i < MIDI_NOTES; i++) { if (!synth->midi_note[i].active) @@ -195,7 +196,7 @@ make_sample(void *synthData, unsigned int sample_rate, int frame) synth->x, sample_rate); - sample += rms * adsr * synth_sample; + sample += synth->midi_note[i].velocity * adsr * synth_sample; } /* filter */ @@ -233,6 +234,16 @@ add_to_viz(synth_t *synth, float sample) } } +void +add_to_fftviz(synth_t *synth, float sample) +{ + synth->fftviz.wave[synth->fftviz.wi++] = sample; + + if (synth->fftviz.wi >= VIZ_BUF) { + synth->fftviz.wi = 0; + } +} + void add_to_delay(synth_t *synth, float sample) { @@ -304,7 +315,30 @@ get_frame(void *outputBuffer, synth_t *synth, int i) #include "fftw3.h" int -process(float * buffer) +get_spectrum(synth_t * synth, float * buffer) +{ + int fft_output_len = FRAMES_PER_BUFFER / 2 + 1; + float input[FRAMES_PER_BUFFER]; + + for( unsigned long i=0; i < FRAMES_PER_BUFFER * 2; i += 2 ) { + input[i / 2] = buffer[i]; + } + fftwf_complex* output = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fft_output_len); + + fftwf_plan forward_plan = fftwf_plan_dft_r2c_1d(fft_output_len, input, output, FFTW_ESTIMATE); + + fftwf_execute(forward_plan); + + for (int i=0; i < fft_output_len - 2*fft_output_len/3; i++ ) { + add_to_fftviz(synth, sqrt(output[i][0] * output[i][0] + output[i][1] * output[i][1])); + } + + fftwf_destroy_plan(forward_plan); + fftwf_free(output); +} + +int +process(synth_t * synth, float * buffer) { int len = FRAMES_PER_BUFFER; @@ -322,6 +356,8 @@ process(float * buffer) fftwf_execute(forward_plan); + // cont: + /* printf("ASDASD!!!\n"); */ /* for (int i=0; i < (len / 2 + 1); i++ ) { */ /* if (frequency_signal[i][0] > 1) { */ /* frequency_signal[i][0] = 0; */ @@ -329,15 +365,16 @@ process(float * buffer) /* if (frequency_signal[i][1] > 1) { */ /* frequency_signal[i][1] = 0; */ /* } */ - /* printf("%f %f | ", sqrt(frequency_signal[i][0] * frequency_signal[i][0] + frequency_signal[i][1] * frequency_signal[i][1]), atan2(frequency_signal[i][1], frequency_signal[i][0])); */ + /* printf("%f %f | \n", sqrt(frequency_signal[i][0] * frequency_signal[i][0] + frequency_signal[i][1] * frequency_signal[i][1]), atan2(frequency_signal[i][1], frequency_signal[i][0])); */ - /* float f = atan2(frequency_signal[i][1], frequency_signal[i][0]); */ - /* if (f > M_PI / 2 && f < 2 * M_PI / 3) { */ - /* frequency_signal[i][0] = 0.0; */ - /* frequency_signal[i][1] = 0.0; */ - /* } */ + /* /\* float f = atan2(frequency_signal[i][1], frequency_signal[i][0]); *\/ */ + /* /\* if (f > M_PI / 2 && f < 2 * M_PI / 3) { *\/ */ + /* /\* frequency_signal[i][0] = 0.0; *\/ */ + /* /\* frequency_signal[i][1] = 0.0; *\/ */ + /* /\* } *\/ */ /* } */ + /* printf("ASDASD!!!\n"); */ fftwf_execute(backward_plan); for (unsigned long i=0; i < len * 2; i += 2) { @@ -401,7 +438,8 @@ sound_gen(const void *inputBuffer, void *outputBuffer, get_frame(buffer, synth, i); } // process buffer - //if (synth->multi) process(buffer); + // process(synth, buffer); + get_spectrum(synth, buffer); // output buffer for( unsigned long i=0; iviz.sample_rate_divider = 1; synth->viz.wi = 0; + synth->fftviz.sample_rate_divider = 1; + synth->fftviz.wi = 0; lpf_init(); synth->fff = create_bw_low_pass_filter(2, SAMPLE_RATE, 400); diff --git a/src/synth_engine.h b/src/synth_engine.h index aee38d9..da15fe2 100644 --- a/src/synth_engine.h +++ b/src/synth_engine.h @@ -15,7 +15,7 @@ #ifndef M_PI -#define M_PI (3.14159265) +#define M_PI (3.14159265) #endif typedef struct lfo_t { @@ -90,6 +90,7 @@ typedef struct { int active; viz_t viz; + viz_t fftviz; osc_t * osctri; } synth_t; diff --git a/src/synth_gui.c b/src/synth_gui.c index ed10547..c210f2b 100644 --- a/src/synth_gui.c +++ b/src/synth_gui.c @@ -191,6 +191,9 @@ draw_signals(synth_t * synth, int x, int y, int width, int height) else DrawCircle(i , y + height / 2 + floor(50 * synth->viz.wave[i - x]), point_radius, RAYWHITE); } + for (int i = x; i < WIDTH - x; i++) { + DrawCircle(i , y + height / 2 + floor(5 * synth->fftviz.wave[i - x]) * -1, point_radius, RED); + } int c = 0; for (int i = 0; i < MIDI_NOTES; i++) { -- cgit v1.2.3