├── .editorconfig ├── .github ├── scripts │ ├── install-debian-deps.sh │ ├── install-macos-cross-platform-deps.sh │ └── install-macos-native-deps.sh └── workflows │ └── build.yml ├── .gitignore ├── COPYING ├── README.md ├── README.txt ├── README_VSCODE.md ├── SoundAsPureForm.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── flake.nix ├── include ├── AsyncAudioFileWriter.hpp ├── AudioToolboxBuffers.hpp ├── AudioToolboxSoundFile.hpp ├── Buffers.hpp ├── DelayUGens.hpp ├── ErrorCodes.hpp ├── FilterUGens.hpp ├── Hash.hpp ├── MathFuns.hpp ├── MathOps.hpp ├── Midi.hpp ├── MultichannelExpansion.hpp ├── Object.hpp ├── Opcode.hpp ├── OscilUGens.hpp ├── Parser.hpp ├── Play.hpp ├── PortableBuffers.hpp ├── RCObj.hpp ├── SndfileSoundFile.hpp ├── SoundFiles.hpp ├── Spectrogram.hpp ├── StreamOps.hpp ├── Types.hpp ├── UGen.hpp ├── VM.hpp ├── ZArr.hpp ├── clz.hpp ├── dsp.hpp ├── elapsedTime.hpp ├── lock.hpp ├── makeImage.hpp ├── primes.hpp ├── rc_ptr.hpp ├── rgen.hpp ├── ringbuffer.hpp └── symbol.hpp ├── libmanta ├── Manta.cpp ├── Manta.h ├── MantaClient.h ├── MantaExceptions.h ├── MantaMulti.cpp ├── MantaMulti.h ├── MantaServer.h ├── MantaUSB.cpp ├── MantaUSB.h ├── MantaVersion.h └── extern │ └── hidapi │ ├── README.txt │ ├── hidapi │ └── hidapi.h │ ├── libusb │ └── hid.c │ ├── m4 │ ├── ax_pthread.m4 │ └── pkg.m4 │ └── mac │ └── hid.c ├── meson.build ├── meson.options ├── nix └── libdispatch │ └── default.nix ├── sapf-bif-examples.txt ├── sapf-examples.txt ├── sapf-prelude.txt ├── src ├── AsyncAudioFileWriter.cpp ├── AudioToolboxBuffers.cpp ├── AudioToolboxSoundFile.cpp ├── Buffers.cpp ├── CoreOps.cpp ├── DelayUGens.cpp ├── ErrorCodes.cpp ├── FilterUGens.cpp ├── MathFuns.cpp ├── MathOps.cpp ├── Midi.cpp ├── MultichannelExpansion.cpp ├── Object.cpp ├── Opcode.cpp ├── OscilUGens.cpp ├── Parser.cpp ├── Play.cpp ├── PortableBuffers.cpp ├── RCObj.cpp ├── RandomOps.cpp ├── SetOps.cpp ├── SndfileSoundFile.cpp ├── SoundFiles.cpp ├── Spectrogram.cpp ├── StreamOps.cpp ├── Types.cpp ├── UGen.cpp ├── VM.cpp ├── ZArr.cpp ├── dsp.cpp ├── elapsedTime.cpp ├── main.cpp ├── makeImage.cpp ├── makeImage.mm ├── primes.cpp └── symbol.cpp ├── subprojects ├── doctest.wrap └── eigen.wrap ├── test ├── doctest.cpp ├── helpers │ └── ArrHelpers.hpp ├── test_AsyncAudioFileWriter.cpp ├── test_MathOps.cpp ├── test_OscilUgens.cpp ├── test_SndfileSoundFile.cpp └── test_StreamOps.cpp └── unit-tests.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{h,c,hpp,cpp,mm}] 2 | indent_style = tab -------------------------------------------------------------------------------- /.github/scripts/install-debian-deps.sh: -------------------------------------------------------------------------------- 1 | sudo apt update 2 | 3 | sudo apt install -y \ 4 | build-essential \ 5 | meson \ 6 | ninja-build \ 7 | libedit-dev \ 8 | libfftw3-dev \ 9 | libsndfile1-dev \ 10 | libxsimd-dev \ 11 | librtaudio-dev 12 | -------------------------------------------------------------------------------- /.github/scripts/install-macos-cross-platform-deps.sh: -------------------------------------------------------------------------------- 1 | HOMEBREW_NO_AUTO_UPDATE=1 brew install \ 2 | fftw \ 3 | libedit \ 4 | libsndfile \ 5 | meson \ 6 | rtaudio \ 7 | xsimd 8 | -------------------------------------------------------------------------------- /.github/scripts/install-macos-native-deps.sh: -------------------------------------------------------------------------------- 1 | HOMEBREW_NO_AUTO_UPDATE=1 brew install \ 2 | libedit \ 3 | meson -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Compile 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | msys2-ucrt64: 11 | runs-on: windows-latest 12 | defaults: 13 | run: 14 | shell: msys2 {0} 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: msys2/setup-msys2@v2 18 | with: 19 | msystem: UCRT64 20 | update: true 21 | install: >- 22 | base-devel 23 | mingw-w64-ucrt-x86_64-toolchain 24 | mingw-w64-ucrt-x86_64-meson 25 | mingw-w64-ucrt-x86_64-fftw 26 | mingw-w64-ucrt-x86_64-libsndfile 27 | mingw-w64-ucrt-x86_64-pkgconf 28 | mingw-w64-ucrt-x86_64-rtaudio 29 | mingw-w64-ucrt-x86_64-readline 30 | mingw-w64-ucrt-x86_64-xsimd 31 | mingw-w64-ucrt-x86_64-ca-certificates 32 | - name: CI-Build 33 | run: | 34 | meson setup --buildtype release build 35 | meson test --verbose -C build 36 | meson compile sapf_x86_64_v3 -C build 37 | - name: Save Build Log Artifact 38 | if: ${{ always() }} 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: meson-build-log-${{ runner.os }}.txt 42 | path: build/meson-logs/meson-log.tx 43 | build: 44 | strategy: 45 | fail-fast: false 46 | matrix: 47 | include: 48 | - os: ubuntu-latest 49 | build_type: cross-platform 50 | - os: macos-latest 51 | build_type: native 52 | - os: macos-latest 53 | build_type: cross-platform 54 | runs-on: ${{ matrix.os }} 55 | 56 | steps: 57 | - uses: actions/checkout@v4 58 | 59 | - name: Install Linux Dependencies 60 | if: runner.os == 'Linux' 61 | run: .github/scripts/install-debian-deps.sh 62 | 63 | - name: Install macOS Native Dependencies 64 | if: runner.os == 'macOS' && matrix.build_type == 'native' 65 | run: .github/scripts/install-macos-native-deps.sh 66 | 67 | - name: Install macOS Cross-Platform Dependencies 68 | if: runner.os == 'macOS' && matrix.build_type == 'cross-platform' 69 | run: .github/scripts/install-macos-cross-platform-deps.sh 70 | 71 | - name: Setup Linux 72 | if: runner.os == 'Linux' 73 | run: meson setup --buildtype release build 74 | 75 | - name: Setup macOS Native 76 | if: runner.os == 'macOS' && matrix.build_type == 'native' 77 | run: meson setup --buildtype release -Daccelerate=true 78 | -Daudiotoolbox=true -Dapple_lock=true -Dcarbon=true 79 | -Dcocoa=true -Dcorefoundation=true -Dcoremidi=true 80 | -Dmach_time=true -Ddispatch=true -Dmanta=false build 81 | 82 | - name: Setup macOS Cross-Platform 83 | if: runner.os == 'macOS' && matrix.build_type == 'cross-platform' 84 | run: meson setup --buildtype release -Daccelerate=false 85 | -Daudiotoolbox=false -Dapple_lock=false -Dcarbon=false 86 | -Dcocoa=false -Dcorefoundation=false -Dcoremidi=false 87 | -Dmach_time=false -Ddispatch=false -Dmanta=false build 88 | 89 | - name: Test 90 | run: meson test --verbose -C build 91 | 92 | - name: Compile M1 93 | if: runner.os == 'macOS' 94 | run: meson compile sapf_arm_m1 -C build 95 | 96 | - name: Compile Linux x86 97 | if: runner.os != 'macOS' 98 | run: meson compile sapf_x86_64_v3 -C build 99 | 100 | - name: Save Build Log Artifact 101 | if: ${{ always() }} 102 | uses: actions/upload-artifact@v4 103 | with: 104 | name: meson-build-log-${{ runner.os }}-${{ matrix.build_type }}.txt 105 | path: build/meson-logs/meson-log.txt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .Trashes 3 | *.swp 4 | *.lock 5 | *~.nib 6 | DerivedData/ 7 | build/ 8 | *.pbxuser 9 | *.mode1v3 10 | *.mode2v3 11 | *.perspectivev3 12 | !default.pbxuser 13 | !default.mode1v3 14 | !default.mode2v3 15 | !default.perspectivev3 16 | *.xccheckout 17 | xcuserdata/ 18 | xcshareddata/ 19 | *.xcworkspace/xcuserdata/ 20 | *.xcworkspace/xcshareddata/ 21 | *.hmap 22 | *.ipa 23 | *.xcuserstate 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | .vscode/ 28 | .idea/ 29 | subprojects/*/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sapf, cross-platform edition 2 | 3 | this is a highly work-in-progress fork of James McCartney's [sapf](https://github.com/lfnoise/sapf) (Sound As Pure Form) which aims to implement cross-platform alternatives for the various macOS libraries used in the original codebase. for the time being, the top priority platform is Linux. 4 | 5 | [original README](README.txt) 6 | 7 | ## building 8 | 9 | a Nix flake is included. simply run: 10 | 11 | ```shell 12 | nix develop 13 | meson setup --buildtype release build 14 | meson compile -C build 15 | ``` 16 | 17 | and you should get a binary at `./build/sapf`. This will be optimized for your 18 | native architecture and has the maximum level of optimization. This is recommended for 19 | normal usage. 20 | 21 | To build an 22 | unoptimized binary, simply omit the `--buildtype release` param (which will default to 23 | `--buildtype debug`). If you've already ran `setup` and want to change the buildtype to `debug`, you can run 24 | ```shell 25 | meson configure --buildtype debug build 26 | meson compile -C build 27 | ``` 28 | Note you can view the current buildtype setting via `meson configure build`. 29 | 30 | You can specify different targets defined in the meson.build file such as `meson compile sapf_x86_64_v3 -C build`. 31 | 32 | if not using Nix, you will need to install dependencies manually instead of the `nix develop`. the mandatory dependencies for a portable build are currently: 33 | 34 | - libedit 35 | - libsndfile 36 | - fftw 37 | - rtaudio 38 | - libxsimd 39 | 40 | for installing dependencies, you can refer to the CI scripts in this repo: 41 | 42 | - [install-debian-deps.sh](.github/scripts/install-debian-deps.sh) (Debian, Ubuntu, Mint, etc.) 43 | - [install-macos-native=deps.sh](.github/scripts/install-macos-native-deps.sh) (macOS with Homebrew, for builds using native macOS libraries) 44 | - [install-macos-cross-platform-deps.sh](.github/scripts/install-macos-cross-platform-deps.sh) (macOS with Homebrew, for builds using cross platform libraries) 45 | 46 | ## running tests 47 | Tests are written using doctest (which is obtained via a wrap) and located in the `tests` folder. 48 | See [the doctest documentation](https://github.com/doctest/doctest/tree/master?tab=readme-ov-file#documentation) for more details. 49 | 50 | You can use meson test to run the tests. 51 | 52 | This will respect the `--buildtype` setting. For optimal debugging experience, 53 | you should use the `debug` buildtype. You can check the current buildtype with 54 | `meson configure build`. To change it, you can run `meson configure --buildtype debug build`. 55 | 56 | The below will run the tests 57 | ```shell 58 | meson test --verbose -C build 59 | ``` 60 | Without `--verbose` you won't get the doctest test report (not to be confused with meson's 61 | own, less useful test report) printed to stdout and instead would have to view the test 62 | log file. 63 | 64 | Note there is currently a feature request for doctest for better integration 65 | with meson but it is not yet implemented ATTOW: https://github.com/doctest/doctest/issues/531 66 | This seems to be why the default meson test report isn't that useful. 67 | 68 | ## Windows Usage Caveats 69 | 70 | Windows support is currently WIP. The following current "quirks" apply: 71 | 72 | - It will default to WASAPI and use your primary output device. Ability to select different devices or audio drivers (ASIO, etc..) is not yet supported. 73 | - When pasting in multiline strings, they should work. Just note that many built-in Windows terminals by default will strip out certain characters like tabs, 74 | ruining your beautiful formatting. This can be changed in the terminal's settings. 75 | 76 | ## Windows Build + Development 77 | 78 | Windows support is achieved by building via [msys2](https://www.msys2.org/) (using mingw-w64-ucrt6) as opposed to building natively on Windows (probably possible but probably much more annoying). 79 | 80 | If you're for some reason not on x86_64, you'll have to replace any of the below references to that architecture 81 | with your own! You can find and view info on packages on [msys2 packages](https://packages.msys2.org/queue) to see 82 | if the package exists for your architecture. 83 | 84 | 1. Install [msys2](https://www.msys2.org/). Make sure to keep track of where you installed it as this will be where your 85 | "root directory" is for your msys2 / mingw64 shells. For this guide we will assume the default of `C:\msys64` 86 | 2. If you haven't yet, clone or copy this repo somewhere inside the msys2 install. For example within the msys2 shell you could install git via 87 | `pacman -S git` and then git clone this repo into your "home" folder. 88 | 3. Open a msys2 (ucrt) shell and install some needed development dependencies. 89 | (Note the ca-certificates are required in order to download the gtest wrap, and in general you'll 90 | have a bad time doing anything on msys2 without these certs.) 91 | ```shell 92 | # press ENTER when prompted to choose "all" 93 | pacman -S --needed base-devel \ 94 | mingw-w64-ucrt-x86_64-toolchain 95 | pacman -S \ 96 | mingw-w64-ucrt-x86_64-meson \ 97 | mingw-w64-ucrt-x86_64-fftw \ 98 | mingw-w64-ucrt-x86_64-libsndfile \ 99 | mingw-w64-ucrt-x86_64-pkgconf \ 100 | mingw-w64-ucrt-x86_64-rtaudio \ 101 | mingw-w64-ucrt-x86_64-readline \ 102 | mingw-w64-ucrt-x86_64-xsimd \ 103 | mingw-w64-ucrt-x86_64-ca-certificates 104 | ``` 105 | 4. Close and reopen the shell to ensure it loads everything you just installed. 106 | 5. Now we can try to build in the msys2 (ucrt) shell. 107 | Navigate to the root directory of this repo. 108 | 6. ```shell 109 | # remove --buildtype release to build an unoptimized exe with debug symbols 110 | meson setup --buildtype release build 111 | meson compile -C build 112 | ``` 113 | 7. You should see `sapf.exe` is created under the `${workspaceFolder}/build` directory. 114 | 8. You need all the required DLLs in order to run it via Windows. Go to your msys2 folder `C:\msys64\ucrt64\bin` 115 | and copy all of the dlls that look like `lib*.dll` (i.e. libreadline8.dll, libogg-0.dll, etc...). This is 116 | more than needed but I'm not sure the exact subset of dlls needed yet. 117 | 9. Now you can run the exe directly by clicking or via your preferred command prompt. 118 | 10. Test if its all working with a simple command (you should hear audio out of your primary output device) 119 | `15 .0 sinosc 200 * 300 + .0 sinosc .1 * play` 120 | 121 | When setting up your IDE, make sure it's using the ucrt64 libraries (C:\msys64\ucrt64\include) 122 | and binaries (C:\msys64\ucrt64\bin) for compilation / linking and NOT your native windows libraries / binaries. 123 | 124 | See README_VSCODE.md for vscode-specific setup. -------------------------------------------------------------------------------- /README_VSCODE.md: -------------------------------------------------------------------------------- 1 | # VS Code Development Setup 2 | 3 | The trick to making it work is to make sure VSCode is using the ucrt64 binaries for the development toolchain, 4 | NOT anything installed natively to Windows. 5 | 6 | 1. Install C/C++ extensions for VSCode. 7 | 2. Install Meson extension for VSCode. 8 | 3. In the settings for Meson, set Meson build path to `C:\msys64\ucrt64\bin\meson` and 9 | set the Build folder to `build`. 10 | 4. Add your `C:\msys64\ucrt\bin` folder to your Path (via Environment Variables). 11 | 5. Open a windows terminal and make sure that `gcc --version` and `g++ --version` and `gdb --version` return 12 | a version string. You can also confirm with `where gcc` that it's using the binary within msys2. 13 | 6. Before opening the folder in vscode, create a `.vscode` subdolder and populate it with some files. See the below "Config files" section for some example config files. Tweak the paths to match your own system. 14 | 7. Open a cpp file to make sure the extensions activate. 15 | 8. You should now have intellisense working (you should be able to "Go to definition" and "find references, etc..."). 16 | 9. You can now build using the Meson build task instead of msys2 shell if you prefer. For best 17 | debugging experience you probably want to build the `sapf_unoptimized_testable` target which disables optimizations. 18 | 10. You can debug via selecting the "Attach (sapf)" configuration (bottom left), manually 19 | running sapf.exe, and then presing F5 and attaching to the sapf.exe process. (Currently haven't figured out 20 | how to get the "launch" version working - it runs but the text is garbled - likely an encoding issue). 21 | - If it times out waiting to attach, try restarting VSCode and maybe also 22 | closing any msys2 shells if you have any open (not sure what's going on here)? 23 | 24 | 25 | 26 | #### Config files 27 | Below setup uses clang but it should work with other toolchains 28 | (clang can be installed via msys2's `mingw-w64-ucrt-x86_64-clang` package) 29 | Example `.vscode/c_cpp_properties.json` 30 | ```json 31 | { 32 | "configurations": [ 33 | { 34 | "name": "ucrt64", 35 | "includePath": [ 36 | "C:/msys64/ucrt64/include/**", 37 | "${workspaceFolder}/**" 38 | ], 39 | "defines": [ 40 | "_DEBUG", 41 | "UNICODE", 42 | "_UNICODE" 43 | ], 44 | "compilerPath": "C:/msys64/ucrt64/bin/clang++.exe", 45 | "windowsSdkVersion": "10.0.22621.0", 46 | "cStandard": "c17", 47 | "cppStandard": "c++17", 48 | "intelliSenseMode": "windows-clang-x64", 49 | "configurationProvider": "mesonbuild.mesonbuild" 50 | } 51 | ], 52 | "version": 4 53 | } 54 | ``` 55 | 56 | Example `.vscode/launch.json` 57 | ```json 58 | { 59 | "version": "0.2.0", 60 | "configurations": [ 61 | { 62 | "name": "(gdb) Attach", 63 | "type": "cppdbg", 64 | "request": "attach", 65 | "program": "${workspaceRoot}/build/sapf.exe", 66 | "MIMode": "gdb", 67 | "miDebuggerPath": "C:\\msys64\\ucrt64\\bin\\gdb.exe", 68 | "setupCommands": [ 69 | { 70 | "description": "Enable pretty-printing for gdb", 71 | "text": "-enable-pretty-printing", 72 | "ignoreFailures": true 73 | }, 74 | { 75 | "description": "Set Disassembly Flavor to Intel", 76 | "text": "-gdb-set disassembly-flavor intel", 77 | "ignoreFailures": true 78 | } 79 | ] 80 | }, 81 | { 82 | "name": "(gdb) Launch (chairbender note - not working ATM, use attach instead - everything is garbled)", 83 | "type": "cppdbg", 84 | "request": "launch", 85 | "program": "${workspaceRoot}/build/sapf.exe", 86 | "args": [], 87 | "stopAtEntry": false, 88 | "cwd": "${workspaceRoot}/build", 89 | "environment": [ 90 | { "name": "MSYSTEM", "value": "UCRT64" }, 91 | { "name": "MSYS2_PATH_TYPE", "value": "inherit" }, 92 | { "name": "PATH", "value": "C:\\msys64\\ucrt64\\bin;${env:PATH}" } 93 | ], 94 | "externalConsole": false, 95 | "MIMode": "gdb", 96 | "miDebuggerPath": "C:\\msys64\\ucrt64\\bin\\gdb.exe", 97 | "setupCommands": [ 98 | { 99 | "description": "Enable pretty-printing for gdb", 100 | "text": "-enable-pretty-printing", 101 | "ignoreFailures": true 102 | }, 103 | { 104 | "description": "Set Disassembly Flavor to Intel", 105 | "text": "-gdb-set disassembly-flavor intel", 106 | "ignoreFailures": true 107 | } 108 | ] 109 | }, 110 | { 111 | "name": "(gdb) Debug Test", 112 | "type": "cppdbg", 113 | "request": "launch", 114 | "program": "${workspaceRoot}/build/test_unoptimized.exe", 115 | "args": [], 116 | "stopAtEntry": false, 117 | "cwd": "${workspaceRoot}/build", 118 | "environment": [ 119 | { "name": "MSYSTEM", "value": "UCRT64" }, 120 | { "name": "MSYS2_PATH_TYPE", "value": "inherit" }, 121 | { "name": "PATH", "value": "C:\\msys64\\ucrt64\\bin;${env:PATH}" } 122 | ], 123 | "externalConsole": false, 124 | "MIMode": "gdb", 125 | "miDebuggerPath": "C:\\msys64\\ucrt64\\bin\\gdb.exe", 126 | "setupCommands": [ 127 | { 128 | "description": "Enable pretty-printing for gdb", 129 | "text": "-enable-pretty-printing", 130 | "ignoreFailures": true 131 | }, 132 | { 133 | "description": "Set Disassembly Flavor to Intel", 134 | "text": "-gdb-set disassembly-flavor intel", 135 | "ignoreFailures": true 136 | } 137 | ], 138 | "preLaunchTask": "Meson: Build test_unoptimized:executable" 139 | } 140 | ] 141 | } 142 | ``` 143 | 144 | Example `.vscode/settings.json` 145 | ```json 146 | { 147 | "C_Cpp.default.compileCommands": "c:\\msys64\\home\\kwhip\\sapf\\build/compile_commands.json", 148 | "C_Cpp.default.configurationProvider": "mesonbuild.mesonbuild" 149 | } 150 | ``` 151 | 152 | Example `.vscode/tasks.json` 153 | ```json 154 | { 155 | "version": "2.0.0", 156 | "tasks": [ 157 | { 158 | "type": "meson", 159 | "target": "sapf:executable", 160 | "mode": "build", 161 | "problemMatcher": [ 162 | "$meson-gcc" 163 | ], 164 | "group": "build", 165 | "label": "Meson: Build sapf:executable" 166 | } 167 | ] 168 | } 169 | ``` -------------------------------------------------------------------------------- /SoundAsPureForm.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SoundAsPureForm.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | outputs = { self, nixpkgs, flake-utils, ... }: 8 | let 9 | llvmPackages = pkgs: pkgs.llvmPackages_16; 10 | in 11 | { 12 | overlays.default = final: prev: 13 | let 14 | system = prev.stdenv.hostPlatform.system; 15 | in { 16 | # sapf = TODO; 17 | libdispatch = final.callPackage ./nix/libdispatch/default.nix { 18 | stdenv = (llvmPackages final).stdenv; 19 | }; 20 | }; 21 | } // 22 | (flake-utils.lib.eachDefaultSystem (system: 23 | let 24 | pkgs = import nixpkgs { 25 | inherit system; 26 | overlays = [self.overlays.default]; 27 | }; 28 | # stdenv = (llvmPackages pkgs).stdenv; 29 | in rec { 30 | # packages.default = pkgs.sapf; 31 | 32 | devShell = pkgs.mkShell.override { 33 | # inherit stdenv; 34 | } { 35 | buildInputs = with pkgs; [ 36 | fftw 37 | (llvmPackages pkgs).lldb 38 | libedit 39 | libsndfile 40 | meson 41 | ninja 42 | pkg-config 43 | rtaudio_6 44 | xsimd 45 | ]; 46 | 47 | # CC = "${stdenv}/bin/clang"; 48 | # CXX = "${stdenv}/bin/clang++"; 49 | }; 50 | } 51 | )); 52 | } 53 | -------------------------------------------------------------------------------- /include/AsyncAudioFileWriter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef SAPF_AUDIOTOOLBOX 4 | #include "ringbuffer.hpp" 5 | #include "Object.hpp" 6 | #include "Buffers.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /*! 13 | * A cross-platform alternative to ExtAudioFileWriteAsync. 14 | * This is NOT thread safe - only one thread should be writing to a given 15 | * file via this object. 16 | * Writes the data from RtBuffers to file (one buffer per channel). 17 | * Use writeAsync to capture the current values in the buffers. The values are stored in a 18 | * ring buffer and are asynchronously written out to the file. 19 | * Upon destruction, it blocks until the buffer is flushed. 20 | * The file is always written in wav format as floats. 21 | */ 22 | constexpr size_t ringBufferSize{1024 * 1024}; 23 | // max number of values to write per write call. 24 | constexpr size_t maxChunkSize{4096}; 25 | class AsyncAudioFileWriter { 26 | private: 27 | SNDFILE* mFile; 28 | std::thread mWriterThread; 29 | int mNumChannels; 30 | 31 | // number of values per each chunk that gets written to the ring buffer + written to disk. 32 | // it has to be a multiple of the number of channels as libsndfile won't allow 33 | // writing a partial segment, but we don't want it to be greater than maxChunkSize 34 | size_t mChunkSize; 35 | 36 | // uses a ptr so this doesn't get allocated on the stack 37 | std::unique_ptr> mRingBuffer; 38 | // a chunk of interleaved audio data from the incoming write request, 39 | // waiting to be written to the ring buffer. 40 | std::vector mRingWriteBuffer; 41 | // a chunk of interleaved audio data just removed from the ring buffer, 42 | // waiting to be written to file 43 | std::vector mWriteBuffer; 44 | 45 | std::mutex mBufferMutex; 46 | std::condition_variable mDataAvailableCondition; 47 | std::condition_variable mSpaceAvailableCondition; 48 | 49 | bool mRunning; 50 | 51 | public: 52 | AsyncAudioFileWriter(const std::string& path, int samplerate, int numChannels); 53 | ~AsyncAudioFileWriter(); 54 | // capture the current data in the buffers and submit it to be written asynchronously. 55 | void writeAsync(const RtBuffers& buffers, unsigned int nBufferFrames); 56 | 57 | private: 58 | void writeLoop(); 59 | }; 60 | 61 | #endif -------------------------------------------------------------------------------- /include/AudioToolboxBuffers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef SAPF_AUDIOTOOLBOX 4 | #include 5 | 6 | class AudioToolboxBuffers { 7 | public: 8 | AudioToolboxBuffers(int inNumChannels); 9 | ~AudioToolboxBuffers(); 10 | 11 | uint32_t numChannels(); 12 | void setNumChannels(size_t i, uint32_t numChannels); 13 | void setData(size_t i, void *data); 14 | void setSize(size_t i, uint32_t size); 15 | 16 | AudioBufferList *abl; 17 | }; 18 | #endif // SAPF_AUDIOTOOLBOX 19 | -------------------------------------------------------------------------------- /include/AudioToolboxSoundFile.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef SAPF_AUDIOTOOLBOX 4 | #include 5 | #include 6 | 7 | class AudioToolboxSoundFile { 8 | public: 9 | AudioToolboxSoundFile(ExtAudioFileRef inXAF, uint32_t inNumChannels, std::string inPath); 10 | ~AudioToolboxSoundFile(); 11 | 12 | uint32_t numChannels(); 13 | int pull(uint32_t *framesRead, AudioBuffers& buffers); 14 | 15 | ExtAudioFileRef mXAF; 16 | uint32_t mNumChannels; 17 | std::string mPath; 18 | 19 | static std::unique_ptr open(const char* path, double threadSampleRate); 20 | static std::unique_ptr create(const char *path, int numChannels, double threadSampleRate, double fileSampleRate, bool interleaved); 21 | }; 22 | 23 | #endif // SAPF_AUDIOTOOLBOX 24 | -------------------------------------------------------------------------------- /include/Buffers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(SAPF_AUDIOTOOLBOX) 4 | #include 5 | #elif defined(SAPF_RTAUDIO_H) 6 | #include SAPF_RTAUDIO_H 7 | #else 8 | #include 9 | #endif 10 | #include 11 | 12 | #ifdef SAPF_AUDIOTOOLBOX 13 | class AUBuffers { 14 | public: 15 | AUBuffers(AudioBufferList *inIoData); 16 | uint32_t count() const; 17 | float *data(int channel) const; 18 | uint32_t size(int channel) const; 19 | AudioBufferList *ioData; 20 | }; 21 | typedef AUBuffers Buffers; 22 | #else 23 | class RtBuffers { 24 | public: 25 | RtBuffers(float *inOut, uint32_t inCount, uint32_t inSize); 26 | RtBuffers(uint32_t inCount, uint32_t inSize); 27 | uint32_t count() const; 28 | float *data(int channel) const; 29 | // despite what this may imply, size isn't actually variable per buffer. 30 | // This is just for compatibility with AUBuffers 31 | uint32_t size(int channel) const; 32 | float *out; 33 | uint32_t theCount; 34 | uint32_t theSize; 35 | }; 36 | typedef RtBuffers Buffers; 37 | #endif -------------------------------------------------------------------------------- /include/DelayUGens.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __taggeddoubles__DelayUGens__ 18 | #define __taggeddoubles__DelayUGens__ 19 | 20 | #include "Object.hpp" 21 | 22 | void AddDelayUGenOps(); 23 | 24 | #endif /* defined(__taggeddoubles__DelayUGens__) */ 25 | -------------------------------------------------------------------------------- /include/ErrorCodes.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __ErrorCodes_h__ 18 | #define __ErrorCodes_h__ 19 | 20 | const int errNone = 0; 21 | const int errHalt = -1000; 22 | const int errFailed = -1001; 23 | const int errIndefiniteOperation = -1002; 24 | const int errWrongType = -1003; 25 | const int errOutOfRange = -1004; 26 | const int errSyntax = -1005; 27 | const int errInternalError = -1006; 28 | const int errWrongState = -1007; 29 | const int errNotFound = -1008; 30 | const int errStackOverflow = -1009; 31 | const int errStackUnderflow = -1010; 32 | const int errInconsistentInheritance = -1011; 33 | const int errUndefinedOperation = -1012; 34 | const int errUserQuit = -1013; 35 | const int kNumErrors = 14; 36 | 37 | extern const char* errString[kNumErrors]; 38 | 39 | #endif 40 | 41 | -------------------------------------------------------------------------------- /include/FilterUGens.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef taggeddoubles_FilterUGens_h 18 | #define taggeddoubles_FilterUGens_h 19 | 20 | #include "Object.hpp" 21 | 22 | void AddFilterUGenOps(); 23 | 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /include/Hash.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef _Hash_ 18 | #define _Hash_ 19 | 20 | #include 21 | 22 | 23 | // hash function for a string 24 | inline int32_t Hash(const char *inKey) 25 | { 26 | // the one-at-a-time hash. 27 | // a very good hash function. ref: a web page by Bob Jenkins. 28 | // http://www.burtleburtle.net/bob/hash/doobs.html 29 | int32_t hash = 0; 30 | while (*inKey) { 31 | hash += *inKey++; 32 | hash += hash << 10; 33 | hash ^= hash >> 6; 34 | } 35 | hash += hash << 3; 36 | hash ^= hash >> 11; 37 | hash += hash << 15; 38 | return hash; 39 | } 40 | 41 | // hash function for a string that also returns the length 42 | inline int32_t Hash(const char *inKey, size_t *outLength) 43 | { 44 | // the one-at-a-time hash. 45 | // a very good hash function. ref: a web page by Bob Jenkins. 46 | const char *origKey = inKey; 47 | int32_t hash = 0; 48 | while (*inKey) { 49 | hash += *inKey++; 50 | hash += hash << 10; 51 | hash ^= hash >> 6; 52 | } 53 | hash += hash << 3; 54 | hash ^= hash >> 11; 55 | hash += hash << 15; 56 | *outLength = inKey - origKey; 57 | return hash; 58 | } 59 | 60 | // hash function for an array of char 61 | inline int32_t Hash(const char *inKey, size_t inLength) 62 | { 63 | // the one-at-a-time hash. 64 | // a very good hash function. ref: a web page by Bob Jenkins. 65 | int32_t hash = 0; 66 | for (size_t i=0; i> 6; 70 | } 71 | hash += hash << 3; 72 | hash ^= hash >> 11; 73 | hash += hash << 15; 74 | return hash; 75 | } 76 | 77 | // hash function for integers 78 | inline int32_t Hash(int32_t inKey) 79 | { 80 | // Thomas Wang's integer hash. 81 | // http://www.concentric.net/~Ttwang/tech/inthash.htm 82 | // a faster hash for integers. also very good. 83 | uint32_t hash = (uint32_t)inKey; 84 | hash += ~(hash << 15); 85 | hash ^= hash >> 10; 86 | hash += hash << 3; 87 | hash ^= hash >> 6; 88 | hash += ~(hash << 11); 89 | hash ^= hash >> 16; 90 | return (int32_t)hash; 91 | } 92 | 93 | inline int64_t Hash64(int64_t inKey) 94 | { 95 | // Thomas Wang's 64 bit integer hash. 96 | uint64_t hash = (uint64_t)inKey; 97 | hash ^= ((~hash) >> 31); 98 | hash += (hash << 28); 99 | hash ^= (hash >> 21); 100 | hash += (hash << 3); 101 | hash ^= ((~hash) >> 5); 102 | hash += (hash << 13); 103 | hash ^= (hash >> 27); 104 | hash += (hash << 32); 105 | return (int64_t)hash; 106 | } 107 | 108 | inline int64_t Hash64bad(int64_t inKey) 109 | { 110 | // Thomas Wang's 64 bit integer hash. 111 | uint64_t hash = (uint64_t)inKey; 112 | hash = (~hash) + (hash << 21); // hash = (hash << 21) - hash - 1; 113 | hash ^= (hash >> 24); 114 | hash += (hash << 3) + (hash << 8); // hash * 265 115 | hash ^= (hash >> 14); 116 | hash += (hash << 2) + (hash << 4); // hash * 21 117 | hash ^= (hash >> 28); 118 | hash += (hash << 31); 119 | return (int64_t)hash; 120 | } 121 | 122 | inline int32_t Hash(const int32_t *inKey, int32_t inLength) 123 | { 124 | // one-at-a-time hashing of a string of int32_t's. 125 | // uses Thomas Wang's integer hash for the combining step. 126 | int32_t hash = 0; 127 | for (int i=0; i. 16 | 17 | #ifndef __taggeddoubles__Midi__ 18 | #define __taggeddoubles__Midi__ 19 | 20 | #include 21 | 22 | void AddMidiOps(); 23 | 24 | #endif /* defined(__taggeddoubles__Midi__) */ 25 | -------------------------------------------------------------------------------- /include/MultichannelExpansion.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __MultichannelExpansion_h__ 18 | #define __MultichannelExpansion_h__ 19 | 20 | #include "VM.hpp" 21 | 22 | Prim* mcx(int n, Arg f, const char* name, const char* help); 23 | Prim* automap(const char* mask, int n, Arg f, const char* inName, const char* inHelp); 24 | List* handleEachOps(Thread& th, int numArgs, Arg fun); 25 | void flop_(Thread& th, Prim* prim); 26 | void flops_(Thread& th, Prim* prim); 27 | void flop1_(Thread& th, Prim* prim); 28 | void lace_(Thread& th, Prim* prim); 29 | void sel_(Thread& th, Prim* prim); 30 | void sell_(Thread& th, Prim* prim); 31 | 32 | #endif 33 | 34 | -------------------------------------------------------------------------------- /include/Opcode.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __Opcode_h__ 18 | #define __Opcode_h__ 19 | 20 | #include "VM.hpp" 21 | 22 | enum { 23 | BAD_OPCODE, 24 | opNone, 25 | opPushImmediate, 26 | opPushLocalVar, 27 | opPushFunVar, 28 | opPushWorkspaceVar, 29 | 30 | opPushFun, 31 | 32 | opCallImmediate, 33 | opCallLocalVar, 34 | opCallFunVar, 35 | opCallWorkspaceVar, 36 | 37 | 38 | opDot, 39 | opComma, 40 | opBindLocal, 41 | opBindLocalFromList, 42 | opBindWorkspaceVar, 43 | opBindWorkspaceVarFromList, 44 | 45 | opParens, 46 | opNewVList, 47 | opNewZList, 48 | opNewForm, 49 | opInherit, 50 | opEach, 51 | 52 | opReturn, 53 | 54 | kNumOpcodes 55 | }; 56 | 57 | extern const char* opcode_name[kNumOpcodes]; 58 | 59 | #endif 60 | 61 | -------------------------------------------------------------------------------- /include/OscilUGens.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __taggeddoubles__OscilUGens__ 18 | #define __taggeddoubles__OscilUGens__ 19 | 20 | #include "Object.hpp" 21 | #include "UGen.hpp" 22 | 23 | void AddOscilUGenOps(); 24 | 25 | struct SinOsc : OneInputUGen 26 | { 27 | Z phase; 28 | Z freqmul; 29 | 30 | SinOsc(Thread& th, Arg freq, Z iphase); 31 | const char* TypeName() const override; 32 | void calc(int n, Z* out, Z* freq, int freqStride); 33 | }; 34 | 35 | struct SinOscPM : TwoInputUGen 36 | { 37 | Z phase; 38 | Z freqmul; 39 | 40 | SinOscPM(Thread& th, Arg freq, Arg phasemod); 41 | const char* TypeName() const override; 42 | void calc(int n, Z* out, Z* freq, Z* phasemod, int freqStride, int phasemodStride); 43 | }; 44 | 45 | void fillWaveTable(int n, const Z* amps, int ampStride, const Z* phases, int phaseStride, Z smooth, Z* table); 46 | 47 | #endif /* defined(__taggeddoubles__OscilUGens__) */ 48 | -------------------------------------------------------------------------------- /include/Parser.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __Parser_h__ 18 | #define __Parser_h__ 19 | 20 | #include "VM.hpp" 21 | 22 | bool parseElems(Thread& th, P& code); 23 | 24 | ////////////////////////////////// 25 | 26 | #pragma mark PIPER SYNTAX 27 | 28 | struct AST : RCObj 29 | { 30 | virtual const char* TypeName() const { return "AST"; } 31 | virtual void dump(std::ostream& ost, int indent) = 0; 32 | virtual void codegen(P& code) = 0; 33 | }; 34 | 35 | typedef P ASTPtr; 36 | 37 | ASTPtr parseExpr(const char*& in); 38 | 39 | void printi(std::ostream& ost, int indent, const char* fmt, ...); 40 | void prints(std::ostream& ost, const char* fmt, ...); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /include/Play.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "VM.hpp" 18 | 19 | void playWithPlayer(Thread& th, V& v); 20 | void recordWithPlayer(Thread& th, V& v, Arg filename); 21 | 22 | void stopPlaying(); 23 | void stopPlayingIfDone(); 24 | 25 | -------------------------------------------------------------------------------- /include/PortableBuffers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef SAPF_AUDIOTOOLBOX 4 | #include 5 | #include 6 | 7 | #include "VM.hpp" 8 | 9 | struct PortableBuffer { 10 | uint32_t numChannels; 11 | uint32_t size; 12 | void *data; 13 | 14 | PortableBuffer(); 15 | }; 16 | 17 | class PortableBuffers { 18 | public: 19 | PortableBuffers(int inNumChannels); 20 | ~PortableBuffers(); 21 | 22 | uint32_t numChannels(); 23 | void setNumChannels(size_t i, uint32_t numChannels); 24 | void setData(size_t i, void *data); 25 | void setSize(size_t i, uint32_t size); 26 | 27 | std::vector buffers; 28 | std::vector interleaved; 29 | }; 30 | #endif // SAPF_AUDIOTOOLBOX 31 | -------------------------------------------------------------------------------- /include/RCObj.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __no_web2__RCObj__ 18 | #define __no_web2__RCObj__ 19 | 20 | #include 21 | #include 22 | #include "rc_ptr.hpp" 23 | 24 | class RCObj 25 | { 26 | public: 27 | mutable std::atomic refcount; 28 | 29 | public: 30 | RCObj(); 31 | RCObj(RCObj const&); 32 | virtual ~RCObj(); 33 | 34 | void retain() const; 35 | void release(); 36 | virtual void norefs(); 37 | 38 | int32_t getRefcount() const { return refcount; } 39 | 40 | void negrefcount(); 41 | void alreadyDead(); 42 | 43 | 44 | virtual const char* TypeName() const = 0; 45 | }; 46 | 47 | inline void retain(RCObj* o) { o->retain(); } 48 | inline void release(RCObj* o) { o->release(); } 49 | 50 | #endif /* defined(__no_web2__RCObj__) */ 51 | -------------------------------------------------------------------------------- /include/SndfileSoundFile.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef SAPF_AUDIOTOOLBOX 4 | #include "PortableBuffers.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include "AsyncAudioFileWriter.hpp" 10 | 11 | class SndfileSoundFile { 12 | public: 13 | SndfileSoundFile(std::string path, std::unique_ptr writer, 14 | SNDFILE *inSndfile, int inNumChannels); 15 | ~SndfileSoundFile(); 16 | 17 | uint32_t numChannels() const; 18 | int pull(uint32_t *framesRead, PortableBuffers& buffers); 19 | // write to file synchronously (blocking). Only functions if create was called with async=false 20 | // bufs is expected to contain only a single buffer with the specified number of channels 21 | // and the buffer data already interleaved (for wav output), as floats. It should have 22 | // the exact amount of frames as indicated by numFrames. 23 | void write(int numFrames, const PortableBuffers& bufs) const; 24 | 25 | // write to file asynchronously (non-blocking). Only functions if create was called with async=true 26 | // captures the current data in the buffers and submits it to be written asynchronously. 27 | // will be flushed when this object is destructed. 28 | void writeAsync(const RtBuffers& buffers, unsigned int nBufferFrames) const; 29 | 30 | static std::unique_ptr open(const char *path); 31 | // async parameter determines whether async writing should be supported (writeAsync) or not (write). 32 | // they are mutually exclusive. 33 | static std::unique_ptr create(const char *path, int numChannels, double threadSampleRate, 34 | double fileSampleRate, bool async); 35 | private: 36 | // file path 37 | const std::string mPath; 38 | 39 | // for async output (recording) 40 | const std::unique_ptr mWriter; 41 | 42 | // for synchronous input / output 43 | SNDFILE* const mSndfile; 44 | // TODO: actually used for output? 45 | const int mNumChannels; 46 | }; 47 | #endif // SAPF_AUDIOTOOLBOX 48 | -------------------------------------------------------------------------------- /include/SoundFiles.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __taggeddoubles__SoundFiles__ 18 | #define __taggeddoubles__SoundFiles__ 19 | 20 | #include "VM.hpp" 21 | 22 | #include "AudioToolboxBuffers.hpp" 23 | #include "PortableBuffers.hpp" 24 | #ifdef SAPF_AUDIOTOOLBOX 25 | typedef AudioToolboxBuffers AudioBuffers; 26 | #else 27 | typedef PortableBuffers AudioBuffers; 28 | #endif 29 | 30 | #include "AudioToolboxSoundFile.hpp" 31 | #include "SndfileSoundFile.hpp" 32 | #ifdef SAPF_AUDIOTOOLBOX 33 | typedef AudioToolboxSoundFile SoundFile; 34 | #else 35 | typedef SndfileSoundFile SoundFile; 36 | #endif 37 | 38 | const int kMaxSFChannels = 1024; 39 | const int kBufSize = 1024; 40 | 41 | void makeRecordingPath(Arg filename, char* path, int len); 42 | 43 | #ifdef SAPF_AUDIOTOOLBOX 44 | std::unique_ptr sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved); 45 | #else 46 | // async indicates whether async writing should be supported, or only synchronous writing. They are mutually exclusive. 47 | std::unique_ptr sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved, bool async); 48 | #endif 49 | void sfwrite(Thread& th, V& v, Arg filename, bool openIt); 50 | void sfread(Thread& th, Arg filename, int64_t offset, int64_t frames); 51 | 52 | #endif /* defined(__taggeddoubles__SoundFiles__) */ 53 | -------------------------------------------------------------------------------- /include/Spectrogram.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef taggeddoubles_Spectrogram_h 18 | #define taggeddoubles_Spectrogram_h 19 | 20 | void spectrogram(int size, double* data, int width, int log2bins, const char* path, double dBfloor); 21 | 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /include/StreamOps.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Object.hpp" 4 | #include "ZArr.hpp" 5 | 6 | void hanning_(Thread& th, Prim* prim); 7 | void hamming_(Thread& th, Prim* prim); 8 | void blackman_(Thread& th, Prim* prim); 9 | #ifdef SAPF_ACCELERATE 10 | void wseg_apply_window(Z* segbuf, Z* window, int n); 11 | #else 12 | void wseg_apply_window(Z* segbuf, ZArr window, int n); 13 | #endif -------------------------------------------------------------------------------- /include/Types.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __boxeddoubles__Types__ 18 | #define __boxeddoubles__Types__ 19 | 20 | #include "Object.hpp" 21 | 22 | enum { 23 | stackEffectUnknown = -1 24 | }; 25 | 26 | enum { 27 | rankUnknown = -2, 28 | rankVariable = -1 29 | }; 30 | 31 | enum { 32 | shapeUnknown = -4, 33 | shapeInfinite = -3, 34 | shapeIndefinite = -2, 35 | shapeFinite = -1 36 | }; 37 | 38 | class StackEffect : public Object 39 | { 40 | int mTakes; 41 | int mLeaves; 42 | 43 | StackEffect() : mTakes(stackEffectUnknown), mLeaves(stackEffectUnknown) {} 44 | StackEffect(int inTakes, int inLeaves) : mTakes(stackEffectUnknown), mLeaves(stackEffectUnknown) {} 45 | }; 46 | 47 | class TypeEnvir; 48 | 49 | struct TypeShape 50 | { 51 | int mRank = rankUnknown; 52 | std::vector mShape; 53 | 54 | TypeShape() {} 55 | TypeShape(TypeShape const& that) : mRank(that.mRank), mShape(that.mShape) {} 56 | TypeShape& operator=(TypeShape const& that) { mRank = that.mRank; mShape = that.mShape; return *this; } 57 | 58 | bool unify(TypeShape const& inThat, TypeShape& outResult) 59 | { 60 | if (mRank == rankUnknown) { 61 | if (inThat.mRank == rankUnknown) { 62 | outResult = *this; 63 | return true; 64 | } 65 | outResult = inThat; 66 | return true; 67 | } 68 | if (mRank != inThat.mRank) 69 | return false; 70 | outResult = inThat; 71 | for (int i = 0; i < mRank; ++i) { 72 | int a = mShape[i]; 73 | int b = inThat.mShape[i]; 74 | int& c = outResult.mShape[i]; 75 | if (a == shapeUnknown) { 76 | c = b; 77 | } else if (b == shapeUnknown) { 78 | c = a; 79 | } else if (a != b) { 80 | return false; 81 | } else { 82 | c = a; 83 | } 84 | } 85 | return true; 86 | } 87 | 88 | // determine shape of auto mapped result 89 | // determing shape of binary operator results 90 | // e.g. [r...] * r -> [r...] 91 | // [[r]] * [r] -> [[r]] 92 | // [r..] * [r] -> [r] maximum rank, minimum shape. 93 | // also with each operators 94 | 95 | }; 96 | 97 | class Type : public Object 98 | { 99 | public: 100 | TypeShape mShape; 101 | 102 | Type() {} 103 | Type(TypeShape const& inShape) : mShape(inShape) {} 104 | virtual ~Type() {} 105 | 106 | virtual bool unify(P const& inThat, P& ioEnvir, P& outResult) = 0; 107 | 108 | virtual bool isTypeReal() const { return false; } 109 | virtual bool isTypeSignal() const { return false; } 110 | virtual bool isTypeRef() const { return false; } 111 | virtual bool isTypeFun() const { return false; } 112 | virtual bool isTypeForm() const { return false; } 113 | virtual bool isTypeTuple() const { return false; } 114 | }; 115 | 116 | class TypeUnknown : public Type 117 | { 118 | const char* TypeName() const { return "TypeReal"; } 119 | virtual bool unify(P const& inThat, P& ioEnvir, P& outResult) 120 | { 121 | TypeShape shape; 122 | if (!mShape.unify(inThat->mShape, shape)) 123 | return false; 124 | 125 | outResult = inThat; 126 | return true; 127 | } 128 | }; 129 | 130 | class TypeVar : public Object 131 | { 132 | int32_t mID; 133 | P mType; 134 | }; 135 | 136 | class TypeEnvir : public Object 137 | { 138 | std::vector> mTypeVars; 139 | }; 140 | 141 | class TypeReal : public Type 142 | { 143 | public: 144 | TypeReal() {} 145 | TypeReal(TypeShape const& inShape) : Type(inShape) {} 146 | 147 | 148 | const char* TypeName() const { return "TypeReal"; } 149 | 150 | virtual bool unify(P const& inThat, P& ioEnvir, P& outResult) 151 | { 152 | TypeShape shape; 153 | if (!mShape.unify(inThat->mShape, shape)) 154 | return false; 155 | 156 | if (inThat->isTypeReal() || inThat->isTypeSignal()) { 157 | outResult = new TypeReal(shape); 158 | return true; 159 | } 160 | return false; 161 | } 162 | }; 163 | 164 | class TypeSignal : public Type 165 | { 166 | int mSignalShape = shapeUnknown; 167 | public: 168 | 169 | TypeSignal() {} 170 | TypeSignal(TypeShape const& inShape, int inSignalShape) : Type(inShape), mSignalShape(inSignalShape) {} 171 | 172 | const char* TypeName() const { return "TypeSignal"; } 173 | 174 | virtual bool unify(P const& inThat, P& ioEnvir, P& outResult) 175 | { 176 | TypeShape shape; 177 | if (!mShape.unify(inThat->mShape, shape)) 178 | return false; 179 | 180 | if (inThat->isTypeReal()) { 181 | outResult = new TypeReal(shape); 182 | return true; 183 | } 184 | if (inThat->isTypeSignal()) { 185 | // unify signal shape 186 | outResult = new TypeSignal(shape, mSignalShape); 187 | return true; 188 | } 189 | return false; 190 | } 191 | }; 192 | 193 | class TypeRef : public Type 194 | { 195 | public: 196 | P mRefType; 197 | 198 | 199 | const char* TypeName() const { return "TypeRef"; } 200 | }; 201 | 202 | class TypeFun : public Type 203 | { 204 | std::vector> mInTypes; 205 | std::vector> mOutTypes; 206 | 207 | const char* TypeName() const { return "TypeFun"; } 208 | }; 209 | 210 | struct FieldType 211 | { 212 | P mLabel; 213 | P mType; 214 | }; 215 | 216 | class TypeForm : public Type 217 | { 218 | std::vector mFieldTypes; 219 | 220 | const char* TypeName() const { return "TypeForm"; } 221 | }; 222 | 223 | class TypeTuple : public Type 224 | { 225 | std::vector> mTypes; 226 | 227 | const char* TypeName() const { return "TypeTuple"; } 228 | }; 229 | 230 | #endif /* defined(__boxeddoubles__Types__) */ 231 | -------------------------------------------------------------------------------- /include/ZArr.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __ZArr_h__ 18 | #define __ZArr_h__ 19 | #ifndef SAPF_ACCELERATE 20 | #define _USE_MATH_DEFINES 21 | #include 22 | #include "Object.hpp" 23 | #include 24 | 25 | #if SAMPLE_IS_DOUBLE 26 | typedef Eigen::Map> ZArr; 27 | typedef Eigen::Map> CZArr; 28 | typedef xsimd::batch ZBatch; 29 | typedef int64_t Z_INT_EQUIV; 30 | #else 31 | typedef Eigen::Map> ZArr; 32 | typedef Eigen::Map> CZArr; 33 | typedef xsimd::batch ZBatch; 34 | typedef int32_t Z_INT_EQUIV; 35 | #endif 36 | 37 | constexpr size_t zbatch_size = ZBatch::size; 38 | 39 | // create mutable Eigen Map over an existing array, without copying 40 | ZArr zarr(Z *vec, int n, int stride); 41 | 42 | // create immutable Eigen Map over an existing array, without copying 43 | CZArr czarr(const Z *vec, int n, int stride); 44 | 45 | #endif 46 | #endif -------------------------------------------------------------------------------- /include/clz.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | /* 18 | 19 | count leading zeroes function and those that can be derived from it 20 | 21 | */ 22 | 23 | // TODO FIXME Replace with C++20's 24 | 25 | #ifndef _CLZ_ 26 | #define _CLZ_ 27 | 28 | 29 | 30 | static int32_t CLZ( int32_t arg ) 31 | { 32 | if (arg == 0) return 32; 33 | return __builtin_clz(arg); 34 | } 35 | 36 | 37 | static int64_t CLZ( int64_t arg ) 38 | { 39 | if (arg == 0) return 64; 40 | return __builtin_clzll(arg); 41 | } 42 | 43 | 44 | 45 | // count trailing zeroes 46 | inline int32_t CTZ(int32_t x) 47 | { 48 | return 32 - CLZ(~x & (x-1)); 49 | } 50 | 51 | // count leading ones 52 | inline int32_t CLO(int32_t x) 53 | { 54 | return CLZ(~x); 55 | } 56 | 57 | // count trailing ones 58 | inline int32_t CTO(int32_t x) 59 | { 60 | return 32 - CLZ(x & (~x-1)); 61 | } 62 | 63 | // number of bits required to represent x. 64 | inline int32_t NUMBITS(int32_t x) 65 | { 66 | return 32 - CLZ(x); 67 | } 68 | 69 | // log2 of the next power of two greater than or equal to x. 70 | inline int32_t LOG2CEIL(int32_t x) 71 | { 72 | return 32 - CLZ(x - 1); 73 | } 74 | 75 | // log2 of the next power of two greater than or equal to x. 76 | inline int64_t LOG2CEIL(int64_t x) 77 | { 78 | return 64 - CLZ(x - 1); 79 | } 80 | 81 | // next power of two greater than or equal to x 82 | inline int32_t NEXTPOWEROFTWO(int32_t x) 83 | { 84 | return int32_t(1) << LOG2CEIL(x); 85 | } 86 | 87 | // next power of two greater than or equal to x 88 | inline int64_t NEXTPOWEROFTWO(int64_t x) 89 | { 90 | return int64_t(1) << LOG2CEIL(x); 91 | } 92 | 93 | // is x a power of two 94 | inline bool ISPOWEROFTWO(int32_t x) 95 | { 96 | return (x & (x-1)) == 0; 97 | } 98 | 99 | inline bool ISPOWEROFTWO64(int64_t x) 100 | { 101 | return (x & (x-1)) == 0; 102 | } 103 | 104 | // input a series of counting integers, outputs a series of gray codes . 105 | inline int32_t GRAYCODE(int32_t x) 106 | { 107 | return x ^ (x>>1); 108 | } 109 | 110 | // find least significant bit 111 | inline int32_t LSBit(int32_t x) 112 | { 113 | return x & -x; 114 | } 115 | 116 | // find least significant bit position 117 | inline int32_t LSBitPos(int32_t x) 118 | { 119 | return CTZ(x & -x); 120 | } 121 | 122 | // find most significant bit position 123 | inline int32_t MSBitPos(int32_t x) 124 | { 125 | return 31 - CLZ(x); 126 | } 127 | 128 | // find most significant bit 129 | inline int32_t MSBit(int32_t x) 130 | { 131 | return int32_t(1) << MSBitPos(x); 132 | } 133 | 134 | // count number of one bits 135 | inline uint32_t ONES(uint32_t x) 136 | { 137 | uint32_t t; 138 | x = x - ((x >> 1) & 0x55555555); 139 | t = ((x >> 2) & 0x33333333); 140 | x = (x & 0x33333333) + t; 141 | x = (x + (x >> 4)) & 0x0F0F0F0F; 142 | x = x + (x << 8); 143 | x = x + (x << 16); 144 | return x >> 24; 145 | } 146 | 147 | // count number of zero bits 148 | inline uint32_t ZEROES(uint32_t x) 149 | { 150 | return ONES(~x); 151 | } 152 | 153 | 154 | // reverse bits in a word 155 | inline uint32_t BitReverse(uint32_t x) 156 | { 157 | x = ((x & 0xAAAAAAAA) >> 1) | ((x & 0x55555555) << 1); 158 | x = ((x & 0xCCCCCCCC) >> 2) | ((x & 0x33333333) << 2); 159 | x = ((x & 0xF0F0F0F0) >> 4) | ((x & 0x0F0F0F0F) << 4); 160 | x = ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8); 161 | return (x >> 16) | (x << 16); 162 | } 163 | 164 | // barrel shifts 165 | inline uint64_t RotateRight (int64_t ix, int64_t s) 166 | { 167 | uint64_t x = ix; 168 | s = s & 63; 169 | return (x << (64-s)) | (x >> s); 170 | } 171 | 172 | inline uint64_t RotateLeft (int64_t ix, int64_t s) 173 | { 174 | uint64_t x = ix; 175 | s = s & 63; 176 | return (x >> (64-s)) | (x << s); 177 | } 178 | 179 | inline uint32_t RotateRight (int32_t ix, int32_t s) 180 | { 181 | uint32_t x = ix; 182 | s = s & 31; 183 | return (x << (32-s)) | (x >> s); 184 | } 185 | 186 | inline uint32_t RotateLeft (int32_t ix, int32_t s) 187 | { 188 | uint32_t x = ix; 189 | s = s & 31; 190 | return (x >> (32-s)) | (x << s); 191 | } 192 | 193 | inline uint8_t RotateRight (int8_t ix, int8_t s) 194 | { 195 | uint8_t x = ix; 196 | s = s & 7; 197 | return (x << (8-s)) | (x >> s); 198 | } 199 | 200 | inline uint8_t RotateLeft (int8_t ix, int8_t s) 201 | { 202 | uint8_t x = ix; 203 | s = s & 7; 204 | return (x >> (8-s)) | (x << s); 205 | } 206 | 207 | #endif 208 | 209 | -------------------------------------------------------------------------------- /include/dsp.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __taggeddoubles__dsp__ 18 | #define __taggeddoubles__dsp__ 19 | 20 | #ifdef SAPF_ACCELERATE 21 | #include 22 | #else 23 | #include 24 | #endif // SAPF_ACCELERATE 25 | 26 | const int kMinFFTLogSize = 2; 27 | const int kMaxFFTLogSize = 16; 28 | 29 | class FFT { 30 | public: 31 | ~FFT(); 32 | void init(size_t log2n); 33 | void forward(double *inReal, double *inImag, double *outReal, double *outImag); 34 | void backward(double *inReal, double *inImag, double *outReal, double *outImag); 35 | void forward_in_place(double *ioReal, double *ioImag); 36 | void backward_in_place(double *ioReal, double *ioImag); 37 | void forward_real(double *inReal, double *outReal, double *outImag); 38 | void backward_real(double *inReal, double *inImag, double *outReal); 39 | 40 | size_t n; 41 | size_t log2n; 42 | private: 43 | #ifdef SAPF_ACCELERATE 44 | FFTSetupD setup; 45 | #else 46 | fftw_complex *in; 47 | fftw_complex *out; 48 | double *in_out_real; 49 | fftw_plan forward_out_of_place_plan; 50 | fftw_plan backward_out_of_place_plan; 51 | fftw_plan forward_in_place_plan; 52 | fftw_plan backward_in_place_plan; 53 | fftw_plan forward_real_plan; 54 | fftw_plan backward_real_plan; 55 | #endif // SAPF_ACCELERATE 56 | }; 57 | 58 | extern FFT ffts[kMaxFFTLogSize+1]; 59 | 60 | void initFFT(); 61 | void fft (int n, double* ioReal, double* ioImag); 62 | void ifft(int n, double* ioReal, double* ioImag); 63 | 64 | void fft (int n, double* inReal, double* inImag, double* outReal, double* outImag); 65 | void ifft(int n, double* inReal, double* inImag, double* outReal, double* outImag); 66 | 67 | void rfft(int n, double* inReal, double* outReal, double* outImag); 68 | void rifft(int n, double* inReal, double* inImag, double* outReal); 69 | 70 | #endif /* defined(__taggeddoubles__dsp__) */ 71 | -------------------------------------------------------------------------------- /include/elapsedTime.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __elapsedTime_h__ 18 | #define __elapsedTime_h__ 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | void initElapsedTime(); 25 | double elapsedTime(); 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif 30 | 31 | #endif 32 | 33 | -------------------------------------------------------------------------------- /include/lock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef SAPF_APPLE_LOCK 4 | #include 5 | 6 | typedef os_unfair_lock Lock; 7 | 8 | #define LOCK_DECLARE(name) mutable Lock name = OS_UNFAIR_LOCK_INIT 9 | 10 | class SpinLocker 11 | { 12 | Lock& lock; 13 | public: 14 | SpinLocker(Lock& inLock) : lock(inLock) 15 | { 16 | os_unfair_lock_lock(&lock); 17 | } 18 | ~SpinLocker() 19 | { 20 | os_unfair_lock_unlock(&lock); 21 | } 22 | }; 23 | 24 | #else 25 | #include 26 | #include 27 | #include 28 | 29 | typedef std::shared_mutex Lock; 30 | 31 | #define LOCK_DECLARE(name) mutable Lock name 32 | 33 | class SpinLocker 34 | { 35 | std::unique_lock w_lock; 36 | public: 37 | SpinLocker(Lock& inLock) : w_lock(inLock) 38 | {} 39 | ~SpinLocker() 40 | {} 41 | }; 42 | 43 | #endif // SAPF_APPLE_LOCK 44 | -------------------------------------------------------------------------------- /include/makeImage.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | typedef struct Bitmap Bitmap; 18 | 19 | Bitmap* createBitmap(int width, int height); 20 | void freeBitmap(Bitmap* bitmap); 21 | 22 | void setPixel(Bitmap* bitmap, int x, int y, int r, int g, int b, int a); 23 | void fillRect(Bitmap* bitmap, int x, int y, int width, int height, int r, int g, int b, int a); 24 | void writeBitmap(Bitmap* bitmap, const char *path); 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /include/primes.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include 18 | 19 | extern const int gLowPrimes[10]; 20 | extern const int gPrimeOffsets[8]; 21 | extern const int gPrimesShift[30]; 22 | 23 | const int kPrimesMaskSize = 33334; 24 | extern uint8_t gPrimesMask[]; 25 | 26 | bool isprime(int64_t n); 27 | 28 | int64_t nextPrime(int64_t x); 29 | -------------------------------------------------------------------------------- /include/rc_ptr.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | // An intrusive reference counting smart pointer. 18 | 19 | template 20 | class P 21 | { 22 | T* p_; 23 | public: 24 | typedef T elem_t; 25 | 26 | P() : p_(nullptr) {} 27 | P(T* p) : p_(p) { if (p_) retain(p_); } 28 | ~P() { if (p_) release(p_); /*p_ = (T*)0xdeaddeaddeaddeadLL;*/ } 29 | 30 | P(P const & r) : p_(r.p_) { if (p_) retain(p_); } 31 | template P(P const & r) : p_(r()) { retain(p_); } 32 | 33 | P(P && r) : p_(r.p_) { r.p_ = nullptr; } 34 | template P(P && r) : p_(r()) { r.p_ = nullptr; } 35 | 36 | P& operator=(P && r) 37 | { 38 | if (this != &r) { 39 | T* oldp = p_; 40 | p_ = r.p_; 41 | r.p_ = nullptr; 42 | if (oldp) release(oldp); 43 | } 44 | return *this; 45 | } 46 | 47 | void swap(P& r) { T* t = p_; p_ = r.p_; r.p_ = t; } 48 | 49 | T& operator*() const { return *p_; } 50 | T* operator->() const { return p_; } 51 | 52 | T* operator()() const { return p_; } 53 | T* get() const { return p_; } 54 | //template operator U*() const { return (U*)p_; } 55 | 56 | bool operator==(P const& that) const { return p_ == that.p_; } 57 | bool operator!=(P const& that) const { return p_ != that.p_; } 58 | bool operator==(T* p) const { return p_ == p; } 59 | bool operator!=(T* p) const { return p_ != p; } 60 | 61 | operator bool () const 62 | { 63 | return p_ != 0; 64 | } 65 | 66 | void set(T* p) 67 | { 68 | if (p != p_) 69 | { 70 | T* oldp = p_; 71 | if (p) retain(p); 72 | p_ = p; 73 | if (oldp) release(oldp); 74 | } 75 | } 76 | 77 | P& operator=(P const& r) 78 | { 79 | set(r.p_); 80 | return *this; 81 | } 82 | 83 | P& operator=(T* p) 84 | { 85 | set(p); 86 | return *this; 87 | } 88 | 89 | template 90 | P& operator=(U* p) 91 | { 92 | set(p); 93 | return *this; 94 | } 95 | 96 | }; 97 | 98 | template 99 | bool operator==(P const & a, P const & b) 100 | { 101 | return a.p_ == b.p_; 102 | } 103 | 104 | template 105 | bool operator!=(P const & a, P const & b) // never throws 106 | { 107 | return a.p_ != b.p_; 108 | } 109 | 110 | template 111 | bool operator==(P const & a, T * b) // never throws 112 | { 113 | return a.p_ == b; 114 | } 115 | 116 | template 117 | bool operator!=(P const & a, T * b) // never throws 118 | { 119 | return a.p_ != b; 120 | } 121 | 122 | template 123 | bool operator==(T * a, P const & b) // never throws 124 | { 125 | return a != b.p_; 126 | } 127 | 128 | template 129 | bool operator!=(T * a, P const & b) // never throws 130 | { 131 | return a == b.p_; 132 | } 133 | 134 | template 135 | bool operator<(P const & a, P const & b) // never throws 136 | { 137 | return a.p_ < b.p_; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /include/rgen.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __rgen_h__ 18 | #define __rgen_h__ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "Hash.hpp" 24 | 25 | inline uint64_t xorshift64star(uint64_t x) { 26 | x ^= x >> 12; // a 27 | x ^= x << 25; // b 28 | x ^= x >> 27; // c 29 | return x * UINT64_C(2685821657736338717); 30 | } 31 | 32 | inline uint64_t xorshift128plus(uint64_t s[2]) { 33 | uint64_t x = s[0]; 34 | uint64_t const y = s[1]; 35 | s[0] = y; 36 | x ^= x << 23; // a 37 | x ^= x >> 17; // b 38 | x ^= y ^ (y >> 26); // c 39 | s[1] = x; 40 | return x + y; 41 | } 42 | 43 | inline uint64_t rotl(const uint64_t x, int k) { 44 | return (x << k) | (x >> (64 - k)); 45 | } 46 | 47 | inline uint64_t xoroshiro128(uint64_t s[2]) { 48 | const uint64_t s0 = s[0]; 49 | uint64_t s1 = s[1]; 50 | const uint64_t result = s0 + s1; 51 | 52 | s1 ^= s0; 53 | s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b 54 | s[1] = rotl(s1, 36); // c 55 | 56 | return result; 57 | } 58 | 59 | inline double itof1(uint64_t i) 60 | { 61 | union { uint64_t i; double f; } u; 62 | u.i = 0x3FF0000000000000LL | (i >> 12); 63 | return u.f - 1.; 64 | } 65 | 66 | const double kScaleR63 = pow(2.,-63); 67 | const double kScaleR31 = pow(2.,-31); 68 | 69 | inline double itof2(uint64_t i, double a) 70 | { 71 | return (double)i * a * kScaleR63 - a; 72 | } 73 | 74 | inline double itof2(uint32_t i, double a) 75 | { 76 | return (double)i * a * kScaleR31 - a; 77 | } 78 | 79 | struct RGen 80 | { 81 | uint64_t s[2]; 82 | 83 | void init(int64_t seed); 84 | int64_t trand(); 85 | 86 | double drand(); // 0 .. 1 87 | double drand2(); // -1 .. 1 88 | double drand8(); // -1/8 .. 1/8 89 | double drand16(); // -1/16 .. 1/16 90 | 91 | double rand(double lo, double hi); 92 | double xrand(double lo, double hi); 93 | double linrand(double lo, double hi); 94 | double trirand(double lo, double hi); 95 | double coin(double p); 96 | 97 | int64_t irand(int64_t lo, int64_t hi); 98 | int64_t irand0(int64_t n); 99 | int64_t irand2(int64_t scale); 100 | int64_t ilinrand(int64_t lo, int64_t hi); 101 | int64_t itrirand(int64_t lo, int64_t hi); 102 | 103 | }; 104 | 105 | 106 | inline void RGen::init(int64_t seed) 107 | { 108 | s[0] = Hash64(seed + 0x43a68b0d0492ba51LL); 109 | s[1] = Hash64(seed + 0x56e376c6e7c29504LL); 110 | } 111 | 112 | inline int64_t RGen::trand() 113 | { 114 | return (int64_t)xoroshiro128(s); 115 | } 116 | 117 | inline double RGen::drand() 118 | { 119 | union { uint64_t i; double f; } u; 120 | u.i = 0x3FF0000000000000LL | ((uint64_t)trand() >> 12); 121 | return u.f - 1.; 122 | } 123 | 124 | inline double RGen::drand2() 125 | { 126 | union { uint64_t i; double f; } u; 127 | u.i = 0x4000000000000000LL | ((uint64_t)trand() >> 12); 128 | return u.f - 3.; 129 | } 130 | 131 | inline double RGen::drand8() 132 | { 133 | union { uint64_t i; double f; } u; 134 | u.i = 0x3FD0000000000000LL | ((uint64_t)trand() >> 12); 135 | return u.f - .375; 136 | } 137 | 138 | inline double RGen::drand16() 139 | { 140 | union { uint64_t i; double f; } u; 141 | u.i = 0x3FC0000000000000LL | ((uint64_t)trand() >> 12); 142 | return u.f - .1875; 143 | } 144 | 145 | inline double RGen::rand(double lo, double hi) 146 | { 147 | return lo + (hi - lo) * drand(); 148 | } 149 | 150 | inline double RGen::xrand(double lo, double hi) 151 | { 152 | return lo * pow(hi / lo, drand()); 153 | } 154 | 155 | inline double RGen::linrand(double lo, double hi) 156 | { 157 | return lo + (hi - lo) * std::min(drand(), drand()); 158 | } 159 | 160 | inline double RGen::trirand(double lo, double hi) 161 | { 162 | return lo + (hi - lo) * (.5 + .5 * (drand() - drand())); 163 | } 164 | 165 | inline double RGen::coin(double p) 166 | { 167 | return drand() < p ? 1. : 0.; 168 | } 169 | 170 | inline int64_t RGen::irand0(int64_t n) 171 | { 172 | return (int64_t)floor(n * drand()); 173 | } 174 | 175 | inline int64_t RGen::irand(int64_t lo, int64_t hi) 176 | { 177 | return lo + (int64_t)floor((hi - lo + 1) * drand()); 178 | } 179 | 180 | inline int64_t RGen::irand2(int64_t scale) 181 | { 182 | double fscale = (double)scale; 183 | return (int64_t)floor((2. * fscale + 1.) * drand() - fscale); 184 | } 185 | 186 | inline int64_t RGen::ilinrand(int64_t lo, int64_t hi) 187 | { 188 | return lo + (int64_t)floor((hi - lo) * std::min(drand(), drand())); 189 | } 190 | 191 | inline int64_t RGen::itrirand(int64_t lo, int64_t hi) 192 | { 193 | double scale = (double)(hi - lo); 194 | return lo + (int64_t)floor(scale * (.5 + .5 * (drand() - drand()))); 195 | } 196 | 197 | #endif 198 | 199 | -------------------------------------------------------------------------------- /include/symbol.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __symbol_h__ 18 | #define __symbol_h__ 19 | 20 | #include "Object.hpp" 21 | 22 | P getsym(const char* name); 23 | 24 | #endif 25 | 26 | -------------------------------------------------------------------------------- /libmanta/Manta.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTA_H 2 | #define _MANTA_H 3 | 4 | #include "MantaUSB.h" 5 | #include "MantaClient.h" 6 | #include "MantaServer.h" 7 | 8 | /************************************************************************//** 9 | * \class Manta 10 | * \brief Superclass that provides an interface to the Manta 11 | * 12 | * The Manta class is intended to use as a base class within your application. 13 | * In order to use libmanta, subclass this and define implementations for any 14 | * callback functions that you would like your application to be notified of. 15 | * The event functions are declared in the MantaClient interface. 16 | * 17 | * Creating an instance of the Manta class (or a subclass) will not initiate 18 | * a connection to any plugged-in mantas. Before you can start communicating 19 | * with the Manta you must call its Connect() method to connect to a physical 20 | * Manta over USB. 21 | * 22 | * Once connected, your application should periodically call the static method 23 | * HandleEvents(). This will take care of servicing the low-level USB 24 | * communication for all connected Mantas. 25 | ****************************************************************************/ 26 | class Manta : 27 | public MantaUSB, 28 | public MantaClient, 29 | public MantaServer 30 | { 31 | public: 32 | Manta(void); 33 | 34 | /* MantaServer messages implemented here */ 35 | virtual void SetPadLED(LEDState state, int ledID); 36 | virtual void SetPadLEDRow(LEDState state, int row, uint8_t mask); 37 | virtual void SetPadLEDColumn(LEDState state, int column, uint8_t mask); 38 | virtual void SetPadLEDFrame(LEDState state, uint8_t mask[]); 39 | virtual void SetSliderLED(LEDState state, int id, uint8_t mask); 40 | virtual void SetButtonLED(LEDState state, int id); 41 | virtual void ResendLEDState(void); 42 | virtual void ClearPadAndButtonLEDs(void); 43 | virtual void ClearButtonLEDs(void); 44 | virtual void Recalibrate(void); 45 | virtual void SetLEDControl(LEDControlType control, bool state); 46 | virtual void SetTurboMode(bool Enabled); 47 | virtual void SetRawMode(bool Enabled); 48 | virtual void SetMaxSensorValues(int *values); 49 | 50 | private: 51 | /* declare superclass callback implemented by this class */ 52 | virtual void FrameReceived(int8_t *frame); 53 | int ScaleSensorValue(int rawValue, int index); 54 | 55 | static uint8_t byteReverse(uint8_t inByte); 56 | static int CalculateVelocity(int firstValue, int secondValue); 57 | static const int AmberIndex = 0; 58 | static const int RedIndex = 10; 59 | static const int SliderIndex = 7; 60 | static const int ButtonIndex = 6; 61 | static const int ConfigIndex = 9; 62 | static const int AverageMaxSensorValues[53]; 63 | 64 | int MaxSensorValues[53]; 65 | uint8_t LastInReport[InPacketLen]; 66 | uint8_t CurrentOutReport[OutPacketLen]; 67 | bool VelocityWaiting[53]; 68 | /* output modes */ 69 | bool CentroidEnabled; 70 | bool MaximumEnabled; 71 | bool PadFrameEnabled; 72 | }; 73 | 74 | #endif // _MANTA_H 75 | -------------------------------------------------------------------------------- /libmanta/MantaClient.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTACLIENT_H 2 | #define _MANTACLIENT_H 3 | 4 | #include 5 | 6 | /************************************************************************//** 7 | * \class MantaClient 8 | * \brief Interface defining all the Events generated by the Manta 9 | * 10 | * The MantaClient virtual class defines all the Events that could be 11 | * generated by the Manta. Your object should provide implementations 12 | * to any of these events that you'd like to listen for 13 | ****************************************************************************/ 14 | class MantaClient 15 | { 16 | public: 17 | virtual ~MantaClient() {} 18 | /* declare callbacks to be implemented by subclasses */ 19 | virtual void PadEvent(int row, int column, int id, int value) {} 20 | virtual void SliderEvent(int id, int value) {} 21 | virtual void ButtonEvent(int id, int value) {} 22 | virtual void PadVelocityEvent(int row, int column, int id, int velocity) {} 23 | virtual void ButtonVelocityEvent(int id, int velocity) {} 24 | virtual void FrameEvent(uint8_t *frame) {} 25 | virtual void DebugPrint(const char *fmt, ...) {} 26 | }; 27 | #endif /* _MANTACLIENT_H */ 28 | -------------------------------------------------------------------------------- /libmanta/MantaExceptions.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTAEXCEPTIONS_H 2 | #define _MANTAEXCEPTIONS_H 3 | 4 | #include 5 | 6 | class LibusbInitException : public std::runtime_error 7 | { 8 | public: 9 | LibusbInitException() : 10 | runtime_error("Error initializing libusb") 11 | { 12 | } 13 | }; 14 | 15 | class MantaNotConnectedException : public std::runtime_error 16 | { 17 | public: 18 | MantaNotConnectedException(MantaUSB *manta) : 19 | runtime_error("Attempted to access the Manta without connecting"), 20 | errorManta(manta) 21 | { 22 | } 23 | MantaUSB *errorManta; 24 | }; 25 | 26 | class MantaNotFoundException : public std::runtime_error 27 | { 28 | public: 29 | MantaNotFoundException() : 30 | runtime_error("Could not find an attached Manta") 31 | { 32 | } 33 | }; 34 | 35 | class MantaOpenException : public std::runtime_error 36 | { 37 | public: 38 | MantaOpenException() : 39 | runtime_error("Could not connect to attached Manta") 40 | { 41 | } 42 | }; 43 | 44 | class MantaCommunicationException : public std::runtime_error 45 | { 46 | public: 47 | MantaCommunicationException(MantaUSB *manta = NULL) : 48 | runtime_error("Communication with Manta interrupted"), 49 | errorManta(manta) 50 | { 51 | } 52 | MantaUSB *errorManta; 53 | }; 54 | 55 | #endif // _MANTAEXCEPTIONS_H 56 | -------------------------------------------------------------------------------- /libmanta/MantaMulti.cpp: -------------------------------------------------------------------------------- 1 | #include "MantaMulti.h" 2 | #include 3 | #include 4 | #include 5 | 6 | MantaMulti::MantaMulti(MantaClient *client) : 7 | ReferenceCount(0) 8 | { 9 | AttachClient(client); 10 | } 11 | 12 | void MantaMulti::AttachClient(MantaClient *client) 13 | { 14 | if(NULL != client) 15 | { 16 | ClientList.push_back(client); 17 | ++ReferenceCount; 18 | } 19 | } 20 | 21 | void MantaMulti::DetachClient(MantaClient *client) 22 | { 23 | list::iterator foundIter; 24 | foundIter = find(ClientList.begin(), ClientList.end(), client); 25 | if(ClientList.end() != foundIter) 26 | { 27 | ClientList.erase(foundIter); 28 | --ReferenceCount; 29 | } 30 | } 31 | 32 | int MantaMulti::GetReferenceCount() 33 | { 34 | return ReferenceCount; 35 | } 36 | 37 | void MantaMulti::PadEvent(int row, int column, int id, int value) 38 | { 39 | for(list::iterator i = ClientList.begin(); 40 | i != ClientList.end(); ++i) 41 | { 42 | (*i)->PadEvent(row, column, id, value); 43 | } 44 | } 45 | 46 | void MantaMulti::SliderEvent(int id, int value) 47 | { 48 | for(list::iterator i = ClientList.begin(); 49 | i != ClientList.end(); ++i) 50 | { 51 | (*i)->SliderEvent(id, value); 52 | } 53 | } 54 | 55 | void MantaMulti::ButtonEvent(int id, int value) 56 | { 57 | for(list::iterator i = ClientList.begin(); 58 | i != ClientList.end(); ++i) 59 | { 60 | (*i)->ButtonEvent(id, value); 61 | } 62 | } 63 | 64 | void MantaMulti::PadVelocityEvent(int row, int column, int id, int velocity) 65 | { 66 | for(list::iterator i = ClientList.begin(); 67 | i != ClientList.end(); ++i) 68 | { 69 | (*i)->PadVelocityEvent(row, column, id, velocity); 70 | } 71 | } 72 | 73 | void MantaMulti::ButtonVelocityEvent(int id, int velocity) 74 | { 75 | for(list::iterator i = ClientList.begin(); 76 | i != ClientList.end(); ++i) 77 | { 78 | (*i)->ButtonVelocityEvent(id, velocity); 79 | } 80 | } 81 | 82 | void MantaMulti::FrameEvent(uint8_t *frame) 83 | { 84 | for(list::iterator i = ClientList.begin(); 85 | i != ClientList.end(); ++i) 86 | { 87 | (*i)->FrameEvent(frame); 88 | } 89 | } 90 | 91 | /* 92 | void MantaMulti::DebugPrint(const char *fmt, ...) 93 | { 94 | if(!ClientList.empty()) 95 | { 96 | va_list args; 97 | char string[256]; 98 | va_start(args, fmt); 99 | vsprintf(string, fmt, args); 100 | va_end (args); 101 | ClientList.front()->DebugPrint(string); 102 | } 103 | } 104 | */ 105 | 106 | -------------------------------------------------------------------------------- /libmanta/MantaMulti.h: -------------------------------------------------------------------------------- 1 | #ifndef MANTAMULTI_H 2 | #define MANTAMULTI_H 3 | 4 | #include "Manta.h" 5 | #include 6 | 7 | using namespace std; 8 | 9 | /************************************************************************//** 10 | * \class MantaMulti 11 | * \brief Superclass that adds functionality for multiple access to a single 12 | * Manta 13 | * 14 | * This class can be used by an application that wants to have multiple 15 | * MantaClients that connect to a single Manta using the MantaServer API. 16 | * If you only need single-access you should use the Manta class instead 17 | ****************************************************************************/ 18 | class MantaMulti : public Manta 19 | { 20 | public: 21 | MantaMulti(MantaClient *client = NULL); 22 | void AttachClient(MantaClient *client); 23 | void DetachClient(MantaClient *client); 24 | int GetReferenceCount(); 25 | 26 | protected: 27 | void PadEvent(int row, int column, int id, int value); 28 | void SliderEvent(int id, int value); 29 | void ButtonEvent(int id, int value); 30 | void PadVelocityEvent(int row, int column, int id, int velocity); 31 | void ButtonVelocityEvent(int id, int velocity); 32 | void FrameEvent(uint8_t *frame); 33 | //void DebugPrint(const char *fmt, ...); 34 | 35 | private: 36 | list ClientList; 37 | int ReferenceCount; 38 | }; 39 | 40 | #endif /* MANTAMULTI_H */ 41 | -------------------------------------------------------------------------------- /libmanta/MantaServer.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTASERVER_H 2 | #define _MANTASERVER_H 3 | 4 | /************************************************************************//** 5 | * \class MantaServer 6 | * \brief Interface defining all the Messages that can be sent to a Manta 7 | * 8 | * The MantaServer virtual class defines all the Messages that the Manta 9 | * understands, as well as the data structures used as arguments. If you 10 | * need a pointer to a Manta in your code, you can make it more general by 11 | * using a MantaServer pointer instead of a pointer to your specific subclass. 12 | ****************************************************************************/ 13 | class MantaServer 14 | { 15 | public: 16 | 17 | enum LEDState { 18 | Off, 19 | Amber, 20 | Red, 21 | All, // only used in SetPadLEDFrame 22 | }; 23 | enum LEDControlType { 24 | PadAndButton, 25 | Slider, 26 | Button 27 | }; 28 | typedef uint8_t LEDFrame[6]; 29 | 30 | virtual ~MantaServer() {} 31 | /* declare callbacks to be implemented by subclasses */ 32 | virtual void SetPadLED(LEDState state, int ledID) = 0; 33 | virtual void SetPadLEDRow(LEDState state, int row, uint8_t mask) = 0; 34 | virtual void SetPadLEDColumn(LEDState state, int column, uint8_t mask) = 0; 35 | virtual void SetPadLEDFrame(LEDState state, uint8_t mask[]) = 0; 36 | virtual void SetSliderLED(LEDState state, int id, uint8_t mask) = 0; 37 | virtual void SetButtonLED(LEDState state, int id) = 0; 38 | virtual void ResendLEDState(void) = 0; 39 | virtual void ClearPadAndButtonLEDs(void) = 0; 40 | virtual void ClearButtonLEDs(void) = 0; 41 | virtual void Recalibrate(void) = 0; 42 | virtual void SetLEDControl(LEDControlType control, bool state) = 0; 43 | virtual void SetTurboMode(bool Enabled) = 0; 44 | virtual void SetRawMode(bool Enabled) = 0; 45 | virtual void SetMaxSensorValues(int *values) = 0; 46 | }; 47 | #endif /* _MANTASERVER_H */ 48 | -------------------------------------------------------------------------------- /libmanta/MantaUSB.cpp: -------------------------------------------------------------------------------- 1 | #include "extern/hidapi/hidapi/hidapi.h" 2 | #include 3 | #include "MantaUSB.h" 4 | #include "MantaExceptions.h" 5 | #include 6 | #include 7 | #include 8 | 9 | MantaUSB::MantaUSB(void) : 10 | SerialNumber(0), 11 | DeviceHandle(NULL) 12 | { 13 | mantaList.push_back(this); 14 | MantaIndex = int(mantaList.size()); 15 | 16 | DebugPrint("%s-%d: Manta %d initialized", __FILE__, __LINE__, MantaIndex); 17 | } 18 | 19 | MantaUSB::~MantaUSB(void) 20 | { 21 | Disconnect(); 22 | mantaList.remove(this); 23 | if(mantaList.empty()) 24 | { 25 | hid_exit(); 26 | } 27 | } 28 | 29 | bool MantaUSB::MessageQueued(void) 30 | { 31 | return GetQueuedTxMessage() != NULL; 32 | } 33 | 34 | /************************************************************************//** 35 | * \brief Writes a USB transfer frame down to the Manta 36 | * \param frame Pointer to the frame to be transmitted 37 | * \param forceQueued Forces this message to be queued instead of merged 38 | * 39 | * WriteFrame() is meant to be called by the Manta subclass, which defines 40 | * methods for the individual messages (setLED, etc). libmanta maintains a 41 | * message queue that gets popped from in the HandleEvents() handler. 42 | * 43 | * The default behavior is that if a message is already queued up for a given 44 | * Manta, subsequent message will be merged into the waiting message instead of 45 | * being further queued (the queued frame will be the end result of all queued 46 | * messages). forceQueued can be set to true to force the message to be queued 47 | * as a separate message instead of being merged 48 | * 49 | * Note: Because WriteFrame() accesses the same message queue that 50 | * HandleEvents() does, they should be protected from each other by a mutex on 51 | * the application level if they're being called from parallel threads. 52 | ****************************************************************************/ 53 | void MantaUSB::WriteFrame(uint8_t *frame, bool forceQueued) 54 | { 55 | if(NULL == DeviceHandle) 56 | { 57 | throw(MantaNotConnectedException(this)); 58 | } 59 | MantaTxQueueEntry *queuedMessage = GetQueuedTxMessage(); 60 | if(queuedMessage && !forceQueued) 61 | { 62 | /* replace the queued packet payload with the new one */ 63 | for(int i = 0; i < OutPacketLen; ++i) 64 | { 65 | /* the first byte of the report is the report ID (0x00) */ 66 | queuedMessage->OutFrame[i+1] = frame[i]; 67 | } 68 | DebugPrint("%s-%d: (WriteFrame) Queued Transfer overwritten on Manta %d", 69 | __FILE__, __LINE__, GetSerialNumber()); 70 | } 71 | else 72 | { 73 | /* no transfer in progress, queue up a new one */ 74 | MantaTxQueueEntry *newMessage = new MantaTxQueueEntry; 75 | newMessage->OutFrame[0] = 0; 76 | newMessage->TargetManta = this; 77 | /* the first byte of the report is the report ID (0x00) */ 78 | memcpy(newMessage->OutFrame + 1, frame, OutPacketLen); 79 | txQueue.push_back(newMessage); 80 | DebugPrint("%s-%d: (WriteFrame) Transfer Queued on Manta %d", 81 | __FILE__, __LINE__, GetSerialNumber()); 82 | } 83 | } 84 | 85 | /************************************************************************//** 86 | * \brief Queries connection status of the Manta 87 | * \returns true if this instance is connected to a physical Manta, false 88 | * if not 89 | * 90 | ****************************************************************************/ 91 | bool MantaUSB::IsConnected(void) 92 | { 93 | return DeviceHandle != NULL; 94 | } 95 | 96 | /************************************************************************//** 97 | * \brief Connects this instance to a Manta 98 | * \param connectionSerial The serial number of the manta to search for. 99 | * 100 | * If connectionSerial is left out or given as 0 then any connected Manta will 101 | * match. If a serial number is given then libmanta will attempt to connect to 102 | * that Manta. If no matching manta is found then Connect will throw a 103 | * MantaNotFoundException. If no exception is thrown then the connection can be 104 | * assumed to have been successful. 105 | * 106 | ****************************************************************************/ 107 | void MantaUSB::Connect(int connectionSerial) 108 | { 109 | #define SERIAL_STRING_SIZE 32 110 | wchar_t serialString[SERIAL_STRING_SIZE]; 111 | 112 | if(IsConnected()) 113 | { 114 | return; 115 | } 116 | 117 | DebugPrint("%s-%d: Attempting to Connect to Manta %d...", 118 | __FILE__, __LINE__, connectionSerial); 119 | if(connectionSerial) 120 | { 121 | swprintf(serialString, SERIAL_STRING_SIZE, L"%d", connectionSerial); 122 | DeviceHandle = hid_open(VendorID, ProductID, serialString); 123 | } 124 | else 125 | { 126 | DeviceHandle = hid_open(VendorID, ProductID, NULL); 127 | } 128 | if(NULL == DeviceHandle) 129 | throw(MantaNotFoundException()); 130 | hid_get_serial_number_string(DeviceHandle, serialString, SERIAL_STRING_SIZE); 131 | SerialNumber = int(wcstol(serialString, NULL, 10)); 132 | int rc = hid_set_nonblocking(DeviceHandle, 1); 133 | printf("hid_set_nonblocking %d\n", rc); 134 | printf("SerialNumber %d\n", SerialNumber); 135 | } 136 | 137 | /************************************************************************//** 138 | * \brief Disconnects this instance from an attached Manta 139 | ****************************************************************************/ 140 | void MantaUSB::Disconnect(void) 141 | { 142 | if(! IsConnected()) 143 | { 144 | return; 145 | } 146 | 147 | DebugPrint("%s-%d: Manta %d Disconnecting...", __FILE__, __LINE__, GetSerialNumber()); 148 | hid_close(DeviceHandle); 149 | DeviceHandle = NULL; 150 | } 151 | 152 | /************************************************************************//** 153 | * \brief Services USB communciation with the Manta 154 | * 155 | * HandleEvents should be called periodically to poll all connected Mantas for 156 | * incoming USB frames as well as to send any messages that have been queued 157 | * up with WriteFrame(). It should be called at least once every 6ms, but you 158 | * may get improved results polling as fast as every 1ms if your application 159 | * supports it. 160 | * 161 | * Note: Because WriteFrame() accesses the same message queue that HandleEvents() 162 | * does, they should be protected from each other by a mutex on the application 163 | * level if they're being called from parallel threads. 164 | ****************************************************************************/ 165 | void MantaUSB::HandleEvents(void) 166 | { 167 | list::iterator i = mantaList.begin(); 168 | /* read from each manta and trigger any events */ 169 | while(mantaList.end() != i) 170 | { 171 | MantaUSB *current = *i; 172 | if(current->IsConnected()) 173 | { 174 | int bytesRead; 175 | int8_t inFrame[InPacketLen]; 176 | 177 | bytesRead = hid_read(current->DeviceHandle, 178 | reinterpret_cast(inFrame), InPacketLen); 179 | if(bytesRead < 0) 180 | { 181 | current->DebugPrint("%s-%d: Read error on Manta %d", 182 | __FILE__, __LINE__, current->GetSerialNumber()); 183 | throw(MantaCommunicationException(current)); 184 | } 185 | else if(bytesRead) 186 | { 187 | current->FrameReceived(inFrame); 188 | } 189 | } 190 | ++i; 191 | } 192 | 193 | /* pop one item off the transmit queue and send down to its target */ 194 | if(! txQueue.empty()) 195 | { 196 | int bytesWritten; 197 | MantaTxQueueEntry *txMessage = txQueue.front(); 198 | txQueue.pop_front(); 199 | bytesWritten = hid_write(txMessage->TargetManta->DeviceHandle, 200 | txMessage->OutFrame, OutPacketLen + 1); 201 | txMessage->TargetManta->DebugPrint("%s-%d: Frame Written to Manta %d", 202 | __FILE__, __LINE__, txMessage->TargetManta->GetSerialNumber()); 203 | for(int j = 0; j < 16; j += 8) 204 | { 205 | uint8_t *frame = txMessage->OutFrame + 1; 206 | txMessage->TargetManta->DebugPrint("\t\t%x %x %x %x %x %x %x %x", 207 | frame[j], frame[j+1], frame[j+2], frame[j+3], frame[j+4], 208 | frame[j+5], frame[j+6], frame[j+7]); 209 | } 210 | delete txMessage; 211 | if(bytesWritten < 0) 212 | { 213 | txMessage->TargetManta->DebugPrint("%s-%d: Write error on Manta %d", 214 | __FILE__, __LINE__, txMessage->TargetManta->GetSerialNumber()); 215 | throw(MantaCommunicationException(txMessage->TargetManta)); 216 | } 217 | } 218 | } 219 | 220 | /************************************************************************//** 221 | * \brief Queries the serial number of the attached Manta 222 | * 223 | * \returns The serial number as an int 224 | ****************************************************************************/ 225 | int MantaUSB::GetSerialNumber(void) 226 | { 227 | return SerialNumber; 228 | } 229 | 230 | /************************************************************************//** 231 | * \brief Returns the Hardware Version (1 or 2) of the attached Manta 232 | * 233 | * \returns The hardware version 234 | ****************************************************************************/ 235 | int MantaUSB::GetHardwareVersion(void) 236 | { 237 | return (SerialNumber < 70) ? 1 : 2; 238 | } 239 | 240 | MantaUSB::MantaTxQueueEntry *MantaUSB::GetQueuedTxMessage() 241 | { 242 | list::iterator i = txQueue.begin(); 243 | /* look for the first queued message matching this manta */ 244 | while(txQueue.end() != i) 245 | { 246 | if((*i)->TargetManta == this) 247 | { 248 | return *i; 249 | } 250 | ++i; 251 | } 252 | return NULL; 253 | } 254 | 255 | /* define static class members */ 256 | list MantaUSB::mantaList; 257 | list MantaUSB::txQueue; 258 | -------------------------------------------------------------------------------- /libmanta/MantaUSB.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTAUSB_H 2 | #define _MANTAUSB_H 3 | 4 | 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | /* forward-declare hidapi types so we don't need to include the 11 | * whole header file */ 12 | 13 | typedef struct hid_device_ hid_device; 14 | 15 | /************************************************************************//** 16 | * \class MantaUSB 17 | * \brief Superclass that handles the low-level USB communication with the 18 | * Manta 19 | * 20 | * The MantaUSB class handles the low-level USB communication with the Manta 21 | * over hidAPI, a cross-platform HID library. 22 | * 23 | * The public methods of this class are meant to be used by applciations using 24 | * libmanta to manage the connection to a physical Manta, as well as servicing 25 | * the low-level USB drivers by periodically calling the HandleEvents() 26 | * function. 27 | ****************************************************************************/ 28 | class MantaUSB 29 | { 30 | public: 31 | MantaUSB(void); 32 | virtual ~MantaUSB(void); 33 | void WriteFrame(uint8_t *frame, bool forceQueued); 34 | bool IsConnected(void); 35 | void Connect(int connectionSerial = 0); 36 | void Disconnect(); 37 | int GetSerialNumber(void); 38 | int GetHardwareVersion(void); 39 | bool MessageQueued(void); 40 | 41 | static void HandleEvents(void); 42 | 43 | protected: 44 | virtual void FrameReceived(int8_t *frame) = 0; 45 | virtual void DebugPrint(const char *fmt, ...) {} 46 | static const int OutPacketLen = 16; 47 | static const int InPacketLen = 64; 48 | int SerialNumber; 49 | int MantaIndex; 50 | 51 | private: 52 | struct MantaTxQueueEntry 53 | { 54 | MantaUSB *TargetManta; 55 | uint8_t OutFrame[17]; 56 | }; 57 | 58 | MantaTxQueueEntry *GetQueuedTxMessage(); 59 | 60 | static const int Interface = 0; 61 | static const int EndpointIn = 0x81; /* endpoint 0x81 address for IN */ 62 | static const int EndpointOut = 0x02; /* endpoint 1 address for OUT */ 63 | static const int Timeout = 5000; /* timeout in ms */ 64 | static const int VendorID = 0x2424; 65 | static const int ProductID = 0x2424; 66 | 67 | hid_device *DeviceHandle; 68 | 69 | static list mantaList; 70 | static list txQueue; 71 | }; 72 | 73 | #endif // _MANTAUSB_H 74 | -------------------------------------------------------------------------------- /libmanta/MantaVersion.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTAVERSION_H 2 | #define _MANTAVERSION_H 3 | 4 | #define LIBMANTA_MAJOR_VERSION 1 5 | #define LIBMANTA_MINOR_VERSION 3 6 | 7 | #endif // _MANTAVERSION_H 8 | -------------------------------------------------------------------------------- /libmanta/extern/hidapi/m4/pkg.m4: -------------------------------------------------------------------------------- 1 | # pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- 2 | # 3 | # Copyright © 2004 Scott James Remnant . 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | # 19 | # As a special exception to the GNU General Public License, if you 20 | # distribute this file as part of a program that contains a 21 | # configuration script generated by Autoconf, you may include it under 22 | # the same distribution terms that you use for the rest of that program. 23 | 24 | # PKG_PROG_PKG_CONFIG([MIN-VERSION]) 25 | # ---------------------------------- 26 | AC_DEFUN([PKG_PROG_PKG_CONFIG], 27 | [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) 28 | m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) 29 | AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl 30 | if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then 31 | AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) 32 | fi 33 | if test -n "$PKG_CONFIG"; then 34 | _pkg_min_version=m4_default([$1], [0.9.0]) 35 | AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) 36 | if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then 37 | AC_MSG_RESULT([yes]) 38 | else 39 | AC_MSG_RESULT([no]) 40 | PKG_CONFIG="" 41 | fi 42 | 43 | fi[]dnl 44 | ])# PKG_PROG_PKG_CONFIG 45 | 46 | # PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) 47 | # 48 | # Check to see whether a particular set of modules exists. Similar 49 | # to PKG_CHECK_MODULES(), but does not set variables or print errors. 50 | # 51 | # 52 | # Similar to PKG_CHECK_MODULES, make sure that the first instance of 53 | # this or PKG_CHECK_MODULES is called, or make sure to call 54 | # PKG_CHECK_EXISTS manually 55 | # -------------------------------------------------------------- 56 | AC_DEFUN([PKG_CHECK_EXISTS], 57 | [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl 58 | if test -n "$PKG_CONFIG" && \ 59 | AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then 60 | m4_ifval([$2], [$2], [:]) 61 | m4_ifvaln([$3], [else 62 | $3])dnl 63 | fi]) 64 | 65 | 66 | # _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) 67 | # --------------------------------------------- 68 | m4_define([_PKG_CONFIG], 69 | [if test -n "$PKG_CONFIG"; then 70 | if test -n "$$1"; then 71 | pkg_cv_[]$1="$$1" 72 | else 73 | PKG_CHECK_EXISTS([$3], 74 | [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], 75 | [pkg_failed=yes]) 76 | fi 77 | else 78 | pkg_failed=untried 79 | fi[]dnl 80 | ])# _PKG_CONFIG 81 | 82 | # _PKG_SHORT_ERRORS_SUPPORTED 83 | # ----------------------------- 84 | AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], 85 | [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) 86 | if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then 87 | _pkg_short_errors_supported=yes 88 | else 89 | _pkg_short_errors_supported=no 90 | fi[]dnl 91 | ])# _PKG_SHORT_ERRORS_SUPPORTED 92 | 93 | 94 | # PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], 95 | # [ACTION-IF-NOT-FOUND]) 96 | # 97 | # 98 | # Note that if there is a possibility the first call to 99 | # PKG_CHECK_MODULES might not happen, you should be sure to include an 100 | # explicit call to PKG_PROG_PKG_CONFIG in your configure.ac 101 | # 102 | # 103 | # -------------------------------------------------------------- 104 | AC_DEFUN([PKG_CHECK_MODULES], 105 | [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl 106 | AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl 107 | AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl 108 | 109 | pkg_failed=no 110 | AC_MSG_CHECKING([for $1]) 111 | 112 | _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) 113 | _PKG_CONFIG([$1][_LIBS], [libs], [$2]) 114 | 115 | m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS 116 | and $1[]_LIBS to avoid the need to call pkg-config. 117 | See the pkg-config man page for more details.]) 118 | 119 | if test $pkg_failed = yes; then 120 | _PKG_SHORT_ERRORS_SUPPORTED 121 | if test $_pkg_short_errors_supported = yes; then 122 | $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"` 123 | else 124 | $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` 125 | fi 126 | # Put the nasty error message in config.log where it belongs 127 | echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD 128 | 129 | ifelse([$4], , [AC_MSG_ERROR(dnl 130 | [Package requirements ($2) were not met: 131 | 132 | $$1_PKG_ERRORS 133 | 134 | Consider adjusting the PKG_CONFIG_PATH environment variable if you 135 | installed software in a non-standard prefix. 136 | 137 | _PKG_TEXT 138 | ])], 139 | [AC_MSG_RESULT([no]) 140 | $4]) 141 | elif test $pkg_failed = untried; then 142 | ifelse([$4], , [AC_MSG_FAILURE(dnl 143 | [The pkg-config script could not be found or is too old. Make sure it 144 | is in your PATH or set the PKG_CONFIG environment variable to the full 145 | path to pkg-config. 146 | 147 | _PKG_TEXT 148 | 149 | To get pkg-config, see .])], 150 | [$4]) 151 | else 152 | $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS 153 | $1[]_LIBS=$pkg_cv_[]$1[]_LIBS 154 | AC_MSG_RESULT([yes]) 155 | ifelse([$3], , :, [$3]) 156 | fi[]dnl 157 | ])# PKG_CHECK_MODULES 158 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'sapf', 3 | ['cpp'], 4 | meson_version : '>=1.1' 5 | ) 6 | 7 | cpp = meson.get_compiler('cpp') 8 | sources = [ 9 | 'src/AudioToolboxBuffers.cpp', 10 | 'src/AudioToolboxSoundFile.cpp', 11 | 'src/CoreOps.cpp', 12 | 'src/DelayUGens.cpp', 13 | 'src/dsp.cpp', 14 | 'src/elapsedTime.cpp', 15 | 'src/ErrorCodes.cpp', 16 | 'src/FilterUGens.cpp', 17 | 'src/MathFuns.cpp', 18 | 'src/MathOps.cpp', 19 | 'src/Midi.cpp', 20 | 'src/MultichannelExpansion.cpp', 21 | 'src/Object.cpp', 22 | 'src/Opcode.cpp', 23 | 'src/OscilUGens.cpp', 24 | 'src/Parser.cpp', 25 | 'src/Play.cpp', 26 | 'src/PortableBuffers.cpp', 27 | 'src/primes.cpp', 28 | 'src/RCObj.cpp', 29 | 'src/RandomOps.cpp', 30 | 'src/SetOps.cpp', 31 | 'src/SndfileSoundFile.cpp', 32 | 'src/SoundFiles.cpp', 33 | 'src/Spectrogram.cpp', 34 | 'src/StreamOps.cpp', 35 | 'src/symbol.cpp', 36 | 'src/Types.cpp', 37 | 'src/UGen.cpp', 38 | 'src/VM.cpp', 39 | 'src/Buffers.cpp', 40 | 'src/AsyncAudioFileWriter.cpp', 41 | 'src/ZArr.cpp', 42 | ] 43 | deps = [] 44 | cpp_args = ['-std=c++17'] 45 | link_args = [] 46 | cpp_compiler = meson.get_compiler('cpp') 47 | 48 | # support clang compilation 49 | if cpp_compiler.get_id() == 'clang' 50 | cpp_args += ['-D_USE_MATH_DEFINES'] 51 | link_args += ['-lpthread'] 52 | endif 53 | 54 | # libedit 55 | if host_machine.system() == 'windows' 56 | deps += dependency('readline', required: true) 57 | deps += dependency('history', required: true) 58 | cpp_args += ['-DUSE_LIBEDIT=0', '-D_POSIX_THREAD_SAFE_FUNCTIONS'] 59 | else 60 | deps += dependency('libedit', required: true) 61 | cpp_args += ['-DUSE_LIBEDIT=1'] 62 | endif 63 | 64 | if get_option('asan') 65 | cpp_args += ['-fsanitize=address', '-O1', '-fno-omit-frame-pointer', '-g'] 66 | link_args += ['-fsanitize=address'] 67 | endif 68 | 69 | if get_option('accelerate') 70 | add_project_arguments('-DSAPF_ACCELERATE', language: ['cpp', 'objcpp']) 71 | link_args += ['-framework', 'Accelerate'] 72 | else 73 | deps += dependency('fftw3', required: true, version: '>=3') 74 | deps += dependency('eigen3', required: true) 75 | deps += dependency('xsimd', required: true) 76 | endif 77 | 78 | if get_option('apple_lock') 79 | add_project_arguments('-DSAPF_APPLE_LOCK', language: ['cpp', 'objcpp']) 80 | endif 81 | 82 | if get_option('audiotoolbox') 83 | add_project_arguments('-DSAPF_AUDIOTOOLBOX', language: ['cpp', 'objcpp']) 84 | link_args += ['-framework', 'AudioToolbox'] 85 | else 86 | deps += dependency('rtaudio', required: true, version: '>=5.2') 87 | if not cpp.has_header('RtAudio.h') and cpp.has_header('rtaudio/RtAudio.h') 88 | add_project_arguments('-DSAPF_RTAUDIO_H=', language: ['cpp', 'objcpp']) 89 | endif 90 | 91 | deps += dependency('sndfile', required: true) 92 | endif 93 | 94 | if get_option('carbon') 95 | add_project_arguments('-DSAPF_CARBON', language: ['cpp', 'objcpp']) 96 | link_args += ['-framework', 'Carbon'] 97 | endif 98 | 99 | if get_option('cocoa') 100 | add_project_arguments('-DSAPF_COCOA', language: ['cpp', 'objcpp']) 101 | sources += ['src/makeImage.mm'] 102 | add_languages('objcpp', required : true) 103 | link_args += ['-framework', 'Cocoa'] 104 | else 105 | sources += 'src/makeImage.cpp' 106 | endif 107 | 108 | if get_option('corefoundation') 109 | add_project_arguments('-DSAPF_COREFOUNDATION', language: ['cpp', 'objcpp']) 110 | link_args += ['-framework', 'CoreFoundation'] 111 | endif 112 | 113 | if get_option('coremidi') 114 | add_project_arguments('-DSAPF_COREMIDI', language: ['cpp', 'objcpp']) 115 | link_args += ['-framework', 'CoreMIDI'] 116 | endif 117 | 118 | if get_option('dispatch') 119 | cpp_args += ['-fblocks'] 120 | add_project_arguments('-DSAPF_DISPATCH', language: ['cpp', 'objcpp']) 121 | endif 122 | 123 | if get_option('mach_time') 124 | add_project_arguments('-DSAPF_MACH_TIME', language: ['cpp', 'objcpp']) 125 | endif 126 | 127 | if get_option('manta') 128 | add_project_arguments('-DSAPF_MANTA', language: ['cpp', 'objcpp']) 129 | endif 130 | 131 | # main should only be in release build because otherwise it conflicts with the main 132 | # method added by doctest in test builds 133 | release_sources = sources + 'src/main.cpp' 134 | 135 | # build targeting the native system this was built on. If buildtype is debug, 136 | # no target architecture will be specified, to ensure the best debugging experience. 137 | executable( 138 | 'sapf', 139 | release_sources, 140 | include_directories: [include_directories('include')], 141 | dependencies: deps, 142 | cpp_args : cpp_args + (get_option('buildtype').startswith('debug') ? [] : ['-march=native']), 143 | link_args : link_args 144 | ) 145 | 146 | # The below builds target specific architectures. Because this program uses 147 | # SIMD, the best performance is achieved when a user uses the most recent architecture supported 148 | # by their CPU. However, users will get the best results (even better performance) if they build the 149 | # library themselves (target `sapf` above with `--buildtype release`) 150 | 151 | # should have maximum compatibility with ancient x86_64 cpus 152 | executable( 153 | 'sapf_x86_64', 154 | release_sources, 155 | include_directories: [include_directories('include')], 156 | dependencies: deps, 157 | cpp_args : cpp_args + ['-march=x86-64'], 158 | link_args : link_args, 159 | build_by_default: false 160 | ) 161 | 162 | # for somewhat older x86 CPUs (2008ish) 163 | executable( 164 | 'sapf_x86_64_v2', 165 | release_sources, 166 | include_directories: [include_directories('include')], 167 | dependencies: deps, 168 | cpp_args : cpp_args + ['-march=x86-64-v2'], 169 | link_args : link_args, 170 | build_by_default: false 171 | ) 172 | 173 | # for more cutting edge x86 cpus 174 | executable( 175 | 'sapf_x86_64_v3', 176 | release_sources, 177 | include_directories: [include_directories('include')], 178 | dependencies: deps, 179 | cpp_args : cpp_args + ['-march=x86-64-v3'], 180 | link_args : link_args, 181 | build_by_default: false 182 | ) 183 | 184 | # support M1 chip and newer 185 | executable( 186 | 'sapf_arm_m1', 187 | release_sources, 188 | include_directories: [include_directories('include')], 189 | dependencies: deps, 190 | cpp_args : cpp_args + ['-march=armv8.4-a', '-mcpu=apple-m1'], 191 | link_args : link_args, 192 | build_by_default: false 193 | ) 194 | 195 | # support M3 chip 196 | executable( 197 | 'sapf_arm_m3', 198 | release_sources, 199 | include_directories: [include_directories('include')], 200 | dependencies: deps, 201 | cpp_args : cpp_args + ['-march=armv8.5-a', '-mcpu=apple-m3'], 202 | link_args : link_args, 203 | build_by_default: false 204 | ) 205 | 206 | # tests 207 | test_deps = deps + dependency('doctest', required: true) 208 | test_sources = sources + [ 209 | 'test/doctest.cpp', 210 | 'test/test_MathOps.cpp', 211 | 'test/test_AsyncAudioFileWriter.cpp', 212 | 'test/test_SndfileSoundFile.cpp', 213 | 'test/test_OscilUgens.cpp', 214 | 'test/test_StreamOps.cpp', 215 | 'test/test_MathOps.cpp' 216 | ] 217 | test_includes = [include_directories('include'), include_directories('test/helpers')] 218 | test_cpp_args = cpp_args + '-DTEST_BUILD' 219 | 220 | # Just like the main executable, this respects the buildtype param. The target architecture is 221 | # omitted if it's a debug build, ensuring the best debugging experience 222 | test_sapf = executable( 223 | 'test_sapf', 224 | test_sources, 225 | include_directories: test_includes, 226 | dependencies: test_deps, 227 | cpp_args : test_cpp_args + (get_option('buildtype').startswith('debug') ? [] : ['-march=native']), 228 | link_args : link_args, 229 | build_by_default: false 230 | ) 231 | 232 | test('all', test_sapf) -------------------------------------------------------------------------------- /meson.options: -------------------------------------------------------------------------------- 1 | option('accelerate', type : 'boolean', value : false) 2 | option('apple_lock', type : 'boolean', value : false) 3 | option('asan', type : 'boolean', value : false) 4 | option('audiotoolbox', type : 'boolean', value : false) 5 | option('carbon', type : 'boolean', value : false) 6 | option('cocoa', type : 'boolean', value : false) 7 | option('corefoundation', type : 'boolean', value : false) 8 | option('coremidi', type : 'boolean', value : false) 9 | option('dispatch', type : 'boolean', value : false) 10 | option('mach_time', type : 'boolean', value : false) 11 | option('manta', type : 'boolean', value : false) 12 | -------------------------------------------------------------------------------- /nix/libdispatch/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | cmake, 3 | lib, 4 | fetchFromGitHub, 5 | stdenv, 6 | }: 7 | 8 | stdenv.mkDerivation (finalAttrs: { 9 | pname = "libdispatch"; 10 | version = "1.3"; 11 | 12 | src = fetchFromGitHub { 13 | owner = "swiftlang"; 14 | repo = "swift-corelibs-libdispatch"; 15 | rev = "swift-6.0.3-RELEASE"; 16 | hash = "sha256-vZ0JddR31wyFOjmSqEuzIJNBQIUgcLpjabbnlSF3LqY="; 17 | }; 18 | 19 | nativeBuildInputs = [ 20 | cmake 21 | ]; 22 | 23 | meta = { 24 | homepage = "https://github.com/swiftlang/swift-corelibs-libdispatch"; 25 | description = "The libdispatch Project, (a.k.a. Grand Central Dispatch), for concurrency on multicore hardware"; 26 | license = lib.licenses.asl20; 27 | platforms = lib.platforms.unix; 28 | }; 29 | }) 30 | -------------------------------------------------------------------------------- /src/AsyncAudioFileWriter.cpp: -------------------------------------------------------------------------------- 1 | #ifndef SAPF_AUDIOTOOLBOX 2 | #include "AsyncAudioFileWriter.hpp" 3 | 4 | AsyncAudioFileWriter::AsyncAudioFileWriter(const std::string &path, const int samplerate, const int numChannels) 5 | : mNumChannels{numChannels}, mChunkSize{maxChunkSize - maxChunkSize % numChannels}, 6 | mRingBuffer{std::make_unique >()}, 7 | mRingWriteBuffer(mChunkSize), mWriteBuffer(mChunkSize), 8 | mRunning{true} { 9 | SF_INFO sfinfo; 10 | sfinfo.channels = numChannels; 11 | sfinfo.samplerate = samplerate; 12 | sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; 13 | 14 | mFile = sf_open(path.c_str(), SFM_WRITE, &sfinfo); 15 | if (!mFile) { 16 | printf("failed to open %s\n", path.c_str()); 17 | throw errNotFound; 18 | } 19 | 20 | mWriterThread = std::thread(&AsyncAudioFileWriter::writeLoop, this); 21 | } 22 | 23 | 24 | // stop async thread and flush all remaining data to file 25 | AsyncAudioFileWriter::~AsyncAudioFileWriter() { 26 | { 27 | std::lock_guard lock{mBufferMutex}; 28 | mRunning = false; 29 | } 30 | mDataAvailableCondition.notify_one(); 31 | mWriterThread.join(); 32 | 33 | sf_close(mFile); 34 | } 35 | 36 | // TODO: note writeBuff has ability to pass a callback, which could potentially be used 37 | // to notify the writeLoop earlier, but would require picking a "count_to_callback". 38 | // Would have to see if it performs better this way. 39 | void AsyncAudioFileWriter::writeAsync(const RtBuffers& buffers, const unsigned int nBufferFrames) { 40 | const auto totalValuesToWrite{mNumChannels * nBufferFrames}; 41 | assert(static_cast(mNumChannels) == buffers.count()); 42 | // index of the interleaved value in the final interleaved audio data 43 | size_t valueIdx{0}; 44 | while (valueIdx < totalValuesToWrite) { 45 | size_t valuesToWrite{0}; 46 | for (; valuesToWrite < mChunkSize && valueIdx < totalValuesToWrite; ++valuesToWrite,++valueIdx) { 47 | mRingWriteBuffer[valuesToWrite] = buffers.data(static_cast(valueIdx % mNumChannels))[valueIdx / mNumChannels]; 48 | } 49 | 50 | // optimistically write to the ring buffer until we're done or we failed to make progress 51 | size_t totalWritten{0}; 52 | size_t written{0}; 53 | do { 54 | written = mRingBuffer->writeBuff(mRingWriteBuffer.data() + totalWritten, valuesToWrite - totalWritten); 55 | // it's possible this is "missed" by the writeLoop, but that's okay - this is just 56 | // an optimistic attempt to write. We'll revert to locking later. 57 | mDataAvailableCondition.notify_one(); 58 | totalWritten += written; 59 | } while (written > 0 && totalWritten != valuesToWrite); 60 | 61 | // either we're done or we weren't able to write 62 | // if we're done, we can return. As mentioned earlier, it's possible that the writeLoop missed 63 | // the notification. That's fine. The thread will get notified again the next call, or it 64 | // will be notified in the destructor. 65 | if (totalWritten == valuesToWrite) {continue;} 66 | 67 | do { 68 | // block until we can write more. We can't be greedy and wait until we can write EVERYTHING 69 | // we have remaining, because the ring buffer is of limited size. So we have to wait until we can 70 | // at least fill the buffer, or finish off what we have remaining 71 | // WARNING: if this blocks, meaning the writeLoop can't keep up with 72 | // the audio thread, it will block the realtime audio thread! There's nothing we can really do in that 73 | // case - increasing the ring buffer size just delays the issue temporarily. 74 | { 75 | std::unique_lock lock{mBufferMutex}; 76 | mSpaceAvailableCondition.wait(lock, [this, totalWritten, valuesToWrite] { 77 | return mRingBuffer->writeAvailable() >= valuesToWrite - totalWritten; 78 | }); 79 | } 80 | // write again from where we left off 81 | totalWritten += mRingBuffer->writeBuff(mRingWriteBuffer.data() + totalWritten, valuesToWrite - totalWritten); 82 | mDataAvailableCondition.notify_one(); 83 | } while (totalWritten != valuesToWrite); 84 | } 85 | // same reasoning goes here for writeLoop potentially missing the notification, but that being okay. 86 | } 87 | 88 | void AsyncAudioFileWriter::writeLoop() { 89 | while (true) { 90 | // optimistic attempt to read from the buffer 91 | auto read{mRingBuffer->readBuff(mWriteBuffer.data(), mChunkSize)}; 92 | if (read == 0) { 93 | // either we've stopped, or we have no data - time to wait and see 94 | { 95 | std::unique_lock lock{mBufferMutex}; 96 | mDataAvailableCondition.wait(lock, [this] { return !mRunning || mRingBuffer->readAvailable(); }); 97 | } 98 | // Since we're the only consumer, we know running is still false, or readAvailable is still positive. 99 | // so we don't still need to hold the lock 100 | // TODO: We copy from ringBuffer into writeBuffer, then write to file. 101 | // Technically, it may be possible to skip this intermediate copy, but it would require 102 | // dealing with cases where we "wrap around" the end of the ring, thus needing to modify 103 | // ringbuffer to support this. 104 | read = mRingBuffer->readBuff(mWriteBuffer.data(), mChunkSize); 105 | if (read == 0) { 106 | // implies readAvailable was 0, so running was definitely false. 107 | // we've stopped, so we can exit 108 | return; 109 | } 110 | } 111 | 112 | if (const auto written{sf_write_float(mFile, mWriteBuffer.data(), static_cast(read))}; written <= 0) { 113 | const auto error{sf_strerror(mFile)}; 114 | printf("failed to write audio data to file - %s\n", error); 115 | } 116 | mSpaceAvailableCondition.notify_one(); 117 | } 118 | } 119 | 120 | #endif -------------------------------------------------------------------------------- /src/AudioToolboxBuffers.cpp: -------------------------------------------------------------------------------- 1 | #ifdef SAPF_AUDIOTOOLBOX 2 | #include "AudioToolboxBuffers.hpp" 3 | 4 | AudioToolboxBuffers::AudioToolboxBuffers(int inNumChannels) { 5 | this->abl = (AudioBufferList*)calloc(1, sizeof(AudioBufferList) + (inNumChannels - 1) * sizeof(AudioBuffer)); 6 | this->abl->mNumberBuffers = inNumChannels; 7 | } 8 | 9 | AudioToolboxBuffers::~AudioToolboxBuffers() { 10 | free(this->abl); 11 | } 12 | 13 | uint32_t AudioToolboxBuffers::numChannels() { 14 | return this->abl->mNumberBuffers; 15 | } 16 | 17 | void AudioToolboxBuffers::setNumChannels(size_t i, uint32_t numChannels) { 18 | this->abl->mBuffers[i].mNumberChannels = numChannels; 19 | } 20 | 21 | void AudioToolboxBuffers::setData(size_t i, void *data) { 22 | this->abl->mBuffers[i].mData = data; 23 | } 24 | 25 | void AudioToolboxBuffers::setSize(size_t i, uint32_t size) { 26 | this->abl->mBuffers[i].mDataByteSize = size; 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /src/AudioToolboxSoundFile.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef SAPF_AUDIOTOOLBOX 3 | #include "AudioToolboxSoundFile.hpp" 4 | 5 | AudioToolboxSoundFile::AudioToolboxSoundFile(ExtAudioFileRef inXAF, uint32_t inNumChannels, std::string inPath) 6 | : mXAF(inXAF), mNumChannels(inNumChannels), mPath(inPath) 7 | {} 8 | 9 | AudioToolboxSoundFile::~AudioToolboxSoundFile() { 10 | ExtAudioFileDispose(this->mXAF); 11 | } 12 | 13 | uint32_t AudioToolboxSoundFile::numChannels() { 14 | return this->mNumChannels; 15 | } 16 | 17 | int AudioToolboxSoundFile::pull(uint32_t *framesRead, AudioBuffers& buffers) { 18 | return ExtAudioFileRead(this->mXAF, framesRead, buffers.abl); 19 | } 20 | 21 | std::unique_ptr AudioToolboxSoundFile::open(const char* path, const double threadSampleRate) { 22 | CFStringRef cfpath = CFStringCreateWithFileSystemRepresentation(0, path); 23 | if (!cfpath) { 24 | post("failed to create path\n"); 25 | return nullptr; 26 | } 27 | CFReleaser cfpathReleaser(cfpath); 28 | 29 | CFURLRef url = CFURLCreateWithFileSystemPath(0, cfpath, kCFURLPOSIXPathStyle, false); 30 | if (!url) { 31 | post("failed to create url\n"); 32 | return nullptr; 33 | } 34 | CFReleaser urlReleaser(url); 35 | 36 | ExtAudioFileRef xaf; 37 | OSStatus err = ExtAudioFileOpenURL(url, &xaf); 38 | 39 | cfpathReleaser.release(); 40 | urlReleaser.release(); 41 | 42 | if (err) { 43 | post("failed to open file %d\n", (int)err); 44 | return nullptr; 45 | } 46 | 47 | AudioStreamBasicDescription fileFormat; 48 | 49 | UInt32 propSize = sizeof(fileFormat); 50 | err = ExtAudioFileGetProperty(xaf, kExtAudioFileProperty_FileDataFormat, &propSize, &fileFormat); 51 | 52 | int numChannels = fileFormat.mChannelsPerFrame; 53 | 54 | AudioStreamBasicDescription clientFormat = { 55 | threadSampleRate, 56 | kAudioFormatLinearPCM, 57 | kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved, 58 | static_cast(sizeof(double)), 59 | 1, 60 | static_cast(sizeof(double)), 61 | static_cast(numChannels), 62 | 64, 63 | 0 64 | }; 65 | 66 | err = ExtAudioFileSetProperty(xaf, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat); 67 | if (err) { 68 | post("failed to set client data format\n"); 69 | ExtAudioFileDispose(xaf); 70 | return {}; 71 | } 72 | 73 | err = ExtAudioFileSeek(xaf, 0); 74 | if (err) { 75 | post("seek failed %d\n", (int)err); 76 | ExtAudioFileDispose(xaf); 77 | return {}; 78 | } 79 | 80 | return std::make_unique(xaf, numChannels, path); 81 | } 82 | 83 | std::unique_ptr AudioToolboxSoundFile::create(const char* path, int numChannels, 84 | double threadSampleRate, double fileSampleRate, 85 | bool interleaved) { 86 | if (fileSampleRate == 0.) 87 | fileSampleRate = threadSampleRate; 88 | 89 | CFStringRef cfpath = CFStringCreateWithFileSystemRepresentation(0, path); 90 | if (!cfpath) { 91 | post("failed to create path '%s'\n", path); 92 | return nullptr; 93 | } 94 | CFReleaser cfpathReleaser(cfpath); 95 | 96 | CFURLRef url = CFURLCreateWithFileSystemPath(0, cfpath, kCFURLPOSIXPathStyle, false); 97 | if (!url) { 98 | post("failed to create url\n"); 99 | return nullptr; 100 | } 101 | CFReleaser urlReleaser(url); 102 | 103 | AudioStreamBasicDescription fileFormat = { 104 | fileSampleRate, 105 | kAudioFormatLinearPCM, 106 | kAudioFormatFlagsNativeFloatPacked, 107 | static_cast(sizeof(float) * numChannels), 108 | 1, 109 | static_cast(sizeof(float) * numChannels), 110 | static_cast(numChannels), 111 | 32, 112 | 0 113 | }; 114 | 115 | int interleavedChannels = interleaved ? numChannels : 1; 116 | UInt32 interleavedBit = interleaved ? 0 : kAudioFormatFlagIsNonInterleaved; 117 | 118 | AudioStreamBasicDescription clientFormat = { 119 | threadSampleRate, 120 | kAudioFormatLinearPCM, 121 | kAudioFormatFlagsNativeFloatPacked | interleavedBit, 122 | static_cast(sizeof(float) * interleavedChannels), 123 | 1, 124 | static_cast(sizeof(float) * interleavedChannels), 125 | static_cast(numChannels), 126 | 32, 127 | 0 128 | }; 129 | 130 | ExtAudioFileRef xaf; 131 | OSStatus err = ExtAudioFileCreateWithURL(url, kAudioFileWAVEType, &fileFormat, nullptr, kAudioFileFlags_EraseFile, &xaf); 132 | 133 | if (err) { 134 | post("failed to create file '%s'. err: %d\n", path, (int)err); 135 | return nullptr; 136 | } 137 | 138 | err = ExtAudioFileSetProperty(xaf, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat); 139 | if (err) { 140 | post("failed to set client data format\n"); 141 | ExtAudioFileDispose(xaf); 142 | return nullptr; 143 | } 144 | 145 | return std::make_unique(xaf, numChannels, path); 146 | } 147 | #endif // SAPF_AUDIOTOOLBOX 148 | -------------------------------------------------------------------------------- /src/Buffers.cpp: -------------------------------------------------------------------------------- 1 | #include "Buffers.hpp" 2 | 3 | #if defined(SAPF_AUDIOTOOLBOX) 4 | AUBuffers::AUBuffers(AudioBufferList *inIoData) 5 | : ioData(inIoData) 6 | {} 7 | 8 | uint32_t AUBuffers::count() const { 9 | return this->ioData->mNumberBuffers; 10 | } 11 | 12 | float *AUBuffers::data(int channel) const { 13 | return (float*) this->ioData->mBuffers[channel].mData; 14 | } 15 | 16 | uint32_t AUBuffers::size(int channel) const { 17 | return this->ioData->mBuffers[channel].mDataByteSize; 18 | } 19 | #else 20 | 21 | RtBuffers::RtBuffers(float *inOut, uint32_t inCount, uint32_t inSize) 22 | : out(inOut), theCount(inCount), theSize(inSize) 23 | {} 24 | 25 | RtBuffers::RtBuffers(uint32_t inCount, uint32_t inSize) 26 | : out(new float[inCount*inSize]), theCount(inCount), theSize(inSize) { 27 | } 28 | 29 | uint32_t RtBuffers::count() const { 30 | return this->theCount; 31 | } 32 | 33 | float *RtBuffers::data(int channel) const { 34 | return this->out + channel * this->theSize; 35 | } 36 | 37 | uint32_t RtBuffers::size(int channel) const { 38 | return this->theSize; 39 | } 40 | #endif -------------------------------------------------------------------------------- /src/ErrorCodes.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "ErrorCodes.hpp" 18 | 19 | const char* errString[kNumErrors] = { 20 | "halt", "failed", "indefinite operation", "wrong type", "out of range", "syntax", "internal bug", 21 | "wrong state", "not found", "stack overflow", "stack underflow", 22 | "inconsistent inheritance", "undefined operation", "user quit" 23 | }; 24 | -------------------------------------------------------------------------------- /src/MathFuns.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "MathFuns.hpp" 18 | #include "VM.hpp" 19 | 20 | double gSineTable[kSineTableSize+1]; 21 | double gDBAmpTable[kDBAmpTableSize+2]; 22 | double gDecayTable[kDecayTableSize+1]; 23 | double gFirstOrderCoeffTable[kFirstOrderCoeffTableSize+1]; 24 | 25 | 26 | inline double freqToTableF(double freq) 27 | { 28 | return std::clamp(3. * log2(freq * .05), 0., 28.999); 29 | } 30 | 31 | Z gFreqToTable[20001]; 32 | 33 | static void initFreqToTable() 34 | { 35 | for (int freq = 0; freq < 20001; ++freq) { 36 | gFreqToTable[freq] = freqToTableF(freq); 37 | } 38 | } 39 | 40 | inline double freqToTable(double freq) 41 | { 42 | double findex = std::clamp(freq, 0., 20000.); 43 | double iindex = floor(findex); 44 | return lut(gFreqToTable, (int)iindex, findex - iindex); 45 | } 46 | 47 | //////////////////////////////////////////////////////////////////////////////////// 48 | 49 | void fillSineTable() 50 | { 51 | for (int i = 0; i < kSineTableSize; ++i) { 52 | gSineTable[i] = sin(gSineTableOmega * i); 53 | } 54 | gSineTable[kSineTableSize] = gSineTable[0]; 55 | } 56 | 57 | void fillDBAmpTable() 58 | { 59 | for (int i = 0; i < kDBAmpTableSize+2; ++i) { 60 | double dbgain = i * kInvDBAmpScale - kDBAmpOffset; 61 | double amp = pow(10., .05 * dbgain); 62 | gDBAmpTable[i] = amp; 63 | } 64 | } 65 | 66 | void fillDecayTable() 67 | { 68 | for (int i = 0; i < kDecayTableSize+1; ++i) { 69 | gDecayTable[i] = exp(log001 * i * .001); 70 | } 71 | } 72 | 73 | void fillFirstOrderCoeffTable() 74 | { 75 | double k = M_PI * kInvFirstOrderCoeffTableSize; 76 | for (int i = 0; i < kFirstOrderCoeffTableSize+1; ++i) { 77 | double x = k * i; 78 | Z b = 2. - cos(x); 79 | gFirstOrderCoeffTable[i] = b - sqrt(b*b - 1.); 80 | } 81 | } 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/Opcode.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "Opcode.hpp" 18 | #include "clz.hpp" 19 | 20 | const char* opcode_name[kNumOpcodes] = 21 | { 22 | "BAD OPCODE", 23 | "opNone", 24 | "opPushImmediate", 25 | "opPushLocalVar", 26 | "opPushFunVar", 27 | "opPushWorkspaceVar", 28 | 29 | "opPushFun", 30 | 31 | "opCallImmediate", 32 | "opCallLocalVar", 33 | "opCallFunVar", 34 | "opCallWorkspaceVar", 35 | 36 | "opDot", 37 | "opComma", 38 | "opBindLocal", 39 | "opBindLocalFromList", 40 | "opBindWorkspaceVar", 41 | "opBindWorkspaceVarFromList", 42 | 43 | "opParens", 44 | "opNewVList", 45 | "opNewZList", 46 | "opNewForm", 47 | "opInherit", 48 | "opEach", 49 | "opReturn" 50 | }; 51 | 52 | 53 | static void printOpcode(Thread& th, Opcode* c) 54 | { 55 | V& v = c->v; 56 | post("%p %s ", c, opcode_name[c->op]); 57 | switch (c->op) { 58 | case opPushImmediate : 59 | case opPushWorkspaceVar : 60 | case opPushFun : 61 | case opCallImmediate : 62 | case opCallWorkspaceVar : 63 | case opDot : 64 | case opComma : 65 | case opInherit : 66 | case opNewForm : 67 | case opBindWorkspaceVar : 68 | case opBindWorkspaceVarFromList : 69 | case opParens : 70 | case opNewVList : 71 | case opNewZList : 72 | v.printShort(th); 73 | break; 74 | 75 | case opPushLocalVar : 76 | case opPushFunVar : 77 | case opCallLocalVar : 78 | case opCallFunVar : 79 | case opBindLocal : 80 | case opBindLocalFromList : 81 | post("%lld", (int64_t)v.i); 82 | break; 83 | case opEach : 84 | post("%llx", (int64_t)v.i); 85 | break; 86 | 87 | case opNone : 88 | case opReturn : 89 | break; 90 | 91 | default : 92 | post("BAD OPCODE\n"); 93 | } 94 | post("\n"); 95 | } 96 | 97 | void Thread::run(Opcode* opc) 98 | { 99 | Thread& th = *this; 100 | try { 101 | for (;;++opc) { 102 | 103 | V& v = opc->v; 104 | 105 | if (vm.traceon) { 106 | post("stack : "); th.printStack(); post("\n"); 107 | printOpcode(th, opc); 108 | } 109 | 110 | switch (opc->op) { 111 | case opNone : 112 | break; 113 | 114 | case opPushImmediate : 115 | push(v); 116 | break; 117 | 118 | case opPushLocalVar : 119 | push(getLocal(v.i)); 120 | break; 121 | 122 | case opPushFunVar : 123 | push(fun->mVars[v.i]); 124 | break; 125 | 126 | case opPushWorkspaceVar : 127 | push(fun->Workspace()->mustGet(th, v)); 128 | break; 129 | 130 | case opPushFun : { 131 | push(new Fun(th, (FunDef*)v.o())); 132 | } break; 133 | 134 | case opCallImmediate : 135 | v.apply(th); 136 | break; 137 | 138 | case opCallLocalVar : 139 | getLocal(v.i).apply(th); 140 | break; 141 | 142 | case opCallFunVar : 143 | fun->mVars[v.i].apply(th); 144 | break; 145 | 146 | case opCallWorkspaceVar : 147 | fun->Workspace()->mustGet(th, v).apply(th); 148 | break; 149 | 150 | case opDot : { 151 | V ioValue; 152 | if (!pop().dot(th, v, ioValue)) 153 | notFound(v); 154 | push(ioValue); 155 | break; 156 | } 157 | case opComma : 158 | push(pop().comma(th, v)); 159 | break; 160 | 161 | case opBindLocal : 162 | getLocal(v.i) = pop(); 163 | break; 164 | case opBindWorkspaceVar : { 165 | V value = pop(); 166 | if (value.isList() && !value.isFinite()) { 167 | post("WARNING: binding a possibly infinite list at the top level can leak unbounded memory!\n"); 168 | } else if (value.isFun()) { 169 | const char* mask = value.GetAutoMapMask(); 170 | const char* help = value.OneLineHelp(); 171 | if (mask || help) { 172 | char* name = ((String*)v.o())->s; 173 | vm.addUdfHelp(name, mask, help); 174 | } 175 | } 176 | fun->Workspace() = fun->Workspace()->putImpure(v, value); // workspace mutation 177 | th.mWorkspace = th.mWorkspace->putImpure(v, value); // workspace mutation 178 | } break; 179 | 180 | case opBindLocalFromList : 181 | case opBindWorkspaceVarFromList : 182 | { 183 | V list = pop(); 184 | BothIn in(list); 185 | while (1) { 186 | if (opc->op == opNone) { 187 | break; 188 | } else { 189 | V value; 190 | if (in.one(th, value)) { 191 | post("not enough items in list for = [..]\n"); 192 | throw errFailed; 193 | } 194 | if (opc->op == opBindLocalFromList) { 195 | getLocal(opc->v.i) = value; 196 | } else if (opc->op == opBindWorkspaceVarFromList) { 197 | v = opc->v; 198 | if (value.isList() && !value.isFinite()) { 199 | post("WARNING: binding a possibly infinite list at the top level can leak unbounded memory!\n"); 200 | } else if (value.isFun()) { 201 | const char* mask = value.GetAutoMapMask(); 202 | const char* help = value.OneLineHelp(); 203 | if (mask || help) { 204 | char* name = ((String*)v.o())->s; 205 | vm.addUdfHelp(name, mask, help); 206 | } 207 | } 208 | fun->Workspace() = fun->Workspace()->putImpure(v, value); // workspace mutation 209 | th.mWorkspace = th.mWorkspace->putImpure(v, value); // workspace mutation 210 | } 211 | } 212 | ++opc; 213 | } 214 | } break; 215 | case opParens : { 216 | ParenStack ss(th); 217 | run(((Code*)v.o())->getOps()); 218 | } break; 219 | case opNewVList : { 220 | V x; 221 | { 222 | SaveStack ss(th); 223 | run(((Code*)v.o())->getOps()); 224 | size_t len = stackDepth(); 225 | vm.newVList->apply_n(th, len); 226 | x = th.pop(); 227 | } 228 | th.push(x); 229 | } break; 230 | case opNewZList : { 231 | V x; 232 | { 233 | SaveStack ss(th); 234 | run(((Code*)v.o())->getOps()); 235 | size_t len = stackDepth(); 236 | vm.newZList->apply_n(th, len); 237 | x = th.pop(); 238 | } 239 | th.push(x); 240 | } break; 241 | case opInherit : { 242 | V result; 243 | { 244 | SaveStack ss(th); 245 | run(((Code*)v.o())->getOps()); 246 | size_t depth = stackDepth(); 247 | if (depth < 1) { 248 | result = vm._ee; 249 | } else if (depth > 1) { 250 | fprintf(stderr, "more arguments than keys for form.\n"); 251 | throw errFailed; 252 | } else { 253 | vm.inherit->apply_n(th, 1); 254 | result = th.pop(); 255 | } 256 | } 257 | th.push(result); 258 | } break; 259 | case opNewForm : { 260 | V result; 261 | { 262 | SaveStack ss(th); 263 | run(((Code*)v.o())->getOps()); 264 | size_t depth = stackDepth(); 265 | TableMap* tmap = (TableMap*)th.top().o(); 266 | size_t numArgs = tmap->mSize; 267 | if (depth == numArgs+1) { 268 | // no inheritance, must insert zero for parent. 269 | th.tuck(numArgs+1, V(0.)); 270 | } else if (depth < numArgs+1) { 271 | fprintf(stderr, "fewer arguments than keys for form.\n"); 272 | throw errStackUnderflow; 273 | } else if (depth > numArgs+2) { 274 | fprintf(stderr, "more arguments than keys for form.\n"); 275 | throw errFailed; 276 | } 277 | vm.newForm->apply_n(th, numArgs+2); 278 | result = th.pop(); 279 | } 280 | th.push(result); 281 | } break; 282 | case opEach : 283 | push(new EachOp(pop(), (int)v.i)); 284 | break; 285 | 286 | case opReturn : return; 287 | 288 | default : 289 | post("BAD OPCODE\n"); 290 | throw errInternalError; 291 | } 292 | } 293 | } catch (...) { 294 | post("backtrace: %s ", opcode_name[opc->op]); 295 | opc->v.printShort(th); 296 | post("\n"); 297 | throw; 298 | } 299 | } 300 | 301 | Code::~Code() { } 302 | 303 | void Code::shrinkToFit() 304 | { 305 | std::vector(ops.begin(), ops.end()).swap(ops); 306 | } 307 | 308 | void Code::add(int _op, Arg v) 309 | { 310 | ops.push_back(Opcode(_op, v)); 311 | } 312 | 313 | void Code::add(int _op, double f) 314 | { 315 | add(_op, V(f)); 316 | } 317 | 318 | void Code::addAll(const P &that) 319 | { 320 | for (Opcode& op : that->ops) { 321 | ops.push_back(op); 322 | } 323 | } 324 | 325 | void Code::decompile(Thread& th, std::string& out) 326 | { 327 | for (Opcode& c : ops) { 328 | V& v = c.v; 329 | switch (c.op) { 330 | case opPushImmediate : { 331 | std::string s; 332 | v.printShort(th, s); 333 | out += s; 334 | break; 335 | } 336 | case opPushWorkspaceVar : 337 | case opPushFun : 338 | case opCallImmediate : 339 | case opCallWorkspaceVar : 340 | case opDot : 341 | case opComma : 342 | case opInherit : 343 | case opNewForm : 344 | case opBindWorkspaceVar : 345 | case opBindWorkspaceVarFromList : 346 | case opParens : 347 | case opNewVList : 348 | case opNewZList : 349 | v.printShort(th); 350 | break; 351 | 352 | case opPushLocalVar : 353 | case opPushFunVar : 354 | case opCallLocalVar : 355 | case opCallFunVar : 356 | case opBindLocal : 357 | case opBindLocalFromList : 358 | post("%lld", (int64_t)v.i); 359 | break; 360 | case opEach : 361 | post("%llx", (int64_t)v.i); 362 | break; 363 | 364 | case opNone : 365 | case opReturn : 366 | break; 367 | 368 | default : 369 | post("BAD OPCODE\n"); 370 | } 371 | } 372 | } 373 | 374 | -------------------------------------------------------------------------------- /src/PortableBuffers.cpp: -------------------------------------------------------------------------------- 1 | #ifndef SAPF_AUDIOTOOLBOX 2 | #include "PortableBuffers.hpp" 3 | 4 | PortableBuffer::PortableBuffer() 5 | : numChannels(0), size(0), data(nullptr) 6 | {} 7 | 8 | PortableBuffers::PortableBuffers(int inNumChannels) 9 | : buffers(inNumChannels) 10 | {} 11 | 12 | PortableBuffers::~PortableBuffers() {} 13 | 14 | uint32_t PortableBuffers::numChannels() { 15 | return this->buffers.size(); 16 | } 17 | 18 | void PortableBuffers::setNumChannels(size_t i, uint32_t numChannels) { 19 | this->buffers[i].numChannels = numChannels; 20 | } 21 | 22 | void PortableBuffers::setData(size_t i, void *data) { 23 | this->buffers[i].data = data; 24 | } 25 | 26 | void PortableBuffers::setSize(size_t i, uint32_t size) { 27 | this->buffers[i].size = size; 28 | this->interleaved.resize(size * this->numChannels()); 29 | } 30 | #endif // SAPF_AUDIOTOOLBOX 31 | -------------------------------------------------------------------------------- /src/RCObj.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "RCObj.hpp" 18 | #include "VM.hpp" 19 | 20 | 21 | RCObj::RCObj() 22 | : refcount(0) 23 | { 24 | #if COLLECT_MINFO 25 | ++vm.totalObjectsAllocated; 26 | #endif 27 | } 28 | 29 | RCObj::RCObj(RCObj const& that) 30 | : refcount(0) 31 | { 32 | #if COLLECT_MINFO 33 | ++vm.totalObjectsAllocated; 34 | #endif 35 | } 36 | 37 | RCObj::~RCObj() 38 | { 39 | #if COLLECT_MINFO 40 | ++vm.totalObjectsFreed; 41 | #endif 42 | } 43 | 44 | 45 | void RCObj::norefs() 46 | { 47 | refcount = -999; 48 | delete this; 49 | } 50 | 51 | 52 | void RCObj::negrefcount() 53 | { 54 | post("RELEASING WITH NEGATIVE REFCOUNT %s %p %d\n", TypeName(), this, refcount.load()); 55 | } 56 | void RCObj::alreadyDead() 57 | { 58 | post("RETAINING ALREADY DEAD OBJECT %s %p\n", TypeName(), this); 59 | } 60 | -------------------------------------------------------------------------------- /src/SndfileSoundFile.cpp: -------------------------------------------------------------------------------- 1 | #ifndef SAPF_AUDIOTOOLBOX 2 | #include "SndfileSoundFile.hpp" 3 | #include "SoundFiles.hpp" 4 | #include 5 | 6 | 7 | SndfileSoundFile::SndfileSoundFile(std::string path, std::unique_ptr writer, SNDFILE *inSndfile, 8 | const int inNumChannels) : 9 | mPath{std::move(path)}, mWriter{std::move(writer)}, 10 | mSndfile{inSndfile}, mNumChannels{inNumChannels} { 11 | } 12 | 13 | SndfileSoundFile::~SndfileSoundFile() { 14 | if (this->mSndfile) { 15 | sf_close(this->mSndfile); 16 | } 17 | } 18 | 19 | uint32_t SndfileSoundFile::numChannels() const { 20 | return this->mNumChannels; 21 | } 22 | 23 | int SndfileSoundFile::pull(uint32_t *framesRead, PortableBuffers& buffers) { 24 | buffers.interleaved.resize(*framesRead * this->mNumChannels * sizeof(double)); 25 | double *interleaved = (double *) buffers.interleaved.data(); 26 | sf_count_t framesReallyRead = sf_readf_double(this->mSndfile, interleaved, *framesRead); 27 | 28 | int result = 0; 29 | if(framesReallyRead >= 0) { 30 | *framesRead = framesReallyRead; 31 | } else { 32 | *framesRead = 0; 33 | result = framesReallyRead; 34 | } 35 | 36 | for(int ch = 0; ch < this->mNumChannels; ch++) { 37 | double *buf = (double *) buffers.buffers[ch].data; 38 | for(sf_count_t frame = 0; frame < framesReallyRead; frame++) { 39 | buf[frame] = interleaved[frame * this->mNumChannels + ch]; 40 | } 41 | } 42 | 43 | return result; 44 | } 45 | 46 | void SndfileSoundFile::write(const int numFrames, const PortableBuffers& bufs) const { 47 | if (const auto written{sf_write_float(mSndfile, (const float*) bufs.buffers[0].data, numFrames * bufs.buffers[0].numChannels)}; written <= 0) { 48 | const auto error{sf_strerror(mSndfile)}; 49 | printf("failed to write audio data to file - %s\n", error); 50 | } 51 | } 52 | 53 | void SndfileSoundFile::writeAsync(const RtBuffers& buffers, const unsigned int nBufferFrames) const { 54 | mWriter->writeAsync(buffers, nBufferFrames); 55 | } 56 | 57 | std::unique_ptr SndfileSoundFile::open(const char *path) { 58 | SNDFILE *sndfile = nullptr; 59 | SF_INFO sfinfo = {0}; 60 | 61 | if((sndfile = sf_open(path, SFM_READ, &sfinfo)) == nullptr) { 62 | post("failed to open file %s\n", sf_strerror(NULL)); 63 | sf_close(sndfile); 64 | return nullptr; 65 | } 66 | 67 | uint32_t numChannels = sfinfo.channels; 68 | 69 | sf_count_t seek_result; 70 | if((seek_result = sf_seek(sndfile, 0, SEEK_SET) < 0)) { 71 | post("failed to seek file %d\n", seek_result); 72 | sf_close(sndfile); 73 | return nullptr; 74 | } 75 | 76 | // TODO: to be implemented in https://github.com/chairbender/sapf/pull/8 77 | return nullptr; 78 | } 79 | 80 | // NOTE: ATTOW, interleaved is always passed as true, and 81 | // the fileSampleRate is always passed as 0, so the thread sample rate is always used. 82 | // (>sf / >sfo doesn't even provide a way to specify the sample rate) 83 | std::unique_ptr SndfileSoundFile::create(const char *path, const int numChannels, 84 | const double threadSampleRate, double fileSampleRate, const bool async) { 85 | if (fileSampleRate == 0.) 86 | fileSampleRate = threadSampleRate; 87 | 88 | std::unique_ptr writer; 89 | SNDFILE *sndfile = nullptr; 90 | if (async) { 91 | writer = std::make_unique(path, fileSampleRate, numChannels); 92 | } else { 93 | SF_INFO sfinfo{ 94 | .samplerate = static_cast(fileSampleRate), 95 | .channels = numChannels, 96 | .format = SF_FORMAT_WAV | SF_FORMAT_FLOAT}; 97 | sndfile = sf_open(path, SFM_WRITE, &sfinfo); 98 | if (!sndfile) { 99 | const auto error{sf_strerror(sndfile)}; 100 | printf("failed to open %s: %s\n", path, error); 101 | throw errNotFound; 102 | } 103 | } 104 | 105 | return std::make_unique(path, std::move(writer), sndfile, numChannels); 106 | } 107 | #endif // SAPF_AUDIOTOOLBOX 108 | -------------------------------------------------------------------------------- /src/SoundFiles.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "SoundFiles.hpp" 18 | #include 19 | 20 | extern char gSessionTime[256]; 21 | 22 | class SFReaderOutputChannel; 23 | 24 | class SFReader : public Object 25 | { 26 | std::unique_ptr mSoundFile; 27 | AudioBuffers mBuffers; 28 | SFReaderOutputChannel* mOutputs; 29 | int64_t mFramesRemaining; 30 | bool mFinished = false; 31 | 32 | public: 33 | 34 | SFReader(std::unique_ptr inSoundFile, int64_t inDuration); 35 | 36 | ~SFReader(); 37 | 38 | virtual const char* TypeName() const override { return "SFReader"; } 39 | 40 | P createOutputs(Thread& th); 41 | 42 | bool pull(Thread& th); 43 | void fulfillOutputs(int blockSize); 44 | void produceOutputs(int shrinkBy); 45 | }; 46 | 47 | class SFReaderOutputChannel : public Gen 48 | { 49 | friend class SFReader; 50 | P mSFReader; 51 | SFReaderOutputChannel* mNextOutput = nullptr; 52 | Z* mDummy = nullptr; 53 | 54 | public: 55 | SFReaderOutputChannel(Thread& th, SFReader* inSFReader) 56 | : Gen(th, itemTypeZ, true), mSFReader(inSFReader) 57 | { 58 | } 59 | 60 | ~SFReaderOutputChannel() 61 | { 62 | if (mDummy) free(mDummy); 63 | } 64 | 65 | virtual void norefs() override 66 | { 67 | mOut = nullptr; 68 | mSFReader = nullptr; 69 | } 70 | 71 | virtual const char* TypeName() const override { return "SFReaderOutputChannel"; } 72 | 73 | virtual void pull(Thread& th) override 74 | { 75 | if (mSFReader->pull(th)) { 76 | end(); 77 | } 78 | } 79 | 80 | }; 81 | 82 | SFReader::SFReader(std::unique_ptr inSoundFile, int64_t inDuration) : 83 | mSoundFile(std::move(inSoundFile)), 84 | mBuffers(mSoundFile->numChannels()), 85 | mFramesRemaining(inDuration) 86 | { 87 | 88 | } 89 | 90 | SFReader::~SFReader() 91 | { 92 | SFReaderOutputChannel* output = mOutputs; 93 | do { 94 | SFReaderOutputChannel* next = output->mNextOutput; 95 | delete output; 96 | output = next; 97 | } while (output); 98 | } 99 | 100 | void SFReader::fulfillOutputs(int blockSize) 101 | { 102 | SFReaderOutputChannel* output = mOutputs; 103 | size_t bufSize = blockSize * sizeof(Z); 104 | for (int i = 0; output; ++i, output = output->mNextOutput){ 105 | Z* out; 106 | if (output->mOut) 107 | out = output->mOut->fulfillz(blockSize); 108 | else { 109 | if (!output->mDummy) 110 | output->mDummy = (Z*)calloc(output->mBlockSize, sizeof(Z)); 111 | 112 | out = output->mDummy; 113 | } 114 | 115 | this->mBuffers.setNumChannels(i, 1); 116 | this->mBuffers.setData(i, out); 117 | this->mBuffers.setSize(i, bufSize); 118 | 119 | memset(out, 0, bufSize); 120 | }; 121 | } 122 | 123 | void SFReader::produceOutputs(int shrinkBy) 124 | { 125 | SFReaderOutputChannel* output = mOutputs; 126 | do { 127 | if (output->mOut) 128 | output->produce(shrinkBy); 129 | output = output->mNextOutput; 130 | } while (output); 131 | } 132 | 133 | P SFReader::createOutputs(Thread& th) 134 | { 135 | const uint32_t numChannels = mSoundFile->numChannels(); 136 | P s = new List(itemTypeV, numChannels); 137 | 138 | // fill s->mArray with ola's output channels. 139 | SFReaderOutputChannel* last = nullptr; 140 | P a = s->mArray; 141 | for (uint32_t i = 0; i < numChannels; ++i) { 142 | SFReaderOutputChannel* c = new SFReaderOutputChannel(th, this); 143 | if (last) last->mNextOutput = c; 144 | else mOutputs = c; 145 | last = c; 146 | a->add(new List(c)); 147 | } 148 | 149 | return s; 150 | } 151 | 152 | bool SFReader::pull(Thread& th) 153 | { 154 | if (mFramesRemaining == 0) 155 | mFinished = true; 156 | 157 | if (mFinished) 158 | return true; 159 | 160 | SFReaderOutputChannel* output = mOutputs; 161 | int blockSize = output->mBlockSize; 162 | if (mFramesRemaining > 0) 163 | blockSize = (int)std::min(mFramesRemaining, (int64_t)blockSize); 164 | 165 | fulfillOutputs(blockSize); 166 | 167 | // read file here. 168 | uint32_t framesRead = blockSize; 169 | int err = mSoundFile->pull(&framesRead, mBuffers); 170 | 171 | if (err || framesRead == 0) { 172 | mFinished = true; 173 | } 174 | 175 | produceOutputs(blockSize - framesRead); 176 | if (mFramesRemaining > 0) mFramesRemaining -= blockSize; 177 | 178 | return mFinished; 179 | } 180 | 181 | void sfread(Thread& th, Arg filename, int64_t offset, int64_t frames) 182 | { 183 | const char* path = ((String*)filename.o())->s; 184 | 185 | #ifdef SAPF_AUDIOTOOLBOX 186 | std::unique_ptr soundFile = SoundFile::open(path, th.rate.sampleRate); 187 | #else 188 | std::unique_ptr soundFile = SoundFile::open(path); 189 | #endif 190 | 191 | if(soundFile != nullptr) { 192 | SFReader* sfr = new SFReader(std::move(soundFile), -1); 193 | th.push(sfr->createOutputs(th)); 194 | } 195 | } 196 | 197 | #ifdef SAPF_AUDIOTOOLBOX 198 | std::unique_ptr sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved) 199 | { 200 | return SoundFile::create(path, numChannels, th.rate.sampleRate, fileSampleRate, interleaved); 201 | } 202 | #else 203 | std::unique_ptr sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved, bool async) 204 | { 205 | return SoundFile::create(path, numChannels, th.rate.sampleRate, fileSampleRate, async); 206 | } 207 | #endif 208 | 209 | std::atomic gFileCount = 0; 210 | 211 | void makeRecordingPath(Arg filename, char* path, int len) 212 | { 213 | if (filename.isString()) { 214 | const char* recDir = getenv("SAPF_RECORDINGS"); 215 | if (!recDir || strlen(recDir)==0) { 216 | #ifdef _WIN32 217 | recDir = getenv("TEMP"); 218 | if (!recDir || strlen(recDir)==0) 219 | recDir = getenv("TMP"); 220 | #else 221 | recDir = "/tmp"; 222 | #endif 223 | if (!recDir || strlen(recDir)==0) 224 | recDir = "."; 225 | } 226 | snprintf(path, len, "%s/%s.wav", recDir, ((String*)filename.o())->s); 227 | } else { 228 | int32_t count = ++gFileCount; 229 | #ifdef _WIN32 230 | const char* tempDir = getenv("TEMP"); 231 | if (!tempDir || strlen(tempDir)==0) 232 | tempDir = getenv("TMP"); 233 | if (!tempDir || strlen(tempDir)==0) 234 | tempDir = "."; 235 | #else 236 | const char* tempDir = "/tmp"; 237 | #endif 238 | snprintf(path, len, "%s/sapf-%s-%04d.wav", tempDir, gSessionTime, count); 239 | 240 | } 241 | } 242 | 243 | void sfwrite(Thread& th, V& v, Arg filename, bool openIt) 244 | { 245 | std::vector in; 246 | 247 | int numChannels = 0; 248 | 249 | if (v.isZList()) { 250 | if (!v.isFinite()) indefiniteOp(">sf : s - indefinite number of frames", ""); 251 | numChannels = 1; 252 | in.push_back(ZIn(v)); 253 | } else { 254 | if (!v.isFinite()) indefiniteOp(">sf : s - indefinite number of channels", ""); 255 | P s = (List*)v.o(); 256 | s = s->pack(th); 257 | Array* a = s->mArray(); 258 | numChannels = (int)a->size(); 259 | 260 | if (numChannels > kMaxSFChannels) 261 | throw errOutOfRange; 262 | 263 | bool allIndefinite = true; 264 | for (int i = 0; i < numChannels; ++i) { 265 | V va = a->at(i); 266 | if (va.isFinite()) allIndefinite = false; 267 | in.push_back(ZIn(va)); 268 | va.o = nullptr; 269 | } 270 | 271 | s = nullptr; 272 | a = nullptr; 273 | 274 | if (allIndefinite) indefiniteOp(">sf : s - all channels have indefinite number of frames", ""); 275 | } 276 | v.o = nullptr; 277 | 278 | char path[1024]; 279 | 280 | makeRecordingPath(filename, path, 1024); 281 | 282 | 283 | #ifdef SAPF_AUDIOTOOLBOX 284 | std::unique_ptr soundFile = sfcreate(th, path, numChannels, 0., true); 285 | #else 286 | std::unique_ptr soundFile = sfcreate(th, path, numChannels, 0., true, false); 287 | #endif 288 | if (!soundFile) return; 289 | 290 | std::valarray buf(0., numChannels * kBufSize); 291 | AudioBuffers bufs(1); 292 | bufs.setNumChannels(0, numChannels); 293 | bufs.setData(0, &buf[0]); 294 | bufs.setSize(0, kBufSize * sizeof(float)); 295 | 296 | int64_t framesPulled = 0; 297 | int64_t framesWritten = 0; 298 | bool done = false; 299 | while (!done) { 300 | int minn = kBufSize; 301 | memset(&buf[0], 0, kBufSize * numChannels); 302 | for (int i = 0; i < numChannels; ++i) { 303 | int n = kBufSize; 304 | bool imdone = in[i].fill(th, n, &buf[0]+i, numChannels); 305 | framesPulled += n; 306 | if (imdone) done = true; 307 | minn = std::min(n, minn); 308 | } 309 | 310 | bufs.setSize(0, numChannels * minn * sizeof(float)); 311 | // TODO: move into a SoundFile method 312 | #ifdef SAPF_AUDIOTOOLBOX 313 | OSStatus err = ExtAudioFileWrite(soundFile->mXAF, minn, bufs.abl); 314 | if (err) { 315 | post("file writing failed %d\n", (int)err); 316 | break; 317 | } 318 | #else 319 | soundFile->write(minn, bufs); 320 | #endif // SAPF_AUDIOTOOLBOX 321 | framesWritten += minn; 322 | } 323 | 324 | post("wrote file '%s' %d channels %g secs\n", path, numChannels, framesWritten * th.rate.invSampleRate); 325 | 326 | soundFile = nullptr; 327 | 328 | if (openIt) { 329 | char cmd[1100]; 330 | #ifdef _WIN32 331 | snprintf(cmd, 1100, "start \"\" \"%s\"", path); 332 | #else 333 | snprintf(cmd, 1100, "open \"%s\"", path); 334 | #endif 335 | system(cmd); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/Spectrogram.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "Spectrogram.hpp" 18 | #include "makeImage.hpp" 19 | #ifdef SAPF_ACCELERATE 20 | #include 21 | #endif // SAPF_ACCELERATE 22 | #include 23 | #include 24 | #include 25 | 26 | static void makeColorTable(unsigned char* table); 27 | 28 | static double bessi0(double x) 29 | { 30 | //returns the modified Bessel function I_0(x) for any real x 31 | //from numerical recipes 32 | double ax, ans; 33 | double y; 34 | 35 | if((ax=fabs(x))<3.75){ 36 | y=x/3.75; 37 | y *= y; 38 | ans =1.0+y*(3.5156229+y*(3.0899424+y*(1.2067492 39 | +y*(0.2659732+y*(0.360768e-1+y*0.45813e-2))))); 40 | } 41 | else{ 42 | y=3.75/ax; 43 | ans = (exp(ax)/sqrt(ax))*(0.39894228+y*(0.1328592e-1 44 | +y*(0.225319e-2+y*(-0.157565e-2+y*(0.916281e-2 45 | +y*(-0.2057706e-1+y*(0.2635537e-1+y*(-0.1647633e-1 46 | +y*0.392377e-2)))))))); 47 | } 48 | 49 | return ans; 50 | } 51 | 52 | static double i0(double x) 53 | { 54 | const double epsilon = 1e-18; 55 | int n = 1; 56 | double S = 1., D = 1., T; 57 | 58 | while (D > epsilon * S) { 59 | T = x / (2 * n++); 60 | D *= T * T; 61 | S += D; 62 | } 63 | return S; 64 | } 65 | 66 | static void calcKaiserWindowD(size_t size, double* window, double stopBandAttenuation) 67 | { 68 | size_t M = size - 1; 69 | size_t N = M-1; 70 | #if VERBOSE 71 | printf("FillKaiser %d %g\n", M, stopBandAttenuation); 72 | #endif 73 | 74 | double alpha = 0.; 75 | if (stopBandAttenuation <= -50.) 76 | alpha = 0.1102 * (-stopBandAttenuation - 8.7); 77 | else if (stopBandAttenuation < -21.) 78 | alpha = 0.5842 * pow(-stopBandAttenuation - 21., 0.4) + 0.07886 * (-stopBandAttenuation - 21.); 79 | 80 | double p = N / 2; 81 | double kk = 1.0 / i0(alpha); 82 | 83 | for(unsigned int k = 0; k < M; k++ ) 84 | { 85 | double x = (k-p) / p; 86 | 87 | // Kaiser window 88 | window[k+1] *= kk * bessi0(alpha * sqrt(1.0 - x*x) ); 89 | } 90 | window[0] = 0.; 91 | window[size-1] = 0.; 92 | #if VERBOSE 93 | printf("done\n"); 94 | #endif 95 | } 96 | 97 | const int border = 8; 98 | 99 | void spectrogram(int size, double* data, int width, int log2bins, const char* path, double dBfloor) 100 | { 101 | #ifdef SAPF_ACCELERATE 102 | int numRealFreqs = 1 << log2bins; 103 | 104 | int log2n = log2bins + 1; 105 | int n = 1 << log2n; 106 | int nOver2 = n / 2; 107 | 108 | 109 | double scale = 1./nOver2; 110 | 111 | int64_t paddedSize = size + n; 112 | double* paddedData = (double*)calloc(paddedSize, sizeof(double)); 113 | memcpy(paddedData + nOver2, data, size * sizeof(double)); 114 | 115 | 116 | double* dBMags = (double*)calloc(numRealFreqs + 1, sizeof(double)); 117 | 118 | double hopSize = size <= n ? 0 : (double)(size - n) / (double)(width - 1); 119 | 120 | double* window = (double*)calloc(n, sizeof(double)); 121 | for (int i = 0; i < n; ++i) window[i] = 1.; 122 | calcKaiserWindowD(n, window, -180.); 123 | 124 | unsigned char table[1028]; 125 | makeColorTable(table); 126 | 127 | int heightOfAmplitudeView = 128; 128 | int heightOfFFT = numRealFreqs+1; 129 | int totalHeight = heightOfAmplitudeView+heightOfFFT+3*border; 130 | int topOfSpectrum = heightOfAmplitudeView + 2*border; 131 | int totalWidth = width+2*border; 132 | Bitmap* b = createBitmap(totalWidth, totalHeight); 133 | fillRect(b, 0, 0, totalWidth, totalHeight, 160, 160, 160, 255); 134 | fillRect(b, border, border, width, heightOfAmplitudeView, 0, 0, 0, 255); 135 | 136 | FFTSetupD fftSetup = vDSP_create_fftsetupD(log2n, kFFTRadix2); 137 | 138 | double* windowedData = (double*)calloc(n, sizeof(double)); 139 | double* interleavedData = (double*)calloc(n, sizeof(double)); 140 | double* resultData = (double*)calloc(n, sizeof(double)); 141 | DSPDoubleSplitComplex interleaved; 142 | interleaved.realp = interleavedData; 143 | interleaved.imagp = interleavedData + nOver2; 144 | DSPDoubleSplitComplex result; 145 | result.realp = resultData; 146 | result.imagp = resultData + nOver2; 147 | double maxmag = 0.; 148 | 149 | double hpos = nOver2; 150 | for (int i = 0; i < width; ++i) { 151 | size_t ihpos = (size_t)hpos; 152 | 153 | // do analysis 154 | // find peak 155 | double peak = 1e-20; 156 | for (int w = 0; w < n; ++w) { 157 | double x = paddedData[w+ihpos]; 158 | x = fabs(x); 159 | if (x > peak) peak = x; 160 | } 161 | 162 | for (int64_t w = 0; w < n; ++w) windowedData[w] = window[w] * paddedData[w+ihpos]; 163 | 164 | vDSP_ctozD((DSPDoubleComplex*)windowedData, 2, &interleaved, 1, nOver2); 165 | 166 | vDSP_fft_zropD(fftSetup, &interleaved, 1, &result, 1, log2n, kFFTDirection_Forward); 167 | 168 | dBMags[0] = result.realp[0] * scale; 169 | dBMags[numRealFreqs] = result.imagp[0] * scale; 170 | if (dBMags[0] > maxmag) maxmag = dBMags[0]; 171 | if (dBMags[numRealFreqs] > maxmag) maxmag = dBMags[numRealFreqs]; 172 | for (int64_t j = 1; j < numRealFreqs-1; ++j) { 173 | double x = result.realp[j] * scale; 174 | double y = result.imagp[j] * scale; 175 | dBMags[j] = sqrt(x*x + y*y); 176 | if (dBMags[j] > maxmag) maxmag = dBMags[j]; 177 | } 178 | 179 | double invmag = 1.; 180 | dBMags[0] = 20.*log2(dBMags[0]*invmag); 181 | dBMags[numRealFreqs] = 20.*log10(dBMags[numRealFreqs]*invmag); 182 | for (int64_t j = 0; j <= numRealFreqs-1; ++j) { 183 | dBMags[j] = 20.*log10(dBMags[j]*invmag); 184 | } 185 | 186 | 187 | 188 | // set pixels 189 | { 190 | double peakdB = 20.*log10(peak); 191 | int peakColorIndex = 256. - peakdB * (256. / dBfloor); 192 | int peakIndex = heightOfAmplitudeView - peakdB * (heightOfAmplitudeView / dBfloor); 193 | if (peakIndex < 0) peakIndex = 0; 194 | if (peakIndex > heightOfAmplitudeView) peakIndex = heightOfAmplitudeView; 195 | if (peakColorIndex < 0) peakColorIndex = 0; 196 | if (peakColorIndex > 255) peakColorIndex = 255; 197 | 198 | unsigned char* t = table + 4*peakColorIndex; 199 | fillRect(b, i+border, border+128-peakIndex, 1, peakIndex, t[0], t[1], t[2], t[3]); 200 | } 201 | 202 | for (int j = 0; j < numRealFreqs; ++j) { 203 | int colorIndex = 256. - dBMags[j] * (256. / dBfloor); 204 | if (colorIndex < 0) colorIndex = 0; 205 | if (colorIndex > 255) colorIndex = 255; 206 | 207 | unsigned char* t = table + 4*colorIndex; 208 | 209 | setPixel(b, i+border, numRealFreqs-j+topOfSpectrum, t[0], t[1], t[2], t[3]); 210 | } 211 | 212 | hpos += hopSize; 213 | } 214 | 215 | vDSP_destroy_fftsetupD(fftSetup); 216 | 217 | writeBitmap(b, path); 218 | freeBitmap(b); 219 | free(dBMags); 220 | free(paddedData); 221 | free(window); 222 | free(windowedData); 223 | free(interleavedData); 224 | free(resultData); 225 | #else 226 | // TODO cross platform spectrogram 227 | #endif // SAPF_ACCELERATE 228 | } 229 | 230 | 231 | static void makeColorTable(unsigned char* table) 232 | { 233 | // white >> red >> yellow >> green >> cyan >> blue >> magenta >> pink >> black 234 | // 0 -20 -40 -60 -80 -100 -120 -140 -160 235 | // 255 224 192 160 128 96 64 32 0 236 | 237 | int colors[9][4] = { 238 | { 0, 0, 64, 255}, // dk blue 239 | { 0, 0, 255, 255}, // blue 240 | {255, 0, 0, 255}, // red 241 | {255, 255, 0, 255}, // yellow 242 | {255, 255, 255, 255} // white 243 | }; 244 | 245 | for (int j = 0; j < 4; ++j) { 246 | for (int i = 0; i < 64; ++i) { 247 | for (int k = 0; k < 4; ++k) { 248 | int x = (colors[j][k] * (64 - i) + colors[j+1][k] * i) / 64; 249 | if (x > 255) x = 255; 250 | table[j*64*4 + i*4 + k + 4] = x; 251 | } 252 | } 253 | } 254 | 255 | table[0] = 0; 256 | table[1] = 0; 257 | table[2] = 0; 258 | table[3] = 255; 259 | } 260 | 261 | -------------------------------------------------------------------------------- /src/Types.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "Types.hpp" 18 | -------------------------------------------------------------------------------- /src/ZArr.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "ZArr.hpp" 18 | #ifndef SAPF_ACCELERATE 19 | 20 | ZArr zarr(Z *vec, const int n, const int stride) { 21 | return ZArr(vec, n, Eigen::InnerStride<>(stride)); 22 | } 23 | 24 | CZArr czarr(const Z *vec, const int n, const int stride) { 25 | return CZArr(vec, n, Eigen::InnerStride<>(stride)); 26 | } 27 | #endif -------------------------------------------------------------------------------- /src/dsp.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "dsp.hpp" 18 | #include 19 | #include 20 | #include 21 | 22 | void FFT::init(size_t log2n) { 23 | this->n = pow(2, log2n); 24 | this->log2n = log2n; 25 | 26 | #ifdef SAPF_ACCELERATE 27 | this->setup = vDSP_create_fftsetupD(this->log2n, kFFTRadix2); 28 | #else 29 | this->in = (fftw_complex *) fftw_malloc(this->n * sizeof(fftw_complex)); 30 | this->out = (fftw_complex *) fftw_malloc(this->n * sizeof(fftw_complex)); 31 | // "Here, n is the “logical” size of the DFT, not necessarily the 32 | // physical size of the array. In particular, the real (double) array 33 | // has n elements, while the complex (fftw_complex) array has n/2+1 34 | // elements (where the division is rounded down). For an in-place 35 | // transform, in and out are aliased to the same array, which must be 36 | // big enough to hold both; so, the real array would actually have 37 | // 2*(n/2+1) elements, where the elements beyond the first n are unused 38 | // padding." 39 | // - https://fftw.org/fftw3_doc/One_002dDimensional-DFTs-of-Real-Data.html 40 | this->in_out_real = (double *) fftw_malloc(2 * (this->n / 2 + 1) * sizeof(double)); 41 | 42 | this->forward_out_of_place_plan = fftw_plan_dft_1d(this->n, this->in, this->out, FFTW_FORWARD, FFTW_ESTIMATE); 43 | this->backward_out_of_place_plan = fftw_plan_dft_1d(this->n, this->in, this->out, FFTW_BACKWARD, FFTW_ESTIMATE); 44 | this->forward_in_place_plan = fftw_plan_dft_1d(this->n, this->in, this->in, FFTW_FORWARD, FFTW_ESTIMATE); 45 | this->backward_in_place_plan = fftw_plan_dft_1d(this->n, this->in, this->in, FFTW_BACKWARD, FFTW_ESTIMATE); 46 | this->forward_real_plan = fftw_plan_dft_r2c_1d(this->n, this->in_out_real, (fftw_complex *) this->in_out_real, FFTW_ESTIMATE); 47 | this->backward_real_plan = fftw_plan_dft_c2r_1d(this->n, (fftw_complex *) this->in_out_real, this->in_out_real, FFTW_ESTIMATE); 48 | #endif // SAPF_ACCELERATE 49 | } 50 | 51 | FFT::~FFT() { 52 | #ifdef SAPF_ACCELERATE 53 | vDSP_destroy_fftsetupD(this->setup); 54 | #else 55 | fftw_destroy_plan(forward_out_of_place_plan); 56 | fftw_destroy_plan(backward_out_of_place_plan); 57 | fftw_destroy_plan(forward_in_place_plan); 58 | fftw_destroy_plan(backward_in_place_plan); 59 | fftw_destroy_plan(forward_real_plan); 60 | fftw_destroy_plan(backward_real_plan); 61 | 62 | fftw_free(this->in); 63 | fftw_free(this->out); 64 | fftw_free(this->in_out_real); 65 | #endif // SAPF_ACCELERATE 66 | } 67 | 68 | void FFT::forward(double *inReal, double *inImag, double *outReal, double *outImag) { 69 | double scale = 2. / this->n; 70 | #ifdef SAPF_ACCELERATE 71 | DSPDoubleSplitComplex in; 72 | DSPDoubleSplitComplex out; 73 | 74 | in.realp = inReal; 75 | in.imagp = inImag; 76 | out.realp = outReal; 77 | out.imagp = outImag; 78 | 79 | vDSP_fft_zopD(this->setup, &in, 1, &out, 1, this->log2n, FFT_FORWARD); 80 | 81 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, this->n); 82 | vDSP_vsmulD(outImag, 1, &scale, outImag, 1, this->n); 83 | #else 84 | for(size_t i = 0; i < this->n; i++) { 85 | this->in[i][0] = inReal[i]; 86 | this->in[i][1] = inImag[i]; 87 | } 88 | fftw_execute(this->forward_out_of_place_plan); 89 | for(size_t i = 0; i < this->n; i++) { 90 | outReal[i] = this->out[i][0] * scale; 91 | outImag[i] = this->out[i][1] * scale; 92 | } 93 | #endif // SAPF_ACCELERATE 94 | } 95 | 96 | void FFT::backward(double *inReal, double *inImag, double *outReal, double *outImag) { 97 | double scale = .5; 98 | #ifdef SAPF_ACCELERATE 99 | DSPDoubleSplitComplex in; 100 | DSPDoubleSplitComplex out; 101 | 102 | in.realp = inReal; 103 | in.imagp = inImag; 104 | out.realp = outReal; 105 | out.imagp = outImag; 106 | 107 | vDSP_fft_zopD(this->setup, &in, 1, &out, 1, this->log2n, FFT_INVERSE); 108 | 109 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, this->n); 110 | vDSP_vsmulD(outImag, 1, &scale, outImag, 1, this->n); 111 | #else 112 | for(size_t i = 0; i < this->n; i++) { 113 | this->in[i][0] = inReal[i]; 114 | this->in[i][1] = inImag[i]; 115 | } 116 | fftw_execute(this->backward_out_of_place_plan); 117 | for(size_t i = 0; i < this->n; i++) { 118 | outReal[i] = this->out[i][0] * scale; 119 | outImag[i] = this->out[i][1] * scale; 120 | } 121 | #endif // SAPF_ACCELERATE 122 | } 123 | 124 | void FFT::forward_in_place(double *ioReal, double *ioImag) { 125 | double scale = 2. / this->n; 126 | #ifdef SAPF_ACCELERATE 127 | DSPDoubleSplitComplex io; 128 | 129 | io.realp = ioReal; 130 | io.imagp = ioImag; 131 | 132 | vDSP_fft_zipD(this->setup, &io, 1, this->log2n, FFT_FORWARD); 133 | 134 | vDSP_vsmulD(ioReal, 1, &scale, ioReal, 1, this->n); 135 | vDSP_vsmulD(ioImag, 1, &scale, ioImag, 1, this->n); 136 | #else 137 | for(size_t i = 0; i < this->n; i++) { 138 | this->in[i][0] = ioReal[i]; 139 | this->in[i][1] = ioImag[i]; 140 | } 141 | fftw_execute(this->forward_in_place_plan); 142 | for(size_t i = 0; i < this->n; i++) { 143 | ioReal[i] = this->in[i][0] * scale; 144 | ioImag[i] = this->in[i][1] * scale; 145 | } 146 | #endif // SAPF_ACCELERATE 147 | } 148 | 149 | void FFT::backward_in_place(double *ioReal, double *ioImag) { 150 | double scale = .5; 151 | #ifdef SAPF_ACCELERATE 152 | DSPDoubleSplitComplex io; 153 | 154 | io.realp = ioReal; 155 | io.imagp = ioImag; 156 | 157 | vDSP_fft_zipD(this->setup, &io, 1, this->log2n, FFT_INVERSE); 158 | 159 | vDSP_vsmulD(ioReal, 1, &scale, ioReal, 1, this->n); 160 | vDSP_vsmulD(ioImag, 1, &scale, ioImag, 1, this->n); 161 | #else 162 | for(size_t i = 0; i < this->n; i++) { 163 | this->in[i][0] = ioReal[i]; 164 | this->in[i][1] = ioImag[i]; 165 | } 166 | fftw_execute(this->backward_in_place_plan); 167 | for(size_t i = 0; i < this->n; i++) { 168 | ioReal[i] = this->in[i][0] * scale; 169 | ioImag[i] = this->in[i][1] * scale; 170 | } 171 | #endif // SAPF_ACCELERATE 172 | } 173 | 174 | void FFT::forward_real(double *inReal, double *outReal, double *outImag) { 175 | double scale = 2. / n; 176 | int n2 = this->n/2; 177 | #ifdef SAPF_ACCELERATE 178 | DSPDoubleSplitComplex in; 179 | DSPDoubleSplitComplex out; 180 | 181 | vDSP_ctozD((DSPDoubleComplex*)inReal, 1, &in, 1, n2); 182 | 183 | out.realp = outReal; 184 | out.imagp = outImag; 185 | 186 | vDSP_fft_zropD(this->setup, &in, 1, &out, 1, this->log2n, FFT_FORWARD); 187 | 188 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, n2); 189 | vDSP_vsmulD(outImag, 1, &scale, outImag, 1, n2); 190 | 191 | out.realp[n2] = out.imagp[0]; 192 | out.imagp[0] = 0.; 193 | out.imagp[n2] = 0.; 194 | #else 195 | for(size_t i = 0; i < this->n; i++) { 196 | this->in_out_real[i] = inReal[i]; 197 | } 198 | fftw_execute(this->forward_real_plan); 199 | for(size_t i = 0; i < n2; i++) { 200 | outReal[i] = this->in_out_real[2*i] * scale; 201 | outImag[i] = this->in_out_real[2*i+1] * scale; 202 | } 203 | #endif // SAPF_ACCELERATE 204 | } 205 | 206 | void FFT::backward_real(double *inReal, double *inImag, double *outReal) { 207 | double scale = .5; 208 | int n2 = this->n/2; 209 | #ifdef SAPF_ACCELERATE 210 | DSPDoubleSplitComplex in; 211 | 212 | in.realp = inReal; 213 | in.imagp = inImag; 214 | 215 | //in.imagp[0] = in.realp[n2]; 216 | in.imagp[0] = 0.; 217 | 218 | vDSP_fft_zripD(this->setup, &in, 1, this->log2n, FFT_INVERSE); 219 | 220 | vDSP_ztocD(&in, 1, (DSPDoubleComplex*)outReal, 2, n2); 221 | 222 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, n); 223 | #else 224 | for(size_t i = 0; i < n2; i++) { 225 | this->in_out_real[2*i] = inReal[i]; 226 | this->in_out_real[2*i+1] = inImag[i]; 227 | } 228 | fftw_execute(this->backward_real_plan); 229 | for(size_t i = 0; i < this->n; i++) { 230 | outReal[i] = this->in_out_real[i] * scale; 231 | } 232 | #endif // SAPF_ACCELERATE 233 | } 234 | 235 | FFT ffts[kMaxFFTLogSize+1]; 236 | 237 | 238 | 239 | void initFFT() 240 | { 241 | for (int i = kMinFFTLogSize; i <= kMaxFFTLogSize; ++i) { 242 | ffts[i].init(i); 243 | } 244 | } 245 | 246 | void fft(int n, double* inReal, double* inImag, double* outReal, double* outImag) 247 | { 248 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 249 | ffts[log2n].forward(inReal, inImag, outReal, outImag); 250 | } 251 | 252 | void ifft(int n, double* inReal, double* inImag, double* outReal, double* outImag) 253 | { 254 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 255 | ffts[log2n].backward(inReal, inImag, outReal, outImag); 256 | } 257 | 258 | void fft(int n, double* ioReal, double* ioImag) 259 | { 260 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 261 | ffts[log2n].forward_in_place(ioReal, ioImag); 262 | } 263 | 264 | void ifft(int n, double* ioReal, double* ioImag) 265 | { 266 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 267 | ffts[log2n].backward_in_place(ioReal, ioImag); 268 | } 269 | 270 | 271 | void rfft(int n, double* inReal, double* outReal, double* outImag) 272 | { 273 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 274 | ffts[log2n].forward_real(inReal, outReal, outImag); 275 | } 276 | 277 | 278 | void rifft(int n, double* inReal, double* inImag, double* outReal) 279 | { 280 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 281 | ffts[log2n].backward_real(inReal, inImag, outReal); 282 | } 283 | 284 | 285 | #define USE_VFORCE 1 286 | 287 | inline void complex_expD_conj(double& re, double& im) 288 | { 289 | double rho = expf(re); 290 | re = rho * cosf(im); 291 | im = rho * sinf(im); 292 | } 293 | 294 | -------------------------------------------------------------------------------- /src/elapsedTime.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "elapsedTime.hpp" 18 | #ifdef SAPF_MACH_TIME 19 | #include 20 | #else 21 | #include 22 | #endif // SAPF_MACH_TIME 23 | #include 24 | 25 | extern "C" { 26 | 27 | static double gHostClockFreq; 28 | 29 | void initElapsedTime() 30 | { 31 | #ifdef SAPF_MACH_TIME 32 | struct mach_timebase_info info; 33 | mach_timebase_info(&info); 34 | gHostClockFreq = 1e9 * ((double)info.numer / (double)info.denom); 35 | #else 36 | gHostClockFreq = 0.0; 37 | #endif // SAPF_MACH_TIME 38 | } 39 | 40 | double elapsedTime() 41 | { 42 | #ifdef SAPF_MACH_TIME 43 | return (double)mach_absolute_time() / gHostClockFreq; 44 | #else 45 | return (double) std::chrono::duration_cast( 46 | std::chrono::steady_clock::now().time_since_epoch() 47 | ).count() / 10e9; 48 | #endif // SAPF_MACH_TIME 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "VM.hpp" 18 | #include 19 | // TODO: Is this even needed in this file? 20 | #if USE_LIBEDIT 21 | #include 22 | #endif 23 | #include 24 | #include 25 | #include "primes.hpp" 26 | #include 27 | #ifdef SAPF_DISPATCH 28 | #include 29 | #else 30 | #include 31 | #endif // SAPF_DISPATCH 32 | #ifdef SAPF_COREFOUNDATION 33 | #include 34 | #endif // SAPF_COREFOUNDATION 35 | 36 | #ifdef SAPF_MANTA 37 | #include "Manta.h" 38 | 39 | class MyManta : public Manta 40 | { 41 | virtual void PadEvent(int row, int column, int id, int value) { 42 | printf("pad %d %d %d %d\n", row, column, id, value); 43 | } 44 | virtual void SliderEvent(int id, int value) { 45 | printf("slider %d %d\n", id, value); 46 | } 47 | virtual void ButtonEvent(int id, int value) { 48 | printf("button %d %d\n", id, value); 49 | } 50 | virtual void PadVelocityEvent(int row, int column, int id, int velocity) { 51 | printf("pad vel %d %d %d %d\n", row, column, id, velocity); 52 | 53 | } 54 | virtual void ButtonVelocityEvent(int id, int velocity) { 55 | printf("button vel %d %d\n", id, velocity); 56 | } 57 | virtual void FrameEvent(uint8_t *frame) {} 58 | virtual void DebugPrint(const char *fmt, ...) {} 59 | }; 60 | 61 | Manta* manta(); 62 | Manta* manta() 63 | { 64 | static MyManta* sManta = new MyManta(); 65 | return sManta; 66 | } 67 | 68 | static void mantaLoop() { 69 | /*** see at bottom for better way ***/ 70 | while(true) { 71 | try { 72 | MantaUSB::HandleEvents(); 73 | usleep(5000); 74 | } catch(...) { 75 | sleep(1); 76 | } 77 | } 78 | } 79 | #endif // SAPF_MANTA 80 | 81 | /* issue: 82 | 83 | [These comments are very old and I have not checked if they are still relevant.] 84 | 85 | TableData alloc should use new 86 | 87 | bugs: 88 | 89 | itd should have a tail time. currently the ugen stops as soon as its input, cutting off the delayed signal. 90 | 91 | + should not stop until both inputs stop? 92 | other additive binops: - avg2 sumsq 93 | 94 | no, use a operator 95 | 96 | --- 97 | 98 | adsrg (gate a d s r --> out) envelope generator with gate. 99 | adsr (dur a d s r --> out) envelope generator with duration. 100 | evgg - (gate levels times curves suspt --> out) envelope generator with gate. suspt is the index of the sustain level. 101 | evg - (dur levels times curves suspt --> out) envelope generator with duration. suspt is the index of the sustain level. 102 | 103 | blip (freq phase nharm --> out) band limited impulse oscillator. 104 | dsf1 (freq phase nharm lharm hmul --> out) sum of sines oscillator. 105 | 106 | formant (freq formfreq bwfreq --> out) formant oscillator 107 | 108 | svf (in freq rq --> [lp hp bp bs]) state variable filter. 109 | moogf (in freq rq --> out) moog ladder low pass filter. 110 | 111 | */ 112 | 113 | extern void AddCoreOps(); 114 | extern void AddMathOps(); 115 | extern void AddStreamOps(); 116 | extern void AddLFOps(); 117 | extern void AddUGenOps(); 118 | extern void AddSetOps(); 119 | extern void AddRandomOps(); 120 | extern void AddMidiOps(); 121 | 122 | const char* gVersionString = "0.1.21"; 123 | 124 | static void usage() 125 | { 126 | fprintf(stdout, "sapf [-r sample-rate][-p prelude-file]\n"); 127 | fprintf(stdout, "\n"); 128 | fprintf(stdout, "sapf [-h]\n"); 129 | fprintf(stdout, " print this help\n"); 130 | fprintf(stdout, "\n"); 131 | } 132 | 133 | static void replLoop(Thread th) { 134 | th.repl(stdin, vm.log_file); 135 | exit(0); 136 | } 137 | 138 | int main (int argc, const char * argv[]) 139 | { 140 | post("------------------------------------------------\n"); 141 | post("A tool for the expression of sound as pure form.\n"); 142 | post("------------------------------------------------\n"); 143 | post("--- version %s\n", gVersionString); 144 | 145 | for (int i = 1; i < argc;) { 146 | int c = argv[i][0]; 147 | if (c == '-') { 148 | c = argv[i][1]; 149 | switch (c) { 150 | case 'r' : { 151 | if (argc <= i+1) { post("expected sample rate after -r\n"); return 1; } 152 | 153 | double sr = atof(argv[i+1]); 154 | if (sr < 1000. || sr > 768000.) { post("sample rate out of range.\n"); return 1; } 155 | vm.setSampleRate(sr); 156 | post("sample rate set to %g\n", vm.ar.sampleRate); 157 | i += 2; 158 | } break; 159 | case 'p' : { 160 | if (argc <= i+1) { post("expected prelude file name after -p\n"); return 1; } 161 | vm.prelude_file = argv[i+1]; 162 | i += 2; 163 | } break; 164 | case 'h' : { 165 | usage(); 166 | exit(0); 167 | } break; 168 | default: 169 | post("unrecognized option -%c\n", c); 170 | } 171 | } else { 172 | post("expected option, got \"%s\"\n", argv[i]); 173 | ++i; 174 | } 175 | } 176 | 177 | 178 | vm.addBifHelp("Argument Automapping legend:"); 179 | vm.addBifHelp(" a - as is. argument is not automapped."); 180 | vm.addBifHelp(" z - argument is expected to be a signal or scalar, streams are auto mapped."); 181 | vm.addBifHelp(" k - argument is expected to be a scalar, signals and streams are automapped."); 182 | vm.addBifHelp(""); 183 | 184 | AddCoreOps(); 185 | AddMathOps(); 186 | AddStreamOps(); 187 | AddRandomOps(); 188 | AddUGenOps(); 189 | AddMidiOps(); 190 | AddSetOps(); 191 | 192 | 193 | vm.log_file = getenv("SAPF_LOG"); 194 | if (!vm.log_file) { 195 | #ifdef _WIN32 196 | const char* home_dir = getenv("USERPROFILE"); 197 | #else 198 | const char* home_dir = getenv("HOME"); 199 | #endif 200 | char logfilename[PATH_MAX]; 201 | snprintf(logfilename, PATH_MAX, "%s/sapf-log.txt", home_dir); 202 | vm.log_file = strdup(logfilename); 203 | } 204 | 205 | #ifdef SAPF_DISPATCH 206 | __block 207 | #endif 208 | Thread th; 209 | 210 | #ifdef SAPF_MANTA 211 | auto m = manta(); 212 | try { 213 | m->Connect(); 214 | } catch(...) { 215 | } 216 | printf("Manta %s connected.\n", m->IsConnected() ? "is" : "IS NOT"); 217 | 218 | #ifdef SAPF_DISPATCH 219 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 220 | mantaLoop(); 221 | }); 222 | #else 223 | std::thread mantaThread([&]() { 224 | mantaLoop(); 225 | }); 226 | #endif // SAPF_DISPATCH 227 | #endif // SAPF_MANTA 228 | 229 | if (!vm.prelude_file) { 230 | vm.prelude_file = getenv("SAPF_PRELUDE"); 231 | } 232 | if (vm.prelude_file) { 233 | loadFile(th, vm.prelude_file); 234 | } 235 | 236 | #ifdef SAPF_DISPATCH 237 | #ifdef SAPF_COREFOUNDATION 238 | // TODO does dispatch_async + CFRunLoopRun have any benefit over dispatch_sync? 239 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 240 | replLoop(th); 241 | }); 242 | 243 | CFRunLoopRun(); 244 | #else 245 | dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 246 | replLoop(th); 247 | }); 248 | #endif // SAPF_COREFOUNDATION 249 | #else 250 | std::thread replThread([&]() { 251 | replLoop(th); 252 | }); 253 | 254 | replThread.join(); 255 | #endif // SAPF_DISPATCH 256 | 257 | return 0; 258 | } 259 | 260 | -------------------------------------------------------------------------------- /src/makeImage.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "makeImage.hpp" 18 | #include 19 | 20 | struct Bitmap { 21 | // TODO 22 | }; 23 | 24 | Bitmap* createBitmap(int width, int height) { 25 | // TODO 26 | Bitmap *bitmap = (Bitmap *) calloc(1, sizeof(Bitmap)); 27 | return bitmap; 28 | } 29 | 30 | void writeBitmap(Bitmap* bitmap, const char *path) { 31 | // TODO 32 | } 33 | 34 | void freeBitmap(Bitmap* bitmap) { 35 | // TODO 36 | } 37 | -------------------------------------------------------------------------------- /src/makeImage.mm: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #import "makeImage.hpp" 18 | #import 19 | 20 | struct Bitmap 21 | { 22 | NSBitmapImageRep* rep; 23 | unsigned char* data; 24 | int bytesPerRow; 25 | }; 26 | 27 | Bitmap* createBitmap(int width, int height) 28 | { 29 | Bitmap* bitmap = (Bitmap*)calloc(1, sizeof(Bitmap)); 30 | bitmap->rep = 31 | [[NSBitmapImageRep alloc] 32 | initWithBitmapDataPlanes: nullptr 33 | pixelsWide: width 34 | pixelsHigh: height 35 | bitsPerSample: 8 36 | samplesPerPixel: 4 37 | hasAlpha: YES 38 | isPlanar: NO 39 | colorSpaceName: NSCalibratedRGBColorSpace 40 | bitmapFormat: NSAlphaNonpremultipliedBitmapFormat 41 | bytesPerRow: 0 42 | bitsPerPixel: 32 43 | ]; 44 | 45 | bitmap->data = [bitmap->rep bitmapData]; 46 | bitmap->bytesPerRow = (int)[bitmap->rep bytesPerRow]; 47 | return bitmap; 48 | } 49 | 50 | void setPixel(Bitmap* bitmap, int x, int y, int r, int g, int b, int a) 51 | { 52 | size_t index = bitmap->bytesPerRow * y + 4 * x; 53 | unsigned char* data = bitmap->data; 54 | 55 | data[index+0] = r; 56 | data[index+1] = g; 57 | data[index+2] = b; 58 | data[index+3] = a; 59 | } 60 | 61 | void fillRect(Bitmap* bitmap, int x, int y, int width, int height, int r, int g, int b, int a) 62 | { 63 | unsigned char* data = bitmap->data; 64 | for (int j = y; j < y + height; ++j) { 65 | size_t index = bitmap->bytesPerRow * j + 4 * x; 66 | for (int i = x; i < x + width; ++i) { 67 | data[index+0] = r; 68 | data[index+1] = g; 69 | data[index+2] = b; 70 | data[index+3] = a; 71 | index += 4; 72 | } 73 | } 74 | } 75 | 76 | void writeBitmap(Bitmap* bitmap, const char *path) 77 | { 78 | //NSData* data = [bitmap->rep TIFFRepresentation]; 79 | //NSDictionary* properties = @{ NSImageCompressionFactor: @.9 }; 80 | NSDictionary* properties = nullptr; 81 | NSData* data = [bitmap->rep representationUsingType: NSJPEGFileType properties: properties]; 82 | NSString* nsstr = [NSString stringWithUTF8String: path]; 83 | [data writeToFile: nsstr atomically: YES]; 84 | } 85 | 86 | void freeBitmap(Bitmap* bitmap) 87 | { 88 | //[bitmap->rep release]; 89 | free(bitmap); 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/symbol.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "symbol.hpp" 18 | #include "VM.hpp" 19 | #include "Hash.hpp" 20 | #include 21 | #include 22 | 23 | const int kSymbolTableSize = 4096; 24 | const int kSymbolTableMask = kSymbolTableSize - 1; 25 | 26 | // global atomic symbol table 27 | volatile std::atomic sSymbolTable[kSymbolTableSize]; 28 | 29 | 30 | 31 | static String* SymbolTable_lookup(String* list, const char* name, int32_t hash) 32 | { 33 | while (list) { 34 | if (list->hash == hash && strcmp(list->s, name) == 0) 35 | return list; 36 | list = list->nextSymbol; 37 | } 38 | return nullptr; 39 | } 40 | 41 | static String* SymbolTable_lookup(const char* name, int hash) 42 | { 43 | return SymbolTable_lookup(sSymbolTable[hash & kSymbolTableMask].load(), name, hash); 44 | } 45 | 46 | static String* SymbolTable_lookup(const char* name) 47 | { 48 | uintptr_t hash = Hash(name); 49 | return SymbolTable_lookup(name, (int)hash); 50 | } 51 | 52 | P getsym(const char* name) 53 | { 54 | // thread safe 55 | 56 | int32_t hash = Hash(name); 57 | int32_t binIndex = hash & kSymbolTableMask; 58 | volatile std::atomic* bin = &sSymbolTable[binIndex]; 59 | while (1) { 60 | // get the head of the list. 61 | String* head = bin->load(); 62 | // search the list for the symbol 63 | String* existingSymbol = head; 64 | while (existingSymbol) { 65 | if (existingSymbol->hash == hash && strcmp(existingSymbol->s, name) == 0) { 66 | return existingSymbol; 67 | } 68 | existingSymbol = existingSymbol->nextSymbol; 69 | } 70 | String* newSymbol = new String(name, hash, head); 71 | if (bin->compare_exchange_weak(head, newSymbol)) { 72 | newSymbol->retain(); 73 | return newSymbol; 74 | } 75 | delete newSymbol; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /subprojects/doctest.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = doctest-2.4.11 3 | source_url = https://github.com/doctest/doctest/archive/refs/tags/v2.4.11.tar.gz 4 | source_filename = doctest-2.4.11.tar.gz 5 | source_hash = 632ed2c05a7f53fa961381497bf8069093f0d6628c5f26286161fbd32a560186 6 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/doctest_2.4.11-1/doctest-2.4.11.tar.gz 7 | wrapdb_version = 2.4.11-1 8 | 9 | [provide] 10 | dependency_names = doctest 11 | -------------------------------------------------------------------------------- /subprojects/eigen.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = eigen-3.4.0 3 | source_url = https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.bz2 4 | source_filename = eigen-3.4.0.tar.bz2 5 | source_hash = b4c198460eba6f28d34894e3a5710998818515104d6e74e5cc331ce31e46e626 6 | patch_filename = eigen_3.4.0-2_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/eigen_3.4.0-2/get_patch 8 | patch_hash = cb764fd9fec02d94aaa2ec673d473793c0d05da4f4154c142f76ef923ea68178 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/eigen_3.4.0-2/eigen-3.4.0.tar.bz2 10 | wrapdb_version = 3.4.0-2 11 | 12 | [provide] 13 | eigen3 = eigen_dep 14 | -------------------------------------------------------------------------------- /test/doctest.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 18 | #include "doctest.h" -------------------------------------------------------------------------------- /test/helpers/ArrHelpers.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __ArrHelpers_h__ 18 | #define __ArrHelpers_h__ 19 | 20 | #define CHECK_ARR(expected, actual, n) \ 21 | do { \ 22 | LOOP(i,n) { CHECK(actual[i] == doctest::Approx(expected[i]).epsilon(1e-9)); } \ 23 | } while (0) 24 | 25 | #endif -------------------------------------------------------------------------------- /test/test_AsyncAudioFileWriter.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef SAPF_AUDIOTOOLBOX 18 | 19 | #include "doctest.h" 20 | #include "AsyncAudioFileWriter.hpp" 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | using std::string, std::filesystem::exists, std::filesystem::remove, std::filesystem::file_size; 27 | 28 | TEST_CASE("AsyncAudioFileWriter writing tests") { 29 | const string testFileName{"test_async_audio_output.wav"}; 30 | constexpr int sampleRate{44100}; 31 | uint32_t bufferSize, numChannels, numFrames; 32 | 33 | if (exists(testFileName)) { 34 | remove(testFileName); 35 | } 36 | 37 | SUBCASE("small single channel") { 38 | bufferSize = 1024; 39 | numChannels = 1; 40 | numFrames = 1024; 41 | } 42 | 43 | SUBCASE("large single channel") { 44 | bufferSize = 1024; 45 | numChannels = 1; 46 | // value that should exceed the max ring buffer size (1024 * 1024) 47 | numFrames = 1024*1200; 48 | } 49 | 50 | SUBCASE("large stereo channel") { 51 | bufferSize = 1024; 52 | numChannels = 2; 53 | numFrames = 1024*520; 54 | } 55 | 56 | SUBCASE("small 7.1 channel") { 57 | bufferSize = 1024; 58 | numChannels = 8; 59 | numFrames = 1024; 60 | } 61 | 62 | SUBCASE("large 7.1 channel") { 63 | bufferSize = 1024; 64 | numChannels = 8; 65 | numFrames = 1024*140; 66 | } 67 | 68 | SUBCASE("6 channels") { 69 | bufferSize = 1024; 70 | numChannels = 6; 71 | numFrames = 1024*100; 72 | } 73 | 74 | SUBCASE("9 channels") { 75 | bufferSize = 1024; 76 | numChannels = 9; 77 | numFrames = 1024*100; 78 | } 79 | 80 | CAPTURE(numChannels); 81 | CAPTURE(numFrames); 82 | CAPTURE(bufferSize); 83 | 84 | // fill the buffers with a non-repeating pattern that also differs between each channel and keep writing 85 | // until we've written the desired number of frames 86 | { 87 | RtBuffers buffers{numChannels, bufferSize}; 88 | AsyncAudioFileWriter writer(testFileName, sampleRate, numChannels); 89 | for (size_t bufStartFrame = 0; bufStartFrame < numFrames; bufStartFrame+=bufferSize) { 90 | for (size_t frame = 0; frame < bufferSize; frame++) { 91 | for (int channel = 0; channel < numChannels; channel++) { 92 | buffers.data(channel)[frame] = -1.0f + ((frame + bufStartFrame + channel) / numFrames)*2; 93 | } 94 | } 95 | writer.writeAsync(buffers, bufferSize); 96 | } 97 | } 98 | 99 | // ensure file was closed (by trying to open it again) 100 | { 101 | std::ifstream testFile{testFileName, std::ios::binary}; 102 | CHECK(testFile.is_open()); 103 | } 104 | 105 | // check the file contents 106 | SF_INFO sfinfo; 107 | SNDFILE *sndfile = sf_open(testFileName.c_str(), SFM_READ, &sfinfo); 108 | REQUIRE(sndfile != nullptr); 109 | CHECK(sfinfo.channels == numChannels); 110 | CHECK(sfinfo.samplerate == sampleRate); 111 | 112 | std::vector buffer(bufferSize * numChannels); 113 | 114 | sf_count_t framesRead{0}; 115 | sf_count_t totalFrames{0}; 116 | 117 | while ((framesRead = sf_readf_float(sndfile, buffer.data(), bufferSize)) > 0) { 118 | for (size_t frame = 0; frame < framesRead; frame++) { 119 | for (size_t channel = 0; channel < numChannels; channel++) { 120 | float expectedValue = -1.0f + ((frame + totalFrames + channel) / numFrames) * 2; 121 | CHECK(buffer[frame * numChannels + channel] == doctest::Approx(expectedValue).epsilon(1e-5)); 122 | } 123 | } 124 | totalFrames += framesRead; 125 | } 126 | 127 | CHECK(totalFrames == numFrames); 128 | sf_close(sndfile); 129 | 130 | // Clean up the test file 131 | if (exists(testFileName)) { 132 | remove(testFileName); 133 | } 134 | } 135 | 136 | #endif 137 | -------------------------------------------------------------------------------- /test/test_OscilUgens.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "dsp.hpp" 18 | #include "Object.hpp" 19 | #include "VM.hpp" 20 | #include "doctest.h" 21 | #include "ArrHelpers.hpp" 22 | #include "OscilUGens.hpp" 23 | 24 | // non-vectorized version for comparison 25 | void sinosc_calc(Z phase, Z freqmul, int n, Z* out, Z* freq, int freqStride) { 26 | for (int i = 0; i < n; ++i) { 27 | out[i] = phase; 28 | phase += *freq * freqmul; 29 | freq += freqStride; 30 | if (phase >= kTwoPi) phase -= kTwoPi; 31 | else if (phase < 0.) phase += kTwoPi; 32 | } 33 | LOOP(i,n) { out[i] = sin(out[i]); } 34 | } 35 | 36 | TEST_CASE("SinOsc SIMD") { 37 | const int n = 100; 38 | Z out[n]; 39 | Z freq[n]; 40 | Z expected[n]; 41 | Z startFreq = 400; 42 | Z freqmul = 1; 43 | Z iphase; 44 | int freqStride; 45 | Thread th; 46 | th.rate.radiansPerSample = freqmul; 47 | LOOP(i,n) { freq[i] = sin(i/(double)n)*400 + 200; } 48 | 49 | SUBCASE("") { 50 | iphase = 0; 51 | freqStride = 1; 52 | } 53 | 54 | SUBCASE("") { 55 | iphase = .3; 56 | freqStride = 1; 57 | } 58 | 59 | SUBCASE("") { 60 | iphase = 0; 61 | freqStride = 0; 62 | } 63 | 64 | SUBCASE("") { 65 | iphase = .3; 66 | freqStride = 0; 67 | } 68 | CAPTURE(iphase); 69 | CAPTURE(freqStride); 70 | 71 | Z phase = sc_wrap(iphase, 0., 1.) * kTwoPi; 72 | 73 | SinOsc osc(th, startFreq, iphase); 74 | osc.calc(n, out, freq, freqStride); 75 | sinosc_calc(phase, freqmul, n, expected, freq, freqStride); 76 | CHECK_ARR(expected, out, n); 77 | } 78 | 79 | // non-vectorized version for comparison 80 | void sinoscpm_calc(Z freqmul, int n, Z* out, Z* freq, Z* phasemod, int freqStride, int phasemodStride) { 81 | Z phase = 0.; 82 | for (int i = 0; i < n; ++i) { 83 | out[i] = phase + *phasemod * kTwoPi; 84 | phase += *freq * freqmul; 85 | freq += freqStride; 86 | phasemod += phasemodStride; 87 | if (phase >= kTwoPi) phase -= kTwoPi; 88 | else if (phase < 0.) phase += kTwoPi; 89 | } 90 | LOOP(i,n) { out[i] = sin(out[i]); } 91 | } 92 | 93 | TEST_CASE("SinOscPM SIMD") { 94 | const int n = 100; 95 | Z out[n]; 96 | Z freq[n]; 97 | Z phasemod[n]; 98 | Z expected[n]; 99 | Z startFreq = 400; 100 | Z freqmul = 1; 101 | int phasemodStride = 1; 102 | Z iphase; 103 | int freqStride; 104 | Thread th; 105 | th.rate.radiansPerSample = freqmul; 106 | LOOP(i,n) { freq[i] = sin(i/(double)n)*400 + 200; } 107 | LOOP(i,n) { phasemod[i] = cos(i/(double)n); } 108 | 109 | SUBCASE("") { 110 | freqStride = 1; 111 | } 112 | SUBCASE("") { 113 | freqStride = 0; 114 | } 115 | 116 | CAPTURE(freqStride); 117 | 118 | SinOscPM osc(th, startFreq, iphase); 119 | osc.calc(n, out, freq, phasemod, freqStride, phasemodStride); 120 | sinoscpm_calc(freqmul, n, expected, freq, phasemod, freqStride, phasemodStride); 121 | CHECK_ARR(expected, out, n); 122 | } 123 | 124 | static void zeroTable(size_t n, Z* table) 125 | { 126 | memset(table, 0, n * sizeof(Z)); 127 | } 128 | 129 | const int kWaveTableSize = 16384; 130 | const size_t kWaveTableSize2 = kWaveTableSize / 2; 131 | void fillwavetable_calc(int n, Z* amps, int ampStride, Z* phases, int phaseStride, Z smooth, Z* table) { 132 | const Z two_pi = 2. * M_PI; 133 | 134 | Z real[kWaveTableSize2]; 135 | Z imag[kWaveTableSize2]; 136 | 137 | zeroTable(kWaveTableSize2, real); 138 | zeroTable(kWaveTableSize2, imag); 139 | 140 | 141 | Z w = M_PI_2 / n; 142 | for (int i = 0; i < n; ++i) { 143 | Z smoothAmp = smooth == 0. ? 1. : pow(cos(w*i), smooth); 144 | real[i+1] = *amps * smoothAmp; 145 | imag[i+1] = (*phases - .25) * two_pi; 146 | amps += ampStride; 147 | phases += phaseStride; 148 | } 149 | 150 | for(size_t i = 0; i < kWaveTableSize2; i++) { 151 | Z radius = real[i]; 152 | Z angle = imag[i]; 153 | real[i] = radius * cos(angle); 154 | imag[i] = radius * sin(angle); 155 | } 156 | rifft(kWaveTableSize, real, imag, table); 157 | } 158 | 159 | TEST_CASE("fillWaveTable SIMD") { 160 | const int n = 100; 161 | Z amps[n]; 162 | int ampStride = 1; 163 | Z phases[n]; 164 | int phaseStride = 1; 165 | Z smooth = 1; 166 | Z out[kWaveTableSize]; 167 | Z expected[kWaveTableSize]; 168 | LOOP(i,n) { amps[i] = sin(i/(double)n)/2. + .5; } 169 | LOOP(i,n) { phases[i] = cos(i/(double)n); } 170 | 171 | SUBCASE("") { 172 | ampStride = 1; 173 | phaseStride = 1; 174 | } 175 | 176 | initFFT(); 177 | fillWaveTable(n, amps, ampStride, phases, phaseStride, smooth, out); 178 | fillwavetable_calc(n , amps, ampStride, phases, phaseStride, smooth, expected); 179 | CHECK_ARR(expected, out, n); 180 | } -------------------------------------------------------------------------------- /test/test_SndfileSoundFile.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef SAPF_AUDIOTOOLBOX 18 | 19 | #include "doctest.h" 20 | #include "SndfileSoundFile.hpp" 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | using std::string, std::filesystem::exists, std::filesystem::remove, std::filesystem::file_size; 28 | 29 | TEST_CASE("SndfileSoundFile writing tests") { 30 | const string testFileName{"test_async_audio_output.wav"}; 31 | constexpr int sampleRate{44100}; 32 | uint32_t bufferSize, numChannels, numFrames; 33 | 34 | if (exists(testFileName)) { 35 | remove(testFileName); 36 | } 37 | 38 | SUBCASE("small single channel") { 39 | bufferSize = 1024; 40 | numChannels = 1; 41 | numFrames = 1024; 42 | } 43 | 44 | SUBCASE("large single channel") { 45 | bufferSize = 1024; 46 | numChannels = 1; 47 | numFrames = 1024*1200; 48 | } 49 | 50 | SUBCASE("large stereo channel") { 51 | bufferSize = 1024; 52 | numChannels = 2; 53 | numFrames = 1024*520; 54 | } 55 | 56 | SUBCASE("small 7.1 channel") { 57 | bufferSize = 1024; 58 | numChannels = 8; 59 | numFrames = 1024; 60 | } 61 | 62 | SUBCASE("large 7.1 channel") { 63 | bufferSize = 1024; 64 | numChannels = 8; 65 | numFrames = 1024*140; 66 | } 67 | 68 | SUBCASE("6 channels") { 69 | bufferSize = 1024; 70 | numChannels = 6; 71 | numFrames = 1024*100; 72 | } 73 | 74 | SUBCASE("9 channels") { 75 | bufferSize = 1024; 76 | numChannels = 9; 77 | numFrames = 1024*100; 78 | } 79 | 80 | CAPTURE(numChannels); 81 | CAPTURE(numFrames); 82 | CAPTURE(bufferSize); 83 | 84 | // fill the buffer (interleaved) with a non-repeating pattern that also differs between each channel and keep writing 85 | // until we've written the desired number of frames 86 | { 87 | std::valarray buf(0., numChannels * bufferSize); 88 | PortableBuffers bufs{1}; 89 | const auto sndfile = SndfileSoundFile::create(testFileName.c_str(), numChannels, 90 | sampleRate, 0., false); 91 | bufs.setNumChannels(0, numChannels); 92 | bufs.setData(0, &buf[0]); 93 | bufs.setSize(0, bufferSize * sizeof(float)); 94 | 95 | 96 | for (size_t bufStartFrame = 0; bufStartFrame < numFrames; bufStartFrame+=bufferSize) { 97 | for (size_t frame = 0; frame < bufferSize; frame++) { 98 | for (int channel = 0; channel < numChannels; channel++) { 99 | buf[frame * numChannels + channel] = -1.0f + ((frame + bufStartFrame + channel) / numFrames)*2; 100 | } 101 | } 102 | sndfile->write(bufferSize, bufs); 103 | } 104 | } 105 | 106 | // ensure file was closed (by trying to open it again) 107 | { 108 | std::ifstream testFile{testFileName, std::ios::binary}; 109 | CHECK(testFile.is_open()); 110 | } 111 | 112 | // check the file contents 113 | SF_INFO sfinfo; 114 | SNDFILE *sndfile = sf_open(testFileName.c_str(), SFM_READ, &sfinfo); 115 | REQUIRE(sndfile != nullptr); 116 | CHECK(sfinfo.channels == numChannels); 117 | CHECK(sfinfo.samplerate == sampleRate); 118 | 119 | std::vector buffer(bufferSize * numChannels); 120 | 121 | sf_count_t framesRead{0}; 122 | sf_count_t totalFrames{0}; 123 | 124 | while ((framesRead = sf_readf_float(sndfile, buffer.data(), bufferSize)) > 0) { 125 | for (size_t frame = 0; frame < framesRead; frame++) { 126 | for (size_t channel = 0; channel < numChannels; channel++) { 127 | float expectedValue = -1.0f + ((frame + totalFrames + channel) / numFrames) * 2; 128 | CHECK(buffer[frame * numChannels + channel] == doctest::Approx(expectedValue).epsilon(1e-5)); 129 | } 130 | } 131 | totalFrames += framesRead; 132 | } 133 | 134 | CHECK(totalFrames == numFrames); 135 | sf_close(sndfile); 136 | 137 | // Clean up the test file 138 | if (exists(testFileName)) { 139 | remove(testFileName); 140 | } 141 | } 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /test/test_StreamOps.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "Object.hpp" 18 | #include "VM.hpp" 19 | #include "doctest.h" 20 | #include "ArrHelpers.hpp" 21 | #include "ZArr.hpp" 22 | #include "StreamOps.hpp" 23 | 24 | // non-vectorized version for comparison 25 | void hann_calc(Z* out, int n) { 26 | for (int i = 0; i < n; i++) { 27 | out[i] = 0.5 * (1 - cos(2*M_PI*i/n)); 28 | } 29 | } 30 | 31 | TEST_CASE("hanning simd") { 32 | const int n = 100; 33 | Z expected[n]; 34 | Thread th; 35 | th.push(n); 36 | 37 | hann_calc(expected, n); 38 | hanning_(th, nullptr); 39 | P outZList = th.popZList("hanning"); 40 | Z* out = outZList->mArray->z(); 41 | 42 | CHECK_ARR(expected, out, n); 43 | } 44 | 45 | void hamm_calc(Z* out, int n) { 46 | for (int i = 0; i < n; i++) { 47 | out[i] = 0.54 - .46 * cos(2*M_PI*i/n); 48 | } 49 | } 50 | 51 | TEST_CASE("hamming simd") { 52 | const int n = 100; 53 | Z expected[n]; 54 | Thread th; 55 | th.push(n); 56 | 57 | hamm_calc(expected, n); 58 | hamming_(th, nullptr); 59 | P outZList = th.popZList("hamming"); 60 | Z* out = outZList->mArray->z(); 61 | 62 | CHECK_ARR(expected, out, n); 63 | } 64 | 65 | void blackman_calc(Z* out, int n) { 66 | for (int i = 0; i < n; i++) { 67 | out[i] = 0.42 68 | - .5 * cos(2*M_PI*i/n) 69 | + .08 * cos(4*M_PI*i/n); 70 | } 71 | } 72 | 73 | TEST_CASE("blackman simd") { 74 | const int n = 100; 75 | Z expected[n]; 76 | Thread th; 77 | th.push(n); 78 | 79 | blackman_calc(expected, n); 80 | blackman_(th, nullptr); 81 | P outZList = th.popZList("blackman"); 82 | Z* out = outZList->mArray->z(); 83 | 84 | CHECK_ARR(expected, out, n); 85 | } 86 | 87 | void calc_winseg_apply_window(Z* segbuf, Z* window, int n) { 88 | LOOP(i,n) { segbuf[i] = segbuf[i] * window[i]; } 89 | } 90 | 91 | TEST_CASE("WinSegment apply window simd") { 92 | const int n = 100; 93 | Z blackman[n]; 94 | blackman_calc(blackman, n); 95 | Z segbuf_expected[n]; 96 | Z segbuf_actual[n]; 97 | LOOP(i, n) { segbuf_expected[i] = sin(i / n); } 98 | LOOP(i, n) { segbuf_actual[i] = sin(i / n); } 99 | 100 | calc_winseg_apply_window(segbuf_expected, blackman, n); 101 | #ifdef SAPF_ACCELERATE 102 | wseg_apply_window(segbuf_actual, blackman, n); 103 | #else 104 | wseg_apply_window(segbuf_actual, zarr(blackman, 1, n), n); 105 | #endif 106 | 107 | CHECK_ARR(segbuf_expected, segbuf_actual, n); 108 | } 109 | --------------------------------------------------------------------------------