diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 60 | ||||
-rw-r--r-- | src/adsr.c | 2 | ||||
-rw-r--r-- | src/archive/wavetable.c | 2 | ||||
-rw-r--r-- | src/b.h | 898 | ||||
-rw-r--r-- | src/biquad_filter.c | 71 | ||||
-rw-r--r-- | src/control.c | 16 | ||||
-rw-r--r-- | src/control.h | 8 | ||||
-rw-r--r-- | src/filter.c | 1 | ||||
-rw-r--r-- | src/gen.h | 17 | ||||
-rw-r--r-- | src/lowpass.c | 1 | ||||
-rw-r--r-- | src/midi.c | 57 | ||||
-rw-r--r-- | src/midi.h | 2 | ||||
-rw-r--r-- | src/notes.h | 3 | ||||
-rw-r--r-- | src/osc.c | 5 | ||||
-rw-r--r-- | src/osc_digisaw.c | 1 | ||||
-rw-r--r-- | src/osc_sound.c | 14 | ||||
-rw-r--r-- | src/osc_tri.c | 2 | ||||
-rw-r--r-- | src/oscillator.h | 22 | ||||
-rw-r--r-- | src/raygui.h | 8 | ||||
-rw-r--r-- | src/sound.c | 85 | ||||
-rw-r--r-- | src/sound.h | 5 | ||||
-rw-r--r-- | src/stats.h | 10 | ||||
-rw-r--r-- | src/synth.c | 24 | ||||
-rw-r--r-- | src/synth_engine.h | 35 | ||||
-rw-r--r-- | src/synth_engine_v2.c | 378 | ||||
-rw-r--r-- | src/synth_gui.c | 179 | ||||
-rw-r--r-- | src/synth_math.h | 22 | ||||
-rw-r--r-- | src/types.h | 38 | ||||
-rw-r--r-- | src/web.c | 257 | ||||
-rw-r--r-- | src/web.h | 12 |
30 files changed, 2029 insertions, 206 deletions
diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index edec1b1..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,60 +0,0 @@ -#bin_PROGRAMS = food cookbook cook synth -bin_PROGRAMS = synth # gtk - -common_sources = adsr.c \ - adsr.h \ - control.c \ - control.h \ - filter.c \ - filter.h \ - lowpass.c \ - lowpass.h \ - Makefile.am \ - midi.c \ - midi.h \ - notes.h \ - osc.c \ - osc.h \ - osc_tri.c \ - osc_sin.c \ - osc_digisaw.c \ - osc_saw.c \ - osc_weird.c \ - osc_sqr.c \ - osc_sound.c \ - pa_ringbuffer.c \ - pa_ringbuffer.h \ - pa_memorybarrier.h \ - raygui.h \ - sound.c \ - sound.h \ - synth_common.h \ - synth_engine_v2.c \ - synth_engine.h \ - synth_gui.c \ - synth_gui.h \ - synth_math.h - -gtk_sources = - -# -fwhole-program allows cross-file inlining, but only works when you put all -# the source files on one gcc command-line. -flto is another way to get the -# same effect. (Link-Time Optimization). clang supports -flto but not -# -fwhole-program. - -# If your program doesn't depend on strict FP rounding -# behaviour, use -ffast-math. If it does, you can usually still use -# -fno-math-errno and stuff like that, without enabling -# -funsafe-math-optimizations. Some FP code can get big speedups from -# fast-math, like auto-vectorization. -AM_CFLAGS = -O3 -march=native -fno-math-errno -funroll-loops -flto -pthread - -synth_SOURCES = synth.c $(common_sources) -synth_LDADD = -lportaudio -lrt -lm -lasound -lraylib -lportmidi -ljack -lfftw3f -lsndfile -lconfig - -# gtk_SOURCES = gtk.c $(common_sources) $(gtk_sources) -# gtk_LDADD = -lportaudio -lrt -lm -lasound -lraylib -lportmidi -ljack -lfftw3f -lsndfile -lconfig -lgtk-4 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lgraphene-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -# gtk_CFLAGS = -I/usr/include/gtk-4.0 -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-6 -I/usr/include/harfbuzz -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/fribidi -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/graphene-1.0 -I/usr/lib/graphene-1.0/include -mfpmath=sse -msse -msse2 -pthread -#cookbook_SOURCES = cookbook.c $(common_sources) -#cookbook_SOURCES = cookbook.c $(common_sources) -#cook_SOURCES = cook.c $(common_sources) @@ -52,7 +52,7 @@ fix_adsr(adsr_t *adsr, float noteOn, float noteOff, unsigned long long elapsed, // convert to samples unsigned long long attack = adsr->a * SAMPLE_RATE + 1; unsigned long long decay = adsr->d * SAMPLE_RATE + 1; - unsigned long long sustain = adsr->s * SAMPLE_RATE + 1; + //unsigned long long sustain = adsr->s * SAMPLE_RATE + 1; unsigned long long release = adsr->r * SAMPLE_RATE + 1; float mod = 0.0f; diff --git a/src/archive/wavetable.c b/src/archive/wavetable.c index ab3308a..c953493 100644 --- a/src/archive/wavetable.c +++ b/src/archive/wavetable.c @@ -108,7 +108,7 @@ wvt_init() wvt_tri_data.data = (float *) malloc(sizeof(float) * 2); wvt_tri_data.data[0] = -1.0f; wvt_tri_data.data[1] = 1.0f; - wvt_sound_init("/home/gramanas/code/synth-project/waves/test1.wav"); + wvt_sound_init("/home/grm/code/synth-project/waves/test1.wav"); } @@ -0,0 +1,898 @@ +// This is an attempt on build library for building C with C akin to nob.h from +// tsoding which is itself based on https://github.com/tsoding/nobuild +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef B_H_ +#define B_H_ + +#define B_ASSERT assert +#define B_REALLOC realloc +#define B_FREE free + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> + +#define B_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0])) +#define B_ARRAY_GET(array, index) \ + (B_ASSERT(index >= 0), B_ASSERT(index < B_ARRAY_LEN(array)), array[index]) + +typedef enum { + B_INFO, + B_CMD, + B_BUILDING, + B_CHANGE, + B_WARNING, + B_ERROR, +} B_Log_Level; + +void b_log(B_Log_Level level, const char *fmt, ...); + +// It is an equivalent of shift command from bash. It basically pops a command line +// argument from the beginning. +char *b_shift_args(int *argc, char ***argv); + +typedef struct { + const char **items; + size_t count; + size_t capacity; +} B_File_Paths; + +typedef enum { + B_FILE_REGULAR = 0, + B_FILE_DIRECTORY, + B_FILE_SYMLINK, + B_FILE_OTHER, +} B_File_Type; + +bool b_mkdir_if_not_exists(const char *path); +bool b_copy_file(const char *src_path, const char *dst_path); +bool b_copy_directory_recursively(const char *src_path, const char *dst_path); +bool b_read_entire_dir(const char *parent, B_File_Paths *children); +bool b_write_entire_file(const char *path, const void *data, size_t size); +B_File_Type b_get_file_type(const char *path); + +#define b_return_defer(value) do { result = (value); goto defer; } while(0) + +// Initial capacity of a dynamic array +#define B_DA_INIT_CAP 256 + +// Append an item to a dynamic array +#define b_da_append(da, item) \ + do { \ + if ((da)->count >= (da)->capacity) { \ + (da)->capacity = (da)->capacity == 0 ? B_DA_INIT_CAP : (da)->capacity*2; \ + (da)->items = B_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ + B_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ + } \ + \ + (da)->items[(da)->count++] = (item); \ + } while (0) + +#define b_da_free(da) B_FREE((da).items) + +// Append several items to a dynamic array +#define b_da_append_many(da, new_items, new_items_count) \ + do { \ + if ((da)->count + (new_items_count) > (da)->capacity) { \ + if ((da)->capacity == 0) { \ + (da)->capacity = B_DA_INIT_CAP; \ + } \ + while ((da)->count + (new_items_count) > (da)->capacity) { \ + (da)->capacity *= 2; \ + } \ + (da)->items = B_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ + B_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ + } \ + memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \ + (da)->count += (new_items_count); \ + } while (0) + +typedef struct { + char *items; + size_t count; + size_t capacity; +} B_String_Builder; + +bool b_read_entire_file(const char *path, B_String_Builder *sb); + +// Append a sized buffer to a string builder +#define b_sb_append_buf(sb, buf, size) b_da_append_many(sb, buf, size) + +// Append a NULL-terminated string to a string builder +#define b_sb_append_cstr(sb, cstr) \ + do { \ + const char *s = (cstr); \ + size_t n = strlen(s); \ + b_da_append_many(sb, s, n); \ + } while (0) + +// Append a single NULL character at the end of a string builder. So then you can +// use it a NULL-terminated C string +#define b_sb_append_null(sb) b_da_append_many(sb, "", 1) + +// Free the memory allocated by a string builder +#define b_sb_free(sb) B_FREE((sb).items) + +// Process handle +typedef int B_Proc; +#define B_INVALID_PROC (-1) + +typedef struct { + B_Proc *items; + size_t count; + size_t capacity; +} B_Procs; + +bool b_procs_wait(B_Procs procs); + +// Wait until the process has finished +bool b_proc_wait(B_Proc proc); + +// A command - the main workhorse of B. B is all about building commands an running them +typedef struct { + const char **items; + size_t count; + size_t capacity; +} B_Cmd; + +// Render a string representation of a command into a string builder. Keep in mind the the +// string builder is not NULL-terminated by default. Use b_sb_append_null if you plan to +// use it as a C string. +void b_cmd_render(B_Cmd cmd, B_String_Builder *render); + +#define b_cmd_append(cmd, ...) \ + b_da_append_many(cmd, ((const char*[]){__VA_ARGS__}), (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*))) + +// Free all the memory allocated by command arguments +#define b_cmd_free(cmd) B_FREE(cmd.items) + +// Run command asynchronously +B_Proc b_cmd_run_async(B_Cmd cmd); + +// Run command synchronously +bool b_cmd_run_sync(B_Cmd cmd); + +#ifndef B_TEMP_CAPACITY +#define B_TEMP_CAPACITY (8*1024*1024) +#endif // B_TEMP_CAPACITY +char *b_temp_strdup(const char *cstr); +void *b_temp_alloc(size_t size); +char *b_temp_sprintf(const char *format, ...); +void b_temp_reset(void); +size_t b_temp_save(void); +void b_temp_rewind(size_t checkpoint); + +int is_path1_modified_after_path2(const char *path1, const char *path2); +bool b_rename(const char *old_path, const char *new_path); +int b_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count); +int b_needs_rebuild1(const char *output_path, const char *input_path); +int b_file_exists(const char *file_path); + +// TODO: add MinGW support for Go Rebuild Urself™ Technology +#ifndef B_REBUILD_URSELF +# if _WIN32 +# if defined(__GNUC__) +# define B_REBUILD_URSELF(binary_path, source_path) "gcc", "-o", binary_path, source_path +# elif defined(__clang__) +# define B_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path +# elif defined(_MSC_VER) +# define B_REBUILD_URSELF(binary_path, source_path) "cl.exe", b_temp_sprintf("/Fe:%s", (binary_path)), source_path +# endif +# else +# if defined(__clang__) +# define B_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path +# else +# define B_REBUILD_URSELF(binary_path, source_path) "cc", "-o", binary_path, source_path +# endif +# endif +#endif + +// Go Rebuild Urself™ Technology +// +// How to use it: +// int main(int argc, char** argv) { +// GO_REBUILD_URSELF(argc, argv); +// // actual work +// return 0; +// } +// +// After your added this macro every time you run ./build it will detect +// that you modified its original source code and will try to rebuild itself +// before doing any actual work. So you only need to bootstrap your build system +// once. +// +// The modification is detected by comparing the last modified times of the executable +// and its source code. The same way the make utility usually does it. +// +// The rebuilding is done by using the REBUILD_URSELF macro which you can redefine +// if you need a special way of bootstraping your build system. (which I personally +// do not recommend since the whole idea of build is to keep the process of bootstrapping +// as simple as possible and doing all of the actual work inside of the build) +// +#define B_GO_REBUILD_URSELF(argc, argv) \ + do { \ + const char *source_path = __FILE__; \ + assert(argc >= 1); \ + const char *binary_path = argv[0]; \ + \ + int rebuild_is_needed = b_needs_rebuild(binary_path, &source_path, 1); \ + if (rebuild_is_needed < 0) exit(1); \ + if (rebuild_is_needed) { \ + B_String_Builder sb = {0}; \ + b_sb_append_cstr(&sb, binary_path); \ + b_sb_append_cstr(&sb, ".old"); \ + b_sb_append_null(&sb); \ + \ + if (!b_rename(binary_path, sb.items)) exit(1); \ + B_Cmd rebuild = {0}; \ + b_cmd_append(&rebuild, B_REBUILD_URSELF(binary_path, source_path)); \ + bool rebuild_succeeded = b_cmd_run_sync(rebuild); \ + b_cmd_free(rebuild); \ + if (!rebuild_succeeded) { \ + b_rename(sb.items, binary_path); \ + exit(1); \ + } \ + \ + B_Cmd cmd = {0}; \ + b_da_append_many(&cmd, argv, argc); \ + if (!b_cmd_run_sync(cmd)) exit(1); \ + exit(0); \ + } \ + } while(0) +// The implementation idea is stolen from https://github.com/zhiayang/nabs + +typedef struct { + size_t count; + const char *data; +} B_String_View; + +const char *b_temp_sv_to_cstr(B_String_View sv); + +B_String_View b_sv_chop_by_delim(B_String_View *sv, char delim); +B_String_View b_sv_trim(B_String_View sv); +bool b_sv_eq(B_String_View a, B_String_View b); +B_String_View b_sv_from_cstr(const char *cstr); +B_String_View b_sv_from_parts(const char *data, size_t count); + +// printf macros for String_View +#ifndef SV_Fmt +#define SV_Fmt "%.*s" +#endif // SV_Fmt +#ifndef SV_Arg +#define SV_Arg(sv) (int) (sv).count, (sv).data +#endif // SV_Arg +// USAGE: +// String_View name = ...; +// printf("Name: "SV_Fmt"\n", SV_Arg(name)); + +/* file.c */ +/* file.h + */ +/* -------- */ +/* file.o */ + +/* temlp.h */ +/* f1.c */ +/* f2.c */ +/* f3.c + */ +/* ------ */ +/* templ.o */ + +/* prog.c */ +/* obj1.o */ +/* obj2.o + */ +/* -------- */ +/* prog */ + + + +#endif // B_H_ + +#ifdef B_IMPLEMENTATION + +static size_t b_temp_size = 0; +static char b_temp[B_TEMP_CAPACITY] = {0}; + +bool b_mkdir_if_not_exists(const char *path) +{ + int result = mkdir(path, 0755); + if (result < 0) { + if (errno == EEXIST) { + //b_log(B_INFO, "directory `%s` already exists", path); + return true; + } + b_log(B_ERROR, "could not create directory `%s`: %s", path, strerror(errno)); + return false; + } + + b_log(B_INFO, "created directory `%s`", path); + return true; +} + +bool b_copy_file(const char *src_path, const char *dst_path) +{ + b_log(B_INFO, "copying %s -> %s", src_path, dst_path); + + int src_fd = -1; + int dst_fd = -1; + size_t buf_size = 32*1024; + char *buf = B_REALLOC(NULL, buf_size); + B_ASSERT(buf != NULL && "Buy more RAM lol!!"); + bool result = true; + + src_fd = open(src_path, O_RDONLY); + if (src_fd < 0) { + b_log(B_ERROR, "Could not open file %s: %s", src_path, strerror(errno)); + b_return_defer(false); + } + + struct stat src_stat; + if (fstat(src_fd, &src_stat) < 0) { + b_log(B_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno)); + b_return_defer(false); + } + + dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode); + if (dst_fd < 0) { + b_log(B_ERROR, "Could not create file %s: %s", dst_path, strerror(errno)); + b_return_defer(false); + } + + for (;;) { + ssize_t n = read(src_fd, buf, buf_size); + if (n == 0) break; + if (n < 0) { + b_log(B_ERROR, "Could not read from file %s: %s", src_path, strerror(errno)); + b_return_defer(false); + } + char *buf2 = buf; + while (n > 0) { + ssize_t m = write(dst_fd, buf2, n); + if (m < 0) { + b_log(B_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno)); + b_return_defer(false); + } + n -= m; + buf2 += m; + } + } + +defer: + free(buf); + close(src_fd); + close(dst_fd); + return result; +} + +void b_cmd_render(B_Cmd cmd, B_String_Builder *render) +{ + for (size_t i = 0; i < cmd.count; ++i) { + const char *arg = cmd.items[i]; + if (arg == NULL) break; + if (i > 0) b_sb_append_cstr(render, " "); + if (!strchr(arg, ' ')) { + b_sb_append_cstr(render, arg); + } else { + b_da_append(render, '\''); + b_sb_append_cstr(render, arg); + b_da_append(render, '\''); + } + } +} + +B_Proc b_cmd_run_async(B_Cmd cmd) +{ + if (cmd.count < 1) { + b_log(B_ERROR, "Could not run empty command"); + return B_INVALID_PROC; + } + + B_String_Builder sb = {0}; + b_cmd_render(cmd, &sb); + b_sb_append_null(&sb); + b_log(B_CMD, "%s", sb.items); + b_sb_free(sb); + memset(&sb, 0, sizeof(sb)); + + pid_t cpid = fork(); + if (cpid < 0) { + b_log(B_ERROR, "Could not fork child process: %s", strerror(errno)); + return B_INVALID_PROC; + } + + if (cpid == 0) { + // NOTE: This leaks a bit of memory in the child process. + // But do we actually care? It's a one off leak anyway... + B_Cmd cmd_null = {0}; + b_da_append_many(&cmd_null, cmd.items, cmd.count); + b_cmd_append(&cmd_null, NULL); + + if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) { + b_log(B_ERROR, "Could not exec child process: %s", strerror(errno)); + exit(1); + } + B_ASSERT(0 && "unreachable"); + } + + return cpid; +} + +bool b_procs_wait(B_Procs procs) +{ + bool success = true; + for (size_t i = 0; i < procs.count; ++i) { + success = b_proc_wait(procs.items[i]) && success; + } + return success; +} + +bool b_proc_wait(B_Proc proc) +{ + if (proc == B_INVALID_PROC) return false; + + for (;;) { + int wstatus = 0; + if (waitpid(proc, &wstatus, 0) < 0) { + b_log(B_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno)); + return false; + } + + if (WIFEXITED(wstatus)) { + int exit_status = WEXITSTATUS(wstatus); + if (exit_status != 0) { + b_log(B_ERROR, "command exited with exit code %d", exit_status); + return false; + } + + break; + } + + if (WIFSIGNALED(wstatus)) { + b_log(B_ERROR, "command process was terminated by %s", strsignal(WTERMSIG(wstatus))); + return false; + } + } + + return true; +} + +bool b_cmd_run_sync(B_Cmd cmd) +{ + B_Proc p = b_cmd_run_async(cmd); + if (p == B_INVALID_PROC) return false; + return b_proc_wait(p); +} + +char *b_shift_args(int *argc, char ***argv) +{ + B_ASSERT(*argc > 0); + char *result = **argv; + (*argv) += 1; + (*argc) -= 1; + return result; +} + +void b_log(B_Log_Level level, const char *fmt, ...) +{ + switch (level) { + case B_INFO: + fprintf(stderr, "[INFO] "); + break; + case B_CMD: + fprintf(stderr, "[CMD] "); + break; + case B_BUILDING: + fprintf(stderr, "[BUILDING] "); + break; + case B_CHANGE: + fprintf(stderr, "[CHANGE] "); + break; + case B_WARNING: + fprintf(stderr, "[WARNING] "); + break; + case B_ERROR: + fprintf(stderr, "[ERROR] "); + break; + default: + B_ASSERT(0 && "unreachable"); + } + + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +bool b_read_entire_dir(const char *parent, B_File_Paths *children) +{ + bool result = true; + DIR *dir = NULL; + + dir = opendir(parent); + if (dir == NULL) { + b_log(B_ERROR, "Could not open directory %s: %s", parent, strerror(errno)); + b_return_defer(false); + } + + errno = 0; + struct dirent *ent = readdir(dir); + while (ent != NULL) { + b_da_append(children, b_temp_strdup(ent->d_name)); + ent = readdir(dir); + } + + if (errno != 0) { + b_log(B_ERROR, "Could not read directory %s: %s", parent, strerror(errno)); + b_return_defer(false); + } + +defer: + if (dir) closedir(dir); + return result; +} + +bool b_write_entire_file(const char *path, const void *data, size_t size) +{ + bool result = true; + + FILE *f = fopen(path, "wb"); + if (f == NULL) { + b_log(B_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno)); + b_return_defer(false); + } + + // len + // v + // aaaaaaaaaa + // ^ + // data + + const char *buf = data; + while (size > 0) { + size_t n = fwrite(buf, 1, size, f); + if (ferror(f)) { + b_log(B_ERROR, "Could not write into file %s: %s\n", path, strerror(errno)); + b_return_defer(false); + } + size -= n; + buf += n; + } + +defer: + if (f) fclose(f); + return result; +} + +B_File_Type b_get_file_type(const char *path) +{ + struct stat statbuf; + if (stat(path, &statbuf) < 0) { + b_log(B_ERROR, "Could not get stat of %s: %s", path, strerror(errno)); + return -1; + } + + switch (statbuf.st_mode & S_IFMT) { + case S_IFDIR: return B_FILE_DIRECTORY; + case S_IFREG: return B_FILE_REGULAR; + case S_IFLNK: return B_FILE_SYMLINK; + default: return B_FILE_OTHER; + } +} + +bool b_copy_directory_recursively(const char *src_path, const char *dst_path) +{ + bool result = true; + B_File_Paths children = {0}; + B_String_Builder src_sb = {0}; + B_String_Builder dst_sb = {0}; + size_t temp_checkpoint = b_temp_save(); + + B_File_Type type = b_get_file_type(src_path); + if (type < 0) return false; + + switch (type) { + case B_FILE_DIRECTORY: { + if (!b_mkdir_if_not_exists(dst_path)) b_return_defer(false); + if (!b_read_entire_dir(src_path, &children)) b_return_defer(false); + + for (size_t i = 0; i < children.count; ++i) { + if (strcmp(children.items[i], ".") == 0) continue; + if (strcmp(children.items[i], "..") == 0) continue; + + src_sb.count = 0; + b_sb_append_cstr(&src_sb, src_path); + b_sb_append_cstr(&src_sb, "/"); + b_sb_append_cstr(&src_sb, children.items[i]); + b_sb_append_null(&src_sb); + + dst_sb.count = 0; + b_sb_append_cstr(&dst_sb, dst_path); + b_sb_append_cstr(&dst_sb, "/"); + b_sb_append_cstr(&dst_sb, children.items[i]); + b_sb_append_null(&dst_sb); + + if (!b_copy_directory_recursively(src_sb.items, dst_sb.items)) { + b_return_defer(false); + } + } + } break; + + case B_FILE_REGULAR: { + if (!b_copy_file(src_path, dst_path)) { + b_return_defer(false); + } + } break; + + case B_FILE_SYMLINK: { + b_log(B_WARNING, "TODO: Copying symlinks is not supported yet"); + } break; + + case B_FILE_OTHER: { + b_log(B_ERROR, "Unsupported type of file %s", src_path); + b_return_defer(false); + } break; + + default: B_ASSERT(0 && "unreachable"); + } + +defer: + b_temp_rewind(temp_checkpoint); + b_da_free(src_sb); + b_da_free(dst_sb); + b_da_free(children); + return result; +} + +char *b_temp_strdup(const char *cstr) +{ + size_t n = strlen(cstr); + char *result = b_temp_alloc(n + 1); + B_ASSERT(result != NULL && "Increase B_TEMP_CAPACITY"); + memcpy(result, cstr, n); + result[n] = '\0'; + return result; +} + +void *b_temp_alloc(size_t size) +{ + if (b_temp_size + size > B_TEMP_CAPACITY) return NULL; + void *result = &b_temp[b_temp_size]; + b_temp_size += size; + return result; +} + +char *b_temp_sprintf(const char *format, ...) +{ + va_list args; + va_start(args, format); + int n = vsnprintf(NULL, 0, format, args); + va_end(args); + + B_ASSERT(n >= 0); + char *result = b_temp_alloc(n + 1); + B_ASSERT(result != NULL && "Extend the size of the temporary allocator"); + // TODO: use proper arenas for the temporary allocator; + va_start(args, format); + vsnprintf(result, n + 1, format, args); + va_end(args); + + return result; +} + +void b_temp_reset(void) +{ + b_temp_size = 0; +} + +size_t b_temp_save(void) +{ + return b_temp_size; +} + +void b_temp_rewind(size_t checkpoint) +{ + b_temp_size = checkpoint; +} + +const char *b_temp_sv_to_cstr(B_String_View sv) +{ + char *result = b_temp_alloc(sv.count + 1); + B_ASSERT(result != NULL && "Extend the size of the temporary allocator"); + memcpy(result, sv.data, sv.count); + result[sv.count] = '\0'; + return result; +} + +int b_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) +{ + struct stat statbuf = {0}; + + if (stat(output_path, &statbuf) < 0) { + // NOTE: if output does not exist it 100% must be rebuilt + if (errno == ENOENT) { + b_log(B_BUILDING, "%s", output_path); + return 1; + } + b_log(B_ERROR, "could not stat %s: %s", output_path, strerror(errno)); + return -1; + } + int output_path_time = statbuf.st_mtime; + + for (size_t i = 0; i < input_paths_count; ++i) { + const char *input_path = input_paths[i]; + if (stat(input_path, &statbuf) < 0) { + // NOTE: non-existing input is an error cause it is needed for building in the first place + b_log(B_ERROR, "could not stat %s: %s", input_path, strerror(errno)); + return -1; + } + int input_path_time = statbuf.st_mtime; + // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild + if (input_path_time > output_path_time) { + b_log(B_CHANGE, "%s", input_path); + return 1; + } + } + + return 0; +} + +int b_needs_rebuild1(const char *output_path, const char *input_path) +{ + return b_needs_rebuild(output_path, &input_path, 1); +} + +bool b_rename(const char *old_path, const char *new_path) +{ + b_log(B_INFO, "renaming %s -> %s", old_path, new_path); + if (rename(old_path, new_path) < 0) { + b_log(B_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno)); + return false; + } + return true; +} + +bool b_read_entire_file(const char *path, B_String_Builder *sb) +{ + bool result = true; + + FILE *f = fopen(path, "rb"); + if (f == NULL) b_return_defer(false); + if (fseek(f, 0, SEEK_END) < 0) b_return_defer(false); + long m = ftell(f); + if (m < 0) b_return_defer(false); + if (fseek(f, 0, SEEK_SET) < 0) b_return_defer(false); + + size_t new_count = sb->count + m; + if (new_count > sb->capacity) { + sb->items = realloc(sb->items, new_count); + B_ASSERT(sb->items != NULL && "Buy more RAM lool!!"); + sb->capacity = new_count; + } + + fread(sb->items + sb->count, m, 1, f); + if (ferror(f)) { + // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case. + b_return_defer(false); + } + sb->count = new_count; + +defer: + if (!result) b_log(B_ERROR, "Could not read file %s: %s", path, strerror(errno)); + if (f) fclose(f); + return result; +} + +B_String_View b_sv_chop_by_delim(B_String_View *sv, char delim) +{ + size_t i = 0; + while (i < sv->count && sv->data[i] != delim) { + i += 1; + } + + B_String_View result = b_sv_from_parts(sv->data, i); + + if (i < sv->count) { + sv->count -= i + 1; + sv->data += i + 1; + } else { + sv->count -= i; + sv->data += i; + } + + return result; +} + +B_String_View b_sv_from_parts(const char *data, size_t count) +{ + B_String_View sv; + sv.count = count; + sv.data = data; + return sv; +} + +B_String_View b_sv_trim_left(B_String_View sv) +{ + size_t i = 0; + while (i < sv.count && isspace(sv.data[i])) { + i += 1; + } + + return b_sv_from_parts(sv.data + i, sv.count - i); +} + +B_String_View b_sv_trim_right(B_String_View sv) +{ + size_t i = 0; + while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { + i += 1; + } + + return b_sv_from_parts(sv.data, sv.count - i); +} + +B_String_View b_sv_trim(B_String_View sv) +{ + return b_sv_trim_right(b_sv_trim_left(sv)); +} + +B_String_View b_sv_from_cstr(const char *cstr) +{ + return b_sv_from_parts(cstr, strlen(cstr)); +} + +bool b_sv_eq(B_String_View a, B_String_View b) +{ + if (a.count != b.count) { + return false; + } else { + return memcmp(a.data, b.data, a.count) == 0; + } +} + +// RETURNS: +// 0 - file does not exists +// 1 - file exists +// -1 - error while checking if file exists. The error is logged +int b_file_exists(const char *file_path) +{ + struct stat statbuf; + if (stat(file_path, &statbuf) < 0) { + if (errno == ENOENT) return 0; + b_log(B_ERROR, "Could not check if file %s exists: %s", file_path, strerror(errno)); + return -1; + } + return 1; +} + +#endif diff --git a/src/biquad_filter.c b/src/biquad_filter.c new file mode 100644 index 0000000..ae33a4c --- /dev/null +++ b/src/biquad_filter.c @@ -0,0 +1,71 @@ +//#include "biquad_filter.h" + +typedef struct biquad_filter_t { + // Filter coefficients + double b0, b1, b2; // Feedforward coefficients + double a1, a2; // Feedback coefficients + + // Delay buffers (history of input and output) + double x1, x2; // Previous input samples + double y1, y2; // Previous output samples +} biquad_filter_t; + +void +biquad_calculate_coefficients(biquad_filter_t* filter, double freq, double Q, double sampleRate, char type) +{ + double omega = 2.0 * M_PI * freq / sampleRate; + double alpha = sin(omega) / (2.0 * Q); + double cos_omega = cos(omega); + + // Initialize coefficients based on filter type + switch (type) { + case 'l': // Low-pass filter + filter->b0 = (1.0 - cos_omega) / 2.0; + filter->b1 = 1.0 - cos_omega; + filter->b2 = filter->b0; + filter->a1 = -2.0 * cos_omega; + filter->a2 = 1.0 - alpha; + break; + case 'h': // High-pass filter + filter->b0 = (1.0 + cos_omega) / 2.0; + filter->b1 = -(1.0 + cos_omega); + filter->b2 = filter->b0; + filter->a1 = -2.0 * cos_omega; + filter->a2 = 1.0 - alpha; + break; + case 'b': // Band-pass filter + filter->b0 = alpha; + filter->b1 = 0.0; + filter->b2 = -alpha; + filter->a1 = -2.0 * cos_omega; + filter->a2 = 1.0 - alpha; + break; + default: + exit(EXIT_FAILURE); // Unsupported filter type + } + + // Normalize coefficients + double a0 = 1.0 + alpha; + filter->b0 /= a0; + filter->b1 /= a0; + filter->b2 /= a0; + filter->a1 /= a0; + filter->a2 /= a0; +} + +double +biquad_process(biquad_filter_t* filter, double input) +{ + double output = filter->b0 * input + + filter->b1 * filter->x1 + + filter->b2 * filter->x2 - + filter->a1 * filter->y1 - + filter->a2 * filter->y2; + + filter->x2 = filter->x1; + filter->x1 = input; + filter->y2 = filter->y1; + filter->y1 = output; + + return output; +} diff --git a/src/control.c b/src/control.c index d952f7f..44aadc9 100644 --- a/src/control.c +++ b/src/control.c @@ -34,6 +34,22 @@ cc_fix(cc_t *cc) } void +cc_set(cc_t *cc, float value) +{ + float new_value; + if (value >= cc->max) { + new_value = cc->max; + } else if (value <= cc->min) { + new_value = cc->min; + } else { + new_value = value; + } + + // calculate mod + cc->mod = new_value - cc->target; +} + +void cc_reset(cc_t *cc) { cc->target = cc->def; diff --git a/src/control.h b/src/control.h index 45e87ed..8fbcdfd 100644 --- a/src/control.h +++ b/src/control.h @@ -24,7 +24,7 @@ typedef struct cc_t { float step; float def; float value; /* active value (start for interpolation) */ - float mod; /* stores the modified value before it is set as target */ + float mod; /* stores the amount a value needs to change to reach new target */ float target; /* target value (end for interpolation) */ } cc_t; @@ -53,6 +53,12 @@ typedef struct cc_t { int cc_step(cc_t *cc, int steps); /** + Set the cc to the target value, + do so using mod. respects min/max etc + */ +void cc_set(cc_t *cc, float value); + +/** Reset the cc to defaults */ void cc_reset(cc_t *cc); diff --git a/src/filter.c b/src/filter.c index 69d7bb6..9dfc19e 100644 --- a/src/filter.c +++ b/src/filter.c @@ -34,6 +34,7 @@ void update_bw_low_pass_filter(BWLowPass* filter, FTR_PRECISION s, FTR_PRECISION f, FTR_PRECISION q) { + (void)q; FTR_PRECISION a = TAN((FTR_PRECISION)(M_PI * f / s)); FTR_PRECISION a2 = a * a; FTR_PRECISION r; diff --git a/src/gen.h b/src/gen.h new file mode 100644 index 0000000..64de944 --- /dev/null +++ b/src/gen.h @@ -0,0 +1,17 @@ +#ifndef GEN_H +#define GEN_H + +#include "synth_engine.h" + +typedef struct { + osc_t ** osc; + int osci; + osc_t ** lfo; + int lfoi; + adsr_t ** asdr; + int adsri; +} gen_t; + +float gen(synth_t * synth, midi_note_t * note, osc_t * osc, sample_rate); + +#endif /* GEN_H */ diff --git a/src/lowpass.c b/src/lowpass.c index 0ec53be..b0abda5 100644 --- a/src/lowpass.c +++ b/src/lowpass.c @@ -274,6 +274,7 @@ void prewarp( double *a0, double *a1, double *a2, double fc, double fs) { + (void)a0; double wp, pi; pi = 4.0 * atan(1.0); @@ -46,6 +46,7 @@ void midi_decode(uint32_t msg, synth_t * synth) { case 0x0B: printf("Control Change: channel=%d, controller=%d, value=%d\n", channel, data1, data2); int x = data2 < 64 ? 1 : -1; + int val; switch (data1) { case 0: cc_step(&synth->cc_adsr_a, x); @@ -94,6 +95,7 @@ void midi_decode(uint32_t msg, synth_t * synth) { if (synth->geni < 6) synth->geni++; break; default: + break; } break; case 0x0C: @@ -103,7 +105,7 @@ void midi_decode(uint32_t msg, synth_t * synth) { printf("Channel Pressure: channel=%d, pressure=%d\n", channel, data1); break; case 0x0E: - int val = ((data2 << 7) | data1) - 8192; + val = ((data2 << 7) | data1) - 8192; printf("Pitch Bend: channel=%d, value=%d\n", channel, val); float pitch; float semitones = 2; @@ -126,6 +128,7 @@ void midi_decode(uint32_t msg, synth_t * synth) { int enable = 0; void midiCallback(PtTimestamp timestamp, void *userData) { + (void)timestamp; midi_t * m = (midi_t *)userData; if (!m->stream) return; @@ -156,12 +159,19 @@ init_midi(midi_t *m, synth_t *synth) printf("midi devs: %d\n", Pm_CountDevices()); const PmDeviceInfo *info; - int i; + int i, c=0; for (i = 0; i < Pm_CountDevices(); i++) { info = Pm_GetDeviceInfo(i); + if (!info->input) { + continue; + } printf("%d: %s [input: %d output: %d opened: %d is_virt:%d] (interf: %s) -- %d\n", i, info->name, info->input, info->output, info->opened, info->is_virtual, info->interf, Pm_GetDefaultInputDeviceID()); + if (synth->midi_device_id == c) { + break; + } + c++; //if (!strcmp("MPK225 MIDI", info->name) && !info->input) break; - if (!strcmp("MPK225 Port A", info->name) && info->input == 1) break; + //if (!strcmp("MPK225 Port A", info->name) && info->input == 1) break; //if (!strcmp("CH345 MIDI 1", info->name) && info->input == 1) break; //if (!strcmp("Midi Through Port-0", info->name) && info->input == 1) break; //if (!strcmp("DigitalKBD MIDI 1", info->name) && info->input == 1) break; @@ -178,6 +188,47 @@ init_midi(midi_t *m, synth_t *synth) enable = 1; } +int +get_midi_device_id(const char * name) +{ + int i, c=0; + const PmDeviceInfo *info; + for (i = 0; i < Pm_CountDevices(); i++) { + info = Pm_GetDeviceInfo(i); + if (!info->input) { + continue; + } + if (!strcmp(name, info->name)) { + return c; + } + c++; + } + return -1; +} + +char * +get_midi_devices() +{ + //Pm_Initialize(); + int i; + char *ret = (char *)malloc(sizeof(char) * 4096); + strcpy(ret, ""); + const PmDeviceInfo *info; + for (i = 0; i < Pm_CountDevices(); i++) { + info = Pm_GetDeviceInfo(i); + if (!info->input) { + continue; + } + printf("!!!!!!!!!!!!!!!!!!!!!!!!! ==> %s ========\n", info->name); + strcat(ret, info->name); + strcat(ret, ";"); + } + ret[strlen(ret) - 1] = '\0'; + //Pm_Terminate(); + + return ret; +} + void terminate_midi(midi_t *m) { @@ -13,5 +13,7 @@ typedef struct midi_t { void init_midi(midi_t *midi, synth_t *synth); void terminate_midi(midi_t *midi); +int get_midi_device_id(const char * name); +char * get_midi_devices(); #endif /* MIDI_H */ diff --git a/src/notes.h b/src/notes.h index ed6b3e6..bab7117 100644 --- a/src/notes.h +++ b/src/notes.h @@ -16,7 +16,8 @@ static float note_B[] = {30.87, 61.74, 123.47, 246.94, 493.88, 987.77, 1975.53, static float *notes[12] = {note_C, note_Db, note_D, note_Eb, note_E, note_F, note_Gb, note_G, note_Ab, note_A, note_Bb, note_B }; -static char * +// do I really want inline? used to be static +inline char * int_to_note(int n) { switch (n) { @@ -57,7 +57,7 @@ osc_load_wav(osc_t * osc, const char * path) { SNDFILE* file; SF_INFO fileInfo; - int numSamplesRead; + int numSamplesRead = 0; // Open the WAV file file = sf_open(path, SFM_READ, &fileInfo); @@ -81,7 +81,8 @@ osc_load_wav(osc_t * osc, const char * path) // Read the WAV file into the buffer numSamplesRead = sf_readf_float(file, osc->data, fileInfo.frames); - + (void)numSamplesRead; + /* float max = -1000; */ /* float min = 1000; */ /* for (int i = 0; i < numSamplesRead; i++) { */ diff --git a/src/osc_digisaw.c b/src/osc_digisaw.c index b71a9e3..bd1d8a4 100644 --- a/src/osc_digisaw.c +++ b/src/osc_digisaw.c @@ -28,6 +28,7 @@ digisaw(int index) else if (index == 18) return 0.8f; else if (index == 19) return 0.9f; else if (index == 20) return 1.0f; + return 0; } float diff --git a/src/osc_sound.c b/src/osc_sound.c index e919e96..fbf8860 100644 --- a/src/osc_sound.c +++ b/src/osc_sound.c @@ -18,13 +18,13 @@ float osc_sound(float offset) { if (!OSC_sound.len) { - // osc_load_wav(&OSC_sound, "/home/gramanas/code/synth-project/waves/test_lick.wav"); - //osc_load_wav(&OSC_sound, "/home/gramanas/code/synth-project/waves/Free Wavetables[128]/FM Synthesis[128-44.1khz-16bit]/FM Sq- NotPM.wav"); - //osc_load_wav(&OSC_sound, "/home/gramanas/code/synth-project/waves/Free Wavetables[2048]/Melda Oscillator[2048-44.1khz-32bit]/Melda SKEW.wav"); - //osc_load_wav(&OSC_sound, "/home/gramanas/code/synth-project/waves/Free Wavetables[2048]/Filter Sweep[2048-44.1khz-32bit]/SweepSaw.wav"); - //osc_load_wav(&OSC_sound, "/home/gramanas/code/synth-project/waves/Free Wavetables[2048]/Additive Synth[2048-44.1khz-32bit]/Add Synth7.wav"); - //osc_load_wav(&OSC_sound, "/home/gramanas/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/gramanas/code/synth-project/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/waves/test_lick.wav"); + //osc_load_wav(&OSC_sound, "/home/grm/code/synth-project/waves/Free Wavetables[128]/FM Synthesis[128-44.1khz-16bit]/FM Sq- NotPM.wav"); + //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/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; } diff --git a/src/osc_tri.c b/src/osc_tri.c index f0c9951..35abe7f 100644 --- a/src/osc_tri.c +++ b/src/osc_tri.c @@ -14,6 +14,7 @@ tri(int index) else if (index == 2) return 0.0f; else if (index == 3) return -1.0f; //else return 0.0f; + return 0; } float @@ -55,7 +56,6 @@ make_tri(const char * name) { osc_t * osc = (osc_t *)malloc(sizeof(osc_t)); - int len = strlen(name); strncpy(osc->name, name, 16); osc->data = NULL; osc->len = 2; diff --git a/src/oscillator.h b/src/oscillator.h new file mode 100644 index 0000000..945d1e3 --- /dev/null +++ b/src/oscillator.h @@ -0,0 +1,22 @@ +struct oscillator { + int type; + char name[128]; + +} + +struct sin_oscillator { + struct oscillator osc; + +} + + + + +get_next_sample(osc_handle, sample_rate, time?) + + + + +pa_callback // sound system callback to get next FRAMES_PER_BUFFER frames +|__ get_frame // creates a single frame -> corresponding to X samples where X is the number of channels (2 for our stereo case) + |__ make_sample diff --git a/src/raygui.h b/src/raygui.h index 833725d..01e9c5b 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -1500,6 +1500,7 @@ int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) #define RAYGUI_TABBAR_ITEM_WIDTH 160 GuiState state = guiState; + (void)state; int closing = -1; Rectangle tabBounds = { bounds.x, bounds.y, RAYGUI_TABBAR_ITEM_WIDTH, bounds.height }; @@ -2411,6 +2412,7 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) int textLength = (int)strlen(text); // Length in bytes (UTF-8 string) int byteSize = 0; const char *textUTF8 = CodepointToUTF8(codepoint, &byteSize); + (void)textUTF8; // Introduce characters if ((textLength + byteSize) < textSize) @@ -2908,6 +2910,7 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *focus, in // Color Panel control Color GuiColorPanel(Rectangle bounds, const char *text, Color color) { + (void)text; const Color colWhite = { 255, 255, 255, 255 }; const Color colBlack = { 0, 0, 0, 255 }; @@ -2989,6 +2992,7 @@ Color GuiColorPanel(Rectangle bounds, const char *text, Color color) // NOTE: Returns alpha value normalized [0..1] float GuiColorBarAlpha(Rectangle bounds, const char *text, float alpha) { + (void)text; #if !defined(RAYGUI_COLORBARALPHA_CHECKED_SIZE) #define RAYGUI_COLORBARALPHA_CHECKED_SIZE 10 #endif @@ -3058,6 +3062,7 @@ float GuiColorBarAlpha(Rectangle bounds, const char *text, float alpha) // float GuiColorBarLuminance() [BLACK->WHITE] float GuiColorBarHue(Rectangle bounds, const char *text, float hue) { + (void)text; GuiState state = guiState; Rectangle selector = { (float)bounds.x - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)bounds.y + hue/360.0f*bounds.height - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2, (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT) }; @@ -3126,6 +3131,7 @@ float GuiColorBarHue(Rectangle bounds, const char *text, float hue) // NOTE: bounds define GuiColorPanel() size Color GuiColorPicker(Rectangle bounds, const char *text, Color color) { + (void)text; color = GuiColorPanel(bounds, NULL, color); Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; @@ -3288,6 +3294,7 @@ int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, co // https://stackoverflow.com/questions/4435450/2d-opengl-drawing-lines-that-dont-exactly-fit-pixel-raster Vector2 GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs) { + (void)text; // Grid lines alpha amount #if !defined(RAYGUI_GRID_ALPHA) #define RAYGUI_GRID_ALPHA 0.15f @@ -3946,6 +3953,7 @@ const char **GetTextLines(const char *text, int *count) int len = 0; *count = 1; int lineSize = 0; // Stores current line size, not returned + (void)lineSize; for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) { diff --git a/src/sound.c b/src/sound.c index 57baa6b..04b3f9b 100644 --- a/src/sound.c +++ b/src/sound.c @@ -1,27 +1,77 @@ #include "sound.h" +#include <portaudio.h> static void StreamFinished( void* synthData ) { - synth_t *synth = (synth_t *) synthData; + (void)synthData; } -void -init_sound(synth_t * synth, PaStreamCallback *streamCallback) +int +get_soundcard_id(const char * name) { Pa_Initialize(); + int i, c=0; + const PaDeviceInfo *deviceInfo; + for( i=0; i< Pa_GetDeviceCount(); i++ ) { + deviceInfo = Pa_GetDeviceInfo(i); + if (deviceInfo->maxOutputChannels == 0) { + continue; + } + if (!strcmp(name, deviceInfo->name)) { + Pa_Terminate(); + return c; + } + c++; + } + Pa_Terminate(); + return -1; +} +char * +get_soundcards() +{ + Pa_Initialize(); int i; + const PaDeviceInfo *deviceInfo; + char *ret = (char *)malloc(sizeof(char) * 4096); + strcpy(ret, ""); + for( i=0; i< Pa_GetDeviceCount(); i++ ) { + deviceInfo = Pa_GetDeviceInfo(i); + if (deviceInfo->maxOutputChannels == 0) { + continue; + } + strcat(ret, deviceInfo->name); + strcat(ret, ";"); + } + ret[strlen(ret) - 1] = '\0'; + Pa_Terminate(); + + return ret; +} + +void +init_sound(synth_t * synth, PaStreamCallback *streamCallback, const int device_id) +{ + printf("Before\n"); + Pa_Initialize(); + printf("after init\n"); + + int i, c=0; const PaDeviceInfo *deviceInfo; for( i=0; i< Pa_GetDeviceCount(); i++ ) { - deviceInfo = Pa_GetDeviceInfo( i ); + deviceInfo = Pa_GetDeviceInfo(i); + if (deviceInfo->maxOutputChannels == 0) { + continue; + } //if (!strcmp("HyperX Cloud II Wireless: USB Audio (hw:2,0)", deviceInfo->name)) break; - printf("dev: %s || %f\n", deviceInfo->name, deviceInfo->defaultSampleRate); + printf("dev: %s || %f || %d channels\n", deviceInfo->name, deviceInfo->defaultSampleRate, deviceInfo->maxOutputChannels); //if (!strcmp("HDA Intel PCH: ALC1220 Analog (hw:0,0)", deviceInfo->name)) break; - if (!strcmp("pulse", deviceInfo->name)) break; + if (c == device_id) break; + c++; } - + PaStreamParameters outputParameters; outputParameters.device = i; Pa_GetDefaultOutputDevice(); /* default output device */ @@ -31,15 +81,20 @@ init_sound(synth_t * synth, PaStreamCallback *streamCallback) outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; - Pa_OpenStream(&(synth->stream), - NULL, /* no input */ - &outputParameters, - SAMPLE_RATE, - FRAMES_PER_BUFFER, - paClipOff | paDitherOff, /* we won't output out of range samples so don't bother clipping them */ - streamCallback, - synth ); + PaError err; + err = Pa_OpenStream(&(synth->stream), + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff | paDitherOff, /* we won't output out of range samples so don't bother clipping them */ + streamCallback, + synth ); + if (err != paNoError) { + printf("Error opening stream with %s!!!!!", Pa_GetDeviceInfo(outputParameters.device)->name); + } + Pa_SetStreamFinishedCallback(synth->stream, &StreamFinished); Pa_StartStream(synth->stream); diff --git a/src/sound.h b/src/sound.h index 9e0c066..384c775 100644 --- a/src/sound.h +++ b/src/sound.h @@ -9,7 +9,10 @@ /* #define SAMPLE_RATE (44100) */ /* #define FRAMES_PER_BUFFER (256) */ -void init_sound(synth_t * synth, PaStreamCallback *streamCallback); +void init_sound(synth_t * synth, PaStreamCallback *streamCallback, const int device_id); + +char *get_soundcards(); +int get_soundcard_id(const char * name); void destroy_sound(synth_t * synth); diff --git a/src/stats.h b/src/stats.h new file mode 100644 index 0000000..a537a44 --- /dev/null +++ b/src/stats.h @@ -0,0 +1,10 @@ +#ifndef STATS_H +#define STATS_H + +typedef struct stats_t { + uint64_t osc_calls; +} stats_t; + +void print_stats(stats_t * stats); + +#endif /* STATS_H */ diff --git a/src/synth.c b/src/synth.c index f7ae019..1b5187b 100644 --- a/src/synth.c +++ b/src/synth.c @@ -23,33 +23,19 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* audio */ -#include <string.h> -#include <portaudio.h> - -#include <portmidi.h> - -/* graphics */ -#include "synth_gui.h" - -/* synth */ #include "synth_engine.h" -#include "midi.h" - -#define NUM_SECONDS (1) - -#define WAVE_SIZE (44100) +#include "synth_gui.h" +#include "web.h" int main(void) { - midi_t midi; - synth_t * synth = init_synth(); - init_midi(&midi, synth); + init_web(synth); + rayrun(synth); - terminate_midi(&midi); + free_web(); free_synth(synth); return 0; diff --git a/src/synth_engine.h b/src/synth_engine.h index 3b0b238..5ff7d8d 100644 --- a/src/synth_engine.h +++ b/src/synth_engine.h @@ -64,6 +64,8 @@ typedef struct { int tmp_index; } synth_viz; +struct midi_t; + typedef struct { PaStream *stream; @@ -71,7 +73,7 @@ typedef struct { cc_t * ccs[128]; int cci; - + cc_t cc_pitch; cc_t cc_cutoff; cc_t cc_resonance; @@ -83,7 +85,15 @@ typedef struct { cc_t cc_adsr_s; cc_t cc_adsr_r; cc_t cc_gain; - + + cc_t cc_f_adsr_a; + cc_t cc_f_adsr_peak; + cc_t cc_f_adsr_d; + cc_t cc_f_adsr_s; + cc_t cc_f_adsr_r; + + int autogain; + float x; midi_note_t midi_note[MIDI_NOTES]; @@ -91,6 +101,7 @@ typedef struct { int midi_active_n; adsr_t adsr; + adsr_t f_adsr; lfo_t lfo; @@ -102,8 +113,12 @@ typedef struct { cc_t cc_del_time; cc_t cc_del_feedback; unsigned long long counter; - + + int f_adsr_enabled; int filter; + int biquad; + char biquad_type; + int clamp; int modifiers[16]; @@ -117,11 +132,21 @@ typedef struct { int active; int sound_active; - + + int soundcard_id; + int midi_device_id; + synth_viz viz; + + struct midi_t * midi; } synth_t; synth_t * init_synth(); -void free_synth(synth_t * synth); +void free_synth(synth_t *synth); +void change_soundcard(synth_t *synth); +void change_midi_device(synth_t *synth); + +int save_synth(synth_t *synth, const char *path); +int load_synth(synth_t *synth, const char *path); #endif /* SYNTH_ENGINE_H */ diff --git a/src/synth_engine_v2.c b/src/synth_engine_v2.c index bfd1d4a..ad1be72 100644 --- a/src/synth_engine_v2.c +++ b/src/synth_engine_v2.c @@ -4,14 +4,17 @@ #include "filter.h" #include "control.h" #include "sound.h" +#include "midi.h" #include "osc.h" +#include <libconfig.h> #include <string.h> #include <time.h> float gen0(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) { + (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; @@ -20,6 +23,7 @@ gen0(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) float gen1(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) { + (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; @@ -28,6 +32,7 @@ gen1(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) float gen2(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) { + (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; @@ -36,6 +41,7 @@ gen2(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) float gen3(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) { + (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; @@ -44,6 +50,7 @@ gen3(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) float gen4(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) { + (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; @@ -52,6 +59,7 @@ gen4(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) float gen5(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) { + (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; @@ -60,6 +68,7 @@ gen5(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) float gen6(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) { + (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; @@ -106,23 +115,60 @@ notes_active(synth_t *synth) } #define CC_GET(name) cc_iget(&synth->cc_##name, frame, FRAMES_PER_BUFFER) +#define CC_SET(name, value) synth->cc_##name.mod = value - synth->cc_##name.target float prev_sample = 0.0f; -float +#include "biquad_filter.c" + +biquad_filter_t biquad = {0}; + + +void do_fliter(synth_t *synth, float *sample, unsigned int sample_rate, int frame) { - if (synth->filter) { - // ALLL THE FILTERS - float cutoff = CC_GET(cutoff); - float reso = CC_GET(resonance); + synth->f_adsr.a = CC_GET(f_adsr_a); + synth->f_adsr.peak = CC_GET(f_adsr_peak); + synth->f_adsr.d = CC_GET(f_adsr_d); + synth->f_adsr.s = CC_GET(f_adsr_s); + synth->f_adsr.r = CC_GET(f_adsr_r); + + // ALLL THE FILTERS + float cutoff = CC_GET(cutoff); + float reso = CC_GET(resonance); + + if (synth->f_adsr_enabled) { + midi_note_t *note; + if (synth->midi_active_n != 0) { + float latest = 0; + for (int i = 0; i < synth->midi_active_n; i++) { + // reverse this and set latest to a big number for the first note played to take precedence + if (synth->midi_active[i]->noteOn > latest) { + latest = synth->midi_active[i]->noteOn; + note = synth->midi_active[i]; + } + } + + cutoff = 50 + cutoff * fix_adsr(&synth->f_adsr, + note->noteOn, + note->noteOff, + note->elapsed, + note->noteOffSample); + } + } + if (synth->filter) { if (cutoff == 0) cutoff = 0.001; lpf_update(reso, cutoff, sample_rate); *sample = lpf_filter(*sample); - update_bw_low_pass_filter(synth->fff, SAMPLE_RATE, cutoff, reso); - *sample = bw_low_pass(synth->fff, *sample); + /* update_bw_low_pass_filter(synth->fff, SAMPLE_RATE, cutoff, reso); */ + /* *sample = bw_low_pass(synth->fff, *sample); */ + } + if (synth->biquad) { + //if (synth->cc_cutoff.mod || synth->cc_resonance.mod) //RUN ONLY WHEN THERE ARE CHANGES + biquad_calculate_coefficients(&biquad, cutoff, reso, sample_rate, synth->biquad_type); + *sample = biquad_process(&biquad, *sample); } } @@ -161,6 +207,19 @@ get_max_sample(synth_t *synth, int test_size) return max; } +# include <stdint.h> // uint32_t + +float Q_rsqrt(float number) +{ + union { + float f; + uint32_t i; + } conv = { .f = number }; + conv.i = 0x5f3759df - (conv.i >> 1); + conv.f *= 1.5F - (number * 0.5F * conv.f * conv.f); + return conv.f; +} + float make_sample(synth_t * synth, unsigned int sample_rate, int frame) { @@ -174,7 +233,7 @@ make_sample(synth_t * synth, unsigned int sample_rate, int frame) rms += synth->midi_active[i]->velocity * synth->midi_active[i]->velocity; } - rms = sqrt(rms / (float)synth->midi_active_n); + rms = 1.0 / Q_rsqrt(rms / (float)synth->midi_active_n); //float max = get_max_sample(synth, 20); synth->adsr.a = CC_GET(adsr_a); @@ -202,15 +261,23 @@ make_sample(synth_t * synth, unsigned int sample_rate, int frame) sample_rate); } + /* 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; + } + //printf("CLICK! %f\n", fabsf(prev_sample) - fabsf(sample)); //if (fabsf(fabsf(prev_sample) - fabsf(sample)) > 0.03) printf("CLICK! (diff: %f)\n", fabsf(prev_sample) - fabsf(sample)); prev_sample = sample; @@ -232,6 +299,7 @@ increment_synth(synth_t *synth) { synth->lfo.elapsed++; synth->adsr.elapsed++; + synth->f_adsr.elapsed++; for (int i = 0; i < synth->midi_active_n; i++) { if (synth->midi_active[i]) @@ -239,23 +307,28 @@ increment_synth(synth_t *synth) } } +float prev = 0; + void -get_frame(void *outputBuffer, synth_t *synth, int i) +get_frame(void *outputBuffer, synth_t *synth, int frame) { - float *out = (float*)outputBuffer + 2 * i; - float s = 0.0f; + float *out = (float*)outputBuffer + (2 * frame); + float sample = 0.0f; if (!notes_active(synth)) { + synth->f_adsr.elapsed = 0; synth->active = 0; + // auto gain test + //synth->cc_gain.target = 1; } if (!synth->delay) { synth->counter = 0; } - s = make_sample(synth, SAMPLE_RATE, i); + sample = make_sample(synth, SAMPLE_RATE, frame); synth->counter++; - if (synth->counter >= (int)(synth->cc_del_time.target * SAMPLE_RATE)) { + if (synth->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) { @@ -264,18 +337,60 @@ get_frame(void *outputBuffer, synth_t *synth, int i) tmp = synth->del[SAMPLE_RATE * 10 + idx]; } - s = clamp(s + synth->cc_del_feedback.target * tmp, -1, 1); + sample = clamp(sample + synth->cc_del_feedback.target * tmp, -1, 1); } - add_to_delay(synth, s); - *out++ = s; - *out++ = s; + add_to_delay(synth, sample); + + + + //sample = clamp(sample, -1, 1); + + *out++ = sample; + *out++ = sample; + + if (sample > 1.0f || sample < -1.0f) + printf("%f\n", sample); + if (prev != 0.0f && fabs(prev - sample) > 0.5f) { + printf("%.2f --> %.2f\n", prev, sample); + } + prev = sample; // move time increment_synth(synth); // viz - PaUtil_WriteRingBuffer(&synth->viz.wave_buffer, &s, 1); + PaUtil_WriteRingBuffer(&synth->viz.wave_buffer, &sample, 1); +} + +/* void */ +/* smooth_buffer1(float *buffer) */ +/* { */ +/* return; */ +/* } */ +void smooth_buffer(float *buffer, int frames_per_buffer, float smooth_factor) { + if (smooth_factor < 0.0f || smooth_factor > 1.0f) { + printf("Invalid smooth factor. It should be between 0 and 1.\n"); + return; + } + + float prev_sample_ch1 = buffer[0]; // First sample for channel 1 + float prev_sample_ch2 = buffer[1]; // First sample for channel 2 + + for (int i = 0; i < frames_per_buffer; i++) { + int ch1_index = 2 * i; // Index for channel 1 + int ch2_index = 2 * i + 1; // Index for channel 2 + + // Smooth channel 1 + buffer[ch1_index] = (1.0f - smooth_factor) * buffer[ch1_index] + + smooth_factor * prev_sample_ch1; + prev_sample_ch1 = buffer[ch1_index]; + + // Smooth channel 2 + buffer[ch2_index] = (1.0f - smooth_factor) * buffer[ch2_index] + + smooth_factor * prev_sample_ch2; + prev_sample_ch2 = buffer[ch2_index]; + } } @@ -292,7 +407,6 @@ sound_gen(const void *inputBuffer, void *outputBuffer, if (!synth->sound_active) return 0; //paContinue; float buffer[2 * FRAMES_PER_BUFFER]; - float buffer2[2 * FRAMES_PER_BUFFER]; (void) timeInfo; (void) statusFlags; @@ -305,14 +419,15 @@ sound_gen(const void *inputBuffer, void *outputBuffer, cc_prep(synth->ccs[i]); } // fill buffer - for( unsigned long i=0; i<framesPerBuffer; i++ ) { + for( unsigned long frame=0; frame<framesPerBuffer; frame++ ) { // use iget inside - get_frame(buffer, synth, i); + get_frame(buffer, synth, frame); } + smooth_buffer(buffer, framesPerBuffer, 0.1f); + // output buffer for( unsigned long i=0; i<framesPerBuffer * 2; i += 2 ) { - // use iget inside *out++ = buffer[i]; *out++ = buffer[i+1]; } @@ -330,13 +445,6 @@ sound_gen(const void *inputBuffer, void *outputBuffer, return paContinue; } -void -m_init_synth(synth_t * synth) -{ - synth = (synth_t *)malloc(sizeof(synth_t)); - -} - synth_t * init_synth(void) { @@ -345,8 +453,9 @@ init_synth(void) synth->cci = 0; // CC(SYNTH, NAME, MIN, MAX, STEP, DEF) - CC(synth->cc_cutoff, "cutoff", 10, 22000, 30, 5000); - CC(synth->cc_resonance, "resonance", 1, 10, .02, 1); + 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_amp, "lfo_amp", 0, 1, .01f, 0); CC(synth->cc_pitch, "pitch", -3, 4, 0.01f, 1); @@ -359,7 +468,14 @@ init_synth(void) CC(synth->cc_del_feedback, "feedback", 0, 1, 0.01f, 0.5f); CC(synth->cc_gain, "gain", 0, 1, 0.01f, 0.5f); - //synth->modi = 0; + CC(synth->cc_f_adsr_a, "fattack", 0, 3, 0.01f, 0.00); + CC(synth->cc_f_adsr_peak, "fpeak", 0, 1, 0.01f, 1.00); + CC(synth->cc_f_adsr_d, "fdecay", 0, 2, 0.01f, 0.3); + CC(synth->cc_f_adsr_s, "fsustain", 0, 1.0f, 0.01f, 0.7f); + CC(synth->cc_f_adsr_r, "frelease", 0, 5, 0.01f, 0.2f); + + // synth->modi = 0; + synth->autogain = 1; synth->x = 1; @@ -370,6 +486,13 @@ init_synth(void) synth->adsr.r = 0.4; synth->adsr.elapsed = 0; + synth->f_adsr.a = 0.00001f; + synth->f_adsr.peak = 1.0f; + synth->f_adsr.d = 0.3; + synth->f_adsr.s = 0.7; + synth->f_adsr.r = 0.4; + synth->f_adsr.elapsed = 0; + synth->lfo.freq = 1.0f; synth->lfo.amp = 0.0f; synth->lfo.elapsed = 0; @@ -394,9 +517,12 @@ init_synth(void) synth->delay = 0; synth->del = (float *) calloc(sizeof(float), SAMPLE_RATE * 30); synth->deli = 0; - synth->counter; + synth->counter = 0; + synth->f_adsr_enabled = 0; synth->filter = 1; + synth->biquad = 0; + synth->biquad_type = 'l'; synth->clamp = 1; synth->gen[0] = gen0; @@ -414,9 +540,6 @@ init_synth(void) synth->fff = create_bw_low_pass_filter(2, SAMPLE_RATE, 400); synth->fff2 = create_bw_band_stop_filter(8, SAMPLE_RATE, 15000, 22000); - synth->sound_active = 0; - init_sound(synth, sound_gen); - synth->viz.rate_divider = 15; // 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); @@ -441,6 +564,16 @@ init_synth(void) synth->wvt_pos = 0; + + + synth->sound_active = 0; + synth->soundcard_id = get_soundcard_id("default"); + init_sound(synth, sound_gen, synth->soundcard_id); + + synth->midi = (midi_t *)malloc(sizeof(midi_t)); + synth->midi_device_id = get_midi_device_id("Midi Through Port-0"); + init_midi(synth->midi, synth); + return synth; } @@ -448,6 +581,7 @@ void free_synth(synth_t * synth) { destroy_sound(synth); + terminate_midi(synth->midi); free(synth->viz.wave_buffer_data); free(synth->viz.fft_buffer_data); @@ -456,6 +590,8 @@ free_synth(synth_t * synth) free(synth->viz.fft_input_buffer); free(synth->viz.fft_output_buffer); free(synth->viz.fft_smooth_buffer); + + free(synth->midi); free_bw_low_pass(synth->fff); free_bw_band_stop(synth->fff2); @@ -463,3 +599,169 @@ free_synth(synth_t * synth) free(synth->del); free(synth); } + +void +change_soundcard(synth_t *synth) +{ + destroy_sound(synth); + synth->sound_active = 0; + init_sound(synth, sound_gen, synth->soundcard_id); +} + +void +change_midi_device(synth_t *synth) +{ + terminate_midi(synth->midi); + free(synth->midi); + + synth->midi = (midi_t *)malloc(sizeof(midi_t)); + init_midi(synth->midi, synth); +} + +int +load_synth(synth_t *synth, const char *path) +{ + (void)path; + config_t cfg; + const char *str; + double FLOAT; + + config_init(&cfg); + + /* Read the file. If there is an error, report it and exit. */ + if(! config_read_file(&cfg, "TEST.cfg")) + { + fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg), + config_error_line(&cfg), config_error_text(&cfg)); + config_destroy(&cfg); + return(EXIT_FAILURE); + } + + /* Get the store name. */ + if(config_lookup_string(&cfg, "synth.name", &str)) + printf("LOADING: %s ----\n---\n", str); + else + fprintf(stderr, "No 'synth.name' setting in configuration file.\n"); + + config_lookup_int(&cfg, "synth.generator", &synth->geni); + + config_lookup_float(&cfg, "synth.adsr.a", &FLOAT); + synth->cc_adsr_a.target = FLOAT; + config_lookup_float(&cfg, "synth.adsr.peak", &FLOAT); + synth->cc_adsr_peak.target = FLOAT; + config_lookup_float(&cfg, "synth.adsr.d", &FLOAT); + synth->cc_adsr_d.target = FLOAT; + config_lookup_float(&cfg, "synth.adsr.s", &FLOAT); + 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.delay.enable", &synth->delay); + config_lookup_float(&cfg, "synth.delay.time", &FLOAT); + synth->cc_del_time.target = FLOAT; + config_lookup_float(&cfg, "synth.delay.feedback", &FLOAT); + synth->cc_del_feedback.target = FLOAT; + + config_lookup_int(&cfg, "synth.filter.enable", &synth->filter); + config_lookup_float(&cfg, "synth.filter.cutoff", &FLOAT); + synth->cc_cutoff.target = FLOAT; + config_lookup_float(&cfg, "synth.filter.resonance", &FLOAT); + synth->cc_resonance.target = FLOAT; + + config_lookup_float(&cfg, "synth.lfo.freq", &FLOAT); + 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.autogain", &synth->autogain); + config_lookup_float(&cfg, "synth.gain", &FLOAT); + synth->cc_gain.target = FLOAT; + + config_destroy(&cfg); + return(EXIT_SUCCESS); +} + +int +save_synth(synth_t *synth, const char *path) +{ + (void)path; + + static const char *output_file = "TEST.cfg"; + + config_t cfg; + config_setting_t *root, *setting, *group, *adsr, *delay, *lfo, *filter; + + config_init(&cfg); + root = config_root_setting(&cfg); + + /* Add some settings to the configuration. */ + 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"); + + setting = config_setting_add(group, "generator", CONFIG_TYPE_INT); + config_setting_set_int(setting, synth->geni); + + adsr = config_setting_add(group, "adsr", CONFIG_TYPE_GROUP); + setting = config_setting_add(adsr, "a", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_adsr_a.target); + setting = config_setting_add(adsr, "peak", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_adsr_peak.target); + setting = config_setting_add(adsr, "d", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_adsr_d.target); + setting = config_setting_add(adsr, "s", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_adsr_s.target); + setting = config_setting_add(adsr, "r", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_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); + setting = config_setting_add(delay, "time", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_del_time.target); + setting = config_setting_add(delay, "feedback", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_del_feedback.target); + + 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); + setting = config_setting_add(filter, "cutoff", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_cutoff.target); + setting = config_setting_add(filter, "resonance", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_resonance.target); + + lfo = config_setting_add(group, "lfo", CONFIG_TYPE_GROUP); + setting = config_setting_add(lfo, "freq", CONFIG_TYPE_FLOAT); + 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(group, "autogain", CONFIG_TYPE_INT); + config_setting_set_int(setting, synth->autogain); + setting = config_setting_add(group, "gain", CONFIG_TYPE_FLOAT); + config_setting_set_float(setting, synth->cc_gain.target); + + + /* array = config_setting_add(root, "numbers", CONFIG_TYPE_ARRAY); */ + + /* for(i = 0; i < 10; ++i) */ + /* { */ + /* setting = config_setting_add(array, NULL, CONFIG_TYPE_INT); */ + /* config_setting_set_int(setting, 10 * i); */ + /* } */ + + /* Write out the new configuration. */ + if(! config_write_file(&cfg, output_file)) + { + fprintf(stderr, "Error while writing file.\n"); + config_destroy(&cfg); + return(EXIT_FAILURE); + } + + fprintf(stderr, "New configuration successfully written to: %s\n", + output_file); + + config_destroy(&cfg); + return(EXIT_SUCCESS); +} diff --git a/src/synth_gui.c b/src/synth_gui.c index 85a5be2..6f8e06b 100644 --- a/src/synth_gui.c +++ b/src/synth_gui.c @@ -1,4 +1,5 @@ #include "synth_gui.h" +#include <portaudio.h> #define RAYGUI_IMPLEMENTATION #include "raygui.h" //#include "raylib.h" @@ -24,8 +25,9 @@ draw_text(synth_t * synth, int x, int y) DrawRectangleLines(x - 6, y - 3, 120, 3 + (count * offset) + 3, GRAY); } + void -mouse(synth_t *synth, PaStream *stream) +mouse(synth_t *synth) { float m = GetMouseWheelMove(); int x = 0; @@ -72,11 +74,11 @@ mouse(synth_t *synth, PaStream *stream) } void -keyboard(synth_t * synth, PaStream *stream) +keyboard(synth_t * synth) { int keys[] = {KEY_Q, KEY_TWO, KEY_W, KEY_THREE, KEY_E, KEY_R, KEY_FIVE, KEY_T, KEY_SIX, KEY_Y, KEY_SEVEN, KEY_U, KEY_I, KEY_NINE, KEY_O, KEY_ZERO, KEY_P, KEY_LEFT_BRACKET, KEY_EQUAL, KEY_RIGHT_BRACKET, 0}; - float note; + //float note; for (int i = 0; keys[i]; i++) { if (IsKeyPressed(keys[i])) { @@ -108,7 +110,7 @@ keyboard(synth_t * synth, PaStream *stream) if (IsKeyReleased(keys[i])) { synth->midi_note[i].noteOff = Pa_GetStreamTime(synth->stream); synth->midi_note[i].noteOffSample = synth->midi_note[i].elapsed; - note = notes[i % 12][(synth->octave + (i / 12)) % 8]; + //note = notes[i % 12][(synth->octave + (i / 12)) % 8]; } } @@ -244,19 +246,19 @@ draw_adsr(synth_t *synth, int x, int y, int width, int height) int x_prev = x; for (int i = 0; i < synth->midi_active_n; i++) { int rec_y = y + height - 1 + - 4.5 * floor( (WIDTH / 24) * + 4.5 * floor( (width / 24) * - fix_adsr(&synth->adsr, synth->midi_active[i]->noteOn, synth->midi_active[i]->noteOff, synth->midi_active[i]->elapsed, synth->midi_active[i]->noteOffSample)); - int rec_width = (WIDTH - x - x) / synth->midi_active_n; - int rec_height = HEIGHT - rec_y; + int rec_width = width / synth->midi_active_n; + int rec_height = rec_y; int red, green, blue; frequencyToColor(synth->midi_active[i]->freq * 25, &red, &green, &blue); - DrawRectangleGradientV(x_prev, rec_y - 10, rec_width, rec_height, ColorAlpha(BLUE, .2) ,ColorAlpha((Color) {red, green, blue}, .2)); + DrawRectangleGradientV(x_prev, rec_y - 10, rec_width, rec_height, ColorAlpha(BLUE, .2) ,ColorAlpha((Color) {red, green, blue, 1}, .2)); //DrawRectangleGradientV(x_prev, rec_y - 10, rec_width, rec_height, ColorAlpha(GREEN, .2) ,ColorAlpha(RED, .2)); //DrawRectangleLines(x_prev, rec_y - 10, rec_width, rec_height, GRAY); x_prev += rec_width; @@ -291,8 +293,8 @@ draw_wave(synth_t *synth, int x, int y, int width, int height) void draw_fft(synth_t *synth, int x, int y, int width, int height) { - int viz_size = width * synth->viz.rate_divider; - int fft_output_len = viz_size / 2 + 1; + size_t viz_size = width * synth->viz.rate_divider; + size_t fft_output_len = viz_size / 2 + 1; fftwf_complex* output = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fft_output_len); @@ -348,7 +350,7 @@ draw_fft(synth_t *synth, int x, int y, int width, int height) y + height, }; float thick = cell_width/3*sqrtf(t); - DrawLineEx(startPos, endPos, thick, BLUE); + DrawLineEx(startPos, endPos, thick, color); } fftwf_free(output); @@ -438,7 +440,7 @@ draw_adsr_graph(synth_t * synth, int x, int y, int width, int height) 0, // note Off (float)i/width * total_samples, 0); - } else if (i < a + d + s + r) { // draw release + } else if (r && i < a + d + s + r) { // draw release point = fix_adsr(&adsr, 0, // note On 1, // note Off @@ -453,7 +455,6 @@ draw_adsr_graph(synth_t * synth, int x, int y, int width, int height) DrawPixel(i + x , y + height - adsr_graph[i], RED); } - int off_idx = a + d + s; for (int i = 0; i < synth->midi_active_n; i++) { midi_note_t * note = synth->midi_active[i]; int elapsed_samples = note->elapsed; @@ -482,7 +483,7 @@ 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) / 2, y - 12, width / 2, 12 }, "", NULL, synth->viz.rate_divider , 1, 150); + synth->viz.rate_divider = GuiSlider((Rectangle){ x + (width / 2.0) / 2, y - 12, width / 2.0, 12 }, "", NULL, synth->viz.rate_divider , 1, 150); int viz_size = width * synth->viz.rate_divider; @@ -529,30 +530,33 @@ draw_signals(synth_t * synth, int x, int y, int width, int height) draw_adsr_graph(synth, x + 20, y + 20, width - 40, height - 40); } } + char * flag = NULL; +char * flag_circle = NULL; void draw_cc_circle(cc_t * cc, int x, int y, int width, int height) { //DrawRectangleLines(x, y, width, height, WHITE); if (CheckCollisionPointRec(GetMousePosition(), (Rectangle){x, y, width, height})) { if (IsMouseButtonPressed(0)) { - flag = cc->name; + flag_circle = cc->name; } } - if (IsMouseButtonDown(0) && flag == cc->name) { + if (IsMouseButtonDown(0) && flag_circle == cc->name) { Vector2 dx = GetMouseDelta(); int x = 1; if (IsKeyDown(KEY_LEFT_SHIFT)) { x = 3; + // test x that steps cc in 10 segments + x = (cc->max - cc->min) / (10 * cc->step); } if (dx.y < 0) cc_step(cc, 1*x); if (dx.y > 0) cc_step(cc, -1*x); } - if (IsMouseButtonReleased(0) && flag == cc->name) { - flag = 0; + if (IsMouseButtonReleased(0) && flag_circle == cc->name) { + flag_circle = 0; } - int min = 110; int max = 110 + (360+70 - 110) * (cc->target) / (cc->max - cc->min); @@ -572,7 +576,8 @@ 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 draw_cc_hbar(cc_t * cc, int x, int y, int width, int height) { char buf1[32], buf2[32]; @@ -586,18 +591,24 @@ 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->target = cc->min; + cc_set(cc, cc->min); } else if (p.x >= x + width) { - cc->target = cc->max; + //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->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)))); } + char tmp[128] = ""; + sprintf(tmp, "%f", cc->value); + ws_send_message(tmp); } if (IsMouseButtonReleased(0) && flag == cc->name) { flag = 0; } - int current = width * (cc->target - cc->min) / (cc->max - cc->min) + cc->min; + //int current = width * (cc->target - cc->min) / (cc->max - cc->min) + cc->min; int fill_width = map(cc->target, cc->min, cc->max, 0, width - 2); @@ -614,7 +625,7 @@ 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; + //int current = height * (cc->target - cc->min) / (cc->max - cc->min) + cc->min - 1; Vector2 p = GetMousePosition(); @@ -625,11 +636,14 @@ 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->target = cc->max; + cc_set(cc, cc->max); } else if (p.y >= y + height) { - cc->target = cc->min; + //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->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))))); } } if (IsMouseButtonReleased(0) && flag == cc->name) { @@ -654,16 +668,21 @@ draw_bars(synth_t * synth, int x, int y, int width, int height, int offset) { int count = 0; - draw_cc_hbar(&synth->cc_adsr_a, x, y + count++ * (height + offset), width, height); - draw_cc_hbar(&synth->cc_adsr_peak, x, y + count++ * (height + offset), width, height); - draw_cc_hbar(&synth->cc_adsr_d, x, y + count++ * (height + offset), width, height); - draw_cc_hbar(&synth->cc_adsr_s, x, y + count++ * (height + offset), width, height); - draw_cc_hbar(&synth->cc_adsr_r, x, y + count++ * (height + offset), width, height); - draw_cc_hbar(&synth->cc_gain, x, y + count++ * (height + offset), width, height); + draw_cc_hbar(&synth->cc_adsr_a , x, y + count++ * (height + offset), width, height); + synth->f_adsr_enabled = GuiCheckBox((Rectangle){ x + width + offset, y + (count - 1) * (height + offset), height, 16 }, "", synth->f_adsr_enabled); + draw_cc_hbar(&synth->cc_adsr_peak , x, y + count++ * (height + offset), width, height); + draw_cc_hbar(&synth->cc_adsr_d , x, y + count++ * (height + offset), width, height); + draw_cc_hbar(&synth->cc_adsr_s , x, y + count++ * (height + offset), width, height); + draw_cc_hbar(&synth->cc_adsr_r , x, y + count++ * (height + offset), width, height); + draw_cc_hbar(&synth->cc_gain, x, y + count++ * (height + offset), width, height); + synth->autogain = GuiCheckBox((Rectangle){ x + width + offset, y + (count - 1) * (height + offset), height, 16 }, "", synth->autogain); draw_cc_hbar(&synth->cc_del_feedback, x, y + count++ * (height + offset), width, height); - draw_cc_hbar(&synth->cc_del_time, x, y + count++ * (height + offset), width, height); + draw_cc_hbar(&synth->cc_del_time , x, y + count++ * (height + offset), width, height); } +#include "sound.h" +#include "midi.h" + void rayrun(synth_t *synth){ PaTime current_time = 0; @@ -671,18 +690,40 @@ rayrun(synth_t *synth){ osc_sound(0); + char *midi_devices = get_midi_devices(); + //char *midi_devices = "test1;test2;test3"; + char *soundcards = get_soundcards(); + int old_soundcard_id = synth->soundcard_id; + int old_midi_device_id = synth->midi_device_id; + InitWindow(WIDTH, HEIGHT, "Raylib synth"); SetTargetFPS(60); // Set our game to run at 60 frames-per-second while (!WindowShouldClose()) { - keyboard(synth, synth->stream); - mouse(synth, synth->stream); + keyboard(synth); + mouse(synth); // Draw //---------------------------------------------------------------------------------- BeginDrawing(); ClearBackground(BLACK); - draw_cc_circle(&synth->cc_pitch, 30, 250, 30, 30); + int fb = 0; + int foffset = 9; + draw_cc_circle(&synth->cc_adsr_a, 30 + (30 + foffset) * fb++, 180, 30, 30); + draw_cc_circle(&synth->cc_adsr_peak, 30 + (30 + foffset) * fb++, 180, 30, 30); + draw_cc_circle(&synth->cc_adsr_d, 30 + (30 + foffset) * fb++, 180, 30, 30); + draw_cc_circle(&synth->cc_adsr_s, 30 + (30 + foffset) * fb++, 180, 30, 30); + draw_cc_circle(&synth->cc_adsr_r, 30 + (30 + foffset) * fb++, 180, 30, 30); + + draw_cc_circle(&synth->cc_pitch, 30, 220, 30, 30); + + fb = 0; + draw_cc_circle(&synth->cc_f_adsr_a, 30 + (30 + foffset) * fb++, 260, 30, 30); + draw_cc_circle(&synth->cc_f_adsr_peak, 30 + (30 + foffset) * fb++, 260, 30, 30); + draw_cc_circle(&synth->cc_f_adsr_d, 30 + (30 + foffset) * fb++, 260, 30, 30); + draw_cc_circle(&synth->cc_f_adsr_s, 30 + (30 + foffset) * fb++, 260, 30, 30); + 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); draw_cc_vbar(&synth->cc_resonance, 330, 20, 24, 256); //draw_cc_vbar(&synth->cc_adsr_s, 30, 250, 30, 30); @@ -691,20 +732,32 @@ rayrun(synth_t *synth){ // GUI char buf[64]; snprintf(buf, sizeof buf, "%d", synth->wvt_pos); - synth->wvt_pos = GuiSlider((Rectangle){WIDTH / 2 - 108, 150 + 42 + 42, 216, 24 }, "", buf, synth->wvt_pos , 0, 127); + synth->wvt_pos = GuiSlider((Rectangle){WIDTH / 2.0 - 108, 150 + 42 + 42, 216, 24 }, "", buf, synth->wvt_pos , 0, 127); 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); - if ( GuiButton((Rectangle){ WIDTH / 2 - 108, 150 - 6 - 6 + 42, 216, 6 }, "")) { + 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 - 108, 150 + 42, 216, 24 }, "x", buf, synth->x , 0.0f, 2.0f); + 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 , 100, 24 }, "FILTER", synth->filter); + 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); + if (synth->biquad) { + if (GuiToggle((Rectangle){WIDTH - 100 - 50 - 100 - 50 , 50 + 24 + 6 + 24 + 6, 24, 24}, "l", synth->biquad_type == 'l')) { + synth->biquad_type = 'l'; + } + if (GuiToggle((Rectangle){WIDTH - 100 - 50 - 100 - 50 + 24 + 8, 50 + 24 + 6 + 24 + 6, 24, 24}, "b", synth->biquad_type == 'b')) { + synth->biquad_type = 'b'; + } + if (GuiToggle((Rectangle){WIDTH - 100 - 50 - 100 - 50 + 24 + 8 + 24 + 8 , 50 + 24 + 6 + 24 + 6, 24, 24}, "h", synth->biquad_type == 'h')) { + synth->biquad_type = 'h'; + } + } GuiSpinner((Rectangle){ WIDTH - 100 - 50 , 50, 100, 24 }, "oct: ", &(synth->octave), 0, 7, 0); snprintf(buf, sizeof buf, "generator %d --> ", synth->geni); @@ -712,6 +765,17 @@ rayrun(synth_t *synth){ synth->clamp = GuiToggle((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6, 100, 24 }, "clamp", synth->clamp); synth->delay = GuiToggle((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24 }, "delay", synth->delay); + + 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 + 24 + 6, 100, 24 }, "LOAD!")) { + load_synth(synth, "asdas"); + } + + static int edit_midi = 0; + static int edit_sound = 0; // signals draw_signals(synth, 20, 390, WIDTH - 2*20, 200); @@ -722,6 +786,34 @@ rayrun(synth_t *synth){ /* snprintf(buf, sizeof buf, "stream time: %f", Pa_GetStreamTime(synth->stream)); */ /* DrawText(buf, WIDTH / 2 -300, HEIGHT - 300, 11, LIGHTGRAY); */ + // midi dev + if (GuiDropdownBox( + (Rectangle){WIDTH - 200 - 50, + 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 , 200, + 24}, + midi_devices, &synth->midi_device_id, edit_midi)) { + edit_midi = !edit_midi; + } + + if (old_midi_device_id != synth->midi_device_id) { + old_midi_device_id = synth->midi_device_id; + change_midi_device(synth); + } + + // audio dev + if (GuiDropdownBox( + (Rectangle){WIDTH - 300 - 50, + 12, 300, + 24}, + soundcards, &synth->soundcard_id, edit_sound)) { + edit_sound = !edit_sound; + } + + if (old_soundcard_id != synth->soundcard_id) { + old_soundcard_id = synth->soundcard_id; + change_soundcard(synth); + } + EndDrawing(); //---------------------------------------------------------------------------------- @@ -729,6 +821,9 @@ rayrun(synth_t *synth){ //printf("%f :: %ld\n", current_time - prev_time, phase); prev_time = current_time; + (void)prev_time; } + free(soundcards); + free(midi_devices); CloseWindow(); } diff --git a/src/synth_math.h b/src/synth_math.h index 2dd9f83..029ed74 100644 --- a/src/synth_math.h +++ b/src/synth_math.h @@ -3,19 +3,23 @@ #include <stddef.h> -static long long +// do I really want inline? used to be static +inline long long gcd(long long a, long long b) { - long long rem; - rem=a-(a/b*b); - if(rem==0) - return b; - else - gcd(b,rem); + long long rem; + rem=a-(a/b*b); + if(rem==0) + return b; + else + gcd(b, rem); + + // unreachable + return 0; } // Function to return LCM of two numbers -static long long +inline long long lcm(long long a, long long b) { return (a / gcd(a, b)) * b; @@ -30,7 +34,7 @@ clamp(float f, int min, int max) } /* 1d convolution */ -static void +inline void convole(float *signal, float *filter, size_t signal_size, size_t filter_size, float *out) { for (size_t i = 0; i < filter_size + signal_size; i++) { diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..f5648b9 --- /dev/null +++ b/src/types.h @@ -0,0 +1,38 @@ +#ifndef SYNTH_TYPES_H +#define SYNTH_TYPES_H + +typedef struct { + void * data; + int size; + int cap; +} list_t; + +list_t a[128]; + +/* void */ +/* ll_add(llist * l, void * data) { */ +/* llist * tmp = l; */ +/* int i = 0; */ +/* do { */ +/* if (!tmp->data) { */ +/* break; */ +/* } */ +/* i++; */ +/* tmp = l->next; */ +/* } while (d); */ +/* tmp->data = data; */ +/* tmp->id = i; */ +/* } */ + +/* + I need a list + - O(1) length calculation + - O(1) access for each element + + + + + + */ + +#endif /* SYNTH_TYPES_H */ diff --git a/src/web.c b/src/web.c new file mode 100644 index 0000000..e23d8b2 --- /dev/null +++ b/src/web.c @@ -0,0 +1,257 @@ +#include "web.h" + +#include <libwebsockets.h> +#include <pthread.h> +#include <microhttpd.h> +#include <unistd.h> + + +#define HTTP_PORT 9966 +#define WS_PORT 9967 + +static struct lws *client_wsi = NULL; +synth_t * synthx; +struct MHD_Daemon *server; +pthread_t server_thread; +#define BUFFER_SIZE 1024 * 4 +char message_buffer[BUFFER_SIZE]; + +// Function to handle slider changes +void handle_slider_change(void *cls, int value) { + synth_t *synth = (synth_t*)cls; + printf("Slider changed: %d\n", value); + cc_set(&synth->cc_cutoff, value); +} + +void handle_button_click(void *cls) { + printf("Button clicked!!!"); + synth_t *synth = (synth_t*)cls; + + int i = 5; + //printf("Note On : %s[%d] %fHz\n", int_to_note(i % 12), (synth->octave + (i / 12)) % 8, note); + synth->midi_note[i].freq = 16.35160 * pow(2, (synth->octave + i / 12.0)); + //synth->midi_note[i].freq = notes[i % 12][(synth->octave + (i / 12)) % 8]; + synth->midi_note[i].channel = -1; + synth->midi_note[i].noteOn = Pa_GetStreamTime(synth->stream); + synth->midi_note[i].noteOff = 0; + synth->midi_note[i].velocity = 1.0; + synth->midi_note[i].elapsed = 0; + synth->midi_note[i].active = 1; + int flag = 1; + for (int j = 0; j < synth->midi_active_n; j++) { + if (synth->midi_active[j] == &synth->midi_note[i]) { + flag = 0; + } + } + if (flag) { + synth->midi_active[synth->midi_active_n++] = &synth->midi_note[i]; + } + + //synth->adsr.elapsed = 0; + synth->active = 1; + + usleep(500000); + + synth->midi_note[i].noteOff = Pa_GetStreamTime(synth->stream); + synth->midi_note[i].noteOffSample = synth->midi_note[i].elapsed; + +} + + +const char *html_header = + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Content-Length: %d\r\n" + "\r\n" + "\r\n"; + +// HTML page with a slider +const char *html_content = + "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + "<title>C SYNTH WEB!</title>\n" + "</head>\n" + "<body>\n" + "<input id='slider' style='width: 100%; height: 200px;' type='range' min='1' max='22000' />\n" + "<button onclick='onButtonClick()'>Trigger</button>\n" + "<button id='but'>ws</button>\n" + "<script>\n" + "const ws = new WebSocket('ws://10.0.0.10:9967');\n" + "const slider = document.getElementById('slider');\n" + "const but = document.getElementById('but');\n" + "\n" + "slider.oninput = function() { ws.send(slider.value); };\n" + "\n" + "but.onclick = function() { ws.send('THIS IS A TEST DONT KILL ME'); };\n" + "\n" + "ws.onmessage = function(event) {\n" + " console.log('Message from server: ' + event.data);\n" + " slider.value = parseInt(event.data);" + "};\n" + "ws.onopen = function() {\n" + " console.log('Connected to WebSocket server');\n" + "};\n" + "ws.onerror = function(error) {\n" + " console.error('WebSocket error: ' + error);\n" + "};\n" + "</script>\n" + "</body>\n" + "</html>\n"; + +// HTTP request handler +enum MHD_Result handle_request(void *cls, struct MHD_Connection *connection, + const char *url, const char *method, + const char *version, const char *upload_data, + size_t *upload_data_size, void **con_cls) { + struct MHD_Response *response; + enum MHD_Result ret; + + if (strcmp(url, "/slider") == 0 && strcmp(method, "GET") == 0) { + const char *value_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "value"); + if (value_str) { + int value = atoi(value_str); + handle_slider_change(cls, value); + } + response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + return ret; + } + + if (strcmp(url, "/button") == 0 && strcmp(method, "GET") == 0) { + handle_button_click(cls); + response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + return ret; + } + + response = MHD_create_response_from_buffer(strlen(html_content), (void *)html_content, MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + return ret; +} + +// Callback to handle WebSocket events +static int callback_ws(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) { + char buf[10000] = ""; + char tmp[10000] = ""; + switch (reason) { + case LWS_CALLBACK_ESTABLISHED: { // When a connection is established + client_wsi = wsi; // Store the WebSocket connection for later use + printf("WebSocket connection established with client: %p\n", wsi); + /* const char *msg = "Hello, Client!"; */ + /* printf("Sending message: %s\n", msg); */ + /* int n = lws_write(wsi, (unsigned char *)msg, strlen(msg), LWS_WRITE_TEXT); */ + /* if (n < 0) { */ + /* printf("Error sending message, error code: %d\n", n); */ + /* } */ + break; + } + case LWS_CALLBACK_RECEIVE: { // When a message is received + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%.*s", (int)len, (char *)in); + printf("Received slider value: %s\n", buffer); + //lws_write(wsi, (unsigned char *)in, len, LWS_WRITE_TEXT); + int value = atoi(buffer); + handle_slider_change(synthx, value); + break; + } + case LWS_CALLBACK_SERVER_WRITEABLE: { + printf("\nLWS_CALLBACK_SERVER_WRITEABLE\n\n"); + /* size_t msg_len = strlen(message_buffer); */ + /* unsigned char buffer[LWS_PRE + BUFFER_SIZE]; */ + /* memcpy(&buffer[LWS_PRE], message_buffer, msg_len); */ + /* lws_write(wsi, (unsigned char *)buffer, strlen(buffer), LWS_WRITE_TEXT); */ + break; + } + case LWS_CALLBACK_HTTP: { + snprintf(tmp, sizeof(tmp), html_header, strlen(html_content)); + strcpy(buf, tmp); + printf("\nHTTP?!\n\n%s\n", tmp); + strcat(buf, html_content); + lws_write(wsi, (unsigned char *)buf, strlen(buf), LWS_WRITE_HTTP); + return -1; + break; + } + case LWS_CALLBACK_CLOSED: { + printf("WebSocket connection closed with client: %p\n", wsi); + } + default: + break; + } + return 0; +} + +// Thread function to run the WebSocket server +void *websocket_server_thread(void *arg) { + struct lws_context_creation_info info; + struct lws_context *context; + struct lws_protocols protocols[] = { + //{ "http-only", callback_http, 0, 0 }, + { "ws", callback_ws, 0, 128 }, + { NULL, NULL, 0, 0 } // Terminator + }; + + memset(&info, 0, sizeof(info)); + info.port = WS_PORT; + //info.user = arg; + info.protocols = protocols; + info.pt_serv_buf_size = 32 * 1024; + info.options = LWS_SERVER_OPTION_VALIDATE_UTF8; + + context = lws_create_context(&info); + if (!context) { + fprintf(stderr, "lws_create_context failed\n"); + return NULL; + } + + printf("WebSocket server running on ws://localhost:%d\n", WS_PORT); + + while (1) { + lws_service(context, 1000); // Service WebSocket events + } + + lws_context_destroy(context); + return NULL; +} + +void +init_web(synth_t * synth) +{ + synthx = synth; + // Start the HTTP server + server = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, HTTP_PORT, NULL, NULL, &handle_request, (void *)synth, MHD_OPTION_END); + if (server == NULL) { + fprintf(stderr, "Failed to start server\n"); + return; + } + + // Create a new thread for the WebSocket server + if (pthread_create(&server_thread, NULL, websocket_server_thread, NULL) != 0) { + fprintf(stderr, "Failed to create server thread\n"); + return; + } +} + +void +free_web() +{ + MHD_stop_daemon(server); +} + +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); + } +} diff --git a/src/web.h b/src/web.h new file mode 100644 index 0000000..8c5edd4 --- /dev/null +++ b/src/web.h @@ -0,0 +1,12 @@ +#ifndef WEB_H +#define WEB_H + +#include "synth_engine.h" + +void init_web(synth_t * synth); +void free_web(); +void ws_send_message(const char *message); + + + +#endif /* WEB_H */ |