├── android ├── jni │ ├── Application.mk │ ├── Android.mk │ ├── net_themaister_fmsynthtest_NativeInterface.h │ └── fmsynth_jni.c ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-ldpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── values │ │ └── strings.xml │ └── layout │ │ └── activity_fullscreen.xml ├── project.properties ├── AndroidManifest.xml ├── src │ └── net │ │ └── themaister │ │ └── fmsynthtest │ │ ├── NativeInterface.java │ │ └── Test.java └── build.xml ├── presets ├── clocky.fmp ├── lead_pad.fmp ├── retro_sci_fi.fmp ├── angry_overture.fmp ├── expressive_harp.fmp ├── old_school_organ.fmp └── square_emotions.fmp ├── .gitignore ├── lv2 ├── manifest.ttl ├── GNUmakefile ├── fmsynth_lv2.cpp ├── fmsynth_gui.cpp └── fmsynth.ttl ├── COPYING ├── GNUmakefile ├── src ├── fmsynth_test.c ├── x86 │ ├── fmsynth_avx.c │ └── fmsynth_sse.c ├── arm │ ├── fmsynth_arm.c │ └── fmsynth_neon.S └── fmsynth.c ├── include ├── fmsynth_private.h └── fmsynth.h └── README.md /android/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_ABI := armeabi-v7a 2 | APP_OPTIM := release 3 | -------------------------------------------------------------------------------- /presets/clocky.fmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/presets/clocky.fmp -------------------------------------------------------------------------------- /presets/lead_pad.fmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/presets/lead_pad.fmp -------------------------------------------------------------------------------- /presets/retro_sci_fi.fmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/presets/retro_sci_fi.fmp -------------------------------------------------------------------------------- /presets/angry_overture.fmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/presets/angry_overture.fmp -------------------------------------------------------------------------------- /presets/expressive_harp.fmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/presets/expressive_harp.fmp -------------------------------------------------------------------------------- /presets/old_school_organ.fmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/presets/old_school_organ.fmp -------------------------------------------------------------------------------- /presets/square_emotions.fmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/presets/square_emotions.fmp -------------------------------------------------------------------------------- /android/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/android/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/android/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/android/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Themaister/libfmsynth/HEAD/android/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.d 2 | *.o 3 | *.a 4 | *.peg 5 | *.so 6 | *.exe 7 | /lv2/fmsynth.lv2 8 | /fmsynth_test 9 | /android/bin 10 | /android/gen 11 | /docs 12 | -------------------------------------------------------------------------------- /lv2/manifest.ttl: -------------------------------------------------------------------------------- 1 | @prefix lv2: . 2 | @prefix rdfs: . 3 | 4 | a lv2:Plugin; 5 | rdfs:seeAlso . 6 | 7 | -------------------------------------------------------------------------------- /android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | FMSynth Test 4 | FMSynth Test 5 | 6 | -------------------------------------------------------------------------------- /android/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-19 12 | -------------------------------------------------------------------------------- /android/res/layout/activity_fullscreen.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /android/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | 5 | LOCAL_MODULE := fmsynth_neon 6 | LOCAL_SRC_FILES := \ 7 | ../../src/fmsynth.c \ 8 | ../../src/fmsynth_test.c \ 9 | ../../src/arm/fmsynth_neon.S \ 10 | fmsynth_jni.c 11 | 12 | LOCAL_ARM_MODE := arm 13 | LOCAL_ARM_NEON := true 14 | 15 | LOCAL_LDLIBS := -lm 16 | LOCAL_CFLAGS := -std=c99 -Wall -Wextra -DFMSYNTH_SIMD -Ofast 17 | LOCAL_C_INCLUDES := ../include 18 | 19 | include $(BUILD_SHARED_LIBRARY) 20 | 21 | include $(CLEAR_VARS) 22 | 23 | LOCAL_MODULE := fmsynth 24 | LOCAL_SRC_FILES := \ 25 | ../../src/fmsynth.c \ 26 | ../../src/fmsynth_test.c \ 27 | fmsynth_jni.c 28 | 29 | LOCAL_ARM_MODE := arm 30 | LOCAL_ARM_NEON := false 31 | 32 | LOCAL_LDLIBS := -lm 33 | LOCAL_CFLAGS := -std=c99 -Wall -Wextra -Ofast 34 | LOCAL_C_INCLUDES := ../include 35 | 36 | include $(BUILD_SHARED_LIBRARY) 37 | -------------------------------------------------------------------------------- /android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Hans-Kristian Arntzen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /android/src/net/themaister/fmsynthtest/NativeInterface.java: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | package net.themaister.fmsynthtest; 20 | 21 | public class NativeInterface { 22 | static { 23 | System.loadLibrary("fmsynth_neon"); 24 | } 25 | 26 | native static float runTest(); 27 | } 28 | -------------------------------------------------------------------------------- /android/jni/net_themaister_fmsynthtest_NativeInterface.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | /* DO NOT EDIT THIS FILE - it is machine generated */ 20 | #include 21 | /* Header for class net_themaister_fmsynthtest_NativeInterface */ 22 | 23 | #ifndef _Included_net_themaister_fmsynthtest_NativeInterface 24 | #define _Included_net_themaister_fmsynthtest_NativeInterface 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | /* 29 | * Class: net_themaister_fmsynthtest_NativeInterface 30 | * Method: runTest 31 | * Signature: ()F 32 | */ 33 | JNIEXPORT jfloat JNICALL Java_net_themaister_fmsynthtest_NativeInterface_runTest 34 | (JNIEnv *, jclass); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | #endif 40 | -------------------------------------------------------------------------------- /android/jni/fmsynth_jni.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #include "net_themaister_fmsynthtest_NativeInterface.h" 20 | 21 | #include 22 | #include 23 | 24 | int fmsynth_android_main(int argc, char *argv[]); 25 | 26 | JNIEXPORT jfloat JNICALL Java_net_themaister_fmsynthtest_NativeInterface_runTest 27 | (JNIEnv *env, jclass clazz) 28 | { 29 | (void)env; 30 | (void)clazz; 31 | 32 | struct timespec start, end; 33 | clock_gettime(CLOCK_MONOTONIC, &start); 34 | 35 | char name[] = "fmsynth_test"; 36 | char *argv[] = { name, NULL }; 37 | fmsynth_android_main(1, argv); 38 | 39 | clock_gettime(CLOCK_MONOTONIC, &end); 40 | 41 | return end.tv_sec - start.tv_sec + 0.000000001 * (end.tv_nsec - start.tv_nsec); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /lv2/GNUmakefile: -------------------------------------------------------------------------------- 1 | BUNDLE := fmsynth.lv2 2 | INSTALL_DIR = /usr/lib/lv2 3 | 4 | OBJDIR := obj 5 | SOURCE := fmsynth_lv2.cpp 6 | OBJECTS := $(addprefix $(OBJDIR)/,$(SOURCE:.cpp=.o)) 7 | GUI_SOURCE := fmsynth_gui.cpp 8 | GUI_OBJECTS := $(addprefix $(OBJDIR)/,$(GUI_SOURCE:.cpp=.o)) 9 | 10 | FMSYNTH_LIB := fmsynth.so 11 | FMSYNTH_GUI_LIB := fmsynth_gui.so 12 | 13 | PRESETS := ../presets 14 | 15 | LV2_GUI_CFLAGS := $(shell pkg-config --cflags lvtk-gtkui-1) 16 | LV2_GUI_LIBS := $(shell pkg-config --libs lvtk-gtkui-1) 17 | LV2_CFLAGS := $(shell pkg-config lvtk-plugin-1 --cflags) 18 | LV2_LIBS := $(shell pkg-config lvtk-plugin-1 --libs) 19 | 20 | LDFLAGS += -fPIC -Wl,-no-undefined -lm 21 | CXXFLAGS += -fPIC -std=c++11 -Wall -pedantic $(LV2_CFLAGS) -I../include 22 | 23 | ifeq ($(ARCH),) 24 | ARCH := $(shell uname -m) 25 | endif 26 | 27 | ifneq ($(findstring armv7,$(ARCH)),) 28 | CXXFLAGS += -mfpu=neon -march=armv7-a -marm 29 | endif 30 | 31 | ifeq ($(DEBUG), 1) 32 | CXXFLAGS += -O0 -g 33 | else 34 | CXXFLAGS += -Ofast 35 | endif 36 | 37 | all: $(BUNDLE) 38 | 39 | $(BUNDLE): manifest.ttl $(FMSYNTH_LIB) $(FMSYNTH_GUI_LIB) 40 | rm -rf $(BUNDLE) 41 | mkdir $(BUNDLE) 42 | install -m644 fmsynth.ttl manifest.ttl $(FMSYNTH_LIB) $(FMSYNTH_GUI_LIB) $(BUNDLE) 43 | cp -r $(PRESETS) $(BUNDLE) 44 | 45 | fmsynth.peg: fmsynth.ttl 46 | ttl2c $< $@ 47 | 48 | $(FMSYNTH_LIB): $(OBJECTS) 49 | $(MAKE) -C ../ 50 | $(CXX) -o $@ $(OBJECTS) ../libfmsynth.a -shared $(LDFLAGS) $(LV2_LIBS) 51 | 52 | $(FMSYNTH_GUI_LIB): $(GUI_OBJECTS) fmsynth.peg 53 | $(MAKE) -C ../ 54 | $(CXX) -o $@ $(GUI_OBJECTS) ../libfmsynth.a -shared $(LDFLAGS) $(CXXFLAGS) $(LV2_GUI_LIBS) 55 | 56 | $(OBJDIR)/%.o: %.cpp fmsynth.peg 57 | @mkdir -p $(dir $@) 58 | $(CXX) -c -o $@ $< $(CXXFLAGS) $(LV2_GUI_CFLAGS) 59 | 60 | install: 61 | cp -r $(BUNDLE) $(INSTALL_DIR) 62 | 63 | clean: 64 | rm -rf $(BUNDLE) fmsynth.so fmsynth_gui.so $(OBJDIR) *.peg 65 | $(MAKE) -C ../ clean 66 | 67 | .PHONY: clean benchmark 68 | 69 | -------------------------------------------------------------------------------- /android/src/net/themaister/fmsynthtest/Test.java: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | package net.themaister.fmsynthtest; 20 | 21 | import android.app.Activity; 22 | import android.os.Bundle; 23 | import android.view.MotionEvent; 24 | import android.view.Window; 25 | import android.view.WindowManager; 26 | import android.content.res.AssetManager; 27 | import android.util.Log; 28 | 29 | public class Test extends Activity { 30 | private Thread thread; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_fullscreen); 36 | 37 | thread = new Thread(new Runnable() { 38 | public void run() { 39 | Log.i("FMSynth Test", "Running ..."); 40 | float time = NativeInterface.runTest(); 41 | Log.i("FMSynth Test", "Test time: " + time + " s."); 42 | } 43 | }); 44 | thread.start(); 45 | 46 | Log.i("FMSynth Test", "onCreate() finished."); 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | 2 | ifeq ($(platform),) 3 | platform = unix 4 | ifeq ($(shell uname -a),) 5 | platform = win 6 | else ifneq ($(findstring MINGW,$(shell uname -a)),) 7 | platform = win 8 | else ifneq ($(findstring Darwin,$(shell uname -a)),) 9 | platform = osx 10 | else ifneq ($(findstring win,$(shell uname -a)),) 11 | platform = win 12 | endif 13 | endif 14 | 15 | ifeq ($(platform),win) 16 | CC = gcc 17 | EXE_SUFFIX := .exe 18 | else 19 | FPIC := -fPIC 20 | endif 21 | 22 | CFLAGS += -std=c99 -Wall -Wextra -pedantic $(FPIC) -Iinclude 23 | LDFLAGS += -lm 24 | 25 | FMSYNTH_STATIC_LIB := libfmsynth.a 26 | OBJDIR := obj 27 | FMSYNTH_C_SOURCES := src/fmsynth.c 28 | FMSYNTH_TEST_SOURCES := src/fmsynth_test.c 29 | FMSYNTH_TEST_OBJECTS := $(addprefix $(OBJDIR)/,$(FMSYNTH_TEST_SOURCES:.c=.o)) 30 | FMSYNTH_TEST := fmsynth_test$(EXE_SUFFIX) 31 | 32 | SIMD = 1 33 | PREFIX = /usr/local 34 | 35 | ifeq ($(ARCH),) 36 | ARCH := $(shell uname -m) 37 | endif 38 | 39 | ifneq ($(findstring armv7,$(ARCH)),) 40 | CFLAGS += -march=armv7-a -mfpu=neon -marm 41 | ASFLAGS += -mfpu=neon 42 | FMSYNTH_ASM_SOURCES += src/arm/fmsynth_neon.S 43 | endif 44 | 45 | ifneq ($(TOOLCHAIN_PREFIX),) 46 | CC = $(TOOLCHAIN_PREFIX)gcc 47 | AS = $(TOOLCHAIN_PREFIX)gcc 48 | AR = $(TOOLCHAIN_PREFIX)ar 49 | endif 50 | 51 | FMSYNTH_OBJECTS := \ 52 | $(addprefix $(OBJDIR)/,$(FMSYNTH_C_SOURCES:.c=.o)) \ 53 | $(addprefix $(OBJDIR)/,$(FMSYNTH_ASM_SOURCES:.S=.o)) 54 | 55 | DEPS := $(FMSYNTH_TEST_OBJECTS:.o=.d) $(FMSYNTH_OBJECTS:.o=.d) 56 | 57 | ifneq ($(TUNE),) 58 | CFLAGS += -mtune=$(TUNE) 59 | else 60 | CFLAGS += -march=native 61 | endif 62 | 63 | ifeq ($(SIMD), 1) 64 | CFLAGS += -DFMSYNTH_SIMD 65 | endif 66 | 67 | ifeq ($(DEBUG), 1) 68 | CFLAGS += -O0 -g 69 | else 70 | CFLAGS += -Ofast 71 | endif 72 | 73 | all: $(FMSYNTH_STATIC_LIB) 74 | 75 | test: $(FMSYNTH_TEST) 76 | 77 | -include $(DEPS) 78 | 79 | $(FMSYNTH_STATIC_LIB): $(FMSYNTH_OBJECTS) 80 | $(AR) rcs $@ $^ 81 | 82 | $(FMSYNTH_TEST): $(FMSYNTH_TEST_OBJECTS) $(FMSYNTH_STATIC_LIB) 83 | $(CC) -o $@ $^ $(LDFLAGS) 84 | 85 | $(OBJDIR)/%.o: %.c 86 | @mkdir -p $(dir $@) 87 | $(CC) -c -o $@ $< $(CFLAGS) -MMD 88 | 89 | $(OBJDIR)/%.o: %.S 90 | @mkdir -p $(dir $@) 91 | $(CC) -c -o $@ $< $(ASFLAGS) 92 | 93 | clean: 94 | rm -f $(FMSYNTH_TEST) $(FMSYNTH_STATIC_LIB) 95 | rm -rf $(OBJDIR) 96 | 97 | install: 98 | @mkdir -p $(PREFIX)/lib 99 | @mkdir -p $(PREFIX)/include 100 | install -m644 $(FMSYNTH_STATIC_LIB) $(PREFIX)/lib/ 101 | install -m644 fmsynth.h $(PREFIX)/include/ 102 | 103 | docs: 104 | doxygen 105 | 106 | .PHONY: clean install docs 107 | 108 | -------------------------------------------------------------------------------- /src/fmsynth_test.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #include "fmsynth.h" 20 | #include 21 | #include 22 | #include 23 | 24 | #ifdef ANDROID 25 | int fmsynth_android_main(int argc, char *argv[]) 26 | #else 27 | int main(int argc, char *argv[]) 28 | #endif 29 | { 30 | FILE *file = NULL; 31 | 32 | if (argc >= 2) 33 | { 34 | file = fopen(argv[1], "w"); 35 | if (!file) 36 | { 37 | return EXIT_FAILURE; 38 | } 39 | } 40 | 41 | fmsynth_t *fm = fmsynth_new(44100.0f, 64); 42 | if (fm == NULL) 43 | { 44 | return EXIT_FAILURE; 45 | } 46 | 47 | for (unsigned i = 0; i < 8; i++) 48 | { 49 | fmsynth_set_parameter(fm, FMSYNTH_PARAM_MOD_TO_CARRIERS0 + i, (i + 1) & 7, 2.0f); 50 | fmsynth_set_parameter(fm, FMSYNTH_PARAM_FREQ_MOD, i, i + 1.0f); 51 | fmsynth_set_parameter(fm, FMSYNTH_PARAM_PAN, i, (i & 1) ? -0.5f : +0.5f); 52 | fmsynth_set_parameter(fm, FMSYNTH_PARAM_CARRIERS, i, 1.0f); 53 | fmsynth_set_parameter(fm, FMSYNTH_PARAM_LFO_FREQ_MOD_DEPTH, i, 0.15f); 54 | } 55 | 56 | for (unsigned i = 0; i < 64; i++) 57 | { 58 | fmsynth_note_on(fm, i + 20, 127); 59 | } 60 | 61 | float left[2048]; 62 | float right[2048]; 63 | for (unsigned i = 0; i < 100; i++) 64 | { 65 | memset(left, 0, sizeof(left)); 66 | memset(right, 0, sizeof(right)); 67 | fmsynth_render(fm, left, right, 2048); 68 | 69 | if (file) 70 | { 71 | for (unsigned x = 0; x < 2048; x++) 72 | { 73 | fprintf(file, "%12.6f %12.6f\n", left[x], right[x]); 74 | } 75 | } 76 | } 77 | 78 | fmsynth_free(fm); 79 | 80 | if (file) 81 | { 82 | fclose(file); 83 | } 84 | 85 | return EXIT_SUCCESS; 86 | } 87 | 88 | -------------------------------------------------------------------------------- /include/fmsynth_private.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #ifndef FMSYNTH_PRIVATE_H__ 20 | #define FMSYNTH_PRIVATE_H__ 21 | 22 | #include "fmsynth.h" 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | struct fmsynth_voice_parameters 29 | { 30 | float amp[FMSYNTH_OPERATORS]; 31 | float pan[FMSYNTH_OPERATORS]; 32 | float freq_mod[FMSYNTH_OPERATORS]; 33 | float freq_offset[FMSYNTH_OPERATORS]; 34 | 35 | float envelope_target[3][FMSYNTH_OPERATORS]; 36 | float envelope_delay[3][FMSYNTH_OPERATORS]; 37 | float envelope_release_time[FMSYNTH_OPERATORS]; 38 | 39 | float keyboard_scaling_mid_point[FMSYNTH_OPERATORS]; 40 | float keyboard_scaling_low_factor[FMSYNTH_OPERATORS]; 41 | float keyboard_scaling_high_factor[FMSYNTH_OPERATORS]; 42 | 43 | float velocity_sensitivity[FMSYNTH_OPERATORS]; 44 | float mod_sensitivity[FMSYNTH_OPERATORS]; 45 | 46 | float lfo_amp_depth[FMSYNTH_OPERATORS]; 47 | float lfo_freq_mod_depth[FMSYNTH_OPERATORS]; 48 | 49 | float enable[FMSYNTH_OPERATORS]; 50 | 51 | float carriers[FMSYNTH_OPERATORS]; 52 | float mod_to_carriers[FMSYNTH_OPERATORS][FMSYNTH_OPERATORS]; 53 | }; 54 | 55 | struct fmsynth_global_parameters 56 | { 57 | float volume; 58 | float lfo_freq; 59 | }; 60 | 61 | fmsynth_status_t fmsynth_preset_load_private(struct fmsynth_global_parameters *global_params, 62 | struct fmsynth_voice_parameters *params, 63 | struct fmsynth_preset_metadata *metadata, 64 | const void *buffer_, size_t size); 65 | 66 | fmsynth_status_t fmsynth_preset_save_private(struct fmsynth_global_parameters *global_params, 67 | struct fmsynth_voice_parameters *params, 68 | const struct fmsynth_preset_metadata *metadata, 69 | void *buffer_, size_t size); 70 | 71 | #ifdef __cplusplus 72 | } 73 | #endif 74 | 75 | #endif 76 | 77 | -------------------------------------------------------------------------------- /android/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | 56 | 57 | 69 | 70 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/x86/fmsynth_avx.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #include 20 | 21 | static void fmsynth_process_frames(fmsynth_t *fm, 22 | struct fmsynth_voice *voice, float *oleft, float *oright, unsigned frames) 23 | { 24 | __m256 phases = _mm256_load_ps(voice->phases); 25 | __m256 env = _mm256_load_ps(voice->env); 26 | 27 | for (unsigned f = 0; f < frames; f++) 28 | { 29 | __m256 x = _mm256_sub_ps(phases, _mm256_set1_ps(0.25f)); 30 | __m256 cmp = _mm256_cmp_ps(phases, _mm256_set1_ps(0.5f), _CMP_LT_OS); 31 | __m256 greater = _mm256_sub_ps(_mm256_set1_ps(0.75f), phases); 32 | x = _mm256_or_ps(_mm256_and_ps(cmp, x), _mm256_andnot_ps(cmp, greater)); 33 | 34 | // Compute sine approximation. 35 | { 36 | __m256 x2 = _mm256_mul_ps(x, x); 37 | __m256 x3 = _mm256_mul_ps(x, x2); 38 | x = _mm256_mul_ps(x, _mm256_set1_ps(2.0f * PI)); 39 | 40 | x = _mm256_sub_ps(x, _mm256_mul_ps(x3, _mm256_set1_ps(INV_FACTORIAL_3_2PIPOW3))); 41 | 42 | x3 = _mm256_mul_ps(x3, x2); 43 | x = _mm256_add_ps(x, _mm256_mul_ps(x3, _mm256_set1_ps(INV_FACTORIAL_5_2PIPOW5))); 44 | 45 | x3 = _mm256_mul_ps(x3, x2); 46 | x = _mm256_sub_ps(x, _mm256_mul_ps(x3, _mm256_set1_ps(INV_FACTORIAL_7_2PIPOW7))); 47 | } 48 | 49 | x = _mm256_mul_ps(x, _mm256_mul_ps(env, _mm256_load_ps(voice->read_mod))); 50 | 51 | env = _mm256_add_ps(env, _mm256_load_ps(voice->target_env_step)); 52 | 53 | __m256 step_rate = _mm256_load_ps(voice->step_rate); 54 | __m256 xmod = _mm256_mul_ps(x, step_rate); 55 | __m256 steps = _mm256_mul_ps(step_rate, _mm256_load_ps(voice->lfo_freq_mod)); 56 | 57 | __m256 perm, lo, hi; 58 | #define MAT_ACCUMULATE(scalar, index) \ 59 | perm = _mm256_permute_ps(scalar, _MM_SHUFFLE(index, index, index, index)); \ 60 | lo = _mm256_permute2f128_ps(perm, perm, 0); \ 61 | hi = _mm256_permute2f128_ps(perm, perm, 17); \ 62 | phases = _mm256_add_ps(phases, \ 63 | _mm256_mul_ps(_mm256_load_ps(fm->params.mod_to_carriers[index + 0]), lo)); \ 64 | steps = _mm256_add_ps(steps, \ 65 | _mm256_mul_ps(_mm256_load_ps(fm->params.mod_to_carriers[index + 4]), hi)); \ 66 | 67 | MAT_ACCUMULATE(xmod, 0); 68 | MAT_ACCUMULATE(xmod, 1); 69 | MAT_ACCUMULATE(xmod, 2); 70 | MAT_ACCUMULATE(xmod, 3); 71 | #undef MAT_ACCUMULATE 72 | 73 | __m256 sleft = _mm256_mul_ps(x, _mm256_load_ps(voice->pan_amp[0])); 74 | __m256 sright = _mm256_mul_ps(x, _mm256_load_ps(voice->pan_amp[1])); 75 | 76 | phases = _mm256_add_ps(phases, steps); 77 | phases = _mm256_sub_ps(phases, _mm256_floor_ps(phases)); 78 | 79 | __m128 left = _mm_add_ps(_mm256_extractf128_ps(sleft, 0), _mm256_extractf128_ps(sleft, 1)); 80 | __m128 right = _mm_add_ps(_mm256_extractf128_ps(sright, 0), _mm256_extractf128_ps(sright, 1)); 81 | 82 | __m128 out = _mm_add_ps(_mm_shuffle_ps(left, right, 83 | _MM_SHUFFLE(1, 0, 1, 0)), 84 | _mm_shuffle_ps(left, right, _MM_SHUFFLE(3, 2, 3, 2))); 85 | out = _mm_add_ps(_mm_permute_ps(out, _MM_SHUFFLE(3, 3, 1, 1)), out); 86 | _mm_store_ss(oleft + f, _mm_add_ss(out, _mm_load_ss(oleft + f))); 87 | _mm_store_ss(oright + f, _mm_add_ss(_mm_movehl_ps(out, out), _mm_load_ss(oright + f))); 88 | } 89 | 90 | _mm256_store_ps(voice->phases, phases); 91 | _mm256_store_ps(voice->env, env); 92 | } 93 | 94 | -------------------------------------------------------------------------------- /lv2/fmsynth_lv2.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #include "fmsynth.h" 20 | #include "fmsynth.peg" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | struct FMSynth : public lvtk::Plugin> 28 | { 29 | using Parent = lvtk::Plugin>; 30 | 31 | FMSynth(double rate) : Parent(peg_n_ports) 32 | { 33 | m_midi_type = map(LV2_MIDI__MidiEvent); 34 | 35 | fm = fmsynth_new(rate, 128); 36 | if (fm == nullptr) 37 | { 38 | throw std::bad_alloc(); 39 | } 40 | } 41 | 42 | ~FMSynth() 43 | { 44 | fmsynth_free(fm); 45 | } 46 | 47 | float clamp(float v, float lo, float hi) 48 | { 49 | return std::min(std::max(v, lo), hi); 50 | } 51 | 52 | float get_param(unsigned index) 53 | { 54 | return clamp(*p(index), peg_ports[index].min, peg_ports[index].max); 55 | } 56 | 57 | void update_parameters() 58 | { 59 | fmsynth_set_global_parameter(fm, FMSYNTH_GLOBAL_PARAM_VOLUME, get_param(peg_volume)); 60 | fmsynth_set_global_parameter(fm, FMSYNTH_GLOBAL_PARAM_LFO_FREQ, get_param(peg_lfofreq)); 61 | 62 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 63 | { 64 | unsigned base_port = peg_amp__op__0_ + o * (peg_amp__op__1_ - peg_amp__op__0_); 65 | for (unsigned i = 0; i < peg_amp__op__1_ - peg_amp__op__0_; i++) 66 | { 67 | fmsynth_set_parameter(fm, i, o, get_param(i + base_port)); 68 | } 69 | } 70 | } 71 | 72 | void run(uint32_t sample_count) 73 | { 74 | const LV2_Atom_Sequence *seq = p(peg_midi); 75 | uint32_t samples_done = 0; 76 | 77 | float *left = p(peg_output_left); 78 | float *right = p(peg_output_right); 79 | 80 | std::memset(left, 0, sample_count * sizeof(float)); 81 | std::memset(right, 0, sample_count * sizeof(float)); 82 | 83 | for (LV2_Atom_Event *ev = lv2_atom_sequence_begin(&seq->body); 84 | !lv2_atom_sequence_is_end(&seq->body, seq->atom.size, ev); 85 | ev = lv2_atom_sequence_next(ev)) 86 | { 87 | update_parameters(); 88 | uint32_t to = ev->time.frames; 89 | 90 | if (to > samples_done) 91 | { 92 | uint32_t to_render = to - samples_done; 93 | fmsynth_render(fm, left, right, to_render); 94 | 95 | samples_done += to_render; 96 | left += to_render; 97 | right += to_render; 98 | } 99 | 100 | if (ev->body.type == m_midi_type) 101 | { 102 | const uint8_t *body = (const uint8_t*)LV2_ATOM_BODY(&ev->body); 103 | fmsynth_parse_midi(fm, body); 104 | #if 0 105 | if (fmsynth_parse_midi(fm, body, size) == FMSYNTH_STATUS_MESSAGE_UNKNOWN) 106 | { 107 | fprintf(stderr, "FM Synth: Unknown message: "); 108 | for (size_t i = 0; i < size; i++) 109 | { 110 | fprintf(stderr, "0x%02x ", body[i]); 111 | } 112 | fprintf(stderr, "\n"); 113 | } 114 | #endif 115 | } 116 | } 117 | 118 | if (sample_count > samples_done) 119 | { 120 | uint32_t to_render = sample_count - samples_done; 121 | fmsynth_render(fm, left, right, to_render); 122 | } 123 | } 124 | 125 | fmsynth_t *fm; 126 | LV2_URID m_midi_type; 127 | }; 128 | 129 | int fmsynth_register = FMSynth::register_class("git://github.com/Themaister/fmsynth"); 130 | 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libfmsynth 2 | 3 | libfmsynth is a C library which implements an FM synthesizer. 4 | Unlike most FM synth implementations in software, this FM synthesizer does not aim to emulate or replicate a particular synth (like DX7) or FM chip. 5 | 6 | The synth was designed primarily to be used as an instrument for my own purposes, hooked up with MIDI to my electric piano. 7 | It was also designed to be potentially useful as a synth backend in other projects. 8 | 9 | The synth core supports: 10 | 11 | - Arbitrary amounts of polyphony 12 | - 8 operators 13 | - No fixed "algorithms" 14 | - Arbitrary modulation, every operator can modulate any other operator, even itself 15 | - Arbitrary carrier selection, every operator can be a carrier 16 | - Sine LFO, separate LFO per voice, modulates amplitude and frequency of operators 17 | - Envelope per operator 18 | - Carrier stereo panning 19 | - Velocity sensitivity per operator 20 | - Mod wheel sensitivity per operator 21 | - Pitch bend 22 | - Keyboard scaling 23 | - Sustain, sustained keys can overlap each other for a very rich sound 24 | - Full floating point implementation optimized for SIMD 25 | - Hard real-time constraints 26 | 27 | In addition, support for some useful auxillary features are implemented: 28 | 29 | - Linux LV2 plugin implementation with simple GTKmm GUI 30 | - Patch load/save as well as direct parameter control through API 31 | - MIDI messages API as well as direct control of the synth with key on/off, etc 32 | 33 | ## Sample sounds/presets 34 | 35 | Some of the sounds you can create using this synth can be heard on my [SoundCloud](https://soundcloud.com/zoned-music/sets/fm-synth) FM synth playlist. 36 | 37 | The sounds do have some effects like delay, reverb and chorus applied to them. 38 | 39 | Some presets can be found in `presets/` directory. 40 | Their raw data can be passed directly to libfmsynths preset API. 41 | 42 | ## License 43 | 44 | libfmsynth is licensed under the permissive MIT license. 45 | 46 | ## Documentation 47 | 48 | The public libfmsynth API is documented with doxygen. Run `doxygen` or `make docs` to generate documentation. 49 | Doxygen 1.8.3 is required. 50 | 51 | After running Doxygen, documents are found in `docs/`. 52 | 53 | ## Optimizations 54 | 55 | libfmsynth is currently optimized for 56 | 57 | - SSE (intrinsics) 58 | - SSE 4.1 (intrinsics) 59 | - AVX (intrinsics) 60 | - ARMv7 NEON (intrinsics and hand coded assembly) 61 | - ARMv8 NEON (intrinsics, untested) 62 | 63 | The SIMD implementations are typically 3-6x faster than the equivalent -Ofast optimized C implementation, even with autovectorization enabled. 64 | 65 | ## Performance 66 | 67 | IPC was measured with `perf` on Linux. 68 | 69 | ### SSE 70 | 71 | At 44.1 kHz, a single core of a 2.66 GHz Core i7 920 can do 750 voice polyphony when fully saturated with SSE 4.1 path. 72 | Throughput is 34.5 Msamples / s (approx. 11 GFlops). 73 | 74 | #### IPC 75 | 1.91 instructions per cycle. 76 | 77 | ### AVX 78 | 79 | The 256-bit vector AVX implementation is roughly 10-15 % faster than SSE (tested on a Sandy Bridge laptop). The reason it's just 10-15 % is because the dependencies between stages in the synth and latency in floating point processing ensure that the execution pipes cannot be fully saturated with useful work. 80 | 81 | #### IPC 82 | 1.00 instructions per cycle. 83 | 84 | ### NEON 85 | 86 | At 44.1 kHz, a single core of a 1.7 GHz Cortex-A15 can do 300 voice polyphony when fully saturated with NEON. 87 | Throughput is 13.1 Msamples / s (approx. 4.75 GFlops). 88 | 89 | #### IPC 90 | 1.00 instructions per cycle. From benchmarking, it does not seem to be possible to get more than one FP32x4 instruction per cycle on NEON. 91 | 92 | ## Signal path 93 | 94 | For a voice of polyphony, LFOs and envelopes are updated every 32nd sample. 95 | Between LFO and envelope updates, a tight loop runs unless it has to exit early due to MIDI updates. 96 | Per sample: 97 | 98 | - Compute sine for 8 operators w/ 4th order Taylor approximation. 99 | - Multiply in envelope, LFO, etc. 100 | - Increment envelope (linear ramp). 101 | - Apply 8-by-8 modulator matrix to result of the sine computation (8-by-8 matrix-vector multiply). 102 | - Accumulate modulation result and base frequencies to operator phases (proper FM, phase integration). 103 | - Mix carrier outputs to left and right channels. 104 | - Wrap around oscillator phase for stability. 105 | 106 | ## Building and installing 107 | 108 | To build, run `make` to build the static library `libfmsynth.a`. The static library has `-fPIC` enabled, to allow linking into a shared library. To build a benchmark/test app, run `make test`. The main purpose of this tool is to benchmark and validate that outputs for C and SIMD paths are adequately similar and that performance is as expected. 109 | 110 | To cross-compile, use `TOOLCHAIN_PREFIX`, e.g. cross-compiling to ARMv7: 111 | 112 | make TOOLCHAIN_PREFIX=arm-linux-gnueabihf- ARCH=armv7 TUNE=cortex-a15 113 | 114 | If `TUNE=` is not set to something, `-march=native` will be assumed. 115 | Note that binaries built with `-march=native` will enable code paths which might not be supported by other processors, especially on x86 if SSE 4.1 or AVX is enabled. 116 | 117 | To install library and header, use `make install PREFIX=$YOUR_PREFIX`. 118 | 119 | ### Building LV2 plugin 120 | 121 | To build libfmsynth as an LV2 plugin you will need: 122 | 123 | - lvtk 124 | - GTKmm 2 125 | - Fairly recent C++11 compiler 126 | 127 | Run: 128 | 129 | cd lv2 130 | make 131 | sudo make install 132 | 133 | The plugin will be installed to `/usr/lib/lv2/`. 134 | Presets in `presets/` are installed to the bundle as well. When attempting to load presets in the UI, one of the shortcuts will point to the LV2 bundle for easy access. 135 | -------------------------------------------------------------------------------- /src/x86/fmsynth_sse.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #if defined(__SSE4_1__) 20 | #include 21 | #else 22 | #include 23 | #endif 24 | 25 | #ifndef __SSE4_1__ 26 | static __m128 floor_sse(__m128 v) 27 | { 28 | const __m128 round32 = _mm_set1_ps(12582912.0f); 29 | __m128 rounded = _mm_sub_ps(_mm_add_ps(v, round32), round32); 30 | __m128 mask = _mm_cmpeq_ps(v, rounded); 31 | 32 | __m128 floored = _mm_sub_ps(_mm_add_ps(_mm_sub_ps(v, _mm_set1_ps(0.5f)), round32), round32); 33 | return _mm_or_ps(_mm_and_ps(v, mask), _mm_andnot_ps(mask, floored)); 34 | } 35 | #endif 36 | 37 | static void fmsynth_process_frames(fmsynth_t *fm, 38 | struct fmsynth_voice *voice, float *oleft, float *oright, unsigned frames) 39 | { 40 | __m128 phases0 = _mm_load_ps(voice->phases + 0); 41 | __m128 phases1 = _mm_load_ps(voice->phases + 4); 42 | 43 | __m128 env0 = _mm_load_ps(voice->env + 0); 44 | __m128 env1 = _mm_load_ps(voice->env + 4); 45 | 46 | for (unsigned f = 0; f < frames; f++) 47 | { 48 | __m128 x0 = _mm_sub_ps(phases0, _mm_set1_ps(0.25f)); 49 | __m128 x1 = _mm_sub_ps(phases1, _mm_set1_ps(0.25f)); 50 | __m128 cmp0 = _mm_cmplt_ps(phases0, _mm_set1_ps(0.5f)); 51 | __m128 cmp1 = _mm_cmplt_ps(phases1, _mm_set1_ps(0.5f)); 52 | __m128 greater0 = _mm_sub_ps(_mm_set1_ps(0.75f), phases0); 53 | __m128 greater1 = _mm_sub_ps(_mm_set1_ps(0.75f), phases1); 54 | x0 = _mm_or_ps(_mm_and_ps(cmp0, x0), _mm_andnot_ps(cmp0, greater0)); 55 | x1 = _mm_or_ps(_mm_and_ps(cmp1, x1), _mm_andnot_ps(cmp1, greater1)); 56 | 57 | // Compute sine approximation. 58 | { 59 | __m128 x20 = _mm_mul_ps(x0, x0); 60 | __m128 x21 = _mm_mul_ps(x1, x1); 61 | __m128 x30 = _mm_mul_ps(x0, x20); 62 | __m128 x31 = _mm_mul_ps(x1, x21); 63 | 64 | x0 = _mm_mul_ps(x0, _mm_set1_ps(2.0f * PI)); 65 | x1 = _mm_mul_ps(x1, _mm_set1_ps(2.0f * PI)); 66 | 67 | x0 = _mm_sub_ps(x0, _mm_mul_ps(x30, _mm_set1_ps(INV_FACTORIAL_3_2PIPOW3))); 68 | x1 = _mm_sub_ps(x1, _mm_mul_ps(x31, _mm_set1_ps(INV_FACTORIAL_3_2PIPOW3))); 69 | 70 | x30 = _mm_mul_ps(x30, x20); 71 | x31 = _mm_mul_ps(x31, x21); 72 | x0 = _mm_add_ps(x0, _mm_mul_ps(x30, _mm_set1_ps(INV_FACTORIAL_5_2PIPOW5))); 73 | x1 = _mm_add_ps(x1, _mm_mul_ps(x31, _mm_set1_ps(INV_FACTORIAL_5_2PIPOW5))); 74 | 75 | x30 = _mm_mul_ps(x30, x20); 76 | x31 = _mm_mul_ps(x31, x21); 77 | x0 = _mm_sub_ps(x0, _mm_mul_ps(x30, _mm_set1_ps(INV_FACTORIAL_7_2PIPOW7))); 78 | x1 = _mm_sub_ps(x1, _mm_mul_ps(x31, _mm_set1_ps(INV_FACTORIAL_7_2PIPOW7))); 79 | } 80 | 81 | x0 = _mm_mul_ps(x0, _mm_mul_ps(env0, _mm_load_ps(voice->read_mod + 0))); 82 | x1 = _mm_mul_ps(x1, _mm_mul_ps(env1, _mm_load_ps(voice->read_mod + 4))); 83 | 84 | env0 = _mm_add_ps(env0, _mm_load_ps(voice->target_env_step + 0)); 85 | env1 = _mm_add_ps(env1, _mm_load_ps(voice->target_env_step + 4)); 86 | 87 | __m128 step_rate0 = _mm_load_ps(voice->step_rate + 0); 88 | __m128 step_rate1 = _mm_load_ps(voice->step_rate + 4); 89 | 90 | __m128 xmod0 = _mm_mul_ps(x0, step_rate0); 91 | __m128 xmod1 = _mm_mul_ps(x1, step_rate1); 92 | 93 | __m128 steps0 = _mm_mul_ps(step_rate0, _mm_load_ps(voice->lfo_freq_mod + 0)); 94 | __m128 steps1 = _mm_mul_ps(step_rate1, _mm_load_ps(voice->lfo_freq_mod + 4)); 95 | const float *vec; 96 | 97 | #define MAT_ACCUMULATE(steps0, steps1, i, scalar, index) \ 98 | vec = fm->params.mod_to_carriers[i]; \ 99 | steps0 = _mm_add_ps(steps0, _mm_mul_ps(_mm_load_ps(vec + 0), \ 100 | _mm_shuffle_ps(scalar, scalar, \ 101 | _MM_SHUFFLE(index, index, index, index)))); \ 102 | steps1 = _mm_add_ps(steps1, _mm_mul_ps(_mm_load_ps(vec + 4), \ 103 | _mm_shuffle_ps(scalar, scalar, \ 104 | _MM_SHUFFLE(index, index, index, index)))) 105 | 106 | MAT_ACCUMULATE(steps0, steps1, 0, xmod0, 0); 107 | MAT_ACCUMULATE(phases0, phases1, 1, xmod0, 1); 108 | MAT_ACCUMULATE(steps0, steps1, 2, xmod0, 2); 109 | MAT_ACCUMULATE(phases0, phases1, 3, xmod0, 3); 110 | MAT_ACCUMULATE(steps0, steps1, 4, xmod1, 0); 111 | MAT_ACCUMULATE(phases0, phases1, 5, xmod1, 1); 112 | MAT_ACCUMULATE(steps0, steps1, 6, xmod1, 2); 113 | MAT_ACCUMULATE(phases0, phases1, 7, xmod1, 3); 114 | #undef MAT_ACCUMULATE 115 | 116 | __m128 left = _mm_mul_ps(x0, _mm_load_ps(voice->pan_amp[0] + 0)); 117 | __m128 right = _mm_mul_ps(x0, _mm_load_ps(voice->pan_amp[1] + 0)); 118 | 119 | phases0 = _mm_add_ps(phases0, steps0); 120 | phases1 = _mm_add_ps(phases1, steps1); 121 | #ifdef __SSE4_1__ 122 | phases0 = _mm_sub_ps(phases0, _mm_floor_ps(phases0)); 123 | phases1 = _mm_sub_ps(phases1, _mm_floor_ps(phases1)); 124 | #else 125 | phases0 = _mm_sub_ps(phases0, floor_sse(phases0)); 126 | phases1 = _mm_sub_ps(phases1, floor_sse(phases1)); 127 | #endif 128 | 129 | left = _mm_add_ps(left, _mm_mul_ps(x1, _mm_load_ps(voice->pan_amp[0] + 4))); 130 | right = _mm_add_ps(right, _mm_mul_ps(x1, _mm_load_ps(voice->pan_amp[1] + 4))); 131 | 132 | __m128 out = _mm_add_ps(_mm_shuffle_ps(left, right, 133 | _MM_SHUFFLE(1, 0, 1, 0)), 134 | _mm_shuffle_ps(left, right, _MM_SHUFFLE(3, 2, 3, 2))); 135 | out = _mm_add_ps(_mm_shuffle_ps(out, out, _MM_SHUFFLE(3, 3, 1, 1)), out); 136 | _mm_store_ss(oleft + f, _mm_add_ss(out, _mm_load_ss(oleft + f))); 137 | _mm_store_ss(oright + f, _mm_add_ss(_mm_movehl_ps(out, out), _mm_load_ss(oright + f))); 138 | } 139 | 140 | _mm_store_ps(voice->phases + 0, phases0); 141 | _mm_store_ps(voice->phases + 4, phases1); 142 | _mm_store_ps(voice->env + 0, env0); 143 | _mm_store_ps(voice->env + 4, env1); 144 | } 145 | 146 | -------------------------------------------------------------------------------- /src/arm/fmsynth_arm.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #define FMSYNTH_NEON_ASM 1 20 | 21 | #if FMSYNTH_NEON_ASM 22 | void fmsynth_process_frames_neon(const float *mod_to_carriers, 23 | const float *voice, float *left, float *right, unsigned frames); 24 | 25 | static void fmsynth_process_frames(fmsynth_t *fm, 26 | struct fmsynth_voice *voice, float *oleft, float *oright, 27 | unsigned frames) 28 | { 29 | fmsynth_process_frames_neon(fm->params.mod_to_carriers[0], voice->phases, 30 | oleft, oright, frames); 31 | } 32 | #else 33 | #include 34 | static inline float32x4_t floor_neon(float32x4_t a) 35 | { 36 | #if __ARM_ARCH >= 8 37 | return vrndqm_f32(a); 38 | #else 39 | const float32x4_t round32 = vdupq_n_f32(12582912.0f); 40 | const float32x4_t vhalf = vdupq_n_f32(0.5f); 41 | 42 | float32x4_t rounded = vsubq_f32(vaddq_f32(a, round32), round32); 43 | uint32x4_t mask = vceqq_f32(a, rounded); 44 | 45 | float32x4_t floored = vsubq_f32(vaddq_f32(vsubq_f32(a, vhalf), round32), round32); 46 | return vreinterpretq_f32_u32(vorrq_u32(vandq_u32(vreinterpretq_u32_f32(a), mask), 47 | vbicq_u32(vreinterpretq_u32_f32(floored), mask))); 48 | #endif 49 | } 50 | 51 | static void fmsynth_process_frames(fmsynth_t * restrict fm_, 52 | struct fmsynth_voice * restrict voice_, float * restrict oleft_, float * restrict oright_, unsigned frames) 53 | { 54 | fmsynth_t *fm = FMSYNTH_ASSUME_ALIGNED(fm_, 16); 55 | struct fmsynth_voice *voice = FMSYNTH_ASSUME_ALIGNED(voice_, 16); 56 | float *oleft = FMSYNTH_ASSUME_ALIGNED(oleft_, 16); 57 | float *oright = FMSYNTH_ASSUME_ALIGNED(oright_, 16); 58 | 59 | float32x4_t phases0 = vld1q_f32(voice->phases + 0); 60 | float32x4_t phases1 = vld1q_f32(voice->phases + 4); 61 | 62 | float32x4_t env0 = vld1q_f32(voice->env + 0); 63 | float32x4_t env1 = vld1q_f32(voice->env + 4); 64 | 65 | const float32x4_t step_rate0 = vld1q_f32(voice->step_rate + 0); 66 | const float32x4_t step_rate1 = vld1q_f32(voice->step_rate + 4); 67 | 68 | for (unsigned f = 0; f < frames; f++) 69 | { 70 | const float32x4_t c_sub = vdupq_n_f32(0.25f); 71 | const float32x4_t c_cmp = vdupq_n_f32(0.5f); 72 | const float32x4_t c_greater = vdupq_n_f32(0.75f); 73 | 74 | float32x4_t x0 = vsubq_f32(phases0, c_sub); 75 | float32x4_t x1 = vsubq_f32(phases1, c_sub); 76 | uint32x4_t cmp0 = vcltq_f32(phases0, c_cmp); 77 | uint32x4_t cmp1 = vcltq_f32(phases1, c_cmp); 78 | float32x4_t greater0 = vsubq_f32(c_greater, phases0); 79 | float32x4_t greater1 = vsubq_f32(c_greater, phases1); 80 | x0 = vreinterpretq_f32_u32( 81 | vorrq_u32(vandq_u32(cmp0, vreinterpretq_u32_f32(x0)), 82 | vbicq_u32(vreinterpretq_u32_f32(greater0), cmp0))); 83 | x1 = vreinterpretq_f32_u32( 84 | vorrq_u32(vandq_u32(cmp1, vreinterpretq_u32_f32(x1)), 85 | vbicq_u32(vreinterpretq_u32_f32(greater1), cmp1))); 86 | 87 | // Compute sine approximation. 88 | { 89 | const float32x4_t fact3 = vdupq_n_f32(INV_FACTORIAL_3_2PIPOW3); 90 | const float32x4_t fact5 = vdupq_n_f32(INV_FACTORIAL_5_2PIPOW5); 91 | const float32x4_t fact7 = vdupq_n_f32(INV_FACTORIAL_7_2PIPOW7); 92 | 93 | float32x4_t x20 = vmulq_f32(x0, x0); 94 | float32x4_t x21 = vmulq_f32(x1, x1); 95 | float32x4_t x30 = vmulq_f32(x0, x20); 96 | float32x4_t x31 = vmulq_f32(x1, x21); 97 | 98 | x0 = vmulq_n_f32(x0, 2.0f * PI); 99 | x1 = vmulq_n_f32(x1, 2.0f * PI); 100 | 101 | x0 = vmlsq_f32(x0, x30, fact3); 102 | x1 = vmlsq_f32(x1, x31, fact3); 103 | 104 | x30 = vmulq_f32(x30, x20); 105 | x31 = vmulq_f32(x31, x21); 106 | 107 | x0 = vmlaq_f32(x0, x30, fact5); 108 | x1 = vmlaq_f32(x1, x31, fact5); 109 | 110 | x30 = vmulq_f32(x30, x20); 111 | x31 = vmulq_f32(x31, x21); 112 | 113 | x0 = vmlsq_f32(x0, x30, fact7); 114 | x1 = vmlsq_f32(x1, x31, fact7); 115 | } 116 | 117 | x0 = vmulq_f32(x0, vmulq_f32(vld1q_f32(voice->read_mod + 0), env0)); 118 | x1 = vmulq_f32(x1, vmulq_f32(vld1q_f32(voice->read_mod + 4), env1)); 119 | 120 | env0 = vaddq_f32(env0, vld1q_f32(voice->target_env_step + 0)); 121 | env1 = vaddq_f32(env1, vld1q_f32(voice->target_env_step + 4)); 122 | 123 | float32x4_t xmod0 = vmulq_f32(x0, step_rate0); 124 | float32x4_t xmod1 = vmulq_f32(x1, step_rate1); 125 | 126 | float32x4_t steps0 = vmulq_f32(step_rate0, vld1q_f32(voice->lfo_freq_mod + 0)); 127 | float32x4_t steps1 = vmulq_f32(step_rate1, vld1q_f32(voice->lfo_freq_mod + 4)); 128 | const float *vec; 129 | 130 | #define MAT_ACCUMULATE(steps0, steps1, i, scalar, index) \ 131 | vec = fm->params.mod_to_carriers[i]; \ 132 | steps0 = vmlaq_lane_f32(steps0, vld1q_f32(vec + 0), scalar, index); \ 133 | steps1 = vmlaq_lane_f32(steps1, vld1q_f32(vec + 4), scalar, index) 134 | 135 | MAT_ACCUMULATE(steps0, steps1, 0, vget_low_f32(xmod0), 0); 136 | MAT_ACCUMULATE(phases0, phases1, 1, vget_low_f32(xmod0), 1); 137 | MAT_ACCUMULATE(steps0, steps1, 2, vget_high_f32(xmod0), 0); 138 | MAT_ACCUMULATE(phases0, phases1, 3, vget_high_f32(xmod0), 1); 139 | MAT_ACCUMULATE(steps0, steps1, 4, vget_low_f32(xmod1), 0); 140 | MAT_ACCUMULATE(phases0, phases1, 5, vget_low_f32(xmod1), 1); 141 | MAT_ACCUMULATE(steps0, steps1, 6, vget_high_f32(xmod1), 0); 142 | MAT_ACCUMULATE(phases0, phases1, 7, vget_high_f32(xmod1), 1); 143 | #undef MAT_ACCUMULATE 144 | 145 | float32x4_t left = vmulq_f32(x0, vld1q_f32(voice->pan_amp[0] + 0)); 146 | float32x4_t right = vmulq_f32(x0, vld1q_f32(voice->pan_amp[1] + 0)); 147 | 148 | phases0 = vaddq_f32(phases0, steps0); 149 | phases1 = vaddq_f32(phases1, steps1); 150 | 151 | left = vmlaq_f32(left, x1, vld1q_f32(voice->pan_amp[0] + 4)); 152 | right = vmlaq_f32(right, x1, vld1q_f32(voice->pan_amp[1] + 4)); 153 | 154 | phases0 = vsubq_f32(phases0, floor_neon(phases0)); 155 | phases1 = vsubq_f32(phases1, floor_neon(phases1)); 156 | 157 | float32x2_t hleft = vadd_f32(vget_low_f32(left), vget_high_f32(left)); 158 | float32x2_t hright = vadd_f32(vget_low_f32(right), vget_high_f32(right)); 159 | float32x2_t out = vpadd_f32(hleft, hright); 160 | 161 | float32x2_t current = vld1_dup_f32(oleft + f); 162 | current = vld1_lane_f32(oright + f, current, 1); 163 | 164 | out = vadd_f32(out, current); 165 | vst1_lane_f32(oleft + f, out, 0); 166 | vst1_lane_f32(oright + f, out, 1); 167 | } 168 | 169 | vst1q_f32(voice->phases + 0, phases0); 170 | vst1q_f32(voice->phases + 4, phases1); 171 | vst1q_f32(voice->env + 0, env0); 172 | vst1q_f32(voice->env + 4, env1); 173 | } 174 | #endif 175 | 176 | -------------------------------------------------------------------------------- /src/arm/fmsynth_neon.S: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | .text 20 | .arm 21 | 22 | #define PHASE0 q8 23 | #define PHASE1 q9 24 | #define ENV0 q10 25 | #define ENV1 q11 26 | 27 | #define X0 q4 28 | #define X1 q5 29 | 30 | // Only used in sine computation. 31 | // Contents can be discarded after taylor approx. 32 | #define X20 q6 33 | #define X21 q7 34 | #define X30 q12 35 | #define X31 q13 36 | 37 | #define XMOD0 q6 38 | #define XMOD1 q7 39 | #define LDREG0 q0 40 | #define LDREG1 q1 41 | #define LDREG2 q2 42 | #define LDREG3 q3 43 | #define LEFT q12 44 | #define RIGHT q13 45 | #define TMP0 q14 46 | #define TMP1 q15 47 | 48 | // Used for fract() computation only. 49 | #define ROUND32 LDREG0 50 | #define HALF LDREG1 51 | #define FLOORED0 LDREG2 52 | #define FLOORED1 LDREG3 53 | #define ROUNDED0 TMP0 54 | #define ROUNDED1 TMP1 55 | #define MASK0 XMOD0 56 | #define MASK1 XMOD1 57 | 58 | #define XMOD_0 d12[0] 59 | #define XMOD_1 d12[1] 60 | #define XMOD_2 d13[0] 61 | #define XMOD_3 d13[1] 62 | #define XMOD_4 d14[0] 63 | #define XMOD_5 d14[1] 64 | #define XMOD_6 d15[0] 65 | #define XMOD_7 d15[1] 66 | 67 | #define LDREG_0 d0[0] 68 | #define LDREG_1 d0[1] 69 | #define LDREG_2 d1[0] 70 | #define LDREG_3 d1[1] 71 | 72 | #define LEFT_0 d24 73 | #define LEFT_1 d25 74 | #define RIGHT_0 d26 75 | #define RIGHT_1 d27 76 | 77 | #define TMP_0 d28 78 | 79 | #define PHASE_OFFSET 0 80 | #define ENV_OFFSET 32 81 | #define BASE_OFFSET 64 82 | 83 | .align 4 84 | .globl fmsynth_process_frames_neon 85 | fmsynth_process_frames_neon: 86 | 87 | vpush {d8 - d15} 88 | push {r4 - r12, lr} 89 | 90 | ldr r4, [sp, #(10 * 4 + 8 * 8)] 91 | 92 | adr r6, CONSTANTS 93 | 94 | add r8, r1, #PHASE_OFFSET 95 | add r9, r1, #ENV_OFFSET 96 | 97 | vld1.32 {PHASE0-PHASE1}, [r8, :128] 98 | vld1.32 {ENV0-ENV1}, [r9, :128] 99 | 100 | .align 4 101 | 1: 102 | // Base offset for READ_MOD. 103 | add r11, r1, #BASE_OFFSET 104 | 105 | // Talyor sine approximation. 106 | // No LUT. 107 | vmov.f32 LDREG0, #0.25 108 | vmov.f32 LDREG1, #0.50 109 | vmov.f32 LDREG2, #0.75 110 | 111 | vclt.f32 MASK0, PHASE0, LDREG1 112 | vclt.f32 MASK1, PHASE1, LDREG1 113 | vsub.f32 X0, PHASE0, LDREG0 114 | vsub.f32 X1, PHASE1, LDREG0 115 | vsub.f32 TMP0, LDREG2, PHASE0 116 | vsub.f32 TMP1, LDREG2, PHASE1 117 | 118 | vand X0, X0, MASK0 119 | vand X1, X1, MASK1 120 | vbic TMP0, TMP0, MASK0 121 | vbic TMP1, TMP1, MASK1 122 | vorr X0, X0, TMP0 123 | vorr X1, X1, TMP1 124 | 125 | vld1.32 {LDREG0-LDREG1}, [r6, :128] 126 | 127 | vmul.f32 X20, X0, X0 128 | vmul.f32 X21, X1, X1 129 | vmul.f32 X30, X0, X20 130 | vmul.f32 X31, X1, X21 131 | 132 | vmul.f32 X0, X0, LDREG1 133 | vmul.f32 X1, X1, LDREG1 134 | 135 | vmls.f32 X0, X30, LDREG_0 136 | vmls.f32 X1, X31, LDREG_0 137 | 138 | vmul.f32 X30, X20, X30 139 | vmul.f32 X31, X21, X31 140 | 141 | vmla.f32 X0, X30, LDREG_1 142 | vmla.f32 X1, X31, LDREG_1 143 | 144 | vmul.f32 X30, X20, X30 145 | vmul.f32 X31, X21, X31 146 | 147 | vmls.f32 X0, X30, LDREG_2 148 | vmls.f32 X1, X31, LDREG_2 149 | // End Taylor sine approximation. 150 | 151 | vld1.32 {LDREG0-LDREG1}, [r11, :128]! 152 | vmul.f32 TMP0, ENV0, LDREG0 153 | vmul.f32 TMP1, ENV1, LDREG1 154 | 155 | // Apply read_mod and envelope. 156 | vmul.f32 X0, X0, TMP0 157 | vmul.f32 X1, X1, TMP1 158 | 159 | // Increment envelope. 160 | vld1.32 {LDREG0-LDREG1}, [r11, :128]! 161 | vadd.f32 ENV0, ENV0, LDREG0 162 | vadd.f32 ENV1, ENV1, LDREG1 163 | 164 | // Modulator version of oscillators. 165 | vld1.32 {LDREG0-LDREG1}, [r11, :128]! 166 | vmul.f32 XMOD0, X0, LDREG0 167 | vmul.f32 XMOD1, X1, LDREG1 168 | 169 | // Initial phase step value. 170 | vld1.32 {LDREG2-LDREG3}, [r11, :128]! 171 | vmul.f32 TMP0, LDREG0, LDREG2 172 | vmul.f32 TMP1, LDREG1, LDREG3 173 | 174 | // 8-by-8 matrix-vector multiply. 175 | // Alternate MLA target to improve pipelining. 176 | // 5 cycle latency for MLA with dependent adder on Cortex-A15. 177 | // Out-of-order should help fill the missing cycles with something useful. 178 | mov r10, r0 179 | vld1.32 {LDREG0-LDREG1}, [r10, :128]! 180 | vmla.f32 PHASE0, LDREG0, XMOD_0 181 | vmla.f32 PHASE1, LDREG1, XMOD_0 182 | 183 | vld1.32 {LDREG0-LDREG1}, [r10, :128]! 184 | vmla.f32 TMP0, LDREG0, XMOD_1 185 | vmla.f32 TMP1, LDREG1, XMOD_1 186 | 187 | vld1.32 {LDREG0-LDREG1}, [r10, :128]! 188 | vmla.f32 PHASE0, LDREG0, XMOD_2 189 | vmla.f32 PHASE1, LDREG1, XMOD_2 190 | 191 | vld1.32 {LDREG0-LDREG1}, [r10, :128]! 192 | vmla.f32 TMP0, LDREG0, XMOD_3 193 | vmla.f32 TMP1, LDREG1, XMOD_3 194 | 195 | vld1.32 {LDREG0-LDREG1}, [r10, :128]! 196 | vmla.f32 PHASE0, LDREG0, XMOD_4 197 | vmla.f32 PHASE1, LDREG1, XMOD_4 198 | 199 | vld1.32 {LDREG0-LDREG1}, [r10, :128]! 200 | vmla.f32 TMP0, LDREG0, XMOD_5 201 | vmla.f32 TMP1, LDREG1, XMOD_5 202 | 203 | vld1.32 {LDREG0-LDREG1}, [r10, :128]! 204 | vmla.f32 PHASE0, LDREG0, XMOD_6 205 | vmla.f32 PHASE1, LDREG1, XMOD_6 206 | 207 | vld1.32 {LDREG0-LDREG1}, [r10, :128]! 208 | vmla.f32 TMP0, LDREG0, XMOD_7 209 | vmla.f32 TMP1, LDREG1, XMOD_7 210 | /////// 211 | 212 | vld1.32 {LDREG0-LDREG1}, [r11, :128]! 213 | vld1.32 {LDREG2-LDREG3}, [r11, :128]! 214 | 215 | vmul.f32 LEFT, X0, LDREG0 216 | vmul.f32 RIGHT, X0, LDREG2 217 | 218 | // Add in interleaved MLA register. 219 | vadd.f32 PHASE0, PHASE0, TMP0 220 | vadd.f32 PHASE1, PHASE1, TMP1 221 | 222 | vmla.f32 LEFT, X1, LDREG1 223 | vmla.f32 RIGHT, X1, LDREG3 224 | 225 | // Compute fract() for phase registers. 226 | // AArch64 gives us proper vrndm in a single operation ... 227 | adr r12, ROUND32_CONSTANT 228 | vld1.f32 {ROUND32}, [r12, :128] 229 | vmov.f32 HALF, #0.5 230 | 231 | vsub.f32 FLOORED0, PHASE0, HALF 232 | vsub.f32 FLOORED1, PHASE1, HALF 233 | 234 | vadd.f32 ROUNDED0, PHASE0, ROUND32 235 | vadd.f32 ROUNDED1, PHASE1, ROUND32 236 | vadd.f32 FLOORED0, FLOORED0, ROUND32 237 | vadd.f32 FLOORED1, FLOORED1, ROUND32 238 | 239 | vsub.f32 ROUNDED0, ROUNDED0, ROUND32 240 | vsub.f32 ROUNDED1, ROUNDED1, ROUND32 241 | vsub.f32 FLOORED0, FLOORED0, ROUND32 242 | vsub.f32 FLOORED1, FLOORED1, ROUND32 243 | 244 | vceq.f32 MASK0, PHASE0, ROUNDED0 245 | vceq.f32 MASK1, PHASE1, ROUNDED1 246 | 247 | vand ROUNDED0, PHASE0, MASK0 248 | vand ROUNDED1, PHASE1, MASK1 249 | vbic FLOORED0, FLOORED0, MASK0 250 | vbic FLOORED1, FLOORED1, MASK1 251 | 252 | vorr FLOORED0, FLOORED0, ROUNDED0 253 | vorr FLOORED1, FLOORED1, ROUNDED1 254 | 255 | vsub.f32 PHASE0, PHASE0, FLOORED0 256 | vsub.f32 PHASE1, PHASE1, FLOORED1 257 | /////// 258 | 259 | // Mix-down 260 | vadd.f32 LEFT_0, LEFT_0, LEFT_1 261 | vadd.f32 LEFT_1, RIGHT_0, RIGHT_1 262 | vpadd.f32 LEFT_0, LEFT_0, LEFT_1 263 | 264 | // Mix in result to buffer. 265 | vld1.32 {TMP_0[0]}, [r2, :32] 266 | vld1.32 {TMP_0[1]}, [r3, :32] 267 | vadd.f32 TMP_0, TMP_0, LEFT_0 268 | vst1.32 {TMP_0[0]}, [r2, :32]! 269 | vst1.32 {TMP_0[1]}, [r3, :32]! 270 | 271 | subs r4, r4, #1 272 | bgt 1b 273 | 274 | // Save back phase and envelopes. 275 | vst1.32 {PHASE0-PHASE1}, [r8, :128] 276 | vst1.32 {ENV0-ENV1}, [r9, :128] 277 | 278 | pop {r4 - r12, lr} 279 | vpop {d8 - d15} 280 | 281 | bx lr 282 | 283 | .align 4 284 | CONSTANTS: 285 | .float 41.341702240399755 286 | .float 81.60524927607504 287 | .float 76.70585975306136 288 | .float 0.0 289 | .float 6.28318530717958 290 | .float 6.28318530717958 291 | .float 6.28318530717958 292 | .float 6.28318530717958 293 | ROUND32_CONSTANT: 294 | .float 12582912.0 295 | .float 12582912.0 296 | .float 12582912.0 297 | .float 12582912.0 298 | 299 | -------------------------------------------------------------------------------- /include/fmsynth.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #ifndef FMSYNTH_H__ 20 | #define FMSYNTH_H__ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #else 29 | #include 30 | #endif 31 | 32 | /** \weakgroup libfmsynth libfmsynth public API 33 | * @{ 34 | */ 35 | 36 | /** 37 | * Number of FM operators supported. Changing this will break ABI 38 | * and SIMD implementations. 39 | */ 40 | #define FMSYNTH_OPERATORS 8 41 | 42 | /** 43 | * Opaque type which encapsulates FM synth state. 44 | */ 45 | typedef struct fmsynth fmsynth_t; 46 | 47 | /** 48 | * Parameters for the synth which are unique per FM operator. 49 | */ 50 | enum fmsynth_parameter 51 | { 52 | FMSYNTH_PARAM_AMP = 0, /**< Linear amplitude for the operator. */ 53 | FMSYNTH_PARAM_PAN, /**< Panning for operator when it's used as a carrier. -1.0 is left, +1.0 is right, +0.0 is centered. */ 54 | 55 | FMSYNTH_PARAM_FREQ_MOD, /**< Frequency mod factor. The base frequency of the operator is note frequency times the freq mod. 56 | E.g. A4 with freq mod of 2.0 would be 880 Hz. */ 57 | FMSYNTH_PARAM_FREQ_OFFSET, /**< A constant frequency offset applied to the oscillator. */ 58 | 59 | FMSYNTH_PARAM_ENVELOPE_TARGET0, /**< The linear amplitude reached in the envelope after FMSYNTH_PARAM_DELAY0 seconds. Initial amplitude is 0. */ 60 | FMSYNTH_PARAM_ENVELOPE_TARGET1, /**< The linear amplitude reached in the envelope after (FMSYNTH_PARAM_DELAY0 + FMSYNTH_PARAM_DELAY1) seconds. */ 61 | FMSYNTH_PARAM_ENVELOPE_TARGET2, /**< The linear amplitide reached in the envelope after (FMSYNTH_PARAM_DELAY0 + FMSYNTH_PARAM_DELAY1 + FMSYNTH_PARAM_DELAY2) seconds. */ 62 | FMSYNTH_PARAM_DELAY0, /**< The time in seconds for the envelope to reach FMSYNTH_PARAM_ENVELOPE_TARGET0. */ 63 | FMSYNTH_PARAM_DELAY1, /**< The time in seconds for the envelope to reach FMSYNTH_PARAM_ENVELOPE_TARGET1. */ 64 | FMSYNTH_PARAM_DELAY2, /**< The time in seconds for the envelope to reach FMSYNTH_PARAM_ENVELOPE_TARGET2. */ 65 | FMSYNTH_PARAM_RELEASE_TIME, /**< After releasing the key, the time it takes for the operator to attenuate 60 dB. */ 66 | 67 | FMSYNTH_PARAM_KEYBOARD_SCALING_MID_POINT, /**< The frequency which splits the keyboard into a "low" and "high" section. 68 | This frequency only depends on the note itself, not FMSYNTH_PARAM_FREQ_MOD, etc. */ 69 | FMSYNTH_PARAM_KEYBOARD_SCALING_LOW_FACTOR, /**< Amplitude scaling factor pow(note_frequency / SCALING_MID_POINT, SCALING_LOW_FACTOR) if the key pressed 70 | is in the "low" section of the keyboard. 71 | Negative values will boost amplitide for lower frequency keys and attenuate amplitude for higher frequency keys. 72 | E.g. A value of -1.0 will add a 6 dB attenuation per octave. */ 73 | FMSYNTH_PARAM_KEYBOARD_SCALING_HIGH_FACTOR, /**< Amplitude scaling factor pow(note_frequency / SCALING_MID_POINT, SCALING_HIGH_FACTOR) if the key pressed 74 | is in the "high" section of the keyboard. 75 | Negative values will boost amplitide for lower frequency keys and attenuate amplitude for higher frequency keys. 76 | E.g. A value of -1.0 will add a 6 dB attenuation per octave. */ 77 | 78 | FMSYNTH_PARAM_VELOCITY_SENSITIVITY, /**< Controls velocity sensitivity. If 0.0, operator amplitude is independent of key velocity. 79 | If 1.0, the operator amplitude is fully dependent on key velocity. 80 | `factor = (1.0 - VELOCITY_SENSITIVITY) + VELOCITY_SENSITIVITY * velocity`. 81 | `velocity` is normalized to [0, 1]. */ 82 | FMSYNTH_PARAM_MOD_WHEEL_SENSITIVITY, /**< If 0.0, operator amplitude is independent of mod wheel state. 83 | If 1.0, operator amplitude is fully dependent on mod wheel state. 84 | `factor = (1.0 - MOD_WHEEL_SENSITIVITY) + MOD_WHEEL_SENSITIVITY * mod_wheel`. 85 | `mod_wheel` is normalized to [0, 1]. */ 86 | 87 | FMSYNTH_PARAM_LFO_AMP_SENSITIVITY, /**< Specifies how much the LFO modulates amplitude. 88 | Modulation factor is: 1.0 + lfo_value * LFO_AMP_SENSITIVITY. 89 | lfo_value has a range of [-1, 1]. */ 90 | FMSYNTH_PARAM_LFO_FREQ_MOD_DEPTH, /**< Specifies how much the LFO modulates frequency. 91 | Modulation factor is: 1.0 + lfo_value * LFO_FREQ_MOD_DEPTH. 92 | lfo_value has a range of [-1, 1]. */ 93 | 94 | FMSYNTH_PARAM_ENABLE, /**< Enable operator if value > 0.5, otherwise, disable. */ 95 | 96 | FMSYNTH_PARAM_CARRIERS, /**< Set carrier mixing factor. If > 0.0, the operator will generate audio that is mixed into the final output. */ 97 | FMSYNTH_PARAM_MOD_TO_CARRIERS0, /**< Sets how much the operator will modulate carrier `N`. Use `FMSYNTH_PARAM_MOD_TO_CARRIERS0 + N` to specify which operator is the modulator target. */ 98 | 99 | FMSYNTH_PARAM_END = FMSYNTH_PARAM_MOD_TO_CARRIERS0 + FMSYNTH_OPERATORS, /**< The number of parameters available. */ 100 | FMSYNTH_PARAM_ENSURE_INT = INT_MAX /**< Ensure the enum is sizeof(int). */ 101 | }; 102 | 103 | /** 104 | * Parameters which are global to the entire synth. 105 | */ 106 | enum fmsynth_global_parameter 107 | { 108 | FMSYNTH_GLOBAL_PARAM_VOLUME = 0, /**< Overall volume of the synth. */ 109 | FMSYNTH_GLOBAL_PARAM_LFO_FREQ, /**< LFO frequency in Hz. */ 110 | 111 | FMSYNTH_GLOBAL_PARAM_END, /**< The number of global parameters available. */ 112 | FMSYNTH_GLOBAL_PARAM_ENSURE_INT = INT_MAX /**< Ensure the enum is sizeof(int). */ 113 | }; 114 | 115 | /** 116 | * Generic status code for certain functions. 117 | */ 118 | typedef enum fmsynth_status 119 | { 120 | FMSYNTH_STATUS_OK = 0, /**< Operation completed successfully. */ 121 | FMSYNTH_STATUS_BUSY, /**< Operation could not complete due to insufficient resources at the moment. */ 122 | 123 | FMSYNTH_STATUS_BUFFER_TOO_SMALL, /**< Provided buffer is too small. */ 124 | FMSYNTH_STATUS_NO_NUL_TERMINATE, /**< Metadata string was not properly NUL-terminated. */ 125 | FMSYNTH_STATUS_INVALID_FORMAT, /**< Provided buffer does not adhere to specified format. */ 126 | 127 | FMSYNTH_STATUS_MESSAGE_UNKNOWN, /**< Provided MIDI message is unknown. */ 128 | 129 | FMSYNTH_STATUS_ENSURE_INT = INT_MAX /**< Ensure the enum is sizeof(int). */ 130 | } fmsynth_status_t; 131 | 132 | /** \addtogroup libfmsynthVersion API versioning */ 133 | /** @{ */ 134 | 135 | /** 136 | * Current API version of libfmsynth. 137 | */ 138 | #define FMSYNTH_VERSION 2 139 | 140 | /** \brief Returns current version of libfmsynth. 141 | * 142 | * If this mismatches with what the application expects, an ABI mismatch is likely. 143 | * @returns Version of libfmsynth. 144 | */ 145 | unsigned fmsynth_get_version(void); 146 | /** @} */ 147 | 148 | /** \addtogroup libfmsynthLifetime Lifetime */ 149 | /** @{ */ 150 | /** \brief Allocate a new instance of an FM synth. 151 | * 152 | * Must be freed later with \ref fmsynth_free. 153 | * 154 | * @param sample_rate Sample rate in Hz for the synthesizer. Cannot be changed once initialized. 155 | * @param max_voices The maximum number of simultaneous voices (polyphony) the synth can support. Cannot be changed once initialized. 156 | * 157 | * @returns Newly allocated instance if successful, otherwise NULL. 158 | */ 159 | fmsynth_t *fmsynth_new(float sample_rate, unsigned max_voices); 160 | 161 | /** \brief Reset FM synth state to initial values. 162 | * 163 | * Resets the internal state as if the instance had just been created using \ref fmsynth_new. 164 | * 165 | * @param fm Handle to an FM synth instance. 166 | */ 167 | void fmsynth_reset(fmsynth_t *fm); 168 | 169 | /** \brief Free an FM synth instance. 170 | * 171 | * @param fm Handle to an FM synth instance. 172 | */ 173 | void fmsynth_free(fmsynth_t *fm); 174 | /** @} */ 175 | 176 | /** \addtogroup libfmsynthParameter Parameter and preset handling */ 177 | /** @{ */ 178 | 179 | /** \brief Set a parameter specific to an operator. 180 | * 181 | * If either parameter or operator_index is out of bounds, this functions is a no-op. 182 | * Updated parameters will not generally be reflected in audio output until a new voice has started. 183 | * 184 | * @param fm Handle to an FM synth instance. 185 | * @param parameter Which parameter to modify. See \ref fmsynth_parameter for which parameters can be used. 186 | * @param operator_index Which operator to modify. Valid range is 0 to \ref FMSYNTH_OPERATORS - 1. 187 | * @param value A floating point value. The meaning is parameter-dependent. 188 | */ 189 | void fmsynth_set_parameter(fmsynth_t *fm, 190 | unsigned parameter, unsigned operator_index, float value); 191 | 192 | float fmsynth_get_parameter(fmsynth_t *fm, 193 | unsigned parameter, unsigned operator_index); 194 | 195 | float fmsynth_convert_from_normalized_parameter(fmsynth_t *fm, 196 | unsigned parameter, 197 | float value); 198 | 199 | float fmsynth_convert_to_normalized_parameter(fmsynth_t *fm, 200 | unsigned parameter, 201 | float value); 202 | 203 | /** \brief Set a parameter global to the FM synth. 204 | * 205 | * If parameter is out of bounds, this functions is a no-op. 206 | * Updated parameters will not generally be reflected in audio output until a new voice has started. 207 | * 208 | * @param fm Handle to an FM synth instance. 209 | * @param parameter Which parameter to modify. See \ref fmsynth_global_parameter for which parameters can be used. 210 | * @param value A floating point value. The meaning is parameter-dependent. 211 | */ 212 | void fmsynth_set_global_parameter(fmsynth_t *fm, 213 | unsigned parameter, float value); 214 | 215 | float fmsynth_get_global_parameter(fmsynth_t *fm, 216 | unsigned parameter); 217 | 218 | float fmsynth_convert_from_normalized_global_parameter(fmsynth_t *fm, 219 | unsigned parameter, 220 | float value); 221 | 222 | float fmsynth_convert_to_normalized_global_parameter(fmsynth_t *fm, 223 | unsigned parameter, 224 | float value); 225 | 226 | /** 227 | * Maximum string size for presets, including NUL-terminator. 228 | * Ensures fixed size presets. 229 | */ 230 | #define FMSYNTH_PRESET_STRING_SIZE 64 231 | 232 | /** 233 | * Metadata structure for presets. 234 | * UTF-8 is assumed. API does not validate that however. 235 | * Strings must be properly NUL-terminated. 236 | */ 237 | struct fmsynth_preset_metadata 238 | { 239 | char name[FMSYNTH_PRESET_STRING_SIZE]; /**< Preset name. */ 240 | char author[FMSYNTH_PRESET_STRING_SIZE]; /**< Preset author. */ 241 | }; 242 | 243 | /** \brief Size in bytes required to hold a preset in memory. 244 | * 245 | * @returns Required size. 246 | */ 247 | size_t fmsynth_preset_size(void); 248 | 249 | /** \brief Saves current preset to memory. 250 | * 251 | * The current preset state of the synth is stored to memory. 252 | * The preset state is portable across platforms and can be stored to disk safely. 253 | * 254 | * @param fm Handle to an FM synth interface. 255 | * @param metadata Pointer to metadata. Can be NULL if no metadata is desired. 256 | * @param buffer Pointer to buffer where preset is stored. 257 | * @param size Size of buffer. Must be at least \ref fmsynth_preset_size. 258 | * 259 | * @returns Error code. 260 | */ 261 | fmsynth_status_t fmsynth_preset_save(fmsynth_t *fm, const struct fmsynth_preset_metadata *metadata, 262 | void *buffer, size_t size); 263 | 264 | /** \brief Load preset from memory. 265 | * 266 | * The current preset state of the synth is stored to memory. 267 | * The preset state is portable across platforms and can be stored to disk safely. 268 | * 269 | * @param fm Handle to an FM synth interface. 270 | * @param metadata Pointer to metadata. Can be NULL if reading metadata is not necessary. 271 | * @param buffer Pointer to buffer where preset can be read. 272 | * @param size Size of buffer. Must be at least \ref fmsynth_preset_size. 273 | * 274 | * @returns Error code. 275 | */ 276 | fmsynth_status_t fmsynth_preset_load(fmsynth_t *fm, struct fmsynth_preset_metadata *metadata, 277 | const void *buffer, size_t size); 278 | /** @} */ 279 | 280 | /** \addtogroup libfmsynthRender Audio rendering */ 281 | /** @{ */ 282 | /** \brief Render audio to buffer 283 | * 284 | * Renders audio to left and right buffers. The rendering is additive. 285 | * Ensure that left and right channels are cleared to zero or contains other audio before calling this function. 286 | * 287 | * @param fm Handle to an FM synth instance. 288 | * @param left A pointer to buffer representing the left channel. 289 | * @param right A pointer to buffer representing the right channel. 290 | * @param frames The number of frames (left and right samples) to render. 291 | * 292 | * @returns Number of voices currently active. 293 | */ 294 | unsigned fmsynth_render(fmsynth_t *fm, float *left, float *right, unsigned frames); 295 | /** @} */ 296 | 297 | /** \addtogroup libfmsynthControl MIDI control interface */ 298 | /** @{ */ 299 | /** \brief Trigger a note on the FM synth. 300 | * 301 | * @param fm Handle to an FM synth instance. 302 | * @param note Which note to press. Note is parsed using MIDI rules, i.e. note = 69 is A4. Valid range is [0, 127]. 303 | * @param velocity Note velocity. Velocity is parsed using MIDI rules. valie range is [0, 127]. 304 | * 305 | * @returns \ref FMSYNTH_STATUS_OK or \ref FMSYNTH_STATUS_BUSY if polyphony is exhausted. 306 | * */ 307 | fmsynth_status_t fmsynth_note_on(fmsynth_t *fm, uint8_t note, uint8_t velocity); 308 | 309 | /** \brief Release a note on the FM synth. 310 | * 311 | * @param fm Handle to an FM synth instance. 312 | * @param note Which note to release. 313 | * All currently pressed notes which match this will be put into either released state or 314 | * sustained state depending on if sustain is currently held. See \ref fmsynth_set_sustain. 315 | */ 316 | void fmsynth_note_off(fmsynth_t *fm, uint8_t note); 317 | 318 | /** \brief Set sustain state for FM synth. 319 | * 320 | * If sustain is held and notes are released, notes will be put in a sustain state instead. 321 | * Sustained notes will not be released until sustain is released as well. 322 | * 323 | * @param fm Handle to an FM synth instance. 324 | * @param enable If true, hold sustain. If false, release sustain, potentially releasing notes as well. 325 | */ 326 | void fmsynth_set_sustain(fmsynth_t *fm, bool enable); 327 | 328 | /** \brief Set modulation wheel state. 329 | * 330 | * @param fm Handle to an FM synth instance. 331 | * @param wheel wheel is parsed using MIDI rules. Valid range is [0, 127]. Intial state is 0. 332 | */ 333 | void fmsynth_set_mod_wheel(fmsynth_t *fm, uint8_t wheel); 334 | 335 | /** \brief Set pitch bend state. 336 | * 337 | * @param fm Handle to an FM synth instance. 338 | * @param value value is parsed using MIDI rules. Value range is [0, 0x3fff]. Initial state is 0x2000 (centered). 339 | * Range for pitch bend is two semitones. 340 | */ 341 | void fmsynth_set_pitch_bend(fmsynth_t *fm, uint16_t value); 342 | 343 | /** \brief Forcibly release all notes. 344 | * 345 | * All notes are released, even if sustain is activated. 346 | * Sustain is also reset to unpressed state. 347 | * 348 | * @param fm Handle to an FM synth instance. 349 | */ 350 | void fmsynth_release_all(fmsynth_t *fm); 351 | 352 | /** \brief Parse single MIDI message. 353 | * 354 | * @param fm Handle to an FM synth instance. 355 | * @param midi_data Pointer to MIDI data. Message type must always be provided. 356 | * E.g. Successive "note on" messages cannot drop the first byte. 357 | * 358 | * @returns Status code. Depends on MIDI message type or \ref FMSYNTH_STATUS_MESSAGE_UNKNOWN if unknown MIDI message is provided. 359 | */ 360 | fmsynth_status_t fmsynth_parse_midi(fmsynth_t *fm, 361 | const uint8_t *midi_data); 362 | /** @} */ 363 | 364 | /** @} */ 365 | 366 | #ifdef __cplusplus 367 | } 368 | #endif 369 | 370 | #endif 371 | 372 | -------------------------------------------------------------------------------- /lv2/fmsynth_gui.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include "fmsynth.peg" 21 | #include "fmsynth_private.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | using namespace sigc; 34 | using namespace Gtk; 35 | 36 | using EventMap = std::unordered_map>; 37 | using ValueMap = std::unordered_map>; 38 | class FMSynthGUI; 39 | 40 | class Slider : public VBox 41 | { 42 | public: 43 | Slider(uint32_t port, Gtk::Orientation orient = Gtk::ORIENTATION_VERTICAL) : port(port) 44 | { 45 | slider = manage(orient == Gtk::ORIENTATION_VERTICAL ? static_cast(new VScale) : static_cast(new HScale)); 46 | slider->set_range(0, 1); 47 | slider->set_draw_value(false); 48 | 49 | if (orient == Gtk::ORIENTATION_VERTICAL) 50 | { 51 | slider->set_size_request(-1, 120); 52 | slider->set_inverted(); 53 | } 54 | else if (orient == Gtk::ORIENTATION_HORIZONTAL) 55 | slider->set_size_request(120, -1); 56 | 57 | entry = manage(new Entry); 58 | entry->set_size_request(50, -1); 59 | 60 | slider->signal_value_changed().connect([this] { 61 | entry->set_text(get_value_string()); 62 | }); 63 | 64 | entry->signal_activate().connect([this] { 65 | float value = get_entry(); 66 | set_value(value); 67 | }); 68 | 69 | pack_start(*entry); 70 | pack_start(*slider); 71 | } 72 | 73 | Scale& get_slider() { return *slider; } 74 | 75 | float get_value() const 76 | { 77 | float vscale = slider->get_value(); 78 | auto& peg = peg_ports[port]; 79 | 80 | if (peg.logarithmic) 81 | { 82 | float min_log = std::log(peg.min); 83 | float max_log = std::log(peg.max); 84 | float lerp_log = min_log + (max_log - min_log) * vscale; 85 | return std::exp(lerp_log); 86 | } 87 | else 88 | return peg.min + (peg.max - peg.min) * vscale; 89 | } 90 | 91 | void set_value(float value) 92 | { 93 | auto& peg = peg_ports[port]; 94 | value = std::min(std::max(value, peg.min), peg.max); 95 | 96 | if (peg.logarithmic) 97 | { 98 | float min_log = std::log(peg.min); 99 | float max_log = std::log(peg.max); 100 | float value_log = std::log(value); 101 | float lerp = (value_log - min_log) / (max_log - min_log); 102 | slider->set_value(lerp); 103 | } 104 | else 105 | { 106 | float lerp = (value - peg.min) / (peg.max - peg.min); 107 | slider->set_value(lerp); 108 | } 109 | 110 | entry->set_text(get_value_string()); 111 | } 112 | 113 | private: 114 | uint32_t port; 115 | Scale *slider; 116 | Entry *entry; 117 | 118 | float get_entry() 119 | { 120 | try { 121 | return std::stof(entry->get_text().c_str()); 122 | } catch(...) { 123 | return peg_ports[port].default_value; 124 | } 125 | } 126 | 127 | Glib::ustring get_value_string() const 128 | { 129 | char buf[64]; 130 | std::snprintf(buf, sizeof(buf), "%.3f", get_value()); 131 | return buf; 132 | } 133 | }; 134 | 135 | class SlidersFrame : public Frame 136 | { 137 | public: 138 | SlidersFrame(const char *frame_desc, uint32_t start_port, const std::vector& descs, 139 | EventMap& events, ValueMap& values, std::function func, uint32_t base_port) 140 | : Frame(frame_desc) 141 | { 142 | auto table = manage(new Table(2, descs.size())); 143 | table->set_col_spacings(5); 144 | table->set_border_width(5); 145 | 146 | unsigned i = 0; 147 | for (auto& desc : descs) 148 | { 149 | uint32_t port = base_port + start_port + i; 150 | auto slider = manage(new Slider(port)); 151 | 152 | slider->get_slider().signal_value_changed().connect([this, slider, func, port] { 153 | func(port, slider->get_value()); 154 | }); 155 | 156 | events[port] = [this, slider, port](float value) { 157 | slider->set_value(value); 158 | }; 159 | 160 | values[port] = [this, slider, port] { return slider->get_value(); }; 161 | 162 | auto label = manage(new Label(desc)); 163 | table->attach(*label, i, i + 1, 0, 1); 164 | table->attach(*slider, i, i + 1, 1, 2); 165 | i++; 166 | } 167 | 168 | set_shadow_type(Gtk::SHADOW_ETCHED_OUT); 169 | set_label_align(Gtk::ALIGN_END, Gtk::ALIGN_START); 170 | add(*table); 171 | } 172 | }; 173 | 174 | class AlgorithmMatrix : public Frame 175 | { 176 | public: 177 | AlgorithmMatrix(EventMap& events, ValueMap& values, std::function func, unsigned operators) 178 | : Frame("FM Matrix") 179 | { 180 | set_border_width(5); 181 | auto vbox = manage(new VBox); 182 | auto table = manage(new Table(operators, operators)); 183 | 184 | auto carrier_frame = manage(new Frame("Carriers")); 185 | carrier_frame->set_border_width(5); 186 | auto carriers = manage(new HBox); 187 | carrier_frame->add(*carriers); 188 | 189 | for (unsigned i = 0; i < operators; i++) 190 | { 191 | unsigned port = (peg_amp__op__1_ - peg_amp__op__0_) * i + peg_carriers__op__0_; 192 | auto btn = manage(new ToggleButton(std::string("#") + std::to_string(i + 1))); 193 | btn->signal_clicked().connect([this, func, port, btn] { 194 | func(port, btn->get_active() ? 1.0f : 0.0f); 195 | }); 196 | events[port] = [this, btn](float value) { 197 | btn->set_active(value > 0.5f); 198 | }; 199 | values[port] = [this, btn] { return btn->get_active() ? 1.0f : 0.0f; }; 200 | carriers->pack_start(*btn); 201 | } 202 | 203 | vbox->pack_start(*carrier_frame); 204 | 205 | for (unsigned c = 0; c < operators; c++) 206 | { 207 | for (unsigned r = 0; r < operators; r++) 208 | { 209 | unsigned port = (peg_amp__op__1_ - peg_amp__op__0_) * c + peg_mod0tooperator__op__0_ + r; 210 | auto btn = manage(new ToggleButton(std::to_string(r + 1) + " -> " + std::to_string(c + 1))); 211 | btn->signal_clicked().connect([this, func, port, btn] { 212 | func(port, btn->get_active() ? 1.0f : 0.0f); 213 | }); 214 | events[port] = [this, btn](float value) { 215 | btn->set_active(value > 0.5f); 216 | }; 217 | values[port] = [this, btn] { return btn->get_active() ? 1.0f : 0.0f; }; 218 | 219 | table->attach(*btn, c, c + 1, r, r + 1); 220 | } 221 | } 222 | 223 | auto matrix_frame = manage(new Frame("Modulators")); 224 | matrix_frame->set_border_width(5); 225 | matrix_frame->add(*table); 226 | vbox->pack_start(*manage(new HSeparator)); 227 | vbox->pack_start(*matrix_frame); 228 | 229 | add(*vbox); 230 | } 231 | }; 232 | 233 | class BasicParameters : public Frame 234 | { 235 | public: 236 | BasicParameters(EventMap& events, ValueMap& values, std::function func) 237 | : Frame("Global Parameters") 238 | { 239 | auto hbox = manage(new HBox); 240 | set_border_width(5); 241 | 242 | vol = manage(new Slider(peg_volume, Gtk::ORIENTATION_HORIZONTAL)); 243 | lfofreq = manage(new Slider(peg_lfofreq, Gtk::ORIENTATION_HORIZONTAL)); 244 | 245 | auto frame = manage(new Frame("Volume")); 246 | frame->set_border_width(5); 247 | frame->add(*vol); 248 | hbox->pack_start(*frame); 249 | 250 | frame = manage(new Frame("LFO Frequency")); 251 | frame->set_border_width(5); 252 | frame->add(*lfofreq); 253 | hbox->pack_start(*frame); 254 | 255 | hbox->pack_start(*frame); 256 | 257 | vol->get_slider().signal_value_changed().connect([this, func] { func(peg_volume, vol->get_value()); }); 258 | lfofreq->get_slider().signal_value_changed().connect([this, func] { func(peg_lfofreq, lfofreq->get_value()); }); 259 | 260 | events[peg_volume] = [this](float value) { vol->set_value(value); }; 261 | events[peg_lfofreq] = [this](float value) { lfofreq->set_value(value); }; 262 | 263 | values[peg_volume] = [this] { return vol->get_value(); }; 264 | values[peg_lfofreq] = [this] { return lfofreq->get_value(); }; 265 | 266 | add(*hbox); 267 | } 268 | 269 | private: 270 | Slider *vol; 271 | Slider *lfofreq; 272 | }; 273 | 274 | class Presets : public Frame 275 | { 276 | public: 277 | Presets(const char *bundle_path, EventMap& events, ValueMap& values, std::function func) 278 | : Frame("Presets"), events(events), values(values), lv2_func(func), bundle_path(bundle_path) 279 | { 280 | auto vbox = manage(new VBox); 281 | auto hbox = manage(new HBox); 282 | set_border_width(5); 283 | 284 | auto table = manage(new Table(2, 2)); 285 | table->set_border_width(5); 286 | 287 | filter.set_name("FMSynth presets"); 288 | filter.add_pattern("*.fmp"); 289 | any_filter.set_name("Any file"); 290 | any_filter.add_pattern("*"); 291 | 292 | auto load = manage(new Button("Load Preset")); 293 | auto save = manage(new Button("Save Preset")); 294 | load->signal_clicked().connect([this] { load_preset(); }); 295 | save->signal_clicked().connect([this] { save_preset(); }); 296 | 297 | table->attach(*manage(new Label("Preset Name:")), 0, 1, 0, 1); 298 | table->attach(*manage(new Label("Preset Author:")), 1, 2, 0, 1); 299 | table->attach(name, 0, 1, 1, 2); 300 | table->attach(author, 1, 2, 1, 2); 301 | 302 | name.set_size_request(50, -1); 303 | author.set_size_request(50, -1); 304 | 305 | hbox->add(*load); 306 | hbox->add(*save); 307 | vbox->add(*hbox); 308 | vbox->add(*table); 309 | add(*vbox); 310 | } 311 | 312 | private: 313 | FileFilter filter; 314 | FileFilter any_filter; 315 | EventMap& events; 316 | ValueMap& values; 317 | std::function lv2_func; 318 | const char *bundle_path; 319 | 320 | Entry name; 321 | Entry author; 322 | 323 | void load_preset() 324 | { 325 | FileChooserDialog dialog("Load Preset", FILE_CHOOSER_ACTION_OPEN); 326 | dialog.add_filter(filter); 327 | dialog.add_filter(any_filter); 328 | dialog.add_button("Cancel", RESPONSE_CANCEL); 329 | dialog.add_button("Open", RESPONSE_OK); 330 | 331 | if (bundle_path) 332 | dialog.add_shortcut_folder(bundle_path); 333 | 334 | int status = dialog.run(); 335 | if (status == RESPONSE_OK) 336 | { 337 | try 338 | { 339 | load_preset_from(dialog.get_uri()); 340 | } 341 | catch (const std::exception& e) 342 | { 343 | MessageDialog error(e.what(), false, MESSAGE_ERROR); 344 | error.run(); 345 | } 346 | catch (const Glib::Error &e) 347 | { 348 | MessageDialog error(e.what(), false, MESSAGE_ERROR); 349 | error.run(); 350 | } 351 | } 352 | } 353 | 354 | static Glib::ustring fixup_suffix(const Glib::ustring& uri) 355 | { 356 | // Add .fmp extension automatically if not provided. 357 | size_t len = uri.length(); 358 | if (len >= 4 && (uri.substr(len - 4) != ".fmp")) 359 | return uri + ".fmp"; 360 | else 361 | return uri; 362 | } 363 | 364 | void save_preset() 365 | { 366 | FileChooserDialog dialog("Save Preset", FILE_CHOOSER_ACTION_SAVE); 367 | dialog.set_do_overwrite_confirmation(true); 368 | dialog.add_filter(filter); 369 | dialog.add_filter(any_filter); 370 | dialog.add_button("Cancel", RESPONSE_CANCEL); 371 | dialog.add_button("Save", RESPONSE_OK); 372 | 373 | int status = dialog.run(); 374 | if (status == RESPONSE_OK) 375 | { 376 | try 377 | { 378 | save_preset_to(fixup_suffix(dialog.get_uri())); 379 | } 380 | catch (const std::exception& e) 381 | { 382 | MessageDialog error(e.what(), false, MESSAGE_ERROR); 383 | error.run(); 384 | } 385 | catch (const Glib::Error &e) 386 | { 387 | MessageDialog error(e.what(), false, MESSAGE_ERROR); 388 | error.run(); 389 | } 390 | } 391 | } 392 | 393 | void load_preset_from(const Glib::ustring& uri) 394 | { 395 | auto file = Gio::File::create_for_uri(uri); 396 | if (!file) 397 | throw std::runtime_error("Failed to open file."); 398 | 399 | auto stream = file->read(); 400 | if (!stream) 401 | throw std::runtime_error("Failed to open file."); 402 | 403 | std::vector buffer(fmsynth_preset_size()); 404 | 405 | gsize did_read; 406 | bool ret = stream->read_all(buffer.data(), buffer.size(), did_read) && did_read == buffer.size(); 407 | stream->close(); 408 | 409 | if (!ret) 410 | throw std::runtime_error("Failed to read file."); 411 | 412 | struct fmsynth_voice_parameters params; 413 | struct fmsynth_global_parameters global_params; 414 | struct fmsynth_preset_metadata metadata; 415 | memset(&metadata, 0, sizeof(metadata)); 416 | 417 | fmsynth_status_t status = fmsynth_preset_load_private(&global_params, ¶ms, 418 | &metadata, buffer.data(), buffer.size()); 419 | ret = status == FMSYNTH_STATUS_OK; 420 | 421 | if (ret) 422 | { 423 | Glib::ustring name_str(metadata.name); 424 | Glib::ustring author_str(metadata.author); 425 | if (!name_str.validate() || !author_str.validate()) 426 | throw std::logic_error("Preset string is not valid UTF-8."); 427 | name.set_text(name_str); 428 | author.set_text(author_str); 429 | 430 | const float *values = params.amp; 431 | 432 | set_parameter(peg_volume, global_params.volume); 433 | set_parameter(peg_lfofreq, global_params.lfo_freq); 434 | for (unsigned p = 0; p < FMSYNTH_PARAM_END; p++) 435 | { 436 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 437 | { 438 | set_parameter(peg_amp__op__0_ + (peg_amp__op__1_ - peg_amp__op__0_) * o + p, 439 | values[p * FMSYNTH_OPERATORS + o]); 440 | } 441 | } 442 | } 443 | else 444 | throw std::runtime_error("Failed to parse preset."); 445 | } 446 | 447 | static void put_raw_string(char *buffer, const Entry& entry) 448 | { 449 | auto text = entry.get_text(); 450 | size_t len = text.bytes(); 451 | 452 | if (len >= FMSYNTH_PRESET_STRING_SIZE) 453 | { 454 | // TODO: Should find a better way to make this limit more intuitive. 455 | throw std::logic_error("Preset string overflows buffer."); 456 | } 457 | 458 | // We've already verified, so memcpy is fine. 459 | memcpy(buffer, text.c_str(), len); 460 | buffer[len] = '\0'; 461 | } 462 | 463 | void save_preset_to(const Glib::ustring& uri) 464 | { 465 | std::vector buffer(fmsynth_preset_size()); 466 | 467 | struct fmsynth_voice_parameters params; 468 | struct fmsynth_global_parameters global_params; 469 | struct fmsynth_preset_metadata metadata; 470 | memset(&metadata, 0, sizeof(metadata)); 471 | 472 | put_raw_string(metadata.name, name); 473 | put_raw_string(metadata.author, author); 474 | 475 | global_params.volume = get_parameter(peg_volume); 476 | global_params.lfo_freq = get_parameter(peg_lfofreq); 477 | 478 | float *values = params.amp; 479 | for (unsigned p = 0; p < FMSYNTH_PARAM_END; p++) 480 | { 481 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 482 | { 483 | values[p * FMSYNTH_OPERATORS + o] = 484 | get_parameter(peg_amp__op__0_ + (peg_amp__op__1_ - peg_amp__op__0_) * o + p); 485 | } 486 | } 487 | 488 | fmsynth_status_t status = fmsynth_preset_save_private(&global_params, ¶ms, 489 | &metadata, buffer.data(), buffer.size()); 490 | 491 | if (status != FMSYNTH_STATUS_OK) 492 | throw std::runtime_error("Failed to save preset to binary format."); 493 | 494 | auto file = Gio::File::create_for_uri(uri); 495 | if (!file) 496 | throw std::runtime_error("Failed to save preset to disk."); 497 | 498 | auto stream = file->replace(); 499 | if (!stream) 500 | throw std::runtime_error("Failed to save preset to disk."); 501 | 502 | gsize ret; 503 | if (!stream->write_all(buffer.data(), buffer.size(), ret) || ret != buffer.size()) 504 | throw std::runtime_error("Failed to write preset to disk."); 505 | } 506 | 507 | void set_parameter(uint32_t id, float value) 508 | { 509 | auto func = events[id]; 510 | if (func) 511 | func(value); 512 | lv2_func(id, value); 513 | } 514 | 515 | float get_parameter(uint32_t id) 516 | { 517 | auto func = values[id]; 518 | if (func) 519 | return func(); 520 | else 521 | return peg_ports[id].default_value; 522 | } 523 | }; 524 | 525 | class AmpPan : public SlidersFrame 526 | { 527 | public: 528 | AmpPan(EventMap& events, ValueMap& values, std::function func, uint32_t base_port) 529 | : SlidersFrame("Amplifier", peg_amp__op__0_, 530 | { "Volume", "Pan" }, 531 | events, values, func, base_port) 532 | {} 533 | }; 534 | 535 | class FreqMod : public Frame 536 | { 537 | public: 538 | FreqMod(EventMap& events, ValueMap& values, std::function func, uint32_t base_port) 539 | : Frame("Oscillator"), func(func), base_port(base_port) 540 | { 541 | auto table = manage(new Table(4, 5)); 542 | table->set_border_width(5); 543 | 544 | entry = manage(new Entry); 545 | entry->set_text("1.00"); 546 | auto button_plus1 = manage(new Button("+1")); 547 | auto button_minus1 = manage(new Button("-1")); 548 | auto button_plus10 = manage(new Button("+0.1")); 549 | auto button_minus10 = manage(new Button("-0.1")); 550 | auto button_plus100 = manage(new Button("+0.01")); 551 | auto button_minus100 = manage(new Button("-0.01")); 552 | 553 | table->attach(*button_plus1, 1, 2, 1, 2); 554 | table->attach(*button_plus10, 2, 3, 1, 2); 555 | table->attach(*button_plus100, 3, 4, 1, 2); 556 | table->attach(*manage(new Label("Ratio:")), 0, 1, 2, 3); 557 | table->attach(*entry, 1, 4, 2, 3); 558 | table->attach(*button_minus1, 1, 2, 3, 4); 559 | table->attach(*button_minus10, 2, 3, 3, 4); 560 | table->attach(*button_minus100, 3, 4, 3, 4); 561 | 562 | entry->signal_activate().connect([this] { 563 | float value = get_entry(); 564 | value = std::min(std::max(value, peg_ports[peg_freqmod__op__0_].min), peg_ports[peg_freqmod__op__0_].max); 565 | entry->set_text(std::to_string(value)); 566 | this->func(this->base_port + peg_freqmod__op__0_, value); 567 | }); 568 | button_plus1->signal_clicked().connect([this] { 569 | add_entry(1.0f); 570 | }); 571 | button_minus1->signal_clicked().connect([this] { 572 | add_entry(-1.0f); 573 | }); 574 | button_plus10->signal_clicked().connect([this] { 575 | add_entry(0.1f); 576 | }); 577 | button_minus10->signal_clicked().connect([this] { 578 | add_entry(-0.1f); 579 | }); 580 | button_plus100->signal_clicked().connect([this] { 581 | add_entry(0.01f); 582 | }); 583 | button_minus100->signal_clicked().connect([this] { 584 | add_entry(-0.01f); 585 | }); 586 | 587 | uint32_t offset_port = base_port + peg_freqoffset__op__0_; 588 | auto offset = manage(new Slider(offset_port)); 589 | 590 | offset->get_slider().signal_value_changed().connect([this, offset_port, offset] { 591 | this->func(offset_port, offset->get_value()); 592 | }); 593 | 594 | table->attach(*manage(new Label("Offset")), 4, 5, 0, 1); 595 | table->attach(*offset, 4, 5, 1, 4); 596 | 597 | events[offset_port] = [this, offset](float value) { offset->set_value(value); }; 598 | events[base_port + peg_freqmod__op__0_] = [this](float value) { entry->set_text(std::to_string(value)); }; 599 | 600 | values[offset_port] = [this, offset] { return offset->get_value(); }; 601 | values[base_port + peg_freqmod__op__0_] = [this] { return get_entry(); }; 602 | 603 | add(*table); 604 | } 605 | 606 | private: 607 | Entry *entry; 608 | std::function func; 609 | uint32_t base_port; 610 | 611 | float get_entry() 612 | { 613 | try { 614 | return std::stof(entry->get_text().c_str()); 615 | } catch(...) { 616 | return 1.0f; 617 | } 618 | } 619 | 620 | void add_entry(float delta) 621 | { 622 | float value = get_entry(); 623 | value += delta; 624 | value = std::min(std::max(value, peg_ports[peg_freqmod__op__0_].min), peg_ports[peg_freqmod__op__0_].max); 625 | value = std::round(value / delta) * delta; 626 | 627 | func(base_port + peg_freqmod__op__0_, value); 628 | 629 | char buf[64]; 630 | std::snprintf(buf, sizeof(buf), "%.2f", value); 631 | entry->set_text(buf); 632 | } 633 | }; 634 | 635 | class LFODepth : public SlidersFrame 636 | { 637 | public: 638 | LFODepth(EventMap& events, ValueMap& values, std::function func, uint32_t base_port) 639 | : SlidersFrame("LFO Sensitivity", peg_lfoampdepth__op__0_, 640 | { "Amp", "Frequency" }, 641 | events, values, func, base_port) 642 | {} 643 | }; 644 | 645 | class KeyboardScaling : public SlidersFrame 646 | { 647 | public: 648 | KeyboardScaling(EventMap& events, ValueMap& values, std::function func, uint32_t base_port) 649 | : SlidersFrame("Keyboard Scaling", peg_keyboardscalingmidpoint__op__0_, 650 | { "Mid", "Lower", "Upper", "Velocity", "Mod Wheel" }, 651 | events, values, func, base_port) 652 | {} 653 | }; 654 | 655 | class EnvelopeTargets : public SlidersFrame 656 | { 657 | public: 658 | EnvelopeTargets(EventMap& events, ValueMap& values, std::function func, uint32_t base_port) 659 | : SlidersFrame("Envelope Targets", peg_envelopetarget0__op__0_, 660 | { "T1", "T2", "T3" }, 661 | events, values, func, base_port) 662 | {} 663 | }; 664 | 665 | class EnvelopeRates : public SlidersFrame 666 | { 667 | public: 668 | EnvelopeRates(EventMap& events, ValueMap& values, std::function func, uint32_t base_port) 669 | : SlidersFrame("Envelope Rates", peg_envelopedelay0__op__0_, 670 | { "R1", "R2", "R3", "R4" }, 671 | events, values, func, base_port) 672 | {} 673 | }; 674 | 675 | class OperatorWidget : public Frame 676 | { 677 | public: 678 | OperatorWidget(const char *label, EventMap& events, ValueMap& values, std::function func, uint32_t base_port) 679 | : Frame(label), port(base_port) 680 | { 681 | auto hbox = manage(new HBox); 682 | hbox->set_spacing(5); 683 | 684 | auto enable = manage(new CheckButton("Enable")); 685 | enable->signal_toggled().connect([this, func, enable] { 686 | func(port + peg_enable__op__0_, enable->get_active() ? 1.0f : 0.0f); 687 | }); 688 | 689 | events[port + peg_enable__op__0_] = [this, enable](float value) { 690 | enable->set_active(value > 0.5f); 691 | }; 692 | 693 | values[port + peg_enable__op__0_] = [this, enable] { return enable->get_active() ? 1.0f : 0.0f; }; 694 | 695 | auto default_op = manage(new Button("Default")); 696 | unsigned start_port = peg_amp__op__0_ + base_port; 697 | default_op->signal_clicked().connect([this, &events, func, start_port] { 698 | for (unsigned i = start_port; i < start_port + peg_amp__op__1_ - peg_amp__op__0_; i++) 699 | { 700 | auto fn = events[i]; 701 | if (fn) 702 | fn(peg_ports[i].default_value); 703 | func(i, peg_ports[i].default_value); 704 | } 705 | }); 706 | 707 | auto vbox = manage(new VBox); 708 | vbox->pack_start(*enable); 709 | vbox->pack_start(*default_op); 710 | vbox->pack_start(*manage(new HSeparator)); 711 | vbox->pack_start(*manage(new AmpPan(events, values, func, port))); 712 | 713 | hbox->pack_start(*vbox); 714 | hbox->pack_start(*manage(new FreqMod(events, values, func, port))); 715 | hbox->pack_start(*manage(new EnvelopeRates(events, values, func, port))); 716 | hbox->pack_start(*manage(new EnvelopeTargets(events, values, func, port))); 717 | hbox->pack_start(*manage(new LFODepth(events, values, func, port))); 718 | hbox->pack_start(*manage(new KeyboardScaling(events, values, func, port))); 719 | 720 | add(*hbox); 721 | } 722 | 723 | private: 724 | uint32_t port; 725 | }; 726 | 727 | class FMSynthGUI : public lvtk::UI, lvtk::URID> 728 | { 729 | public: 730 | FMSynthGUI(const std::string&) 731 | { 732 | auto write_ctrl = [this](uint32_t port, float value) { write_control(port, value); }; 733 | 734 | auto hbox = manage(new HBox); 735 | 736 | auto vbox = manage(new VBox); 737 | auto notebook = manage(new Notebook); 738 | unsigned operators = (peg_n_ports - peg_amp__op__0_) / (peg_amp__op__1_ - peg_amp__op__0_); 739 | 740 | for (unsigned i = 0; i < operators; i++) 741 | { 742 | unsigned base_port = (peg_amp__op__1_ - peg_amp__op__0_) * i; 743 | auto str = std::string("Operator #") + std::to_string(i + 1); 744 | auto op = manage(new OperatorWidget(str.c_str(), event_map, value_map, write_ctrl, base_port)); 745 | 746 | notebook->append_page(*op, str); 747 | } 748 | 749 | auto top_hbox = manage(new HBox); 750 | 751 | top_hbox->add(*manage(new BasicParameters(event_map, value_map, write_ctrl))); 752 | top_hbox->add(*manage(new Presets(bundle_path(), event_map, value_map, write_ctrl))); 753 | 754 | vbox->pack_start(*top_hbox); 755 | vbox->pack_start(*notebook); 756 | 757 | hbox->pack_start(*vbox); 758 | hbox->pack_start(*manage(new AlgorithmMatrix(event_map, value_map, write_ctrl, operators))); 759 | 760 | add(*hbox); 761 | } 762 | 763 | void port_event(uint32_t port, uint32_t, uint32_t, const void *buffer) 764 | { 765 | //fprintf(stderr, "Port event() %u -> %.3f\n", port, *static_cast(buffer)); 766 | auto itr = event_map.find(port); 767 | if (itr != end(event_map)) 768 | itr->second(*static_cast(buffer)); 769 | } 770 | 771 | private: 772 | EventMap event_map; 773 | ValueMap value_map; 774 | }; 775 | 776 | int ui_class = FMSynthGUI::register_class("git://github.com/Themaister/fmsynth/gui"); 777 | 778 | -------------------------------------------------------------------------------- /src/fmsynth.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014 Hans-Kristian Arntzen 2 | * 3 | * Permission is hereby granted, free of charge, 4 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation the rights to 6 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #include "fmsynth_private.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #ifndef max 28 | #define max(a, b) ((a) > (b) ? (a) : (b)) 29 | #endif 30 | 31 | #ifndef min 32 | #define min(a, b) ((a) < (b) ? (a) : (b)) 33 | #endif 34 | 35 | #ifdef __GNUC__ 36 | #define FMSYNTH_ALIGNED_PRE(x) 37 | #define FMSYNTH_ALIGNED_CACHE_PRE 38 | #define FMSYNTH_ALIGNED_POST(x) __attribute__((aligned(x))) 39 | #define FMSYNTH_ALIGNED_CACHE_POST FMSYNTH_ALIGNED_POST(64) 40 | #define FMSYNTH_NOINLINE __attribute__((noinline)) 41 | #define FMSYNTH_ASSUME_ALIGNED(x, align) __builtin_assume_aligned(x, align) 42 | #elif defined(_MSC_VER) 43 | #define FMSYNTH_ALIGNED_PRE(x) __declspec(align(x)) 44 | #define FMSYNTH_ALIGNED_CACHE_PRE FMSYNTH_ALIGNED_PRE(64) 45 | #define FMSYNTH_ALIGNED_POST(x) 46 | #define FMSYNTH_ALIGNED_CACHE_POST 47 | #define FMSYNTH_NOINLINE __declspec(noinline) 48 | #define FMSYNTH_ASSUME_ALIGNED(x, align) x 49 | #else 50 | #define FMSYNTH_ALIGNED_PRE(x) 51 | #define FMSYNTH_ALIGNED_CACHE_PRE 52 | #define FMSYNTH_ALIGNED_POST(x) 53 | #define FMSYNTH_ALIGNED_CACHE_POST 54 | #define FMSYNTH_NOINLINE 55 | #define FMSYNTH_ASSUME_ALIGNED(x, align) x 56 | #endif 57 | 58 | #undef PI 59 | #define PI 3.14159265359f 60 | 61 | #define INV_FACTORIAL_3_2PIPOW3 ((1.0f / 6.0f) * (2.0f * PI) * (2.0f * PI) * (2.0f * PI)) 62 | #define INV_FACTORIAL_5_2PIPOW5 ((1.0f / 120.0f) * (2.0f * PI) * (2.0f * PI) * (2.0f * PI) * (2.0f * PI) * (2.0f * PI)) 63 | #define INV_FACTORIAL_7_2PIPOW7 ((1.0f / 5040.0f) * (2.0f * PI) * (2.0f * PI) * (2.0f * PI) * (2.0f * PI) * (2.0f * PI) * (2.0f * PI) * (2.0f * PI)) 64 | 65 | #define FMSYNTH_FRAMES_PER_LFO 32 66 | 67 | enum fmsynth_voice_state 68 | { 69 | FMSYNTH_VOICE_INACTIVE = 0, 70 | FMSYNTH_VOICE_RUNNING, 71 | FMSYNTH_VOICE_SUSTAINED, 72 | FMSYNTH_VOICE_RELEASED 73 | }; 74 | 75 | struct fmsynth_voice 76 | { 77 | enum fmsynth_voice_state state; 78 | uint8_t note; 79 | uint8_t enable; 80 | uint8_t dead; 81 | 82 | float base_freq; 83 | float env_speed; 84 | float pos; 85 | float speed; 86 | 87 | float lfo_step; 88 | float lfo_phase; 89 | unsigned count; 90 | 91 | // Used in process_frames(). Should be local in cache. 92 | FMSYNTH_ALIGNED_CACHE_PRE float phases[FMSYNTH_OPERATORS] FMSYNTH_ALIGNED_CACHE_POST; 93 | float env[FMSYNTH_OPERATORS]; 94 | float read_mod[FMSYNTH_OPERATORS]; 95 | float target_env_step[FMSYNTH_OPERATORS]; 96 | float step_rate[FMSYNTH_OPERATORS]; 97 | float lfo_freq_mod[FMSYNTH_OPERATORS]; 98 | float pan_amp[2][FMSYNTH_OPERATORS]; 99 | 100 | // Using when updating envelope (every N sample). 101 | float falloff[FMSYNTH_OPERATORS]; 102 | float end_time[FMSYNTH_OPERATORS]; 103 | float target_env[FMSYNTH_OPERATORS]; 104 | 105 | float release_time[FMSYNTH_OPERATORS]; 106 | float target[4][FMSYNTH_OPERATORS]; 107 | float time[4][FMSYNTH_OPERATORS]; 108 | float lerp[3][FMSYNTH_OPERATORS]; 109 | 110 | float amp[FMSYNTH_OPERATORS]; 111 | float wheel_amp[FMSYNTH_OPERATORS]; 112 | float lfo_amp[FMSYNTH_OPERATORS]; 113 | }; 114 | 115 | struct fmsynth 116 | { 117 | FMSYNTH_ALIGNED_CACHE_PRE struct fmsynth_voice_parameters params FMSYNTH_ALIGNED_CACHE_POST; 118 | FMSYNTH_ALIGNED_CACHE_PRE struct fmsynth_global_parameters global_params FMSYNTH_ALIGNED_CACHE_POST; 119 | 120 | float sample_rate; 121 | float inv_sample_rate; 122 | 123 | float bend; 124 | float wheel; 125 | bool sustained; 126 | 127 | unsigned max_voices; 128 | FMSYNTH_ALIGNED_CACHE_PRE struct fmsynth_voice voices[] FMSYNTH_ALIGNED_CACHE_POST; 129 | }; 130 | 131 | static void *fmsynth_memory_alloc(size_t alignment, size_t size) 132 | { 133 | void **place; 134 | uintptr_t addr = 0; 135 | void *ptr = malloc(alignment + size + sizeof(uintptr_t)); 136 | 137 | if (!ptr) 138 | { 139 | return NULL; 140 | } 141 | 142 | addr = ((uintptr_t)ptr + sizeof(uintptr_t) + alignment) 143 | & ~(alignment - 1); 144 | place = (void**)addr; 145 | place[-1] = ptr; 146 | 147 | return (void*)addr; 148 | } 149 | 150 | static void fmsynth_memory_free(void *ptr) 151 | { 152 | void **p = (void**)ptr; 153 | free(p[-1]); 154 | } 155 | 156 | static void fmsynth_init_voices(fmsynth_t *fm) 157 | { 158 | memset(fm->voices, 0, fm->max_voices * sizeof(*fm->voices)); 159 | 160 | for (unsigned v = 0; v < fm->max_voices; v++) 161 | { 162 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) 163 | { 164 | fm->voices[v].amp[i] = 1.0f; 165 | fm->voices[v].pan_amp[0][i] = 1.0f; 166 | fm->voices[v].pan_amp[1][i] = 1.0f; 167 | fm->voices[v].wheel_amp[i] = 1.0f; 168 | fm->voices[v].lfo_amp[i] = 1.0f; 169 | fm->voices[v].lfo_freq_mod[i] = 1.0f; 170 | } 171 | } 172 | fm->bend = 1.0f; 173 | } 174 | 175 | static void fmsynth_set_default_parameters( 176 | struct fmsynth_voice_parameters *params) 177 | { 178 | #undef set_default 179 | #define set_default(x, v) do { \ 180 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) \ 181 | { \ 182 | params->x[i] = v; \ 183 | } \ 184 | } while(0) 185 | 186 | set_default(amp, 1.0f); 187 | set_default(pan, 0.0f); 188 | 189 | set_default(freq_mod, 1.0f); 190 | set_default(freq_offset, 0.0f); 191 | 192 | set_default(envelope_target[0], 1.0f); 193 | set_default(envelope_target[1], 0.5f); 194 | set_default(envelope_target[2], 0.25f); 195 | set_default(envelope_delay[0], 0.05f); 196 | set_default(envelope_delay[1], 0.05f); 197 | set_default(envelope_delay[2], 0.25f); 198 | set_default(envelope_release_time, 0.50f); 199 | 200 | set_default(keyboard_scaling_mid_point, 440.0f); 201 | set_default(keyboard_scaling_low_factor, 0.0f); 202 | set_default(keyboard_scaling_high_factor, 0.0f); 203 | 204 | set_default(velocity_sensitivity, 1.0f); 205 | set_default(mod_sensitivity, 0.0f); 206 | 207 | set_default(lfo_amp_depth, 0.0f); 208 | set_default(lfo_freq_mod_depth, 0.0f); 209 | 210 | set_default(enable, 1.0f); 211 | 212 | params->carriers[0] = 1.0f; 213 | for (unsigned c = 1; c < FMSYNTH_OPERATORS; c++) 214 | { 215 | params->carriers[c] = 0.0f; 216 | } 217 | 218 | for (unsigned x = 0; x < FMSYNTH_OPERATORS; x++) 219 | { 220 | set_default(mod_to_carriers[x], 0.0f); 221 | } 222 | 223 | #undef set_default 224 | } 225 | 226 | static void fmsynth_set_default_global_parameters( 227 | struct fmsynth_global_parameters *params) 228 | { 229 | params->volume = 0.2f; 230 | params->lfo_freq = 0.1f; 231 | } 232 | 233 | void fmsynth_reset(fmsynth_t *fm) 234 | { 235 | fmsynth_init_voices(fm); 236 | fmsynth_set_default_parameters(&fm->params); 237 | fmsynth_set_default_global_parameters(&fm->global_params); 238 | } 239 | 240 | fmsynth_t *fmsynth_new(float sample_rate, unsigned max_voices) 241 | { 242 | size_t fmsynth_size = sizeof(fmsynth_t) + 243 | max_voices * sizeof(struct fmsynth_voice); 244 | 245 | fmsynth_t *fm = fmsynth_memory_alloc(64, fmsynth_size); 246 | if (fm == NULL) 247 | { 248 | return NULL; 249 | } 250 | 251 | memset(fm, 0, fmsynth_size); 252 | fm->max_voices = max_voices; 253 | 254 | fm->sample_rate = sample_rate; 255 | fm->inv_sample_rate = 1.0f / sample_rate; 256 | 257 | fmsynth_reset(fm); 258 | return fm; 259 | } 260 | 261 | void fmsynth_free(fmsynth_t *fm) 262 | { 263 | fmsynth_memory_free(fm); 264 | } 265 | 266 | static float pitch_bend_to_ratio(uint16_t bend) 267 | { 268 | // Two semitones range. 269 | return powf(2.0f, (bend - 8192.0f) / (8192.0f * 6.0f)); 270 | } 271 | 272 | static float note_to_frequency(uint8_t note) 273 | { 274 | return 440.0f * powf(2.0f, (note - 69.0f) / 12.0f); 275 | } 276 | 277 | static void fmsynth_update_target_envelope(struct fmsynth_voice *voice) 278 | { 279 | voice->pos += voice->speed * FMSYNTH_FRAMES_PER_LFO; 280 | 281 | if (voice->state == FMSYNTH_VOICE_RELEASED) 282 | { 283 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) 284 | { 285 | voice->target_env[i] *= voice->falloff[i]; 286 | if (voice->pos >= voice->end_time[i]) 287 | { 288 | voice->dead |= 1 << i; 289 | } 290 | } 291 | } 292 | else 293 | { 294 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) 295 | { 296 | if (voice->pos >= voice->time[3][i]) 297 | { 298 | voice->target_env[i] = voice->target[3][i]; 299 | } 300 | else if (voice->pos >= voice->time[2][i]) 301 | { 302 | voice->target_env[i] = voice->target[2][i] + 303 | (voice->pos - voice->time[2][i]) * voice->lerp[2][i]; 304 | } 305 | else if (voice->pos >= voice->time[1][i]) 306 | { 307 | voice->target_env[i] = voice->target[1][i] + 308 | (voice->pos - voice->time[1][i]) * voice->lerp[1][i]; 309 | } 310 | else 311 | { 312 | voice->target_env[i] = voice->target[0][i] + 313 | (voice->pos - voice->time[0][i]) * voice->lerp[0][i]; 314 | } 315 | } 316 | } 317 | 318 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) 319 | { 320 | voice->target_env_step[i] = 321 | (voice->target_env[i] - voice->env[i]) * (1.0f / FMSYNTH_FRAMES_PER_LFO); 322 | } 323 | } 324 | 325 | static void fmsynth_reset_envelope(fmsynth_t *fm, struct fmsynth_voice *voice) 326 | { 327 | voice->pos = 0.0f; 328 | voice->count = 0; 329 | voice->speed = fm->inv_sample_rate; 330 | voice->dead = 0; 331 | 332 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) 333 | { 334 | voice->env[i] = voice->target[0][i] = 0.0f; 335 | voice->time[0][i] = 0.0f; 336 | 337 | for (unsigned j = 1; j <= 3; j++) 338 | { 339 | voice->target[j][i] = fm->params.envelope_target[j - 1][i]; 340 | voice->time[j][i] = fm->params.envelope_delay[j - 1][i] + 341 | voice->time[j - 1][i]; 342 | } 343 | 344 | for (unsigned j = 0; j < 3; j++) 345 | { 346 | voice->lerp[j][i] = (voice->target[j + 1][i] - voice->target[j][i]) / 347 | (voice->time[j + 1][i] - voice->time[j][i]); 348 | } 349 | 350 | voice->release_time[i] = fm->params.envelope_release_time[i]; 351 | voice->falloff[i] = expf(logf(0.001f) * FMSYNTH_FRAMES_PER_LFO * 352 | fm->inv_sample_rate / voice->release_time[i]); 353 | } 354 | 355 | fmsynth_update_target_envelope(voice); 356 | } 357 | 358 | static void fmsynth_reset_voice(fmsynth_t *fm, struct fmsynth_voice *voice, 359 | float volume, float velocity, float freq) 360 | { 361 | voice->enable = 0; 362 | 363 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) 364 | { 365 | voice->phases[i] = 0.25f; 366 | 367 | float mod_amp = 1.0f - fm->params.velocity_sensitivity[i]; 368 | mod_amp += fm->params.velocity_sensitivity[i] * velocity; 369 | 370 | float ratio = freq / fm->params.keyboard_scaling_mid_point[i]; 371 | float factor = ratio > 1.0f ? 372 | fm->params.keyboard_scaling_high_factor[i] : 373 | fm->params.keyboard_scaling_low_factor[i]; 374 | 375 | mod_amp *= powf(ratio, factor); 376 | 377 | bool enable = fm->params.enable[i] > 0.5f; 378 | voice->enable |= enable << i; 379 | 380 | if (enable) 381 | { 382 | voice->amp[i] = mod_amp * fm->params.amp[i]; 383 | } 384 | else 385 | { 386 | voice->amp[i] = 0.0f; 387 | } 388 | 389 | voice->wheel_amp[i] = 1.0f - fm->params.mod_sensitivity[i] + 390 | fm->params.mod_sensitivity[i] * fm->wheel; 391 | voice->pan_amp[0][i] = volume * min(1.0f - fm->params.pan[i], 1.0f) * 392 | fm->params.carriers[i]; 393 | voice->pan_amp[1][i] = volume * min(1.0f + fm->params.pan[i], 1.0f) * 394 | fm->params.carriers[i]; 395 | 396 | voice->lfo_amp[i] = 1.0f; 397 | voice->lfo_freq_mod[i] = 1.0f; 398 | } 399 | 400 | voice->state = FMSYNTH_VOICE_RUNNING; 401 | fmsynth_reset_envelope(fm, voice); 402 | } 403 | 404 | static void fmsynth_voice_update_read_mod(struct fmsynth_voice *voice) 405 | { 406 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) 407 | { 408 | voice->read_mod[i] = 409 | voice->wheel_amp[i] * voice->lfo_amp[i] * voice->amp[i]; 410 | } 411 | } 412 | 413 | static void fmsynth_trigger_voice(fmsynth_t *fm, struct fmsynth_voice *voice, 414 | uint8_t note, uint8_t velocity) 415 | { 416 | voice->note = note; 417 | voice->base_freq = note_to_frequency(note); 418 | 419 | float freq = fm->bend * voice->base_freq; 420 | float mod_vel = velocity * (1.0f / 127.0f); 421 | 422 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 423 | { 424 | voice->step_rate[o] = 425 | (freq * fm->params.freq_mod[o] + fm->params.freq_offset[o]) * 426 | fm->inv_sample_rate; 427 | } 428 | 429 | fmsynth_reset_voice(fm, voice, 430 | fm->global_params.volume, mod_vel, voice->base_freq); 431 | fmsynth_voice_update_read_mod(voice); 432 | 433 | voice->lfo_phase = 0.25f; 434 | voice->lfo_step = FMSYNTH_FRAMES_PER_LFO * fm->global_params.lfo_freq * fm->inv_sample_rate; 435 | voice->count = 0; 436 | } 437 | 438 | fmsynth_status_t fmsynth_note_on(fmsynth_t *fm, uint8_t note, uint8_t velocity) 439 | { 440 | struct fmsynth_voice *voice = NULL; 441 | for (unsigned i = 0; i < fm->max_voices; i++) 442 | { 443 | if (fm->voices[i].state == FMSYNTH_VOICE_INACTIVE) 444 | { 445 | voice = &fm->voices[i]; 446 | break; 447 | } 448 | } 449 | 450 | if (voice) 451 | { 452 | fmsynth_trigger_voice(fm, voice, note, velocity); 453 | return FMSYNTH_STATUS_OK; 454 | } 455 | else 456 | { 457 | return FMSYNTH_STATUS_BUSY; 458 | } 459 | } 460 | 461 | static void fmsynth_release_voice(struct fmsynth_voice *voice) 462 | { 463 | voice->state = FMSYNTH_VOICE_RELEASED; 464 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) 465 | { 466 | voice->end_time[i] = voice->pos + voice->release_time[i]; 467 | } 468 | } 469 | 470 | void fmsynth_note_off(fmsynth_t *fm, uint8_t note) 471 | { 472 | for (unsigned i = 0; i < fm->max_voices; i++) 473 | { 474 | if (fm->voices[i].note == note && 475 | fm->voices[i].state == FMSYNTH_VOICE_RUNNING) 476 | { 477 | if (fm->sustained) 478 | { 479 | fm->voices[i].state = FMSYNTH_VOICE_SUSTAINED; 480 | } 481 | else 482 | { 483 | fmsynth_release_voice(&fm->voices[i]); 484 | } 485 | } 486 | } 487 | } 488 | 489 | void fmsynth_set_sustain(fmsynth_t *fm, bool enable) 490 | { 491 | bool releasing = fm->sustained && !enable; 492 | fm->sustained = enable; 493 | 494 | if (releasing) 495 | { 496 | for (unsigned i = 0; i < fm->max_voices; i++) 497 | { 498 | if (fm->voices[i].state == FMSYNTH_VOICE_SUSTAINED) 499 | { 500 | fmsynth_release_voice(&fm->voices[i]); 501 | } 502 | } 503 | } 504 | } 505 | 506 | void fmsynth_set_mod_wheel(fmsynth_t *fm, uint8_t wheel) 507 | { 508 | float value = wheel * (1.0f / 127.0f); 509 | fm->wheel = value; 510 | 511 | for (unsigned v = 0; v < fm->max_voices; v++) 512 | { 513 | struct fmsynth_voice *voice = &fm->voices[v]; 514 | 515 | if (voice->state != FMSYNTH_VOICE_INACTIVE) 516 | { 517 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 518 | { 519 | voice->wheel_amp[o] = 1.0f - fm->params.mod_sensitivity[o] + 520 | fm->params.mod_sensitivity[o] * value; 521 | } 522 | 523 | fmsynth_voice_update_read_mod(voice); 524 | } 525 | } 526 | } 527 | 528 | void fmsynth_set_pitch_bend(fmsynth_t *fm, uint16_t value) 529 | { 530 | float bend = pitch_bend_to_ratio(value); 531 | fm->bend = bend; 532 | 533 | for (unsigned v = 0; v < fm->max_voices; v++) 534 | { 535 | struct fmsynth_voice *voice = &fm->voices[v]; 536 | 537 | if (voice->state != FMSYNTH_VOICE_INACTIVE) 538 | { 539 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 540 | { 541 | float freq = bend * voice->base_freq; 542 | voice->step_rate[o] = 543 | (freq * fm->params.freq_mod[o] + fm->params.freq_offset[o]) * 544 | fm->inv_sample_rate; 545 | } 546 | } 547 | } 548 | } 549 | 550 | static void fmsynth_voice_set_lfo_value(struct fmsynth_voice *voice, 551 | const struct fmsynth_voice_parameters *params, float value) 552 | { 553 | for (unsigned i = 0; i < FMSYNTH_OPERATORS; i++) 554 | { 555 | voice->lfo_amp[i] = 1.0f + params->lfo_amp_depth[i] * value; 556 | voice->lfo_freq_mod[i] = 1.0f + params->lfo_freq_mod_depth[i] * value; 557 | } 558 | 559 | fmsynth_voice_update_read_mod(voice); 560 | } 561 | 562 | void fmsynth_release_all(fmsynth_t *fm) 563 | { 564 | for (unsigned i = 0; i < fm->max_voices; i++) 565 | { 566 | fmsynth_release_voice(&fm->voices[i]); 567 | } 568 | fm->sustained = false; 569 | } 570 | 571 | fmsynth_status_t fmsynth_parse_midi(fmsynth_t *fm, 572 | const uint8_t *data) 573 | { 574 | if ((data[0] & 0xf0) == 0x90) 575 | { 576 | if (data[2] != 0) 577 | { 578 | return fmsynth_note_on(fm, data[1], data[2]); 579 | } 580 | else 581 | { 582 | fmsynth_note_off(fm, data[1]); 583 | return FMSYNTH_STATUS_OK; 584 | } 585 | } 586 | else if ((data[0] & 0xf0) == 0x80) 587 | { 588 | fmsynth_note_off(fm, data[1]); 589 | return FMSYNTH_STATUS_OK; 590 | } 591 | else if ((data[0] & 0xf0) == 0xb0 && data[1] == 64) 592 | { 593 | fmsynth_set_sustain(fm, data[2] >= 64); 594 | return FMSYNTH_STATUS_OK; 595 | } 596 | else if ((data[0] & 0xf0) == 0xb0 && data[1] == 1) 597 | { 598 | fmsynth_set_mod_wheel(fm, data[2]); 599 | return FMSYNTH_STATUS_OK; 600 | } 601 | else if ((data[0] == 0xff) || 602 | (((data[0] & 0xf0) == 0xb0) && data[1] == 120)) 603 | { 604 | // Reset, All Sound Off 605 | fmsynth_release_all(fm); 606 | return FMSYNTH_STATUS_OK; 607 | } 608 | else if ((((data[0] & 0xf0) == 0xb0) && data[1] == 123) || 609 | data[0] == 0xfc) 610 | { 611 | // All Notes Off, STOP 612 | fmsynth_release_all(fm); 613 | return FMSYNTH_STATUS_OK; 614 | } 615 | else if ((data[0] & 0xf0) == 0xe0) 616 | { 617 | // Pitch bend 618 | uint16_t bend = data[1] | (data[2] << 7); 619 | fmsynth_set_pitch_bend(fm, bend); 620 | return FMSYNTH_STATUS_OK; 621 | } 622 | else if (data[0] == 0xf8) 623 | { 624 | // Timing message, just ignore. 625 | return FMSYNTH_STATUS_OK; 626 | } 627 | else 628 | { 629 | return FMSYNTH_STATUS_MESSAGE_UNKNOWN; 630 | } 631 | } 632 | 633 | struct fmsynth_parameter_data 634 | { 635 | const char *name; 636 | float minimum; 637 | float maximum; 638 | float default_value; 639 | bool logarithmic; 640 | }; 641 | 642 | static const struct fmsynth_parameter_data global_parameter_data[] = { 643 | { "Amp", 0.0f, 1.0f, 0.2f, false }, 644 | { "LFO Freq", 0.1f, 64.0f, 0.1f, true }, 645 | }; 646 | 647 | static const struct fmsynth_parameter_data parameter_data[] = { 648 | { "Volume", 0.005f, 16.0f, 1.0f, true }, 649 | { "Pan", -1.0f, 1.0f, 0.0f, false }, 650 | { "FreqMod", 0.0f, 16.0f, 1.0f, false }, 651 | { "FreqOffset", -128.0f, 128.0f, 0.0f, false }, 652 | { "Env T0", 0.0f, 1.0f, 1.0f, false }, 653 | { "Env T1", 0.0f, 1.0f, 0.5f, false }, 654 | { "Env T2", 0.0f, 1.0f, 0.25f, false }, 655 | { "Env D0", 0.005f, 8.0f, 0.05f, true }, 656 | { "Env D1", 0.005f, 8.0f, 0.05f, true }, 657 | { "Env D2", 0.005f, 8.0f, 0.25f, true }, 658 | { "Env Rel", 0.005f, 8.0f, 0.5f, true }, 659 | { "KeyScale Mid", 50.0f, 5000.0f, 440.0f, true }, 660 | { "KeyScale LoFactor", -2.0f, 2.0f, 0.0f, false }, 661 | { "KeyScale HiFactor", -2.0f, 2.0f, 0.0f, false }, 662 | { "Velocity Sensitivity", 0.0f, 1.0f, 1.0f, false }, 663 | { "ModWheel Sensitivity", 0.0f, 1.0f, 0.0f, false }, 664 | { "LFOAmpDepth", 0.0f, 1.0f, 0.0f, false }, 665 | { "LFOFreqDepth", 0.0f, 0.025f, 0.0f, false }, 666 | { "Enable", 0.0f, 1.0f, 1.0f, false }, 667 | { "Carrier", 0.0f, 1.0f, 1.0f, false }, 668 | { "Mod0ToOperator", 0.0f, 1.0f, 0.0f, false }, 669 | { "Mod1ToOperator", 0.0f, 1.0f, 0.0f, false }, 670 | { "Mod2ToOperator", 0.0f, 1.0f, 0.0f, false }, 671 | { "Mod3ToOperator", 0.0f, 1.0f, 0.0f, false }, 672 | { "Mod4ToOperator", 0.0f, 1.0f, 0.0f, false }, 673 | { "Mod5ToOperator", 0.0f, 1.0f, 0.0f, false }, 674 | { "Mod6ToOperator", 0.0f, 1.0f, 0.0f, false }, 675 | { "Mod7ToOperator", 0.0f, 1.0f, 0.0f, false }, 676 | }; 677 | 678 | void fmsynth_set_parameter(fmsynth_t *fm, 679 | unsigned parameter, unsigned operator_index, float value) 680 | { 681 | if (parameter < FMSYNTH_PARAM_END && operator_index < FMSYNTH_OPERATORS) 682 | { 683 | float *param = fm->params.amp; 684 | param[parameter * FMSYNTH_OPERATORS + operator_index] = value; 685 | } 686 | } 687 | 688 | static float convert_from_normalized(const struct fmsynth_parameter_data *data, float value) 689 | { 690 | if (data->logarithmic) 691 | { 692 | float minlog = log2f(data->minimum); 693 | float maxlog = log2f(data->maximum); 694 | return exp2f(minlog * (1.0f - value) + maxlog * value); 695 | } 696 | else 697 | return data->minimum * (1.0f - value) + data->maximum * value; 698 | } 699 | 700 | static float convert_to_normalized(const struct fmsynth_parameter_data *data, float value) 701 | { 702 | if (data->logarithmic) 703 | { 704 | float minlog = log2f(data->minimum); 705 | float maxlog = log2f(data->maximum); 706 | float l = log2f(value); 707 | return (l - minlog) / (maxlog - minlog); 708 | } 709 | else 710 | return (value - data->minimum) / (data->maximum - data->minimum); 711 | } 712 | 713 | float fmsynth_convert_to_normalized_global_parameter(fmsynth_t *fm, 714 | unsigned parameter, float value) 715 | { 716 | (void)fm; 717 | if (parameter < FMSYNTH_GLOBAL_PARAM_END) 718 | { 719 | const struct fmsynth_parameter_data *data = &global_parameter_data[parameter]; 720 | return convert_to_normalized(data, value); 721 | } 722 | else 723 | return 0.0f; 724 | } 725 | 726 | float fmsynth_convert_from_normalized_global_parameter(fmsynth_t *fm, 727 | unsigned parameter, float value) 728 | { 729 | (void)fm; 730 | if (parameter < FMSYNTH_GLOBAL_PARAM_END) 731 | { 732 | const struct fmsynth_parameter_data *data = &global_parameter_data[parameter]; 733 | return convert_from_normalized(data, value); 734 | } 735 | else 736 | return 0.0f; 737 | } 738 | 739 | float fmsynth_convert_to_normalized_parameter(fmsynth_t *fm, 740 | unsigned parameter, float value) 741 | { 742 | (void)fm; 743 | if (parameter < FMSYNTH_PARAM_END) 744 | { 745 | const struct fmsynth_parameter_data *data = ¶meter_data[parameter]; 746 | return convert_to_normalized(data, value); 747 | } 748 | else 749 | return 0.0f; 750 | } 751 | 752 | float fmsynth_convert_from_normalized_parameter(fmsynth_t *fm, 753 | unsigned parameter, float value) 754 | { 755 | (void)fm; 756 | if (parameter < FMSYNTH_PARAM_END) 757 | { 758 | const struct fmsynth_parameter_data *data = ¶meter_data[parameter]; 759 | return convert_from_normalized(data, value); 760 | } 761 | else 762 | return 0.0f; 763 | } 764 | 765 | float fmsynth_get_parameter(fmsynth_t *fm, 766 | unsigned parameter, unsigned operator_index) 767 | { 768 | if (parameter < FMSYNTH_PARAM_END && operator_index < FMSYNTH_OPERATORS) 769 | { 770 | float *param = fm->params.amp; 771 | return param[parameter * FMSYNTH_OPERATORS + operator_index]; 772 | } 773 | else 774 | return 0.0f; 775 | } 776 | 777 | void fmsynth_set_global_parameter(fmsynth_t *fm, 778 | unsigned parameter, float value) 779 | { 780 | if (parameter < FMSYNTH_GLOBAL_PARAM_END) 781 | { 782 | float *param = &fm->global_params.volume; 783 | param[parameter] = value; 784 | } 785 | } 786 | 787 | float fmsynth_get_global_parameter(fmsynth_t *fm, 788 | unsigned parameter) 789 | { 790 | if (parameter < FMSYNTH_GLOBAL_PARAM_END) 791 | { 792 | float *param = &fm->global_params.volume; 793 | return param[parameter]; 794 | } 795 | else 796 | return 0.0f; 797 | } 798 | 799 | static bool fmsynth_voice_update_active(struct fmsynth_voice *voice) 800 | { 801 | if (voice->enable & (~voice->dead)) 802 | { 803 | return true; 804 | } 805 | else 806 | { 807 | voice->state = FMSYNTH_VOICE_INACTIVE; 808 | return false; 809 | } 810 | } 811 | 812 | static float fmsynth_oscillator(float phase) 813 | { 814 | float x = phase < 0.5f ? (phase - 0.25f) : (0.75f - phase); 815 | 816 | float x2 = x * x; 817 | float x3 = x2 * x; 818 | x *= 2.0f * PI; 819 | x -= x3 * INV_FACTORIAL_3_2PIPOW3; 820 | 821 | float x5 = x3 * x2; 822 | x += x5 * INV_FACTORIAL_5_2PIPOW5; 823 | 824 | float x7 = x5 * x2; 825 | x -= x7 * INV_FACTORIAL_7_2PIPOW7; 826 | 827 | return x; 828 | } 829 | 830 | #if defined(__AVX__) && defined(FMSYNTH_SIMD) 831 | #include "x86/fmsynth_avx.c" 832 | #elif defined(__SSE__) && defined(FMSYNTH_SIMD) 833 | #include "x86/fmsynth_sse.c" 834 | #elif defined(__ARM_NEON__) && defined(FMSYNTH_SIMD) 835 | #include "arm/fmsynth_arm.c" 836 | #else 837 | static void fmsynth_process_frames(fmsynth_t *fm, 838 | struct fmsynth_voice *voice, float *left, float *right, unsigned frames) 839 | { 840 | float cached[FMSYNTH_OPERATORS]; 841 | float cached_modulator[FMSYNTH_OPERATORS]; 842 | float steps[FMSYNTH_OPERATORS]; 843 | 844 | for (unsigned f = 0; f < frames; f++) 845 | { 846 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 847 | { 848 | steps[o] = voice->lfo_freq_mod[o] * voice->step_rate[o]; 849 | } 850 | 851 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 852 | { 853 | float value = voice->env[o] * voice->read_mod[o] * 854 | fmsynth_oscillator(voice->phases[o]); 855 | 856 | cached[o] = value; 857 | cached_modulator[o] = value * voice->step_rate[o]; 858 | voice->env[o] += voice->target_env_step[o]; 859 | } 860 | 861 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 862 | { 863 | float scalar = cached_modulator[o]; 864 | const float *vec = fm->params.mod_to_carriers[o]; 865 | for (unsigned j = 0; j < FMSYNTH_OPERATORS; j++) 866 | steps[j] += scalar * vec[j]; 867 | } 868 | 869 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 870 | { 871 | voice->phases[o] += steps[o]; 872 | voice->phases[o] -= floorf(voice->phases[o]); 873 | } 874 | 875 | for (unsigned o = 0; o < FMSYNTH_OPERATORS; o++) 876 | { 877 | left[f] += cached[o] * voice->pan_amp[0][o]; 878 | right[f] += cached[o] * voice->pan_amp[1][o]; 879 | } 880 | } 881 | } 882 | #endif 883 | 884 | static void fmsynth_render_voice(fmsynth_t *fm, struct fmsynth_voice *voice, 885 | float *left, float *right, unsigned frames) 886 | { 887 | while (frames) 888 | { 889 | unsigned to_render = min(FMSYNTH_FRAMES_PER_LFO - voice->count, frames); 890 | 891 | fmsynth_process_frames(fm, voice, left, right, to_render); 892 | 893 | left += to_render; 894 | right += to_render; 895 | frames -= to_render; 896 | voice->count += to_render; 897 | 898 | if (voice->count == FMSYNTH_FRAMES_PER_LFO) 899 | { 900 | float lfo_value = fmsynth_oscillator(voice->lfo_phase); 901 | voice->lfo_phase += voice->lfo_step; 902 | voice->lfo_phase -= floorf(voice->lfo_phase); 903 | voice->count = 0; 904 | 905 | fmsynth_voice_set_lfo_value(voice, &fm->params, lfo_value); 906 | fmsynth_update_target_envelope(voice); 907 | } 908 | } 909 | } 910 | 911 | unsigned fmsynth_render(fmsynth_t *fm, float *left, float *right, 912 | unsigned frames) 913 | { 914 | unsigned active_voices = 0; 915 | for (unsigned i = 0; i < fm->max_voices; i++) 916 | { 917 | if (fm->voices[i].state != FMSYNTH_VOICE_INACTIVE) 918 | { 919 | fmsynth_render_voice(fm, &fm->voices[i], left, right, frames); 920 | if (fmsynth_voice_update_active(&fm->voices[i])) 921 | { 922 | active_voices++; 923 | } 924 | } 925 | } 926 | 927 | return active_voices; 928 | } 929 | 930 | size_t fmsynth_preset_size(void) 931 | { 932 | return 933 | 8 + 934 | sizeof(struct fmsynth_preset_metadata) + 935 | FMSYNTH_PARAM_END * FMSYNTH_OPERATORS * sizeof(uint32_t) + 936 | FMSYNTH_GLOBAL_PARAM_END * sizeof(uint32_t); 937 | } 938 | 939 | // We don't need full precision mantissa. 940 | // Allows packing floating point in 32-bit in a portable way. 941 | static uint32_t pack_float(float value) 942 | { 943 | int exponent; 944 | float mantissa = frexpf(value, &exponent); 945 | 946 | int32_t fixed_mantissa = (int32_t)roundf(mantissa * 0x8000); 947 | int16_t fractional; 948 | if (fixed_mantissa > 0x7fff) 949 | { 950 | fractional = 0x7fff; 951 | } 952 | else if (fixed_mantissa < -0x8000) 953 | { 954 | fractional = -0x8000; 955 | } 956 | else 957 | { 958 | fractional = fixed_mantissa; 959 | } 960 | 961 | return (((uint32_t)exponent & 0xffff) << 16) | (uint16_t)fractional; 962 | } 963 | 964 | static float unpack_float(uint32_t value) 965 | { 966 | if (value == 0) 967 | { 968 | return 0.0f; 969 | } 970 | 971 | int exp = (int16_t)(value >> 16); 972 | float fractional = (float)(int16_t)(value & 0xffff) / 0x8000; 973 | 974 | return ldexpf(fractional, exp); 975 | } 976 | 977 | static void write_u32(uint8_t *buffer, uint32_t value) 978 | { 979 | buffer[0] = (uint8_t)(value >> 24); 980 | buffer[1] = (uint8_t)(value >> 16); 981 | buffer[2] = (uint8_t)(value >> 8); 982 | buffer[3] = (uint8_t)(value >> 0); 983 | } 984 | 985 | static uint32_t read_u32(const uint8_t *buffer) 986 | { 987 | return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3] << 0); 988 | } 989 | 990 | fmsynth_status_t fmsynth_preset_save(fmsynth_t *fm, const struct fmsynth_preset_metadata *metadata, 991 | void *buffer, size_t size) 992 | { 993 | return fmsynth_preset_save_private(&fm->global_params, &fm->params, 994 | metadata, buffer, size); 995 | } 996 | 997 | fmsynth_status_t fmsynth_preset_save_private(struct fmsynth_global_parameters *global_params, 998 | struct fmsynth_voice_parameters *voice_params, 999 | const struct fmsynth_preset_metadata *metadata, 1000 | void *buffer_, size_t size) 1001 | { 1002 | uint8_t *buffer = buffer_; 1003 | 1004 | if (size < fmsynth_preset_size()) 1005 | { 1006 | return FMSYNTH_STATUS_BUFFER_TOO_SMALL; 1007 | } 1008 | 1009 | if (metadata) 1010 | { 1011 | if (metadata->name[FMSYNTH_PRESET_STRING_SIZE - 1] != '\0' || 1012 | metadata->author[FMSYNTH_PRESET_STRING_SIZE - 1] != '\0') 1013 | { 1014 | return FMSYNTH_STATUS_NO_NUL_TERMINATE; 1015 | } 1016 | } 1017 | 1018 | memcpy(buffer, "FMSYNTH1", 8); 1019 | buffer += 8; 1020 | 1021 | if (metadata) 1022 | { 1023 | memcpy(buffer, metadata->name, sizeof(metadata->name)); 1024 | memcpy(buffer + sizeof(metadata->name), metadata->author, sizeof(metadata->author)); 1025 | } 1026 | else 1027 | { 1028 | memset(buffer, 0, sizeof(metadata->name)); 1029 | memset(buffer + sizeof(metadata->name), 0, sizeof(metadata->author)); 1030 | } 1031 | buffer += sizeof(metadata->name) + sizeof(metadata->author); 1032 | 1033 | const float *globals = &global_params->volume; 1034 | for (unsigned i = 0; i < FMSYNTH_GLOBAL_PARAM_END; i++) 1035 | { 1036 | write_u32(buffer, pack_float(globals[i])); 1037 | buffer += sizeof(uint32_t); 1038 | } 1039 | 1040 | const float *params = voice_params->amp; 1041 | for (unsigned i = 0; i < FMSYNTH_PARAM_END * FMSYNTH_OPERATORS; i++) 1042 | { 1043 | write_u32(buffer, pack_float(params[i])); 1044 | buffer += sizeof(uint32_t); 1045 | } 1046 | 1047 | return FMSYNTH_STATUS_OK; 1048 | } 1049 | 1050 | fmsynth_status_t fmsynth_preset_load(fmsynth_t *fm, struct fmsynth_preset_metadata *metadata, 1051 | const void *buffer, size_t size) 1052 | { 1053 | return fmsynth_preset_load_private(&fm->global_params, &fm->params, 1054 | metadata, buffer, size); 1055 | } 1056 | 1057 | fmsynth_status_t fmsynth_preset_load_private(struct fmsynth_global_parameters *global_params, 1058 | struct fmsynth_voice_parameters *voice_params, 1059 | struct fmsynth_preset_metadata *metadata, 1060 | const void *buffer_, size_t size) 1061 | { 1062 | const uint8_t *buffer = buffer_; 1063 | 1064 | if (size < fmsynth_preset_size()) 1065 | { 1066 | return FMSYNTH_STATUS_BUFFER_TOO_SMALL; 1067 | } 1068 | 1069 | if (memcmp(buffer, "FMSYNTH1", 8) != 0) 1070 | { 1071 | return FMSYNTH_STATUS_INVALID_FORMAT; 1072 | } 1073 | buffer += 8; 1074 | 1075 | if (buffer[FMSYNTH_PRESET_STRING_SIZE - 1] != '\0') 1076 | { 1077 | return FMSYNTH_STATUS_NO_NUL_TERMINATE; 1078 | } 1079 | 1080 | if (metadata) 1081 | { 1082 | memcpy(metadata->name, buffer, sizeof(metadata->name)); 1083 | } 1084 | buffer += sizeof(metadata->name); 1085 | 1086 | if (buffer[FMSYNTH_PRESET_STRING_SIZE - 1] != '\0') 1087 | { 1088 | return FMSYNTH_STATUS_NO_NUL_TERMINATE; 1089 | } 1090 | 1091 | if (metadata) 1092 | { 1093 | memcpy(metadata->author, buffer, sizeof(metadata->author)); 1094 | } 1095 | buffer += sizeof(metadata->author); 1096 | 1097 | float *globals = &global_params->volume; 1098 | for (unsigned i = 0; i < FMSYNTH_GLOBAL_PARAM_END; i++) 1099 | { 1100 | globals[i] = unpack_float(read_u32(buffer)); 1101 | buffer += sizeof(uint32_t); 1102 | } 1103 | 1104 | float *params = voice_params->amp; 1105 | for (unsigned i = 0; i < FMSYNTH_PARAM_END * FMSYNTH_OPERATORS; i++) 1106 | { 1107 | params[i] = unpack_float(read_u32(buffer)); 1108 | buffer += sizeof(uint32_t); 1109 | } 1110 | 1111 | return FMSYNTH_STATUS_OK; 1112 | } 1113 | 1114 | unsigned fmsynth_get_version(void) 1115 | { 1116 | return FMSYNTH_VERSION; 1117 | } 1118 | 1119 | -------------------------------------------------------------------------------- /lv2/fmsynth.ttl: -------------------------------------------------------------------------------- 1 | @prefix lv2: . 2 | @prefix doap: . 3 | @prefix pg: . 4 | @prefix ll: . 5 | @prefix ev: . 6 | @prefix foaf: . 7 | @prefix guiext: . 8 | @prefix epp: . 9 | @prefix atom: . 10 | @prefix urid: . 11 | 12 | 13 | a foaf:Person; 14 | foaf:name "Hans-Kristian Arntzen"; 15 | foaf:mbox ; 16 | foaf:homepage . 17 | 18 | 19 | a guiext:GtkUI; 20 | guiext:binary ; 21 | guiext:requiredFeature guiext:makeResident. 22 | 23 | a pg:StereoGroup. 24 | 25 | 26 | a lv2:Plugin, lv2:InstrumentPlugin; 27 | lv2:binary ; 28 | lv2:optionalFeature lv2:hardRTCapable; 29 | lv2:requiredFeature urid:map; 30 | doap:name "FM Synth"; 31 | doap:license ; 32 | doap:shortdesc "FM Synthesizer"; 33 | doap:maintainer ; 34 | ll:pegName "peg"; 35 | guiext:ui ; 36 | 37 | lv2:port [ 38 | a lv2:AudioPort, lv2:OutputPort; 39 | lv2:index 0; 40 | lv2:symbol "output_left"; 41 | lv2:name "Left Output"; 42 | pg:membership [ 43 | pg:group ; 44 | pg:role pg:leftChannel; 45 | ]; 46 | ], 47 | 48 | [ 49 | a lv2:AudioPort, lv2:OutputPort; 50 | lv2:index 1; 51 | lv2:symbol "output_right"; 52 | lv2:name "Right Output"; 53 | pg:membership [ 54 | pg:group ; 55 | pg:role pg:rightChannel; 56 | ]; 57 | ], 58 | 59 | [ 60 | a atom:AtomPort, lv2:InputPort; 61 | lv2:index 2; 62 | lv2:symbol "midi"; 63 | lv2:name "MIDI"; 64 | atom:bufferType atom:Sequence; 65 | atom:supports ; 66 | ], 67 | 68 | 69 | [ 70 | a lv2:ControlPort, lv2:InputPort; 71 | lv2:index 3; 72 | lv2:symbol "volume"; 73 | lv2:name "Volume"; 74 | lv2:minimum 0.000000; 75 | lv2:maximum 1.000000; 76 | lv2:default 0.200000; 77 | ], 78 | 79 | [ 80 | a lv2:ControlPort, lv2:InputPort; 81 | lv2:index 4; 82 | lv2:symbol "lfofreq"; 83 | lv2:name "LFOFreq"; 84 | lv2:minimum 0.100000; 85 | lv2:maximum 64.000000; 86 | lv2:default 0.100000; 87 | lv2:portProperty epp:logarithmic; 88 | ], 89 | 90 | [ 91 | a lv2:ControlPort, lv2:InputPort; 92 | lv2:index 5; 93 | lv2:symbol "amp__op__0_"; 94 | lv2:name "Amp (Op #0)"; 95 | lv2:minimum 0.005000; 96 | lv2:maximum 16.000000; 97 | lv2:default 1.000000; 98 | lv2:portProperty epp:logarithmic; 99 | ], 100 | 101 | [ 102 | a lv2:ControlPort, lv2:InputPort; 103 | lv2:index 6; 104 | lv2:symbol "pan__op__0_"; 105 | lv2:name "Pan (Op #0)"; 106 | lv2:minimum -1.000000; 107 | lv2:maximum 1.000000; 108 | lv2:default 0.000000; 109 | ], 110 | 111 | [ 112 | a lv2:ControlPort, lv2:InputPort; 113 | lv2:index 7; 114 | lv2:symbol "freqmod__op__0_"; 115 | lv2:name "FreqMod (Op #0)"; 116 | lv2:minimum 0.000000; 117 | lv2:maximum 16.000000; 118 | lv2:default 1.000000; 119 | ], 120 | 121 | [ 122 | a lv2:ControlPort, lv2:InputPort; 123 | lv2:index 8; 124 | lv2:symbol "freqoffset__op__0_"; 125 | lv2:name "FreqOffset (Op #0)"; 126 | lv2:minimum -128.000000; 127 | lv2:maximum 128.000000; 128 | lv2:default 0.000000; 129 | ], 130 | 131 | [ 132 | a lv2:ControlPort, lv2:InputPort; 133 | lv2:index 9; 134 | lv2:symbol "envelopetarget0__op__0_"; 135 | lv2:name "EnvelopeTarget0 (Op #0)"; 136 | lv2:minimum 0.000000; 137 | lv2:maximum 1.000000; 138 | lv2:default 1.000000; 139 | ], 140 | 141 | [ 142 | a lv2:ControlPort, lv2:InputPort; 143 | lv2:index 10; 144 | lv2:symbol "envelopetarget1__op__0_"; 145 | lv2:name "EnvelopeTarget1 (Op #0)"; 146 | lv2:minimum 0.000000; 147 | lv2:maximum 1.000000; 148 | lv2:default 0.500000; 149 | ], 150 | 151 | [ 152 | a lv2:ControlPort, lv2:InputPort; 153 | lv2:index 11; 154 | lv2:symbol "envelopetarget2__op__0_"; 155 | lv2:name "EnvelopeTarget2 (Op #0)"; 156 | lv2:minimum 0.000000; 157 | lv2:maximum 1.000000; 158 | lv2:default 0.250000; 159 | ], 160 | 161 | [ 162 | a lv2:ControlPort, lv2:InputPort; 163 | lv2:index 12; 164 | lv2:symbol "envelopedelay0__op__0_"; 165 | lv2:name "EnvelopeDelay0 (Op #0)"; 166 | lv2:minimum 0.005000; 167 | lv2:maximum 8.000000; 168 | lv2:default 0.050000; 169 | lv2:portProperty epp:logarithmic; 170 | ], 171 | 172 | [ 173 | a lv2:ControlPort, lv2:InputPort; 174 | lv2:index 13; 175 | lv2:symbol "envelopedelay1__op__0_"; 176 | lv2:name "EnvelopeDelay1 (Op #0)"; 177 | lv2:minimum 0.005000; 178 | lv2:maximum 8.000000; 179 | lv2:default 0.050000; 180 | lv2:portProperty epp:logarithmic; 181 | ], 182 | 183 | [ 184 | a lv2:ControlPort, lv2:InputPort; 185 | lv2:index 14; 186 | lv2:symbol "envelopedelay2__op__0_"; 187 | lv2:name "EnvelopeDelay2 (Op #0)"; 188 | lv2:minimum 0.005000; 189 | lv2:maximum 8.000000; 190 | lv2:default 0.250000; 191 | lv2:portProperty epp:logarithmic; 192 | ], 193 | 194 | [ 195 | a lv2:ControlPort, lv2:InputPort; 196 | lv2:index 15; 197 | lv2:symbol "envelopereleasetime__op__0_"; 198 | lv2:name "EnvelopeReleaseTime (Op #0)"; 199 | lv2:minimum 0.005000; 200 | lv2:maximum 8.000000; 201 | lv2:default 0.500000; 202 | lv2:portProperty epp:logarithmic; 203 | ], 204 | 205 | [ 206 | a lv2:ControlPort, lv2:InputPort; 207 | lv2:index 16; 208 | lv2:symbol "keyboardscalingmidpoint__op__0_"; 209 | lv2:name "KeyboardScalingMidPoint (Op #0)"; 210 | lv2:minimum 50.000000; 211 | lv2:maximum 5000.000000; 212 | lv2:default 440.000000; 213 | lv2:portProperty epp:logarithmic; 214 | ], 215 | 216 | [ 217 | a lv2:ControlPort, lv2:InputPort; 218 | lv2:index 17; 219 | lv2:symbol "keyboardscalinglowfactor__op__0_"; 220 | lv2:name "KeyboardScalingLowFactor (Op #0)"; 221 | lv2:minimum -2.000000; 222 | lv2:maximum 2.000000; 223 | lv2:default 0.000000; 224 | ], 225 | 226 | [ 227 | a lv2:ControlPort, lv2:InputPort; 228 | lv2:index 18; 229 | lv2:symbol "keyboardscalinghighfactor__op__0_"; 230 | lv2:name "KeyboardScalingHighFactor (Op #0)"; 231 | lv2:minimum -2.000000; 232 | lv2:maximum 2.000000; 233 | lv2:default 0.000000; 234 | ], 235 | 236 | [ 237 | a lv2:ControlPort, lv2:InputPort; 238 | lv2:index 19; 239 | lv2:symbol "velocitysensitivity__op__0_"; 240 | lv2:name "VelocitySensitivity (Op #0)"; 241 | lv2:minimum 0.000000; 242 | lv2:maximum 1.000000; 243 | lv2:default 1.000000; 244 | ], 245 | 246 | [ 247 | a lv2:ControlPort, lv2:InputPort; 248 | lv2:index 20; 249 | lv2:symbol "modsensitivity__op__0_"; 250 | lv2:name "ModSensitivity (Op #0)"; 251 | lv2:minimum 0.000000; 252 | lv2:maximum 1.000000; 253 | lv2:default 0.000000; 254 | ], 255 | 256 | [ 257 | a lv2:ControlPort, lv2:InputPort; 258 | lv2:index 21; 259 | lv2:symbol "lfoampdepth__op__0_"; 260 | lv2:name "LFOAmpDepth (Op #0)"; 261 | lv2:minimum 0.000000; 262 | lv2:maximum 1.000000; 263 | lv2:default 0.000000; 264 | ], 265 | 266 | [ 267 | a lv2:ControlPort, lv2:InputPort; 268 | lv2:index 22; 269 | lv2:symbol "lfofreqmoddepth__op__0_"; 270 | lv2:name "LFOFreqModDepth (Op #0)"; 271 | lv2:minimum 0.000000; 272 | lv2:maximum 0.025000; 273 | lv2:default 0.000000; 274 | ], 275 | 276 | [ 277 | a lv2:ControlPort, lv2:InputPort; 278 | lv2:index 23; 279 | lv2:symbol "enable__op__0_"; 280 | lv2:name "Enable (Op #0)"; 281 | lv2:minimum 0; 282 | lv2:maximum 1; 283 | lv2:default 1; 284 | lv2:portProperty lv2:toggled; 285 | ], 286 | 287 | [ 288 | a lv2:ControlPort, lv2:InputPort; 289 | lv2:index 24; 290 | lv2:symbol "carriers__op__0_"; 291 | lv2:name "Carriers (Op #0)"; 292 | lv2:minimum 0.000000; 293 | lv2:maximum 1.000000; 294 | lv2:default 1.000000; 295 | ], 296 | 297 | [ 298 | a lv2:ControlPort, lv2:InputPort; 299 | lv2:index 25; 300 | lv2:symbol "mod0tooperator__op__0_"; 301 | lv2:name "Mod0ToOperator (Op #0)"; 302 | lv2:minimum 0.000000; 303 | lv2:maximum 1.000000; 304 | lv2:default 0.000000; 305 | ], 306 | 307 | [ 308 | a lv2:ControlPort, lv2:InputPort; 309 | lv2:index 26; 310 | lv2:symbol "mod1tooperator__op__0_"; 311 | lv2:name "Mod1ToOperator (Op #0)"; 312 | lv2:minimum 0.000000; 313 | lv2:maximum 1.000000; 314 | lv2:default 0.000000; 315 | ], 316 | 317 | [ 318 | a lv2:ControlPort, lv2:InputPort; 319 | lv2:index 27; 320 | lv2:symbol "mod2tooperator__op__0_"; 321 | lv2:name "Mod2ToOperator (Op #0)"; 322 | lv2:minimum 0.000000; 323 | lv2:maximum 1.000000; 324 | lv2:default 0.000000; 325 | ], 326 | 327 | [ 328 | a lv2:ControlPort, lv2:InputPort; 329 | lv2:index 28; 330 | lv2:symbol "mod3tooperator__op__0_"; 331 | lv2:name "Mod3ToOperator (Op #0)"; 332 | lv2:minimum 0.000000; 333 | lv2:maximum 1.000000; 334 | lv2:default 0.000000; 335 | ], 336 | 337 | [ 338 | a lv2:ControlPort, lv2:InputPort; 339 | lv2:index 29; 340 | lv2:symbol "mod4tooperator__op__0_"; 341 | lv2:name "Mod4ToOperator (Op #0)"; 342 | lv2:minimum 0.000000; 343 | lv2:maximum 1.000000; 344 | lv2:default 0.000000; 345 | ], 346 | 347 | [ 348 | a lv2:ControlPort, lv2:InputPort; 349 | lv2:index 30; 350 | lv2:symbol "mod5tooperator__op__0_"; 351 | lv2:name "Mod5ToOperator (Op #0)"; 352 | lv2:minimum 0.000000; 353 | lv2:maximum 1.000000; 354 | lv2:default 0.000000; 355 | ], 356 | 357 | [ 358 | a lv2:ControlPort, lv2:InputPort; 359 | lv2:index 31; 360 | lv2:symbol "mod6tooperator__op__0_"; 361 | lv2:name "Mod6ToOperator (Op #0)"; 362 | lv2:minimum 0.000000; 363 | lv2:maximum 1.000000; 364 | lv2:default 0.000000; 365 | ], 366 | 367 | [ 368 | a lv2:ControlPort, lv2:InputPort; 369 | lv2:index 32; 370 | lv2:symbol "mod7tooperator__op__0_"; 371 | lv2:name "Mod7ToOperator (Op #0)"; 372 | lv2:minimum 0.000000; 373 | lv2:maximum 1.000000; 374 | lv2:default 0.000000; 375 | ], 376 | 377 | [ 378 | a lv2:ControlPort, lv2:InputPort; 379 | lv2:index 33; 380 | lv2:symbol "amp__op__1_"; 381 | lv2:name "Amp (Op #1)"; 382 | lv2:minimum 0.005000; 383 | lv2:maximum 16.000000; 384 | lv2:default 1.000000; 385 | lv2:portProperty epp:logarithmic; 386 | ], 387 | 388 | [ 389 | a lv2:ControlPort, lv2:InputPort; 390 | lv2:index 34; 391 | lv2:symbol "pan__op__1_"; 392 | lv2:name "Pan (Op #1)"; 393 | lv2:minimum -1.000000; 394 | lv2:maximum 1.000000; 395 | lv2:default 0.000000; 396 | ], 397 | 398 | [ 399 | a lv2:ControlPort, lv2:InputPort; 400 | lv2:index 35; 401 | lv2:symbol "freqmod__op__1_"; 402 | lv2:name "FreqMod (Op #1)"; 403 | lv2:minimum 0.000000; 404 | lv2:maximum 16.000000; 405 | lv2:default 1.000000; 406 | ], 407 | 408 | [ 409 | a lv2:ControlPort, lv2:InputPort; 410 | lv2:index 36; 411 | lv2:symbol "freqoffset__op__1_"; 412 | lv2:name "FreqOffset (Op #1)"; 413 | lv2:minimum -128.000000; 414 | lv2:maximum 128.000000; 415 | lv2:default 0.000000; 416 | ], 417 | 418 | [ 419 | a lv2:ControlPort, lv2:InputPort; 420 | lv2:index 37; 421 | lv2:symbol "envelopetarget0__op__1_"; 422 | lv2:name "EnvelopeTarget0 (Op #1)"; 423 | lv2:minimum 0.000000; 424 | lv2:maximum 1.000000; 425 | lv2:default 1.000000; 426 | ], 427 | 428 | [ 429 | a lv2:ControlPort, lv2:InputPort; 430 | lv2:index 38; 431 | lv2:symbol "envelopetarget1__op__1_"; 432 | lv2:name "EnvelopeTarget1 (Op #1)"; 433 | lv2:minimum 0.000000; 434 | lv2:maximum 1.000000; 435 | lv2:default 0.500000; 436 | ], 437 | 438 | [ 439 | a lv2:ControlPort, lv2:InputPort; 440 | lv2:index 39; 441 | lv2:symbol "envelopetarget2__op__1_"; 442 | lv2:name "EnvelopeTarget2 (Op #1)"; 443 | lv2:minimum 0.000000; 444 | lv2:maximum 1.000000; 445 | lv2:default 0.250000; 446 | ], 447 | 448 | [ 449 | a lv2:ControlPort, lv2:InputPort; 450 | lv2:index 40; 451 | lv2:symbol "envelopedelay0__op__1_"; 452 | lv2:name "EnvelopeDelay0 (Op #1)"; 453 | lv2:minimum 0.005000; 454 | lv2:maximum 8.000000; 455 | lv2:default 0.050000; 456 | lv2:portProperty epp:logarithmic; 457 | ], 458 | 459 | [ 460 | a lv2:ControlPort, lv2:InputPort; 461 | lv2:index 41; 462 | lv2:symbol "envelopedelay1__op__1_"; 463 | lv2:name "EnvelopeDelay1 (Op #1)"; 464 | lv2:minimum 0.005000; 465 | lv2:maximum 8.000000; 466 | lv2:default 0.050000; 467 | lv2:portProperty epp:logarithmic; 468 | ], 469 | 470 | [ 471 | a lv2:ControlPort, lv2:InputPort; 472 | lv2:index 42; 473 | lv2:symbol "envelopedelay2__op__1_"; 474 | lv2:name "EnvelopeDelay2 (Op #1)"; 475 | lv2:minimum 0.005000; 476 | lv2:maximum 8.000000; 477 | lv2:default 0.250000; 478 | lv2:portProperty epp:logarithmic; 479 | ], 480 | 481 | [ 482 | a lv2:ControlPort, lv2:InputPort; 483 | lv2:index 43; 484 | lv2:symbol "envelopereleasetime__op__1_"; 485 | lv2:name "EnvelopeReleaseTime (Op #1)"; 486 | lv2:minimum 0.005000; 487 | lv2:maximum 8.000000; 488 | lv2:default 0.500000; 489 | lv2:portProperty epp:logarithmic; 490 | ], 491 | 492 | [ 493 | a lv2:ControlPort, lv2:InputPort; 494 | lv2:index 44; 495 | lv2:symbol "keyboardscalingmidpoint__op__1_"; 496 | lv2:name "KeyboardScalingMidPoint (Op #1)"; 497 | lv2:minimum 50.000000; 498 | lv2:maximum 5000.000000; 499 | lv2:default 440.000000; 500 | lv2:portProperty epp:logarithmic; 501 | ], 502 | 503 | [ 504 | a lv2:ControlPort, lv2:InputPort; 505 | lv2:index 45; 506 | lv2:symbol "keyboardscalinglowfactor__op__1_"; 507 | lv2:name "KeyboardScalingLowFactor (Op #1)"; 508 | lv2:minimum -2.000000; 509 | lv2:maximum 2.000000; 510 | lv2:default 0.000000; 511 | ], 512 | 513 | [ 514 | a lv2:ControlPort, lv2:InputPort; 515 | lv2:index 46; 516 | lv2:symbol "keyboardscalinghighfactor__op__1_"; 517 | lv2:name "KeyboardScalingHighFactor (Op #1)"; 518 | lv2:minimum -2.000000; 519 | lv2:maximum 2.000000; 520 | lv2:default 0.000000; 521 | ], 522 | 523 | [ 524 | a lv2:ControlPort, lv2:InputPort; 525 | lv2:index 47; 526 | lv2:symbol "velocitysensitivity__op__1_"; 527 | lv2:name "VelocitySensitivity (Op #1)"; 528 | lv2:minimum 0.000000; 529 | lv2:maximum 1.000000; 530 | lv2:default 1.000000; 531 | ], 532 | 533 | [ 534 | a lv2:ControlPort, lv2:InputPort; 535 | lv2:index 48; 536 | lv2:symbol "modsensitivity__op__1_"; 537 | lv2:name "ModSensitivity (Op #1)"; 538 | lv2:minimum 0.000000; 539 | lv2:maximum 1.000000; 540 | lv2:default 0.000000; 541 | ], 542 | 543 | [ 544 | a lv2:ControlPort, lv2:InputPort; 545 | lv2:index 49; 546 | lv2:symbol "lfoampdepth__op__1_"; 547 | lv2:name "LFOAmpDepth (Op #1)"; 548 | lv2:minimum 0.000000; 549 | lv2:maximum 1.000000; 550 | lv2:default 0.000000; 551 | ], 552 | 553 | [ 554 | a lv2:ControlPort, lv2:InputPort; 555 | lv2:index 50; 556 | lv2:symbol "lfofreqmoddepth__op__1_"; 557 | lv2:name "LFOFreqModDepth (Op #1)"; 558 | lv2:minimum 0.000000; 559 | lv2:maximum 0.025000; 560 | lv2:default 0.000000; 561 | ], 562 | 563 | [ 564 | a lv2:ControlPort, lv2:InputPort; 565 | lv2:index 51; 566 | lv2:symbol "enable__op__1_"; 567 | lv2:name "Enable (Op #1)"; 568 | lv2:minimum 0; 569 | lv2:maximum 1; 570 | lv2:default 1; 571 | lv2:portProperty lv2:toggled; 572 | ], 573 | 574 | [ 575 | a lv2:ControlPort, lv2:InputPort; 576 | lv2:index 52; 577 | lv2:symbol "carriers__op__1_"; 578 | lv2:name "Carriers (Op #1)"; 579 | lv2:minimum 0.000000; 580 | lv2:maximum 1.000000; 581 | lv2:default 0.000000; 582 | ], 583 | 584 | [ 585 | a lv2:ControlPort, lv2:InputPort; 586 | lv2:index 53; 587 | lv2:symbol "mod0tooperator__op__1_"; 588 | lv2:name "Mod0ToOperator (Op #1)"; 589 | lv2:minimum 0.000000; 590 | lv2:maximum 1.000000; 591 | lv2:default 0.000000; 592 | ], 593 | 594 | [ 595 | a lv2:ControlPort, lv2:InputPort; 596 | lv2:index 54; 597 | lv2:symbol "mod1tooperator__op__1_"; 598 | lv2:name "Mod1ToOperator (Op #1)"; 599 | lv2:minimum 0.000000; 600 | lv2:maximum 1.000000; 601 | lv2:default 0.000000; 602 | ], 603 | 604 | [ 605 | a lv2:ControlPort, lv2:InputPort; 606 | lv2:index 55; 607 | lv2:symbol "mod2tooperator__op__1_"; 608 | lv2:name "Mod2ToOperator (Op #1)"; 609 | lv2:minimum 0.000000; 610 | lv2:maximum 1.000000; 611 | lv2:default 0.000000; 612 | ], 613 | 614 | [ 615 | a lv2:ControlPort, lv2:InputPort; 616 | lv2:index 56; 617 | lv2:symbol "mod3tooperator__op__1_"; 618 | lv2:name "Mod3ToOperator (Op #1)"; 619 | lv2:minimum 0.000000; 620 | lv2:maximum 1.000000; 621 | lv2:default 0.000000; 622 | ], 623 | 624 | [ 625 | a lv2:ControlPort, lv2:InputPort; 626 | lv2:index 57; 627 | lv2:symbol "mod4tooperator__op__1_"; 628 | lv2:name "Mod4ToOperator (Op #1)"; 629 | lv2:minimum 0.000000; 630 | lv2:maximum 1.000000; 631 | lv2:default 0.000000; 632 | ], 633 | 634 | [ 635 | a lv2:ControlPort, lv2:InputPort; 636 | lv2:index 58; 637 | lv2:symbol "mod5tooperator__op__1_"; 638 | lv2:name "Mod5ToOperator (Op #1)"; 639 | lv2:minimum 0.000000; 640 | lv2:maximum 1.000000; 641 | lv2:default 0.000000; 642 | ], 643 | 644 | [ 645 | a lv2:ControlPort, lv2:InputPort; 646 | lv2:index 59; 647 | lv2:symbol "mod6tooperator__op__1_"; 648 | lv2:name "Mod6ToOperator (Op #1)"; 649 | lv2:minimum 0.000000; 650 | lv2:maximum 1.000000; 651 | lv2:default 0.000000; 652 | ], 653 | 654 | [ 655 | a lv2:ControlPort, lv2:InputPort; 656 | lv2:index 60; 657 | lv2:symbol "mod7tooperator__op__1_"; 658 | lv2:name "Mod7ToOperator (Op #1)"; 659 | lv2:minimum 0.000000; 660 | lv2:maximum 1.000000; 661 | lv2:default 0.000000; 662 | ], 663 | 664 | [ 665 | a lv2:ControlPort, lv2:InputPort; 666 | lv2:index 61; 667 | lv2:symbol "amp__op__2_"; 668 | lv2:name "Amp (Op #2)"; 669 | lv2:minimum 0.005000; 670 | lv2:maximum 16.000000; 671 | lv2:default 1.000000; 672 | lv2:portProperty epp:logarithmic; 673 | ], 674 | 675 | [ 676 | a lv2:ControlPort, lv2:InputPort; 677 | lv2:index 62; 678 | lv2:symbol "pan__op__2_"; 679 | lv2:name "Pan (Op #2)"; 680 | lv2:minimum -1.000000; 681 | lv2:maximum 1.000000; 682 | lv2:default 0.000000; 683 | ], 684 | 685 | [ 686 | a lv2:ControlPort, lv2:InputPort; 687 | lv2:index 63; 688 | lv2:symbol "freqmod__op__2_"; 689 | lv2:name "FreqMod (Op #2)"; 690 | lv2:minimum 0.000000; 691 | lv2:maximum 16.000000; 692 | lv2:default 1.000000; 693 | ], 694 | 695 | [ 696 | a lv2:ControlPort, lv2:InputPort; 697 | lv2:index 64; 698 | lv2:symbol "freqoffset__op__2_"; 699 | lv2:name "FreqOffset (Op #2)"; 700 | lv2:minimum -128.000000; 701 | lv2:maximum 128.000000; 702 | lv2:default 0.000000; 703 | ], 704 | 705 | [ 706 | a lv2:ControlPort, lv2:InputPort; 707 | lv2:index 65; 708 | lv2:symbol "envelopetarget0__op__2_"; 709 | lv2:name "EnvelopeTarget0 (Op #2)"; 710 | lv2:minimum 0.000000; 711 | lv2:maximum 1.000000; 712 | lv2:default 1.000000; 713 | ], 714 | 715 | [ 716 | a lv2:ControlPort, lv2:InputPort; 717 | lv2:index 66; 718 | lv2:symbol "envelopetarget1__op__2_"; 719 | lv2:name "EnvelopeTarget1 (Op #2)"; 720 | lv2:minimum 0.000000; 721 | lv2:maximum 1.000000; 722 | lv2:default 0.500000; 723 | ], 724 | 725 | [ 726 | a lv2:ControlPort, lv2:InputPort; 727 | lv2:index 67; 728 | lv2:symbol "envelopetarget2__op__2_"; 729 | lv2:name "EnvelopeTarget2 (Op #2)"; 730 | lv2:minimum 0.000000; 731 | lv2:maximum 1.000000; 732 | lv2:default 0.250000; 733 | ], 734 | 735 | [ 736 | a lv2:ControlPort, lv2:InputPort; 737 | lv2:index 68; 738 | lv2:symbol "envelopedelay0__op__2_"; 739 | lv2:name "EnvelopeDelay0 (Op #2)"; 740 | lv2:minimum 0.005000; 741 | lv2:maximum 8.000000; 742 | lv2:default 0.050000; 743 | lv2:portProperty epp:logarithmic; 744 | ], 745 | 746 | [ 747 | a lv2:ControlPort, lv2:InputPort; 748 | lv2:index 69; 749 | lv2:symbol "envelopedelay1__op__2_"; 750 | lv2:name "EnvelopeDelay1 (Op #2)"; 751 | lv2:minimum 0.005000; 752 | lv2:maximum 8.000000; 753 | lv2:default 0.050000; 754 | lv2:portProperty epp:logarithmic; 755 | ], 756 | 757 | [ 758 | a lv2:ControlPort, lv2:InputPort; 759 | lv2:index 70; 760 | lv2:symbol "envelopedelay2__op__2_"; 761 | lv2:name "EnvelopeDelay2 (Op #2)"; 762 | lv2:minimum 0.005000; 763 | lv2:maximum 8.000000; 764 | lv2:default 0.250000; 765 | lv2:portProperty epp:logarithmic; 766 | ], 767 | 768 | [ 769 | a lv2:ControlPort, lv2:InputPort; 770 | lv2:index 71; 771 | lv2:symbol "envelopereleasetime__op__2_"; 772 | lv2:name "EnvelopeReleaseTime (Op #2)"; 773 | lv2:minimum 0.005000; 774 | lv2:maximum 8.000000; 775 | lv2:default 0.500000; 776 | lv2:portProperty epp:logarithmic; 777 | ], 778 | 779 | [ 780 | a lv2:ControlPort, lv2:InputPort; 781 | lv2:index 72; 782 | lv2:symbol "keyboardscalingmidpoint__op__2_"; 783 | lv2:name "KeyboardScalingMidPoint (Op #2)"; 784 | lv2:minimum 50.000000; 785 | lv2:maximum 5000.000000; 786 | lv2:default 440.000000; 787 | lv2:portProperty epp:logarithmic; 788 | ], 789 | 790 | [ 791 | a lv2:ControlPort, lv2:InputPort; 792 | lv2:index 73; 793 | lv2:symbol "keyboardscalinglowfactor__op__2_"; 794 | lv2:name "KeyboardScalingLowFactor (Op #2)"; 795 | lv2:minimum -2.000000; 796 | lv2:maximum 2.000000; 797 | lv2:default 0.000000; 798 | ], 799 | 800 | [ 801 | a lv2:ControlPort, lv2:InputPort; 802 | lv2:index 74; 803 | lv2:symbol "keyboardscalinghighfactor__op__2_"; 804 | lv2:name "KeyboardScalingHighFactor (Op #2)"; 805 | lv2:minimum -2.000000; 806 | lv2:maximum 2.000000; 807 | lv2:default 0.000000; 808 | ], 809 | 810 | [ 811 | a lv2:ControlPort, lv2:InputPort; 812 | lv2:index 75; 813 | lv2:symbol "velocitysensitivity__op__2_"; 814 | lv2:name "VelocitySensitivity (Op #2)"; 815 | lv2:minimum 0.000000; 816 | lv2:maximum 1.000000; 817 | lv2:default 1.000000; 818 | ], 819 | 820 | [ 821 | a lv2:ControlPort, lv2:InputPort; 822 | lv2:index 76; 823 | lv2:symbol "modsensitivity__op__2_"; 824 | lv2:name "ModSensitivity (Op #2)"; 825 | lv2:minimum 0.000000; 826 | lv2:maximum 1.000000; 827 | lv2:default 0.000000; 828 | ], 829 | 830 | [ 831 | a lv2:ControlPort, lv2:InputPort; 832 | lv2:index 77; 833 | lv2:symbol "lfoampdepth__op__2_"; 834 | lv2:name "LFOAmpDepth (Op #2)"; 835 | lv2:minimum 0.000000; 836 | lv2:maximum 1.000000; 837 | lv2:default 0.000000; 838 | ], 839 | 840 | [ 841 | a lv2:ControlPort, lv2:InputPort; 842 | lv2:index 78; 843 | lv2:symbol "lfofreqmoddepth__op__2_"; 844 | lv2:name "LFOFreqModDepth (Op #2)"; 845 | lv2:minimum 0.000000; 846 | lv2:maximum 0.025000; 847 | lv2:default 0.000000; 848 | ], 849 | 850 | [ 851 | a lv2:ControlPort, lv2:InputPort; 852 | lv2:index 79; 853 | lv2:symbol "enable__op__2_"; 854 | lv2:name "Enable (Op #2)"; 855 | lv2:minimum 0; 856 | lv2:maximum 1; 857 | lv2:default 1; 858 | lv2:portProperty lv2:toggled; 859 | ], 860 | 861 | [ 862 | a lv2:ControlPort, lv2:InputPort; 863 | lv2:index 80; 864 | lv2:symbol "carriers__op__2_"; 865 | lv2:name "Carriers (Op #2)"; 866 | lv2:minimum 0.000000; 867 | lv2:maximum 1.000000; 868 | lv2:default 0.000000; 869 | ], 870 | 871 | [ 872 | a lv2:ControlPort, lv2:InputPort; 873 | lv2:index 81; 874 | lv2:symbol "mod0tooperator__op__2_"; 875 | lv2:name "Mod0ToOperator (Op #2)"; 876 | lv2:minimum 0.000000; 877 | lv2:maximum 1.000000; 878 | lv2:default 0.000000; 879 | ], 880 | 881 | [ 882 | a lv2:ControlPort, lv2:InputPort; 883 | lv2:index 82; 884 | lv2:symbol "mod1tooperator__op__2_"; 885 | lv2:name "Mod1ToOperator (Op #2)"; 886 | lv2:minimum 0.000000; 887 | lv2:maximum 1.000000; 888 | lv2:default 0.000000; 889 | ], 890 | 891 | [ 892 | a lv2:ControlPort, lv2:InputPort; 893 | lv2:index 83; 894 | lv2:symbol "mod2tooperator__op__2_"; 895 | lv2:name "Mod2ToOperator (Op #2)"; 896 | lv2:minimum 0.000000; 897 | lv2:maximum 1.000000; 898 | lv2:default 0.000000; 899 | ], 900 | 901 | [ 902 | a lv2:ControlPort, lv2:InputPort; 903 | lv2:index 84; 904 | lv2:symbol "mod3tooperator__op__2_"; 905 | lv2:name "Mod3ToOperator (Op #2)"; 906 | lv2:minimum 0.000000; 907 | lv2:maximum 1.000000; 908 | lv2:default 0.000000; 909 | ], 910 | 911 | [ 912 | a lv2:ControlPort, lv2:InputPort; 913 | lv2:index 85; 914 | lv2:symbol "mod4tooperator__op__2_"; 915 | lv2:name "Mod4ToOperator (Op #2)"; 916 | lv2:minimum 0.000000; 917 | lv2:maximum 1.000000; 918 | lv2:default 0.000000; 919 | ], 920 | 921 | [ 922 | a lv2:ControlPort, lv2:InputPort; 923 | lv2:index 86; 924 | lv2:symbol "mod5tooperator__op__2_"; 925 | lv2:name "Mod5ToOperator (Op #2)"; 926 | lv2:minimum 0.000000; 927 | lv2:maximum 1.000000; 928 | lv2:default 0.000000; 929 | ], 930 | 931 | [ 932 | a lv2:ControlPort, lv2:InputPort; 933 | lv2:index 87; 934 | lv2:symbol "mod6tooperator__op__2_"; 935 | lv2:name "Mod6ToOperator (Op #2)"; 936 | lv2:minimum 0.000000; 937 | lv2:maximum 1.000000; 938 | lv2:default 0.000000; 939 | ], 940 | 941 | [ 942 | a lv2:ControlPort, lv2:InputPort; 943 | lv2:index 88; 944 | lv2:symbol "mod7tooperator__op__2_"; 945 | lv2:name "Mod7ToOperator (Op #2)"; 946 | lv2:minimum 0.000000; 947 | lv2:maximum 1.000000; 948 | lv2:default 0.000000; 949 | ], 950 | 951 | [ 952 | a lv2:ControlPort, lv2:InputPort; 953 | lv2:index 89; 954 | lv2:symbol "amp__op__3_"; 955 | lv2:name "Amp (Op #3)"; 956 | lv2:minimum 0.005000; 957 | lv2:maximum 16.000000; 958 | lv2:default 1.000000; 959 | lv2:portProperty epp:logarithmic; 960 | ], 961 | 962 | [ 963 | a lv2:ControlPort, lv2:InputPort; 964 | lv2:index 90; 965 | lv2:symbol "pan__op__3_"; 966 | lv2:name "Pan (Op #3)"; 967 | lv2:minimum -1.000000; 968 | lv2:maximum 1.000000; 969 | lv2:default 0.000000; 970 | ], 971 | 972 | [ 973 | a lv2:ControlPort, lv2:InputPort; 974 | lv2:index 91; 975 | lv2:symbol "freqmod__op__3_"; 976 | lv2:name "FreqMod (Op #3)"; 977 | lv2:minimum 0.000000; 978 | lv2:maximum 16.000000; 979 | lv2:default 1.000000; 980 | ], 981 | 982 | [ 983 | a lv2:ControlPort, lv2:InputPort; 984 | lv2:index 92; 985 | lv2:symbol "freqoffset__op__3_"; 986 | lv2:name "FreqOffset (Op #3)"; 987 | lv2:minimum -128.000000; 988 | lv2:maximum 128.000000; 989 | lv2:default 0.000000; 990 | ], 991 | 992 | [ 993 | a lv2:ControlPort, lv2:InputPort; 994 | lv2:index 93; 995 | lv2:symbol "envelopetarget0__op__3_"; 996 | lv2:name "EnvelopeTarget0 (Op #3)"; 997 | lv2:minimum 0.000000; 998 | lv2:maximum 1.000000; 999 | lv2:default 1.000000; 1000 | ], 1001 | 1002 | [ 1003 | a lv2:ControlPort, lv2:InputPort; 1004 | lv2:index 94; 1005 | lv2:symbol "envelopetarget1__op__3_"; 1006 | lv2:name "EnvelopeTarget1 (Op #3)"; 1007 | lv2:minimum 0.000000; 1008 | lv2:maximum 1.000000; 1009 | lv2:default 0.500000; 1010 | ], 1011 | 1012 | [ 1013 | a lv2:ControlPort, lv2:InputPort; 1014 | lv2:index 95; 1015 | lv2:symbol "envelopetarget2__op__3_"; 1016 | lv2:name "EnvelopeTarget2 (Op #3)"; 1017 | lv2:minimum 0.000000; 1018 | lv2:maximum 1.000000; 1019 | lv2:default 0.250000; 1020 | ], 1021 | 1022 | [ 1023 | a lv2:ControlPort, lv2:InputPort; 1024 | lv2:index 96; 1025 | lv2:symbol "envelopedelay0__op__3_"; 1026 | lv2:name "EnvelopeDelay0 (Op #3)"; 1027 | lv2:minimum 0.005000; 1028 | lv2:maximum 8.000000; 1029 | lv2:default 0.050000; 1030 | lv2:portProperty epp:logarithmic; 1031 | ], 1032 | 1033 | [ 1034 | a lv2:ControlPort, lv2:InputPort; 1035 | lv2:index 97; 1036 | lv2:symbol "envelopedelay1__op__3_"; 1037 | lv2:name "EnvelopeDelay1 (Op #3)"; 1038 | lv2:minimum 0.005000; 1039 | lv2:maximum 8.000000; 1040 | lv2:default 0.050000; 1041 | lv2:portProperty epp:logarithmic; 1042 | ], 1043 | 1044 | [ 1045 | a lv2:ControlPort, lv2:InputPort; 1046 | lv2:index 98; 1047 | lv2:symbol "envelopedelay2__op__3_"; 1048 | lv2:name "EnvelopeDelay2 (Op #3)"; 1049 | lv2:minimum 0.005000; 1050 | lv2:maximum 8.000000; 1051 | lv2:default 0.250000; 1052 | lv2:portProperty epp:logarithmic; 1053 | ], 1054 | 1055 | [ 1056 | a lv2:ControlPort, lv2:InputPort; 1057 | lv2:index 99; 1058 | lv2:symbol "envelopereleasetime__op__3_"; 1059 | lv2:name "EnvelopeReleaseTime (Op #3)"; 1060 | lv2:minimum 0.005000; 1061 | lv2:maximum 8.000000; 1062 | lv2:default 0.500000; 1063 | lv2:portProperty epp:logarithmic; 1064 | ], 1065 | 1066 | [ 1067 | a lv2:ControlPort, lv2:InputPort; 1068 | lv2:index 100; 1069 | lv2:symbol "keyboardscalingmidpoint__op__3_"; 1070 | lv2:name "KeyboardScalingMidPoint (Op #3)"; 1071 | lv2:minimum 50.000000; 1072 | lv2:maximum 5000.000000; 1073 | lv2:default 440.000000; 1074 | lv2:portProperty epp:logarithmic; 1075 | ], 1076 | 1077 | [ 1078 | a lv2:ControlPort, lv2:InputPort; 1079 | lv2:index 101; 1080 | lv2:symbol "keyboardscalinglowfactor__op__3_"; 1081 | lv2:name "KeyboardScalingLowFactor (Op #3)"; 1082 | lv2:minimum -2.000000; 1083 | lv2:maximum 2.000000; 1084 | lv2:default 0.000000; 1085 | ], 1086 | 1087 | [ 1088 | a lv2:ControlPort, lv2:InputPort; 1089 | lv2:index 102; 1090 | lv2:symbol "keyboardscalinghighfactor__op__3_"; 1091 | lv2:name "KeyboardScalingHighFactor (Op #3)"; 1092 | lv2:minimum -2.000000; 1093 | lv2:maximum 2.000000; 1094 | lv2:default 0.000000; 1095 | ], 1096 | 1097 | [ 1098 | a lv2:ControlPort, lv2:InputPort; 1099 | lv2:index 103; 1100 | lv2:symbol "velocitysensitivity__op__3_"; 1101 | lv2:name "VelocitySensitivity (Op #3)"; 1102 | lv2:minimum 0.000000; 1103 | lv2:maximum 1.000000; 1104 | lv2:default 1.000000; 1105 | ], 1106 | 1107 | [ 1108 | a lv2:ControlPort, lv2:InputPort; 1109 | lv2:index 104; 1110 | lv2:symbol "modsensitivity__op__3_"; 1111 | lv2:name "ModSensitivity (Op #3)"; 1112 | lv2:minimum 0.000000; 1113 | lv2:maximum 1.000000; 1114 | lv2:default 0.000000; 1115 | ], 1116 | 1117 | [ 1118 | a lv2:ControlPort, lv2:InputPort; 1119 | lv2:index 105; 1120 | lv2:symbol "lfoampdepth__op__3_"; 1121 | lv2:name "LFOAmpDepth (Op #3)"; 1122 | lv2:minimum 0.000000; 1123 | lv2:maximum 1.000000; 1124 | lv2:default 0.000000; 1125 | ], 1126 | 1127 | [ 1128 | a lv2:ControlPort, lv2:InputPort; 1129 | lv2:index 106; 1130 | lv2:symbol "lfofreqmoddepth__op__3_"; 1131 | lv2:name "LFOFreqModDepth (Op #3)"; 1132 | lv2:minimum 0.000000; 1133 | lv2:maximum 0.025000; 1134 | lv2:default 0.000000; 1135 | ], 1136 | 1137 | [ 1138 | a lv2:ControlPort, lv2:InputPort; 1139 | lv2:index 107; 1140 | lv2:symbol "enable__op__3_"; 1141 | lv2:name "Enable (Op #3)"; 1142 | lv2:minimum 0; 1143 | lv2:maximum 1; 1144 | lv2:default 1; 1145 | lv2:portProperty lv2:toggled; 1146 | ], 1147 | 1148 | [ 1149 | a lv2:ControlPort, lv2:InputPort; 1150 | lv2:index 108; 1151 | lv2:symbol "carriers__op__3_"; 1152 | lv2:name "Carriers (Op #3)"; 1153 | lv2:minimum 0.000000; 1154 | lv2:maximum 1.000000; 1155 | lv2:default 0.000000; 1156 | ], 1157 | 1158 | [ 1159 | a lv2:ControlPort, lv2:InputPort; 1160 | lv2:index 109; 1161 | lv2:symbol "mod0tooperator__op__3_"; 1162 | lv2:name "Mod0ToOperator (Op #3)"; 1163 | lv2:minimum 0.000000; 1164 | lv2:maximum 1.000000; 1165 | lv2:default 0.000000; 1166 | ], 1167 | 1168 | [ 1169 | a lv2:ControlPort, lv2:InputPort; 1170 | lv2:index 110; 1171 | lv2:symbol "mod1tooperator__op__3_"; 1172 | lv2:name "Mod1ToOperator (Op #3)"; 1173 | lv2:minimum 0.000000; 1174 | lv2:maximum 1.000000; 1175 | lv2:default 0.000000; 1176 | ], 1177 | 1178 | [ 1179 | a lv2:ControlPort, lv2:InputPort; 1180 | lv2:index 111; 1181 | lv2:symbol "mod2tooperator__op__3_"; 1182 | lv2:name "Mod2ToOperator (Op #3)"; 1183 | lv2:minimum 0.000000; 1184 | lv2:maximum 1.000000; 1185 | lv2:default 0.000000; 1186 | ], 1187 | 1188 | [ 1189 | a lv2:ControlPort, lv2:InputPort; 1190 | lv2:index 112; 1191 | lv2:symbol "mod3tooperator__op__3_"; 1192 | lv2:name "Mod3ToOperator (Op #3)"; 1193 | lv2:minimum 0.000000; 1194 | lv2:maximum 1.000000; 1195 | lv2:default 0.000000; 1196 | ], 1197 | 1198 | [ 1199 | a lv2:ControlPort, lv2:InputPort; 1200 | lv2:index 113; 1201 | lv2:symbol "mod4tooperator__op__3_"; 1202 | lv2:name "Mod4ToOperator (Op #3)"; 1203 | lv2:minimum 0.000000; 1204 | lv2:maximum 1.000000; 1205 | lv2:default 0.000000; 1206 | ], 1207 | 1208 | [ 1209 | a lv2:ControlPort, lv2:InputPort; 1210 | lv2:index 114; 1211 | lv2:symbol "mod5tooperator__op__3_"; 1212 | lv2:name "Mod5ToOperator (Op #3)"; 1213 | lv2:minimum 0.000000; 1214 | lv2:maximum 1.000000; 1215 | lv2:default 0.000000; 1216 | ], 1217 | 1218 | [ 1219 | a lv2:ControlPort, lv2:InputPort; 1220 | lv2:index 115; 1221 | lv2:symbol "mod6tooperator__op__3_"; 1222 | lv2:name "Mod6ToOperator (Op #3)"; 1223 | lv2:minimum 0.000000; 1224 | lv2:maximum 1.000000; 1225 | lv2:default 0.000000; 1226 | ], 1227 | 1228 | [ 1229 | a lv2:ControlPort, lv2:InputPort; 1230 | lv2:index 116; 1231 | lv2:symbol "mod7tooperator__op__3_"; 1232 | lv2:name "Mod7ToOperator (Op #3)"; 1233 | lv2:minimum 0.000000; 1234 | lv2:maximum 1.000000; 1235 | lv2:default 0.000000; 1236 | ], 1237 | 1238 | [ 1239 | a lv2:ControlPort, lv2:InputPort; 1240 | lv2:index 117; 1241 | lv2:symbol "amp__op__4_"; 1242 | lv2:name "Amp (Op #4)"; 1243 | lv2:minimum 0.005000; 1244 | lv2:maximum 16.000000; 1245 | lv2:default 1.000000; 1246 | lv2:portProperty epp:logarithmic; 1247 | ], 1248 | 1249 | [ 1250 | a lv2:ControlPort, lv2:InputPort; 1251 | lv2:index 118; 1252 | lv2:symbol "pan__op__4_"; 1253 | lv2:name "Pan (Op #4)"; 1254 | lv2:minimum -1.000000; 1255 | lv2:maximum 1.000000; 1256 | lv2:default 0.000000; 1257 | ], 1258 | 1259 | [ 1260 | a lv2:ControlPort, lv2:InputPort; 1261 | lv2:index 119; 1262 | lv2:symbol "freqmod__op__4_"; 1263 | lv2:name "FreqMod (Op #4)"; 1264 | lv2:minimum 0.000000; 1265 | lv2:maximum 16.000000; 1266 | lv2:default 1.000000; 1267 | ], 1268 | 1269 | [ 1270 | a lv2:ControlPort, lv2:InputPort; 1271 | lv2:index 120; 1272 | lv2:symbol "freqoffset__op__4_"; 1273 | lv2:name "FreqOffset (Op #4)"; 1274 | lv2:minimum -128.000000; 1275 | lv2:maximum 128.000000; 1276 | lv2:default 0.000000; 1277 | ], 1278 | 1279 | [ 1280 | a lv2:ControlPort, lv2:InputPort; 1281 | lv2:index 121; 1282 | lv2:symbol "envelopetarget0__op__4_"; 1283 | lv2:name "EnvelopeTarget0 (Op #4)"; 1284 | lv2:minimum 0.000000; 1285 | lv2:maximum 1.000000; 1286 | lv2:default 1.000000; 1287 | ], 1288 | 1289 | [ 1290 | a lv2:ControlPort, lv2:InputPort; 1291 | lv2:index 122; 1292 | lv2:symbol "envelopetarget1__op__4_"; 1293 | lv2:name "EnvelopeTarget1 (Op #4)"; 1294 | lv2:minimum 0.000000; 1295 | lv2:maximum 1.000000; 1296 | lv2:default 0.500000; 1297 | ], 1298 | 1299 | [ 1300 | a lv2:ControlPort, lv2:InputPort; 1301 | lv2:index 123; 1302 | lv2:symbol "envelopetarget2__op__4_"; 1303 | lv2:name "EnvelopeTarget2 (Op #4)"; 1304 | lv2:minimum 0.000000; 1305 | lv2:maximum 1.000000; 1306 | lv2:default 0.250000; 1307 | ], 1308 | 1309 | [ 1310 | a lv2:ControlPort, lv2:InputPort; 1311 | lv2:index 124; 1312 | lv2:symbol "envelopedelay0__op__4_"; 1313 | lv2:name "EnvelopeDelay0 (Op #4)"; 1314 | lv2:minimum 0.005000; 1315 | lv2:maximum 8.000000; 1316 | lv2:default 0.050000; 1317 | lv2:portProperty epp:logarithmic; 1318 | ], 1319 | 1320 | [ 1321 | a lv2:ControlPort, lv2:InputPort; 1322 | lv2:index 125; 1323 | lv2:symbol "envelopedelay1__op__4_"; 1324 | lv2:name "EnvelopeDelay1 (Op #4)"; 1325 | lv2:minimum 0.005000; 1326 | lv2:maximum 8.000000; 1327 | lv2:default 0.050000; 1328 | lv2:portProperty epp:logarithmic; 1329 | ], 1330 | 1331 | [ 1332 | a lv2:ControlPort, lv2:InputPort; 1333 | lv2:index 126; 1334 | lv2:symbol "envelopedelay2__op__4_"; 1335 | lv2:name "EnvelopeDelay2 (Op #4)"; 1336 | lv2:minimum 0.005000; 1337 | lv2:maximum 8.000000; 1338 | lv2:default 0.250000; 1339 | lv2:portProperty epp:logarithmic; 1340 | ], 1341 | 1342 | [ 1343 | a lv2:ControlPort, lv2:InputPort; 1344 | lv2:index 127; 1345 | lv2:symbol "envelopereleasetime__op__4_"; 1346 | lv2:name "EnvelopeReleaseTime (Op #4)"; 1347 | lv2:minimum 0.005000; 1348 | lv2:maximum 8.000000; 1349 | lv2:default 0.500000; 1350 | lv2:portProperty epp:logarithmic; 1351 | ], 1352 | 1353 | [ 1354 | a lv2:ControlPort, lv2:InputPort; 1355 | lv2:index 128; 1356 | lv2:symbol "keyboardscalingmidpoint__op__4_"; 1357 | lv2:name "KeyboardScalingMidPoint (Op #4)"; 1358 | lv2:minimum 50.000000; 1359 | lv2:maximum 5000.000000; 1360 | lv2:default 440.000000; 1361 | lv2:portProperty epp:logarithmic; 1362 | ], 1363 | 1364 | [ 1365 | a lv2:ControlPort, lv2:InputPort; 1366 | lv2:index 129; 1367 | lv2:symbol "keyboardscalinglowfactor__op__4_"; 1368 | lv2:name "KeyboardScalingLowFactor (Op #4)"; 1369 | lv2:minimum -2.000000; 1370 | lv2:maximum 2.000000; 1371 | lv2:default 0.000000; 1372 | ], 1373 | 1374 | [ 1375 | a lv2:ControlPort, lv2:InputPort; 1376 | lv2:index 130; 1377 | lv2:symbol "keyboardscalinghighfactor__op__4_"; 1378 | lv2:name "KeyboardScalingHighFactor (Op #4)"; 1379 | lv2:minimum -2.000000; 1380 | lv2:maximum 2.000000; 1381 | lv2:default 0.000000; 1382 | ], 1383 | 1384 | [ 1385 | a lv2:ControlPort, lv2:InputPort; 1386 | lv2:index 131; 1387 | lv2:symbol "velocitysensitivity__op__4_"; 1388 | lv2:name "VelocitySensitivity (Op #4)"; 1389 | lv2:minimum 0.000000; 1390 | lv2:maximum 1.000000; 1391 | lv2:default 1.000000; 1392 | ], 1393 | 1394 | [ 1395 | a lv2:ControlPort, lv2:InputPort; 1396 | lv2:index 132; 1397 | lv2:symbol "modsensitivity__op__4_"; 1398 | lv2:name "ModSensitivity (Op #4)"; 1399 | lv2:minimum 0.000000; 1400 | lv2:maximum 1.000000; 1401 | lv2:default 0.000000; 1402 | ], 1403 | 1404 | [ 1405 | a lv2:ControlPort, lv2:InputPort; 1406 | lv2:index 133; 1407 | lv2:symbol "lfoampdepth__op__4_"; 1408 | lv2:name "LFOAmpDepth (Op #4)"; 1409 | lv2:minimum 0.000000; 1410 | lv2:maximum 1.000000; 1411 | lv2:default 0.000000; 1412 | ], 1413 | 1414 | [ 1415 | a lv2:ControlPort, lv2:InputPort; 1416 | lv2:index 134; 1417 | lv2:symbol "lfofreqmoddepth__op__4_"; 1418 | lv2:name "LFOFreqModDepth (Op #4)"; 1419 | lv2:minimum 0.000000; 1420 | lv2:maximum 0.025000; 1421 | lv2:default 0.000000; 1422 | ], 1423 | 1424 | [ 1425 | a lv2:ControlPort, lv2:InputPort; 1426 | lv2:index 135; 1427 | lv2:symbol "enable__op__4_"; 1428 | lv2:name "Enable (Op #4)"; 1429 | lv2:minimum 0; 1430 | lv2:maximum 1; 1431 | lv2:default 1; 1432 | lv2:portProperty lv2:toggled; 1433 | ], 1434 | 1435 | [ 1436 | a lv2:ControlPort, lv2:InputPort; 1437 | lv2:index 136; 1438 | lv2:symbol "carriers__op__4_"; 1439 | lv2:name "Carriers (Op #4)"; 1440 | lv2:minimum 0.000000; 1441 | lv2:maximum 1.000000; 1442 | lv2:default 0.000000; 1443 | ], 1444 | 1445 | [ 1446 | a lv2:ControlPort, lv2:InputPort; 1447 | lv2:index 137; 1448 | lv2:symbol "mod0tooperator__op__4_"; 1449 | lv2:name "Mod0ToOperator (Op #4)"; 1450 | lv2:minimum 0.000000; 1451 | lv2:maximum 1.000000; 1452 | lv2:default 0.000000; 1453 | ], 1454 | 1455 | [ 1456 | a lv2:ControlPort, lv2:InputPort; 1457 | lv2:index 138; 1458 | lv2:symbol "mod1tooperator__op__4_"; 1459 | lv2:name "Mod1ToOperator (Op #4)"; 1460 | lv2:minimum 0.000000; 1461 | lv2:maximum 1.000000; 1462 | lv2:default 0.000000; 1463 | ], 1464 | 1465 | [ 1466 | a lv2:ControlPort, lv2:InputPort; 1467 | lv2:index 139; 1468 | lv2:symbol "mod2tooperator__op__4_"; 1469 | lv2:name "Mod2ToOperator (Op #4)"; 1470 | lv2:minimum 0.000000; 1471 | lv2:maximum 1.000000; 1472 | lv2:default 0.000000; 1473 | ], 1474 | 1475 | [ 1476 | a lv2:ControlPort, lv2:InputPort; 1477 | lv2:index 140; 1478 | lv2:symbol "mod3tooperator__op__4_"; 1479 | lv2:name "Mod3ToOperator (Op #4)"; 1480 | lv2:minimum 0.000000; 1481 | lv2:maximum 1.000000; 1482 | lv2:default 0.000000; 1483 | ], 1484 | 1485 | [ 1486 | a lv2:ControlPort, lv2:InputPort; 1487 | lv2:index 141; 1488 | lv2:symbol "mod4tooperator__op__4_"; 1489 | lv2:name "Mod4ToOperator (Op #4)"; 1490 | lv2:minimum 0.000000; 1491 | lv2:maximum 1.000000; 1492 | lv2:default 0.000000; 1493 | ], 1494 | 1495 | [ 1496 | a lv2:ControlPort, lv2:InputPort; 1497 | lv2:index 142; 1498 | lv2:symbol "mod5tooperator__op__4_"; 1499 | lv2:name "Mod5ToOperator (Op #4)"; 1500 | lv2:minimum 0.000000; 1501 | lv2:maximum 1.000000; 1502 | lv2:default 0.000000; 1503 | ], 1504 | 1505 | [ 1506 | a lv2:ControlPort, lv2:InputPort; 1507 | lv2:index 143; 1508 | lv2:symbol "mod6tooperator__op__4_"; 1509 | lv2:name "Mod6ToOperator (Op #4)"; 1510 | lv2:minimum 0.000000; 1511 | lv2:maximum 1.000000; 1512 | lv2:default 0.000000; 1513 | ], 1514 | 1515 | [ 1516 | a lv2:ControlPort, lv2:InputPort; 1517 | lv2:index 144; 1518 | lv2:symbol "mod7tooperator__op__4_"; 1519 | lv2:name "Mod7ToOperator (Op #4)"; 1520 | lv2:minimum 0.000000; 1521 | lv2:maximum 1.000000; 1522 | lv2:default 0.000000; 1523 | ], 1524 | 1525 | [ 1526 | a lv2:ControlPort, lv2:InputPort; 1527 | lv2:index 145; 1528 | lv2:symbol "amp__op__5_"; 1529 | lv2:name "Amp (Op #5)"; 1530 | lv2:minimum 0.005000; 1531 | lv2:maximum 16.000000; 1532 | lv2:default 1.000000; 1533 | lv2:portProperty epp:logarithmic; 1534 | ], 1535 | 1536 | [ 1537 | a lv2:ControlPort, lv2:InputPort; 1538 | lv2:index 146; 1539 | lv2:symbol "pan__op__5_"; 1540 | lv2:name "Pan (Op #5)"; 1541 | lv2:minimum -1.000000; 1542 | lv2:maximum 1.000000; 1543 | lv2:default 0.000000; 1544 | ], 1545 | 1546 | [ 1547 | a lv2:ControlPort, lv2:InputPort; 1548 | lv2:index 147; 1549 | lv2:symbol "freqmod__op__5_"; 1550 | lv2:name "FreqMod (Op #5)"; 1551 | lv2:minimum 0.000000; 1552 | lv2:maximum 16.000000; 1553 | lv2:default 1.000000; 1554 | ], 1555 | 1556 | [ 1557 | a lv2:ControlPort, lv2:InputPort; 1558 | lv2:index 148; 1559 | lv2:symbol "freqoffset__op__5_"; 1560 | lv2:name "FreqOffset (Op #5)"; 1561 | lv2:minimum -128.000000; 1562 | lv2:maximum 128.000000; 1563 | lv2:default 0.000000; 1564 | ], 1565 | 1566 | [ 1567 | a lv2:ControlPort, lv2:InputPort; 1568 | lv2:index 149; 1569 | lv2:symbol "envelopetarget0__op__5_"; 1570 | lv2:name "EnvelopeTarget0 (Op #5)"; 1571 | lv2:minimum 0.000000; 1572 | lv2:maximum 1.000000; 1573 | lv2:default 1.000000; 1574 | ], 1575 | 1576 | [ 1577 | a lv2:ControlPort, lv2:InputPort; 1578 | lv2:index 150; 1579 | lv2:symbol "envelopetarget1__op__5_"; 1580 | lv2:name "EnvelopeTarget1 (Op #5)"; 1581 | lv2:minimum 0.000000; 1582 | lv2:maximum 1.000000; 1583 | lv2:default 0.500000; 1584 | ], 1585 | 1586 | [ 1587 | a lv2:ControlPort, lv2:InputPort; 1588 | lv2:index 151; 1589 | lv2:symbol "envelopetarget2__op__5_"; 1590 | lv2:name "EnvelopeTarget2 (Op #5)"; 1591 | lv2:minimum 0.000000; 1592 | lv2:maximum 1.000000; 1593 | lv2:default 0.250000; 1594 | ], 1595 | 1596 | [ 1597 | a lv2:ControlPort, lv2:InputPort; 1598 | lv2:index 152; 1599 | lv2:symbol "envelopedelay0__op__5_"; 1600 | lv2:name "EnvelopeDelay0 (Op #5)"; 1601 | lv2:minimum 0.005000; 1602 | lv2:maximum 8.000000; 1603 | lv2:default 0.050000; 1604 | lv2:portProperty epp:logarithmic; 1605 | ], 1606 | 1607 | [ 1608 | a lv2:ControlPort, lv2:InputPort; 1609 | lv2:index 153; 1610 | lv2:symbol "envelopedelay1__op__5_"; 1611 | lv2:name "EnvelopeDelay1 (Op #5)"; 1612 | lv2:minimum 0.005000; 1613 | lv2:maximum 8.000000; 1614 | lv2:default 0.050000; 1615 | lv2:portProperty epp:logarithmic; 1616 | ], 1617 | 1618 | [ 1619 | a lv2:ControlPort, lv2:InputPort; 1620 | lv2:index 154; 1621 | lv2:symbol "envelopedelay2__op__5_"; 1622 | lv2:name "EnvelopeDelay2 (Op #5)"; 1623 | lv2:minimum 0.005000; 1624 | lv2:maximum 8.000000; 1625 | lv2:default 0.250000; 1626 | lv2:portProperty epp:logarithmic; 1627 | ], 1628 | 1629 | [ 1630 | a lv2:ControlPort, lv2:InputPort; 1631 | lv2:index 155; 1632 | lv2:symbol "envelopereleasetime__op__5_"; 1633 | lv2:name "EnvelopeReleaseTime (Op #5)"; 1634 | lv2:minimum 0.005000; 1635 | lv2:maximum 8.000000; 1636 | lv2:default 0.500000; 1637 | lv2:portProperty epp:logarithmic; 1638 | ], 1639 | 1640 | [ 1641 | a lv2:ControlPort, lv2:InputPort; 1642 | lv2:index 156; 1643 | lv2:symbol "keyboardscalingmidpoint__op__5_"; 1644 | lv2:name "KeyboardScalingMidPoint (Op #5)"; 1645 | lv2:minimum 50.000000; 1646 | lv2:maximum 5000.000000; 1647 | lv2:default 440.000000; 1648 | lv2:portProperty epp:logarithmic; 1649 | ], 1650 | 1651 | [ 1652 | a lv2:ControlPort, lv2:InputPort; 1653 | lv2:index 157; 1654 | lv2:symbol "keyboardscalinglowfactor__op__5_"; 1655 | lv2:name "KeyboardScalingLowFactor (Op #5)"; 1656 | lv2:minimum -2.000000; 1657 | lv2:maximum 2.000000; 1658 | lv2:default 0.000000; 1659 | ], 1660 | 1661 | [ 1662 | a lv2:ControlPort, lv2:InputPort; 1663 | lv2:index 158; 1664 | lv2:symbol "keyboardscalinghighfactor__op__5_"; 1665 | lv2:name "KeyboardScalingHighFactor (Op #5)"; 1666 | lv2:minimum -2.000000; 1667 | lv2:maximum 2.000000; 1668 | lv2:default 0.000000; 1669 | ], 1670 | 1671 | [ 1672 | a lv2:ControlPort, lv2:InputPort; 1673 | lv2:index 159; 1674 | lv2:symbol "velocitysensitivity__op__5_"; 1675 | lv2:name "VelocitySensitivity (Op #5)"; 1676 | lv2:minimum 0.000000; 1677 | lv2:maximum 1.000000; 1678 | lv2:default 1.000000; 1679 | ], 1680 | 1681 | [ 1682 | a lv2:ControlPort, lv2:InputPort; 1683 | lv2:index 160; 1684 | lv2:symbol "modsensitivity__op__5_"; 1685 | lv2:name "ModSensitivity (Op #5)"; 1686 | lv2:minimum 0.000000; 1687 | lv2:maximum 1.000000; 1688 | lv2:default 0.000000; 1689 | ], 1690 | 1691 | [ 1692 | a lv2:ControlPort, lv2:InputPort; 1693 | lv2:index 161; 1694 | lv2:symbol "lfoampdepth__op__5_"; 1695 | lv2:name "LFOAmpDepth (Op #5)"; 1696 | lv2:minimum 0.000000; 1697 | lv2:maximum 1.000000; 1698 | lv2:default 0.000000; 1699 | ], 1700 | 1701 | [ 1702 | a lv2:ControlPort, lv2:InputPort; 1703 | lv2:index 162; 1704 | lv2:symbol "lfofreqmoddepth__op__5_"; 1705 | lv2:name "LFOFreqModDepth (Op #5)"; 1706 | lv2:minimum 0.000000; 1707 | lv2:maximum 0.025000; 1708 | lv2:default 0.000000; 1709 | ], 1710 | 1711 | [ 1712 | a lv2:ControlPort, lv2:InputPort; 1713 | lv2:index 163; 1714 | lv2:symbol "enable__op__5_"; 1715 | lv2:name "Enable (Op #5)"; 1716 | lv2:minimum 0; 1717 | lv2:maximum 1; 1718 | lv2:default 1; 1719 | lv2:portProperty lv2:toggled; 1720 | ], 1721 | 1722 | [ 1723 | a lv2:ControlPort, lv2:InputPort; 1724 | lv2:index 164; 1725 | lv2:symbol "carriers__op__5_"; 1726 | lv2:name "Carriers (Op #5)"; 1727 | lv2:minimum 0.000000; 1728 | lv2:maximum 1.000000; 1729 | lv2:default 0.000000; 1730 | ], 1731 | 1732 | [ 1733 | a lv2:ControlPort, lv2:InputPort; 1734 | lv2:index 165; 1735 | lv2:symbol "mod0tooperator__op__5_"; 1736 | lv2:name "Mod0ToOperator (Op #5)"; 1737 | lv2:minimum 0.000000; 1738 | lv2:maximum 1.000000; 1739 | lv2:default 0.000000; 1740 | ], 1741 | 1742 | [ 1743 | a lv2:ControlPort, lv2:InputPort; 1744 | lv2:index 166; 1745 | lv2:symbol "mod1tooperator__op__5_"; 1746 | lv2:name "Mod1ToOperator (Op #5)"; 1747 | lv2:minimum 0.000000; 1748 | lv2:maximum 1.000000; 1749 | lv2:default 0.000000; 1750 | ], 1751 | 1752 | [ 1753 | a lv2:ControlPort, lv2:InputPort; 1754 | lv2:index 167; 1755 | lv2:symbol "mod2tooperator__op__5_"; 1756 | lv2:name "Mod2ToOperator (Op #5)"; 1757 | lv2:minimum 0.000000; 1758 | lv2:maximum 1.000000; 1759 | lv2:default 0.000000; 1760 | ], 1761 | 1762 | [ 1763 | a lv2:ControlPort, lv2:InputPort; 1764 | lv2:index 168; 1765 | lv2:symbol "mod3tooperator__op__5_"; 1766 | lv2:name "Mod3ToOperator (Op #5)"; 1767 | lv2:minimum 0.000000; 1768 | lv2:maximum 1.000000; 1769 | lv2:default 0.000000; 1770 | ], 1771 | 1772 | [ 1773 | a lv2:ControlPort, lv2:InputPort; 1774 | lv2:index 169; 1775 | lv2:symbol "mod4tooperator__op__5_"; 1776 | lv2:name "Mod4ToOperator (Op #5)"; 1777 | lv2:minimum 0.000000; 1778 | lv2:maximum 1.000000; 1779 | lv2:default 0.000000; 1780 | ], 1781 | 1782 | [ 1783 | a lv2:ControlPort, lv2:InputPort; 1784 | lv2:index 170; 1785 | lv2:symbol "mod5tooperator__op__5_"; 1786 | lv2:name "Mod5ToOperator (Op #5)"; 1787 | lv2:minimum 0.000000; 1788 | lv2:maximum 1.000000; 1789 | lv2:default 0.000000; 1790 | ], 1791 | 1792 | [ 1793 | a lv2:ControlPort, lv2:InputPort; 1794 | lv2:index 171; 1795 | lv2:symbol "mod6tooperator__op__5_"; 1796 | lv2:name "Mod6ToOperator (Op #5)"; 1797 | lv2:minimum 0.000000; 1798 | lv2:maximum 1.000000; 1799 | lv2:default 0.000000; 1800 | ], 1801 | 1802 | [ 1803 | a lv2:ControlPort, lv2:InputPort; 1804 | lv2:index 172; 1805 | lv2:symbol "mod7tooperator__op__5_"; 1806 | lv2:name "Mod7ToOperator (Op #5)"; 1807 | lv2:minimum 0.000000; 1808 | lv2:maximum 1.000000; 1809 | lv2:default 0.000000; 1810 | ], 1811 | 1812 | [ 1813 | a lv2:ControlPort, lv2:InputPort; 1814 | lv2:index 173; 1815 | lv2:symbol "amp__op__6_"; 1816 | lv2:name "Amp (Op #6)"; 1817 | lv2:minimum 0.005000; 1818 | lv2:maximum 16.000000; 1819 | lv2:default 1.000000; 1820 | lv2:portProperty epp:logarithmic; 1821 | ], 1822 | 1823 | [ 1824 | a lv2:ControlPort, lv2:InputPort; 1825 | lv2:index 174; 1826 | lv2:symbol "pan__op__6_"; 1827 | lv2:name "Pan (Op #6)"; 1828 | lv2:minimum -1.000000; 1829 | lv2:maximum 1.000000; 1830 | lv2:default 0.000000; 1831 | ], 1832 | 1833 | [ 1834 | a lv2:ControlPort, lv2:InputPort; 1835 | lv2:index 175; 1836 | lv2:symbol "freqmod__op__6_"; 1837 | lv2:name "FreqMod (Op #6)"; 1838 | lv2:minimum 0.000000; 1839 | lv2:maximum 16.000000; 1840 | lv2:default 1.000000; 1841 | ], 1842 | 1843 | [ 1844 | a lv2:ControlPort, lv2:InputPort; 1845 | lv2:index 176; 1846 | lv2:symbol "freqoffset__op__6_"; 1847 | lv2:name "FreqOffset (Op #6)"; 1848 | lv2:minimum -128.000000; 1849 | lv2:maximum 128.000000; 1850 | lv2:default 0.000000; 1851 | ], 1852 | 1853 | [ 1854 | a lv2:ControlPort, lv2:InputPort; 1855 | lv2:index 177; 1856 | lv2:symbol "envelopetarget0__op__6_"; 1857 | lv2:name "EnvelopeTarget0 (Op #6)"; 1858 | lv2:minimum 0.000000; 1859 | lv2:maximum 1.000000; 1860 | lv2:default 1.000000; 1861 | ], 1862 | 1863 | [ 1864 | a lv2:ControlPort, lv2:InputPort; 1865 | lv2:index 178; 1866 | lv2:symbol "envelopetarget1__op__6_"; 1867 | lv2:name "EnvelopeTarget1 (Op #6)"; 1868 | lv2:minimum 0.000000; 1869 | lv2:maximum 1.000000; 1870 | lv2:default 0.500000; 1871 | ], 1872 | 1873 | [ 1874 | a lv2:ControlPort, lv2:InputPort; 1875 | lv2:index 179; 1876 | lv2:symbol "envelopetarget2__op__6_"; 1877 | lv2:name "EnvelopeTarget2 (Op #6)"; 1878 | lv2:minimum 0.000000; 1879 | lv2:maximum 1.000000; 1880 | lv2:default 0.250000; 1881 | ], 1882 | 1883 | [ 1884 | a lv2:ControlPort, lv2:InputPort; 1885 | lv2:index 180; 1886 | lv2:symbol "envelopedelay0__op__6_"; 1887 | lv2:name "EnvelopeDelay0 (Op #6)"; 1888 | lv2:minimum 0.005000; 1889 | lv2:maximum 8.000000; 1890 | lv2:default 0.050000; 1891 | lv2:portProperty epp:logarithmic; 1892 | ], 1893 | 1894 | [ 1895 | a lv2:ControlPort, lv2:InputPort; 1896 | lv2:index 181; 1897 | lv2:symbol "envelopedelay1__op__6_"; 1898 | lv2:name "EnvelopeDelay1 (Op #6)"; 1899 | lv2:minimum 0.005000; 1900 | lv2:maximum 8.000000; 1901 | lv2:default 0.050000; 1902 | lv2:portProperty epp:logarithmic; 1903 | ], 1904 | 1905 | [ 1906 | a lv2:ControlPort, lv2:InputPort; 1907 | lv2:index 182; 1908 | lv2:symbol "envelopedelay2__op__6_"; 1909 | lv2:name "EnvelopeDelay2 (Op #6)"; 1910 | lv2:minimum 0.005000; 1911 | lv2:maximum 8.000000; 1912 | lv2:default 0.250000; 1913 | lv2:portProperty epp:logarithmic; 1914 | ], 1915 | 1916 | [ 1917 | a lv2:ControlPort, lv2:InputPort; 1918 | lv2:index 183; 1919 | lv2:symbol "envelopereleasetime__op__6_"; 1920 | lv2:name "EnvelopeReleaseTime (Op #6)"; 1921 | lv2:minimum 0.005000; 1922 | lv2:maximum 8.000000; 1923 | lv2:default 0.500000; 1924 | lv2:portProperty epp:logarithmic; 1925 | ], 1926 | 1927 | [ 1928 | a lv2:ControlPort, lv2:InputPort; 1929 | lv2:index 184; 1930 | lv2:symbol "keyboardscalingmidpoint__op__6_"; 1931 | lv2:name "KeyboardScalingMidPoint (Op #6)"; 1932 | lv2:minimum 50.000000; 1933 | lv2:maximum 5000.000000; 1934 | lv2:default 440.000000; 1935 | lv2:portProperty epp:logarithmic; 1936 | ], 1937 | 1938 | [ 1939 | a lv2:ControlPort, lv2:InputPort; 1940 | lv2:index 185; 1941 | lv2:symbol "keyboardscalinglowfactor__op__6_"; 1942 | lv2:name "KeyboardScalingLowFactor (Op #6)"; 1943 | lv2:minimum -2.000000; 1944 | lv2:maximum 2.000000; 1945 | lv2:default 0.000000; 1946 | ], 1947 | 1948 | [ 1949 | a lv2:ControlPort, lv2:InputPort; 1950 | lv2:index 186; 1951 | lv2:symbol "keyboardscalinghighfactor__op__6_"; 1952 | lv2:name "KeyboardScalingHighFactor (Op #6)"; 1953 | lv2:minimum -2.000000; 1954 | lv2:maximum 2.000000; 1955 | lv2:default 0.000000; 1956 | ], 1957 | 1958 | [ 1959 | a lv2:ControlPort, lv2:InputPort; 1960 | lv2:index 187; 1961 | lv2:symbol "velocitysensitivity__op__6_"; 1962 | lv2:name "VelocitySensitivity (Op #6)"; 1963 | lv2:minimum 0.000000; 1964 | lv2:maximum 1.000000; 1965 | lv2:default 1.000000; 1966 | ], 1967 | 1968 | [ 1969 | a lv2:ControlPort, lv2:InputPort; 1970 | lv2:index 188; 1971 | lv2:symbol "modsensitivity__op__6_"; 1972 | lv2:name "ModSensitivity (Op #6)"; 1973 | lv2:minimum 0.000000; 1974 | lv2:maximum 1.000000; 1975 | lv2:default 0.000000; 1976 | ], 1977 | 1978 | [ 1979 | a lv2:ControlPort, lv2:InputPort; 1980 | lv2:index 189; 1981 | lv2:symbol "lfoampdepth__op__6_"; 1982 | lv2:name "LFOAmpDepth (Op #6)"; 1983 | lv2:minimum 0.000000; 1984 | lv2:maximum 1.000000; 1985 | lv2:default 0.000000; 1986 | ], 1987 | 1988 | [ 1989 | a lv2:ControlPort, lv2:InputPort; 1990 | lv2:index 190; 1991 | lv2:symbol "lfofreqmoddepth__op__6_"; 1992 | lv2:name "LFOFreqModDepth (Op #6)"; 1993 | lv2:minimum 0.000000; 1994 | lv2:maximum 0.025000; 1995 | lv2:default 0.000000; 1996 | ], 1997 | 1998 | [ 1999 | a lv2:ControlPort, lv2:InputPort; 2000 | lv2:index 191; 2001 | lv2:symbol "enable__op__6_"; 2002 | lv2:name "Enable (Op #6)"; 2003 | lv2:minimum 0; 2004 | lv2:maximum 1; 2005 | lv2:default 1; 2006 | lv2:portProperty lv2:toggled; 2007 | ], 2008 | 2009 | [ 2010 | a lv2:ControlPort, lv2:InputPort; 2011 | lv2:index 192; 2012 | lv2:symbol "carriers__op__6_"; 2013 | lv2:name "Carriers (Op #6)"; 2014 | lv2:minimum 0.000000; 2015 | lv2:maximum 1.000000; 2016 | lv2:default 0.000000; 2017 | ], 2018 | 2019 | [ 2020 | a lv2:ControlPort, lv2:InputPort; 2021 | lv2:index 193; 2022 | lv2:symbol "mod0tooperator__op__6_"; 2023 | lv2:name "Mod0ToOperator (Op #6)"; 2024 | lv2:minimum 0.000000; 2025 | lv2:maximum 1.000000; 2026 | lv2:default 0.000000; 2027 | ], 2028 | 2029 | [ 2030 | a lv2:ControlPort, lv2:InputPort; 2031 | lv2:index 194; 2032 | lv2:symbol "mod1tooperator__op__6_"; 2033 | lv2:name "Mod1ToOperator (Op #6)"; 2034 | lv2:minimum 0.000000; 2035 | lv2:maximum 1.000000; 2036 | lv2:default 0.000000; 2037 | ], 2038 | 2039 | [ 2040 | a lv2:ControlPort, lv2:InputPort; 2041 | lv2:index 195; 2042 | lv2:symbol "mod2tooperator__op__6_"; 2043 | lv2:name "Mod2ToOperator (Op #6)"; 2044 | lv2:minimum 0.000000; 2045 | lv2:maximum 1.000000; 2046 | lv2:default 0.000000; 2047 | ], 2048 | 2049 | [ 2050 | a lv2:ControlPort, lv2:InputPort; 2051 | lv2:index 196; 2052 | lv2:symbol "mod3tooperator__op__6_"; 2053 | lv2:name "Mod3ToOperator (Op #6)"; 2054 | lv2:minimum 0.000000; 2055 | lv2:maximum 1.000000; 2056 | lv2:default 0.000000; 2057 | ], 2058 | 2059 | [ 2060 | a lv2:ControlPort, lv2:InputPort; 2061 | lv2:index 197; 2062 | lv2:symbol "mod4tooperator__op__6_"; 2063 | lv2:name "Mod4ToOperator (Op #6)"; 2064 | lv2:minimum 0.000000; 2065 | lv2:maximum 1.000000; 2066 | lv2:default 0.000000; 2067 | ], 2068 | 2069 | [ 2070 | a lv2:ControlPort, lv2:InputPort; 2071 | lv2:index 198; 2072 | lv2:symbol "mod5tooperator__op__6_"; 2073 | lv2:name "Mod5ToOperator (Op #6)"; 2074 | lv2:minimum 0.000000; 2075 | lv2:maximum 1.000000; 2076 | lv2:default 0.000000; 2077 | ], 2078 | 2079 | [ 2080 | a lv2:ControlPort, lv2:InputPort; 2081 | lv2:index 199; 2082 | lv2:symbol "mod6tooperator__op__6_"; 2083 | lv2:name "Mod6ToOperator (Op #6)"; 2084 | lv2:minimum 0.000000; 2085 | lv2:maximum 1.000000; 2086 | lv2:default 0.000000; 2087 | ], 2088 | 2089 | [ 2090 | a lv2:ControlPort, lv2:InputPort; 2091 | lv2:index 200; 2092 | lv2:symbol "mod7tooperator__op__6_"; 2093 | lv2:name "Mod7ToOperator (Op #6)"; 2094 | lv2:minimum 0.000000; 2095 | lv2:maximum 1.000000; 2096 | lv2:default 0.000000; 2097 | ], 2098 | 2099 | [ 2100 | a lv2:ControlPort, lv2:InputPort; 2101 | lv2:index 201; 2102 | lv2:symbol "amp__op__7_"; 2103 | lv2:name "Amp (Op #7)"; 2104 | lv2:minimum 0.005000; 2105 | lv2:maximum 16.000000; 2106 | lv2:default 1.000000; 2107 | lv2:portProperty epp:logarithmic; 2108 | ], 2109 | 2110 | [ 2111 | a lv2:ControlPort, lv2:InputPort; 2112 | lv2:index 202; 2113 | lv2:symbol "pan__op__7_"; 2114 | lv2:name "Pan (Op #7)"; 2115 | lv2:minimum -1.000000; 2116 | lv2:maximum 1.000000; 2117 | lv2:default 0.000000; 2118 | ], 2119 | 2120 | [ 2121 | a lv2:ControlPort, lv2:InputPort; 2122 | lv2:index 203; 2123 | lv2:symbol "freqmod__op__7_"; 2124 | lv2:name "FreqMod (Op #7)"; 2125 | lv2:minimum 0.000000; 2126 | lv2:maximum 16.000000; 2127 | lv2:default 1.000000; 2128 | ], 2129 | 2130 | [ 2131 | a lv2:ControlPort, lv2:InputPort; 2132 | lv2:index 204; 2133 | lv2:symbol "freqoffset__op__7_"; 2134 | lv2:name "FreqOffset (Op #7)"; 2135 | lv2:minimum -128.000000; 2136 | lv2:maximum 128.000000; 2137 | lv2:default 0.000000; 2138 | ], 2139 | 2140 | [ 2141 | a lv2:ControlPort, lv2:InputPort; 2142 | lv2:index 205; 2143 | lv2:symbol "envelopetarget0__op__7_"; 2144 | lv2:name "EnvelopeTarget0 (Op #7)"; 2145 | lv2:minimum 0.000000; 2146 | lv2:maximum 1.000000; 2147 | lv2:default 1.000000; 2148 | ], 2149 | 2150 | [ 2151 | a lv2:ControlPort, lv2:InputPort; 2152 | lv2:index 206; 2153 | lv2:symbol "envelopetarget1__op__7_"; 2154 | lv2:name "EnvelopeTarget1 (Op #7)"; 2155 | lv2:minimum 0.000000; 2156 | lv2:maximum 1.000000; 2157 | lv2:default 0.500000; 2158 | ], 2159 | 2160 | [ 2161 | a lv2:ControlPort, lv2:InputPort; 2162 | lv2:index 207; 2163 | lv2:symbol "envelopetarget2__op__7_"; 2164 | lv2:name "EnvelopeTarget2 (Op #7)"; 2165 | lv2:minimum 0.000000; 2166 | lv2:maximum 1.000000; 2167 | lv2:default 0.250000; 2168 | ], 2169 | 2170 | [ 2171 | a lv2:ControlPort, lv2:InputPort; 2172 | lv2:index 208; 2173 | lv2:symbol "envelopedelay0__op__7_"; 2174 | lv2:name "EnvelopeDelay0 (Op #7)"; 2175 | lv2:minimum 0.005000; 2176 | lv2:maximum 8.000000; 2177 | lv2:default 0.050000; 2178 | lv2:portProperty epp:logarithmic; 2179 | ], 2180 | 2181 | [ 2182 | a lv2:ControlPort, lv2:InputPort; 2183 | lv2:index 209; 2184 | lv2:symbol "envelopedelay1__op__7_"; 2185 | lv2:name "EnvelopeDelay1 (Op #7)"; 2186 | lv2:minimum 0.005000; 2187 | lv2:maximum 8.000000; 2188 | lv2:default 0.050000; 2189 | lv2:portProperty epp:logarithmic; 2190 | ], 2191 | 2192 | [ 2193 | a lv2:ControlPort, lv2:InputPort; 2194 | lv2:index 210; 2195 | lv2:symbol "envelopedelay2__op__7_"; 2196 | lv2:name "EnvelopeDelay2 (Op #7)"; 2197 | lv2:minimum 0.005000; 2198 | lv2:maximum 8.000000; 2199 | lv2:default 0.250000; 2200 | lv2:portProperty epp:logarithmic; 2201 | ], 2202 | 2203 | [ 2204 | a lv2:ControlPort, lv2:InputPort; 2205 | lv2:index 211; 2206 | lv2:symbol "envelopereleasetime__op__7_"; 2207 | lv2:name "EnvelopeReleaseTime (Op #7)"; 2208 | lv2:minimum 0.005000; 2209 | lv2:maximum 8.000000; 2210 | lv2:default 0.500000; 2211 | lv2:portProperty epp:logarithmic; 2212 | ], 2213 | 2214 | [ 2215 | a lv2:ControlPort, lv2:InputPort; 2216 | lv2:index 212; 2217 | lv2:symbol "keyboardscalingmidpoint__op__7_"; 2218 | lv2:name "KeyboardScalingMidPoint (Op #7)"; 2219 | lv2:minimum 50.000000; 2220 | lv2:maximum 5000.000000; 2221 | lv2:default 440.000000; 2222 | lv2:portProperty epp:logarithmic; 2223 | ], 2224 | 2225 | [ 2226 | a lv2:ControlPort, lv2:InputPort; 2227 | lv2:index 213; 2228 | lv2:symbol "keyboardscalinglowfactor__op__7_"; 2229 | lv2:name "KeyboardScalingLowFactor (Op #7)"; 2230 | lv2:minimum -2.000000; 2231 | lv2:maximum 2.000000; 2232 | lv2:default 0.000000; 2233 | ], 2234 | 2235 | [ 2236 | a lv2:ControlPort, lv2:InputPort; 2237 | lv2:index 214; 2238 | lv2:symbol "keyboardscalinghighfactor__op__7_"; 2239 | lv2:name "KeyboardScalingHighFactor (Op #7)"; 2240 | lv2:minimum -2.000000; 2241 | lv2:maximum 2.000000; 2242 | lv2:default 0.000000; 2243 | ], 2244 | 2245 | [ 2246 | a lv2:ControlPort, lv2:InputPort; 2247 | lv2:index 215; 2248 | lv2:symbol "velocitysensitivity__op__7_"; 2249 | lv2:name "VelocitySensitivity (Op #7)"; 2250 | lv2:minimum 0.000000; 2251 | lv2:maximum 1.000000; 2252 | lv2:default 1.000000; 2253 | ], 2254 | 2255 | [ 2256 | a lv2:ControlPort, lv2:InputPort; 2257 | lv2:index 216; 2258 | lv2:symbol "modsensitivity__op__7_"; 2259 | lv2:name "ModSensitivity (Op #7)"; 2260 | lv2:minimum 0.000000; 2261 | lv2:maximum 1.000000; 2262 | lv2:default 0.000000; 2263 | ], 2264 | 2265 | [ 2266 | a lv2:ControlPort, lv2:InputPort; 2267 | lv2:index 217; 2268 | lv2:symbol "lfoampdepth__op__7_"; 2269 | lv2:name "LFOAmpDepth (Op #7)"; 2270 | lv2:minimum 0.000000; 2271 | lv2:maximum 1.000000; 2272 | lv2:default 0.000000; 2273 | ], 2274 | 2275 | [ 2276 | a lv2:ControlPort, lv2:InputPort; 2277 | lv2:index 218; 2278 | lv2:symbol "lfofreqmoddepth__op__7_"; 2279 | lv2:name "LFOFreqModDepth (Op #7)"; 2280 | lv2:minimum 0.000000; 2281 | lv2:maximum 0.025000; 2282 | lv2:default 0.000000; 2283 | ], 2284 | 2285 | [ 2286 | a lv2:ControlPort, lv2:InputPort; 2287 | lv2:index 219; 2288 | lv2:symbol "enable__op__7_"; 2289 | lv2:name "Enable (Op #7)"; 2290 | lv2:minimum 0; 2291 | lv2:maximum 1; 2292 | lv2:default 1; 2293 | lv2:portProperty lv2:toggled; 2294 | ], 2295 | 2296 | [ 2297 | a lv2:ControlPort, lv2:InputPort; 2298 | lv2:index 220; 2299 | lv2:symbol "carriers__op__7_"; 2300 | lv2:name "Carriers (Op #7)"; 2301 | lv2:minimum 0.000000; 2302 | lv2:maximum 1.000000; 2303 | lv2:default 0.000000; 2304 | ], 2305 | 2306 | [ 2307 | a lv2:ControlPort, lv2:InputPort; 2308 | lv2:index 221; 2309 | lv2:symbol "mod0tooperator__op__7_"; 2310 | lv2:name "Mod0ToOperator (Op #7)"; 2311 | lv2:minimum 0.000000; 2312 | lv2:maximum 1.000000; 2313 | lv2:default 0.000000; 2314 | ], 2315 | 2316 | [ 2317 | a lv2:ControlPort, lv2:InputPort; 2318 | lv2:index 222; 2319 | lv2:symbol "mod1tooperator__op__7_"; 2320 | lv2:name "Mod1ToOperator (Op #7)"; 2321 | lv2:minimum 0.000000; 2322 | lv2:maximum 1.000000; 2323 | lv2:default 0.000000; 2324 | ], 2325 | 2326 | [ 2327 | a lv2:ControlPort, lv2:InputPort; 2328 | lv2:index 223; 2329 | lv2:symbol "mod2tooperator__op__7_"; 2330 | lv2:name "Mod2ToOperator (Op #7)"; 2331 | lv2:minimum 0.000000; 2332 | lv2:maximum 1.000000; 2333 | lv2:default 0.000000; 2334 | ], 2335 | 2336 | [ 2337 | a lv2:ControlPort, lv2:InputPort; 2338 | lv2:index 224; 2339 | lv2:symbol "mod3tooperator__op__7_"; 2340 | lv2:name "Mod3ToOperator (Op #7)"; 2341 | lv2:minimum 0.000000; 2342 | lv2:maximum 1.000000; 2343 | lv2:default 0.000000; 2344 | ], 2345 | 2346 | [ 2347 | a lv2:ControlPort, lv2:InputPort; 2348 | lv2:index 225; 2349 | lv2:symbol "mod4tooperator__op__7_"; 2350 | lv2:name "Mod4ToOperator (Op #7)"; 2351 | lv2:minimum 0.000000; 2352 | lv2:maximum 1.000000; 2353 | lv2:default 0.000000; 2354 | ], 2355 | 2356 | [ 2357 | a lv2:ControlPort, lv2:InputPort; 2358 | lv2:index 226; 2359 | lv2:symbol "mod5tooperator__op__7_"; 2360 | lv2:name "Mod5ToOperator (Op #7)"; 2361 | lv2:minimum 0.000000; 2362 | lv2:maximum 1.000000; 2363 | lv2:default 0.000000; 2364 | ], 2365 | 2366 | [ 2367 | a lv2:ControlPort, lv2:InputPort; 2368 | lv2:index 227; 2369 | lv2:symbol "mod6tooperator__op__7_"; 2370 | lv2:name "Mod6ToOperator (Op #7)"; 2371 | lv2:minimum 0.000000; 2372 | lv2:maximum 1.000000; 2373 | lv2:default 0.000000; 2374 | ], 2375 | 2376 | [ 2377 | a lv2:ControlPort, lv2:InputPort; 2378 | lv2:index 228; 2379 | lv2:symbol "mod7tooperator__op__7_"; 2380 | lv2:name "Mod7ToOperator (Op #7)"; 2381 | lv2:minimum 0.000000; 2382 | lv2:maximum 1.000000; 2383 | lv2:default 0.000000; 2384 | ]. 2385 | 2386 | --------------------------------------------------------------------------------