summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/archive/gtk.c (renamed from src/gtk.c)0
-rw-r--r--src/generator.c114
-rw-r--r--src/generator.h10
-rw-r--r--src/gui.h8
-rw-r--r--src/gui_functions.c23
-rw-r--r--src/os.c255
-rw-r--r--src/os.h7
-rw-r--r--src/osc.c51
-rw-r--r--src/osc.h2
-rw-r--r--src/osc_sound.c14
-rw-r--r--src/sequencer.c165
-rw-r--r--src/synth.c12
-rw-r--r--src/synth_engine.h13
-rw-r--r--src/synth_engine_v2.c249
-rw-r--r--src/synth_gui.c672
-rw-r--r--src/web.c74
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 */
diff --git a/src/gui.h b/src/gui.h
index f151e35..ac1436f 100644
--- a/src/gui.h
+++ b/src/gui.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 */
diff --git a/src/osc.c b/src/osc.c
index 28e35b4..70e7c32 100644
--- a/src/osc.c
+++ b/src/osc.c
@@ -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)
{
diff --git a/src/osc.h b/src/osc.h
index 9cd0472..ef85225 100644
--- a/src/osc.h
+++ b/src/osc.h
@@ -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, &note_dup, synth->x, SAMPLE_RATE);
+ osc_wave[i] += synth->gen[synth->geni](note_dup.freq * 3, &note_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, &note_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, &note_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);
diff --git a/src/web.c b/src/web.c
index f3aa5f7..afd8c57 100644
--- a/src/web.c
+++ b/src/web.c
@@ -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); ???
}
}