diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2ffba3b..20bd4a9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,9 +13,9 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 + - uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: '3.12' - uses: BSFishy/pip-action@v1 with: packages: | @@ -48,9 +48,9 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 + - uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: '3.12' - uses: BSFishy/pip-action@v1 with: packages: | diff --git a/.vscode/launch.json b/.vscode/launch.json index 7dccef6..23c7a13 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -105,7 +105,7 @@ "type": "cortex-debug" }, { - "name": "Remote Bluemchen Dusting", + "name": "Remote Nehcmeulb Dusting", "configFiles": [ "interface/stlink.cfg", "target/stm32h7x.cfg" @@ -134,6 +134,246 @@ "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", "type": "cortex-debug" }, + { + "name": "Remote Bluemchen Chorus", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/chorus", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/bluemchen/chorus" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/bluemchen/chorus/build/signaletic-bluemchen-chorus.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Bluemchen Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Bluemchen 1962b Reverb", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/reverb/1962b", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/bluemchen/reverb/1962b" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/bluemchen/reverb/1962b/build/signaletic-bluemchen-1962b.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Bluemchen 1962b Reverb", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Bluemchen 1962c Reverb", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/reverb/1962c", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/bluemchen/reverb/1962c" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/bluemchen/reverb/1962c/build/signaletic-bluemchen-1962c.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Bluemchen 1962c Reverb", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Bluemchen Moorer Reverb", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/reverb/moorer", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/bluemchen/reverb/moorer" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/bluemchen/reverb/moorer/build/signaletic-bluemchen-moorer.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Bluemchen Moorer Reverb", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Bluemchen Calibrator", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/calibrator", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/bluemchen/calibrator" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/bluemchen/calibrator/build/signaletic-bluemchen-calibrator.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Bluemchen Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Bluemchen Bare Board Test", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/bare-board-test", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/bluemchen/bare-board-test" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/bluemchen/bare-board-test/build/bluemchen-bare-board-test.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Bluemchen Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote DPT passthrough", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/dpt/passthrough", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/dpt/passthrough" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/dpt/passthrough/build/signaletic-dpt-passthrough.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build DPT Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote DPT triangles", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/dpt/triangles", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/dpt/triangles" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/dpt/triangles/build/signaletic-dpt-triangles.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build DPT Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, { "name": "Remote DPT lfos", "configFiles": [ @@ -194,6 +434,246 @@ "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", "type": "cortex-debug" }, + { + "name": "Remote Patch.Init() Chorus", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/patch_init/chorus", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/patch_init/chorus" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/patch_init/chorus/build/patch-init-chorus.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Patch.Init() Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Patch.Init() Calibrator", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/patch_init/calibrator", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/patch_init/calibrator" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/patch_init/calibrator/build/patch-init-calibrator.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Patch.Init() Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Lichen Medium Bare Board Test", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/lichen-medium/bare-board-test", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/lichen-medium/bare-board-test" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/lichen-medium/bare-board-test/build/lichen-medium-bare-board-test.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Lichen Medium Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Lichen Freddie Bare Board Test", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/lichen-freddie/bare-board-test", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/lichen-freddie/bare-board-test" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/lichen-freddie/bare-board-test/build/lichen-freddie-bare-board-test.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Lichen Freddie Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Lichen Bifocals Sine Oscillator", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/lichen-bifocals/sine-oscillator", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/lichen-bifocals/sine-oscillator" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/lichen-bifocals/sine-oscillator/build/lichen-bifocals-sine-oscillator.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Lichen Bifocals Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Lichen Bifocals Filter", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/lichen-bifocals/filter", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/lichen-bifocals/filter" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/lichen-bifocals/filter/build/lichen-bifocals-filter.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Lichen Bifocals Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Lichen Medium Sines", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/lichen-medium/sines", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/lichen-medium/sines" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/lichen-medium/sines/build/lichen-medium-sines.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Lichen Medium Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, + { + "name": "Remote Lichen Medium Chorus", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/lichen-medium/chorus", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/lichen-medium/chorus" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/lichen-medium/chorus/build/lichen-medium-chorus.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Lichen Medium Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, { "name": "Remote Versio Filter", "configFiles": [ @@ -223,6 +703,36 @@ "showDevDebugOutput": "parsed", "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", "type": "cortex-debug" + }, + { + "name": "Remote Versio Sine", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/versio/sines", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/versio/sines" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/versio/sines/build/signaletic-versio-sines.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Versio Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" } ], "version": "0.2.0" diff --git a/.vscode/settings.json b/.vscode/settings.json index 8802096..ee71e1b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,65 @@ { "files.associations": { + "*.html": "html", "libsignaletic.h": "c", "tlsf.h": "c", - "stddef.h": "c" + "stddef.h": "c", + "__locale": "c", + "regex": "c", + "string": "c", + "string_view": "c", + "ios": "cpp", + "istream": "c", + "vector": "cpp", + "__bit_reference": "cpp", + "__node_handle": "cpp", + "atomic": "cpp", + "bitset": "cpp", + "__memory": "cpp", + "limits": "cpp", + "locale": "cpp", + "optional": "cpp", + "ratio": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "__hash_table": "cpp", + "__split_buffer": "cpp", + "__tree": "cpp", + "array": "cpp", + "initializer_list": "cpp", + "map": "cpp", + "unordered_map": "cpp", + "signaletic-host.h": "c", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__mutex_base": "cpp", + "__threading_support": "cpp", + "__verbose_abort": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "complex": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "exception": "cpp", + "iosfwd": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "algorithm": "cpp" } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ddf6763..8f5718e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -95,11 +95,67 @@ "type": "shell" }, { - "label": "Debug Build DPT Examples", + "label": "Debug Build Bluemchen 1962a Reverb", + "dependsOn": [ + "Build libdaisy" + ], + "command": "make clean;DEBUG=1 make", + "options": { + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/reverb/1962a" + }, + "problemMatcher": [ + "$gcc" + ], + "type": "shell" + }, + { + "label": "Debug Build Bluemchen 1962b Reverb", + "dependsOn": [ + "Build libdaisy" + ], + "command": "make clean;DEBUG=1 make", + "options": { + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/reverb/1962b" + }, + "problemMatcher": [ + "$gcc" + ], + "type": "shell" + }, + { + "label": "Debug Build Bluemchen 1962c Reverb", "dependsOn": [ "Build libdaisy" ], "command": "make clean;DEBUG=1 make", + "options": { + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/reverb/1962c" + }, + "problemMatcher": [ + "$gcc" + ], + "type": "shell" + }, + { + "label": "Debug Build Bluemchen Moorer Reverb", + "dependsOn": [ + "Build libdaisy" + ], + "command": "make clean;DEBUG=1 OPT=-Og make", + "options": { + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/reverb/moorer" + }, + "problemMatcher": [ + "$gcc" + ], + "type": "shell" + }, + { + "label": "Debug Build DPT Examples", + "dependsOn": [ + "Build libdaisy" + ], + "command": "make clean;DEBUG=1 OPT=-Og make", "options": { "cwd": "${workspaceFolder}/hosts/daisy/examples/dpt" }, @@ -113,7 +169,7 @@ "dependsOn": [ "Build libdaisy" ], - "command": "make clean;DEBUG=1 make", + "command": "make clean;DEBUG=1 OPT=-Og make", "options": { "cwd": "${workspaceFolder}/hosts/daisy/examples/patch_init" }, @@ -127,7 +183,7 @@ "dependsOn": [ "Build libdaisy" ], - "command": "make clean;DEBUG=1 make", + "command": "make clean;DEBUG=1 OPT=-Og make", "options": { "cwd": "${workspaceFolder}/hosts/daisy/examples/versio" }, @@ -136,6 +192,48 @@ ], "type": "shell" }, + { + "label": "Debug Build Lichen Medium Examples", + "dependsOn": [ + "Build libdaisy" + ], + "command": "make clean;DEBUG=1 OPT=-Og make", + "options": { + "cwd": "${workspaceFolder}/hosts/daisy/examples/lichen-medium" + }, + "problemMatcher": [ + "$gcc" + ], + "type": "shell" + }, + { + "label": "Debug Build Lichen Bifocals Examples", + "dependsOn": [ + "Build libdaisy" + ], + "command": "make clean;DEBUG=1 OPT=-Og make", + "options": { + "cwd": "${workspaceFolder}/hosts/daisy/examples/lichen-bifocals" + }, + "problemMatcher": [ + "$gcc" + ], + "type": "shell" + }, + { + "label": "Debug Build Lichen Freddie Examples", + "dependsOn": [ + "Build libdaisy" + ], + "command": "make clean;DEBUG=1 OPT=-Og make", + "options": { + "cwd": "${workspaceFolder}/hosts/daisy/examples/lichen-freddie" + }, + "problemMatcher": [ + "$gcc" + ], + "type": "shell" + }, { "label": "Build Bluemchen Examples", "dependsOn": [ @@ -183,7 +281,7 @@ "type": "shell" }, { - "label": "Build and Flash Bluemchen Dusting", + "label": "Build and Flash Nehcmeulb Dusting", "options": { "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/dusting" }, @@ -196,6 +294,20 @@ ], "type": "shell" }, + { + "label": "Build and Flash Nehcmeulb Calibrator", + "options": { + "cwd": "${workspaceFolder}/hosts/daisy/examples/bluemchen/calibrator" + }, + "dependsOn": [ + "Build Bluemchen Examples" + ], + "command": "make program", + "problemMatcher": [ + "$gcc" + ], + "type": "shell" + }, { "label": "Build libdaisy", "options": { diff --git a/hosts/daisy/examples/Makefile b/hosts/daisy/examples/Makefile index 6ea6e82..33afb89 100644 --- a/hosts/daisy/examples/Makefile +++ b/hosts/daisy/examples/Makefile @@ -1,11 +1,17 @@ all: $(MAKE) -C bluemchen $(MAKE) -C dpt + $(MAKE) -C lichen-bifocals + $(MAKE) -C lichen-freddie + $(MAKE) -C lichen-medium $(MAKE) -C patch_init $(MAKE) -C versio clean: $(MAKE) -C bluemchen clean $(MAKE) -C dpt clean + $(MAKE) -C lichen-bifocals clean + $(MAKE) -C lichen-freddie clean + $(MAKE) -C lichen-medium clean $(MAKE) -C patch_init clean $(MAKE) -C versio clean diff --git a/hosts/daisy/examples/bluemchen/Makefile b/hosts/daisy/examples/bluemchen/Makefile index 74c5ec9..fa57c07 100644 --- a/hosts/daisy/examples/bluemchen/Makefile +++ b/hosts/daisy/examples/bluemchen/Makefile @@ -1,11 +1,25 @@ all: - $(MAKE) -C oscillator + $(MAKE) -C bare-board-test + $(MAKE) -C calibrator + $(MAKE) -C chorus + $(MAKE) -C dusting $(MAKE) -C filter $(MAKE) -C looper - $(MAKE) -C dusting + $(MAKE) -C oscillator + $(MAKE) -C reverb/1962a + $(MAKE) -C reverb/1962b + $(MAKE) -C reverb/1962c + $(MAKE) -C reverb/moorer clean: - $(MAKE) -C oscillator clean + $(MAKE) -C bare-board-test clean + $(MAKE) -C calibrator clean + $(MAKE) -C chorus clean + $(MAKE) -C dusting clean $(MAKE) -C filter clean $(MAKE) -C looper clean - $(MAKE) -C dusting clean + $(MAKE) -C oscillator clean + $(MAKE) -C reverb/1962a clean + $(MAKE) -C reverb/1962b clean + $(MAKE) -C reverb/1962c clean + $(MAKE) -C reverb/moorer clean diff --git a/hosts/daisy/examples/bluemchen/bare-board-test/Makefile b/hosts/daisy/examples/bluemchen/bare-board-test/Makefile new file mode 100644 index 0000000..6c00dd1 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/bare-board-test/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= bluemchen-bare-board-test + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = src/${TARGET}.cpp ../../../src/sig-daisy-seed.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/bluemchen/bare-board-test/src/bluemchen-bare-board-test.cpp b/hosts/daisy/examples/bluemchen/bare-board-test/src/bluemchen-bare-board-test.cpp new file mode 100644 index 0000000..d0103b6 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/bare-board-test/src/bluemchen-bare-board-test.cpp @@ -0,0 +1,233 @@ +#include "daisy.h" +#include +#include "../../../../include/signaletic-daisy-host.hpp" +#include "../../../../include/sig-daisy-seed.hpp" +#include "dev/oled_ssd130x.h" + +#define SAMPLERATE 48000 +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; + +sig::libdaisy::seed::SeedBoard board; +daisy::OledDisplay display; +FixedCapStr<20> displayStr; +struct sig_daisy_Host* host; +sig::libdaisy::AnalogInput knob1; +float knob1RawValue = 0.0f; +sig::libdaisy::AnalogInput knob2; +float knob2RawValue = 0.0f; +sig::libdaisy::AnalogInput cv1; +float cv1RawValue = 0.0f; +sig::libdaisy::AnalogInput cv2; +float cv2RawValue = 0.0f; +struct sig_dsp_Value* freq; +sig::libdaisy::GateInput gateIn; +struct sig_dsp_Value* gain; +struct sig_dsp_Oscillator* sine; +struct sig_dsp_Value* switchValue; +struct sig_dsp_ScaleOffset* switchValueScale; +struct sig_dsp_BinaryOp* harmonizerFreqScale; +struct sig_dsp_Oscillator* harmonizer; +sig::libdaisy::Toggle button; +struct sig_dsp_Value* buttonValue; +float buttonRawValue = 0.0f; +sig::libdaisy::Encoder encoder; +struct sig_dsp_Value* encoderValue; +float encoderRawValue = 0.0f; +struct sig_dsp_BinaryOp* mixer; +struct sig_dsp_BinaryOp* attenuator; + +void initDisplay() { + daisy::OledDisplay::Config display_config; + display_config.driver_config.transport_config.Defaults(); + display.Init(display_config); +} + +void buildSignalGraph(struct sig_Allocator* allocator, + struct sig_SignalContext* context, + struct sig_List* signals, + struct sig_AudioSettings* audioSettings, + struct sig_Status* status) { + + freq = sig_dsp_Value_new(allocator, context); + freq->parameters.value = 220.0f; + sig_List_append(signals, freq, status); + + buttonValue = sig_dsp_Value_new(allocator, context); + sig_List_append(signals, buttonValue, status); + buttonValue->parameters.value = 0.0f; + + switchValue = sig_dsp_Value_new(allocator, context); + sig_List_append(signals, switchValue, status); + switchValue->parameters.value = -1.0f; + + switchValueScale = sig_dsp_ScaleOffset_new(allocator, context); + sig_List_append(signals, switchValueScale, status); + switchValueScale->inputs.source = switchValue->outputs.main; + switchValueScale->parameters.scale = 1.25f; + switchValueScale->parameters.offset = 0.75f; + + harmonizerFreqScale = sig_dsp_Mul_new(allocator, context); + sig_List_append(signals, harmonizerFreqScale, status); + harmonizerFreqScale->inputs.left = freq->outputs.main; + harmonizerFreqScale->inputs.right = switchValueScale->outputs.main; + + harmonizer = sig_dsp_LFTriangle_new(allocator, context); + sig_List_append(signals, harmonizer, status); + harmonizer->inputs.freq = harmonizerFreqScale->outputs.main; + harmonizer->inputs.mul = buttonValue->outputs.main; + + sine = sig_dsp_Sine_new(allocator, context); + sig_List_append(signals, sine, status); + sine->inputs.freq = freq->outputs.main; + + mixer = sig_dsp_Add_new(allocator, context); + sig_List_append(signals, mixer, status); + mixer->inputs.left = sine->outputs.main; + mixer->inputs.right = harmonizer->outputs.main; + + gain = sig_dsp_Value_new(allocator, context); + gain->parameters.value = 0.5f; + sig_List_append(signals, gain, status); + + attenuator = sig_dsp_Mul_new(allocator, context); + sig_List_append(signals, attenuator, status); + attenuator->inputs.left = mixer->outputs.main; + attenuator->inputs.right = gain->outputs.main; +} + +void AudioCallback(daisy::AudioHandle::InputBuffer in, + daisy::AudioHandle::OutputBuffer out, size_t size) { + knob1RawValue = knob1.Value(); + knob2RawValue = knob2.Value(); + cv1RawValue = cv1.Value(); + cv2RawValue = cv2.Value(); + buttonRawValue = button.Value(); + encoderRawValue += encoder.Value(); + freq->parameters.value = 1760.0f * knob1RawValue; + buttonValue->parameters.value = buttonRawValue; + encoderValue->parameters.value = encoderRawValue; + + evaluator->evaluate((struct sig_dsp_SignalEvaluator*) evaluator); + + for (size_t i = 0; i < size; i++) { + float sig = attenuator->outputs.main[i]; + out[0][i] = sig; + out[1][i] = sig; + } +} + +void updateOLED() { + display.Fill(false); + + displayStr.Clear(); + displayStr.Append("Button "); + displayStr.AppendFloat(buttonRawValue, 1); + display.SetCursor(0, 0); + display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("Enc "); + displayStr.AppendFloat(encoderRawValue, 1); + display.SetCursor(0, 8); + display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.AppendFloat(knob1RawValue, 2); + displayStr.Append(" "); + displayStr.AppendFloat(knob2RawValue, 2); + display.SetCursor(0, 16); + display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.AppendFloat(cv1RawValue, 2); + displayStr.Append(" "); + displayStr.AppendFloat(cv2RawValue, 2); + display.SetCursor(0, 24); + display.WriteString(displayStr.Cstr(), Font_6x8, true); + + display.Update(); +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 1 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + board.Init(audioSettings.blockSize, audioSettings.sampleRate); + + // TODO: Move ADC initialization out of the Init function + dsy_gpio_pin adcPins[] = { + sig::libdaisy::seed::PIN_D16, + sig::libdaisy::seed::PIN_D15, + sig::libdaisy::seed::PIN_D21, + sig::libdaisy::seed::PIN_D18 + }; + + board.InitADC(adcPins, 4); + board.adc.Start(); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + buildSignalGraph(&allocator, context, &signals, &audioSettings, &status); + + sig::libdaisy::ADCChannelSpec knob1Spec = { + .pin = adcPins[0], + .normalization = sig::libdaisy::BI_TO_UNIPOLAR + }; + knob1.Init(&board.adc, knob1Spec, 0); + + sig::libdaisy::ADCChannelSpec knob2Spec = { + .pin = adcPins[1], + .normalization = sig::libdaisy::BI_TO_UNIPOLAR + }; + knob2.Init(&board.adc, knob2Spec, 1); + + sig::libdaisy::ADCChannelSpec cv1Spec = { + .pin = adcPins[2], + .normalization = sig::libdaisy::INVERT + }; + cv1.Init(&board.adc, cv1Spec, 2); + + sig::libdaisy::ADCChannelSpec cv2Spec = { + .pin = adcPins[3], + .normalization = sig::libdaisy::INVERT + }; + cv2.Init(&board.adc, cv2Spec, 3); + + // Encoder button and rotation. + button.Init(sig::libdaisy::seed::PIN_D28); + encoder.Init(sig::libdaisy::seed::PIN_D27, sig::libdaisy::seed::PIN_D26); + + board.audio.Start(AudioCallback); + + initDisplay(); + + while (1) { + updateOLED(); + } +} diff --git a/hosts/daisy/examples/bluemchen/calibrator/Makefile b/hosts/daisy/examples/bluemchen/calibrator/Makefile new file mode 100644 index 0000000..ddd44de --- /dev/null +++ b/hosts/daisy/examples/bluemchen/calibrator/Makefile @@ -0,0 +1,20 @@ +# Project Name +TARGET ?= signaletic-bluemchen-calibrator + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/bluemchen/calibrator/src/signaletic-bluemchen-calibrator.cpp b/hosts/daisy/examples/bluemchen/calibrator/src/signaletic-bluemchen-calibrator.cpp new file mode 100644 index 0000000..ea7c762 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/calibrator/src/signaletic-bluemchen-calibrator.cpp @@ -0,0 +1,193 @@ +#include +#include "../../../../include/kxmx-bluemchen-device.hpp" + +#define SAMPLERATE 48000 +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +daisy::FixedCapStr<20> displayStr; +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +sig::libdaisy::DaisyHost host; + +struct sig_dsp_ConstantValue* ampScale; +struct sig_host_EncoderIn* encoderIn; +struct sig_dsp_Accumulate* encoderAccumulator; +struct sig_dsp_Branch* leftCalibrationSelector; +struct sig_dsp_Branch* rightCalibrationSelector; +struct sig_host_CVIn* cv1In; +struct sig_host_CVIn* cv2In; +struct sig_dsp_Calibrator* cv1Calibrator; +struct sig_dsp_Calibrator* cv2Calibrator; + +struct sig_dsp_Oscillator* sine1; +struct sig_dsp_LinearToFreq* sine1Voct; +struct sig_dsp_Oscillator* sine2; +struct sig_dsp_LinearToFreq* sine2Voct; + +struct sig_host_AudioOut* audio1Out; +struct sig_host_AudioOut* audio2Out; + +void UpdateOled() { + host.device.display.Fill(false); + + displayStr.Clear(); + displayStr.Append("Calibrator"); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + float sine1Hz = sine1Voct->outputs.main[0]; + float sine2Hz = sine2Voct->outputs.main[0]; + float calibrationSide = (int) encoderAccumulator->outputs.main[0]; + int stage = (int) (calibrationSide == 0.0f ? + cv1Calibrator->stage : cv2Calibrator->stage); + displayStr.Clear(); + displayStr.AppendFloat(sine1Hz, 3); + displayStr.Append("Hz"); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.AppendFloat(sine2Hz, 3); + displayStr.Append("Hz"); + host.device.display.SetCursor(0, 16); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("CV "); + displayStr.AppendInt((int) calibrationSide + 1); + displayStr.Append(" - "); + displayStr.AppendInt(stage); + displayStr.Append("V"); + host.device.display.SetCursor(0, 24); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + host.device.display.Update(); +} + +void buildSignalGraph(struct sig_SignalContext* context, struct sig_Status* status) { + encoderIn = sig_host_EncoderIn_new(&allocator, context); + encoderIn->hardware = &host.device.hardware; + sig_List_append(&signals, encoderIn, status); + encoderIn->parameters.buttonControl = sig_host_BUTTON_1; + encoderIn->parameters.turnControl = sig_host_ENCODER_1; + + encoderAccumulator = sig_dsp_Accumulate_new(&allocator, context); + sig_List_append(&signals, encoderAccumulator, status); + encoderAccumulator->parameters.accumulatorStart = 0.0f; + // Due to a bug in sig_dsp_Accumulate, we have to manually set + // the accumulator state. Signaletic needs dataflow support for + // parameters and connections! + encoderAccumulator->accumulator = + encoderAccumulator->parameters.accumulatorStart; + encoderAccumulator->parameters.maxValue = 1.0f; + encoderAccumulator->parameters.wrap = 1.0f; + encoderAccumulator->inputs.source = encoderIn->outputs.increment; + + leftCalibrationSelector = sig_dsp_Branch_new(&allocator, context); + sig_List_append(&signals, leftCalibrationSelector, status); + leftCalibrationSelector->inputs.condition = + encoderAccumulator->outputs.main; + leftCalibrationSelector->inputs.on = context->silence->outputs.main; + leftCalibrationSelector->inputs.off = encoderIn->outputs.button; + + rightCalibrationSelector = sig_dsp_Branch_new(&allocator, context); + sig_List_append(&signals, rightCalibrationSelector, status); + rightCalibrationSelector->inputs.condition = + encoderAccumulator->outputs.main; + rightCalibrationSelector->inputs.on = encoderIn->outputs.button; + rightCalibrationSelector->inputs.off = context->silence->outputs.main; + + cv1In = sig_host_CVIn_new(&allocator, context); + cv1In->hardware = &host.device.hardware; + sig_List_append(&signals, cv1In, status); + cv1In->parameters.control = sig_host_CV_IN_1; + cv1In->parameters.scale = 5.0f; + + cv1Calibrator = sig_dsp_Calibrator_new(&allocator, context); + sig_List_append(&signals, cv1Calibrator, status); + cv1Calibrator->inputs.source = cv1In->outputs.main; + cv1Calibrator->inputs.gate = leftCalibrationSelector->outputs.main; + + cv2In = sig_host_CVIn_new(&allocator, context); + cv2In->hardware = &host.device.hardware; + sig_List_append(&signals, cv2In, status); + cv2In->parameters.control = sig_host_CV_IN_2; + cv2In->parameters.scale = 5.0f; + + cv2Calibrator = sig_dsp_Calibrator_new(&allocator, context); + sig_List_append(&signals, cv2Calibrator, status); + cv2Calibrator->inputs.source = cv2In->outputs.main; + cv2Calibrator->inputs.gate = rightCalibrationSelector->outputs.main; + + ampScale = sig_dsp_ConstantValue_new(&allocator, context, 0.5f); + + sine1Voct = sig_dsp_LinearToFreq_new(&allocator, context); + sig_List_append(&signals, sine1Voct, status); + sine1Voct->inputs.source = cv1Calibrator->outputs.main; + + sine1 = sig_dsp_Sine_new(&allocator, context); + sig_List_append(&signals, sine1, status); + sine1->inputs.freq = sine1Voct->outputs.main; + sine1->inputs.mul = ampScale->outputs.main; + + audio1Out = sig_host_AudioOut_new(&allocator, context); + audio1Out->hardware = &host.device.hardware; + sig_List_append(&signals, audio1Out, status); + audio1Out->parameters.channel = sig_host_AUDIO_OUT_1; + audio1Out->inputs.source = sine1->outputs.main; + + sine2Voct = sig_dsp_LinearToFreq_new(&allocator, context); + sig_List_append(&signals, sine2Voct, status); + sine2Voct->inputs.source = cv2Calibrator->outputs.main; + + sine2 = sig_dsp_Sine_new(&allocator, context); + sig_List_append(&signals, sine2, status); + sine2->inputs.freq = sine2Voct->outputs.main; + sine2->inputs.mul = ampScale->outputs.main; + + audio2Out = sig_host_AudioOut_new(&allocator, context); + audio2Out->hardware = &host.device.hardware; + sig_List_append(&signals, audio2Out, status); + audio2Out->parameters.channel = sig_host_AUDIO_OUT_2; + audio2Out->inputs.source = sine2->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 48 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + buildSignalGraph(context, &status); + + host.Start(); + + while (1) { + UpdateOled(); + } +} diff --git a/hosts/daisy/examples/bluemchen/chorus/Makefile b/hosts/daisy/examples/bluemchen/chorus/Makefile new file mode 100644 index 0000000..a63f72d --- /dev/null +++ b/hosts/daisy/examples/bluemchen/chorus/Makefile @@ -0,0 +1,20 @@ +# Project Name +TARGET ?= signaletic-bluemchen-chorus + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/bluemchen/chorus/src/signaletic-bluemchen-chorus.cpp b/hosts/daisy/examples/bluemchen/chorus/src/signaletic-bluemchen-chorus.cpp new file mode 100644 index 0000000..c94beb5 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/chorus/src/signaletic-bluemchen-chorus.cpp @@ -0,0 +1,188 @@ +#include +#include "../../../../include/kxmx-bluemchen-device.hpp" + +#define SAMPLERATE 48000 +#define DELAY_LINE_LENGTH 48000 // 1 second +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 32 + +FixedCapStr<20> displayStr; + +float DSY_SDRAM_BSS leftDelayLineSamples[DELAY_LINE_LENGTH]; +struct sig_Buffer leftDelayLineBuffer = { + .length = DELAY_LINE_LENGTH, + .samples = leftDelayLineSamples +}; + +float DSY_SDRAM_BSS rightDelayLineSamples[DELAY_LINE_LENGTH]; +struct sig_Buffer rightDelayLineBuffer = { + .length = DELAY_LINE_LENGTH, + .samples = rightDelayLineSamples +}; + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +sig::libdaisy::DaisyHost host; + +struct sig_host_FilteredCVIn* mixKnob; +struct sig_host_FilteredCVIn* lfoSpeedKnob; +struct sig_dsp_ConstantValue* lfoDepth; +struct sig_dsp_ConstantValue* lfoOffset; +struct sig_host_AudioIn* leftIn; +struct sig_dsp_Oscillator* leftLFO; +struct sig_dsp_Delay* leftDelay; +struct sig_dsp_LinearXFade* leftWetDryMixer; +struct sig_host_AudioOut* leftOut; +struct sig_dsp_ConstantValue* lfoPhaseOffset; +struct sig_dsp_Oscillator* rightLFO; +struct sig_dsp_Delay* rightDelay; +struct sig_dsp_LinearXFade* rightWetDryMixer; +struct sig_host_AudioOut* rightOut; + +void UpdateOled() { + host.device.display.Fill(false); + + displayStr.Clear(); + displayStr.Append("Chorus"); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("Mix "); + displayStr.AppendFloat(mixKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("LFO "); + displayStr.AppendFloat(lfoSpeedKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 16); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + host.device.display.Update(); +} + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + mixKnob = sig_host_FilteredCVIn_new(&allocator, context); + mixKnob->hardware = &host.device.hardware; + sig_List_append(&signals, mixKnob, status); + mixKnob->parameters.control = sig_host_KNOB_1; + mixKnob->parameters.scale = 2.0f; + mixKnob->parameters.offset = -1.0f; + mixKnob->parameters.time = 0.1f; + + // Juno-60 Chorus + // https://github.com/pendragon-andyh/Juno60/blob/master/Chorus/README.md + lfoSpeedKnob = sig_host_FilteredCVIn_new(&allocator, context); + lfoSpeedKnob->hardware = &host.device.hardware; + sig_List_append(&signals, lfoSpeedKnob, status); + lfoSpeedKnob->parameters.control = sig_host_KNOB_2; + // These are in the range of the Juno chorus 1 & and 2 + // lfoSpeedKnob->parameters.scale = 0.35f; + // lfoSpeedKnob->parameters.offset = 0.513f; + // Where as these can go a little wilder. + // 4.0 is about the threshold of consance at full wet. + lfoSpeedKnob->parameters.scale = 9.75f; + lfoSpeedKnob->parameters.time = 0.1f; + + lfoDepth = sig_dsp_ConstantValue_new(&allocator, context, 0.001845f); + lfoOffset = sig_dsp_ConstantValue_new(&allocator, context, 0.003505f); + + leftIn = sig_host_AudioIn_new(&allocator, context); + leftIn->hardware = &host.device.hardware; + sig_List_append(&signals, leftIn, status); + leftIn->parameters.channel = sig_host_AUDIO_IN_1; + + leftLFO = sig_dsp_LFTriangle_new(&allocator, context); + sig_List_append(&signals, leftLFO, status); + leftLFO->inputs.freq = lfoSpeedKnob->outputs.main; + leftLFO->inputs.mul = lfoDepth->outputs.main; + leftLFO->inputs.add = lfoOffset->outputs.main; + + leftDelay = sig_dsp_Delay_new(&allocator, context); + sig_List_append(&signals, leftDelay, status); + leftDelay->delayLine = sig_DelayLine_newWithTransferredBuffer(&allocator, + &leftDelayLineBuffer); + leftDelay->inputs.source = leftIn->outputs.main; + leftDelay->inputs.delayTime = leftLFO->outputs.main; + + leftWetDryMixer = sig_dsp_LinearXFade_new(&allocator, context); + sig_List_append(&signals, leftWetDryMixer, status); + leftWetDryMixer->inputs.left = leftIn->outputs.main; + leftWetDryMixer->inputs.right = leftDelay->outputs.main; + leftWetDryMixer->inputs.mix = mixKnob->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = leftWetDryMixer->outputs.main; + + // The phase of the right LFO is 180 degrees from the left. + lfoPhaseOffset = sig_dsp_ConstantValue_new(&allocator, context, 0.5f); + rightLFO = sig_dsp_LFTriangle_new(&allocator, context); + sig_List_append(&signals, rightLFO, status); + rightLFO->inputs.freq = lfoSpeedKnob->outputs.main; + rightLFO->inputs.phaseOffset = lfoPhaseOffset->outputs.main; + rightLFO->inputs.mul = lfoDepth->outputs.main; + rightLFO->inputs.add = lfoOffset->outputs.main; + + rightDelay = sig_dsp_Delay_new(&allocator, context); + sig_List_append(&signals, rightDelay, status); + rightDelay->delayLine = sig_DelayLine_newWithTransferredBuffer(&allocator, + &rightDelayLineBuffer); + rightDelay->inputs.source = leftIn->outputs.main; + rightDelay->inputs.delayTime = rightLFO->outputs.main; + + rightWetDryMixer = sig_dsp_LinearXFade_new(&allocator, context); + sig_List_append(&signals, rightWetDryMixer, status); + rightWetDryMixer->inputs.left = leftIn->outputs.main; + rightWetDryMixer->inputs.right = rightDelay->outputs.main; + rightWetDryMixer->inputs.mix = mixKnob->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = rightWetDryMixer->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 48 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + buildSignalGraph(context, &status); + + host.Start(); + + while (1) { + UpdateOled(); + } +} diff --git a/hosts/daisy/examples/bluemchen/dusting/Makefile b/hosts/daisy/examples/bluemchen/dusting/Makefile index 8ea49a8..98aa7c4 100644 --- a/hosts/daisy/examples/bluemchen/dusting/Makefile +++ b/hosts/daisy/examples/bluemchen/dusting/Makefile @@ -1,14 +1,14 @@ # Project Name TARGET ?= signaletic-nehcmeulb-dusting -DEBUG = 0 -OPT = -O3 +DEBUG = 1 +OPT = -O0 # Sources -C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include -CPP_SOURCES = ../../../vendor/kxmx_bluemchen/src/kxmx_bluemchen.cpp ../../../src/signaletic-daisy-host.cpp ../../../src/daisy-bluemchen-host.cpp src/${TARGET}.cpp +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp USE_FATFS = 0 diff --git a/hosts/daisy/examples/bluemchen/dusting/src/signaletic-nehcmeulb-dusting.cpp b/hosts/daisy/examples/bluemchen/dusting/src/signaletic-nehcmeulb-dusting.cpp index 9af8929..bda758c 100644 --- a/hosts/daisy/examples/bluemchen/dusting/src/signaletic-nehcmeulb-dusting.cpp +++ b/hosts/daisy/examples/bluemchen/dusting/src/signaletic-nehcmeulb-dusting.cpp @@ -1,12 +1,12 @@ -#include -#include #include -#include "../../../../vendor/kxmx_bluemchen/src/kxmx_bluemchen.h" -#include "../../../../include/daisy-bluemchen-host.h" +#include "../../../../include/kxmx-nehcmeulb-device.hpp" +#define SAMPLERATE 48000 #define HEAP_SIZE 1024 * 256 // 256KB #define MAX_NUM_SIGNALS 32 +FixedCapStr<20> displayStr; + uint8_t memory[HEAP_SIZE]; struct sig_AllocatorHeap heap = { .length = HEAP_SIZE, @@ -18,35 +18,34 @@ struct sig_Allocator allocator = { .heap = &heap }; -kxmx::Bluemchen bluemchen; -daisy::FixedCapStr<20> displayStr; - struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; struct sig_List signals; struct sig_dsp_SignalListEvaluator* evaluator; -struct sig_daisy_Host* host; - -struct sig_daisy_FilteredCVIn* densityKnob; -struct sig_daisy_FilteredCVIn* durationKnob; -struct sig_daisy_AudioIn* clockIn; -struct sig_dsp_ClockFreqDetector* clockFrequency; +sig::libdaisy::DaisyHost host; + +struct sig_host_EncoderIn* tapTempo; +struct sig_host_FilteredCVIn* densityKnob; +struct sig_host_FilteredCVIn* durationKnob; +struct sig_host_CVIn* clockIn; +struct sig_dsp_BinaryOp* clockPulseSum; +struct sig_dsp_ClockDetector* clock; struct sig_dsp_BinaryOp* densityClockSum; struct sig_dsp_DustGate* cvDustGate; struct sig_dsp_BinaryOp* audioDensity; struct sig_dsp_ConstantValue* audioDensityScale; struct sig_dsp_DustGate* audioDustGate; -struct sig_daisy_CVOut* dustCVOut; -struct sig_daisy_CVOut* clockCVOut; -struct sig_daisy_AudioOut* leftAudioOut; -struct sig_daisy_AudioOut* rightAudioOut; +struct sig_host_CVOut* dustCVOut; +struct sig_host_CVOut* clockCVOut; +struct sig_host_AudioOut* leftAudioOut; +struct sig_host_AudioOut* rightAudioOut; void UpdateOled() { - bluemchen.display.Fill(false); + host.device.display.Fill(false); displayStr.Clear(); displayStr.Append(" Dust Dust"); - bluemchen.display.SetCursor(0, 0); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); displayStr.Clear(); float density = densityClockSum->outputs.main[0]; @@ -59,37 +58,57 @@ void UpdateOled() { displayStr.Append(" Hz"); } - bluemchen.display.SetCursor(44, 16); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(44, 16); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); displayStr.Clear(); displayStr.AppendFloat(formatted, 0); - bluemchen.display.SetCursor(0, 8); - bluemchen.display.WriteString(displayStr.Cstr(), Font_11x18, true); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_11x18, true); + + float gateState = cvDustGate->outputs.main[0]; + if (gateState > 0.0f) { + displayStr.Clear(); + displayStr.Append("o"); + host.device.display.SetCursor(28, 24); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + } - bluemchen.display.Update(); + host.device.display.Update(); } void buildGraph(struct sig_SignalContext* context, struct sig_Status* status) { - densityKnob = sig_daisy_FilteredCVIn_new(&allocator, context, host); + tapTempo = sig_host_EncoderIn_new(&allocator, context); + tapTempo->hardware = &host.device.hardware; + sig_List_append(&signals, tapTempo, status); + + densityKnob = sig_host_FilteredCVIn_new(&allocator, context); + densityKnob->hardware = &host.device.hardware; sig_List_append(&signals, densityKnob, status); - densityKnob->parameters.control = sig_daisy_Nehcmeulb_CV_IN_KNOB1; - densityKnob->parameters.scale = 20.0f; + densityKnob->parameters.control = sig_host_KNOB_1; + densityKnob->parameters.scale = 10.0f; - durationKnob = sig_daisy_FilteredCVIn_new(&allocator, context, host); + durationKnob = sig_host_FilteredCVIn_new(&allocator, context); + durationKnob->hardware = &host.device.hardware; sig_List_append(&signals, durationKnob, status); - durationKnob->parameters.control = sig_daisy_Nehcmeulb_CV_IN_KNOB2; + durationKnob->parameters.control = sig_host_KNOB_2; - // TODO: Does the Nehcmeulb actually split the input to both the - // CV and audio input pins on the Daisy? If so, we should read clock - // signals from the CV input instead of audio. - clockIn = sig_daisy_AudioIn_new(&allocator, context, host); + // Nehcmeulb splits the input coming from the audio in jacks + // to both the ADC and the audio codec inputs. + // Here, we want to read DC pulses so the CV input is the right place. + clockIn = sig_host_CVIn_new(&allocator, context); + clockIn->hardware = &host.device.hardware; sig_List_append(&signals, clockIn, status); - clockIn->parameters.channel = sig_daisy_AUDIO_IN_1; + clockIn->parameters.control = sig_host_CV_IN_1; + + clockPulseSum = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, clockPulseSum, status); + clockPulseSum->inputs.left = tapTempo->outputs.button; + clockPulseSum->inputs.right = clockIn->outputs.main; - clockFrequency = sig_dsp_ClockFreqDetector_new(&allocator, context); - sig_List_append(&signals, clockFrequency, status); - clockFrequency->inputs.source = clockIn->outputs.main; + clock = sig_dsp_ClockDetector_new(&allocator, context); + sig_List_append(&signals, clock, status); + clock->inputs.source = clockPulseSum->outputs.main; // TODO: Add a CV input for controlling density, // which is centred on 0.0 and allows for negative values @@ -97,7 +116,7 @@ void buildGraph(struct sig_SignalContext* context, struct sig_Status* status) { densityClockSum = sig_dsp_Add_new(&allocator, context); sig_List_append(&signals, densityClockSum, status); - densityClockSum->inputs.left = clockFrequency->outputs.main; + densityClockSum->inputs.left = clock->outputs.main; densityClockSum->inputs.right = densityKnob->outputs.main; cvDustGate = sig_dsp_DustGate_new(&allocator, context); @@ -105,16 +124,18 @@ void buildGraph(struct sig_SignalContext* context, struct sig_Status* status) { cvDustGate->inputs.density = densityClockSum->outputs.main; cvDustGate->inputs.durationPercentage = durationKnob->outputs.main; - dustCVOut = sig_daisy_CVOut_new(&allocator, context, host); + dustCVOut = sig_host_CVOut_new(&allocator, context); + dustCVOut->hardware = &host.device.hardware; sig_List_append(&signals, dustCVOut, status); - dustCVOut->parameters.control = sig_daisy_Nehcmeulb_CV_OUT_1; + dustCVOut->parameters.control = sig_host_CV_OUT_1; dustCVOut->inputs.source = cvDustGate->outputs.main; - // TODO: Output a regular pulse wave at the densityClockSum frequency + // TODO: Output an envelope on each trigger on CV_OUT_2 - clockCVOut = sig_daisy_CVOut_new(&allocator, context, host); + clockCVOut = sig_host_CVOut_new(&allocator, context); + clockCVOut->hardware = &host.device.hardware; sig_List_append(&signals, clockCVOut, status); - clockCVOut->parameters.control = sig_daisy_Nehcmeulb_CV_OUT_2; + clockCVOut->parameters.control = sig_host_CV_OUT_2; clockCVOut->inputs.source = cvDustGate->outputs.main; audioDensityScale = sig_dsp_ConstantValue_new(&allocator, context, 200.0f); @@ -130,14 +151,16 @@ void buildGraph(struct sig_SignalContext* context, struct sig_Status* status) { audioDustGate->inputs.density = audioDensity->outputs.main; audioDustGate->inputs.durationPercentage = durationKnob->outputs.main; - leftAudioOut = sig_daisy_AudioOut_new(&allocator, context, host); + leftAudioOut = sig_host_AudioOut_new(&allocator, context); + leftAudioOut->hardware = &host.device.hardware; sig_List_append(&signals, leftAudioOut, status); - leftAudioOut->parameters.channel = 0; + leftAudioOut->parameters.channel = sig_host_AUDIO_OUT_1; leftAudioOut->inputs.source = audioDustGate->outputs.main; - rightAudioOut = sig_daisy_AudioOut_new(&allocator, context, host); + rightAudioOut = sig_host_AudioOut_new(&allocator, context); + rightAudioOut->hardware = &host.device.hardware; sig_List_append(&signals, rightAudioOut, status); - rightAudioOut->parameters.channel = 1; + rightAudioOut->parameters.channel = sig_host_AUDIO_OUT_2; rightAudioOut->inputs.source = audioDustGate->outputs.main; } @@ -149,22 +172,19 @@ int main(void) { sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); struct sig_AudioSettings audioSettings = { - .sampleRate = 48000, + .sampleRate = SAMPLERATE, .numChannels = 2, - .blockSize = 2 + .blockSize = 48 }; struct sig_SignalContext* context = sig_SignalContext_new(&allocator, &audioSettings); evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); - host = sig_daisy_NehcmeulbHost_new(&allocator, - &audioSettings, - &bluemchen, - (struct sig_dsp_SignalEvaluator*) evaluator); - sig_daisy_Host_registerGlobalHost(host); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + buildGraph(context, &status); - host->impl->start(host); + host.Start(); while (1) { UpdateOled(); diff --git a/hosts/daisy/examples/bluemchen/filter/Makefile b/hosts/daisy/examples/bluemchen/filter/Makefile index 89c889a..d911794 100644 --- a/hosts/daisy/examples/bluemchen/filter/Makefile +++ b/hosts/daisy/examples/bluemchen/filter/Makefile @@ -5,10 +5,10 @@ DEBUG = 0 OPT = -O3 # Sources -C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include -CPP_SOURCES = ../../../vendor/kxmx_bluemchen/src/kxmx_bluemchen.cpp ../../../src/signaletic-daisy-host.cpp ../../../src/daisy-bluemchen-host.cpp src/${TARGET}.cpp +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp USE_FATFS = 0 diff --git a/hosts/daisy/examples/bluemchen/filter/src/signaletic-bluemchen-filter.cpp b/hosts/daisy/examples/bluemchen/filter/src/signaletic-bluemchen-filter.cpp index a7725ef..cada036 100644 --- a/hosts/daisy/examples/bluemchen/filter/src/signaletic-bluemchen-filter.cpp +++ b/hosts/daisy/examples/bluemchen/filter/src/signaletic-bluemchen-filter.cpp @@ -1,48 +1,27 @@ -#include -#include #include -#include "../../../../include/daisy-bluemchen-host.h" - -using namespace kxmx; -using namespace daisy; +#include "../../../../include/kxmx-bluemchen-device.hpp" FixedCapStr<20> displayStr; -#define HEAP_SIZE 1024 * 256 // 256KB -#define MAX_NUM_SIGNALS 32 - -uint8_t memory[HEAP_SIZE]; -struct sig_AllocatorHeap heap = { - .length = HEAP_SIZE, - .memory = (void*) memory -}; - -struct sig_Allocator allocator = { - .impl = &sig_TLSFAllocatorImpl, - .heap = &heap -}; - -struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; -struct sig_List signals; -struct sig_dsp_SignalListEvaluator* evaluator; - -Bluemchen bluemchen; -struct sig_daisy_Host* host; +#define SAMPLERATE 96000 +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 64 -#define NUM_FILTER_MODES 8 +// The best, with band passes +#define NUM_FILTER_MODES 9 #define NUM_FILTER_STAGES 5 - float mixingCoefficients[NUM_FILTER_STAGES][NUM_FILTER_MODES] = { - // 4LP, 3LP, 2LP, 2BP, 4BP, 4HP, 2HP, 4APP. - { 0, 0, 0, 0, 0, 1, 1, 1}, // Input gain (A) - { 0, 0, 0, -1, 0, -4, -2, -4}, // Pole 1 (B) - { 0, 0, 1, 1, 1, 6, 1, 12}, // Pole 2 (C) - { 0, -1, 0, 0, -2, -4, 0, -16}, // Pole 3 (D) - { 1, 0, 0, 0, 1, 1, 0, 8} // Pole 4 (E) + // 4LP, 2LP, 2BP, 4-1LP, ??, 4APP, 2HP, 3HP, 4HP + { 0, 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, -1, -1, -2, -4, -2, -3, -4 }, + { 0, 1, 1, 0, 2, 12, 1, 3, 6 }, + { 0, 0, 0, 0, -4, -16, 0, -1, -4 }, + { 1, 0, 0, 1, 4, 8, 0, 0, 1 } }; -const char filterModeStrings[NUM_FILTER_MODES][4] = { - "4LP", "3LP", "2LP", "2BP", "4BP", "4HP", "2HP", "PHA" +// TODO: Better names for these modes. +const char filterModeStrings[NUM_FILTER_MODES][5] = { + "4LP", "2LP", "2BP", "41LP", "??", "4APP", "2HP", "3HP", "4HP" }; struct sig_Buffer aCoefficientBuffer = { @@ -70,7 +49,23 @@ struct sig_Buffer eCoefficientBuffer = { .samples = mixingCoefficients[4] }; -struct sig_daisy_EncoderIn* encoderIn; +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +sig::libdaisy::DaisyHost host; + +struct sig_host_EncoderIn* encoderIn; struct sig_dsp_List* aList; struct sig_dsp_Smooth* aSmooth; struct sig_dsp_List* bList; @@ -81,10 +76,11 @@ struct sig_dsp_List* dList; struct sig_dsp_Smooth* dSmooth; struct sig_dsp_List* eList; struct sig_dsp_Smooth* eSmooth; -struct sig_daisy_FilteredCVIn* frequencyKnob; -struct sig_daisy_FilteredCVIn* resonanceKnob; -struct sig_daisy_FilteredCVIn* vOctCVIn; -struct sig_daisy_FilteredCVIn* frequencySkewCV; +struct sig_host_FilteredCVIn* frequencyKnob; +struct sig_host_FilteredCVIn* resonanceKnob; +struct sig_host_CVIn* vOctCVIn; +struct sig_dsp_Calibrator* vOctCalibrator; +struct sig_host_FilteredCVIn* skewCV; struct sig_dsp_Abs* rectifiedSkew; struct sig_dsp_BinaryOp* frequencyCVSkewAdder; struct sig_dsp_BinaryOp* frequencyKnobPlusVOct; @@ -92,53 +88,55 @@ struct sig_dsp_Branch* leftFrequencyCVSkewed; struct sig_dsp_LinearToFreq* leftFrequency; struct sig_dsp_Branch* rightFrequencyCVSkewed; struct sig_dsp_LinearToFreq* rightFrequency; -struct sig_daisy_AudioIn* leftIn; -struct sig_daisy_AudioIn* rightIn; +struct sig_host_AudioIn* leftIn; +struct sig_host_AudioIn* rightIn; struct sig_dsp_Ladder* leftFilter; struct sig_dsp_Ladder* rightFilter; struct sig_dsp_Tanh* leftSaturation; struct sig_dsp_Tanh* rightSaturation; -struct sig_daisy_AudioOut* leftOut; -struct sig_daisy_AudioOut* rightOut; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; void UpdateOled() { - bluemchen.display.Fill(false); + host.device.display.Fill(false); displayStr.Clear(); displayStr.Append(" Bifocals"); - bluemchen.display.SetCursor(0, 0); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); displayStr.Clear(); displayStr.Append("F: "); displayStr.AppendFloat(leftFrequency->outputs.main[0], 0); - bluemchen.display.SetCursor(0, 8); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); displayStr.Clear(); displayStr.Append("Hz"); - bluemchen.display.SetCursor(47, 8); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(47, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); displayStr.Clear(); displayStr.Append("R: "); displayStr.AppendFloat(resonanceKnob->outputs.main[0], 2); - bluemchen.display.SetCursor(0, 16); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(0, 16); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); displayStr.Clear(); displayStr.Append(filterModeStrings[(size_t) aList->outputs.index[0]]); - bluemchen.display.SetCursor(0, 24); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(0, 24); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); - bluemchen.display.Update(); + host.device.display.Update(); } void buildSignalGraph(struct sig_SignalContext* context, struct sig_Status* status) { - encoderIn = sig_daisy_EncoderIn_new(&allocator, context, host); + encoderIn = sig_host_EncoderIn_new(&allocator, context); + encoderIn->hardware = &host.device.hardware; sig_List_append(&signals, encoderIn, status); - encoderIn->parameters.control = 0; + encoderIn->parameters.turnControl = sig_host_ENCODER_1; + encoderIn->parameters.buttonControl = sig_host_BUTTON_1; aList = sig_dsp_List_new(&allocator, context); sig_List_append(&signals, aList, status); @@ -202,35 +200,40 @@ void buildSignalGraph(struct sig_SignalContext* context, // Bluemchen AnalogControls are all unipolar, // so they need to be scaled to bipolar values. - frequencyKnob = sig_daisy_FilteredCVIn_new(&allocator, context, host); + frequencyKnob = sig_host_FilteredCVIn_new(&allocator, context); + frequencyKnob->hardware = &host.device.hardware; sig_List_append(&signals, frequencyKnob, status); - frequencyKnob->parameters.control = sig_daisy_Bluemchen_CV_IN_KNOB_1; + frequencyKnob->parameters.control = sig_host_KNOB_1; frequencyKnob->parameters.scale = 10.0f; frequencyKnob->parameters.offset = -5.0f; frequencyKnob->parameters.time = 0.1f; - vOctCVIn = sig_daisy_FilteredCVIn_new(&allocator, context, host); + vOctCVIn = sig_host_CVIn_new(&allocator, context); + vOctCVIn->hardware = &host.device.hardware; sig_List_append(&signals, vOctCVIn, status); - vOctCVIn->parameters.control = sig_daisy_Bluemchen_CV_IN_CV1; - vOctCVIn->parameters.scale = 4.0f; - vOctCVIn->parameters.offset = -2.0f; - vOctCVIn->parameters.time = 0.1f; + vOctCVIn->parameters.control = sig_host_CV_IN_1; + vOctCVIn->parameters.scale = 5.0f; + + vOctCalibrator = sig_dsp_Calibrator_new(&allocator, context); + sig_List_append(&signals, vOctCalibrator, status); + vOctCalibrator->inputs.source = vOctCVIn->outputs.main; + vOctCalibrator->inputs.gate = encoderIn->outputs.button; frequencyKnobPlusVOct = sig_dsp_Add_new(&allocator, context); sig_List_append(&signals, frequencyKnobPlusVOct, status); frequencyKnobPlusVOct->inputs.left = frequencyKnob->outputs.main; - frequencyKnobPlusVOct->inputs.right = vOctCVIn->outputs.main; + frequencyKnobPlusVOct->inputs.right = vOctCalibrator->outputs.main; - frequencySkewCV = sig_daisy_FilteredCVIn_new(&allocator, context, host); - sig_List_append(&signals, frequencySkewCV, status); - frequencySkewCV->parameters.control = sig_daisy_Bluemchen_CV_IN_CV2; - frequencySkewCV->parameters.scale = 5.0f; - frequencySkewCV->parameters.offset = -2.5f; - frequencySkewCV->parameters.time = 0.1f; + skewCV = sig_host_FilteredCVIn_new(&allocator, context); + skewCV->hardware = &host.device.hardware; + sig_List_append(&signals, skewCV, status); + skewCV->parameters.control = sig_host_CV_IN_2; + skewCV->parameters.scale = 2.5f; + skewCV->parameters.time = 0.1f; rectifiedSkew = sig_dsp_Abs_new(&allocator, context); sig_List_append(&signals, rectifiedSkew, status); - rectifiedSkew->inputs.source = frequencySkewCV->outputs.main; + rectifiedSkew->inputs.source = skewCV->outputs.main; frequencyCVSkewAdder = sig_dsp_Add_new(&allocator, context); sig_List_append(&signals, frequencyCVSkewAdder, status); @@ -239,7 +242,7 @@ void buildSignalGraph(struct sig_SignalContext* context, leftFrequencyCVSkewed = sig_dsp_Branch_new(&allocator, context); sig_List_append(&signals, leftFrequencyCVSkewed, status); - leftFrequencyCVSkewed->inputs.condition = frequencySkewCV->outputs.main; + leftFrequencyCVSkewed->inputs.condition = skewCV->outputs.main; leftFrequencyCVSkewed->inputs.on = frequencyKnobPlusVOct->outputs.main; leftFrequencyCVSkewed->inputs.off = frequencyCVSkewAdder->outputs.main; @@ -249,7 +252,7 @@ void buildSignalGraph(struct sig_SignalContext* context, rightFrequencyCVSkewed = sig_dsp_Branch_new(&allocator, context); sig_List_append(&signals, rightFrequencyCVSkewed, status); - rightFrequencyCVSkewed->inputs.condition = frequencySkewCV->outputs.main; + rightFrequencyCVSkewed->inputs.condition = skewCV->outputs.main; rightFrequencyCVSkewed->inputs.on = frequencyCVSkewAdder->outputs.main; rightFrequencyCVSkewed->inputs.off = frequencyKnobPlusVOct->outputs.main; @@ -257,18 +260,19 @@ void buildSignalGraph(struct sig_SignalContext* context, sig_List_append(&signals, rightFrequency, status); rightFrequency->inputs.source = rightFrequencyCVSkewed->outputs.main; - resonanceKnob = sig_daisy_FilteredCVIn_new(&allocator, context, host); + resonanceKnob = sig_host_FilteredCVIn_new(&allocator, context); + resonanceKnob->hardware = &host.device.hardware; sig_List_append(&signals, resonanceKnob, status); - resonanceKnob->parameters.control = sig_daisy_Bluemchen_CV_IN_KNOB_2; + resonanceKnob->parameters.control = sig_host_KNOB_2; resonanceKnob->parameters.scale = 1.8f; // 4.0f for Bob - leftIn = sig_daisy_AudioIn_new(&allocator, context, host); + leftIn = sig_host_AudioIn_new(&allocator, context); + leftIn->hardware = &host.device.hardware; sig_List_append(&signals, leftIn, status); leftIn->parameters.channel = 0; leftFilter = sig_dsp_Ladder_new(&allocator, context); sig_List_append(&signals, leftFilter, status); - leftFilter->parameters.overdrive = 1.1f; leftFilter->parameters.passbandGain = 0.5f; leftFilter->inputs.source = leftIn->outputs.main; leftFilter->inputs.frequency = leftFrequency->outputs.main; @@ -283,18 +287,19 @@ void buildSignalGraph(struct sig_SignalContext* context, sig_List_append(&signals, leftSaturation, status); leftSaturation->inputs.source = leftFilter->outputs.main; - leftOut = sig_daisy_AudioOut_new(&allocator, context, host); + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; sig_List_append(&signals, leftOut, status); leftOut->parameters.channel = 0; leftOut->inputs.source = leftSaturation->outputs.main; - rightIn = sig_daisy_AudioIn_new(&allocator, context, host); + rightIn = sig_host_AudioIn_new(&allocator, context); + rightIn->hardware = &host.device.hardware; sig_List_append(&signals, rightIn, status); rightIn->parameters.channel = 1; rightFilter = sig_dsp_Ladder_new(&allocator, context); sig_List_append(&signals, rightFilter, status); - rightFilter->parameters.overdrive = 1.1f; leftFilter->parameters.passbandGain = 0.5f; rightFilter->inputs.source = rightIn->outputs.main; rightFilter->inputs.frequency = rightFrequency->outputs.main; @@ -309,7 +314,8 @@ void buildSignalGraph(struct sig_SignalContext* context, sig_List_append(&signals, rightSaturation, status); rightSaturation->inputs.source = rightFilter->outputs.main; - rightOut = sig_daisy_AudioOut_new(&allocator, context, host); + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; sig_List_append(&signals, rightOut, status); rightOut->parameters.channel = 1; rightOut->inputs.source = rightSaturation->outputs.main; @@ -319,26 +325,23 @@ int main(void) { allocator.impl->init(&allocator); struct sig_AudioSettings audioSettings = { - .sampleRate = 96000, + .sampleRate = SAMPLERATE, .numChannels = 2, - .blockSize = 16 + .blockSize = 2 }; struct sig_Status status; sig_Status_init(&status); sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); - evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); - host = sig_daisy_BluemchenHost_new(&allocator, - &audioSettings, - &bluemchen, - (struct sig_dsp_SignalEvaluator*) evaluator); - sig_daisy_Host_registerGlobalHost(host); - struct sig_SignalContext* context = sig_SignalContext_new(&allocator, &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + buildSignalGraph(context, &status); - host->impl->start(host); + + host.Start(); while (1) { UpdateOled(); diff --git a/hosts/daisy/examples/bluemchen/looper/Makefile b/hosts/daisy/examples/bluemchen/looper/Makefile index 941d688..646de78 100644 --- a/hosts/daisy/examples/bluemchen/looper/Makefile +++ b/hosts/daisy/examples/bluemchen/looper/Makefile @@ -5,11 +5,11 @@ DEBUG = 0 OPT = -O3 # Sources -C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp CPP_INCLUDES += -I../../src/include -CPP_SOURCES = ../../../vendor/kxmx_bluemchen/src/kxmx_bluemchen.cpp src/${TARGET}.cpp USE_FATFS = 1 diff --git a/hosts/daisy/examples/bluemchen/looper/src/signaletic-bluemchen-looper.cpp b/hosts/daisy/examples/bluemchen/looper/src/signaletic-bluemchen-looper.cpp index 4e22259..c2b44ea 100644 --- a/hosts/daisy/examples/bluemchen/looper/src/signaletic-bluemchen-looper.cpp +++ b/hosts/daisy/examples/bluemchen/looper/src/signaletic-bluemchen-looper.cpp @@ -1,33 +1,13 @@ -#include "../../../../vendor/kxmx_bluemchen/src/kxmx_bluemchen.h" -#include #include +#include "../../../../include/kxmx-bluemchen-device.hpp" #include "../../../../include/looper-view.h" #include -using namespace kxmx; -using namespace daisy; - -Bluemchen bluemchen; -Parameter startKnob; -Parameter endKnob; -Parameter speedCV; -Parameter skewCV; -Parameter recordGateCV; - -#define SIGNAL_HEAP_SIZE 1024 * 384 -char signalMemory[SIGNAL_HEAP_SIZE]; -struct sig_AllocatorHeap heap = { - .length = SIGNAL_HEAP_SIZE, - .memory = (void*) signalMemory -}; - -struct sig_Allocator allocator = { - .impl = &sig_TLSFAllocatorImpl, - .heap = &heap -}; - +#define SAMPLERATE 48000 #define LOOP_TIME_SECS 60 -#define LOOP_LENGTH 48000 * LOOP_TIME_SECS +#define LOOP_LENGTH SAMPLERATE * LOOP_TIME_SECS +#define SIGNAL_HEAP_SIZE 1024 * 384 +#define MAX_NUM_SIGNALS 32 #define LONG_ENCODER_PRESS 2.0f float DSY_SDRAM_BSS leftSamples[LOOP_LENGTH]; @@ -41,259 +21,234 @@ struct sig_Buffer rightBuffer = { .samples = rightSamples }; -struct sig_dsp_Value* start; -struct sig_dsp_Smooth* startSmoother; -struct sig_dsp_Value* end; -struct sig_dsp_Smooth* endSmoother; -struct sig_dsp_Value* speedIncrement; -struct sig_dsp_Accumulate* speedControl; -struct sig_dsp_Value* speedMod; -struct sig_dsp_Smooth* speedModSmoother; +char signalMemory[SIGNAL_HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = SIGNAL_HEAP_SIZE, + .memory = (void*) signalMemory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +sig::libdaisy::DaisyHost host; +struct sig_ui_daisy_LooperView looperView; + +struct sig_host_EncoderIn* encoder; +struct sig_dsp_ConstantValue* encoderSpeedScale; +struct sig_dsp_BinaryOp* encoderSpeedIncrement; +struct sig_dsp_Accumulate* speedAccumulator; +struct sig_host_FilteredCVIn* startKnob; +struct sig_host_FilteredCVIn* endKnob; +struct sig_host_FilteredCVIn* speedCV; +struct sig_host_FilteredCVIn* skewCV; struct sig_dsp_BinaryOp* speedAdder; -struct sig_dsp_Value* speedSkew; -struct sig_dsp_Smooth* speedSkewSmoother; struct sig_dsp_Invert* leftSpeedSkewInverter; -struct sig_dsp_BinaryOp* leftSpeedAdder; -struct sig_dsp_BinaryOp* rightSpeedAdder; -struct sig_dsp_Value* encoderButton; +struct sig_dsp_BinaryOp* leftSpeed; +struct sig_dsp_BinaryOp* rightSpeed; +struct sig_dsp_ConstantValue* tapDuration; +struct sig_dsp_ConstantValue* longPressDuration; struct sig_dsp_TimedTriggerCounter* encoderTap; struct sig_dsp_GatedTimer* encoderLongPress; struct sig_dsp_ToggleGate* recordGate; +struct sig_host_AudioIn* leftIn; +struct sig_host_AudioIn* rightIn; struct sig_dsp_Looper* leftLooper; struct sig_dsp_Looper* rightLooper; struct sig_dsp_BinaryOp* leftGain; struct sig_dsp_BinaryOp* rightGain; - -struct sig_ui_daisy_LooperView looperView; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; void UpdateOled() { + // Note: this is required until we have something that + // offers some way to get the current value from a Signal. + // See https://github.com/continuing-creativity/signaletic/issues/19 + size_t lastSampIdx = leftSpeed->signal.audioSettings->blockSize - 1; + looperView.leftSpeed = leftSpeed->outputs.main[lastSampIdx]; + looperView.rightSpeed = rightSpeed->outputs.main[lastSampIdx]; + bool foregroundOn = !recordGate->isGateOpen; - bluemchen.display.Fill(!foregroundOn); + host.device.display.Fill(!foregroundOn); sig_ui_daisy_LooperView_render(&looperView, - startSmoother->previousSample, - endSmoother->previousSample, + startKnob->filter->previousSample, + endKnob->filter->previousSample, leftLooper->playbackPos, foregroundOn); - bluemchen.display.Update(); + host.device.display.Update(); } -void UpdateControls() { - bluemchen.encoder.Debounce(); - startKnob.Process(); - endKnob.Process(); - speedCV.Process(); - skewCV.Process(); -} - -void AudioCallback(daisy::AudioHandle::InputBuffer in, - daisy::AudioHandle::OutputBuffer out, size_t size) { - UpdateControls(); - - float encoderPressed = bluemchen.encoder.Pressed() ? 1.0f : 0.0f; - - // Bind control values to Signals. - // TODO: These should be handled by Host-provided Signals - // for knob inputs, CV, and the encoder's various parameters. - // https://github.com/continuing-creativity/signaletic/issues/22 - start->parameters.value = startKnob.Value(); - end->parameters.value = endKnob.Value(); - speedIncrement->parameters.value = bluemchen.encoder.Increment() * - 0.01; - encoderButton->parameters.value = encoderPressed; - speedMod->parameters.value = speedCV.Value(); - speedSkew->parameters.value = skewCV.Value(); - - start->signal.generate(start); - startSmoother->signal.generate(startSmoother); - end->signal.generate(end); - endSmoother->signal.generate(endSmoother); - speedIncrement->signal.generate(speedIncrement); - speedControl->signal.generate(speedControl); - speedMod->signal.generate(speedMod); - speedModSmoother->signal.generate(speedModSmoother); - speedAdder->signal.generate(speedAdder); - speedSkew->signal.generate(speedSkew); - speedSkewSmoother->signal.generate(speedSkewSmoother); - encoderButton->signal.generate(encoderButton); - encoderTap->signal.generate(encoderTap); - encoderLongPress->signal.generate(encoderLongPress); - recordGate->signal.generate(recordGate); - - // TODO: Need a host-provided Signal to do this. - // https://github.com/continuing-creativity/signaletic/issues/22 - for (size_t i = 0; i < size; i++) { - leftLooper->inputs.source[i] = in[0][i]; - rightLooper->inputs.source[i] = in[1][i]; - } - - leftSpeedSkewInverter->signal.generate(leftSpeedSkewInverter); - leftSpeedAdder->signal.generate(leftSpeedAdder); - leftLooper->signal.generate(leftLooper); - leftGain->signal.generate(leftGain); - - rightSpeedAdder->signal.generate(rightSpeedAdder); - rightLooper->signal.generate(rightLooper); - rightGain->signal.generate(rightGain); - - // Copy mono buffer to stereo output. - for (size_t i = 0; i < size; i++) { - out[0][i] = leftGain->outputs.main[i]; - out[1][i] = rightGain->outputs.main[i]; - } - - // Note: this is required until we have something that - // offers some way to get the current value from a Signal. - // See https://github.com/continuing-creativity/signaletic/issues/19 - size_t lastSamp = leftSpeedAdder->signal.audioSettings->blockSize - 1; - looperView.leftSpeed = leftSpeedAdder->outputs.main[lastSamp]; - looperView.rightSpeed = rightSpeedAdder->outputs.main[lastSamp]; -} - -void initControls() { - startKnob.Init(bluemchen.controls[bluemchen.CTRL_1], - 0.0f, 1.0f, Parameter::LINEAR); - endKnob.Init(bluemchen.controls[bluemchen.CTRL_2], - 0.0f, 1.0f, Parameter::LINEAR); - speedCV.Init(bluemchen.controls[bluemchen.CTRL_3], - -2.0f, 2.0f, Parameter::LINEAR); - skewCV.Init(bluemchen.controls[bluemchen.CTRL_4], - -0.5f, 0.5f, Parameter::LINEAR); -} - -int main(void) { - bluemchen.Init(); - allocator.impl->init(&allocator); - - struct sig_AudioSettings audioSettings = { - .sampleRate = bluemchen.AudioSampleRate(), - .numChannels = 1, - .blockSize = 48 - }; - - struct sig_SignalContext* context = sig_SignalContext_new(&allocator, - &audioSettings); - - bluemchen.SetAudioBlockSize(audioSettings.blockSize); - bluemchen.StartAdc(); - initControls(); - - start = sig_dsp_Value_new(&allocator, context); - start->parameters.value = 0.0f; - - startSmoother = sig_dsp_Smooth_new(&allocator, context); - startSmoother->parameters.time = 0.01f; - startSmoother->inputs.source = start->outputs.main; - - end = sig_dsp_Value_new(&allocator, context); - end->parameters.value = 1.0f; - - endSmoother = sig_dsp_Smooth_new(&allocator, context); - endSmoother->parameters.time = 0.01f; - endSmoother->inputs.source = end->outputs.main; - - speedIncrement = sig_dsp_Value_new(&allocator, context); - speedIncrement->parameters.value = 1.0f; - - speedControl = sig_dsp_Accumulate_new(&allocator, context); - speedControl->parameters.accumulatorStart = 1.0f; - speedControl->inputs.source = speedIncrement->outputs.main; - - speedMod = sig_dsp_Value_new(&allocator, context); - speedMod->parameters.value = 0.0f; - - speedModSmoother = sig_dsp_Smooth_new(&allocator, context); - speedModSmoother->parameters.time = 0.01f; - speedModSmoother->inputs.source = speedMod->outputs.main; +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + startKnob = sig_host_FilteredCVIn_new(&allocator, context); + startKnob->hardware = &host.device.hardware; + sig_List_append(&signals, startKnob, status); + startKnob->parameters.control = sig_host_KNOB_1; + + endKnob = sig_host_FilteredCVIn_new(&allocator, context); + endKnob->hardware = &host.device.hardware; + sig_List_append(&signals, endKnob, status); + endKnob->parameters.control = sig_host_KNOB_2; + + encoder = sig_host_EncoderIn_new(&allocator, context); + encoder->hardware = &host.device.hardware; + sig_List_append(&signals, encoder, status); + encoder->parameters.turnControl = sig_host_ENCODER_1; + encoder->parameters.buttonControl = sig_host_BUTTON_1; + + encoderSpeedScale = sig_dsp_ConstantValue_new(&allocator, context, 0.01f); + + encoderSpeedIncrement = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, encoderSpeedIncrement, status); + encoderSpeedIncrement->inputs.left = encoder->outputs.increment; + encoderSpeedIncrement->inputs.right = encoderSpeedScale->outputs.main; + + speedAccumulator = sig_dsp_Accumulate_new(&allocator, context); + sig_List_append(&signals, speedAccumulator, status); + speedAccumulator->parameters.accumulatorStart = 1.0f; + speedAccumulator->inputs.source = encoderSpeedIncrement->outputs.main; + + speedCV = sig_host_FilteredCVIn_new(&allocator, context); + speedCV->hardware = &host.device.hardware; + sig_List_append(&signals, speedCV, status); + speedCV->parameters.control = sig_host_CV_IN_1; + speedCV->parameters.scale = 2.0f; speedAdder = sig_dsp_Add_new(&allocator, context); - speedAdder->inputs.left = speedControl->outputs.main; - speedAdder->inputs.right = speedModSmoother->outputs.main; + sig_List_append(&signals, speedAdder, status); + speedAdder->inputs.left = speedAccumulator->outputs.main; + speedAdder->inputs.right = speedCV->outputs.main; - speedSkew = sig_dsp_Value_new(&allocator, context); - speedSkew->parameters.value = 0.0f; - - speedSkewSmoother = sig_dsp_Smooth_new(&allocator, context); - speedSkewSmoother->parameters.time = 0.01f; - speedSkewSmoother->inputs.source = speedSkew->outputs.main; + skewCV = sig_host_FilteredCVIn_new(&allocator, context); + skewCV->hardware = &host.device.hardware; + sig_List_append(&signals, skewCV, status); + skewCV->parameters.control = sig_host_CV_IN_2; + skewCV->parameters.scale = 0.5f; leftSpeedSkewInverter = sig_dsp_Invert_new(&allocator, context); - leftSpeedSkewInverter->inputs.source = speedSkewSmoother->outputs.main; + sig_List_append(&signals, leftSpeedSkewInverter, status); + leftSpeedSkewInverter->inputs.source = skewCV->outputs.main; - leftSpeedAdder = sig_dsp_Add_new(&allocator, context); - leftSpeedAdder->inputs.left = speedAdder->outputs.main; - leftSpeedAdder->inputs.right = leftSpeedSkewInverter->outputs.main; + leftSpeed = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, leftSpeed, status); + leftSpeed->inputs.left = speedAdder->outputs.main; + leftSpeed->inputs.right = leftSpeedSkewInverter->outputs.main; - rightSpeedAdder = sig_dsp_Add_new(&allocator, context); - rightSpeedAdder->inputs.left = speedAdder->outputs.main; - rightSpeedAdder->inputs.right = speedSkewSmoother->outputs.main; + rightSpeed = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, rightSpeed, status); + rightSpeed->inputs.left = speedAdder->outputs.main; + rightSpeed->inputs.right = skewCV->outputs.main; - encoderButton = sig_dsp_Value_new(&allocator, context); - encoderButton->parameters.value = 0.0f; + tapDuration = sig_dsp_ConstantValue_new(&allocator, context, 0.5f); encoderTap = sig_dsp_TimedTriggerCounter_new(&allocator, context); - encoderTap->inputs.source = encoderButton->outputs.main; - encoderTap->inputs.duration = sig_AudioBlock_newWithValue(&allocator, - &audioSettings, 0.5f); - encoderTap->inputs.count = sig_AudioBlock_newWithValue(&allocator, - &audioSettings, 1.0f); + sig_List_append(&signals, encoderTap, status); + encoderTap->inputs.source = encoder->outputs.button; + encoderTap->inputs.duration = tapDuration->outputs.main; + encoderTap->inputs.count = context->unity->outputs.main; recordGate = sig_dsp_ToggleGate_new(&allocator, context); + sig_List_append(&signals, recordGate, status); recordGate->inputs.trigger = encoderTap->outputs.main; + longPressDuration = sig_dsp_ConstantValue_new(&allocator, context, + LONG_ENCODER_PRESS); + encoderLongPress = sig_dsp_GatedTimer_new(&allocator, context); - encoderLongPress->inputs.gate = encoderButton->outputs.main; - encoderLongPress->inputs.duration = sig_AudioBlock_newWithValue( - &allocator, &audioSettings, LONG_ENCODER_PRESS); - - struct sig_dsp_Looper_Inputs leftLooperInputs = { - // TODO: Need a Daisy Host-provided Signal - // for reading audio input (gh-22). - // For now, just use an empty block that - // is copied into manually in the audio callback. - .source = sig_AudioBlock_newWithValue(&allocator, - &audioSettings, 0.0f), - .start = startSmoother->outputs.main, - .end = endSmoother->outputs.main, - .speed = leftSpeedAdder->outputs.main, - .record = recordGate->outputs.main, - .clear = encoderLongPress->outputs.main - }; + sig_List_append(&signals, encoderLongPress, status); + encoderLongPress->inputs.gate = encoder->outputs.button; + encoderLongPress->inputs.duration = longPressDuration->outputs.main; + + leftIn = sig_host_AudioIn_new(&allocator, context); + leftIn->hardware = &host.device.hardware; + sig_List_append(&signals, leftIn, status); + leftIn->parameters.channel = sig_host_AUDIO_IN_1; - sig_fillWithSilence(leftSamples, LOOP_LENGTH); leftLooper = sig_dsp_Looper_new(&allocator, context); - leftLooper->inputs = leftLooperInputs; + sig_List_append(&signals, leftLooper, status); + leftLooper->inputs.source = leftIn->outputs.main; + leftLooper->inputs.start = startKnob->outputs.main; + leftLooper->inputs.end = endKnob->outputs.main; + leftLooper->inputs.speed = leftSpeed->outputs.main; + leftLooper->inputs.record = recordGate->outputs.main; + leftLooper->inputs.clear = encoderLongPress->outputs.main; + + // TODO: Need better buffer management. + sig_fillWithSilence(leftSamples, LOOP_LENGTH); sig_dsp_Looper_setBuffer(leftLooper, &leftBuffer); - struct sig_dsp_Looper_Inputs rightLooperInputs = { - .source = sig_AudioBlock_newWithValue(&allocator, - &audioSettings, 0.0f), - .start = leftLooperInputs.start, - .end = leftLooperInputs.end, - .speed = rightSpeedAdder->outputs.main, - .record = leftLooperInputs.record, - .clear = leftLooperInputs.clear - }; - sig_fillWithSilence(rightSamples, LOOP_LENGTH); + rightIn = sig_host_AudioIn_new(&allocator, context); + rightIn->hardware = &host.device.hardware; + sig_List_append(&signals, rightIn, status); + rightIn->parameters.channel = sig_host_AUDIO_IN_2; + rightLooper = sig_dsp_Looper_new(&allocator, context); - rightLooper->inputs = rightLooperInputs; + sig_List_append(&signals, rightLooper, status); + rightLooper->inputs.source = rightIn->outputs.main; + rightLooper->inputs.start = startKnob->outputs.main; + rightLooper->inputs.end = endKnob->outputs.main; + rightLooper->inputs.speed = rightSpeed->outputs.main; + rightLooper->inputs.record = recordGate->outputs.main; + rightLooper->inputs.clear = encoderLongPress->outputs.main; + + sig_fillWithSilence(rightSamples, LOOP_LENGTH); sig_dsp_Looper_setBuffer(rightLooper, &rightBuffer); // Bluemchen's output circuit clips as it approaches full gain, // so 0.85 seems to be around the practical maximum value. + // TODO: This should be moved into the Device implemenation. struct sig_dsp_ConstantValue* gainAmount = sig_dsp_ConstantValue_new( &allocator, context, 0.70f); leftGain = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, leftGain, status); leftGain->inputs.left = leftLooper->outputs.main; leftGain->inputs.right = gainAmount->outputs.main; rightGain = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, rightGain, status); rightGain->inputs.left = rightLooper->outputs.main; rightGain->inputs.right = gainAmount->outputs.main; - bluemchen.StartAudio(AudioCallback); + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = leftGain->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = rightGain->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 48 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + buildSignalGraph(context, &status); + host.Start(); struct sig_ui_Rect looperViewRect = { .x = 0, @@ -304,7 +259,7 @@ int main(void) { struct sig_ui_daisy_Canvas canvas = { .geometry = sig_ui_daisy_Canvas_geometryFromRect(&looperViewRect), - .display = &(bluemchen.display) + .display = &host.device.display }; struct sig_ui_daisy_LoopRenderer loopRenderer = { diff --git a/hosts/daisy/examples/bluemchen/oscillator/Makefile b/hosts/daisy/examples/bluemchen/oscillator/Makefile index f232928..b4611b9 100644 --- a/hosts/daisy/examples/bluemchen/oscillator/Makefile +++ b/hosts/daisy/examples/bluemchen/oscillator/Makefile @@ -5,10 +5,10 @@ DEBUG = 0 OPT = -O3 # Sources -C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include -CPP_SOURCES = ../../../vendor/kxmx_bluemchen/src/kxmx_bluemchen.cpp ../../../src/signaletic-daisy-host.cpp ../../../src/daisy-bluemchen-host.cpp src/${TARGET}.cpp +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp USE_FATFS = 0 diff --git a/hosts/daisy/examples/bluemchen/oscillator/src/signaletic-bluemchen-oscillator.cpp b/hosts/daisy/examples/bluemchen/oscillator/src/signaletic-bluemchen-oscillator.cpp index 22c05bb..9e331c9 100644 --- a/hosts/daisy/examples/bluemchen/oscillator/src/signaletic-bluemchen-oscillator.cpp +++ b/hosts/daisy/examples/bluemchen/oscillator/src/signaletic-bluemchen-oscillator.cpp @@ -1,10 +1,7 @@ -#include -#include #include -#include "../../../../include/daisy-bluemchen-host.h" +#include "../../../../include/kxmx-bluemchen-device.hpp" -using namespace kxmx; -using namespace daisy; +using namespace sig::libdaisy; FixedCapStr<20> displayStr; @@ -26,66 +23,66 @@ struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; struct sig_List signals; struct sig_dsp_SignalListEvaluator* evaluator; -Bluemchen bluemchen; -struct sig_daisy_Host* host; +DaisyHost host; -struct sig_daisy_FilteredCVIn* coarseFreqKnob; -struct sig_daisy_FilteredCVIn* fineFreqKnob; -struct sig_daisy_CVIn* vOctCVIn; +struct sig_host_FilteredCVIn* coarseFreqKnob; +struct sig_host_FilteredCVIn* fineFreqKnob; +struct sig_host_CVIn* vOctCVIn; struct sig_dsp_BinaryOp* coarsePlusVOct; struct sig_dsp_BinaryOp* coarseVOctPlusFine; struct sig_dsp_LinearToFreq* frequency; struct sig_dsp_Oscillator* osc; struct sig_dsp_ConstantValue* gainLevel; struct sig_dsp_BinaryOp* gain; -struct sig_daisy_AudioOut* leftOut; -struct sig_daisy_AudioOut* rightOut; - +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; void UpdateOled() { - bluemchen.display.Fill(false); + host.device.display.Fill(false); displayStr.Clear(); displayStr.Append("Starscill"); - bluemchen.display.SetCursor(0, 0); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); displayStr.Clear(); displayStr.AppendFloat(frequency->outputs.main[0], 1); - bluemchen.display.SetCursor(0, 8); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); displayStr.Clear(); displayStr.Append(" Hz"); - bluemchen.display.SetCursor(46, 8); - bluemchen.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.SetCursor(46, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); - bluemchen.display.Update(); + host.device.display.Update(); } void buildSignalGraph(struct sig_SignalContext* context, struct sig_Status* status) { /** Frequency controls **/ - // Bluemchen AnalogControls are all unipolar, - // so they need to be scaled to bipolar values. - coarseFreqKnob = sig_daisy_FilteredCVIn_new(&allocator, context, host); + coarseFreqKnob = sig_host_FilteredCVIn_new(&allocator, context); + coarseFreqKnob->hardware = &host.device.hardware; sig_List_append(&signals, coarseFreqKnob, status); - coarseFreqKnob->parameters.control = bluemchen.CTRL_1; - coarseFreqKnob->parameters.scale = 10.0f; - coarseFreqKnob->parameters.offset = -5.0f; + coarseFreqKnob->parameters.control = sig_host_KNOB_1; + coarseFreqKnob->parameters.scale = 17.27f; // Nearly 20 KHz + coarseFreqKnob->parameters.offset = -11.0f; // 0.1 Hz coarseFreqKnob->parameters.time = 0.01f; - fineFreqKnob = sig_daisy_FilteredCVIn_new(&allocator, context, host); + fineFreqKnob = sig_host_FilteredCVIn_new(&allocator, context); + fineFreqKnob->hardware = &host.device.hardware; sig_List_append(&signals, fineFreqKnob, status); - fineFreqKnob->parameters.control = bluemchen.CTRL_2; - fineFreqKnob->parameters.offset = -0.5f; + fineFreqKnob->parameters.control = sig_host_KNOB_2; + // Fine frequency knob range is scaled to +/- 1 semitone + fineFreqKnob->parameters.scale = 2.0f/12.0f; + fineFreqKnob->parameters.offset = -(1.0f/12.0f); fineFreqKnob->parameters.time = 0.01f; - vOctCVIn = sig_daisy_CVIn_new(&allocator, context, host); + vOctCVIn = sig_host_CVIn_new(&allocator, context); + vOctCVIn->hardware = &host.device.hardware; sig_List_append(&signals, vOctCVIn, status); - vOctCVIn->parameters.control = bluemchen.CTRL_4; - vOctCVIn->parameters.scale = 10.0f; - vOctCVIn->parameters.offset = -5.0f; + vOctCVIn->parameters.control = sig_host_CV_IN_1; + vOctCVIn->parameters.scale = 5.0f; coarsePlusVOct = sig_dsp_Add_new(&allocator, context); sig_List_append(&signals, coarsePlusVOct, status); @@ -120,13 +117,15 @@ void buildSignalGraph(struct sig_SignalContext* context, gain->inputs.right = gainLevel->outputs.main; sig_List_append(&signals, gain, status); - leftOut = sig_daisy_AudioOut_new(&allocator, context, host); - leftOut->parameters.channel = 0; + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; leftOut->inputs.source = gain->outputs.main; sig_List_append(&signals, leftOut, status); - rightOut = sig_daisy_AudioOut_new(&allocator, context, host); - rightOut->parameters.channel = 1; + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; rightOut->inputs.source = gain->outputs.main; sig_List_append(&signals, rightOut, status); } @@ -145,16 +144,12 @@ int main(void) { sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); - host = sig_daisy_BluemchenHost_new(&allocator, - &audioSettings, - &bluemchen, - (struct sig_dsp_SignalEvaluator*) evaluator); - sig_daisy_Host_registerGlobalHost(host); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); struct sig_SignalContext* context = sig_SignalContext_new(&allocator, &audioSettings); buildSignalGraph(context, &status); - host->impl->start(host); + host.Start(); while (1) { UpdateOled(); diff --git a/hosts/daisy/examples/bluemchen/reverb/1962a/Makefile b/hosts/daisy/examples/bluemchen/reverb/1962a/Makefile new file mode 100644 index 0000000..a5eb542 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/1962a/Makefile @@ -0,0 +1,20 @@ +# Project Name +TARGET ?= signaletic-bluemchen-1962a + +DEBUG = 0 +OPT = -O3 + +# Sources +C_SOURCES += ../../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../../libsignaletic/src/libsignaletic.c ../../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../../libsignaletic/vendor/tlsf -I../../../../../../libsignaletic/include + +CPP_SOURCES = ../../../../src/signaletic-daisy-host.cpp ../../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/bluemchen/reverb/1962a/src/signaletic-bluemchen-1962a.cpp b/hosts/daisy/examples/bluemchen/reverb/1962a/src/signaletic-bluemchen-1962a.cpp new file mode 100644 index 0000000..2aa9593 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/1962a/src/signaletic-bluemchen-1962a.cpp @@ -0,0 +1,233 @@ +#include +#include "../../../../include/kxmx-bluemchen-device.hpp" + +FixedCapStr<20> displayStr; + +#define SAMPLERATE 48000 +#define MAX_DELAY_LINE_LENGTH SAMPLERATE * 1 // 1 second. +#define DELAY_LINE_HEAP_SIZE 63*1024*1024 // Grab nearly the whole SDRAM. +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +uint8_t DSY_SDRAM_BSS delayLineMemory[DELAY_LINE_HEAP_SIZE]; +struct sig_AllocatorHeap delayLineHeap = { + .length = DELAY_LINE_HEAP_SIZE, + .memory = (void *) delayLineMemory +}; +struct sig_Allocator delayLineAllocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &delayLineHeap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; + +DaisyHost host; + +struct sig_host_FilteredCVIn* mixKnob; +struct sig_host_FilteredCVIn* timeScaleKnob; +struct sig_host_AudioIn* audioIn; +struct sig_dsp_ConstantValue* ohSeven; +struct sig_dsp_ConstantValue* minusOhSeven; +struct sig_DelayLine* dl1; +struct sig_dsp_ConstantValue* delayTime1; +struct sig_dsp_BinaryOp* delayTimeScale1; +struct sig_dsp_Allpass* ap1; +struct sig_DelayLine* dl2; +struct sig_dsp_ConstantValue* delayTime2; +struct sig_dsp_BinaryOp* delayTimeScale2; +struct sig_dsp_Allpass* ap2; +struct sig_DelayLine* dl3; +struct sig_dsp_ConstantValue* delayTime3; +struct sig_dsp_BinaryOp* delayTimeScale3; +struct sig_dsp_Allpass* ap3; +struct sig_DelayLine* dl4; +struct sig_dsp_ConstantValue* delayTime4; +struct sig_dsp_BinaryOp* delayTimeScale4; +struct sig_dsp_Allpass* ap4; +struct sig_DelayLine* dl5; +struct sig_dsp_ConstantValue* delayTime5; +struct sig_dsp_BinaryOp* delayTimeScale5; +struct sig_dsp_Allpass* ap5; +struct sig_dsp_LinearXFade* wetDryMixer; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; + +void UpdateOled() { + host.device.display.Fill(false); + + displayStr.Clear(); + displayStr.Append("1961a"); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("Mix "); + displayStr.AppendFloat(mixKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("Time "); + displayStr.AppendFloat(delayTimeScale1->outputs.main[0], 2); + host.device.display.SetCursor(0, 16); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + host.device.display.Update(); +} + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + mixKnob = sig_host_FilteredCVIn_new(&allocator, context); + mixKnob->hardware = &host.device.hardware; + sig_List_append(&signals, mixKnob, status); + mixKnob->parameters.control = sig_host_KNOB_1; + mixKnob->parameters.scale = 2.0f; + mixKnob->parameters.offset = -1.0f; + mixKnob->parameters.time = 0.1f; + + timeScaleKnob = sig_host_FilteredCVIn_new(&allocator, context); + timeScaleKnob->hardware = &host.device.hardware; + sig_List_append(&signals, timeScaleKnob, status); + timeScaleKnob->parameters.control = sig_host_KNOB_2; + timeScaleKnob->parameters.scale = 9.9f; + timeScaleKnob->parameters.offset = 0.1f; + // Lots of smoothing to help with the pitch shift that occurs + // when modulating a delay line. + timeScaleKnob->parameters.time = 0.5f; + + audioIn = sig_host_AudioIn_new(&allocator, context); + audioIn->hardware = &host.device.hardware; + sig_List_append(&signals, audioIn, status); + audioIn->parameters.channel = sig_host_AUDIO_IN_1; + + // Allpass parameters are from Shroeder and Logan, + // 'Colorless' Artificial Reverb, 1961. + // http://languagelog.ldc.upenn.edu/myl/Logan1961.pdf + ohSeven = sig_dsp_ConstantValue_new(&allocator, context, 0.7f); + minusOhSeven = sig_dsp_ConstantValue_new(&allocator, context, -0.7f); + + dl1 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + delayTime1 = sig_dsp_ConstantValue_new(&allocator, context, 0.1f); + delayTimeScale1 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, delayTimeScale1, status); + delayTimeScale1->inputs.left = delayTime1->outputs.main; + delayTimeScale1->inputs.right = timeScaleKnob->outputs.main; + ap1 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap1, status); + ap1->delayLine = dl1; + ap1->inputs.source = audioIn->outputs.main; + ap1->inputs.delayTime = delayTimeScale1->outputs.main; + ap1->inputs.g = ohSeven->outputs.main; + + dl2 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + delayTime2 = sig_dsp_ConstantValue_new(&allocator, context, 0.068f); + delayTimeScale2 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, delayTimeScale2, status); + delayTimeScale2->inputs.left = delayTime2->outputs.main; + delayTimeScale2->inputs.right = timeScaleKnob->outputs.main; + ap2 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap2, status); + ap2->delayLine = dl2; + ap2->inputs.source = ap1->outputs.main; + ap2->inputs.delayTime = delayTimeScale2->outputs.main; + ap2->inputs.g = minusOhSeven->outputs.main; + + dl3 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + delayTime3 = sig_dsp_ConstantValue_new(&allocator, context, 0.06f); + delayTimeScale3 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, delayTimeScale3, status); + delayTimeScale3->inputs.left = delayTime3->outputs.main; + delayTimeScale3->inputs.right = timeScaleKnob->outputs.main; + ap3 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap3, status); + ap3->delayLine = dl3; + ap3->inputs.source = ap2->outputs.main; + ap3->inputs.delayTime = delayTimeScale3->outputs.main; + ap3->inputs.g = ohSeven->outputs.main; + + dl4 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + delayTime4 = sig_dsp_ConstantValue_new(&allocator, context, 0.0197f); + delayTimeScale4 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, delayTimeScale4, status); + delayTimeScale4->inputs.left = delayTime4->outputs.main; + delayTimeScale4->inputs.right = timeScaleKnob->outputs.main; + ap4 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap4, status); + ap4->delayLine = dl4; + ap4->inputs.source = ap3->outputs.main; + ap4->inputs.delayTime = delayTimeScale4->outputs.main; + ap4->inputs.g = ohSeven->outputs.main; + + dl5 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + delayTime5 = sig_dsp_ConstantValue_new(&allocator, context, 0.00585f); + delayTimeScale5 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, delayTimeScale5, status); + delayTimeScale5->inputs.left = delayTime5->outputs.main; + delayTimeScale5->inputs.right = timeScaleKnob->outputs.main; + ap5 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap5, status); + ap5->delayLine = dl5; + ap5->inputs.source = ap4->outputs.main; + ap5->inputs.delayTime = delayTimeScale5->outputs.main; + ap5->inputs.g = ohSeven->outputs.main; + + wetDryMixer = sig_dsp_LinearXFade_new(&allocator, context); + sig_List_append(&signals, wetDryMixer, status); + wetDryMixer->inputs.left = audioIn->outputs.main; + wetDryMixer->inputs.right = ap5->outputs.main; + wetDryMixer->inputs.mix = mixKnob->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = wetDryMixer->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = wetDryMixer->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 48 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + // Can't write to memory in SDRAM until after the board has + // been initialized. + delayLineAllocator.impl->init(&delayLineAllocator); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + buildSignalGraph(context, &status); + host.Start(); + + while (1) { + UpdateOled(); + } +} diff --git a/hosts/daisy/examples/bluemchen/reverb/1962b/Makefile b/hosts/daisy/examples/bluemchen/reverb/1962b/Makefile new file mode 100644 index 0000000..68bb784 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/1962b/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= signaletic-bluemchen-1962b + +DEBUG = 0 +OPT = -O3 + +# Sources +C_SOURCES += ../../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../../libsignaletic/src/libsignaletic.c ../../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../../libsignaletic/vendor/tlsf -I../../../../../../libsignaletic/include + +CPP_SOURCES = ../../../../src/signaletic-daisy-host.cpp ../../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile + diff --git a/hosts/daisy/examples/bluemchen/reverb/1962b/src/signaletic-bluemchen-1962b.cpp b/hosts/daisy/examples/bluemchen/reverb/1962b/src/signaletic-bluemchen-1962b.cpp new file mode 100644 index 0000000..92ccec0 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/1962b/src/signaletic-bluemchen-1962b.cpp @@ -0,0 +1,277 @@ +/* + Manfred Schroeder's second reverb topology in + "Natural Sounding Artificial Reverberation" (1962), + which nests five all pass filters into an additional +allpass-style delay line and mixes the dry and wet signals together. + + Sean Costello has a good blog post about the design: + https://valhalladsp.com/2009/05/30/schroeder-reverbs-the-forgotten-algorithm/ +*/ +#include +#include "../../../../include/kxmx-bluemchen-device.hpp" + +FixedCapStr<20> displayStr; + +#define SAMPLERATE 48000 +#define MAX_DELAY_LINE_LENGTH SAMPLERATE * 1 // 1 second. +#define DELAY_LINE_HEAP_SIZE 63*1024*1024 // Grab nearly the whole SDRAM. +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +uint8_t DSY_SDRAM_BSS delayLineMemory[DELAY_LINE_HEAP_SIZE]; +struct sig_AllocatorHeap delayLineHeap = { + .length = DELAY_LINE_HEAP_SIZE, + .memory = (void *) delayLineMemory +}; +struct sig_Allocator delayLineAllocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &delayLineHeap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; + +DaisyHost host; + +struct sig_dsp_ConstantValue* one; +struct sig_dsp_ConstantValue* ohSeven; +struct sig_dsp_ConstantValue* minusOhSeven; +struct sig_host_FilteredCVIn* gKnob; +struct sig_host_FilteredCVIn* delayTimeKnob; +struct sig_host_AudioIn* audioIn; +struct sig_DelayLine* outerDL; +struct sig_dsp_Delay* delayTap; +struct sig_DelayLine* dl1; +struct sig_dsp_ConstantValue* ap1DelayTime; +struct sig_dsp_Allpass* ap1; +struct sig_DelayLine* dl2; +struct sig_dsp_ConstantValue* ap2DelayTime; +struct sig_dsp_Allpass* ap2; +struct sig_DelayLine* dl3; +struct sig_dsp_ConstantValue* ap3DelayTime; +struct sig_dsp_Allpass* ap3; +struct sig_DelayLine* dl4; +struct sig_dsp_ConstantValue* ap4DelayTime; +struct sig_dsp_Allpass* ap4; +struct sig_DelayLine* dl5; +struct sig_dsp_ConstantValue* ap5DelayTime; +struct sig_dsp_Allpass* ap5; +struct sig_dsp_BinaryOp* feedback; +struct sig_dsp_BinaryOp* feedbackInputSum; +struct sig_dsp_DelayWrite* delayWrite; +struct sig_dsp_BinaryOp* gSquared; +struct sig_dsp_BinaryOp* wetGain; +struct sig_dsp_BinaryOp* wet; +struct sig_dsp_Invert* minusG; +struct sig_dsp_BinaryOp* dry; +struct sig_dsp_BinaryOp* wetDryMix; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; + +void UpdateOled() { + host.device.display.Fill(false); + + displayStr.Clear(); + displayStr.Append("1962b"); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("Delay "); + displayStr.AppendFloat(delayTimeKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("g "); + displayStr.AppendFloat(gKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 16); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + host.device.display.Update(); +} + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + one = sig_dsp_ConstantValue_new(&allocator, context, 1.0f); + ohSeven = sig_dsp_ConstantValue_new(&allocator, context, 0.7f); + minusOhSeven = sig_dsp_ConstantValue_new(&allocator, context, -0.7f); + + delayTimeKnob = sig_host_FilteredCVIn_new(&allocator, context); + delayTimeKnob->hardware = &host.device.hardware; + sig_List_append(&signals, delayTimeKnob, status); + delayTimeKnob->parameters.scale = 0.999f; + delayTimeKnob->parameters.offset = 0.001f; + delayTimeKnob->parameters.control = sig_host_KNOB_1; + // Lots of smoothing to help with the pitch shift that occurs + // when modulating a delay line. + delayTimeKnob->parameters.time = 0.25f; + + gKnob = sig_host_FilteredCVIn_new(&allocator, context); + gKnob->hardware = &host.device.hardware; + sig_List_append(&signals, gKnob, status); + gKnob->parameters.control = sig_host_KNOB_2; + gKnob->parameters.scale = 0.999f; + gKnob->parameters.time = 0.001f; + + audioIn = sig_host_AudioIn_new(&allocator, context); + audioIn->hardware = &host.device.hardware; + sig_List_append(&signals, audioIn, status); + audioIn->parameters.channel = sig_host_AUDIO_IN_1; + + // Allpass parameters are from Shroeder, + // Natural Sounding Artificial Reverberation, 1962. + // Outer delay line delayTime = 30 ms, g = 0.893 + // Inner all pass parameters match the 1962a algorithm. + outerDL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + delayTap = sig_dsp_DelayTap_new(&allocator, context); + sig_List_append(&signals, delayTap, status); + delayTap->delayLine = outerDL; + delayTap->inputs.source = audioIn->outputs.main; + delayTap->inputs.delayTime = delayTimeKnob->outputs.main; + + dl1 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap1DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.1f); + ap1 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap1, status); + ap1->delayLine = dl1; + ap1->inputs.source = delayTap->outputs.main; + ap1->inputs.delayTime = ap1DelayTime->outputs.main; + ap1->inputs.g = ohSeven->outputs.main; + + dl2 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap2DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.068f); + ap2 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap2, status); + ap2->delayLine = dl2; + ap2->inputs.source = ap1->outputs.main; + ap2->inputs.delayTime = ap2DelayTime->outputs.main; + ap2->inputs.g = minusOhSeven->outputs.main; + + dl3 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap3DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.06f); + ap3 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap3, status); + ap3->delayLine = dl3; + ap3->inputs.source = ap2->outputs.main; + ap3->inputs.delayTime = ap3DelayTime->outputs.main; + ap3->inputs.g = ohSeven->outputs.main; + + dl4 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap4DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.0197f); + ap4 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap4, status); + ap4->delayLine = dl4; + ap4->inputs.source = ap3->outputs.main; + ap4->inputs.delayTime = ap4DelayTime->outputs.main; + ap4->inputs.g = ohSeven->outputs.main; + + dl5 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap5DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.00585f); + ap5 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap5, status); + ap5->delayLine = dl5; + ap5->inputs.source = ap4->outputs.main; + ap5->inputs.delayTime = ap5DelayTime->outputs.main; + ap5->inputs.g = ohSeven->outputs.main; + + feedback = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, feedback, status); + feedback->inputs.left = ap5->outputs.main; + feedback->inputs.right = gKnob->outputs.main; + + feedbackInputSum = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, feedbackInputSum, status); + feedbackInputSum->inputs.left = audioIn->outputs.main; + feedbackInputSum->inputs.right = feedback->outputs.main; + + delayWrite = sig_dsp_DelayWrite_new(&allocator, context); + sig_List_append(&signals, delayWrite, status); + delayWrite->delayLine = outerDL; + delayWrite->inputs.source = feedbackInputSum->outputs.main; + + gSquared = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, gSquared, status); + gSquared->inputs.left = gKnob->outputs.main; + gSquared->inputs.right = gKnob->outputs.main; + + wetGain = sig_dsp_Sub_new(&allocator, context); + sig_List_append(&signals, wetGain, status); + wetGain->inputs.left = one->outputs.main; + wetGain->inputs.right = gSquared->outputs.main; + + wet = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, wet, status); + wet->inputs.left = ap5->outputs.main; + wet->inputs.right = wetGain->outputs.main; + + minusG = sig_dsp_Invert_new(&allocator, context); + sig_List_append(&signals, minusG, status); + minusG->inputs.source = gKnob->outputs.main; + + dry = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, dry, status); + dry->inputs.left = audioIn->outputs.main; + dry->inputs.right = minusG->outputs.main; + + wetDryMix = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, wetDryMix, status); + wetDryMix->inputs.left = wet->outputs.main; + wetDryMix->inputs.right = dry->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = wetDryMix->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = wetDryMix->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 1 // This graph must run at a block size of one because it + // uses DelayWrite to create a nested delay line around + // a series all pass reverb. + // TODO: Add support for mixed rates. + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + // Can't write to memory in SDRAM until after the board has + // been initialized. + delayLineAllocator.impl->init(&delayLineAllocator); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + buildSignalGraph(context, &status); + host.Start(); + + while (1) { + UpdateOled(); + } +} diff --git a/hosts/daisy/examples/bluemchen/reverb/1962c/Makefile b/hosts/daisy/examples/bluemchen/reverb/1962c/Makefile new file mode 100644 index 0000000..7f366b6 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/1962c/Makefile @@ -0,0 +1,20 @@ +# Project Name +TARGET ?= signaletic-bluemchen-1962c + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../../libsignaletic/src/libsignaletic.c ../../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../../libsignaletic/vendor/tlsf -I../../../../../../libsignaletic/include + +CPP_SOURCES = ../../../../src/signaletic-daisy-host.cpp ../../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/bluemchen/reverb/1962c/src/signaletic-bluemchen-1962c.cpp b/hosts/daisy/examples/bluemchen/reverb/1962c/src/signaletic-bluemchen-1962c.cpp new file mode 100644 index 0000000..a6ee173 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/1962c/src/signaletic-bluemchen-1962c.cpp @@ -0,0 +1,275 @@ +#include +#include "../../../../include/kxmx-bluemchen-device.hpp" + +FixedCapStr<20> displayStr; + +#define SAMPLERATE 96000 +#define MAX_DELAY_LINE_LENGTH SAMPLERATE * 1 // 1 second. +#define DELAY_LINE_HEAP_SIZE 63*1024*1024 // Grab nearly the whole SDRAM. +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +uint8_t DSY_SDRAM_BSS delayLineMemory[DELAY_LINE_HEAP_SIZE]; +struct sig_AllocatorHeap delayLineHeap = { + .length = DELAY_LINE_HEAP_SIZE, + .memory = (void *) delayLineMemory +}; +struct sig_Allocator delayLineAllocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &delayLineHeap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; + +DaisyHost host; + +struct sig_host_AudioIn* audioIn; +struct sig_host_FilteredCVIn* delayTimeScaleKnob; +struct sig_host_FilteredCVIn* feedbackGainScaleKnob; +struct sig_dsp_ConstantValue* apGain; +struct sig_dsp_ConstantValue* combLPFCoefficient; +struct sig_DelayLine* dl1; +struct sig_dsp_ConstantValue* c1DelayTime; +struct sig_dsp_BinaryOp* c1ScaledDelayTime; +struct sig_dsp_Comb* c1; +struct sig_DelayLine* dl2; +struct sig_dsp_ConstantValue* c2DelayTime; +struct sig_dsp_BinaryOp* c2ScaledDelayTime; +struct sig_dsp_Comb* c2; +struct sig_DelayLine* dl3; +struct sig_dsp_ConstantValue* c3DelayTime; +struct sig_dsp_BinaryOp* c3ScaledDelayTime; +struct sig_dsp_Comb* c3; +struct sig_DelayLine* dl4; +struct sig_dsp_ConstantValue* c4DelayTime; +struct sig_dsp_BinaryOp* c4ScaledDelayTime; +struct sig_dsp_Comb* c4; +struct sig_dsp_BinaryOp* sum1; +struct sig_dsp_BinaryOp* sum2; +struct sig_dsp_BinaryOp* sum3; +struct sig_dsp_ConstantValue* combGain; +struct sig_dsp_BinaryOp* scaledCombMix; +struct sig_DelayLine* dl5; +struct sig_dsp_ConstantValue* ap1DelayTime; +struct sig_dsp_Allpass* ap1; +struct sig_DelayLine* dl6; +struct sig_dsp_ConstantValue* ap2DelayTime; +struct sig_dsp_Allpass* ap2; +struct sig_host_AudioOut* leftOut; +struct sig_dsp_Invert* ap2Inverted; +struct sig_host_AudioOut* rightOut; + +void UpdateOled() { + host.device.display.Fill(false); + + displayStr.Clear(); + displayStr.Append("1962c"); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("Del "); + displayStr.AppendFloat(delayTimeScaleKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("g "); + displayStr.AppendFloat(feedbackGainScaleKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 16); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.Update(); +} + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + audioIn = sig_host_AudioIn_new(&allocator, context); + audioIn->hardware = &host.device.hardware; + sig_List_append(&signals, audioIn, status); + audioIn->parameters.channel = sig_host_AUDIO_IN_1; + + // Parameters are from Dodge and Jerse + // rvt between 1.5 and 2 seconds for a concert hall + // node rvt delay time + // c1 rvt 29.7 ms + // c2 rvt 37.1 ms + // c3 rvt 41.1 ms + // c4 rvt 43.7 ms + // ap1 5 ms 96.83 ms + // ap2 1.7 ms 32.92 ms + // (The gains for both all passes calculate as 0.87479, + // but Schroeder recommends a g of 0.7 for both allpasses). + + delayTimeScaleKnob = sig_host_FilteredCVIn_new(&allocator, context); + delayTimeScaleKnob->hardware = &host.device.hardware; + sig_List_append(&signals, delayTimeScaleKnob, status); + delayTimeScaleKnob->parameters.control = sig_host_KNOB_1; + delayTimeScaleKnob->parameters.scale = 99.999f; + delayTimeScaleKnob->parameters.offset = 0.001f; + // Lots of smoothing to help with the pitch shift that occurs + // when modulating a delay line. + delayTimeScaleKnob->parameters.time = 0.25f; + + feedbackGainScaleKnob = sig_host_FilteredCVIn_new(&allocator, context); + feedbackGainScaleKnob->hardware = &host.device.hardware; + sig_List_append(&signals, feedbackGainScaleKnob, status); + feedbackGainScaleKnob->parameters.control = sig_host_KNOB_2; + feedbackGainScaleKnob->parameters.scale = 0.999f; + feedbackGainScaleKnob->parameters.offset = 0.001f; + + apGain = sig_dsp_ConstantValue_new(&allocator, context, 0.7f); + combLPFCoefficient = sig_dsp_ConstantValue_new(&allocator, context, 0.55f); + + dl1 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c1DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.0297f); + c1ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c1ScaledDelayTime, status); + c1ScaledDelayTime->inputs.left = c1DelayTime->outputs.main; + c1ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c1 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c1, status); + c1->delayLine = dl1; + c1->inputs.source = audioIn->outputs.main; + c1->inputs.feedbackGain = feedbackGainScaleKnob->outputs.main; + c1->inputs.delayTime = c1ScaledDelayTime->outputs.main; + c1->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; + + dl2 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c2DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.0371f); + c2ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c2ScaledDelayTime, status); + c2ScaledDelayTime->inputs.left = c2DelayTime->outputs.main; + c2ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c2 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c2, status); + c2->delayLine = dl2; + c2->inputs.source = audioIn->outputs.main; + c2->inputs.feedbackGain = feedbackGainScaleKnob->outputs.main; + c2->inputs.delayTime = c2ScaledDelayTime->outputs.main; + c2->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; + + dl3 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c3DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.0411f); + c3ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c3ScaledDelayTime, status); + c3ScaledDelayTime->inputs.left = c3DelayTime->outputs.main; + c3ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c3 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c3, status); + c3->delayLine = dl3; + c3->inputs.source = audioIn->outputs.main; + c3->inputs.feedbackGain = feedbackGainScaleKnob->outputs.main; + c3->inputs.delayTime = c3ScaledDelayTime->outputs.main; + c3->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; + + dl4 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c4DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.0437f); + c4ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c4ScaledDelayTime, status); + c4ScaledDelayTime->inputs.left = c4DelayTime->outputs.main; + c4ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c4 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c4, status); + c4->delayLine = dl4; + c4->inputs.source = audioIn->outputs.main; + c4->inputs.feedbackGain = feedbackGainScaleKnob->outputs.main; + c4->inputs.delayTime = c4ScaledDelayTime->outputs.main; + c4->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; + + sum1 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum1, status); + sum1->inputs.left = c1->outputs.main; + sum1->inputs.right = c2->outputs.main; + + sum2 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum2, status); + sum2->inputs.left = sum1->outputs.main; + sum2->inputs.right = c3->outputs.main; + + sum3 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum3, status); + sum3->inputs.left = sum2->outputs.main; + sum3->inputs.right = c4->outputs.main; + + combGain = sig_dsp_ConstantValue_new(&allocator, context, 0.2f); + scaledCombMix = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, scaledCombMix, status); + scaledCombMix->inputs.left = sum3->outputs.main; + scaledCombMix->inputs.right = combGain->outputs.main; + + ap1DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.09683f); + dl5 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap1 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap1, status); + ap1->delayLine = dl5; + ap1->inputs.source = scaledCombMix->outputs.main; + ap1->inputs.delayTime = ap1DelayTime->outputs.main; + ap1->inputs.g = apGain->outputs.main; + + ap2DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.03292f); + dl6 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap2 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap2, status); + ap2->delayLine = dl6; + ap2->inputs.source = ap1->outputs.main; + ap2->inputs.delayTime = ap2DelayTime->outputs.main; + ap2->inputs.g = apGain->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = ap2->outputs.main; + + ap2Inverted = sig_dsp_Invert_new(&allocator, context); + sig_List_append(&signals, ap2Inverted, status); + ap2Inverted->inputs.source = ap2->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = ap2Inverted->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 96 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + // Can't write to memory in SDRAM until after the board has + // been initialized. + delayLineAllocator.impl->init(&delayLineAllocator); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + buildSignalGraph(context, &status); + host.Start(); + + while (1) { + UpdateOled(); + } +} diff --git a/hosts/daisy/examples/bluemchen/reverb/jcrev/Makefile b/hosts/daisy/examples/bluemchen/reverb/jcrev/Makefile new file mode 100644 index 0000000..ac4ba9a --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/jcrev/Makefile @@ -0,0 +1,20 @@ +# Project Name +TARGET ?= signaletic-bluemchen-jcrev + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../../libsignaletic/src/libsignaletic.c ../../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../../libsignaletic/vendor/tlsf -I../../../../../../libsignaletic/include + +CPP_SOURCES = ../../../../src/signaletic-daisy-host.cpp ../../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/bluemchen/reverb/jcrev/src/signaletic-bluemchen-jcrev.cpp b/hosts/daisy/examples/bluemchen/reverb/jcrev/src/signaletic-bluemchen-jcrev.cpp new file mode 100644 index 0000000..0a4b36f --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/jcrev/src/signaletic-bluemchen-jcrev.cpp @@ -0,0 +1,374 @@ +#include +#include "../../../../include/kxmx-bluemchen-device.hpp" + +FixedCapStr<20> displayStr; + +#define SAMPLERATE 96000 +#define MAX_DELAY_LINE_LENGTH SAMPLERATE * 1 // 1 second. +#define DELAY_LINE_HEAP_SIZE 63*1024*1024 // Grab nearly the whole SDRAM. +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +uint8_t DSY_SDRAM_BSS delayLineMemory[DELAY_LINE_HEAP_SIZE]; +struct sig_AllocatorHeap delayLineHeap = { + .length = DELAY_LINE_HEAP_SIZE, + .memory = (void *) delayLineMemory +}; +struct sig_Allocator delayLineAllocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &delayLineHeap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; + +DaisyHost host; + +struct sig_host_AudioIn* audioIn; +struct sig_host_FilteredCVIn* delayTimeScaleKnob; +struct sig_host_FilteredCVIn* feedbackGainScaleKnob; +struct sig_dsp_ConstantValue* apGain; +struct sig_dsp_BinaryOp* apScaledGain; + +struct sig_DelayLine* ap1DL; +struct sig_dsp_ConstantValue* ap1DelayTime; +struct sig_dsp_BinaryOp* ap1ScaledDelayTime; +struct sig_dsp_Allpass* ap1; +struct sig_DelayLine* ap2DL; +struct sig_dsp_ConstantValue* ap2DelayTime; +struct sig_dsp_BinaryOp* ap2ScaledDelayTime; +struct sig_dsp_Allpass* ap2; +struct sig_DelayLine* ap3DL; +struct sig_dsp_ConstantValue* ap3DelayTime; +struct sig_dsp_BinaryOp* ap3ScaledDelayTime; +struct sig_dsp_Allpass* ap3; + +struct sig_dsp_ConstantValue* combLPFCoefficient; +struct sig_DelayLine* c1DL; +struct sig_dsp_ConstantValue* c1DelayTime; +struct sig_dsp_BinaryOp* c1ScaledDelayTime; +struct sig_dsp_ConstantValue* c1Gain; +struct sig_dsp_BinaryOp* c1ScaledGain; +struct sig_dsp_Comb* c1; +struct sig_DelayLine* c2DL; +struct sig_dsp_ConstantValue* c2DelayTime; +struct sig_dsp_BinaryOp* c2ScaledDelayTime; +struct sig_dsp_ConstantValue* c2Gain; +struct sig_dsp_BinaryOp* c2ScaledGain; +struct sig_dsp_Comb* c2; +struct sig_DelayLine* c3DL; +struct sig_dsp_ConstantValue* c3DelayTime; +struct sig_dsp_BinaryOp* c3ScaledDelayTime; +struct sig_dsp_ConstantValue* c3Gain; +struct sig_dsp_BinaryOp* c3ScaledGain; +struct sig_dsp_Comb* c3; +struct sig_DelayLine* c4DL; +struct sig_dsp_ConstantValue* c4DelayTime; +struct sig_dsp_BinaryOp* c4ScaledDelayTime; +struct sig_dsp_ConstantValue* c4Gain; +struct sig_dsp_BinaryOp* c4ScaledGain; +struct sig_dsp_Comb* c4; + +struct sig_dsp_BinaryOp* sum1; +struct sig_dsp_BinaryOp* sum2; +struct sig_dsp_BinaryOp* sum3; +struct sig_dsp_ConstantValue* combGain; +struct sig_dsp_BinaryOp* scaledCombMix; + +struct sig_DelayLine* d1DL; +struct sig_dsp_ConstantValue* d1DelayTime; +struct sig_dsp_Delay* d1; +struct sig_DelayLine* d2DL; +struct sig_dsp_ConstantValue* d2DelayTime; +struct sig_dsp_Delay* d2; + +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; + +void UpdateOled() { + host.device.display.Fill(false); + + displayStr.Clear(); + displayStr.Append("JCRev"); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("Del "); + displayStr.AppendFloat(delayTimeScaleKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("g "); + displayStr.AppendFloat(feedbackGainScaleKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 16); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.Update(); +} + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + // Parameters are from Julius O. Smith, + // "A Schroeder Reverb called JCRev" + // https://ccrma.stanford.edu/~jos/Reverb/A_Schroeder_Reverberator_called.html + // Note that there are two different versions of JCRev topology + // documented by JOS. One uses decorrelation delays at the end, + // the other does not. + // Both seem to come from the CLM source code: + // https://github.com/radiganm/clm/blob/master/jcrev.ins + // My best guess is that the delay times specified in the JOS link + // above are at 44.1KHz, whereas those documented at the link below + // are at 12.8KHz: + // https://ccrma.stanford.edu/~jos/pasp/Schroeder_Reverberators.html + // Node Delay Time G + // ap1 0.02383 sec 0.7 + // ap2 0.00764 sec 0.7 + // ap3 0.00256 sec 0.7 + // c1 0.10882 sec 0.742 + // c2 0.11336 sec 0.733 + // c3 0.12243 sec 0.715 + // c4 0.13154 sec 0.697 + // d1 0.013 sec - + // d2 0.011 sec - + // d3 0.15 sec - (not used) + // d4 0.017 sec - (not used) + // (First JOS link has d1-d4 delay times as 0.046, 0.057, 0.041, 0.054) + + audioIn = sig_host_AudioIn_new(&allocator, context); + audioIn->hardware = &host.device.hardware; + sig_List_append(&signals, audioIn, status); + audioIn->parameters.channel = sig_host_AUDIO_IN_1; + + delayTimeScaleKnob = sig_host_FilteredCVIn_new(&allocator, context); + delayTimeScaleKnob->hardware = &host.device.hardware; + sig_List_append(&signals, delayTimeScaleKnob, status); + delayTimeScaleKnob->parameters.control = sig_host_KNOB_1; + delayTimeScaleKnob->parameters.scale = 9.999f; + delayTimeScaleKnob->parameters.offset = 0.001f; + // Lots of smoothing to help with the pitch shift that occurs + // when modulating a delay line. + delayTimeScaleKnob->parameters.time = 0.25f; + + feedbackGainScaleKnob = sig_host_FilteredCVIn_new(&allocator, context); + feedbackGainScaleKnob->hardware = &host.device.hardware; + sig_List_append(&signals, feedbackGainScaleKnob, status); + feedbackGainScaleKnob->parameters.control = sig_host_KNOB_2; + feedbackGainScaleKnob->parameters.scale = 1.349f; + feedbackGainScaleKnob->parameters.offset = 0.001f; + + apGain = sig_dsp_ConstantValue_new(&allocator, context, 0.7f); + apScaledGain = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, apScaledGain, status); + apScaledGain->inputs.left = apGain->outputs.main; + apScaledGain->inputs.right = feedbackGainScaleKnob->outputs.main; + combLPFCoefficient = sig_dsp_ConstantValue_new(&allocator, context, 0.0f); + + /** Series All Pass **/ + ap1DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.02383f); + ap1ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, ap1ScaledDelayTime, status); + ap1ScaledDelayTime->inputs.left = ap1DelayTime->outputs.main; + ap1ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + ap1DL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap1 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap1, status); + ap1->delayLine = ap1DL; + ap1->inputs.source = audioIn->outputs.main; + ap1->inputs.delayTime = ap1ScaledDelayTime->outputs.main; + ap1->inputs.g = apScaledGain->outputs.main; + + ap2DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.00764f); + ap2ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, ap2ScaledDelayTime, status); + ap2ScaledDelayTime->inputs.left = ap2DelayTime->outputs.main; + ap2ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + ap2DL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap2 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap2, status); + ap2->delayLine = ap2DL; + ap2->inputs.source = ap1->outputs.main; + ap2->inputs.delayTime = ap2ScaledDelayTime->outputs.main; + ap2->inputs.g = apScaledGain->outputs.main; + + ap3DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.00256f); + ap3ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, ap3ScaledDelayTime, status); + ap3ScaledDelayTime->inputs.left = ap3DelayTime->outputs.main; + ap3ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + ap3DL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap3 = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap3, status); + ap3->delayLine = ap3DL; + ap3->inputs.source = ap2->outputs.main; + ap3->inputs.delayTime = ap3ScaledDelayTime->outputs.main; + ap3->inputs.g = apScaledGain->outputs.main; + + /** Parallel Combs */ + c1DL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c1DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.10882f); + c1ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c1ScaledDelayTime, status); + c1ScaledDelayTime->inputs.left = c1DelayTime->outputs.main; + c1ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c1Gain = sig_dsp_ConstantValue_new(&allocator, context, 0.742f); + c1ScaledGain = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c1ScaledGain, status); + c1ScaledGain->inputs.left = c1Gain->outputs.main; + c1ScaledGain->inputs.right = feedbackGainScaleKnob->outputs.main; + c1 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c1, status); + c1->delayLine = c1DL; + c1->inputs.source = ap3->outputs.main; + c1->inputs.feedbackGain = c1ScaledGain->outputs.main; + c1->inputs.delayTime = c1ScaledDelayTime->outputs.main; + c1->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; + + c2DL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c2DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.11336f); + c2ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c2ScaledDelayTime, status); + c2ScaledDelayTime->inputs.left = c2DelayTime->outputs.main; + c2ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c2Gain = sig_dsp_ConstantValue_new(&allocator, context, 0.733f); + c2ScaledGain = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c2ScaledGain, status); + c2ScaledGain->inputs.left = c2Gain->outputs.main; + c2ScaledGain->inputs.right = feedbackGainScaleKnob->outputs.main; + c2 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c2, status); + c2->delayLine = c2DL; + c2->inputs.source = ap3->outputs.main; + c2->inputs.feedbackGain = c2ScaledGain->outputs.main; + c2->inputs.delayTime = c2ScaledDelayTime->outputs.main; + c2->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; + + c3DL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c3DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.12243f); + c3ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c3ScaledDelayTime, status); + c3ScaledDelayTime->inputs.left = c3DelayTime->outputs.main; + c3ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c3Gain = sig_dsp_ConstantValue_new(&allocator, context, 0.715f); + c3ScaledGain = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c3ScaledGain, status); + c3ScaledGain->inputs.left = c3Gain->outputs.main; + c3ScaledGain->inputs.right = feedbackGainScaleKnob->outputs.main; + c3 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c3, status); + c3->delayLine = c3DL; + c3->inputs.source = ap3->outputs.main; + c3->inputs.feedbackGain = c3ScaledGain->outputs.main; + c3->inputs.delayTime = c3ScaledDelayTime->outputs.main; + c3->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; + + c4DL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c4DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.13154f); + c4ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c4ScaledDelayTime, status); + c4ScaledDelayTime->inputs.left = c4DelayTime->outputs.main; + c4ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c4Gain = sig_dsp_ConstantValue_new(&allocator, context, 0.697f); + c4ScaledGain = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c4ScaledGain, status); + c4ScaledGain->inputs.left = c4Gain->outputs.main; + c4ScaledGain->inputs.right = feedbackGainScaleKnob->outputs.main; + c4 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c4, status); + c4->delayLine = c4DL; + c4->inputs.source = ap3->outputs.main; + c4->inputs.feedbackGain = c4ScaledGain->outputs.main; + c4->inputs.delayTime = c4ScaledDelayTime->outputs.main; + c4->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; + + sum1 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum1, status); + sum1->inputs.left = c1->outputs.main; + sum1->inputs.right = c2->outputs.main; + + sum2 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum2, status); + sum2->inputs.left = sum1->outputs.main; + sum2->inputs.right = c3->outputs.main; + + sum3 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum3, status); + sum3->inputs.left = sum2->outputs.main; + sum3->inputs.right = c4->outputs.main; + + combGain = sig_dsp_ConstantValue_new(&allocator, context, 0.2f); + scaledCombMix = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, scaledCombMix, status); + scaledCombMix->inputs.left = sum3->outputs.main; + scaledCombMix->inputs.right = combGain->outputs.main; + + /** Decorrelation Delays **/ + d1DL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + d1DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.046f); + d1 = sig_dsp_Delay_new(&allocator, context); + sig_List_append(&signals, d1, status); + d1->delayLine = d1DL; + d1->inputs.source = scaledCombMix->outputs.main; + d1->inputs.delayTime = d1DelayTime->outputs.main; + + d2DL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + d2DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.057f); + d2 = sig_dsp_Delay_new(&allocator, context); + sig_List_append(&signals, d2, status); + d2->delayLine = d2DL; + d2->inputs.source = scaledCombMix->outputs.main; + d2->inputs.delayTime = d2DelayTime->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = d1->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = d2->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = 48000, + .numChannels = 2, + .blockSize = 48 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + // Can't write to memory in SDRAM until after the board has + // been initialized. + delayLineAllocator.impl->init(&delayLineAllocator); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + buildSignalGraph(context, &status); + host.Start(); + + while (1) { + UpdateOled(); + } +} diff --git a/hosts/daisy/examples/bluemchen/reverb/moorer/Makefile b/hosts/daisy/examples/bluemchen/reverb/moorer/Makefile new file mode 100644 index 0000000..b2ef629 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/moorer/Makefile @@ -0,0 +1,20 @@ +# Project Name +TARGET ?= signaletic-bluemchen-moorer + +DEBUG = 0 +OPT = -O3 + +# Sources +C_SOURCES += ../../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../../libsignaletic/src/libsignaletic.c ../../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../../libsignaletic/vendor/tlsf -I../../../../../../libsignaletic/include + +CPP_SOURCES = ../../../../src/signaletic-daisy-host.cpp ../../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/bluemchen/reverb/moorer/include/signaletic-bluemchen-moorer-signals.h b/hosts/daisy/examples/bluemchen/reverb/moorer/include/signaletic-bluemchen-moorer-signals.h new file mode 100644 index 0000000..2da02d6 --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/moorer/include/signaletic-bluemchen-moorer-signals.h @@ -0,0 +1,85 @@ +#include + +struct sig_dsp_MultiTapDelay_Inputs { + float_array_ptr source; + float_array_ptr timeScale; +}; + + +// FIXME: Need multichannel inputs so these delay taps can be modulatable. +struct sig_dsp_MultiTapDelay { + struct sig_dsp_Signal signal; + struct sig_dsp_MultiTapDelay_Inputs inputs; + struct sig_dsp_ScaleOffset_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + + struct sig_DelayLine* delayLine; + struct sig_Buffer* tapTimes; + struct sig_Buffer* tapGains; +}; + +struct sig_dsp_MultiTapDelay* sig_dsp_MultiTapDelay_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_MultiTapDelay_init(struct sig_dsp_MultiTapDelay* self, + struct sig_SignalContext* context); +void sig_dsp_MultiTapDelay_generate(void* signal); +void sig_dsp_MultiTapDelay_destroy(struct sig_Allocator* allocator, + struct sig_dsp_MultiTapDelay* self); + +struct sig_dsp_MultiTapDelay* sig_dsp_MultiTapDelay_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_MultiTapDelay* self = sig_MALLOC(allocator, + struct sig_dsp_MultiTapDelay); + // TODO: Improve buffer management throughout Signaletic. + self->delayLine = context->oneSampleDelayLine; + self->tapTimes = context->emptyBuffer; + self->tapGains = context->emptyBuffer; + + sig_dsp_MultiTapDelay_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_MultiTapDelay_init(struct sig_dsp_MultiTapDelay* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_MultiTapDelay_generate); + + struct sig_dsp_ScaleOffset_Parameters parameters = { + .scale = 1.0, + .offset= 0.0f + }; + self->parameters = parameters; + sig_CONNECT_TO_SILENCE(self, source, context); + sig_CONNECT_TO_UNITY(self, timeScale, context); +} + +void sig_dsp_MultiTapDelay_generate(void* signal) { + struct sig_dsp_MultiTapDelay* self = (struct sig_dsp_MultiTapDelay*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float timeScale = FLOAT_ARRAY(self->inputs.timeScale)[i]; + float tapSum = sig_DelayLine_linearReadAtTimes( + self->delayLine, + source, + self->tapTimes->samples, + self->tapGains->samples, + self->tapTimes->length, + self->signal.audioSettings->sampleRate, + timeScale); + + FLOAT_ARRAY(self->outputs.main)[i] = tapSum * self->parameters.scale + + self->parameters.offset; + sig_DelayLine_write(self->delayLine, source); + } +} + +void sig_dsp_MultiTapDelay_destroy(struct sig_Allocator* allocator, + struct sig_dsp_MultiTapDelay* self) { + // Don't destroy the delay line or the buffers; they're not owned. + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} diff --git a/hosts/daisy/examples/bluemchen/reverb/moorer/src/signaletic-bluemchen-moorer.cpp b/hosts/daisy/examples/bluemchen/reverb/moorer/src/signaletic-bluemchen-moorer.cpp new file mode 100644 index 0000000..515ef6f --- /dev/null +++ b/hosts/daisy/examples/bluemchen/reverb/moorer/src/signaletic-bluemchen-moorer.cpp @@ -0,0 +1,491 @@ +#include +#include "../../../../include/kxmx-bluemchen-device.hpp" +#include "../include/signaletic-bluemchen-moorer-signals.h" + +FixedCapStr<20> displayStr; + +#define SAMPLERATE 48000 +#define MAX_DELAY_LINE_LENGTH SAMPLERATE // One second +#define DELAY_LINE_HEAP_SIZE 63*1024*1024 // Grab nearly the whole SDRAM. +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 64 + +#define NUM_EARLY_ECHO_TAPS 19 +float earlyEchoDelayTimeValues[NUM_EARLY_ECHO_TAPS] = { + 0.0f, 0.0043f, 0.0215f, 0.0225f, + 0.0268f, 0.0270f, 0.0298f, 0.0458f, + 0.0485f, 0.0572f, 0.0587f, 0.0595f, + 0.0612f, 0.0707f, 0.0708f, 0.0726f, + 0.0741f, 0.0753f, 0.0797f +}; + +struct sig_Buffer earlyEchoDelayTimes = { + .length = NUM_EARLY_ECHO_TAPS, + .samples = (float_array_ptr) &earlyEchoDelayTimeValues +}; + +float earlyEchoGainValues[NUM_EARLY_ECHO_TAPS] = { + 1.0f, 0.841f, 0.504f, 0.491f, + 0.379f, 0.380f, 0.346f, 0.289f, + 0.272f, 0.192f, 0.193f, 0.217f, + 0.181f, 0.180f, 0.181f, 0.176f, + 0.142f, 0.167f, 0.134f +}; + +struct sig_Buffer earlyEchoGains = { + .length = NUM_EARLY_ECHO_TAPS, + .samples = (float_array_ptr) &earlyEchoGainValues +}; + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +uint8_t DSY_SDRAM_BSS delayLineMemory[DELAY_LINE_HEAP_SIZE]; +struct sig_AllocatorHeap delayLineHeap = { + .length = DELAY_LINE_HEAP_SIZE, + .memory = (void *) delayLineMemory +}; +struct sig_Allocator delayLineAllocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &delayLineHeap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; + +DaisyHost host; + +struct sig_host_AudioIn* audioIn; +struct sig_host_FilteredCVIn* delayTimeScaleKnob; +struct sig_host_FilteredCVIn* combGainKnob; +struct sig_dsp_ConstantValue* one; + +struct sig_DelayLine* earlyEchoDL; +struct sig_dsp_MultiTapDelay* earlyEchoes; +struct sig_dsp_ConstantValue* reverberatorDelayTime; +struct sig_dsp_BinaryOp* reverberatorDelayTimeScale; +struct sig_DelayLine* reverberatorDelayDL; +struct sig_dsp_Delay* reverberatorDelay; + +struct sig_DelayLine* dl1; +struct sig_dsp_ConstantValue* c1DelayTime; +struct sig_dsp_BinaryOp* c1ScaledDelayTime; +struct sig_dsp_ConstantValue* c1LPFGain; +struct sig_dsp_BinaryOp* oneMinusC1G1; +struct sig_dsp_BinaryOp* c1G2; +struct sig_dsp_Comb* c1; + +struct sig_DelayLine* dl2; +struct sig_dsp_ConstantValue* c2DelayTime; +struct sig_dsp_BinaryOp* c2ScaledDelayTime; +struct sig_dsp_ConstantValue* c2LPFGain; +struct sig_dsp_BinaryOp* oneMinusC2G1; +struct sig_dsp_BinaryOp* c2G2; +struct sig_dsp_Comb* c2; + +struct sig_DelayLine* dl3; +struct sig_dsp_ConstantValue* c3DelayTime; +struct sig_dsp_BinaryOp* c3ScaledDelayTime; +struct sig_dsp_ConstantValue* c3LPFGain; +struct sig_dsp_BinaryOp* oneMinusC3G1; +struct sig_dsp_BinaryOp* c3G2; +struct sig_dsp_Comb* c3; + +struct sig_DelayLine* dl4; +struct sig_dsp_ConstantValue* c4DelayTime; +struct sig_dsp_BinaryOp* c4ScaledDelayTime; +struct sig_dsp_ConstantValue* c4LPFGain; +struct sig_dsp_BinaryOp* oneMinusC4G1; +struct sig_dsp_BinaryOp* c4G2; +struct sig_dsp_Comb* c4; + +struct sig_DelayLine* dl5; +struct sig_dsp_ConstantValue* c5DelayTime; +struct sig_dsp_BinaryOp* c5ScaledDelayTime; +struct sig_dsp_ConstantValue* c5LPFGain; +struct sig_dsp_BinaryOp* oneMinusC5G1; +struct sig_dsp_BinaryOp* c5G2; +struct sig_dsp_Comb* c5; + +struct sig_DelayLine* dl6; +struct sig_dsp_ConstantValue* c6DelayTime; +struct sig_dsp_BinaryOp* c6ScaledDelayTime; +struct sig_dsp_ConstantValue* c6LPFGain; +struct sig_dsp_BinaryOp* oneMinusC6G1; +struct sig_dsp_BinaryOp* c6G2; +struct sig_dsp_Comb* c6; + +// TODO: Oof, need multichannel inputs. +struct sig_dsp_BinaryOp* sum1; +struct sig_dsp_BinaryOp* sum2; +struct sig_dsp_BinaryOp* sum3; +struct sig_dsp_BinaryOp* sum4; +struct sig_dsp_BinaryOp* sum5; + +struct sig_dsp_ConstantValue* combMixGain; +struct sig_dsp_BinaryOp* scaledCombMix; + +struct sig_DelayLine* dl7; +struct sig_dsp_ConstantValue* apDelayTime; +struct sig_dsp_ConstantValue* apGain; +struct sig_dsp_Allpass* ap; + +struct sig_dsp_BinaryOp* reverberatorMixScale; +struct sig_dsp_BinaryOp* earlyEchoesReverberatorMix; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; + +void UpdateOled() { + host.device.display.Fill(false); + + displayStr.Clear(); + displayStr.Append("Moorer"); + host.device.display.SetCursor(0, 0); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("Del "); + displayStr.AppendFloat(delayTimeScaleKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 8); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + + displayStr.Clear(); + displayStr.Append("g "); + displayStr.AppendFloat(combGainKnob->outputs.main[0], 2); + host.device.display.SetCursor(0, 16); + host.device.display.WriteString(displayStr.Cstr(), Font_6x8, true); + host.device.display.Update(); +} + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + // Parameters are from Moorer (1979) "About This Reverberation Business." + // Unit Delay Time g1 g2 + // Six parallel comb filters: + // The gain (g1) values listed here are interpolated to 48KHz from + // Moorer's tables, which listed values at 25 and 50 KHz. + // c1 0.05 0.44 "all the g2 terms set to a constant + // number g times (1 - g1)... + // each comb will have a different value + // of g1, but should have all the same + // value for g. This number g determines + // the overall reverberation time. + // For example, values around 0.83 seem to + // give a reverberation time of about + // 2.0 seconds with these delays." + // c2 0.056 0.46 + // c3 0.061 0.48 + // c4 0.068 0.50 + // c5 0.072 0.51 + // c6 0.078 0.53 + // One all pass: + // ap 0.006 0.7 + + /** Controls **/ + delayTimeScaleKnob = sig_host_FilteredCVIn_new(&allocator, context); + delayTimeScaleKnob->hardware = &host.device.hardware; + sig_List_append(&signals, delayTimeScaleKnob, status); + delayTimeScaleKnob->parameters.control = sig_host_KNOB_1; + delayTimeScaleKnob->parameters.scale = 9.999f; + delayTimeScaleKnob->parameters.offset = 0.001f; + delayTimeScaleKnob->parameters.time = 0.25f; // Smoother mod pitch shift + + combGainKnob = sig_host_FilteredCVIn_new(&allocator, context); + combGainKnob->hardware = &host.device.hardware; + sig_List_append(&signals, combGainKnob, status); + combGainKnob->parameters.control = sig_host_KNOB_2; + combGainKnob->parameters.scale = 1.6999f; + combGainKnob->parameters.offset = 0.001f; + + audioIn = sig_host_AudioIn_new(&allocator, context); + audioIn->hardware = &host.device.hardware; + sig_List_append(&signals, audioIn, status); + audioIn->parameters.channel = sig_host_AUDIO_IN_1; + + /** Early Echoes **/ + // TODO: Add support for scaling the delay taps, + // which will allow the user to change the room size. + earlyEchoDL = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + earlyEchoes = sig_dsp_MultiTapDelay_new(&allocator, context); + sig_List_append(&signals, earlyEchoes, status); + earlyEchoes->delayLine = earlyEchoDL; + earlyEchoes->tapTimes = &earlyEchoDelayTimes; + earlyEchoes->tapGains = &earlyEchoGains; + earlyEchoes->inputs.source = audioIn->outputs.main; + earlyEchoes->inputs.timeScale = delayTimeScaleKnob->outputs.main; + earlyEchoes->parameters.scale = 0.125f; // TODO: Is this scaling needed? + + /** Comb Filters **/ + one = sig_dsp_ConstantValue_new(&allocator, context, 1.0f); + + dl1 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c1DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.05f); + c1ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c1ScaledDelayTime, status); + c1ScaledDelayTime->inputs.left = c1DelayTime->outputs.main; + c1ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c1LPFGain = sig_dsp_ConstantValue_new(&allocator, context, 0.44f); + oneMinusC1G1 = sig_dsp_Sub_new(&allocator, context); + sig_List_append(&signals, oneMinusC1G1, status); + oneMinusC1G1->inputs.left = one->outputs.main; + oneMinusC1G1->inputs.right = c1LPFGain->outputs.main; + c1G2 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c1G2, status); + c1G2->inputs.left = combGainKnob->outputs.main; + c1G2->inputs.right = oneMinusC1G1->outputs.main; + c1 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c1, status); + c1->delayLine = dl1; + c1->inputs.source = earlyEchoes->outputs.main; + c1->inputs.feedbackGain = c1G2->outputs.main; + c1->inputs.delayTime = c1ScaledDelayTime->outputs.main; + c1->inputs.lpfCoefficient = c1LPFGain->outputs.main; + + dl2 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c2DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.056f); + c2ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c2ScaledDelayTime, status); + c2ScaledDelayTime->inputs.left = c2DelayTime->outputs.main; + c2ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c2LPFGain = sig_dsp_ConstantValue_new(&allocator, context, 0.46f); + oneMinusC2G1 = sig_dsp_Sub_new(&allocator, context); + sig_List_append(&signals, oneMinusC2G1, status); + oneMinusC2G1->inputs.left = one->outputs.main; + oneMinusC2G1->inputs.right = c2LPFGain->outputs.main; + c2G2 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c2G2, status); + c2G2->inputs.left = combGainKnob->outputs.main; + c2G2->inputs.right = oneMinusC2G1->outputs.main; + c2 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c2, status); + c2->delayLine = dl2; + c2->inputs.source = earlyEchoes->outputs.main; + c2->inputs.feedbackGain = c2G2->outputs.main; + c2->inputs.delayTime = c2ScaledDelayTime->outputs.main; + c2->inputs.lpfCoefficient = c2LPFGain->outputs.main; + + dl3 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c3DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.061f); + c3ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c3ScaledDelayTime, status); + c3ScaledDelayTime->inputs.left = c3DelayTime->outputs.main; + c3ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c3LPFGain = sig_dsp_ConstantValue_new(&allocator, context, 0.48f); + oneMinusC3G1 = sig_dsp_Sub_new(&allocator, context); + sig_List_append(&signals, oneMinusC3G1, status); + oneMinusC3G1->inputs.left = one->outputs.main; + oneMinusC3G1->inputs.right = c3LPFGain->outputs.main; + c3G2 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c3G2, status); + c3G2->inputs.left = combGainKnob->outputs.main; + c3G2->inputs.right = oneMinusC3G1->outputs.main; + c3 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c3, status); + c3->delayLine = dl3; + c3->inputs.source = earlyEchoes->outputs.main; + c3->inputs.feedbackGain = c3G2->outputs.main; + c3->inputs.delayTime = c3ScaledDelayTime->outputs.main; + c3->inputs.lpfCoefficient = c3LPFGain->outputs.main; + + dl4 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c4DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.068f); + c4ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c4ScaledDelayTime, status); + c4ScaledDelayTime->inputs.left = c4DelayTime->outputs.main; + c4ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c4LPFGain = sig_dsp_ConstantValue_new(&allocator, context, 0.50f); + oneMinusC4G1 = sig_dsp_Sub_new(&allocator, context); + sig_List_append(&signals, oneMinusC4G1, status); + oneMinusC4G1->inputs.left = one->outputs.main; + oneMinusC4G1->inputs.right = c4LPFGain->outputs.main; + c4G2 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c4G2, status); + c4G2->inputs.left = combGainKnob->outputs.main; + c4G2->inputs.right = oneMinusC4G1->outputs.main; + c4 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c4, status); + c4->delayLine = dl4; + c4->inputs.source = earlyEchoes->outputs.main; + c4->inputs.feedbackGain = c4G2->outputs.main; + c4->inputs.delayTime = c4ScaledDelayTime->outputs.main; + c4->inputs.lpfCoefficient = c4LPFGain->outputs.main; + + dl5 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c5DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.072f); + c5ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c5ScaledDelayTime, status); + c5ScaledDelayTime->inputs.left = c5DelayTime->outputs.main; + c5ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c5LPFGain = sig_dsp_ConstantValue_new(&allocator, context, 0.51f); + oneMinusC5G1 = sig_dsp_Sub_new(&allocator, context); + sig_List_append(&signals, oneMinusC5G1, status); + oneMinusC5G1->inputs.left = one->outputs.main; + oneMinusC5G1->inputs.right = c5LPFGain->outputs.main; + c5G2 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c5G2, status); + c5G2->inputs.left = combGainKnob->outputs.main; + c5G2->inputs.right = oneMinusC5G1->outputs.main; + c5 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c5, status); + c5->delayLine = dl5; + c5->inputs.source = earlyEchoes->outputs.main; + c5->inputs.feedbackGain = c5G2->outputs.main; + c5->inputs.delayTime = c5ScaledDelayTime->outputs.main; + c5->inputs.lpfCoefficient = c5LPFGain->outputs.main; + + dl6 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + c6DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.078f); + c6ScaledDelayTime = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c6ScaledDelayTime, status); + c6ScaledDelayTime->inputs.left = c6DelayTime->outputs.main; + c6ScaledDelayTime->inputs.right = delayTimeScaleKnob->outputs.main; + c6LPFGain = sig_dsp_ConstantValue_new(&allocator, context, 0.53f); + oneMinusC6G1 = sig_dsp_Sub_new(&allocator, context); + sig_List_append(&signals, oneMinusC6G1, status); + oneMinusC6G1->inputs.left = one->outputs.main; + oneMinusC6G1->inputs.right = c6LPFGain->outputs.main; + c6G2 = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, c6G2, status); + c6G2->inputs.left = combGainKnob->outputs.main; + c6G2->inputs.right = oneMinusC6G1->outputs.main; + c6 = sig_dsp_Comb_new(&allocator, context); + sig_List_append(&signals, c6, status); + c6->delayLine = dl6; + c6->inputs.source = earlyEchoes->outputs.main; + c6->inputs.feedbackGain = c6G2->outputs.main; + c6->inputs.delayTime = c6ScaledDelayTime->outputs.main; + c6->inputs.lpfCoefficient = c6LPFGain->outputs.main; + + sum1 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum1, status); + sum1->inputs.left = c1->outputs.main; + sum1->inputs.right = c2->outputs.main; + + sum2 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum2, status); + sum2->inputs.left = sum1->outputs.main; + sum2->inputs.right = c3->outputs.main; + + sum3 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum3, status); + sum3->inputs.left = sum2->outputs.main; + sum3->inputs.right = c4->outputs.main; + + sum4 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum4, status); + sum4->inputs.left = sum3->outputs.main; + sum4->inputs.right = c5->outputs.main; + + sum5 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, sum5, status); + sum5->inputs.left = sum4->outputs.main; + sum5->inputs.right = c6->outputs.main; + + combMixGain = sig_dsp_ConstantValue_new(&allocator, context, 0.2f); + scaledCombMix = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, scaledCombMix, status); + scaledCombMix->inputs.left = sum5->outputs.main; + scaledCombMix->inputs.right = combMixGain->outputs.main; + + /** All Pass **/ + // Note: I don't scale the all pass delay time with the delay time knob, + // because I think it sounds better tuned as is (Moorer has a comment about + // needing to keep the delay time of the all pass short but + // not too short on p19). + apDelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.005f); + apGain = sig_dsp_ConstantValue_new(&allocator, context, 0.7f); + dl7 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); + ap = sig_dsp_Allpass_new(&allocator, context); + sig_List_append(&signals, ap, status); + ap->delayLine = dl7; + ap->inputs.source = scaledCombMix->outputs.main; + ap->inputs.delayTime = apDelayTime->outputs.main; + ap->inputs.g = apGain->outputs.main; + + reverberatorDelayTime = sig_dsp_ConstantValue_new(&allocator, context, + 0.0247); // FIXME: This has to be responsive to delay time scale. + // Moorer says "the delays D1 and D2 are set such that the + // first echo from the reverberator [0.055] coincides + // with the end of the last echo from the early + // response [0.0797]. This means either + // D1 [the early echo delay] or D2 [the reverberator delay] + // will be zero, depending on whether the total delay of + // the early echo is longer or + // shorter than the shortest delay in the reverberator." + reverberatorDelayTimeScale = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, reverberatorDelayTimeScale, status); + reverberatorDelayTimeScale->inputs.left = + reverberatorDelayTime->outputs.main; + reverberatorDelayTimeScale->inputs.right = delayTimeScaleKnob->outputs.main; + reverberatorDelayDL = sig_DelayLine_new(&delayLineAllocator, + MAX_DELAY_LINE_LENGTH); + reverberatorDelay = sig_dsp_Delay_new(&allocator, context); + sig_List_append(&signals, reverberatorDelay, status); + reverberatorDelay->delayLine = reverberatorDelayDL; + reverberatorDelay->inputs.source = ap->outputs.main; + reverberatorDelay->inputs.delayTime = + reverberatorDelayTimeScale->outputs.main; + + reverberatorMixScale = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, reverberatorMixScale, status); + reverberatorMixScale->inputs.left = reverberatorDelay->outputs.main; + reverberatorMixScale->inputs.right = combGainKnob->outputs.main; + + earlyEchoesReverberatorMix = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, earlyEchoesReverberatorMix, status); + earlyEchoesReverberatorMix->inputs.left = earlyEchoes->outputs.main; + earlyEchoesReverberatorMix->inputs.right = + reverberatorMixScale->outputs.main; + + // TODO: Add wet/dry mix + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = earlyEchoesReverberatorMix->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = earlyEchoesReverberatorMix->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 2 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + // Can't write to memory in SDRAM until after the board has + // been initialized. + delayLineAllocator.impl->init(&delayLineAllocator); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + buildSignalGraph(context, &status); + host.Start(); + + while (1) { + UpdateOled(); + } +} diff --git a/hosts/daisy/examples/dpt/Makefile b/hosts/daisy/examples/dpt/Makefile index 420997f..aa72bd6 100644 --- a/hosts/daisy/examples/dpt/Makefile +++ b/hosts/daisy/examples/dpt/Makefile @@ -1,5 +1,7 @@ all: $(MAKE) -C lfos + $(MAKE) -C passthrough clean: $(MAKE) -C lfos clean + $(MAKE) -C passthrough clean diff --git a/hosts/daisy/examples/dpt/lfos/Makefile b/hosts/daisy/examples/dpt/lfos/Makefile index b72abbf..ead8557 100644 --- a/hosts/daisy/examples/dpt/lfos/Makefile +++ b/hosts/daisy/examples/dpt/lfos/Makefile @@ -1,15 +1,15 @@ # Project Name TARGET ?= signaletic-dpt-lfos -DEBUG = 0 -OPT = -O3 +DEBUG = 1 +OPT = -O0 # Sources -C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include -CPP_SOURCES = src/${TARGET}.cpp ../../../vendor/dpt/lib/daisy_dpt.cpp ../../../vendor/dpt/lib/dev/DAC7554.cpp ../../../src/signaletic-daisy-host.cpp ../../../src/daisy-dpt-host.cpp +CPP_SOURCES = ../../../vendor/dpt/lib/dev/DAC7554.cpp ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp USE_FATFS = 0 diff --git a/hosts/daisy/examples/dpt/lfos/include/signaletic-dpt-lfos-signals.h b/hosts/daisy/examples/dpt/lfos/include/signaletic-dpt-lfos-signals.h new file mode 100644 index 0000000..305dfb5 --- /dev/null +++ b/hosts/daisy/examples/dpt/lfos/include/signaletic-dpt-lfos-signals.h @@ -0,0 +1,105 @@ +#include +#include "../../../../include/signaletic-host.h" + +struct sig_host_ClockedLFO_Inputs { + float_array_ptr clockFreq; +}; + +struct sig_host_ClockedLFO_Parameters { + int freqScaleCVInputControl; + int lfoGainCVInputControl; + int cvOutputControl; +}; + +// TODO: This isn't quite the correct name. +struct sig_host_ClockedLFO { + struct sig_dsp_Signal signal; + struct sig_host_ClockedLFO_Inputs inputs; + struct sig_host_ClockedLFO_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_FilteredCVIn* freqScaleIn; + struct sig_dsp_BinaryOp* clockFreqMultiplier; + struct sig_host_FilteredCVIn* lfoGainIn; + struct sig_dsp_Oscillator* lfo; + struct sig_host_CVOut* cvOut; + struct sig_host_HardwareInterface* hardware; +}; + +void sig_host_ClockedLFO_generate(void* signal) { + struct sig_host_ClockedLFO* self = + (struct sig_host_ClockedLFO*) signal; + // FIXME: Need to either more formally separate construction from + // initialization, or introduce an additional lifecycle hook for + // handling this kind of initialization logic. + self->freqScaleIn->hardware = self->hardware; + self->freqScaleIn->parameters.control = + self->parameters.freqScaleCVInputControl; + self->lfoGainIn->hardware = self->hardware; + self->lfoGainIn->parameters.control = + self->parameters.lfoGainCVInputControl; + self->cvOut->hardware = self->hardware; + self->cvOut->parameters.control = self->parameters.cvOutputControl; + + self->clockFreqMultiplier->inputs.left = self->inputs.clockFreq; + + self->freqScaleIn->signal.generate(self->freqScaleIn); + self->clockFreqMultiplier->signal.generate(self->clockFreqMultiplier); + self->lfoGainIn->signal.generate(self->lfoGainIn); + self->lfo->signal.generate(self->lfo); + self->cvOut->signal.generate(self->cvOut); +} + +void sig_host_ClockedLFO_init(struct sig_host_ClockedLFO* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_ClockedLFO_generate); + self->parameters.freqScaleCVInputControl = 1; + self->parameters.lfoGainCVInputControl = 2; + self->parameters.cvOutputControl = 1; + + self->freqScaleIn->parameters.scale = 9.99f; + self->freqScaleIn->parameters.offset = 0.01f; + + // TODO: Implement proper calibration for CV output + // My DPT seems to output -4.67V to 7.96V, + // this was tuned by hand with the (uncalibrated) VCV Rack oscilloscope. + // cvOut->parameters.scale = 0.68; + // cvOut->parameters.offset = -0.32; + + self->clockFreqMultiplier->inputs.right = self->freqScaleIn->outputs.main; + self->lfo->inputs.freq = self->clockFreqMultiplier->outputs.main; + self->lfo->inputs.mul = self->lfoGainIn->outputs.main; + self->outputs.main = self->lfo->outputs.main; + self->cvOut->inputs.source = self->lfo->outputs.main; + sig_CONNECT_TO_SILENCE(self, clockFreq, context); +} + +struct sig_host_ClockedLFO* sig_host_ClockedLFO_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_host_ClockedLFO* self = sig_MALLOC(allocator, + struct sig_host_ClockedLFO); + + self->freqScaleIn = sig_host_FilteredCVIn_new(allocator, context); + self->clockFreqMultiplier = sig_dsp_Mul_new(allocator, context); + self->lfoGainIn = sig_host_FilteredCVIn_new(allocator, context); + self->lfo = sig_dsp_LFTriangle_new(allocator, context); + self->cvOut = sig_host_CVOut_new(allocator, context); + + sig_host_ClockedLFO_init(self, context); + + return self; +} + +void sig_host_ClockedLFO_destroy(struct sig_Allocator* allocator, + struct sig_host_ClockedLFO* self) { + sig_host_FilteredCVIn_destroy(allocator, self->freqScaleIn); + sig_dsp_Mul_destroy(allocator, self->clockFreqMultiplier); + sig_host_FilteredCVIn_destroy(allocator, self->lfoGainIn); + sig_dsp_LFTriangle_destroy(allocator, self->lfo); + sig_host_CVOut_destroy(allocator, self->cvOut); + + // We don't call sig_dsp_Signal_destroy + // because our output is borrowed from self->lfo, + // which was already freed in LFTriangle's destructor. + allocator->impl->free(allocator, self); +} + diff --git a/hosts/daisy/examples/dpt/lfos/src/signaletic-dpt-lfos.cpp b/hosts/daisy/examples/dpt/lfos/src/signaletic-dpt-lfos.cpp index 4ee9c75..dfb881e 100644 --- a/hosts/daisy/examples/dpt/lfos/src/signaletic-dpt-lfos.cpp +++ b/hosts/daisy/examples/dpt/lfos/src/signaletic-dpt-lfos.cpp @@ -1,7 +1,7 @@ -#include "daisy.h" -#include -#include "../../../../include/daisy-dpt-host.h" +#include "../include/signaletic-dpt-lfos-signals.h" +#include "../../../../include/dspcoffee-dpt-device.hpp" +#define SAMPLERATE 48000 #define HEAP_SIZE 1024 * 256 // 256KB #define MAX_NUM_SIGNALS 128 @@ -11,13 +11,12 @@ struct sig_AllocatorHeap heap = { .memory = (void*) memory }; -struct sig_Allocator alloc = { +struct sig_Allocator allocator = { .impl = &sig_TLSFAllocatorImpl, .heap = &heap }; -daisy::dpt::DPT dpt; -struct sig_daisy_Host* dptHost; +DaisyHost host; struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; struct sig_List signals = { @@ -27,71 +26,95 @@ struct sig_List signals = { }; struct sig_dsp_SignalListEvaluator* evaluator; -struct sig_daisy_GateIn* clockInput; -struct sig_dsp_ClockFreqDetector* clockFreq; -struct sig_daisy_CVIn* lfoAmpValue; -struct sig_dsp_BinaryOp* lfoGain; -struct sig_daisy_CVIn* lfoClockScaleValue; -struct sig_dsp_BinaryOp* lfoClockScale; -struct sig_dsp_Oscillator* lfo; -struct sig_daisy_CVOut* cv1Out; -struct sig_daisy_GateOut* gate1Out; +struct sig_host_GateIn* clockInput; +struct sig_dsp_ClockDetector* clockFreq; +struct sig_host_ClockedLFO* lfo1; +struct sig_host_ClockedLFO* lfo2; +struct sig_host_ClockedLFO* lfo3; +struct sig_host_ClockedLFO* lfo4; +struct sig_dsp_BinaryOp* lfo1PlusLFO3; +struct sig_dsp_BinaryOp* lfo2PlusLFO4; +struct sig_host_CVOut* lfo1And3Out; +struct sig_host_CVOut* lfo2And4Out; +struct sig_host_GateOut* gate1Out; void InitCVInputs(struct sig_SignalContext* context, struct sig_Status* status) { - clockInput = sig_daisy_GateIn_new(&alloc, context, dptHost); - clockInput->parameters.control = sig_daisy_DPT_GATE_IN_1; + clockInput = sig_host_GateIn_new(&allocator, context); + clockInput->hardware = &host.device.hardware; + clockInput->parameters.control = sig_host_GATE_IN_1; sig_List_append(&signals, clockInput, status); - - lfoClockScaleValue = sig_daisy_CVIn_new(&alloc, context, dptHost); - lfoClockScaleValue->parameters.control = sig_daisy_DPT_CV_IN_1; - lfoClockScaleValue->parameters.scale = 9.9f; - lfoClockScaleValue->parameters.offset = 0.1f; - sig_List_append(&signals, lfoClockScaleValue, status); - - lfoAmpValue = sig_daisy_CVIn_new(&alloc, context, dptHost); - lfoAmpValue->parameters.control = sig_daisy_DPT_CV_IN_2; - sig_List_append(&signals, lfoAmpValue, status); } void InitClock(struct sig_SignalContext* context, struct sig_Status* status) { - clockFreq = sig_dsp_ClockFreqDetector_new(&alloc, context); + clockFreq = sig_dsp_ClockDetector_new(&allocator, context); clockFreq->inputs.source = clockInput->outputs.main; sig_List_append(&signals, clockFreq, status); } void InitLFO(struct sig_SignalContext* context, struct sig_Status* status) { - lfoClockScale = sig_dsp_Mul_new(&alloc, context); - lfoClockScale->inputs.left = clockFreq->outputs.main; - lfoClockScale->inputs.right = lfoClockScaleValue->outputs.main; - sig_List_append(&signals, lfoClockScale, status); - - lfo = sig_dsp_LFTriangle_new(&alloc, context); - lfo->inputs.freq = lfoClockScale->outputs.main; - lfo->inputs.mul = sig_AudioBlock_newWithValue(&alloc, - context->audioSettings, 1.0f); - sig_List_append(&signals, lfo, status); - - lfoGain = sig_dsp_Mul_new(&alloc, context); - lfoGain->inputs.left = lfo->outputs.main; - lfoGain->inputs.right = lfoAmpValue->outputs.main; - sig_List_append(&signals, lfoGain, status); + lfo1 = sig_host_ClockedLFO_new(&allocator, context); + lfo1->hardware = &host.device.hardware; + sig_List_append(&signals, lfo1, status); + lfo1->parameters.freqScaleCVInputControl = sig_host_CV_IN_1; + lfo1->parameters.lfoGainCVInputControl = sig_host_CV_IN_2; + lfo1->parameters.cvOutputControl = sig_host_CV_OUT_1; + lfo1->inputs.clockFreq = clockFreq->outputs.main; + + lfo2 = sig_host_ClockedLFO_new(&allocator, context); + lfo2->hardware = &host.device.hardware; + sig_List_append(&signals, lfo2, status); + lfo2->parameters.freqScaleCVInputControl = sig_host_CV_IN_3; + lfo2->parameters.lfoGainCVInputControl = sig_host_CV_IN_4; + lfo2->parameters.cvOutputControl = sig_host_CV_OUT_2; + lfo2->inputs.clockFreq = clockFreq->outputs.main; + + lfo3 = sig_host_ClockedLFO_new(&allocator, context); + lfo3->hardware = &host.device.hardware; + sig_List_append(&signals, lfo3, status); + lfo3->inputs.clockFreq = clockFreq->outputs.main; + lfo3->parameters.freqScaleCVInputControl = sig_host_CV_IN_5; + lfo3->parameters.lfoGainCVInputControl = sig_host_CV_IN_6; + lfo3->parameters.cvOutputControl = sig_host_CV_OUT_3; + + lfo4 = sig_host_ClockedLFO_new(&allocator, context); + lfo4->hardware = &host.device.hardware; + sig_List_append(&signals, lfo4, status); + lfo4->inputs.clockFreq = clockFreq->outputs.main; + lfo4->parameters.freqScaleCVInputControl = sig_host_CV_IN_7; + lfo4->parameters.lfoGainCVInputControl = sig_host_CV_IN_8; + lfo4->parameters.cvOutputControl = sig_host_CV_OUT_4; + + lfo1PlusLFO3 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, lfo1PlusLFO3, status); + lfo1PlusLFO3->inputs.left = lfo1->outputs.main; + lfo1PlusLFO3->inputs.right = lfo3->outputs.main; + + lfo2PlusLFO4 = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, lfo2PlusLFO4, status); + lfo2PlusLFO4->inputs.left = lfo2->outputs.main; + lfo2PlusLFO4->inputs.right = lfo4->outputs.main; + + lfo1And3Out = sig_host_CVOut_new(&allocator, context); + lfo1And3Out->hardware = &host.device.hardware; + sig_List_append(&signals, lfo1And3Out, status); + lfo1And3Out->inputs.source = lfo1PlusLFO3->outputs.main; + lfo1And3Out->parameters.control = sig_host_CV_OUT_5; + lfo1And3Out->parameters.scale = 0.5f; // TODO: Replace with a wavefolder. + + lfo2And4Out = sig_host_CVOut_new(&allocator, context); + lfo2And4Out->hardware = &host.device.hardware; + sig_List_append(&signals, lfo2And4Out, status); + lfo2And4Out->inputs.source = lfo2PlusLFO4->outputs.main; + lfo2And4Out->parameters.control = sig_host_CV_OUT_6; + lfo2And4Out->parameters.scale = 0.5f; } void InitCVOutputs(struct sig_SignalContext* context, struct sig_Status* status) { - cv1Out = sig_daisy_CVOut_new(&alloc, context, dptHost); - cv1Out->parameters.control = sig_daisy_DPT_CV_OUT_1; - cv1Out->inputs.source = lfoGain->outputs.main; - sig_List_append(&signals, cv1Out, status); - - // TODO: My DPT seems to output -4.67V to 7.96V, - // this was tuned by hand with the VCV Rack oscilloscope. - cv1Out->parameters.scale = 0.68; - cv1Out->parameters.offset = -0.32; - - gate1Out = sig_daisy_GateOut_new(&alloc, context, dptHost); - gate1Out->parameters.control = sig_daisy_DPT_GATE_OUT_1; + gate1Out = sig_host_GateOut_new(&allocator, context); + gate1Out->hardware = &host.device.hardware; + gate1Out->parameters.control = sig_host_GATE_OUT_1; gate1Out->inputs.source = clockInput->outputs.main; sig_List_append(&signals, gate1Out, status); } @@ -105,28 +128,25 @@ void buildSignalGraph(struct sig_SignalContext* context, } int main(void) { + allocator.impl->init(&allocator); + struct sig_AudioSettings audioSettings = { - .sampleRate = 48000, + .sampleRate = SAMPLERATE, .numChannels = 2, - .blockSize = 1 + .blockSize = 48 }; struct sig_Status status; sig_Status_init(&status); - alloc.impl->init(&alloc); sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); - evaluator = sig_dsp_SignalListEvaluator_new(&alloc, &signals); - dptHost = sig_daisy_DPTHost_new(&alloc, - &audioSettings, &dpt, (struct sig_dsp_SignalEvaluator*) evaluator); - sig_daisy_Host_registerGlobalHost(dptHost); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); struct sig_SignalContext* context = sig_SignalContext_new( - &alloc, &audioSettings); + &allocator, &audioSettings); buildSignalGraph(context, &status); - dptHost->impl->start(dptHost); - - while (1) { + host.Start(); - } + while (1) {} } diff --git a/hosts/daisy/examples/dpt/passthrough/Makefile b/hosts/daisy/examples/dpt/passthrough/Makefile new file mode 100644 index 0000000..a79facc --- /dev/null +++ b/hosts/daisy/examples/dpt/passthrough/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= signaletic-dpt-passthrough + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = ../../../vendor/dpt/lib/dev/DAC7554.cpp ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/dpt/passthrough/src/signaletic-dpt-passthrough.cpp b/hosts/daisy/examples/dpt/passthrough/src/signaletic-dpt-passthrough.cpp new file mode 100644 index 0000000..5f184fa --- /dev/null +++ b/hosts/daisy/examples/dpt/passthrough/src/signaletic-dpt-passthrough.cpp @@ -0,0 +1,185 @@ +#include "../../../../include/dspcoffee-dpt-device.hpp" + +#define SAMPLERATE 96000 +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 128 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +DaisyHost host; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals = { + .items = (void**) &listStorage, + .capacity = MAX_NUM_SIGNALS, + .length = 0 +}; + +struct sig_dsp_SignalListEvaluator* evaluator; +struct sig_host_CVIn* cv1In; +struct sig_host_CVIn* cv2In; +struct sig_host_CVIn* cv3In; +struct sig_host_CVIn* cv4In; +struct sig_host_CVIn* cv5In; +struct sig_host_CVIn* cv6In; +struct sig_host_CVOut* cv1Out; +struct sig_host_CVOut* cv2Out; +struct sig_host_CVOut* cv3Out; +struct sig_host_CVOut* cv4Out; +struct sig_host_CVOut* cv5Out; +struct sig_host_CVOut* cv6Out; +struct sig_host_GateIn* gate1In; +struct sig_host_GateIn* gate2In; +struct sig_host_GateOut* gate1Out; +struct sig_host_GateOut* gate2Out; +struct sig_host_AudioIn* leftAudioIn; +struct sig_host_AudioIn* rightAudioIn; +struct sig_host_AudioOut* leftAudioOut; +struct sig_host_AudioOut* rightAudioOut; + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + cv1In = sig_host_CVIn_new(&allocator, context); + cv1In->hardware = &host.device.hardware; + sig_List_append(&signals, cv1In, status); + cv1In->parameters.control = sig_host_CV_IN_1; + + cv2In = sig_host_CVIn_new(&allocator, context); + cv2In->hardware = &host.device.hardware; + sig_List_append(&signals, cv2In, status); + cv2In->parameters.control = sig_host_CV_IN_2; + + cv3In = sig_host_CVIn_new(&allocator, context); + cv3In->hardware = &host.device.hardware; + sig_List_append(&signals, cv3In, status); + cv3In->parameters.control = sig_host_CV_IN_3; + + cv4In = sig_host_CVIn_new(&allocator, context); + cv4In->hardware = &host.device.hardware; + sig_List_append(&signals, cv4In, status); + cv4In->parameters.control = sig_host_CV_IN_4; + + cv5In = sig_host_CVIn_new(&allocator, context); + cv5In->hardware = &host.device.hardware; + sig_List_append(&signals, cv5In, status); + cv5In->parameters.control = sig_host_CV_IN_5; + + cv6In = sig_host_CVIn_new(&allocator, context); + cv6In->hardware = &host.device.hardware; + sig_List_append(&signals, cv6In, status); + cv6In->parameters.control = sig_host_CV_IN_6; + + cv1Out = sig_host_CVOut_new(&allocator, context); + cv1Out->hardware = &host.device.hardware; + sig_List_append(&signals, cv1Out, status); + cv1Out->inputs.source = cv1In->outputs.main; + cv1Out->parameters.control = sig_host_CV_OUT_1; + + cv2Out = sig_host_CVOut_new(&allocator, context); + cv2Out->hardware = &host.device.hardware; + sig_List_append(&signals, cv2Out, status); + cv2Out->inputs.source = cv2In->outputs.main; + cv2Out->parameters.control = sig_host_CV_OUT_2; + + cv3Out = sig_host_CVOut_new(&allocator, context); + cv3Out->hardware = &host.device.hardware; + sig_List_append(&signals, cv3Out, status); + cv3Out->inputs.source = cv3In->outputs.main; + cv3Out->parameters.control = sig_host_CV_OUT_3; + + cv4Out = sig_host_CVOut_new(&allocator, context); + cv4Out->hardware = &host.device.hardware; + sig_List_append(&signals, cv4Out, status); + cv4Out->inputs.source = cv4In->outputs.main; + cv4Out->parameters.control = sig_host_CV_OUT_4; + + cv5Out = sig_host_CVOut_new(&allocator, context); + cv5Out->hardware = &host.device.hardware; + sig_List_append(&signals, cv5Out, status); + cv5Out->inputs.source = cv5In->outputs.main; + cv5Out->parameters.control = sig_host_CV_OUT_5; + + cv6Out = sig_host_CVOut_new(&allocator, context); + cv6Out->hardware = &host.device.hardware; + sig_List_append(&signals, cv6Out, status); + cv6Out->inputs.source = cv6In->outputs.main; + cv6Out->parameters.control = sig_host_CV_OUT_6; + + gate1In = sig_host_GateIn_new(&allocator, context); + gate1In->hardware = &host.device.hardware; + sig_List_append(&signals, gate1In, status); + gate1In->parameters.control = sig_host_GATE_IN_1; + + gate2In = sig_host_GateIn_new(&allocator, context); + gate2In->hardware = &host.device.hardware; + sig_List_append(&signals, gate2In, status); + gate2In->parameters.control = sig_host_GATE_IN_2; + + gate1Out = sig_host_GateOut_new(&allocator, context); + gate1Out->hardware = &host.device.hardware; + sig_List_append(&signals, gate1Out, status); + gate1Out->parameters.control = sig_host_GATE_OUT_1; + gate1Out->inputs.source = gate1In->outputs.main; + + gate2Out = sig_host_GateOut_new(&allocator, context); + gate2Out->hardware = &host.device.hardware; + sig_List_append(&signals, gate2Out, status); + gate2Out->parameters.control = sig_host_GATE_OUT_2; + gate2Out->inputs.source = gate2In->outputs.main; + + leftAudioIn = sig_host_AudioIn_new(&allocator, context); + leftAudioIn->hardware = &host.device.hardware; + sig_List_append(&signals, leftAudioIn, status); + leftAudioIn->parameters.channel = sig_host_AUDIO_IN_1; + + rightAudioIn = sig_host_AudioIn_new(&allocator, context); + rightAudioIn->hardware = &host.device.hardware; + sig_List_append(&signals, rightAudioIn, status); + rightAudioIn->parameters.channel = sig_host_AUDIO_IN_2; + + leftAudioOut = sig_host_AudioOut_new(&allocator, context); + leftAudioOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftAudioOut, status); + leftAudioOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftAudioOut->inputs.source = leftAudioIn->outputs.main; + + + rightAudioOut = sig_host_AudioOut_new(&allocator, context); + rightAudioOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightAudioOut, status); + rightAudioOut->parameters.channel = sig_host_AUDIO_IN_2; + rightAudioOut->inputs.source = rightAudioIn->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 1 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + struct sig_SignalContext* context = sig_SignalContext_new( + &allocator, &audioSettings); + buildSignalGraph(context, &status); + host.Start(); + + while (1) {} +} diff --git a/hosts/daisy/examples/dpt/triangles/Makefile b/hosts/daisy/examples/dpt/triangles/Makefile new file mode 100644 index 0000000..0a95e8a --- /dev/null +++ b/hosts/daisy/examples/dpt/triangles/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= signaletic-dpt-triangles + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = ../../../vendor/dpt/lib/dev/DAC7554.cpp ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/dpt/triangles/src/signaletic-dpt-triangles.cpp b/hosts/daisy/examples/dpt/triangles/src/signaletic-dpt-triangles.cpp new file mode 100644 index 0000000..a5b9207 --- /dev/null +++ b/hosts/daisy/examples/dpt/triangles/src/signaletic-dpt-triangles.cpp @@ -0,0 +1,93 @@ +#include "../../../../include/dspcoffee-dpt-device.hpp" + +#define SAMPLERATE 96000 +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 128 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +DaisyHost host; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals = { + .items = (void**) &listStorage, + .capacity = MAX_NUM_SIGNALS, + .length = 0 +}; + +struct sig_dsp_SignalListEvaluator* evaluator; +struct sig_host_CVIn* frequency; +struct sig_host_CVIn* triangleGain; +struct sig_dsp_Oscillator* triangle; +struct sig_dsp_BinaryOp* scaledTriangle; +struct sig_host_CVOut* cv1Out; +struct sig_host_CVOut* cv3Out; +float cv1Value; +float cv2Value; + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + frequency = sig_host_CVIn_new(&allocator, context); + frequency->hardware = &host.device.hardware; + sig_List_append(&signals, frequency, status); + frequency->parameters.control = sig_host_CV_IN_1; + frequency->parameters.scale = 110.0f; + + triangle = sig_dsp_LFTriangle_new(&allocator, context); + sig_List_append(&signals, triangle, status); + triangle->inputs.freq = frequency->outputs.main; + + triangleGain =sig_host_CVIn_new(&allocator, context); triangleGain->hardware = &host.device.hardware; + sig_List_append(&signals, triangleGain, status); + triangleGain->parameters.control = sig_host_CV_IN_2; + + scaledTriangle= sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, scaledTriangle, status); + scaledTriangle->inputs.left = triangle->outputs.main; + scaledTriangle->inputs.right = triangleGain->outputs.main; + + cv1Out = sig_host_CVOut_new(&allocator, context); + cv1Out->hardware = &host.device.hardware; + sig_List_append(&signals, cv1Out, status); + cv1Out->inputs.source = scaledTriangle->outputs.main; + cv1Out->parameters.control = sig_host_CV_OUT_1; + + cv3Out = sig_host_CVOut_new(&allocator, context); + cv3Out->hardware = &host.device.hardware; + sig_List_append(&signals, cv3Out, status); + cv3Out->inputs.source = scaledTriangle->outputs.main; + cv3Out->parameters.control = sig_host_CV_OUT_3; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 1 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + struct sig_SignalContext* context = sig_SignalContext_new( + &allocator, &audioSettings); + buildSignalGraph(context, &status); + host.Start(); + + while (1) {} +} diff --git a/hosts/daisy/examples/lichen-bifocals/Makefile b/hosts/daisy/examples/lichen-bifocals/Makefile new file mode 100644 index 0000000..4911598 --- /dev/null +++ b/hosts/daisy/examples/lichen-bifocals/Makefile @@ -0,0 +1,5 @@ +all: + $(MAKE) -C filter + +clean: + $(MAKE) -C filter clean diff --git a/hosts/daisy/examples/lichen-bifocals/filter/Makefile b/hosts/daisy/examples/lichen-bifocals/filter/Makefile new file mode 100644 index 0000000..673df2e --- /dev/null +++ b/hosts/daisy/examples/lichen-bifocals/filter/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= lichen-bifocals-filter + +DEBUG = 0 +OPT = -O3 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/lichen-bifocals/filter/src/lichen-bifocals-filter.cpp b/hosts/daisy/examples/lichen-bifocals/filter/src/lichen-bifocals-filter.cpp new file mode 100644 index 0000000..ae81075 --- /dev/null +++ b/hosts/daisy/examples/lichen-bifocals/filter/src/lichen-bifocals-filter.cpp @@ -0,0 +1,393 @@ +// Inspired by the pole-mixing multimode from the Oberheim XPander. +// https://electricdruid.net/multimode-filters-part-2-pole-mixing-filters/ +// and Valimaki and Huovilainen, "Oscillator and Filter Algorithms for Virtual +// Analog Synthesis." Computer Music Journal, Vol. 30, No. 2 (Summer, 2006), +// pp. 19-31. https://www.jstor.org/stable/3682001 + +/* +Mixing coefficients for filter responses, as listed in Electric Druid (2020): +(in the form [Response name {Input, 1-pole, 2-pole, 3-pole, 4-pole}]) +x = included in the Oberheim XPander +Low Pass: + 6dB lowpass {0, -1, 0, 0, 0} x + 12dB lowpass {0, 0, 1, 0, 0} x + 18dB lowpass {0, 0, 0, -1, 0} x + 24dB lowpass {0, 0, 0, 0, 1} x +High Pass: + 6dB highpass {1, -1, 0, 0, 0} x + 12dB highpass {1, -2, 1, 0, 0} x + 18dB highpass {1, -3, 3, -1, 0} x + 24dB highpass {1, -4, 6, -4, 1} +Band Pass: + 12dB bandpass {0, -1, 1, 0, 0} x + 24dB bandpass {0, 0, 1, -2, 1} x + 6dB highpass + 12dB lowpass {0, 0, 1, -1, 0} + 6dB highpass + 18dB lowpass {0, 0, 0, -1, 1} + 12dB highpass + 6dB lowpass {0, -1, 2, -1, 0} x + 18dB highpass + 6dB lowpass {0, -1, 3, -3, 1} x +Notch: + 12dB notch {1, -2, 2, -0, 0} x + 18dB notch {1, -3, 3, -2, 0} + 12dB notch + 6db lowpass {0, -1, 2, -2, 0} x +All Pass: + 6dB all pass {1, -2, 0, 0, 0} + 12dB all pass {1, -4, 4, 0, 0} + 18dB all pass {1, -6, 12, -8, 0} x + 24dB all pass {1, -8, 24, -32, 16} + 18dB allpass + 6dB lowpass {0, -1, 3, -6, 4} x +All Pass Phaser: + 6dB phaser {1, -1, 0, 0, 0} (same as the 6dB high pass) + 12dB phaser {1, -2, 2, 0, 0} + 18dB phaser {1, -3, 6, -4, 0} + 24dB phaser {1, -4, 12, -16, 8} +*/ + +/* +Knob Scaling: + Pole 1 (B): -4..4 (should be -8..0) + Pole 2 (C): -6..6 (should be 0..24) + Pole 3 (D): -8..0 (should be -32..0) + Pole 4 (E): 0..4 (should be 0..16) +*/ +#include +#include "../../../../include/lichen-bifocals-device.hpp" + +#define SAMPLERATE 96000 +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 64 + +// The best, with band passes +#define NUM_FILTER_MODES 9 +#define NUM_FILTER_STAGES 5 +float mixingCoefficients[NUM_FILTER_STAGES][NUM_FILTER_MODES] = { + // 4LP, 2LP, 2BP, 4-1LP, ??, 4APP, 2HP, 3HP, 4HP + { 0, 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, -1, -1, -2, -4, -2, -3, -4 }, + { 0, 1, 1, 0, 2, 12, 1, 3, 6 }, + { 0, 0, 0, 0, -4, -16, 0, -1, -4 }, + { 1, 0, 0, 1, 4, 8, 0, 0, 1 } +}; + +struct sig_Buffer aCoefficientBuffer = { + .length = NUM_FILTER_MODES, + .samples = mixingCoefficients[0] +}; + +struct sig_Buffer bCoefficientBuffer = { + .length = NUM_FILTER_MODES, + .samples = mixingCoefficients[1] +}; + +struct sig_Buffer cCoefficientBuffer = { + .length = NUM_FILTER_MODES, + .samples = mixingCoefficients[2] +}; + +struct sig_Buffer dCoefficientBuffer = { + .length = NUM_FILTER_MODES, + .samples = mixingCoefficients[3] +}; + +struct sig_Buffer eCoefficientBuffer = { + .length = NUM_FILTER_MODES, + .samples = mixingCoefficients[4] +}; + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +sig::libdaisy::DaisyHost host; + +struct sig_dsp_List* aList; +struct sig_dsp_List* bList; +struct sig_dsp_List* cList; +struct sig_dsp_List* dList; +struct sig_dsp_List* eList; +struct sig_host_FilteredCVIn* shapeKnob; +struct sig_host_FilteredCVIn* frequencyKnob; +struct sig_host_FilteredCVIn* resonanceKnob; +struct sig_host_FilteredCVIn* resonanceCV; +struct sig_dsp_BinaryOp* resonance; +struct sig_host_FilteredCVIn* shapeCVIn; +struct sig_dsp_BinaryOp* shape; +struct sig_host_CVIn* vOctCVIn; +struct sig_host_FilteredCVIn* skewKnob; +struct sig_host_FilteredCVIn* skewCV; +struct sig_dsp_BinaryOp* skew; +struct sig_dsp_Abs* rectifiedSkew; +struct sig_dsp_BinaryOp* skewedFrequency; +struct sig_dsp_BinaryOp* frequency; +struct sig_dsp_Branch* leftSkewedFrequency; +struct sig_dsp_LinearToFreq* leftFrequency; +struct sig_dsp_Branch* rightSkewedFrequency; +struct sig_dsp_LinearToFreq* rightFrequency; +struct sig_host_AudioIn* leftIn; +struct sig_host_FilteredCVIn* gainKnob; +struct sig_host_FilteredCVIn* gainCV; +struct sig_dsp_BinaryOp* gain; +struct sig_host_AudioIn* rightIn; +struct sig_dsp_BinaryOp* leftVCA; +struct sig_dsp_BinaryOp* rightVCA; +struct sig_dsp_Ladder* leftFilter; +struct sig_dsp_Ladder* rightFilter; +struct sig_dsp_Tanh* leftSaturation; +struct sig_dsp_Tanh* rightSaturation; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + shapeKnob = sig_host_FilteredCVIn_new(&allocator, context); + shapeKnob->hardware = &host.device.hardware; + sig_List_append(&signals, shapeKnob, status); + shapeKnob->parameters.control = sig_host_KNOB_4; + + shapeCVIn = sig_host_FilteredCVIn_new(&allocator, context); + shapeCVIn->hardware = &host.device.hardware; + sig_List_append(&signals, shapeCVIn, status); + shapeCVIn->parameters.control = sig_host_CV_IN_1; + shapeCVIn->parameters.scale = 2.0f; + + shape = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, shape, status); + shape->inputs.left = shapeKnob->outputs.main; + shape->inputs.right = shapeCVIn->outputs.main; + + aList = sig_dsp_List_new(&allocator, context); + sig_List_append(&signals, aList, status); + aList->list = &aCoefficientBuffer; + aList->parameters.wrap = 0.0f; + aList->parameters.interpolate = 1.0f; + aList->inputs.index = shape->outputs.main; + + bList = sig_dsp_List_new(&allocator, context); + sig_List_append(&signals, bList, status); + bList->list = &bCoefficientBuffer; + bList->parameters.wrap = 0.0f; + bList->parameters.interpolate = 1.0f; + bList->inputs.index = shape->outputs.main; + + cList = sig_dsp_List_new(&allocator, context); + sig_List_append(&signals, cList, status); + cList->list = &cCoefficientBuffer; + cList->parameters.wrap = 0.0f; + cList->parameters.interpolate = 1.0f; + cList->inputs.index = shape->outputs.main; + + dList = sig_dsp_List_new(&allocator, context); + sig_List_append(&signals, dList, status); + dList->list = &dCoefficientBuffer; + dList->parameters.wrap = 0.0f; + dList->parameters.interpolate = 1.0f; + dList->inputs.index = shape->outputs.main; + + eList = sig_dsp_List_new(&allocator, context); + sig_List_append(&signals, eList, status); + eList->list = &eCoefficientBuffer; + eList->parameters.wrap = 0.0f; + eList->parameters.interpolate = 1.0f; + eList->inputs.index = shape->outputs.main; + + frequencyKnob = sig_host_FilteredCVIn_new(&allocator, context); + frequencyKnob->hardware = &host.device.hardware; + sig_List_append(&signals, frequencyKnob, status); + frequencyKnob->parameters.control = sig_host_KNOB_1; + frequencyKnob->parameters.scale = 10.0f; + frequencyKnob->parameters.offset = -5.0f; + frequencyKnob->parameters.time = 0.1f; + + vOctCVIn = sig_host_CVIn_new(&allocator, context); + vOctCVIn->hardware = &host.device.hardware; + sig_List_append(&signals, vOctCVIn, status); + vOctCVIn->parameters.control = sig_host_CV_IN_2; + vOctCVIn->parameters.scale = 5.0f; + + frequency = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, frequency, status); + frequency->inputs.left = frequencyKnob->outputs.main; + frequency->inputs.right = vOctCVIn->outputs.main; + + skewKnob = sig_host_FilteredCVIn_new(&allocator, context); + skewKnob->hardware = &host.device.hardware; + sig_List_append(&signals, skewKnob, status); + skewKnob->parameters.control = sig_host_KNOB_5; + skewKnob->parameters.scale = 5.0f; + skewKnob->parameters.offset = -2.5f; + skewKnob->parameters.time = 0.1f; + + skewCV = sig_host_FilteredCVIn_new(&allocator, context); + skewCV->hardware = &host.device.hardware; + sig_List_append(&signals, skewCV, status); + skewCV->parameters.control = sig_host_CV_IN_5; + skewCV->parameters.scale = 2.5f; + skewCV->parameters.time = 0.1f; + + skew = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, skew, status); + skew->inputs.left = skewKnob->outputs.main; + skew->inputs.right = skewCV->outputs.main; + + rectifiedSkew = sig_dsp_Abs_new(&allocator, context); + sig_List_append(&signals, rectifiedSkew, status); + rectifiedSkew->inputs.source = skew->outputs.main; + + skewedFrequency = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, skewedFrequency, status); + skewedFrequency->inputs.left = frequency->outputs.main; + skewedFrequency->inputs.right = rectifiedSkew->outputs.main; + + leftSkewedFrequency = sig_dsp_Branch_new(&allocator, context); + sig_List_append(&signals, leftSkewedFrequency, status); + leftSkewedFrequency->inputs.condition = skew->outputs.main; + leftSkewedFrequency->inputs.on = frequency->outputs.main; + leftSkewedFrequency->inputs.off = skewedFrequency->outputs.main; + + leftFrequency = sig_dsp_LinearToFreq_new(&allocator, context); + sig_List_append(&signals, leftFrequency, status); + leftFrequency->inputs.source = leftSkewedFrequency->outputs.main; + + rightSkewedFrequency = sig_dsp_Branch_new(&allocator, context); + sig_List_append(&signals, rightSkewedFrequency, status); + rightSkewedFrequency->inputs.condition = skew->outputs.main; + rightSkewedFrequency->inputs.on = skewedFrequency->outputs.main; + rightSkewedFrequency->inputs.off = frequency->outputs.main; + + rightFrequency = sig_dsp_LinearToFreq_new(&allocator, context); + sig_List_append(&signals, rightFrequency, status); + rightFrequency->inputs.source = rightSkewedFrequency->outputs.main; + + resonanceKnob = sig_host_FilteredCVIn_new(&allocator, context); + resonanceKnob->hardware = &host.device.hardware; + sig_List_append(&signals, resonanceKnob, status); + resonanceKnob->parameters.control = sig_host_KNOB_2; + resonanceKnob->parameters.scale = 1.8f; // 4.0f for Bob + + resonanceCV = sig_host_FilteredCVIn_new(&allocator, context); + resonanceCV->hardware = &host.device.hardware; + sig_List_append(&signals, resonanceCV, status); + resonanceCV->parameters.control = sig_host_CV_IN_3; + resonanceCV->parameters.scale = 1.8f; // 4.0f for Bob + + resonance = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, resonance, status); + resonance->inputs.left = resonanceKnob->outputs.main; + resonance->inputs.right = resonanceCV->outputs.main; + + // TODO: Clamp or calibrate to between 0 and 4 + gainKnob = sig_host_FilteredCVIn_new(&allocator, context); + gainKnob->hardware = &host.device.hardware; + sig_List_append(&signals, gainKnob, status); + gainKnob->parameters.control = sig_host_KNOB_3; + gainKnob->parameters.scale = 3.0f; + + gainCV = sig_host_FilteredCVIn_new(&allocator, context); + gainCV->hardware = &host.device.hardware; + sig_List_append(&signals, gainCV, status); + gainCV->parameters.control = sig_host_CV_IN_4; + gainCV->parameters.scale = 2.5f; + + gain = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, gain, status); + gain->inputs.left = gainKnob->outputs.main; + gain->inputs.right = gainCV->outputs.main; + + leftIn = sig_host_AudioIn_new(&allocator, context); + leftIn->hardware = &host.device.hardware; + sig_List_append(&signals, leftIn, status); + leftIn->parameters.channel = sig_host_AUDIO_IN_1; + + leftVCA = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, leftVCA, status); + leftVCA->inputs.left = leftIn->outputs.main; + leftVCA->inputs.right = gain->outputs.main; + + leftFilter = sig_dsp_Ladder_new(&allocator, context); + sig_List_append(&signals, leftFilter, status); + leftFilter->parameters.passbandGain = 0.5f; + leftFilter->inputs.source = leftVCA->outputs.main; + leftFilter->inputs.frequency = leftFrequency->outputs.main; + leftFilter->inputs.resonance = resonance->outputs.main; + leftFilter->inputs.inputGain = aList->outputs.main; + leftFilter->inputs.pole1Gain = bList->outputs.main; + leftFilter->inputs.pole2Gain = cList->outputs.main; + leftFilter->inputs.pole3Gain = dList->outputs.main; + leftFilter->inputs.pole4Gain = eList->outputs.main; + + leftSaturation = sig_dsp_Tanh_new(&allocator, context); + sig_List_append(&signals, leftSaturation, status); + leftSaturation->inputs.source = leftFilter->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = leftSaturation->outputs.main; + + rightIn = sig_host_AudioIn_new(&allocator, context); + rightIn->hardware = &host.device.hardware; + sig_List_append(&signals, rightIn, status); + rightIn->parameters.channel = sig_host_AUDIO_IN_2; + + rightVCA = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, rightVCA, status); + rightVCA->inputs.left = rightIn->outputs.main; + rightVCA->inputs.right = gain->outputs.main; + + rightFilter = sig_dsp_Ladder_new(&allocator, context); + sig_List_append(&signals, rightFilter, status); + rightFilter->parameters.passbandGain = 0.5f; + rightFilter->inputs.source = rightVCA->outputs.main; + rightFilter->inputs.frequency = rightFrequency->outputs.main; + rightFilter->inputs.resonance = resonance->outputs.main; + rightFilter->inputs.inputGain = aList->outputs.main; + rightFilter->inputs.pole1Gain = bList->outputs.main; + rightFilter->inputs.pole2Gain = cList->outputs.main; + rightFilter->inputs.pole3Gain = dList->outputs.main; + rightFilter->inputs.pole4Gain = eList->outputs.main; + + rightSaturation = sig_dsp_Tanh_new(&allocator, context); + sig_List_append(&signals, rightSaturation, status); + rightSaturation->inputs.source = rightFilter->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = rightSaturation->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 2 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + buildSignalGraph(context, &status); + + host.Start(); + + while (1) {} +} diff --git a/hosts/daisy/examples/lichen-bifocals/sine-oscillator/Makefile b/hosts/daisy/examples/lichen-bifocals/sine-oscillator/Makefile new file mode 100644 index 0000000..ec96fff --- /dev/null +++ b/hosts/daisy/examples/lichen-bifocals/sine-oscillator/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= lichen-bifocals-sine-oscillator + +DEBUG = 1 +OPT = -Og + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/lichen-bifocals/sine-oscillator/src/lichen-bifocals-sine-oscillator.cpp b/hosts/daisy/examples/lichen-bifocals/sine-oscillator/src/lichen-bifocals-sine-oscillator.cpp new file mode 100644 index 0000000..16d3e82 --- /dev/null +++ b/hosts/daisy/examples/lichen-bifocals/sine-oscillator/src/lichen-bifocals-sine-oscillator.cpp @@ -0,0 +1,109 @@ +#include +#include "../../../../include/lichen-bifocals-device.hpp" + +using namespace lichen::bifocals; +using namespace sig::libdaisy; + +#define SAMPLERATE 96000 +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +DaisyHost host; + +struct sig_host_FilteredCVIn* freqKnob; +struct sig_host_CVIn* freqCV; +struct sig_dsp_BinaryOp* freqSum; +struct sig_dsp_Oscillator* osc; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; + +FixedCapStr<6> formattedValue; + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + freqKnob = sig_host_FilteredCVIn_new(&allocator, context); + freqKnob->hardware = &host.device.hardware; + sig_List_append(&signals, freqKnob, status); + freqKnob->parameters.control = sig_host_KNOB_1; + freqKnob->parameters.scale = 1760.0f; + + freqCV = sig_host_CVIn_new(&allocator, context); + freqCV->hardware = &host.device.hardware; + sig_List_append(&signals, freqCV, status); + freqCV->parameters.control = sig_host_CV_IN_2; + freqCV->parameters.scale = 440.0f; + + freqSum = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, freqSum, status); + freqSum->inputs.left = freqKnob->outputs.main; + freqSum->inputs.right = freqCV->outputs.main; + + osc = sig_dsp_Sine_new(&allocator, context); + sig_List_append(&signals, osc, status); + osc->inputs.freq = freqSum->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = osc->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = osc->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 48 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + buildSignalGraph(context, &status); + + host.Start(); + + daisy::Logger::StartLog(true); + + uint32_t then = System::GetNow(); + while (1) { + uint32_t now = System::GetNow(); + if (now - then >= 1000) { + float value = host.device.adcController.channelBank.values[5]; + formattedValue.Clear(); + formattedValue.AppendFloat(value, 3); + daisy::Logger::PrintLine("Value: %s", + formattedValue.Cstr()); + + then = now; + } + } +} diff --git a/hosts/daisy/examples/lichen-freddie/Makefile b/hosts/daisy/examples/lichen-freddie/Makefile new file mode 100644 index 0000000..9fbe8a8 --- /dev/null +++ b/hosts/daisy/examples/lichen-freddie/Makefile @@ -0,0 +1,9 @@ +all: + $(MAKE) -C bare-board-test + $(MAKE) -C filter + $(MAKE) -C sine-oscillator + +clean: + $(MAKE) -C bare-board-test clean + $(MAKE) -C filter clean + $(MAKE) -C sine-oscillator clean diff --git a/hosts/daisy/examples/lichen-freddie/bare-board-test/Makefile b/hosts/daisy/examples/lichen-freddie/bare-board-test/Makefile new file mode 100644 index 0000000..2eb4aa2 --- /dev/null +++ b/hosts/daisy/examples/lichen-freddie/bare-board-test/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= lichen-freddie-bare-board-test + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include -I/include +CPP_SOURCES = src/${TARGET}.cpp ../../../src/sig-daisy-seed.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/lichen-freddie/bare-board-test/include/ring-buffer.hpp b/hosts/daisy/examples/lichen-freddie/bare-board-test/include/ring-buffer.hpp new file mode 100644 index 0000000..eb91dbd --- /dev/null +++ b/hosts/daisy/examples/lichen-freddie/bare-board-test/include/ring-buffer.hpp @@ -0,0 +1,63 @@ +#pragma once +#include // For size_t + +namespace sig { + template class RingBuffer { + public: + size_t capacity = size; + T buffer[size]; + volatile size_t readIdx; + volatile size_t writeIdx; + + void Init() { + readIdx = 0; + writeIdx = 0; + } + + /** + * @brief Returns the number of unread elements in the buffer. + * + * @return size_t the number of unread elements + */ + inline size_t readable() { + return (size + writeIdx - readIdx) % size; + } + + /** + * @brief Returns the number of elements that can be written + * without overwriting unread data. + * + * @return size_t the number of writeable items remaining + */ + inline size_t writeable() { + // FIXME: This returns 0 whenever all elements have been read + // (i.e. when it's empty), including in its initial state. + // This article has a good overview of the issue: + // https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/ + return (size + readIdx - writeIdx) % size; + } + + /** + * @brief Directly + * + * @return T + */ + inline T read() { + size_t i = readIdx; + T result = buffer[i]; + readIdx = (i + 1) % size; + + return result; + } + + inline void write(T value) { + size_t i = writeIdx; + buffer[i] = value; + writeIdx = (i + 1) % size; + } + + inline void Flush() { + writeIdx = readIdx; + } + }; +}; diff --git a/hosts/daisy/examples/lichen-freddie/bare-board-test/src/lichen-freddie-bare-board-test.cpp b/hosts/daisy/examples/lichen-freddie/bare-board-test/src/lichen-freddie-bare-board-test.cpp new file mode 100644 index 0000000..125826a --- /dev/null +++ b/hosts/daisy/examples/lichen-freddie/bare-board-test/src/lichen-freddie-bare-board-test.cpp @@ -0,0 +1,200 @@ +#include "daisy.h" +#include +#include +#include "../../../../include/signaletic-daisy-host.hpp" +#include "../../../../include/lichen-freddie-device.hpp" +#include "../include/ring-buffer.hpp" + +#define SAMPLERATE 48000 +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 32 +#define NUM_CONTROLS 8 +#define MIDI_QUEUE_SIZE 128 +#define MIDI_MESSAGE_SIZE 3 +#define SMOOTH_COEFFICIENT 0.0001f + +const uint8_t sliderToCC[NUM_CONTROLS] = {0, 1, 2, 3, 4, 5, 6, 7}; +const uint8_t buttonToCC[NUM_CONTROLS] = {48, 49, 50, 51, 52, 53, 54, 55}; +// MIDI notes corresponding to the first 8 pads of MPC. +const uint8_t buttonToNote[NUM_CONTROLS] = {37, 36, 42, 82, 40, 38, 46, 44}; + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; + +lichen::freddie::FreddieDevice device; +MidiUsbHandler usbMIDI; +MidiUartHandler trsMIDI; + +sig::libdaisy::Toggle buttons[NUM_CONTROLS]; +sig::libdaisy::GPIOOutput leds[NUM_CONTROLS]; +float previousButtonValues[NUM_CONTROLS] = {0.0f}; +uint8_t previousSliderMIDIValues[NUM_CONTROLS] = {0}; +bool buttonState[NUM_CONTROLS] = {false}; +sig::RingBuffer, MIDI_QUEUE_SIZE> + midiEvents; +struct sig_filter_Smooth sliderFilters[NUM_CONTROLS]; + +void enqueueMIDIMessage(std::array msg) { + midiEvents.write(msg); +} + +void sendMIDIMessage(std::array msg) { + usbMIDI.SendMessage(msg.data(), MIDI_MESSAGE_SIZE); + trsMIDI.SendMessage(msg.data(), MIDI_MESSAGE_SIZE); +} + +void midiCCEvent(uint8_t ccNum, uint8_t value, uint8_t channel = 0) { + std::array msg = {0}; + msg[0] = (channel & 0x0F) + 0xB0; + msg[1] = ccNum & 0x7F; + msg[2] = value & 0x7F; + enqueueMIDIMessage(msg); +} + +void midiNoteEvent(bool isNoteOn, uint8_t note, uint8_t velocity, + uint8_t channel = 0) { + std::array msg = {0}; + msg[0] = (channel & 0x0F) + (isNoteOn ? 0x90 : 0x80); + msg[1] = note & 0x7F; + msg[2] = velocity & 0x7F; + enqueueMIDIMessage(msg); +} + +inline void processSliders(size_t control) { + float sliderValue = device.hardware.adcChannels[control]; + float previousSliderMIDIValue = previousSliderMIDIValues[control]; + float smoothedSliderValue = sig_filter_Smooth_generate( + &sliderFilters[control], sliderValue); + uint8_t sliderMIDIValue = static_cast( + roundf(smoothedSliderValue * 127)); + + if (sliderMIDIValue != previousSliderMIDIValue) { + midiCCEvent(sliderToCC[control], sliderMIDIValue); + } + previousSliderMIDIValues[control] = sliderMIDIValue; +} + +inline void processLEDs(size_t control) { + device.hardware.gpioOutputs[control] = static_cast( + buttonState[control]); +} + +inline void buttonCCLatchingLED(size_t control, float buttonValue, + float previousButtonValue) { + if (buttonValue != previousButtonValue) { + // The button's state has changed (either pressed or released). + if (buttonValue > 0.0f && previousButtonValue <= 0.0f) { + // The button has been pressed. Toggle the LED. + buttonState[control] = !buttonState[control]; + } + midiCCEvent(buttonToCC[control], buttonValue <= 0 ? 0 : 127); + } +} + +inline void buttonNoteLatching(size_t control, float buttonValue, + float previousButtonValue) { + if (buttonValue != previousButtonValue) { + // The button's state has changed (either pressed or released). + if (buttonValue > 0.0f && previousButtonValue <= 0.0f) { + // The button has been pressed. + // Toggle the LED and the note. + buttonState[control] = !buttonState[control]; + + if (buttonState[control]) { + midiNoteEvent(true, buttonToNote[control], 127, 0); + } else { + midiNoteEvent(false, buttonToNote[control], 0, 0); + } + } + } +} + +inline void processButtons(size_t control) { + // Read button presses. + float buttonValue = device.hardware.toggles[control]; + float previousButtonValue = previousButtonValues[control]; + + buttonNoteLatching(control, buttonValue, previousButtonValue); + + previousButtonValues[control] = buttonValue; +} + +void AudioCallback(daisy::AudioHandle::InputBuffer in, + daisy::AudioHandle::OutputBuffer out, size_t size) { + + evaluator->evaluate((struct sig_dsp_SignalEvaluator*) evaluator); + + device.Read(); + + for (size_t i = 0; i < NUM_CONTROLS; i++) { + processButtons(i); + processLEDs(i); + processSliders(i); + } + + device.Write(); +} + +void processMIDIEvents() { + size_t eventsToRead = midiEvents.readable(); + for (size_t i = 0; i < eventsToRead; i++) { + sendMIDIMessage(midiEvents.read()); + } +} + +void initMIDI() { + MidiUsbHandler::Config usbConfig; + usbConfig.transport_config.periph = MidiUsbTransport::Config::INTERNAL; + usbMIDI.Init(usbConfig); + + MidiUartHandler::Config trsConfig; + trsConfig.transport_config.tx = sig::libdaisy::seed::PIN_D13; + trsConfig.transport_config.rx = sig::libdaisy::seed::PIN_NONE; + trsConfig.transport_config.periph = + UartHandler::Config::Peripheral::USART_1; + trsMIDI.Init(trsConfig); + + midiEvents.Init(); +} + +void initSliderFilters() { + for (size_t i = 0; i < NUM_CONTROLS; i++) { + sig_filter_Smooth_init(&sliderFilters[i], SMOOTH_COEFFICIENT); + } +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 48 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + device.Init(&audioSettings); + initMIDI(); + initSliderFilters(); + device.Start(); + device.board.audio.Start(AudioCallback); + + while (1) { + processMIDIEvents(); + } +} diff --git a/hosts/daisy/examples/lichen-medium/Makefile b/hosts/daisy/examples/lichen-medium/Makefile new file mode 100644 index 0000000..e400e6d --- /dev/null +++ b/hosts/daisy/examples/lichen-medium/Makefile @@ -0,0 +1,9 @@ +all: + $(MAKE) -C bare-board-test + $(MAKE) -C chorus + $(MAKE) -C sines + +clean: + $(MAKE) -C bare-board-test clean + $(MAKE) -C chorus clean + $(MAKE) -C sines clean diff --git a/hosts/daisy/examples/lichen-medium/bare-board-test/Makefile b/hosts/daisy/examples/lichen-medium/bare-board-test/Makefile new file mode 100644 index 0000000..218448c --- /dev/null +++ b/hosts/daisy/examples/lichen-medium/bare-board-test/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= lichen-medium-bare-board-test + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = src/${TARGET}.cpp ../../../src/sig-daisy-patch-sm.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/lichen-medium/bare-board-test/src/lichen-medium-bare-board-test.cpp b/hosts/daisy/examples/lichen-medium/bare-board-test/src/lichen-medium-bare-board-test.cpp new file mode 100644 index 0000000..1f8a40b --- /dev/null +++ b/hosts/daisy/examples/lichen-medium/bare-board-test/src/lichen-medium-bare-board-test.cpp @@ -0,0 +1,136 @@ +#include "daisy.h" +#include +#include "../../../../include/lichen-medium-device.hpp" + +#define SAMPLERATE 48000 +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; + +lichen::medium::MediumDevice medium; +struct sig_dsp_Value* freq; +struct sig_dsp_Value* gain; +struct sig_dsp_Oscillator* sine; +struct sig_dsp_Value* switchValue; +struct sig_dsp_ScaleOffset* switchValueScale; +struct sig_dsp_BinaryOp* harmonizerFreqScale; +struct sig_dsp_Oscillator* harmonizer; +struct sig_dsp_Value* buttonValue; +struct sig_dsp_BinaryOp* mixer; +struct sig_dsp_BinaryOp* attenuator; + +void buildSignalGraph(struct sig_Allocator* allocator, + struct sig_SignalContext* context, + struct sig_List* signals, + struct sig_AudioSettings* audioSettings, + struct sig_Status* status) { + + freq = sig_dsp_Value_new(allocator, context); + freq->parameters.value = 220.0f; + sig_List_append(signals, freq, status); + + buttonValue = sig_dsp_Value_new(allocator, context); + sig_List_append(signals, buttonValue, status); + buttonValue->parameters.value = 0.0f; + + switchValue = sig_dsp_Value_new(allocator, context); + sig_List_append(signals, switchValue, status); + switchValue->parameters.value = 0.0f; + + switchValueScale = sig_dsp_ScaleOffset_new(allocator, context); + sig_List_append(signals, switchValueScale, status); + switchValueScale->inputs.source = switchValue->outputs.main; + switchValueScale->parameters.scale = 1.25f; + switchValueScale->parameters.offset = 0.75f; + + harmonizerFreqScale = sig_dsp_Mul_new(allocator, context); + sig_List_append(signals, harmonizerFreqScale, status); + harmonizerFreqScale->inputs.left = freq->outputs.main; + harmonizerFreqScale->inputs.right = switchValueScale->outputs.main; + + harmonizer = sig_dsp_LFTriangle_new(allocator, context); + sig_List_append(signals, harmonizer, status); + harmonizer->inputs.freq = harmonizerFreqScale->outputs.main; + harmonizer->inputs.mul = buttonValue->outputs.main; + + sine = sig_dsp_Sine_new(allocator, context); + sig_List_append(signals, sine, status); + sine->inputs.freq = freq->outputs.main; + + mixer = sig_dsp_Add_new(allocator, context); + sig_List_append(signals, mixer, status); + mixer->inputs.left = sine->outputs.main; + mixer->inputs.right = harmonizer->outputs.main; + + gain = sig_dsp_Value_new(allocator, context); + gain->parameters.value = 0.5f; + sig_List_append(signals, gain, status); + + attenuator = sig_dsp_Mul_new(allocator, context); + sig_List_append(signals, attenuator, status); + attenuator->inputs.left = mixer->outputs.main; + attenuator->inputs.right = gain->outputs.main; +} + +void AudioCallback(daisy::AudioHandle::InputBuffer in, + daisy::AudioHandle::OutputBuffer out, size_t size) { + medium.Read(); + + freq->parameters.value = 1760.0f * + (medium.adcController.channelBank.values[0] + + medium.adcController.channelBank.values[6]); + buttonValue->parameters.value = medium.buttonBank.values[0]; + switchValue->parameters.value = medium.switchBank.values[0]; + + evaluator->evaluate((struct sig_dsp_SignalEvaluator*) evaluator); + + for (size_t i = 0; i < size; i++) { + float sig = attenuator->outputs.main[i]; + out[0][i] = sig; + out[1][i] = sig; + + medium.dacOutputBank.values[0] = medium.gateBank.values[0] * 0.5f; + medium.Write(); + } +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 1 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + medium.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + buildSignalGraph(&allocator, context, &signals, &audioSettings, &status); + + medium.Start(AudioCallback); + + while (1) { + + } +} diff --git a/hosts/daisy/examples/lichen-medium/chorus/Makefile b/hosts/daisy/examples/lichen-medium/chorus/Makefile new file mode 100644 index 0000000..cf720ae --- /dev/null +++ b/hosts/daisy/examples/lichen-medium/chorus/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= lichen-medium-chorus + +DEBUG = 0 +OPT = -O3 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/lichen-medium/chorus/src/lichen-medium-chorus.cpp b/hosts/daisy/examples/lichen-medium/chorus/src/lichen-medium-chorus.cpp new file mode 100644 index 0000000..737cb79 --- /dev/null +++ b/hosts/daisy/examples/lichen-medium/chorus/src/lichen-medium-chorus.cpp @@ -0,0 +1,194 @@ +#include +#include "../../../../include/lichen-medium-device.hpp" + +#define SAMPLERATE 96000 +#define DELAY_LINE_LENGTH SAMPLERATE +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 32 + +float DSY_SDRAM_BSS leftDelayLineSamples[DELAY_LINE_LENGTH]; +struct sig_Buffer leftDelayLineBuffer = { + .length = DELAY_LINE_LENGTH, + .samples = leftDelayLineSamples +}; +struct sig_DelayLine leftDelayLine = { + .buffer = &leftDelayLineBuffer, + .writeIdx = 0 +}; + +float DSY_SDRAM_BSS rightDelayLineSamples[DELAY_LINE_LENGTH]; +struct sig_Buffer rightDelayLineBuffer = { + .length = DELAY_LINE_LENGTH, + .samples = rightDelayLineSamples +}; +struct sig_DelayLine rightDelayLine = { + .buffer = &rightDelayLineBuffer, + .writeIdx = 0 +}; + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; + +sig::libdaisy::DaisyHost host; + +struct sig_dsp_ConstantValue* blend; +struct sig_dsp_ConstantValue* feedforward; +struct sig_dsp_ConstantValue* feedback; +struct sig_dsp_ConstantValue* delayTime; +struct sig_dsp_ConstantValue* modulationSpeed; +struct sig_dsp_ConstantValue* modulationWidth; +struct sig_host_FilteredCVIn* blendKnob; +struct sig_host_FilteredCVIn* feedForwardKnob; +struct sig_host_FilteredCVIn* feedbackKnob; +struct sig_host_FilteredCVIn* delayTimeKnob; +struct sig_host_FilteredCVIn* modulationSpeedKnob; +struct sig_host_FilteredCVIn* modulationWidthKnob; +struct sig_dsp_Chorus* leftChorus; +struct sig_dsp_Chorus* rightChorus; +struct sig_host_AudioIn* leftIn; +struct sig_host_AudioIn* rightIn; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; +struct sig_host_CVOut* modulatorLEDOut; + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + // White chorus + // See Dattoro Part 2 pp. 775-6. + blend = sig_dsp_ConstantValue_new(&allocator, context, + 0.7071f); + feedforward = sig_dsp_ConstantValue_new(&allocator, context, + 1.0f); // Dattoro Part 2 p. 775 + feedback = sig_dsp_ConstantValue_new(&allocator, context, + 0.7071f); // Dattoro Part 2 p. 775 + delayTime = sig_dsp_ConstantValue_new(&allocator, context, + 0.00907029478458f); // ~9ms. Dattoro Part 2 p. 776. + modulationSpeed = sig_dsp_ConstantValue_new(&allocator, context, 0.15f); + modulationWidth = sig_dsp_ConstantValue_new(&allocator, context, + 0.007936507936508f); // ~8ms. Dattoro Part 2 p776. + + blendKnob = sig_host_FilteredCVIn_new(&allocator, context); + blendKnob->hardware = &host.device.hardware; + sig_List_append(&signals, blendKnob, status); + blendKnob->parameters.control = sig_host_KNOB_1; + + delayTimeKnob = sig_host_FilteredCVIn_new(&allocator, context); + delayTimeKnob->hardware = &host.device.hardware; + sig_List_append(&signals, delayTimeKnob, status); + delayTimeKnob->parameters.control = sig_host_KNOB_2; + + feedForwardKnob = sig_host_FilteredCVIn_new(&allocator, context); + feedForwardKnob->hardware = &host.device.hardware; + sig_List_append(&signals, feedForwardKnob, status); + feedForwardKnob->parameters.control = sig_host_KNOB_3; + + feedbackKnob = sig_host_FilteredCVIn_new(&allocator, context); + feedbackKnob->hardware = &host.device.hardware; + sig_List_append(&signals, feedbackKnob, status); + feedbackKnob->parameters.control = sig_host_KNOB_4; + feedbackKnob->parameters.scale = 2.0f; + feedbackKnob->parameters.offset = -1.0f; + + modulationSpeedKnob = sig_host_FilteredCVIn_new(&allocator, context); + modulationSpeedKnob->hardware = &host.device.hardware; + sig_List_append(&signals, modulationSpeedKnob, status); + modulationSpeedKnob->parameters.control = sig_host_KNOB_5; + modulationSpeedKnob->parameters.scale = 2.0f; + + modulationWidthKnob = sig_host_FilteredCVIn_new(&allocator, context); + modulationWidthKnob->hardware = &host.device.hardware; + sig_List_append(&signals, modulationWidthKnob, status); + modulationWidthKnob->parameters.control = sig_host_KNOB_6; + modulationWidthKnob->parameters.scale = 0.001f; + + leftIn = sig_host_AudioIn_new(&allocator, context); + leftIn->hardware = &host.device.hardware; + sig_List_append(&signals, leftIn, status); + leftIn->parameters.channel = sig_host_AUDIO_IN_1; + + leftChorus = sig_dsp_Chorus_new(&allocator, context); + sig_List_append(&signals, leftChorus, status); + sig_DelayLine_init(&leftDelayLine); + leftChorus->delayLine = &leftDelayLine; + leftChorus->inputs.source = leftIn->outputs.main; + leftChorus->inputs.delayTime = delayTimeKnob->outputs.main; + leftChorus->inputs.blend = blendKnob->outputs.main; + leftChorus->inputs.feedforwardGain = feedForwardKnob->outputs.main; + leftChorus->inputs.feedbackGain = feedbackKnob->outputs.main; + leftChorus->inputs.speed = modulationSpeedKnob->outputs.main; + leftChorus->inputs.width = modulationWidthKnob->outputs.main; + + rightIn = sig_host_AudioIn_new(&allocator, context); + rightIn->hardware = &host.device.hardware; + sig_List_append(&signals, rightIn, status); + rightIn->parameters.channel = sig_host_AUDIO_IN_2; + + rightChorus = sig_dsp_Chorus_new(&allocator, context); + sig_List_append(&signals, rightChorus, status); + sig_DelayLine_init(&rightDelayLine); + rightChorus->delayLine = &rightDelayLine; + rightChorus->inputs.source = rightIn->outputs.main; + rightChorus->inputs.delayTime = delayTimeKnob->outputs.main; + rightChorus->inputs.blend = blendKnob->outputs.main; + rightChorus->inputs.feedforwardGain = feedForwardKnob->outputs.main; + rightChorus->inputs.feedbackGain = feedbackKnob->outputs.main; + rightChorus->inputs.speed = modulationSpeedKnob->outputs.main; + rightChorus->inputs.width = modulationWidthKnob->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = leftChorus->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = rightChorus->outputs.main; + + modulatorLEDOut = sig_host_CVOut_new(&allocator, context); + modulatorLEDOut->hardware = &host.device.hardware; + sig_List_append(&signals, modulatorLEDOut, status); + modulatorLEDOut->parameters.control = sig_host_CV_OUT_1; + modulatorLEDOut->inputs.source = leftChorus->outputs.modulator; + modulatorLEDOut->parameters.scale = 0.25f; + modulatorLEDOut->parameters.offset = 0.25f; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 8 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + buildSignalGraph(context, &status); + host.Start(); + + while (1) { + + } +} diff --git a/hosts/daisy/examples/lichen-medium/sines/Makefile b/hosts/daisy/examples/lichen-medium/sines/Makefile new file mode 100644 index 0000000..d038531 --- /dev/null +++ b/hosts/daisy/examples/lichen-medium/sines/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= lichen-medium-sines + +DEBUG = 1 +OPT = -Og + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/lichen-medium/sines/src/lichen-medium-sines.cpp b/hosts/daisy/examples/lichen-medium/sines/src/lichen-medium-sines.cpp new file mode 100644 index 0000000..07ea14b --- /dev/null +++ b/hosts/daisy/examples/lichen-medium/sines/src/lichen-medium-sines.cpp @@ -0,0 +1,92 @@ +#include +#include "../../../../include/lichen-medium-device.hpp" + +using namespace sig::libdaisy; + +#define SAMPLERATE 96000 +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +DaisyHost host; + +struct sig_host_FilteredCVIn* freqKnob; +struct sig_host_CVIn* freqCV; +struct sig_dsp_BinaryOp* freqSum; +struct sig_dsp_Oscillator* osc; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + freqKnob = sig_host_FilteredCVIn_new(&allocator, context); + freqKnob->hardware = &host.device.hardware; + sig_List_append(&signals, freqKnob, status); + freqKnob->parameters.control = sig_host_KNOB_1; + freqKnob->parameters.scale = 1760.0f; + + freqCV = sig_host_CVIn_new(&allocator, context); + freqCV->hardware = &host.device.hardware; + sig_List_append(&signals, freqCV, status); + freqCV->parameters.control = sig_host_CV_IN_1; + freqCV->parameters.scale = 440.0f; + + freqSum = sig_dsp_Add_new(&allocator, context); + sig_List_append(&signals, freqSum, status); + freqSum->inputs.left = freqKnob->outputs.main; + freqSum->inputs.right = freqCV->outputs.main; + + osc = sig_dsp_Sine_new(&allocator, context); + sig_List_append(&signals, osc, status); + osc->inputs.freq = freqSum->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = osc->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = osc->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 48 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + buildSignalGraph(context, &status); + + host.Start(); + + while (1) {} +} diff --git a/hosts/daisy/examples/patch_init/Makefile b/hosts/daisy/examples/patch_init/Makefile index ff3e66c..86dd21b 100644 --- a/hosts/daisy/examples/patch_init/Makefile +++ b/hosts/daisy/examples/patch_init/Makefile @@ -1,5 +1,9 @@ all: $(MAKE) -C fm-osc + $(MAKE) -C calibrator + $(MAKE) -C chorus clean: - $(MAKE) -C fm-osc clean + $(MAKE) -C fm-osc + $(MAKE) -C calibrator clean + $(MAKE) -C chorus clean diff --git a/hosts/daisy/examples/patch_init/calibrator/Makefile b/hosts/daisy/examples/patch_init/calibrator/Makefile new file mode 100644 index 0000000..32de2f1 --- /dev/null +++ b/hosts/daisy/examples/patch_init/calibrator/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= patch-init-calibrator + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp b/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp new file mode 100644 index 0000000..fa96df1 --- /dev/null +++ b/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp @@ -0,0 +1,98 @@ +#include +#include "../../../../include/electrosmith-patch-init-device.hpp" + +#define SAMPLERATE 48000 +#define HEAP_SIZE 1024 * 256 // 256KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +sig::libdaisy::DaisyHost host; + +struct sig_dsp_ConstantValue* ampScale; +struct sig_host_SwitchIn* button; +struct sig_host_CVIn* cv1In; +struct sig_dsp_Calibrator* cv1Calibrator; +struct sig_dsp_Oscillator* sine1; +struct sig_dsp_LinearToFreq* sine1Voct; +struct sig_host_AudioOut* audio1Out; +struct sig_host_AudioOut* audio2Out; + + +void buildGraph(struct sig_SignalContext* context, struct sig_Status* status) { + button = sig_host_SwitchIn_new(&allocator, context); + button->hardware = &host.device.hardware; + sig_List_append(&signals, button, status); + button->parameters.control = sig_host_TOGGLE_1; + + cv1In = sig_host_CVIn_new(&allocator, context); + cv1In->hardware = &host.device.hardware; + sig_List_append(&signals, cv1In, status); + cv1In->parameters.control = sig_host_CV_IN_1; + cv1In->parameters.scale = 5.0f; + + cv1Calibrator = sig_dsp_Calibrator_new(&allocator, context); + sig_List_append(&signals, cv1Calibrator, status); + cv1Calibrator->inputs.source = cv1In->outputs.main; + cv1Calibrator->inputs.gate = button->outputs.main; + + ampScale = sig_dsp_ConstantValue_new(&allocator, context, 0.5f); + + sine1Voct = sig_dsp_LinearToFreq_new(&allocator, context); + sig_List_append(&signals, sine1Voct, status); + sine1Voct->inputs.source = cv1Calibrator->outputs.main; + + sine1 = sig_dsp_Sine_new(&allocator, context); + sig_List_append(&signals, sine1, status); + sine1->inputs.freq = sine1Voct->outputs.main; + sine1->inputs.mul = ampScale->outputs.main; + + audio1Out = sig_host_AudioOut_new(&allocator, context); + audio1Out->hardware = &host.device.hardware; + sig_List_append(&signals, audio1Out, status); + audio1Out->parameters.channel = sig_host_AUDIO_OUT_1; + audio1Out->inputs.source = sine1->outputs.main; + + audio2Out = sig_host_AudioOut_new(&allocator, context); + audio2Out->hardware = &host.device.hardware; + sig_List_append(&signals, audio2Out, status); + audio2Out->parameters.channel = sig_host_AUDIO_OUT_2; + audio2Out->inputs.source = sine1->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 48 + }; + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + buildGraph(context, &status); + + host.Start(); + + while (1) {} +} diff --git a/hosts/daisy/examples/patch_init/chorus/Makefile b/hosts/daisy/examples/patch_init/chorus/Makefile new file mode 100644 index 0000000..367f39d --- /dev/null +++ b/hosts/daisy/examples/patch_init/chorus/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= patch-init-chorus + +DEBUG = 0 +OPT = -O3 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/patch_init/chorus/src/patch-init-chorus.cpp b/hosts/daisy/examples/patch_init/chorus/src/patch-init-chorus.cpp new file mode 100644 index 0000000..336c35b --- /dev/null +++ b/hosts/daisy/examples/patch_init/chorus/src/patch-init-chorus.cpp @@ -0,0 +1,179 @@ +#include +#include "../../../../include/electrosmith-patch-init-device.hpp" + +#define SAMPLERATE 96000 +#define DELAY_LINE_LENGTH SAMPLERATE +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 32 + +float DSY_SDRAM_BSS leftDelayLineSamples[DELAY_LINE_LENGTH]; +struct sig_Buffer leftDelayLineBuffer = { + .length = DELAY_LINE_LENGTH, + .samples = leftDelayLineSamples +}; +struct sig_DelayLine leftDelayLine = { + .buffer = &leftDelayLineBuffer, + .writeIdx = 0 +}; + +float DSY_SDRAM_BSS rightDelayLineSamples[DELAY_LINE_LENGTH]; +struct sig_Buffer rightDelayLineBuffer = { + .length = DELAY_LINE_LENGTH, + .samples = rightDelayLineSamples +}; +struct sig_DelayLine rightDelayLine = { + .buffer = &rightDelayLineBuffer, + .writeIdx = 0 +}; + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +sig::libdaisy::DaisyHost host; + +struct sig_dsp_ConstantValue* blend; +struct sig_dsp_ConstantValue* feedforward; +struct sig_dsp_ConstantValue* feedback; +struct sig_dsp_ConstantValue* delayTime; +struct sig_dsp_ConstantValue* modulationSpeed; +struct sig_dsp_ConstantValue* modulationWidth; +struct sig_host_FilteredCVIn* blendKnob; +struct sig_host_FilteredCVIn* feedForwardKnob; +struct sig_host_FilteredCVIn* feedbackKnob; +struct sig_host_FilteredCVIn* delayTimeKnob; +struct sig_dsp_Chorus* leftChorus; +struct sig_dsp_Chorus* rightChorus; +struct sig_host_AudioIn* leftIn; +struct sig_host_AudioIn* rightIn; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; +struct sig_host_CVOut* modulatorLEDOut; + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + // White chorus + // See Dattoro Part 2 pp. 775-6. + blend = sig_dsp_ConstantValue_new(&allocator, context, + 0.7071f); + feedforward = sig_dsp_ConstantValue_new(&allocator, context, + 1.0f); // Dattoro Part 2 p. 775 + feedback = sig_dsp_ConstantValue_new(&allocator, context, + 0.7071f); // Dattoro Part 2 p. 775 + delayTime = sig_dsp_ConstantValue_new(&allocator, context, + 0.00907029478458f); // ~9ms. Dattoro Part 2 p. 776. + modulationSpeed = sig_dsp_ConstantValue_new(&allocator, context, 0.15f); + modulationWidth = sig_dsp_ConstantValue_new(&allocator, context, + 0.007936507936508f); // ~8ms. Dattoro Part 2 p776. + + blendKnob = sig_host_FilteredCVIn_new(&allocator, context); + blendKnob->hardware = &host.device.hardware; + sig_List_append(&signals, blendKnob, status); + blendKnob->parameters.control = sig_host_KNOB_1; + + delayTimeKnob = sig_host_FilteredCVIn_new(&allocator, context); + delayTimeKnob->hardware = &host.device.hardware; + sig_List_append(&signals, delayTimeKnob, status); + delayTimeKnob->parameters.control = sig_host_KNOB_2; + + feedForwardKnob = sig_host_FilteredCVIn_new(&allocator, context); + feedForwardKnob->hardware = &host.device.hardware; + sig_List_append(&signals, feedForwardKnob, status); + feedForwardKnob->parameters.control = sig_host_KNOB_3; + + feedbackKnob = sig_host_FilteredCVIn_new(&allocator, context); + feedbackKnob->hardware = &host.device.hardware; + sig_List_append(&signals, feedbackKnob, status); + feedbackKnob->parameters.control = sig_host_KNOB_4; + feedbackKnob->parameters.scale = 2.0f; + feedbackKnob->parameters.offset = -1.0f; + + leftIn = sig_host_AudioIn_new(&allocator, context); + leftIn->hardware = &host.device.hardware; + sig_List_append(&signals, leftIn, status); + leftIn->parameters.channel = sig_host_AUDIO_IN_1; + + leftChorus = sig_dsp_Chorus_new(&allocator, context); + sig_List_append(&signals, leftChorus, status); + sig_DelayLine_init(&leftDelayLine); + leftChorus->delayLine = &leftDelayLine; + leftChorus->inputs.source = leftIn->outputs.main; + leftChorus->inputs.delayTime = delayTimeKnob->outputs.main; + leftChorus->inputs.blend = blendKnob->outputs.main; + leftChorus->inputs.feedforwardGain = feedForwardKnob->outputs.main; + leftChorus->inputs.feedbackGain = feedbackKnob->outputs.main; + leftChorus->inputs.speed = modulationSpeed->outputs.main; + leftChorus->inputs.width = modulationWidth->outputs.main; + + rightIn = sig_host_AudioIn_new(&allocator, context); + rightIn->hardware = &host.device.hardware; + sig_List_append(&signals, rightIn, status); + rightIn->parameters.channel = sig_host_AUDIO_IN_2; + + rightChorus = sig_dsp_Chorus_new(&allocator, context); + sig_List_append(&signals, rightChorus, status); + sig_DelayLine_init(&rightDelayLine); + rightChorus->delayLine = &rightDelayLine; + rightChorus->inputs.source = rightIn->outputs.main; + rightChorus->inputs.delayTime = delayTimeKnob->outputs.main; + rightChorus->inputs.blend = blendKnob->outputs.main; + rightChorus->inputs.feedforwardGain = feedForwardKnob->outputs.main; + rightChorus->inputs.feedbackGain = feedbackKnob->outputs.main; + rightChorus->inputs.speed = modulationSpeed->outputs.main; + rightChorus->inputs.width = modulationWidth->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = leftChorus->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = rightChorus->outputs.main; + + modulatorLEDOut = sig_host_CVOut_new(&allocator, context); + modulatorLEDOut->hardware = &host.device.hardware; + sig_List_append(&signals, modulatorLEDOut, status); + modulatorLEDOut->parameters.control = sig_host_CV_OUT_2; + modulatorLEDOut->inputs.source = leftChorus->outputs.modulator; + modulatorLEDOut->parameters.scale = 0.5f; + modulatorLEDOut->parameters.offset = 0.5f; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 1 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + buildSignalGraph(context, &status); + + host.Start(); + + while (1) {} +} diff --git a/hosts/daisy/examples/patch_init/fm-osc/Makefile b/hosts/daisy/examples/patch_init/fm-osc/Makefile index 01a766e..331389b 100644 --- a/hosts/daisy/examples/patch_init/fm-osc/Makefile +++ b/hosts/daisy/examples/patch_init/fm-osc/Makefile @@ -5,11 +5,11 @@ DEBUG = 0 OPT = -O3 # Sources -C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include -CPP_SOURCES = src/${TARGET}.cpp ../../../src/signaletic-daisy-host.cpp ../../../src/daisy-patch-sm-host.cpp +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-patch-sm.cpp src/${TARGET}.cpp USE_FATFS = 0 diff --git a/hosts/daisy/examples/patch_init/fm-osc/src/patch-init-fm-osc.cpp b/hosts/daisy/examples/patch_init/fm-osc/src/patch-init-fm-osc.cpp index 1df66a8..d8b79e4 100644 --- a/hosts/daisy/examples/patch_init/fm-osc/src/patch-init-fm-osc.cpp +++ b/hosts/daisy/examples/patch_init/fm-osc/src/patch-init-fm-osc.cpp @@ -1,13 +1,11 @@ -#include "daisy.h" #include -#include "../../../../include/daisy-patch-sm-host.h" +#include "../../../../include/electrosmith-patch-init-device.hpp" +#define SAMPLERATE 96000 #define HEAP_SIZE 1024 * 256 // 256KB #define MAX_NUM_SIGNALS 64 #define NUM_RATIOS 33 -struct sig_Status status; - uint8_t memory[HEAP_SIZE]; struct sig_AllocatorHeap heap = { .length = HEAP_SIZE, @@ -41,24 +39,24 @@ struct sig_Buffer ratioList = { .samples = ratios }; -daisy::patch_sm::DaisyPatchSM patchInit; -struct sig_daisy_Host* host; - struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; -struct sig_List* signals; - +struct sig_List signals; struct sig_dsp_SignalListEvaluator* evaluator; -struct sig_daisy_FilteredCVIn* coarseFrequencyKnob; -struct sig_daisy_FilteredCVIn* fineFrequencyKnob; -struct sig_daisy_CVIn* vOctCV; +sig::libdaisy::DaisyHost host; + +struct sig_host_FilteredCVIn* coarseFrequencyKnob; +struct sig_host_FilteredCVIn* fineFrequencyKnob; +struct sig_host_SwitchIn* button; +struct sig_host_CVIn* vOctCV; +struct sig_dsp_Calibrator* vOctCalibrator; struct sig_dsp_BinaryOp* coarsePlusVOct; struct sig_dsp_BinaryOp* coarseVOctPlusFine; struct sig_dsp_LinearToFreq* fundamentalFrequency; -struct sig_daisy_SwitchIn* switchIn; +struct sig_host_SwitchIn* switchIn; struct sig_dsp_Branch* ratioFreeBranch; -struct sig_daisy_FilteredCVIn* ratioKnob; +struct sig_host_FilteredCVIn* ratioKnob; struct sig_dsp_List* ratioListSignal; -struct sig_daisy_FilteredCVIn* ratioCV; +struct sig_host_FilteredCVIn* ratioCV; struct sig_dsp_BinaryOp* combinedRatio; struct sig_dsp_ConstantValue* ratioFreeScaleValue; struct sig_dsp_BinaryOp* ratioFreeScale; @@ -66,10 +64,10 @@ struct sig_dsp_ConstantValue* ratioFreeOffsetValue; struct sig_dsp_BinaryOp* ratioFreeOffset; struct sig_dsp_LinearToFreq* modulatorFreeFrequency; struct sig_dsp_BinaryOp* ratioFree; -struct sig_daisy_FilteredCVIn* indexKnob; -struct sig_daisy_FilteredCVIn* indexCV; +struct sig_host_FilteredCVIn* indexKnob; +struct sig_host_FilteredCVIn* indexCV; struct sig_dsp_BinaryOp* combinedIndex; -struct sig_daisy_FilteredCVIn* indexSkewCV; +struct sig_host_FilteredCVIn* indexSkewCV; struct sig_dsp_Abs* rectifiedIndexSkew; struct sig_dsp_BinaryOp* indexSkewAdder; struct sig_dsp_Branch* leftIndex; @@ -79,51 +77,67 @@ struct sig_dsp_Branch* rightIndex; struct sig_dsp_TwoOpFM* rightOp; struct sig_dsp_LinearToFreq* lfoFundamentalFrequency; struct sig_dsp_TwoOpFM* lfo; -struct sig_daisy_AudioOut* leftOut; -struct sig_daisy_AudioOut* rightOut; -struct sig_daisy_CVOut* lfoCVOut; -struct sig_daisy_CVOut* lfoLEDOut; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; +struct sig_host_CVOut* lfoCVOut; +struct sig_host_CVOut* lfoLEDOut; void buildControlGraph(struct sig_Allocator* allocator, struct sig_List* signals, struct sig_SignalContext* context, struct sig_Status* status) { - coarseFrequencyKnob = sig_daisy_FilteredCVIn_new(allocator, - context, host); + coarseFrequencyKnob = sig_host_FilteredCVIn_new(allocator, context); + coarseFrequencyKnob->hardware = &host.device.hardware; sig_List_append(signals, coarseFrequencyKnob, status); - coarseFrequencyKnob->parameters.control = sig_daisy_PatchInit_KNOB_1; + coarseFrequencyKnob->parameters.control = sig_host_KNOB_1; coarseFrequencyKnob->parameters.scale = 5.0f; - coarseFrequencyKnob->parameters.offset = -3.5f; + coarseFrequencyKnob->parameters.offset = -2.5f; - fineFrequencyKnob = sig_daisy_FilteredCVIn_new(allocator, context, host); + fineFrequencyKnob = sig_host_FilteredCVIn_new(allocator, context); + fineFrequencyKnob->hardware = &host.device.hardware; sig_List_append(signals, fineFrequencyKnob, status); - fineFrequencyKnob->parameters.control = sig_daisy_PatchInit_KNOB_3; + fineFrequencyKnob->parameters.control = sig_host_KNOB_3; fineFrequencyKnob->parameters.offset = -0.5f; - switchIn = sig_daisy_SwitchIn_new(allocator, context, host); + switchIn = sig_host_SwitchIn_new(allocator, context); + switchIn->hardware = &host.device.hardware; sig_List_append(signals, switchIn, status); - switchIn->parameters.control = sig_daisy_PatchInit_TOGGLE; + switchIn->parameters.control = sig_host_TOGGLE_2; - ratioKnob = sig_daisy_FilteredCVIn_new(allocator, context, host); + ratioKnob = sig_host_FilteredCVIn_new(allocator, context); + ratioKnob->hardware = &host.device.hardware; sig_List_append(signals, ratioKnob, status); - ratioKnob->parameters.control = sig_daisy_PatchInit_KNOB_2; + ratioKnob->parameters.control = sig_host_KNOB_2; ratioKnob->parameters.scale = 0.5f; - indexKnob = sig_daisy_FilteredCVIn_new(allocator, context, host); + indexKnob = sig_host_FilteredCVIn_new(allocator, context); + indexKnob->hardware = &host.device.hardware; sig_List_append(signals, indexKnob, status); - indexKnob->parameters.control = sig_daisy_PatchInit_KNOB_4; + indexKnob->parameters.control = sig_host_KNOB_4; + + button = sig_host_SwitchIn_new(allocator, context); + button->hardware = &host.device.hardware; + sig_List_append(signals, button, status); + button->parameters.control = sig_host_TOGGLE_1; } void buildCVInputGraph(struct sig_Allocator* allocator, struct sig_List* signals, struct sig_SignalContext* context, struct sig_Status* status) { - vOctCV = sig_daisy_CVIn_new(allocator, context, host); + vOctCV = sig_host_CVIn_new(allocator, context); + vOctCV->hardware = &host.device.hardware; sig_List_append(signals, vOctCV, status); - vOctCV->parameters.control = sig_daisy_PatchInit_CV_IN_1; + vOctCV->parameters.control = sig_host_CV_IN_1; vOctCV->parameters.scale = 5.0f; - ratioCV = sig_daisy_FilteredCVIn_new(allocator, context, host); + vOctCalibrator = sig_dsp_Calibrator_new(allocator, context); + sig_List_append(signals, vOctCalibrator, status); + vOctCalibrator->inputs.source = vOctCV->outputs.main; + vOctCalibrator->inputs.gate = button->outputs.main; + + ratioCV = sig_host_FilteredCVIn_new(allocator, context); + ratioCV->hardware = &host.device.hardware; sig_List_append(signals, ratioCV, status); - ratioCV->parameters.control = sig_daisy_PatchInit_CV_IN_2; + ratioCV->parameters.control = sig_host_CV_IN_2; ratioCV->parameters.scale = 0.25f; ratioCV->parameters.offset = 0.25f; @@ -137,18 +151,20 @@ void buildCVInputGraph(struct sig_Allocator* allocator, ratioListSignal->list = &ratioList; ratioListSignal->inputs.index = combinedRatio->outputs.main; - indexCV = sig_daisy_FilteredCVIn_new(allocator, context, host); + indexCV = sig_host_FilteredCVIn_new(allocator, context); + indexCV->hardware = &host.device.hardware; sig_List_append(signals, indexCV, status); - indexCV->parameters.control = sig_daisy_PatchInit_CV_IN_3; + indexCV->parameters.control = sig_host_CV_IN_3; combinedIndex = sig_dsp_Add_new(allocator, context); sig_List_append(signals, combinedIndex, status); combinedIndex->inputs.left = indexKnob->outputs.main; combinedIndex->inputs.right = indexCV->outputs.main; - indexSkewCV = sig_daisy_FilteredCVIn_new(allocator, context, host); + indexSkewCV = sig_host_FilteredCVIn_new(allocator, context); + indexSkewCV->hardware = &host.device.hardware; sig_List_append(signals, indexSkewCV, status); - indexSkewCV->parameters.control = sig_daisy_PatchInit_CV_IN_4; + indexSkewCV->parameters.control = sig_host_CV_IN_4; indexSkewCV->parameters.scale = 1.125f; // Tuned by ear for subtlety. rectifiedIndexSkew = sig_dsp_Abs_new(allocator, context); @@ -167,7 +183,7 @@ void buildFrequencyGraph(struct sig_Allocator* allocator, coarsePlusVOct = sig_dsp_Add_new(allocator, context); sig_List_append(signals, coarsePlusVOct, status); coarsePlusVOct->inputs.left = coarseFrequencyKnob->outputs.main; - coarsePlusVOct->inputs.right = vOctCV->outputs.main; + coarsePlusVOct->inputs.right = vOctCalibrator->outputs.main; coarseVOctPlusFine = sig_dsp_Add_new(allocator, context); sig_List_append(signals, coarseVOctPlusFine, status); @@ -256,53 +272,54 @@ void buildSignalGraph(struct sig_Allocator* allocator, lfo->inputs.ratio = ratioFreeBranch->outputs.main; lfo->inputs.index = combinedIndex->outputs.main; - leftOut = sig_daisy_AudioOut_new(allocator, context, host); + leftOut = sig_host_AudioOut_new(allocator, context); + leftOut->hardware = &host.device.hardware; sig_List_append(signals, leftOut, status); - leftOut->parameters.channel = sig_daisy_AUDIO_OUT_1; + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; leftOut->parameters.scale = 0.9f; // PatchSM DAC is a just a touch hot. leftOut->inputs.source = leftOp->outputs.main; - rightOut = sig_daisy_AudioOut_new(allocator, context, host); + rightOut = sig_host_AudioOut_new(allocator, context); + rightOut->hardware = &host.device.hardware; sig_List_append(signals, rightOut, status); - rightOut->parameters.channel = sig_daisy_AUDIO_OUT_2; + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; rightOut->parameters.scale = 0.9f; rightOut->inputs.source = rightOp->outputs.main; - lfoCVOut = sig_daisy_CVOut_new(allocator, context, host); + lfoCVOut = sig_host_CVOut_new(allocator, context); + lfoCVOut->hardware = &host.device.hardware; sig_List_append(signals, lfoCVOut, status); - lfoCVOut->parameters.control = sig_daisy_PatchInit_CV_OUT; + lfoCVOut->parameters.control = sig_host_CV_OUT_1; lfoCVOut->inputs.source = lfo->outputs.main; - lfoLEDOut = sig_daisy_CVOut_new(allocator, context, host); + lfoLEDOut = sig_host_CVOut_new(allocator, context); + lfoLEDOut->hardware = &host.device.hardware; sig_List_append(signals, lfoLEDOut, status); - lfoLEDOut->parameters.control = sig_daisy_PatchInit_LED; + lfoLEDOut->parameters.control = sig_host_CV_OUT_2; lfoLEDOut->inputs.source = lfo->outputs.main; } int main(void) { + allocator.impl->init(&allocator); + struct sig_AudioSettings audioSettings = { - .sampleRate = 96000, + .sampleRate = SAMPLERATE, .numChannels = 2, .blockSize = 128 }; + struct sig_Status status; sig_Status_init(&status); - allocator.impl->init(&allocator); - signals = sig_List_new(&allocator, MAX_NUM_SIGNALS); - evaluator = sig_dsp_SignalListEvaluator_new(&allocator, signals); - - host = sig_daisy_PatchSMHost_new(&allocator, - &audioSettings, &patchInit, - (struct sig_dsp_SignalEvaluator*) evaluator); - sig_daisy_Host_registerGlobalHost(host); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); struct sig_SignalContext* context = sig_SignalContext_new(&allocator, &audioSettings); - buildSignalGraph(&allocator, signals, context, &status); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); - host->impl->start(host); + buildSignalGraph(&allocator, &signals, context, &status); - while (1) { + host.Start(); - } + while (1) {} } diff --git a/hosts/daisy/examples/versio/Makefile b/hosts/daisy/examples/versio/Makefile index 899fc09..b6cf751 100644 --- a/hosts/daisy/examples/versio/Makefile +++ b/hosts/daisy/examples/versio/Makefile @@ -1,5 +1,7 @@ all: $(MAKE) -C pole-mix-filter + $(MAKE) -C sines clean: $(MAKE) -C pole-mix-filter clean + $(MAKE) -C sines clean diff --git a/hosts/daisy/examples/versio/pole-mix-filter/Makefile b/hosts/daisy/examples/versio/pole-mix-filter/Makefile index 28e97c5..20a777d 100644 --- a/hosts/daisy/examples/versio/pole-mix-filter/Makefile +++ b/hosts/daisy/examples/versio/pole-mix-filter/Makefile @@ -5,11 +5,11 @@ DEBUG = 0 OPT = -O3 # Sources -C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include -CPP_SOURCES = src/${TARGET}.cpp ../../../src/signaletic-daisy-host.cpp ../../../src/daisy-versio-host.cpp +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp USE_FATFS = 0 diff --git a/hosts/daisy/examples/versio/pole-mix-filter/src/signaletic-versio-pole-mix-filter.cpp b/hosts/daisy/examples/versio/pole-mix-filter/src/signaletic-versio-pole-mix-filter.cpp index 91b5c91..39203e1 100644 --- a/hosts/daisy/examples/versio/pole-mix-filter/src/signaletic-versio-pole-mix-filter.cpp +++ b/hosts/daisy/examples/versio/pole-mix-filter/src/signaletic-versio-pole-mix-filter.cpp @@ -49,13 +49,10 @@ Knob Scaling: Pole 3 (D): -8..0 (should be -32..0) Pole 4 (E): 0..4 (should be 0..16) */ -#include -#include #include -#include "../../../../include/daisy-versio-host.h" - -using namespace daisy; +#include "../../../../include/ne-versio-device.hpp" +#define SAMPLERATE 96000 #define HEAP_SIZE 1024 * 256 // 256KB #define MAX_NUM_SIGNALS 32 @@ -74,49 +71,48 @@ struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; struct sig_List signals; struct sig_dsp_SignalListEvaluator* evaluator; -DaisyVersio versio; -struct sig_daisy_Host* host; - -struct sig_daisy_FilteredCVIn* frequencyCV; -struct sig_daisy_FilteredCVIn* resonance; -struct sig_daisy_FilteredCVIn* frequencySkewCV; -struct sig_daisy_TriSwitchIn* a; -struct sig_daisy_FilteredCVIn* b; -struct sig_daisy_FilteredCVIn* c; -struct sig_daisy_FilteredCVIn* d; -struct sig_daisy_FilteredCVIn* e; +sig::libdaisy::DaisyHost host; + +struct sig_host_FilteredCVIn* frequencyCV; +struct sig_host_FilteredCVIn* resonance; +struct sig_host_FilteredCVIn* frequencySkewCV; +struct sig_host_TriSwitchIn* a; +struct sig_host_FilteredCVIn* b; +struct sig_host_FilteredCVIn* c; +struct sig_host_FilteredCVIn* d; +struct sig_host_FilteredCVIn* e; struct sig_dsp_Abs* rectifiedSkew; struct sig_dsp_BinaryOp* frequencyCVSkewAdder; struct sig_dsp_Branch* leftFrequencyCVSkewed; struct sig_dsp_LinearToFreq* leftFrequency; struct sig_dsp_Branch* rightFrequencyCVSkewed; struct sig_dsp_LinearToFreq* rightFrequency; -struct sig_daisy_AudioIn* leftIn; -struct sig_daisy_AudioIn* rightIn; +struct sig_host_AudioIn* leftIn; +struct sig_host_AudioIn* rightIn; +struct sig_dsp_ConstantValue* preGain; +struct sig_dsp_BinaryOp* leftVCA; +struct sig_dsp_BinaryOp* rightVCA; struct sig_dsp_Ladder* leftFilter; struct sig_dsp_Ladder* rightFilter; struct sig_dsp_Tanh* leftSaturation; struct sig_dsp_Tanh* rightSaturation; -struct sig_daisy_AudioOut* leftOut; -struct sig_daisy_AudioOut* rightOut; - +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; void buildSignalGraph(struct sig_SignalContext* context, struct sig_Status* status) { - // Versio controls are all unipolar, - // so they need to be scaled to bipolar values. - frequencyCV = sig_daisy_FilteredCVIn_new(&allocator, context, host); + frequencyCV = sig_host_FilteredCVIn_new(&allocator, context); + frequencyCV->hardware = &host.device.hardware; sig_List_append(&signals, frequencyCV, status); - frequencyCV->parameters.control = sig_daisy_Versio_CV_IN_1; - frequencyCV->parameters.scale = 10.0f; - frequencyCV->parameters.offset = -5.0f; + frequencyCV->parameters.control = sig_host_CV_IN_1; + frequencyCV->parameters.scale = 5.0f; frequencyCV->parameters.time = 0.1f; - frequencySkewCV = sig_daisy_FilteredCVIn_new(&allocator, context, host); + frequencySkewCV = sig_host_FilteredCVIn_new(&allocator, context); + frequencySkewCV->hardware = &host.device.hardware; sig_List_append(&signals, frequencySkewCV, status); - frequencySkewCV->parameters.control = sig_daisy_Versio_CV_IN_7; - frequencySkewCV->parameters.scale = 5.0f; - frequencySkewCV->parameters.offset = -2.5f; + frequencySkewCV->parameters.control = sig_host_CV_IN_7; + frequencySkewCV->parameters.scale = 2.5f; frequencySkewCV->parameters.time = 0.1f; rectifiedSkew = sig_dsp_Abs_new(&allocator, context); @@ -148,48 +144,66 @@ void buildSignalGraph(struct sig_SignalContext* context, sig_List_append(&signals, rightFrequency, status); rightFrequency->inputs.source = rightFrequencyCVSkewed->outputs.main; - resonance = sig_daisy_FilteredCVIn_new(&allocator, context, host); + resonance = sig_host_FilteredCVIn_new(&allocator, context); + resonance->hardware = &host.device.hardware; sig_List_append(&signals, resonance, status); - resonance->parameters.control = sig_daisy_Versio_CV_IN_5; - resonance->parameters.scale = 1.8f; + resonance->parameters.control = sig_host_CV_IN_5; + resonance->parameters.scale = 0.9f; + resonance->parameters.offset = 0.9f; - a = sig_daisy_TriSwitchIn_new(&allocator, context, host); + a = sig_host_TriSwitchIn_new(&allocator, context); + a->hardware = &host.device.hardware; sig_List_append(&signals, a, status); - a->parameters.control = sig_daisy_Versio_TRI_SWITCH_1; + a->parameters.control = sig_host_TRISWITCH_1; - b = sig_daisy_FilteredCVIn_new(&allocator, context, host); + b = sig_host_FilteredCVIn_new(&allocator, context); + b->hardware = &host.device.hardware; sig_List_append(&signals, b, status); - b->parameters.control = sig_daisy_Versio_CV_IN_2; - b->parameters.scale = -8.0f; + b->parameters.control = sig_host_CV_IN_2; + b->parameters.scale = -4.0f; + b->parameters.offset = -4.0f; b->parameters.time = 0.1f; - c = sig_daisy_FilteredCVIn_new(&allocator, context, host); + c = sig_host_FilteredCVIn_new(&allocator, context); + c->hardware = &host.device.hardware; sig_List_append(&signals, c, status); - c->parameters.control = sig_daisy_Versio_CV_IN_3; - c->parameters.scale = 24.0f; + c->parameters.control = sig_host_CV_IN_3; + c->parameters.scale = 12.0f; + c->parameters.offset = 12.0f; c->parameters.time = 0.1f; - d = sig_daisy_FilteredCVIn_new(&allocator, context, host); + d = sig_host_FilteredCVIn_new(&allocator, context); + d->hardware = &host.device.hardware; sig_List_append(&signals, d, status); - d->parameters.control = sig_daisy_Versio_CV_IN_6; - d->parameters.scale = -32.0f; + d->parameters.control = sig_host_CV_IN_6; + d->parameters.scale = -16.0f; + d->parameters.offset = -16.0f; d->parameters.time = 0.1f; - e = sig_daisy_FilteredCVIn_new(&allocator, context, host); + e = sig_host_FilteredCVIn_new(&allocator, context); + e->hardware = &host.device.hardware; sig_List_append(&signals, e, status); - e->parameters.control = sig_daisy_Versio_CV_IN_4; - e->parameters.scale = 16.0f; + e->parameters.control = sig_host_CV_IN_4; + e->parameters.scale = 8.0f; + e->parameters.offset = 8.0f; e->parameters.time = 0.1f; - leftIn = sig_daisy_AudioIn_new(&allocator, context, host); + leftIn = sig_host_AudioIn_new(&allocator, context); + leftIn->hardware = &host.device.hardware; sig_List_append(&signals, leftIn, status); - leftIn->parameters.channel = 0; + leftIn->parameters.channel = sig_host_AUDIO_IN_1; + + preGain = sig_dsp_ConstantValue_new(&allocator, context, 1.1f); + + leftVCA = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, leftVCA, status); + leftVCA->inputs.left = leftIn->outputs.main; + leftVCA->inputs.right = preGain->outputs.main; leftFilter = sig_dsp_Ladder_new(&allocator, context); sig_List_append(&signals, leftFilter, status); - leftFilter->parameters.overdrive = 1.1f; leftFilter->parameters.passbandGain = 0.5f; - leftFilter->inputs.source = leftIn->outputs.main; + leftFilter->inputs.source = leftVCA->outputs.main; leftFilter->inputs.frequency = leftFrequency->outputs.main; leftFilter->inputs.resonance = resonance->outputs.main; leftFilter->inputs.inputGain = a->outputs.main; @@ -202,20 +216,26 @@ void buildSignalGraph(struct sig_SignalContext* context, sig_List_append(&signals, leftSaturation, status); leftSaturation->inputs.source = leftFilter->outputs.main; - leftOut = sig_daisy_AudioOut_new(&allocator, context, host); + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; sig_List_append(&signals, leftOut, status); - leftOut->parameters.channel = 0; + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; leftOut->inputs.source = leftSaturation->outputs.main; - rightIn = sig_daisy_AudioIn_new(&allocator, context, host); + rightIn = sig_host_AudioIn_new(&allocator, context); + rightIn->hardware = &host.device.hardware; sig_List_append(&signals, rightIn, status); - rightIn->parameters.channel = 1; + rightIn->parameters.channel = sig_host_AUDIO_IN_2; + + rightVCA = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, rightVCA, status); + rightVCA->inputs.left = rightIn->outputs.main; + rightVCA->inputs.right = preGain->outputs.main; rightFilter = sig_dsp_Ladder_new(&allocator, context); sig_List_append(&signals, rightFilter, status); - rightFilter->parameters.overdrive = 1.1f; leftFilter->parameters.passbandGain = 0.5f; - rightFilter->inputs.source = rightIn->outputs.main; + rightFilter->inputs.source = rightVCA->outputs.main; rightFilter->inputs.frequency = rightFrequency->outputs.main; rightFilter->inputs.resonance = resonance->outputs.main; rightFilter->inputs.inputGain = a->outputs.main; @@ -228,9 +248,10 @@ void buildSignalGraph(struct sig_SignalContext* context, sig_List_append(&signals, rightSaturation, status); rightSaturation->inputs.source = rightFilter->outputs.main; - rightOut = sig_daisy_AudioOut_new(&allocator, context, host); + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; sig_List_append(&signals, rightOut, status); - rightOut->parameters.channel = 1; + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; rightOut->inputs.source = rightSaturation->outputs.main; } @@ -238,7 +259,7 @@ int main(void) { allocator.impl->init(&allocator); struct sig_AudioSettings audioSettings = { - .sampleRate = 96000, + .sampleRate = SAMPLERATE, .numChannels = 2, .blockSize = 16 }; @@ -247,16 +268,14 @@ int main(void) { sig_Status_init(&status); sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); - evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); - host = sig_daisy_VersioHost_new(&allocator, &audioSettings, &versio, - (struct sig_dsp_SignalEvaluator*) evaluator); - sig_daisy_Host_registerGlobalHost(host); - struct sig_SignalContext* context = sig_SignalContext_new(&allocator, &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + buildSignalGraph(context, &status); - host->impl->start(host); - while (1) { - } + host.Start(); + + while (1) {} } diff --git a/hosts/daisy/examples/versio/sines/Makefile b/hosts/daisy/examples/versio/sines/Makefile new file mode 100644 index 0000000..f445153 --- /dev/null +++ b/hosts/daisy/examples/versio/sines/Makefile @@ -0,0 +1,21 @@ +# Project Name +TARGET ?= signaletic-versio-sines + +DEBUG = 1 +OPT = -O0 + +# Sources +C_SOURCES += ../../../../../libsignaletic/vendor/tlsf/tlsf.c ../../../../../libsignaletic/src/libsignaletic.c ../../../src/signaletic-host.c +C_INCLUDES += -I../../../../../libsignaletic/vendor/tlsf -I../../../../../libsignaletic/include + +CPP_INCLUDES += -I../vendor/lib -I../../../vendor/lib/dev -I../../../include +CPP_SOURCES = ../../../src/signaletic-daisy-host.cpp ../../../src/sig-daisy-seed.cpp src/${TARGET}.cpp + +USE_FATFS = 0 + +# Library Locations +LIBDAISY_DIR = ../../../vendor/libDaisy + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/hosts/daisy/examples/versio/sines/src/signaletic-versio-sines.cpp b/hosts/daisy/examples/versio/sines/src/signaletic-versio-sines.cpp new file mode 100644 index 0000000..aad6c0b --- /dev/null +++ b/hosts/daisy/examples/versio/sines/src/signaletic-versio-sines.cpp @@ -0,0 +1,93 @@ +#include +#include "../../../../include/ne-versio-device.hpp" + +using namespace sig::libdaisy; + +#define SAMPLERATE 96000 +#define HEAP_SIZE 1024 * 384 // 384 KB +#define MAX_NUM_SIGNALS 32 + +uint8_t memory[HEAP_SIZE]; +struct sig_AllocatorHeap heap = { + .length = HEAP_SIZE, + .memory = (void*) memory +}; + +struct sig_Allocator allocator = { + .impl = &sig_TLSFAllocatorImpl, + .heap = &heap +}; + +struct sig_dsp_Signal* listStorage[MAX_NUM_SIGNALS]; +struct sig_List signals; +struct sig_dsp_SignalListEvaluator* evaluator; +DaisyHost host; + +struct sig_host_CVIn* freqCV; +struct sig_dsp_Oscillator* osc; +struct sig_host_CVIn* gain; +struct sig_dsp_BinaryOp* vca; +struct sig_host_AudioOut* leftOut; +struct sig_host_AudioOut* rightOut; + +void buildSignalGraph(struct sig_SignalContext* context, + struct sig_Status* status) { + freqCV = sig_host_CVIn_new(&allocator, context); + freqCV->hardware = &host.device.hardware; + sig_List_append(&signals, freqCV, status); + freqCV->parameters.control = sig_host_CV_IN_1; + freqCV->parameters.scale = 1760.0f; + + osc = sig_dsp_Sine_new(&allocator, context); + sig_List_append(&signals, osc, status); + osc->inputs.freq = freqCV->outputs.main; + + gain = sig_host_CVIn_new(&allocator, context); + gain->hardware = &host.device.hardware; + sig_List_append(&signals, gain, status); + gain->parameters.control = sig_host_CV_IN_2; + gain->parameters.scale = 0.5f; + gain->parameters.offset = 0.5f; + + vca = sig_dsp_Mul_new(&allocator, context); + sig_List_append(&signals, vca, status); + vca->inputs.left = osc->outputs.main; + vca->inputs.right = gain->outputs.main; + + leftOut = sig_host_AudioOut_new(&allocator, context); + leftOut->hardware = &host.device.hardware; + sig_List_append(&signals, leftOut, status); + leftOut->parameters.channel = sig_host_AUDIO_OUT_1; + leftOut->inputs.source = vca->outputs.main; + + rightOut = sig_host_AudioOut_new(&allocator, context); + rightOut->hardware = &host.device.hardware; + sig_List_append(&signals, rightOut, status); + rightOut->parameters.channel = sig_host_AUDIO_OUT_2; + rightOut->inputs.source = vca->outputs.main; +} + +int main(void) { + allocator.impl->init(&allocator); + + struct sig_AudioSettings audioSettings = { + .sampleRate = SAMPLERATE, + .numChannels = 2, + .blockSize = 1 + }; + + struct sig_Status status; + sig_Status_init(&status); + sig_List_init(&signals, (void**) &listStorage, MAX_NUM_SIGNALS); + + struct sig_SignalContext* context = sig_SignalContext_new(&allocator, + &audioSettings); + evaluator = sig_dsp_SignalListEvaluator_new(&allocator, &signals); + host.Init(&audioSettings, (struct sig_dsp_SignalEvaluator*) evaluator); + + buildSignalGraph(context, &status); + + host.Start(); + + while (1) {} +} diff --git a/hosts/daisy/include/daisy-bluemchen-host.h b/hosts/daisy/include/daisy-bluemchen-host.h deleted file mode 100644 index 74a8d2f..0000000 --- a/hosts/daisy/include/daisy-bluemchen-host.h +++ /dev/null @@ -1,105 +0,0 @@ -#include "./signaletic-daisy-host.h" -#include "../vendor/kxmx_bluemchen/src/kxmx_bluemchen.h" - -enum { - sig_daisy_Bluemchen_CV_IN_KNOB_1 = kxmx::Bluemchen::Ctrl::CTRL_1, - sig_daisy_Bluemchen_CV_IN_KNOB_2 = kxmx::Bluemchen::Ctrl::CTRL_2, - sig_daisy_Bluemchen_CV_IN_CV1 = kxmx::Bluemchen::Ctrl::CTRL_3, - sig_daisy_Bluemchen_CV_IN_CV2 = kxmx::Bluemchen::Ctrl::CTRL_4, - sig_daisy_Bluemchen_CV_IN_LAST = kxmx::Bluemchen::Ctrl::CTRL_LAST -}; - -const int sig_daisy_Bluemchen_NUM_ANALOG_INPUTS = kxmx::Bluemchen::Ctrl::CTRL_LAST; -const int sig_daisy_Bluemchen_NUM_ANALOG_OUTPUTS = 0; -const int sig_daisy_Bluemchen_NUM_GATE_INPUTS = 0; -const int sig_daisy_Bluemchen_NUM_GATE_OUTPUTS = 0; -const int sig_daisy_Bluemchen_NUM_SWITCHES = 0; -const int sig_daisy_Bluemchen_NUM_TRI_SWITCHES = 0; -const int sig_daisy_Bluemchen_NUM_ENCODERS = 1; - -enum { - sig_daisy_Nehcmeulb_CV_IN_KNOB1 = kxmx::Bluemchen::Ctrl::CTRL_1, - sig_daisy_Nehcmeulb_CV_IN_KNOB2 = kxmx::Bluemchen::Ctrl::CTRL_2, - sig_daisy_Nehcmeulb_CV_IN_LAST -}; - -enum { - sig_daisy_Nehcmeulb_CV_OUT_1 = 0, - sig_daisy_Nehcmeulb_CV_OUT_2, - sig_daisy_Nehcmeulb_CV_OUT_BOTH, - sig_daisy_Nehcmeulb_CV_OUT_LAST -}; - -const int sig_daisy_Nehcmeulb_NUM_ANALOG_INPUTS = sig_daisy_Nehcmeulb_CV_IN_LAST; -const int sig_daisy_Nehcmeulb_NUM_ANALOG_OUTPUTS = 2; -const int sig_daisy_Nehcmeulb_NUM_GATE_INPUTS = 0; -const int sig_daisy_Nehcmeulb_NUM_GATE_OUTPUTS = 0; -const int sig_daisy_Nehcmeulb_NUM_SWITCHES = 0; -const int sig_daisy_Nehcmeulb_NUM_TRI_SWITCHES = 0; -const int sig_daisy_Nehcmeulb_NUM_ENCODERS = 1; - -extern struct sig_daisy_Host_Impl sig_daisy_BluemchenHostImpl; - -void sig_daisy_BluemchenHostImpl_setControlValue(struct sig_daisy_Host* host, - int control, float value); - -void sig_daisy_BluemchenHostImpl_start(struct sig_daisy_Host* host); - -void sig_daisy_BluemchenHostImpl_stop(struct sig_daisy_Host* host); - -/** - * @brief Creates a new BluemchenHost instance. - * - * @param allocator the allocator to use - * @param bluemchen the Bluemchen board support object - * @param evaluator a signal evaluator to use in the audio callback - * @return struct sig_daisy_Host* - */ -struct sig_daisy_Host* sig_daisy_BluemchenHost_new( - struct sig_Allocator* allocator, - struct sig_AudioSettings* audioSettings, - kxmx::Bluemchen* bluemchen, - struct sig_dsp_SignalEvaluator* evaluator); - -/** - * @brief Initializes a Bluemchen board object with the specified configuration - * - * @param self the BluemchenHost_Board instance to initialize - * @param bluemchen the Blumchen board support object - * @param boardConfig the board configuration object to use (e.g. for the Bluemchen or Nehcmeulb) - */ -void sig_daisy_BluemchenHost_Board_init(struct sig_daisy_Host_Board* self, - kxmx::Bluemchen* bluemchen, - struct sig_daisy_Host_BoardConfiguration* boardConfig); - -/** - * @brief Initializes the state of a BluemchenHost instance. - * - * @param self the BluemchenHost to initialize - * @param bluemchen the Bluemchen board support object - * @param evaluator a signal evaluator to use in the audio callback - */ -void sig_daisy_BluemchenHost_init(struct sig_daisy_Host* self, - struct sig_AudioSettings* audioSettings, - struct sig_daisy_Host_BoardConfiguration* boardConfig, - kxmx::Bluemchen* bluemchen, - struct sig_dsp_SignalEvaluator* evaluator); - -/** - * @brief Destroys a BluemchenHost instance. - * - * @param allocator the allocator to use - * @param self the BluemchenHost to destroy - */ -void sig_daisy_BluemchenHost_destroy(struct sig_Allocator* allocator, - struct sig_daisy_Host* self); - - -struct sig_daisy_Host* sig_daisy_NehcmeulbHost_new( - struct sig_Allocator* allocator, - struct sig_AudioSettings* audioSettings, - kxmx::Bluemchen* bluemchen, - struct sig_dsp_SignalEvaluator* evaluator); - -void sig_daisy_NehcmeulbHost_destroy(struct sig_Allocator* allocator, - struct sig_daisy_Host* self); diff --git a/hosts/daisy/include/daisy-dpt-host.h b/hosts/daisy/include/daisy-dpt-host.h deleted file mode 100644 index 637690a..0000000 --- a/hosts/daisy/include/daisy-dpt-host.h +++ /dev/null @@ -1,88 +0,0 @@ -#include "./signaletic-daisy-host.h" -#include "../vendor/dpt/lib/daisy_dpt.h" - -enum { - sig_daisy_DPT_CV_IN_1 = daisy::dpt::CV_1, - sig_daisy_DPT_CV_IN_2 = daisy::dpt::CV_2, - sig_daisy_DPT_CV_IN_3 = daisy::dpt::CV_3, - sig_daisy_DPT_CV_IN_4 = daisy::dpt::CV_4, - sig_daisy_DPT_CV_IN_5 = daisy::dpt::CV_5, - sig_daisy_DPT_CV_IN_6 = daisy::dpt::CV_6, - sig_daisy_DPT_CV_IN_7 = daisy::dpt::CV_7, - sig_daisy_DPT_CV_IN_8 = daisy::dpt::CV_8, - sig_daisy_DPT_CV_IN_LAST -}; - -const int sig_daisy_DPT_NUM_ANALOG_INPUTS = daisy::dpt::ADC_LAST; - -enum { - sig_daisy_DPT_CV_OUT_1 = 0, - sig_daisy_DPT_CV_OUT_2, - sig_daisy_DPT_CV_OUT_3, - sig_daisy_DPT_CV_OUT_4, - sig_daisy_DPT_CV_OUT_5, - sig_daisy_DPT_CV_OUT_6, - sig_daisy_DPT_CV_OUT_LAST -}; - -const int sig_daisy_DPT_NUM_ANALOG_OUTPUTS = sig_daisy_DPT_CV_OUT_LAST; - -enum { - sig_daisy_DPT_GATE_IN_1 = sig_daisy_GATE_IN_1, - sig_daisy_DPT_GATE_IN_2 = sig_daisy_GATE_IN_2, - sig_daisy_DPT_GATE_IN_LAST = sig_daisy_GATE_IN_LAST -}; - -const int sig_daisy_DPT_NUM_GATE_INPUTS = sig_daisy_DPT_GATE_IN_LAST; - -enum { - sig_daisy_DPT_GATE_OUT_1 = sig_daisy_GATE_OUT_1, - sig_daisy_DPT_GATE_OUT_2 = sig_daisy_GATE_OUT_2, - sig_daisy_DPT_GATE_OUT_LAST = sig_daisy_GATE_OUT_LAST -}; - -const int sig_daisy_DPT_NUM_GATE_OUTPUTS = sig_daisy_GATE_OUT_LAST; - -const int sig_daisy_DPT_NUM_SWITCHES = 0; -const int sig_daisy_DPT_NUM_TRI_SWITCHES = 0; -const int sig_daisy_DPT_NUM_ENCODERS = 0; - -void sig_daisy_DPT_dacWriterCallback(void* dptHost); - -struct sig_daisy_DPTHost { - struct sig_daisy_Host host; - daisy::Dac7554* expansionDAC; - uint16_t expansionDACBuffer[4]; -}; - -extern struct sig_daisy_Host_Impl sig_daisy_DPTHostImpl; - -void sig_daisy_DPTHostImpl_start(struct sig_daisy_Host* host); - -void sig_daisy_DPTHostImpl_stop(struct sig_daisy_Host* host); - -void sig_daisy_DPTHostImpl_setControlValue( - struct sig_daisy_Host* host, int control, float value); - -float sig_daisy_DPTHostImpl_getGateValue( - struct sig_daisy_Host* host, int control); - -void sig_daisy_DPTHostImpl_setGateValue( - struct sig_daisy_Host* host, int control, float value); - -struct sig_daisy_Host* sig_daisy_DPTHost_new(struct sig_Allocator* allocator, - sig_AudioSettings* audioSettings, - daisy::dpt::DPT* dpt, - struct sig_dsp_SignalEvaluator* evaluator); - -void sig_daisy_DPTHost_Board_init(struct sig_daisy_Host_Board* self, - daisy::dpt::DPT* dpt); - -void sig_daisy_DPTHost_init(struct sig_daisy_DPTHost* self, - sig_AudioSettings* audioSettings, - daisy::dpt::DPT* dpt, - struct sig_dsp_SignalEvaluator* evaluator); - -void sig_daisy_DPTHost_destroy( - struct sig_Allocator* allocator, - struct sig_daisy_DPTHost* self); diff --git a/hosts/daisy/include/daisy-patch-sm-host.h b/hosts/daisy/include/daisy-patch-sm-host.h deleted file mode 100644 index e48452c..0000000 --- a/hosts/daisy/include/daisy-patch-sm-host.h +++ /dev/null @@ -1,139 +0,0 @@ -#include "./signaletic-daisy-host.h" -#include "../vendor/libDaisy/src/daisy_patch_sm.h" - -enum { - sig_daisy_PatchSM_CV_IN_1 = daisy::patch_sm::CV_1, - sig_daisy_PatchSM_CV_IN_2 = daisy::patch_sm::CV_2, - sig_daisy_PatchSM_CV_IN_3 = daisy::patch_sm::CV_3, - sig_daisy_PatchSM_CV_IN_4 = daisy::patch_sm::CV_4, - sig_daisy_PatchSM_CV_IN_5 = daisy::patch_sm::CV_5, - sig_daisy_PatchSM_CV_IN_6 = daisy::patch_sm::CV_6, - sig_daisy_PatchSM_CV_IN_7 = daisy::patch_sm::CV_7, - sig_daisy_PatchSM_CV_IN_8 = daisy::patch_sm::CV_8, - sig_daisy_PatchSM_ADC_IN_9 = daisy::patch_sm::ADC_9, - sig_daisy_PatchSM_ADC_IN_10 = daisy::patch_sm::ADC_10, - sig_daisy_PatchSM_ADC_IN_11 = daisy::patch_sm::ADC_11, - sig_daisy_PatchSM_ADC_IN_12 = daisy::patch_sm::ADC_12, - sig_daisy_PatchSM_CV_IN_LAST -}; - -const int sig_daisy_PatchSM_NUM_ANALOG_INPUTS = daisy::patch_sm::ADC_LAST; - -enum { - sig_daisy_PatchSM_CV_OUT_1 = daisy::patch_sm::CV_OUT_1, - sig_daisy_PatchSM_CV_OUT_2 = daisy::patch_sm::CV_OUT_2, - sig_daisy_PatchSM_CV_OUT_LAST -}; - -const int sig_daisy_PatchSM_NUM_ANALOG_OUTPUTS = 2; - -enum { - sig_daisy_PatchSM_GATE_IN_1 = sig_daisy_GATE_IN_1, - sig_daisy_PatchSM_GATE_IN_2 = sig_daisy_GATE_IN_2, - sig_daisy_PatchSM_GATE_IN_LAST = sig_daisy_GATE_IN_LAST -}; - -const int sig_daisy_PatchSM_NUM_GATE_INPUTS = sig_daisy_GATE_IN_LAST; - -enum { - sig_daisy_PatchSM_GATE_OUT_1 = sig_daisy_GATE_OUT_1, - sig_daisy_PatchSM_GATE_OUT_2 = sig_daisy_GATE_OUT_2, - sig_daisy_PatchSM_GATE_OUT_LAST = sig_daisy_GATE_OUT_LAST -}; - -const int sig_daisy_PatchSM_NUM_GATE_OUTPUTS = sig_daisy_GATE_OUT_LAST; - - -enum { - sig_daisy_PatchSM_SWITCH_1, - sig_daisy_PatchSM_SWITCH_2, - sig_daisy_PatchSM_SWITCH_LAST -}; -const int sig_daisy_PatchSM_NUM_SWITCHES = sig_daisy_PatchSM_SWITCH_LAST; -const int sig_daisy_PatchSM_NUM_TRI_SWITCHES = 0; -const int sig_daisy_PatchSM_NUM_ENCODERS = 0; - -// TODO: What are the additional 4x ADC pins on the Patch SM, -// and where are they mapped (if at all) on the Patch init? -enum { - sig_daisy_PatchInit_KNOB_1 = daisy::patch_sm::CV_1, - sig_daisy_PatchInit_KNOB_2 = daisy::patch_sm::CV_2, - sig_daisy_PatchInit_KNOB_3 = daisy::patch_sm::CV_3, - sig_daisy_PatchInit_KNOB_4 = daisy::patch_sm::CV_4, - sig_daisy_PatchInit_CV_IN_1 = daisy::patch_sm::CV_5, - sig_daisy_PatchInit_CV_IN_2 = daisy::patch_sm::CV_6, - sig_daisy_PatchInit_CV_IN_3 = daisy::patch_sm::CV_7, - sig_daisy_PatchInit_CV_IN_4 = daisy::patch_sm::CV_8, - sig_daisy_PatchInit_CV_IN_LAST -}; - -const int sig_daisy_PatchInit_NUM_ANALOG_INPUTS = sig_daisy_PatchInit_CV_IN_LAST; - -enum { - sig_daisy_PatchInit_CV_OUT = daisy::patch_sm::CV_OUT_1, - sig_daisy_PatchInit_LED = daisy::patch_sm::CV_OUT_2, - sig_daisy_PatchInit_CV_OUT_LAST -}; - -const int sig_daisy_PatchInit_NUM_ANALOG_OUTPUTS = 2; - -enum { - sig_daisy_PatchInit_GATE_IN_1 = sig_daisy_GATE_IN_1, - sig_daisy_PatchInit_GATE_IN_2 = sig_daisy_GATE_IN_2, - sig_daisy_PatchInit_GATE_IN_LAST = sig_daisy_GATE_IN_LAST -}; - -const int sig_daisy_PatchInit_NUM_GATE_INPUTS = sig_daisy_PatchInit_GATE_IN_LAST; - -enum { - sig_daisy_PatchInit_GATE_OUT_1 = sig_daisy_GATE_OUT_1, - sig_daisy_PatchInit_GATE_OUT_2 = sig_daisy_GATE_OUT_2, - sig_daisy_PatchInit_GATE_OUT_LAST = sig_daisy_GATE_OUT_LAST -}; - -const int sig_daisy_PatchInit_NUM_GATE_OUTPUTS = sig_daisy_PatchInit_GATE_OUT_LAST; - -enum { - sig_daisy_PatchInit_BUTTON = sig_daisy_PatchSM_SWITCH_1, - sig_daisy_PatchInit_TOGGLE = sig_daisy_PatchSM_SWITCH_2, - sig_daisy_PatchInit_SWITCH_LAST -}; - -const int sig_daisy_PatchInit_NUM_SWITCHES = sig_daisy_PatchInit_SWITCH_LAST; -const int sig_daisy_PatchInit_NUM_TRI_SWITCHES = 0; -const int sig_daisy_PatchInit_NUM_ENCODERS = 0; - -extern struct sig_daisy_Host_Impl sig_daisy_PatchSMHostImpl; - -void sig_daisy_PatchSMHostImpl_start( - struct sig_daisy_Host* host); - -void sig_daisy_PatchSMHostImpl_stop( - struct sig_daisy_Host* host); - -void sig_daisy_PatchSMHostImpl_setControlValue( - struct sig_daisy_Host* host, int control, float value); - -float sig_daisy_PatchSMHostImpl_getGateValue( - struct sig_daisy_Host* host, int control); - -void sig_daisy_PatchSMHostImpl_setGateValue( - struct sig_daisy_Host* host, int control, float value); - -struct sig_daisy_Host* sig_daisy_PatchSMHost_new( - struct sig_Allocator* allocator, - sig_AudioSettings* audioSettings, - daisy::patch_sm::DaisyPatchSM* board, - struct sig_dsp_SignalEvaluator* evaluator); - -void sig_daisy_PatchSMHost_init(struct sig_daisy_Host* self, - sig_AudioSettings* audioSettings, - daisy::patch_sm::DaisyPatchSM* patchSM, - struct sig_dsp_SignalEvaluator* evaluator); - -void sig_daisy_PatchSMHost_Board_init(struct sig_daisy_Host_Board* self, - daisy::patch_sm::DaisyPatchSM* patchSM); - -void sig_daisy_PatchSMHost_destroy( - struct sig_Allocator* allocator, - struct sig_daisy_Host* self); diff --git a/hosts/daisy/include/daisy-versio-host.h b/hosts/daisy/include/daisy-versio-host.h deleted file mode 100644 index b9e901b..0000000 --- a/hosts/daisy/include/daisy-versio-host.h +++ /dev/null @@ -1,60 +0,0 @@ -#include "./signaletic-daisy-host.h" -#include "../vendor/libDaisy/src/daisy_versio.h" - -enum { - sig_daisy_Versio_CV_IN_1 = daisy::DaisyVersio::AV_KNOBS::KNOB_0, - sig_daisy_Versio_CV_IN_2 = daisy::DaisyVersio::AV_KNOBS::KNOB_1, - sig_daisy_Versio_CV_IN_3 = daisy::DaisyVersio::AV_KNOBS::KNOB_2, - sig_daisy_Versio_CV_IN_4 = daisy::DaisyVersio::AV_KNOBS::KNOB_3, - sig_daisy_Versio_CV_IN_5 = daisy::DaisyVersio::AV_KNOBS::KNOB_4, - sig_daisy_Versio_CV_IN_6 = daisy::DaisyVersio::AV_KNOBS::KNOB_5, - sig_daisy_Versio_CV_IN_7 = daisy::DaisyVersio::AV_KNOBS::KNOB_6, - sig_daisy_Versio_CV_IN_LAST -}; - -const int sig_daisy_Versio_NUM_ANALOG_INPUTS = sig_daisy_Versio_CV_IN_LAST; -const int sig_daisy_Versio_NUM_ANALOG_OUTPUTS = 0; -const int sig_daisy_Versio_NUM_GATE_INPUTS = 0; -const int sig_daisy_Versio_NUM_GATE_OUTPUTS = 0; -const int sig_daisy_Versio_NUM_ENCODERS = 0; - -// Versio has one button -enum { - sig_daisy_Versio_SWITCH_1, - sig_daisy_Versio_SWITCH_LAST -}; - -const int sig_daisy_Versio_NUM_SWITCHES = sig_daisy_Versio_SWITCH_LAST; - -enum { - sig_daisy_Versio_TRI_SWITCH_1 = daisy::DaisyVersio::AV_TOGGLE3::SW_0, - sig_daisy_Versio_TRI_SWITCH_2 = daisy::DaisyVersio::AV_TOGGLE3::SW_1, - sig_daisy_Versio_TRI_SWITCH_LAST -}; - -const int sig_daisy_Versio_NUM_TRI_SWITCHES = sig_daisy_Versio_TRI_SWITCH_LAST; - - -// TODO: Add support for RGB LEDs. - -extern struct sig_daisy_Host_Impl sig_daisy_VersioHostImpl; - -void sig_daisy_VersioHostImpl_start( - struct sig_daisy_Host* host); - -void sig_daisy_VersioHostImpl_stop( - struct sig_daisy_Host* host); - -struct sig_daisy_Host* sig_daisy_VersioHost_new( - struct sig_Allocator* allocator, - struct sig_AudioSettings* audioSettings, - daisy::DaisyVersio* versio, - struct sig_dsp_SignalEvaluator* evaluator); - -void sig_daisy_VersioHost_Board_init(struct sig_daisy_Host_Board* self, - daisy::DaisyVersio* versio); - -void sig_daisy_VersioHost_init(struct sig_daisy_Host* self, - struct sig_AudioSettings* audioSettings, - daisy::DaisyVersio* versio, - struct sig_dsp_SignalEvaluator* evaluator); diff --git a/hosts/daisy/include/dspcoffee-dpt-device.hpp b/hosts/daisy/include/dspcoffee-dpt-device.hpp new file mode 100644 index 0000000..217a376 --- /dev/null +++ b/hosts/daisy/include/dspcoffee-dpt-device.hpp @@ -0,0 +1,241 @@ +#pragma once + +#include "signaletic-host.h" +#include "signaletic-daisy-host.hpp" +#include "sig-daisy-patch-sm.hpp" +#include "../vendor/dpt/lib/dev/DAC7554.h" +#include "../vendor/libDaisy/src/util/hal_map.h" +#include "../vendor/libDaisy/src/per/tim.h" + +using namespace sig::libdaisy; + +struct Normalization DPT_INTERNAL_DAC_NORMALIZATION = { + .scale = 0.651f, + .offset = -0.348f +}; + +enum { + sig_host_CV_IN_1 = 0, + sig_host_CV_IN_2, + sig_host_CV_IN_3, + sig_host_CV_IN_4, + sig_host_CV_IN_5, + sig_host_CV_IN_6, + sig_host_CV_IN_7, + sig_host_CV_IN_8 +}; + +enum { + sig_host_CV_OUT_1 = 0, + sig_host_CV_OUT_2, + sig_host_CV_OUT_3, + sig_host_CV_OUT_4, + sig_host_CV_OUT_5, + sig_host_CV_OUT_6 +}; + +enum { + sig_host_GATE_IN_1 = 0, + sig_host_GATE_IN_2 +}; + +enum { + sig_host_GATE_OUT_1 = 0, + sig_host_GATE_OUT_2 +}; + +enum { + sig_host_AUDIO_IN_1 = 0, + sig_host_AUDIO_IN_2 +}; + +enum { + sig_host_AUDIO_OUT_1 = 0, + sig_host_AUDIO_OUT_2 +}; + +namespace dspcoffee { +namespace dpt { + static const size_t NUM_ADC_CHANNELS = 8; + + // DPT exposes eight CV inputs from the PatchSM; + // its knobs configured as hardware attenuators of the incoming + // CV jacks, normaled to ~5V when unplugged. + static ADCChannelSpec ADC_CHANNEL_SPECS[NUM_ADC_CHANNELS] = { + {patchsm::PIN_CV_1, INVERT}, // C5 + {patchsm::PIN_CV_2, INVERT}, // C4 + {patchsm::PIN_CV_3, INVERT}, // C3 + {patchsm::PIN_CV_4, INVERT}, // C2 + {patchsm::PIN_CV_8, INVERT}, // C9 + {patchsm::PIN_CV_7, INVERT}, // C8 + {patchsm::PIN_CV_5, INVERT}, // C6 + {patchsm::PIN_CV_6, INVERT} // C7 + }; + + static const size_t NUM_GATES = 2; + + static dsy_gpio_pin GATE_IN_PINS[NUM_GATES] = { + patchsm::PIN_B10, + patchsm::PIN_B9 + }; + + static dsy_gpio_pin GATE_OUT_PINS[NUM_GATES] = { + patchsm::PIN_B6, + patchsm::PIN_B5 + }; + + static const size_t NUM_INTERNAL_DAC_CHANNELS = 2; + static const size_t NUM_EXTERNAL_DAC_CHANNELS = 4; + static const size_t NUM_DAC_CHANNELS = NUM_INTERNAL_DAC_CHANNELS + + NUM_EXTERNAL_DAC_CHANNELS; + + class DPTDevice { + public: + patchsm::PatchSMBoard board; + uint16_t externalDACBuffer[NUM_EXTERNAL_DAC_CHANNELS]; + daisy::Dac7554 externalDAC; + daisy::TimerHandle tim5; + ADCController adcController; + GateInput gateInputs[NUM_GATES]; + InputBank gateInputBank; + GPIOOutput gateOutputs[NUM_GATES]; + OutputBank gateOutputBank; + BipolarInvertedBufferedAnalogOutput dacChannels[NUM_DAC_CHANNELS]; + OutputBank + dacOutputBank; + struct sig_host_HardwareInterface hardware; + + static void onEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + DPTDevice* self = static_cast(hardware->userData); + self->Read(); + } + + static void afterEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + DPTDevice* self = static_cast(hardware->userData); + self->Write(); + } + + static void externalDACWriteCallback(void* data) { + DPTDevice* self = static_cast(data); + self->externalDAC.Write(self->externalDACBuffer); + self->externalDAC.WriteDac7554(); + } + + void InitExternalDACWriteTimer( + struct sig_AudioSettings* audioSettings, + daisy::TimerHandle::PeriodElapsedCallback cb, void *data) { + uint32_t target_freq = + static_cast(audioSettings->sampleRate); + uint32_t tim_base_freq = daisy::System::GetPClk2Freq(); + uint32_t tim_period = tim_base_freq / target_freq; + + daisy::TimerHandle::Config timerConfig; + timerConfig.periph = + daisy::TimerHandle::Config::Peripheral::TIM_5; + timerConfig.dir = daisy::TimerHandle::Config::CounterDir::UP; + timerConfig.period = tim_period; + timerConfig.enable_irq = true; + tim5.Init(timerConfig); + HAL_NVIC_SetPriority(TIM5_IRQn, 0x0, 0x0); + tim5.SetCallback(cb, data); + } + + void InitExternalDAC(struct sig_AudioSettings* audioSettings, + daisy::TimerHandle::PeriodElapsedCallback cb, void *data) { + externalDAC.Init(); + InitExternalDACWriteTimer(audioSettings, cb, data); + } + + void Init(struct sig_AudioSettings* audioSettings, + struct sig_dsp_SignalEvaluator* evaluator) { + board.Init(audioSettings->blockSize, audioSettings->sampleRate); + // The DAC and ADC have to be initialized after the board. + InitADCController(); + InitDAC(); + InitExternalDAC(audioSettings, externalDACWriteCallback, + this); + InitControls(); + + hardware = { + .evaluator = evaluator, + .onEvaluateSignals = onEvaluateSignals, + .afterEvaluateSignals = afterEvaluateSignals, + .userData = this, + .numAudioInputChannels = 2, + .audioInputChannels = NULL, // Supplied by audio callback + .numAudioOutputChannels = 2, + .audioOutputChannels = NULL, // Supplied by audio callback + .numADCChannels = NUM_ADC_CHANNELS, + .adcChannels = adcController.channelBank.values, + .numDACChannels = NUM_DAC_CHANNELS, + .dacChannels = dacOutputBank.values, + .numGateInputs = NUM_GATES, + .gateInputs = gateInputBank.values, + .numGPIOOutputs = NUM_GATES, + .gpioOutputs = gateOutputBank.values, + .numToggles = 0, + .toggles = NULL, + .numTriSwitches = 0, + .triSwitches = NULL + }; + } + + void InitADCController() { + adcController.Init(&board.adc, ADC_CHANNEL_SPECS); + } + + void InitDAC() { + // TODO: We're actually managing two different buffers + // here. A better architecture would allow for + // multiple DAC (and ADC) instances. + for (size_t i = 0; i < NUM_INTERNAL_DAC_CHANNELS; i++) { + dacChannels[i].Init(board.dacOutputValues, i, + DPT_INTERNAL_DAC_NORMALIZATION); + } + + for (size_t i = 0; i < NUM_EXTERNAL_DAC_CHANNELS; i++) { + size_t j = NUM_INTERNAL_DAC_CHANNELS + i; + dacChannels[j].Init(externalDACBuffer, i, NO_NORMALIZATION); + } + + dacOutputBank.outputs = dacChannels; + } + + void InitControls() { + for (size_t i = 0; i < NUM_GATES; i++) { + gateInputs[i].Init(GATE_IN_PINS[i]); + gateOutputs[i].Init(GATE_OUT_PINS[i], + DSY_GPIO_MODE_OUTPUT_PP, DSY_GPIO_NOPULL); + } + gateInputBank.inputs = gateInputs; + gateOutputBank.outputs = gateOutputs; + } + + void Start(daisy::AudioHandle::AudioCallback callback) { + adcController.Start(); + board.StartDac(); + tim5.Start(); + board.audio.Start(callback); + } + + void Stop () { + adcController.Stop(); + board.dac.Stop(); + tim5.Stop(); + board.audio.Stop(); + } + + inline void Read() { + adcController.Read(); + gateInputBank.Read(); + } + + inline void Write() { + gateOutputBank.Write(); + dacOutputBank.Write(); + } + }; +}; +}; diff --git a/hosts/daisy/include/electrosmith-patch-init-device.hpp b/hosts/daisy/include/electrosmith-patch-init-device.hpp new file mode 100644 index 0000000..7cf89e6 --- /dev/null +++ b/hosts/daisy/include/electrosmith-patch-init-device.hpp @@ -0,0 +1,179 @@ +#pragma once + +#include "patchsm-device.hpp" + +using namespace sig::libdaisy; + +enum { + sig_host_KNOB_1 = 0, + sig_host_KNOB_2, + sig_host_KNOB_3, + sig_host_KNOB_4 +}; + +enum { + sig_host_CV_IN_1 = 4, + sig_host_CV_IN_2, + sig_host_CV_IN_3, + sig_host_CV_IN_4 +}; + +enum { + sig_host_CV_OUT_1 = 0, + sig_host_CV_OUT_2 +}; + +enum { + sig_host_TOGGLE_1 = 0, + sig_host_TOGGLE_2 +}; + +namespace electrosmith { +namespace patchinit { + static const size_t NUM_ADC_CHANNELS = 8; + + static ADCChannelSpec ADC_CHANNEL_SPECS[NUM_ADC_CHANNELS] = { + // Knobs + {patchsm::PIN_CV_1, INVERT}, // CV1/Pin C5 + {patchsm::PIN_CV_2, INVERT}, // CV2/Pin C4 + {patchsm::PIN_CV_3, INVERT}, // CV3/Pin C3 + {patchsm::PIN_CV_4, INVERT}, // CV4/Pin C2 + + // CV Jacks + {patchsm::PIN_CV_8, INVERT}, // CV5/Pin C6 + {patchsm::PIN_CV_7, INVERT}, // CV6/Pin C7 + {patchsm::PIN_CV_5, INVERT}, // CV7/Pin C8 + {patchsm::PIN_CV_6, INVERT}, // CV8/Pin C9 + + }; + + static const size_t NUM_GATES = 2; + + static dsy_gpio_pin GATE_IN_PINS[NUM_GATES] = { + patchsm::PIN_B10, + patchsm::PIN_B9 + }; + + static dsy_gpio_pin GATE_OUT_PINS[NUM_GATES] = { + patchsm::PIN_B5, + patchsm::PIN_B6 + }; + + static const size_t NUM_TOGGLES = 2; + static dsy_gpio_pin TOGGLE_PINS[NUM_GATES] = { + patchsm::PIN_B7, + patchsm::PIN_B8 + }; + + static const size_t NUM_DAC_CHANNELS = 2; + + class PatchInitDevice : public electrosmith::BasePatchSMDevice { + public: + ADCController adcController; + GateInput gateInputs[NUM_GATES]; + InputBank gateInputBank; + GPIOOutput gateOutputs[NUM_GATES]; + OutputBank gateOutputBank; + Toggle toggles[NUM_TOGGLES]; + InputBank toggleBank; + BufferedAnalogOutput dacChannels[NUM_DAC_CHANNELS]; + OutputBank dacOutputBank; + + static void onEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + PatchInitDevice* self = + static_cast (hardware->userData); + self->Read(); + } + + static void afterEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + PatchInitDevice* self = + static_cast (hardware->userData); + self->Write(); + } + + void Init(struct sig_AudioSettings* audioSettings, + struct sig_dsp_SignalEvaluator* evaluator) { + board.Init(audioSettings->blockSize, audioSettings->sampleRate); + // The DAC and ADC have to be initialized after the board. + InitADCController(); + InitDAC(); + InitControls(); + + hardware = { + .evaluator = evaluator, + .onEvaluateSignals = onEvaluateSignals, + .afterEvaluateSignals = afterEvaluateSignals, + .userData = this, + .numAudioInputChannels = 2, + .audioInputChannels = NULL, // Supplied by audio callback + .numAudioOutputChannels = 2, + .audioOutputChannels = NULL, // Supplied by audio callback + .numADCChannels = NUM_ADC_CHANNELS, + .adcChannels = adcController.channelBank.values, + .numDACChannels = NUM_DAC_CHANNELS, + .dacChannels = dacOutputBank.values, + .numGateInputs = NUM_GATES, + .gateInputs = gateInputBank.values, + .numGPIOOutputs = NUM_GATES, + .gpioOutputs = gateOutputBank.values, + .numToggles = NUM_TOGGLES, + .toggles = toggleBank.values, + .numTriSwitches = 0, + .triSwitches = NULL + }; + } + + void InitADCController() { + adcController.Init(&board.adc, ADC_CHANNEL_SPECS); + } + + void InitDAC() { + for (size_t i = 0; i < NUM_DAC_CHANNELS; i++) { + dacChannels[i].Init(board.dacOutputValues, i); + } + + dacOutputBank.outputs = dacChannels; + } + + void InitControls() { + for (size_t i = 0; i < NUM_GATES; i++) { + gateInputs[i].Init(GATE_IN_PINS[i]); + gateOutputs[i].Init(GATE_OUT_PINS[i], + DSY_GPIO_MODE_OUTPUT_PP, DSY_GPIO_NOPULL); + } + gateInputBank.inputs = gateInputs; + gateOutputBank.outputs = gateOutputs; + + for (size_t i = 0; i < NUM_TOGGLES; i++) { + toggles[i].Init(TOGGLE_PINS[i]); + } + toggleBank.inputs = toggles; + } + + void Start(daisy::AudioHandle::AudioCallback callback) { + adcController.Start(); + board.StartDac(); + board.audio.Start(callback); + } + + void Stop () { + adcController.Stop(); + board.dac.Stop(); + board.audio.Stop(); + } + + inline void Read() { + adcController.Read(); + gateInputBank.Read(); + toggleBank.Read(); + } + + inline void Write() { + dacOutputBank.Write(); + gateOutputBank.Write(); + } + }; +}; +}; diff --git a/hosts/daisy/include/kxmx-bluemchen-device.hpp b/hosts/daisy/include/kxmx-bluemchen-device.hpp new file mode 100644 index 0000000..307b8f3 --- /dev/null +++ b/hosts/daisy/include/kxmx-bluemchen-device.hpp @@ -0,0 +1,162 @@ +#pragma once + +#include "signaletic-host.h" +#include "signaletic-daisy-host.hpp" +#include "sig-daisy-seed.hpp" +#include "dev/oled_ssd130x.h" + +using namespace sig::libdaisy; + +enum { + sig_host_KNOB_1 = 0, + sig_host_KNOB_2 +}; + +enum { + sig_host_CV_IN_1 = 2, + sig_host_CV_IN_2 +}; + +enum { + sig_host_BUTTON_1 = 0 +}; + +enum { + sig_host_ENCODER_1 = 0 +}; + +enum { + sig_host_AUDIO_IN_1 = 0, + sig_host_AUDIO_IN_2 +}; + +enum { + sig_host_AUDIO_OUT_1 = 0, + sig_host_AUDIO_OUT_2 +}; + +namespace kxmx { +namespace bluemchen { + static const size_t NUM_ADC_CHANNELS = 4; + static const size_t NUM_BUTTONS = 1; + static dsy_gpio_pin BUTTON_PINS[NUM_BUTTONS] = { + seed::PIN_D28 + }; + + static const size_t NUM_ENCODERS = 1; + static dsy_gpio_pin ENCODER_PINS[NUM_BUTTONS][2] = { + {seed::PIN_D27, seed::PIN_D26} + }; + + static ADCChannelSpec ADC_CHANNEL_SPECS[NUM_ADC_CHANNELS] = { + {seed::PIN_D16, BI_TO_UNIPOLAR}, + {seed::PIN_D15, BI_TO_UNIPOLAR}, + {seed::PIN_D21, INVERT}, + {seed::PIN_D18, INVERT} + }; + + class BluemchenDevice { + public: + seed::SeedBoard board; + ADCController adcController; + Toggle buttons[NUM_BUTTONS]; + InputBank buttonBank; + sig::libdaisy::Encoder encoders[NUM_ENCODERS]; + InputBank encoderBank; + daisy::OledDisplay + display; + daisy::MidiUartHandler midi; + struct sig_host_HardwareInterface hardware; + + void Init(struct sig_AudioSettings* audioSettings, + struct sig_dsp_SignalEvaluator* evaluator) { + board.Init(audioSettings->blockSize, audioSettings->sampleRate); + InitADCController(); + + hardware = { + .evaluator = evaluator, + .onEvaluateSignals = onEvaluateSignals, + .afterEvaluateSignals = afterEvaluateSignals, + .userData = this, + .numAudioInputChannels = 2, + .audioInputChannels = NULL, // Supplied by audio callback + .numAudioOutputChannels = 2, + .audioOutputChannels = NULL, // Supplied by audio callback + .numADCChannels = NUM_ADC_CHANNELS, + .adcChannels = adcController.channelBank.values, + .numDACChannels = 0, + .dacChannels = NULL, + .numGateInputs = 0, + .gateInputs = NULL, + .numGPIOOutputs = 0, + .gpioOutputs = NULL, + .numToggles = 1, + .toggles = buttonBank.values, + .numTriSwitches = 0, + .triSwitches = NULL, + .numEncoders = 1, + .encoders = encoderBank.values + }; + + InitDisplay(); + InitMidi(); + InitControls(); + } + + void InitADCController() { + adcController.Init(&board.adc, ADC_CHANNEL_SPECS); + } + + void InitDisplay() { + daisy::OledDisplay::Config config; + config.driver_config.transport_config.Defaults(); + display.Init(config); + } + + void InitControls() { + buttons[0].Init(BUTTON_PINS[0]); + buttonBank.inputs = buttons; + + encoders[0].Init(ENCODER_PINS[0][0], ENCODER_PINS[0][1]); + encoderBank.inputs = encoders; + } + + void InitMidi() { + daisy::MidiUartHandler::Config config; + midi.Init(config); + } + + void Start(daisy::AudioHandle::AudioCallback callback) { + adcController.Start(); + board.audio.Start(callback); + } + + void Stop () { + adcController.Stop(); + board.audio.Stop(); + } + + inline void Read() { + adcController.Read(); + buttonBank.Read(); + encoderBank.Read(); + } + + inline void Write() {} + + static void onEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + BluemchenDevice* self = static_cast( + hardware->userData); + self->Read(); + } + + static void afterEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + BluemchenDevice* self = static_cast( + hardware->userData); + self->Write(); + } + }; +}; +}; diff --git a/hosts/daisy/include/kxmx-nehcmeulb-device.hpp b/hosts/daisy/include/kxmx-nehcmeulb-device.hpp new file mode 100644 index 0000000..f31af6d --- /dev/null +++ b/hosts/daisy/include/kxmx-nehcmeulb-device.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "kxmx-bluemchen-device.hpp" + +enum { + sig_host_CV_OUT_1 = 0, + sig_host_CV_OUT_2 +}; + +namespace kxmx { +namespace bluemchen { + static const size_t NUM_DAC_CHANNELS = 2; + + class NehcmeulbDevice : public kxmx::bluemchen::BluemchenDevice { + public: + PollingAnalogOutput dacChannels[NUM_DAC_CHANNELS]; + OutputBank dacOutputBank; + + void Init(struct sig_AudioSettings* audioSettings, + struct sig_dsp_SignalEvaluator* evaluator) { + board.Init(audioSettings->blockSize, audioSettings->sampleRate); + InitADCController(); + InitDAC(); + + hardware = { + .evaluator = evaluator, + .onEvaluateSignals = onEvaluateSignals, + .afterEvaluateSignals = afterEvaluateSignals, + .userData = this, + .numAudioInputChannels = 2, + .audioInputChannels = NULL, // Supplied by audio callback + .numAudioOutputChannels = 2, + .audioOutputChannels = NULL, // Supplied by audio callback + .numADCChannels = NUM_ADC_CHANNELS, + .adcChannels = adcController.channelBank.values, + .numDACChannels = NUM_DAC_CHANNELS, + .dacChannels = dacOutputBank.values, + .numGateInputs = 0, + .gateInputs = NULL, + .numGPIOOutputs = 0, + .gpioOutputs = NULL, + .numToggles = 1, + .toggles = buttonBank.values, + .numTriSwitches = 0, + .triSwitches = NULL, + .numEncoders = 1, + .encoders = encoderBank.values + }; + + InitDisplay(); + InitMidi(); + InitControls(); + } + + void InitDAC() { + board.InitDAC(); + for (size_t i = 0; i < NUM_DAC_CHANNELS; i++) { + dacChannels[i].Init(&board.dac, i); + } + + dacOutputBank.outputs = dacChannels; + } + + void Start(daisy::AudioHandle::AudioCallback callback) { + adcController.Start(); + board.audio.Start(callback); + } + + void Stop () { + adcController.Stop(); + board.dac.Stop(); + board.audio.Stop(); + } + + void Write() { + dacOutputBank.Write(); + } + + static void afterEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + NehcmeulbDevice* self = static_cast( + hardware->userData); + self->Write(); + } + }; +} +} diff --git a/hosts/daisy/include/lichen-bifocals-device.hpp b/hosts/daisy/include/lichen-bifocals-device.hpp new file mode 100644 index 0000000..ea34379 --- /dev/null +++ b/hosts/daisy/include/lichen-bifocals-device.hpp @@ -0,0 +1,169 @@ +#pragma once + +#include "signaletic-host.h" +#include "signaletic-daisy-host.hpp" +#include "sig-daisy-patch-sm.hpp" + +using namespace sig::libdaisy; + +enum { + sig_host_KNOB_1 = 0, + sig_host_KNOB_2, + sig_host_KNOB_3, + sig_host_KNOB_4, + sig_host_KNOB_5 +}; + +enum { + sig_host_CV_IN_1 = 5, + sig_host_CV_IN_2, + sig_host_CV_IN_3, + sig_host_CV_IN_4, + sig_host_CV_IN_5 +}; + +enum { + sig_host_CV_OUT_1 = 0 +}; + +enum { + sig_host_AUDIO_IN_1 = 0, + sig_host_AUDIO_IN_2 +}; + +enum { + sig_host_AUDIO_OUT_1 = 0, + sig_host_AUDIO_OUT_2 +}; + +namespace lichen { +namespace bifocals { + static const size_t NUM_ADC_CHANNELS = 10; + + // These pins are ordered based on the panel: + // knobs first in labelled order, then CV jacks in labelled order. + static ADCChannelSpec ADC_CHANNEL_SPECS[NUM_ADC_CHANNELS] = { + // Freq Knob/POT_2_CV_7/Pin C8 - Unipolar + {patchsm::PIN_CV_7, INVERT}, + // Reso Knob/CV_IN_5/Pin C6 + {patchsm::PIN_CV_5, INVERT}, + // Gain Knob/POT_1_CV_6/Pin C7 + {patchsm::PIN_CV_6, INVERT}, + // Knob four/POT_4_CV_9/Pin A2 + {patchsm::PIN_ADC_9, BI_TO_UNIPOLAR}, + // Knob five/POT_5_CV_10/Pin A3 + {patchsm::PIN_ADC_10, BI_TO_UNIPOLAR}, + + // Shape CV/CV_IN_1/Pin C5 + {patchsm::PIN_CV_1, INVERT}, + // Frequency CV/CV_IN_2/Pin C4 + {patchsm::PIN_CV_2, INVERT}, + // Reso CV/CV_IN_3/Pin C3 + {patchsm::PIN_CV_3, INVERT}, + // Gain CV/CV_IN_4/Pin C2 + {patchsm::PIN_CV_4, INVERT}, + // Skew CV/POT_3_CV_8/Pin C9 + {patchsm::PIN_CV_8, INVERT} + }; + + static const size_t NUM_BUTTONS = 1; + static dsy_gpio_pin BUTTON_PINS[NUM_BUTTONS] = { + patchsm::PIN_D1 + }; + + static const size_t NUM_DAC_CHANNELS = 1; + + class BifocalsDevice { + public: + patchsm::PatchSMBoard board; + ADCController adcController; + Toggle buttons[NUM_BUTTONS]; + InputBank buttonBank; + BufferedAnalogOutput dacChannels[NUM_DAC_CHANNELS]; + OutputBank dacOutputBank; + struct sig_host_HardwareInterface hardware; + + static void onEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + BifocalsDevice* self = static_cast (hardware->userData); + self->Read(); + } + + static void afterEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + BifocalsDevice* self = static_cast (hardware->userData); + self->Write(); + } + + void Init(struct sig_AudioSettings* audioSettings, + struct sig_dsp_SignalEvaluator* evaluator) { + board.Init(audioSettings->blockSize, audioSettings->sampleRate); + // The DAC and ADC have to be initialized after the board. + InitADCController(); + InitDAC(); + InitControls(); + + hardware = { + .evaluator = evaluator, + .onEvaluateSignals = onEvaluateSignals, + .afterEvaluateSignals = afterEvaluateSignals, + .userData = this, + .numAudioInputChannels = 2, + .audioInputChannels = NULL, // Supplied by audio callback + .numAudioOutputChannels = 2, + .audioOutputChannels = NULL, // Supplied by audio callback + .numADCChannels = NUM_ADC_CHANNELS, + .adcChannels = adcController.channelBank.values, + .numDACChannels = NUM_DAC_CHANNELS, + .dacChannels = dacOutputBank.values, + .numGateInputs = 0, + .gateInputs = NULL, + .numGPIOOutputs = 0, + .gpioOutputs = NULL, + .numToggles = NUM_BUTTONS, + .toggles = buttonBank.values, + .numTriSwitches = 0, + .triSwitches = NULL + }; + } + + void InitADCController() { + adcController.Init(&board.adc, ADC_CHANNEL_SPECS); + } + + void InitDAC() { + for (size_t i = 0; i < NUM_DAC_CHANNELS; i++) { + dacChannels[i].Init(board.dacOutputValues, i); + } + + dacOutputBank.outputs = dacChannels; + } + + void InitControls() { + buttons[0].Init(BUTTON_PINS[0]); + buttonBank.inputs = buttons; + } + + void Start(daisy::AudioHandle::AudioCallback callback) { + adcController.Start(); + board.StartDac(); + board.audio.Start(callback); + } + + void Stop () { + adcController.Stop(); + board.dac.Stop(); + board.audio.Stop(); + } + + inline void Read() { + adcController.Read(); + buttonBank.Read(); + } + + inline void Write() { + dacOutputBank.Write(); + } + }; +}; +}; diff --git a/hosts/daisy/include/lichen-freddie-device.hpp b/hosts/daisy/include/lichen-freddie-device.hpp new file mode 100644 index 0000000..0410441 --- /dev/null +++ b/hosts/daisy/include/lichen-freddie-device.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include "signaletic-host.h" +#include "signaletic-daisy-host.hpp" +#include "sig-daisy-seed.hpp" + +using namespace sig::libdaisy; + +enum { + sig_host_KNOB_1 = 0, + sig_host_KNOB_2, + sig_host_KNOB_3, + sig_host_KNOB_4, + sig_host_KNOB_5, + sig_host_KNOB_6, + sig_host_KNOB_7, + sig_host_KNOB_8 +}; + +namespace lichen { +namespace freddie { + static const size_t NUM_CONTROLS = 8; + + static ADCChannelSpec ADC_CHANNEL_SPECS[NUM_CONTROLS] = { + {seed::PIN_ADC_0, INVERT}, + {seed::PIN_ADC_1, INVERT}, + {seed::PIN_ADC_2, INVERT}, + {seed::PIN_ADC_3, INVERT}, + {seed::PIN_ADC_4, INVERT}, + {seed::PIN_ADC_5, INVERT}, + {seed::PIN_ADC_6, INVERT}, + {seed::PIN_ADC_7, INVERT} + }; + + static dsy_gpio_pin BUTTON_PINS[NUM_CONTROLS] = { + seed::PIN_D0, + seed::PIN_D1, + seed::PIN_D2, + seed::PIN_D3, + seed::PIN_D4, + seed::PIN_D5, + seed::PIN_D6, + seed::PIN_D7 + }; + + static dsy_gpio_pin LED_PINS[] = { + seed::PIN_D8, + seed::PIN_D9, + seed::PIN_D10, + seed::PIN_D11, + seed::PIN_D12, + seed::PIN_D25, + seed::PIN_D26, + seed::PIN_D27 + }; + + class FreddieDevice { + public: + seed::SeedBoard board; + ADCController adcController; + Toggle buttons[NUM_CONTROLS]; + InputBank buttonBank; + GPIOOutput leds[NUM_CONTROLS]; + OutputBank ledBank; + struct sig_host_HardwareInterface hardware; + + void Init(struct sig_AudioSettings* audioSettings) { + board.Init(audioSettings->blockSize, audioSettings->sampleRate); + InitADCController(); + InitButtons(); + InitLEDs(); + + hardware = { + .numAudioInputChannels = 0, + .audioInputChannels = NULL, + .numAudioOutputChannels = 0, + .audioOutputChannels = NULL, + .numADCChannels = NUM_CONTROLS, + .adcChannels = adcController.channelBank.values, + .numDACChannels = 0, + .dacChannels = NULL, + .numGateInputs = 0, + .gateInputs = NULL, + .numGPIOOutputs = NUM_CONTROLS, + .gpioOutputs = ledBank.values, + .numToggles = NUM_CONTROLS, + .toggles = buttonBank.values, + .numTriSwitches = 0, + .triSwitches = NULL + }; + } + + void InitADCController() { + adcController.Init(&board.adc, ADC_CHANNEL_SPECS); + } + + void InitButtons() { + for (size_t i = 0; i < NUM_CONTROLS; i++) { + buttons[i].Init(BUTTON_PINS[i]); + } + + buttonBank.inputs = buttons; + } + + void InitLEDs() { + for (size_t i = 0; i < NUM_CONTROLS; i++) { + leds[i].Init(LED_PINS[i], DSY_GPIO_MODE_OUTPUT_PP, + DSY_GPIO_NOPULL); + } + + ledBank.outputs = leds; + } + + void Start() { + adcController.Start(); + } + + void Stop () { + adcController.Stop(); + } + + inline void Read() { + adcController.Read(); + buttonBank.Read(); + } + + inline void Write() { + ledBank.Write(); + } + }; +}; +}; diff --git a/hosts/daisy/include/lichen-medium-device.hpp b/hosts/daisy/include/lichen-medium-device.hpp new file mode 100644 index 0000000..0d9a082 --- /dev/null +++ b/hosts/daisy/include/lichen-medium-device.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include "signaletic-host.h" +#include "signaletic-daisy-host.hpp" +#include "sig-daisy-patch-sm.hpp" + +using namespace sig::libdaisy; + +enum { + sig_host_KNOB_1 = 0, + sig_host_KNOB_2, + sig_host_KNOB_3, + sig_host_KNOB_4, + sig_host_KNOB_5, + sig_host_KNOB_6 +}; + +enum { + sig_host_CV_IN_1 = 6, + sig_host_CV_IN_2, + sig_host_CV_IN_3, + sig_host_CV_IN_4, + sig_host_CV_IN_5, + sig_host_CV_IN_6 +}; + +enum { + sig_host_CV_OUT_1 = 0 +}; + +enum { + sig_host_AUDIO_IN_1 = 0, + sig_host_AUDIO_IN_2 +}; + +enum { + sig_host_AUDIO_OUT_1 = 0, + sig_host_AUDIO_OUT_2 +}; + +namespace lichen { +namespace medium { + static const size_t NUM_ADC_CHANNELS = 12; + + // These pins are ordered based on the panel: + // knobs first in labelled order, then CV jacks in labelled order. + static ADCChannelSpec ADC_CHANNEL_SPECS[NUM_ADC_CHANNELS] = { + {patchsm::PIN_CV_5, INVERT}, // Knob one/POT_CV_1/Pin C8 + {patchsm::PIN_CV_6, INVERT}, // Knob two/POT_CV_2/Pin C9 + {patchsm::PIN_ADC_9, BI_TO_UNIPOLAR}, // Knob three/POT_CV_3/Pin A2 + {patchsm::PIN_ADC_11, BI_TO_UNIPOLAR}, // Knob four/POT_CV_4/Pin A3 + {patchsm::PIN_ADC_10, BI_TO_UNIPOLAR}, // Knob five/POT_CV_5/Pin D9 + {patchsm::PIN_ADC_12, BI_TO_UNIPOLAR}, // Knob six/POT_CV_6/Pin D8 + + {patchsm::PIN_CV_1, INVERT}, // CV1 ("seven")/CV_IN_1/Pin C5 + {patchsm::PIN_CV_2, INVERT}, // CV2 ("eight")/CV_IN_2Pin C4 + {patchsm::PIN_CV_3, INVERT}, // CV3 ("nine")/CV_IN_3/Pin C3 + {patchsm::PIN_CV_7, INVERT}, // CV6 ("ten")/CV_IN_6/Pin C7 + {patchsm::PIN_CV_8, INVERT}, // CV5 ("eleven")/CV_IN_5/Pin C6 + {patchsm::PIN_CV_4, INVERT} // CV4 ("twelve")/CV_IN_4/Pin C2 + }; + + static const size_t NUM_GATES = 1; + + static dsy_gpio_pin GATE_PINS[NUM_GATES] = { + patchsm::PIN_B10 + }; + + static const size_t NUM_TRISWITCHES = 1; + static dsy_gpio_pin TRISWITCH_PINS[NUM_GATES][2] = { + { + patchsm::PIN_B7, + patchsm::PIN_B8 + } + }; + + static const size_t NUM_BUTTONS = 1; + static dsy_gpio_pin BUTTON_PINS[NUM_BUTTONS] = { + patchsm::PIN_D1 + }; + + static const size_t NUM_DAC_CHANNELS = 1; + + class MediumDevice { + public: + patchsm::PatchSMBoard board; + ADCController adcController; + GateInput gates[NUM_GATES]; + InputBank gateBank; + TriSwitch triSwitches[NUM_TRISWITCHES]; + InputBank switchBank; + Toggle buttons[NUM_BUTTONS]; + InputBank buttonBank; + BufferedAnalogOutput dacChannels[NUM_DAC_CHANNELS]; + OutputBank dacOutputBank; + struct sig_host_HardwareInterface hardware; + + static void onEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + MediumDevice* self = static_cast (hardware->userData); + self->Read(); + } + + static void afterEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + MediumDevice* self = static_cast (hardware->userData); + self->Write(); + } + + void Init(struct sig_AudioSettings* audioSettings, + struct sig_dsp_SignalEvaluator* evaluator) { + board.Init(audioSettings->blockSize, audioSettings->sampleRate); + // The DAC and ADC have to be initialized after the board. + InitADCController(); + InitDAC(); + InitControls(); + + hardware = { + .evaluator = evaluator, + .onEvaluateSignals = onEvaluateSignals, + .afterEvaluateSignals = afterEvaluateSignals, + .userData = this, + .numAudioInputChannels = 2, + .audioInputChannels = NULL, // Supplied by audio callback + .numAudioOutputChannels = 2, + .audioOutputChannels = NULL, // Supplied by audio callback + .numADCChannels = NUM_ADC_CHANNELS, + .adcChannels = adcController.channelBank.values, + .numDACChannels = NUM_DAC_CHANNELS, + .dacChannels = dacOutputBank.values, + .numGateInputs = NUM_GATES, + .gateInputs = gateBank.values, + .numGPIOOutputs = 0, + .gpioOutputs = NULL, + .numToggles = NUM_BUTTONS, + .toggles = buttonBank.values, + .numTriSwitches = NUM_TRISWITCHES, + .triSwitches = switchBank.values + }; + } + + void InitADCController() { + adcController.Init(&board.adc, ADC_CHANNEL_SPECS); + } + + void InitDAC() { + for (size_t i = 0; i < NUM_DAC_CHANNELS; i++) { + dacChannels[i].Init(board.dacOutputValues, i); + } + + dacOutputBank.outputs = dacChannels; + } + + void InitControls() { + gates[0].Init(GATE_PINS[0]); + gateBank.inputs = gates; + triSwitches[0].Init(TRISWITCH_PINS[0]); + switchBank.inputs = triSwitches; + buttons[0].Init(BUTTON_PINS[0]); + buttonBank.inputs = buttons; + } + + void Start(daisy::AudioHandle::AudioCallback callback) { + adcController.Start(); + board.StartDac(); + board.audio.Start(callback); + } + + void Stop () { + adcController.Stop(); + board.dac.Stop(); + board.audio.Stop(); + } + + inline void Read() { + adcController.Read(); + gateBank.Read(); + switchBank.Read(); + buttonBank.Read(); + } + + inline void Write() { + dacOutputBank.Write(); + } + }; +}; +}; diff --git a/hosts/daisy/include/looper-view.h b/hosts/daisy/include/looper-view.h index 18e0d40..bed5b15 100644 --- a/hosts/daisy/include/looper-view.h +++ b/hosts/daisy/include/looper-view.h @@ -1,3 +1,6 @@ +#ifndef SIGNALETIC_LOOPER_VIEW_H +#define SIGNALETIC_LOOPER_VIEW_H + // Note: This is a C++ file because I haven't gotten around to // considering how to wrap Daisy objects in C, // and doing so doesn't seem hugely important since we're certain @@ -172,3 +175,5 @@ void sig_ui_daisy_LooperView_render(struct sig_ui_daisy_LooperView* self, sig_ui_daisy_LoopRenderer_drawPositionLine(self->loopRenderer, playbackPos, self->positionLineThickness, foregroundOn); } + +#endif // SIGNALETIC_LOOPER_VIEW_H diff --git a/hosts/daisy/include/ne-versio-device.hpp b/hosts/daisy/include/ne-versio-device.hpp new file mode 100644 index 0000000..9a7a56e --- /dev/null +++ b/hosts/daisy/include/ne-versio-device.hpp @@ -0,0 +1,163 @@ +#pragma once + +#include "signaletic-host.h" +#include "signaletic-daisy-host.hpp" +#include "sig-daisy-seed.hpp" + +using namespace sig::libdaisy; + +enum { + sig_host_CV_IN_1 = 0, + sig_host_CV_IN_2, + sig_host_CV_IN_3, + sig_host_CV_IN_4, + sig_host_CV_IN_5, + sig_host_CV_IN_6, + sig_host_CV_IN_7 +}; + +enum { + sig_host_GATE_IN_1 = 0 +}; + +enum { + sig_host_BUTTON_1 = 0 +}; + +enum { + sig_host_TRISWITCH_1 = 0, + sig_host_TRISWITCH_2 +}; + +enum { + sig_host_AUDIO_IN_1 = 0, + sig_host_AUDIO_IN_2 +}; + +enum { + sig_host_AUDIO_OUT_1 = 0, + sig_host_AUDIO_OUT_2 +}; + +// TODO: Add support for Versio's LED banks. +namespace ne { +namespace versio { + static const size_t NUM_ADC_CHANNELS = 7; + static ADCChannelSpec ADC_CHANNEL_SPECS[NUM_ADC_CHANNELS] = { + {seed::PIN_D21, INVERT}, + {seed::PIN_D22, INVERT}, + {seed::PIN_D28, INVERT}, + {seed::PIN_D23, INVERT}, + {seed::PIN_D16, INVERT}, + {seed::PIN_D17, INVERT}, + {seed::PIN_D19, INVERT} + }; + + static const size_t NUM_GATES = 1; + static dsy_gpio_pin GATE_INPUT_PINS[NUM_GATES] = { + seed::PIN_D24 + }; + + static const size_t NUM_BUTTONS = 1; + static dsy_gpio_pin BUTTON_PINS[NUM_BUTTONS] = { + seed::PIN_D30 + }; + + static const size_t NUM_TRISWITCHES = 2; + static dsy_gpio_pin TRISWITCH_PINS[NUM_TRISWITCHES][2] = { + {seed::PIN_D6, seed::PIN_D5}, + {seed::PIN_D1, seed::PIN_D0} + }; + + class VersioDevice { + public: + seed::SeedBoard board; + ADCController adcController; + GateInput gateInputs[NUM_GATES]; + InputBank gateInputBank; + Toggle buttons[NUM_BUTTONS]; + InputBank buttonBank; + TriSwitch triSwitches[NUM_TRISWITCHES]; + InputBank switchBank; + struct sig_host_HardwareInterface hardware; + + void Init(struct sig_AudioSettings* audioSettings, + struct sig_dsp_SignalEvaluator* evaluator) { + board.Init(audioSettings->blockSize, audioSettings->sampleRate); + InitADCController(); + InitControls(); + + hardware = { + .evaluator = evaluator, + .onEvaluateSignals = onEvaluateSignals, + .afterEvaluateSignals = afterEvaluateSignals, + .userData = this, + .numAudioInputChannels = 2, + .audioInputChannels = NULL, // Supplied by audio callback + .numAudioOutputChannels = 2, + .audioOutputChannels = NULL, // Supplied by audio callback + .numADCChannels = NUM_ADC_CHANNELS, + .adcChannels = adcController.channelBank.values, + .numDACChannels = 0, + .dacChannels = NULL, + .numGateInputs = 1, + .gateInputs = gateInputBank.values, + .numGPIOOutputs = 0, + .gpioOutputs = NULL, + .numToggles = 1, + .toggles = buttonBank.values, + .numTriSwitches = NUM_TRISWITCHES, + .triSwitches = switchBank.values, + .numEncoders = 0, + .encoders = NULL + }; + + InitControls(); + } + + void InitADCController() { + adcController.Init(&board.adc, ADC_CHANNEL_SPECS); + } + + void InitControls() { + gateInputs[0].Init(GATE_INPUT_PINS[0]); + gateInputBank.inputs = gateInputs; + + for (size_t i = 0; i < NUM_TRISWITCHES; i++) { + triSwitches[i].Init(TRISWITCH_PINS[i]); + } + switchBank.inputs = triSwitches; + + buttons[0].Init(BUTTON_PINS[0]); + buttonBank.inputs = buttons; + } + + void Start(daisy::AudioHandle::AudioCallback callback) { + adcController.Start(); + board.audio.Start(callback); + } + + void Stop () { + adcController.Stop(); + board.audio.Stop(); + } + + inline void Read() { + adcController.Read(); + gateInputBank.Read(); + buttonBank.Read(); + switchBank.Read(); + } + + static void onEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) { + VersioDevice* self = static_cast( + hardware->userData); + self->Read(); + } + + static void afterEvaluateSignals(size_t size, + struct sig_host_HardwareInterface* hardware) {} + }; +}; +}; diff --git a/hosts/daisy/include/patchsm-device.hpp b/hosts/daisy/include/patchsm-device.hpp new file mode 100644 index 0000000..7967960 --- /dev/null +++ b/hosts/daisy/include/patchsm-device.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "signaletic-host.h" +#include "signaletic-daisy-host.hpp" +#include "sig-daisy-patch-sm.hpp" + +using namespace sig::libdaisy; + +enum { + sig_host_AUDIO_IN_1 = 0, + sig_host_AUDIO_IN_2 +}; + +enum { + sig_host_AUDIO_OUT_1 = 0, + sig_host_AUDIO_OUT_2 +}; + +namespace electrosmith { + class BasePatchSMDevice { + public: + patchsm::PatchSMBoard board; + struct sig_host_HardwareInterface hardware; + }; +}; diff --git a/hosts/daisy/include/sig-daisy-board.hpp b/hosts/daisy/include/sig-daisy-board.hpp new file mode 100644 index 0000000..d876906 --- /dev/null +++ b/hosts/daisy/include/sig-daisy-board.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "daisy.h" + +using namespace daisy; + +namespace sig { +namespace libdaisy { + class Board { + public: + System system; + SdramHandle sdram; + QSPIHandle qspi; + AudioHandle audio; + AdcHandle adc; + DacHandle dac; + UsbHandle usb; + dsy_gpio userLED; + float callbackRate; + + SaiHandle::Config::SampleRate sampleRateFromFloat(float sr) { + SaiHandle::Config::SampleRate sai_sr; + switch(int(sr)) { + case 8000: + sai_sr = SaiHandle::Config::SampleRate::SAI_8KHZ; + break; + case 16000: + sai_sr = SaiHandle::Config::SampleRate::SAI_16KHZ; + break; + case 32000: + sai_sr = SaiHandle::Config::SampleRate::SAI_32KHZ; + break; + case 48000: + sai_sr = SaiHandle::Config::SampleRate::SAI_48KHZ; + break; + case 96000: + sai_sr = SaiHandle::Config::SampleRate::SAI_96KHZ; + break; + default: + sai_sr = SaiHandle::Config::SampleRate::SAI_48KHZ; + break; + } + + return sai_sr; + } + + void SetAudioSampleRate(float sr) { + SaiHandle::Config::SampleRate sai_sr = sampleRateFromFloat(sr); + audio.SetSampleRate(sai_sr); + callbackRate = audio.GetSampleRate() / + audio.GetConfig().blocksize; + } + + void InitADC(dsy_gpio_pin* pins, size_t numADCPins) { + AdcChannelConfig adcConfigs[numADCPins]; + + for (size_t i = 0; i < numADCPins; i++) { + adcConfigs[i].InitSingle(pins[i]); + } + + adc.Init(adcConfigs, numADCPins); + } + }; +}; +}; diff --git a/hosts/daisy/include/sig-daisy-patch-sm.hpp b/hosts/daisy/include/sig-daisy-patch-sm.hpp new file mode 100644 index 0000000..ee05694 --- /dev/null +++ b/hosts/daisy/include/sig-daisy-patch-sm.hpp @@ -0,0 +1,81 @@ +#pragma once +#include "sig-daisy-board.hpp" + +namespace sig { +namespace libdaisy { +namespace patchsm { + static constexpr dsy_gpio_pin PIN_NONE = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_A1 = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_A2 = {DSY_GPIOA, 1}; + static constexpr dsy_gpio_pin PIN_A3 = {DSY_GPIOA, 0}; + static constexpr dsy_gpio_pin PIN_A4 = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_A5 = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_A6 = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_A7 = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_A8 = {DSY_GPIOA, 14}; + static constexpr dsy_gpio_pin PIN_A9 = {DSY_GPIOA, 15}; + static constexpr dsy_gpio_pin PIN_A10 = {DSY_GPIOX, 0}; + + static constexpr dsy_gpio_pin PIN_B1 = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_B2 = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_B3 = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_B4 = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_B5 = {DSY_GPIOC, 13}; + static constexpr dsy_gpio_pin PIN_B6 = {DSY_GPIOC, 14}; + static constexpr dsy_gpio_pin PIN_B7 = {DSY_GPIOB, 8}; + static constexpr dsy_gpio_pin PIN_B8 = {DSY_GPIOB, 9}; + static constexpr dsy_gpio_pin PIN_B9 = {DSY_GPIOG, 14}; + static constexpr dsy_gpio_pin PIN_B10 = {DSY_GPIOG, 13}; + + static constexpr dsy_gpio_pin PIN_C1 = {DSY_GPIOA, 5}; + static constexpr dsy_gpio_pin PIN_C2 = {DSY_GPIOA, 7}; + static constexpr dsy_gpio_pin PIN_C3 = {DSY_GPIOA, 2}; + static constexpr dsy_gpio_pin PIN_C4 = {DSY_GPIOA, 6}; + static constexpr dsy_gpio_pin PIN_C5 = {DSY_GPIOA, 3}; + static constexpr dsy_gpio_pin PIN_C6 = {DSY_GPIOB, 1}; + static constexpr dsy_gpio_pin PIN_C7 = {DSY_GPIOC, 4}; + static constexpr dsy_gpio_pin PIN_C8 = {DSY_GPIOC, 0}; + static constexpr dsy_gpio_pin PIN_C9 = {DSY_GPIOC, 1}; + static constexpr dsy_gpio_pin PIN_C10 = {DSY_GPIOA, 4}; + + static constexpr dsy_gpio_pin PIN_D1 = {DSY_GPIOB, 4}; + static constexpr dsy_gpio_pin PIN_D2 = {DSY_GPIOC, 11}; + static constexpr dsy_gpio_pin PIN_D3 = {DSY_GPIOC, 10}; + static constexpr dsy_gpio_pin PIN_D4 = {DSY_GPIOC, 9}; + static constexpr dsy_gpio_pin PIN_D5 = {DSY_GPIOC, 8}; + static constexpr dsy_gpio_pin PIN_D6 = {DSY_GPIOC, 12}; + static constexpr dsy_gpio_pin PIN_D7 = {DSY_GPIOD, 2}; + static constexpr dsy_gpio_pin PIN_D8 = {DSY_GPIOC, 2}; + static constexpr dsy_gpio_pin PIN_D9 = {DSY_GPIOC, 3}; + static constexpr dsy_gpio_pin PIN_D10 = {DSY_GPIOD, 3}; + + static constexpr dsy_gpio_pin PIN_CV_1 = PIN_C5; + static constexpr dsy_gpio_pin PIN_CV_2 = PIN_C4; + static constexpr dsy_gpio_pin PIN_CV_3 = PIN_C3; + static constexpr dsy_gpio_pin PIN_CV_4 = PIN_C2; + static constexpr dsy_gpio_pin PIN_CV_5 = PIN_C6; + static constexpr dsy_gpio_pin PIN_CV_6 = PIN_C7; + static constexpr dsy_gpio_pin PIN_CV_7 = PIN_C8; + static constexpr dsy_gpio_pin PIN_CV_8 = PIN_C9; + static constexpr dsy_gpio_pin PIN_ADC_9 = PIN_A2; + static constexpr dsy_gpio_pin PIN_ADC_10 = PIN_A3; + static constexpr dsy_gpio_pin PIN_ADC_11 = PIN_D9; + static constexpr dsy_gpio_pin PIN_ADC_12 = PIN_D8; + static constexpr dsy_gpio_pin PIN_USER_LED = {DSY_GPIOC, 7}; + + class PatchSMBoard : public Board { + public: + daisy::Pcm3060 codec; + size_t dac_buffer_size_; + uint16_t* dacBuffer[2]; + uint16_t* dacOutputValues; + bool dac_running_; + + void Init(size_t blockSize, float sampleRate); + void InitDac(); + void StartDac(daisy::DacHandle::DacCallback callback = nullptr); + static void DefaultDacCallback(uint16_t **output, size_t size); + }; +}; +}; +}; diff --git a/hosts/daisy/include/sig-daisy-seed.hpp b/hosts/daisy/include/sig-daisy-seed.hpp new file mode 100644 index 0000000..8be6992 --- /dev/null +++ b/hosts/daisy/include/sig-daisy-seed.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include "sig-daisy-board.hpp" + +namespace sig { +namespace libdaisy { +namespace seed { + static constexpr dsy_gpio_pin PIN_NONE = {DSY_GPIOX, 0}; + static constexpr dsy_gpio_pin PIN_D0 = {DSY_GPIOB, 12}; + static constexpr dsy_gpio_pin PIN_D1 = {DSY_GPIOC, 11}; + static constexpr dsy_gpio_pin PIN_D2 = {DSY_GPIOC, 10}; + static constexpr dsy_gpio_pin PIN_D3 = {DSY_GPIOC, 9}; + static constexpr dsy_gpio_pin PIN_D4 = {DSY_GPIOC, 8}; + static constexpr dsy_gpio_pin PIN_D5 = {DSY_GPIOD, 2}; + static constexpr dsy_gpio_pin PIN_D6 = {DSY_GPIOC, 12}; + static constexpr dsy_gpio_pin PIN_D7 = {DSY_GPIOG, 10}; + static constexpr dsy_gpio_pin PIN_D8 = {DSY_GPIOG, 11}; + static constexpr dsy_gpio_pin PIN_D9 = {DSY_GPIOB, 4}; + static constexpr dsy_gpio_pin PIN_D10 = {DSY_GPIOB, 5}; + static constexpr dsy_gpio_pin PIN_D11 = {DSY_GPIOB, 8}; + static constexpr dsy_gpio_pin PIN_D12 = {DSY_GPIOB, 9}; + static constexpr dsy_gpio_pin PIN_D13 = {DSY_GPIOB, 6}; + static constexpr dsy_gpio_pin PIN_D14 = {DSY_GPIOB, 7}; + static constexpr dsy_gpio_pin PIN_D15 = {DSY_GPIOC, 0}; + static constexpr dsy_gpio_pin PIN_D16 = {DSY_GPIOA, 3}; + static constexpr dsy_gpio_pin PIN_D17 = {DSY_GPIOB, 1}; + static constexpr dsy_gpio_pin PIN_D18 = {DSY_GPIOA, 7}; + static constexpr dsy_gpio_pin PIN_D19 = {DSY_GPIOA, 6}; + static constexpr dsy_gpio_pin PIN_D20 = {DSY_GPIOC, 1}; + static constexpr dsy_gpio_pin PIN_D21 = {DSY_GPIOC, 4}; + static constexpr dsy_gpio_pin PIN_D22 = {DSY_GPIOA, 5}; + static constexpr dsy_gpio_pin PIN_D23 = {DSY_GPIOA, 4}; + static constexpr dsy_gpio_pin PIN_D24 = {DSY_GPIOA, 1}; + static constexpr dsy_gpio_pin PIN_D25 = {DSY_GPIOA, 0}; + static constexpr dsy_gpio_pin PIN_D26 = {DSY_GPIOD, 11}; + static constexpr dsy_gpio_pin PIN_D27 = {DSY_GPIOG, 9}; + static constexpr dsy_gpio_pin PIN_D28 = {DSY_GPIOA, 2}; + static constexpr dsy_gpio_pin PIN_D29 = {DSY_GPIOB, 14}; + static constexpr dsy_gpio_pin PIN_D30 = {DSY_GPIOB, 15}; + static constexpr dsy_gpio_pin PIN_USER_LED = {DSY_GPIOC, 7}; + + /** Analog pins share same pins as digital pins */ + static constexpr dsy_gpio_pin PIN_A0 = PIN_D15; + static constexpr dsy_gpio_pin PIN_A1 = PIN_D16; + static constexpr dsy_gpio_pin PIN_A2 = PIN_D17; + static constexpr dsy_gpio_pin PIN_A3 = PIN_D18; + static constexpr dsy_gpio_pin PIN_A4 = PIN_D19; + static constexpr dsy_gpio_pin PIN_A5 = PIN_D20; + static constexpr dsy_gpio_pin PIN_A6 = PIN_D21; + static constexpr dsy_gpio_pin PIN_A7 = PIN_D22; + static constexpr dsy_gpio_pin PIN_A8 = PIN_D23; + static constexpr dsy_gpio_pin PIN_A9 = PIN_D24; + static constexpr dsy_gpio_pin PIN_A10 = PIN_D25; + static constexpr dsy_gpio_pin PIN_A11 = PIN_D28; + + static constexpr dsy_gpio_pin PIN_ADC_0 = PIN_A0; + static constexpr dsy_gpio_pin PIN_ADC_1 = PIN_A1; + static constexpr dsy_gpio_pin PIN_ADC_2 = PIN_A2; + static constexpr dsy_gpio_pin PIN_ADC_3 = PIN_A3; + static constexpr dsy_gpio_pin PIN_ADC_4 = PIN_A4; + static constexpr dsy_gpio_pin PIN_ADC_5 = PIN_A5; + static constexpr dsy_gpio_pin PIN_ADC_6 = PIN_A6; + static constexpr dsy_gpio_pin PIN_ADC_7 = PIN_A7; + static constexpr dsy_gpio_pin PIN_ADC_8 = PIN_A8; + static constexpr dsy_gpio_pin PIN_ADC_9 = PIN_A9; + static constexpr dsy_gpio_pin PIN_ADC_10 = PIN_A10; + static constexpr dsy_gpio_pin PIN_ADC_11 = PIN_A11; + + static constexpr dsy_gpio_pin PIN_DAC_OUT_1 = PIN_ADC_8; + static constexpr dsy_gpio_pin PIN_DAC_OUT_2= PIN_ADC_7; + + /** Pins unique to Daisy Seed 2 DFM */ + static constexpr dsy_gpio_pin PIN_D31 = {DSY_GPIOC, 2}; + static constexpr dsy_gpio_pin PIN_D32 = {DSY_GPIOC, 3}; + + /** Analog Pin alias */ + static constexpr dsy_gpio_pin PIN_A12 = PIN_D31; + static constexpr dsy_gpio_pin PIN_A13 = PIN_D32; + + class SeedBoard : public Board { + public: + enum class BoardVersion { + DAISY_SEED, + DAISY_SEED_1_1 + }; + + void Init(size_t blockSize, float sampleRate); + void InitAudio(size_t blockSize, float sampleRate); + + /** + * @brief Initializes the onboard DAC. + * + * Not called by default, since the DAC pins are shared + * with the ADC, so some configurations may choose not + * to initialize it at all. + */ + void InitDAC(daisy::DacHandle::Channel channel = + daisy::DacHandle::Channel::BOTH); + + BoardVersion CheckBoardVersion(); + }; +}; +}; +}; diff --git a/hosts/daisy/include/signaletic-daisy-host.h b/hosts/daisy/include/signaletic-daisy-host.h deleted file mode 100644 index 0910c9c..0000000 --- a/hosts/daisy/include/signaletic-daisy-host.h +++ /dev/null @@ -1,475 +0,0 @@ -#include -#include "daisy.h" - -#define MAX_NUM_CONTROLS 16 - -enum { - sig_daisy_AUDIO_IN_1 = 0, - sig_daisy_AUDIO_IN_2, - sig_daisy_AUDIO_IN_LAST -}; - -enum { - sig_daisy_AUDIO_OUT_1 = 0, - sig_daisy_AUDIO_OUT_2, - sig_daisy_AUDIO_OUT_LAST -}; - -enum { - sig_daisy_GATE_IN_1 = 0, - sig_daisy_GATE_IN_2, - sig_daisy_GATE_IN_LAST -}; - -enum { - sig_daisy_GATE_OUT_1 = 0, - sig_daisy_GATE_OUT_2, - sig_daisy_GATE_OUT_LAST -}; - -struct sig_daisy_Host_BoardConfiguration { - int numAudioInputChannels; - int numAudioOutputChannels; - int numAnalogInputs; - int numAnalogOutputs; - int numGateInputs; - int numGateOutputs; - int numSwitches; - int numTriSwitches; - int numEncoders; -}; - -struct sig_daisy_Host_Board { - struct sig_daisy_Host_BoardConfiguration* config; - daisy::AudioHandle::InputBuffer audioInputs; - daisy::AudioHandle::OutputBuffer audioOutputs; - daisy::AnalogControl* analogControls; - daisy::DacHandle* dac; - daisy::GateIn* gateInputs[MAX_NUM_CONTROLS]; - // TODO: It's unwieldy and unscalable to accumulate all possible - // hardware UI controls into one Board object. We need - // a more dynamic data structure for referencing - // hardware capabilities. - dsy_gpio* gateOutputs[MAX_NUM_CONTROLS]; - daisy::Switch switches[MAX_NUM_CONTROLS]; - daisy::Switch3 triSwitches[MAX_NUM_CONTROLS]; - daisy::Encoder* encoders[MAX_NUM_CONTROLS]; - void* boardInstance; -}; - -typedef void (*sig_daisy_Host_onEvaluateSignals)( - daisy::AudioHandle::InputBuffer in, - daisy::AudioHandle::OutputBuffer out, - size_t size, - struct sig_daisy_Host* host, - void* userData); - -typedef void (*sig_daisy_Host_afterEvaluateSignals)( - daisy::AudioHandle::InputBuffer in, - daisy::AudioHandle::OutputBuffer out, - size_t size, - struct sig_daisy_Host* host, - void* userData); - -struct sig_daisy_Host { - struct sig_daisy_Host_Impl* impl; - struct sig_daisy_Host_Board board; - struct sig_AudioSettings* audioSettings; - struct sig_dsp_SignalEvaluator* evaluator; - sig_daisy_Host_onEvaluateSignals onEvaluateSignals; - sig_daisy_Host_afterEvaluateSignals afterEvaluateSignals; - void* userData; -}; - -daisy::SaiHandle::Config::SampleRate sig_daisy_Host_convertSampleRate( - float sampleRate); - -void sig_daisy_Host_addOnEvaluateSignalsListener( - struct sig_daisy_Host* self, - sig_daisy_Host_onEvaluateSignals listener, - void* userData); - -void sig_daisy_Host_addAfterEvaluateSignalsListener( - struct sig_daisy_Host* self, - sig_daisy_Host_onEvaluateSignals listener, - void* userData); - -void sig_daisy_Host_audioCallback(daisy::AudioHandle::InputBuffer in, - daisy::AudioHandle::OutputBuffer out, size_t size); - -void sig_daisy_Host_init(struct sig_daisy_Host* self); - -void sig_daisy_Host_registerGlobalHost(struct sig_daisy_Host* host); - -void sig_daisy_Host_noOpAudioCallback(daisy::AudioHandle::InputBuffer in, - daisy::AudioHandle::OutputBuffer out, size_t size, - struct sig_daisy_Host* host, void* userData); - -typedef float (*sig_daisy_Host_getControlValue)( - struct sig_daisy_Host* host, int control); - -typedef void (*sig_daisy_Host_setControlValue)( - struct sig_daisy_Host* host, int control, float value); - -typedef float (*sig_daisy_Host_getGateValue)( - struct sig_daisy_Host* host, int control); - -typedef void (*sig_daisy_Host_setGateValue)( - struct sig_daisy_Host* host, int control, float value); - -typedef float (*sig_daisy_Host_getSwitchValue)( - struct sig_daisy_Host* host, int control); - -typedef float (*sig_daisy_Host_getTriSwitchValue)( - struct sig_daisy_Host* host, int control); - -typedef float (*sig_daisy_Host_getEncoderIncrement)( - struct sig_daisy_Host* host, int control); - -typedef float (*sig_daisy_Host_getEncoderButtonValue)( - struct sig_daisy_Host* host, int control); - -typedef void (*sig_daisy_Host_start)(struct sig_daisy_Host* host); -typedef void (*sig_daisy_Host_stop)(struct sig_daisy_Host* host); - -struct sig_daisy_Host_Impl { - sig_daisy_Host_getControlValue getControlValue; - sig_daisy_Host_setControlValue setControlValue; - sig_daisy_Host_getGateValue getGateValue; - sig_daisy_Host_setGateValue setGateValue; - sig_daisy_Host_getSwitchValue getSwitchValue; - sig_daisy_Host_getTriSwitchValue getTriSwitchValue; - sig_daisy_Host_getEncoderIncrement getEncoderIncrement; - sig_daisy_Host_getEncoderButtonValue getEncoderButtonValue; - sig_daisy_Host_start start; - sig_daisy_Host_stop stop; -}; - -/** - * @brief Writes a bipolar float in the range of -1.0 to 1.0 to the - * on-board Daisy DAC that has been configured for polling (i.e. not DMA) mode. - * - * @param dac a handle to the DAC - * @param control the control to write to - * @param value the value to write - */ -void sig_daisy_Host_writeValueToDACPolling(daisy::DacHandle* dac, - int control, float value); - -float sig_daisy_HostImpl_noOpGetControl(struct sig_daisy_Host* host, - int control); - -void sig_daisy_HostImpl_noOpSetControl(struct sig_daisy_Host* host, - int control, float value); - -/** - * @brief Processes and returns the value for the specified Analog Control. - * This implementation should work with most Daisy boards, assuming they - * provide public access to an array of daisy::AnalogControls (which most do). - * - * @param host the host instance - * @param control the control number to process - * @return float the value of the control - */ -float sig_daisy_HostImpl_processControlValue(struct sig_daisy_Host* host, - int control); - -void sig_daisy_HostImpl_setControlValue( - struct sig_daisy_Host* host, int control, float value); - -float sig_daisy_HostImpl_getGateValue(struct sig_daisy_Host* host, - int control); - -void sig_daisy_HostImpl_setGateValue(struct sig_daisy_Host* host, - int control, float value); - -float sig_daisy_HostImpl_getSwitchValue(struct sig_daisy_Host* host, - int control); - -float sig_daisy_HostImpl_getTriSwitchValue(struct sig_daisy_Host* host, - int control); - -float sig_daisy_HostImpl_getEncoderIncrement(struct sig_daisy_Host* host, - int control); - -float sig_daisy_HostImpl_processEncoderButtonValue(struct sig_daisy_Host* host, - int control); - -struct sig_daisy_CV_Parameters { - float scale; - float offset; - int control; -}; - -struct sig_daisy_GateIn { - struct sig_dsp_Signal signal; - struct sig_daisy_CV_Parameters parameters; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; -}; - -struct sig_daisy_GateIn* sig_daisy_GateIn_new( - struct sig_Allocator* allocator, - struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_GateIn_init(struct sig_daisy_GateIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_GateIn_generate(void* signal); -void sig_daisy_GateIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_GateIn* self); - - -struct sig_daisy_GateOut_Inputs { - float_array_ptr source; -}; - -struct sig_daisy_GateOut { - struct sig_dsp_Signal signal; - struct sig_daisy_CV_Parameters parameters; - struct sig_daisy_GateOut_Inputs inputs; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; -}; - -struct sig_daisy_GateOut* sig_daisy_GateOut_new( - struct sig_Allocator* allocator, - struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_GateOut_init(struct sig_daisy_GateOut* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_GateOut_generate(void* signal); -void sig_daisy_GateOut_destroy(struct sig_Allocator* allocator, - struct sig_daisy_GateOut* self); - - -struct sig_daisy_CVIn { - struct sig_dsp_Signal signal; - struct sig_daisy_CV_Parameters parameters; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; -}; - -struct sig_daisy_CVIn* sig_daisy_CVIn_new(struct sig_Allocator* allocator, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_CVIn_init(struct sig_daisy_CVIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_CVIn_generate(void* signal); -void sig_daisy_CVIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_CVIn* self); - -struct sig_daisy_FilteredCVIn_Parameters { - float scale; - float offset; - int control; - float time; -}; - -struct sig_daisy_FilteredCVIn { - struct sig_dsp_Signal signal; - struct sig_daisy_FilteredCVIn_Parameters parameters; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; - struct sig_daisy_CVIn* cvIn; - struct sig_dsp_Smooth* filter; -}; - -struct sig_daisy_FilteredCVIn* sig_daisy_FilteredCVIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_FilteredCVIn_init(struct sig_daisy_FilteredCVIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_FilteredCVIn_generate(void* signal); -void sig_daisy_FilteredCVIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_FilteredCVIn* self); - - -struct sig_daisy_VOctCVIn_Parameters { - float scale; - float offset; - int control; - float middleFreq; -}; - -struct sig_daisy_VOctCVIn { - struct sig_dsp_Signal signal; - struct sig_daisy_VOctCVIn_Parameters parameters; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; - struct sig_daisy_CVIn* cvIn; - struct sig_dsp_LinearToFreq* cvConverter; -}; - -struct sig_daisy_VOctCVIn* sig_daisy_VOctCVIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_VOctCVIn_init(struct sig_daisy_VOctCVIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_VOctCVIn_generate(void* signal); -void sig_daisy_VOctCVIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_VOctCVIn* self); - -// TODO: Replace SwitchIn and TriSwitchIn with a more generic -// implementation that operates on a configurable list of GPIO pins -// (since a three way switch just consists of two separate pins). - -struct sig_daisy_SwitchIn { - struct sig_dsp_Signal signal; - struct sig_daisy_CV_Parameters parameters; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; - daisy::Switch switchInstance; -}; - -struct sig_daisy_SwitchIn* sig_daisy_SwitchIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_SwitchIn_init(struct sig_daisy_SwitchIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_SwitchIn_generate(void* signal); -void sig_daisy_SwitchIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_SwitchIn* self); - -struct sig_daisy_TriSwitchIn { - struct sig_dsp_Signal signal; - struct sig_daisy_CV_Parameters parameters; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; - daisy::Switch switchInstance; -}; - -struct sig_daisy_TriSwitchIn* sig_daisy_TriSwitchIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_TriSwitchIn_init(struct sig_daisy_TriSwitchIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_TriSwitchIn_generate(void* signal); -void sig_daisy_TriSwitchIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_TriSwitchIn* self); - - -struct sig_daisy_EncoderIn_Outputs { - /** - * @brief The encoder's accumulated value. - * This output tracks the sum of all increment values over time. - */ - float_array_ptr main; - - /** - * @brief The encoder's increment value. - * This output represents the state of change of the encoder: - * -1.0 if the encoder was turned counterclockwise, - * +1.0 if turned clockwise, - * 0.0 if the encoder was not turned - */ - float_array_ptr increment; // The encoder's increment value - - /** - * @brief A gate signal for the encoder's button. - * This output will be > 1.0 if the button is currently pressed, - * and 0.0 it is not. - */ - float_array_ptr button; -}; - -void sig_daisy_EncoderIn_Outputs_newAudioBlocks(struct sig_Allocator* allocator, - struct sig_AudioSettings* audioSettings, - struct sig_daisy_EncoderIn_Outputs* outputs); - -void sig_daisy_EncoderIn_Outputs_destroyAudioBlocks( - struct sig_Allocator* allocator, - struct sig_daisy_EncoderIn_Outputs* outputs); - -struct sig_daisy_EncoderIn { - struct sig_dsp_Signal signal; - struct sig_daisy_CV_Parameters parameters; - struct sig_daisy_EncoderIn_Outputs outputs; - struct sig_daisy_Host* host; - daisy::Encoder encoder; - - float accumulatedValue; -}; - -struct sig_daisy_EncoderIn* sig_daisy_EncoderIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_EncoderIn_init(struct sig_daisy_EncoderIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_EncoderIn_generate(void* signal); -void sig_daisy_EncoderIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_EncoderIn* self); - - -struct sig_daisy_CVOut_Inputs { - float_array_ptr source; -}; - -// TODO: Should we have a "no output" outputs type, -// or continue with the idea of passing through the input -// for chaining? -struct sig_daisy_CVOut { - struct sig_dsp_Signal signal; - struct sig_daisy_CV_Parameters parameters; - struct sig_daisy_CVOut_Inputs inputs; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; -}; - -struct sig_daisy_CVOut* sig_daisy_CVOut_new( - struct sig_Allocator* allocator, - struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_CVOut_init(struct sig_daisy_CVOut* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_CVOut_generate(void* signal); -void sig_daisy_CVOut_destroy(struct sig_Allocator* allocator, - struct sig_daisy_CVOut* self); - -struct sig_daisy_AudioParameters { - int channel; - float scale; -}; - -struct sig_daisy_AudioOut_Inputs { - float_array_ptr source; -}; - -struct sig_daisy_AudioOut { - struct sig_dsp_Signal signal; - struct sig_daisy_AudioParameters parameters; - struct sig_daisy_AudioOut_Inputs inputs; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; -}; - -struct sig_daisy_AudioOut* sig_daisy_AudioOut_new( - struct sig_Allocator* allocator, - struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_AudioOut_init(struct sig_daisy_AudioOut* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_AudioOut_generate(void* signal); -void sig_daisy_AudioOut_destroy(struct sig_Allocator* allocator, - struct sig_daisy_AudioOut* self); - - -struct sig_daisy_AudioIn { - struct sig_dsp_Signal signal; - struct sig_daisy_AudioParameters parameters; - struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_daisy_Host* host; -}; - -struct sig_daisy_AudioIn* sig_daisy_AudioIn_new( - struct sig_Allocator* allocator, - struct sig_SignalContext* context, - struct sig_daisy_Host* host); -void sig_daisy_AudioIn_init(struct sig_daisy_AudioIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host); -void sig_daisy_AudioIn_generate(void* signal); -void sig_daisy_AudioIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_AudioIn* self); - - -struct sig_daisy_Encoder { - -}; diff --git a/hosts/daisy/include/signaletic-daisy-host.hpp b/hosts/daisy/include/signaletic-daisy-host.hpp new file mode 100644 index 0000000..9913d77 --- /dev/null +++ b/hosts/daisy/include/signaletic-daisy-host.hpp @@ -0,0 +1,383 @@ +#pragma once + +#include +#include "daisy.h" +#include "signaletic-host.h" + +namespace sig { +namespace libdaisy { + +struct Normalization { + float scale; + float offset; +}; + +static constexpr struct Normalization NO_NORMALIZATION = { + .scale = 1.0f, + .offset = 0.0f +}; + +static constexpr struct Normalization UNI_TO_BIPOLAR = { + .scale = 2.0f, + .offset = -1.0f +}; + +static constexpr struct Normalization BI_TO_UNIPOLAR = { + .scale = 0.5f, + .offset = 0.5f +}; + +static constexpr struct Normalization INVERT = { + .scale = -1.0f, + .offset = 0.0f +}; + +static constexpr struct Normalization INV_UNI_TO_BIPOLAR = { + .scale = -2.0f, + .offset = 1.0f +}; + +static constexpr struct Normalization INV_BI_TO_UNIPOLAR = { + .scale = -0.5f, + .offset = 0.5f +}; + +struct ADCChannelSpec { + dsy_gpio_pin pin; + struct Normalization normalization; +}; + +template class InputBank { + public: + T* inputs; + float values[size] = {0.0}; + + inline void Read() { + for (size_t i = 0; i < size; i++) { + values[i] = inputs[i].Value(); + } + } +}; + +template class OutputBank { + public: + T* outputs; + float values[size]; + inline void Write() { + for (size_t i = 0; i < size; i++) { + outputs[i].Write(values[i]); + } + } +}; + +class BaseAnalogInput { + public: + uint16_t* adcPtr; + float scale = 1.0f; + float offset = 0.0f; + + void Init(daisy::AdcHandle* adc, ADCChannelSpec spec, int adcChannel) { + adcPtr = adc->GetPtr(adcChannel); + scale = spec.normalization.scale; + offset = spec.normalization.offset; + } +}; + +class AnalogInput : public BaseAnalogInput { + public: + /** + * @brief Returns the value of the ADC channel as a float + * scaled to a range of -1.0 to 1.0f. + * + * @return float the ADC channel's current value + */ + inline float Value() { + float converted = sig_uint16ToBipolar(*adcPtr); + float normalized = converted * scale + offset; + return normalized; + } +}; + +class UnipolarAnalogInput : public BaseAnalogInput { + public: + /** + * @brief Returns the inverted value of the ADC channel as + * a float scaled to the range of 0.0 to 1.0f. + * + * @return float the ADC channel's current value + */ + inline float Value() { + return sig_uint16ToUnipolar(*adcPtr); + } +}; + +class InvertedAnalogInput : public AnalogInput { + public: + /** + * @brief Returns the inverted value of the ADC channel as + * a float scaled to the range of -1.0 to 1.0f. + * + * @return float the ADC channel's current value + */ + inline float Value() { + float converted = sig_invUint16ToBipolar(*adcPtr); + return converted; + } +}; + +template class ADCController { + public: + daisy::AdcHandle* adcHandle; + T inputs[numChannels]; + InputBank channelBank; + + void Init(daisy::AdcHandle* inAdcHandle, ADCChannelSpec* channelSpecs) { + adcHandle = inAdcHandle; + + daisy::AdcChannelConfig adcConfigs[numChannels]; + for (size_t i = 0; i < numChannels; i++) { + adcConfigs[i].InitSingle(channelSpecs[i].pin); + } + + adcHandle->Init(adcConfigs, numChannels); + + for (size_t i = 0; i < numChannels; i++) { + inputs[i].Init(adcHandle, channelSpecs[i], i); + } + + channelBank.inputs = inputs; + } + + void Start() { + adcHandle->Start(); + } + + void Stop() { + adcHandle->Stop(); + } + + inline void Read() { + channelBank.Read(); + } +}; + +class BaseAnalogOutput { + public: + float scale = 1.0f; + float offset = 0.0f; +}; + +class BufferedAnalogOutput : public BaseAnalogOutput{ + public: + uint16_t* dacOutputs; + size_t channel; + + void Init(uint16_t* inDACOutputs, size_t inChannel, + struct Normalization normalization = NO_NORMALIZATION) { + dacOutputs = inDACOutputs; + channel = inChannel; + scale = normalization.scale; + offset = normalization.offset; + } + + inline void Write(float value) { + float normalizedValue = value * scale + offset; + dacOutputs[channel] = sig_unipolarToUint12(normalizedValue); + } +}; + +// TODO: Probably worth parameterizing the write strategy, +// though it will make for quite long templated definitions, +// e.g. OutputBank> dacOutputBank; +class BipolarInvertedBufferedAnalogOutput : public BufferedAnalogOutput{ + public: + inline void Write(float value) { + float normalizedValue = value * scale + offset; + dacOutputs[channel] = sig_bipolarToInvUint12(normalizedValue); + } +}; + +class PollingAnalogOutput : public BaseAnalogOutput { + public: + daisy::DacHandle* dac; + size_t channel; + + void Init(daisy::DacHandle* inDAC, size_t inChannel, + struct Normalization normalization = NO_NORMALIZATION) { + dac = inDAC; + channel = inChannel; + scale = normalization.scale; + offset = normalization.offset; + } + + inline void Write(float value) { + float normalizedValue = value * scale + offset; + uint16_t convertedValue = sig_unipolarToUint12(normalizedValue); + dac->WriteValue(static_cast(channel), + convertedValue); + } +}; + +class GPIO { + public: + dsy_gpio gpio; + + void Init(dsy_gpio_pin pin, dsy_gpio_mode mode, dsy_gpio_pull pull) { + gpio.pin = pin; + gpio.mode = mode; + gpio.pull = pull; + + dsy_gpio_init(&gpio); + } +}; + +class GPIOInput : public GPIO { + public: + inline float Value() { + return (float) dsy_gpio_read(&gpio); + } +}; + +class GPIOOutput : public GPIO { + public: + inline void Write(float value) { + dsy_gpio_write(&gpio, (uint8_t) value); + } +}; + + +class GateInput { + public: + GPIOInput gpioSource; + + void Init(dsy_gpio_pin pin) { + gpioSource.Init(pin, DSY_GPIO_MODE_INPUT, DSY_GPIO_PULLUP); + } + + inline float Value() { + return !gpioSource.Value(); + } +}; + +class Toggle { + public: + uint32_t previousDebounce; + uint8_t state; + dsy_gpio gpio; + + void Init(dsy_gpio_pin pin) { + previousDebounce = daisy::System::GetNow(); + state = 0x00; + gpio.pin = pin; + gpio.mode = DSY_GPIO_MODE_INPUT; + gpio.pull = DSY_GPIO_PULLUP; + dsy_gpio_init(&gpio); + } + + inline float Value() { + // See https://www.ganssle.com/debouncing-pt2.htm + // for a good overview of debouncing algorithms. + uint32_t now = daisy::System::GetNow(); + if (now - previousDebounce >= 1) { + state = (state << 1) | !dsy_gpio_read(&gpio); + previousDebounce = now; + } + return (float) state == 0xff; + } +}; + +class TriSwitch { + public: + Toggle switchA; + Toggle switchB; + + void Init(dsy_gpio_pin pins[2]) { + switchA.Init(pins[0]); + switchB.Init(pins[1]); + } + + void Init(dsy_gpio_pin pinA, dsy_gpio_pin pinB) { + switchA.Init(pinA); + switchB.Init(pinB); + } + + inline float Value() { + return switchA.Value() + -switchB.Value(); + } +}; + +class Encoder { + public: + uint32_t previousDebounce; + uint8_t a; + uint8_t b; + dsy_gpio gpioA; + dsy_gpio gpioB; + + void Init(dsy_gpio_pin pinA, dsy_gpio_pin pinB) { + gpioA.pin = pinA; + gpioA.mode = DSY_GPIO_MODE_INPUT; + gpioA.pull = DSY_GPIO_PULLUP; + + gpioB.pin = pinB; + gpioB.mode = DSY_GPIO_MODE_INPUT; + gpioB.pull = DSY_GPIO_PULLUP; + + dsy_gpio_init(&gpioA); + dsy_gpio_init(&gpioB); + + previousDebounce = daisy::System::GetNow(); + a = 255; + b = 255; + } + + inline float Value() { + uint32_t now = daisy::System::GetNow(); + float increment = 0.0f; + + if (now - previousDebounce >= 1) { + a = (a << 1) | dsy_gpio_read(&gpioA); + b = (b << 1) | dsy_gpio_read(&gpioB); + uint8_t aMasked = a & 3; + uint8_t bMasked = b & 3; + + if (aMasked == 2 && bMasked == 0) { + increment = 1.0f; + } else if (bMasked == 2 && aMasked == 0) { + increment = -1.0f; + } + + previousDebounce = now; + } + + return increment; + } +}; + + +void DaisyHostAudioCallback(daisy::AudioHandle::InputBuffer in, + daisy::AudioHandle::OutputBuffer out, size_t size); + +template class DaisyHost { + public: + T device; + struct sig_AudioSettings* audioSettings; + + void Init(struct sig_AudioSettings* inAudioSettings, + struct sig_dsp_SignalEvaluator* evaluator) { + audioSettings = inAudioSettings; + device.Init(audioSettings, evaluator); + sig_host_registerGlobalHardwareInterface(&device.hardware); + } + + void Start() { + device.Start(DaisyHostAudioCallback); + } + + void Stop() { + device.Stop(); + device.board.audio.Stop(); + } +}; +}; +}; diff --git a/hosts/daisy/include/signaletic-host.h b/hosts/daisy/include/signaletic-host.h new file mode 100644 index 0000000..da97e21 --- /dev/null +++ b/hosts/daisy/include/signaletic-host.h @@ -0,0 +1,345 @@ +#ifndef SIGNALETIC_HOST_H +#define SIGNALETIC_HOST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include // For size_t + +struct sig_host_HardwareInterface; + +typedef void (*sig_host_onEvaluateSignals)( + size_t size, + struct sig_host_HardwareInterface* hardware); + +typedef void (*sig_host_afterEvaluateSignals)( + size_t size, + struct sig_host_HardwareInterface* hardware); + +void sig_host_noOpAudioEventCallback(size_t size, + struct sig_host_HardwareInterface* hardware); + + +struct sig_host_HardwareInterface { + struct sig_dsp_SignalEvaluator* evaluator; + + sig_host_onEvaluateSignals onEvaluateSignals; + sig_host_afterEvaluateSignals afterEvaluateSignals; + + void* userData; + + size_t numAudioInputChannels; + float** audioInputChannels; + + size_t numAudioOutputChannels; + float** audioOutputChannels; + + // TODO: Should these be sig_Buffers instead? + size_t numADCChannels; + float* adcChannels; + + size_t numDACChannels; + float* dacChannels; + + size_t numGateInputs; + float* gateInputs; + + size_t numGPIOOutputs; + float* gpioOutputs; + + size_t numToggles; + float* toggles; + + size_t numTriSwitches; + float* triSwitches; + + size_t numEncoders; + float* encoders; +}; + +void sig_host_registerGlobalHardwareInterface( + struct sig_host_HardwareInterface* hardware); + +struct sig_host_HardwareInterface* sig_host_getGlobalHardwareInterface(); + +struct sig_host_CV_Parameters { + float scale; + float offset; + int control; +}; + + +struct sig_host_GateIn { + struct sig_dsp_Signal signal; + struct sig_host_CV_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; +}; + +struct sig_host_GateIn* sig_host_GateIn_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context); +void sig_host_GateIn_init(struct sig_host_GateIn* self, + struct sig_SignalContext* context); +void sig_host_GateIn_generate(void* signal); +void sig_host_GateIn_destroy(struct sig_Allocator* allocator, + struct sig_host_GateIn* self); + +struct sig_host_GateOut_Inputs { + float_array_ptr source; +}; + + +struct sig_host_GateOut { + struct sig_dsp_Signal signal; + struct sig_host_CV_Parameters parameters; + struct sig_host_GateOut_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; +}; + +struct sig_host_GateOut* sig_host_GateOut_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context); +void sig_host_GateOut_init(struct sig_host_GateOut* self, + struct sig_SignalContext* context); +void sig_host_GateOut_generate(void* signal); +void sig_host_GateOut_destroy(struct sig_Allocator* allocator, + struct sig_host_GateOut* self); + + +struct sig_host_CVIn { + struct sig_dsp_Signal signal; + struct sig_host_CV_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; +}; + +struct sig_host_CVIn* sig_host_CVIn_new(struct sig_Allocator* allocator, + struct sig_SignalContext* context); +void sig_host_CVIn_init(struct sig_host_CVIn* self, + struct sig_SignalContext* context); +void sig_host_CVIn_generate(void* signal); +void sig_host_CVIn_destroy(struct sig_Allocator* allocator, + struct sig_host_CVIn* self); + + +struct sig_host_FilteredCVIn_Parameters { + float scale; + float offset; + int control; + float time; +}; + +struct sig_host_FilteredCVIn { + struct sig_dsp_Signal signal; + struct sig_host_FilteredCVIn_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; + struct sig_host_CVIn* cvIn; + struct sig_dsp_Smooth* filter; +}; + +struct sig_host_FilteredCVIn* sig_host_FilteredCVIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_host_FilteredCVIn_init(struct sig_host_FilteredCVIn* self, + struct sig_SignalContext* context); +void sig_host_FilteredCVIn_generate(void* signal); +void sig_host_FilteredCVIn_destroy(struct sig_Allocator* allocator, + struct sig_host_FilteredCVIn* self); + + +struct sig_host_VOctCVIn_Parameters { + float scale; + float offset; + int control; + float middleFreq; +}; + +struct sig_host_VOctCVIn { + struct sig_dsp_Signal signal; + struct sig_host_VOctCVIn_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; + struct sig_host_CVIn* cvIn; + struct sig_dsp_LinearToFreq* cvConverter; +}; + +struct sig_host_VOctCVIn* sig_host_VOctCVIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_host_VOctCVIn_init(struct sig_host_VOctCVIn* self, + struct sig_SignalContext* context); +void sig_host_VOctCVIn_generate(void* signal); +void sig_host_VOctCVIn_destroy(struct sig_Allocator* allocator, + struct sig_host_VOctCVIn* self); + + +struct sig_host_CVOut_Inputs { + float_array_ptr source; +}; + +// TODO: Should we have a "no output" outputs type, +// or continue with the idea of passing through the input +// for chaining? +struct sig_host_CVOut { + struct sig_dsp_Signal signal; + struct sig_host_CV_Parameters parameters; + struct sig_host_CVOut_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; +}; + +struct sig_host_CVOut* sig_host_CVOut_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context); +void sig_host_CVOut_init(struct sig_host_CVOut* self, + struct sig_SignalContext* context); +void sig_host_CVOut_generate(void* signal); +void sig_host_CVOut_destroy(struct sig_Allocator* allocator, + struct sig_host_CVOut* self); + + +struct sig_host_AudioParameters { + int channel; + float scale; +}; + +struct sig_host_AudioOut_Inputs { + float_array_ptr source; +}; + +struct sig_host_AudioOut { + struct sig_dsp_Signal signal; + struct sig_host_AudioParameters parameters; + struct sig_host_AudioOut_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; +}; + +struct sig_host_AudioOut* sig_host_AudioOut_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context); +void sig_host_AudioOut_init(struct sig_host_AudioOut* self, + struct sig_SignalContext* context); +void sig_host_AudioOut_generate(void* signal); +void sig_host_AudioOut_destroy(struct sig_Allocator* allocator, + struct sig_host_AudioOut* self); + + +struct sig_host_AudioIn { + struct sig_dsp_Signal signal; + struct sig_host_AudioParameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; +}; + +struct sig_host_AudioIn* sig_host_AudioIn_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context); +void sig_host_AudioIn_init(struct sig_host_AudioIn* self, + struct sig_SignalContext* context); +void sig_host_AudioIn_generate(void* signal); +void sig_host_AudioIn_destroy(struct sig_Allocator* allocator, + struct sig_host_AudioIn* self); + + + +// TODO: Replace SwitchIn and TriSwitchIn with a more generic +// implementation that operates on a configurable list of GPIO pins +// (since a three way switch just consists of two separate pins). + +struct sig_host_SwitchIn { + struct sig_dsp_Signal signal; + struct sig_host_CV_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; +}; + +struct sig_host_SwitchIn* sig_host_SwitchIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_host_SwitchIn_init(struct sig_host_SwitchIn* self, + struct sig_SignalContext* context); +void sig_host_SwitchIn_generate(void* signal); +void sig_host_SwitchIn_destroy(struct sig_Allocator* allocator, + struct sig_host_SwitchIn* self); + +struct sig_host_TriSwitchIn { + struct sig_dsp_Signal signal; + struct sig_host_CV_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_host_HardwareInterface* hardware; +}; + +struct sig_host_TriSwitchIn* sig_host_TriSwitchIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_host_TriSwitchIn_init(struct sig_host_TriSwitchIn* self, + struct sig_SignalContext* context); +void sig_host_TriSwitchIn_generate(void* signal); +void sig_host_TriSwitchIn_destroy(struct sig_Allocator* allocator, + struct sig_host_TriSwitchIn* self); + + +struct sig_host_EncoderIn_Parameters { + float scale; + float offset; + int turnControl; + int buttonControl; +}; + +struct sig_host_EncoderIn_Outputs { + /** + * @brief The encoder's accumulated value. + * This output tracks the sum of all increment values over time. + */ + float_array_ptr main; + + /** + * @brief The encoder's increment value. + * This output represents the state of change of the encoder: + * -1.0 if the encoder was turned counterclockwise, + * +1.0 if turned clockwise, + * 0.0 if the encoder was not turned + */ + float_array_ptr increment; // The encoder's increment value + + /** + * @brief A gate signal for the encoder's button. + * This output will be > 1.0 if the button is currently pressed, + * and 0.0 it is not. + */ + float_array_ptr button; +}; + +void sig_host_EncoderIn_Outputs_newAudioBlocks(struct sig_Allocator* allocator, + struct sig_AudioSettings* audioSettings, + struct sig_host_EncoderIn_Outputs* outputs); + +void sig_host_EncoderIn_Outputs_destroyAudioBlocks( + struct sig_Allocator* allocator, + struct sig_host_EncoderIn_Outputs* outputs); + +struct sig_host_EncoderIn { + struct sig_dsp_Signal signal; + struct sig_host_EncoderIn_Parameters parameters; + struct sig_host_EncoderIn_Outputs outputs; + struct sig_host_HardwareInterface* hardware; + + float accumulator; +}; + +struct sig_host_EncoderIn* sig_host_EncoderIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_host_EncoderIn_init(struct sig_host_EncoderIn* self, + struct sig_SignalContext* context); +void sig_host_EncoderIn_generate(void* signal); +void sig_host_EncoderIn_destroy(struct sig_Allocator* allocator, + struct sig_host_EncoderIn* self); + + +#ifdef __cplusplus +} +#endif + +#endif /* SIGNALETIC_HOST_H */ diff --git a/hosts/daisy/src/daisy-bluemchen-host.cpp b/hosts/daisy/src/daisy-bluemchen-host.cpp deleted file mode 100644 index 548ee9b..0000000 --- a/hosts/daisy/src/daisy-bluemchen-host.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "../include/daisy-bluemchen-host.h" - -struct sig_daisy_Host_BoardConfiguration sig_daisy_BluemchenConfig = { - .numAudioInputChannels = 2, - .numAudioOutputChannels = 2, - .numAnalogInputs = sig_daisy_Bluemchen_NUM_ANALOG_INPUTS, - .numAnalogOutputs = sig_daisy_Bluemchen_NUM_ANALOG_OUTPUTS, - .numGateInputs = sig_daisy_Bluemchen_NUM_GATE_INPUTS, - .numGateOutputs = sig_daisy_Bluemchen_NUM_GATE_OUTPUTS, - .numSwitches = sig_daisy_Bluemchen_NUM_SWITCHES, - .numTriSwitches = sig_daisy_Bluemchen_NUM_TRI_SWITCHES, - .numEncoders = sig_daisy_Bluemchen_NUM_ENCODERS -}; - -struct sig_daisy_Host_BoardConfiguration sig_daisy_NehcmeulbConfig = { - .numAudioInputChannels = 2, - .numAudioOutputChannels = 2, - .numAnalogInputs = sig_daisy_Nehcmeulb_NUM_ANALOG_INPUTS, - .numAnalogOutputs = sig_daisy_Nehcmeulb_NUM_ANALOG_OUTPUTS, - .numGateInputs = sig_daisy_Nehcmeulb_NUM_GATE_INPUTS, - .numGateOutputs = sig_daisy_Nehcmeulb_NUM_GATE_OUTPUTS, - .numSwitches = sig_daisy_Nehcmeulb_NUM_SWITCHES, - .numTriSwitches = sig_daisy_Nehcmeulb_NUM_TRI_SWITCHES, - .numEncoders = sig_daisy_Nehcmeulb_NUM_ENCODERS -}; - -struct sig_daisy_Host_Impl sig_daisy_BluemchenHostImpl = { - .getControlValue = sig_daisy_HostImpl_processControlValue, - .setControlValue = sig_daisy_BluemchenHostImpl_setControlValue, - .getGateValue = sig_daisy_HostImpl_noOpGetControl, - .setGateValue = sig_daisy_HostImpl_noOpSetControl, - .getSwitchValue = sig_daisy_HostImpl_noOpGetControl, - .getTriSwitchValue = sig_daisy_HostImpl_noOpGetControl, - .getEncoderIncrement = sig_daisy_HostImpl_getEncoderIncrement, - .getEncoderButtonValue = sig_daisy_HostImpl_processEncoderButtonValue, - .start = sig_daisy_BluemchenHostImpl_start, - .stop = sig_daisy_BluemchenHostImpl_stop -}; - -void sig_daisy_BluemchenHostImpl_setControlValue(struct sig_daisy_Host* host, - int control, float value) { - if (control > -1 && control < host->board.config->numAnalogOutputs) { - daisy::DacHandle::Channel channel = - static_cast(control); - host->board.dac->WriteValue(channel, sig_unipolarToUint12(value)); - } -} - -void sig_daisy_BluemchenHostImpl_start(struct sig_daisy_Host* host) { - kxmx::Bluemchen* bluemchen = static_cast( - host->board.boardInstance); - bluemchen->StartAdc(); - bluemchen->StartAudio(sig_daisy_Host_audioCallback); -} - -void sig_daisy_BluemchenHostImpl_stop(struct sig_daisy_Host* host) { - kxmx::Bluemchen* bluemchen = static_cast( - host->board.boardInstance); - bluemchen->StopAudio(); -} - -struct sig_daisy_Host* sig_daisy_BluemchenHost_new( - struct sig_Allocator* allocator, - struct sig_AudioSettings* audioSettings, - kxmx::Bluemchen* bluemchen, - struct sig_dsp_SignalEvaluator* evaluator) { - struct sig_daisy_Host* self = sig_MALLOC(allocator, struct sig_daisy_Host); - sig_daisy_BluemchenHost_init(self, - audioSettings, - &sig_daisy_BluemchenConfig, - bluemchen, - evaluator); - - return self; -} - -struct sig_daisy_Host* sig_daisy_NehcmeulbHost_new( - struct sig_Allocator* allocator, - struct sig_AudioSettings* audioSettings, - kxmx::Bluemchen* bluemchen, - struct sig_dsp_SignalEvaluator* evaluator) { - struct sig_daisy_Host* self = sig_MALLOC(allocator, struct sig_daisy_Host); - sig_daisy_BluemchenHost_init(self, - audioSettings, - &sig_daisy_NehcmeulbConfig, - bluemchen, - evaluator); - - return self; -} - -void sig_daisy_BluemchenHost_Board_init( - struct sig_daisy_Host_Board* self, - kxmx::Bluemchen* bluemchen, - struct sig_daisy_Host_BoardConfiguration* boardConfig) { - self->boardInstance = (void*) bluemchen; - self->config = boardConfig; - self->analogControls = &bluemchen->controls[0]; - self->dac = &bluemchen->seed.dac; - // Bluemchen has no gates. - self->gateInputs[0] = NULL; - self->gateInputs[1] = NULL; - self->gateOutputs[0] = NULL; - self->gateOutputs[1] = NULL; - self->encoders[0] = &bluemchen->encoder; -} - - -void sig_daisy_BluemchenHost_init( - struct sig_daisy_Host* self, - struct sig_AudioSettings* audioSettings, - struct sig_daisy_Host_BoardConfiguration* boardConfig, - kxmx::Bluemchen* bluemchen, - struct sig_dsp_SignalEvaluator* evaluator) { - self->impl = &sig_daisy_BluemchenHostImpl; - self->audioSettings = audioSettings; - self->evaluator = evaluator; - - bluemchen->Init(); - daisy::SaiHandle::Config::SampleRate sampleRate = - sig_daisy_Host_convertSampleRate(audioSettings->sampleRate); - bluemchen->SetAudioSampleRate(sampleRate); - bluemchen->SetAudioBlockSize(audioSettings->blockSize); - sig_daisy_BluemchenHost_Board_init(&self->board, bluemchen, boardConfig); - sig_daisy_Host_init(self); -} - -void sig_daisy_BluemchenHost_destroy(struct sig_Allocator* allocator, - struct sig_daisy_Host* self) { - allocator->impl->free(allocator, self); -} - -void sig_daisy_NehcmeulbHost_destroy(struct sig_Allocator* allocator, - struct sig_daisy_Host* self) { - sig_daisy_BluemchenHost_destroy(allocator, self); -} diff --git a/hosts/daisy/src/daisy-dpt-host.cpp b/hosts/daisy/src/daisy-dpt-host.cpp deleted file mode 100644 index c4fcfb3..0000000 --- a/hosts/daisy/src/daisy-dpt-host.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "../include/daisy-dpt-host.h" - -void sig_daisy_DPT_dacWriterCallback(void* dptHost) { - struct sig_daisy_DPTHost* self = static_cast( - dptHost); - daisy::Dac7554* expansionDAC = self->expansionDAC; - expansionDAC->Write(self->expansionDACBuffer); - expansionDAC->WriteDac7554(); -} - -struct sig_daisy_Host_BoardConfiguration sig_daisy_DPTConfig = { - .numAudioInputChannels = 2, - .numAudioOutputChannels = 2, - .numAnalogInputs = sig_daisy_DPT_NUM_ANALOG_INPUTS, - .numAnalogOutputs = sig_daisy_DPT_NUM_ANALOG_OUTPUTS, - .numGateInputs = sig_daisy_DPT_NUM_GATE_INPUTS, - .numGateOutputs = sig_daisy_DPT_NUM_GATE_OUTPUTS, - .numSwitches = sig_daisy_DPT_NUM_SWITCHES, - .numTriSwitches = sig_daisy_DPT_NUM_TRI_SWITCHES, - .numEncoders = sig_daisy_DPT_NUM_ENCODERS -}; - -struct sig_daisy_Host_Impl sig_daisy_DPTHostImpl = { - .getControlValue = sig_daisy_HostImpl_processControlValue, - .setControlValue = sig_daisy_DPTHostImpl_setControlValue, - .getGateValue = sig_daisy_HostImpl_getGateValue, - .setGateValue = sig_daisy_HostImpl_setGateValue, - .getSwitchValue = sig_daisy_HostImpl_noOpGetControl, - .getTriSwitchValue = sig_daisy_HostImpl_noOpGetControl, - .getEncoderIncrement = sig_daisy_HostImpl_noOpGetControl, - .getEncoderButtonValue = sig_daisy_HostImpl_noOpGetControl, - .start = sig_daisy_DPTHostImpl_start, - .stop = sig_daisy_DPTHostImpl_stop, -}; - -void sig_daisy_DPTHostImpl_start(struct sig_daisy_Host* host) { - daisy::dpt::DPT* dpt = - static_cast(host->board.boardInstance); - dpt->StartAdc(); - dpt->StartAudio(sig_daisy_Host_audioCallback); -} - -void sig_daisy_DPTHostImpl_stop(struct sig_daisy_Host* host) { - daisy::dpt::DPT* dpt = - static_cast(host->board.boardInstance); - dpt->StopAudio(); -} - -void sig_daisy_DPTHostImpl_setControlValue(struct sig_daisy_Host* host, - int control, float value) { - struct sig_daisy_DPTHost* self = (struct sig_daisy_DPTHost*) host; - - if (control < 0 || control >= sig_daisy_DPT_NUM_ANALOG_OUTPUTS) { - return; - } - - uint16_t convertedValue = sig_bipolarToInvUint12(value); - - // Controls are indexed 0-5, - // but 2-5 need to be placed into a buffer that will be - // written in the DAC timer interrupt. - // And libdaisy indexes the two CV outs on the Seed/Patch SM - // as 1 and 2, because 0 is reserved for writing to both outputs. - if (control > 1) { - self->expansionDACBuffer[control - 2] = convertedValue; - } else { - daisy::dpt::DPT* dpt = static_cast( - self->host.board.boardInstance); - dpt->WriteCvOut(control + 1, convertedValue, true); - } -} - -struct sig_daisy_Host* sig_daisy_DPTHost_new(struct sig_Allocator* allocator, - sig_AudioSettings* audioSettings, - daisy::dpt::DPT* dpt, - struct sig_dsp_SignalEvaluator* evaluator) { - struct sig_daisy_DPTHost* self = sig_MALLOC(allocator, - struct sig_daisy_DPTHost); - sig_daisy_DPTHost_init(self, audioSettings, dpt, evaluator); - - return &self->host; -} - -void sig_daisy_DPTHost_Board_init(struct sig_daisy_Host_Board* self, - daisy::dpt::DPT* dpt) { - self->boardInstance = (void*) dpt; - self->config = &sig_daisy_DPTConfig; - self->analogControls = &(dpt->controls[0]); - self->gateInputs[0] = &dpt->gate_in_1; - self->gateInputs[1] = &dpt->gate_in_2; - self->gateOutputs[0] = &dpt->gate_out_1; - self->gateOutputs[1] = &dpt->gate_out_2; - self->dac = &dpt->dac; -} - -void sig_daisy_DPTHost_init(struct sig_daisy_DPTHost* self, - sig_AudioSettings* audioSettings, - daisy::dpt::DPT* dpt, - struct sig_dsp_SignalEvaluator* evaluator) { - self->host.impl = &sig_daisy_DPTHostImpl; - self->host.audioSettings = audioSettings; - self->host.evaluator = evaluator; - self->expansionDAC = &dpt->dac_exp; - self->expansionDACBuffer[0] = 4095; - self->expansionDACBuffer[1] = 4095; - self->expansionDACBuffer[2] = 4095; - self->expansionDACBuffer[3] = 4095; - - dpt->Init(); - dpt->SetAudioBlockSize(audioSettings->blockSize); - dpt->SetAudioSampleRate(audioSettings->sampleRate); - sig_daisy_DPTHost_Board_init(&self->host.board, dpt); - sig_daisy_Host_init(&self->host); - dpt->InitTimer(&sig_daisy_DPT_dacWriterCallback, self); -} - -void sig_daisy_DPTHost_destroy(struct sig_Allocator* allocator, - struct sig_daisy_DPTHost* self) { - allocator->impl->free(allocator, self); -} diff --git a/hosts/daisy/src/daisy-patch-sm-host.cpp b/hosts/daisy/src/daisy-patch-sm-host.cpp deleted file mode 100644 index 1ceb12d..0000000 --- a/hosts/daisy/src/daisy-patch-sm-host.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "../include/daisy-patch-sm-host.h" - -struct sig_daisy_Host_BoardConfiguration sig_daisy_PatchSMConfig = { - .numAudioInputChannels = 2, - .numAudioOutputChannels = 2, - .numAnalogInputs = sig_daisy_PatchSM_NUM_ANALOG_INPUTS, - .numAnalogOutputs = sig_daisy_PatchSM_NUM_ANALOG_OUTPUTS, - .numGateInputs = sig_daisy_PatchSM_NUM_GATE_INPUTS, - .numGateOutputs = sig_daisy_PatchSM_NUM_GATE_OUTPUTS, - .numSwitches = sig_daisy_PatchSM_NUM_SWITCHES, - .numTriSwitches = sig_daisy_PatchSM_NUM_TRI_SWITCHES, - .numEncoders = sig_daisy_PatchSM_NUM_ENCODERS -}; - -struct sig_daisy_Host_BoardConfiguration sig_daisy_PatchInitConfig = { - .numAudioInputChannels = 2, - .numAudioOutputChannels = 2, - .numAnalogInputs = sig_daisy_PatchInit_NUM_ANALOG_INPUTS, - .numAnalogOutputs = sig_daisy_PatchInit_NUM_ANALOG_OUTPUTS, - .numGateInputs = sig_daisy_PatchInit_NUM_GATE_INPUTS, - .numGateOutputs = sig_daisy_PatchInit_NUM_GATE_OUTPUTS, - .numSwitches = sig_daisy_PatchInit_NUM_SWITCHES, - .numTriSwitches = sig_daisy_PatchInit_NUM_TRI_SWITCHES, - .numEncoders = sig_daisy_PatchInit_NUM_ENCODERS -}; - -struct sig_daisy_Host_Impl sig_daisy_PatchSMHostImpl = { - .getControlValue = sig_daisy_HostImpl_processControlValue, - .setControlValue = sig_daisy_PatchSMHostImpl_setControlValue, - .getGateValue = sig_daisy_HostImpl_getGateValue, - .setGateValue = sig_daisy_HostImpl_setGateValue, - .getSwitchValue = sig_daisy_HostImpl_getSwitchValue, - .getTriSwitchValue = sig_daisy_HostImpl_noOpGetControl, - .getEncoderIncrement = sig_daisy_HostImpl_noOpGetControl, - .getEncoderButtonValue = sig_daisy_HostImpl_noOpGetControl, - .start = sig_daisy_PatchSMHostImpl_start, - .stop = sig_daisy_PatchSMHostImpl_stop -}; - -void sig_daisy_PatchSMHostImpl_start(struct sig_daisy_Host* host) { - daisy::patch_sm::DaisyPatchSM* patchSM = - static_cast(host->board.boardInstance); - patchSM->StartAdc(); - patchSM->StartAudio(sig_daisy_Host_audioCallback); -} - -void sig_daisy_PatchSMHostImpl_stop(struct sig_daisy_Host* host) { - daisy::patch_sm::DaisyPatchSM* patchSM = - static_cast(host->board.boardInstance); - patchSM->StopAudio(); -} - -// TODO: Implement a generic version of this that can work -// with any Daisy board that has DMA enabled. -void sig_daisy_PatchSMHostImpl_setControlValue(struct sig_daisy_Host* host, - int control, float value) { - daisy::patch_sm::DaisyPatchSM* patchSM = - static_cast(host->board.boardInstance); - - float rectified = fabsf(value); - float scaled = rectified * 5.0f; - - // Controls are off by one because 0 signifies both in the Daisy API. - // We don't expose this to Signaletic users, because it is awkward. - patchSM->WriteCvOut(control, scaled); -} - -struct sig_daisy_Host* sig_daisy_PatchSMHost_new( - struct sig_Allocator* allocator, - struct sig_AudioSettings* audioSettings, - daisy::patch_sm::DaisyPatchSM* patchSM, - struct sig_dsp_SignalEvaluator* evaluator) { - struct sig_daisy_Host* self = sig_MALLOC(allocator, - struct sig_daisy_Host); - sig_daisy_PatchSMHost_init(self, audioSettings, patchSM, evaluator); - - return self; -} - -void sig_daisy_PatchSMHost_Board_init(struct sig_daisy_Host_Board* self, - daisy::patch_sm::DaisyPatchSM* patchSM) { - self->config = &sig_daisy_PatchSMConfig; - self->analogControls = &patchSM->controls[0]; - self->dac = &patchSM->dac; - self->gateInputs[0] = &patchSM->gate_in_1; - self->gateInputs[1] = &patchSM->gate_in_2; - self->gateOutputs[0] = &patchSM->gate_out_1; - self->gateOutputs[1] = &patchSM->gate_out_2; - self->switches[0].Init(patchSM->B7, patchSM->AudioCallbackRate()); - self->switches[1].Init(patchSM->B8, patchSM->AudioCallbackRate()); - self->boardInstance = (void*) patchSM; -} - -void sig_daisy_PatchSMHost_init(struct sig_daisy_Host* self, - struct sig_AudioSettings* audioSettings, - daisy::patch_sm::DaisyPatchSM* patchSM, - struct sig_dsp_SignalEvaluator* evaluator) { - self->impl = &sig_daisy_PatchSMHostImpl; - self->audioSettings = audioSettings; - self->evaluator = evaluator; - - patchSM->Init(); - patchSM->SetAudioBlockSize(audioSettings->blockSize); - patchSM->SetAudioSampleRate(audioSettings->sampleRate); - sig_daisy_PatchSMHost_Board_init(&self->board, patchSM); - sig_daisy_Host_init(self); -} - -void sig_daisy_PatchSMHost_destroy(struct sig_Allocator* allocator, - struct sig_daisy_Host* self) { - allocator->impl->free(allocator, self); -} diff --git a/hosts/daisy/src/daisy-versio-host.cpp b/hosts/daisy/src/daisy-versio-host.cpp deleted file mode 100644 index b40fd0c..0000000 --- a/hosts/daisy/src/daisy-versio-host.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "../include/daisy-versio-host.h" - -struct sig_daisy_Host_BoardConfiguration sig_daisy_VersioConfig = { - .numAudioInputChannels = 2, - .numAudioOutputChannels = 2, - .numAnalogInputs = sig_daisy_Versio_NUM_ANALOG_INPUTS, - .numAnalogOutputs = sig_daisy_Versio_NUM_ANALOG_OUTPUTS, - .numGateInputs = sig_daisy_Versio_NUM_GATE_INPUTS, - .numGateOutputs = sig_daisy_Versio_NUM_GATE_OUTPUTS, - .numSwitches = sig_daisy_Versio_NUM_SWITCHES, - .numTriSwitches = sig_daisy_Versio_NUM_TRI_SWITCHES, - .numEncoders = sig_daisy_Versio_NUM_ENCODERS -}; - -struct sig_daisy_Host_Impl sig_daisy_VersioHostImpl = { - .getControlValue = sig_daisy_HostImpl_processControlValue, - .setControlValue = sig_daisy_HostImpl_noOpSetControl, - .getGateValue = sig_daisy_HostImpl_noOpGetControl, - .setGateValue = sig_daisy_HostImpl_noOpSetControl, - .getSwitchValue = sig_daisy_HostImpl_getSwitchValue, - .getTriSwitchValue = sig_daisy_HostImpl_getTriSwitchValue, - .getEncoderIncrement = sig_daisy_HostImpl_noOpGetControl, - .getEncoderButtonValue = sig_daisy_HostImpl_noOpGetControl, - .start = sig_daisy_VersioHostImpl_start, - .stop = sig_daisy_VersioHostImpl_stop -}; - -void sig_daisy_VersioHostImpl_start(struct sig_daisy_Host* host) { - daisy::DaisyVersio* versio = static_cast( - host->board.boardInstance); - versio->StartAdc(); - versio->StartAudio(sig_daisy_Host_audioCallback); -} - -void sig_daisy_VersioHostImpl_stop(struct sig_daisy_Host* host) { - daisy::DaisyVersio* versio = static_cast( - host->board.boardInstance); - versio->StopAudio(); -} - -struct sig_daisy_Host* sig_daisy_VersioHost_new( - struct sig_Allocator* allocator, - struct sig_AudioSettings* audioSettings, - daisy::DaisyVersio* versio, - struct sig_dsp_SignalEvaluator* evaluator) { - struct sig_daisy_Host* self = sig_MALLOC(allocator, - struct sig_daisy_Host); - sig_daisy_VersioHost_init(self, audioSettings, versio, evaluator); - - return self; -} - -void sig_daisy_VersioHost_Board_init(struct sig_daisy_Host_Board* self, - daisy::DaisyVersio* versio) { - self->config = &sig_daisy_VersioConfig; - self->analogControls = &versio->knobs[0]; - self->dac = &versio->seed.dac; - self->gateInputs[0] = &versio->gate; - self->gateInputs[1] = NULL; - self->gateOutputs[0] = NULL; - self->gateOutputs[1] = NULL; - self->switches[0] = versio->tap; - self->triSwitches[0] = versio->sw[0]; - self->boardInstance = (void*) versio; -} - -void sig_daisy_VersioHost_init(struct sig_daisy_Host* self, - struct sig_AudioSettings* audioSettings, - daisy::DaisyVersio* versio, - struct sig_dsp_SignalEvaluator* evaluator) { - self->impl = &sig_daisy_VersioHostImpl; - self->audioSettings = audioSettings; - self->evaluator = evaluator; - - versio->Init(); - versio->SetAudioBlockSize(audioSettings->blockSize); - daisy::SaiHandle::Config::SampleRate daisySR = - sig_daisy_Host_convertSampleRate(audioSettings->sampleRate); - versio->SetAudioSampleRate(daisySR); - sig_daisy_VersioHost_Board_init(&self->board, versio); - sig_daisy_Host_init(self); -} - -void sig_daisy_VersioHost_destroy(struct sig_Allocator* allocator, - struct sig_daisy_Host* self) { - allocator->impl->free(allocator, self); -} diff --git a/hosts/daisy/src/sig-daisy-patch-sm.cpp b/hosts/daisy/src/sig-daisy-patch-sm.cpp new file mode 100644 index 0000000..5ea9a2d --- /dev/null +++ b/hosts/daisy/src/sig-daisy-patch-sm.cpp @@ -0,0 +1,129 @@ +#include "../include/sig-daisy-patch-sm.hpp" + +using namespace daisy; + +/** outside of class static buffer(s) for DMA and DAC access */ +uint16_t DMA_BUFFER_MEM_SECTION sig_daisy_patch_sm_dac_buffer[2][48]; +// TODO: I think this should be a block-sized ring buffer, +// so that we're maximizing throughput between the audio callback +// and the DMA callback. +static uint16_t sig_daisy_patch_sm_dac_outputValues[2]; + +void sig::libdaisy::patchsm::PatchSMBoard::Init(size_t blockSize, float sampleRate) { + dac_running_ = false; + dac_buffer_size_ = 48; + sig_daisy_patch_sm_dac_outputValues[0] = 0; + sig_daisy_patch_sm_dac_outputValues[1] = 0; + dacOutputValues = sig_daisy_patch_sm_dac_outputValues; + dacBuffer[0] = sig_daisy_patch_sm_dac_buffer[0]; + dacBuffer[1] = sig_daisy_patch_sm_dac_buffer[1]; + + System::Config syscfg; + syscfg.Boost(); + + auto memory = System::GetProgramMemoryRegion(); + if (memory != System::MemoryRegion::INTERNAL_FLASH) { + syscfg.skip_clocks = true; + } + + system.Init(syscfg); + /** Memories */ + if (memory == System::MemoryRegion::INTERNAL_FLASH) { + /** FMC SDRAM */ + sdram.Init(); + } + + if (memory != System::MemoryRegion::QSPI) { + /** QUADSPI FLASH */ + QSPIHandle::Config qspi_config; + qspi_config.device = QSPIHandle::Config::Device::IS25LP064A; + qspi_config.mode = QSPIHandle::Config::Mode::MEMORY_MAPPED; + qspi_config.pin_config.io0 = {DSY_GPIOF, 8}; + qspi_config.pin_config.io1 = {DSY_GPIOF, 9}; + qspi_config.pin_config.io2 = {DSY_GPIOF, 7}; + qspi_config.pin_config.io3 = {DSY_GPIOF, 6}; + qspi_config.pin_config.clk = {DSY_GPIOF, 10}; + qspi_config.pin_config.ncs = {DSY_GPIOG, 6}; + qspi.Init(qspi_config); + } + + /** Audio */ + // Audio Init + SaiHandle::Config sai_config; + sai_config.periph = SaiHandle::Config::Peripheral::SAI_1; + sai_config.sr = SaiHandle::Config::SampleRate::SAI_48KHZ; + sai_config.bit_depth = SaiHandle::Config::BitDepth::SAI_24BIT; + sai_config.a_sync = SaiHandle::Config::Sync::MASTER; // Srsly, Electrosmith? + sai_config.b_sync = SaiHandle::Config::Sync::SLAVE; // Ooof! + sai_config.a_dir = SaiHandle::Config::Direction::RECEIVE; + sai_config.b_dir = SaiHandle::Config::Direction::TRANSMIT; + sai_config.pin_config.fs = {DSY_GPIOE, 4}; + sai_config.pin_config.mclk = {DSY_GPIOE, 2}; + sai_config.pin_config.sck = {DSY_GPIOE, 5}; + sai_config.pin_config.sa = {DSY_GPIOE, 6}; + sai_config.pin_config.sb = {DSY_GPIOE, 3}; + SaiHandle sai_1_handle; + sai_1_handle.Init(sai_config); + I2CHandle::Config i2c_cfg; + i2c_cfg.periph = I2CHandle::Config::Peripheral::I2C_2; + i2c_cfg.mode = I2CHandle::Config::Mode::I2C_MASTER; + i2c_cfg.speed = I2CHandle::Config::Speed::I2C_400KHZ; + i2c_cfg.pin_config.scl = {DSY_GPIOB, 10}; + i2c_cfg.pin_config.sda = {DSY_GPIOB, 11}; + I2CHandle i2c2; + i2c2.Init(i2c_cfg); + codec.Init(i2c2); + + AudioHandle::Config audio_config; + audio_config.blocksize = blockSize; + audio_config.samplerate = sampleRateFromFloat(sampleRate); + audio_config.postgain = 1.f; + audio.Init(audio_config, sai_1_handle); + callbackRate = audio.GetSampleRate() / audio.GetConfig().blocksize; + + /** Fixed-function Digital I/O */ + userLED.mode = DSY_GPIO_MODE_OUTPUT_PP; + userLED.pull = DSY_GPIO_NOPULL; + userLED.pin = PIN_USER_LED; + dsy_gpio_init(&userLED); + + InitDac(); +} + + +void sig::libdaisy::patchsm::PatchSMBoard::InitDac() { + DacHandle::Config dac_config; + dac_config.mode = DacHandle::Mode::DMA; + dac_config.bitdepth = DacHandle::BitDepth:: + BITS_12; /**< Sets the output value to 0-4095 */ + dac_config.chn = DacHandle::Channel::BOTH; + // TODO: Apparently this can be removed to ensure that that the CV outputs + // actually reach 0V. + dac_config.buff_state = DacHandle::BufferState::ENABLED; + dac_config.target_samplerate = 48000; + dac.Init(dac_config); +} + +void sig::libdaisy::patchsm::PatchSMBoard::StartDac( + DacHandle::DacCallback callback) { + if (dac_running_) { + dac.Stop(); + } + + dac.Start( + dacBuffer[0], + dacBuffer[1], + dac_buffer_size_, + callback == nullptr ? DefaultDacCallback : callback); + dac_running_ = true; +} + +// TODO: Implement more generic DMA DAC support +// that can support arbitrary channels. +void sig::libdaisy::patchsm::PatchSMBoard::DefaultDacCallback(uint16_t **output, + size_t size) { + for (size_t i = 0; i < size; i++) { + output[0][i] = sig_daisy_patch_sm_dac_outputValues[0]; + output[1][i] = sig_daisy_patch_sm_dac_outputValues[1]; + } +} diff --git a/hosts/daisy/src/sig-daisy-seed.cpp b/hosts/daisy/src/sig-daisy-seed.cpp new file mode 100644 index 0000000..a496451 --- /dev/null +++ b/hosts/daisy/src/sig-daisy-seed.cpp @@ -0,0 +1,145 @@ +#include "../include/sig-daisy-seed.hpp" + +extern "C" { + #include "dev/codec_ak4556.h" +} + +using namespace daisy; + +void sig::libdaisy::seed::SeedBoard::Init(size_t blockSize, float sampleRate) { + System::Config syscfg; + syscfg.Boost(); + + QSPIHandle::Config qspi_config; + qspi_config.device = QSPIHandle::Config::Device::IS25LP064A; + qspi_config.mode = QSPIHandle::Config::Mode::MEMORY_MAPPED; + + qspi_config.pin_config.io0 = dsy_pin(DSY_GPIOF, 8); + qspi_config.pin_config.io1 = dsy_pin(DSY_GPIOF, 9); + qspi_config.pin_config.io2 = dsy_pin(DSY_GPIOF, 7); + qspi_config.pin_config.io3 = dsy_pin(DSY_GPIOF, 6); + qspi_config.pin_config.clk = dsy_pin(DSY_GPIOF, 10); + qspi_config.pin_config.ncs = dsy_pin(DSY_GPIOG, 6); + + // Configure the built-in GPIOs. + userLED.pin = PIN_USER_LED; + userLED.mode = DSY_GPIO_MODE_OUTPUT_PP; + + auto memory = System::GetProgramMemoryRegion(); + + if (memory != System::MemoryRegion::INTERNAL_FLASH) { + syscfg.skip_clocks = true; + } + + system.Init(syscfg); + + if (memory != System::MemoryRegion::QSPI) { + qspi.Init(qspi_config); + } + + if (memory == System::MemoryRegion::INTERNAL_FLASH) { + dsy_gpio_init(&userLED); + sdram.Init(); + } + + InitAudio(blockSize, sampleRate); +} + +void sig::libdaisy::seed::SeedBoard::InitAudio(size_t blockSize, + float sampleRate) { + // SAI1 -- Peripheral + // Configure + SaiHandle::Config sai_config; + sai_config.periph = SaiHandle::Config::Peripheral::SAI_1; + sai_config.sr = SaiHandle::Config::SampleRate::SAI_48KHZ; + sai_config.bit_depth = SaiHandle::Config::BitDepth::SAI_24BIT; + sai_config.a_sync = SaiHandle::Config::Sync::MASTER; + sai_config.b_sync = SaiHandle::Config::Sync::SLAVE; + sai_config.pin_config.fs = {DSY_GPIOE, 4}; + sai_config.pin_config.mclk = {DSY_GPIOE, 2}; + sai_config.pin_config.sck = {DSY_GPIOE, 5}; + + // Device-based Init + switch(CheckBoardVersion()) { + case BoardVersion::DAISY_SEED_1_1: + { + // Data Line Directions + sai_config.a_dir = SaiHandle::Config::Direction::RECEIVE; + sai_config.pin_config.sa = {DSY_GPIOE, 6}; + sai_config.b_dir = SaiHandle::Config::Direction::TRANSMIT; + sai_config.pin_config.sb = {DSY_GPIOE, 3}; + I2CHandle::Config i2c_config; + i2c_config.mode = I2CHandle::Config::Mode::I2C_MASTER; + i2c_config.periph = I2CHandle::Config::Peripheral::I2C_2; + i2c_config.speed = I2CHandle::Config::Speed::I2C_400KHZ; + i2c_config.pin_config.scl = {DSY_GPIOH, 4}; + i2c_config.pin_config.sda = {DSY_GPIOB, 11}; + I2CHandle i2c_handle; + i2c_handle.Init(i2c_config); + Wm8731::Config codec_cfg; + codec_cfg.Defaults(); + Wm8731 codec; + codec.Init(codec_cfg, i2c_handle); + } + break; + case BoardVersion::DAISY_SEED: + default: + { + // Data Line Directions + sai_config.a_dir = SaiHandle::Config::Direction::TRANSMIT; + sai_config.pin_config.sa = {DSY_GPIOE, 6}; + sai_config.b_dir = SaiHandle::Config::Direction::RECEIVE; + sai_config.pin_config.sb = {DSY_GPIOE, 3}; + dsy_gpio_pin codec_reset_pin; + codec_reset_pin = {DSY_GPIOB, 11}; + Ak4556::Init(codec_reset_pin); + } + break; + } + + // Then Initialize + SaiHandle sai_1_handle; + sai_1_handle.Init(sai_config); + + // Audio + AudioHandle::Config audio_config; + audio_config.blocksize = blockSize; + audio_config.samplerate = sampleRateFromFloat(sampleRate); + audio_config.postgain = 1.f; + audio.Init(audio_config, sai_1_handle); + callbackRate = audio.GetSampleRate() / audio.GetConfig().blocksize; +} + +void sig::libdaisy::seed::SeedBoard::InitDAC( + daisy::DacHandle::Channel channel) { + // TODO: This is sourced from the kxmx_Bluemchen. + // Is DMA-based DAC access an option instead of polling? + daisy::DacHandle::Config cfg; + cfg.bitdepth = daisy::DacHandle::BitDepth::BITS_12; + cfg.buff_state = daisy::DacHandle::BufferState::ENABLED; + cfg.mode = daisy::DacHandle::Mode::POLLING; + cfg.chn = channel; + dac.Init(cfg); + dac.WriteValue(daisy::DacHandle::Channel::BOTH, 0); +} + +sig::libdaisy::seed::SeedBoard::BoardVersion + sig::libdaisy::seed::SeedBoard::CheckBoardVersion() { + /** Version Checks: + * * Fall through is Daisy Seed v1 (aka Daisy Seed rev4) + * * PD3 tied to gnd is Daisy Seed v1.1 (aka Daisy Seed rev5) + * * PD4 tied to gnd reserved for future hardware + */ + dsy_gpio pincheck { + .pin = {DSY_GPIOD, 3}, + .mode = DSY_GPIO_MODE_INPUT, + .pull = DSY_GPIO_PULLUP + }; + dsy_gpio_init(&pincheck); + + if(!dsy_gpio_read(&pincheck)) { + return BoardVersion::DAISY_SEED_1_1; + } else { + return BoardVersion::DAISY_SEED; + } +} diff --git a/hosts/daisy/src/signaletic-daisy-host.cpp b/hosts/daisy/src/signaletic-daisy-host.cpp index 16dc6da..32c1c1e 100644 --- a/hosts/daisy/src/signaletic-daisy-host.cpp +++ b/hosts/daisy/src/signaletic-daisy-host.cpp @@ -1,759 +1,19 @@ -#include "../include/signaletic-daisy-host.h" +#include "../include/signaletic-daisy-host.hpp" -struct sig_daisy_Host* sig_daisy_Host_globalHost = NULL; - -daisy::SaiHandle::Config::SampleRate sig_daisy_Host_convertSampleRate( - float sampleRate) { - // Copy-pasted from libdaisy - // because the Seed has a completely different API - // for setting sample rates than the PatchSM. - // Srsly, Electrosmith?! - // https://github.com/electro-smith/libDaisy/blob/v5.3.0/src/daisy_patch_sm.cpp#L383-L409 - daisy::SaiHandle::Config::SampleRate sai_sr; - - switch(int(sampleRate)) { - case 8000: - sai_sr = daisy::SaiHandle::Config::SampleRate::SAI_8KHZ; - break; - case 16000: - sai_sr = daisy::SaiHandle::Config::SampleRate::SAI_16KHZ; - break; - case 32000: - sai_sr = daisy::SaiHandle::Config::SampleRate::SAI_32KHZ; - break; - case 48000: - sai_sr = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; - break; - case 96000: - sai_sr = daisy::SaiHandle::Config::SampleRate::SAI_96KHZ; - break; - default: - sai_sr = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; - break; - } - - return sai_sr; -} - -// TODO: userData is shared, but the API makes it seem as if -// different pointers could be provided for each of these callbacks. -// We either need to support two pointers to user data, or provide -// a different API for registering a single shared userData -// pointer for all callbacks. -void sig_daisy_Host_addOnEvaluateSignalsListener( - struct sig_daisy_Host* self, - sig_daisy_Host_onEvaluateSignals listener, - void* userData) { - self->onEvaluateSignals = listener; - self->userData = userData; -} - -void sig_daisy_Host_addAfterEvaluateSignalsListener( - struct sig_daisy_Host* self, - sig_daisy_Host_onEvaluateSignals listener, - void* userData) { - self->afterEvaluateSignals = listener; - self->userData = userData; -} - -void sig_daisy_Host_registerGlobalHost(struct sig_daisy_Host* host) { - if (sig_daisy_Host_globalHost != NULL && - sig_daisy_Host_globalHost != host) { - // TODO: Clean up memory leak here, - // or throw an error and do nothing if this is called more than once. - } - - sig_daisy_Host_globalHost = host; -} - -void sig_daisy_Host_noOpAudioCallback(daisy::AudioHandle::InputBuffer in, - daisy::AudioHandle::OutputBuffer out, size_t size, - struct sig_daisy_Host* host, void* userData) {} - -void sig_daisy_Host_init(struct sig_daisy_Host* self) { - self->onEvaluateSignals = sig_daisy_Host_noOpAudioCallback; - self->afterEvaluateSignals = sig_daisy_Host_noOpAudioCallback; -}; - -void sig_daisy_Host_audioCallback(daisy::AudioHandle::InputBuffer in, +void sig::libdaisy::DaisyHostAudioCallback(daisy::AudioHandle::InputBuffer in, daisy::AudioHandle::OutputBuffer out, size_t size) { - struct sig_daisy_Host* self = sig_daisy_Host_globalHost; - self->board.audioInputs = in; - self->board.audioOutputs = out; + struct sig_host_HardwareInterface* hardware = + sig_host_getGlobalHardwareInterface(); + hardware->audioInputChannels = (float**) in; + hardware->audioOutputChannels = (float**) out; // Invoke callback before evaluating the signal graph. - sig_daisy_Host_globalHost->onEvaluateSignals(in, out, size, - self, self->userData); + hardware->onEvaluateSignals(size, hardware); // Evaluate the signal graph. - struct sig_dsp_SignalEvaluator* evaluator = - sig_daisy_Host_globalHost->evaluator; + struct sig_dsp_SignalEvaluator* evaluator = hardware->evaluator; evaluator->evaluate(evaluator); // Invoke the after callback. - sig_daisy_Host_globalHost->afterEvaluateSignals(in, out, size, self, - self->userData); -} - - -float sig_daisy_HostImpl_noOpGetControl(struct sig_daisy_Host* host, - int control) { - return 0.0f; -} - -void sig_daisy_HostImpl_noOpSetControl(struct sig_daisy_Host* host, - int control, float value) {} - -float sig_daisy_HostImpl_processControlValue(struct sig_daisy_Host* host, - int control) { - return control > -1 && control < host->board.config->numAnalogInputs ? - host->board.analogControls[control].Process() : 0.0f; -} - -void sig_daisy_HostImpl_setControlValue(struct sig_daisy_Host* host, - int control, float value) { - if (control > -1 && control < host->board.config->numAnalogOutputs) { - sig_daisy_Host_writeValueToDACPolling(host->board.dac, control, value); - } -} - -void sig_daisy_Host_writeValueToDACPolling(daisy::DacHandle* dac, - int control, float value) { - daisy::DacHandle::Channel channel = static_cast( - control); - dac->WriteValue(channel, sig_bipolarToInvUint12(value)); -} - -float sig_daisy_HostImpl_getGateValue(struct sig_daisy_Host* host, - int control) { - if (control < 0 || control > host->board.config->numGateInputs) { - return 0.0f; - } - - daisy::GateIn* gate = host->board.gateInputs[control]; - // The gate is inverted (i.e. true when voltage is 0V). - // See https://electro-smith.github.io/libDaisy/classdaisy_1_1_gate_in.html#a08f75c6621307249de3107df96cfab2d - float sample = gate->State() ? 0.0f : 1.0f; - - return sample; -} - -void sig_daisy_HostImpl_setGateValue(struct sig_daisy_Host* host, - int control, float value) { - if (control < 0 || control >= host->board.config->numGateOutputs) { - return; - } - - dsy_gpio* gate = host->board.gateOutputs[control]; - dsy_gpio_write(gate, value > 0.0f ? 0 : 1); -} - -float sig_daisy_HostImpl_getSwitchValue(struct sig_daisy_Host* host, - int control) { - float sample = 0.0f; - if (control > -1 && control < host->board.config->numSwitches) { - daisy::Switch* sw = &(host->board.switches[control]); - sw->Debounce(); - sample = (float) sw->Pressed(); - } - - return sample; -} - -float sig_daisy_HostImpl_getTriSwitchValue(struct sig_daisy_Host* host, - int control) { - float sample = 0.0f; - if (control > -1 && control < host->board.config->numTriSwitches) { - daisy::Switch3* sw = &(host->board.triSwitches[control]); - int rawValue = sw->Read(); - // Left/up is mapped to +1, centre is 0, down/right is -1. - sample = rawValue == sw->POS_UP ? 1.0f : rawValue == sw->POS_DOWN ? - -1.0f : 0.0f; - } - - return sample; -} - - -float sig_daisy_HostImpl_getEncoderIncrement(struct sig_daisy_Host* host, - int control) { - float increment = 0.0f; - - if (control > -1 && control < host->board.config->numEncoders) { - daisy::Encoder* enc = host->board.encoders[control]; - increment = (float) enc->Increment(); - } - - return increment; -} - -float sig_daisy_HostImpl_processEncoderButtonValue(struct sig_daisy_Host* host, - int control) { - bool isPressed = false; - - if (control > -1 && control < host->board.config->numEncoders) { - daisy::Encoder* enc = host->board.encoders[control]; - // TODO: We need to separate the evaluation of controls - // from value retrieval so that multiple instances that are - // bound to a signal control won't cause time to "tick" too fast. - enc->Debounce(); - isPressed = enc->Pressed(); - } - - return isPressed ? 1.0f : 0.0f; -} - -struct sig_daisy_GateIn* sig_daisy_GateIn_new( - struct sig_Allocator* allocator, - struct sig_SignalContext* context, - struct sig_daisy_Host* host) { - struct sig_daisy_GateIn* self = sig_MALLOC(allocator, - struct sig_daisy_GateIn); - sig_daisy_GateIn_init(self, context, host); - sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, - context->audioSettings, &self->outputs); - - return self; -} - -void sig_daisy_GateIn_init(struct sig_daisy_GateIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_GateIn_generate); - self->host = host; - self->parameters = { - .scale = 1.0f, - .offset = 0.0f, - .control = 0 - }; -} - -void sig_daisy_GateIn_generate(void* signal) { - struct sig_daisy_GateIn* self = (struct sig_daisy_GateIn*) signal; - struct sig_daisy_Host* host = self->host; - float scale = self->parameters.scale; - float offset = self->parameters.offset; - int control = self->parameters.control; - - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - // TODO: Should this only be called at the block rate? - float sample = host->impl->getGateValue(host, control); - FLOAT_ARRAY(self->outputs.main)[i] = sample * scale + offset; - } -} - -void sig_daisy_GateIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_GateIn* self) { - sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, - &self->outputs); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - - -struct sig_daisy_GateOut* sig_daisy_GateOut_new( - struct sig_Allocator* allocator, - struct sig_SignalContext* context, - struct sig_daisy_Host* host) { - struct sig_daisy_GateOut* self = sig_MALLOC(allocator, - struct sig_daisy_GateOut); - sig_daisy_GateOut_init(self, context, host); - sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, - context->audioSettings, &self->outputs); - - return self; -} - -void sig_daisy_GateOut_init(struct sig_daisy_GateOut* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_GateOut_generate); - self->host = host; - self->parameters = { - .scale = 1.0f, - .offset = 0.0f, - .control = 0 - }; - - sig_CONNECT_TO_SILENCE(self, source, context); -} - -void sig_daisy_GateOut_generate(void* signal) { - struct sig_daisy_GateOut* self = (struct sig_daisy_GateOut*) signal; - struct sig_daisy_Host* host = self->host; - float scale = self->parameters.scale; - float offset = self->parameters.offset; - int control = self->parameters.control; - - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - float value = FLOAT_ARRAY(self->inputs.source)[i] * scale + offset; - FLOAT_ARRAY(self->outputs.main)[i] = value; - // TODO: Should this only be called at the block rate? - host->impl->setGateValue(host, control, value); - } -} - -void sig_daisy_GateOut_destroy(struct sig_Allocator* allocator, - struct sig_daisy_GateOut* self) { - sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, - &self->outputs); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - - -struct sig_daisy_CVIn* sig_daisy_CVIn_new(struct sig_Allocator* allocator, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - struct sig_daisy_CVIn* self = sig_MALLOC(allocator, - struct sig_daisy_CVIn); - sig_daisy_CVIn_init(self, context, host); - sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, - context->audioSettings, &self->outputs); - - return self; -} - -void sig_daisy_CVIn_init(struct sig_daisy_CVIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_CVIn_generate); - self->host = host; - self->parameters = { - .scale = 1.0f, - .offset = 0.0f, - .control = 0 - }; -} - -void sig_daisy_CVIn_generate(void* signal) { - struct sig_daisy_CVIn* self = (struct sig_daisy_CVIn*) signal; - struct sig_daisy_Host* host = self->host; - float scale = self->parameters.scale; - float offset = self->parameters.offset; - int control = self->parameters.control; - - float rawCV = host->impl->getControlValue(host, control); - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - float sample = rawCV * scale + offset; - FLOAT_ARRAY(self->outputs.main)[i] = sample; - } -} - -void sig_daisy_CVIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_CVIn* self) { - sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, - &self->outputs); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - -struct sig_daisy_FilteredCVIn* sig_daisy_FilteredCVIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host) { - struct sig_daisy_FilteredCVIn* self = sig_MALLOC(allocator, - struct sig_daisy_FilteredCVIn); - self->cvIn = sig_daisy_CVIn_new(allocator, context, host); - self->filter = sig_dsp_Smooth_new(allocator, context); - sig_daisy_FilteredCVIn_init(self, context, host); - - return self; -} - -void sig_daisy_FilteredCVIn_init(struct sig_daisy_FilteredCVIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_FilteredCVIn_generate); - self->host = host; - self->parameters = { - .scale = 1.0f, - .offset = 0.0f, - .control = 0, - .time = 0.01f - }; - self->filter->inputs.source = self->cvIn->outputs.main; - self->outputs = self->filter->outputs; -} - -void sig_daisy_FilteredCVIn_generate(void* signal) { - struct sig_daisy_FilteredCVIn* self = - (struct sig_daisy_FilteredCVIn*) signal; - // TODO: We have to update these parameters - // at block rate because there's no way to know if there - // was actually a parameter change made and parameters are - // stored by value, not by pointer. - self->filter->parameters.time = self->parameters.time; - self->cvIn->parameters.control = self->parameters.control; - self->cvIn->parameters.scale = self->parameters.scale; - self->cvIn->parameters.offset = self->parameters.offset; - - self->cvIn->signal.generate(self->cvIn); - self->filter->signal.generate(self->filter); -} - -void sig_daisy_FilteredCVIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_FilteredCVIn* self) { - sig_daisy_CVIn_destroy(allocator, self->cvIn); - sig_dsp_Smooth_destroy(allocator, self->filter); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - - -struct sig_daisy_VOctCVIn* sig_daisy_VOctCVIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host) { - struct sig_daisy_VOctCVIn* self = sig_MALLOC(allocator, - struct sig_daisy_VOctCVIn); - self->cvIn = sig_daisy_CVIn_new(allocator, context, host); - self->cvConverter = sig_dsp_LinearToFreq_new(allocator, context); - sig_daisy_VOctCVIn_init(self, context, host); - - return self; -} - -void sig_daisy_VOctCVIn_init(struct sig_daisy_VOctCVIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_VOctCVIn_generate); - self->host = host; - self->parameters = { - .scale = 1.0f, - .offset = 0.0f, - .control = 0, - .middleFreq = self->cvConverter->parameters.middleFreq - }; - self->outputs = self->cvConverter->outputs; - self->cvConverter->inputs.source = self->cvIn->outputs.main; -} - -void sig_daisy_VOctCVIn_generate(void* signal) { - struct sig_daisy_VOctCVIn* self = (struct sig_daisy_VOctCVIn*) signal; - // TODO: We always have to update these parameters - // at block rate because there's no way to know if there - // was actually a parameter change made and parameters are not pointers. - self->cvConverter->parameters.middleFreq = self->parameters.middleFreq; - self->cvIn->parameters.control = self->parameters.control; - self->cvIn->parameters.scale = self->parameters.scale; - self->cvIn->parameters.offset = self->parameters.offset; - - self->cvIn->signal.generate(self->cvIn); - self->cvConverter->signal.generate(self->cvConverter); -} - -void sig_daisy_VOctCVIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_VOctCVIn* self) { - sig_daisy_CVIn_destroy(allocator, self->cvIn); - sig_dsp_LinearToFreq_destroy(allocator, self->cvConverter); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - - -struct sig_daisy_SwitchIn* sig_daisy_SwitchIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host) { - struct sig_daisy_SwitchIn* self = sig_MALLOC(allocator, - struct sig_daisy_SwitchIn); - sig_daisy_SwitchIn_init(self, context, host); - sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, - context->audioSettings, &self->outputs); - - return self; -} - -void sig_daisy_SwitchIn_init(struct sig_daisy_SwitchIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_SwitchIn_generate); - self->host = host; - self->parameters = { - .scale = 1.0f, - .offset = 0.0f, - .control = 0 - }; -} - -void sig_daisy_SwitchIn_generate(void* signal) { - struct sig_daisy_SwitchIn* self = (struct sig_daisy_SwitchIn*) signal; - struct sig_daisy_Host* host = self->host; - float scale = self->parameters.scale; - float offset = self->parameters.offset; - int control = self->parameters.control; - - float sample = host->impl->getSwitchValue(host, control); - float scaledSample = sample * scale + offset; - - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - FLOAT_ARRAY(self->outputs.main)[i] = scaledSample; - } -} - -void sig_daisy_SwitchIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_SwitchIn* self) { - sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, - &self->outputs); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - - - -struct sig_daisy_TriSwitchIn* sig_daisy_TriSwitchIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host) { - struct sig_daisy_TriSwitchIn* self = sig_MALLOC(allocator, - struct sig_daisy_TriSwitchIn); - sig_daisy_TriSwitchIn_init(self, context, host); - sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, - context->audioSettings, &self->outputs); - - return self; -} - -void sig_daisy_TriSwitchIn_init(struct sig_daisy_TriSwitchIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_TriSwitchIn_generate); - self->host = host; - self->parameters = { - .scale = 1.0f, - .offset = 0.0f, - .control = 0 - }; -} - -void sig_daisy_TriSwitchIn_generate(void* signal) { - struct sig_daisy_TriSwitchIn* self = (struct sig_daisy_TriSwitchIn*) signal; - struct sig_daisy_Host* host = self->host; - float scale = self->parameters.scale; - float offset = self->parameters.offset; - int control = self->parameters.control; - - float sample = host->impl->getTriSwitchValue(host, control); - float scaledSample = sample * scale + offset; - - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - FLOAT_ARRAY(self->outputs.main)[i] = scaledSample; - } -} - -void sig_daisy_TriSwitchIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_SwitchIn* self) { - sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, - &self->outputs); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - - - -void sig_daisy_EncoderIn_Outputs_newAudioBlocks(struct sig_Allocator* allocator, - struct sig_AudioSettings* audioSettings, - struct sig_daisy_EncoderIn_Outputs* outputs) { - outputs->main = sig_AudioBlock_newSilent(allocator, audioSettings); - outputs->button = sig_AudioBlock_newSilent(allocator, audioSettings); -} - -void sig_daisy_EncoderIn_Outputs_destroyAudioBlocks( - struct sig_Allocator* allocator, - struct sig_daisy_EncoderIn_Outputs* outputs) { - sig_AudioBlock_destroy(allocator, outputs->main); - sig_AudioBlock_destroy(allocator, outputs->button); -} - -struct sig_daisy_EncoderIn* sig_daisy_EncoderIn_new( - struct sig_Allocator* allocator, struct sig_SignalContext* context, - struct sig_daisy_Host* host) { - struct sig_daisy_EncoderIn* self = sig_MALLOC(allocator, - struct sig_daisy_EncoderIn); - sig_daisy_EncoderIn_init(self, context, host); - sig_daisy_EncoderIn_Outputs_newAudioBlocks(allocator, - context->audioSettings, &self->outputs); - - return self; -} - -void sig_daisy_EncoderIn_init(struct sig_daisy_EncoderIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_EncoderIn_generate); - self->host = host; - self->parameters = { - .scale = 1.0f, - .offset = 0.0f, - .control = 0 - }; - - self->accumulatedValue = 0.0f; -} - -void sig_daisy_EncoderIn_generate(void* signal) { - struct sig_daisy_EncoderIn* self = (struct sig_daisy_EncoderIn*) signal; - struct sig_daisy_Host* host = self->host; - float scale = self->parameters.scale; - float offset = self->parameters.offset; - int control = self->parameters.control; - - float incrementValue = host->impl->getEncoderIncrement(host, control); - float buttonValue = host->impl->getEncoderButtonValue(host, control); - float accumulatedValue = self->accumulatedValue + incrementValue; - float scaledAccumulatedValue = accumulatedValue * scale + offset; - - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - FLOAT_ARRAY(self->outputs.main)[i] = scaledAccumulatedValue; - FLOAT_ARRAY(self->outputs.increment)[i] = incrementValue; - FLOAT_ARRAY(self->outputs.button)[i] = buttonValue; - } - - self->accumulatedValue = accumulatedValue; -} - -void sig_daisy_EncoderIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_EncoderIn* self) { - sig_daisy_EncoderIn_Outputs_destroyAudioBlocks(allocator, - &self->outputs); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - - -struct sig_daisy_CVOut* sig_daisy_CVOut_new(struct sig_Allocator* allocator, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - struct sig_daisy_CVOut* self = sig_MALLOC(allocator, - struct sig_daisy_CVOut); - sig_daisy_CVOut_init(self, context, host); - sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, - context->audioSettings, &self->outputs); - - return self; -} - -void sig_daisy_CVOut_init(struct sig_daisy_CVOut* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_CVOut_generate); - self->host = host; - self->parameters = { - .scale = 1.0f, - .offset = 0.0f, - .control = 0 - }; - - sig_CONNECT_TO_SILENCE(self, source, context); -} - -void sig_daisy_CVOut_generate(void* signal) { - struct sig_daisy_CVOut* self = (struct sig_daisy_CVOut*) signal; - struct sig_daisy_Host* host = self->host; - float scale = self->parameters.scale; - float offset = self->parameters.offset; - int control = self->parameters.control; - - // Pass through the value to the output buffer, - // so even sink signals can be chained. - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - float source = FLOAT_ARRAY(self->inputs.source)[i]; - float sample = source * scale + offset; - FLOAT_ARRAY(self->outputs.main)[i] = sample; - // TODO: Should this only be called at the block rate? - host->impl->setControlValue(host, control, sample); - } -} - -void sig_daisy_CVIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_CVOut* self) { - sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, - &self->outputs); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - -struct sig_daisy_AudioOut* sig_daisy_AudioOut_new( - struct sig_Allocator* allocator, - struct sig_SignalContext* context, - struct sig_daisy_Host* host) { - struct sig_daisy_AudioOut* self = sig_MALLOC(allocator, - struct sig_daisy_AudioOut); - sig_daisy_AudioOut_init(self, context, host); - sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, - context->audioSettings, &self->outputs); - - return self; -} - -void sig_daisy_AudioOut_init(struct sig_daisy_AudioOut* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_AudioOut_generate); - self->host = host; - self->parameters = { - .channel = 0, - .scale = 1.0f - }; - - sig_CONNECT_TO_SILENCE(self, source, context); -} - -void sig_daisy_AudioOut_generate(void* signal) { - struct sig_daisy_AudioOut* self = (struct sig_daisy_AudioOut*) signal; - struct sig_daisy_Host* host = self->host; - daisy::AudioHandle::OutputBuffer out = host->board.audioOutputs; - float_array_ptr source = self->inputs.source; - int channel = self->parameters.channel; - float scale = self->parameters.scale; - - // TODO: We need a validation stage for Signals so that we can - // avoid these conditionals happening at block rate. - if (channel < 0 || - channel >= host->board.config->numAudioOutputChannels) { - // There's a channel mismatch, just do nothing. - return; - } - - // TODO: Is it safe to assume here that the Signal's block size is - // the same as the Host's? - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - out[channel][i] = FLOAT_ARRAY(source)[i] * scale; - } -} - -void sig_daisy_AudioOut_destroy(struct sig_Allocator* allocator, - struct sig_daisy_AudioOut* self) { - sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, - &self->outputs); - sig_dsp_Signal_destroy(allocator, (void*) self); -} - - -struct sig_daisy_AudioIn* sig_daisy_AudioIn_new( - struct sig_Allocator* allocator, - struct sig_SignalContext* context, - struct sig_daisy_Host* host) { - struct sig_daisy_AudioIn* self = sig_MALLOC(allocator, - struct sig_daisy_AudioIn); - sig_daisy_AudioIn_init(self, context, host); - sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, - context->audioSettings, &self->outputs); - - return self; -} - -void sig_daisy_AudioIn_init(struct sig_daisy_AudioIn* self, - struct sig_SignalContext* context, struct sig_daisy_Host* host) { - sig_dsp_Signal_init(self, context, *sig_daisy_AudioIn_generate); - self->host = host; - self->parameters = { - .channel = 0, - .scale = 1.0f - }; -} - -void sig_daisy_AudioIn_generate(void* signal) { - struct sig_daisy_AudioIn* self = (struct sig_daisy_AudioIn*) signal; - struct sig_daisy_Host* host = self->host; - daisy::AudioHandle::InputBuffer in = host->board.audioInputs; - int channel = self->parameters.channel; - float scale = self->parameters.scale; - - // TODO: We need a validation stage for Signals so that we can - // avoid these conditionals happening at block rate. - if (channel < 0 || - channel >= host->board.config->numAudioInputChannels) { - // There's a channel mismatch, just do nothing. - return; - } - - // TODO: Is it safe to assume here that the Signal's block size is - // the same as the Host's? - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - FLOAT_ARRAY(self->outputs.main)[i] = in[channel][i] * scale; - } -} - -void sig_daisy_AudioIn_destroy(struct sig_Allocator* allocator, - struct sig_daisy_AudioIn* self) { - sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, - &self->outputs); - sig_dsp_Signal_destroy(allocator, (void*) self); + hardware->afterEvaluateSignals(size, hardware); } diff --git a/hosts/daisy/src/signaletic-host.c b/hosts/daisy/src/signaletic-host.c new file mode 100644 index 0000000..425d863 --- /dev/null +++ b/hosts/daisy/src/signaletic-host.c @@ -0,0 +1,529 @@ +#include "../include/signaletic-host.h" + +static struct sig_host_HardwareInterface* sig_host_globalHardwareInterface; + +void sig_host_registerGlobalHardwareInterface( + struct sig_host_HardwareInterface* hardware) { + sig_host_globalHardwareInterface = hardware; +} + +struct sig_host_HardwareInterface* sig_host_getGlobalHardwareInterface() { + return sig_host_globalHardwareInterface; +} + +void sig_host_noOpAudioEventCallback(size_t size, + struct sig_host_HardwareInterface* hardware) {}; + +struct sig_host_GateIn* sig_host_GateIn_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + struct sig_host_GateIn* self = sig_MALLOC(allocator, + struct sig_host_GateIn); + sig_host_GateIn_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + + +void sig_host_GateIn_init(struct sig_host_GateIn* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_GateIn_generate); + self->parameters.scale = 1.0f; + self->parameters.offset = 0.0f; + self->parameters.control = 0; +} + +void sig_host_GateIn_generate(void* signal) { + struct sig_host_GateIn* self = (struct sig_host_GateIn*) signal; + struct sig_host_HardwareInterface* hardware = self->hardware; + float scale = self->parameters.scale; + float offset = self->parameters.offset; + int control = self->parameters.control; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float sample = hardware->gateInputs[control]; + FLOAT_ARRAY(self->outputs.main)[i] = sample * scale + offset; + } +} + +void sig_host_GateIn_destroy(struct sig_Allocator* allocator, + struct sig_host_GateIn* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + +struct sig_host_GateOut* sig_host_GateOut_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + struct sig_host_GateOut* self = sig_MALLOC(allocator, + struct sig_host_GateOut); + sig_host_GateOut_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_host_GateOut_init(struct sig_host_GateOut* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_GateOut_generate); + self->parameters.scale = 1.0f; + self->parameters.offset = 0.0f; + self->parameters.control = 0; + + sig_CONNECT_TO_SILENCE(self, source, context); +} + +void sig_host_GateOut_generate(void* signal) { + struct sig_host_GateOut* self = (struct sig_host_GateOut*) signal; + struct sig_host_HardwareInterface* hardware = self->hardware; + float scale = self->parameters.scale; + float offset = self->parameters.offset; + int control = self->parameters.control; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float value = FLOAT_ARRAY(self->inputs.source)[i] * scale + offset; + FLOAT_ARRAY(self->outputs.main)[i] = value; + // TODO: Should this only be called at the block rate? + hardware->gpioOutputs[control] = value; + } +} + +void sig_host_GateOut_destroy(struct sig_Allocator* allocator, + struct sig_host_GateOut* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + +struct sig_host_CVIn* sig_host_CVIn_new(struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + struct sig_host_CVIn* self = sig_MALLOC(allocator, + struct sig_host_CVIn); + sig_host_CVIn_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_host_CVIn_init(struct sig_host_CVIn* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_CVIn_generate); + self->parameters.scale = 1.0f; + self->parameters.offset = 0.0f; + self->parameters.control = 0; +} + +void sig_host_CVIn_generate(void* signal) { + struct sig_host_CVIn* self = (struct sig_host_CVIn*) signal; + struct sig_host_HardwareInterface* hardware = self->hardware; + float scale = self->parameters.scale; + float offset = self->parameters.offset; + int control = self->parameters.control; + + float rawCV = hardware->adcChannels[control]; + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float sample = rawCV * scale + offset; + FLOAT_ARRAY(self->outputs.main)[i] = sample; + } +} + +void sig_host_CVIn_destroy(struct sig_Allocator* allocator, + struct sig_host_CVIn* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + +struct sig_host_FilteredCVIn* sig_host_FilteredCVIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_host_FilteredCVIn* self = sig_MALLOC(allocator, + struct sig_host_FilteredCVIn); + self->cvIn = sig_host_CVIn_new(allocator, context); + self->filter = sig_dsp_Smooth_new(allocator, context); + sig_host_FilteredCVIn_init(self, context); + + return self; +} + +void sig_host_FilteredCVIn_init(struct sig_host_FilteredCVIn* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_FilteredCVIn_generate); + self->parameters.scale = 1.0f; + self->parameters.offset = 0.0f; + self->parameters.control = 0; + self->parameters.time = 0.01f; + self->filter->inputs.source = self->cvIn->outputs.main; + self->outputs = self->filter->outputs; +} + +void sig_host_FilteredCVIn_generate(void* signal) { + struct sig_host_FilteredCVIn* self = + (struct sig_host_FilteredCVIn*) signal; + // TODO: We have to update these parameters + // at block rate because there's no way to know if there + // was actually a parameter change made and parameters are + // stored by value, not by pointer. + self->cvIn->hardware = self->hardware; + self->filter->parameters.time = self->parameters.time; + self->cvIn->parameters.control = self->parameters.control; + self->cvIn->parameters.scale = self->parameters.scale; + self->cvIn->parameters.offset = self->parameters.offset; + + self->cvIn->signal.generate(self->cvIn); + self->filter->signal.generate(self->filter); +} + +void sig_host_FilteredCVIn_destroy(struct sig_Allocator* allocator, + struct sig_host_FilteredCVIn* self) { + sig_host_CVIn_destroy(allocator, self->cvIn); + sig_dsp_Smooth_destroy(allocator, self->filter); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + +struct sig_host_VOctCVIn* sig_host_VOctCVIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_host_VOctCVIn* self = sig_MALLOC(allocator, + struct sig_host_VOctCVIn); + self->cvIn = sig_host_CVIn_new(allocator, context); + self->cvConverter = sig_dsp_LinearToFreq_new(allocator, context); + sig_host_VOctCVIn_init(self, context); + + return self; +} + +void sig_host_VOctCVIn_init(struct sig_host_VOctCVIn* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_VOctCVIn_generate); + self->parameters.scale = 1.0f; + self->parameters.offset = 0.0f; + self->parameters.control = 0; + self->parameters.middleFreq = self->cvConverter->parameters.middleFreq; + + self->outputs = self->cvConverter->outputs; + self->cvConverter->inputs.source = self->cvIn->outputs.main; +} + +void sig_host_VOctCVIn_generate(void* signal) { + struct sig_host_VOctCVIn* self = (struct sig_host_VOctCVIn*) signal; + // TODO: We always have to update these parameters + // at block rate because there's no way to know if there + // was actually a parameter change made and parameters are not pointers. + self->cvIn->hardware = self->hardware; + self->cvConverter->parameters.middleFreq = self->parameters.middleFreq; + self->cvIn->parameters.control = self->parameters.control; + self->cvIn->parameters.scale = self->parameters.scale; + self->cvIn->parameters.offset = self->parameters.offset; + + self->cvIn->signal.generate(self->cvIn); + self->cvConverter->signal.generate(self->cvConverter); +} + +void sig_host_VOctCVIn_destroy(struct sig_Allocator* allocator, + struct sig_host_VOctCVIn* self) { + sig_host_CVIn_destroy(allocator, self->cvIn); + sig_dsp_LinearToFreq_destroy(allocator, self->cvConverter); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + +struct sig_host_CVOut* sig_host_CVOut_new(struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + struct sig_host_CVOut* self = sig_MALLOC(allocator, + struct sig_host_CVOut); + sig_host_CVOut_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_host_CVOut_init(struct sig_host_CVOut* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_CVOut_generate); + self->parameters.scale = 1.0f; + self->parameters.offset = 0.0f; + self->parameters.control = 0; + + sig_CONNECT_TO_SILENCE(self, source, context); +} + +void sig_host_CVOut_generate(void* signal) { + struct sig_host_CVOut* self = (struct sig_host_CVOut*) signal; + struct sig_host_HardwareInterface* hardware = self->hardware; + float scale = self->parameters.scale; + float offset = self->parameters.offset; + int control = self->parameters.control; + + // Pass through the value to the output buffer, + // so even sink signals can be chained. + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float sample = source * scale + offset; + FLOAT_ARRAY(self->outputs.main)[i] = sample; + // TODO: Should this only be called at the block rate? + hardware->dacChannels[control] = sample; + } +} + + +struct sig_host_AudioOut* sig_host_AudioOut_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + struct sig_host_AudioOut* self = sig_MALLOC(allocator, + struct sig_host_AudioOut); + sig_host_AudioOut_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_host_AudioOut_init(struct sig_host_AudioOut* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_AudioOut_generate); + self->parameters.channel = 0; + self->parameters.scale = 1.0f; + + sig_CONNECT_TO_SILENCE(self, source, context); +} + +void sig_host_AudioOut_generate(void* signal) { + struct sig_host_AudioOut* self = (struct sig_host_AudioOut*) signal; + struct sig_host_HardwareInterface* hardware = self->hardware; + float** out = hardware->audioOutputChannels; + float_array_ptr source = self->inputs.source; + int channel = self->parameters.channel; + float scale = self->parameters.scale; + + // TODO: We need a validation stage for Signals so that we can + // avoid these conditionals happening at block rate. + if (channel < 0 || + channel >= hardware->numAudioOutputChannels) { + // There's a channel mismatch, just do nothing. + return; + } + + // TODO: Is it safe to assume here that the Signal's block size is + // the same as the Host's? + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + out[channel][i] = FLOAT_ARRAY(source)[i] * scale; + } +} + +void sig_host_AudioOut_destroy(struct sig_Allocator* allocator, + struct sig_host_AudioOut* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + +struct sig_host_AudioIn* sig_host_AudioIn_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + struct sig_host_AudioIn* self = sig_MALLOC(allocator, + struct sig_host_AudioIn); + sig_host_AudioIn_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_host_AudioIn_init(struct sig_host_AudioIn* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_AudioIn_generate); + self->parameters.channel = 0; + self->parameters.scale = 1.0f; +} + +void sig_host_AudioIn_generate(void* signal) { + struct sig_host_AudioIn* self = (struct sig_host_AudioIn*) signal; + struct sig_host_HardwareInterface* hardware = self->hardware; + float** in = hardware->audioInputChannels; + int channel = self->parameters.channel; + float scale = self->parameters.scale; + + // TODO: We need a validation stage for Signals so that we can + // avoid these conditionals happening at block rate. + if (channel < 0 || + channel >= hardware->numAudioInputChannels) { + // There's a channel mismatch, just do nothing. + return; + } + + // TODO: Is it safe to assume here that the Signal's block size is + // the same as the Host's? + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + FLOAT_ARRAY(self->outputs.main)[i] = in[channel][i] * scale; + } +} + +void sig_host_AudioIn_destroy(struct sig_Allocator* allocator, + struct sig_host_AudioIn* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + +struct sig_host_SwitchIn* sig_host_SwitchIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_host_SwitchIn* self = sig_MALLOC(allocator, + struct sig_host_SwitchIn); + sig_host_SwitchIn_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_host_SwitchIn_init(struct sig_host_SwitchIn* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_SwitchIn_generate); + self->parameters.scale = 1.0f; + self->parameters.offset = 0.0f; + self->parameters.control = 0; +} + +void sig_host_SwitchIn_generate(void* signal) { + struct sig_host_SwitchIn* self = (struct sig_host_SwitchIn*) signal; + struct sig_host_HardwareInterface* hardware = self->hardware; + float scale = self->parameters.scale; + float offset = self->parameters.offset; + int control = self->parameters.control; + + float sample = hardware->toggles[control]; + float scaledSample = sample * scale + offset; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + FLOAT_ARRAY(self->outputs.main)[i] = scaledSample; + } +} + +void sig_host_SwitchIn_destroy(struct sig_Allocator* allocator, + struct sig_host_SwitchIn* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + +struct sig_host_TriSwitchIn* sig_host_TriSwitchIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_host_TriSwitchIn* self = sig_MALLOC(allocator, + struct sig_host_TriSwitchIn); + sig_host_TriSwitchIn_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_host_TriSwitchIn_init(struct sig_host_TriSwitchIn* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_TriSwitchIn_generate); + self->parameters.scale = 1.0f; + self->parameters.offset = 0.0f; + self->parameters.control = 0; +} + +void sig_host_TriSwitchIn_generate(void* signal) { + struct sig_host_TriSwitchIn* self = (struct sig_host_TriSwitchIn*) signal; + struct sig_host_HardwareInterface* hardware = self->hardware; + float scale = self->parameters.scale; + float offset = self->parameters.offset; + int control = self->parameters.control; + + float sample = hardware->triSwitches[control]; + float scaledSample = sample * scale + offset; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + FLOAT_ARRAY(self->outputs.main)[i] = scaledSample; + } +} + +void sig_host_TriSwitchIn_destroy(struct sig_Allocator* allocator, + struct sig_host_TriSwitchIn* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + + +void sig_host_EncoderIn_Outputs_newAudioBlocks(struct sig_Allocator* allocator, + struct sig_AudioSettings* audioSettings, + struct sig_host_EncoderIn_Outputs* outputs) { + outputs->main = sig_AudioBlock_newSilent(allocator, audioSettings); + outputs->increment = sig_AudioBlock_newSilent(allocator, audioSettings); + outputs->button = sig_AudioBlock_newSilent(allocator, audioSettings); +} + +void sig_host_EncoderIn_Outputs_destroyAudioBlocks( + struct sig_Allocator* allocator, + struct sig_host_EncoderIn_Outputs* outputs) { + sig_AudioBlock_destroy(allocator, outputs->main); + sig_AudioBlock_destroy(allocator, outputs->increment); + sig_AudioBlock_destroy(allocator, outputs->button); +} + +struct sig_host_EncoderIn* sig_host_EncoderIn_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_host_EncoderIn* self = sig_MALLOC(allocator, + struct sig_host_EncoderIn); + sig_host_EncoderIn_init(self, context); + sig_host_EncoderIn_Outputs_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_host_EncoderIn_init(struct sig_host_EncoderIn* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_host_EncoderIn_generate); + self->parameters.scale = 1.0f; + self->parameters.offset = 0.0f; + self->parameters.turnControl = 0; + self->parameters.buttonControl = 0; + + self->accumulator = 0.0f; +} + +void sig_host_EncoderIn_generate(void* signal) { + struct sig_host_EncoderIn* self = (struct sig_host_EncoderIn*) signal; + struct sig_host_HardwareInterface* hardware = self->hardware; + float scale = self->parameters.scale; + float offset = self->parameters.offset; + int turnControl = self->parameters.turnControl; + int buttonControl = self->parameters.buttonControl; + + float increment = (hardware->numEncoders > turnControl) ? + hardware->encoders[turnControl] : 0.0f; + float button = (hardware->numToggles > buttonControl) ? + hardware->toggles[buttonControl] : 0.0f; + + self->accumulator += increment; + float scaledAccumulation = self->accumulator * scale + offset; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + FLOAT_ARRAY(self->outputs.main)[i] = scaledAccumulation; + FLOAT_ARRAY(self->outputs.increment)[i] = increment; + FLOAT_ARRAY(self->outputs.button)[i] = button; + } +} + +void sig_host_EncoderIn_destroy(struct sig_Allocator* allocator, + struct sig_host_EncoderIn* self) { + sig_host_EncoderIn_Outputs_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} diff --git a/hosts/daisy/vendor/dpt/lib/dev/DAC7554.h b/hosts/daisy/vendor/dpt/lib/dev/DAC7554.h index f311772..f3e926c 100644 --- a/hosts/daisy/vendor/dpt/lib/dev/DAC7554.h +++ b/hosts/daisy/vendor/dpt/lib/dev/DAC7554.h @@ -3,7 +3,6 @@ #define DSY_DEV_DAC_7554_H /**< Macro */ #include #include -#include #include "daisy_core.h" using namespace std; @@ -13,10 +12,10 @@ namespace daisy { /** @addtogroup analog_digital_conversion - @{ + @{ */ -/** +/** Driver for DAC7554, based on code from driver for DAC8568 Based on Code from Westlicht Performer - https://westlicht.github.io/performer/ Port for Daisy by Making Sound Machines - https://github.com/makingsoundmachines @@ -59,8 +58,6 @@ class Dac7554 typedef uint16_t Value; - vector buf; - uint8_t buff[1024]; uint16_t buff16[1024]; @@ -74,7 +71,7 @@ class Dac7554 // configuration currently only uses SPI1, w/ soft chip select. - /** + /** Takes an argument for the pin cfg \param pin_cfg should be a pointer to an array of Dac7554::NUM_PINS dsy_gpio_pins */ @@ -106,4 +103,4 @@ class Dac7554 /** @} */ } // namespace daisy -#endif \ No newline at end of file +#endif diff --git a/libsignaletic/include/libsignaletic.h b/libsignaletic/include/libsignaletic.h index a4f596f..148863b 100644 --- a/libsignaletic/include/libsignaletic.h +++ b/libsignaletic/include/libsignaletic.h @@ -37,8 +37,8 @@ static const float sig_PI = 3.14159265358979323846f; static const float sig_TWOPI = 6.283185307179586f; static const float sig_RECIP_TWOPI = 0.159154943091895f; static const float sig_LOG0_001 = -6.907755278982137f; -static const float sig_LOG2 = 0.6931471805599453; -static const float sig_FREQ_C4 = 261.6256; +static const float sig_LOG2 = 0.6931471805599453f; +static const float sig_FREQ_C4 = 261.6256f; enum sig_Result { SIG_RESULT_NONE, @@ -171,6 +171,39 @@ uint16_t sig_bipolarToUint12(float sample); */ uint16_t sig_bipolarToInvUint12(float sample); +/** + * Converts an unsigned 16-bit integer in the range 0-65536 to a + * bipolar floating point sample in the range -1.0 to 1.0. + * + * This function does not clamp the sample. + * + * @param sample the unsigned 16-bit sample to convert + * @return the sample converted to normalized floating point + */ +float sig_uint16ToBipolar(uint16_t sample); + +/** + * Converts an unsigned 16-bit integer in the range 0-65536 to a + * unipolar floating point sample in the range 0.0 to 1.0. + * + * This function does not clamp the sample. + * + * @param sample the unsigned 16-bit sample to convert + * @return the sample converted to normalized unipolar floating point + */ +float sig_uint16ToUnipolar(uint16_t sample); + +/** + * Converts an unsigned 16-bit integer in the range 65536-0 to a + * bipolar floating point sample in the range -1.0 to 1.0. + * + * This function does not clamp the sample. + * + * @param sample the inverted unsigned 16-bit sample to convert + * @return the sample converted to normalized floating point + */ +float sig_invUint16ToBipolar(uint16_t sample); + /** * Converts MIDI note numbers into frequencies in hertz. * This algorithm assumes A4 = 440 Hz = MIDI note #69. @@ -209,6 +242,34 @@ float sig_linearToFreq(float value, float middleFreq); */ float sig_freqToLinear(float freq, float middleFreq); +/** + * @brief Sums all values in an array. + * + * @param values an array of floating point values + * @param length the length of the array + * @return float the sum of all values + */ +float sig_sum(float_array_ptr values, size_t length); + +/** + * @brief Returns the index of the smallest value in an array. + * + * @param values an array of floating point values + * @param length the length of the warray + * @return size_t the index to the smallest item in the array + */ +size_t sig_indexOfMin(float_array_ptr values, size_t length); + +/** + * @brief Returns the index of the largest value in an array. + * + * @param values an array of floating point values + * @param length the length of the warray + * @return size_t the index to the largest item in the array + + */ +size_t sig_indexOfMax(float_array_ptr values, size_t length); + /** * Type definition for array fill functions. * @@ -281,6 +342,36 @@ float sig_interpolate_linear(float idx, float_array_ptr table, */ float sig_interpolate_cubic(float idx, float_array_ptr table, size_t length); +/** + * @brief Returns the mean average of all values in the specified array. + * + * @param values an array of floating point values to average + * @param length the number of values in the array + * @return float the mean of all values + */ +float sig_filter_mean(float_array_ptr values, size_t length); + +/** + * @brief Returns the mean average of values in the array, after excluding the + * largest and smallest values. + * + * @param values an array of floating point values to average + * @param length the number of values in the array + * @return float float the mean of all values + */ +float sig_filter_meanExcludeMinMax(float_array_ptr values, size_t length); + +/** + * @brief An exponential moving average filter that implements the formula + * y[n] = a * x[n] + (1 - a) * y[n-1] + * + * @param current the current sample (i.e. x[n]) + * @param previous the previous output sample (i.e. y[n-1]) + * @param a the coefficient (between 0 and 1); values closer to 0 apply more filtering) + * @return float the filtered sample (i.e. y[n]) + */ +float sig_filter_ema(float current, float previous, float a); + /** * @brief A one pole filter that implements the formula * y[n] = b0 * x[n] + a1 * y[n-1]. @@ -349,6 +440,16 @@ float sig_filter_smooth(float current, float previous, float coeff); float sig_filter_smooth_calculateCoefficient(float timeSecs, float sampleRate); +// TODO: Documentation! +struct sig_filter_Smooth { + float coeff; + float previous; +}; + +void sig_filter_Smooth_init(struct sig_filter_Smooth* self, float coeff); + +float sig_filter_Smooth_generate(struct sig_filter_Smooth* self, float value); + /** * Type definition for a waveform generator function. * @@ -398,6 +499,61 @@ float sig_waveform_reverseSaw(float phase); float sig_waveform_triangle(float phase); +/** + * @brief A fast sine approximation implemented using a Chamberlin SVF. + * This approximation is good up to about 1/6 the sampleRate. + * + * For details, see Dattoro "Effect Design Part 3" and + * https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/ + */ +struct sig_osc_FastLFSine { + float sampleRate; + float f; + float sinZ; + float cosZ; +}; + +/** + * @brief Initializes a FastLFSine oscillator. + * The default frequency is 1 Hz. + * + * @param self the oscillator to initialize + */ +void sig_osc_FastLFSine_init(struct sig_osc_FastLFSine* self, + float sampleRate); + +/** + * @brief Updates the frequency coefficient for the specified frequency. + * + * @param self the oscillator instance + * @param frequency the frequency + * @param sampleRate the current sample rate + */ +void sig_osc_FastLFSine_setFrequency(struct sig_osc_FastLFSine* self, + float frequency); + +/** + * @brief Updates the frequency coefficient using an approximation + * that is suitable for low frequencies. + * + * @param self the oscillator instance + * @param frequency the frequency + * @param sampleRate the current sample rate + */ +void sig_osc_FastLFSine_setFrequencyFast(struct sig_osc_FastLFSine* self, + float frequency); + +/** + * @brief Generates new values for the sinZ and cosZ delays, + * which provide the output of the system. cosZ is 90 degrees offset from sinZ. + * + * See: + * https://www.earlevel.com/DigitalAudio/images/StateVarLfoBlock.gif + * + * @param self the oscillator instance + */ +void sig_osc_FastLFSine_generate(struct sig_osc_FastLFSine* self); + /** * An AudioSettings structure holds key information * about the current configuration of the audio system, @@ -590,6 +746,7 @@ void sig_List_destroy(struct sig_Allocator* allocator, struct sig_List* self); + /** * Allocates a new AudioSettings instance with * the values from sig_DEFAULT_AUDIO_SETTINGS. @@ -614,6 +771,7 @@ void sig_AudioSettings_destroy(struct sig_Allocator* allocator, struct sig_SignalContext { struct sig_AudioSettings* audioSettings; struct sig_Buffer* emptyBuffer; + struct sig_DelayLine* oneSampleDelayLine; struct sig_dsp_ConstantValue* silence; struct sig_dsp_ConstantValue* unity; }; @@ -763,13 +921,171 @@ float_array_ptr sig_AudioBlock_newSilent(struct sig_Allocator* allocator, void sig_AudioBlock_destroy(struct sig_Allocator* allocator, float_array_ptr self); + +/** + * @brief A modulatable delay line + * with support for comb and allpass configurations. + * + */ +struct sig_DelayLine { + struct sig_Buffer* buffer; + size_t writeIdx; +}; + +struct sig_DelayLine* sig_DelayLine_new(struct sig_Allocator* allocator, + size_t maxDelayLength); + +struct sig_DelayLine* sig_DelayLine_newSeconds(struct sig_Allocator* allocator, + struct sig_AudioSettings* audioSettings, float maxDelaySecs); + +struct sig_DelayLine* sig_DelayLine_newWithTransferredBuffer( + struct sig_Allocator* allocator, struct sig_Buffer* buffer); + +void sig_DelayLine_init(struct sig_DelayLine* self); + +typedef void (*sig_DelayLine_readFn)(void* signal); + +/** + * @brief Reads from the delay line without interpolation. + * This function does not support fractional delay times. + * + * @param self the delay line to read from + * @param readPos the position (in samples) at which to read from the delay + * @return float the output of the delay line + */ +float sig_DelayLine_readAt(struct sig_DelayLine* self, size_t readPos); + +/** + * @brief Reads from the delay line using linear interpolation. + * This function supports fractional delay times. + * + * @param self the delay line to read from + * @param readPos the position (in samples) at which to read from the delay + * @return float the output of the delay line + */ +float sig_DelayLine_linearReadAt(struct sig_DelayLine* self, float readPos); + +/** + * @brief Reads from the delay line using cubic interpolation. + * This function supports fractional delay times. + * + * @param self the delay line to read from + * @param readPos the position (in samples) at which to read from the delay + * @return float the output of the delay line + */ +float sig_DelayLine_cubicReadAt(struct sig_DelayLine* self, float readPos); + +/** + * @brief Reads from the delay line using allpass interpolation. + * This function supports fractional delay times. + * + * @param self the delay line to read from + * @param readPos the position (in samples) to read from the delay at + * @param previousSample the previous interpolated sample from the delay line + * @return float the output of the delay line + */ +float sig_DelayLine_allpassReadAt(struct sig_DelayLine* self, + float readPos, float previousSample); + +/** + * @brief Reads from the delay line without interpolation. + * This function does not support fractional delay times, and will truncate + * the delay time tap down to the nearest sample. + * + * @param self the delay line to read from + * @param tapTime the time (in seconds) at which to read from the delay + * @param sampleRate the current sample rate + * @return float the output of the delay line + */ +float sig_DelayLine_readAtTime(struct sig_DelayLine* self, float source, + float tapTime, float sampleRate); + +/** + * @brief Reads from the delay line at the specified time + * (in seconds) using linear interpolation. + * This function supports fractional delay times. + * + * @param self the delay line to read from + * @param tapTime the time (in seconds) at which to read from the delay + * @param sampleRate the current sample rate + * @return float the output of the delay line + */ +float sig_DelayLine_linearReadAtTime(struct sig_DelayLine* self, float source, + float tapTime, float sampleRate); + +/** + * @brief Reads from the delay line at the specified time + * (in seconds) using cubic interpolation. + * This function supports fractional delay times. + * + * @param self the delay line to read from + * @param tapTime the time (in seconds) at which to read from the delay + * @param sampleRate the current sample rate + * @return float the output of the delay line + */ +float sig_DelayLine_cubicReadAtTime(struct sig_DelayLine* self, float source, + float tapTime, float sampleRate); + +/** + * @brief Reads from the delay line at the specified time + * (in seconds) using allpass interpolation. + * This function supports fractional delay times. + * + * @param self the delay line to read from + * @param tapTime the time (in seconds) at which to read from the delay + * @param sampleRate the current sample rate + * @param previousSample the previous interpolated sample from the delay line + * @return float the output of the delay line + */ +float sig_DelayLine_allpassReadAtTime(struct sig_DelayLine* self, + float source, float tapTime, float sampleRate, float previousSample); + +float sig_DelayLine_readAtTimes(struct sig_DelayLine* self, float source, + float* tapTimes, float* tapGains, size_t numTaps, + float sampleRate, float timeScale); + +float sig_DelayLine_linearReadAtTimes(struct sig_DelayLine* self, + float source, float* tapTimes, float* tapGains, size_t numTaps, + float sampleRate, float timeScale); + +float sig_DelayLine_cubicReadAtTimes(struct sig_DelayLine* self, + float source, float* tapTimes, float* tapGains, size_t numTaps, + float sampleRate, float timeScale); + +void sig_DelayLine_write(struct sig_DelayLine* self, float sample); + +float sig_DelayLine_calcFeedbackGain(float delayTime, float decayTime); + +float sig_DelayLine_feedback(float sample, float read, float g); + +float sig_DelayLine_comb(struct sig_DelayLine* self, float sample, + size_t readPos, float g); + +float sig_DelayLine_cubicComb(struct sig_DelayLine* self, float sample, + float readPos, float g); + +float sig_DelayLine_allpass(struct sig_DelayLine* self, float sample, + size_t readPos, float g); + +float sig_DelayLine_linearAllpass(struct sig_DelayLine* self, + float sample, float readPos, float g); + +float sig_DelayLine_cubicAllpass(struct sig_DelayLine* self, float sample, + float readPos, float g); + +void sig_DelayLine_destroy(struct sig_Allocator* allocator, + struct sig_DelayLine* self); + +float sig_linearXFade(float left, float right, float mix); + + + // TODO: Should the signal argument at least be defined // as a struct sig_dsp_Signal*, rather than void*? // Either way, this is cast by the implementation to whatever // concrete Signal type is appropriate. typedef void (*sig_dsp_generateFn)(void* signal); - struct sig_dsp_Signal { struct sig_AudioSettings* audioSettings; sig_dsp_generateFn generate; @@ -879,6 +1195,7 @@ void sig_dsp_ConstantValue_destroy(struct sig_Allocator* allocator, struct sig_dsp_ConstantValue* self); + struct sig_dsp_Abs_Inputs { float_array_ptr source; }; @@ -898,6 +1215,33 @@ void sig_dsp_Abs_destroy(struct sig_Allocator* allocator, struct sig_dsp_Abs* self); + +struct sig_dsp_ScaleOffset_Inputs { + float_array_ptr source; +}; + +struct sig_dsp_ScaleOffset_Parameters { + float scale; + float offset; +}; + +struct sig_dsp_ScaleOffset { + struct sig_dsp_Signal signal; + struct sig_dsp_ScaleOffset_Inputs inputs; + struct sig_dsp_ScaleOffset_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; +}; + +struct sig_dsp_ScaleOffset* sig_dsp_ScaleOffset_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_ScaleOffset_init(struct sig_dsp_ScaleOffset* self, + struct sig_SignalContext* context); +void sig_dsp_ScaleOffset_generate(void* signal); +void sig_dsp_ScaleOffset_destroy(struct sig_Allocator* allocator, + struct sig_dsp_ScaleOffset* self); + + + struct sig_dsp_BinaryOp_Inputs { float_array_ptr left; float_array_ptr right; @@ -922,6 +1266,14 @@ void sig_dsp_Add_generate(void* signal); void sig_dsp_Add_destroy(struct sig_Allocator* allocator, struct sig_dsp_BinaryOp* self); +struct sig_dsp_BinaryOp* sig_dsp_Sub_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_Sub_init(struct sig_dsp_BinaryOp* self, + struct sig_SignalContext* context); +void sig_dsp_Sub_generate(void* signal); +void sig_dsp_Sub_destroy(struct sig_Allocator* allocator, + struct sig_dsp_BinaryOp* self); + struct sig_dsp_BinaryOp* sig_dsp_Mul_new( struct sig_Allocator* allocator, struct sig_SignalContext* context); void sig_dsp_Mul_init(struct sig_dsp_BinaryOp* self, @@ -977,6 +1329,8 @@ struct sig_dsp_Accumulate_Inputs { struct sig_dsp_Accumulate_Parameters { float accumulatorStart; + float wrap; + float maxValue; }; /** @@ -1223,6 +1577,33 @@ void sig_dsp_Smooth_destroy(struct sig_Allocator* allocator, struct sig_dsp_Smooth* self); + +struct sig_dsp_EMA_Inputs { + float_array_ptr source; +}; + +struct sig_dsp_EMA_Parameters { + float alpha; +}; + +struct sig_dsp_EMA { + struct sig_dsp_Signal signal; + struct sig_dsp_EMA_Inputs inputs; + struct sig_dsp_EMA_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; + float previousSample; +}; + +void sig_dsp_EMA_init(struct sig_dsp_EMA* self, + struct sig_SignalContext* context); +struct sig_dsp_EMA* sig_dsp_EMA_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_EMA_generate(void* signal); +void sig_dsp_EMA_destroy(struct sig_Allocator* allocator, + struct sig_dsp_EMA* self); + + + enum sig_dsp_OnePole_Mode { sig_dsp_OnePole_Mode_HIGH_PASS, sig_dsp_OnePole_Mode_LOW_PASS, @@ -1446,13 +1827,44 @@ void sig_dsp_DustGate_destroy(struct sig_Allocator* allocator, struct sig_dsp_DustGate* self); -struct sig_dsp_ClockFreqDetector_Inputs { +/** + * @brief Inputs for a ClockDetector. + */ +struct sig_dsp_ClockDetector_Inputs { + /** + * @brief The incoming clock signal. + */ float_array_ptr source; }; -struct sig_dsp_ClockFreqDetector_Parameters { +struct sig_dsp_ClockDetector_Outputs { + /** + * @brief The detected clock frequency in pulses per second (i.e. Hz). + */ + float_array_ptr main; + + /** + * @brief The detected clock frequency in beats per minute. + */ + float_array_ptr bpm; +}; + +void sig_dsp_ClockDetector_Outputs_newAudioBlocks( + struct sig_Allocator* allocator, + struct sig_AudioSettings* audioSettings, + struct sig_dsp_ClockDetector_Outputs* outputs); + +void sig_dsp_ClockDetector_Outputs_destroyAudioBlocks( + struct sig_Allocator* allocator, + struct sig_dsp_ClockDetector_Outputs* outputs); + +struct sig_dsp_ClockDetector_Parameters { + /** + * @brief The minimum value that a trigger must reach to be detected + * as a clock pulse. + * + */ float threshold; - float timeoutDuration; }; /** @@ -1463,27 +1875,29 @@ struct sig_dsp_ClockFreqDetector_Parameters { * Inputs: * - source the incoming clock signal */ -struct sig_dsp_ClockFreqDetector { +struct sig_dsp_ClockDetector { struct sig_dsp_Signal signal; - struct sig_dsp_ClockFreqDetector_Inputs inputs; - struct sig_dsp_ClockFreqDetector_Parameters parameters; - struct sig_dsp_Signal_SingleMonoOutput outputs; + struct sig_dsp_ClockDetector_Inputs inputs; + struct sig_dsp_ClockDetector_Parameters parameters; + struct sig_dsp_ClockDetector_Outputs outputs; float previousTrigger; bool isRisingEdge; + uint8_t numPulsesDetected; uint32_t samplesSinceLastPulse; float clockFreq; uint32_t pulseDurSamples; }; -void sig_dsp_ClockFreqDetector_init( - struct sig_dsp_ClockFreqDetector* self, +void sig_dsp_ClockDetector_init( + struct sig_dsp_ClockDetector* self, struct sig_SignalContext* context); -struct sig_dsp_ClockFreqDetector* sig_dsp_ClockFreqDetector_new( +struct sig_dsp_ClockDetector* sig_dsp_ClockDetector_new( struct sig_Allocator* allocator, struct sig_SignalContext* context); -void sig_dsp_ClockFreqDetector_generate(void* signal); -void sig_dsp_ClockFreqDetector_destroy(struct sig_Allocator* allocator, - struct sig_dsp_ClockFreqDetector* self); +void sig_dsp_ClockDetector_generate(void* signal); +void sig_dsp_ClockDetector_destroy(struct sig_Allocator* allocator, + struct sig_dsp_ClockDetector* self); + struct sig_dsp_LinearToFreq_Inputs { @@ -1552,6 +1966,7 @@ void sig_dsp_List_Outputs_destroyAudioBlocks(struct sig_Allocator* allocator, struct sig_dsp_List_Parameters { float wrap; float normalizeIndex; + float interpolate; }; struct sig_dsp_List { @@ -1593,6 +2008,8 @@ struct sig_dsp_LinearMap* sig_dsp_LinearMap_new( struct sig_Allocator* allocator, struct sig_SignalContext* context); void sig_dsp_LinearMap_init(struct sig_dsp_LinearMap* self, struct sig_SignalContext* context); +float sig_dsp_List_constrain(bool shouldWrap, float index, + float lastIndex, float listLength); void sig_dsp_LinearMap_generate(void* signal); void sig_dsp_LinearMap_destroy(struct sig_Allocator* allocator, struct sig_dsp_LinearMap* self); @@ -1731,12 +2148,6 @@ struct sig_dsp_Ladder_Parameters { * higher values will amplify the lower frequencies when resonance is high. */ float passbandGain; - - /** - * @brief Input gain applied prior to the filter, - * allowing for greater saturation levels (best between 0.0-4.0) - */ - float overdrive; }; /** @@ -1827,6 +2238,213 @@ void sig_dsp_TiltEQ_destroy(struct sig_Allocator* allocator, struct sig_dsp_TiltEQ* self); + +struct sig_dsp_Delay_Inputs { + float_array_ptr source; + float_array_ptr delayTime; +}; + +struct sig_dsp_Delay { + struct sig_dsp_Signal signal; + struct sig_dsp_Delay_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; + + struct sig_DelayLine* delayLine; +}; + +struct sig_dsp_Delay* sig_dsp_Delay_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_Delay_init(struct sig_dsp_Delay* self, + struct sig_SignalContext* context); +void sig_dsp_Delay_read(struct sig_dsp_Delay* self, float source, + size_t i); +void sig_dsp_Delay_generate(void* signal); +void sig_dsp_Delay_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Delay* self); + +struct sig_dsp_Delay* sig_dsp_DelayTap_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_DelayTap_init(struct sig_dsp_Delay* self, + struct sig_SignalContext* context); +void sig_dsp_DelayTap_generate(void* signal); +void sig_dsp_DelayTap_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Delay* self); + +struct sig_dsp_DelayWrite_Inputs { + float_array_ptr source; +}; + +struct sig_dsp_DelayWrite { + struct sig_dsp_Signal signal; + struct sig_dsp_DelayWrite_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; + + struct sig_DelayLine* delayLine; +}; + +struct sig_dsp_DelayWrite* sig_dsp_DelayWrite_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_DelayWrite_init(struct sig_dsp_DelayWrite* self, + struct sig_SignalContext* context); +void sig_dsp_DelayWrite_generate(void* signal); +void sig_dsp_DelayWrite_destroy(struct sig_Allocator* allocator, + struct sig_dsp_DelayWrite* self); + + +struct sig_dsp_Comb_Inputs { + float_array_ptr source; + float_array_ptr delayTime; + float_array_ptr feedbackGain; + float_array_ptr lpfCoefficient; +}; + +struct sig_dsp_Comb { + struct sig_dsp_Signal signal; + struct sig_dsp_Comb_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; + + struct sig_DelayLine* delayLine; + float previousSample; +}; + +struct sig_dsp_Comb* sig_dsp_Comb_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_Comb_init(struct sig_dsp_Comb* self, + struct sig_SignalContext* context); +void sig_dsp_Comb_generate(void* signal); +void sig_dsp_Comb_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Comb* self); + + + +struct sig_dsp_Allpass_Inputs { + float_array_ptr source; + float_array_ptr delayTime; + float_array_ptr g; +}; + +struct sig_dsp_Allpass { + struct sig_dsp_Signal signal; + struct sig_dsp_Allpass_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; + + struct sig_DelayLine* delayLine; +}; + +struct sig_dsp_Allpass* sig_dsp_Allpass_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_Allpass_init(struct sig_dsp_Allpass* self, + struct sig_SignalContext* context); +void sig_dsp_Allpass_generate(void* signal); +void sig_dsp_Allpass_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Allpass* self); + + + +struct sig_dsp_Chorus_Inputs { + float_array_ptr source; + float_array_ptr delayTime; + float_array_ptr speed; + float_array_ptr width; + float_array_ptr feedbackGain; + float_array_ptr feedforwardGain; + float_array_ptr blend; +}; + +struct sig_dsp_Chorus_Outputs { + float_array_ptr main; + float_array_ptr modulator; +}; + +struct sig_dsp_Chorus { + struct sig_dsp_Signal signal; + struct sig_dsp_Chorus_Inputs inputs; + struct sig_dsp_Chorus_Outputs outputs; + + struct sig_DelayLine* delayLine; + struct sig_osc_FastLFSine modulator; + float previousFixedOutput; + float previousModulatedOutput; +}; + +struct sig_dsp_Chorus* sig_dsp_Chorus_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_Chorus_init(struct sig_dsp_Chorus* self, + struct sig_SignalContext* context); +void sig_dsp_Chorus_generate(void* signal); +void sig_dsp_Chorus_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Chorus* self); + + + +struct sig_dsp_LinearXFade_Inputs { + float_array_ptr left; + float_array_ptr right; + float_array_ptr mix; +}; + +struct sig_dsp_LinearXFade { + struct sig_dsp_Signal signal; + struct sig_dsp_LinearXFade_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; +}; + +struct sig_dsp_LinearXFade* sig_dsp_LinearXFade_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_LinearXFade_init(struct sig_dsp_LinearXFade* self, + struct sig_SignalContext* context); +void sig_dsp_LinearXFade_generate(void* signal); +void sig_dsp_LinearXFade_destroy(struct sig_Allocator* allocator, + struct sig_dsp_LinearXFade* self); + + + +// TODO: Don't hardcode these. +#define sig_dsp_Calibrator_NUM_STAGES 6 +#define sig_dsp_Calibrator_TARGET_VALUES {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 4.75f} + +struct sig_dsp_Calibrator_Inputs { + float_array_ptr source; + float_array_ptr gate; +}; + +struct sig_dsp_Calibrator_Node { + float target; + size_t numSamplesRecorded; + float min; + float max; + float sum; + float avg; + float diff; +}; + +void sig_dsp_Calibrator_Node_init(struct sig_dsp_Calibrator_Node* nodes, + float* targetValues, size_t numNodes); + +size_t sig_dsp_Calibrator_locateIntervalForValue(float x, + struct sig_dsp_Calibrator_Node* nodes, size_t numNodes); + +float sig_dsp_Calibrator_fitValueToCalibrationData(float x, + struct sig_dsp_Calibrator_Node* nodes, size_t numNodes); + +struct sig_dsp_Calibrator { + struct sig_dsp_Signal signal; + struct sig_dsp_Calibrator_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; + + struct sig_dsp_Calibrator_Node nodes[sig_dsp_Calibrator_NUM_STAGES]; + float previousGate; + size_t stage; +}; + +struct sig_dsp_Calibrator* sig_dsp_Calibrator_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_Calibrator_init(struct sig_dsp_Calibrator* self, + struct sig_SignalContext* context); +void sig_dsp_Calibrator_generate(void* signal); +void sig_dsp_Calibrator_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Calibrator* self); + #ifdef __cplusplus } #endif diff --git a/libsignaletic/src/libsignaletic.c b/libsignaletic/src/libsignaletic.c index 48b3d57..6c0071c 100644 --- a/libsignaletic/src/libsignaletic.c +++ b/libsignaletic/src/libsignaletic.c @@ -83,19 +83,36 @@ inline float sig_linearMap(float value, return (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin; } -uint16_t sig_unipolarToUint12(float sample) { +extern inline uint16_t sig_unipolarToUint12(float sample) { return (uint16_t) (sample * 4095.0f); } -uint16_t sig_bipolarToUint12(float sample) { +extern inline uint16_t sig_bipolarToUint12(float sample) { float normalized = sample * 0.5 + 0.5; return (uint16_t) (normalized * 4095.0f); } -uint16_t sig_bipolarToInvUint12(float sample) { +extern inline uint16_t sig_bipolarToInvUint12(float sample) { return sig_bipolarToUint12(-sample); } +extern inline float sig_uint16ToBipolar(uint16_t sample) { + float normalized = (float) (sample / 65535.0f); + float scaled = normalized * 2.0f - 1.0f; + + return scaled; +} + +extern inline float sig_uint16ToUnipolar(uint16_t sample) { + float normalized = (float) (sample / 65535.0f); + + return normalized; +} + +extern inline float sig_invUint16ToBipolar(uint16_t sample) { + return -sig_uint16ToBipolar(sample); +} + float sig_midiToFreq(float midiNum) { return powf(2, (midiNum - 69.0f) / 12.0f) * 440.0f; } @@ -112,6 +129,56 @@ float sig_freqToLinear(float freq, float middleFreq) { return (logf(freq / middleFreq) / sig_LOG2); } +// TODO: Unit tests. +inline float sig_sum(float_array_ptr values, size_t length) { + float sum = 0; + for (size_t i = 0; i < length; i++) { + sum += FLOAT_ARRAY(values)[i]; + } + + return sum; +} + +// TODO: Unit tests. +inline size_t sig_indexOfMin(float_array_ptr values, size_t length) { + size_t indexOfMin = 0; + + if (length < 1) { + return indexOfMin; + } + + float minValue = FLOAT_ARRAY(values)[0]; + for (size_t i = 1; i < length; i++) { + float currentValue = FLOAT_ARRAY(values)[i]; + if (currentValue < minValue) { + indexOfMin = i; + minValue = currentValue; + } + } + + return indexOfMin; +} + +// TODO: Unit tests. +inline size_t sig_indexOfMax(float_array_ptr values, size_t length) { + size_t indexOfMax = 0; + + if (length < 1) { + return indexOfMax; + } + + float maxValue = FLOAT_ARRAY(values)[0]; + for (size_t i = 1; i < length; i++) { + float currentValue = FLOAT_ARRAY(values)[i]; + if (currentValue > maxValue) { + indexOfMax = i; + maxValue = currentValue; + } + } + + return indexOfMax; +} + float sig_randomFill(size_t i, float_array_ptr array) { return sig_randf(); } @@ -140,8 +207,6 @@ float sig_interpolate_linear(float idx, float_array_ptr table, int32_t idxIntegral = (int32_t) idx; float idxFractional = idx - (float) idxIntegral; float a = FLOAT_ARRAY(table)[idxIntegral]; - // TODO: Do we want to wrap around the end like this, - // or should we expect users to provide us with idx within bounds? float b = FLOAT_ARRAY(table)[(idxIntegral + 1) % length]; return a + (b - a) * idxFractional; @@ -152,9 +217,6 @@ float sig_interpolate_cubic(float idx, float_array_ptr table, size_t length) { size_t idxIntegral = (size_t) idx; float idxFractional = idx - (float) idxIntegral; - - // TODO: As above, are these modulo operations required, - // or should we expect users to provide us in-bound values? const size_t i0 = idxIntegral % length; const float xm1 = FLOAT_ARRAY(table)[i0 > 0 ? i0 - 1 : length - 1]; const float x0 = FLOAT_ARRAY(table)[i0]; @@ -170,10 +232,37 @@ float sig_interpolate_cubic(float idx, float_array_ptr table, idxFractional + x0; } +// TODO: Unit tests. +inline float sig_filter_mean(float_array_ptr values, size_t length) { + float sum = sig_sum(values, length); + return sum / (float) length; +} + +// TODO: Unit tests. +inline float sig_filter_meanExcludeMinMax(float_array_ptr values, + size_t length) { + if (length < 1) { + return 0.0f; + } else if (length < 3) { + return sig_filter_mean(values, length); + } + + float min = FLOAT_ARRAY(values)[sig_indexOfMin(values, length)]; + float max = FLOAT_ARRAY(values)[sig_indexOfMax(values, length)]; + float exclude = min + max; + float sum = sig_sum(values, length) - exclude; + return sum / (float) (length - 2); +} + +// TODO: Unit tests. +inline float sig_filter_ema(float current, float previous, float a) { + return (a * current) + (1 - a) * previous; +} + inline float sig_filter_onepole(float current, float previous, float b0, float a1) { return b0 * current - a1 * previous; -}; +} inline float sig_filter_onepole_HPF_calculateA1( float frequency, float sampleRate) { @@ -204,6 +293,20 @@ inline float sig_filter_smooth_calculateCoefficient(float time, return expf(sig_LOG0_001 / (time * sampleRate)); } + +void sig_filter_Smooth_init(struct sig_filter_Smooth* self, float coeff) { + self->coeff = coeff; + self->previous = 0; +} + +inline float sig_filter_Smooth_generate(struct sig_filter_Smooth* self, + float value) { + float smoothed = sig_filter_smooth(value, self->previous, self->coeff); + self->previous = smoothed; + + return smoothed; +} + // TODO: Unit tests. float sig_waveform_sine(float phase) { return sinf(phase); @@ -234,6 +337,33 @@ float sig_waveform_triangle(float phase) { return 2.0f * (val - 0.5f); } + + +void sig_osc_FastLFSine_init(struct sig_osc_FastLFSine* self, + float sampleRate) { + self->sampleRate = sampleRate; + self->sinZ = 0.0f; + self->cosZ = 1.0f; + sig_osc_FastLFSine_setFrequency(self, 1.0f); +} + +inline void sig_osc_FastLFSine_setFrequency(struct sig_osc_FastLFSine* self, + float frequency) { + self->f = 2.0f * sinf(sig_PI * frequency / self->sampleRate); +} + +inline void sig_osc_FastLFSine_setFrequencyFast(struct sig_osc_FastLFSine* self, + float frequency) { + self->f = sig_TWOPI * frequency / self->sampleRate; +} + +inline void sig_osc_FastLFSine_generate(struct sig_osc_FastLFSine* self) { + self->sinZ = self->sinZ + self->f * self->cosZ; + self->cosZ = self->cosZ - self->f * self->sinZ; +} + + + // TODO: Implement enough test coverage for sig_Allocator // to support a switch from TLSF to another memory allocator // implementation sometime in the future (gh-26). @@ -244,6 +374,9 @@ void sig_TLSFAllocator_init(struct sig_Allocator* allocator) { void* sig_TLSFAllocator_malloc(struct sig_Allocator* allocator, size_t size) { + // TODO: TLSF will return a null pointer if we're out of memory. + // The Allocator API needs to be extended to always take a Status object + // for any operation that can fail. return tlsf_malloc(allocator->heap->memory, size); } @@ -288,7 +421,8 @@ void sig_List_insert(struct sig_List* self, } if (index == self->length) { - return sig_List_append(self, item, status); + sig_List_append(self, item, status); + return; } if (self->length >= self->capacity) { @@ -393,6 +527,7 @@ void sig_AudioSettings_destroy(struct sig_Allocator* allocator, } + struct sig_SignalContext* sig_SignalContext_new( struct sig_Allocator* allocator, struct sig_AudioSettings* audioSettings) { struct sig_SignalContext* self = sig_MALLOC(allocator, @@ -403,6 +538,9 @@ struct sig_SignalContext* sig_SignalContext_new( struct sig_Buffer* emptyBuffer = sig_Buffer_new(allocator, 0); self->emptyBuffer = emptyBuffer; + struct sig_DelayLine* oneSampleDelayLine = sig_DelayLine_new(allocator, 1); + self->oneSampleDelayLine = oneSampleDelayLine; + struct sig_dsp_ConstantValue* silence = sig_dsp_ConstantValue_new( allocator, self, 0.0f); self->silence = silence; @@ -416,6 +554,8 @@ struct sig_SignalContext* sig_SignalContext_new( void sig_SignalContext_destroy(struct sig_Allocator* allocator, struct sig_SignalContext* self) { + sig_Buffer_destroy(allocator, self->emptyBuffer); + sig_DelayLine_destroy(allocator, self->oneSampleDelayLine); sig_dsp_ConstantValue_destroy(allocator, self->silence); sig_dsp_ConstantValue_destroy(allocator, self->unity); allocator->impl->free(allocator, self); @@ -485,6 +625,7 @@ void sig_Buffer_fillWithValue(struct sig_Buffer* self, float value) { sig_fillWithValue(self->samples, self->length, value); } + void sig_Buffer_fillWithSilence(struct sig_Buffer* self) { sig_fillWithSilence(self->samples, self->length); } @@ -526,9 +667,7 @@ void sig_Buffer_destroy(struct sig_Allocator* allocator, struct sig_Buffer* self struct sig_Buffer* sig_BufferView_new( struct sig_Allocator* allocator, struct sig_Buffer* buffer, size_t startIdx, size_t length) { - struct sig_Buffer* self = (struct sig_Buffer*) - allocator->impl->malloc(allocator, - sizeof(struct sig_Buffer)); + struct sig_Buffer* self = sig_MALLOC(allocator, struct sig_Buffer); // TODO: Need to signal an error rather than // just returning a null pointer and a length of zero. @@ -551,6 +690,266 @@ void sig_BufferView_destroy(struct sig_Allocator* allocator, } + +struct sig_DelayLine* sig_DelayLine_new(struct sig_Allocator* allocator, + size_t maxDelayLength) { + struct sig_DelayLine* self = sig_MALLOC(allocator, struct sig_DelayLine); + self->buffer = sig_Buffer_new(allocator, maxDelayLength); + sig_DelayLine_init(self); + + return self; +} + +struct sig_DelayLine* sig_DelayLine_newSeconds(struct sig_Allocator* allocator, + struct sig_AudioSettings* audioSettings, float maxDelaySecs) { + size_t maxDelayLength = (size_t) roundf( + maxDelaySecs * audioSettings->sampleRate); + + return sig_DelayLine_new(allocator, maxDelayLength); +} + +struct sig_DelayLine* sig_DelayLine_newWithTransferredBuffer( + struct sig_Allocator* allocator, struct sig_Buffer* buffer) { + struct sig_DelayLine* self = sig_MALLOC(allocator, struct sig_DelayLine); + self->buffer = buffer; + sig_DelayLine_init(self); + + return self; +} + +void sig_DelayLine_init(struct sig_DelayLine* self) { + self->writeIdx = 0; + sig_Buffer_fillWithSilence(self->buffer); // Zero the delay line. +} + +inline float sig_DelayLine_readAt(struct sig_DelayLine* self, size_t readPos) { + size_t idx = (self->writeIdx + readPos) % self->buffer->length; + return FLOAT_ARRAY(self->buffer->samples)[idx]; +} + +inline float sig_DelayLine_linearReadAt(struct sig_DelayLine* self, + float readPos) { + size_t maxDelayLength = self->buffer->length; + float* delayLineSamples = self->buffer->samples; + int32_t integ = (int32_t) readPos; + float frac = readPos - (float) integ; + float a = delayLineSamples[(self->writeIdx + integ) % maxDelayLength]; + float b = delayLineSamples[(self->writeIdx + integ + 1) % maxDelayLength]; + + return a + (b - a) * frac; +} + +inline float sig_DelayLine_cubicReadAt(struct sig_DelayLine* self, + float readPos) { + size_t maxDelayLength = self->buffer->length; + float* delayLineSamples = self->buffer->samples; + + int32_t integ = (int32_t) readPos; + float frac = readPos - (float) integ; + int32_t t = (self->writeIdx + integ + maxDelayLength); + float xm1 = delayLineSamples[(t - 1) % maxDelayLength]; + float x0 = delayLineSamples[t % maxDelayLength]; + float x1 = delayLineSamples[(t + 1) % maxDelayLength]; + float x2 = delayLineSamples[(t + 2) % maxDelayLength]; + float c = (x1 - xm1) * 0.5f; + float v = x0 - x1; + float w = c + v; + float a = w + v + (x2 - x0) * 0.5f; + float bNeg = w + a; + + return (((a * frac) - bNeg) * frac + c) * frac + x0; +} + +inline float sig_DelayLine_allpassReadAt(struct sig_DelayLine* self, + float readPos, float previousSample) { + size_t maxDelayLength = self->buffer->length; + float* delayLineSamples = self->buffer->samples; + int32_t integ = (int32_t) readPos; + float frac = readPos - (float) integ; + float invFrac = 1.0f - frac; + float a = delayLineSamples[(self->writeIdx + integ) % maxDelayLength]; + float b = delayLineSamples[(self->writeIdx + integ + 1) % maxDelayLength]; + + return b + invFrac * a - invFrac * previousSample; +} + +#define sig_DelayLine_readAtTime_IMPL(self, source, tapTime, sampleRate,\ + readFn)\ + float sample;\ + if (tapTime <= 0.0f) {\ + sample = source;\ + } else {\ + float readPos = tapTime * sampleRate;\ + float maxDelayLength = (float) self->buffer->length;\ + if (readPos >= maxDelayLength) {\ + readPos = maxDelayLength - 1;\ + }\ + sample = readFn(self, readPos);\ + }\ + return sample + +inline float sig_DelayLine_readAtTime(struct sig_DelayLine* self, float source, + float tapTime, float sampleRate) { + sig_DelayLine_readAtTime_IMPL(self, source, tapTime, sampleRate, + sig_DelayLine_readAt); +} + +inline float sig_DelayLine_linearReadAtTime(struct sig_DelayLine* self, + float source, float tapTime, float sampleRate) { + sig_DelayLine_readAtTime_IMPL(self, source, tapTime, sampleRate, + sig_DelayLine_linearReadAt); +} + +inline float sig_DelayLine_cubicReadAtTime(struct sig_DelayLine* self, + float source, float tapTime, float sampleRate) { + sig_DelayLine_readAtTime_IMPL(self, source, tapTime, sampleRate, + sig_DelayLine_cubicReadAt); +} + +inline float sig_DelayLine_allpassReadAtTime(struct sig_DelayLine* self, + float source, float tapTime, float sampleRate, float previousSample) { + // TODO: Cut and pasted from the sig_DelayLine_readAtTime_IMPL macro above. + float sample; + if (tapTime <= 0.0f) { + sample = source; + } else { + float readPos = tapTime * sampleRate; + float maxDelayLength = (float) self->buffer->length; + if (readPos >= maxDelayLength) { + readPos = maxDelayLength - 1; + } + sample = sig_DelayLine_allpassReadAt(self, readPos, previousSample); + } + + return sample; +} + +#define sig_DelayLine_readAtTimes_IMPL(self, source, tapTimes, tapGains,\ + numTaps, sampleRate, timeScale, readFn)\ + float tapSum = 0;\ + for (size_t i = 0; i < numTaps; i++) {\ + float tapTime = FLOAT_ARRAY(tapTimes)[i];\ + float scaledTapTime = tapTime * timeScale;\ + float gain = FLOAT_ARRAY(tapGains)[i];\ + float sample;\ + if (tapTime <= 0.0f) {\ + sample = source;\ + } else {\ + float readPos = scaledTapTime * sampleRate;\ + float maxDelayLength = (float) self->buffer->length;\ + if (readPos >= maxDelayLength) {\ + readPos = maxDelayLength - 1;\ + }\ + sample = readFn(self, readPos);\ + }\ + tapSum += sample * gain;\ + }\ + return tapSum + +inline float sig_DelayLine_readAtTimes(struct sig_DelayLine* self, + float source, float* tapTimes, float* tapGains, size_t numTaps, + float sampleRate, float timeScale) { + sig_DelayLine_readAtTimes_IMPL(self, source, tapTimes, + tapGains, numTaps, sampleRate, timeScale, sig_DelayLine_readAt); +} + +inline float sig_DelayLine_linearReadAtTimes(struct sig_DelayLine* self, + float source, float* tapTimes, float* tapGains, size_t numTaps, + float sampleRate, float timeScale) { + sig_DelayLine_readAtTimes_IMPL(self, source, tapTimes, + tapGains, numTaps, sampleRate, timeScale, sig_DelayLine_linearReadAt); +} + +inline float sig_DelayLine_cubicReadAtTimes(struct sig_DelayLine* self, + float source, float* tapTimes, float* tapGains, size_t numTaps, + float sampleRate, float timeScale) { + sig_DelayLine_readAtTimes_IMPL(self, source, tapTimes, + tapGains,numTaps, sampleRate, timeScale, sig_DelayLine_cubicReadAt); +} + +inline void sig_DelayLine_write(struct sig_DelayLine* self, float sample) { + size_t maxDelayLength = self->buffer->length; + FLOAT_ARRAY(self->buffer->samples)[self->writeIdx] = sample; + self->writeIdx = (self->writeIdx - 1 + maxDelayLength) % maxDelayLength; +} + +inline float sig_DelayLine_calcFeedbackGain(float delayTime, float decayTime) { + // Convert 60dB time in secs to feedback gain (g) coefficient + // (also why is the equation in Dodge and Jerse wrong?) + if (delayTime <= 0.0f || decayTime <= 0.0f) { + return 0.0f; + } + + return expf(sig_LOG0_001 * delayTime / decayTime); +} + +inline float sig_DelayLine_feedback(float sample, float read, float g) { + return sample + (g * read); +} + +#define sig_DelayLine_comb_IMPL(self, sample, readPos, g, readFn) \ + float read = readFn(self, readPos); \ + float toWrite = sig_DelayLine_feedback(sample, read, g); \ + sig_DelayLine_write(self, toWrite); \ + return read + +inline float sig_DelayLine_comb(struct sig_DelayLine* self, float sample, + size_t readPos, float g) { + sig_DelayLine_comb_IMPL(self, sample, readPos, g, sig_DelayLine_readAt); +} + +inline float sig_DelayLine_linearComb(struct sig_DelayLine* self, float sample, + float readPos, float g) { + sig_DelayLine_comb_IMPL(self, sample, readPos, g, + sig_DelayLine_linearReadAt); +} + +inline float sig_DelayLine_cubicComb(struct sig_DelayLine* self, float sample, + float readPos, float g) { + sig_DelayLine_comb_IMPL(self, sample, readPos, g, + sig_DelayLine_cubicReadAt); +} + +#define sig_DelayLine_allpass_IMPL(self, sample, readPos, g, readFn) \ + float read = readFn(self, readPos); \ + float toWrite = sample + (g * read); \ + sig_DelayLine_write(self, toWrite); \ + return read - (g * toWrite) \ + +inline float sig_DelayLine_allpass(struct sig_DelayLine* self, float sample, + size_t readPos, float g) { + sig_DelayLine_allpass_IMPL(self, sample, readPos, g, sig_DelayLine_readAt); +} + +inline float sig_DelayLine_linearAllpass(struct sig_DelayLine* self, + float sample, float readPos, float g) { + sig_DelayLine_allpass_IMPL(self, sample, readPos, g, + sig_DelayLine_linearReadAt); +} + +inline float sig_DelayLine_cubicAllpass(struct sig_DelayLine* self, + float sample, float readPos, float g) { + sig_DelayLine_allpass_IMPL(self, sample, readPos, g, + sig_DelayLine_cubicReadAt); +} + +void sig_DelayLine_destroy(struct sig_Allocator* allocator, + struct sig_DelayLine* self) { + sig_Buffer_destroy(allocator, self->buffer); + allocator->impl->free(allocator, self); +} + +inline float sig_linearXFade(float left, float right, float mix) { + float clipped = sig_clamp(mix, -1.0f, 1.0f); + // At -1.0f, left gain should be 1.0 and right gain should be 0.0; + // at 0.0, left and right gains should be 0.5; + // At 1.0 left gain should be 0.0f and right gain should be 1.0. + float gain = clipped * 0.5f + 0.5f; + float sample = left + gain * (right - left); + + return sample; +} + void sig_dsp_Signal_init(void* signal, struct sig_SignalContext* context, sig_dsp_generateFn generate) { struct sig_dsp_Signal* self = (struct sig_dsp_Signal*) signal; @@ -724,6 +1123,51 @@ void sig_dsp_Abs_destroy(struct sig_Allocator* allocator, } + +struct sig_dsp_ScaleOffset* sig_dsp_ScaleOffset_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_ScaleOffset* self = sig_MALLOC(allocator, + struct sig_dsp_ScaleOffset); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + sig_dsp_ScaleOffset_init(self, context); + + return self; +} + +void sig_dsp_ScaleOffset_init(struct sig_dsp_ScaleOffset* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_ScaleOffset_generate); + + struct sig_dsp_ScaleOffset_Parameters parameters = { + .scale = 1.0, + .offset= 0.0f + }; + self->parameters = parameters; + + sig_CONNECT_TO_SILENCE(self, source, context); +} + +void sig_dsp_ScaleOffset_generate(void* signal) { + struct sig_dsp_ScaleOffset* self = (struct sig_dsp_ScaleOffset*) signal; + + float scale = self->parameters.scale; + float offset = self->parameters.offset; + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + FLOAT_ARRAY(self->outputs.main)[i] = source * scale + offset; + } +} + +void sig_dsp_ScaleOffset_destroy(struct sig_Allocator* allocator, + struct sig_dsp_ScaleOffset* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + + struct sig_dsp_BinaryOp* sig_dsp_BinaryOp_new(struct sig_Allocator* allocator, struct sig_SignalContext* context, sig_dsp_generateFn generate) { struct sig_dsp_BinaryOp* self = sig_MALLOC(allocator, @@ -780,6 +1224,36 @@ void sig_dsp_Add_destroy(struct sig_Allocator* allocator, +struct sig_dsp_BinaryOp* sig_dsp_Sub_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + return sig_dsp_BinaryOp_new(allocator, context, *sig_dsp_Sub_generate); +} + +void sig_dsp_Sub_init(struct sig_dsp_BinaryOp* self, + struct sig_SignalContext* context) { + sig_dsp_BinaryOp_init(self, context, *sig_dsp_Sub_generate); +} + +// TODO: Unit tests. +void sig_dsp_Sub_generate(void* signal) { + struct sig_dsp_BinaryOp* self = (struct sig_dsp_BinaryOp*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float left = FLOAT_ARRAY(self->inputs.left)[i]; + float right = FLOAT_ARRAY(self->inputs.right)[i]; + float val = left - right; + + FLOAT_ARRAY(self->outputs.main)[i] = val; + } +} + +void sig_dsp_Sub_destroy(struct sig_Allocator* allocator, + struct sig_dsp_BinaryOp* self) { + sig_dsp_BinaryOp_destroy(allocator, self); +} + + + struct sig_dsp_BinaryOp* sig_dsp_Mul_new(struct sig_Allocator* allocator, struct sig_SignalContext* context) { return sig_dsp_BinaryOp_new(allocator, context, *sig_dsp_Mul_generate); @@ -883,10 +1357,15 @@ void sig_dsp_Accumulate_init(struct sig_dsp_Accumulate* self, sig_dsp_Signal_init(self, context, *sig_dsp_Accumulate_generate); struct sig_dsp_Accumulate_Parameters parameters = { - .accumulatorStart = 1.0 + .accumulatorStart = 1.0f, + .wrap = 0.0f, + .maxValue = 1.0f }; self->parameters = parameters; + // FIXME: This happens too early for users to + // override the parameter, and thus the accumulator + // is always initialized to 1. self->accumulator = parameters.accumulatorStart; self->previousReset = 0.0f; @@ -900,14 +1379,24 @@ void sig_dsp_Accumulate_generate(void* signal) { struct sig_dsp_Accumulate* self = (struct sig_dsp_Accumulate*) signal; + self->accumulator += FLOAT_ARRAY(self->inputs.source)[0]; + float reset = FLOAT_ARRAY(self->inputs.reset)[0]; + float wrap = self->parameters.wrap; + float maxValue = self->parameters.maxValue; + + // Reset the accumulator if we received a trigger + // or if we're wrapping and we've gone past the min/max value. if (reset > 0.0f && self->previousReset <= 0.0f) { - // Reset the accumulator if we received a trigger. self->accumulator = self->parameters.accumulatorStart; + } else if (wrap > 0.0f) { + if (self->accumulator > maxValue) { + self->accumulator = self->parameters.accumulatorStart; + } else if (self->accumulator < self->parameters.accumulatorStart) { + self->accumulator = self->parameters.maxValue; + } } - self->accumulator += FLOAT_ARRAY(self->inputs.source)[0]; - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { FLOAT_ARRAY(self->outputs.main)[i] = self->accumulator; } @@ -954,8 +1443,8 @@ void sig_dsp_GatedTimer_generate(void* signal) { for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { // TODO: MSVC compiler warning loss of precision. unsigned long durationSamps = (unsigned long) - FLOAT_ARRAY(self->inputs.duration)[i] * - self->signal.audioSettings->sampleRate; + (FLOAT_ARRAY(self->inputs.duration)[i] * + self->signal.audioSettings->sampleRate); float gate = FLOAT_ARRAY(self->inputs.gate)[i]; if (gate > 0.0f) { @@ -1396,7 +1885,52 @@ void sig_dsp_OnePole_generate(void* signal) { void sig_dsp_OnePole_destroy(struct sig_Allocator* allocator, struct sig_dsp_OnePole* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + + +void sig_dsp_EMA_init(struct sig_dsp_EMA* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_EMA_generate); + self->parameters.alpha = 0.1f; + self->previousSample = 0.0f; + sig_CONNECT_TO_SILENCE(self, source, context); +} + +struct sig_dsp_EMA* sig_dsp_EMA_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_EMA* self = sig_MALLOC(allocator, + struct sig_dsp_EMA); + sig_dsp_EMA_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; + +} +void sig_dsp_EMA_generate(void* signal) { + struct sig_dsp_EMA* self = (struct sig_dsp_EMA*) signal; + float previousSample = self->previousSample; + float alpha = self->parameters.alpha; + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float sample = sig_filter_ema(source, previousSample, alpha); + FLOAT_ARRAY(self->outputs.main)[i] = sample; + previousSample = sample; + } + + self->previousSample = previousSample; +} + +void sig_dsp_EMA_destroy(struct sig_Allocator* allocator, + struct sig_dsp_EMA* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); } @@ -1797,21 +2331,10 @@ struct sig_dsp_DustGate* sig_dsp_DustGate_new(struct sig_Allocator* allocator, struct sig_dsp_DustGate); self->reciprocalDensity = sig_dsp_Div_new(allocator, context); - self->reciprocalDensity->inputs.left = context->unity->outputs.main; - self->densityDurationMultiplier = sig_dsp_Mul_new(allocator, context); - self->densityDurationMultiplier->inputs.left = - self->reciprocalDensity->outputs.main; - self->dust = sig_dsp_Dust_new(allocator, context); - self->gate = sig_dsp_TimedGate_new(allocator, context); - self->gate->inputs.trigger = self->dust->outputs.main; - self->gate->inputs.duration = - self->densityDurationMultiplier->outputs.main; - sig_dsp_DustGate_init(self, context); - self->outputs.main = self->gate->outputs.main; return self; } @@ -1820,9 +2343,17 @@ struct sig_dsp_DustGate* sig_dsp_DustGate_new(struct sig_Allocator* allocator, void sig_dsp_DustGate_init(struct sig_dsp_DustGate* self, struct sig_SignalContext* context) { sig_dsp_Signal_init(self, context, *sig_dsp_DustGate_generate); + self->reciprocalDensity->inputs.left = context->unity->outputs.main; + self->densityDurationMultiplier->inputs.left = + self->reciprocalDensity->outputs.main; + self->gate->inputs.trigger = self->dust->outputs.main; + self->gate->inputs.duration = + self->densityDurationMultiplier->outputs.main; + self->outputs.main = self->gate->outputs.main; + sig_CONNECT_TO_SILENCE(self, density, context); sig_CONNECT_TO_SILENCE(self, durationPercentage, context); -}; +} void sig_dsp_DustGate_generate(void* signal) { struct sig_dsp_DustGate* self = (struct sig_dsp_DustGate*) signal; @@ -1861,63 +2392,81 @@ void sig_dsp_DustGate_destroy(struct sig_Allocator* allocator, } -void sig_dsp_ClockFreqDetector_init(struct sig_dsp_ClockFreqDetector* self, + +void sig_dsp_ClockDetector_Outputs_newAudioBlocks( + struct sig_Allocator* allocator, + struct sig_AudioSettings* audioSettings, + struct sig_dsp_ClockDetector_Outputs* outputs) { + outputs->main = sig_AudioBlock_newSilent(allocator, audioSettings); + outputs->bpm = sig_AudioBlock_newSilent(allocator, audioSettings); +} + +void sig_dsp_ClockDetector_Outputs_destroyAudioBlocks( + struct sig_Allocator* allocator, + struct sig_dsp_ClockDetector_Outputs* outputs) { + + sig_AudioBlock_destroy(allocator, outputs->main); + sig_AudioBlock_destroy(allocator, outputs->bpm); +} + +void sig_dsp_ClockDetector_init(struct sig_dsp_ClockDetector* self, struct sig_SignalContext* context) { - sig_dsp_Signal_init(self, context, *sig_dsp_ClockFreqDetector_generate); + sig_dsp_Signal_init(self, context, *sig_dsp_ClockDetector_generate); - struct sig_dsp_ClockFreqDetector_Parameters params = { - .threshold = 0.1f, - .timeoutDuration = 120.0f + struct sig_dsp_ClockDetector_Parameters params = { + .threshold = 0.04f // 0.2V, assuming 10Vpp }; self->parameters = params; self->previousTrigger = 0.0f; - self->samplesSinceLastPulse = 0; + self->isRisingEdge = false; + self->numPulsesDetected = 0; + self->samplesSinceLastPulse = (uint32_t) + self->signal.audioSettings->sampleRate; self->clockFreq = 0.0f; self->pulseDurSamples = 0; sig_CONNECT_TO_SILENCE(self, source, context); } -struct sig_dsp_ClockFreqDetector* sig_dsp_ClockFreqDetector_new( +struct sig_dsp_ClockDetector* sig_dsp_ClockDetector_new( struct sig_Allocator* allocator, struct sig_SignalContext* context) { - struct sig_dsp_ClockFreqDetector* self = sig_MALLOC(allocator, - struct sig_dsp_ClockFreqDetector); - sig_dsp_ClockFreqDetector_init(self, context); - sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + struct sig_dsp_ClockDetector* self = sig_MALLOC(allocator, + struct sig_dsp_ClockDetector); + sig_dsp_ClockDetector_init(self, context); + sig_dsp_ClockDetector_Outputs_newAudioBlocks(allocator, context->audioSettings, &self->outputs); return self; } -static inline float sig_dsp_ClockFreqDetector_calcClockFreq( +static inline float sig_dsp_ClockDetector_calcClockFreq( float sampleRate, uint32_t samplesSinceLastPulse, float prevFreq) { float freq = sampleRate / (float) samplesSinceLastPulse; - // TODO: Is an LPF good, or is a moving average better? - return sig_filter_smooth(freq, prevFreq, 0.01f); + + return freq; } -void sig_dsp_ClockFreqDetector_generate(void* signal) { - struct sig_dsp_ClockFreqDetector* self = - (struct sig_dsp_ClockFreqDetector*) signal; +void sig_dsp_ClockDetector_generate(void* signal) { + struct sig_dsp_ClockDetector* self = + (struct sig_dsp_ClockDetector*) signal; float_array_ptr source = self->inputs.source; - float_array_ptr output = self->outputs.main; float previousTrigger = self->previousTrigger; float clockFreq = self->clockFreq; bool isRisingEdge = self->isRisingEdge; + uint32_t numPulsesDetected = self->numPulsesDetected; uint32_t samplesSinceLastPulse = self->samplesSinceLastPulse; float sampleRate = self->signal.audioSettings->sampleRate; float threshold = self->parameters.threshold; - float timeoutDuration = self->parameters.timeoutDuration; uint32_t pulseDurSamples = self->pulseDurSamples; for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { samplesSinceLastPulse++; float sourceSamp = FLOAT_ARRAY(source)[i]; - if (sourceSamp > 0.0f && previousTrigger <= 0.0f) { + if (sourceSamp > 0.0f && previousTrigger < threshold) { // Start of rising edge. isRisingEdge = true; } else if (sourceSamp < previousTrigger) { @@ -1929,38 +2478,42 @@ void sig_dsp_ClockFreqDetector_generate(void* signal) { if (isRisingEdge && sourceSamp >= threshold) { // Signal is rising and threshold has been reached, // so this is a pulse. - clockFreq = sig_dsp_ClockFreqDetector_calcClockFreq( - sampleRate, samplesSinceLastPulse, clockFreq); + numPulsesDetected++; + if (numPulsesDetected > 1) { + // Need to have found at least two pulses before + // the frequency can be reliably determined. + clockFreq = sig_dsp_ClockDetector_calcClockFreq( + sampleRate, samplesSinceLastPulse, clockFreq); + numPulsesDetected = 1; + } pulseDurSamples = samplesSinceLastPulse; samplesSinceLastPulse = 0; isRisingEdge = false; - } else if (samplesSinceLastPulse > sampleRate * timeoutDuration) { - // It's been too long since we've received a pulse. - clockFreq = 0.0f; - } else if (samplesSinceLastPulse > pulseDurSamples) { - // Tempo is slowing down; recalculate it. - clockFreq = sig_dsp_ClockFreqDetector_calcClockFreq( - sampleRate, samplesSinceLastPulse, clockFreq); } - FLOAT_ARRAY(output)[i] = clockFreq; + FLOAT_ARRAY(self->outputs.main)[i] = clockFreq; + FLOAT_ARRAY(self->outputs.bpm)[i] = clockFreq * 60.0f; + previousTrigger = sourceSamp; } self->previousTrigger = previousTrigger; self->clockFreq = clockFreq; self->isRisingEdge = isRisingEdge; + self->numPulsesDetected = numPulsesDetected; self->samplesSinceLastPulse = samplesSinceLastPulse; self->pulseDurSamples = pulseDurSamples; } -void sig_dsp_ClockFreqDetector_destroy(struct sig_Allocator* allocator, - struct sig_dsp_ClockFreqDetector* self) { - sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, +void sig_dsp_ClockDetector_destroy(struct sig_Allocator* allocator, + struct sig_dsp_ClockDetector* self) { + sig_dsp_ClockDetector_Outputs_destroyAudioBlocks(allocator, &self->outputs); sig_dsp_Signal_destroy(allocator, self); } + + struct sig_dsp_LinearToFreq* sig_dsp_LinearToFreq_new( struct sig_Allocator* allocator, struct sig_SignalContext* context) { struct sig_dsp_LinearToFreq* self = sig_MALLOC(allocator, @@ -2067,9 +2620,27 @@ void sig_dsp_List_init(struct sig_dsp_List* self, sig_dsp_Signal_init(self, context, *sig_dsp_List_generate); self->parameters.wrap = 1.0f; self->parameters.normalizeIndex = 1.0f; + self->parameters.interpolate = 0.0f; sig_CONNECT_TO_SILENCE(self, index, context); } +inline float sig_dsp_List_constrain(bool shouldWrap, float index, + float lastIndex, float listLength) { + if (shouldWrap) { + while (index < 0.0f) { + index = listLength + index; + } + + while (index > lastIndex) { + index -= lastIndex; + } + } else { + index = sig_clamp(index, 0.0f, lastIndex); + } + + return index; +} + void sig_dsp_List_generate(void* signal) { struct sig_dsp_List* self = (struct sig_dsp_List*) signal; struct sig_Buffer* list = self->list; @@ -2083,6 +2654,8 @@ void sig_dsp_List_generate(void* signal) { size_t lastIndex = listLength - 1; float lastIndexF = (float) lastIndex; bool shouldWrap = self->parameters.wrap > 0.0f; + bool shouldInterpolate = self->parameters.interpolate > 0.0f; + bool shouldNormalize = self->parameters.normalizeIndex > 0.0f; if (listLength < 1) { // There's nothing in the list; just output silence. @@ -2094,16 +2667,26 @@ void sig_dsp_List_generate(void* signal) { } else { for (size_t i = 0; i < blockSize; i++) { float index = FLOAT_ARRAY(self->inputs.index)[i]; - float scaledIndex = self->parameters.normalizeIndex > 0.0f ? - index * lastIndexF : index; - float roundedIndex = roundf(scaledIndex); - float wrappedIndex = shouldWrap ? - sig_flooredfmodf(roundedIndex, listLengthF) : - sig_clamp(roundedIndex, 0.0f, lastIndexF); - - float sample = FLOAT_ARRAY(list->samples)[(size_t) wrappedIndex]; + float sample = 0.0f; + + if (shouldNormalize) { + index = index * lastIndexF; + } + + if (shouldInterpolate) { + index = sig_dsp_List_constrain(shouldWrap, index, lastIndexF, + listLengthF); + sample = sig_interpolate_linear(index, list->samples, + listLength); + } else { + index = roundf(index); + index = sig_dsp_List_constrain(shouldWrap, index, lastIndexF, + listLengthF); + sample = FLOAT_ARRAY(list->samples)[(size_t) index]; + } + FLOAT_ARRAY(self->outputs.main)[i] = sample; - FLOAT_ARRAY(self->outputs.index)[i] = wrappedIndex; + FLOAT_ARRAY(self->outputs.index)[i] = index; FLOAT_ARRAY(self->outputs.length)[i] = listLengthF; } } @@ -2400,12 +2983,11 @@ void sig_dsp_Ladder_init( sig_dsp_Signal_init(self, context, *sig_dsp_Ladder_generate); struct sig_dsp_Ladder_Parameters parameters = { - .passbandGain = 0.5f, - .overdrive = 1.0f + .passbandGain = 0.5f }; self->parameters = parameters; - self->interpolation = 2; + self->interpolation = 4; self->interpolationRecip = 1.0f / self->interpolation; self->alpha = 1.0f; self->beta[0] = self->beta[1] = self->beta[2] = self->beta[3] = 0.0f; @@ -2454,11 +3036,10 @@ void sig_dsp_Ladder_generate(void* signal) { float interpolationRecip = self->interpolationRecip; for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { - float input = FLOAT_ARRAY(self->inputs.source)[i] * - self->parameters.overdrive; + float input = FLOAT_ARRAY(self->inputs.source)[i]; float frequency = FLOAT_ARRAY(self->inputs.frequency)[i]; float resonance = FLOAT_ARRAY(self->inputs.resonance)[i]; - float totals[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float totals[5] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; float interp = 0.0f; // Recalculate coefficients if the frequency has changed. @@ -2470,33 +3051,30 @@ void sig_dsp_Ladder_generate(void* signal) { self->k = 4.0f * resonance; for (size_t os = 0; os < self->interpolation; os++) { - float u = (interp * self->prevInput + (1.0f - interp) * input) - - (self->z1[3] - self->parameters.passbandGain * input) * - self->k * self->qAdjust; + float inInterp = interp * self->prevInput + (1.0f - interp) * input; + float u = inInterp - (self->z1[3] - self->parameters.passbandGain * + inInterp) * self->k * self->qAdjust; u = sig_fastTanhf(u); - float stage1 = sig_dsp_Ladder_calcStage(self, - u, 0); - totals[0] += stage1 * interpolationRecip; - float stage2 = sig_dsp_Ladder_calcStage(self, - stage1, 1); - totals[1] += stage2 * interpolationRecip; - float stage3 = sig_dsp_Ladder_calcStage(self, - stage2, 2); - totals[2] += stage3 * interpolationRecip; - float stage4 = sig_dsp_Ladder_calcStage(self, - stage3, 3); - totals[3] += stage4 * interpolationRecip; + totals[0] = u; + float stage1 = sig_dsp_Ladder_calcStage(self, u, 0); + totals[1] += stage1 * interpolationRecip; + float stage2 = sig_dsp_Ladder_calcStage(self, stage1, 1); + totals[2] += stage2 * interpolationRecip; + float stage3 = sig_dsp_Ladder_calcStage(self, stage2, 2); + totals[3] += stage3 * interpolationRecip; + float stage4 = sig_dsp_Ladder_calcStage(self, stage3, 3); + totals[4] += stage4 * interpolationRecip; interp += interpolationRecip; } self->prevInput = input; FLOAT_ARRAY(self->outputs.main)[i] = - (input * FLOAT_ARRAY(self->inputs.inputGain)[i]) + - (totals[0] * FLOAT_ARRAY(self->inputs.pole1Gain)[i]) + - (totals[1] * FLOAT_ARRAY(self->inputs.pole2Gain)[i]) + - (totals[2] * FLOAT_ARRAY(self->inputs.pole3Gain)[i]) + - (totals[3] * FLOAT_ARRAY(self->inputs.pole4Gain)[i]); - FLOAT_ARRAY(self->outputs.twoPole)[i] = totals[1]; - FLOAT_ARRAY(self->outputs.fourPole)[i] = totals[3]; + (totals[0] * FLOAT_ARRAY(self->inputs.inputGain)[i]) + + (totals[1] * FLOAT_ARRAY(self->inputs.pole1Gain)[i]) + + (totals[2] * FLOAT_ARRAY(self->inputs.pole2Gain)[i]) + + (totals[3] * FLOAT_ARRAY(self->inputs.pole3Gain)[i]) + + (totals[4] * FLOAT_ARRAY(self->inputs.pole4Gain)[i]); + FLOAT_ARRAY(self->outputs.twoPole)[i] = totals[2]; + FLOAT_ARRAY(self->outputs.fourPole)[i] = totals[4]; } } @@ -2533,8 +3111,8 @@ void sig_dsp_TiltEQ_init(struct sig_dsp_TiltEQ* self, void sig_dsp_TiltEQ_generate(void* signal) { struct sig_dsp_TiltEQ* self = (struct sig_dsp_TiltEQ*) signal; - float amp = 8.656170; // 6.0f / log(2) - float gfactor = 5.0; // Proportional gain. + float amp = 8.656170f; // 6.0f / log(2) + float gfactor = 5.0f; // Proportional gain. float sr3 = self->sr3; float lpOut = self->lpOut; @@ -2574,3 +3152,519 @@ void sig_dsp_TiltEQ_destroy(struct sig_Allocator* allocator, &self->outputs); sig_dsp_Signal_destroy(allocator, self); } + + + +struct sig_dsp_Delay* sig_dsp_Delay_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_Delay* self = sig_MALLOC(allocator, struct sig_dsp_Delay); + // TODO: Improve buffer management throughout Signaletic. + self->delayLine = context->oneSampleDelayLine; + sig_dsp_Delay_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_Delay_init(struct sig_dsp_Delay* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_Delay_generate); + + sig_CONNECT_TO_SILENCE(self, source, context); + sig_CONNECT_TO_SILENCE(self, delayTime, context); +} + +inline void sig_dsp_Delay_read(struct sig_dsp_Delay* self, float source, + size_t i) { + float delayTime = FLOAT_ARRAY(self->inputs.delayTime)[i]; + + FLOAT_ARRAY(self->outputs.main)[i] = sig_DelayLine_cubicReadAtTime( + self->delayLine, + source, + delayTime, + self->signal.audioSettings->sampleRate); +} + +void sig_dsp_Delay_generate(void* signal) { + struct sig_dsp_Delay* self = (struct sig_dsp_Delay*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + sig_dsp_Delay_read(self, source, i); + sig_DelayLine_write(self->delayLine, source); + } +} + +void sig_dsp_Delay_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Delay* self) { + // Don't destroy the delay line; it isn't owned. + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} + + + +struct sig_dsp_Delay* sig_dsp_DelayTap_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + // TODO: Copy-pasted from sig_dsp_Delay but we have a different + // initialization function we need to call here. Seems like it's time to + // decompose Signals into more function pointers! + struct sig_dsp_Delay* self = sig_MALLOC(allocator, struct sig_dsp_Delay); + // TODO: Improve buffer management throughout Signaletic. + self->delayLine = context->oneSampleDelayLine; + + sig_dsp_Delay_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_DelayTap_init(struct sig_dsp_Delay* self, + struct sig_SignalContext* context) { + sig_dsp_Delay_init(self, context); + sig_dsp_Signal_init(self, context, *sig_dsp_DelayTap_generate); +} + +void sig_dsp_DelayTap_generate(void* signal) { + struct sig_dsp_Delay* self = (struct sig_dsp_Delay*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + sig_dsp_Delay_read(self, source, i); + } +} + +void sig_dsp_DelayTap_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Delay* self) { + // Don't destroy the delay line; it isn't owned. + sig_dsp_Delay_destroy(allocator, self); +} + + + +struct sig_dsp_DelayWrite* sig_dsp_DelayWrite_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_DelayWrite* self = sig_MALLOC(allocator, + struct sig_dsp_DelayWrite); + // TODO: Improve buffer management throughout Signaletic. + self->delayLine = context->oneSampleDelayLine; + + sig_dsp_DelayWrite_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_DelayWrite_init(struct sig_dsp_DelayWrite* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_DelayWrite_generate); + + sig_CONNECT_TO_SILENCE(self, source, context); +} + +void sig_dsp_DelayWrite_generate(void* signal) { + struct sig_dsp_DelayWrite* self = (struct sig_dsp_DelayWrite*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + sig_DelayLine_write(self->delayLine, source); + } + +} + +void sig_dsp_DelayWrite_destroy(struct sig_Allocator* allocator, + struct sig_dsp_DelayWrite* self) { + // Don't destroy the delay line; it isn't owned. + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} + + + +// TODO: Resolve duplication with sig_dsp_Delay +struct sig_dsp_Comb* sig_dsp_Comb_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_Comb* self = sig_MALLOC(allocator, struct sig_dsp_Comb); + // TODO: Improve buffer management throughout Signaletic. + self->delayLine = context->oneSampleDelayLine; + + sig_dsp_Comb_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_Comb_init(struct sig_dsp_Comb* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_Comb_generate); + self->previousSample = 0.0f; + + sig_CONNECT_TO_SILENCE(self, source, context); + sig_CONNECT_TO_SILENCE(self, delayTime, context); + sig_CONNECT_TO_SILENCE(self, feedbackGain, context); + sig_CONNECT_TO_SILENCE(self, lpfCoefficient, context); +} + +void sig_dsp_Comb_generate(void* signal) { + struct sig_dsp_Comb* self = (struct sig_dsp_Comb*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float maxDelayLength = (float) self->delayLine->buffer->length; + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float feedbackGain = FLOAT_ARRAY(self->inputs.feedbackGain)[i]; + float delayTime = FLOAT_ARRAY(self->inputs.delayTime)[i]; + float lpfCoefficient = FLOAT_ARRAY(self->inputs.lpfCoefficient)[i]; + float readPos = (delayTime * self->signal.audioSettings->sampleRate); + if (readPos >= maxDelayLength) { + readPos = maxDelayLength - 1; + } + + delayTime = sig_fmaxf(delayTime, 0.00001f); // Delay time can't be zero. + float read = sig_DelayLine_linearReadAt(self->delayLine, readPos); + float outputSample = sig_filter_smooth(read, self->previousSample, + lpfCoefficient); + float toWrite = sig_DelayLine_feedback(source, outputSample, + feedbackGain); + sig_DelayLine_write(self->delayLine, toWrite); + FLOAT_ARRAY(self->outputs.main)[i] = outputSample; + self->previousSample = outputSample; + } +} + +void sig_dsp_Comb_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Comb* self) { + // Don't destroy the delay line; it isn't owned. + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} + + + +// TODO: Resolve duplication with sig_dsp_Delay and Comb +struct sig_dsp_Allpass* sig_dsp_Allpass_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_Allpass* self = sig_MALLOC(allocator, + struct sig_dsp_Allpass); + // TODO: Improve buffer management throughout Signaletic. + self->delayLine = context->oneSampleDelayLine; + + sig_dsp_Allpass_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_Allpass_init(struct sig_dsp_Allpass* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_Allpass_generate); + + sig_CONNECT_TO_SILENCE(self, source, context); + sig_CONNECT_TO_SILENCE(self, delayTime, context); + sig_CONNECT_TO_SILENCE(self, g, context); +} + +void sig_dsp_Allpass_generate(void* signal) { + struct sig_dsp_Allpass* self = (struct sig_dsp_Allpass*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float maxDelayLength = (float) self->delayLine->buffer->length; + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float delayTime = FLOAT_ARRAY(self->inputs.delayTime)[i]; + float g = FLOAT_ARRAY(self->inputs.g)[i]; + float readPos = (delayTime * self->signal.audioSettings->sampleRate); + if (readPos >= maxDelayLength) { + readPos = maxDelayLength - 1; + } + + if (delayTime <= 0.0f || g <= 0.0f) { + FLOAT_ARRAY(self->outputs.main)[i] = source; + } else { + FLOAT_ARRAY(self->outputs.main)[i] = sig_DelayLine_linearAllpass( + self->delayLine, source, readPos, g); + } + } +} + +void sig_dsp_Allpass_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Allpass* self) { + // Don't destroy the delay line; it isn't owned. + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} + + +void sig_dsp_Chorus_Outputs_newAudioBlocks( + struct sig_Allocator* allocator, + struct sig_AudioSettings* audioSettings, + struct sig_dsp_Chorus_Outputs* outputs) { + outputs->main = sig_AudioBlock_newSilent(allocator, audioSettings); + outputs->modulator = sig_AudioBlock_newSilent(allocator, audioSettings); +} + +void sig_dsp_Chorus_Outputs_destroyAudioBlocks( + struct sig_Allocator* allocator, + struct sig_dsp_Chorus_Outputs* outputs) { + sig_AudioBlock_destroy(allocator, outputs->main); + sig_AudioBlock_destroy(allocator, outputs->modulator); +} + +struct sig_dsp_Chorus* sig_dsp_Chorus_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_Chorus* self = sig_MALLOC(allocator, + struct sig_dsp_Chorus); + // TODO: Improve buffer management throughout Signaletic. + self->delayLine = context->oneSampleDelayLine; + + sig_dsp_Chorus_init(self, context); + sig_dsp_Chorus_Outputs_newAudioBlocks(allocator, context->audioSettings, + &self->outputs); + + return self; +} + +void sig_dsp_Chorus_init(struct sig_dsp_Chorus* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_Chorus_generate); + + sig_osc_FastLFSine_init(&self->modulator, + self->signal.audioSettings->sampleRate); + + self->previousFixedOutput = 0.0f; + self->previousModulatedOutput = 0.0f; + + sig_CONNECT_TO_SILENCE(self, source, context); + sig_CONNECT_TO_SILENCE(self, delayTime, context); + sig_CONNECT_TO_SILENCE(self, speed, context); + sig_CONNECT_TO_SILENCE(self, width, context); + sig_CONNECT_TO_SILENCE(self, feedbackGain, context); + sig_CONNECT_TO_SILENCE(self, feedforwardGain, context); + sig_CONNECT_TO_SILENCE(self, blend, context); +} + +void sig_dsp_Chorus_generate(void* signal) { + struct sig_dsp_Chorus* self = (struct sig_dsp_Chorus*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float delayTime = FLOAT_ARRAY(self->inputs.delayTime)[i]; + float speed = FLOAT_ARRAY(self->inputs.speed)[i]; + float width = FLOAT_ARRAY(self->inputs.width)[i]; + float feedbackGain = FLOAT_ARRAY(self->inputs.feedbackGain)[i]; + float feedforwardGain = FLOAT_ARRAY(self->inputs.feedforwardGain)[i]; + float blend = FLOAT_ARRAY(self->inputs.blend)[i]; + + sig_osc_FastLFSine_setFrequencyFast(&self->modulator, speed); + sig_osc_FastLFSine_generate(&self->modulator); + FLOAT_ARRAY(self->outputs.modulator)[i] = self->modulator.sinZ; + + // TODO: Add one pole low pass filters in both the feedback and + // feedforward lines to support echo. + + float fixedRead = sig_DelayLine_allpassReadAtTime(self->delayLine, + source, delayTime, self->signal.audioSettings->sampleRate, + self->previousFixedOutput); + self->previousFixedOutput = fixedRead; + float toWrite = source - (fixedRead * feedbackGain); + sig_DelayLine_write(self->delayLine, toWrite); + + float modulatedDelayTime = self->modulator.sinZ * width + delayTime; + float modulatedRead = sig_DelayLine_allpassReadAtTime(self->delayLine, + source, modulatedDelayTime, self->signal.audioSettings->sampleRate, + self->previousModulatedOutput); + self->previousModulatedOutput = modulatedRead; + float feedforwardSample = modulatedRead * feedforwardGain; + float output = (toWrite * blend) + feedforwardSample; + + // TODO: What kind of gain staging should we do here? + // It seems likely that we can have up to 3x gain depending on + // the values of feedbackGain, feedforwardGain, and blend. + FLOAT_ARRAY(self->outputs.main)[i] = sig_fastTanhf(output / 3.0f); + } +} + +void sig_dsp_Chorus_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Chorus* self) { + // Don't destroy the delay line; it isn't owned. + sig_dsp_Chorus_Outputs_destroyAudioBlocks(allocator, &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} + + + +struct sig_dsp_LinearXFade* sig_dsp_LinearXFade_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_LinearXFade* self = sig_MALLOC(allocator, + struct sig_dsp_LinearXFade); + sig_dsp_LinearXFade_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_LinearXFade_init(struct sig_dsp_LinearXFade* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_LinearXFade_generate); + + sig_CONNECT_TO_SILENCE(self, left, context); + sig_CONNECT_TO_SILENCE(self, right, context); + sig_CONNECT_TO_SILENCE(self, mix, context); +} + +void sig_dsp_LinearXFade_generate(void* signal) { + struct sig_dsp_LinearXFade* self = (struct sig_dsp_LinearXFade*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float left = FLOAT_ARRAY(self->inputs.left)[i]; + float right = FLOAT_ARRAY(self->inputs.right)[i]; + float mix = FLOAT_ARRAY(self->inputs.mix)[i]; + FLOAT_ARRAY(self->outputs.main)[i] = sig_linearXFade(left, right, mix); + } +} + +void sig_dsp_LinearXFade_destroy(struct sig_Allocator* allocator, + struct sig_dsp_LinearXFade* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} + + + +struct sig_dsp_Calibrator* sig_dsp_Calibrator_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_Calibrator* self = sig_MALLOC(allocator, + struct sig_dsp_Calibrator); + sig_dsp_Calibrator_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +inline void sig_dsp_Calibrator_Node_init( + struct sig_dsp_Calibrator_Node* nodes, + float* targetValues, size_t numNodes) { + for (size_t i = 0; i < numNodes; i++) { + struct sig_dsp_Calibrator_Node* node = &nodes[i]; + node->target = node->avg = targetValues[i]; + node->numSamplesRecorded = 0; + node->min = INFINITY; + node->max = -INFINITY; + node->sum = 0.0f; + node->diff = 0.0f; + } +} + +void sig_dsp_Calibrator_init(struct sig_dsp_Calibrator* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_Calibrator_generate); + self->previousGate = 0.0f; + self->stage = 0; + + float targetValues[sig_dsp_Calibrator_NUM_STAGES] = + sig_dsp_Calibrator_TARGET_VALUES; + sig_dsp_Calibrator_Node_init(self->nodes, + targetValues, sig_dsp_Calibrator_NUM_STAGES); + + sig_CONNECT_TO_SILENCE(self, source, context); + sig_CONNECT_TO_SILENCE(self, gate, context); +} + +inline size_t sig_dsp_Calibrator_locateIntervalForValue(float x, + struct sig_dsp_Calibrator_Node* nodes, size_t numNodes) { + size_t lastNodeIdx = numNodes - 1; + size_t intervalEndIdx = lastNodeIdx; + for (size_t i = 0; i < lastNodeIdx - 1; i++) { + size_t nextIdx = i + 1; + struct sig_dsp_Calibrator_Node nextState = nodes[nextIdx]; + if (x < nextState.avg) { + intervalEndIdx = nextIdx; + break; + } + } + + return intervalEndIdx; +} + +inline float sig_dsp_Calibrator_fitValueToCalibrationData(float x, + struct sig_dsp_Calibrator_Node* nodes, size_t numNodes) { + // Calibrate using piecewise linear fit from + // readings sampled at 0.0, 1.0, 2.0, 3.0, 4.0 and 4.75V. + // The ADC tops out slightly before 5 volts, + // So 4.75V is 9 semitones above the fourth octave. + + // Find the segment in which the current value is located. + size_t intervalEndIdx = sig_dsp_Calibrator_locateIntervalForValue(x, + nodes, numNodes); + struct sig_dsp_Calibrator_Node start = nodes[intervalEndIdx - 1]; + struct sig_dsp_Calibrator_Node end = nodes[intervalEndIdx]; + + // y is the interval values measured during the calibration process. + float yk = start.avg; + float ykplus1 = end.avg; + + // t is the target interval values. + float tk = start.target; + float tkplus1 = end.target; + + // Interpolate the calibrated value. + return tk + ((tkplus1 - tk) / (ykplus1 - yk)) * (x - yk); +}; + +void sig_dsp_Calibrator_generate(void* signal) { + struct sig_dsp_Calibrator* self = (struct sig_dsp_Calibrator*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float gate = FLOAT_ARRAY(self->inputs.gate)[i]; + size_t stage = self->stage; + struct sig_dsp_Calibrator_Node* node = &self->nodes[stage]; + + if (gate <= 0.0f && self->previousGate > 0.0f) { + // Gate is low; recording has just stopped. + // Calculate offset by discarding the highest and lowest values, + // and then averaging the rest. + node->sum -= node->min; + node->sum -= node->max; + node->avg = node->sum / + (node->numSamplesRecorded - 2); + node->diff = -(node->target - node->avg); + self->stage = (stage + 1) % sig_dsp_Calibrator_NUM_STAGES; + } else if (gate > 0.0f) { + // Gate is high; we're recording. + node->sum += source; + node->numSamplesRecorded++; + + if (source < node->min) { + node->min = source; + } + + if (source > node->max) { + node->max = source; + } + } + + float px = sig_dsp_Calibrator_fitValueToCalibrationData(source, + self->nodes, sig_dsp_Calibrator_NUM_STAGES); + FLOAT_ARRAY(self->outputs.main)[i] = px; + + self->previousGate = gate; + } +} + +void sig_dsp_Calibrator_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Calibrator* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} diff --git a/libsignaletic/tests/test-libsignaletic.c b/libsignaletic/tests/test-libsignaletic.c index a23d5d5..2245e78 100644 --- a/libsignaletic/tests/test-libsignaletic.c +++ b/libsignaletic/tests/test-libsignaletic.c @@ -211,7 +211,7 @@ void test_sig_fillWithSilence(void) { testAssertBufferIsSilent(&allocator, buffer, 16); } -void test_sig_AudioSettings_new() { +void test_sig_AudioSettings_new(void) { struct sig_AudioSettings* s = sig_AudioSettings_new(&allocator); TEST_ASSERT_EQUAL_FLOAT_MESSAGE(sig_DEFAULT_AUDIOSETTINGS.sampleRate, @@ -226,7 +226,7 @@ void test_sig_AudioSettings_new() { sig_AudioSettings_destroy(&allocator, s); } -void test_sig_samplesToSeconds() { +void test_sig_samplesToSeconds(void) { struct sig_AudioSettings* s = sig_AudioSettings_new(&allocator); size_t expected = 48000; @@ -344,6 +344,46 @@ void test_sig_BufferView(void) { "The subarray should contain the correct values."); } +void test_sig_linearXFade(void) { + float left = 0.66f; + float right = 0.45f; + float mix = -1.0f; + float expected = left; + float actual = sig_linearXFade(left, right, mix); + TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, "Left signal only."); + + mix = 1.0f; + expected = right; + actual = sig_linearXFade(left, right, mix); + TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, "Right signal only."); + + mix = 0.0f; + expected = 0.555f; + actual = sig_linearXFade(left, right, mix); + TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, + "Both signals mixed evenly."); + + mix = -0.25f; + expected = 0.58125f; + actual = sig_linearXFade(left, right, mix); + TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, + "25 percent to the left."); + + left = 0.001f; + right = 0.01f; + mix = -0.5f; + expected = 0.00325f; + actual = sig_linearXFade(left, right, mix); + TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, + "50 percent to the left."); + + mix = 0.5f; + expected = 0.00775f; + actual = sig_linearXFade(left, right, mix); + TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, + "50 percent to the left."); +} + void test_sig_dsp_Value(void) { struct sig_dsp_Value* value = sig_dsp_Value_new(&allocator, context); value->parameters.value = 123.45f; @@ -698,7 +738,7 @@ struct sig_test_BufferPlayer* WaveformPlayer_new( sig_waveform_generator waveform, float sampleRate, float freq, float duration) { struct sig_Buffer* waveformBuffer = sig_Buffer_new(&allocator, - (size_t) audioSettings->sampleRate * duration); + (size_t) (audioSettings->sampleRate * duration)); sig_Buffer_fillWithWaveform(waveformBuffer, waveform, sampleRate, 0.0f, freq); @@ -714,7 +754,7 @@ void WaveformPlayer_destroy(struct sig_test_BufferPlayer* player) { void testClockDetector(struct sig_test_BufferPlayer* clockPlayer, float duration, float expectedFreq) { - struct sig_dsp_ClockFreqDetector* det = sig_dsp_ClockFreqDetector_new( + struct sig_dsp_ClockDetector* det = sig_dsp_ClockDetector_new( &allocator, context); det->inputs.source = clockPlayer->outputs.main; @@ -726,7 +766,14 @@ void testClockDetector(struct sig_test_BufferPlayer* clockPlayer, FLOAT_ARRAY(det->outputs.main)[0], "The clock's frequency should have been detected correctly."); - sig_dsp_ClockFreqDetector_destroy(&allocator, det); + float expectedBPM = expectedFreq * 60.0f; + + TEST_ASSERT_FLOAT_WITHIN_MESSAGE(0.05, + expectedBPM, + FLOAT_ARRAY(det->outputs.bpm)[0], + "The clock's bpm tempo should have been detected correctly."); + + sig_dsp_ClockDetector_destroy(&allocator, det); } void testClockDetector_SingleWaveform(sig_waveform_generator waveform, @@ -740,19 +787,19 @@ void testClockDetector_SingleWaveform(sig_waveform_generator waveform, WaveformPlayer_destroy(clockPlayer); } -void test_sig_dsp_ClockFreqDetector_square() { +void test_sig_dsp_ClockDetector_square(void) { // Square wave, 2Hz for two seconds. testClockDetector_SingleWaveform(sig_waveform_square, 2.0f, 2.0f); } -void test_sig_dsp_ClockFreqDetector_sine() { +void test_sig_dsp_ClockDetector_sine(void) { // Sine wave, 10 Hz. testClockDetector_SingleWaveform(sig_waveform_sine, 2.0f, 10.0f); } -void test_sig_dsp_ClockFreqDetector_slowDown() { +void test_sig_dsp_ClockDetector_slowDown(void) { float bufferDuration = 2.0f; - size_t bufferLen = (size_t) audioSettings->sampleRate * bufferDuration; + size_t bufferLen = (size_t) (audioSettings->sampleRate * bufferDuration); size_t halfBufferLen = bufferLen / 2; float fastSpeed = 10.0f; float slowSpeed = 2.0f; @@ -780,13 +827,14 @@ void test_sig_dsp_ClockFreqDetector_slowDown() { sig_test_BufferPlayer_destroy(&allocator, clockPlayer); } -void test_sig_dsp_ClockFreqDetector_stop() { +void test_sig_dsp_ClockDetector_stop(void) { float bufferDuration = 122.0f; float clockDuration = 1.0f; - size_t bufferLen = (size_t) audioSettings->sampleRate * bufferDuration; - size_t clockSectionLen = (size_t) audioSettings->sampleRate * clockDuration; + size_t bufferLen = (size_t) (audioSettings->sampleRate * bufferDuration); + size_t clockSectionLen = (size_t) + (audioSettings->sampleRate * clockDuration); size_t silentSectionLen = (size_t) - audioSettings->sampleRate * (bufferDuration - clockDuration); + (audioSettings->sampleRate * (bufferDuration - clockDuration)); float clockFreq = 10.0f; struct sig_Buffer* waveformBuffer = sig_Buffer_new(&allocator, bufferLen); @@ -803,7 +851,9 @@ void test_sig_dsp_ClockFreqDetector_stop() { struct sig_test_BufferPlayer* clockPlayer = sig_test_BufferPlayer_new( &allocator, context, waveformBuffer); - testClockDetector(clockPlayer, bufferDuration, 0.0f); + // When it no longer receives clock pulses, the detector + // should hold on to whatever tempo it last calculated. + testClockDetector(clockPlayer, bufferDuration, clockFreq); sig_BufferView_destroy(&allocator, silentSection); sig_BufferView_destroy(&allocator, clockSection); @@ -1014,6 +1064,9 @@ void test_sig_dsp_List_wrapping(void) { // Fractional indexes >= half should be rounded up. generateAndTestListIndex(idx, 0.125f, list, 2.0f); + // Fractional indexes >= half should be rounded up. + generateAndTestListIndex(idx, 0.25f, list, 2.0f); + // Another even index. generateAndTestListIndex(idx, 0.5f, list, 3.0f); @@ -1029,15 +1082,14 @@ void test_sig_dsp_List_wrapping(void) { // A slightly out of bounds index should round down to the last index. generateAndTestListIndex(idx, 1.1f, list, 5.0f); - // But a larger index should round up and - // wrap around to return the first value. - generateAndTestListIndex(idx, 1.25f, list, 1.0f); + // But a larger index should wrap and round up. + generateAndTestListIndex(idx, 1.25f, list, 2.0f); // Larger indices should wrap around past the beginning. - generateAndTestListIndex(idx, 1.5f, list, 2.0f); + generateAndTestListIndex(idx, 1.5f, list, 3.0f); - // Very large indices should wrap around the beginning again. - generateAndTestListIndex(idx, 3.0f, list, 3.0f); + // Very large indices should wrap around several times. + generateAndTestListIndex(idx, 3.0f, list, 5.0f); // Negative indices should wrap around the end. // Note the index "shift" here: -0.25 should point @@ -1073,6 +1125,46 @@ void test_sig_dsp_List_wrapping(void) { generateAndTestListIndex(idx, -1.15f, list, 1.0f); } +void test_sig_dsp_List_noNormalization(void) { + struct sig_dsp_Value* idx = sig_dsp_Value_new(&allocator, + context); + + float listItems[5] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}; + struct sig_Buffer listBuffer = { + .length = 5, + .samples = listItems + }; + + struct sig_dsp_List* list = sig_dsp_List_new(&allocator, context); + list->parameters.normalizeIndex = 0.0f; + list->list = &listBuffer; + list->inputs.index = idx->outputs.main; + + // No interpolation. + + // Exactly the first index. + generateAndTestListIndex(idx, 0.0f, list, 1.0f); + + // Exactly the last index. + generateAndTestListIndex(idx, 4.0f, list, 5.0f); + + // Wrap around. + generateAndTestListIndex(idx, 5.0f, list, 2.0f); + + // Fractional index, round down. + generateAndTestListIndex(idx, 5.1f, list, 2.0f); + + // Fractional index, round up. + generateAndTestListIndex(idx, 1.8f, list, 3.0f); + + // Interpolation + list->parameters.interpolate = 1.0f; + generateAndTestListIndex(idx, 5.1f, list, 2.1f); + + // Wrap around, interpolation. + generateAndTestListIndex(idx, -2.5f, list, 3.5f); +} + void test_sig_dsp_List_clamping(void) { struct sig_dsp_Value* idx = sig_dsp_Value_new(&allocator, context); @@ -1101,6 +1193,45 @@ void test_sig_dsp_List_clamping(void) { generateAndTestListIndex(idx, -1.5f, list, 1.0f); } +void test_sig_dsp_List_interpolation(void) { + struct sig_dsp_Value* idx = sig_dsp_Value_new(&allocator, + context); + + float listItems[5] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}; + struct sig_Buffer listBuffer = { + .length = 5, + .samples = listItems + }; + + struct sig_dsp_List* list = sig_dsp_List_new(&allocator, context); + list->parameters.wrap = 1.0f; + list->parameters.interpolate = 1.0f; + list->parameters.normalizeIndex = 1.0f; + list->list = &listBuffer; + list->inputs.index = idx->outputs.main; + + // Start + generateAndTestListIndex(idx, 0.0f, list, 1.0f); + + // Interpolated + generateAndTestListIndex(idx, 0.1f, list, 1.4f); + + // Halfway + generateAndTestListIndex(idx, 0.5f, list, 3.0f); + + // Interpolated + generateAndTestListIndex(idx, 0.6f, list, 3.4f); + + // End + generateAndTestListIndex(idx, 1.0f, list, 5.0f); + + // Almost end. + generateAndTestListIndex(idx, 0.99999999999f, list, 5.0f); + + // Wrap around and interpolate. 1.1f should be the same as 0.1f + generateAndTestListIndex(idx, 1.1f, list, 1.4f); +} + void test_sig_dsp_List_noList(void) { struct sig_dsp_Value* idx = sig_dsp_Value_new(&allocator, context); @@ -1149,6 +1280,7 @@ int main(void) { RUN_TEST(test_sig_AudioBlock_newWithValue); RUN_TEST(test_sig_Buffer); RUN_TEST(test_sig_BufferView); + RUN_TEST(test_sig_linearXFade); RUN_TEST(test_sig_dsp_Value); RUN_TEST(test_sig_dsp_ConstantValue); RUN_TEST(test_sig_dsp_TimedTriggerCounter); @@ -1158,15 +1290,17 @@ int main(void) { RUN_TEST(test_sig_dsp_Sine_phaseWrapsAt2PI); RUN_TEST(test_test_sig_dsp_Sine_isOffset); RUN_TEST(test_sig_dsp_Dust); - RUN_TEST(test_sig_dsp_ClockFreqDetector_square); - RUN_TEST(test_sig_dsp_ClockFreqDetector_sine); - RUN_TEST(test_sig_dsp_ClockFreqDetector_slowDown); - RUN_TEST(test_sig_dsp_ClockFreqDetector_stop); + RUN_TEST(test_sig_dsp_ClockDetector_square); + RUN_TEST(test_sig_dsp_ClockDetector_sine); + RUN_TEST(test_sig_dsp_ClockDetector_slowDown); + RUN_TEST(test_sig_dsp_ClockDetector_stop); RUN_TEST(test_sig_dsp_TimedGate_unipolar); RUN_TEST(test_sig_dsp_TimedGate_resetOnTrigger); RUN_TEST(test_sig_dsp_TimedGate_bipolar); RUN_TEST(test_sig_dsp_List_wrapping); + RUN_TEST(test_sig_dsp_List_noNormalization); RUN_TEST(test_sig_dsp_List_clamping); + RUN_TEST(test_sig_dsp_List_interpolation); RUN_TEST(test_sig_dsp_List_noList); return UNITY_END(); diff --git a/libsignaletic/wasm/bindings/libsignaletic-web-bindings.idl b/libsignaletic/wasm/bindings/libsignaletic-web-bindings.idl index 66e2a86..1d073e3 100644 --- a/libsignaletic/wasm/bindings/libsignaletic-web-bindings.idl +++ b/libsignaletic/wasm/bindings/libsignaletic-web-bindings.idl @@ -37,11 +37,11 @@ interface Signals { sig_dsp_Oscillator triangle); - sig_dsp_ClockFreqDetector ClockFreqDetector_new(sig_Allocator allocator, + sig_dsp_ClockDetector ClockDetector_new(sig_Allocator allocator, sig_SignalContext context); - void ClockFreqDetector_destroy(sig_Allocator allocator, - sig_dsp_ClockFreqDetector detector); + void ClockDetector_destroy(sig_Allocator allocator, + sig_dsp_ClockDetector detector); }; interface Signaletic { @@ -219,13 +219,19 @@ interface sig_dsp_BinaryOp { }; -interface sig_dsp_ClockFreqDetector_Inputs { + +interface sig_dsp_ClockDetector_Outputs { + attribute any main; + attribute any bpm; +}; + +interface sig_dsp_ClockDetector_Inputs { attribute any source; }; -interface sig_dsp_ClockFreqDetector { +interface sig_dsp_ClockDetector { [Value] attribute sig_dsp_Signal signal; - [Value] attribute sig_dsp_ClockFreqDetector_Inputs inputs; - [Value] attribute sig_dsp_Signal_SingleMonoOutput outputs; + [Value] attribute sig_dsp_ClockDetector_Inputs inputs; + [Value] attribute sig_dsp_ClockDetector_Outputs outputs; }; diff --git a/libsignaletic/wasm/bindings/src/libsignaletic-web.cpp b/libsignaletic/wasm/bindings/src/libsignaletic-web.cpp index a309ec7..6692090 100644 --- a/libsignaletic/wasm/bindings/src/libsignaletic-web.cpp +++ b/libsignaletic/wasm/bindings/src/libsignaletic-web.cpp @@ -71,15 +71,15 @@ class Signals { } - struct sig_dsp_ClockFreqDetector* ClockFreqDetector_new( + struct sig_dsp_ClockDetector* ClockDetector_new( struct sig_Allocator* allocator, struct sig_SignalContext* context) { - return sig_dsp_ClockFreqDetector_new(allocator, context); + return sig_dsp_ClockDetector_new(allocator, context); } - void ClockFreqDetector_destroy(struct sig_Allocator* allocator, - struct sig_dsp_ClockFreqDetector* self) { - return sig_dsp_ClockFreqDetector_destroy(allocator, self); + void ClockDetector_destroy(struct sig_Allocator* allocator, + struct sig_dsp_ClockDetector* self) { + return sig_dsp_ClockDetector_destroy(allocator, self); } };