diff --git a/Builds/LinuxMakefile/Makefile b/Builds/LinuxMakefile/Makefile
index 6b3385f8..6410f4ba 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=0.6.1" "-DJUCE_APP_VERSION_HEX=0x601" $(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 := $(DEPFLAGS) "-DLINUX=1" "-DDEBUG=1" "-D_DEBUG=1" "-DPIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==" "-DSAMPLER_SKIP_UI" "-DJUCE_MODAL_LOOPS_PERMITTED" "-DHAVE_LIBSAMPLERATE" "-DUSE_BUILTIN_FFT" "-DUSE_PTHREADS" "-DBUILD_DAWDREAMER_FAUST" "-DBUILD_DAWDREAMER_RUBBERBAND" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=0.6.2" "-DJUCE_APP_VERSION_HEX=0x602" $(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=0.6.1" "-DJUCE_APP_VERSION_HEX=0x601" $(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 := $(DEPFLAGS) "-DLINUX=1" "-DNDEBUG=1" "-DPIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==" "-DSAMPLER_SKIP_UI" "-DJUCE_MODAL_LOOPS_PERMITTED" "-DHAVE_LIBSAMPLERATE" "-DUSE_BUILTIN_FFT" "-DUSE_PTHREADS" "-DBUILD_DAWDREAMER_FAUST" "-DBUILD_DAWDREAMER_RUBBERBAND" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=0.6.2" "-DJUCE_APP_VERSION_HEX=0x602" $(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 cef663ba..76f260b7 100644
--- a/Builds/MacOSX/DawDreamer.xcodeproj/project.pbxproj
+++ b/Builds/MacOSX/DawDreamer.xcodeproj/project.pbxproj
@@ -61,6 +61,7 @@
C98D426E06BA4F72F0E4C8DB /* Resampler.cpp */ = {isa = PBXBuildFile; fileRef = EF9722353A0A2BDA8376FCC6; };
CAE5EFB597BE247AE021B656 /* StretcherImpl.cpp */ = {isa = PBXBuildFile; fileRef = CF40530FC23B166C749C7CD4; };
D8E2F775028097109496AAA1 /* Cocoa.framework */ = {isa = PBXBuildFile; fileRef = 7EC708BB8670BC4B971F031E; };
+ D915E61C72D98F0B97F31690 /* StandalonePluginWindow.h */ = {isa = PBXBuildFile; fileRef = 44A26898F037A3E6B43EB911; };
DFE780784ED99EACC1B654A6 /* include_juce_gui_extra.mm */ = {isa = PBXBuildFile; fileRef = 06AE4EC72C9D2D0775EF879E; };
E358C00D8D92D35AFBC9944C /* FaustProcessor.cpp */ = {isa = PBXBuildFile; fileRef = BFD142EBB8AFBADC8AB04A8A; };
E6C27C09FEC5B3BBEC75F414 /* CoreAudioKit.framework */ = {isa = PBXBuildFile; fileRef = E4E23DF360EE14C337676E2D; };
@@ -119,6 +120,7 @@
3AAA87ED81FE59EA46C6FE62 /* juce_core */ /* juce_core */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_core; path = ../../JuceLibraryCode/modules/juce_core; sourceTree = SOURCE_ROOT; };
3C4A562AA5734126DF3D041A /* SpectralDifferenceAudioCurve.cpp */ /* SpectralDifferenceAudioCurve.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SpectralDifferenceAudioCurve.cpp; path = ../../thirdparty/rubberband/src/audiocurves/SpectralDifferenceAudioCurve.cpp; sourceTree = SOURCE_ROOT; };
41F9CCC5586A494C6A26B0B1 /* samplerate.h */ /* samplerate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = samplerate.h; path = ../../thirdparty/libsamplerate/include/samplerate.h; sourceTree = SOURCE_ROOT; };
+ 44A26898F037A3E6B43EB911 /* StandalonePluginWindow.h */ /* StandalonePluginWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = StandalonePluginWindow.h; path = ../../Source/StandalonePluginWindow.h; sourceTree = SOURCE_ROOT; };
45D38E4893A53D1263DF4E82 /* CompoundAudioCurve.cpp */ /* CompoundAudioCurve.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = CompoundAudioCurve.cpp; path = ../../thirdparty/rubberband/src/audiocurves/CompoundAudioCurve.cpp; sourceTree = SOURCE_ROOT; };
4782971C3066F785B5354384 /* DataModel.h */ /* DataModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DataModel.h; path = ../../Source/Sampler/Source/DataModels/DataModel.h; sourceTree = SOURCE_ROOT; };
4915A3C580F0DAA69FAAED98 /* CompressorProcessor.h */ /* CompressorProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CompressorProcessor.h; path = ../../Source/CompressorProcessor.h; sourceTree = SOURCE_ROOT; };
@@ -559,6 +561,7 @@
E553BE5C53ED6D87F71CC515 /* Processors */ = {
isa = PBXGroup;
children = (
+ 44A26898F037A3E6B43EB911,
D5EBA3BB8BAA3B120AB80BBF,
C713F7F77E549E8C08771E49,
70B722073FC3FB8EC6C4E41D,
@@ -726,6 +729,7 @@
0C01D9069A27A2ECB0B24253,
CAE5EFB597BE247AE021B656,
21FB7BCF683F51D403A7475D,
+ D915E61C72D98F0B97F31690,
911FCAF5D38B28CA617932B9,
EC456483653E6271848DB065,
972909F8E3C96F724A1E69AE,
@@ -777,14 +781,15 @@
"DEBUG=1",
"PIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==",
"SAMPLER_SKIP_UI",
+ "JUCE_MODAL_LOOPS_PERMITTED",
"HAVE_LIBSAMPLERATE",
"HAVE_VDSP",
"USE_PTHREADS",
"BUILD_DAWDREAMER_FAUST",
"BUILD_DAWDREAMER_RUBBERBAND",
"JUCER_XCODE_MAC_F6D2F4CF=1",
- "JUCE_APP_VERSION=0.6.1",
- "JUCE_APP_VERSION_HEX=0x601",
+ "JUCE_APP_VERSION=0.6.2",
+ "JUCE_APP_VERSION_HEX=0x602",
"JucePlugin_Build_VST=0",
"JucePlugin_Build_VST3=0",
"JucePlugin_Build_AU=0",
@@ -902,14 +907,15 @@
"NDEBUG=1",
"PIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==",
"SAMPLER_SKIP_UI",
+ "JUCE_MODAL_LOOPS_PERMITTED",
"HAVE_LIBSAMPLERATE",
"HAVE_VDSP",
"USE_PTHREADS",
"BUILD_DAWDREAMER_FAUST",
"BUILD_DAWDREAMER_RUBBERBAND",
"JUCER_XCODE_MAC_F6D2F4CF=1",
- "JUCE_APP_VERSION=0.6.1",
- "JUCE_APP_VERSION_HEX=0x601",
+ "JUCE_APP_VERSION=0.6.2",
+ "JUCE_APP_VERSION_HEX=0x602",
"JucePlugin_Build_VST=0",
"JucePlugin_Build_VST3=0",
"JucePlugin_Build_AU=0",
diff --git a/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj b/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj
index 6bdf9c15..ed34c74b 100644
--- a/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj
+++ b/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj
@@ -66,7 +66,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\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=0.6.1;JUCE_APP_VERSION_HEX=0x601;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)
+ _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;PIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==;SAMPLER_SKIP_UI;JUCE_MODAL_LOOPS_PERMITTED;_WIN32;__SSE__;__SSE2__;BUILD_DAWDREAMER_FAUST;BUILD_DAWDREAMER_RUBBERBAND;NOMINMAX;HAVE_LIBSAMPLERATE;HAVE_KISSFFT;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=0.6.2;JUCE_APP_VERSION_HEX=0x602;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
NotUsing
@@ -116,7 +116,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\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=0.6.1;JUCE_APP_VERSION_HEX=0x601;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)
+ _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;PIP_JUCE_EXAMPLES_DIRECTORY=QzpcdG9vbHNcSlVDRVxleGFtcGxlcw==;SAMPLER_SKIP_UI;JUCE_MODAL_LOOPS_PERMITTED;_WIN32;__SSE__;__SSE2__;BUILD_DAWDREAMER_FAUST;BUILD_DAWDREAMER_RUBBERBAND;NOMINMAX;HAVE_LIBSAMPLERATE;HAVE_KISSFFT;JUCER_VS2019_78A5026=1;JUCE_APP_VERSION=0.6.2;JUCE_APP_VERSION_HEX=0x602;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
NotUsing
@@ -2316,6 +2316,7 @@ 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 e52b812d..a986a562 100644
--- a/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj.filters
+++ b/Builds/VisualStudio2019/DawDreamer_DynamicLibrary.vcxproj.filters
@@ -2979,6 +2979,9 @@
DawDreamer\Rubberband
+
+ DawDreamer\Processors
+
DawDreamer\Processors\Sampler\Components
diff --git a/Builds/VisualStudio2019/resources.rc b/Builds/VisualStudio2019/resources.rc
index 25359650..af74e07a 100644
--- a/Builds/VisualStudio2019/resources.rc
+++ b/Builds/VisualStudio2019/resources.rc
@@ -9,16 +9,16 @@
#include
VS_VERSION_INFO VERSIONINFO
-FILEVERSION 0,6,1,0
+FILEVERSION 0,6,2,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "FileDescription", "DawDreamer\0"
- VALUE "FileVersion", "0.6.1\0"
+ VALUE "FileVersion", "0.6.2\0"
VALUE "ProductName", "DawDreamer\0"
- VALUE "ProductVersion", "0.6.1\0"
+ VALUE "ProductVersion", "0.6.2\0"
END
END
diff --git a/DawDreamer.jucer b/DawDreamer.jucer
index 481acdd2..2c2a7a8a 100644
--- a/DawDreamer.jucer
+++ b/DawDreamer.jucer
@@ -1,6 +1,6 @@
-
+
myFilter, myFilter;
+process = si.bus(4) :> sp.stereoize(myFilter);
```
#### **faust_test_stereo_mixdown.py:**
@@ -220,11 +236,11 @@ engine.render(DURATION)
### Polyphony in Faust
-Polyphony is supported too. You simply need to provide DSP code that refers to correctly named parameters such as `freq` or `note`, `gain`, and `gate`. For more information, see the FAUST [manual](https://faustdoc.grame.fr/manual/midi/#standard-polyphony-parameters). In DawDreamer, you must set the number of voices on the processor to 1 or higher. 0 disables polyphony. Refer to `tests/test_faust_poly.py`.
+Polyphony is supported too. You simply need to provide DSP code that refers to correctly named parameters such as `freq` or `note`, `gain`, and `gate`. For more information, see the FAUST [manual](https://faustdoc.grame.fr/manual/midi/#standard-polyphony-parameters). In DawDreamer, you must set the number of voices on the processor to 1 or higher. The default (0) disables polyphony. Refer to [tests/test_faust_poly\*.py](https://github.com/DBraun/DawDreamer/tree/main/tests).
### Soundfiles in Faust
-Faust code in DawDreamer can use the [soundfile](https://faustdoc.grame.fr/manual/syntax/#soundfile-primitive) primitive. Normally `soundfile` is meant to load `.wav` files, but DawDreamer uses it to receive data from numpy arrays.
+Faust code in DawDreamer can use the [soundfile](https://faustdoc.grame.fr/manual/syntax/#soundfile-primitive) primitive. Normally `soundfile` is meant to load `.wav` files, but DawDreamer uses it to receive data from numpy arrays. Refer to [tests/test_faust_soundfile.py](https://github.com/DBraun/DawDreamer/blob/main/tests/test_faust_soundfile.py)
**soundfile_test.py**
```python
@@ -255,7 +271,7 @@ engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE)
playback_processor = engine.make_playbackwarp_processor("drums", load_audio_file("drums.wav"))
playback_processor.time_ratio = 2. # Play back in twice the amount of time (i.e., slowed down).
-playback_processor.transpose = 3. # Up 3 semitones.
+playback_processor.transpose = -5. # Down 5 semitones.
graph = [
(playback_processor, []),
@@ -282,7 +298,8 @@ graph = [
engine.load_graph(graph)
```
-This will set several properties:
+The `set_clip_file` method will set several properties:
+* `.warp_markers` (np.array [N, 2]) : List of pairs of (time in samples, time in beats)
* `.start_marker` (float) : Start marker position in beats relative to 1.1.1
* `.end_marker` (float) : End marker position in beats relative to 1.1.1
* `.loop_start` (float) : Loop start position in beats relative to 1.1.1
@@ -292,7 +309,7 @@ This will set several properties:
Any of these properties can be changed after an `.asd` file is loaded.
-If `.warp_on` is True, then any value set by `.time_ratio` will be ignored. If `.warp_on` is False, then the `start_marker` and `loop_start` are the first sample of the audio, and the `end_marker` and `loop_end` are the last sample.
+If `.warp_on` is True, then any value set by `.time_ratio` will be ignored.
With `set_clip_positions`, you can use the same audio clip at multiple places along the timeline.
diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp
index 2846999c..d4af45ba 100644
--- a/Source/PluginProcessor.cpp
+++ b/Source/PluginProcessor.cpp
@@ -4,6 +4,8 @@
#include
+#include "StandalonePluginWindow.h"
+
using juce::ExtensionsVisitor;
struct PresetVisitor : public ExtensionsVisitor {
@@ -33,6 +35,26 @@ PluginProcessor::PluginProcessor(std::string newUniqueName, double sampleRate, i
isLoaded = loadPlugin(sampleRate, samplesPerBlock);
}
+void
+PluginProcessor::openEditor() {
+ if (!myPlugin) {
+ throw std::runtime_error(
+ "Editor cannot be shown because the plugin isn't loaded.");
+ }
+
+ if (!juce::Desktop::getInstance().getDisplays().getPrimaryDisplay()) {
+ throw std::runtime_error(
+ "Editor cannot be shown because no visual display devices are available.");
+ }
+
+ if (!juce::MessageManager::getInstance()->isThisTheMessageThread()) {
+ throw std::runtime_error(
+ "Plugin UI windows can only be shown from the main thread.");
+ }
+
+ StandalonePluginWindow::openWindowAndWait(*this, *myPlugin);
+}
+
bool
PluginProcessor::loadPlugin(double sampleRate, int samplesPerBlock) {
OwnedArray pluginDescriptions;
@@ -83,11 +105,22 @@ PluginProcessor::loadPlugin(double sampleRate, int samplesPerBlock) {
auto outputs = myPlugin->getTotalNumOutputChannels();
this->setPlayConfigDetails(inputs, outputs, sampleRate, samplesPerBlock);
myPlugin->setPlayConfigDetails(inputs, outputs, sampleRate, samplesPerBlock);
+ myPlugin->prepareToPlay(sampleRate, samplesPerBlock);
myPlugin->setNonRealtime(true);
mySampleRate = sampleRate;
createParameterLayout();
+ {
+ // Process a block of silence a few times to "warm up" the processor.
+ juce::AudioSampleBuffer audioBuffer = AudioSampleBuffer(std::max(inputs, outputs), samplesPerBlock);
+ MidiBuffer emptyMidiBuffer;
+ for (int i = 0; i < 5; i++) {
+ audioBuffer.clear();
+ myPlugin->processBlock(audioBuffer, emptyMidiBuffer);
+ }
+ }
+
return true;
}
@@ -118,6 +151,15 @@ PluginProcessor::canApplyBusesLayout(const juce::AudioProcessor::BusesLayout& la
return myPlugin->checkBusesLayoutSupported(layout);
}
+bool
+PluginProcessor::setBusesLayout(const BusesLayout& arr) {
+ if (myPlugin.get()) {
+ AudioProcessor::setBusesLayout(arr);
+ return myPlugin->setBusesLayout(arr);
+ }
+ return false;
+}
+
void
PluginProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
{
@@ -329,6 +371,8 @@ PluginProcessor::setParameter(const int paramIndex, const float value)
return;
}
+ myPlugin->setParameter(paramIndex, value);
+
std::string paramID = std::to_string(paramIndex);
ProcessorBase::setAutomationVal(paramID, value);
diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h
index 1b75f3bc..a02f8c13 100644
--- a/Source/PluginProcessor.h
+++ b/Source/PluginProcessor.h
@@ -13,12 +13,23 @@ class PluginProcessor : public ProcessorBase
bool canApplyBusesLayout(const juce::AudioProcessor::BusesLayout& layout);
- bool setBusesLayout(const BusesLayout& arr) {
- if (myPlugin.get()) {
- AudioProcessor::setBusesLayout(arr);
- return myPlugin->setBusesLayout(arr);
+ bool canApplyBusInputsAndOutputs(int inputs, int outputs) {
+ BusesLayout busesLayout = this->makeBusesLayout(inputs, outputs);
+ return this->canApplyBusesLayout(busesLayout);
+ }
+
+ bool setBusesLayout(const BusesLayout& arr);
+
+ bool setMainBusInputsAndOutputs(int inputs, int outputs) {
+ BusesLayout busesLayout = this->makeBusesLayout(inputs, outputs);
+
+ if (this->canApplyBusesLayout(busesLayout)) {
+ return this->setBusesLayout(busesLayout);
+ }
+ else {
+ throw std::invalid_argument(this->getUniqueName() + " CANNOT ApplyBusesLayout inputs: " + std::to_string(inputs) + " outputs: " + std::to_string(outputs));
+ return false;
}
- return false;
}
void numChannelsChanged() {
@@ -66,6 +77,35 @@ class PluginProcessor : public ProcessorBase
void setPlayHead(AudioPlayHead* newPlayHead);
+ void openEditor();
+
+ void loadStateInformation(std::string filepath) {
+
+ MemoryBlock state;
+ File file = File(filepath);
+ file.loadFileAsData(state);
+
+ myPlugin->setStateInformation((const char*)state.getData(), (int)state.getSize());
+
+ for (int i = 0; i < myPlugin->AudioProcessor::getNumParameters(); i++) {
+ std::string paramID = std::to_string(i);
+ ProcessorBase::setAutomationVal(paramID, myPlugin->getParameter(i));
+ }
+ }
+
+ void saveStateInformation(std::string filepath) {
+ if (!myPlugin) {
+ throw std::runtime_error("Please load the plugin first");
+ }
+ MemoryBlock state;
+ myPlugin->getStateInformation(state);
+
+ juce::File file(filepath);
+ juce::FileOutputStream fos(file);
+
+ fos.write(state.getData(), state.getSize());
+ }
+
private:
bool loadPlugin(double sampleRate, int samplesPerBlock);
diff --git a/Source/ProcessorBase.h b/Source/ProcessorBase.h
index 5fa6cd14..689386ad 100644
--- a/Source/ProcessorBase.h
+++ b/Source/ProcessorBase.h
@@ -135,24 +135,25 @@ class ProcessorBase : public juce::AudioProcessor
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);
+ bool setMainBusInputsAndOutputs(int inputs, int outputs) {
+ BusesLayout busesLayout = makeBusesLayout(inputs, outputs);
if (this->canApplyBusesLayout(busesLayout)) {
- bool result = this->setBusesLayout(busesLayout);
+ return this->setBusesLayout(busesLayout);
}
else {
- std::cerr << this->getUniqueName() << " CANNOT ApplyBusesLayout inputs: " << inputs << " outputs: " << outputs << std::endl;
+ throw std::invalid_argument(this->getUniqueName() + " CANNOT ApplyBusesLayout inputs: " + std::to_string(inputs) + " outputs: " + std::to_string(outputs));
+ return false;
}
}
+ bool canApplyBusInputsAndOutputs(int inputs, int outputs) {
+ BusesLayout busesLayout = makeBusesLayout(inputs, outputs);
+ return this->canApplyBusesLayout(busesLayout);
+ }
+
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ProcessorBase)
@@ -161,7 +162,6 @@ class ProcessorBase : public juce::AudioProcessor
bool m_isConnectedInGraph = false;
protected:
-
AudioProcessorValueTreeState myParameters;
@@ -171,4 +171,13 @@ class ProcessorBase : public juce::AudioProcessor
return params;
}
bool m_recordEnable = false;
+
+ BusesLayout makeBusesLayout(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);
+ return busesLayout;
+ }
};
diff --git a/Source/StandalonePluginWindow.h b/Source/StandalonePluginWindow.h
new file mode 100644
index 00000000..dec11fe4
--- /dev/null
+++ b/Source/StandalonePluginWindow.h
@@ -0,0 +1,120 @@
+/*
+ * pedalboard
+ * Copyright 2021 Spotify AB
+ *
+ * Licensed under the GNU Public License, Version 3.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// This file was slightly modified from
+// https://github.com/spotify/pedalboard/blob/49cefb490191679034238a14db6ce3cc7e96b79d/pedalboard/ExternalPlugin.h#L167-L245
+
+class StandalonePluginWindow : public juce::DocumentWindow {
+public:
+ StandalonePluginWindow(PluginProcessor& dawDreamerPluginProcessor, juce::AudioProcessor& processor)
+ : DocumentWindow("DawDreamer: " + processor.getName(),
+ juce::LookAndFeel::getDefaultLookAndFeel().findColour(
+ juce::ResizableWindow::backgroundColourId),
+ //juce::DocumentWindow::minimiseButton |
+ juce::DocumentWindow::closeButton),
+ processor(processor),
+ dawDreamerPluginProcessor(dawDreamerPluginProcessor) {
+ setUsingNativeTitleBar(true);
+
+ if (processor.hasEditor()) {
+ if (auto* editor = processor.createEditorIfNeeded()) {
+ setContentOwned(editor, true);
+ setResizable(editor->isResizable(), false);
+ }
+ else {
+ throw std::runtime_error("Failed to create plugin editor UI.");
+ }
+ }
+ else {
+ throw std::runtime_error("Plugin has no available editor UI.");
+ }
+ }
+
+ /**
+ * Open a native window to show a given AudioProcessor's editor UI,
+ * pumping the juce::MessageManager run loop as necessary to service
+ * UI events.
+ */
+ static void openWindowAndWait(PluginProcessor& dawDreamerPluginProcessor, juce::AudioProcessor& processor) {
+ bool shouldThrowErrorAlreadySet = false;
+
+ JUCE_AUTORELEASEPOOL{
+ StandalonePluginWindow window(dawDreamerPluginProcessor, processor);
+ window.show();
+
+ // Run in a tight loop so that we don't have to call ->stopDispatchLoop(),
+ // which causes the MessageManager to become unusable in the future.
+ // The window can be closed by sending a KeyboardInterrupt or closing
+ // the window in the UI.
+ while (window.isVisible()) {
+ if (PyErr_CheckSignals() != 0) {
+ window.closeButtonPressed();
+ shouldThrowErrorAlreadySet = true;
+ break;
+ }
+
+ {
+ // Release the GIL to allow other Python threads to run in the
+ // background while we the UI is running:
+ py::gil_scoped_release release;
+ juce::MessageManager::getInstance()->runDispatchLoopUntil(10);
+ }
+ }
+ }
+
+ // Once the Autorelease pool has been drained, pump the dispatch loop one
+ // more time to process any window close events:
+ juce::MessageManager::getInstance()->runDispatchLoopUntil(10);
+
+ if (shouldThrowErrorAlreadySet) {
+ throw py::error_already_set();
+ }
+ }
+
+ void closeButtonPressed() override {
+ setVisible(false);
+
+ for (int j = 0; j < 2; j++) {
+ for (int i = 0; i < processor.getNumParameters(); ++i)
+ {
+ auto parameterName = processor.getParameterName(i);
+ std::string paramID = std::to_string(i);
+ // give it a valid single sample of automation.
+ dawDreamerPluginProcessor.setAutomationVal(paramID, processor.getParameter(i));
+ }
+ }
+ }
+
+ ~StandalonePluginWindow() override { clearContentComponent(); }
+
+ void show() {
+
+ centreWithSize(getWidth(), getHeight());
+
+ setVisible(true);
+ toFront(true);
+ juce::Process::makeForegroundProcess();
+ }
+
+private:
+ juce::AudioProcessor& processor;
+ PluginProcessor& dawDreamerPluginProcessor;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(StandalonePluginWindow)
+};
\ No newline at end of file
diff --git a/Source/source.cpp b/Source/source.cpp
index 06ca9936..d7ff9979 100644
--- a/Source/source.cpp
+++ b/Source/source.cpp
@@ -90,7 +90,7 @@ play the audio in double the amount of time, so it will sound slowed down.")
.def_property("loop_end", &PlaybackWarpProcessor::getLoopEnd, &PlaybackWarpProcessor::setLoopEnd, "The loop end position in beats (typically quarter notes) relative to 1.1.1")
.def_property("start_marker", &PlaybackWarpProcessor::getStartMarker, &PlaybackWarpProcessor::setStartMarker, "The start position in beats (typically quarter notes) relative to 1.1.1")
.def_property("end_marker", &PlaybackWarpProcessor::getEndMarker, &PlaybackWarpProcessor::setEndMarker, "The end position in beats (typically quarter notes) relative to 1.1.1")
- .def_property("warp_markers", &PlaybackWarpProcessor::getWarpMarkers, &PlaybackWarpProcessor::setWarpMarkers, "Get/set the warp markers as a 2D array of time positions in seconds and positions in beats.")
+ .def_property("warp_markers", &PlaybackWarpProcessor::getWarpMarkers, &PlaybackWarpProcessor::setWarpMarkers, "Get/set the warp markers as an (N, 2) numpy array of time positions in samples and positions in beats.")
.def("reset_warp_markers", &PlaybackWarpProcessor::resetWarpMarkers, arg("bpm"), "Reset the warp markers with a BPM.")
.def("set_clip_file", &PlaybackWarpProcessor::loadAbletonClipInfo, arg("asd_file_path"), "Load an Ableton Live file with an \".asd\" extension")
.def("set_data", &PlaybackWarpProcessor::setData, arg("data"), "Set the audio as a numpy array shaped (Channels, Samples).")
@@ -157,6 +157,11 @@ but the filter mode cannot under automation.";
.doc() = "An Add Processor adds one or more stereo inputs with corresponding gain parameters.";
py::class_, ProcessorBase>(m, "PluginProcessor")
+ .def("can_set_bus", &PluginProcessorWrapper::canApplyBusInputsAndOutputs, arg("inputs"), arg("outputs"), "Return bool for whether this combination of input and output channels can be set.")
+ .def("set_bus", &PluginProcessorWrapper::setMainBusInputsAndOutputs, arg("inputs"), arg("outputs"), "Set the number of input and output channels. An error will be thrown for an unaccepted option.")
+ .def("save_state", &PluginProcessorWrapper::saveStateInformation, arg("filepath"), "Save the state to a file.")
+ .def("load_state", &PluginProcessorWrapper::loadStateInformation, arg("filepath"), "Load the state from a file.")
+ .def("open_editor", &PluginProcessorWrapper::openEditor, "Open the UI editor for the plugin.")
.def("load_preset", &PluginProcessorWrapper::loadPreset, arg("filepath"), "Load an FXP preset with an absolute filepath and \".fxp\" extension.")
.def("load_vst3_preset", &PluginProcessorWrapper::loadVST3Preset, arg("filepath"), "Load a VST3 preset with an absolute filepath and \".vstpreset\" extension.")
.def("get_patch", &PluginProcessorWrapper::wrapperGetPatch)
diff --git a/docs/conf.py b/docs/conf.py
index 2782f2c3..8d9a13a0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -14,6 +14,7 @@
import sys
import os
+import datetime
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -53,17 +54,25 @@
# General information about the project.
project = u'DawDreamer'
-copyright = u'2021, David Braun'
+copyright = datetime.datetime.now().strftime('%Y') + u', David Braun'
author = u'David Braun'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
-#
+
+def get_dawdreamer_version():
+ import xml.etree.ElementTree as ET
+ from pathlib import Path
+ tree = ET.parse(Path(__file__).parent.parent / 'DawDreamer.jucer')
+ root = tree.getroot()
+ version = root.attrib['version']
+ return version
+
# The short X.Y version.
-version = u'0.0.1'
+version = get_dawdreamer_version()
# The full version, including alpha/beta/rc tags.
-release = u'0.0.1'
+release = get_dawdreamer_version()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -76,7 +85,7 @@
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -119,7 +128,15 @@
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
+html_theme_options = {
+ # 'github_banner': True,
+ 'github_button': True,
+ 'github_user': 'dbraun',
+ 'github_repo': 'DawDreamer',
+ 'sidebar_width': '320px',
+ 'page_width': '1200px',
+ # 'html_last_updated_fmt': '%b %d, %Y'
+}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
@@ -143,7 +160,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+# html_static_path = ['_static']
html_static_path = []
# Add any extra paths that contain custom files (such as robots.txt or
@@ -153,7 +170,7 @@
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
diff --git a/tests/dawdreamer_utils.py b/tests/dawdreamer_utils.py
index 4ec2d103..9ff462f5 100644
--- a/tests/dawdreamer_utils.py
+++ b/tests/dawdreamer_utils.py
@@ -1,5 +1,5 @@
from scipy.io import wavfile
-from os.path import abspath, isfile
+from os.path import abspath, isfile, basename
from pathlib import Path
import numpy as np
import pathlib
diff --git a/tests/output/.gitignore b/tests/output/.gitignore
index 697e56f2..2d99b548 100644
--- a/tests/output/.gitignore
+++ b/tests/output/.gitignore
@@ -1 +1,2 @@
+state*
*.wav
\ No newline at end of file
diff --git a/tests/test_faust_poly.py b/tests/test_faust_poly.py
index 3228e467..0d7f7638 100644
--- a/tests/test_faust_poly.py
+++ b/tests/test_faust_poly.py
@@ -72,3 +72,55 @@ def test_faust_poly():
audio2 = _test_faust_poly(OUTPUT / 'test_faust_poly_automation_decay_ungrouped.wav', group_voices=False, decay=.5)
assert(np.allclose(audio1[:,:-1], audio2[:,:-1])) # todo: don't drop last sample
+
+
+def _test_faust_sine(midi_path: str, buffer_size=1):
+
+ engine = daw.RenderEngine(SAMPLE_RATE, buffer_size)
+
+ faust_processor = engine.make_faust_processor("faust")
+ faust_processor.num_voices = 16
+ faust_processor.group_voices = True
+ faust_processor.release_length = .5 # note that this is the maximum of the "release" hslider below
+
+ faust_processor.set_dsp_string(
+ f"""
+ declare name "MyInstrument";
+
+ declare options "[nvoices:8]"; // FaustProcessor has a property which will override this.
+ import("stdfaust.lib");
+
+ 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
+
+ attack = hslider("attack", .002, 0.001, 10., 0.);
+ decay = hslider("decay", .05, 0.001, 10., 0.);
+ sustain = hslider("sustain", 1.0, 0.0, 1., 0.);
+ release = hslider("release", .05, 0.001, {faust_processor.release_length}, 0.);
+
+ envVol = 0.35*gain*en.adsr(attack, decay, sustain, release, gate);
+
+ process = os.osc(freq)*envVol <: _, _;
+ effect = _, _;
+ """
+ )
+ faust_processor.compile()
+ # desc = faust_processor.get_parameters_description()
+ # for par in desc:
+ # print(par)
+
+ faust_processor.load_midi(abspath(ASSETS / midi_path))
+
+ graph = [
+ (faust_processor, [])
+ ]
+
+ engine.load_graph(graph)
+ render(engine, file_path=OUTPUT / ('test_faust_sine_' + basename(midi_path) + '.wav'), duration=10.)
+
+ audio = engine.get_audio()
+ assert(np.mean(np.abs(audio)) > .0001)
+
+def test_faust_sine1():
+ _test_faust_sine(abspath(ASSETS / 'MIDI-Unprocessed_SMF_02_R1_2004_01-05_ORIG_MID--AUDIO_02_R1_2004_05_Track05_wav.midi'))
\ No newline at end of file
diff --git a/tests/test_playbackwarp_processor.py b/tests/test_playbackwarp_processor.py
index 356f70ca..f26cdd7f 100644
--- a/tests/test_playbackwarp_processor.py
+++ b/tests/test_playbackwarp_processor.py
@@ -29,6 +29,11 @@ def test_playbackwarp_processor1():
warp_markers = drums.warp_markers
+ # re-assign and test that it stayed the same
+ drums.warp_markers = warp_markers
+ assert (drums.warp_markers == warp_markers).all()
+
+ # add one interpolated warp marker in the middle
warp1 = warp_markers[0]
warp3 = warp_markers[-1]
warp2 = 0.5*(warp1+warp3)
@@ -40,8 +45,7 @@ def test_playbackwarp_processor1():
drums.warp_markers = warp_markers
- assert drums.warp_markers.shape[0] == 3
- assert drums.warp_markers.shape[1] == 2
+ assert (drums.warp_markers == warp_markers).all()
graph = [
(drums, []),
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index 1164e937..aed03515 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -260,51 +260,55 @@ def test_plugin_goodhertz_sidechain():
_test_plugin_goodhertz_sidechain(do_sidechain=False)
-# def test_plugin_effect_ambisonics(set_data=False):
+def test_plugin_effect_ambisonics():
-# if MY_SYSTEM != "Windows":
-# return
+ if MY_SYSTEM != "Windows":
+ return
+
+ DURATION = 5.
-# DURATION = 5.
+ engine = daw.RenderEngine(48000, 128)
-# engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE)
+ data = load_audio_file("assets/575854__yellowtree__d-b-funk-loop.wav", DURATION+.1)
+
+ # convert to mono (1, N)
+ data = data.mean(axis=0, keepdims=True)
+
+ playback_processor = engine.make_playback_processor("playback", data)
-# data = load_audio_file("assets/575854__yellowtree__d-b-funk-loop.wav", DURATION+.1)
-# playback_processor = engine.make_playback_processor("playback", data)
+ plugin_path = "C:/VSTPlugIns/sparta/sparta_ambiENC.dll"
-# data = data.mean(axis=0, keepdims=True)
+ if not isfile(plugin_path):
+ return
-# if set_data:
-# playback_processor.set_data(data)
+ proc_encoder = engine.make_plugin_processor("effect", plugin_path)
-# plugin_name = "sparta_ambiENC.vst" if MY_SYSTEM == "Darwin" else "sparta_ambiENC.dll"
+ # print('inputs: ', proc_encoder.get_num_input_channels(), ' outputs: ', proc_encoder.get_num_output_channels())
-# plugin_path = abspath("plugins/"+plugin_name)
-# if not isfile(plugin_path):
-# return
+ # proc_encoder.open_editor()
-# effect = engine.make_plugin_processor("effect", plugin_path)
+ proc_encoder.set_bus(1, 4)
-# effect.set_parameter(0, 1)
+ # print('inputs: ', proc_encoder.get_num_input_channels(), ' outputs: ', proc_encoder.get_num_output_channels())
-# assert(effect.get_num_input_channels() == 64)
-# assert(effect.get_num_output_channels() == 64)
+ assert(proc_encoder.get_num_input_channels() == 1)
+ assert(proc_encoder.get_num_output_channels() == 4)
-# # for par in effect.get_plugin_parameters_description():
-# # print(par)
+ # for par in proc_encoder.get_plugin_parameters_description():
+ # print(par)
-# graph = [
-# (playback_processor, []),
-# (effect, ["playback"])
-# ]
+ graph = [
+ (playback_processor, []),
+ (proc_encoder, ["playback"])
+ ]
-# assert(engine.load_graph(graph))
+ assert(engine.load_graph(graph))
-# render(engine, file_path='output/test_plugin_effect_ambisonics.wav', duration=DURATION)
+ render(engine, file_path='output/test_plugin_effect_ambisonics.wav', duration=DURATION)
-# audio = engine.get_audio()
+ audio = engine.get_audio()
-# assert(effect.get_num_output_channels() == audio.shape[0])
+ assert(proc_encoder.get_num_output_channels() == audio.shape[0])
def test_plugin_upright_piano():
@@ -345,5 +349,145 @@ def test_plugin_upright_piano():
audio = np.array(audio, np.float32).transpose()
wavfile.write(OUTPUT / 'test_plugin_upright_piano.wav', SAMPLE_RATE, audio)
-# if __name__ == '__main__':
-# test_plugin_upright_piano()
+
+def test_plugin_editor():
+
+ if MY_SYSTEM not in ["Windows"]:
+ # We don't Serum on platforms other than Windows.
+ return
+
+ # plugin_path = "C:/VSTPlugIns/Serum_x64.dll"
+ plugin_path = "C:/VSTPlugIns/TAL-NoiseMaker-64.vst3"
+ # plugin_path = "C:/VSTPlugIns/sparta/sparta_ambiBIN.dll"
+
+ plugin_basename = os.path.splitext(basename(plugin_path))[0]
+
+ if not isfile(plugin_path):
+ return
+
+ DURATION = 5.
+
+ engine = daw.RenderEngine(SAMPLE_RATE, 128)
+
+ synth = engine.make_plugin_processor("synth", plugin_path)
+
+ state_file_path = abspath(OUTPUT / (f'state_test_plugin_{plugin_basename}'))
+
+ if isfile(state_file_path):
+ synth.load_state(state_file_path)
+
+ # synth.open_editor()
+
+ synth.save_state(state_file_path)
+
+ # print(synth.get_plugin_parameters_description())
+
+ synth.get_parameter(0)
+ synth.set_parameter(0, synth.get_parameter(0))
+ synth.set_automation(0, np.array([synth.get_parameter(0)]))
+
+ print('inputs: ', synth.get_num_input_channels(), ' outputs: ', synth.get_num_output_channels())
+
+ # 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)
+ synth.add_midi_note(67, 127, 0.75, .5)
+
+ assert(synth.n_midi_events == 3*2) # multiply by 2 because of the off-notes.
+
+ graph = [
+ (synth, []),
+ ]
+
+ engine.load_graph(graph)
+
+ render(engine, file_path=OUTPUT / (f'test_plugin_{plugin_basename}.wav'), duration=DURATION)
+
+ audio = engine.get_audio()
+ assert(not np.allclose(audio*0., audio, atol=1e-07))
+
+
+def test_plugin_iem(plugin_path="C:/VSTPlugIns/IEMPluginSuite/VST2/IEM/MultiEncoder.dll",
+ plugin_path2="C:/VSTPlugIns/IEMPluginSuite/VST2/IEM/BinauralDecoder.dll"
+ ):
+
+ if "PYTEST_CURRENT_TEST" in os.environ:
+ # we are not actually using pytest, so open the UI.
+ # todo: need a pytest way to test open_editor()
+ return
+
+
+ if not isfile(plugin_path):
+ return
+
+ plugin_basename = os.path.splitext(basename(plugin_path))[0]
+
+ DURATION = 5.
+
+ engine = daw.RenderEngine(SAMPLE_RATE, 128)
+
+ ambisonics_encoder = engine.make_plugin_processor("ambisonics_encoder", plugin_path)
+ ambisonics_decoder = engine.make_plugin_processor("ambisonics_decoder", plugin_path2)
+ ambisonics_encoder.record = True
+ ambisonics_decoder.record = True
+
+ state_file_path = abspath(OUTPUT / (f'state_test_plugin_{plugin_basename}'))
+
+ if isfile(state_file_path):
+ ambisonics_encoder.load_state(state_file_path)
+
+ AMBISONICS_ORDER = 3
+ num_inputs = 1
+ num_outputs = (AMBISONICS_ORDER+1)**2 # this is a fixed equation
+
+ ambisonics_encoder.set_bus(num_inputs, num_outputs)
+
+ # The UI window will open. In the upper-left, select 1-channel input.
+ # In the upper-right select 3rd-order ambisonics (AMBISONICS_ORDER)
+ ambisonics_encoder.open_editor()
+
+ ambisonics_encoder.save_state(state_file_path)
+
+ assert ambisonics_encoder.get_num_input_channels() == num_inputs
+ assert ambisonics_encoder.get_num_output_channels() == num_outputs
+
+ # print(ambisonics_encoder.get_plugin_parameters_description())
+ # print('inputs: ', ambisonics_encoder.get_num_input_channels(), ' outputs: ', ambisonics_encoder.get_num_output_channels())
+
+ ambisonics_decoder.set_bus(num_outputs, 2)
+
+ # Remember to select AMBISONICS_ORDER ambisonics.
+ ambisonics_decoder.open_editor()
+
+ assert ambisonics_decoder.get_num_input_channels() == num_outputs
+ assert ambisonics_decoder.get_num_output_channels() == 2
+
+ data = load_audio_file("assets/575854__yellowtree__d-b-funk-loop.wav", DURATION+.1)
+ # convert to mono (1, N)
+ data = data.mean(axis=0, keepdims=True)
+
+ graph = [
+ (engine.make_playback_processor("playback", data), []),
+ (ambisonics_encoder, ["playback"]),
+ (ambisonics_decoder, [ambisonics_encoder.get_name()])
+ ]
+
+ engine.load_graph(graph)
+ engine.render(DURATION)
+
+ audio = ambisonics_decoder.get_audio()
+ assert(not np.allclose(audio*0., audio, atol=1e-07))
+ file_path = OUTPUT / f'test_plugin_{plugin_basename}_decoder.wav'
+ wavfile.write(file_path, SAMPLE_RATE, audio.transpose())
+
+ audio = ambisonics_encoder.get_audio()
+ assert(not np.allclose(audio*0., audio, atol=1e-07))
+ file_path = OUTPUT / f'test_plugin_{plugin_basename}_encoder.wav'
+ wavfile.write(file_path, SAMPLE_RATE, audio.transpose())
+
+
+if __name__ == '__main__':
+ print('All done!')
\ No newline at end of file