diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/archive/gtk.c (renamed from src/gtk.c) | 0 | ||||
| -rw-r--r-- | src/generator.c | 114 | ||||
| -rw-r--r-- | src/generator.h | 10 | ||||
| -rw-r--r-- | src/gui.h | 8 | ||||
| -rw-r--r-- | src/gui_functions.c | 23 | ||||
| -rw-r--r-- | src/os.c | 255 | ||||
| -rw-r--r-- | src/os.h | 7 | ||||
| -rw-r--r-- | src/osc.c | 51 | ||||
| -rw-r--r-- | src/osc.h | 2 | ||||
| -rw-r--r-- | src/osc_sound.c | 14 | ||||
| -rw-r--r-- | src/sequencer.c | 165 | ||||
| -rw-r--r-- | src/synth.c | 12 | ||||
| -rw-r--r-- | src/synth_engine.h | 13 | ||||
| -rw-r--r-- | src/synth_engine_v2.c | 249 | ||||
| -rw-r--r-- | src/synth_gui.c | 672 | ||||
| -rw-r--r-- | src/web.c | 74 |
16 files changed, 1452 insertions, 217 deletions
diff --git a/src/gtk.c b/src/archive/gtk.c index 2b91b94..2b91b94 100644 --- a/src/gtk.c +++ b/src/archive/gtk.c diff --git a/src/generator.c b/src/generator.c index ab2206f..de75bc8 100644 --- a/src/generator.c +++ b/src/generator.c @@ -3,21 +3,30 @@ #include "generator.h" +float play_tree(sound_node *root, midi_note_t *note) +{ + (void) root; + (void) note; + return 0; +} float generate(generator * g, synth_t * s, int i, sound_node * root) { + (void) g; float sample = 0; - sampe = play_tree(root, s->midi_note[i]); - + sample = play_tree(root, &s->midi_note[i]); + return sample; } int sound_tree_add_node(sound_node * root, const char * cmp) { - + (void) root; + (void) cmp; + return 0; } int @@ -25,52 +34,111 @@ 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")) { + if (! config_read_file(&cfg, path)) { 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); + setting = config_root_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); + char * name = config_setting_name(x); printf("%s -- %d\n", config_setting_name(x), config_setting_type(x)); if (name[0] == 'o') { // osc // TODO - configure_osc(tree, id); + printf("Creating osc: %s\n", config_setting_name(x)); + int count2 = config_setting_length(x); + config_setting_t *s; + + for (int j = 0; j < count2; ++j) { + s = config_setting_get_elem(x, j); + printf("%s -> ", config_setting_name(s)); + switch (config_setting_type(s)) { + case CONFIG_TYPE_INT: + printf("%d\n", config_setting_get_int(s)); + break; + case CONFIG_TYPE_FLOAT: + printf("%f\n", config_setting_get_float(s)); + break; + case CONFIG_TYPE_STRING: + printf("%s\n", config_setting_get_string(s)); + break; + } + } + + //configure_osc(tree, id); } else if (name[0] == 'a') { // adsr // TODO - configure_adsr(tree, id); + printf("Creating adsr: %s\n", config_setting_name(x)); + int count2 = config_setting_length(x); + config_setting_t *s; + + for (int j = 0; j < count2; ++j) { + s = config_setting_get_elem(x, j); + printf("%s -> ", config_setting_name(s)); + switch (config_setting_type(s)) { + case CONFIG_TYPE_INT: + printf("%d\n", config_setting_get_int(s)); + break; + case CONFIG_TYPE_FLOAT: + printf("%f\n", config_setting_get_float(s)); + break; + case CONFIG_TYPE_STRING: + printf("%s\n", config_setting_get_string(s)); + break; + } + } + //configure_adsr(tree, id); } else if (name[0] == 'l') { // lfo // TODO - configure_lfo(tree, id); + printf("Creating lfo: %s\n", config_setting_name(x)); + int count2 = config_setting_length(x); + config_setting_t *s; + + for (int j = 0; j < count2; ++j) { + s = config_setting_get_elem(x, j); + printf("%s -> ", config_setting_name(s)); + switch (config_setting_type(s)) { + case CONFIG_TYPE_INT: + printf("%d\n", config_setting_get_int(s)); + break; + case CONFIG_TYPE_FLOAT: + printf("%f\n", config_setting_get_float(s)); + break; + case CONFIG_TYPE_STRING: + printf("%s\n", config_setting_get_string(s)); + break; + } + } + //configure_lfo(tree, id); } else { - printf("%s: wrong!", name); + printf("%s: wrong!\n", name); } } } - + + 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("adding sound node: %s = %f\n", config_setting_name(x), config_setting_type(x) == CONFIG_TYPE_INT ? (float)config_setting_get_int(x) : config_setting_get_float(x)); + // TODO + sound_tree_add_node(tree, config_setting_name(x)); + } + } + config_destroy(&cfg); + return 0; } diff --git a/src/generator.h b/src/generator.h index df90578..72d10ca 100644 --- a/src/generator.h +++ b/src/generator.h @@ -15,7 +15,7 @@ enum osc_enum { SQR }; -enum adsr_type { +enum adsr_enum { LINEAR, CUBIC }; @@ -43,7 +43,7 @@ typedef struct sound_node { int n; } sound_node; -struct generator { +typedef struct generator { // list of indexes for oscilators, lfos and adsrs float osc[MAX_CMP]; float lfo[MAX_CMP]; @@ -58,12 +58,14 @@ struct generator { //o0-saw:l0-sin:l1-sin:a0-linear //a0-o0/frequency:l0-o0/frequency:l1-l0/frequency:o0-1 -}; +} generator; int sound_tree_add_node(sound_node * root, const char * cmp); // needs to "play" the wiring defined in the struct // using sounds from `s` and the midi note `i` -float generate(generator * g, synth_t * s, int i); +float generate(generator * g, synth_t * s, int i, sound_node * root); + +int load_preset(sound_node * tree, const char * path); #endif /* GENERATOR_H */ @@ -7,8 +7,16 @@ typedef enum screen_e { SCREEN_ERR } screen_e; +typedef enum popup_e { + POPUP_NONE = 0, + POPUP_SAVE_PATCH, + POPUP_LOAD_PATCH, + POPUP_ERR +} popup_e; + typedef struct synth_gui { screen_e screen; + popup_e popup; int audiomidi_initialized; } synth_gui; diff --git a/src/gui_functions.c b/src/gui_functions.c new file mode 100644 index 0000000..b024368 --- /dev/null +++ b/src/gui_functions.c @@ -0,0 +1,23 @@ +/* + * Header library for gui functions + */ +#ifndef SYNTH_GUI_H_ +#define SYNTH_GUI_H_ + +#include "control.h" +#include <raylib.h> + +void frequencyToColor(float frequency, int *red, int *green, int *blue); +float generic_vbar(float val, float def, float min, float max, int x, int y, int width, int height); +float generic_hbar(float val, float def, float min, float max, int x, int y, int width, int height); + +void draw_cc_circle(cc_t * cc, int x, int y, int width, int height); +void draw_cc_hbar(cc_t * cc, int x, int y, int width, int height); +void draw_cc_vbar(cc_t * cc, int x, int y, int width, int height); + +int gui_string_spinner(Rectangle rect, char * text, int * index); + +#endif // SYNTH_GUI_H_ +#ifdef SYNTH_GUI_IMPLEMENTATION + +#endif // SYNTH_GUI_IMPLEMENTATION diff --git a/src/os.c b/src/os.c new file mode 100644 index 0000000..c9f4ee8 --- /dev/null +++ b/src/os.c @@ -0,0 +1,255 @@ +#include "os.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <sys/stat.h> + +// Function to check if a string ends with a specific suffix (case insensitive) +int ends_with_wav(const char *str) { + size_t len = strlen(str); + if (len < 4) return 0; + + const char *suffix = str + len - 4; + return (strcasecmp(suffix, ".wav") == 0); +} + +// Function to extract the example name from folder format: "example name[2048-44.1khz-32bit]" +char* extract_example_name(const char *folder_name) { + char *result = malloc(strlen(folder_name) + 1); + if (!result) return NULL; + + strcpy(result, folder_name); + + // Find the opening bracket and terminate the string there + char *bracket = strchr(result, '['); + if (bracket) { + *bracket = '\0'; + } + + // Remove trailing whitespace + int len = strlen(result); + while (len > 0 && (result[len-1] == ' ' || result[len-1] == '\t')) { + result[--len] = '\0'; + } + + return result; +} + +char* scan_for_wav(const char *directory_path) { + DIR *dir; + struct dirent *entry; + struct stat statbuf; + char full_path[1024]; + char *result = NULL; + size_t result_size = 0; + size_t result_capacity = 1024; + int first_entry = 1; + + // Initialize result buffer + result = malloc(result_capacity); + if (!result) return NULL; + result[0] = '\0'; + + // Open directory + dir = opendir(directory_path); + if (!dir) { + free(result); + return NULL; + } + + // Iterate through directory entries + while ((entry = readdir(dir)) != NULL) { + // Skip . and .. entries + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // Create full path + snprintf(full_path, sizeof(full_path), "%s/%s", directory_path, entry->d_name); + + // Check if it's a directory + if (stat(full_path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { + // This is a subdirectory, scan for WAV files in it + DIR *subdir; + struct dirent *subentry; + + subdir = opendir(full_path); + if (!subdir) continue; + + // Extract example name from folder name + char *example_name = extract_example_name(entry->d_name); + if (!example_name) { + closedir(subdir); + continue; + } + + // Look for WAV files in subdirectory + while ((subentry = readdir(subdir)) != NULL) { + if (ends_with_wav(subentry->d_name)) { + // Calculate needed space for new entry (full path) + size_t needed_space = strlen(full_path) + 1 + strlen(subentry->d_name) + 2; // +2 for ';' and '\0' + if (!first_entry) needed_space += 1; // for semicolon separator + + // Resize buffer if needed + while (result_size + needed_space >= result_capacity) { + result_capacity *= 2; + char *new_result = realloc(result, result_capacity); + if (!new_result) { + free(result); + free(example_name); + closedir(subdir); + closedir(dir); + return NULL; + } + result = new_result; + } + + // Add separator if not first entry + if (!first_entry) { + strcat(result, ";"); + result_size += 1; + } + + // Add the full path + strcat(result, full_path); + strcat(result, "/"); + strcat(result, subentry->d_name); + result_size += strlen(full_path) + 1 + strlen(subentry->d_name); + + first_entry = 0; + } + } + + free(example_name); + closedir(subdir); + } + } + + closedir(dir); + + // If no WAV files found, return empty string + if (result_size == 0) { + strcpy(result, ""); + } + + return result; +} + + +// Main function to scan directory and return formatted string +char* scan_for_wav_pretty(const char *directory_path) { + DIR *dir; + struct dirent *entry; + struct stat statbuf; + char full_path[1024]; + char *result = NULL; + size_t result_size = 0; + size_t result_capacity = 1024; + int first_entry = 1; + + // Initialize result buffer + result = malloc(result_capacity); + if (!result) return NULL; + result[0] = '\0'; + + // Open directory + dir = opendir(directory_path); + if (!dir) { + free(result); + return NULL; + } + + // Iterate through directory entries + while ((entry = readdir(dir)) != NULL) { + // Skip . and .. entries + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // Create full path + snprintf(full_path, sizeof(full_path), "%s/%s", directory_path, entry->d_name); + + // Check if it's a directory + if (stat(full_path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { + // This is a subdirectory, scan for WAV files in it + DIR *subdir; + struct dirent *subentry; + + subdir = opendir(full_path); + if (!subdir) continue; + + // Extract example name from folder name + char *example_name = extract_example_name(entry->d_name); + if (!example_name) { + closedir(subdir); + continue; + } + + // Look for WAV files in subdirectory + while ((subentry = readdir(subdir)) != NULL) { + if (ends_with_wav(subentry->d_name)) { + // Calculate needed space for new entry + size_t needed_space = strlen(example_name) + 1 + strlen(subentry->d_name) + 2; // +2 for ';' and '\0' + if (!first_entry) needed_space += 1; // for semicolon separator + + // Resize buffer if needed + while (result_size + needed_space >= result_capacity) { + result_capacity *= 2; + char *new_result = realloc(result, result_capacity); + if (!new_result) { + free(result); + free(example_name); + closedir(subdir); + closedir(dir); + return NULL; + } + result = new_result; + } + + // Add separator if not first entry + if (!first_entry) { + strcat(result, ";"); + result_size += 1; + } + + // Add the formatted entry + strcat(result, example_name); + strcat(result, "/"); + strcat(result, subentry->d_name); + result_size += strlen(example_name) + 1 + strlen(subentry->d_name); + + first_entry = 0; + } + } + + free(example_name); + closedir(subdir); + } + } + + closedir(dir); + + // If no WAV files found, return empty string + if (result_size == 0) { + strcpy(result, ""); + } + + return result; +} + +/* // Example usage */ +/* int main() { */ +/* const char *test_dir = "/path/to/your/directory"; */ +/* char *result = scan_wav_directory(test_dir); */ + +/* if (result) { */ +/* printf("Found WAV files: %s\n", result); */ +/* free(result); */ +/* } else { */ +/* printf("Error scanning directory or no WAV files found.\n"); */ +/* } */ + +/* return 0; */ +/* } */ diff --git a/src/os.h b/src/os.h new file mode 100644 index 0000000..78617bc --- /dev/null +++ b/src/os.h @@ -0,0 +1,7 @@ +#ifndef OS_H +#define OS_H + +char* scan_for_wav_pretty(const char *directory_path); +char* scan_for_wav(const char *directory_path); + +#endif /* OS_H */ @@ -18,6 +18,12 @@ osc_interpolate(float offset, float start, float end) { return m * x + b; } +/** + * Takes a floating-point position (offset) in the oscillator/buffer. + * Wraps it so it stays between osc->start and osc->len - 1 (cyclically). + * Converts it to an integer index. + * Returns the next index, wrapping back to osc->start if it reaches the end. + */ int osc_next_index(osc_t * osc, float offset) { @@ -27,7 +33,18 @@ osc_next_index(osc_t * osc, float offset) int current_index = (int)offset; return current_index + 1 >= osc->len ? osc->start : current_index + 1; } +// TODO same from AI: check it +/* int */ +/* osc_next_index(osc_t *osc, float offset) { */ +/* float range = osc->len - osc->start; */ +/* // Wrap offset into the [start, len) range */ +/* offset = osc->start + fmodf(offset - osc->start + range, range); */ + +/* // Get integer index and advance */ +/* int next = (int)offset + 1; */ +/* return (next >= osc->len) ? osc->start : next; */ +/* } */ float osc_next_offset(osc_t * osc, float f, float offset) @@ -52,6 +69,40 @@ osc_next_offset(osc_t * osc, float f, float offset) return offset; } +/* static inline float wrap_offset(float x, float start, float len) { */ +/* float range = len - start; */ +/* x -= start; */ +/* x -= range * floorf(x / range); // efficient wrap to [0, range) */ +/* return start + x; */ +/* } */ + +/* float osc_next_offset(osc_t * osc, float f, float offset) */ +/* { */ +/* // Wrap once at the start */ +/* if (offset < osc->start || offset >= osc->len) { */ +/* offset = wrap_offset(offset, osc->start, osc->len); */ +/* } */ + +/* if (osc->type == SAMPLE) { */ +/* // Precomputed constant for (16.35160 * 2^(2 + 5/12)) */ +/* // = base frequency for A2 mapping */ +/* #define BASE_A2_FACTOR 220.00000f */ +/* f /= BASE_A2_FACTOR; */ +/* } */ + +/* // Precompute conversion factor */ +/* float step = f * ((osc->len - osc->start) / (float)SAMPLE_RATE); */ + +/* offset += step; */ + +/* // Single wrap without fmodf (branchless for most cases) */ +/* if (offset >= osc->len) { */ +/* offset -= osc->len; */ +/* } */ + +/* return offset; */ +/* } */ + int osc_load_wav(osc_t * osc, const char * path) { @@ -57,6 +57,8 @@ int osc_next_index(osc_t * osc, float offset); int osc_load_wav(osc_t * osc, const char * path); +void osc_sound_change_wavetable(char *path); + osc_t * make_tri(const char * name); /***************/ diff --git a/src/osc_sound.c b/src/osc_sound.c index 4d34834..fc06a26 100644 --- a/src/osc_sound.c +++ b/src/osc_sound.c @@ -23,8 +23,8 @@ osc_sound(float offset) //osc_load_wav(&OSC_sound, "/home/grm/code/synth-project/waves/Free Wavetables[2048]/Melda Oscillator[2048-44.1khz-32bit]/Melda SKEW.wav"); //osc_load_wav(&OSC_sound, "/home/grm/code/synth-project/waves/Free Wavetables[2048]/Filter Sweep[2048-44.1khz-32bit]/SweepSaw.wav"); //osc_load_wav(&OSC_sound, "/home/grm/code/synth-project/waves/Free Wavetables[2048]/Additive Synth[2048-44.1khz-32bit]/Add Synth7.wav"); - //osc_load_wav(&OSC_sound, "/home/grm/code/synth-project/waves/Free Wavetables[2048]/Korg Analog Synth PhaseShift[2048-44.1khz-32bit]/MS 20 Saw MPS.wav"); - osc_load_wav(&OSC_sound, "/home/grm/code/synth-project-b/waves/Free Wavetables[2048]/Korg Analog Synth PhaseShift[2048-44.1khz-32bit]/MonoPoly Saw PS1.wav"); + osc_load_wav(&OSC_sound, "/home/grm/code/synth-project-b/waves/Free Wavetables[2048]/Korg Analog Synth PhaseShift[2048-44.1khz-32bit]/MS 20 Saw MPS.wav"); + //osc_load_wav(&OSC_sound, "/home/grm/code/synth-project-b/waves/Free Wavetables[2048]/Korg Analog Synth PhaseShift[2048-44.1khz-32bit]/MonoPoly Saw PS1.wav"); OSC_sound.start = wvt_size*0; OSC_sound.len = OSC_sound.start + wvt_size; } @@ -34,6 +34,16 @@ osc_sound(float offset) OSC_sound.data[osc_next_index(&OSC_sound, offset)]); } +void +osc_sound_change_wavetable(char *path) +{ + float * old_data = OSC_sound.data; + osc_load_wav(&OSC_sound, path); + OSC_sound.start = wvt_size*0; + OSC_sound.len = OSC_sound.start + wvt_size; + free(old_data); +} + float osc_sound_next(float f, float offset) { diff --git a/src/sequencer.c b/src/sequencer.c new file mode 100644 index 0000000..12c0741 --- /dev/null +++ b/src/sequencer.c @@ -0,0 +1,165 @@ +#include <stdlib.h> +#include <portmidi.h> +#include <porttime.h> +#include <stdio.h> +#include <string.h> +#include <raylib.h> +#include <pthread.h> +#include <math.h> + +#include "synth_common.h" + +#define MIDI_NOTE_ON 0x90 +#define MIDI_NOTE_OFF 0x80 +#define MIDI_CHANNEL 0 +#define SLEEP_INTERVAL_MS 2.0 // main loop sleep + +typedef struct step_t { + PmTimestamp note_on; + PmTimestamp note_off; + float duration; + int note; +} step_t; + +typedef struct pattert_t { + step_t *steps; + uint n; // total + uint c; // current +} pattern_t; + +typedef struct sequencer_t { + PortMidiStream *stream; + int exitted; + + float bpm; + int steps; + pattern_t *pattern; +} sequencer_t; + +sequencer_t * +init_sequencer() { + sequencer_t *seq = (sequencer_t *)malloc(sizeof(sequencer_t)); + + seq->exitted = 0; + seq->bpm = 100.0f; + seq->steps = 16; + + seq->pattern = (pattern_t *)malloc(sizeof(pattern_t)); + seq->pattern->steps = (step_t *)malloc(16 * sizeof(step_t)); + seq->pattern->n = 16; + seq->pattern->c = 0; + + for (uint i = 0; i < seq->pattern->n; i++) { + step_t *step = &seq->pattern->steps[i]; + step->note = 40 + i; + } + + seq->stream = NULL; + PmError err = Pm_Initialize(); + if (err != pmNoError) { + fprintf(stdout, "Pm Error: %s\n", Pm_GetErrorText(err)); + } + + const PmDeviceInfo *info; + int i; + for (i = 0; i < Pm_CountDevices(); i++) { + info = Pm_GetDeviceInfo(i); + if (!info->output) { + continue; + } + printf("%d: %s [input: %d output: %d opened: %d is_virt:%d] (interface: %s)\n", i, info->name, info->input, info->output, info->opened, info->is_virtual, info->interf); + if (0 == strcmp("Midi Through Port-0", info->name)) + break; + } + + printf("Selected device: %s [input: %d output: %d opened: %d is_virt:%d] (interf: %s)\n", info->name, info->input, info->output, info->opened, info->is_virtual, info->interf); + + //no start for output Pt_Start(1, midiCallback, m); + err = Pm_OpenOutput(&(seq->stream), i, NULL, 512, NULL, NULL, 0); + + if (err != pmNoError) { + fprintf(stdout, "Pm Error: %s\n", Pm_GetErrorText(err)); + } + + return seq; +} + +void +free_sequencer(sequencer_t * seq) { + Pm_WriteShort(seq->stream, 0, Pm_Message(0xFC /* MIDI_STOP */, 0, 0)); + + Pm_Close(seq->stream); + Pm_Terminate(); + + free(seq); +} + +static inline double ms_per_beat(sequencer_t * seq) { + return 60000.0 / seq->bpm; +} +static inline double ms_per_step(sequencer_t * seq) { + return ms_per_beat(seq) / seq->steps; +} + +void +rayrun(sequencer_t * seq) { + (void)seq; + SetTraceLogLevel(LOG_ERROR); + InitWindow(WIDTH, HEIGHT, "Raylib sequencer"); + SetTargetFPS(60); + while (!WindowShouldClose()) { + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + ClearBackground(BLACK); + + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + seq->exitted = 1; + CloseWindow(); +} + +void * +sequencer_thread(void *arg) +{ + sequencer_t *seq = (sequencer_t *)arg; + + while (!seq->exitted) { + //PmTimestamp current_time = Pt_Time(); + + step_t *step = &seq->pattern->steps[seq->pattern->c++]; + if (seq->pattern->c >= seq->pattern->n) seq->pattern->c = 0; + + Pm_WriteShort(seq->stream, 0, Pm_Message(MIDI_NOTE_ON | MIDI_CHANNEL, step->note, 100)); + + Pt_Sleep(ms_per_step(seq)*4); + Pm_WriteShort(seq->stream, 0, Pm_Message(MIDI_NOTE_OFF | MIDI_CHANNEL, step->note, 0)); + + //Pt_Sleep(SLEEP_INTERVAL_MS); + } + + printf("[Sequencer] Stopping.\n"); + return NULL; +} + + +int +main(void) { + sequencer_t * seq = init_sequencer(); + + pthread_t seq_thread; + + // start sequencer thread + pthread_create(&seq_thread, NULL, sequencer_thread, seq); + + // start gui + rayrun(seq); + + pthread_join(seq_thread, NULL); + + free_sequencer(seq); + + return 0; +} diff --git a/src/synth.c b/src/synth.c index 1b5187b..f5038b6 100644 --- a/src/synth.c +++ b/src/synth.c @@ -26,17 +26,27 @@ #include "synth_engine.h" #include "synth_gui.h" #include "web.h" +//#include "generator.h" + +// Add this function to your code +const char *__lsan_default_options() { + return "suppressions=lsan.supp:print_suppressions=0"; +} int main(void) { synth_t * synth = init_synth(); + /* sound_node sn = {0}; */ + /* load_preset(&sn, "preset.synth"); */ + init_web(synth); - + rayrun(synth); free_web(); free_synth(synth); + return 0; } diff --git a/src/synth_engine.h b/src/synth_engine.h index e198c22..6bc504a 100644 --- a/src/synth_engine.h +++ b/src/synth_engine.h @@ -52,9 +52,11 @@ typedef struct synth_viz { int wave_enabled; int adsr_enabled; int adsr_graph_enabled; + int filter_adsr_graph_enabled; int osc_enabled; int freeze; int rate_divider; + float y_divider; float *wave_buffer_data; PaUtilRingBuffer wave_buffer; @@ -101,8 +103,6 @@ typedef struct { int autogain; - float x; - midi_note_t midi_note[MIDI_NOTES]; midi_note_t * midi_active[MIDI_NOTES]; int midi_active_n; @@ -110,6 +110,9 @@ typedef struct { adsr_t adsr; adsr_t f_adsr; + int lfo_on_pitch; + int lfo_on_cutoff; + float lfo_index; lfo_t lfo; int octave; @@ -119,7 +122,7 @@ typedef struct { int deli; cc_t cc_del_time; cc_t cc_del_feedback; - unsigned long long counter; + unsigned long long delay_counter; int f_adsr_enabled; int filter; @@ -131,7 +134,7 @@ typedef struct { int modifiers[16]; int modi; - float (*gen[7]) (float freq, midi_note_t * midi_note, float x, unsigned int sample_rate); + float (*gen[7]) (float freq, midi_note_t * midi_note); int geni; BWLowPass* fff; @@ -147,6 +150,8 @@ typedef struct { struct midi_t *midi; + char * patch_name; + synth_gui gui; } synth_t; diff --git a/src/synth_engine_v2.c b/src/synth_engine_v2.c index 4d9721c..3c5238e 100644 --- a/src/synth_engine_v2.c +++ b/src/synth_engine_v2.c @@ -1,7 +1,6 @@ #include "synth_engine.h" #include "synth_math.h" #include "lowpass.h" -#include "filter.h" #include "control.h" #include "sound.h" #include "midi.h" @@ -12,63 +11,56 @@ #include <time.h> float -gen0(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +gen0(float f, midi_note_t * midi_note) { - (void)x; (void)sample_rate; float sample = osc_sin(midi_note->wvt_index); midi_note->wvt_index = osc_sin_next(f, midi_note->wvt_index); return sample; } float -gen1(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +gen1(float f, midi_note_t * midi_note) { - (void)x; (void)sample_rate; float sample = osc_saw(midi_note->wvt_index); midi_note->wvt_index = osc_saw_next(f, midi_note->wvt_index); return sample; } float -gen2(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +gen2(float f, midi_note_t * midi_note) { - (void)x; (void)sample_rate; float sample = osc_weird(midi_note->wvt_index); midi_note->wvt_index = osc_weird_next(f, midi_note->wvt_index); return sample; } float -gen3(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +gen3(float f, midi_note_t * midi_note) { - (void)x; (void)sample_rate; float sample = osc_tri(midi_note->wvt_index); midi_note->wvt_index = osc_tri_next(f, midi_note->wvt_index); return sample; } float -gen4(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +gen4(float f, midi_note_t * midi_note) { - (void)x; (void)sample_rate; float sample = osc_sound(midi_note->wvt_index); midi_note->wvt_index = osc_sound_next(f, midi_note->wvt_index); return sample; } float -gen5(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +gen5(float f, midi_note_t * midi_note) { - (void)x; (void)sample_rate; float sample = osc_digisaw(midi_note->wvt_index); midi_note->wvt_index = osc_digisaw_next(f, midi_note->wvt_index); return sample; } float -gen6(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +gen6(float f, midi_note_t * midi_note) { - (void)x; (void)sample_rate; float sample = osc_sqr(midi_note->wvt_index); midi_note->wvt_index = osc_sqr_next(f, midi_note->wvt_index); return sample; @@ -123,7 +115,6 @@ float prev_sample = 0.0f; biquad_filter_t biquad = {0}; - void do_fliter(synth_t *synth, float *sample, unsigned int sample_rate, int frame) { @@ -149,14 +140,23 @@ do_fliter(synth_t *synth, float *sample, unsigned int sample_rate, int frame) } } - cutoff = 50 + cutoff * fix_adsr(&synth->f_adsr, - note->noteOn, - note->noteOff, - note->elapsed, - note->noteOffSample); + float adsr = fix_adsr(&synth->f_adsr, + note->noteOn, + note->noteOff, + note->elapsed, + note->noteOffSample); + if (adsr < 0.1) adsr = 0.1; + cutoff = cutoff * adsr; + reso = reso * adsr; } } + if (synth->lfo_on_cutoff) { + cutoff = cutoff + 22000 * CC_GET(lfo_amp) * osc_sin(synth->lfo_index); + if (cutoff <= 0) cutoff = 0.001; + synth->lfo_index = osc_sin_next(CC_GET(lfo_freq), synth->lfo_index); + } + if (synth->filter) { if (cutoff == 0) cutoff = 0.001; lpf_update(reso, cutoff, sample_rate); @@ -195,7 +195,7 @@ get_max_sample(synth_t *synth, int test_size) note_dup.adsr = note->adsr; note_dup.active = note->active; for (int i = 0; i < test_size; i++) { - osc_wave[i] += synth->gen[synth->geni](note_dup.freq * 3, ¬e_dup, synth->x, SAMPLE_RATE); + osc_wave[i] += synth->gen[synth->geni](note_dup.freq * 3, ¬e_dup); } } @@ -207,8 +207,6 @@ get_max_sample(synth_t *synth, int test_size) return max; } -# include <stdint.h> // uint32_t - float Q_rsqrt(float number) { union { @@ -221,6 +219,25 @@ float Q_rsqrt(float number) } float +make_single_sample(synth_t *synth, float freq, midi_note_t * note) +{ + return synth->gen[synth->geni](freq, note); +} + +/** + * @brief generate a sample from the currently selected generator + * + * Applies ADSR, filter and merges all the `voices` using root mean square + * + * @param synth synth struct + * @param sample_rate sample rate + * @param frame # of current frame + * + * @return float returns the generated sample + * + * @note This is called SAMPLE_RATE times per second (on average), needs to be quick + */ +float make_sample(synth_t * synth, unsigned int sample_rate, int frame) { float sample = 0.0f; @@ -241,6 +258,10 @@ make_sample(synth_t * synth, unsigned int sample_rate, int frame) synth->adsr.d = CC_GET(adsr_d); synth->adsr.s = CC_GET(adsr_s); synth->adsr.r = CC_GET(adsr_r); + + // global lfo + /* float glfo_value = osc_sin(synth->lfo_index); */ + /* synth->lfo_index = osc_sin_next(CC_GET(lfo_freq), synth->lfo_index); */ for (int i = 0; i < synth->midi_active_n; i++) { note = synth->midi_active[i]; @@ -251,31 +272,35 @@ make_sample(synth_t * synth, unsigned int sample_rate, int frame) note->noteOffSample); float targ_freq = note->freq * CC_GET(pitch); - targ_freq = targ_freq + targ_freq * CC_GET(lfo_amp) * osc_sin(note->lfo_index); + float lfo_value = osc_sin(note->lfo_index); + + if (synth->lfo_on_pitch) { + targ_freq = targ_freq + + targ_freq * CC_GET(lfo_amp) * lfo_value; // can switch to glfo + } note->lfo_index = osc_sin_next(CC_GET(lfo_freq), note->lfo_index); - sample += rms * note->velocity * adsr * synth->gen[synth->geni](targ_freq, - note, - synth->x, - sample_rate); + sample += rms * note->velocity * adsr * make_single_sample(synth, + targ_freq, + note); } - /* filter */ do_fliter(synth, &sample, sample_rate, frame); - sample = CC_GET(gain) * sample; // band stop for high freqs //sample = bw_band_stop(synth->fff2, sample); - //if (synth->clamp) sample = clamp(sample, -1, 1); + if (synth->clamp) sample = clamp(sample, -1, 1); // autogain - if (synth->autogain && (sample >= 1 || sample <= -1)) { - synth->cc_gain.target *= 0.999; + if (synth->autogain) { + if (sample >= 1 || sample <= -1) { + synth->cc_gain.target *= 0.999; + } } //printf("CLICK! %f\n", fabsf(prev_sample) - fabsf(sample)); @@ -309,6 +334,25 @@ increment_synth(synth_t *synth) float prev = 0; +/** + * @brief generates a single frame, also handles the delay + * + * each frame in the current implementation is a pair of samples one for the + * left channel and one for the right. We first check with notes_active if + * there are any notes still ringing so we can set the active timer to 0. Then + * we get the sample from the generators, and we blend any possible delay sound + * from before as well as add the new sample to the delay for future use. Then + * we increment the synth counters to be ready for the next sample and we make + * sure to write our sample to the viz buffer as well for visuals. + * + * @param outputBuffer where to store the generated samples + * @param synth synth struct + * @param frame # of frame in the buffer + * + * @return void void + * + * @note <--> + */ void get_frame(void *outputBuffer, synth_t *synth, int frame) { @@ -318,17 +362,18 @@ get_frame(void *outputBuffer, synth_t *synth, int frame) if (!notes_active(synth)) { synth->f_adsr.elapsed = 0; synth->active = 0; + synth->lfo_index = 0.0f; // auto gain test //synth->cc_gain.target = 1; } if (!synth->delay) { - synth->counter = 0; + synth->delay_counter = 0; } sample = make_sample(synth, SAMPLE_RATE, frame); - synth->counter++; - if (synth->counter >= (unsigned long long)(synth->cc_del_time.target * SAMPLE_RATE)) { + synth->delay_counter++; + if (synth->delay_counter >= (unsigned long long)(synth->cc_del_time.target * SAMPLE_RATE)) { int idx = (synth->deli - (int)(synth->cc_del_time.target * SAMPLE_RATE)) % (SAMPLE_RATE * 10); float tmp; if (idx >= 0) { @@ -342,8 +387,6 @@ get_frame(void *outputBuffer, synth_t *synth, int frame) add_to_delay(synth, sample); - - //sample = clamp(sample, -1, 1); *out++ = sample; @@ -393,7 +436,37 @@ void smooth_buffer(float *buffer, int frames_per_buffer, float smooth_factor) { } } - +/** + * @brief portaudio callback to generate next batch of samples + * + * For a given SAMPLE_RATE we need to create SAMPLE_RATE frames per second so + * we get sound from the speaker. A frame is all the different channel values + * for a given timestamp. A stereo setup (like this) has 2 samples in each + * frame (left and right). The soundcard requests frames in a buffer where we + * control the size. The more frames the buffer has, the more latency we + * introduce to the system since in order for our changes in the synth to take + * effect the soundcard needs to request the next buffer. We handle this by + * picking a low enough framesPerBuffer number (FRAMES_PER_BUFFER). Since each + * second the soundcard needs to generate SAMPLE_RATE frames, it will call the + * sound_gen function SAMPLE_RATE / FRAMES_PER_BUFFER times. It follows that + * this function should be as fast as possible for realtime performance (with + * the upper bound of 1 / (SAMPLE_RATE / FRAMES_PER_BUFFER) seconds). Failing + * to meet this *will* result in loud clicks and noise. + * + * This function is called asynchronously by the portaudio thread. It prepares + * all the CCs and the fills the outputBuffer frame by frame. + * + * @param inputBuffer unused + * @param outputBuffer where the resulting samples are written + * @param framesPerBuffer how many frames the buffer has + * @param timeInfo unused + * @param statusFlags unused + * @param synthData user data, contains the synth struct + * + * @return int paContinue(0) on success + * + * @note this funcion shouldn't fail + */ int sound_gen(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, @@ -402,12 +475,10 @@ sound_gen(const void *inputBuffer, void *outputBuffer, void *synthData) { synth_t *synth = (synth_t*)synthData; - float *out = (float*)outputBuffer; - if (!synth->sound_active) return 0; //paContinue; float buffer[2 * FRAMES_PER_BUFFER]; - + float *out = (float*)outputBuffer; (void) timeInfo; (void) statusFlags; (void) inputBuffer; @@ -424,7 +495,7 @@ sound_gen(const void *inputBuffer, void *outputBuffer, get_frame(buffer, synth, frame); } - smooth_buffer(buffer, framesPerBuffer, 0.1f); + //smooth_buffer(buffer, framesPerBuffer, 0.1f); // output buffer for( unsigned long i=0; i<framesPerBuffer * 2; i += 2 ) { @@ -439,7 +510,7 @@ sound_gen(const void *inputBuffer, void *outputBuffer, clock_t end = clock(); double time_spent = (double)(end - begin) / CLOCKS_PER_SEC; if (time_spent > (double)FRAMES_PER_BUFFER / SAMPLE_RATE) { - printf("To generate %d samples per second we need to generate a sample every 1 / %d second. Since we generate samples by batching them in groups of %d, each group should take longer than %f. This one took %f\n", SAMPLE_RATE, SAMPLE_RATE, FRAMES_PER_BUFFER, (float)FRAMES_PER_BUFFER / SAMPLE_RATE, time_spent); + printf("To generate %d samples per second we need to generate a sample every 1 / %d second. Since we generate samples by batching them in groups of %d, each group should not take longer than %f. This one took %f\n", SAMPLE_RATE, SAMPLE_RATE, FRAMES_PER_BUFFER, (float)FRAMES_PER_BUFFER / SAMPLE_RATE, time_spent); } return paContinue; @@ -456,7 +527,7 @@ init_synth(void) CC(synth->cc_cutoff, "cutoff", 50, 22000, 30, 5000); //CC(synth->cc_resonance, "resonance", 1, 10, .02, 1); CC(synth->cc_resonance, "resonance", 0.01, 10, .02, 0.5); - CC(synth->cc_lfo_freq, "lfo_freq", 1, 1000, 2, 1); + CC(synth->cc_lfo_freq, "lfo_freq", 0.1, 10, .01, 1); CC(synth->cc_lfo_amp, "lfo_amp", 0, 1, .01f, 0); CC(synth->cc_pitch, "pitch", -3, 4, 0.01f, 1); CC(synth->cc_adsr_a, "attack", 0, 3, 0.01f, 0.00); @@ -477,8 +548,6 @@ init_synth(void) // synth->modi = 0; synth->autogain = 1; - synth->x = 1; - synth->adsr.a = 0.00001f; synth->adsr.peak = 1.0f; synth->adsr.d = 0.3; @@ -493,6 +562,10 @@ init_synth(void) synth->f_adsr.r = 0.4; synth->f_adsr.elapsed = 0; + synth->lfo_on_pitch = 0; + synth->lfo_on_cutoff = 0; + synth->lfo_index = 0.0f; + synth->lfo.freq = 1.0f; synth->lfo.amp = 0.0f; synth->lfo.elapsed = 0; @@ -517,11 +590,11 @@ init_synth(void) synth->delay = 0; synth->del = (float *) calloc(sizeof(float), SAMPLE_RATE * 30); synth->deli = 0; - synth->counter = 0; + synth->delay_counter = 0; synth->f_adsr_enabled = 0; - synth->filter = 1; - synth->biquad = 0; + synth->filter = 0; + synth->biquad = 1; synth->biquad_type = 'l'; synth->clamp = 1; @@ -541,10 +614,11 @@ init_synth(void) synth->fff2 = create_bw_band_stop_filter(8, SAMPLE_RATE, 15000, 22000); synth->viz.rate_divider = 15; -// for (int i = 0; i < RING_SIZE; i++) synth->viz.wave_buffer_data[i] = 0; + synth->viz.y_divider = 1; + // for (int i = 0; i < RING_SIZE; i++) synth->viz.wave_buffer_data[i] = 0; synth->viz.wave_buffer_data = (float *)calloc(sizeof(float), RING_SIZE); PaUtil_InitializeRingBuffer(&synth->viz.wave_buffer, sizeof(float), RING_SIZE, synth->viz.wave_buffer_data); -// for (int i = 0; i < RING_SIZE; i++) synth->viz.fft_buffer_data[i] = 0; + // for (int i = 0; i < RING_SIZE; i++) synth->viz.fft_buffer_data[i] = 0; synth->viz.fft_buffer_data = (float *)calloc(sizeof(float), RING_SIZE); PaUtil_InitializeRingBuffer(&synth->viz.fft_buffer, sizeof(float), RING_SIZE, synth->viz.fft_buffer_data); @@ -554,16 +628,15 @@ init_synth(void) synth->viz.fft_output_buffer = (float *)calloc(sizeof(float), RING_SIZE * 8); synth->viz.fft_smooth_buffer = (float *)calloc(sizeof(float), RING_SIZE * 8); - synth->viz.spectrum_enabled = 0; + synth->viz.spectrum_enabled = 1; synth->viz.wave_enabled = 0; synth->viz.adsr_enabled = 0; - synth->viz.adsr_graph_enabled = 1; - synth->viz.osc_enabled = 0; + synth->viz.adsr_graph_enabled = 0; + synth->viz.osc_enabled = 1; synth->viz.freeze = 0; synth->viz.tmp_index = 0; synth->wvt_pos = 0; - synth->sound_active = 0; strcpy(synth->soundcard.name, "default"); @@ -576,6 +649,8 @@ init_synth(void) synth->gui.screen = SCREEN_MAIN; synth->gui.audiomidi_initialized = 0; + synth->patch_name = NULL; + return synth; } @@ -599,6 +674,10 @@ free_synth(synth_t * synth) free_bw_band_stop(synth->fff2); free(synth->del); + + lpf_close(); + + if (synth->patch_name) free(synth->patch_name); free(synth); } @@ -630,7 +709,7 @@ load_synth(synth_t *synth, const char *path) config_init(&cfg); /* Read the file. If there is an error, report it and exit. */ - if(! config_read_file(&cfg, "TEST.cfg")) + if(! config_read_file(&cfg, path)) { fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg), config_error_line(&cfg), config_error_text(&cfg)); @@ -645,6 +724,8 @@ load_synth(synth_t *synth, const char *path) fprintf(stderr, "No 'synth.name' setting in configuration file.\n"); config_lookup_int(&cfg, "synth.generator", &synth->geni); + config_lookup_int(&cfg, "synth.octave", &synth->octave); + config_lookup_int(&cfg, "synth.clamp", &synth->clamp); config_lookup_float(&cfg, "synth.adsr.a", &FLOAT); synth->cc_adsr_a.target = FLOAT; @@ -656,6 +737,18 @@ load_synth(synth_t *synth, const char *path) synth->cc_adsr_s.target = FLOAT; config_lookup_float(&cfg, "synth.adsr.r", &FLOAT); synth->cc_adsr_r.target = FLOAT; + + config_lookup_int(&cfg, "synth.f_adsr.enable", &synth->f_adsr_enabled); + config_lookup_float(&cfg, "synth.f_adsr.a", &FLOAT); + synth->cc_f_adsr_a.target = FLOAT; + config_lookup_float(&cfg, "synth.f_adsr.peak", &FLOAT); + synth->cc_f_adsr_peak.target = FLOAT; + config_lookup_float(&cfg, "synth.f_adsr.d", &FLOAT); + synth->cc_f_adsr_d.target = FLOAT; + config_lookup_float(&cfg, "synth.f_adsr.s", &FLOAT); + synth->cc_f_adsr_s.target = FLOAT; + config_lookup_float(&cfg, "synth.f_adsr.r", &FLOAT); + synth->cc_f_adsr_r.target = FLOAT; config_lookup_int(&cfg, "synth.delay.enable", &synth->delay); config_lookup_float(&cfg, "synth.delay.time", &FLOAT); @@ -663,6 +756,10 @@ load_synth(synth_t *synth, const char *path) config_lookup_float(&cfg, "synth.delay.feedback", &FLOAT); synth->cc_del_feedback.target = FLOAT; + config_lookup_int(&cfg, "synth.biquad.enable", &synth->biquad); + int placeholder; + config_lookup_int(&cfg, "synth.biquad.type", &placeholder); + synth->biquad_type = placeholder; config_lookup_int(&cfg, "synth.filter.enable", &synth->filter); config_lookup_float(&cfg, "synth.filter.cutoff", &FLOAT); synth->cc_cutoff.target = FLOAT; @@ -673,6 +770,8 @@ load_synth(synth_t *synth, const char *path) synth->cc_lfo_freq.target = FLOAT; config_lookup_float(&cfg, "synth.lfo.amp", &FLOAT); synth->cc_lfo_amp.target = FLOAT; + config_lookup_int(&cfg, "synth.lfo.on_pitch", &synth->lfo_on_pitch); + config_lookup_int(&cfg, "synth.lfo.on_cutoff", &synth->lfo_on_cutoff); config_lookup_int(&cfg, "synth.autogain", &synth->autogain); config_lookup_float(&cfg, "synth.gain", &FLOAT); @@ -687,10 +786,10 @@ save_synth(synth_t *synth, const char *path) { (void)path; - static const char *output_file = "TEST.cfg"; + const char *output_file = path;//"TEST.cfg"; config_t cfg; - config_setting_t *root, *setting, *group, *adsr, *delay, *lfo, *filter; + config_setting_t *root, *setting, *group, *adsr, *f_adsr, *delay, *lfo, *filter; config_init(&cfg); root = config_root_setting(&cfg); @@ -699,10 +798,14 @@ save_synth(synth_t *synth, const char *path) group = config_setting_add(root, "synth", CONFIG_TYPE_GROUP); setting = config_setting_add(group, "name", CONFIG_TYPE_STRING); - config_setting_set_string(setting, "example synth name"); + config_setting_set_string(setting, path); setting = config_setting_add(group, "generator", CONFIG_TYPE_INT); config_setting_set_int(setting, synth->geni); + setting = config_setting_add(group, "octave", CONFIG_TYPE_INT); + config_setting_set_int(setting, synth->octave); + setting = config_setting_add(group, "clamp", CONFIG_TYPE_INT); + config_setting_set_int(setting, synth->clamp); adsr = config_setting_add(group, "adsr", CONFIG_TYPE_GROUP); setting = config_setting_add(adsr, "a", CONFIG_TYPE_FLOAT); @@ -716,6 +819,20 @@ save_synth(synth_t *synth, const char *path) setting = config_setting_add(adsr, "r", CONFIG_TYPE_FLOAT); config_setting_set_float(setting, synth->cc_adsr_r.target); + f_adsr = config_setting_add(group, "f_adsr", CONFIG_TYPE_GROUP); + setting = config_setting_add(f_adsr, "enable", CONFIG_TYPE_INT); + config_setting_set_int(setting, synth->f_adsr_enabled); + setting = config_setting_add(f_adsr, "a", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_f_adsr_a.target); + setting = config_setting_add(f_adsr, "peak", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_f_adsr_peak.target); + setting = config_setting_add(f_adsr, "d", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_f_adsr_d.target); + setting = config_setting_add(f_adsr, "s", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_f_adsr_s.target); + setting = config_setting_add(f_adsr, "r", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_f_adsr_r.target); + delay = config_setting_add(group, "delay", CONFIG_TYPE_GROUP); setting = config_setting_add(delay, "enable", CONFIG_TYPE_INT); config_setting_set_int(setting, synth->delay); @@ -724,6 +841,12 @@ save_synth(synth_t *synth, const char *path) setting = config_setting_add(delay, "feedback", CONFIG_TYPE_FLOAT); config_setting_set_float(setting, synth->cc_del_feedback.target); + filter = config_setting_add(group, "biquad", CONFIG_TYPE_GROUP); + setting = config_setting_add(filter, "enable", CONFIG_TYPE_INT); + config_setting_set_int(setting, synth->biquad); + setting = config_setting_add(filter, "type", CONFIG_TYPE_INT); + config_setting_set_int(setting, synth->biquad_type); + filter = config_setting_add(group, "filter", CONFIG_TYPE_GROUP); setting = config_setting_add(filter, "enable", CONFIG_TYPE_INT); config_setting_set_int(setting, synth->filter); @@ -737,6 +860,10 @@ save_synth(synth_t *synth, const char *path) config_setting_set_float(setting, synth->cc_lfo_freq.target); setting = config_setting_add(lfo, "amp", CONFIG_TYPE_FLOAT); config_setting_set_float(setting, synth->cc_lfo_amp.target); + setting = config_setting_add(lfo, "on_pitch", CONFIG_TYPE_INT); + config_setting_set_int(setting, synth->lfo_on_pitch); + setting = config_setting_add(lfo, "on_cutoff", CONFIG_TYPE_INT); + config_setting_set_int(setting, synth->lfo_on_cutoff); setting = config_setting_add(group, "autogain", CONFIG_TYPE_INT); config_setting_set_int(setting, synth->autogain); diff --git a/src/synth_gui.c b/src/synth_gui.c index 215b8f5..265b539 100644 --- a/src/synth_gui.c +++ b/src/synth_gui.c @@ -3,6 +3,7 @@ #include <portaudio.h> #define RAYGUI_IMPLEMENTATION #include "raygui.h" +#include "os.h" //#include "raylib.h" void @@ -33,7 +34,7 @@ draw_text(synth_t * synth, int x, int y) void mouse(synth_t *synth) { - float m = GetMouseWheelMove(); + float m = GetMouseWheelMove(); int x = 0; if (m < 0) x = -1; else if (m > 0) x = 1; @@ -236,14 +237,6 @@ void frequencyToColor(float frequency, int *red, int *green, int *blue) { #include "fftw3.h" -float -amp(float re, float im) { - float a = fabsf(re); - float b = fabsf(im); - if (a < b) return b; - return a; -} - void draw_adsr(synth_t *synth, int x, int y, int width, int height) { @@ -284,7 +277,7 @@ draw_wave(synth_t *synth, int x, int y, int width, int height) col = MAGENTA; else col = WHITE; - DrawPixel(i + x , y + height / 2 + (int)floor(50.0 * synth->viz.wave_viz_buffer[ii + j]), col); + DrawPixel(i + x , y + height / 2 + (int)floor(50.0 * synth->viz.wave_viz_buffer[ii + j] * synth->viz.y_divider), col); } } /* for (int j = 0; j < 100; j++) { */ @@ -364,42 +357,32 @@ void draw_osc(synth_t * synth, int x, int y, int width, int height) { DrawRectangle(x, y, width, height, BLACK); - DrawRectangleLines(x, y, width, height, WHITE); + DrawRectangleLines(x, y, width, height, GRAY); + float osc_wave[width]; + float osc_max = 0; + for (int i = 0; i < width; i++) { osc_wave[i] = 0; } - for (int i = 0; i < synth->midi_active_n; i++) { - midi_note_t * note = synth->midi_active[i]; - midi_note_t note_dup; - note_dup.freq = note->freq; - note_dup.channel = note->channel; - note_dup.noteOn = note->noteOn; - note_dup.noteOff = note->noteOff; - note_dup.velocity = note->velocity; - note_dup.wvt_index = 0; - note_dup.lfo_index = note->lfo_index; - note_dup.elapsed = note->elapsed; - note_dup.noteOffSample = note->noteOffSample; - note_dup.adsr = note->adsr; - note_dup.active = note->active; - for (int i = 0; i < width; i++) { - float freq = SAMPLE_RATE/(float)width; - osc_wave[i] += synth->gen[synth->geni](freq, ¬e_dup, synth->x, SAMPLE_RATE); - } - break; - } - - float max = 0; + midi_note_t note_dup = {0}; for (int i = 0; i < width; i++) { - if (fabs(osc_wave[i]) > max) max = fabs(osc_wave[i]); + float freq = SAMPLE_RATE/(float)width; + osc_wave[i] += synth->gen[synth->geni](freq, ¬e_dup); } - if (synth->midi_active_n) { - for (int i = 0; i < width; i++) { - DrawPixel(i + x , y + height / 2.0 + floor(height/2.0 * osc_wave[i] / max), RED); - } + for (int i = 0; i < width; i++) { + if (fabs(osc_wave[i]) > osc_max) osc_max = fabs(osc_wave[i]); + } + + Color color = GRAY; + if (synth->midi_active_n > 0) { + color = RED; + } + for (int i = 0; i < width; i++) { + //DrawPixel(i + x , y + height / 2.0 + floor(height/2.0 * osc_wave[i] / osc_max), WHITE); + DrawCircle(i + x , y + height / 2.0 + floor(height/2.0 * osc_wave[i] / osc_max), 2, color); } } @@ -482,12 +465,222 @@ draw_adsr_graph(synth_t * synth, int x, int y, int width, int height) } void +draw_filter_adsr_graph(synth_t * synth, int x, int y, int width, int height) +{ + adsr_t f_adsr; + + f_adsr.a = synth->cc_f_adsr_a.target; + f_adsr.peak = synth->cc_f_adsr_peak.target; + f_adsr.d = synth->cc_f_adsr_d.target; + f_adsr.s = synth->cc_f_adsr_s.target; + f_adsr.r = synth->cc_f_adsr_r.target; + + float total = synth->cc_f_adsr_a.target + synth->cc_f_adsr_d.target + synth->cc_f_adsr_r.target + 0.3; /*sustain*/ + int total_samples = total * SAMPLE_RATE; + int a = synth->cc_f_adsr_a.target/total * width; + int d = synth->cc_f_adsr_d.target/total * width; + int s = 0.3/total * width; + int r = synth->cc_f_adsr_r.target/total * width; + + float f_adsr_graph[width]; + for (int i = 0; i < width; i++) { + float point = 0; + + if (i < a) { //draw attack + point = fix_adsr(&f_adsr, + 1, // note On + 0, // note Off + (float)i/width * total_samples, + 0); + } else if (i < a + d) { // draw decay + point = fix_adsr(&f_adsr, + 1, // note On + 0, // note Off + (float)i/width * total_samples, + 0); + } else if (i < a + d + s) { // draw sustain + point = fix_adsr(&f_adsr, + 1, // note On + 0, // note Off + (float)i/width * total_samples, + 0); + } else if (r && i < a + d + s + r) { // draw release + point = fix_adsr(&f_adsr, + 0, // note On + 1, // note Off + (float)i/width * total_samples, + (float)(a + d + s)/width * total_samples); + } + f_adsr_graph[i] = point * height; + } + f_adsr_graph[0] = f_adsr_graph[1]; // remove 1st 0 + + // Draw filter ADSR in blue to distinguish from amplitude ADSR + for (int i = 0; i < width; i++) { + DrawPixel(i + x , y + height - f_adsr_graph[i], BLUE); + } + + // Draw current position indicators for active notes + for (int i = 0; i < synth->midi_active_n; i++) { + midi_note_t * note = synth->midi_active[i]; + int elapsed_samples = note->elapsed; + float elapsed_secs = (float)elapsed_samples / SAMPLE_RATE; + int j = elapsed_secs / total * width; + + if (note->noteOff == 0) { + if (j < a + d) { + DrawCircle(j + x , y + height - f_adsr_graph[j], 3, BLUE); + } else if (j < a + d + s - 1) { + DrawCircle(j + x , y + height - f_adsr_graph[j], 3, BLUE); + } else if (j > a + d + s - 1) { + DrawCircle(a + d + s + x , y + height - f_adsr_graph[a + d + s], 3, BLUE); + } + } else { // note off + int id = a + d + s + (float)(note->elapsed - note->noteOffSample)/SAMPLE_RATE / total * width; + if (id > 0 && id < width) + DrawCircle(id + x , y + height - f_adsr_graph[id], 3, BLUE); + } + } +} + +float +map(float value, float start1, float end1, float start2, float end2) { + return start2 + (end2 - start2) * ((value - start1) / (end1 - start1)); +} + + +/** + * This uses the FNV-1a hash algorithm, which provides better distribution and + * handles any integer values without collisions from the combining operation + * itself. + */ +uint +rect_checksum(int x, int y, int width, int height) { + unsigned int hash = 2166136261u; // FNV offset basis + hash = (hash ^ x) * 16777619u; + hash = (hash ^ y) * 16777619u; + hash = (hash ^ width) * 16777619u; + hash = (hash ^ height) * 16777619u; + return hash; +} + +uint rect_id_pressed = 0; + +float +generic_vbar(float val, float def, float min, float max, int x, int y, int width, int height) { + + Vector2 p = GetMousePosition(); + uint checksum = rect_checksum(x, y, width, height); + + if (CheckCollisionPointRec(p, (Rectangle){x, y, width, height})) { + if (IsMouseButtonPressed(0)) { + rect_id_pressed = checksum; + } + if (IsMouseButtonPressed(1)) { + val = def; + } + } + if (IsMouseButtonDown(0) && rect_id_pressed == checksum) { + if (p.y < y) { + val = max; + } else if (p.y >= y + height) { + val = min; + } else { + val = min + (max - min) * (1.0f - ((p.y - y) / (float)height)); + } + } + if (IsMouseButtonReleased(0) && rect_id_pressed == checksum) { + rect_id_pressed = 0; + } + + int fill_height = map(val, min, max, 0, height - 2); + + DrawRectangleLines(x, y, width, height, WHITE); + DrawRectangle(x + 1, y + height - fill_height - 1, width - 2, fill_height, Fade(MAROON, 0.7f)); + + return val; +} + +float +generic_hbar(float val, float def, float min, float max, + int x, int y, int width, int height) +{ + Vector2 p = GetMousePosition(); + uint checksum = rect_checksum(x, y, width, height); + + if (CheckCollisionPointRec(p, (Rectangle){x, y, width, height})) { + if (IsMouseButtonPressed(0)) { + rect_id_pressed = checksum; + } + if (IsMouseButtonPressed(1)) { + val = def; + } + } + + if (IsMouseButtonDown(0) && rect_id_pressed == checksum) { + if (p.x < x) { + val = min; + } else if (p.x >= x + width) { + val = max; + } else { + val = min + (max - min) * ((p.x - x) / (float)width); + } + } + + if (IsMouseButtonReleased(0) && rect_id_pressed == checksum) { + rect_id_pressed = 0; + } + + int fill_width = map(val, min, max, 0, width - 2); + + DrawRectangleLines(x, y, width, height, WHITE); + DrawRectangle(x + 1, y + 1, fill_width, height - 2, Fade(MAROON, 0.7f)); + + return val; +} + +void draw_signals(synth_t * synth, int x, int y, int width, int height) { DrawRectangleLines(x, y, width, height, WHITE); - if (synth->viz.wave_enabled || synth->viz.spectrum_enabled) - synth->viz.rate_divider = GuiSlider((Rectangle){ x + (width / 2.0) / 2, y - 12, width / 2.0, 12 }, "", NULL, synth->viz.rate_divider , 1, 150); + if (synth->viz.wave_enabled || synth->viz.spectrum_enabled) { + synth->viz.rate_divider = generic_hbar(synth->viz.rate_divider, 15, 1, 150, x + (width / 2.0) / 2, y - 12, width / 2.0, 12); + /* synth->viz.rate_divider = */ + /* GuiSlider((Rectangle){x + (width / 2.0) / 2, y - 12, width / 2.0, 12}, */ + /* "", NULL, synth->viz.rate_divider, 1, 150); */ + synth->viz.y_divider = generic_vbar(synth->viz.y_divider, 1.0, 0.5, 3.0, x - 10, y, 10, height); + /* /\* GuiSlider((Rectangle){x - 10, y, 10, height}, "", NULL, *\/ */ + /* /\* synth->viz.y_divider, 0.5, 3); *\/ */ + + /* Vector2 p = GetMousePosition(); */ + + /* if (CheckCollisionPointRec(p, (Rectangle){x - 10, y, 10, height})) { */ + /* if (IsMouseButtonPressed(0)) { */ + /* is_y_divider_pressed = 1; */ + /* } */ + /* } */ + /* float min = 0.5; */ + /* float max = 3; */ + /* if (IsMouseButtonDown(0) && is_y_divider_pressed) { */ + /* if (p.y < y) { */ + /* synth->viz.y_divider = max; */ + /* } else if (p.y >= y + height) { */ + /* synth->viz.y_divider = min; */ + /* } else { */ + /* synth->viz.y_divider = min + max - (min + (max - min)) * ((((p.y - y) * (float)1/height) - 0) / (1 - 0)); */ + /* } */ + /* } */ + /* if (IsMouseButtonReleased(0) && is_y_divider_pressed) { */ + /* is_y_divider_pressed = 0; */ + /* } */ + + /* int fill_height = map(synth->viz.y_divider, min, max, 0, height - 2); */ + + /* DrawRectangleLines(x - 10, y, 10, height, WHITE); */ + /* DrawRectangle(x - 10 + 1, y + height - fill_height - 1, 10 - 2, fill_height, Fade(MAROON, 0.7f)); */ + + } int viz_size = width * synth->viz.rate_divider; @@ -524,15 +717,15 @@ draw_signals(synth_t * synth, int x, int y, int width, int height) draw_adsr(synth, x, y, width, height); } - synth->viz.osc_enabled = GuiCheckBox((Rectangle){ x + width - 170, y, 16, 16 }, "osc", synth->viz.osc_enabled); - if (synth->viz.osc_enabled) { - draw_osc(synth, x + width - 80, y + height - 80, 80, 80); - } - synth->viz.adsr_graph_enabled = GuiCheckBox((Rectangle){ x + width - 270, y, 16, 16 }, "adsr graph", synth->viz.adsr_graph_enabled); if (synth->viz.adsr_graph_enabled) { draw_adsr_graph(synth, x + 20, y + 20, width - 40, height - 40); } + + synth->viz.filter_adsr_graph_enabled = GuiCheckBox((Rectangle){ x + width - 370, y, 16, 16 }, "filter adsr", synth->viz.filter_adsr_graph_enabled); + if (synth->viz.filter_adsr_graph_enabled) { + draw_filter_adsr_graph(synth, x + 20, y + 20, width - 40, height - 40); + } } char * flag = NULL; @@ -575,11 +768,6 @@ draw_cc_circle(cc_t * cc, int x, int y, int width, int height) { } -float -map(float value, float start1, float end1, float start2, float end2) { - return start2 + (end2 - start2) * ((value - start1) / (end1 - start1)); -} - #define CC_SET(cc, value) cc->mod = value - cc->target #include "web.h" void @@ -595,14 +783,11 @@ draw_cc_hbar(cc_t * cc, int x, int y, int width, int height) { } if (IsMouseButtonDown(0) && flag == cc->name) { if (p.x < x) { - //cc->target = cc->min; cc_set(cc, cc->min); } else if (p.x >= x + width) { - //cc->target = cc->max; cc_set(cc, cc->max); } else { - //cc->target = (cc->min + (cc->max - cc->min) * ((((p.x - x) * (float)1/width) - 0) / (1 - 0))); - cc_set(cc, (cc->min + (cc->max - cc->min) * ((((p.x - x) * (float)1/width) - 0) / (1 - 0)))); + cc_set(cc, cc->min + (cc->max - cc->min) * ((p.x - x) / (float)width)); } char tmp[128] = ""; sprintf(tmp, "%f", cc->value); @@ -629,7 +814,6 @@ draw_cc_hbar(cc_t * cc, int x, int y, int width, int height) { void draw_cc_vbar(cc_t * cc, int x, int y, int width, int height) { char buf1[32], buf2[32]; - //int current = height * (cc->target - cc->min) / (cc->max - cc->min) + cc->min - 1; Vector2 p = GetMousePosition(); @@ -640,14 +824,11 @@ draw_cc_vbar(cc_t * cc, int x, int y, int width, int height) { } if (IsMouseButtonDown(0) && flag == cc->name) { if (p.y < y) { - //cc->target = cc->max; cc_set(cc, cc->max); } else if (p.y >= y + height) { - //cc->target = cc->min; cc_set(cc, cc->min); } else { - //cc->target = cc->min + cc->max - (cc->min + (cc->max - cc->min) * ((((p.y - y) * (float)1/height) - 0) / (1 - 0))); - cc_set(cc, (cc->min + cc->max - (cc->min + (cc->max - cc->min) * ((((p.y - y) * (float)1/height) - 0) / (1 - 0))))); + cc_set(cc, cc->min + (cc->max - cc->min) * (1.0f - ((p.y - y) / (float)height))); } } if (IsMouseButtonReleased(0) && flag == cc->name) { @@ -707,10 +888,71 @@ void get_nth_entry(const char *str, int n, char *result) { result[0] = '\0'; } +void draw_load_patch(synth_t *synth) { + static bool save_name_edit = false; + static char save_text[1024] = ""; // TODO default to current patch name + + GuiPanel((Rectangle){ WIDTH / 2.0 - 200, HEIGHT / 2.0 - 140, 400, 280 }, "Load patch"); + if (GuiButton( + (Rectangle){WIDTH / 2.0 - 50 - 50 - 3, HEIGHT / 2.0 - 12, 100, 24}, + "load")) { + if (strlen(save_text) > 0) { + if (load_synth(synth, save_text)) { + fprintf(stderr, "Can't load %s\n", save_text); + return; + } + synth->patch_name = strdup(save_text); + synth->gui.popup = POPUP_NONE; + } + } + if (GuiButton((Rectangle){WIDTH / 2.0 + 3, HEIGHT / 2.0 - 12, 100, 24}, + "cancel")) { + synth->gui.popup = POPUP_NONE; + } + + + if (GuiTextBox( + (Rectangle){WIDTH / 2.0 - 125, HEIGHT / 2.0 - 18 - 24 - 6, 250, 36}, + save_text, 20, save_name_edit)) { + save_name_edit = true; + } +} + +void draw_save_patch(synth_t *synth) +{ + static bool save_name_edit = false; + static char save_text[1024] = ""; // TODO default to current patch name + + GuiPanel((Rectangle){ WIDTH / 2.0 - 200, HEIGHT / 2.0 - 140, 400, 280 }, "Save patch"); + if (GuiButton( + (Rectangle){WIDTH / 2.0 - 50 - 50 - 3, HEIGHT / 2.0 - 12, 100, 24}, + "save")) { + if (strlen(save_text) > 0) { + if (save_synth(synth, save_text)) { + fprintf(stderr, "Can't save at %s\n", save_text); + return; + } + synth->gui.popup = POPUP_NONE; + } + } + if (GuiButton((Rectangle){WIDTH / 2.0 + 3, HEIGHT / 2.0 - 12, 100, 24}, + "cancel")) { + synth->gui.popup = POPUP_NONE; + } + + + if (GuiTextBox( + (Rectangle){WIDTH / 2.0 - 125, HEIGHT / 2.0 - 18 - 24 - 6, 250, 36}, + save_text, 20, save_name_edit)) { + save_name_edit = true; + } +} + + char *soundcards = NULL; char *midi_devices = NULL; void -draw_audiomidisetup(synth_t *synth, const char *midi_devices) +draw_audiomidisetup(synth_t *synth) { static int edit_midi = 0; static int edit_sound = 0; @@ -727,9 +969,6 @@ draw_audiomidisetup(synth_t *synth, const char *midi_devices) printf("================ AUDIOMIDISETUP GUI INITIALIZED!\n"); } - BeginDrawing(); - ClearBackground(BLACK); - if (GuiButton((Rectangle){WIDTH - 100 - 50 - 6 - 100, 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24}, "cancel")) { @@ -767,14 +1006,205 @@ draw_audiomidisetup(synth_t *synth, const char *midi_devices) soundcards, &soundcard_pick, edit_sound)) { edit_sound = !edit_sound; } +} - /* if (old_midi_device_id != synth->midi_device_id) { */ - /* old_midi_device_id = synth->midi_device_id; */ - /* } */ +char** split_string(const char* str, char delimiter, int* itemCount) { + if (!str || !itemCount) { + if (itemCount) *itemCount = 0; + return NULL; + } + // First pass: count delimiters to determine array size + int count = 1; + for (int i = 0; str[i]; i++) { + if (str[i] == delimiter) { + count++; + } + } + // Allocate array of string pointers + char** result = malloc(count * sizeof(char*)); + if (!result) { + *itemCount = 0; + return NULL; + } + + // Create a working copy of the string + char* str_copy = malloc(strlen(str) + 1); + if (!str_copy) { + free(result); + *itemCount = 0; + return NULL; + } + strcpy(str_copy, str); + + // Second pass: split the string + int index = 0; + char* token = str_copy; + char* next_delim; + + while ((next_delim = strchr(token, delimiter)) != NULL) { + *next_delim = '\0'; // Replace delimiter with null terminator + + // Allocate memory for this substring and copy it + result[index] = malloc(strlen(token) + 1); + if (result[index]) { + strcpy(result[index], token); + } + index++; + token = next_delim + 1; // Move to next token + } + + // Handle the last token (after the last delimiter or the whole string if no delimiters) + result[index] = malloc(strlen(token) + 1); + if (result[index]) { + strcpy(result[index], token); + } + + free(str_copy); + *itemCount = count; + return result; +} + +// Function to free the memory allocated by split_string +void free_split_result(char** result, int itemCount) { + if (result) { + for (int i = 0; i < itemCount; i++) { + free(result[i]); + } + free(result); + } +} + +int +gui_string_spinner(Rectangle rect, char * text, int * index) +{ + int itemCount = 0; + char **items = NULL; + int change = 0; + + float m = GetMouseWheelMove(); + int x = 0; + if (m < 0) x = -1; + else if (m > 0) x = 1; + Vector2 p = GetMousePosition(); + + if (text != NULL) items = split_string(text, ';', &itemCount); + + if (CheckCollisionPointRec(p, rect)) { + if (x > 0) { + if (IsKeyDown(KEY_LEFT_SHIFT)) { + (*index)+=10; + } else { + (*index)++; + } + if (*index >= itemCount) { + *index = itemCount - 1; // set to itemCound to loop + } + change = 1; + } else if (x < 0) { + if (IsKeyDown(KEY_LEFT_SHIFT)) { + (*index)-=10; + } else { + (*index)--; + } + if (*index <= -1) { + *index = 0; // set to itemCound to loop + } + change = 1; + } + } + + if (GuiButton((Rectangle){rect.x, rect.y, rect.height, rect.height}, "<")) { + (*index)--; + if (*index <= -1) { + *index = 0; // set to itemCound to loop + } + change = 1; + } + float box_x = rect.x + (rect.height + 1); + float box_width = rect.width - (rect.height + 1) - (rect.height + 1); + DrawRectangleLines(box_x, rect.y, box_width, rect.height, WHITE); + + int text_size = 10; + + char buf[1024] = ""; + + sprintf(buf, "%d: %s", *index, items[*index]); + DrawText(buf, + box_x + box_width/2 - (float)MeasureText(buf, text_size) / 2, + rect.y + rect.height / 2 - (float)text_size / 2, + text_size, + WHITE); + + if (GuiButton((Rectangle){rect.x + (rect.height + 1) + rect.width - (rect.height + 1) - (rect.height + 1), rect.y, rect.height, rect.height}, ">")) { + (*index)++; + if (*index >= itemCount) { + *index = itemCount - 1; // set to itemCound to loop + } + change = 1; + } + + free_split_result(items, itemCount); + + return change; +} + +int prev_active = -1; + +int sound_file_picker_initialized = 0; +char * wavs = NULL; +char * wavs_full = NULL; + +void +draw_sound_file_picker(Rectangle rect) +{ + static int scroll_index = 0; // Current scroll position + static int active_item = 0; // Currently selected item (-1 means none selected) + + if (!sound_file_picker_initialized) { + wavs = scan_for_wav_pretty("/home/grm/code/synth-project-b/waves/Free Wavetables[2048]"); + //wavs[strlen(wavs) - 1] = '\0'; // remove last ; + wavs_full = scan_for_wav("/home/grm/code/synth-project-b/waves/Free Wavetables[2048]"); + //wavs_full[strlen(wavs_full) - 1] = '\0'; + /* printf("strlen wavs: %ld\n", strlen(wavs)); */ + /* printf("strlen wavs_full: %ld\n", strlen(wavs_full)); */ + sound_file_picker_initialized = 1; + } + + /* char aaa[10000] = ""; */ + /* char *t = aaa; */ + /* int i = 0; */ + /* for (int j =0; j < 1000; j++) { */ + /* sprintf(t, "%d;", i++); */ + /* t += strlen(t); */ + /* } */ + /* aaa[strlen(aaa) - 1] = '\0'; */ + + if (gui_string_spinner(rect, wavs, &scroll_index)) { + char path[1024] = ""; + get_nth_entry(wavs_full, scroll_index, path); + printf("changing wavetable to: %s\n", path); + osc_sound_change_wavetable(path); + } + prev_active = active_item; + //active_item = GuiListView(rect, aaa, &scroll_index, active_item); + /* if (prev_active != active_item && active_item != -1) { */ + /* char path[1024] = ""; */ + /* get_nth_entry(wavs_full, active_item, path); */ + /* printf("changing wavetable to: %s\n", path); */ + /* osc_sound_change_wavetable(path); */ + /* // sound changed */ + /* } */ + /* if (GuiDropdownBox((Rectangle){WIDTH - 300 - 50, */ + /* 12 + 24 + 6, 300, */ + /* 24}, */ + /* wavs, &wav_pick, edit_wav)) { */ + /* edit_wav = !edit_wav; */ + /* } */ + //free(wavs); + //free(wavs_full); - EndDrawing(); } void @@ -785,8 +1215,6 @@ draw_main(synth_t *synth) if (midi_devices) free(midi_devices); synth->gui.audiomidi_initialized = 0; } - BeginDrawing(); - ClearBackground(BLACK); int fb = 0; int foffset = 9; @@ -806,6 +1234,15 @@ draw_main(synth_t *synth) draw_cc_circle(&synth->cc_f_adsr_r, 30 + (30 + foffset) * fb++, 260, 30, 30); draw_cc_hbar(&synth->cc_cutoff, 30, 300, 256, 24); + synth->lfo_on_cutoff = GuiCheckBox((Rectangle){30 + 256 + 6, 300, 12, 24}, + "lfo", synth->lfo_on_cutoff); + if (synth->lfo_on_cutoff) { + float cutoff = synth->cc_cutoff.target; + cutoff = cutoff + 22000 * synth->cc_lfo_amp.target * osc_sin(synth->lfo_index); + if (cutoff <= 0) cutoff = 0.001; + + generic_hbar(cutoff, 50, synth->cc_cutoff.min, synth->cc_cutoff.max, 30, 300, 256, 24); + } draw_cc_vbar(&synth->cc_resonance, 330, 20, 24, 256); //draw_cc_vbar(&synth->cc_adsr_s, 30, 250, 30, 30); @@ -813,18 +1250,28 @@ draw_main(synth_t *synth) // GUI char buf[64]; snprintf(buf, sizeof buf, "%d", synth->wvt_pos); - synth->wvt_pos = GuiSlider((Rectangle){WIDTH / 2.0 - 108, 150 + 42 + 42, 216, 24 }, "", buf, synth->wvt_pos , 0, 127); + float wvt_pos_x = WIDTH / 2.0 - 108, wvt_pos_y = 150 + 42 + 42, wvt_pos_width = 216, wvt_pos_height = 24, wvt_pos_font_size = 10; + synth->wvt_pos = generic_hbar(synth->wvt_pos, 1.0, 0, 127, wvt_pos_x, wvt_pos_y, wvt_pos_width, wvt_pos_height); + DrawText(buf, wvt_pos_x + wvt_pos_width + 5, wvt_pos_y + wvt_pos_height / 2 - wvt_pos_font_size / 2, wvt_pos_font_size, GRAY); set_sound_start(synth->wvt_pos*2048); set_sound_len(synth->wvt_pos*2048 + 2048); draw_bars(synth, 20, 20, 200, 16, 3); draw_text(synth, WIDTH / 2 - 108, 20); + synth->lfo_on_pitch = GuiCheckBox((Rectangle){WIDTH / 2.0 - 108 + 120, 20, 12, 12}, + "lfo", synth->lfo_on_pitch); - if ( GuiButton((Rectangle){ WIDTH / 2.0 - 108, 150 - 6 - 6 + 42, 216, 6 }, "")) { - synth->x = 1; + //synth->viz.osc_enabled = GuiCheckBox((Rectangle){ x + width - 170, y, 16, 16 }, "osc", synth->viz.osc_enabled); + if (synth->viz.osc_enabled) { + draw_osc(synth, WIDTH / 2 + 180, 140, 80, 80); } - snprintf(buf, sizeof buf, "%.1f", synth->x); - synth->x = GuiSlider((Rectangle){ WIDTH / 2.0 - 108, 150 + 42, 216, 24 }, "x", buf, synth->x , 0.0f, 2.0f); + + // x variable + /* if ( GuiButton((Rectangle){ WIDTH / 2.0 - 108, 150 - 6 - 6 + 42, 216, 6 }, "")) { */ + /* synth->x = 1; */ + /* } */ + /* snprintf(buf, sizeof buf, "%.1f", synth->x); */ + /* synth->x = GuiSlider((Rectangle){ WIDTH / 2.0 - 108, 150 + 42, 216, 24 }, "x", buf, synth->x , 0.0f, 2.0f); */ synth->filter = GuiToggle((Rectangle){ WIDTH - 100 - 50 - 100 - 50 , 50 , 46, 24 }, "LP", synth->filter); synth->biquad = GuiToggle((Rectangle){ WIDTH - 100 - 50 - 100 - 50 + 46 + 8 , 50 , 46, 24 }, "bq", synth->biquad); @@ -850,51 +1297,104 @@ draw_main(synth_t *synth) if ( GuiButton((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24 }, "audiomidi")) { synth->gui.screen = SCREEN_AUDIOMIDISETUP; } - if ( GuiButton((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24 }, "SAVE!")) { - save_synth(synth, "asdas"); + if (GuiButton((Rectangle){WIDTH - 100 - 50, + 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, + 100, 24}, + "SAVE!")) { + synth->gui.popup = POPUP_SAVE_PATCH; } if ( GuiButton((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24 }, "LOAD!")) { - load_synth(synth, "asdas"); + synth->gui.popup = POPUP_LOAD_PATCH; + } + + if ( GuiButton((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24 }, "reLOAD!")) { + if (synth->patch_name && load_synth(synth, synth->patch_name)) { + fprintf(stderr, "Can't load %s\n", synth->patch_name); + } } // signals draw_signals(synth, 20, 390, WIDTH - 2*20, 200); + + if (synth->geni == 4) + draw_sound_file_picker((Rectangle){ WIDTH / 2.0 - 128, 150 + 122, 256 + 60, 24 }); + //draw_signals(synth, 300, 390, WIDTH - 2*300, 200); //DrawText("THE SYNTH!!!!!!!!!!!!!!!!!!1", WIDTH / 2 - 100, 50, 20, LIGHTGRAY); /* DrawText("KEYBOARD: Q .. ]", WIDTH / 2 -300, HEIGHT - 300 - 50, 20, LIGHTGRAY); */ /* snprintf(buf, sizeof buf, "stream time: %f", Pa_GetStreamTime(synth->stream)); */ /* DrawText(buf, WIDTH / 2 -300, HEIGHT - 300, 11, LIGHTGRAY); */ - EndDrawing(); } void rayrun(synth_t *synth){ PaTime current_time = 0; PaTime prev_time = 0; + int mod = 0; osc_sound(0); - /* //char *midi_devices = "test1;test2;test3"; */ - char *midi_devices = get_midi_devices(); - /* int old_soundcard_id = synth->soundcard_id; */ - /* int old_midi_device_id = synth->midi_device_id; */ - SetTraceLogLevel(LOG_ERROR); InitWindow(WIDTH, HEIGHT, "Raylib synth"); SetTargetFPS(60); // Set our game to run at 60 frames-per-second while (!WindowShouldClose()) { - keyboard(synth); - mouse(synth); + if (synth->gui.popup == POPUP_NONE) { + keyboard(synth); + mouse(synth); + } + if (true || mod++ % 10 == 0) { + char b[256]; + sprintf(b, "wvt_pos:%d", synth->wvt_pos); + ws_send_message(b); + sprintf(b, "cci:%d", synth->cci); + ws_send_message(b); + sprintf(b, "autogain:%d", synth->autogain); + ws_send_message(b); + sprintf(b, "midi_active_n:%d", synth->midi_active_n); + ws_send_message(b); + sprintf(b, "octave:%d", synth->octave); + ws_send_message(b); + sprintf(b, "delay:%d", synth->delay); + ws_send_message(b); + sprintf(b, "deli:%d", synth->deli); + ws_send_message(b); + sprintf(b, "f_adsr_enabled:%d", synth->f_adsr_enabled); + ws_send_message(b); + sprintf(b, "filter:%d", synth->filter); + ws_send_message(b); + sprintf(b, "biquad:%d", synth->biquad); + ws_send_message(b); + sprintf(b, "clamp:%d", synth->clamp); + ws_send_message(b); + sprintf(b, "modi:%d", synth->modi); + ws_send_message(b); + sprintf(b, "geni:%d", synth->geni ); + ws_send_message(b); + sprintf(b, "active:%d", synth->active); + ws_send_message(b); + sprintf(b, "sound_active:%d", synth->sound_active); + ws_send_message(b); + } // Draw //---------------------------------------------------------------------------------- + BeginDrawing(); + ClearBackground(BLACK); + if (synth->gui.screen == SCREEN_MAIN) draw_main(synth); else if (synth->gui.screen == SCREEN_AUDIOMIDISETUP) - draw_audiomidisetup(synth, midi_devices); + draw_audiomidisetup(synth); + + if (synth->gui.popup == POPUP_SAVE_PATCH) + draw_save_patch(synth); + else if (synth->gui.popup == POPUP_LOAD_PATCH) + draw_load_patch(synth); + + EndDrawing(); //---------------------------------------------------------------------------------- current_time = Pa_GetStreamTime(synth->stream); @@ -58,6 +58,38 @@ char *read_file_to_string(const char *filename) { return buffer; } +char *get_from_template() { + int size = 1024^3 * 1000; + char * ret = (char *)malloc(sizeof(char) * size); + + int pipefd[2]; + if (pipe(pipefd) == -1) { + perror("pipe"); + return NULL; + } + + synth_t * synth = synthx; + #define OUT pipefd[1] + #define INT(x) dprintf(OUT, "%d", x); + #define FLOAT(x) dprintf(OUT, "%f", x); + #define STR(x) dprintf(OUT, "%s", x); + #define PERCENT dprintf(OUT, "%s", "%"); + #include "index.html.h" + close(pipefd[1]); + + // Read from the pipe into a buffer + ssize_t bytes_read = read(pipefd[0], ret, size - 1); + if (bytes_read == -1) { + perror("read"); + return NULL; + } + + // Null-terminate the ret + ret[bytes_read] = '\0'; + close(pipefd[0]); + return ret; +} + int key_to_number(char key) { switch (key) { @@ -164,7 +196,7 @@ static int callback_ws(struct lws *wsi, enum lws_callback_reasons reason, break; } case LWS_CALLBACK_SERVER_WRITEABLE: { - printf("\nLWS_CALLBACK_SERVER_WRITEABLE\n\n"); + printf("LWS_CALLBACK_SERVER_WRITEABLE\n"); /* size_t msg_len = strlen(message_buffer); */ /* unsigned char buffer[LWS_PRE + BUFFER_SIZE]; */ /* memcpy(&buffer[LWS_PRE], message_buffer, msg_len); */ @@ -172,10 +204,12 @@ static int callback_ws(struct lws *wsi, enum lws_callback_reasons reason, break; } case LWS_CALLBACK_HTTP: { + html_content = get_from_template(); snprintf(tmp, sizeof(tmp), html_header, strlen(html_content)); strcpy(buf, tmp); strcat(buf, html_content); lws_write(wsi, (unsigned char *)buf, strlen(buf), LWS_WRITE_HTTP); + free(html_content); break; } case LWS_CALLBACK_CLOSED: { @@ -221,40 +255,10 @@ void *websocket_server_thread(void *arg) { return NULL; } -char *get_from_template() { - int size = 1024^3 * 1000; - char * ret = (char *)malloc(sizeof(char) * size); - - int pipefd[2]; - if (pipe(pipefd) == -1) { - perror("pipe"); - return NULL; - } - - #define OUT pipefd[1] - #define INT(x) dprintf(OUT, "%d", x); - #define PERCENT dprintf(OUT, "%s", "%"); - #include "index.html.h" - close(pipefd[1]); - - // Read from the pipe into a buffer - ssize_t bytes_read = read(pipefd[0], ret, size - 1); - if (bytes_read == -1) { - perror("read"); - return NULL; - } - - // Null-terminate the ret - ret[bytes_read] = '\0'; - close(pipefd[0]); - return ret; -} - void init_web(synth_t * synth) { //html_content = read_file_to_string("src/index.html"); - html_content = get_from_template(); synthx = synth; lws_set_log_level(LLL_WARN, NULL); @@ -274,14 +278,12 @@ free_web() void ws_send_message(const char *message) { if (client_wsi != NULL) { - /* strcpy(message_buffer, message); */ - /* lws_callback_on_writable(client_wsi); */ size_t msg_len = strlen(message); unsigned char buffer[LWS_PRE + BUFFER_SIZE]; memcpy(&buffer[LWS_PRE], message, msg_len); lws_write(client_wsi, &buffer[LWS_PRE], msg_len, LWS_WRITE_TEXT); - printf("[WS]: Sent <<%s>>\n", message); - - //lws_write(client_wsi, (unsigned char *)message, strlen(message), LWS_WRITE_TEXT); + // printf("[WS]: Sent <<%s>>\n", message); + // do I need this? + //lws_callback_on_writable(client_wsi); ??? } } |
