summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am60
-rw-r--r--src/adsr.c2
-rw-r--r--src/archive/wavetable.c2
-rw-r--r--src/b.h898
-rw-r--r--src/biquad_filter.c71
-rw-r--r--src/control.c16
-rw-r--r--src/control.h8
-rw-r--r--src/filter.c1
-rw-r--r--src/gen.h17
-rw-r--r--src/lowpass.c1
-rw-r--r--src/midi.c57
-rw-r--r--src/midi.h2
-rw-r--r--src/notes.h3
-rw-r--r--src/osc.c5
-rw-r--r--src/osc_digisaw.c1
-rw-r--r--src/osc_sound.c14
-rw-r--r--src/osc_tri.c2
-rw-r--r--src/oscillator.h22
-rw-r--r--src/raygui.h8
-rw-r--r--src/sound.c85
-rw-r--r--src/sound.h5
-rw-r--r--src/stats.h10
-rw-r--r--src/synth.c24
-rw-r--r--src/synth_engine.h35
-rw-r--r--src/synth_engine_v2.c378
-rw-r--r--src/synth_gui.c179
-rw-r--r--src/synth_math.h22
-rw-r--r--src/types.h38
-rw-r--r--src/web.c257
-rw-r--r--src/web.h12
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)
diff --git a/src/adsr.c b/src/adsr.c
index 439d7b1..1ba5b38 100644
--- a/src/adsr.c
+++ b/src/adsr.c
@@ -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");
}
diff --git a/src/b.h b/src/b.h
new file mode 100644
index 0000000..0b3bf2e
--- /dev/null
+++ b/src/b.h
@@ -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);
diff --git a/src/midi.c b/src/midi.c
index 389a670..5a556e9 100644
--- a/src/midi.c
+++ b/src/midi.c
@@ -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)
{
diff --git a/src/midi.h b/src/midi.h
index fd9b749..004e824 100644
--- a/src/midi.h
+++ b/src/midi.h
@@ -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) {
diff --git a/src/osc.c b/src/osc.c
index c3c5d48..16e6894 100644
--- a/src/osc.c
+++ b/src/osc.c
@@ -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 */