diff --git a/.github/workflows/all.yml b/.github/workflows/all.yml index 803dee09..7b985bce 100644 --- a/.github/workflows/all.yml +++ b/.github/workflows/all.yml @@ -74,12 +74,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel pytest numpy librosa scipy - - - name: Test with pytest - run: | - cd tests - pytest . + pip install setuptools wheel numpy librosa scipy - name: Checkout faustlibraries uses: actions/checkout@v2 @@ -94,7 +89,7 @@ jobs: rm -rf dawdreamer/faustlibraries/.git - name: Install cibuildwheel - run: python -m pip install cibuildwheel>=2.1.1 + run: python -m pip install cibuildwheel>=2.3.1 # # I think the audit is failing because the build links against local LLVM-related things. # # or https://cibuildwheel.readthedocs.io/en/stable/faq/#linux-builds-on-docker @@ -107,7 +102,7 @@ jobs: # CIBW_BUILD_VERBOSITY: 1 # CIBW_REPAIR_WHEEL_COMMAND_LINUX: pip install auditwheel-symbols && (auditwheel repair -w {dest_dir} {wheel} || auditwheel-symbols --manylinux 2010 {wheel}) # CIBW_TEST_REQUIRES: -r test-requirements.txt - # CIBW_TEST_COMMAND: "cd {project}/tests && pytest ." + # CIBW_TEST_COMMAND: "cd {project}/tests && python -m pytest ." # CIBW_ARCHS: auto64 # CIBW_SKIP: "*pp* *p36-* *p37-* *p38-* *p310-*" @@ -149,14 +144,16 @@ jobs: # run: | # echo "Running tests" # cd /DawDreamer/tests - # pytest . + # python -m pytest -s . build-windows: - runs-on: windows-latest - + runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.7, 3.8, 3.9] + include: + - { os: windows-latest, python-version: "3.7", CIBW-BUILD: "cp37*"} + - { os: windows-latest, python-version: "3.8", CIBW-BUILD: "cp38*"} + - { os: windows-latest, python-version: "3.9", CIBW-BUILD: "cp39*"} steps: - uses: actions/checkout@v2 @@ -171,7 +168,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel pytest numpy librosa scipy + pip install setuptools wheel numpy librosa scipy - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.0.3 @@ -189,8 +186,6 @@ jobs: # necessary for setup.py to work. run: | cp -v -r faustlibraries dawdreamer - mkdir ${{env.pythonLocation}}/../share/faust - cp -v faustlibraries/*.lib ${{env.pythonLocation}}/../share/faust - name: Build libsamplerate run: | @@ -204,49 +199,19 @@ jobs: run: | msbuild Builds/VisualStudio2019/DawDreamer.sln /property:Configuration=Release - - name: Test with pytest - run: | - cd tests - pytest . - - name: Install cibuildwheel - run: python -m pip install cibuildwheel>=2.1.1 - - - name: Build wheels 3.7 - if: ${{ matrix.python-version == '3.7' }} - run: | - python -m cibuildwheel --output-dir wheelhouse - env: - PYTHONMAJOR: ${{ matrix.python-version }} - CIBW_BUILD_VERBOSITY: 1 - CIBW_TEST_REQUIRES: -r test-requirements.txt - # CIBW_TEST_COMMAND: "cd /D {project}\\tests && pytest ." - CIBW_ARCHS: auto64 - CIBW_BUILD: "cp37*" - - - name: Build wheels 3.8 - if: ${{ matrix.python-version == '3.8' }} - run: | - python -m cibuildwheel --output-dir wheelhouse - env: - PYTHONMAJOR: ${{ matrix.python-version }} - CIBW_BUILD_VERBOSITY: 1 - CIBW_TEST_REQUIRES: -r test-requirements.txt - # CIBW_TEST_COMMAND: "cd /D {project}\\tests && pytest ." - CIBW_ARCHS: auto64 - CIBW_BUILD: "cp38*" + run: python -m pip install cibuildwheel>=2.3.1 - - name: Build wheels 3.9 - if: ${{ matrix.python-version == '3.9' }} + - name: Build Wheels run: | python -m cibuildwheel --output-dir wheelhouse env: PYTHONMAJOR: ${{ matrix.python-version }} CIBW_BUILD_VERBOSITY: 1 CIBW_TEST_REQUIRES: -r test-requirements.txt - # CIBW_TEST_COMMAND: "cd /D {project}\\tests && pytest ." + CIBW_TEST_COMMAND: "cd /D {project}\\tests && python -m pytest ." CIBW_ARCHS: auto64 - CIBW_BUILD: "cp39*" + CIBW_BUILD: ${{matrix.CIBW-BUILD}} - uses: actions/upload-artifact@v2 with: @@ -270,38 +235,17 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.8, 3.9] - os: [macos-10.15] + include: + - { os: macos-latest, build: cp37-macosx_x86_64, archs-macos: "x86_64", python-version: "3.7", osx-archs: "x86_64", ARCHS: "x86_64", python-major: "3.7m" } + - { os: macos-latest, build: cp38-macosx_universal2, archs-macos: "universal2", python-version: "3.8", osx-archs: "x86_64;arm64", ARCHS: "x86_64 arm64", python-major: "3.8" } + - { os: macos-latest, build: cp39-macosx_universal2, archs-macos: "universal2", python-version: "3.9", osx-archs: "x86_64;arm64", ARCHS: "x86_64 arm64", python-major: "3.9" } + - { os: macos-latest, build: cp310-macosx_universal2, archs-macos: "universal2", python-version: "3.10", osx-archs: "x86_64;arm64", ARCHS: "x86_64 arm64", python-major: "3.10" } + steps: - uses: actions/checkout@v2 with: submodules: true - - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel pytest numpy librosa scipy - - - name: Checkout faustlibraries - uses: actions/checkout@v2 - with: - repository: grame-cncm/faustlibraries - path: faustlibraries - - - name: copy faust libraries - run: | - mkdir -p ${{env.pythonLocation}}/../share/faust - cp $GITHUB_WORKSPACE/faustlibraries/*.lib ${{env.pythonLocation}}/../share/faust/ - mkdir -p ${{env.pythonLocation}}/share/faust - cp $GITHUB_WORKSPACE/faustlibraries/*.lib ${{env.pythonLocation}}/share/faust/ - mkdir -p /usr/local/share/faust/ - cp $GITHUB_WORKSPACE/faustlibraries/*.lib /usr/local/share/faust/ - - name: Get CMake uses: lukka/get-cmake@latest @@ -309,40 +253,15 @@ jobs: run: | cd thirdparty/libsamplerate mkdir build_release - cmake -DCMAKE_BUILD_TYPE=Release -Bbuild_release + cmake -DCMAKE_BUILD_TYPE=Release -Bbuild_release -DCMAKE_OSX_ARCHITECTURES="${{matrix.osx-archs}}" -DLIBSAMPLERATE_EXAMPLES=off -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 make --directory=build_release + cd ../.. - - name: Build MacOS (Release) - # the Projucer refers to PYTHONMAJOR - env: - PYTHONMAJOR: ${{ matrix.python-version }} - run: | - xcodebuild -configuration Release -project Builds/MacOSX/DawDreamer.xcodeproj/ - mv Builds/MacOSX/build/Release/dawdreamer.so.dylib Builds/MacOSX/build/Release/dawdreamer.so - otool -L Builds/MacOSX/build/Release/dawdreamer.so - install_name_tool -change @rpath/libfaust.2.dylib @loader_path/libfaust.2.dylib Builds/MacOSX/build/Release/dawdreamer.so - otool -L Builds/MacOSX/build/Release/dawdreamer.so - - - name: Test with pytest - run: | - cp Builds/MacOSX/build/Release/dawdreamer.so ${{env.pythonLocation}}/bin/dawdreamer.so - cp thirdparty/libfaust/darwin-x64/Release/libfaust.a ${{env.pythonLocation}}/bin/libfaust.2.dylib - cd tests - pytest . - - - name: otool shenanigans - # Note: on an ordinary consumer macOS system, the env var $pythonLocation should be /Library/Frameworks/Python.framework/Versions/3.8 - # This section assumes there is a Unix Executable file at /Library/Frameworks/Python.framework/Versions/3.8/Resources/Python.app/Contents/MacOS/Python - # making @executable_path resolve to /Library/Frameworks/Python.framework/Versions/3.8/Resources/Python.app/Contents/MacOS/ - # However, I was hoping @executable_path would just be /Library/Frameworks/Python.framework/Versions/3.8/bin - # because of the executable at /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 which is itself a reference to - # /Library/Frameworks/Python.framework/Versions/3.8/Python - env: - PYTHONMAJOR: ${{ matrix.python-version }} - run: | - otool -L Builds/MacOSX/build/Release/dawdreamer.so - install_name_tool -change ${{env.pythonLocation}}/lib/libpython${{env.PYTHONMAJOR}}.dylib @executable_path/../../../../Python Builds/MacOSX/build/Release/dawdreamer.so - otool -L Builds/MacOSX/build/Release/dawdreamer.so + - name: Checkout faustlibraries + uses: actions/checkout@v2 + with: + repository: grame-cncm/faustlibraries + path: faustlibraries - name: copy faust libraries # necessary for setup.py to work. @@ -350,38 +269,28 @@ jobs: cp -v -r faustlibraries dawdreamer rm -rf dawdreamer/faustlibraries/.git - - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.1.1 - - - name: Build wheels 3.8 - if: ${{ matrix.python-version == '3.8' }} - run: | - python -m cibuildwheel --output-dir wheelhouse + - name: Build wheels ${{ matrix.python-version }} + uses: pypa/cibuildwheel@v2.3.1 env: - PYTHONMAJOR: ${{ matrix.python-version }} - CIBW_BUILD_VERBOSITY: 1 - CIBW_TEST_REQUIRES: -r test-requirements.txt - # # Might need to compile libfaust in Github action for tests to work - # CIBW_TEST_COMMAND: "cd {project}/tests && pytest ." - CIBW_BUILD: "cp38*" + # note that the Projucer project refers to PYTHONMAJOR and pythonLocation, so they must be set here + PYTHONMAJOR: ${{ matrix.python-major }} MACOSX_DEPLOYMENT_TARGET: 10.15 - CIBW_ARCHS: auto64 - CIBW_ARCHS_MACOS: x86_64 universal2 # Support Apple Silicon - - - name: Build wheels 3.9 - if: ${{ matrix.python-version == '3.9' }} - run: | - python -m cibuildwheel --output-dir wheelhouse - env: - PYTHONMAJOR: ${{ matrix.python-version }} CIBW_BUILD_VERBOSITY: 1 + CIBW_BEFORE_BUILD: | + export pythonLocation=$(python3-config --prefix) + otool -L $pythonLocation/bin/python3 + xcodebuild ARCHS="${{matrix.ARCHS}}" ONLY_ACTIVE_ARCH=NO -configuration Release -project Builds/MacOSX/DawDreamer.xcodeproj/ + mv Builds/MacOSX/build/Release/dawdreamer.so.dylib Builds/MacOSX/build/Release/dawdreamer.so + otool -L Builds/MacOSX/build/Release/dawdreamer.so + install_name_tool -change @rpath/libfaust.2.dylib @loader_path/libfaust.2.dylib Builds/MacOSX/build/Release/dawdreamer.so + otool -L Builds/MacOSX/build/Release/dawdreamer.so CIBW_TEST_REQUIRES: -r test-requirements.txt - # # Might need to compile libfaust in Github action for tests to work - # CIBW_TEST_COMMAND: "cd {project}/tests && pytest ." - CIBW_BUILD: "cp39*" - MACOSX_DEPLOYMENT_TARGET: 10.15 + CIBW_TEST_COMMAND: "cd {project}/tests; python -m pytest -s ." + CIBW_BUILD: ${{matrix.build}} CIBW_ARCHS: auto64 - CIBW_ARCHS_MACOS: x86_64 universal2 # Support Apple Silicon + CIBW_ARCHS_MACOS: ${{matrix.archs-macos}} + # todo: want to test Python 3.10 + CIBW_TEST_SKIP: "*310*" - uses: actions/upload-artifact@v2 with: @@ -402,4 +311,4 @@ jobs: - uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/Builds/LinuxMakefile/Makefile b/Builds/LinuxMakefile/Makefile index 3126cf66..a97b81b1 100644 --- a/Builds/LinuxMakefile/Makefile +++ b/Builds/LinuxMakefile/Makefile @@ -35,7 +35,7 @@ ifeq ($(CONFIG),Debug) TARGET_ARCH := endif - JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DDEBUG=1" "-D_DEBUG=1" "-DPIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==" "-DSAMPLER_SKIP_UI" "-DHAVE_LIBSAMPLERATE" "-DUSE_BUILTIN_FFT" "-DUSE_PTHREADS" "-DBUILD_DAWDREAMER_FAUST" "-DBUILD_DAWDREAMER_RUBBERBAND" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell pkg-config --cflags alsa freetype2 libcurl) -pthread -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK -I../../thirdparty/JUCE/modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../JuceLibraryCode/modules -I/usr/include/python3.9 -I../../thirdparty/pybind11/include -I../../thirdparty/faust/architecture -I../../thirdparty/faust/compiler -I../../thirdparty/libsamplerate/src -I../../thirdparty/libsamplerate/include -I../../thirdparty/rubberband -I../../thirdparty/rubberband/rubberband -I../../thirdparty/rubberband/src/kissfft -I../../thirdparty/rubberband/src -I../../thirdparty/portable_endian/include $(CPPFLAGS) + JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DDEBUG=1" "-D_DEBUG=1" "-DPIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==" "-DSAMPLER_SKIP_UI" "-DHAVE_LIBSAMPLERATE" "-DUSE_BUILTIN_FFT" "-DUSE_PTHREADS" "-DBUILD_DAWDREAMER_FAUST" "-DBUILD_DAWDREAMER_RUBBERBAND" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell pkg-config --cflags alsa freetype2 libcurl) -pthread -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK -I../../thirdparty/JUCE/modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../JuceLibraryCode/modules -I/usr/include/python3.9 -I../../thirdparty/pybind11/include -I../../thirdparty/faust/architecture -I../../thirdparty/faust/compiler -I../../thirdparty/faust/compiler/utils -I../../thirdparty/libsamplerate/src -I../../thirdparty/libsamplerate/include -I../../thirdparty/rubberband -I../../thirdparty/rubberband/rubberband -I../../thirdparty/rubberband/src/kissfft -I../../thirdparty/rubberband/src -I../../thirdparty/portable_endian/include $(CPPFLAGS) JUCE_CPPFLAGS_DYNAMIC_LIBRARY := "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=0" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_RTAS=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=0" "-DJucePlugin_Build_Unity=0" JUCE_CFLAGS_DYNAMIC_LIBRARY := -fPIC -fvisibility=hidden JUCE_LDFLAGS_DYNAMIC_LIBRARY := -shared @@ -58,7 +58,7 @@ ifeq ($(CONFIG),Release) TARGET_ARCH := endif - JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DNDEBUG=1" "-DPIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==" "-DSAMPLER_SKIP_UI" "-DHAVE_LIBSAMPLERATE" "-DUSE_BUILTIN_FFT" "-DUSE_PTHREADS" "-DBUILD_DAWDREAMER_FAUST" "-DBUILD_DAWDREAMER_RUBBERBAND" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell pkg-config --cflags alsa freetype2 libcurl) -pthread -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK -I../../thirdparty/JUCE/modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../JuceLibraryCode/modules -I/usr/include/python3.9 -I../../thirdparty/pybind11/include -I../../thirdparty/faust/architecture -I../../thirdparty/faust/compiler -I../../thirdparty/libsamplerate/src -I../../thirdparty/libsamplerate/include -I../../thirdparty/rubberband -I../../thirdparty/rubberband/rubberband -I../../thirdparty/rubberband/src/kissfft -I../../thirdparty/rubberband/src -I../../thirdparty/portable_endian/include $(CPPFLAGS) + JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DNDEBUG=1" "-DPIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==" "-DSAMPLER_SKIP_UI" "-DHAVE_LIBSAMPLERATE" "-DUSE_BUILTIN_FFT" "-DUSE_PTHREADS" "-DBUILD_DAWDREAMER_FAUST" "-DBUILD_DAWDREAMER_RUBBERBAND" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000" $(shell pkg-config --cflags alsa freetype2 libcurl) -pthread -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK -I../../thirdparty/JUCE/modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../JuceLibraryCode/modules -I/usr/include/python3.9 -I../../thirdparty/pybind11/include -I../../thirdparty/faust/architecture -I../../thirdparty/faust/compiler -I../../thirdparty/faust/compiler/utils -I../../thirdparty/libsamplerate/src -I../../thirdparty/libsamplerate/include -I../../thirdparty/rubberband -I../../thirdparty/rubberband/rubberband -I../../thirdparty/rubberband/src/kissfft -I../../thirdparty/rubberband/src -I../../thirdparty/portable_endian/include $(CPPFLAGS) JUCE_CPPFLAGS_DYNAMIC_LIBRARY := "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=0" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_RTAS=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=0" "-DJucePlugin_Build_Unity=0" JUCE_CFLAGS_DYNAMIC_LIBRARY := -fPIC -fvisibility=hidden JUCE_LDFLAGS_DYNAMIC_LIBRARY := -shared diff --git a/Builds/MacOSX/DawDreamer.xcodeproj/project.pbxproj b/Builds/MacOSX/DawDreamer.xcodeproj/project.pbxproj index 5c0a6ee1..64a0055b 100644 --- a/Builds/MacOSX/DawDreamer.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/DawDreamer.xcodeproj/project.pbxproj @@ -181,7 +181,6 @@ 9ABCCF2DBAA01DE762D8BD60 /* FaustProcessor.h */ /* FaustProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FaustProcessor.h; path = ../../Source/FaustProcessor.h; sourceTree = SOURCE_ROOT; }; 9B17E0333F55A9D2F7686E6B /* Allocators.h */ /* Allocators.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Allocators.h; path = ../../thirdparty/rubberband/src/system/Allocators.h; sourceTree = SOURCE_ROOT; }; 9BDB3F17F6111E9FDF1761B4 /* MPESettingsDataModel.h */ /* MPESettingsDataModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MPESettingsDataModel.h; path = ../../Source/Sampler/Source/DataModels/MPESettingsDataModel.h; sourceTree = SOURCE_ROOT; }; - 9BF0B18CB113A977D0117997 /* RecorderProcessor.h */ /* RecorderProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RecorderProcessor.h; path = ../../Source/RecorderProcessor.h; sourceTree = SOURCE_ROOT; }; 9C07609F0F8527BD049E1A07 /* AudioCurveCalculator.h */ /* AudioCurveCalculator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AudioCurveCalculator.h; path = ../../thirdparty/rubberband/src/dsp/AudioCurveCalculator.h; sourceTree = SOURCE_ROOT; }; 9F37A0F01B91ECF513361AD0 /* MPELegacySettingsComponent.h */ /* MPELegacySettingsComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MPELegacySettingsComponent.h; path = ../../Source/Sampler/Source/Components/MPELegacySettingsComponent.h; sourceTree = SOURCE_ROOT; }; 9FDB1268E8AD800788F80B72 /* include_juce_gui_basics.mm */ /* include_juce_gui_basics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_gui_basics.mm; path = ../../JuceLibraryCode/include_juce_gui_basics.mm; sourceTree = SOURCE_ROOT; }; @@ -580,7 +579,6 @@ 2F962B6F6EA479E59F083A16, D68C9F4F1959369550A30E40, 253FF1803B46DE27BAFE0B1E, - 9BF0B18CB113A977D0117997, ); name = Processors; sourceTree = ""; @@ -670,7 +668,7 @@ 4A73310BAAAEBD94149F5709 = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1320; ORGANIZATIONNAME = ""; }; buildConfigurationList = 2DB318F4D74C3E6033F2C720; @@ -764,7 +762,6 @@ 0B1D4C0B3A54E25448D24D59 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(NATIVE_ARCH_ACTUAL)"; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_LINK_OBJC_RUNTIME = NO; @@ -772,6 +769,7 @@ COMBINE_HIDPI_IMAGES = YES; CONFIGURATION_BUILD_DIR = "$(PROJECT_DIR)/build/$(CONFIGURATION)"; COPY_PHASE_STRIP = NO; + EXCLUDED_ARCHS = "i386 arm64e"; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -807,6 +805,7 @@ "/usr/local/include", "$(SRCROOT)/../../thirdparty/faust/architecture", "$(SRCROOT)/../../thirdparty/faust/compiler", + "$(SRCROOT)/../../thirdparty/faust/compiler/utils", "$(SRCROOT)/../../thirdparty/libsamplerate/src", "$(SRCROOT)/../../thirdparty/libsamplerate/include", "$(SRCROOT)/../../thirdparty/rubberband", @@ -824,14 +823,15 @@ "\"../../thirdparty/libsamplerate/build_debug/src\"", ); MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../thirdparty/JUCE/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../JuceLibraryCode $(SRCROOT)/../../JuceLibraryCode/modules $(pythonLocation)/include/python$(PYTHONMAJOR) $(SRCROOT)/../../thirdparty/pybind11/include /usr/local/include $(SRCROOT)/../../thirdparty/faust/architecture $(SRCROOT)/../../thirdparty/faust/compiler $(SRCROOT)/../../thirdparty/libsamplerate/src $(SRCROOT)/../../thirdparty/libsamplerate/include $(SRCROOT)/../../thirdparty/rubberband $(SRCROOT)/../../thirdparty/rubberband/rubberband $(SRCROOT)/../../thirdparty/rubberband/src/kissfft $(SRCROOT)/../../thirdparty/rubberband/src $(SRCROOT)/../../thirdparty/portable_endian/include"; + MTL_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../thirdparty/JUCE/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../JuceLibraryCode $(SRCROOT)/../../JuceLibraryCode/modules $(pythonLocation)/include/python$(PYTHONMAJOR) $(SRCROOT)/../../thirdparty/pybind11/include /usr/local/include $(SRCROOT)/../../thirdparty/faust/architecture $(SRCROOT)/../../thirdparty/faust/compiler $(SRCROOT)/../../thirdparty/faust/compiler/utils $(SRCROOT)/../../thirdparty/libsamplerate/src $(SRCROOT)/../../thirdparty/libsamplerate/include $(SRCROOT)/../../thirdparty/rubberband $(SRCROOT)/../../thirdparty/rubberband/rubberband $(SRCROOT)/../../thirdparty/rubberband/src/kissfft $(SRCROOT)/../../thirdparty/rubberband/src $(SRCROOT)/../../thirdparty/portable_endian/include"; + OTHER_CFLAGS = "-fPIC"; OTHER_CPLUSPLUSFLAGS = "-fPIC"; - OTHER_LDFLAGS = "-lsamplerate -lfaust -shared -lpython$(PYTHONMAJOR)"; - PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.DawDreamer; + OTHER_LDFLAGS = "-lsamplerate -lfaust -shared -Wl,-undefined,dynamic_lookup"; + PRODUCT_BUNDLE_IDENTIFIER = design.dirt.DawDreamer; PRODUCT_NAME = "dawdreamer.so"; USE_HEADERMAP = NO; VALIDATE_WORKSPACE_SKIPPED_SDK_FRAMEWORKS = OpenGL; - VALID_ARCHS = "i386 x86_64 arm64 arm64e"; + VALID_ARCHS = "arm64 x86_64"; }; name = Debug; }; @@ -876,6 +876,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "dawdreamer.so"; SDKROOT = macosx; WARNING_CFLAGS = "-Wreorder"; @@ -886,7 +887,6 @@ 7D16F0F72A8F9DD6F7543FE8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(NATIVE_ARCH_ACTUAL)"; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_LINK_OBJC_RUNTIME = NO; @@ -894,6 +894,7 @@ COMBINE_HIDPI_IMAGES = YES; CONFIGURATION_BUILD_DIR = "$(PROJECT_DIR)/build/$(CONFIGURATION)"; DEAD_CODE_STRIPPING = YES; + EXCLUDED_ARCHS = "i386 arm64e"; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_OPTIMIZATION_LEVEL = 3; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -929,6 +930,7 @@ "/usr/local/include", "$(SRCROOT)/../../thirdparty/faust/architecture", "$(SRCROOT)/../../thirdparty/faust/compiler", + "$(SRCROOT)/../../thirdparty/faust/compiler/utils", "$(SRCROOT)/../../thirdparty/libsamplerate/src", "$(SRCROOT)/../../thirdparty/libsamplerate/include", "$(SRCROOT)/../../thirdparty/rubberband", @@ -947,14 +949,15 @@ ); LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../thirdparty/JUCE/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../JuceLibraryCode $(SRCROOT)/../../JuceLibraryCode/modules $(pythonLocation)/include/python$(PYTHONMAJOR) $(SRCROOT)/../../thirdparty/pybind11/include /usr/local/include $(SRCROOT)/../../thirdparty/faust/architecture $(SRCROOT)/../../thirdparty/faust/compiler $(SRCROOT)/../../thirdparty/libsamplerate/src $(SRCROOT)/../../thirdparty/libsamplerate/include $(SRCROOT)/../../thirdparty/rubberband $(SRCROOT)/../../thirdparty/rubberband/rubberband $(SRCROOT)/../../thirdparty/rubberband/src/kissfft $(SRCROOT)/../../thirdparty/rubberband/src $(SRCROOT)/../../thirdparty/portable_endian/include"; + MTL_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../thirdparty/JUCE/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../JuceLibraryCode $(SRCROOT)/../../JuceLibraryCode/modules $(pythonLocation)/include/python$(PYTHONMAJOR) $(SRCROOT)/../../thirdparty/pybind11/include /usr/local/include $(SRCROOT)/../../thirdparty/faust/architecture $(SRCROOT)/../../thirdparty/faust/compiler $(SRCROOT)/../../thirdparty/faust/compiler/utils $(SRCROOT)/../../thirdparty/libsamplerate/src $(SRCROOT)/../../thirdparty/libsamplerate/include $(SRCROOT)/../../thirdparty/rubberband $(SRCROOT)/../../thirdparty/rubberband/rubberband $(SRCROOT)/../../thirdparty/rubberband/src/kissfft $(SRCROOT)/../../thirdparty/rubberband/src $(SRCROOT)/../../thirdparty/portable_endian/include"; + OTHER_CFLAGS = "-fPIC"; OTHER_CPLUSPLUSFLAGS = "-fPIC"; - OTHER_LDFLAGS = "-lsamplerate -lfaust -shared -lpython$(PYTHONMAJOR)"; - PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.DawDreamer; + OTHER_LDFLAGS = "-lsamplerate -lfaust -shared -Wl,-undefined,dynamic_lookup"; + PRODUCT_BUNDLE_IDENTIFIER = design.dirt.DawDreamer; PRODUCT_NAME = "dawdreamer.so"; USE_HEADERMAP = NO; VALIDATE_WORKSPACE_SKIPPED_SDK_FRAMEWORKS = OpenGL; - VALID_ARCHS = "i386 x86_64 arm64 arm64e"; + VALID_ARCHS = "arm64 x86_64"; }; name = Release; }; diff --git a/Builds/MacOSX/DawDreamer.xcodeproj/project.xcworkspace/xcuserdata/davidbraun.xcuserdatad/UserInterfaceState.xcuserstate b/Builds/MacOSX/DawDreamer.xcodeproj/project.xcworkspace/xcuserdata/davidbraun.xcuserdatad/UserInterfaceState.xcuserstate index 0cb47c84..ff48f421 100644 Binary files a/Builds/MacOSX/DawDreamer.xcodeproj/project.xcworkspace/xcuserdata/davidbraun.xcuserdatad/UserInterfaceState.xcuserstate and b/Builds/MacOSX/DawDreamer.xcodeproj/project.xcworkspace/xcuserdata/davidbraun.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Builds/VisualStudio2019/DawDreamer.sln b/Builds/VisualStudio2019/DawDreamer.sln index b303ab63..8a25fbc8 100644 --- a/Builds/VisualStudio2019/DawDreamer.sln +++ b/Builds/VisualStudio2019/DawDreamer.sln @@ -1,5 +1,6 @@ + Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2019 +# Visual Studio Version 16 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DawDreamer - Dynamic Library", "DawDreamer_DynamicLibrary.vcxproj", "{3319B6CA-AD36-2186-0C27-DF21B3E5CAC8}" EndProject diff --git a/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj b/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj index 6a5c9277..0a28d96a 100644 --- a/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj +++ b/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj @@ -65,7 +65,7 @@ Disabled ProgramDatabase - ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\thirdparty\JUCE\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;$(pythonLocation)\include;..\..\thirdparty\pybind11\include;..\..\thirdparty\faust\architecture;..\..\thirdparty\faust\compiler;..\..\thirdparty\libsamplerate\src;..\..\thirdparty\libsamplerate\include;..\..\thirdparty\rubberband;..\..\thirdparty\rubberband\rubberband;..\..\thirdparty\rubberband\src\kissfft;..\..\thirdparty\rubberband\src;..\..\thirdparty\portable_endian\include;%(AdditionalIncludeDirectories) + ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\thirdparty\JUCE\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;$(pythonLocation)\include;..\..\thirdparty\pybind11\include;..\..\thirdparty\faust\architecture;..\..\thirdparty\faust\compiler;..\..\thirdparty\faust\compiler\utils;..\..\thirdparty\libsamplerate\src;..\..\thirdparty\libsamplerate\include;..\..\thirdparty\rubberband;..\..\thirdparty\rubberband\rubberband;..\..\thirdparty\rubberband\src\kissfft;..\..\thirdparty\rubberband\src;..\..\thirdparty\portable_endian\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;PIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==;SAMPLER_SKIP_UI;_WIN32;__SSE__;__SSE2__;BUILD_DAWDREAMER_FAUST;BUILD_DAWDREAMER_RUBBERBAND;NOMINMAX;HAVE_LIBSAMPLERATE;HAVE_KISSFFT;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;_LIB;%(PreprocessorDefinitions) MultiThreadedDebugDLL true @@ -115,7 +115,7 @@ copy "..\..\thirdparty\libfaust\win-x64\Debug\bin\faust.dll" "$(p Full - ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\thirdparty\JUCE\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;$(pythonLocation)\include;..\..\thirdparty\pybind11\include;..\..\thirdparty\faust\architecture;..\..\thirdparty\faust\compiler;..\..\thirdparty\libsamplerate\src;..\..\thirdparty\libsamplerate\include;..\..\thirdparty\rubberband;..\..\thirdparty\rubberband\rubberband;..\..\thirdparty\rubberband\src\kissfft;..\..\thirdparty\rubberband\src;..\..\thirdparty\portable_endian\include;%(AdditionalIncludeDirectories) + ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\thirdparty\JUCE\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;$(pythonLocation)\include;..\..\thirdparty\pybind11\include;..\..\thirdparty\faust\architecture;..\..\thirdparty\faust\compiler;..\..\thirdparty\faust\compiler\utils;..\..\thirdparty\libsamplerate\src;..\..\thirdparty\libsamplerate\include;..\..\thirdparty\rubberband;..\..\thirdparty\rubberband\rubberband;..\..\thirdparty\rubberband\src\kissfft;..\..\thirdparty\rubberband\src;..\..\thirdparty\portable_endian\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;PIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==;SAMPLER_SKIP_UI;_WIN32;__SSE__;__SSE2__;BUILD_DAWDREAMER_FAUST;BUILD_DAWDREAMER_RUBBERBAND;NOMINMAX;HAVE_LIBSAMPLERATE;HAVE_KISSFFT;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=1.0.0;JUCE_APP_VERSION_HEX=0x10000;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;_LIB;%(PreprocessorDefinitions) MultiThreadedDLL true @@ -2356,7 +2356,6 @@ copy "..\..\thirdparty\libfaust\win-x64\Release\bin\faust.dll" "$ - diff --git a/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj.filters b/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj.filters index 6567954a..e52b812d 100644 --- a/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj.filters +++ b/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj.filters @@ -3099,9 +3099,6 @@ DawDreamer\Processors - - DawDreamer\Processors - DawDreamer diff --git a/CITATION.cff b/CITATION.cff index b75c466e..05fe6df8 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,5 +4,8 @@ authors: - family-names: "Braun" given-names: "David" orcid: "https://orcid.org/0000-0001-8866-0756" -title: "DawDreamer" -url: "https://github.com/DBraun/DawDreamer" \ No newline at end of file +title: "DawDreamer: Bridging the Gap Between Digital Audio Workstations and Python Interfaces" +journal: "Late-Breaking/Demo ISMIR" +volume: 2021 +year: 2021 +url: "https://archives.ismir.net/ismir2021/latebreaking/000001.pdf" \ No newline at end of file diff --git a/DawDreamer.jucer b/DawDreamer.jucer index 181a2e0c..64d01f42 100644 --- a/DawDreamer.jucer +++ b/DawDreamer.jucer @@ -1,7 +1,7 @@ - + extraLinkerFlags="-shared -Wl,-undefined,dynamic_lookup" externalLibraries="samplerate faust" + extraDefs="SAMPLER_SKIP_UI HAVE_LIBSAMPLERATE HAVE_VDSP USE_PTHREADS BUILD_DAWDREAMER_FAUST BUILD_DAWDREAMER_RUBBERBAND" + xcodeValidArchs="arm64,x86_64"> @@ -278,10 +277,10 @@ externalLibraries="samplerate faust LLVM-11"> @@ -306,10 +305,10 @@ extraDefs="SAMPLER_SKIP_UI WIN32 _WIN32 _WINDOWS __SSE__ __SSE2__ BUILD_DAWDREAMER_FAUST BUILD_DAWDREAMER_RUBBERBAND NOMINMAX HAVE_LIBSAMPLERATE HAVE_KISSFFT" externalLibraries="faust.lib samplerate.lib"> - - diff --git a/JuceLibraryCode/AppConfig.h b/JuceLibraryCode/AppConfig.h index a4c4210d..3be304b9 100644 --- a/JuceLibraryCode/AppConfig.h +++ b/JuceLibraryCode/AppConfig.h @@ -43,7 +43,7 @@ #define JUCE_USE_DARK_SPLASH_SCREEN 1 -#define JUCE_PROJUCER_VERSION 0x60100 +#define JUCE_PROJUCER_VERSION 0x60104 //============================================================================== #define JUCE_MODULE_AVAILABLE_juce_audio_basics 1 diff --git a/README.md b/README.md index 505b7d9e..8797282e 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,12 @@ # DawDreamer +Read the [introduction](https://arxiv.org/abs/2111.09931) to DawDreamer, which was presented as a Late-Breaking Demo at the [2021 ISMIR Conference](https://ismir2021.ismir.net/lbd/). + DawDreamer is an audio-processing Python framework supporting core [DAW](https://en.wikipedia.org/wiki/Digital_audio_workstation) features: -* Composing graphs of audio processors +* Composing graphs of multi-channel audio processors * Audio playback -* VST instruments -* VST effects +* VST instruments and effects * [FAUST](http://faust.grame.fr/) effects and [polyphonic instruments](https://faustdoc.grame.fr/manual/midi/#standard-polyphony-parameters) * Time-stretching and looping according to Ableton Live warp markers * Pitch-warping @@ -44,7 +45,7 @@ from scipy.io import wavfile import librosa SAMPLE_RATE = 44100 -BUFFER_SIZE = 128 # For speed when not using automation, choose a larger buffer such as 512. +BUFFER_SIZE = 128 # Parameters will undergo automation at this block size. SYNTH_PLUGIN = "C:/path/to/synth.dll" # for instruments, DLLs work. SYNTH_PRESET = "C:/path/to/preset.fxp" REVERB_PLUGIN = "C:/path/to/reverb.dll" # for effects, both DLLs and .vst3 files work @@ -68,6 +69,7 @@ engine.set_bpm(120.) # default is 120. DURATION = 10 # How many seconds we want to render. +# Load audio into a numpy array shaped (Number Channels, Number Samples) vocals = load_audio_file(VOCALS_PATH, duration=10.) piano = load_audio_file(PIANO_PATH, duration=10.) @@ -86,6 +88,10 @@ print(synth.get_parameter_name(1)) # For Serum, returns "A Pan" (the panning of # The Plugin Processor can set automation according to a parameter index. synth.set_automation(1, make_sine(.5, DURATION)) # 0.5 Hz sine wave. +# For any processor type, we can get the number of inputs and outputs +print("synth num inputs: ", synth.get_num_input_channels()) +print("synth num outputs: ", synth.get_num_output_channels()) + # The sampler processor works like the plugin processor. # Provide audio for the sample, and then provide MIDI. # The note value affects the pitch and playback speed of the sample. @@ -130,7 +136,7 @@ engine.load_graph(graph) engine.render(DURATION) # Render 10 seconds audio. # Return the audio from the graph's last processor, even if its recording wasn't enabled. -# The shape will be numpy.ndarray shaped (2, NUM_SAMPLES) +# The shape will be numpy.ndarray shaped (chans, samples) audio = engine.get_audio() wavfile.write('my_song.wav', SAMPLE_RATE, audio.transpose()) # don't forget to transpose! @@ -156,7 +162,7 @@ engine.render(DURATION) # render audio again! ### Using FAUST processors -Let's start by looking at FAUST DSP files, which end in `.dsp`. For convenience, the standard library is always imported, so you don't need to `import("stdfaust.lib");` All code must result in a `process` with 2 outputs and an even number of inputs. Here's an example using a demo stereo reverb: +Let's start by looking at FAUST DSP files, which end in `.dsp`. For convenience, the standard library is always imported, so you don't need to `import("stdfaust.lib");` Here's an example using a demo stereo reverb: #### **faust_reverb.dsp:** ```dsp @@ -232,7 +238,7 @@ Faust code in DawDreamer can use the [soundfile](https://faustdoc.grame.fr/manua **soundfile_test.py** ```python -# suppose audio1, audio2, and audio3 are np.array shaped (2, N samples) +# suppose audio1, audio2, and audio3 are np.array shaped (Channels, Samples) soundfiles = { 'mySound': [audio1, audio2, audio3] } diff --git a/Source/AddProcessor.h b/Source/AddProcessor.h index fd3904aa..b114888c 100644 --- a/Source/AddProcessor.h +++ b/Source/AddProcessor.h @@ -6,6 +6,7 @@ class AddProcessor : public ProcessorBase { public: AddProcessor(std::string newUniqueName, std::vector gainLevels) : ProcessorBase(newUniqueName), myGainLevels{ gainLevels } { + setMainBusInputsAndOutputs(gainLevels.size()*2, gainLevels.size()*2); } void prepareToPlay(double, int) { @@ -51,7 +52,10 @@ class AddProcessor : public ProcessorBase const juce::String getName() { return "AddProcessor"; }; - void setGainLevels(const std::vector gainLevels) { myGainLevels = gainLevels; } + void setGainLevels(const std::vector gainLevels) { + myGainLevels = gainLevels; + setMainBusInputsAndOutputs(gainLevels.size()*2, gainLevels.size()*2); + } const std::vector getGainLevels() { return myGainLevels; } @@ -66,4 +70,4 @@ class AddProcessor : public ProcessorBase return myGainLevels.at(index); } -}; \ No newline at end of file +}; diff --git a/Source/AllProcessors.h b/Source/AllProcessors.h index 27d1af24..c11d03ac 100644 --- a/Source/AllProcessors.h +++ b/Source/AllProcessors.h @@ -10,7 +10,6 @@ #include "PlaybackProcessor.h" #include "PlaybackWarpProcessor.h" #include "PluginProcessor.h" -#include "RecorderProcessor.h" #include "ReverbProcessor.h" #include "PannerProcessor.h" -#include "SamplerProcessor.h" \ No newline at end of file +#include "SamplerProcessor.h" diff --git a/Source/CompressorProcessor.h b/Source/CompressorProcessor.h index 0e3d218a..079b6114 100644 --- a/Source/CompressorProcessor.h +++ b/Source/CompressorProcessor.h @@ -19,6 +19,7 @@ class CompressorProcessor : public ProcessorBase myRelease = myParameters.getRawParameterValue("release"); updateParameters(); + setMainBusInputsAndOutputs(2, 2); } void prepareToPlay(double sampleRate, int samplesPerBlock) { @@ -92,4 +93,4 @@ class CompressorProcessor : public ProcessorBase return params; } -}; \ No newline at end of file +}; diff --git a/Source/DelayProcessor.h b/Source/DelayProcessor.h index 0330e309..91324b04 100644 --- a/Source/DelayProcessor.h +++ b/Source/DelayProcessor.h @@ -13,7 +13,7 @@ class DelayProcessor : public ProcessorBase myDelaySize = myParameters.getRawParameterValue("delay"); myWetLevel = myParameters.getRawParameterValue("wet_level"); - + setMainBusInputsAndOutputs(2, 2); } void prepareToPlay(double sampleRate, int samplesPerBlock) { @@ -111,4 +111,4 @@ class DelayProcessor : public ProcessorBase return params; } -}; \ No newline at end of file +}; diff --git a/Source/FaustProcessor.cpp b/Source/FaustProcessor.cpp index 926301ea..51771be4 100644 --- a/Source/FaustProcessor.cpp +++ b/Source/FaustProcessor.cpp @@ -37,11 +37,6 @@ FaustProcessor::FaustProcessor(std::string newUniqueName, double sampleRate, int m_numOutputChannels = 0; // auto import m_autoImport = "// FaustProcessor (DawDreamer) auto import:\nimport(\"stdfaust.lib\");\n"; - -#ifdef WIN32 - // At the start of every process - guiUpdateMutex = CreateMutex(NULL, FALSE, "Faust gui::update Mutex"); // todo: enable mutex on linux and macOS -#endif } FaustProcessor::~FaustProcessor() { @@ -49,6 +44,11 @@ FaustProcessor::~FaustProcessor() { deleteAllDSPFactories(); } +bool +FaustProcessor::canApplyBusesLayout(const juce::AudioProcessor::BusesLayout& layout) { + return (layout.getMainInputChannels() == m_numInputChannels) && (layout.getMainOutputChannels() == m_numOutputChannels); +} + void FaustProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { @@ -159,28 +159,13 @@ FaustProcessor::automateParameters() { // several voices might share the same parameters in a group. // Therefore we have to call updateAllGuis to update all dependent parameters. if (m_nvoices > 0 && m_groupVoices) { -#ifdef WIN32 // When you want to access shared memory: - DWORD dwWaitResult = WaitForSingleObject(guiUpdateMutex, INFINITE); - - if (dwWaitResult == WAIT_OBJECT_0 || dwWaitResult == WAIT_ABANDONED) - { - if (dwWaitResult == WAIT_ABANDONED) - { - // todo: - // Shared memory is maybe in inconsistent state because other program - // crashed while holding the mutex. Check the memory for consistency - } - - // Have Faust update all GUIs. - GUI::updateAllGuis(); - - // After this line other processes can access shared memory - ReleaseMutex(guiUpdateMutex); - } -#else - GUI::updateAllGuis(); // todo: enable mutex on linux and macOS -#endif + if (guiUpdateMutex.Lock()) { + // Have Faust update all GUIs. + GUI::updateAllGuis(); + + guiUpdateMutex.Unlock(); + } } } @@ -295,16 +280,28 @@ FaustProcessor::compile() auto theCode = m_autoImport + "\n" + m_code; std::string m_errorString; - + +#if __APPLE__ + // on macOS, specifying the target like this helps us handle LLVM on both x86_64 and arm64. + // Crucially, LLVM must have been compiled separately for each architecture. + // A different libfaust must have also been compiled for the corresponding LLVM architecture + // and the `LLVM_BUILD_UNIVERSAL` preprocessor must have been set while building libfaust. + // Then the two libfaust.2.dylib can get combined with + // `lipo x86_64.libfaust.2.dylib arm64.libfaust.2.dylib -create -output libfaust.2.dylib` + auto target = getDSPMachineTarget(); +#else + auto target = std::string(""); +#endif + // create new factory bool is_polyphonic = m_nvoices > 0; if (is_polyphonic) { m_poly_factory = createPolyDSPFactoryFromString("DawDreamer", theCode, - argc, argv, "", m_errorString, optimize); + argc, argv, target.c_str(), m_errorString, optimize); } else { m_factory = createDSPFactoryFromString("DawDreamer", theCode, - argc, argv, "", m_errorString, optimize); + argc, argv, target.c_str(), m_errorString, optimize); } // check for error @@ -338,7 +335,7 @@ FaustProcessor::compile() // (false, true) works m_dsp_poly = m_poly_factory->createPolyDSPInstance(m_nvoices, true, m_groupVoices); if (!m_dsp_poly) { - std::cerr << "FaustProcessor::compile(): Cannot create instance." << std::endl; + std::cerr << "FaustProcessor::compile(): Cannot create Poly DSP instance." << std::endl; FAUSTPROCESSOR_FAIL_COMPILE } } @@ -346,7 +343,7 @@ FaustProcessor::compile() // create DSP instance m_dsp = m_factory->createDSPInstance(); if (!m_dsp) { - std::cerr << "FaustProcessor::compile(): Cannot create instance." << std::endl; + std::cerr << "FaustProcessor::compile(): Cannot create DSP instance." << std::endl; FAUSTPROCESSOR_FAIL_COMPILE } } @@ -357,14 +354,11 @@ FaustProcessor::compile() int inputs = theDsp->getNumInputs(); int outputs = theDsp->getNumOutputs(); - if (outputs != 2) { - std::cerr << "FaustProcessor::compile(): FaustProcessor must have DSP code with 2 output channels but was compiled for " << m_numOutputChannels << "." << std::endl; - FAUSTPROCESSOR_FAIL_COMPILE - } - m_numInputChannels = inputs; m_numOutputChannels = outputs; - + + setMainBusInputsAndOutputs(inputs, outputs); + // make new UI if (is_polyphonic) { @@ -809,4 +803,4 @@ FaustProcessor::setSoundfiles(py::dict d) { } } -#endif \ No newline at end of file +#endif diff --git a/Source/FaustProcessor.h b/Source/FaustProcessor.h index d041b41d..ae235bcd 100644 --- a/Source/FaustProcessor.h +++ b/Source/FaustProcessor.h @@ -12,6 +12,7 @@ #include "faust/gui/SoundUI.h" #include "faust/midi/rt-midi.h" +#include "TMutex.h" #include #include @@ -74,6 +75,7 @@ class MySoundUI : public SoundUI { for (int chan = 0; chan < buffer.getNumChannels(); chan++) { for (int sample = 0; sample < numSamples; sample++) { // todo: don't assume float + // todo: use memcpy or similar to be faster static_cast(soundfile->fBuffers)[chan][offset + sample] = buffer.getSample(chan, sample); } } @@ -102,6 +104,7 @@ class FaustProcessor : public ProcessorBase FaustProcessor(std::string newUniqueName, double sampleRate, int samplesPerBlock); ~FaustProcessor(); + bool canApplyBusesLayout(const juce::AudioProcessor::BusesLayout& layout); void prepareToPlay(double sampleRate, int samplesPerBlock); void processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer& midiBuffer); @@ -194,10 +197,8 @@ class FaustProcessor : public ProcessorBase std::map m_map_juceIndex_to_faustIndex; std::map m_map_juceIndex_to_parAddress; - -#ifdef WIN32 - HANDLE guiUpdateMutex; // todo: enable mutex on linux and macOS -#endif + + TMutex guiUpdateMutex; }; -#endif \ No newline at end of file +#endif diff --git a/Source/FilterProcessor.cpp b/Source/FilterProcessor.cpp index 5b86c12e..bb4eaca1 100644 --- a/Source/FilterProcessor.cpp +++ b/Source/FilterProcessor.cpp @@ -13,6 +13,7 @@ FilterProcessor::FilterProcessor(std::string newUniqueName, std::string mode, fl myGain = myParameters.getRawParameterValue("gain"); setMode(mode); + setMainBusInputsAndOutputs(2, 2); } const juce::String FilterProcessor::getName() { return "FilterProcessor"; } diff --git a/Source/OscillatorProcessor.h b/Source/OscillatorProcessor.h index afe9ed7e..5c2f51ea 100644 --- a/Source/OscillatorProcessor.h +++ b/Source/OscillatorProcessor.h @@ -11,6 +11,7 @@ class OscillatorProcessor : public ProcessorBase myFreq = freq; myOscillator.setFrequency(freq); myOscillator.initialise([](float x) { return std::sin(x); }); + setMainBusInputsAndOutputs(0, 2); } OscillatorProcessor():ProcessorBase( "osc1" ) @@ -18,6 +19,7 @@ class OscillatorProcessor : public ProcessorBase myFreq = 440.f; myOscillator.setFrequency(myFreq); myOscillator.initialise([](float x) { return std::sin(x); }); + setMainBusInputsAndOutputs(0, 2); } void @@ -48,4 +50,4 @@ class OscillatorProcessor : public ProcessorBase private: juce::dsp::Oscillator myOscillator; -}; \ No newline at end of file +}; diff --git a/Source/PannerProcessor.h b/Source/PannerProcessor.h index 14bbc974..1228a0f6 100644 --- a/Source/PannerProcessor.h +++ b/Source/PannerProcessor.h @@ -12,7 +12,7 @@ class PannerProcessor : public ProcessorBase setPan(panVal); myPan = myParameters.getRawParameterValue("pan"); - + setMainBusInputsAndOutputs(2, 2); } void prepareToPlay(double sampleRate, int samplesPerBlock) { @@ -132,4 +132,4 @@ class PannerProcessor : public ProcessorBase return juce::dsp::PannerRule::balanced; } } -}; \ No newline at end of file +}; diff --git a/Source/PlaybackProcessor.h b/Source/PlaybackProcessor.h index 01b1c0a3..cb412bd4 100644 --- a/Source/PlaybackProcessor.h +++ b/Source/PlaybackProcessor.h @@ -8,12 +8,14 @@ class PlaybackProcessor : public ProcessorBase public: PlaybackProcessor(std::string newUniqueName, std::vector> inputData) : ProcessorBase{ newUniqueName } { - int numChans = inputData.size(); + int numChannels = inputData.size(); int numSamples = inputData.at(0).size(); - myPlaybackData.setSize(numChans, numSamples); - for (int chan = 0; chan < 2; chan++) { - myPlaybackData.copyFrom(chan, 0, inputData.at(std::min(numChans, chan)).data(), numSamples); + myPlaybackData.setSize(numChannels, numSamples); + for (int chan = 0; chan < numChannels; chan++) { + myPlaybackData.copyFrom(chan, 0, inputData.at(chan).data(), numSamples); } + + setMainBusInputsAndOutputs(0, numChannels); } PlaybackProcessor(std::string newUniqueName, py::array_t input) : ProcessorBase{ newUniqueName } @@ -25,7 +27,7 @@ class PlaybackProcessor : public ProcessorBase prepareToPlay(double, int) {} void - processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer& midiBuffer) + processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer& midiBuffer) { AudioPlayHead::CurrentPositionInfo posInfo; getPlayHead()->getCurrentPosition(posInfo); @@ -33,7 +35,8 @@ class PlaybackProcessor : public ProcessorBase buffer.applyGain(0.); int numSamples = std::min(buffer.getNumSamples(), myPlaybackData.getNumSamples() - (int)posInfo.timeInSamples); - for (int chan = 0; chan < buffer.getNumChannels() && numSamples; chan++) { + int numChannels = buffer.getNumChannels(); + for (int chan = 0; chan < numChannels; chan++) { auto srcPtr = myPlaybackData.getReadPointer(chan); srcPtr += posInfo.timeInSamples; buffer.copyFrom(chan, 0, srcPtr, numSamples); @@ -51,16 +54,18 @@ class PlaybackProcessor : public ProcessorBase float* input_ptr = (float*)input.data(); myPlaybackData.setSize(input.shape(0), input.shape(1)); + + int numChannels = input.shape(0); for (int y = 0; y < input.shape(1); y++) { for (int x = 0; x < input.shape(0); x++) { myPlaybackData.setSample(x, y, input_ptr[x * input.shape(1) + y]); } } + setMainBusInputsAndOutputs(0, numChannels); } private: juce::AudioSampleBuffer myPlaybackData; - }; diff --git a/Source/PlaybackWarpProcessor.h b/Source/PlaybackWarpProcessor.h index 7df8d27d..d92ee172 100644 --- a/Source/PlaybackWarpProcessor.h +++ b/Source/PlaybackWarpProcessor.h @@ -13,13 +13,13 @@ class PlaybackWarpProcessor : public ProcessorBase public: PlaybackWarpProcessor(std::string newUniqueName, std::vector> inputData, double sr) : ProcessorBase{ createParameterLayout, newUniqueName } { - const int numChannels = (int) inputData.size(); + m_numChannels = (int) inputData.size(); + setMainBusInputsAndOutputs(0, m_numChannels); const int numSamples = (int)inputData.at(0).size(); - // set to stereo - myPlaybackData.setSize(channels, numSamples); - for (int chan = 0; chan < channels; chan++) { - myPlaybackData.copyFrom(chan, 0, inputData.at(std::min(chan, numChannels)).data(), numSamples); + myPlaybackData.setSize(m_numChannels, numSamples); + for (int chan = 0; chan < m_numChannels; chan++) { + myPlaybackData.copyFrom(chan, 0, inputData.at(chan).data(), numSamples); } init(sr); @@ -36,7 +36,7 @@ class PlaybackWarpProcessor : public ProcessorBase m_sample_rate = sr; setAutomationVal("transpose", 0.); myTranspose = myParameters.getRawParameterValue("transpose"); - setupRubberband(sr); + setupRubberband(sr, m_numChannels); setClipPositionsDefault(); } @@ -177,10 +177,10 @@ class PlaybackWarpProcessor : public ProcessorBase numToRetrieve = std::min(numToRetrieve,int(std::ceil( (m_currentClip.end_pos-movingPPQ)/(posInfo.bpm)*60.*m_sample_rate))); if (numToRetrieve > 0) { - m_nonInterleavedBuffer.setSize(channels, numToRetrieve); + m_nonInterleavedBuffer.setSize(m_numChannels, numToRetrieve); numToRetrieve = m_rbstretcher->retrieve(m_nonInterleavedBuffer.getArrayOfWritePointers(), numToRetrieve); - for (int chan = 0; chan < channels; chan++) { + for (int chan = 0; chan < m_numChannels; chan++) { auto chanPtr = m_nonInterleavedBuffer.getReadPointer(chan); buffer.copyFrom(chan, numWritten, chanPtr, numToRetrieve); } @@ -194,7 +194,7 @@ class PlaybackWarpProcessor : public ProcessorBase m_clipIndex += 1; if (m_clipIndex < m_clips.size()) { m_currentClip = m_clips.at(m_clipIndex); - setupRubberband(m_sample_rate); + setupRubberband(m_sample_rate, m_numChannels); if (m_clipInfo.warp_on) { sampleReadIndex = m_clipInfo.beat_to_sample(m_clipInfo.start_marker + m_currentClip.start_marker_offset, m_sample_rate); } @@ -210,7 +210,7 @@ class PlaybackWarpProcessor : public ProcessorBase if (nextPPQ < m_currentClip.start_pos || movingPPQ < m_currentClip.start_pos) { // write some zeros into the output - for (int chan = 0; chan < channels; chan++) { + for (int chan = 0; chan < m_numChannels; chan++) { buffer.setSample(chan, numWritten, 0.f); } numWritten += 1; @@ -227,7 +227,7 @@ class PlaybackWarpProcessor : public ProcessorBase if (m_clipIndex < m_clips.size()) { // Use the next clip position. m_currentClip = m_clips.at(m_clipIndex); - setupRubberband(m_sample_rate); + setupRubberband(m_sample_rate, m_numChannels); if (m_clipInfo.warp_on) { sampleReadIndex = m_clipInfo.beat_to_sample(m_clipInfo.start_marker + m_currentClip.start_marker_offset, m_sample_rate); } @@ -237,7 +237,7 @@ class PlaybackWarpProcessor : public ProcessorBase continue; } else { - for (int chan = 0; chan < channels; chan++) { + for (int chan = 0; chan < m_numChannels; chan++) { buffer.setSample(chan, numWritten, 0.f); } numWritten += 1; @@ -274,12 +274,12 @@ class PlaybackWarpProcessor : public ProcessorBase } } - m_nonInterleavedBuffer.setSize(channels, 1); + m_nonInterleavedBuffer.setSize(m_numChannels, 1); // can we read from the playback data or are we out of bounds and we need to pass zeros to rubberband? const int last_sample = myPlaybackData.getNumSamples() - 1; if (sampleReadIndex > -1 && sampleReadIndex <= last_sample) { - for (int chan = 0; chan < channels; chan++) { + for (int chan = 0; chan < m_numChannels; chan++) { m_nonInterleavedBuffer.copyFrom(chan, 0, myPlaybackData, chan, sampleReadIndex, 1); } } @@ -299,7 +299,7 @@ class PlaybackWarpProcessor : public ProcessorBase void reset() { - setupRubberband(m_sample_rate); + setupRubberband(m_sample_rate, m_numChannels); m_clipIndex = 0; @@ -319,11 +319,12 @@ class PlaybackWarpProcessor : public ProcessorBase void setData(py::array_t input) { float* input_ptr = (float*)input.data(); - const int numChannels = (int) input.shape(0); + m_numChannels = (int) input.shape(0); + setMainBusInputsAndOutputs(0, m_numChannels); const int numSamples = (int) input.shape(1); - myPlaybackData.setSize(numChannels, numSamples); - for (int chan = 0; chan < channels; chan++) { + myPlaybackData.setSize(m_numChannels, numSamples); + for (int chan = 0; chan < m_numChannels; chan++) { myPlaybackData.copyFrom(chan, 0, input_ptr, numSamples); input_ptr += numSamples; } @@ -339,7 +340,7 @@ class PlaybackWarpProcessor : public ProcessorBase std::unique_ptr m_rbstretcher; - const int channels = 2; + int m_numChannels = 2; juce::AudioSampleBuffer m_nonInterleavedBuffer; int sampleReadIndex = 0; @@ -357,7 +358,7 @@ class PlaybackWarpProcessor : public ProcessorBase // Note that we call this instead of calling m_rbstretcher->reset() because // that method doesn't seem to work correctly. // It's better to just create a whole new stretcher object. - void setupRubberband(float sr) { + void setupRubberband(float sr, int numChannels) { using namespace RubberBand; RubberBandStretcher::Options options = 0; @@ -388,7 +389,7 @@ class PlaybackWarpProcessor : public ProcessorBase m_rbstretcher = std::make_unique( sr, - 2, + numChannels, options, 1., 1.); diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 99d7a1a1..2f69dc29 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -8,10 +8,7 @@ PluginProcessor::PluginProcessor(std::string newUniqueName, double sampleRate, i { myPluginPath = path; - loadPlugin(sampleRate, samplesPerBlock); - - // in processBlock, the size will be set correctly. - myCopyBuffer.setSize(myCopyBufferNumChans, samplesPerBlock); + isLoaded = loadPlugin(sampleRate, samplesPerBlock); } bool @@ -21,6 +18,8 @@ PluginProcessor::loadPlugin(double sampleRate, int samplesPerBlock) { AudioPluginFormatManager pluginFormatManager; pluginFormatManager.addDefaultFormats(); + + juce::MessageManager::getInstance(); // to avoid runtime jassert(false) thrown by JUCE for (int i = pluginFormatManager.getNumFormats(); --i >= 0;) { @@ -30,7 +29,7 @@ PluginProcessor::loadPlugin(double sampleRate, int samplesPerBlock) { *pluginFormatManager.getFormat(i)); } - if (myPlugin) + if (myPlugin.get()) { myPlugin->releaseResources(); myPlugin.release(); @@ -39,7 +38,7 @@ PluginProcessor::loadPlugin(double sampleRate, int samplesPerBlock) { // If there is a problem here first check the preprocessor definitions // in the projucer are sensible - is it set up to scan for plugin's? if (pluginDescriptions.size() <= 0) { - std::cerr << "Unable to load plugin. The path should be absolute.\n"; + std::cerr << "Unable to load plugin." << std::endl; return false; } @@ -50,28 +49,28 @@ PluginProcessor::loadPlugin(double sampleRate, int samplesPerBlock) { samplesPerBlock, errorMessage); - if (myPlugin != nullptr) + if (myPlugin.get() == nullptr) { - // Success so set up plugin, then set up features and get all available - // parameters from this given plugin. - myPlugin->prepareToPlay(sampleRate, samplesPerBlock); - myPlugin->setNonRealtime(true); - myCopyBufferNumChans = std::max(myPlugin->getTotalNumInputChannels(), myPlugin->getTotalNumOutputChannels()); - - mySampleRate = sampleRate; - - createParameterLayout(); - - return true; + std::cerr << "PluginProcessor::loadPlugin error: " << errorMessage.toStdString() << std::endl; + return false; } - std::cout << "PluginProcessor::loadPlugin error: " << errorMessage.toStdString() << std::endl; - return false; + myPlugin->enableAllBuses(); + + auto inputs = myPlugin->getTotalNumInputChannels(); + auto outputs = myPlugin->getTotalNumOutputChannels(); + this->setPlayConfigDetails(inputs, outputs, sampleRate, samplesPerBlock); + myPlugin->setPlayConfigDetails(inputs, outputs, sampleRate, samplesPerBlock); + myPlugin->setNonRealtime(true); + mySampleRate = sampleRate; + + createParameterLayout(); + return true; } PluginProcessor::~PluginProcessor() { - if (myPlugin) + if (myPlugin.get()) { myPlugin->releaseResources(); myPlugin.release(); @@ -81,15 +80,25 @@ PluginProcessor::~PluginProcessor() { void PluginProcessor::setPlayHead(AudioPlayHead* newPlayHead) { AudioProcessor::setPlayHead(newPlayHead); - if (myPlugin) { + if (myPlugin.get()) { myPlugin->setPlayHead(newPlayHead); } } +bool +PluginProcessor::canApplyBusesLayout(const juce::AudioProcessor::BusesLayout& layout) { + + if (!myPlugin.get()) { + return false; + } + + return myPlugin->checkBusesLayoutSupported(layout); +} + void PluginProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { - if (myPlugin) { + if (myPlugin.get()) { myPlugin->prepareToPlay(sampleRate, samplesPerBlock); } } @@ -99,7 +108,17 @@ PluginProcessor::processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer& { AudioPlayHead::CurrentPositionInfo posInfo; getPlayHead()->getCurrentPosition(posInfo); - + myRenderMidiBuffer.clear(); + + if (!myPlugin.get() || !isLoaded) { + buffer.clear(); + + if (posInfo.ppqPosition == 0) { + std::cerr << "Error: no plugin was processed." << std::endl; + } + return; + } + automateParameters(); long long int start = posInfo.timeInSamples; @@ -113,40 +132,8 @@ PluginProcessor::processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer& } } while (myIsMessageBetween && myMidiEventsDoRemain); - if (myPlugin) { - - /* - First copy from buffer to myCopyBuffer. - Why? Some plugins involve multiple buses (e.g., sidechain compression). You can check this with - `myPlugin->getBusCount()`. However, it can be difficult to add or remove buses: `myPlugin->canRemoveBus(1);` - That function may actually not be able to remove a secondary (optional) sidechain bus. - myPlugin->processBlock will expect to receive a buffer whose number of channels is the max(the total of all the input bus - channels, the total of all output bus channels). When users create a graph with DawDreamer, they may pass only 1 stereo input - to a plugin with an optional sidechain. This will cause the arg buffer to have 2 channels. However, myPlugin->processBlock would - expect 4 channels (2 channels for each input bus, the second bus being the unspecified sidechain input). Therefore, the solution - is to have myCopyBuffer be the larger size, and to copy whatever channels exist in buffer into it. In effect, the sidechain input - will have zeros. Then we copy the results of myCopyBuffer back to buffer so that other processors receive the result. - */ - - int numSamples = buffer.getNumSamples(); - - myCopyBuffer.setSize(std::max(buffer.getNumChannels(), myCopyBufferNumChans), numSamples, false, true, false); - - for (int i = 0; i < buffer.getNumChannels(); i++) - { - myCopyBuffer.copyFrom(i, 0, buffer.getReadPointer(i), numSamples); - } - - myPlugin->processBlock(myCopyBuffer, myRenderMidiBuffer); - - // copy myCopyBuffer back to buffer because this is how it gets passed to other processors. - for (int i = 0; i < 2; i++) - { - buffer.copyFrom(i, 0, myCopyBuffer.getReadPointer(i), numSamples); - } - - } - + myPlugin->processBlock(buffer, myRenderMidiBuffer); + ProcessorBase::processBlock(buffer, midiBuffer); } @@ -156,7 +143,7 @@ PluginProcessor::automateParameters() { AudioPlayHead::CurrentPositionInfo posInfo; getPlayHead()->getCurrentPosition(posInfo); - if (myPlugin) { + if (myPlugin.get()) { for (int i = 0; i < myPlugin->AudioProcessor::getNumParameters(); i++) { @@ -164,6 +151,7 @@ PluginProcessor::automateParameters() { auto theParameter = ((AutomateParameterFloat*)myParameters.getParameter(paramID)); if (theParameter) { + // todo: change to setParameterNotifyingHost? myPlugin->setParameter(i, theParameter->sample(posInfo.timeInSamples)); } else { @@ -176,7 +164,9 @@ PluginProcessor::automateParameters() { void PluginProcessor::reset() { - myPlugin->reset(); + if (myPlugin.get()) { + myPlugin->reset(); + } if (myMidiIterator) { delete myMidiIterator; @@ -561,4 +551,4 @@ PluginProcessorWrapper::getPluginParametersDescription() } return myList; -} \ No newline at end of file +} diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index fb1e456d..83c3a23e 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -11,6 +11,23 @@ class PluginProcessor : public ProcessorBase PluginProcessor(std::string newUniqueName, double sampleRate, int samplesPerBlock, std::string path); ~PluginProcessor(); + bool canApplyBusesLayout(const juce::AudioProcessor::BusesLayout& layout); + + bool setBusesLayout(const BusesLayout& arr) { + if (myPlugin.get()) { + AudioProcessor::setBusesLayout(arr); + return myPlugin->setBusesLayout(arr); + } + return false; + } + + void numChannelsChanged() { + ProcessorBase::numChannelsChanged(); + if (myPlugin.get()) { + myPlugin->setPlayConfigDetails(this->getTotalNumInputChannels(), this->getTotalNumOutputChannels(), this->getSampleRate(), this->getBlockSize()); + } + } + void prepareToPlay(double sampleRate, int samplesPerBlock); bool supportsDoublePrecisionProcessing() { return myPlugin ? myPlugin->supportsDoublePrecisionProcessing() : false; } @@ -52,6 +69,7 @@ class PluginProcessor : public ProcessorBase private: bool loadPlugin(double sampleRate, int samplesPerBlock); + bool isLoaded = false; std::string myPluginPath; double mySampleRate; @@ -68,11 +86,7 @@ class PluginProcessor : public ProcessorBase protected: - std::unique_ptr> myPlugin; - // For an explanation of myCopyBuffer, read PluginProcessor::processBlock - juce::AudioSampleBuffer myCopyBuffer; - int myCopyBufferNumChans = 2; - + std::unique_ptr> myPlugin = nullptr; }; @@ -99,4 +113,4 @@ class PluginProcessorWrapper : public PluginProcessor py::list getPluginParametersDescription(); -}; \ No newline at end of file +}; diff --git a/Source/ProcessorBase.cpp b/Source/ProcessorBase.cpp index f4f0b6fb..3b74b7a6 100644 --- a/Source/ProcessorBase.cpp +++ b/Source/ProcessorBase.cpp @@ -1,5 +1,10 @@ #include "ProcessorBase.h" +void +ProcessorBase::numChannelsChanged() { + m_isConnectedInGraph = false; +} + void ProcessorBase::getStateInformation(juce::MemoryBlock& destData) { @@ -101,4 +106,4 @@ py::array_t ProcessorBase::getAutomationNumpy(std::string parameterName) } return arr; -} \ No newline at end of file +} diff --git a/Source/ProcessorBase.h b/Source/ProcessorBase.h index b8955e42..5fa6cd14 100644 --- a/Source/ProcessorBase.h +++ b/Source/ProcessorBase.h @@ -32,12 +32,11 @@ class ProcessorBase : public juce::AudioProcessor AudioPlayHead::CurrentPositionInfo posInfo; getPlayHead()->getCurrentPosition(posInfo); - const int numberChannels = buffer.getNumChannels(); + const int numberChannels = myRecordBuffer.getNumChannels(); + int numSamplesToCopy = std::min(buffer.getNumSamples(), (int) myRecordBuffer.getNumSamples() -(int)posInfo.timeInSamples); for (int chan = 0; chan < numberChannels; chan++) { - // Write the sample to the engine's history for the correct channel. - int numSamplesToCopy = std::min(buffer.getNumSamples(), (int) myRecordBuffer.getNumSamples() -(int)posInfo.timeInSamples); - // assume numSamplesToCopy > 0 + // Write the sample to the engine's history for the correct channel. myRecordBuffer.copyFrom(chan, posInfo.timeInSamples, buffer.getReadPointer(chan), numSamplesToCopy); } } @@ -52,6 +51,15 @@ class ProcessorBase : public juce::AudioProcessor bool producesMidi() const override { return false; } double getTailLengthSeconds() const override { return 0; } + //============================================================================== + virtual bool canApplyBusesLayout(const juce::AudioProcessor::BusesLayout& layout) { + return AudioProcessor::canApplyBusesLayout(layout); + } + + virtual bool setBusesLayout(const BusesLayout& arr) { + return AudioProcessor::setBusesLayout(arr); + } + //============================================================================== int getNumPrograms() override { return 0; } int getCurrentProgram() override { return 0; } @@ -102,11 +110,46 @@ class ProcessorBase : public juce::AudioProcessor } void setRecorderLength(int numSamples) { + int numChannels = this->getTotalNumOutputChannels(); + if (m_recordEnable) { - myRecordBuffer.setSize(2, numSamples); + myRecordBuffer.setSize(numChannels, numSamples); + } + else { + myRecordBuffer.setSize(numChannels, 0); + } + } + + int + getTotalNumOutputChannels() { + return AudioProcessor::getTotalNumOutputChannels(); + } + + int + getTotalNumInputChannels() { + return AudioProcessor::getTotalNumInputChannels(); + } + + virtual void numChannelsChanged(); + + bool isConnectedInGraph() { return m_isConnectedInGraph;} + void setConnectedInGraph(bool isConnected) { + m_isConnectedInGraph = isConnected; + + } + + void setMainBusInputsAndOutputs(int inputs, int outputs) { + BusesLayout busesLayout; + const AudioChannelSet inputChannelSet = AudioChannelSet::discreteChannels(inputs); + const AudioChannelSet outputChannelSet = AudioChannelSet::discreteChannels(outputs); + busesLayout.inputBuses.add(inputChannelSet); + busesLayout.outputBuses.add(outputChannelSet); + + if (this->canApplyBusesLayout(busesLayout)) { + bool result = this->setBusesLayout(busesLayout); } else { - myRecordBuffer.setSize(2, 0); + std::cerr << this->getUniqueName() << " CANNOT ApplyBusesLayout inputs: " << inputs << " outputs: " << outputs << std::endl; } } @@ -115,8 +158,10 @@ class ProcessorBase : public juce::AudioProcessor JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ProcessorBase) std::string myUniqueName; juce::AudioSampleBuffer myRecordBuffer; + bool m_isConnectedInGraph = false; protected: + AudioProcessorValueTreeState myParameters; @@ -126,4 +171,4 @@ class ProcessorBase : public juce::AudioProcessor return params; } bool m_recordEnable = false; -}; \ No newline at end of file +}; diff --git a/Source/RecorderProcessor.h b/Source/RecorderProcessor.h deleted file mode 100644 index 96f168fd..00000000 --- a/Source/RecorderProcessor.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "ProcessorBase.h" - -//============================================================================== -class RecorderProcessor : public ProcessorBase -{ -public: - RecorderProcessor(std::string newUniqueName) : ProcessorBase{ newUniqueName } - { - m_recordEnable = true; - } - - void prepareToPlay(double, int) - { - } - - void processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer& midiBuffer) - { - ProcessorBase::processBlock(buffer, midiBuffer); - } - - void reset(){} - - const juce::String getName() const { return "RecorderProcessor"; } -}; \ No newline at end of file diff --git a/Source/RenderEngine.cpp b/Source/RenderEngine.cpp index 71eb5154..bea385ce 100644 --- a/Source/RenderEngine.cpp +++ b/Source/RenderEngine.cpp @@ -8,7 +8,6 @@ RenderEngine::RenderEngine(double sr, int bs) : myMainProcessorGraph.reset(new juce::AudioProcessorGraph()); myMainProcessorGraph->setNonRealtime(true); myMainProcessorGraph->setPlayHead(this); - myRecordedSamples = std::vector>(myNumOutputAudioChans, std::vector(0)); } RenderEngine::~RenderEngine() @@ -17,144 +16,163 @@ RenderEngine::~RenderEngine() } bool -RenderEngine::loadGraph(DAG inDagNodes, int numInputAudioChans=2, int numOutputAudioChans=2) { +RenderEngine::loadGraph(DAG inDagNodes) { bool success = true; - + std::vector* dagNodes = (std::vector*) &inDagNodes; myMainProcessorGraph->clear(); - myNumInputAudioChans = numInputAudioChans; - myNumOutputAudioChans = numOutputAudioChans; - using AudioGraphIOProcessor = juce::AudioProcessorGraph::AudioGraphIOProcessor; - myMidiInputNode = myMainProcessorGraph->addNode(std::make_unique(AudioGraphIOProcessor::midiInputNode)); - - juce::ReferenceCountedArray slots; int nodeInt = 0; - std::unordered_map uniqueNameToSlotIndex; + m_UniqueNameToSlotIndex.clear(); + m_UniqueNameToInputs.clear(); + + slots.clear(); for (auto node : *dagNodes) { auto processorBase = node.processorBase; - std::vector inputs = node.inputs; auto myNode = myMainProcessorGraph->addNode((std::unique_ptr)processorBase); // todo: does incReferenceCount() cause memory leak?? - // If we don't do it, later calls to this function to load a new graph crash at - // myMainProcessorGraph->clear(); + // If we don't do it, later calls to this function to load a new graph crash at myMainProcessorGraph->clear(); myNode.get()->incReferenceCount(); slots.set(nodeInt, myNode); - //slots.getUnchecked(nodeInt)->getProcessor()->setNonRealtime(true); // assume processors are initialized in non-real-time mode. - slots.getUnchecked(nodeInt)->getProcessor()->setPlayConfigDetails(myNumOutputAudioChans *(int)(inputs.size()), - myNumOutputAudioChans, - mySampleRate, myBufferSize); - - if (processorBase->acceptsMidi()) { - // Connect MIDI. - // Assume the first node is the one that needs to receive MIDI. - myMainProcessorGraph->addConnection({ { myMidiInputNode->nodeID, juce::AudioProcessorGraph::midiChannelIndex }, - { slots.getUnchecked(nodeInt)->nodeID, juce::AudioProcessorGraph::midiChannelIndex } }); - } + m_UniqueNameToSlotIndex[processorBase->getUniqueName()] = nodeInt; + m_UniqueNameToInputs[processorBase->getUniqueName()] = node.inputs; - uniqueNameToSlotIndex[processorBase->getUniqueName()] = nodeInt; - - int inputIndex = 0; - for (const std::string inputName : inputs) { + nodeInt++; + } - if (uniqueNameToSlotIndex.find(inputName) == uniqueNameToSlotIndex.end()) - { - std::cout << "Error connecting " << inputName << " to " << processorBase->getUniqueName() << ";" << std::endl; - std::cout << "You might need to place " << inputName << " earlier in the graph." << std::endl; - success = false; - continue; - } + myMainProcessorGraph->enableAllBuses(); + // NB: don't enableAllBuses on all the processors in the graph because + // it will actually mess them up (FaustProcessor) + + return success; +} - int slotIndexOfInput = uniqueNameToSlotIndex[inputName]; +bool +RenderEngine::connectGraph() { - for (int channel = 0; channel < myNumOutputAudioChans; ++channel) { - int chanSource = channel; - int chanDest = inputIndex * myNumOutputAudioChans + channel; - bool result = myMainProcessorGraph->addConnection({ { slots.getUnchecked(slotIndexOfInput)->nodeID, chanSource }, - { slots.getUnchecked(nodeInt)->nodeID, chanDest } }); - if (!result) { - std::cout << "Error connecting " << inputName << " " << chanSource << " to " << processorBase->getUniqueName() << " " << chanDest << std::endl; - success = false; - } + // remove all connections + for (auto connection : myMainProcessorGraph->getConnections()) { + myMainProcessorGraph->removeConnection(connection); + } + + int numNodes = myMainProcessorGraph->getNumNodes(); + for (int i = 0; i < numNodes; i++) { + auto processor = dynamic_cast (myMainProcessorGraph->getNode(i)->getProcessor()); + + int numInputAudioChans = 0; + + std::string myUniqueName = processor->getUniqueName(); + + auto inputs = m_UniqueNameToInputs[myUniqueName]; + + for (auto otherNode : myMainProcessorGraph->getNodes()) { + auto otherName = ((ProcessorBase*) otherNode->getProcessor())->getUniqueName(); + + if (std::find(inputs.begin(), inputs.end(), otherName) != inputs.end()) { + numInputAudioChans += otherNode->getProcessor()->getMainBusNumOutputChannels(); } + } - inputIndex++; + processor->setPlayHead(this); + processor->automateParameters(); + int numOutputAudioChans = processor->getMainBusNumOutputChannels(); + int actualInputs = processor->getMainBusNumInputChannels(); + if (numInputAudioChans > actualInputs) { + std::cerr << "Too many inputs (" << numInputAudioChans << ") are trying to connect to " << actualInputs << " inputs for processor " << myUniqueName << std::endl; + return false; } - nodeInt++; - } + numInputAudioChans = std::max(numInputAudioChans, actualInputs); + + //std::cerr << processor->getUniqueName() << " setPlayConfigDetails inputs: " << numInputAudioChans << " outputs: " << numOutputAudioChans << std::endl; + + processor->setPlayConfigDetails(numInputAudioChans, numOutputAudioChans, mySampleRate, myBufferSize); + + int chanDest = 0; - if (!slots.isEmpty()) { + for (const std::string inputName : m_UniqueNameToInputs[myUniqueName]) { - auto lastNodeID = slots.getLast()->nodeID; + if (m_UniqueNameToSlotIndex.find(inputName) == m_UniqueNameToSlotIndex.end()) + { + std::cout << "Error connecting " << inputName << " to " << myUniqueName << ";" << std::endl; + std::cout << "You might need to place " << inputName << " earlier in the graph." << std::endl; + return false; + } + + int slotIndexOfInput = m_UniqueNameToSlotIndex[inputName]; - slots.set(nodeInt, myMainProcessorGraph->addNode(std::make_unique("_output_recorder"))); - slots.getUnchecked(nodeInt)->getProcessor()->setPlayConfigDetails(myNumInputAudioChans, - myNumOutputAudioChans, - mySampleRate, myBufferSize); - slots.getUnchecked(nodeInt)->getProcessor()->prepareToPlay(mySampleRate, myBufferSize); + auto inputProcessor = myMainProcessorGraph->getNode(slotIndexOfInput)->getProcessor(); + + for (int chanSource = 0; chanSource < inputProcessor->getMainBusNumOutputChannels(); chanSource++) { - auto recorderNodeID = slots.getUnchecked(nodeInt)->nodeID; + AudioProcessorGraph::Connection connection = { { slots.getUnchecked(slotIndexOfInput)->nodeID, chanSource }, + { slots.getUnchecked(i)->nodeID, chanDest } }; - for (int channel = 0; channel < myNumOutputAudioChans; ++channel) - { - bool result = myMainProcessorGraph->addConnection({ { lastNodeID, channel }, - { recorderNodeID, channel } }); - if (!result) { - std::cout << "unable to connect to recorderNode" << std::endl; - success = false; + bool result = myMainProcessorGraph->canConnect(connection) && myMainProcessorGraph->addConnection(connection); + if (!result) { + std::cout << "Error connecting " << inputName << " " << chanSource << " to " << myUniqueName << " " << chanDest << std::endl; + return false; + } + + chanDest++; } } - } - for (auto node : myMainProcessorGraph->getNodes()) { - node->getProcessor()->enableAllBuses(); + processor->setConnectedInGraph(true); + } - myMainProcessorGraph->setPlayConfigDetails(myNumInputAudioChans, - myNumOutputAudioChans, - mySampleRate, myBufferSize); - + myMainProcessorGraph->setPlayConfigDetails(0, 0, mySampleRate, myBufferSize); myMainProcessorGraph->prepareToPlay(mySampleRate, myBufferSize); for (auto node : myMainProcessorGraph->getNodes()) { node->getProcessor()->prepareToPlay(mySampleRate, myBufferSize); - node->getProcessor()->setPlayHead(this); } - - return success; + + return true; } -void +bool RenderEngine::render(const double renderLength) { int numRenderedSamples = renderLength * mySampleRate; if (numRenderedSamples <= 0) { std::cerr << "Render length must be greater than zero."; - return; + return false; } + + int numberOfBuffers = myBufferSize == 1 ? numRenderedSamples : int(std::ceil((numRenderedSamples -1.) / myBufferSize)); + + bool graphIsConnected = true; + + int numNodes = myMainProcessorGraph->getNumNodes(); + for (int i = 0; i < numNodes; i++) { + auto faustProcessor = dynamic_cast (myMainProcessorGraph->getNode(i)->getProcessor()); + if (faustProcessor && (!faustProcessor->isCompiled())) { + if (!faustProcessor->compile()) { + std::cerr << "Faust didn't compile correctly." << std::endl; + return false; + } + } + auto processor = dynamic_cast (myMainProcessorGraph->getNode(i)->getProcessor()); + if (processor) { + graphIsConnected = graphIsConnected && processor->isConnectedInGraph(); + } - int numberOfBuffers = int(std::ceil((numRenderedSamples -1.) / myBufferSize)); - - AudioSampleBuffer audioBuffer(myNumOutputAudioChans, myBufferSize); - - // Clear main buffer and prepare to record samples over multiple buffer passes. - myRecordedSamples.clear(); - myRecordedSamples = std::vector>(myNumOutputAudioChans, std::vector(numRenderedSamples, 0.f)); + } myMainProcessorGraph->reset(); myMainProcessorGraph->setPlayHead(this); myCurrentPositionInfo.resetToDefault(); - myCurrentPositionInfo.bpm = myBPM; myCurrentPositionInfo.isPlaying = true; myCurrentPositionInfo.isRecording = true; @@ -162,19 +180,36 @@ RenderEngine::render(const double renderLength) { myCurrentPositionInfo.timeSigNumerator = 4; myCurrentPositionInfo.timeSigDenominator = 4; myCurrentPositionInfo.isLooping = false; - - for (int i = 0; i < myMainProcessorGraph->getNumNodes(); i++) { + + if (!graphIsConnected) { + bool result = connectGraph(); + if (!result) { + std::cerr << "Unable to connect graph." << std::endl; + return false; + } + } + + int numInputAudioChans = myMainProcessorGraph->getNode(0)->getProcessor()->getTotalNumInputChannels(); + int numOutputAudioChans = myMainProcessorGraph->getNode(myMainProcessorGraph->getNumNodes()-1)->getProcessor()->getMainBusNumOutputChannels(); + + int audioBufferNumChans = std::max(numOutputAudioChans, numInputAudioChans); + AudioSampleBuffer audioBuffer(audioBufferNumChans, myBufferSize); + + for (int i = 0; i < numNodes; i++) { auto processor = dynamic_cast (myMainProcessorGraph->getNode(i)->getProcessor()); if (processor) { - processor->setRecorderLength(numRenderedSamples); + if (i == numNodes-1) { + // always force the last processor to record. todo: maybe this is clumsy. + processor->setRecordEnable(true); + } + processor->setRecorderLength(processor->getRecordEnable() ? numRenderedSamples : 0); } } MidiBuffer renderMidiBuffer; - + for (long long int i = 0; i < numberOfBuffers; ++i) { - // This gets RecorderProcessor to write to this RenderEngine's myRecordedSamples. myMainProcessorGraph->processBlock(audioBuffer, renderMidiBuffer); myCurrentPositionInfo.timeInSamples += myBufferSize; @@ -183,11 +218,13 @@ RenderEngine::render(const double renderLength) { myCurrentPositionInfo.isPlaying = false; myCurrentPositionInfo.isRecording = false; + + return true; } void RenderEngine::setBPM(double bpm) { if (bpm <= 0) { - std::cerr << "BPM must be positive."; + std::cerr << "BPM must be positive." << std::endl; return; } myBPM = bpm; @@ -196,15 +233,22 @@ void RenderEngine::setBPM(double bpm) { py::array_t RenderEngine::getAudioFrames() { - auto nodes = myMainProcessorGraph->getNodes(); - for (auto& node: nodes) { - - auto processor = dynamic_cast(node->getProcessor()); - if (processor) { - return processor->getAudioFrames(); - } + + if (nodes.size() == 0) { + // NB: For some reason we can't initialize the array as shape (2, 0) + py::array_t arr({ 2, 1 }); + arr.resize({ 2, 0 }); + return arr; + } + + auto node = nodes.getLast(); + + auto processor = dynamic_cast(node->getProcessor()); + if (processor) { + auto uniqueName = processor->getUniqueName(); + return getAudioFramesForName(uniqueName); } // NB: For some reason we can't initialize the array as shape (2, 0) @@ -217,7 +261,6 @@ RenderEngine::getAudioFrames() py::array_t RenderEngine::getAudioFramesForName(std::string& name) { - auto nodes = myMainProcessorGraph->getNodes(); for (auto& node : nodes) { @@ -236,7 +279,6 @@ RenderEngine::getAudioFramesForName(std::string& name) return arr; } - bool RenderEngine::getCurrentPosition(CurrentPositionInfo& result) { result = myCurrentPositionInfo; diff --git a/Source/RenderEngine.h b/Source/RenderEngine.h index 18068d63..c04fe5bb 100644 --- a/Source/RenderEngine.h +++ b/Source/RenderEngine.h @@ -26,9 +26,9 @@ class RenderEngine : AudioPlayHead ~RenderEngine(); // RenderEngine(const RenderEngine&) = delete; - bool loadGraph(DAG dagNodes, int numInputAudioChans, int numOutputAudioChans); + bool loadGraph(DAG dagNodes); - void render (const double renderLength); + bool render (const double renderLength); void setBPM(double bpm); @@ -47,18 +47,15 @@ class RenderEngine : AudioPlayHead double mySampleRate; int myBufferSize; double myBPM = 120.; + std::unordered_map m_UniqueNameToSlotIndex; + std::unordered_map> m_UniqueNameToInputs; + + bool connectGraph(); private: - - std::vector> myRecordedSamples; std::unique_ptr myMainProcessorGraph; - - juce::AudioProcessorGraph::Node::Ptr myMidiInputNode; - - int myNumInputAudioChans = 2; - int myNumOutputAudioChans = 2; + juce::ReferenceCountedArray slots; CurrentPositionInfo myCurrentPositionInfo; - }; diff --git a/Source/RenderEngineWrapper.cpp b/Source/RenderEngineWrapper.cpp index fc00785d..69fb3cda 100644 --- a/Source/RenderEngineWrapper.cpp +++ b/Source/RenderEngineWrapper.cpp @@ -103,7 +103,7 @@ RenderEngineWrapper::makeFaustProcessor(const std::string& name) #endif bool -RenderEngineWrapper::loadGraphWrapper(py::object dagObj, int numInputAudioChans = 2, int numOutputAudioChans = 2) { +RenderEngineWrapper::loadGraphWrapper(py::object dagObj) { if (!py::isinstance(dagObj)) { return false; @@ -160,5 +160,5 @@ RenderEngineWrapper::loadGraphWrapper(py::object dagObj, int numInputAudioChans buildingDag->nodes.push_back(dagNode); } - return RenderEngine::loadGraph(*buildingDag, numInputAudioChans, numOutputAudioChans); + return RenderEngine::loadGraph(*buildingDag); } diff --git a/Source/RenderEngineWrapper.h b/Source/RenderEngineWrapper.h index 4c79e492..4fe413b1 100644 --- a/Source/RenderEngineWrapper.h +++ b/Source/RenderEngineWrapper.h @@ -50,6 +50,6 @@ class RenderEngineWrapper : public RenderEngine std::shared_ptr makeFaustProcessor(const std::string& name); #endif - bool loadGraphWrapper(py::object dagObj, int numInputAudioChans, int numOutputAudioChans); + bool loadGraphWrapper(py::object dagObj); -}; \ No newline at end of file +}; diff --git a/Source/ReverbProcessor.h b/Source/ReverbProcessor.h index 2bdd816a..902a0a5c 100644 --- a/Source/ReverbProcessor.h +++ b/Source/ReverbProcessor.h @@ -19,7 +19,7 @@ class ReverbProcessor : public ProcessorBase myDryLevel = myParameters.getRawParameterValue("dry_level"); myWetLevel = myParameters.getRawParameterValue("wet_level"); myWidth = myParameters.getRawParameterValue("width"); - + setMainBusInputsAndOutputs(2, 2); } void prepareToPlay(double sampleRate, int samplesPerBlock) { @@ -104,4 +104,4 @@ class ReverbProcessor : public ProcessorBase return params; } -}; \ No newline at end of file +}; diff --git a/Source/SamplerProcessor.h b/Source/SamplerProcessor.h index 396b3b1e..bb4dfea3 100644 --- a/Source/SamplerProcessor.h +++ b/Source/SamplerProcessor.h @@ -11,6 +11,7 @@ class SamplerProcessor : public ProcessorBase createParameterLayout(); sampler.setNonRealtime(true); sampler.setSample(inputData, mySampleRate); + setMainBusInputsAndOutputs(0, inputData.size()); } SamplerProcessor(std::string newUniqueName, py::array_t input, double sr, int blocksize) : ProcessorBase{ newUniqueName }, mySampleRate{ sr } @@ -18,6 +19,7 @@ class SamplerProcessor : public ProcessorBase createParameterLayout(); sampler.setNonRealtime(true); setData(input); + setMainBusInputsAndOutputs(0, input.shape(0)); } ~SamplerProcessor() {} @@ -29,7 +31,7 @@ class SamplerProcessor : public ProcessorBase wrapperGetParameter(int parameterIndex) { if (parameterIndex >= sampler.getNumParameters()) { - std::cout << "Parameter not found for index: " << parameterIndex << std::endl; + std::cerr << "Parameter not found for index: " << parameterIndex << std::endl; return 0.; } @@ -39,14 +41,15 @@ class SamplerProcessor : public ProcessorBase } void - wrapperSetParameter(const int paramIndex, const float value) + wrapperSetParameter(const int parameterIndex, const float value) { - auto parameterName = sampler.getParameterName(paramIndex); - - if (parameterName == "Param") { + if (parameterIndex >= sampler.getNumParameters()) { + std::cerr << "Parameter not found for index: " << parameterIndex << std::endl; return; } + auto parameterName = sampler.getParameterName(parameterIndex); + ProcessorBase::setAutomationVal(parameterName.toStdString(), value); } @@ -83,6 +86,9 @@ class SamplerProcessor : public ProcessorBase automateParameters(); + buffer.clear(); // todo: why did this become necessary? + midiBuffer.clear(); + long long int start = posInfo.timeInSamples; long long int end = start + buffer.getNumSamples(); @@ -240,19 +246,14 @@ class SamplerProcessor : public ProcessorBase ValueTree blankState; myParameters.replaceState(blankState); - int usedParameterAmount = 0; for (int i = 0; i < sampler.getNumParameters(); ++i) { auto parameterName = sampler.getParameterName(i); // Ensure the parameter is not unused. - if (parameterName != "Param") - { - ++usedParameterAmount; - myParameters.createAndAddParameter(std::make_unique(parameterName, parameterName, NormalisableRange(0.f, 1.f), 0.f)); - // give it a valid single sample of automation. - ProcessorBase::setAutomationVal(parameterName.toStdString(), sampler.getParameter(i)); - } + myParameters.createAndAddParameter(std::make_unique(parameterName, parameterName, NormalisableRange(0.f, 1.f), 0.f)); + // give it a valid single sample of automation. + ProcessorBase::setAutomationVal(parameterName.toStdString(), sampler.getParameter(i)); } } @@ -271,7 +272,7 @@ class SamplerProcessor : public ProcessorBase sampler.setParameterRawNotifyingHost(i, theParameter->sample(posInfo.timeInSamples)); } else { - std::cout << "Error automateParameters: " << theName << std::endl; + std::cerr << "Error automateParameters: " << theName << std::endl; } } @@ -291,4 +292,4 @@ class SamplerProcessor : public ProcessorBase MidiBuffer::Iterator* myMidiIterator = nullptr; bool myIsMessageBetween = false; bool myMidiEventsDoRemain = false; -}; \ No newline at end of file +}; diff --git a/Source/source.cpp b/Source/source.cpp index 07c8d0d2..a66ec6fc 100644 --- a/Source/source.cpp +++ b/Source/source.cpp @@ -46,7 +46,7 @@ PYBIND11_MODULE(dawdreamer, m) """ ---------- )pbdoc") - .def("get_automation", &ProcessorBase::getAutomationNumpy, arg("parameter_name"), R"pbdoc( +.def("get_automation", &ProcessorBase::getAutomationNumpy, arg("parameter_name"), R"pbdoc( Get a parameter's automation as a numpy array. Parameters @@ -63,6 +63,8 @@ PYBIND11_MODULE(dawdreamer, m) ---------- )pbdoc") + .def("get_num_output_channels", &ProcessorBase::getTotalNumOutputChannels, "Get the total number of output channels (2 indicates stereo output).") + .def("get_num_input_channels", &ProcessorBase::getTotalNumInputChannels, "Get the total number of input channels (2 indicates stereo input).") .def_property("record", &ProcessorBase::getRecordEnable, &ProcessorBase::setRecordEnable, "Whether recording of this processor is enabled." ) .def("get_audio", &ProcessorBase::getAudioFrames, "Get the audio data of the processor after a render, assuming recording was enabled.") .def("get_name", &ProcessorBase::getUniqueName, "Get the user-defined name of a processor instance.").doc() = R"pbdoc( @@ -235,8 +237,7 @@ Note that note-ons and note-offs are counted separately.") .def("set_bpm", &RenderEngineWrapper::setBPM, arg("bpm"), "Set the beats-per-minute of the engine.") .def("get_audio", &RenderEngine::getAudioFrames, "Get the most recently rendered audio as a numpy array.") .def("get_audio", &RenderEngine::getAudioFramesForName, arg("name"), "Get the most recently rendered audio for a specific processor.") - .def("load_graph", &RenderEngineWrapper::loadGraphWrapper, arg("dag"), arg("num_input_audio_chans") = 2, arg("num_out_audio_chans") = 2, - "Load a directed acyclic graph of processors.") + .def("load_graph", &RenderEngineWrapper::loadGraphWrapper, arg("dag"), "Load a directed acyclic graph of processors.") .def("make_oscillator_processor", &RenderEngineWrapper::makeOscillatorProcessor, arg("name"), arg("frequency"), "Make an Oscillator Processor", returnPolicy) .def("make_plugin_processor", &RenderEngineWrapper::makePluginProcessor, arg("name"), arg("plugin_path"), diff --git a/build_macos.sh b/build_macos.sh new file mode 100644 index 00000000..daa3e275 --- /dev/null +++ b/build_macos.sh @@ -0,0 +1,42 @@ +# check that these are right for your system: +export PYTHONMAJOR=3.9 +export pythonLocation=/Library/Frameworks/Python.framework/Versions/3.9 + +# Below this you shouldn't need to change anything except for the part about codesigning. + +# Build Libsamplerate +cd thirdparty/libsamplerate +mkdir build_release +cmake -DCMAKE_BUILD_TYPE=Release -Bbuild_release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DLIBSAMPLERATE_EXAMPLES=off -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 +make --directory=build_release +cd ../.. + +# build macOS release +export MACOSX_DEPLOYMENT_TARGET=10.15 +xcodebuild -configuration Release -project Builds/MacOSX/DawDreamer.xcodeproj/ +mv Builds/MacOSX/build/Release/dawdreamer.so.dylib Builds/MacOSX/build/Release/dawdreamer.so +# otool -L Builds/MacOSX/build/Release/dawdreamer.so +install_name_tool -change @rpath/libfaust.2.dylib @loader_path/libfaust.2.dylib Builds/MacOSX/build/Release/dawdreamer.so +# otool -L Builds/MacOSX/build/Release/dawdreamer.so + +# codesigning +# Open Keychain Access. Go to "login". Look for "Apple Development". +# run `export CODESIGN_IDENTITY="Apple Development: example@example.com (ABCDE12345)"` with your own info substituted. +# You can put this in your ~/.zshrc file too. +codesign --force --deep --sign "$CODESIGN_IDENTITY" Builds/MacOSX/build/Release/dawdreamer.so + +# # Confirm the codesigning +codesign -vvvv Builds/MacOSX/build/Release/dawdreamer.so + +rm tests/dawdreamer.so +cp Builds/MacOSX/build/Release/dawdreamer.so tests/dawdreamer.so +cp thirdparty/libfaust/darwin-x64/Release/libfaust.a tests/libfaust.2.dylib + +# # To make a wheel locally: +# pip install setuptools wheel build delocate +# python3 -m build --wheel +# delocate-listdeps dist/dawdreamer-0.5.8.1-cp39-cp39-macosx_10_15_universal2.whl +# delocate-wheel --require-archs x86_64 -w repaired_wheel dist/dawdreamer-0.5.8.1-cp39-cp39-macosx_10_15_universal2.whl +# pip install repaired_wheel/dawdreamer-0.5.8.1-cp39-cp39-macosx_10_15_universal2.whl +# cd tests +# python -m pytest -s . \ No newline at end of file diff --git a/dawdreamer/__init__.py b/dawdreamer/__init__.py index c0644cde..494302c0 100644 --- a/dawdreamer/__init__.py +++ b/dawdreamer/__init__.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- +# NB: on cibuildwheel, this __init__.py file ends up at roughly +# /private/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/tmp6qlby_wi/lib/python3.8/site-packages/dawdreamer/__init__.py from .dawdreamer import * diff --git a/setup.py b/setup.py index 3c8699aa..00e5817d 100644 --- a/setup.py +++ b/setup.py @@ -7,13 +7,14 @@ from setuptools import setup, Extension from setuptools.dist import Distribution import os +import os.path from pathlib import Path import shutil import platform import glob -python_requires = "==" + os.environ['PYTHONMAJOR'] + '.*' # set with github action +python_requires = "==" + os.environ['PYTHONMAJOR'].replace('m', '') + '.*' # set with github action print(f'python_requires: {python_requires}') @@ -77,11 +78,9 @@ def has_ext_modules(foo): ) faustlibraries = list(glob.glob('dawdreamer/faustlibraries/*', recursive=True)) -# drop hidden files -faustlibraries = list(filter(lambda x: '.git' not in x, faustlibraries)) if not faustlibraries: - raise ValueError("You need to put the FAUST .lib files in dawdreamer/faustlibraries/") + raise ValueError("You need to put the faustlibraries repo inside dawdreamer.") package_data += faustlibraries @@ -100,10 +99,10 @@ def has_ext_modules(foo): name='dawdreamer', url='https://github.com/DBraun/DawDreamer', project_urls={ - 'Documentation': 'https://ccrma.stanford.edu/~braun/dawdreamer', + 'Documentation': 'https://dirt.design/DawDreamer', 'Source': 'https://github.com/DBraun/DawDreamer', }, - version='0.5.7.10', + version='0.5.8.1', author='David Braun', author_email='braun@ccrma.stanford.edu', description='An audio-processing Python library supporting core DAW features', @@ -121,7 +120,8 @@ def has_ext_modules(foo): "Topic :: Multimedia :: Sound/Audio", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9" + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10" ], keywords='audio music sound', python_requires=python_requires, diff --git a/test-requirements.txt b/test-requirements.txt index bfdb5537..5c487db7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -2,4 +2,4 @@ librosa>=0.8.1 numpy>=1.21.2 pytest>=6.2.4 scipy>=1.7.0 -requests +requests>=2.26.0 diff --git a/tests/faust_dsp/polyphonic_sampler.dsp b/tests/faust_dsp/polyphonic_sampler.dsp index 8267244f..2844a087 100644 --- a/tests/faust_dsp/polyphonic_sampler.dsp +++ b/tests/faust_dsp/polyphonic_sampler.dsp @@ -14,32 +14,6 @@ import("stdfaust.lib"); // SAMPLE_R_SEQ = waveform{0.0, 0.0} : !, _; // SAMPLE_LENGTH = 4; // the length of SAMPLE_L_SEQ and SAMPLE_R_SEQ -// The following functions (lagrange_h, lagrangeN, frdtable) -// were written by Dario Sanfilippo and were merged into Faust here: -// https://github.com/grame-cncm/faustlibraries/pull/74 -// They are reproduced here because the latest distribution of Faust -// still doesn't include them. - -declare frdtable author "Dario Sanfilippo"; -declare frdtable copyright "Copyright (C) 2021 Dario Sanfilippo - "; -declare frdtable license "LGPL v3.0 license"; - -lagrange_h(N, idx) = par(n, N + 1, prod(k, N + 1, f(n, k))) - with { - f(n, k) = ((idx - k) * (n != k) + (n == k)) / ((n - k) + (n == k)); - }; - -lagrangeN(N, idx) = lagrange_h(N, idx) , - si.bus(N + 1) : ro.interleave(N + 1, 2) : par(i, N + 1, *) :> _; -frdtable(N, S, init, idx) = - lagrangeN(N, f_idx, par(i, N+1, table(i_idx - int(N / 2) + i))) - with { - table(j) = rdtable(S, init, int(ma.modulo(j, S))); - f_idx = ma.frac(idx) + int(N / 2); - i_idx = int(idx); - }; - // variation of hs_phasor that doesn't loop. It's like a one-shot trigger. my_phasor(tablesize,freq,c) = inc*on_memory : + ~ (_*(1-start_pulse)) : min(1.) *(tablesize) with { @@ -66,7 +40,7 @@ envVol = en.adsr(.002, 0.1, 0.9, .1, gate); ridx = my_phasor(SAMPLE_LENGTH, freq, gate); -process = frdtable(LAGRANGE_ORDER, SAMPLE_LENGTH, SAMPLE_L_SEQ, ridx)*gain*envVol*0.5, - frdtable(LAGRANGE_ORDER, SAMPLE_LENGTH, SAMPLE_R_SEQ, ridx)*gain*envVol*0.5; +process = it.frdtable(LAGRANGE_ORDER, SAMPLE_LENGTH, SAMPLE_L_SEQ, ridx)*gain*envVol*0.5, + it.frdtable(LAGRANGE_ORDER, SAMPLE_LENGTH, SAMPLE_R_SEQ, ridx)*gain*envVol*0.5; // polyphonic DSP code must declare a stereo effect effect = _, _; \ No newline at end of file diff --git a/tests/faust_dsp/polyphonic_wavetable.dsp b/tests/faust_dsp/polyphonic_wavetable.dsp index e225d4fb..d90180cc 100644 --- a/tests/faust_dsp/polyphonic_wavetable.dsp +++ b/tests/faust_dsp/polyphonic_wavetable.dsp @@ -15,32 +15,6 @@ import("stdfaust.lib"); // CYCLE_SEQ = waveform{0.0, 1.0, 0.0, -1.0} : !, _; // CYCLE_LENGTH = 4; // the length of CYCLE_SEQ -// The following functions (lagrange_h, lagrangeN, frdtable) -// were written by Dario Sanfilippo and were merged into Faust here: -// https://github.com/grame-cncm/faustlibraries/pull/74 -// They are reproduced here because the latest distribution of Faust -// still doesn't include them. - -declare frdtable author "Dario Sanfilippo"; -declare frdtable copyright "Copyright (C) 2021 Dario Sanfilippo - "; -declare frdtable license "LGPL v3.0 license"; - -lagrange_h(N, idx) = par(n, N + 1, prod(k, N + 1, f(n, k))) - with { - f(n, k) = ((idx - k) * (n != k) + (n == k)) / ((n - k) + (n == k)); - }; - -lagrangeN(N, idx) = lagrange_h(N, idx) , - si.bus(N + 1) : ro.interleave(N + 1, 2) : par(i, N + 1, *) :> _; -frdtable(N, S, init, idx) = - lagrangeN(N, f_idx, par(i, N+1, table(i_idx - int(N / 2) + i))) - with { - table(j) = rdtable(S, init, int(ma.modulo(j, S))); - f_idx = ma.frac(idx) + int(N / 2); - i_idx = int(idx); - }; - freq = hslider("freq",200,50,1000,0.01); // note pitch gain = hslider("gain",0.1,0,1,0.01); // note velocity gate = button("gate"); // note on/off @@ -49,6 +23,6 @@ envVol = en.adsr(.002, 0.1, 0.9, .1, gate); ridx = os.hs_phasor(CYCLE_LENGTH, freq, envVol == 0.); // or (abs(envVol) < 1e-4) -process = frdtable(LAGRANGE_ORDER, CYCLE_LENGTH, CYCLE_SEQ, ridx)*gain*envVol*0.5 <: _, _; +process = it.frdtable(LAGRANGE_ORDER, CYCLE_LENGTH, CYCLE_SEQ, ridx)*gain*envVol*0.5 <: _, _; // polyphonic DSP code must declare a stereo effect effect = _, _; \ No newline at end of file diff --git a/tests/plugins/RoughRider3.component/Contents/Info.plist b/tests/plugins/RoughRider3.component/Contents/Info.plist new file mode 100644 index 00000000..084a05e9 --- /dev/null +++ b/tests/plugins/RoughRider3.component/Contents/Info.plist @@ -0,0 +1,71 @@ + + + + + AudioComponents + + + description + RoughRider3 + factoryFunction + RoughRider3AUFactory + manufacturer + AuDa + name + Audio Damage, Inc.: RoughRider3 + sandboxSafe + + subtype + RR30 + type + aufx + version + 196864 + + + BuildMachineOSBuild + 20B5012d + CFBundleDisplayName + RoughRider3 + CFBundleExecutable + RoughRider3 + CFBundleIdentifier + com.audiodamage.roughrider3 + CFBundleName + RoughRider3 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 3.1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 3.1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12B5025f + DTPlatformName + macosx + DTPlatformVersion + 11.0 + DTSDKBuild + 20A5384b + DTSDKName + macosx11.0 + DTXcode + 1220 + DTXcodeBuild + 12B5025f + LSMinimumSystemVersion + 10.11 + NSHighResolutionCapable + + NSHumanReadableCopyright + 2020 Audio Damage, Inc. + + diff --git a/tests/plugins/RoughRider3.component/Contents/MacOS/RoughRider3 b/tests/plugins/RoughRider3.component/Contents/MacOS/RoughRider3 new file mode 100755 index 00000000..37869b11 Binary files /dev/null and b/tests/plugins/RoughRider3.component/Contents/MacOS/RoughRider3 differ diff --git a/tests/plugins/RoughRider3.component/Contents/PkgInfo b/tests/plugins/RoughRider3.component/Contents/PkgInfo new file mode 100644 index 00000000..19a9cf67 --- /dev/null +++ b/tests/plugins/RoughRider3.component/Contents/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file diff --git a/tests/plugins/RoughRider3.component/Contents/Resources/RecentFilesMenuTemplate.nib b/tests/plugins/RoughRider3.component/Contents/Resources/RecentFilesMenuTemplate.nib new file mode 100644 index 00000000..cec7f7c7 Binary files /dev/null and b/tests/plugins/RoughRider3.component/Contents/Resources/RecentFilesMenuTemplate.nib differ diff --git a/tests/plugins/RoughRider3.component/Contents/Resources/RoughRider3.rsrc b/tests/plugins/RoughRider3.component/Contents/Resources/RoughRider3.rsrc new file mode 100644 index 00000000..642188f2 Binary files /dev/null and b/tests/plugins/RoughRider3.component/Contents/Resources/RoughRider3.rsrc differ diff --git a/tests/plugins/RoughRider3.component/Contents/_CodeSignature/CodeResources b/tests/plugins/RoughRider3.component/Contents/_CodeSignature/CodeResources new file mode 100644 index 00000000..c3c5503a --- /dev/null +++ b/tests/plugins/RoughRider3.component/Contents/_CodeSignature/CodeResources @@ -0,0 +1,139 @@ + + + + + files + + Resources/RecentFilesMenuTemplate.nib + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + Resources/RoughRider3.rsrc + + da94cePCgwgL/iR9I8Kizi7DqA0= + + + files2 + + Resources/RecentFilesMenuTemplate.nib + + hash2 + + cY60xaOlVk3YMq6aNs9knz7yV86JbdkhaT7rYQG9AIo= + + + Resources/RoughRider3.rsrc + + hash2 + + ++W1jt0kgRn9oqNxIHB/VbdFNOA+fBGl9IKVppBbyOY= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/tests/plugins/RoughRider3.vst/Contents/Info.plist b/tests/plugins/RoughRider3.vst/Contents/Info.plist new file mode 100644 index 00000000..1d6a15e7 --- /dev/null +++ b/tests/plugins/RoughRider3.vst/Contents/Info.plist @@ -0,0 +1,50 @@ + + + + + BuildMachineOSBuild + 20B5012d + CFBundleDisplayName + RoughRider3 + CFBundleExecutable + RoughRider3 + CFBundleIdentifier + com.audiodamage.roughrider3 + CFBundleName + RoughRider3 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 3.1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 3.1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12B5025f + DTPlatformName + macosx + DTPlatformVersion + 11.0 + DTSDKBuild + 20A5384b + DTSDKName + macosx11.0 + DTXcode + 1220 + DTXcodeBuild + 12B5025f + LSMinimumSystemVersion + 10.11 + NSHighResolutionCapable + + NSHumanReadableCopyright + 2020 Audio Damage, Inc. + + diff --git a/tests/plugins/RoughRider3.vst/Contents/MacOS/RoughRider3 b/tests/plugins/RoughRider3.vst/Contents/MacOS/RoughRider3 new file mode 100755 index 00000000..98ca5e05 Binary files /dev/null and b/tests/plugins/RoughRider3.vst/Contents/MacOS/RoughRider3 differ diff --git a/tests/plugins/RoughRider3.vst/Contents/PkgInfo b/tests/plugins/RoughRider3.vst/Contents/PkgInfo new file mode 100644 index 00000000..19a9cf67 --- /dev/null +++ b/tests/plugins/RoughRider3.vst/Contents/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file diff --git a/tests/plugins/RoughRider3.vst/Contents/Resources/RecentFilesMenuTemplate.nib b/tests/plugins/RoughRider3.vst/Contents/Resources/RecentFilesMenuTemplate.nib new file mode 100644 index 00000000..cec7f7c7 Binary files /dev/null and b/tests/plugins/RoughRider3.vst/Contents/Resources/RecentFilesMenuTemplate.nib differ diff --git a/tests/plugins/RoughRider3.vst/Contents/_CodeSignature/CodeResources b/tests/plugins/RoughRider3.vst/Contents/_CodeSignature/CodeResources new file mode 100644 index 00000000..5349bbbf --- /dev/null +++ b/tests/plugins/RoughRider3.vst/Contents/_CodeSignature/CodeResources @@ -0,0 +1,128 @@ + + + + + files + + Resources/RecentFilesMenuTemplate.nib + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + + files2 + + Resources/RecentFilesMenuTemplate.nib + + hash2 + + cY60xaOlVk3YMq6aNs9knz7yV86JbdkhaT7rYQG9AIo= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/tests/plugins/RoughRider3.vst3/Contents/Info.plist b/tests/plugins/RoughRider3.vst3/Contents/Info.plist new file mode 100644 index 00000000..1d6a15e7 --- /dev/null +++ b/tests/plugins/RoughRider3.vst3/Contents/Info.plist @@ -0,0 +1,50 @@ + + + + + BuildMachineOSBuild + 20B5012d + CFBundleDisplayName + RoughRider3 + CFBundleExecutable + RoughRider3 + CFBundleIdentifier + com.audiodamage.roughrider3 + CFBundleName + RoughRider3 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 3.1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 3.1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12B5025f + DTPlatformName + macosx + DTPlatformVersion + 11.0 + DTSDKBuild + 20A5384b + DTSDKName + macosx11.0 + DTXcode + 1220 + DTXcodeBuild + 12B5025f + LSMinimumSystemVersion + 10.11 + NSHighResolutionCapable + + NSHumanReadableCopyright + 2020 Audio Damage, Inc. + + diff --git a/tests/plugins/RoughRider3.vst3/Contents/MacOS/RoughRider3 b/tests/plugins/RoughRider3.vst3/Contents/MacOS/RoughRider3 new file mode 100755 index 00000000..544429d7 Binary files /dev/null and b/tests/plugins/RoughRider3.vst3/Contents/MacOS/RoughRider3 differ diff --git a/tests/plugins/RoughRider3.vst3/Contents/PkgInfo b/tests/plugins/RoughRider3.vst3/Contents/PkgInfo new file mode 100644 index 00000000..19a9cf67 --- /dev/null +++ b/tests/plugins/RoughRider3.vst3/Contents/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file diff --git a/tests/plugins/RoughRider3.vst3/Contents/Resources/RecentFilesMenuTemplate.nib b/tests/plugins/RoughRider3.vst3/Contents/Resources/RecentFilesMenuTemplate.nib new file mode 100644 index 00000000..cec7f7c7 Binary files /dev/null and b/tests/plugins/RoughRider3.vst3/Contents/Resources/RecentFilesMenuTemplate.nib differ diff --git a/tests/plugins/RoughRider3.vst3/Contents/_CodeSignature/CodeResources b/tests/plugins/RoughRider3.vst3/Contents/_CodeSignature/CodeResources new file mode 100644 index 00000000..5349bbbf --- /dev/null +++ b/tests/plugins/RoughRider3.vst3/Contents/_CodeSignature/CodeResources @@ -0,0 +1,128 @@ + + + + + files + + Resources/RecentFilesMenuTemplate.nib + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + + files2 + + Resources/RecentFilesMenuTemplate.nib + + hash2 + + cY60xaOlVk3YMq6aNs9knz7yV86JbdkhaT7rYQG9AIo= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/tests/plugins/ValhallaFreqEcho.component/Contents/Info.plist b/tests/plugins/ValhallaFreqEcho.component/Contents/Info.plist new file mode 100644 index 00000000..eefd45c3 --- /dev/null +++ b/tests/plugins/ValhallaFreqEcho.component/Contents/Info.plist @@ -0,0 +1,71 @@ + + + + + AudioComponents + + + description + ValhallaFreqEcho + factoryFunction + ValhallaFreqEchoAUFactory + manufacturer + oDin + name + Valhalla DSP, LLC: ValhallaFreqEcho + sandboxSafe + + subtype + FqEh + type + aufx + version + 66053 + + + BuildMachineOSBuild + 20C69 + CFBundleDisplayName + ValhallaFreqEcho + CFBundleExecutable + ValhallaFreqEcho + CFBundleIdentifier + com.ValhallaDSP.ValhallaFreqEcho + CFBundleName + ValhallaFreqEcho + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.2.5 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.2.5 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12C33 + DTPlatformName + macosx + DTPlatformVersion + 11.1 + DTSDKBuild + 20C63 + DTSDKName + macosx11.1 + DTXcode + 1230 + DTXcodeBuild + 12C33 + LSMinimumSystemVersion + 10.9 + NSHighResolutionCapable + + NSHumanReadableCopyright + Copyright (c) 2020, Valhalla DSP, LLC. All Rights Reserved. + + diff --git a/tests/plugins/ValhallaFreqEcho.component/Contents/MacOS/ValhallaFreqEcho b/tests/plugins/ValhallaFreqEcho.component/Contents/MacOS/ValhallaFreqEcho new file mode 100755 index 00000000..6e2ab0ca Binary files /dev/null and b/tests/plugins/ValhallaFreqEcho.component/Contents/MacOS/ValhallaFreqEcho differ diff --git a/tests/plugins/ValhallaFreqEcho.component/Contents/PkgInfo b/tests/plugins/ValhallaFreqEcho.component/Contents/PkgInfo new file mode 100644 index 00000000..19a9cf67 --- /dev/null +++ b/tests/plugins/ValhallaFreqEcho.component/Contents/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file diff --git a/tests/plugins/ValhallaFreqEcho.component/Contents/Resources/RecentFilesMenuTemplate.nib b/tests/plugins/ValhallaFreqEcho.component/Contents/Resources/RecentFilesMenuTemplate.nib new file mode 100644 index 00000000..cec7f7c7 Binary files /dev/null and b/tests/plugins/ValhallaFreqEcho.component/Contents/Resources/RecentFilesMenuTemplate.nib differ diff --git a/tests/plugins/ValhallaFreqEcho.component/Contents/Resources/ValhallaFreqEcho.rsrc b/tests/plugins/ValhallaFreqEcho.component/Contents/Resources/ValhallaFreqEcho.rsrc new file mode 100644 index 00000000..7ff3979f Binary files /dev/null and b/tests/plugins/ValhallaFreqEcho.component/Contents/Resources/ValhallaFreqEcho.rsrc differ diff --git a/tests/plugins/ValhallaFreqEcho.component/Contents/_CodeSignature/CodeResources b/tests/plugins/ValhallaFreqEcho.component/Contents/_CodeSignature/CodeResources new file mode 100644 index 00000000..bf61182c --- /dev/null +++ b/tests/plugins/ValhallaFreqEcho.component/Contents/_CodeSignature/CodeResources @@ -0,0 +1,147 @@ + + + + + files + + Resources/RecentFilesMenuTemplate.nib + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + Resources/ValhallaFreqEcho.rsrc + + 61YaEYpzM5lVm/BskJRzwpfKycw= + + + files2 + + Resources/RecentFilesMenuTemplate.nib + + hash + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + hash2 + + cY60xaOlVk3YMq6aNs9knz7yV86JbdkhaT7rYQG9AIo= + + + Resources/ValhallaFreqEcho.rsrc + + hash + + 61YaEYpzM5lVm/BskJRzwpfKycw= + + hash2 + + mNLTV5ipgRXaNMHMQXGxROaA5DQKhWvqLmnnr1ssDZo= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/tests/plugins/ValhallaFreqEcho.vst/Contents/Info.plist b/tests/plugins/ValhallaFreqEcho.vst/Contents/Info.plist new file mode 100644 index 00000000..fdf16d1e --- /dev/null +++ b/tests/plugins/ValhallaFreqEcho.vst/Contents/Info.plist @@ -0,0 +1,50 @@ + + + + + BuildMachineOSBuild + 20C69 + CFBundleDisplayName + ValhallaFreqEcho + CFBundleExecutable + ValhallaFreqEcho + CFBundleIdentifier + com.ValhallaDSP.ValhallaFreqEcho + CFBundleName + ValhallaFreqEcho + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.2.5 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.2.5 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12C33 + DTPlatformName + macosx + DTPlatformVersion + 11.1 + DTSDKBuild + 20C63 + DTSDKName + macosx11.1 + DTXcode + 1230 + DTXcodeBuild + 12C33 + LSMinimumSystemVersion + 10.9 + NSHighResolutionCapable + + NSHumanReadableCopyright + Copyright (c) 2020, Valhalla DSP, LLC. All Rights Reserved. + + diff --git a/tests/plugins/ValhallaFreqEcho.vst/Contents/MacOS/ValhallaFreqEcho b/tests/plugins/ValhallaFreqEcho.vst/Contents/MacOS/ValhallaFreqEcho new file mode 100755 index 00000000..387902a4 Binary files /dev/null and b/tests/plugins/ValhallaFreqEcho.vst/Contents/MacOS/ValhallaFreqEcho differ diff --git a/tests/plugins/ValhallaFreqEcho.vst/Contents/PkgInfo b/tests/plugins/ValhallaFreqEcho.vst/Contents/PkgInfo new file mode 100644 index 00000000..19a9cf67 --- /dev/null +++ b/tests/plugins/ValhallaFreqEcho.vst/Contents/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file diff --git a/tests/plugins/ValhallaFreqEcho.vst/Contents/Resources/RecentFilesMenuTemplate.nib b/tests/plugins/ValhallaFreqEcho.vst/Contents/Resources/RecentFilesMenuTemplate.nib new file mode 100644 index 00000000..cec7f7c7 Binary files /dev/null and b/tests/plugins/ValhallaFreqEcho.vst/Contents/Resources/RecentFilesMenuTemplate.nib differ diff --git a/tests/plugins/ValhallaFreqEcho.vst/Contents/_CodeSignature/CodeResources b/tests/plugins/ValhallaFreqEcho.vst/Contents/_CodeSignature/CodeResources new file mode 100644 index 00000000..7fbf163e --- /dev/null +++ b/tests/plugins/ValhallaFreqEcho.vst/Contents/_CodeSignature/CodeResources @@ -0,0 +1,132 @@ + + + + + files + + Resources/RecentFilesMenuTemplate.nib + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + + files2 + + Resources/RecentFilesMenuTemplate.nib + + hash + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + hash2 + + cY60xaOlVk3YMq6aNs9knz7yV86JbdkhaT7rYQG9AIo= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/tests/plugins/ValhallaFreqEcho.vst3/Contents/Info.plist b/tests/plugins/ValhallaFreqEcho.vst3/Contents/Info.plist new file mode 100644 index 00000000..fdf16d1e --- /dev/null +++ b/tests/plugins/ValhallaFreqEcho.vst3/Contents/Info.plist @@ -0,0 +1,50 @@ + + + + + BuildMachineOSBuild + 20C69 + CFBundleDisplayName + ValhallaFreqEcho + CFBundleExecutable + ValhallaFreqEcho + CFBundleIdentifier + com.ValhallaDSP.ValhallaFreqEcho + CFBundleName + ValhallaFreqEcho + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.2.5 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.2.5 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12C33 + DTPlatformName + macosx + DTPlatformVersion + 11.1 + DTSDKBuild + 20C63 + DTSDKName + macosx11.1 + DTXcode + 1230 + DTXcodeBuild + 12C33 + LSMinimumSystemVersion + 10.9 + NSHighResolutionCapable + + NSHumanReadableCopyright + Copyright (c) 2020, Valhalla DSP, LLC. All Rights Reserved. + + diff --git a/tests/plugins/ValhallaFreqEcho.vst3/Contents/MacOS/ValhallaFreqEcho b/tests/plugins/ValhallaFreqEcho.vst3/Contents/MacOS/ValhallaFreqEcho new file mode 100755 index 00000000..bf6db3ef Binary files /dev/null and b/tests/plugins/ValhallaFreqEcho.vst3/Contents/MacOS/ValhallaFreqEcho differ diff --git a/tests/plugins/ValhallaFreqEcho.vst3/Contents/PkgInfo b/tests/plugins/ValhallaFreqEcho.vst3/Contents/PkgInfo new file mode 100644 index 00000000..19a9cf67 --- /dev/null +++ b/tests/plugins/ValhallaFreqEcho.vst3/Contents/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file diff --git a/tests/plugins/ValhallaFreqEcho.vst3/Contents/Resources/RecentFilesMenuTemplate.nib b/tests/plugins/ValhallaFreqEcho.vst3/Contents/Resources/RecentFilesMenuTemplate.nib new file mode 100644 index 00000000..cec7f7c7 Binary files /dev/null and b/tests/plugins/ValhallaFreqEcho.vst3/Contents/Resources/RecentFilesMenuTemplate.nib differ diff --git a/tests/plugins/ValhallaFreqEcho.vst3/Contents/_CodeSignature/CodeResources b/tests/plugins/ValhallaFreqEcho.vst3/Contents/_CodeSignature/CodeResources new file mode 100644 index 00000000..7fbf163e --- /dev/null +++ b/tests/plugins/ValhallaFreqEcho.vst3/Contents/_CodeSignature/CodeResources @@ -0,0 +1,132 @@ + + + + + files + + Resources/RecentFilesMenuTemplate.nib + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + + files2 + + Resources/RecentFilesMenuTemplate.nib + + hash + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + hash2 + + cY60xaOlVk3YMq6aNs9knz7yV86JbdkhaT7rYQG9AIo= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/tests/test_faust_dx7.py b/tests/test_faust_dx7.py index 1682066c..1d2cd650 100644 --- a/tests/test_faust_dx7.py +++ b/tests/test_faust_dx7.py @@ -48,5 +48,6 @@ def _test_faust_dx7(algorithm=0, num_voices=8, buffer_size=2048): def test_faust_dx7(): - for i in range(32): + # just test 2 out of 32 to save some time + for i in range(2): _test_faust_dx7(algorithm=i, num_voices=8, buffer_size=2048) diff --git a/tests/test_faust_poly_sampler.py b/tests/test_faust_poly_sampler.py index 9d7b3cfa..956c0852 100644 --- a/tests/test_faust_poly_sampler.py +++ b/tests/test_faust_poly_sampler.py @@ -2,6 +2,9 @@ BUFFER_SIZE = 1024 +# todo: this example is bad because it turns a waveform into a very long string, +# which takes a long time to compile as Faust code. It would be better to +# find a way to use the soundfile primitive with a wavecycle. def _test_faust_poly_sampler(sample_seq, output_path, lagrange_order=4): engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE) diff --git a/tests/test_faust_poly_wavetable.py b/tests/test_faust_poly_wavetable.py index eff1bf44..9f9a461d 100644 --- a/tests/test_faust_poly_wavetable.py +++ b/tests/test_faust_poly_wavetable.py @@ -2,6 +2,9 @@ BUFFER_SIZE = 1 +# todo: this example is bad because it turns a waveform into a very long string, +# which takes a long time to compile as Faust code. It would be better to +# find a way to use the soundfile primitive with a wavecycle. def _test_faust_poly_wavetable(wavecycle, output_path, lagrange_order=4): engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE) diff --git a/tests/test_faust_processor.py b/tests/test_faust_processor.py index 4ff2409c..28f631d8 100644 --- a/tests/test_faust_processor.py +++ b/tests/test_faust_processor.py @@ -30,8 +30,58 @@ def test_faust_passthrough(): # Todo: the last sample is inaccurate by a little bit # So we trim the last sample and compare - data = data[:,:audio.shape[1]-1] - audio = audio[:,:audio.shape[1]-1] + data = data[:,:audio.shape[1]] + audio = audio[:,:audio.shape[1]] + + assert(np.allclose(data, audio, atol=1e-07)) + + # do the same for noise + data = np.random.rand(2, int(SAMPLE_RATE*(DURATION+.1))) + playback_processor.set_data(data) + render(engine) + audio = engine.get_audio() + + data = data[:,:audio.shape[1]] + audio = audio[:,:audio.shape[1]] + + assert(np.allclose(data, audio, atol=1e-07)) + + +def test_faust_multichannel_in_out(): + + DURATION = 5.1 + + engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE) + + numChannels = 9 + underscores = ",".join('_'*numChannels) + + data = np.sin(np.linspace(0, 4000, num=int(44100*(DURATION+.1)))) + data = np.stack([data for _ in range(numChannels)]) + + playback_processor = engine.make_playback_processor("playback", data) + + faust_processor = engine.make_faust_processor("faust") + assert(faust_processor.set_dsp_string(f'process = {underscores};')) + assert(faust_processor.compile()) + + # print(faust_processor.get_parameters_description()) + + graph = [ + (playback_processor, []), + (faust_processor, ["playback"]) + ] + + assert(engine.load_graph(graph)) + + render(engine, file_path='output/test_faust_multichannel_in_out.wav') + + audio = engine.get_audio() + + # Todo: the last sample is inaccurate by a little bit + # So we trim the last sample and compare + data = data[:,:audio.shape[1]] + audio = audio[:,:audio.shape[1]] assert(np.allclose(data, audio, atol=1e-07)) @@ -118,7 +168,6 @@ def test_faust_automation(): dsp_path = abspath("faust_dsp/two_stereo_inputs_filter.dsp") faust_processor = engine.make_faust_processor("faust") - faust_processor.set_dsp(dsp_path) assert(faust_processor.set_dsp(dsp_path)) assert(faust_processor.compile()) @@ -138,3 +187,66 @@ def test_faust_automation(): assert(engine.load_graph(graph)) render(engine, file_path='output/test_faust_automation.wav') + +def test_faust_ambisonics_encoding(ambisonics_order=2, set_data=False): + + engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE) + + data = load_audio_file("assets/575854__yellowtree__d-b-funk-loop.wav") + + data = data.mean(axis=0, keepdims=True) + + assert(data.ndim == 2) + assert(data.shape[0] == 1) + + playback_processor = engine.make_playback_processor("playback", data) + + if set_data: + playback_processor.set_data(data) + + faust_processor = engine.make_faust_processor("faust") + + dsp_code = f""" + + import("stdfaust.lib"); + + L = {ambisonics_order}; + + encoder3Dxyz(n, x, tx, ty, tz) = ho.encoder3D(n, signal, angle, elevation) + with {{ + max_gain = 10.; // todo: user can pick this. + // 10 indicates don't multiply the signal by more than 10 even if it's very close in 3D + xz_square = tx*tx+tz*tz; + signal = x / max(1./max_gain, sqrt(xz_square+ty*ty)); + + angle = atan2(tx, -tz); + elevation = atan2(ty, sqrt(xz_square)); + }}; + + // todo: ma.EPSILON doesn't work on Linux? + eps = .00001; + //eps = ma.EPSILON; + process(sig) = encoder3Dxyz(L, sig, + hslider("tx", 1., -10000., 10000., eps), + hslider("ty", 1., -10000., 10000., eps), + hslider("tz", 1., -10000., 10000., eps) + ); + + """ + + faust_processor.set_dsp_string(dsp_code) + assert(faust_processor.compile()) + + # print(faust_processor.get_parameters_description()) + + assert(faust_processor.get_num_input_channels() == 1) + assert(faust_processor.get_num_output_channels() == (ambisonics_order+1)**2) + + graph = [ + (playback_processor, []), + (faust_processor, ["playback"]) + ] + + assert(engine.load_graph(graph)) + + render(engine, file_path='output/test_faust_ambisonics_encoding.wav') \ No newline at end of file diff --git a/tests/test_faust_soundfile.py b/tests/test_faust_soundfile.py index 746cd9ef..3c3169f6 100644 --- a/tests/test_faust_soundfile.py +++ b/tests/test_faust_soundfile.py @@ -44,6 +44,43 @@ def _test_faust_soundfile(sample_seq, output_path, sound_choice=0): audio = engine.get_audio() assert(np.mean(np.abs(audio)) > .01) +def _test_faust_soundfile_multichannel(output_path): + + engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE) + + faust_processor = engine.make_faust_processor("faust") + + dsp_path = abspath("faust_dsp/soundfile.dsp") + + numChannels = 9 + + sample_seq = np.sin(np.linspace(0, 4000, num=int(44100*5.0))) + sample_seq = np.stack([sample_seq for _ in range(numChannels)]) + + # set_soundfiles + soundfiles = { + 'mySound': [sample_seq] + } + faust_processor.set_soundfiles(soundfiles) + + underscores = ",".join('_'*numChannels) + dsp_string = f'process = 0,_~+(1):soundfile("mySound",{numChannels}):!,!,{underscores};' + assert(faust_processor.set_dsp_string(dsp_string)) + assert(faust_processor.compile()) + # desc = faust_processor.get_parameters_description() + # for par in desc: + # print(par) + + graph = [ + (faust_processor, []) + ] + + assert(engine.load_graph(graph)) + render(engine, file_path='output/'+output_path, duration=3.) + + audio = engine.get_audio() + assert(np.mean(np.abs(audio)) > .01) + def download_grand_piano(): """Download the dataset if it's missing""" @@ -122,4 +159,5 @@ def test_faust_soundfile_cymbal(): sample_seq = load_audio_file("assets/60988__folktelemetry__crash-fast-14.wav") _test_faust_soundfile(sample_seq, 'test_faust_soundfile_0.wav', sound_choice=0) _test_faust_soundfile(sample_seq, 'test_faust_soundfile_1.wav', sound_choice=1) - _test_faust_soundfile(sample_seq, 'test_faust_soundfile_2.wav', sound_choice=2) \ No newline at end of file + _test_faust_soundfile(sample_seq, 'test_faust_soundfile_2.wav', sound_choice=2) + _test_faust_soundfile_multichannel('test_faust_soundfile_3.wav') \ No newline at end of file diff --git a/tests/test_playback_processor.py b/tests/test_playback_processor.py index 672a0262..9f18f131 100644 --- a/tests/test_playback_processor.py +++ b/tests/test_playback_processor.py @@ -1,6 +1,6 @@ from utils import * -BUFFER_SIZE = 16 +BUFFER_SIZE = 1 def test_playback(set_data=False): @@ -25,3 +25,14 @@ def test_playback(set_data=False): output = engine.get_audio() wavfile.write('output/test_playback.wav', SAMPLE_RATE, output.transpose()) + + # do the same for noise + data = np.random.rand(2, int(SAMPLE_RATE*(DURATION+.1))) + playback_processor.set_data(data) + render(engine) + audio = engine.get_audio() + + data = data[:,:audio.shape[1]] + audio = audio[:,:audio.shape[1]] + + assert(np.allclose(data, audio, atol=1e-07)) diff --git a/tests/test_plugins.py b/tests/test_plugins.py index cf0aecf8..b09e5ed7 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -1,32 +1,38 @@ from utils import * import platform +import os.path +import os MY_SYSTEM = platform.system() # "Darwin" is macOS. "Windows" is Windows. BUFFER_SIZE = 16 -def test_plugin_effect(set_data=False): +def _test_stereo_plugin_effect(plugin_path, expected_num_inputs): - if MY_SYSTEM not in ["Darwin", "Windows"]: - # We don't test LV2 plugins on Linux yet. + # Skip .component plugins on GitHub Actions workflows + if os.getenv("CIBW_TEST_REQUIRES") and plugin_path.endswith('.component'): return + if MY_SYSTEM == 'Darwin': + # macOS treats .component and .vst3 as directories + assert(os.path.isdir(plugin_path)) + else: + assert(os.path.isfile(plugin_path)) + DURATION = 5. engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE) data = load_audio_file("assets/575854__yellowtree__d-b-funk-loop.wav", DURATION+.1) - playback_processor = engine.make_playback_processor("playback", data) - if set_data: - playback_processor.set_data(data) - - plugin_name = "DimensionExpander.vst" if MY_SYSTEM == "Darwin" else "Dimension Expander_x64.dll" + playback_processor = engine.make_playback_processor("playback", data) - effect = engine.make_plugin_processor("effect", abspath("plugins/"+plugin_name)) + effect = engine.make_plugin_processor("effect", plugin_path) # print(effect.get_plugin_parameters_description()) + assert(effect.get_num_input_channels() == expected_num_inputs) + assert(effect.get_num_output_channels() == 2) graph = [ (playback_processor, []), @@ -35,12 +41,42 @@ def test_plugin_effect(set_data=False): assert(engine.load_graph(graph)) - render(engine, file_path='output/test_plugin_effect.wav', duration=DURATION) + plugin_basename = os.path.basename(plugin_path) + + render(engine, file_path=f'output/test_plugin_effect_{plugin_basename}.wav', duration=DURATION) + + # check that it's non-silent + audio = engine.get_audio() + assert(np.mean(np.abs(audio)) > .05) + +def test_stereo_plugin_effects(): + + if MY_SYSTEM not in ["Darwin", "Windows"]: + # todo: we should test LV2 plugins on Linux. + return + + plugin_paths = [] + + if MY_SYSTEM == 'Darwin': + # todo: the Valhalla Freq Echo plugins sometimes work and sometimes just output NAN. + # plugin_paths.append((abspath("plugins/ValhallaFreqEcho.vst"), 2)) + # plugin_paths.append((abspath("plugins/ValhallaFreqEcho.vst3"), 2)) + # plugin_paths.append((abspath("plugins/ValhallaFreqEcho.component"), 2)) + + # RoughRider has an optional mono sidechain input + plugin_paths.append((abspath("plugins/RoughRider3.vst"), 3)) + plugin_paths.append((abspath("plugins/RoughRider3.vst3"), 3)) + plugin_paths.append((abspath("plugins/RoughRider3.component"), 3)) + elif MY_SYSTEM == 'Windows': + plugin_paths.append((abspath("plugins/Dimension Expander_x64.dll"), 2)) + + for plugin_args in plugin_paths: + _test_stereo_plugin_effect(*plugin_args) def test_plugin_instrument(): if MY_SYSTEM not in ["Darwin", "Windows"]: - # We don't test LV2 plugins on Linux yet. + # todo: we should test LV2 plugins on Linux. return DURATION = 5. @@ -53,7 +89,8 @@ def test_plugin_instrument(): # print(synth.get_plugin_parameters_description()) - # (MIDI note, velocity, start sec, duration sec) + # (MIDI note, velocity, start sec, duration sec) + # todo: on macOS the note is skipped if it starts at exactly 0.0. synth.add_midi_note(60, 60, 0.0, .25) synth.add_midi_note(64, 80, 0.5, .5) synth.add_midi_note(67, 127, 0.75, .5) @@ -93,6 +130,9 @@ def test_plugin_serum(): assert(synth.set_parameter(0, synth.get_parameter(0))) assert(synth.set_automation(0, np.array([synth.get_parameter(0)]))) + assert(synth.get_num_input_channels() == 0) + assert(synth.get_num_output_channels() == 2) + # (MIDI note, velocity, start sec, duration sec) synth.add_midi_note(60, 60, 0.0, .25) synth.add_midi_note(64, 80, 0.5, .5) @@ -110,3 +150,115 @@ def test_plugin_serum(): audio = engine.get_audio() assert(not np.allclose(audio*0., audio, atol=1e-07)) + + +def _test_plugin_goodhertz_sidechain(do_sidechain=True): + + if MY_SYSTEM not in ["Windows"]: + # We don't Goodhertz on platforms other than Windows. + return + + plugin_path = "C:/VSTPlugIns/Goodhertz/Ghz Vulf Compressor 3.vst3" + + if not isfile(plugin_path): + return + + engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE) + + DURATION = 5.1 + + vocals = load_audio_file("assets/575854__yellowtree__d-b-funk-loop.wav", duration=DURATION) + drums = load_audio_file("assets/60988__folktelemetry__crash-fast-14.wav", duration=DURATION) + + drums *= .1 + + vocals_processor = engine.make_playback_processor("vocals", vocals) + drums_processor = engine.make_playback_processor("drums", drums) + + plugin = engine.make_plugin_processor("plugin", plugin_path) + + # plugin.set_parameter(2, 1.) + # plugin.set_parameter(5, .1) + # plugin.set_parameter(13, 1.) + # plugin.set_parameter(15, 1.) + # plugin.set_parameter(18, 1.) + + if do_sidechain: + + plugin.set_parameter(19, 0.5) + # parameter 19 is the "External Sidechain" for Vulf Compressor. In the UI, click the three dots, which opens the panel + # Then look for "External Sidechain" and set it to 50%. + + graph = [ + (vocals_processor, []), + (drums_processor, []), + (plugin, ["vocals", "drums"]) + ] + else: + graph = [ + (vocals_processor, []), + (plugin, ["vocals"]) + ] + + assert(engine.load_graph(graph)) + + sidechain_on = "on" if do_sidechain else "off" + + file_path = f'output/test_plugin_goodhertz_sidechain_{sidechain_on}.wav' + + render(engine, file_path=file_path, duration=DURATION) + + audio = engine.get_audio() + assert(not np.allclose(audio*0., audio, atol=1e-07)) + + +def test_plugin_goodhertz_sidechain(): + _test_plugin_goodhertz_sidechain(do_sidechain=True) + _test_plugin_goodhertz_sidechain(do_sidechain=False) + + +# def test_plugin_effect_ambisonics(set_data=False): + +# if MY_SYSTEM != "Windows": +# return + +# DURATION = 5. + +# engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE) + +# data = load_audio_file("assets/575854__yellowtree__d-b-funk-loop.wav", DURATION+.1) +# playback_processor = engine.make_playback_processor("playback", data) + +# data = data.mean(axis=0, keepdims=True) + +# if set_data: +# playback_processor.set_data(data) + +# plugin_name = "sparta_ambiENC.vst" if MY_SYSTEM == "Darwin" else "sparta_ambiENC.dll" + +# plugin_path = abspath("plugins/"+plugin_name) +# if not isfile(plugin_path): +# return + +# effect = engine.make_plugin_processor("effect", plugin_path) + +# effect.set_parameter(0, 1) + +# assert(effect.get_num_input_channels() == 64) +# assert(effect.get_num_output_channels() == 64) + +# # for par in effect.get_plugin_parameters_description(): +# # print(par) + +# graph = [ +# (playback_processor, []), +# (effect, ["playback"]) +# ] + +# assert(engine.load_graph(graph)) + +# render(engine, file_path='output/test_plugin_effect_ambisonics.wav', duration=DURATION) + +# audio = engine.get_audio() + +# assert(effect.get_num_output_channels() == audio.shape[0]) diff --git a/tests/test_sampler.py b/tests/test_sampler.py index cd1f224f..62fac537 100644 --- a/tests/test_sampler.py +++ b/tests/test_sampler.py @@ -1,6 +1,6 @@ from utils import * -BUFFER_SIZE = 4096*4 +BUFFER_SIZE = 512 def test_sampler(set_data=False): @@ -22,6 +22,8 @@ def get_par_index(desc, par_name): if set_data: sampler_processor.set_data(data) + assert(sampler_processor.get_num_output_channels() == 2) + desc = sampler_processor.get_parameters_description() # print(desc) diff --git a/tests/utils.py b/tests/utils.py index 8c40f63a..45e738de 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,11 +1,16 @@ -import pytest -import librosa from scipy.io import wavfile from os.path import abspath, isfile import numpy as np import pathlib import random +USE_LIBROSA = True +try: + import librosa +except ModuleNotFoundError as e: + import soundfile + USE_LIBROSA = False + import dawdreamer as daw SAMPLE_RATE = 44100 @@ -17,16 +22,22 @@ def make_sine(freq: float, duration: float, sr=SAMPLE_RATE): def load_audio_file(file_path, duration=None): - #import soundfile - #sig, rate = soundfile.read(file_path, always_2d=True, samplerate=SAMPLE_RATE, stop=int(duration*SAMPLE_RATE)) + if USE_LIBROSA: + + sig, rate = librosa.load(file_path, duration=duration, mono=False, sr=SAMPLE_RATE) + assert(rate == SAMPLE_RATE) + + else: + # todo: soundfile doesn't allow you to specify the duration or sample rate, unless the file is RAW + sig, rate = soundfile.read(file_path, always_2d=True) + sig = sig.T - sig, rate = librosa.load(file_path, duration=duration, mono=False, sr=SAMPLE_RATE) - assert(rate == SAMPLE_RATE) return sig + def render(engine, file_path=None, duration=5.): - engine.render(duration) + assert(engine.render(duration)) output = engine.get_audio() diff --git a/thirdparty/JUCE_6 b/thirdparty/JUCE_6 index 90e8da0c..9055820a 160000 --- a/thirdparty/JUCE_6 +++ b/thirdparty/JUCE_6 @@ -1 +1 @@ -Subproject commit 90e8da0cfb54ac593cdbed74c3d0c9b09bad3a9f +Subproject commit 9055820a30770479e30994c9a91029474cf60f7d diff --git a/thirdparty/faust b/thirdparty/faust index 6275eabb..0c6d52d9 160000 --- a/thirdparty/faust +++ b/thirdparty/faust @@ -1 +1 @@ -Subproject commit 6275eabbde7bc736c69bf44278bd343d27e90f94 +Subproject commit 0c6d52d9c20f839481a5c35f9547aa26c778e2c1 diff --git a/thirdparty/libfaust/darwin-x64/Release/libfaust.a b/thirdparty/libfaust/darwin-x64/Release/libfaust.a index b32e8cc7..10b777d8 100755 Binary files a/thirdparty/libfaust/darwin-x64/Release/libfaust.a and b/thirdparty/libfaust/darwin-x64/Release/libfaust.a differ diff --git a/thirdparty/libfaust/win-x64/Release/bin/faust.dll b/thirdparty/libfaust/win-x64/Release/bin/faust.dll index 63a624b9..0fc62daf 100644 Binary files a/thirdparty/libfaust/win-x64/Release/bin/faust.dll and b/thirdparty/libfaust/win-x64/Release/bin/faust.dll differ diff --git a/thirdparty/libfaust/win-x64/Release/lib/faust.lib b/thirdparty/libfaust/win-x64/Release/lib/faust.lib index c441c9a6..78cd1bc3 100644 Binary files a/thirdparty/libfaust/win-x64/Release/lib/faust.lib and b/thirdparty/libfaust/win-x64/Release/lib/faust.lib differ