From 500b9a07b93d6cd3e771edc5698e06d163da60f1 Mon Sep 17 00:00:00 2001 From: grm Date: Sat, 22 Feb 2025 02:36:27 +0200 Subject: a year of changes (web, better soundcard handling, biquad) --- b.c | 185 ++++++++++ build-aux/compile | 348 ------------------- build-aux/depcomp | 791 ------------------------------------------ build-aux/install-sh | 541 ----------------------------- build-aux/missing | 215 ------------ configure.ac | 54 --- src/Makefile.am | 60 ---- src/adsr.c | 2 +- src/archive/wavetable.c | 2 +- src/b.h | 898 ++++++++++++++++++++++++++++++++++++++++++++++++ src/biquad_filter.c | 71 ++++ src/control.c | 16 + src/control.h | 8 +- src/filter.c | 1 + src/gen.h | 17 + src/lowpass.c | 1 + src/midi.c | 57 ++- src/midi.h | 2 + src/notes.h | 3 +- src/osc.c | 5 +- src/osc_digisaw.c | 1 + src/osc_sound.c | 14 +- src/osc_tri.c | 2 +- src/oscillator.h | 22 ++ src/raygui.h | 8 + src/sound.c | 85 ++++- src/sound.h | 5 +- src/stats.h | 10 + src/synth.c | 24 +- src/synth_engine.h | 35 +- src/synth_engine_v2.c | 378 ++++++++++++++++++-- src/synth_gui.c | 179 +++++++--- src/synth_math.h | 22 +- src/types.h | 38 ++ src/web.c | 257 ++++++++++++++ src/web.h | 12 + 36 files changed, 2214 insertions(+), 2155 deletions(-) create mode 100644 b.c delete mode 100755 build-aux/compile delete mode 100755 build-aux/depcomp delete mode 100755 build-aux/install-sh delete mode 100755 build-aux/missing delete mode 100644 configure.ac delete mode 100644 src/Makefile.am create mode 100644 src/b.h create mode 100644 src/biquad_filter.c create mode 100644 src/gen.h create mode 100644 src/oscillator.h create mode 100644 src/stats.h create mode 100644 src/types.h create mode 100644 src/web.c create mode 100644 src/web.h diff --git a/b.c b/b.c new file mode 100644 index 0000000..8eae71f --- /dev/null +++ b/b.c @@ -0,0 +1,185 @@ +#define B_IMPLEMENTATION +#include "src/b.h" +#include + +#define BUILD_DIR "build/" + +int debug_level = 5; + +void +debug_or_release(B_Cmd* cmd) +{ + if (debug_level == 0) + b_cmd_append(cmd, "-O3", "-s", "-DNDEBUG"); + else if (debug_level == 1) + b_cmd_append(cmd, "-O2", "-ggdb", "-DVKDEBUG"); + else if (debug_level == 2) + b_cmd_append(cmd, "-O1", "-ggdb", "-DVKDEBUG"); + else { + //b_cmd_append(cmd, "-O0", "-ggdb", "-DVKDEBUG", "-pg"); + b_cmd_append(cmd, "-O0", "-ggdb", "-DVKDEBUG"); + b_cmd_append(cmd, "-fsanitize=address"); + } +} + +void +inlcude_dirs(B_Cmd* cmd) +{ + b_cmd_append(cmd, "-I./src/"); +} + +void +cflags(B_Cmd* cmd) +{ + b_cmd_append(cmd, "-Wall", "-Wextra"); + + debug_or_release(cmd); + + b_cmd_append(cmd, "-march=native"); + b_cmd_append(cmd, "-fno-math-errno", "-funroll-loops"); + b_cmd_append(cmd, "-flto", "-pthread"); + b_cmd_append(cmd, "-lportaudio", "-lrt", "-lm", "-lasound", "-lraylib", "-lportmidi", "-ljack", "-lfftw3f", "-lsndfile", "-lconfig", "-lmicrohttpd", "-lpthread", "-lwebsockets"); + + inlcude_dirs(cmd); +} + +void cxxflags(B_Cmd *cmd) +{ + b_cmd_append(cmd, "-Wall", "-Wextra"); + b_cmd_append(cmd, "-Wno-string-plus-int", "-Wno-nullability-completeness", "-Wno-unused-function", "-Wno-missing-field-initializers", "-Wno-unused-parameter", "-Wno-unused-variable"); + + debug_or_release(cmd); + + b_cmd_append(cmd, "-march=native"); + b_cmd_append(cmd, "-fno-math-errno", "-funroll-loops"); + b_cmd_append(cmd, "-flto", "-pthread"); + + inlcude_dirs(cmd); + //b_cmd_append(cmd, "-O3"); +} + +void cxx(B_Cmd *cmd) +{ + b_cmd_append(cmd, "clang"); + cxxflags(cmd); +} + +void cc(B_Cmd *cmd) +{ + b_cmd_append(cmd, "clang"); + cflags(cmd); +} + +void libs(B_Cmd *cmd) +{ + b_cmd_append(cmd, "-lSDL2", "-lm", "-lvulkan", "-lshaderc_shared", "-lstdc++"); +} + +bool +build_c(bool force, + B_Cmd* cmd, + const char** input_paths, + size_t input_paths_len, + const char** dep_paths, + size_t dep_paths_len, + const char* output_path) +{ + int rebuild_is_needed = + b_needs_rebuild(output_path, input_paths, input_paths_len); + + int dep_rebuild = 0; + if (rebuild_is_needed == 0) + dep_rebuild = b_needs_rebuild(output_path, dep_paths, dep_paths_len); + + if (rebuild_is_needed < 0 || dep_rebuild < 0) return false; + + if (force || rebuild_is_needed || dep_rebuild) { + cmd->count = 0; + cc(cmd); + b_cmd_append(cmd, "-o", output_path); + b_da_append_many(cmd, input_paths, input_paths_len); + libs(cmd); + return b_cmd_run_sync(*cmd); + } + + b_log(B_INFO, "%s is up-to-date", output_path); + return true; +} + +int +main(int argc, char *argv[]) +{ + B_GO_REBUILD_URSELF(argc, argv); + + const char *program_name = b_shift_args(&argc, &argv); + + bool force = false; + + while (argc > 0) { + const char *flag = b_shift_args(&argc, &argv); + if (strcmp(flag, "-f") == 0) { + force = true; + } else { + b_log(B_ERROR, "Unknown flag `%s`", flag); + return 1; + } + } + + const char *synth_deps[] = { + "src/adsr.h", + "src/b.h", + "src/control.h", + "src/filter.h", + "src/generator.h", + "src/gen.h", + "src/lowpass.h", + "src/midi.h", + "src/notes.h", + "src/osc.h", + "src/pa_memorybarrier.h", + "src/pa_ringbuffer.h", + "src/raygui.h", + "src/sound.h", + "src/stats.h", + "src/synth_common.h", + "src/synth_engine.h", + "src/synth_gui.h", + "src/synth_math.h", + "src/types.h", + "src/web.h", + }; + + const char* synth_paths[] = { + "src/adsr.c", + "src/control.c", + "src/filter.c", +// "src/generator.c", +// "src/gtk.c", + "src/lowpass.c", + "src/midi.c", + "src/osc.c", + "src/osc_digisaw.c", + "src/osc_saw.c", + "src/osc_sin.c", + "src/osc_sound.c", + "src/osc_sqr.c", + "src/osc_tri.c", + "src/osc_weird.c", + "src/pa_ringbuffer.c", + "src/sound.c", + "src/synth.c", + "src/synth_engine_v2.c", + "src/synth_gui.c", + "src/web.c", + }; + + B_Cmd cmd = {0}; + + b_mkdir_if_not_exists(BUILD_DIR); + + if (!build_c(force, &cmd, synth_paths, B_ARRAY_LEN(synth_paths), synth_deps, + B_ARRAY_LEN(synth_deps), BUILD_DIR "synth")) + return 1; + + return 0; +} diff --git a/build-aux/compile b/build-aux/compile deleted file mode 100755 index df363c8..0000000 --- a/build-aux/compile +++ /dev/null @@ -1,348 +0,0 @@ -#! /bin/sh -# Wrapper for compilers which do not understand '-c -o'. - -scriptversion=2018-03-07.03; # UTC - -# Copyright (C) 1999-2021 Free Software Foundation, Inc. -# Written by Tom Tromey . -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# This file is maintained in Automake, please report -# bugs to or send patches to -# . - -nl=' -' - -# We need space, tab and new line, in precisely that order. Quoting is -# there to prevent tools from complaining about whitespace usage. -IFS=" "" $nl" - -file_conv= - -# func_file_conv build_file lazy -# Convert a $build file to $host form and store it in $file -# Currently only supports Windows hosts. If the determined conversion -# type is listed in (the comma separated) LAZY, no conversion will -# take place. -func_file_conv () -{ - file=$1 - case $file in - / | /[!/]*) # absolute file, and not a UNC file - if test -z "$file_conv"; then - # lazily determine how to convert abs files - case `uname -s` in - MINGW*) - file_conv=mingw - ;; - CYGWIN* | MSYS*) - file_conv=cygwin - ;; - *) - file_conv=wine - ;; - esac - fi - case $file_conv/,$2, in - *,$file_conv,*) - ;; - mingw/*) - file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` - ;; - cygwin/* | msys/*) - file=`cygpath -m "$file" || echo "$file"` - ;; - wine/*) - file=`winepath -w "$file" || echo "$file"` - ;; - esac - ;; - esac -} - -# func_cl_dashL linkdir -# Make cl look for libraries in LINKDIR -func_cl_dashL () -{ - func_file_conv "$1" - if test -z "$lib_path"; then - lib_path=$file - else - lib_path="$lib_path;$file" - fi - linker_opts="$linker_opts -LIBPATH:$file" -} - -# func_cl_dashl library -# Do a library search-path lookup for cl -func_cl_dashl () -{ - lib=$1 - found=no - save_IFS=$IFS - IFS=';' - for dir in $lib_path $LIB - do - IFS=$save_IFS - if $shared && test -f "$dir/$lib.dll.lib"; then - found=yes - lib=$dir/$lib.dll.lib - break - fi - if test -f "$dir/$lib.lib"; then - found=yes - lib=$dir/$lib.lib - break - fi - if test -f "$dir/lib$lib.a"; then - found=yes - lib=$dir/lib$lib.a - break - fi - done - IFS=$save_IFS - - if test "$found" != yes; then - lib=$lib.lib - fi -} - -# func_cl_wrapper cl arg... -# Adjust compile command to suit cl -func_cl_wrapper () -{ - # Assume a capable shell - lib_path= - shared=: - linker_opts= - for arg - do - if test -n "$eat"; then - eat= - else - case $1 in - -o) - # configure might choose to run compile as 'compile cc -o foo foo.c'. - eat=1 - case $2 in - *.o | *.[oO][bB][jJ]) - func_file_conv "$2" - set x "$@" -Fo"$file" - shift - ;; - *) - func_file_conv "$2" - set x "$@" -Fe"$file" - shift - ;; - esac - ;; - -I) - eat=1 - func_file_conv "$2" mingw - set x "$@" -I"$file" - shift - ;; - -I*) - func_file_conv "${1#-I}" mingw - set x "$@" -I"$file" - shift - ;; - -l) - eat=1 - func_cl_dashl "$2" - set x "$@" "$lib" - shift - ;; - -l*) - func_cl_dashl "${1#-l}" - set x "$@" "$lib" - shift - ;; - -L) - eat=1 - func_cl_dashL "$2" - ;; - -L*) - func_cl_dashL "${1#-L}" - ;; - -static) - shared=false - ;; - -Wl,*) - arg=${1#-Wl,} - save_ifs="$IFS"; IFS=',' - for flag in $arg; do - IFS="$save_ifs" - linker_opts="$linker_opts $flag" - done - IFS="$save_ifs" - ;; - -Xlinker) - eat=1 - linker_opts="$linker_opts $2" - ;; - -*) - set x "$@" "$1" - shift - ;; - *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) - func_file_conv "$1" - set x "$@" -Tp"$file" - shift - ;; - *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) - func_file_conv "$1" mingw - set x "$@" "$file" - shift - ;; - *) - set x "$@" "$1" - shift - ;; - esac - fi - shift - done - if test -n "$linker_opts"; then - linker_opts="-link$linker_opts" - fi - exec "$@" $linker_opts - exit 1 -} - -eat= - -case $1 in - '') - echo "$0: No command. Try '$0 --help' for more information." 1>&2 - exit 1; - ;; - -h | --h*) - cat <<\EOF -Usage: compile [--help] [--version] PROGRAM [ARGS] - -Wrapper for compilers which do not understand '-c -o'. -Remove '-o dest.o' from ARGS, run PROGRAM with the remaining -arguments, and rename the output as expected. - -If you are trying to build a whole package this is not the -right script to run: please start by reading the file 'INSTALL'. - -Report bugs to . -EOF - exit $? - ;; - -v | --v*) - echo "compile $scriptversion" - exit $? - ;; - cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \ - icl | *[/\\]icl | icl.exe | *[/\\]icl.exe ) - func_cl_wrapper "$@" # Doesn't return... - ;; -esac - -ofile= -cfile= - -for arg -do - if test -n "$eat"; then - eat= - else - case $1 in - -o) - # configure might choose to run compile as 'compile cc -o foo foo.c'. - # So we strip '-o arg' only if arg is an object. - eat=1 - case $2 in - *.o | *.obj) - ofile=$2 - ;; - *) - set x "$@" -o "$2" - shift - ;; - esac - ;; - *.c) - cfile=$1 - set x "$@" "$1" - shift - ;; - *) - set x "$@" "$1" - shift - ;; - esac - fi - shift -done - -if test -z "$ofile" || test -z "$cfile"; then - # If no '-o' option was seen then we might have been invoked from a - # pattern rule where we don't need one. That is ok -- this is a - # normal compilation that the losing compiler can handle. If no - # '.c' file was seen then we are probably linking. That is also - # ok. - exec "$@" -fi - -# Name of file we expect compiler to create. -cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` - -# Create the lock directory. -# Note: use '[/\\:.-]' here to ensure that we don't use the same name -# that we are using for the .o file. Also, base the name on the expected -# object file name, since that is what matters with a parallel build. -lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d -while true; do - if mkdir "$lockdir" >/dev/null 2>&1; then - break - fi - sleep 1 -done -# FIXME: race condition here if user kills between mkdir and trap. -trap "rmdir '$lockdir'; exit 1" 1 2 15 - -# Run the compile. -"$@" -ret=$? - -if test -f "$cofile"; then - test "$cofile" = "$ofile" || mv "$cofile" "$ofile" -elif test -f "${cofile}bj"; then - test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" -fi - -rmdir "$lockdir" -exit $ret - -# Local Variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC0" -# time-stamp-end: "; # UTC" -# End: diff --git a/build-aux/depcomp b/build-aux/depcomp deleted file mode 100755 index 715e343..0000000 --- a/build-aux/depcomp +++ /dev/null @@ -1,791 +0,0 @@ -#! /bin/sh -# depcomp - compile a program generating dependencies as side-effects - -scriptversion=2018-03-07.03; # UTC - -# Copyright (C) 1999-2021 Free Software Foundation, Inc. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# Originally written by Alexandre Oliva . - -case $1 in - '') - echo "$0: No command. Try '$0 --help' for more information." 1>&2 - exit 1; - ;; - -h | --h*) - cat <<\EOF -Usage: depcomp [--help] [--version] PROGRAM [ARGS] - -Run PROGRAMS ARGS to compile a file, generating dependencies -as side-effects. - -Environment variables: - depmode Dependency tracking mode. - source Source file read by 'PROGRAMS ARGS'. - object Object file output by 'PROGRAMS ARGS'. - DEPDIR directory where to store dependencies. - depfile Dependency file to output. - tmpdepfile Temporary file to use when outputting dependencies. - libtool Whether libtool is used (yes/no). - -Report bugs to . -EOF - exit $? - ;; - -v | --v*) - echo "depcomp $scriptversion" - exit $? - ;; -esac - -# Get the directory component of the given path, and save it in the -# global variables '$dir'. Note that this directory component will -# be either empty or ending with a '/' character. This is deliberate. -set_dir_from () -{ - case $1 in - */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; - *) dir=;; - esac -} - -# Get the suffix-stripped basename of the given path, and save it the -# global variable '$base'. -set_base_from () -{ - base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` -} - -# If no dependency file was actually created by the compiler invocation, -# we still have to create a dummy depfile, to avoid errors with the -# Makefile "include basename.Plo" scheme. -make_dummy_depfile () -{ - echo "#dummy" > "$depfile" -} - -# Factor out some common post-processing of the generated depfile. -# Requires the auxiliary global variable '$tmpdepfile' to be set. -aix_post_process_depfile () -{ - # If the compiler actually managed to produce a dependency file, - # post-process it. - if test -f "$tmpdepfile"; then - # Each line is of the form 'foo.o: dependency.h'. - # Do two passes, one to just change these to - # $object: dependency.h - # and one to simply output - # dependency.h: - # which is needed to avoid the deleted-header problem. - { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" - sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" - } > "$depfile" - rm -f "$tmpdepfile" - else - make_dummy_depfile - fi -} - -# A tabulation character. -tab=' ' -# A newline character. -nl=' -' -# Character ranges might be problematic outside the C locale. -# These definitions help. -upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ -lower=abcdefghijklmnopqrstuvwxyz -digits=0123456789 -alpha=${upper}${lower} - -if test -z "$depmode" || test -z "$source" || test -z "$object"; then - echo "depcomp: Variables source, object and depmode must be set" 1>&2 - exit 1 -fi - -# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. -depfile=${depfile-`echo "$object" | - sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} -tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} - -rm -f "$tmpdepfile" - -# Avoid interferences from the environment. -gccflag= dashmflag= - -# Some modes work just like other modes, but use different flags. We -# parameterize here, but still list the modes in the big case below, -# to make depend.m4 easier to write. Note that we *cannot* use a case -# here, because this file can only contain one case statement. -if test "$depmode" = hp; then - # HP compiler uses -M and no extra arg. - gccflag=-M - depmode=gcc -fi - -if test "$depmode" = dashXmstdout; then - # This is just like dashmstdout with a different argument. - dashmflag=-xM - depmode=dashmstdout -fi - -cygpath_u="cygpath -u -f -" -if test "$depmode" = msvcmsys; then - # This is just like msvisualcpp but w/o cygpath translation. - # Just convert the backslash-escaped backslashes to single forward - # slashes to satisfy depend.m4 - cygpath_u='sed s,\\\\,/,g' - depmode=msvisualcpp -fi - -if test "$depmode" = msvc7msys; then - # This is just like msvc7 but w/o cygpath translation. - # Just convert the backslash-escaped backslashes to single forward - # slashes to satisfy depend.m4 - cygpath_u='sed s,\\\\,/,g' - depmode=msvc7 -fi - -if test "$depmode" = xlc; then - # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. - gccflag=-qmakedep=gcc,-MF - depmode=gcc -fi - -case "$depmode" in -gcc3) -## gcc 3 implements dependency tracking that does exactly what -## we want. Yay! Note: for some reason libtool 1.4 doesn't like -## it if -MD -MP comes after the -MF stuff. Hmm. -## Unfortunately, FreeBSD c89 acceptance of flags depends upon -## the command line argument order; so add the flags where they -## appear in depend2.am. Note that the slowdown incurred here -## affects only configure: in makefiles, %FASTDEP% shortcuts this. - for arg - do - case $arg in - -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; - *) set fnord "$@" "$arg" ;; - esac - shift # fnord - shift # $arg - done - "$@" - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - mv "$tmpdepfile" "$depfile" - ;; - -gcc) -## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. -## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. -## (see the conditional assignment to $gccflag above). -## There are various ways to get dependency output from gcc. Here's -## why we pick this rather obscure method: -## - Don't want to use -MD because we'd like the dependencies to end -## up in a subdir. Having to rename by hand is ugly. -## (We might end up doing this anyway to support other compilers.) -## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like -## -MM, not -M (despite what the docs say). Also, it might not be -## supported by the other compilers which use the 'gcc' depmode. -## - Using -M directly means running the compiler twice (even worse -## than renaming). - if test -z "$gccflag"; then - gccflag=-MD, - fi - "$@" -Wp,"$gccflag$tmpdepfile" - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - echo "$object : \\" > "$depfile" - # The second -e expression handles DOS-style file names with drive - # letters. - sed -e 's/^[^:]*: / /' \ - -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" -## This next piece of magic avoids the "deleted header file" problem. -## The problem is that when a header file which appears in a .P file -## is deleted, the dependency causes make to die (because there is -## typically no way to rebuild the header). We avoid this by adding -## dummy dependencies for each header file. Too bad gcc doesn't do -## this for us directly. -## Some versions of gcc put a space before the ':'. On the theory -## that the space means something, we add a space to the output as -## well. hp depmode also adds that space, but also prefixes the VPATH -## to the object. Take care to not repeat it in the output. -## Some versions of the HPUX 10.20 sed can't process this invocation -## correctly. Breaking it into two sed invocations is a workaround. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -hp) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -sgi) - if test "$libtool" = yes; then - "$@" "-Wp,-MDupdate,$tmpdepfile" - else - "$@" -MDupdate "$tmpdepfile" - fi - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - - if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files - echo "$object : \\" > "$depfile" - # Clip off the initial element (the dependent). Don't try to be - # clever and replace this with sed code, as IRIX sed won't handle - # lines with more than a fixed number of characters (4096 in - # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; - # the IRIX cc adds comments like '#:fec' to the end of the - # dependency line. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ - | tr "$nl" ' ' >> "$depfile" - echo >> "$depfile" - # The second pass generates a dummy entry for each header file. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ - >> "$depfile" - else - make_dummy_depfile - fi - rm -f "$tmpdepfile" - ;; - -xlc) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -aix) - # The C for AIX Compiler uses -M and outputs the dependencies - # in a .u file. In older versions, this file always lives in the - # current directory. Also, the AIX compiler puts '$object:' at the - # start of each line; $object doesn't have directory information. - # Version 6 uses the directory in both cases. - set_dir_from "$object" - set_base_from "$object" - if test "$libtool" = yes; then - tmpdepfile1=$dir$base.u - tmpdepfile2=$base.u - tmpdepfile3=$dir.libs/$base.u - "$@" -Wc,-M - else - tmpdepfile1=$dir$base.u - tmpdepfile2=$dir$base.u - tmpdepfile3=$dir$base.u - "$@" -M - fi - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - exit $stat - fi - - for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - do - test -f "$tmpdepfile" && break - done - aix_post_process_depfile - ;; - -tcc) - # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 - # FIXME: That version still under development at the moment of writing. - # Make that this statement remains true also for stable, released - # versions. - # It will wrap lines (doesn't matter whether long or short) with a - # trailing '\', as in: - # - # foo.o : \ - # foo.c \ - # foo.h \ - # - # It will put a trailing '\' even on the last line, and will use leading - # spaces rather than leading tabs (at least since its commit 0394caf7 - # "Emit spaces for -MD"). - "$@" -MD -MF "$tmpdepfile" - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. - # We have to change lines of the first kind to '$object: \'. - sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" - # And for each line of the second kind, we have to emit a 'dep.h:' - # dummy dependency, to avoid the deleted-header problem. - sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" - rm -f "$tmpdepfile" - ;; - -## The order of this option in the case statement is important, since the -## shell code in configure will try each of these formats in the order -## listed in this file. A plain '-MD' option would be understood by many -## compilers, so we must ensure this comes after the gcc and icc options. -pgcc) - # Portland's C compiler understands '-MD'. - # Will always output deps to 'file.d' where file is the root name of the - # source file under compilation, even if file resides in a subdirectory. - # The object file name does not affect the name of the '.d' file. - # pgcc 10.2 will output - # foo.o: sub/foo.c sub/foo.h - # and will wrap long lines using '\' : - # foo.o: sub/foo.c ... \ - # sub/foo.h ... \ - # ... - set_dir_from "$object" - # Use the source, not the object, to determine the base name, since - # that's sadly what pgcc will do too. - set_base_from "$source" - tmpdepfile=$base.d - - # For projects that build the same source file twice into different object - # files, the pgcc approach of using the *source* file root name can cause - # problems in parallel builds. Use a locking strategy to avoid stomping on - # the same $tmpdepfile. - lockdir=$base.d-lock - trap " - echo '$0: caught signal, cleaning up...' >&2 - rmdir '$lockdir' - exit 1 - " 1 2 13 15 - numtries=100 - i=$numtries - while test $i -gt 0; do - # mkdir is a portable test-and-set. - if mkdir "$lockdir" 2>/dev/null; then - # This process acquired the lock. - "$@" -MD - stat=$? - # Release the lock. - rmdir "$lockdir" - break - else - # If the lock is being held by a different process, wait - # until the winning process is done or we timeout. - while test -d "$lockdir" && test $i -gt 0; do - sleep 1 - i=`expr $i - 1` - done - fi - i=`expr $i - 1` - done - trap - 1 2 13 15 - if test $i -le 0; then - echo "$0: failed to acquire lock after $numtries attempts" >&2 - echo "$0: check lockdir '$lockdir'" >&2 - exit 1 - fi - - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - # Each line is of the form `foo.o: dependent.h', - # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. - # Do two passes, one to just change these to - # `$object: dependent.h' and one to simply `dependent.h:'. - sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process this invocation - # correctly. Breaking it into two sed invocations is a workaround. - sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -hp2) - # The "hp" stanza above does not work with aCC (C++) and HP's ia64 - # compilers, which have integrated preprocessors. The correct option - # to use with these is +Maked; it writes dependencies to a file named - # 'foo.d', which lands next to the object file, wherever that - # happens to be. - # Much of this is similar to the tru64 case; see comments there. - set_dir_from "$object" - set_base_from "$object" - if test "$libtool" = yes; then - tmpdepfile1=$dir$base.d - tmpdepfile2=$dir.libs/$base.d - "$@" -Wc,+Maked - else - tmpdepfile1=$dir$base.d - tmpdepfile2=$dir$base.d - "$@" +Maked - fi - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile1" "$tmpdepfile2" - exit $stat - fi - - for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" - do - test -f "$tmpdepfile" && break - done - if test -f "$tmpdepfile"; then - sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" - # Add 'dependent.h:' lines. - sed -ne '2,${ - s/^ *// - s/ \\*$// - s/$/:/ - p - }' "$tmpdepfile" >> "$depfile" - else - make_dummy_depfile - fi - rm -f "$tmpdepfile" "$tmpdepfile2" - ;; - -tru64) - # The Tru64 compiler uses -MD to generate dependencies as a side - # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. - # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put - # dependencies in 'foo.d' instead, so we check for that too. - # Subdirectories are respected. - set_dir_from "$object" - set_base_from "$object" - - if test "$libtool" = yes; then - # Libtool generates 2 separate objects for the 2 libraries. These - # two compilations output dependencies in $dir.libs/$base.o.d and - # in $dir$base.o.d. We have to check for both files, because - # one of the two compilations can be disabled. We should prefer - # $dir$base.o.d over $dir.libs/$base.o.d because the latter is - # automatically cleaned when .libs/ is deleted, while ignoring - # the former would cause a distcleancheck panic. - tmpdepfile1=$dir$base.o.d # libtool 1.5 - tmpdepfile2=$dir.libs/$base.o.d # Likewise. - tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 - "$@" -Wc,-MD - else - tmpdepfile1=$dir$base.d - tmpdepfile2=$dir$base.d - tmpdepfile3=$dir$base.d - "$@" -MD - fi - - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - exit $stat - fi - - for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - do - test -f "$tmpdepfile" && break - done - # Same post-processing that is required for AIX mode. - aix_post_process_depfile - ;; - -msvc7) - if test "$libtool" = yes; then - showIncludes=-Wc,-showIncludes - else - showIncludes=-showIncludes - fi - "$@" $showIncludes > "$tmpdepfile" - stat=$? - grep -v '^Note: including file: ' "$tmpdepfile" - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - echo "$object : \\" > "$depfile" - # The first sed program below extracts the file names and escapes - # backslashes for cygpath. The second sed program outputs the file - # name when reading, but also accumulates all include files in the - # hold buffer in order to output them again at the end. This only - # works with sed implementations that can handle large buffers. - sed < "$tmpdepfile" -n ' -/^Note: including file: *\(.*\)/ { - s//\1/ - s/\\/\\\\/g - p -}' | $cygpath_u | sort -u | sed -n ' -s/ /\\ /g -s/\(.*\)/'"$tab"'\1 \\/p -s/.\(.*\) \\/\1:/ -H -$ { - s/.*/'"$tab"'/ - G - p -}' >> "$depfile" - echo >> "$depfile" # make sure the fragment doesn't end with a backslash - rm -f "$tmpdepfile" - ;; - -msvc7msys) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -#nosideeffect) - # This comment above is used by automake to tell side-effect - # dependency tracking mechanisms from slower ones. - -dashmstdout) - # Important note: in order to support this mode, a compiler *must* - # always write the preprocessed file to stdout, regardless of -o. - "$@" || exit $? - - # Remove the call to Libtool. - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - - # Remove '-o $object'. - IFS=" " - for arg - do - case $arg in - -o) - shift - ;; - $object) - shift - ;; - *) - set fnord "$@" "$arg" - shift # fnord - shift # $arg - ;; - esac - done - - test -z "$dashmflag" && dashmflag=-M - # Require at least two characters before searching for ':' - # in the target name. This is to cope with DOS-style filenames: - # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. - "$@" $dashmflag | - sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" - rm -f "$depfile" - cat < "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process this sed invocation - # correctly. Breaking it into two sed invocations is a workaround. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -dashXmstdout) - # This case only exists to satisfy depend.m4. It is never actually - # run, as this mode is specially recognized in the preamble. - exit 1 - ;; - -makedepend) - "$@" || exit $? - # Remove any Libtool call - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - # X makedepend - shift - cleared=no eat=no - for arg - do - case $cleared in - no) - set ""; shift - cleared=yes ;; - esac - if test $eat = yes; then - eat=no - continue - fi - case "$arg" in - -D*|-I*) - set fnord "$@" "$arg"; shift ;; - # Strip any option that makedepend may not understand. Remove - # the object too, otherwise makedepend will parse it as a source file. - -arch) - eat=yes ;; - -*|$object) - ;; - *) - set fnord "$@" "$arg"; shift ;; - esac - done - obj_suffix=`echo "$object" | sed 's/^.*\././'` - touch "$tmpdepfile" - ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" - rm -f "$depfile" - # makedepend may prepend the VPATH from the source file name to the object. - # No need to regex-escape $object, excess matching of '.' is harmless. - sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process the last invocation - # correctly. Breaking it into two sed invocations is a workaround. - sed '1,2d' "$tmpdepfile" \ - | tr ' ' "$nl" \ - | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" "$tmpdepfile".bak - ;; - -cpp) - # Important note: in order to support this mode, a compiler *must* - # always write the preprocessed file to stdout. - "$@" || exit $? - - # Remove the call to Libtool. - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - - # Remove '-o $object'. - IFS=" " - for arg - do - case $arg in - -o) - shift - ;; - $object) - shift - ;; - *) - set fnord "$@" "$arg" - shift # fnord - shift # $arg - ;; - esac - done - - "$@" -E \ - | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ - -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ - | sed '$ s: \\$::' > "$tmpdepfile" - rm -f "$depfile" - echo "$object : \\" > "$depfile" - cat < "$tmpdepfile" >> "$depfile" - sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -msvisualcpp) - # Important note: in order to support this mode, a compiler *must* - # always write the preprocessed file to stdout. - "$@" || exit $? - - # Remove the call to Libtool. - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - - IFS=" " - for arg - do - case "$arg" in - -o) - shift - ;; - $object) - shift - ;; - "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") - set fnord "$@" - shift - shift - ;; - *) - set fnord "$@" "$arg" - shift - shift - ;; - esac - done - "$@" -E 2>/dev/null | - sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" - rm -f "$depfile" - echo "$object : \\" > "$depfile" - sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" - echo "$tab" >> "$depfile" - sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -msvcmsys) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -none) - exec "$@" - ;; - -*) - echo "Unknown depmode $depmode" 1>&2 - exit 1 - ;; -esac - -exit 0 - -# Local Variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC0" -# time-stamp-end: "; # UTC" -# End: diff --git a/build-aux/install-sh b/build-aux/install-sh deleted file mode 100755 index ec298b5..0000000 --- a/build-aux/install-sh +++ /dev/null @@ -1,541 +0,0 @@ -#!/bin/sh -# install - install a program, script, or datafile - -scriptversion=2020-11-14.01; # UTC - -# This originates from X11R5 (mit/util/scripts/install.sh), which was -# later released in X11R6 (xc/config/util/install.sh) with the -# following copyright and license. -# -# Copyright (C) 1994 X Consortium -# -# 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 -# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- -# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the name of the X Consortium shall not -# be used in advertising or otherwise to promote the sale, use or other deal- -# ings in this Software without prior written authorization from the X Consor- -# tium. -# -# -# FSF changes to this file are in the public domain. -# -# Calling this script install-sh is preferred over install.sh, to prevent -# 'make' implicit rules from creating a file called install from it -# when there is no Makefile. -# -# This script is compatible with the BSD install script, but was written -# from scratch. - -tab=' ' -nl=' -' -IFS=" $tab$nl" - -# Set DOITPROG to "echo" to test this script. - -doit=${DOITPROG-} -doit_exec=${doit:-exec} - -# Put in absolute file names if you don't have them in your path; -# or use environment vars. - -chgrpprog=${CHGRPPROG-chgrp} -chmodprog=${CHMODPROG-chmod} -chownprog=${CHOWNPROG-chown} -cmpprog=${CMPPROG-cmp} -cpprog=${CPPROG-cp} -mkdirprog=${MKDIRPROG-mkdir} -mvprog=${MVPROG-mv} -rmprog=${RMPROG-rm} -stripprog=${STRIPPROG-strip} - -posix_mkdir= - -# Desired mode of installed file. -mode=0755 - -# Create dirs (including intermediate dirs) using mode 755. -# This is like GNU 'install' as of coreutils 8.32 (2020). -mkdir_umask=22 - -backupsuffix= -chgrpcmd= -chmodcmd=$chmodprog -chowncmd= -mvcmd=$mvprog -rmcmd="$rmprog -f" -stripcmd= - -src= -dst= -dir_arg= -dst_arg= - -copy_on_change=false -is_target_a_directory=possibly - -usage="\ -Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE - or: $0 [OPTION]... SRCFILES... DIRECTORY - or: $0 [OPTION]... -t DIRECTORY SRCFILES... - or: $0 [OPTION]... -d DIRECTORIES... - -In the 1st form, copy SRCFILE to DSTFILE. -In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. -In the 4th, create DIRECTORIES. - -Options: - --help display this help and exit. - --version display version info and exit. - - -c (ignored) - -C install only if different (preserve data modification time) - -d create directories instead of installing files. - -g GROUP $chgrpprog installed files to GROUP. - -m MODE $chmodprog installed files to MODE. - -o USER $chownprog installed files to USER. - -p pass -p to $cpprog. - -s $stripprog installed files. - -S SUFFIX attempt to back up existing files, with suffix SUFFIX. - -t DIRECTORY install into DIRECTORY. - -T report an error if DSTFILE is a directory. - -Environment variables override the default commands: - CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG - RMPROG STRIPPROG - -By default, rm is invoked with -f; when overridden with RMPROG, -it's up to you to specify -f if you want it. - -If -S is not specified, no backups are attempted. - -Email bug reports to bug-automake@gnu.org. -Automake home page: https://www.gnu.org/software/automake/ -" - -while test $# -ne 0; do - case $1 in - -c) ;; - - -C) copy_on_change=true;; - - -d) dir_arg=true;; - - -g) chgrpcmd="$chgrpprog $2" - shift;; - - --help) echo "$usage"; exit $?;; - - -m) mode=$2 - case $mode in - *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) - echo "$0: invalid mode: $mode" >&2 - exit 1;; - esac - shift;; - - -o) chowncmd="$chownprog $2" - shift;; - - -p) cpprog="$cpprog -p";; - - -s) stripcmd=$stripprog;; - - -S) backupsuffix="$2" - shift;; - - -t) - is_target_a_directory=always - dst_arg=$2 - # Protect names problematic for 'test' and other utilities. - case $dst_arg in - -* | [=\(\)!]) dst_arg=./$dst_arg;; - esac - shift;; - - -T) is_target_a_directory=never;; - - --version) echo "$0 $scriptversion"; exit $?;; - - --) shift - break;; - - -*) echo "$0: invalid option: $1" >&2 - exit 1;; - - *) break;; - esac - shift -done - -# We allow the use of options -d and -T together, by making -d -# take the precedence; this is for compatibility with GNU install. - -if test -n "$dir_arg"; then - if test -n "$dst_arg"; then - echo "$0: target directory not allowed when installing a directory." >&2 - exit 1 - fi -fi - -if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then - # When -d is used, all remaining arguments are directories to create. - # When -t is used, the destination is already specified. - # Otherwise, the last argument is the destination. Remove it from $@. - for arg - do - if test -n "$dst_arg"; then - # $@ is not empty: it contains at least $arg. - set fnord "$@" "$dst_arg" - shift # fnord - fi - shift # arg - dst_arg=$arg - # Protect names problematic for 'test' and other utilities. - case $dst_arg in - -* | [=\(\)!]) dst_arg=./$dst_arg;; - esac - done -fi - -if test $# -eq 0; then - if test -z "$dir_arg"; then - echo "$0: no input file specified." >&2 - exit 1 - fi - # It's OK to call 'install-sh -d' without argument. - # This can happen when creating conditional directories. - exit 0 -fi - -if test -z "$dir_arg"; then - if test $# -gt 1 || test "$is_target_a_directory" = always; then - if test ! -d "$dst_arg"; then - echo "$0: $dst_arg: Is not a directory." >&2 - exit 1 - fi - fi -fi - -if test -z "$dir_arg"; then - do_exit='(exit $ret); exit $ret' - trap "ret=129; $do_exit" 1 - trap "ret=130; $do_exit" 2 - trap "ret=141; $do_exit" 13 - trap "ret=143; $do_exit" 15 - - # Set umask so as not to create temps with too-generous modes. - # However, 'strip' requires both read and write access to temps. - case $mode in - # Optimize common cases. - *644) cp_umask=133;; - *755) cp_umask=22;; - - *[0-7]) - if test -z "$stripcmd"; then - u_plus_rw= - else - u_plus_rw='% 200' - fi - cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; - *) - if test -z "$stripcmd"; then - u_plus_rw= - else - u_plus_rw=,u+rw - fi - cp_umask=$mode$u_plus_rw;; - esac -fi - -for src -do - # Protect names problematic for 'test' and other utilities. - case $src in - -* | [=\(\)!]) src=./$src;; - esac - - if test -n "$dir_arg"; then - dst=$src - dstdir=$dst - test -d "$dstdir" - dstdir_status=$? - # Don't chown directories that already exist. - if test $dstdir_status = 0; then - chowncmd="" - fi - else - - # Waiting for this to be detected by the "$cpprog $src $dsttmp" command - # might cause directories to be created, which would be especially bad - # if $src (and thus $dsttmp) contains '*'. - if test ! -f "$src" && test ! -d "$src"; then - echo "$0: $src does not exist." >&2 - exit 1 - fi - - if test -z "$dst_arg"; then - echo "$0: no destination specified." >&2 - exit 1 - fi - dst=$dst_arg - - # If destination is a directory, append the input filename. - if test -d "$dst"; then - if test "$is_target_a_directory" = never; then - echo "$0: $dst_arg: Is a directory" >&2 - exit 1 - fi - dstdir=$dst - dstbase=`basename "$src"` - case $dst in - */) dst=$dst$dstbase;; - *) dst=$dst/$dstbase;; - esac - dstdir_status=0 - else - dstdir=`dirname "$dst"` - test -d "$dstdir" - dstdir_status=$? - fi - fi - - case $dstdir in - */) dstdirslash=$dstdir;; - *) dstdirslash=$dstdir/;; - esac - - obsolete_mkdir_used=false - - if test $dstdir_status != 0; then - case $posix_mkdir in - '') - # With -d, create the new directory with the user-specified mode. - # Otherwise, rely on $mkdir_umask. - if test -n "$dir_arg"; then - mkdir_mode=-m$mode - else - mkdir_mode= - fi - - posix_mkdir=false - # The $RANDOM variable is not portable (e.g., dash). Use it - # here however when possible just to lower collision chance. - tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ - - trap ' - ret=$? - rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null - exit $ret - ' 0 - - # Because "mkdir -p" follows existing symlinks and we likely work - # directly in world-writeable /tmp, make sure that the '$tmpdir' - # directory is successfully created first before we actually test - # 'mkdir -p'. - if (umask $mkdir_umask && - $mkdirprog $mkdir_mode "$tmpdir" && - exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 - then - if test -z "$dir_arg" || { - # Check for POSIX incompatibilities with -m. - # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or - # other-writable bit of parent directory when it shouldn't. - # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. - test_tmpdir="$tmpdir/a" - ls_ld_tmpdir=`ls -ld "$test_tmpdir"` - case $ls_ld_tmpdir in - d????-?r-*) different_mode=700;; - d????-?--*) different_mode=755;; - *) false;; - esac && - $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { - ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` - test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" - } - } - then posix_mkdir=: - fi - rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" - else - # Remove any dirs left behind by ancient mkdir implementations. - rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null - fi - trap '' 0;; - esac - - if - $posix_mkdir && ( - umask $mkdir_umask && - $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" - ) - then : - else - - # mkdir does not conform to POSIX, - # or it failed possibly due to a race condition. Create the - # directory the slow way, step by step, checking for races as we go. - - case $dstdir in - /*) prefix='/';; - [-=\(\)!]*) prefix='./';; - *) prefix='';; - esac - - oIFS=$IFS - IFS=/ - set -f - set fnord $dstdir - shift - set +f - IFS=$oIFS - - prefixes= - - for d - do - test X"$d" = X && continue - - prefix=$prefix$d - if test -d "$prefix"; then - prefixes= - else - if $posix_mkdir; then - (umask $mkdir_umask && - $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break - # Don't fail if two instances are running concurrently. - test -d "$prefix" || exit 1 - else - case $prefix in - *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; - *) qprefix=$prefix;; - esac - prefixes="$prefixes '$qprefix'" - fi - fi - prefix=$prefix/ - done - - if test -n "$prefixes"; then - # Don't fail if two instances are running concurrently. - (umask $mkdir_umask && - eval "\$doit_exec \$mkdirprog $prefixes") || - test -d "$dstdir" || exit 1 - obsolete_mkdir_used=true - fi - fi - fi - - if test -n "$dir_arg"; then - { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && - { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && - { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || - test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 - else - - # Make a couple of temp file names in the proper directory. - dsttmp=${dstdirslash}_inst.$$_ - rmtmp=${dstdirslash}_rm.$$_ - - # Trap to clean up those temp files at exit. - trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 - - # Copy the file name to the temp name. - (umask $cp_umask && - { test -z "$stripcmd" || { - # Create $dsttmp read-write so that cp doesn't create it read-only, - # which would cause strip to fail. - if test -z "$doit"; then - : >"$dsttmp" # No need to fork-exec 'touch'. - else - $doit touch "$dsttmp" - fi - } - } && - $doit_exec $cpprog "$src" "$dsttmp") && - - # and set any options; do chmod last to preserve setuid bits. - # - # If any of these fail, we abort the whole thing. If we want to - # ignore errors from any of these, just make sure not to ignore - # errors from the above "$doit $cpprog $src $dsttmp" command. - # - { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && - { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && - { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && - { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && - - # If -C, don't bother to copy if it wouldn't change the file. - if $copy_on_change && - old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && - new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && - set -f && - set X $old && old=:$2:$4:$5:$6 && - set X $new && new=:$2:$4:$5:$6 && - set +f && - test "$old" = "$new" && - $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 - then - rm -f "$dsttmp" - else - # If $backupsuffix is set, and the file being installed - # already exists, attempt a backup. Don't worry if it fails, - # e.g., if mv doesn't support -f. - if test -n "$backupsuffix" && test -f "$dst"; then - $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null - fi - - # Rename the file to the real destination. - $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || - - # The rename failed, perhaps because mv can't rename something else - # to itself, or perhaps because mv is so ancient that it does not - # support -f. - { - # Now remove or move aside any old file at destination location. - # We try this two ways since rm can't unlink itself on some - # systems and the destination file might be busy for other - # reasons. In this case, the final cleanup might fail but the new - # file should still install successfully. - { - test ! -f "$dst" || - $doit $rmcmd "$dst" 2>/dev/null || - { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && - { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } - } || - { echo "$0: cannot unlink or rename $dst" >&2 - (exit 1); exit 1 - } - } && - - # Now rename the file to the real destination. - $doit $mvcmd "$dsttmp" "$dst" - } - fi || exit 1 - - trap '' 0 - fi -done - -# Local variables: -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC0" -# time-stamp-end: "; # UTC" -# End: diff --git a/build-aux/missing b/build-aux/missing deleted file mode 100755 index 1fe1611..0000000 --- a/build-aux/missing +++ /dev/null @@ -1,215 +0,0 @@ -#! /bin/sh -# Common wrapper for a few potentially missing GNU programs. - -scriptversion=2018-03-07.03; # UTC - -# Copyright (C) 1996-2021 Free Software Foundation, Inc. -# Originally written by Fran,cois Pinard , 1996. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -if test $# -eq 0; then - echo 1>&2 "Try '$0 --help' for more information" - exit 1 -fi - -case $1 in - - --is-lightweight) - # Used by our autoconf macros to check whether the available missing - # script is modern enough. - exit 0 - ;; - - --run) - # Back-compat with the calling convention used by older automake. - shift - ;; - - -h|--h|--he|--hel|--help) - echo "\ -$0 [OPTION]... PROGRAM [ARGUMENT]... - -Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due -to PROGRAM being missing or too old. - -Options: - -h, --help display this help and exit - -v, --version output version information and exit - -Supported PROGRAM values: - aclocal autoconf autoheader autom4te automake makeinfo - bison yacc flex lex help2man - -Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and -'g' are ignored when checking the name. - -Send bug reports to ." - exit $? - ;; - - -v|--v|--ve|--ver|--vers|--versi|--versio|--version) - echo "missing $scriptversion (GNU Automake)" - exit $? - ;; - - -*) - echo 1>&2 "$0: unknown '$1' option" - echo 1>&2 "Try '$0 --help' for more information" - exit 1 - ;; - -esac - -# Run the given program, remember its exit status. -"$@"; st=$? - -# If it succeeded, we are done. -test $st -eq 0 && exit 0 - -# Also exit now if we it failed (or wasn't found), and '--version' was -# passed; such an option is passed most likely to detect whether the -# program is present and works. -case $2 in --version|--help) exit $st;; esac - -# Exit code 63 means version mismatch. This often happens when the user -# tries to use an ancient version of a tool on a file that requires a -# minimum version. -if test $st -eq 63; then - msg="probably too old" -elif test $st -eq 127; then - # Program was missing. - msg="missing on your system" -else - # Program was found and executed, but failed. Give up. - exit $st -fi - -perl_URL=https://www.perl.org/ -flex_URL=https://github.com/westes/flex -gnu_software_URL=https://www.gnu.org/software - -program_details () -{ - case $1 in - aclocal|automake) - echo "The '$1' program is part of the GNU Automake package:" - echo "<$gnu_software_URL/automake>" - echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" - echo "<$gnu_software_URL/autoconf>" - echo "<$gnu_software_URL/m4/>" - echo "<$perl_URL>" - ;; - autoconf|autom4te|autoheader) - echo "The '$1' program is part of the GNU Autoconf package:" - echo "<$gnu_software_URL/autoconf/>" - echo "It also requires GNU m4 and Perl in order to run:" - echo "<$gnu_software_URL/m4/>" - echo "<$perl_URL>" - ;; - esac -} - -give_advice () -{ - # Normalize program name to check for. - normalized_program=`echo "$1" | sed ' - s/^gnu-//; t - s/^gnu//; t - s/^g//; t'` - - printf '%s\n' "'$1' is $msg." - - configure_deps="'configure.ac' or m4 files included by 'configure.ac'" - case $normalized_program in - autoconf*) - echo "You should only need it if you modified 'configure.ac'," - echo "or m4 files included by it." - program_details 'autoconf' - ;; - autoheader*) - echo "You should only need it if you modified 'acconfig.h' or" - echo "$configure_deps." - program_details 'autoheader' - ;; - automake*) - echo "You should only need it if you modified 'Makefile.am' or" - echo "$configure_deps." - program_details 'automake' - ;; - aclocal*) - echo "You should only need it if you modified 'acinclude.m4' or" - echo "$configure_deps." - program_details 'aclocal' - ;; - autom4te*) - echo "You might have modified some maintainer files that require" - echo "the 'autom4te' program to be rebuilt." - program_details 'autom4te' - ;; - bison*|yacc*) - echo "You should only need it if you modified a '.y' file." - echo "You may want to install the GNU Bison package:" - echo "<$gnu_software_URL/bison/>" - ;; - lex*|flex*) - echo "You should only need it if you modified a '.l' file." - echo "You may want to install the Fast Lexical Analyzer package:" - echo "<$flex_URL>" - ;; - help2man*) - echo "You should only need it if you modified a dependency" \ - "of a man page." - echo "You may want to install the GNU Help2man package:" - echo "<$gnu_software_URL/help2man/>" - ;; - makeinfo*) - echo "You should only need it if you modified a '.texi' file, or" - echo "any other file indirectly affecting the aspect of the manual." - echo "You might want to install the Texinfo package:" - echo "<$gnu_software_URL/texinfo/>" - echo "The spurious makeinfo call might also be the consequence of" - echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" - echo "want to install GNU make:" - echo "<$gnu_software_URL/make/>" - ;; - *) - echo "You might have modified some files without having the proper" - echo "tools for further handling them. Check the 'README' file, it" - echo "often tells you about the needed prerequisites for installing" - echo "this package. You may also peek at any GNU archive site, in" - echo "case some other package contains this missing '$1' program." - ;; - esac -} - -give_advice "$1" | sed -e '1s/^/WARNING: /' \ - -e '2,$s/^/ /' >&2 - -# Propagate the correct exit status (expected to be 127 for a program -# not found, 63 for a program that failed due to version mismatch). -exit $st - -# Local variables: -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC0" -# time-stamp-end: "; # UTC" -# End: diff --git a/configure.ac b/configure.ac deleted file mode 100644 index ebf1d63..0000000 --- a/configure.ac +++ /dev/null @@ -1,54 +0,0 @@ -# Must init the autoconf setup -# The first parameter is project name -# second is version number -# third is bug report address -AC_INIT([synth],[0.0.1]) - -# Safety checks in case user overwritten --srcdir -AC_CONFIG_SRCDIR([src/synth.c]) - -# Store the auxiliary build tools (e.g., install-sh, config.sub, config.guess) -# in this dir (build-aux) -AC_CONFIG_AUX_DIR([build-aux]) - -# Init automake, and specify this program use relaxed structures. -# i.e. this program doesn't follow the gnu coding standards, and doesn't have -# ChangeLog, COPYING, AUTHORS, INSTALL, README etc. files. -AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects dist-xz]) - -AC_PROG_CC - -#PKG_CHECK_MODULES([CHECK], [check >= 0.9.6]) - -# Set default cflags -#: ${CFLAGS="-O3 -pedantic"} - -# Checks for header files. -dnl AC_CHECK_HEADERS([stdlib.h]) -AC_CHECK_HEADERS([raylib.h]) -AC_CHECK_HEADERS([portaudio.h]) -AC_CHECK_HEADERS([portmidi.h]) - -dnl AC_CHECK_FUNCS([memset]) -dnl AC_CHECK_FUNCS([strcasecmp]) -dnl AC_CHECK_FUNCS([strdup]) -dnl AC_CHECK_FUNCS([strerror]) -dnl AC_CHECK_FUNCS([strstr]) -dnl AC_CHECK_FUNCS([strtol]) -dnl AC_CHECK_HEADERS([stdint.h]) -dnl #AC_FUNC_MALLOC -dnl #AC_FUNC_REALLOC -dnl AC_TYPE_INT32_T -dnl AC_TYPE_SIZE_T -dnl AC_TYPE_UINT32_T -dnl AC_TYPE_UINT64_T -dnl AC_TYPE_UINT8_T - -# Tells automake to create a Makefile -# See https://www.gnu.org/software/automake/manual/html_node/Requirements.html -AC_CONFIG_FILES([Makefile - src/Makefile]) - -dnl AC_REQUIRE_AUX_FILE([tap-driver.sh]) -# Generate the output -AC_OUTPUT 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 @@ -33,6 +33,22 @@ cc_fix(cc_t *cc) cc->mod = 0; } +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) { 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; @@ -52,6 +52,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 */ 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 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 -#include - -#include - -/* 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 #include #include 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 // 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; icci = 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 #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 -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 +#include +#include +#include + + +#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 = + "\n" + "\n" + "\n" + "C SYNTH WEB!\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\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 */ -- cgit v1.2.3