copy from mtytel/Vital
This commit is contained in:
parent
80ef284fdf
commit
809e9a7bff
2887 changed files with 930198 additions and 0 deletions
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
VTune\ Profiler\ Results
|
||||||
|
cmake-build*
|
||||||
|
*.swp
|
||||||
|
*.Trashes
|
||||||
|
xcuserdata
|
||||||
|
xcshareddata
|
||||||
|
*.xcworkspace
|
||||||
|
build
|
||||||
|
.clang_complete
|
||||||
|
.vs
|
||||||
|
.ycm_extra_conf.py*
|
||||||
|
*.pbxuser
|
||||||
|
*.perspective
|
||||||
|
*.perspectivev3
|
||||||
|
*.sdf
|
||||||
|
*.suo
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.tlog
|
||||||
|
*.user
|
||||||
|
*.app
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.component
|
||||||
|
*.VST
|
||||||
|
*.lv2
|
||||||
|
*.vst
|
||||||
|
*.VST3
|
||||||
|
*.vst3
|
||||||
|
*.tar.gz
|
||||||
|
Debug
|
||||||
|
Profile
|
||||||
|
Release
|
||||||
209
Makefile
Normal file
209
Makefile
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
|
||||||
|
ifndef CONFIG
|
||||||
|
CONFIG=Release
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifndef LIBDIR
|
||||||
|
LIBDIR=/usr/lib/
|
||||||
|
endif
|
||||||
|
|
||||||
|
BUILD_DATE="$(shell date +'%Y %m %d %H %M')"
|
||||||
|
|
||||||
|
PAID := 1
|
||||||
|
|
||||||
|
|
||||||
|
VERSION := $(shell sh -c 'grep -oh -m 1 "VERSION=[0-9\.]*" standalone/builds/linux/Makefile | cut -d "=" -f 2')
|
||||||
|
|
||||||
|
MACHINE := $(shell sh -c 'uname -m 2> /dev/null || echo not')
|
||||||
|
ifneq (,$(findstring aarch,$(MACHINE)))
|
||||||
|
SIMDFLAGS := -march=armv8-a -mtune=cortex-a53
|
||||||
|
GLFLAGS := -DOPENGL_ES=1
|
||||||
|
else
|
||||||
|
ifneq (,$(findstring arm,$(MACHINE)))
|
||||||
|
SIMDFLAGS := -march=armv8-a -mtune=cortex-a53 -mfpu=neon-fp-armv8 -mfloat-abi=hard
|
||||||
|
GLFLAGS := -DOPENGL_ES=1
|
||||||
|
else
|
||||||
|
SIMDFLAGS := -msse2
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
PROGRAM = vital
|
||||||
|
LIB_PROGRAM = Vital
|
||||||
|
LIB_PROGRAM_FX = VitalFX
|
||||||
|
BIN = $(DESTDIR)/usr/bin
|
||||||
|
BINFILE = $(BIN)/$(PROGRAM)
|
||||||
|
LV2 = $(DESTDIR)/$(LIBDIR)/lv2/$(LIB_PROGRAM).lv2
|
||||||
|
EFFECTS_LV2 = $(DESTDIR)/$(LIBDIR)/lv2/$(LIB_PROGRAM_FX).lv2
|
||||||
|
VSTDIR = $(DESTDIR)/$(LIBDIR)/vst
|
||||||
|
VST = $(VSTDIR)/$(LIB_PROGRAM).so
|
||||||
|
VST3DIR = $(DESTDIR)/$(LIBDIR)/vst3
|
||||||
|
VST3 = $(VST3DIR)/$(LIB_PROGRAM).vst3
|
||||||
|
VST3SUBDIR = Contents/x86_64-linux
|
||||||
|
EFFECTS_VST = $(VSTDIR)/$(LIB_PROGRAM_FX).so
|
||||||
|
EFFECTS_VST3 = $(VST3DIR)/$(LIB_PROGRAM_FX).vst3
|
||||||
|
SYSDATA = $(DESTDIR)/usr/share/$(PROGRAM)
|
||||||
|
MAN = $(DESTDIR)/usr/share/man/man1/
|
||||||
|
CHANGES = $(DESTDIR)/usr/share/doc/$(PROGRAM)/
|
||||||
|
DESKTOP = $(DESTDIR)/usr/share/applications/
|
||||||
|
ZIP_FOLDER = $(LIB_PROGRAM)Binaries
|
||||||
|
|
||||||
|
ICONS = $(DESTDIR)/usr/share/icons/hicolor/
|
||||||
|
ICON16 = images/vital_icon_16.png
|
||||||
|
ICON22 = images/vital_icon_22.png
|
||||||
|
ICON24 = images/vital_icon_24.png
|
||||||
|
ICON32 = images/vital_icon_32.png
|
||||||
|
ICON48 = images/vital_icon_48.png
|
||||||
|
ICON64 = images/vital_icon_64.png
|
||||||
|
ICON128 = images/vital_icon_128.png
|
||||||
|
ICON256 = images/vital_icon_256.png
|
||||||
|
XPMDEST = $(DESTDIR)/usr/share/pixmaps
|
||||||
|
ICONXPM = images/vital.xpm
|
||||||
|
|
||||||
|
ICONDEST16 = $(ICONS)/16x16/apps
|
||||||
|
ICONDEST22 = $(ICONS)/22x22/apps
|
||||||
|
ICONDEST24 = $(ICONS)/24x24/apps
|
||||||
|
ICONDEST32 = $(ICONS)/32x32/apps
|
||||||
|
ICONDEST48 = $(ICONS)/48x48/apps
|
||||||
|
ICONDEST64 = $(ICONS)/64x64/apps
|
||||||
|
ICONDEST128 = $(ICONS)/128x128/apps
|
||||||
|
ICONDEST256 = $(ICONS)/256x256/apps
|
||||||
|
|
||||||
|
all: standalone vst vst3 lv2
|
||||||
|
|
||||||
|
install_icons:
|
||||||
|
install -d $(ICONDEST16) $(ICONDEST22) $(ICONDEST24) $(ICONDEST32)
|
||||||
|
install -d $(ICONDEST48) $(ICONDEST64) $(ICONDEST128) $(ICONDEST256)
|
||||||
|
install -d $(XPMDEST)
|
||||||
|
cp $(ICONXPM) $(XPMDEST)/$(PROGRAM).xpm
|
||||||
|
cp $(ICON16) $(ICONDEST16)/$(PROGRAM).png
|
||||||
|
cp $(ICON22) $(ICONDEST22)/$(PROGRAM).png
|
||||||
|
cp $(ICON24) $(ICONDEST24)/$(PROGRAM).png
|
||||||
|
cp $(ICON32) $(ICONDEST32)/$(PROGRAM).png
|
||||||
|
cp $(ICON48) $(ICONDEST48)/$(PROGRAM).png
|
||||||
|
cp $(ICON64) $(ICONDEST64)/$(PROGRAM).png
|
||||||
|
cp $(ICON128) $(ICONDEST128)/$(PROGRAM).png
|
||||||
|
cp $(ICON256) $(ICONDEST256)/$(PROGRAM).png
|
||||||
|
|
||||||
|
standalone:
|
||||||
|
$(MAKE) -C standalone/builds/linux CONFIG=$(CONFIG) SIMDFLAGS="$(SIMDFLAGS)" GLFLAGS="$(GLFLAGS)" BUILD_DATE=$(BUILD_DATE)
|
||||||
|
|
||||||
|
lv2:
|
||||||
|
$(MAKE) -C plugin/builds/linux_lv2 CONFIG=$(CONFIG) AR=gcc-ar SIMDFLAGS="$(SIMDFLAGS)" GLFLAGS="$(GLFLAGS)" BUILD_DATE=$(BUILD_DATE)
|
||||||
|
|
||||||
|
effects_lv2:
|
||||||
|
$(MAKE) -C effects/builds/linux_lv2 CONFIG=$(CONFIG) AR=gcc-ar SIMDFLAGS="$(SIMDFLAGS)" GLFLAGS="$(GLFLAGS)" BUILD_DATE=$(BUILD_DATE)
|
||||||
|
|
||||||
|
vst:
|
||||||
|
$(MAKE) -C plugin/builds/linux_vst VST CONFIG=$(CONFIG) AR=gcc-ar SIMDFLAGS="$(SIMDFLAGS)" GLFLAGS="$(GLFLAGS)" BUILD_DATE=$(BUILD_DATE)
|
||||||
|
|
||||||
|
vst3:
|
||||||
|
$(MAKE) -C plugin/builds/linux_vst VST3 CONFIG=$(CONFIG) AR=gcc-ar SIMDFLAGS="$(SIMDFLAGS)" GLFLAGS="$(GLFLAGS)" BUILD_DATE=$(BUILD_DATE)
|
||||||
|
|
||||||
|
effects_vst:
|
||||||
|
$(MAKE) -C effects/builds/linux_vst VST CONFIG=$(CONFIG) AR=gcc-ar SIMDFLAGS="$(SIMDFLAGS)" GLFLAGS="$(GLFLAGS)" BUILD_DATE=$(BUILD_DATE)
|
||||||
|
|
||||||
|
effects_vst3:
|
||||||
|
$(MAKE) -C effects/builds/linux_vst VST3 CONFIG=$(CONFIG) AR=gcc-ar SIMDFLAGS="$(SIMDFLAGS)" GLFLAGS="$(GLFLAGS)" BUILD_DATE=$(BUILD_DATE)
|
||||||
|
|
||||||
|
headless_server:
|
||||||
|
$(MAKE) -C headless/builds/linux CONFIG=$(CONFIG) SIMDFLAGS="$(SIMDFLAGS)" GLFLAGS="$(GLFLAGS)" BUILD_DATE=$(BUILD_DATE)
|
||||||
|
|
||||||
|
test:
|
||||||
|
$(MAKE) -C tests/builds/linux CONFIG=$(CONFIG) SIMDFLAGS="$(SIMDFLAGS)" GLFLAGS="$(GLFLAGS)" BUILD_DATE=$(BUILD_DATE)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(MAKE) clean -C standalone/builds/linux CONFIG=$(CONFIG)
|
||||||
|
$(MAKE) clean -C plugin/builds/linux_vst CONFIG=$(CONFIG)
|
||||||
|
$(MAKE) clean -C plugin/builds/linux_lv2 CONFIG=$(CONFIG)
|
||||||
|
$(MAKE) clean -C effects/builds/linux_vst CONFIG=$(CONFIG)
|
||||||
|
$(MAKE) clean -C effects/builds/linux_lv2 CONFIG=$(CONFIG)
|
||||||
|
$(MAKE) clean -C headless/builds/linux CONFIG=$(CONFIG)
|
||||||
|
$(MAKE) clean -C tests/builds/linux CONFIG=$(CONFIG)
|
||||||
|
|
||||||
|
install_standalone: standalone install_icons
|
||||||
|
install -d $(BIN) $(MAN) $(CHANGES) $(DESKTOP)
|
||||||
|
install standalone/builds/linux/build/$(PROGRAM) $(BIN)
|
||||||
|
install -m644 standalone/vital.desktop $(DESKTOP)/vital.desktop
|
||||||
|
|
||||||
|
install_lv2: lv2
|
||||||
|
install -d $(LV2)
|
||||||
|
install -m644 plugin/builds/linux_lv2/Vital.lv2/* $(LV2)
|
||||||
|
|
||||||
|
install_effects_lv2: effects_lv2
|
||||||
|
install -d $(EFFECTS_LV2)
|
||||||
|
install -m644 effects/builds/linux_lv2/VitalFX.lv2/* $(EFFECTS_LV2)
|
||||||
|
|
||||||
|
install_vst: vst
|
||||||
|
install -d $(VSTDIR)
|
||||||
|
install plugin/builds/linux_vst/build/Vital.so $(VST)
|
||||||
|
|
||||||
|
install_effects_vst: effects_vst
|
||||||
|
install -d $(VSTDIR)
|
||||||
|
install effects/builds/linux_vst/build/VitalFX.so $(EFFECTS_VST)
|
||||||
|
|
||||||
|
install_vst3: vst3
|
||||||
|
install -d $(VST3)/$(VST3SUBDIR)
|
||||||
|
install -m644 plugin/builds/linux_vst/build/Vital.vst3/$(VST3SUBDIR)/* $(VST3)/$(VST3SUBDIR)
|
||||||
|
|
||||||
|
install_effects_vst3: effects_vst3
|
||||||
|
install -d $(EFFECTS_VST3)/$(VST3SUBDIR)
|
||||||
|
install -m644 plugin/builds/linux_vst/build/VitalFX.vst3/$(VST3SUBDIR)/* $(EFFECTS_VST3)/$(VST3SUBDIR)
|
||||||
|
|
||||||
|
install: install_standalone install_lv2 install_vst install_vst3
|
||||||
|
install_effects: install_effects_lv2 install_effects_vst install_effects_vst3
|
||||||
|
|
||||||
|
dist:
|
||||||
|
rm -rf $(PROGRAM)
|
||||||
|
mkdir $(PROGRAM)
|
||||||
|
-cp -rf * $(PROGRAM)/
|
||||||
|
$(MAKE) clean -C $(PROGRAM) CONFIG=Release
|
||||||
|
$(MAKE) clean -C $(PROGRAM) CONFIG=Debug
|
||||||
|
rm -rf $(PROGRAM)/.git
|
||||||
|
rm -rf $(PROGRAM)/plugin/builds/CLion
|
||||||
|
rm -rf $(PROGRAM)/plugin/builds/iOS
|
||||||
|
rm -rf $(PROGRAM)/plugin/builds/osx
|
||||||
|
rm -rf $(PROGRAM)/plugin/builds/vs17
|
||||||
|
rm -rf $(PROGRAM)/plugin/builds/vs19
|
||||||
|
rm -rf $(PROGRAM)/effects/builds/CLion
|
||||||
|
rm -rf $(PROGRAM)/effects/builds/iOS
|
||||||
|
rm -rf $(PROGRAM)/effects/builds/osx
|
||||||
|
rm -rf $(PROGRAM)/effects/builds/vs17
|
||||||
|
rm -rf $(PROGRAM)/effects/builds/vs19
|
||||||
|
rm -rf $(PROGRAM)/standalone/builds/CLion
|
||||||
|
rm -rf $(PROGRAM)/standalone/builds/iOS
|
||||||
|
rm -rf $(PROGRAM)/standalone/builds/osx
|
||||||
|
rm -rf $(PROGRAM)/standalone/builds/vs17
|
||||||
|
rm -rf $(PROGRAM)/standalone/builds/vs19
|
||||||
|
rm -rf $(PROGRAM)/tests/builds/CLion
|
||||||
|
rm -rf $(PROGRAM)/tests/builds/iOS
|
||||||
|
rm -rf $(PROGRAM)/tests/builds/osx
|
||||||
|
rm -rf $(PROGRAM)/tests/builds/vs17
|
||||||
|
rm -rf $(PROGRAM)/tests/builds/vs19
|
||||||
|
mv $(PROGRAM) $(PROGRAM)-$(VERSION)
|
||||||
|
tar -cvzf $(PROGRAM)_$(VERSION).orig.tar.gz $(PROGRAM)-$(VERSION)
|
||||||
|
rm -rf $(PROGRAM)-$(VERSION)
|
||||||
|
|
||||||
|
zip_binaries:
|
||||||
|
mkdir $(ZIP_FOLDER)
|
||||||
|
cp -r plugin/builds/linux_lv2/Vital.lv2 $(ZIP_FOLDER)
|
||||||
|
cp -r plugin/builds/linux_vst/build/Vital.so $(ZIP_FOLDER)
|
||||||
|
cp -r plugin/builds/linux_vst/build/Vital.vst3 $(ZIP_FOLDER)
|
||||||
|
cp -r standalone/builds/linux/build/$(PROGRAM) $(ZIP_FOLDER)
|
||||||
|
zip -r $(ZIP_FOLDER) $(ZIP_FOLDER)
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -rf $(LV2)
|
||||||
|
rm -rf $(VST)
|
||||||
|
rm -rf $(SYSDATA)
|
||||||
|
rm -rf $(BINFILE)
|
||||||
|
rm $(ICONDEST16)/$(PROGRAM).png
|
||||||
|
rm $(ICONDEST22)/$(PROGRAM).png
|
||||||
|
rm $(ICONDEST24)/$(PROGRAM).png
|
||||||
|
rm $(ICONDEST32)/$(PROGRAM).png
|
||||||
|
rm $(ICONDEST48)/$(PROGRAM).png
|
||||||
|
rm $(ICONDEST64)/$(PROGRAM).png
|
||||||
|
rm $(ICONDEST128)/$(PROGRAM).png
|
||||||
|
rm $(ICONDEST256)/$(PROGRAM).png
|
||||||
|
rm $(XPMDEST)/$(PROGRAM).xpm
|
||||||
|
|
||||||
|
.PHONY: standalone
|
||||||
27
README.md
Normal file
27
README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Vital
|
||||||
|
Vital is a spectral warping wavetable synthesizer. This is the source.
|
||||||
|
|
||||||
|
This repository is updated on a delay after binary releases.
|
||||||
|
|
||||||
|
## Code Licensing
|
||||||
|
If you are making a proprietary or closed source app and would like to use Vital's source code, contact licensing@vital.audio for non GPLv3 licensing options.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
Create an account and download Vital at [vital.audio](https://vital.audio)
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
Report bugs (e.g.non-code and non-compiling issues) to https://forum.vital.audio
|
||||||
|
|
||||||
|
Feel free to report issues on building/compiling here but note that I'm not prioritizing them.
|
||||||
|
|
||||||
|
## Pull requests
|
||||||
|
I will not take any pull requests.
|
||||||
|
|
||||||
|
## What can you do with the source
|
||||||
|
The source code is licensed under the GPLv3. If you download the source or create builds you must comply with that license.
|
||||||
|
|
||||||
|
### Things you can't do with this source
|
||||||
|
- Do not create an app and distribute it on the iOS app store. The app store is not comptabile with GPLv3 and you'll only get an exception for this if you're paying for a GPLv3 exception for Vital's source (see Code Licensing above).
|
||||||
|
- Do not use the name "Vital", "Vital Audio", "Tytel" or "Matt Tytel" for marketing or to name any distribution of binaries built with this source. This source code does not give you rights to infringe on trademarks.
|
||||||
|
- Do not connect to any web service at https://vital.audio, https://account.vital.audio or https://store.vital.audio from your own builds. This is against the terms of using those sites.
|
||||||
|
- Do not distribute the presets that come with the free version of Vital. They're under a separate license that does not allow redistribution.
|
||||||
165
headless/JuceLibraryCode/AppConfig.h
Normal file
165
headless/JuceLibraryCode/AppConfig.h
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
There's a section below where you can add your own custom code safely, and the
|
||||||
|
Projucer will preserve the contents of that block, but the best way to change
|
||||||
|
any of these definitions is by using the Projucer's project settings.
|
||||||
|
|
||||||
|
Any commented-out settings will assume their default values.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// [BEGIN_USER_CODE_SECTION]
|
||||||
|
|
||||||
|
// (You can add your own code in this section, and the Projucer will not overwrite it)
|
||||||
|
|
||||||
|
// [END_USER_CODE_SECTION]
|
||||||
|
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
In accordance with the terms of the JUCE 6 End-Use License Agreement, the
|
||||||
|
JUCE Code in SECTION A cannot be removed, changed or otherwise rendered
|
||||||
|
ineffective unless you have a JUCE Indie or Pro license, or are using JUCE
|
||||||
|
under the GPL v3 license.
|
||||||
|
|
||||||
|
End User License Agreement: www.juce.com/juce-6-licence
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
// BEGIN SECTION A
|
||||||
|
|
||||||
|
#ifndef JUCE_DISPLAY_SPLASH_SCREEN
|
||||||
|
#define JUCE_DISPLAY_SPLASH_SCREEN 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// END SECTION A
|
||||||
|
|
||||||
|
#define JUCE_USE_DARK_SPLASH_SCREEN 1
|
||||||
|
|
||||||
|
#define JUCE_PROJUCER_VERSION 0x60005
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#define JUCE_MODULE_AVAILABLE_juce_audio_basics 1
|
||||||
|
#define JUCE_MODULE_AVAILABLE_juce_audio_formats 1
|
||||||
|
#define JUCE_MODULE_AVAILABLE_juce_core 1
|
||||||
|
#define JUCE_MODULE_AVAILABLE_juce_data_structures 1
|
||||||
|
#define JUCE_MODULE_AVAILABLE_juce_dsp 1
|
||||||
|
#define JUCE_MODULE_AVAILABLE_juce_events 1
|
||||||
|
|
||||||
|
#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// juce_audio_formats flags:
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_FLAC
|
||||||
|
//#define JUCE_USE_FLAC 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_OGGVORBIS
|
||||||
|
//#define JUCE_USE_OGGVORBIS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_MP3AUDIOFORMAT
|
||||||
|
//#define JUCE_USE_MP3AUDIOFORMAT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_LAME_AUDIO_FORMAT
|
||||||
|
//#define JUCE_USE_LAME_AUDIO_FORMAT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_WINDOWS_MEDIA_FORMAT
|
||||||
|
//#define JUCE_USE_WINDOWS_MEDIA_FORMAT 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// juce_core flags:
|
||||||
|
|
||||||
|
#ifndef JUCE_FORCE_DEBUG
|
||||||
|
//#define JUCE_FORCE_DEBUG 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_LOG_ASSERTIONS
|
||||||
|
//#define JUCE_LOG_ASSERTIONS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_CHECK_MEMORY_LEAKS
|
||||||
|
//#define JUCE_CHECK_MEMORY_LEAKS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
|
||||||
|
//#define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_INCLUDE_ZLIB_CODE
|
||||||
|
//#define JUCE_INCLUDE_ZLIB_CODE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_USE_CURL
|
||||||
|
#define JUCE_USE_CURL 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_LOAD_CURL_SYMBOLS_LAZILY
|
||||||
|
//#define JUCE_LOAD_CURL_SYMBOLS_LAZILY 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_CATCH_UNHANDLED_EXCEPTIONS
|
||||||
|
//#define JUCE_CATCH_UNHANDLED_EXCEPTIONS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_ALLOW_STATIC_NULL_VARIABLES
|
||||||
|
//#define JUCE_ALLOW_STATIC_NULL_VARIABLES 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_STRICT_REFCOUNTEDPOINTER
|
||||||
|
//#define JUCE_STRICT_REFCOUNTEDPOINTER 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_ENABLE_ALLOCATION_HOOKS
|
||||||
|
//#define JUCE_ENABLE_ALLOCATION_HOOKS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// juce_dsp flags:
|
||||||
|
|
||||||
|
#ifndef JUCE_ASSERTION_FIRFILTER
|
||||||
|
//#define JUCE_ASSERTION_FIRFILTER 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_DSP_USE_INTEL_MKL
|
||||||
|
//#define JUCE_DSP_USE_INTEL_MKL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_DSP_USE_SHARED_FFTW
|
||||||
|
//#define JUCE_DSP_USE_SHARED_FFTW 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_DSP_USE_STATIC_FFTW
|
||||||
|
//#define JUCE_DSP_USE_STATIC_FFTW 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||||
|
//#define JUCE_DSP_ENABLE_SNAP_TO_ZERO 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// juce_events flags:
|
||||||
|
|
||||||
|
#ifndef JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK
|
||||||
|
//#define JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
#ifndef JUCE_STANDALONE_APPLICATION
|
||||||
|
#if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)
|
||||||
|
#define JUCE_STANDALONE_APPLICATION JucePlugin_Build_Standalone
|
||||||
|
#else
|
||||||
|
#define JUCE_STANDALONE_APPLICATION 1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
48
headless/JuceLibraryCode/JuceHeader.h
Normal file
48
headless/JuceLibraryCode/JuceHeader.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
This is the header file that your files should include in order to get all the
|
||||||
|
JUCE library headers. You should avoid including the JUCE headers directly in
|
||||||
|
your own source files, because that wouldn't pick up the correct configuration
|
||||||
|
options for your app.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
|
||||||
|
#include <juce_audio_basics/juce_audio_basics.h>
|
||||||
|
#include <juce_audio_formats/juce_audio_formats.h>
|
||||||
|
#include <juce_core/juce_core.h>
|
||||||
|
#include <juce_data_structures/juce_data_structures.h>
|
||||||
|
#include <juce_dsp/juce_dsp.h>
|
||||||
|
#include <juce_events/juce_events.h>
|
||||||
|
|
||||||
|
|
||||||
|
#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION
|
||||||
|
/** If you've hit this error then the version of the Projucer that was used to generate this project is
|
||||||
|
older than the version of the JUCE modules being included. To fix this error, re-save your project
|
||||||
|
using the latest version of the Projucer or, if you aren't using the Projucer to manage your project,
|
||||||
|
remove the JUCE_PROJUCER_VERSION define from the AppConfig.h file.
|
||||||
|
*/
|
||||||
|
#error "This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! DONT_SET_USING_JUCE_NAMESPACE
|
||||||
|
// If your code uses a lot of JUCE classes, then this will obviously save you
|
||||||
|
// a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE.
|
||||||
|
using namespace juce;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! JUCE_DONT_DECLARE_PROJECTINFO
|
||||||
|
namespace ProjectInfo
|
||||||
|
{
|
||||||
|
const char* const projectName = "Vital";
|
||||||
|
const char* const companyName = "Matt Tytel";
|
||||||
|
const char* const versionString = "99999.9.9";
|
||||||
|
const int versionNumber = 0x869f0909;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
12
headless/JuceLibraryCode/ReadMe.txt
Normal file
12
headless/JuceLibraryCode/ReadMe.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
Important Note!!
|
||||||
|
================
|
||||||
|
|
||||||
|
The purpose of this folder is to contain files that are auto-generated by the Projucer,
|
||||||
|
and ALL files in this folder will be mercilessly DELETED and completely re-written whenever
|
||||||
|
the Projucer saves your project.
|
||||||
|
|
||||||
|
Therefore, it's a bad idea to make any manual changes to the files in here, or to
|
||||||
|
put any of your own files in here if you don't want to lose them. (Of course you may choose
|
||||||
|
to add the folder's contents to your version-control system so that you can re-merge your own
|
||||||
|
modifications after the Projucer has saved its changes).
|
||||||
9
headless/JuceLibraryCode/include_juce_audio_basics.cpp
Normal file
9
headless/JuceLibraryCode/include_juce_audio_basics.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_audio_basics/juce_audio_basics.cpp>
|
||||||
9
headless/JuceLibraryCode/include_juce_audio_basics.mm
Normal file
9
headless/JuceLibraryCode/include_juce_audio_basics.mm
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_audio_basics/juce_audio_basics.mm>
|
||||||
9
headless/JuceLibraryCode/include_juce_audio_formats.cpp
Normal file
9
headless/JuceLibraryCode/include_juce_audio_formats.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_audio_formats/juce_audio_formats.cpp>
|
||||||
9
headless/JuceLibraryCode/include_juce_audio_formats.mm
Normal file
9
headless/JuceLibraryCode/include_juce_audio_formats.mm
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_audio_formats/juce_audio_formats.mm>
|
||||||
9
headless/JuceLibraryCode/include_juce_core.cpp
Normal file
9
headless/JuceLibraryCode/include_juce_core.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_core/juce_core.cpp>
|
||||||
9
headless/JuceLibraryCode/include_juce_core.mm
Normal file
9
headless/JuceLibraryCode/include_juce_core.mm
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_core/juce_core.mm>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_data_structures/juce_data_structures.cpp>
|
||||||
9
headless/JuceLibraryCode/include_juce_data_structures.mm
Normal file
9
headless/JuceLibraryCode/include_juce_data_structures.mm
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_data_structures/juce_data_structures.mm>
|
||||||
9
headless/JuceLibraryCode/include_juce_dsp.cpp
Normal file
9
headless/JuceLibraryCode/include_juce_dsp.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_dsp/juce_dsp.cpp>
|
||||||
9
headless/JuceLibraryCode/include_juce_dsp.mm
Normal file
9
headless/JuceLibraryCode/include_juce_dsp.mm
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_dsp/juce_dsp.mm>
|
||||||
9
headless/JuceLibraryCode/include_juce_events.cpp
Normal file
9
headless/JuceLibraryCode/include_juce_events.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_events/juce_events.cpp>
|
||||||
9
headless/JuceLibraryCode/include_juce_events.mm
Normal file
9
headless/JuceLibraryCode/include_juce_events.mm
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
IMPORTANT! This file is auto-generated each time you save your
|
||||||
|
project - if you alter its contents, your changes may be overwritten!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include <juce_events/juce_events.mm>
|
||||||
147
headless/builds/linux/Makefile
Normal file
147
headless/builds/linux/Makefile
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
# Automatically generated makefile, created by the Projucer
|
||||||
|
# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!
|
||||||
|
|
||||||
|
# build with "V=1" for verbose builds
|
||||||
|
ifeq ($(V), 1)
|
||||||
|
V_AT =
|
||||||
|
else
|
||||||
|
V_AT = @
|
||||||
|
endif
|
||||||
|
|
||||||
|
# (this disables dependency generation if multiple architectures are set)
|
||||||
|
DEPFLAGS := $(if $(word 2, $(TARGET_ARCH)), , -MMD)
|
||||||
|
|
||||||
|
ifndef STRIP
|
||||||
|
STRIP=strip
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifndef AR
|
||||||
|
AR=ar
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifndef CONFIG
|
||||||
|
CONFIG=Debug
|
||||||
|
endif
|
||||||
|
|
||||||
|
JUCE_ARCH_LABEL := $(shell uname -m)
|
||||||
|
|
||||||
|
ifeq ($(CONFIG),Debug)
|
||||||
|
JUCE_BINDIR := build
|
||||||
|
JUCE_LIBDIR := build
|
||||||
|
JUCE_OBJDIR := build/intermediate/Debug
|
||||||
|
JUCE_OUTDIR := build
|
||||||
|
|
||||||
|
ifeq ($(TARGET_ARCH),)
|
||||||
|
TARGET_ARCH :=
|
||||||
|
endif
|
||||||
|
|
||||||
|
JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DDEBUG=1" "-D_DEBUG=1" "-DBUILD_DATE=$(BUILD_DATE)" "-DJUCE_JACK_CLIENT_NAME=\"Vital\"" "-DJUCE_ALSA_MIDI_INPUT_NAME=\"Vital\"" "-DJUCE_ALSA_MIDI_OUTPUT_NAME=\"Vital\"" "-DJUCE_USE_XRANDR=0" "-DJUCE_DSP_USE_SHARED_FFTW=1" "-DHEADLESS=1" "-DNO_AUTH=1" "-DJUCER_LINUX_MAKE_6B3E762A=1" "-DJUCE_APP_VERSION=99999.9.9" "-DJUCE_APP_VERSION_HEX=0x869f0909" $(shell pkg-config --cflags libcurl) -pthread -I../../JuceLibraryCode -I../../../third_party/JUCE/modules -I../../../src/common -I../../../src/common/wavetable -I../../../src/interface/editor_components -I../../../src/interface/editor_sections -I../../../src/interface/look_and_feel -I../../../src/interface/wavetable -I../../../src/interface/wavetable/editors -I../../../src/interface/wavetable/overlays -I../../../src/standalone -I../../../src/synthesis/synth_engine -I../../../src/synthesis/effects -I../../../src/synthesis/filters -I../../../src/synthesis/framework -I../../../src/synthesis/lookups -I../../../src/synthesis/modulators -I../../../src/synthesis/modules -I../../../src/synthesis/producers -I../../../src/synthesis/utilities -I../../../third_party $(CPPFLAGS)
|
||||||
|
JUCE_CPPFLAGS_APP := "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=0" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_RTAS=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=0" "-DJucePlugin_Build_Unity=0"
|
||||||
|
JUCE_TARGET_APP := vital
|
||||||
|
|
||||||
|
JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -g -ggdb -O0 -ffast-math ${SIMDFLAGS} ${GLFLAGS} -ftree-vectorize -ftree-slp-vectorize -funroll-loops $(CFLAGS)
|
||||||
|
JUCE_CXXFLAGS += $(JUCE_CFLAGS) -std=c++14 $(CXXFLAGS)
|
||||||
|
JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) -L/usr/X11R6/lib/ $(shell pkg-config --libs libcurl) -fvisibility=hidden -ffast-math ${SIMDFLAGS} ${GLFLAGS} -ftree-vectorize -ftree-slp-vectorize -lcurl -lrt -ldl -lpthread $(LDFLAGS)
|
||||||
|
|
||||||
|
CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(CONFIG),Release)
|
||||||
|
JUCE_BINDIR := build
|
||||||
|
JUCE_LIBDIR := build
|
||||||
|
JUCE_OBJDIR := build/intermediate/Release
|
||||||
|
JUCE_OUTDIR := build
|
||||||
|
|
||||||
|
ifeq ($(TARGET_ARCH),)
|
||||||
|
TARGET_ARCH :=
|
||||||
|
endif
|
||||||
|
|
||||||
|
JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DNDEBUG=1" "-DBUILD_DATE=$(BUILD_DATE)" "-DJUCE_JACK_CLIENT_NAME=\"Vital\"" "-DJUCE_ALSA_MIDI_INPUT_NAME=\"Vital\"" "-DJUCE_ALSA_MIDI_OUTPUT_NAME=\"Vital\"" "-DJUCE_USE_XRANDR=0" "-DJUCE_DSP_USE_SHARED_FFTW=1" "-DHEADLESS=1" "-DNO_AUTH=1" "-DJUCER_LINUX_MAKE_6B3E762A=1" "-DJUCE_APP_VERSION=99999.9.9" "-DJUCE_APP_VERSION_HEX=0x869f0909" $(shell pkg-config --cflags libcurl) -pthread -I../../JuceLibraryCode -I../../../third_party/JUCE/modules -I../../../src/common -I../../../src/common/wavetable -I../../../src/interface/editor_components -I../../../src/interface/editor_sections -I../../../src/interface/look_and_feel -I../../../src/interface/wavetable -I../../../src/interface/wavetable/editors -I../../../src/interface/wavetable/overlays -I../../../src/standalone -I../../../src/synthesis/synth_engine -I../../../src/synthesis/effects -I../../../src/synthesis/filters -I../../../src/synthesis/framework -I../../../src/synthesis/lookups -I../../../src/synthesis/modulators -I../../../src/synthesis/modules -I../../../src/synthesis/producers -I../../../src/synthesis/utilities -I../../../third_party $(CPPFLAGS)
|
||||||
|
JUCE_CPPFLAGS_APP := "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=0" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_RTAS=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=0" "-DJucePlugin_Build_Unity=0"
|
||||||
|
JUCE_TARGET_APP := vital
|
||||||
|
|
||||||
|
JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -Ofast -flto -ffast-math ${SIMDFLAGS} ${GLFLAGS} -ftree-vectorize -ftree-slp-vectorize -funroll-loops $(CFLAGS)
|
||||||
|
JUCE_CXXFLAGS += $(JUCE_CFLAGS) -std=c++14 $(CXXFLAGS)
|
||||||
|
JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) -L/usr/X11R6/lib/ $(shell pkg-config --libs libcurl) -fvisibility=hidden -flto -ffast-math ${SIMDFLAGS} ${GLFLAGS} -ftree-vectorize -ftree-slp-vectorize -lcurl -lrt -ldl -lpthread $(LDFLAGS)
|
||||||
|
|
||||||
|
CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)
|
||||||
|
endif
|
||||||
|
|
||||||
|
OBJECTS_APP := \
|
||||||
|
$(JUCE_OBJDIR)/main_f0db04ea.o \
|
||||||
|
$(JUCE_OBJDIR)/common_24cbed85.o \
|
||||||
|
$(JUCE_OBJDIR)/synthesis_1ee447c4.o \
|
||||||
|
$(JUCE_OBJDIR)/include_juce_audio_basics_8a4e984a.o \
|
||||||
|
$(JUCE_OBJDIR)/include_juce_audio_formats_15f82001.o \
|
||||||
|
$(JUCE_OBJDIR)/include_juce_core_f26d17db.o \
|
||||||
|
$(JUCE_OBJDIR)/include_juce_data_structures_7471b1e3.o \
|
||||||
|
$(JUCE_OBJDIR)/include_juce_dsp_aeb2060f.o \
|
||||||
|
$(JUCE_OBJDIR)/include_juce_events_fd7d695.o \
|
||||||
|
|
||||||
|
.PHONY: clean all strip
|
||||||
|
|
||||||
|
all : $(JUCE_OUTDIR)/$(JUCE_TARGET_APP)
|
||||||
|
|
||||||
|
$(JUCE_OUTDIR)/$(JUCE_TARGET_APP) : $(OBJECTS_APP) $(RESOURCES)
|
||||||
|
@command -v pkg-config >/dev/null 2>&1 || { echo >&2 "pkg-config not installed. Please, install it."; exit 1; }
|
||||||
|
@pkg-config --print-errors libcurl
|
||||||
|
@echo Linking "Vital - App"
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_BINDIR)
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_LIBDIR)
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OUTDIR)
|
||||||
|
$(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_APP) $(OBJECTS_APP) $(JUCE_LDFLAGS) $(JUCE_LDFLAGS_APP) $(RESOURCES) $(TARGET_ARCH)
|
||||||
|
|
||||||
|
$(JUCE_OBJDIR)/main_f0db04ea.o: ../../../src/headless/main.cpp
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
|
||||||
|
@echo "Compiling main.cpp"
|
||||||
|
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
|
||||||
|
|
||||||
|
$(JUCE_OBJDIR)/common_24cbed85.o: ../../../src/unity_build/common.cpp
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
|
||||||
|
@echo "Compiling common.cpp"
|
||||||
|
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
|
||||||
|
|
||||||
|
$(JUCE_OBJDIR)/synthesis_1ee447c4.o: ../../../src/unity_build/synthesis.cpp
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
|
||||||
|
@echo "Compiling synthesis.cpp"
|
||||||
|
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
|
||||||
|
|
||||||
|
$(JUCE_OBJDIR)/include_juce_audio_basics_8a4e984a.o: ../../JuceLibraryCode/include_juce_audio_basics.cpp
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
|
||||||
|
@echo "Compiling include_juce_audio_basics.cpp"
|
||||||
|
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
|
||||||
|
|
||||||
|
$(JUCE_OBJDIR)/include_juce_audio_formats_15f82001.o: ../../JuceLibraryCode/include_juce_audio_formats.cpp
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
|
||||||
|
@echo "Compiling include_juce_audio_formats.cpp"
|
||||||
|
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
|
||||||
|
|
||||||
|
$(JUCE_OBJDIR)/include_juce_core_f26d17db.o: ../../JuceLibraryCode/include_juce_core.cpp
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
|
||||||
|
@echo "Compiling include_juce_core.cpp"
|
||||||
|
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
|
||||||
|
|
||||||
|
$(JUCE_OBJDIR)/include_juce_data_structures_7471b1e3.o: ../../JuceLibraryCode/include_juce_data_structures.cpp
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
|
||||||
|
@echo "Compiling include_juce_data_structures.cpp"
|
||||||
|
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
|
||||||
|
|
||||||
|
$(JUCE_OBJDIR)/include_juce_dsp_aeb2060f.o: ../../JuceLibraryCode/include_juce_dsp.cpp
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
|
||||||
|
@echo "Compiling include_juce_dsp.cpp"
|
||||||
|
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
|
||||||
|
|
||||||
|
$(JUCE_OBJDIR)/include_juce_events_fd7d695.o: ../../JuceLibraryCode/include_juce_events.cpp
|
||||||
|
-$(V_AT)mkdir -p $(JUCE_OBJDIR)
|
||||||
|
@echo "Compiling include_juce_events.cpp"
|
||||||
|
$(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo Cleaning Vital
|
||||||
|
$(V_AT)$(CLEANCMD)
|
||||||
|
|
||||||
|
strip:
|
||||||
|
@echo Stripping Vital
|
||||||
|
-$(V_AT)$(STRIP) --strip-unneeded $(JUCE_OUTDIR)/$(TARGET)
|
||||||
|
|
||||||
|
-include $(OBJECTS_APP:%.o=%.d)
|
||||||
29
headless/builds/osx/Info-App.plist
Normal file
29
headless/builds/osx/Info-App.plist
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>${EXECUTABLE_NAME}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string></string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>org.tytel.vital</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>Vital</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Vital</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>99999.9.9</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>99999.9.9</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Matt Tytel</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
headless/builds/osx/RecentFilesMenuTemplate.nib
generated
Normal file
BIN
headless/builds/osx/RecentFilesMenuTemplate.nib
generated
Normal file
Binary file not shown.
6
headless/builds/osx/Vital.entitlements
Normal file
6
headless/builds/osx/Vital.entitlements
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
2387
headless/builds/osx/Vital.xcodeproj/project.pbxproj
Normal file
2387
headless/builds/osx/Vital.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load diff
396
headless/vital.jucer
Normal file
396
headless/vital.jucer
Normal file
|
|
@ -0,0 +1,396 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<JUCERPROJECT id="GfsdNK" name="Vital" projectType="guiapp" version="99999.9.9"
|
||||||
|
bundleIdentifier="org.tytel.vital" includeBinaryInAppConfig="1"
|
||||||
|
companyName="Matt Tytel" companyWebsite="vital.audio" companyEmail="matthewtytel@gmail.com"
|
||||||
|
defines="" displaySplashScreen="0" reportAppUsage="0" splashScreenColour="Dark"
|
||||||
|
cppLanguageStandard="14" companyCopyright="Matt Tytel" jucerFormatVersion="1">
|
||||||
|
<MAINGROUP id="CeypXq" name="Vital">
|
||||||
|
<GROUP id="{5E20F1A0-5E75-7060-2F6C-FA57FB1890B9}" name="src">
|
||||||
|
<GROUP id="{24238426-E22D-9B0B-53E8-F1FE2E36A406}" name="common">
|
||||||
|
<GROUP id="{4ABC3884-D1B2-B9F8-BBBE-13809C729B01}" name="wavetable">
|
||||||
|
<FILE id="oe6zDB" name="file_source.cpp" compile="0" resource="0" file="../src/common/wavetable/file_source.cpp"/>
|
||||||
|
<FILE id="PNqFcj" name="file_source.h" compile="0" resource="0" file="../src/common/wavetable/file_source.h"/>
|
||||||
|
<FILE id="KdwN2l" name="frequency_filter_modifier.cpp" compile="0"
|
||||||
|
resource="0" file="../src/common/wavetable/frequency_filter_modifier.cpp"/>
|
||||||
|
<FILE id="jUH4x2" name="frequency_filter_modifier.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/frequency_filter_modifier.h"/>
|
||||||
|
<FILE id="EeWkLu" name="phase_modifier.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/phase_modifier.cpp"/>
|
||||||
|
<FILE id="evrRrr" name="phase_modifier.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/phase_modifier.h"/>
|
||||||
|
<FILE id="EE6gxY" name="pitch_detector.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/pitch_detector.cpp"/>
|
||||||
|
<FILE id="ICq0kR" name="pitch_detector.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/pitch_detector.h"/>
|
||||||
|
<FILE id="eanv2x" name="shepard_tone_source.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/shepard_tone_source.cpp"/>
|
||||||
|
<FILE id="r9ixAB" name="shepard_tone_source.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/shepard_tone_source.h"/>
|
||||||
|
<FILE id="NSw3FK" name="slew_limit_modifier.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/slew_limit_modifier.cpp"/>
|
||||||
|
<FILE id="oY7wsX" name="slew_limit_modifier.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/slew_limit_modifier.h"/>
|
||||||
|
<FILE id="xezXym" name="wave_fold_modifier.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wave_fold_modifier.cpp"/>
|
||||||
|
<FILE id="mbOjU5" name="wave_fold_modifier.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wave_fold_modifier.h"/>
|
||||||
|
<FILE id="xXN8oh" name="wave_line_source.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wave_line_source.cpp"/>
|
||||||
|
<FILE id="SqTW26" name="wave_line_source.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wave_line_source.h"/>
|
||||||
|
<FILE id="Trqfzt" name="wave_source.cpp" compile="0" resource="0" file="../src/common/wavetable/wave_source.cpp"/>
|
||||||
|
<FILE id="LnOIlK" name="wave_source.h" compile="0" resource="0" file="../src/common/wavetable/wave_source.h"/>
|
||||||
|
<FILE id="S5enhN" name="wave_warp_modifier.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wave_warp_modifier.cpp"/>
|
||||||
|
<FILE id="txcV3N" name="wave_warp_modifier.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wave_warp_modifier.h"/>
|
||||||
|
<FILE id="sSIrCT" name="wave_window_modifier.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wave_window_modifier.cpp"/>
|
||||||
|
<FILE id="QpecXl" name="wave_window_modifier.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wave_window_modifier.h"/>
|
||||||
|
<FILE id="BBS3SC" name="wavetable_component.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wavetable_component.cpp"/>
|
||||||
|
<FILE id="GjVmR5" name="wavetable_component.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wavetable_component.h"/>
|
||||||
|
<FILE id="EC6VRW" name="wavetable_component_factory.cpp" compile="0"
|
||||||
|
resource="0" file="../src/common/wavetable/wavetable_component_factory.cpp"/>
|
||||||
|
<FILE id="mLuOfy" name="wavetable_component_factory.h" compile="0"
|
||||||
|
resource="0" file="../src/common/wavetable/wavetable_component_factory.h"/>
|
||||||
|
<FILE id="lIeQfH" name="wavetable_creator.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wavetable_creator.cpp"/>
|
||||||
|
<FILE id="xrhpt4" name="wavetable_creator.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wavetable_creator.h"/>
|
||||||
|
<FILE id="ttfQpv" name="wavetable_group.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wavetable_group.cpp"/>
|
||||||
|
<FILE id="i84L1E" name="wavetable_group.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wavetable_group.h"/>
|
||||||
|
<FILE id="loSZ0a" name="wavetable_keyframe.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wavetable_keyframe.cpp"/>
|
||||||
|
<FILE id="gncjoq" name="wavetable_keyframe.h" compile="0" resource="0"
|
||||||
|
file="../src/common/wavetable/wavetable_keyframe.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<FILE id="kZoVCz" name="border_bounds_constrainer.cpp" compile="0"
|
||||||
|
resource="0" file="../src/common/border_bounds_constrainer.cpp"/>
|
||||||
|
<FILE id="izwxRz" name="border_bounds_constrainer.h" compile="0" resource="0"
|
||||||
|
file="../src/common/border_bounds_constrainer.h"/>
|
||||||
|
<FILE id="JVTTVk" name="folder_browser.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/folder_browser.cpp"/>
|
||||||
|
<FILE id="KT9WHk" name="folder_browser.h" compile="0" resource="0"
|
||||||
|
file="../src/common/folder_browser.h"/>
|
||||||
|
<FILE id="O7P8do" name="fourier_transform.h" compile="0" resource="0"
|
||||||
|
file="../src/common/fourier_transform.h"/>
|
||||||
|
<FILE id="oqQjU3" name="line_generator.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/line_generator.cpp"/>
|
||||||
|
<FILE id="WochiB" name="line_generator.h" compile="0" resource="0"
|
||||||
|
file="../src/common/line_generator.h"/>
|
||||||
|
<FILE id="shXQuy" name="load_save.cpp" compile="0" resource="0" file="../src/common/load_save.cpp"/>
|
||||||
|
<FILE id="YsKDUQ" name="load_save.h" compile="0" resource="0" file="../src/common/load_save.h"/>
|
||||||
|
<FILE id="LN5QQ0" name="midi_manager.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/midi_manager.cpp"/>
|
||||||
|
<FILE id="sE0Jer" name="midi_manager.h" compile="0" resource="0" file="../src/common/midi_manager.h"/>
|
||||||
|
<FILE id="Xxn5pD" name="startup.cpp" compile="0" resource="0" file="../src/common/startup.cpp"/>
|
||||||
|
<FILE id="VY2QQ2" name="startup.h" compile="0" resource="0" file="../src/common/startup.h"/>
|
||||||
|
<FILE id="JLxUzB" name="synth_base.cpp" compile="0" resource="0" file="../src/common/synth_base.cpp"/>
|
||||||
|
<FILE id="FYbklc" name="synth_base.h" compile="0" resource="0" file="../src/common/synth_base.h"/>
|
||||||
|
<FILE id="pOB6Hr" name="synth_constants.h" compile="0" resource="0"
|
||||||
|
file="../src/common/synth_constants.h"/>
|
||||||
|
<FILE id="J88miL" name="synth_gui_interface.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/synth_gui_interface.cpp"/>
|
||||||
|
<FILE id="EURXvy" name="synth_gui_interface.h" compile="0" resource="0"
|
||||||
|
file="../src/common/synth_gui_interface.h"/>
|
||||||
|
<FILE id="V9u92v" name="synth_parameters.cpp" compile="0" resource="0"
|
||||||
|
file="../src/common/synth_parameters.cpp"/>
|
||||||
|
<FILE id="p1Q9zF" name="synth_parameters.h" compile="0" resource="0"
|
||||||
|
file="../src/common/synth_parameters.h"/>
|
||||||
|
<FILE id="uNVeO7" name="synth_types.cpp" compile="0" resource="0" file="../src/common/synth_types.cpp"/>
|
||||||
|
<FILE id="GjKj1E" name="synth_types.h" compile="0" resource="0" file="../src/common/synth_types.h"/>
|
||||||
|
<FILE id="xhXY3Q" name="tuning.cpp" compile="0" resource="0" file="../src/common/tuning.cpp"/>
|
||||||
|
<FILE id="hr0FmH" name="tuning.h" compile="0" resource="0" file="../src/common/tuning.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{994C5173-6686-7ED5-90AC-C96AACD31CB9}" name="headless">
|
||||||
|
<FILE id="sp5m0v" name="main.cpp" compile="1" resource="0" file="../src/headless/main.cpp"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{A5C9FACE-F05D-CF5D-CF7D-2B2E3AAAC5C6}" name="synthesis">
|
||||||
|
<GROUP id="{5CFAF50C-54C0-50C0-7CC6-12E5173CC110}" name="effects">
|
||||||
|
<FILE id="aCNwJd" name="compressor.cpp" compile="0" resource="0" file="../src/synthesis/effects/compressor.cpp"/>
|
||||||
|
<FILE id="m8TLhD" name="compressor.h" compile="0" resource="0" file="../src/synthesis/effects/compressor.h"/>
|
||||||
|
<FILE id="sxlSiK" name="delay.cpp" compile="0" resource="0" file="../src/synthesis/effects/delay.cpp"/>
|
||||||
|
<FILE id="kTeDfB" name="delay.h" compile="0" resource="0" file="../src/synthesis/effects/delay.h"/>
|
||||||
|
<FILE id="y8R5gV" name="distortion.cpp" compile="0" resource="0" file="../src/synthesis/effects/distortion.cpp"/>
|
||||||
|
<FILE id="lAtVZz" name="distortion.h" compile="0" resource="0" file="../src/synthesis/effects/distortion.h"/>
|
||||||
|
<FILE id="YXzEAf" name="phaser.cpp" compile="0" resource="0" file="../src/synthesis/effects/phaser.cpp"/>
|
||||||
|
<FILE id="v4goRR" name="phaser.h" compile="0" resource="0" file="../src/synthesis/effects/phaser.h"/>
|
||||||
|
<FILE id="CJ0cmj" name="reverb.cpp" compile="0" resource="0" file="../src/synthesis/effects/reverb.cpp"/>
|
||||||
|
<FILE id="Tevudl" name="reverb.h" compile="0" resource="0" file="../src/synthesis/effects/reverb.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{E64E341B-EC07-8E8D-EDA9-A409B614FDF7}" name="filters">
|
||||||
|
<FILE id="lPtPzS" name="comb_filter.cpp" compile="0" resource="0" file="../src/synthesis/filters/comb_filter.cpp"/>
|
||||||
|
<FILE id="j5EId2" name="comb_filter.h" compile="0" resource="0" file="../src/synthesis/filters/comb_filter.h"/>
|
||||||
|
<FILE id="RIzg4Y" name="dc_filter.cpp" compile="0" resource="0" file="../src/synthesis/filters/dc_filter.cpp"/>
|
||||||
|
<FILE id="XpqKwH" name="dc_filter.h" compile="0" resource="0" file="../src/synthesis/filters/dc_filter.h"/>
|
||||||
|
<FILE id="y50aDm" name="decimator.cpp" compile="0" resource="0" file="../src/synthesis/filters/decimator.cpp"/>
|
||||||
|
<FILE id="MgJKf7" name="decimator.h" compile="0" resource="0" file="../src/synthesis/filters/decimator.h"/>
|
||||||
|
<FILE id="Jh9lo5" name="digital_svf.cpp" compile="0" resource="0" file="../src/synthesis/filters/digital_svf.cpp"/>
|
||||||
|
<FILE id="Hz7pGs" name="digital_svf.h" compile="0" resource="0" file="../src/synthesis/filters/digital_svf.h"/>
|
||||||
|
<FILE id="Efu2pW" name="diode_filter.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/diode_filter.cpp"/>
|
||||||
|
<FILE id="R8kJxY" name="diode_filter.h" compile="0" resource="0" file="../src/synthesis/filters/diode_filter.h"/>
|
||||||
|
<FILE id="fRKKE7" name="dirty_filter.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/dirty_filter.cpp"/>
|
||||||
|
<FILE id="Yhizwh" name="dirty_filter.h" compile="0" resource="0" file="../src/synthesis/filters/dirty_filter.h"/>
|
||||||
|
<FILE id="iFOzQV" name="fir_halfband_decimator.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/fir_halfband_decimator.cpp"/>
|
||||||
|
<FILE id="Gv6EpA" name="fir_halfband_decimator.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/fir_halfband_decimator.h"/>
|
||||||
|
<FILE id="TmxuEz" name="formant_filter.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/formant_filter.cpp"/>
|
||||||
|
<FILE id="EMpV5T" name="formant_filter.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/formant_filter.h"/>
|
||||||
|
<FILE id="rzspcD" name="formant_manager.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/formant_manager.cpp"/>
|
||||||
|
<FILE id="EhTc2y" name="formant_manager.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/formant_manager.h"/>
|
||||||
|
<FILE id="w1i83Y" name="iir_halfband_decimator.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/iir_halfband_decimator.cpp"/>
|
||||||
|
<FILE id="ZfvMzK" name="iir_halfband_decimator.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/iir_halfband_decimator.h"/>
|
||||||
|
<FILE id="QJw5bc" name="ladder_filter.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/ladder_filter.cpp"/>
|
||||||
|
<FILE id="XlAdkz" name="ladder_filter.h" compile="0" resource="0" file="../src/synthesis/filters/ladder_filter.h"/>
|
||||||
|
<FILE id="CMjLtN" name="linkwitz_riley_filter.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/linkwitz_riley_filter.cpp"/>
|
||||||
|
<FILE id="ShoZK2" name="linkwitz_riley_filter.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/linkwitz_riley_filter.h"/>
|
||||||
|
<FILE id="u7SfJu" name="one_pole_filter.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/one_pole_filter.h"/>
|
||||||
|
<FILE id="TN4e3F" name="phaser_filter.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/phaser_filter.cpp"/>
|
||||||
|
<FILE id="CXTqre" name="phaser_filter.h" compile="0" resource="0" file="../src/synthesis/filters/phaser_filter.h"/>
|
||||||
|
<FILE id="dXxyVr" name="sallen_key_filter.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/sallen_key_filter.cpp"/>
|
||||||
|
<FILE id="Et5X2A" name="sallen_key_filter.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/sallen_key_filter.h"/>
|
||||||
|
<FILE id="cq1Bv6" name="synth_filter.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/filters/synth_filter.cpp"/>
|
||||||
|
<FILE id="EEmVZX" name="synth_filter.h" compile="0" resource="0" file="../src/synthesis/filters/synth_filter.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{77B6F61E-3BFE-28DD-28BB-9F3780938AC4}" name="framework">
|
||||||
|
<FILE id="qHGm97" name="circular_queue.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/framework/circular_queue.h"/>
|
||||||
|
<FILE id="HmdVGQ" name="common.h" compile="0" resource="0" file="../src/synthesis/framework/common.h"/>
|
||||||
|
<FILE id="IgLqPT" name="feedback.cpp" compile="0" resource="0" file="../src/synthesis/framework/feedback.cpp"/>
|
||||||
|
<FILE id="birmLJ" name="feedback.h" compile="0" resource="0" file="../src/synthesis/framework/feedback.h"/>
|
||||||
|
<FILE id="f7K13U" name="futils.h" compile="0" resource="0" file="../src/synthesis/framework/futils.h"/>
|
||||||
|
<FILE id="iCcsYn" name="matrix.h" compile="0" resource="0" file="../src/synthesis/framework/matrix.h"/>
|
||||||
|
<FILE id="OjPY4Y" name="note_handler.h" compile="0" resource="0" file="../src/synthesis/framework/note_handler.h"/>
|
||||||
|
<FILE id="ttUKze" name="operators.cpp" compile="0" resource="0" file="../src/synthesis/framework/operators.cpp"/>
|
||||||
|
<FILE id="iFcCHi" name="operators.h" compile="0" resource="0" file="../src/synthesis/framework/operators.h"/>
|
||||||
|
<FILE id="xFsi2z" name="poly_utils.h" compile="0" resource="0" file="../src/synthesis/framework/poly_utils.h"/>
|
||||||
|
<FILE id="rx7EqI" name="poly_values.h" compile="0" resource="0" file="../src/synthesis/framework/poly_values.h"/>
|
||||||
|
<FILE id="IWVKrn" name="processor.cpp" compile="0" resource="0" file="../src/synthesis/framework/processor.cpp"/>
|
||||||
|
<FILE id="yYEj6C" name="processor.h" compile="0" resource="0" file="../src/synthesis/framework/processor.h"/>
|
||||||
|
<FILE id="pEikV1" name="processor_router.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/framework/processor_router.cpp"/>
|
||||||
|
<FILE id="xjyJUA" name="processor_router.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/framework/processor_router.h"/>
|
||||||
|
<FILE id="V2hnUG" name="synth_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/framework/synth_module.cpp"/>
|
||||||
|
<FILE id="LMO1qK" name="synth_module.h" compile="0" resource="0" file="../src/synthesis/framework/synth_module.h"/>
|
||||||
|
<FILE id="HtuRKh" name="utils.cpp" compile="0" resource="0" file="../src/synthesis/framework/utils.cpp"/>
|
||||||
|
<FILE id="JIQPrc" name="utils.h" compile="0" resource="0" file="../src/synthesis/framework/utils.h"/>
|
||||||
|
<FILE id="gXRMaO" name="value.cpp" compile="0" resource="0" file="../src/synthesis/framework/value.cpp"/>
|
||||||
|
<FILE id="hq4ULs" name="value.h" compile="0" resource="0" file="../src/synthesis/framework/value.h"/>
|
||||||
|
<FILE id="IHvsNC" name="voice_handler.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/framework/voice_handler.cpp"/>
|
||||||
|
<FILE id="VQpRmA" name="voice_handler.h" compile="0" resource="0" file="../src/synthesis/framework/voice_handler.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{3DA70314-F7FB-917E-089C-A6DAFFF1A5FC}" name="lookups">
|
||||||
|
<FILE id="sXc1yd" name="lookup_table.h" compile="0" resource="0" file="../src/synthesis/lookups/lookup_table.h"/>
|
||||||
|
<FILE id="avsD8m" name="memory.h" compile="0" resource="0" file="../src/synthesis/lookups/memory.h"/>
|
||||||
|
<FILE id="PJfaJL" name="wave_frame.cpp" compile="0" resource="0" file="../src/synthesis/lookups/wave_frame.cpp"/>
|
||||||
|
<FILE id="ocfU1s" name="wave_frame.h" compile="0" resource="0" file="../src/synthesis/lookups/wave_frame.h"/>
|
||||||
|
<FILE id="IBeYrU" name="wavetable.cpp" compile="0" resource="0" file="../src/synthesis/lookups/wavetable.cpp"/>
|
||||||
|
<FILE id="Fc1QKT" name="wavetable.h" compile="0" resource="0" file="../src/synthesis/lookups/wavetable.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{D78B1446-F592-53F1-FC62-7D5C966375F2}" name="modulators">
|
||||||
|
<FILE id="XonX7g" name="envelope.cpp" compile="0" resource="0" file="../src/synthesis/modulators/envelope.cpp"/>
|
||||||
|
<FILE id="MLCOkP" name="envelope.h" compile="0" resource="0" file="../src/synthesis/modulators/envelope.h"/>
|
||||||
|
<FILE id="PSmy36" name="line_map.cpp" compile="0" resource="0" file="../src/synthesis/modulators/line_map.cpp"/>
|
||||||
|
<FILE id="EcfIJt" name="line_map.h" compile="0" resource="0" file="../src/synthesis/modulators/line_map.h"/>
|
||||||
|
<FILE id="zJmtl5" name="random_lfo.cpp" compile="0" resource="0" file="../src/synthesis/modulators/random_lfo.cpp"/>
|
||||||
|
<FILE id="bo3lkH" name="random_lfo.h" compile="0" resource="0" file="../src/synthesis/modulators/random_lfo.h"/>
|
||||||
|
<FILE id="Y3a60G" name="synth_lfo.cpp" compile="0" resource="0" file="../src/synthesis/modulators/synth_lfo.cpp"/>
|
||||||
|
<FILE id="iGi53L" name="synth_lfo.h" compile="0" resource="0" file="../src/synthesis/modulators/synth_lfo.h"/>
|
||||||
|
<FILE id="vanrpw" name="trigger_random.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modulators/trigger_random.cpp"/>
|
||||||
|
<FILE id="HiTq3x" name="trigger_random.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modulators/trigger_random.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{7AD7C86B-0DF8-2FF3-4B00-48E56A12CDD6}" name="modules">
|
||||||
|
<FILE id="YvP9VT" name="chorus_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/chorus_module.cpp"/>
|
||||||
|
<FILE id="WvrPiI" name="chorus_module.h" compile="0" resource="0" file="../src/synthesis/modules/chorus_module.h"/>
|
||||||
|
<FILE id="uH4eoI" name="comb_module.cpp" compile="0" resource="0" file="../src/synthesis/modules/comb_module.cpp"/>
|
||||||
|
<FILE id="mfYz3m" name="comb_module.h" compile="0" resource="0" file="../src/synthesis/modules/comb_module.h"/>
|
||||||
|
<FILE id="a4bXu6" name="compressor_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/compressor_module.cpp"/>
|
||||||
|
<FILE id="Lm66nP" name="compressor_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/compressor_module.h"/>
|
||||||
|
<FILE id="NOmCK3" name="delay_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/delay_module.cpp"/>
|
||||||
|
<FILE id="cJah2Y" name="delay_module.h" compile="0" resource="0" file="../src/synthesis/modules/delay_module.h"/>
|
||||||
|
<FILE id="AuUpM4" name="distortion_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/distortion_module.cpp"/>
|
||||||
|
<FILE id="xE2cXp" name="distortion_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/distortion_module.h"/>
|
||||||
|
<FILE id="GH1YXC" name="envelope_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/envelope_module.cpp"/>
|
||||||
|
<FILE id="g0ZUlf" name="envelope_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/envelope_module.h"/>
|
||||||
|
<FILE id="eenLYS" name="equalizer_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/equalizer_module.cpp"/>
|
||||||
|
<FILE id="P895N1" name="equalizer_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/equalizer_module.h"/>
|
||||||
|
<FILE id="L2p4rV" name="filter_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/filter_module.cpp"/>
|
||||||
|
<FILE id="JRQR6A" name="filter_module.h" compile="0" resource="0" file="../src/synthesis/modules/filter_module.h"/>
|
||||||
|
<FILE id="caguYj" name="filters_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/filters_module.cpp"/>
|
||||||
|
<FILE id="hSBDEw" name="filters_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/filters_module.h"/>
|
||||||
|
<FILE id="L5gTC7" name="flanger_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/flanger_module.cpp"/>
|
||||||
|
<FILE id="tkqRxb" name="flanger_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/flanger_module.h"/>
|
||||||
|
<FILE id="uR1p9q" name="formant_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/formant_module.cpp"/>
|
||||||
|
<FILE id="o2gMSE" name="formant_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/formant_module.h"/>
|
||||||
|
<FILE id="H1CKkp" name="lfo_module.cpp" compile="0" resource="0" file="../src/synthesis/modules/lfo_module.cpp"/>
|
||||||
|
<FILE id="wCtymg" name="lfo_module.h" compile="0" resource="0" file="../src/synthesis/modules/lfo_module.h"/>
|
||||||
|
<FILE id="Z6sUl0" name="modulation_connection_processor.cpp" compile="0"
|
||||||
|
resource="0" file="../src/synthesis/modules/modulation_connection_processor.cpp"/>
|
||||||
|
<FILE id="w5qtfY" name="modulation_connection_processor.h" compile="0"
|
||||||
|
resource="0" file="../src/synthesis/modules/modulation_connection_processor.h"/>
|
||||||
|
<FILE id="EhFVim" name="oscillator_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/oscillator_module.cpp"/>
|
||||||
|
<FILE id="RScZyn" name="oscillator_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/oscillator_module.h"/>
|
||||||
|
<FILE id="ly3McA" name="phaser_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/phaser_module.cpp"/>
|
||||||
|
<FILE id="Bxt5OJ" name="phaser_module.h" compile="0" resource="0" file="../src/synthesis/modules/phaser_module.h"/>
|
||||||
|
<FILE id="eWReLf" name="producers_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/producers_module.cpp"/>
|
||||||
|
<FILE id="z5wG4V" name="producers_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/producers_module.h"/>
|
||||||
|
<FILE id="ag0jZx" name="random_lfo_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/random_lfo_module.cpp"/>
|
||||||
|
<FILE id="mjELIt" name="random_lfo_module.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/random_lfo_module.h"/>
|
||||||
|
<FILE id="d7nvCd" name="reorderable_effect_chain.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/reorderable_effect_chain.cpp"/>
|
||||||
|
<FILE id="hbv7nz" name="reorderable_effect_chain.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/reorderable_effect_chain.h"/>
|
||||||
|
<FILE id="vyagYY" name="reverb_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/reverb_module.cpp"/>
|
||||||
|
<FILE id="xfYwSE" name="reverb_module.h" compile="0" resource="0" file="../src/synthesis/modules/reverb_module.h"/>
|
||||||
|
<FILE id="y9GvZL" name="sample_module.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/modules/sample_module.cpp"/>
|
||||||
|
<FILE id="ayVUKi" name="sample_module.h" compile="0" resource="0" file="../src/synthesis/modules/sample_module.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{1F81E4BF-5696-7696-7E95-7AA0E119A748}" name="producers">
|
||||||
|
<FILE id="k2LDTh" name="sample_source.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/producers/sample_source.cpp"/>
|
||||||
|
<FILE id="mZfVF9" name="sample_source.h" compile="0" resource="0" file="../src/synthesis/producers/sample_source.h"/>
|
||||||
|
<FILE id="kyMr1d" name="synth_oscillator.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/producers/synth_oscillator.cpp"/>
|
||||||
|
<FILE id="nehC8Y" name="synth_oscillator.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/producers/synth_oscillator.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{48203804-B755-4600-7713-4492E108CDF6}" name="utilities">
|
||||||
|
<FILE id="Pile5u" name="legato_filter.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/utilities/legato_filter.cpp"/>
|
||||||
|
<FILE id="hTumTZ" name="legato_filter.h" compile="0" resource="0" file="../src/synthesis/utilities/legato_filter.h"/>
|
||||||
|
<FILE id="E8XJvz" name="peak_meter.cpp" compile="0" resource="0" file="../src/synthesis/utilities/peak_meter.cpp"/>
|
||||||
|
<FILE id="fqz6b7" name="peak_meter.h" compile="0" resource="0" file="../src/synthesis/utilities/peak_meter.h"/>
|
||||||
|
<FILE id="nH8lht" name="portamento_slope.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/utilities/portamento_slope.cpp"/>
|
||||||
|
<FILE id="xminR9" name="portamento_slope.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/utilities/portamento_slope.h"/>
|
||||||
|
<FILE id="E9KdfW" name="smooth_value.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/utilities/smooth_value.cpp"/>
|
||||||
|
<FILE id="JKOlA3" name="smooth_value.h" compile="0" resource="0" file="../src/synthesis/utilities/smooth_value.h"/>
|
||||||
|
<FILE id="lB9jFO" name="value_switch.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/utilities/value_switch.cpp"/>
|
||||||
|
<FILE id="chFZdz" name="value_switch.h" compile="0" resource="0" file="../src/synthesis/utilities/value_switch.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<FILE id="onnaOK" name="synth_engine.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/synth_engine.cpp"/>
|
||||||
|
<FILE id="BGPvD1" name="synth_engine.h" compile="0" resource="0" file="../src/synthesis/synth_engine.h"/>
|
||||||
|
<FILE id="Hsedog" name="synth_voice_handler.cpp" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/synth_voice_handler.cpp"/>
|
||||||
|
<FILE id="PFriMv" name="synth_voice_handler.h" compile="0" resource="0"
|
||||||
|
file="../src/synthesis/synth_voice_handler.h"/>
|
||||||
|
</GROUP>
|
||||||
|
<GROUP id="{7156E271-CE4F-69F7-BD16-1AE4D1B568AC}" name="unity_build">
|
||||||
|
<FILE id="ykH5qq" name="common.cpp" compile="1" resource="0" file="../src/unity_build/common.cpp"/>
|
||||||
|
<FILE id="Hh2SQe" name="synthesis.cpp" compile="1" resource="0" file="../src/unity_build/synthesis.cpp"/>
|
||||||
|
</GROUP>
|
||||||
|
</GROUP>
|
||||||
|
</MAINGROUP>
|
||||||
|
<EXPORTFORMATS>
|
||||||
|
<LINUX_MAKE targetFolder="builds/linux" bigIcon="JqKIEw" smallIcon="oFf3hH"
|
||||||
|
extraCompilerFlags="-ffast-math ${SIMDFLAGS} ${GLFLAGS} -ftree-vectorize -ftree-slp-vectorize -funroll-loops"
|
||||||
|
extraLinkerFlags="-ffast-math ${SIMDFLAGS} ${GLFLAGS} -ftree-vectorize -ftree-slp-vectorize -lcurl"
|
||||||
|
extraDefs="BUILD_DATE=$(BUILD_DATE) JUCE_JACK_CLIENT_NAME="Vital" JUCE_ALSA_MIDI_INPUT_NAME="Vital" JUCE_ALSA_MIDI_OUTPUT_NAME="Vital" JUCE_USE_XRANDR=0 JUCE_DSP_USE_SHARED_FFTW=1 HEADLESS=1 NO_AUTH=1">
|
||||||
|
<CONFIGURATIONS>
|
||||||
|
<CONFIGURATION name="Debug" libraryPath="/usr/X11R6/lib/" isDebug="1" optimisation="1"
|
||||||
|
targetName="vital" headerPath="../../../src/common ../../../src/common/wavetable ../../../src/interface/editor_components ../../../src/interface/editor_sections ../../../src/interface/look_and_feel ../../../src/interface/wavetable ../../../src/interface/wavetable/editors ../../../src/interface/wavetable/overlays ../../../src/standalone ../../../src/synthesis/synth_engine ../../../src/synthesis/effects ../../../src/synthesis/filters ../../../src/synthesis/framework ../../../src/synthesis/lookups ../../../src/synthesis/modulators ../../../src/synthesis/modules ../../../src/synthesis/producers ../../../src/synthesis/utilities ../../../third_party"
|
||||||
|
linuxArchitecture="" defines=""/>
|
||||||
|
<CONFIGURATION name="Release" libraryPath="/usr/X11R6/lib/" isDebug="0" optimisation="6"
|
||||||
|
targetName="vital" headerPath="../../../src/common ../../../src/common/wavetable ../../../src/interface/editor_components ../../../src/interface/editor_sections ../../../src/interface/look_and_feel ../../../src/interface/wavetable ../../../src/interface/wavetable/editors ../../../src/interface/wavetable/overlays ../../../src/standalone ../../../src/synthesis/synth_engine ../../../src/synthesis/effects ../../../src/synthesis/filters ../../../src/synthesis/framework ../../../src/synthesis/lookups ../../../src/synthesis/modulators ../../../src/synthesis/modules ../../../src/synthesis/producers ../../../src/synthesis/utilities ../../../third_party"
|
||||||
|
linuxArchitecture="" defines="" linkTimeOptimisation="1"/>
|
||||||
|
</CONFIGURATIONS>
|
||||||
|
<MODULEPATHS>
|
||||||
|
<MODULEPATH id="juce_core" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_events" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_data_structures" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_audio_basics" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_audio_formats" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_dsp" path="../third_party/JUCE/modules"/>
|
||||||
|
</MODULEPATHS>
|
||||||
|
</LINUX_MAKE>
|
||||||
|
<XCODE_MAC targetFolder="builds/osx" extraDefs="HEADLESS=1 NO_AUTH=1">
|
||||||
|
<CONFIGURATIONS>
|
||||||
|
<CONFIGURATION isDebug="1" name="Debug" headerPath="../../../src/common ../../../src/common/wavetable ../../../src/interface/editor_components ../../../src/interface/editor_sections ../../../src/interface/look_and_feel ../../../src/interface/wavetable ../../../src/interface/wavetable/editors ../../../src/interface/wavetable/overlays ../../../src/standalone ../../../src/synthesis/synth_engine ../../../src/synthesis/effects ../../../src/synthesis/filters ../../../src/synthesis/framework ../../../src/synthesis/lookups ../../../src/synthesis/modulators ../../../src/synthesis/modules ../../../src/synthesis/producers ../../../src/synthesis/utilities ../../../third_party"
|
||||||
|
osxCompatibility="10.7 SDK"/>
|
||||||
|
<CONFIGURATION isDebug="0" name="Release" headerPath="../../../src/common ../../../src/common/wavetable ../../../src/interface/editor_components ../../../src/interface/editor_sections ../../../src/interface/look_and_feel ../../../src/interface/wavetable ../../../src/interface/wavetable/editors ../../../src/interface/wavetable/overlays ../../../src/standalone ../../../src/synthesis/synth_engine ../../../src/synthesis/effects ../../../src/synthesis/filters ../../../src/synthesis/framework ../../../src/synthesis/lookups ../../../src/synthesis/modulators ../../../src/synthesis/modules ../../../src/synthesis/producers ../../../src/synthesis/utilities ../../../third_party"
|
||||||
|
optimisation="6"/>
|
||||||
|
</CONFIGURATIONS>
|
||||||
|
<MODULEPATHS>
|
||||||
|
<MODULEPATH id="juce_events" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_dsp" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_data_structures" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_core" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_audio_formats" path="../third_party/JUCE/modules"/>
|
||||||
|
<MODULEPATH id="juce_audio_basics" path="../third_party/JUCE/modules"/>
|
||||||
|
</MODULEPATHS>
|
||||||
|
</XCODE_MAC>
|
||||||
|
</EXPORTFORMATS>
|
||||||
|
<MODULES>
|
||||||
|
<MODULES id="juce_audio_basics" showAllCode="1" useLocalCopy="0"/>
|
||||||
|
<MODULES id="juce_audio_formats" showAllCode="1" useLocalCopy="0"/>
|
||||||
|
<MODULES id="juce_core" showAllCode="1" useLocalCopy="0"/>
|
||||||
|
<MODULES id="juce_data_structures" showAllCode="1" useLocalCopy="0"/>
|
||||||
|
<MODULE id="juce_dsp" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/>
|
||||||
|
<MODULES id="juce_events" showAllCode="1" useLocalCopy="0"/>
|
||||||
|
</MODULES>
|
||||||
|
<JUCEOPTIONS JUCE_WASAPI="1" JUCE_DIRECTSOUND="1" JUCE_ALSA="1" JUCE_JACK="1"
|
||||||
|
JUCE_WEB_BROWSER="0" JUCE_USE_CURL="1"/>
|
||||||
|
<LIVE_SETTINGS>
|
||||||
|
<OSX/>
|
||||||
|
<WINDOWS/>
|
||||||
|
<LINUX/>
|
||||||
|
</LIVE_SETTINGS>
|
||||||
|
</JUCERPROJECT>
|
||||||
103
src/common/authentication.h
Normal file
103
src/common/authentication.h
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if NDEBUG && !NO_AUTH
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "load_save.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#include <firebase/app.h>
|
||||||
|
#include <firebase/auth.h>
|
||||||
|
#else
|
||||||
|
#include "firebase/app.h"
|
||||||
|
#include "firebase/auth.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class Authentication {
|
||||||
|
public:
|
||||||
|
static void onTokenRefreshResult(const firebase::Future<std::string>& completed_future, void* ref_data) {
|
||||||
|
const MessageManagerLock lock(Thread::getCurrentThread());
|
||||||
|
if (!lock.lockWasGained())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (completed_future.status() != firebase::kFutureStatusComplete) {
|
||||||
|
LoadSave::writeErrorLog("Firebase getting token error: not complete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completed_future.error()) {
|
||||||
|
std::string error = "Firebase getting token error: error code ";
|
||||||
|
LoadSave::writeErrorLog(error + std::to_string(completed_future.error()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication* reference = (Authentication*)ref_data;
|
||||||
|
reference->setToken(*completed_future.result());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create() {
|
||||||
|
if (firebase::App::GetInstance() != nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
firebase::AppOptions auth_app_options = firebase::AppOptions();
|
||||||
|
auth_app_options.set_app_id("");
|
||||||
|
auth_app_options.set_api_key("");
|
||||||
|
auth_app_options.set_project_id("");
|
||||||
|
|
||||||
|
firebase::App::Create(auth_app_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication() : auth_(nullptr) { }
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
if (auth_ == nullptr)
|
||||||
|
auth_ = firebase::auth::Auth::GetAuth(firebase::App::GetInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasAuth() const { return auth_ != nullptr; }
|
||||||
|
firebase::auth::Auth* auth() const { return auth_; }
|
||||||
|
void setToken(const std::string& token) { token_ = token; }
|
||||||
|
std::string token() const { return token_; }
|
||||||
|
bool loggedIn() { return auth_ && auth_->current_user() != nullptr; }
|
||||||
|
|
||||||
|
void refreshToken() {
|
||||||
|
if (auth_ == nullptr || auth_->current_user() == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
firebase::Future<std::string> future = auth_->current_user()->GetToken(false);
|
||||||
|
future.OnCompletion(onTokenRefreshResult, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
firebase::auth::Auth* auth_;
|
||||||
|
std::string token_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
class Authentication {
|
||||||
|
public:
|
||||||
|
static void create() { }
|
||||||
|
|
||||||
|
std::string token() { return ""; }
|
||||||
|
bool loggedIn() { return false; }
|
||||||
|
void refreshToken() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
66
src/common/border_bounds_constrainer.cpp
Normal file
66
src/common/border_bounds_constrainer.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "border_bounds_constrainer.h"
|
||||||
|
#include "full_interface.h"
|
||||||
|
#include "load_save.h"
|
||||||
|
#include "synth_gui_interface.h"
|
||||||
|
|
||||||
|
void BorderBoundsConstrainer::checkBounds(Rectangle<int>& bounds, const Rectangle<int>& previous,
|
||||||
|
const Rectangle<int>& limits,
|
||||||
|
bool stretching_top, bool stretching_left,
|
||||||
|
bool stretching_bottom, bool stretching_right) {
|
||||||
|
border_.subtractFrom(bounds);
|
||||||
|
double aspect_ratio = getFixedAspectRatio();
|
||||||
|
|
||||||
|
ComponentBoundsConstrainer::checkBounds(bounds, previous, limits,
|
||||||
|
stretching_top, stretching_left,
|
||||||
|
stretching_bottom, stretching_right);
|
||||||
|
|
||||||
|
Rectangle<int> display_area = Desktop::getInstance().getDisplays().getTotalBounds(true);
|
||||||
|
if (gui_) {
|
||||||
|
ComponentPeer* peer = gui_->getPeer();
|
||||||
|
if (peer)
|
||||||
|
peer->getFrameSize().subtractFrom(display_area);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display_area.getWidth() < bounds.getWidth()) {
|
||||||
|
int new_width = display_area.getWidth();
|
||||||
|
int new_height = std::round(new_width / aspect_ratio);
|
||||||
|
bounds.setWidth(new_width);
|
||||||
|
bounds.setHeight(new_height);
|
||||||
|
}
|
||||||
|
if (display_area.getHeight() < bounds.getHeight()) {
|
||||||
|
int new_height = display_area.getHeight();
|
||||||
|
int new_width = std::round(new_height * aspect_ratio);
|
||||||
|
bounds.setWidth(new_width);
|
||||||
|
bounds.setHeight(new_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
border_.addTo(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BorderBoundsConstrainer::resizeStart() {
|
||||||
|
if (gui_)
|
||||||
|
gui_->enableRedoBackground(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BorderBoundsConstrainer::resizeEnd() {
|
||||||
|
if (gui_) {
|
||||||
|
LoadSave::saveWindowSize(gui_->getWidth() / (1.0f * vital::kDefaultWindowWidth));
|
||||||
|
gui_->enableRedoBackground(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/common/border_bounds_constrainer.h
Normal file
44
src/common/border_bounds_constrainer.h
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
class FullInterface;
|
||||||
|
|
||||||
|
class BorderBoundsConstrainer : public ComponentBoundsConstrainer {
|
||||||
|
public:
|
||||||
|
BorderBoundsConstrainer() : ComponentBoundsConstrainer(), gui_(nullptr) { }
|
||||||
|
|
||||||
|
virtual void checkBounds(Rectangle<int>& bounds, const Rectangle<int>& previous,
|
||||||
|
const Rectangle<int>& limits,
|
||||||
|
bool stretching_top, bool stretching_left,
|
||||||
|
bool stretching_bottom, bool stretching_right) override;
|
||||||
|
|
||||||
|
virtual void resizeStart() override;
|
||||||
|
virtual void resizeEnd() override;
|
||||||
|
|
||||||
|
void setBorder(const BorderSize<int>& border) { border_ = border; }
|
||||||
|
void setGui(FullInterface* gui) { gui_ = gui; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FullInterface* gui_;
|
||||||
|
BorderSize<int> border_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(BorderBoundsConstrainer)
|
||||||
|
};
|
||||||
|
|
||||||
187
src/common/fourier_transform.h
Normal file
187
src/common/fourier_transform.h
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
#if INTEL_IPP
|
||||||
|
|
||||||
|
#include "ipps.h"
|
||||||
|
|
||||||
|
class FourierTransform {
|
||||||
|
public:
|
||||||
|
FourierTransform(int bits) : size_(1 << bits) {
|
||||||
|
int spec_size = 0;
|
||||||
|
int spec_buffer_size = 0;
|
||||||
|
int buffer_size = 0;
|
||||||
|
ippsFFTGetSize_R_32f(bits, IPP_FFT_DIV_INV_BY_N, ippAlgHintNone, &spec_size, &spec_buffer_size, &buffer_size);
|
||||||
|
|
||||||
|
spec_ = std::make_unique<Ipp8u[]>(spec_size);
|
||||||
|
spec_buffer_ = std::make_unique<Ipp8u[]>(spec_buffer_size);
|
||||||
|
buffer_ = std::make_unique<Ipp8u[]>(buffer_size);
|
||||||
|
|
||||||
|
ippsFFTInit_R_32f(&ipp_specs_, bits, IPP_FFT_DIV_INV_BY_N, ippAlgHintNone, spec_.get(), spec_buffer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void transformRealForward(float* data) {
|
||||||
|
data[size_] = 0.0f;
|
||||||
|
ippsFFTFwd_RToPerm_32f_I((Ipp32f*)data, ipp_specs_, buffer_.get());
|
||||||
|
data[size_] = data[1];
|
||||||
|
data[size_ + 1] = 0.0f;
|
||||||
|
data[1] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void transformRealInverse(float* data) {
|
||||||
|
data[1] = data[size_];
|
||||||
|
ippsFFTInv_PermToR_32f_I((Ipp32f*)data, ipp_specs_, buffer_.get());
|
||||||
|
memset(data + size_, 0, size_ * sizeof(float));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int size_;
|
||||||
|
IppsFFTSpec_R_32f *ipp_specs_;
|
||||||
|
std::unique_ptr<Ipp8u[]> spec_;
|
||||||
|
std::unique_ptr<Ipp8u[]> spec_buffer_;
|
||||||
|
std::unique_ptr<Ipp8u[]> buffer_;
|
||||||
|
|
||||||
|
JUCE_LEAK_DETECTOR(FourierTransform)
|
||||||
|
};
|
||||||
|
|
||||||
|
#elif JUCE_MODULE_AVAILABLE_juce_dsp
|
||||||
|
|
||||||
|
class FourierTransform {
|
||||||
|
public:
|
||||||
|
FourierTransform(int bits) : fft_(bits) { }
|
||||||
|
|
||||||
|
void transformRealForward(float* data) { fft_.performRealOnlyForwardTransform(data, true); }
|
||||||
|
void transformRealInverse(float* data) { fft_.performRealOnlyInverseTransform(data); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
dsp::FFT fft_;
|
||||||
|
|
||||||
|
JUCE_LEAK_DETECTOR(FourierTransform)
|
||||||
|
};
|
||||||
|
|
||||||
|
#elif __APPLE__
|
||||||
|
#define VIMAGE_H
|
||||||
|
#include <Accelerate/Accelerate.h>
|
||||||
|
|
||||||
|
class FourierTransform {
|
||||||
|
public:
|
||||||
|
FourierTransform(vDSP_Length bits) : setup_(vDSP_create_fftsetup(bits, 2)), bits_(bits), size_(1 << bits) { }
|
||||||
|
~FourierTransform() {
|
||||||
|
vDSP_destroy_fftsetup(setup_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void transformRealForward(float* data) {
|
||||||
|
static const float kMult = 0.5f;
|
||||||
|
data[size_] = 0.0f;
|
||||||
|
DSPSplitComplex split = { data, data + 1 };
|
||||||
|
vDSP_fft_zrip(setup_, &split, 2, bits_, kFFTDirection_Forward);
|
||||||
|
vDSP_vsmul(data, 1, &kMult, data, 1, size_);
|
||||||
|
|
||||||
|
data[size_] = data[1];
|
||||||
|
data[size_ + 1] = 0.0f;
|
||||||
|
data[1] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void transformRealInverse(float* data) {
|
||||||
|
float multiplier = 1.0f / size_;
|
||||||
|
DSPSplitComplex split = { data, data + 1 };
|
||||||
|
data[1] = data[size_];
|
||||||
|
|
||||||
|
vDSP_fft_zrip(setup_, &split, 2, bits_, kFFTDirection_Inverse);
|
||||||
|
vDSP_vsmul(data, 1, &multiplier, data, 1, size_ * 2);
|
||||||
|
memset(data + size_, 0, size_ * sizeof(float));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FFTSetup setup_;
|
||||||
|
vDSP_Length bits_;
|
||||||
|
vDSP_Length size_;
|
||||||
|
|
||||||
|
JUCE_LEAK_DETECTOR(FourierTransform)
|
||||||
|
};
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include "kissfft/kissfft.h"
|
||||||
|
|
||||||
|
class FourierTransform {
|
||||||
|
public:
|
||||||
|
FourierTransform(size_t bits) : bits_(bits), size_(1 << bits), forward_(size_, false), inverse_(size_, true) {
|
||||||
|
buffer_ = std::make_unique<std::complex<float>[]>(size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
~FourierTransform() { }
|
||||||
|
|
||||||
|
void transformRealForward(float* data) {
|
||||||
|
for (int i = size_ - 1; i >= 0; --i) {
|
||||||
|
data[2 * i] = data[i];
|
||||||
|
data[2 * i + 1] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_.transform((std::complex<float>*)data, buffer_.get());
|
||||||
|
|
||||||
|
int num_floats = size_ * 2;
|
||||||
|
memcpy(data, buffer_.get(), num_floats * sizeof(float));
|
||||||
|
data[size_] = data[1];
|
||||||
|
data[size_ + 1] = 0.0f;
|
||||||
|
data[1] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void transformRealInverse(float* data) {
|
||||||
|
data[0] *= 0.5f;
|
||||||
|
data[1] = data[size_];
|
||||||
|
inverse_.transform((std::complex<float>*)data, buffer_.get());
|
||||||
|
int num_floats = size_ * 2;
|
||||||
|
|
||||||
|
float multiplier = 2.0f / size_;
|
||||||
|
for (int i = 0; i < size_; ++i)
|
||||||
|
data[i] = buffer_[i].real() * multiplier;
|
||||||
|
|
||||||
|
memset(data + size_, 0, size_ * sizeof(float));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t bits_;
|
||||||
|
size_t size_;
|
||||||
|
std::unique_ptr<std::complex<float>[]> buffer_;
|
||||||
|
kissfft<float> forward_;
|
||||||
|
kissfft<float> inverse_;
|
||||||
|
|
||||||
|
JUCE_LEAK_DETECTOR(FourierTransform)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <size_t bits>
|
||||||
|
class FFT {
|
||||||
|
public:
|
||||||
|
static FourierTransform* transform() {
|
||||||
|
static FFT<bits> instance;
|
||||||
|
return &instance.fourier_transform_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FFT() : fourier_transform_(bits) { }
|
||||||
|
|
||||||
|
FourierTransform fourier_transform_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace vital
|
||||||
308
src/common/line_generator.cpp
Normal file
308
src/common/line_generator.cpp
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "line_generator.h"
|
||||||
|
#include "poly_utils.h"
|
||||||
|
#include "futils.h"
|
||||||
|
|
||||||
|
LineGenerator::LineGenerator(int resolution) : points_(), powers_(), num_points_(2), resolution_(resolution),
|
||||||
|
loop_(false), smooth_(false), linear_(true), render_count_(0) {
|
||||||
|
buffer_ = std::make_unique<vital::mono_float[]>(resolution + kExtraValues);
|
||||||
|
initLinear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::initLinear() {
|
||||||
|
points_[0] = { 0.0f, 1.0f };
|
||||||
|
points_[1] = { 1.0f, 0.0f };
|
||||||
|
powers_[0] = 0.0f;
|
||||||
|
powers_[1] = 0.0f;
|
||||||
|
num_points_ = 2;
|
||||||
|
linear_ = true;
|
||||||
|
name_ = "Linear";
|
||||||
|
smooth_ = false;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::initTriangle() {
|
||||||
|
points_[0] = { 0.0f, 1.0f };
|
||||||
|
points_[1] = { 0.5f, 0.0f };
|
||||||
|
points_[2] = { 1.0f, 1.0f };
|
||||||
|
powers_[0] = 0.0f;
|
||||||
|
powers_[1] = 0.0f;
|
||||||
|
powers_[2] = 0.0f;
|
||||||
|
num_points_ = 3;
|
||||||
|
linear_ = false;
|
||||||
|
name_ = "Triangle";
|
||||||
|
smooth_ = false;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::initSquare() {
|
||||||
|
num_points_ = 5;
|
||||||
|
points_[0] = { 0.0f, 1.0f };
|
||||||
|
points_[1] = { 0.0f, 0.0f };
|
||||||
|
points_[2] = { 0.5f, 0.0f };
|
||||||
|
points_[3] = { 0.5f, 1.0f };
|
||||||
|
points_[4] = { 1.0f, 1.0f };
|
||||||
|
|
||||||
|
for (int i = 0; i < num_points_; ++i)
|
||||||
|
powers_[i] = 0.0f;
|
||||||
|
|
||||||
|
linear_ = false;
|
||||||
|
name_ = "Square";
|
||||||
|
smooth_ = false;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::initSin() {
|
||||||
|
points_[0] = { 0.0f, 1.0f };
|
||||||
|
points_[1] = { 0.5f, 0.0f };
|
||||||
|
points_[2] = { 1.0f, 1.0f };
|
||||||
|
powers_[0] = 0.0f;
|
||||||
|
powers_[1] = 0.0f;
|
||||||
|
powers_[2] = 0.0f;
|
||||||
|
num_points_ = 3;
|
||||||
|
linear_ = false;
|
||||||
|
name_ = "Sin";
|
||||||
|
smooth_ = true;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::initSawUp() {
|
||||||
|
num_points_ = 3;
|
||||||
|
points_[0] = { 0.0f, 1.0f };
|
||||||
|
points_[1] = { 1.0f, 0.0f };
|
||||||
|
points_[2] = { 1.0f, 1.0f };
|
||||||
|
powers_[0] = 0.0f;
|
||||||
|
powers_[1] = 0.0f;
|
||||||
|
powers_[2] = 0.0f;
|
||||||
|
linear_ = false;
|
||||||
|
name_ = "Saw Up";
|
||||||
|
smooth_ = false;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::initSawDown() {
|
||||||
|
num_points_ = 3;
|
||||||
|
points_[0] = { 0.0f, 0.0f };
|
||||||
|
points_[1] = { 1.0f, 1.0f };
|
||||||
|
points_[2] = { 1.0f, 0.0f };
|
||||||
|
powers_[0] = 0.0f;
|
||||||
|
powers_[1] = 0.0f;
|
||||||
|
powers_[2] = 0.0f;
|
||||||
|
linear_ = false;
|
||||||
|
name_ = "Saw Down";
|
||||||
|
smooth_ = false;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
json LineGenerator::stateToJson() {
|
||||||
|
json point_data;
|
||||||
|
json power_data;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_points_; ++i) {
|
||||||
|
std::pair<float, float> point = points_[i];
|
||||||
|
point_data.push_back(point.first);
|
||||||
|
point_data.push_back(point.second);
|
||||||
|
power_data.push_back(powers_[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
json data;
|
||||||
|
data["num_points"] = num_points_;
|
||||||
|
data["points"] = point_data;
|
||||||
|
data["powers"] = power_data;
|
||||||
|
data["name"] = name_;
|
||||||
|
data["smooth"] = smooth_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LineGenerator::isValidJson(json data) {
|
||||||
|
if (!data.count("num_points") || !data.count("points") || !data.count("powers"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
json point_data = data["points"];
|
||||||
|
json power_data = data["powers"];
|
||||||
|
return point_data.is_array() && power_data.is_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::jsonToState(json data) {
|
||||||
|
num_points_ = data["num_points"];
|
||||||
|
json point_data = data["points"];
|
||||||
|
json power_data = data["powers"];
|
||||||
|
name_ = "";
|
||||||
|
if (data.count("name"))
|
||||||
|
name_ = data["name"].get<std::string>();
|
||||||
|
|
||||||
|
smooth_ = false;
|
||||||
|
if (data.count("smooth"))
|
||||||
|
smooth_ = data["smooth"];
|
||||||
|
|
||||||
|
int num_read = kMaxPoints;
|
||||||
|
num_read = std::min(num_read, static_cast<int>(power_data.size()));
|
||||||
|
|
||||||
|
for (int i = 0; i < num_read; ++i) {
|
||||||
|
points_[i] = std::pair<float, float>(point_data[2 * i], point_data[2 * i + 1]);
|
||||||
|
powers_[i] = power_data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
checkLineIsLinear();
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::render() {
|
||||||
|
render_count_++;
|
||||||
|
|
||||||
|
int point_index = 0;
|
||||||
|
std::pair<float, float> last_point = points_[point_index];
|
||||||
|
float current_power = 0.0f;
|
||||||
|
std::pair<float, float> current_point = points_[point_index];
|
||||||
|
if (loop_) {
|
||||||
|
last_point = points_[num_points_ - 1];
|
||||||
|
last_point.first -= 1.0f;
|
||||||
|
current_power = powers_[num_points_ - 1];
|
||||||
|
}
|
||||||
|
for (int i = 0; i < resolution_; ++i) {
|
||||||
|
float x = i / (resolution_ - 1.0f);
|
||||||
|
float t = 1.0f;
|
||||||
|
if (current_point.first > last_point.first)
|
||||||
|
t = (x - last_point.first) / (current_point.first - last_point.first);
|
||||||
|
|
||||||
|
if (smooth_)
|
||||||
|
t = smoothTransition(t);
|
||||||
|
|
||||||
|
t = vital::utils::clamp(vital::futils::powerScale(t, current_power), 0.0f, 1.0f);
|
||||||
|
|
||||||
|
float y = last_point.second + t * (current_point.second - last_point.second);
|
||||||
|
buffer_[i + 1] = 1.0f - y;
|
||||||
|
|
||||||
|
while (x > current_point.first && point_index < num_points_) {
|
||||||
|
current_power = powers_[point_index % num_points_];
|
||||||
|
point_index++;
|
||||||
|
last_point = current_point;
|
||||||
|
current_point = points_[point_index % num_points_];
|
||||||
|
if (point_index >= num_points_) {
|
||||||
|
current_point.first += 1.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loop_) {
|
||||||
|
buffer_[0] = buffer_[resolution_];
|
||||||
|
buffer_[resolution_ + 1] = buffer_[1];
|
||||||
|
buffer_[resolution_ + 2] = buffer_[2];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer_[0] = buffer_[1];
|
||||||
|
buffer_[resolution_ + 1] = buffer_[resolution_];
|
||||||
|
buffer_[resolution_ + 2] = buffer_[resolution_];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float LineGenerator::valueAtPhase(float phase) {
|
||||||
|
float scaled_phase = vital::utils::clamp(phase, 0.0f, 1.0f) * resolution_;
|
||||||
|
int index = scaled_phase;
|
||||||
|
return vital::utils::interpolate(buffer_[index + 1], buffer_[index + 2], scaled_phase - index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::checkLineIsLinear() {
|
||||||
|
linear_ = !smooth_ && num_points_ == 2 && powers_[0] == 0.0f &&
|
||||||
|
points_[0] == std::pair<float, float>(0.0f, 1.0f) &&
|
||||||
|
points_[1] == std::pair<float, float>(1.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float LineGenerator::getValueBetweenPoints(float x, int index_from, int index_to) {
|
||||||
|
VITAL_ASSERT(index_from >= 0 && index_to < num_points_);
|
||||||
|
|
||||||
|
std::pair<float, float> first = points_[index_from];
|
||||||
|
std::pair<float, float> second = points_[index_to];
|
||||||
|
float power = powers_[index_from];
|
||||||
|
|
||||||
|
float width = second.first - first.first;
|
||||||
|
if (width <= 0.0f)
|
||||||
|
return second.second;
|
||||||
|
|
||||||
|
float t = (x - first.first) / width;
|
||||||
|
if (smooth_)
|
||||||
|
t = smoothTransition(t);
|
||||||
|
|
||||||
|
t = vital::utils::clamp(vital::futils::powerScale(t, power), 0.0f, 1.0f);
|
||||||
|
return t * (second.second - first.second) + first.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LineGenerator::getValueAtPhase(float phase) {
|
||||||
|
for (int i = 0; i < num_points_ - 1; ++i) {
|
||||||
|
if (points_[i].first <= phase && points_[i + 1].first >= phase)
|
||||||
|
return getValueBetweenPoints(phase, i, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastPoint().second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::addPoint(int index, std::pair<float, float> position) {
|
||||||
|
for (int i = num_points_; i > index; --i) {
|
||||||
|
points_[i] = points_[i - 1];
|
||||||
|
powers_[i] = powers_[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
num_points_++;
|
||||||
|
points_[index] = position;
|
||||||
|
powers_[index] = 0.0f;
|
||||||
|
checkLineIsLinear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::addMiddlePoint(int index) {
|
||||||
|
VITAL_ASSERT(index > 0);
|
||||||
|
|
||||||
|
float x = (points_[index - 1].first + points_[index].first) * 0.5f;
|
||||||
|
float y = getValueBetweenPoints(x, index - 1, index);
|
||||||
|
addPoint(index, { x, y });
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::removePoint(int index) {
|
||||||
|
num_points_--;
|
||||||
|
for (int i = index; i < num_points_; ++i) {
|
||||||
|
points_[i] = points_[i + 1];
|
||||||
|
powers_[i] = powers_[i + 1];
|
||||||
|
}
|
||||||
|
checkLineIsLinear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::flipHorizontal() {
|
||||||
|
for (int i = 0; i < (num_points_ + 1) / 2; ++i) {
|
||||||
|
float tmp_x = 1.0f - points_[i].first;
|
||||||
|
float tmp_y = points_[i].second;
|
||||||
|
points_[i].first = 1.0f - points_[num_points_ - i - 1].first;
|
||||||
|
points_[i].second = points_[num_points_ - i - 1].second;
|
||||||
|
points_[num_points_ - i - 1].first = tmp_x;
|
||||||
|
points_[num_points_ - i - 1].second = tmp_y;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < num_points_ / 2; ++i) {
|
||||||
|
float tmp_power = powers_[i];
|
||||||
|
powers_[i] = -powers_[num_points_ - i - 2];
|
||||||
|
powers_[num_points_ - i - 2] = -tmp_power;
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
checkLineIsLinear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineGenerator::flipVertical() {
|
||||||
|
for (int i = 0; i < num_points_; ++i)
|
||||||
|
points_[i].second = 1.0f - points_[i].second;
|
||||||
|
|
||||||
|
render();
|
||||||
|
checkLineIsLinear();
|
||||||
|
}
|
||||||
126
src/common/line_generator.h
Normal file
126
src/common/line_generator.h
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "json/json.h"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
class LineGenerator {
|
||||||
|
public:
|
||||||
|
static constexpr int kMaxPoints = 100;
|
||||||
|
static constexpr int kDefaultResolution = 2048;
|
||||||
|
static constexpr int kExtraValues = 3;
|
||||||
|
|
||||||
|
static force_inline float smoothTransition(float t) {
|
||||||
|
return 0.5f * sinf((t - 0.5f) * vital::kPi) + 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineGenerator(int resolution = kDefaultResolution);
|
||||||
|
virtual ~LineGenerator() { }
|
||||||
|
|
||||||
|
void setLoop(bool loop) { loop_ = loop; render(); }
|
||||||
|
void setName(const std::string& name) { name_ = name; }
|
||||||
|
void setLastBrowsedFile(const std::string& path) { last_browsed_file_ = path; }
|
||||||
|
void setSmooth(bool smooth) { smooth_ = smooth; checkLineIsLinear(); render(); }
|
||||||
|
void initLinear();
|
||||||
|
void initTriangle();
|
||||||
|
void initSquare();
|
||||||
|
void initSin();
|
||||||
|
void initSawUp();
|
||||||
|
void initSawDown();
|
||||||
|
void render();
|
||||||
|
json stateToJson();
|
||||||
|
static bool isValidJson(json data);
|
||||||
|
void jsonToState(json data);
|
||||||
|
float valueAtPhase(float phase);
|
||||||
|
void checkLineIsLinear();
|
||||||
|
|
||||||
|
float getValueBetweenPoints(float x, int index_from, int index_to);
|
||||||
|
float getValueAtPhase(float phase);
|
||||||
|
std::string getName() const { return name_; }
|
||||||
|
std::string getLastBrowsedFile() const { return last_browsed_file_; }
|
||||||
|
|
||||||
|
void addPoint(int index, std::pair<float, float> position);
|
||||||
|
void addMiddlePoint(int index);
|
||||||
|
void removePoint(int index);
|
||||||
|
|
||||||
|
void flipHorizontal();
|
||||||
|
void flipVertical();
|
||||||
|
|
||||||
|
std::pair<float, float> lastPoint() const { return points_[num_points_ - 1]; }
|
||||||
|
float lastPower() const { return powers_[num_points_ - 1]; }
|
||||||
|
|
||||||
|
force_inline int resolution() const { return resolution_; }
|
||||||
|
force_inline bool linear() const { return linear_; }
|
||||||
|
force_inline bool smooth() const { return smooth_; }
|
||||||
|
force_inline vital::mono_float* getBuffer() const { return buffer_.get() + 1; }
|
||||||
|
force_inline vital::mono_float* getCubicInterpolationBuffer() const { return buffer_.get(); }
|
||||||
|
|
||||||
|
force_inline std::pair<float, float> getPoint(int index) const {
|
||||||
|
VITAL_ASSERT(index < kMaxPoints && index >= 0);
|
||||||
|
return points_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline float getPower(int index) const {
|
||||||
|
VITAL_ASSERT(index < kMaxPoints && index >= 0);
|
||||||
|
return powers_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline int getNumPoints() const {
|
||||||
|
return num_points_;
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline void setPoint(int index, std::pair<float, float> point) {
|
||||||
|
VITAL_ASSERT(index < kMaxPoints && index >= 0);
|
||||||
|
points_[index] = point;
|
||||||
|
checkLineIsLinear();
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline void setPower(int index, float power) {
|
||||||
|
VITAL_ASSERT(index < kMaxPoints && index >= 0);
|
||||||
|
powers_[index] = power;
|
||||||
|
checkLineIsLinear();
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline void setNumPoints(int num_points) {
|
||||||
|
VITAL_ASSERT(num_points <= kMaxPoints && num_points >= 0);
|
||||||
|
num_points_ = num_points;
|
||||||
|
checkLineIsLinear();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getRenderCount() const { return render_count_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string name_;
|
||||||
|
std::string last_browsed_file_;
|
||||||
|
std::pair<float, float> points_[kMaxPoints];
|
||||||
|
float powers_[kMaxPoints];
|
||||||
|
int num_points_;
|
||||||
|
int resolution_;
|
||||||
|
|
||||||
|
std::unique_ptr<vital::mono_float[]> buffer_;
|
||||||
|
bool loop_;
|
||||||
|
bool smooth_;
|
||||||
|
bool linear_;
|
||||||
|
int render_count_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LineGenerator)
|
||||||
|
};
|
||||||
|
|
||||||
1956
src/common/load_save.cpp
Normal file
1956
src/common/load_save.cpp
Normal file
File diff suppressed because it is too large
Load diff
183
src/common/load_save.h
Normal file
183
src/common/load_save.h
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "json/json.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
class StringLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MidiManager;
|
||||||
|
class SynthBase;
|
||||||
|
|
||||||
|
class LoadSave {
|
||||||
|
public:
|
||||||
|
class FileSorterAscending {
|
||||||
|
public:
|
||||||
|
FileSorterAscending() { }
|
||||||
|
|
||||||
|
static int compareElements(File a, File b) {
|
||||||
|
return a.getFullPathName().toLowerCase().compareNatural(b.getFullPathName().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FileSorterAscending)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PresetStyle {
|
||||||
|
kBass,
|
||||||
|
kLead,
|
||||||
|
kKeys,
|
||||||
|
kPad,
|
||||||
|
kPercussion,
|
||||||
|
kSequence,
|
||||||
|
kExperimental,
|
||||||
|
kSfx,
|
||||||
|
kTemplate,
|
||||||
|
kNumPresetStyles
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int kMaxCommentLength = 500;
|
||||||
|
static const std::string kUserDirectoryName;
|
||||||
|
static const std::string kPresetFolderName;
|
||||||
|
static const std::string kWavetableFolderName;
|
||||||
|
static const std::string kSkinFolderName;
|
||||||
|
static const std::string kSampleFolderName;
|
||||||
|
static const std::string kLfoFolderName;
|
||||||
|
static const std::string kAdditionalWavetableFoldersName;
|
||||||
|
static const std::string kAdditionalSampleFoldersName;
|
||||||
|
|
||||||
|
static void convertBufferToPcm(json& data, const std::string& field);
|
||||||
|
static void convertPcmToFloatBuffer(json& data, const std::string& field);
|
||||||
|
static json stateToJson(SynthBase* synth, const CriticalSection& critical_section);
|
||||||
|
|
||||||
|
static void loadControls(SynthBase* synth, const json& data);
|
||||||
|
static void loadModulations(SynthBase* synth, const json& modulations);
|
||||||
|
static void loadSample(SynthBase* synth, const json& sample);
|
||||||
|
static void loadWavetables(SynthBase* synth, const json& wavetables);
|
||||||
|
static void loadLfos(SynthBase* synth, const json& lfos);
|
||||||
|
static void loadSaveState(std::map<std::string, String>& save_info, json data);
|
||||||
|
|
||||||
|
static void initSaveInfo(std::map<std::string, String>& save_info);
|
||||||
|
static json updateFromOldVersion(json state);
|
||||||
|
static bool jsonToState(SynthBase* synth, std::map<std::string, String>& save_info, json state);
|
||||||
|
|
||||||
|
static String getAuthorFromFile(const File& file);
|
||||||
|
static String getStyleFromFile(const File& file);
|
||||||
|
static std::string getAuthor(json file);
|
||||||
|
static std::string getLicense(json state);
|
||||||
|
|
||||||
|
static File getConfigFile();
|
||||||
|
static void writeCrashLog(String crash_log);
|
||||||
|
static void writeErrorLog(String error_log);
|
||||||
|
static json getConfigJson();
|
||||||
|
static File getFavoritesFile();
|
||||||
|
static File getDefaultSkin();
|
||||||
|
static json getFavoritesJson();
|
||||||
|
static void addFavorite(const File& new_favorite);
|
||||||
|
static void removeFavorite(const File& old_favorite);
|
||||||
|
static std::set<std::string> getFavorites();
|
||||||
|
static bool hasDataDirectory();
|
||||||
|
static File getAvailablePacksFile();
|
||||||
|
static json getAvailablePacks();
|
||||||
|
static File getInstalledPacksFile();
|
||||||
|
static json getInstalledPacks();
|
||||||
|
static void saveInstalledPacks(const json& packs);
|
||||||
|
static void markPackInstalled(int id);
|
||||||
|
static void markPackInstalled(const std::string& name);
|
||||||
|
static void saveDataDirectory(const File& data_directory);
|
||||||
|
static bool isInstalled();
|
||||||
|
static bool wasUpgraded();
|
||||||
|
static bool isExpired();
|
||||||
|
static bool doesExpire();
|
||||||
|
static int getDaysToExpire();
|
||||||
|
static bool shouldCheckForUpdates();
|
||||||
|
static bool shouldWorkOffline();
|
||||||
|
static std::string getLoadedSkin();
|
||||||
|
static bool shouldAnimateWidgets();
|
||||||
|
static bool displayHzFrequency();
|
||||||
|
static bool authenticated();
|
||||||
|
static int getOversamplingAmount();
|
||||||
|
static float loadWindowSize();
|
||||||
|
static String loadVersion();
|
||||||
|
static String loadContentVersion();
|
||||||
|
static void saveJsonToConfig(json config_state);
|
||||||
|
static void saveJsonToFavorites(json favorites_json);
|
||||||
|
static void saveAuthor(std::string author);
|
||||||
|
static void savePreferredTTWTLanguage(std::string language);
|
||||||
|
static void saveLayoutConfig(vital::StringLayout* layout);
|
||||||
|
static void saveVersionConfig();
|
||||||
|
static void saveContentVersion(std::string version);
|
||||||
|
static void saveUpdateCheckConfig(bool check_for_updates);
|
||||||
|
static void saveWorkOffline(bool work_offline);
|
||||||
|
static void saveLoadedSkin(const std::string& name);
|
||||||
|
static void saveAnimateWidgets(bool animate_widgets);
|
||||||
|
static void saveDisplayHzFrequency(bool display_hz);
|
||||||
|
static void saveAuthenticated(bool authenticated);
|
||||||
|
static void saveWindowSize(float window_size);
|
||||||
|
static void saveMidiMapConfig(MidiManager* midi_manager);
|
||||||
|
static void loadConfig(MidiManager* midi_manager, vital::StringLayout* layout = nullptr);
|
||||||
|
|
||||||
|
static std::wstring getComputerKeyboardLayout();
|
||||||
|
static std::string getPreferredTTWTLanguage();
|
||||||
|
static std::string getAuthor();
|
||||||
|
static std::pair<wchar_t, wchar_t> getComputerKeyboardOctaveControls();
|
||||||
|
static void saveAdditionalFolders(const std::string& name, std::vector<std::string> folders);
|
||||||
|
static std::vector<std::string> getAdditionalFolders(const std::string& name);
|
||||||
|
|
||||||
|
static File getDataDirectory();
|
||||||
|
static std::vector<File> getDirectories(const String& folder_name);
|
||||||
|
static std::vector<File> getPresetDirectories();
|
||||||
|
static std::vector<File> getWavetableDirectories();
|
||||||
|
static std::vector<File> getSkinDirectories();
|
||||||
|
static std::vector<File> getSampleDirectories();
|
||||||
|
static std::vector<File> getLfoDirectories();
|
||||||
|
static File getUserDirectory();
|
||||||
|
static File getUserPresetDirectory();
|
||||||
|
static File getUserWavetableDirectory();
|
||||||
|
static File getUserSkinDirectory();
|
||||||
|
static File getUserSampleDirectory();
|
||||||
|
static File getUserLfoDirectory();
|
||||||
|
static void getAllFilesOfTypeInDirectories(Array<File>& files, const String& extensions,
|
||||||
|
const std::vector<File>& directories);
|
||||||
|
static void getAllPresets(Array<File>& presets);
|
||||||
|
static void getAllWavetables(Array<File>& wavetables);
|
||||||
|
static void getAllSkins(Array<File>& skins);
|
||||||
|
static void getAllLfos(Array<File>& lfos);
|
||||||
|
static void getAllSamples(Array<File>& samples);
|
||||||
|
static void getAllUserPresets(Array<File>& presets);
|
||||||
|
static void getAllUserWavetables(Array<File>& wavetables);
|
||||||
|
static void getAllUserLfos(Array<File>& lfos);
|
||||||
|
static void getAllUserSamples(Array<File>& samples);
|
||||||
|
static int compareFeatureVersionStrings(String a, String b);
|
||||||
|
static int compareVersionStrings(String a, String b);
|
||||||
|
|
||||||
|
static File getShiftedFile(const String directory_name, const String& extensions,
|
||||||
|
const std::string& additional_folders_name, const File& current_file, int shift);
|
||||||
|
|
||||||
|
private:
|
||||||
|
LoadSave() { }
|
||||||
|
};
|
||||||
|
|
||||||
324
src/common/midi_manager.cpp
Normal file
324
src/common/midi_manager.cpp
Normal file
|
|
@ -0,0 +1,324 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "midi_manager.h"
|
||||||
|
#include "sound_engine.h"
|
||||||
|
#include "synth_types.h"
|
||||||
|
#include "load_save.h"
|
||||||
|
#include "synth_base.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int kMidiControlBits = 7;
|
||||||
|
constexpr float kHighResolutionMax = (1 << (2 * kMidiControlBits)) - 1.0f;
|
||||||
|
constexpr float kControlMax = (1 << kMidiControlBits) - 1.0f;
|
||||||
|
|
||||||
|
force_inline vital::mono_float toHighResolutionValue(int msb, int lsb) {
|
||||||
|
if (lsb < 0)
|
||||||
|
return msb / kControlMax;
|
||||||
|
|
||||||
|
return ((msb << kMidiControlBits) + lsb) / kHighResolutionMax;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
MidiManager::MidiManager(SynthBase* synth, MidiKeyboardState* keyboard_state,
|
||||||
|
std::map<std::string, String>* gui_state, Listener* listener) :
|
||||||
|
synth_(synth), keyboard_state_(keyboard_state), gui_state_(gui_state),
|
||||||
|
listener_(listener), armed_value_(nullptr),
|
||||||
|
msb_pressure_values_(), msb_slide_values_() {
|
||||||
|
engine_ = synth_->getEngine();
|
||||||
|
current_bank_ = -1;
|
||||||
|
current_folder_ = -1;
|
||||||
|
current_preset_ = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::kNumMidiChannels; ++i) {
|
||||||
|
lsb_slide_values_[i] = -1;
|
||||||
|
lsb_pressure_values_[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mpe_enabled_ = false;
|
||||||
|
mpe_zone_layout_.setLowerZone(vital::kNumMidiChannels - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiManager::~MidiManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::armMidiLearn(std::string name) {
|
||||||
|
current_bank_ = -1;
|
||||||
|
current_folder_ = -1;
|
||||||
|
current_preset_ = -1;
|
||||||
|
armed_value_ = &vital::Parameters::getDetails(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::cancelMidiLearn() {
|
||||||
|
armed_value_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::clearMidiLearn(const std::string& name) {
|
||||||
|
for (auto& controls : midi_learn_map_) {
|
||||||
|
if (controls.second.count(name)) {
|
||||||
|
midi_learn_map_[controls.first].erase(name);
|
||||||
|
LoadSave::saveMidiMapConfig(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::midiInput(int midi_id, vital::mono_float value) {
|
||||||
|
if (armed_value_) {
|
||||||
|
midi_learn_map_[midi_id][armed_value_->name] = armed_value_;
|
||||||
|
armed_value_ = nullptr;
|
||||||
|
|
||||||
|
// TODO: Probably shouldn't write this config on the audio thread.
|
||||||
|
LoadSave::saveMidiMapConfig(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (midi_learn_map_.count(midi_id)) {
|
||||||
|
for (auto& control : midi_learn_map_[midi_id]) {
|
||||||
|
const vital::ValueDetails* details = control.second;
|
||||||
|
vital::mono_float percent = value / kControlMax;
|
||||||
|
vital::mono_float range = details->max - details->min;
|
||||||
|
vital::mono_float translated = percent * range + details->min;
|
||||||
|
|
||||||
|
if (details->value_scale == vital::ValueDetails::kIndexed)
|
||||||
|
translated = std::round(translated);
|
||||||
|
listener_->valueChangedThroughMidi(control.first, translated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MidiManager::isMidiMapped(const std::string& name) const {
|
||||||
|
for (auto& controls : midi_learn_map_) {
|
||||||
|
if (controls.second.count(name))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::setSampleRate(double sample_rate) {
|
||||||
|
midi_collector_.reset(sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::removeNextBlockOfMessages(MidiBuffer& buffer, int num_samples) {
|
||||||
|
midi_collector_.removeNextBlockOfMessages(buffer, num_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::readMpeMessage(const MidiMessage& message) {
|
||||||
|
mpe_zone_layout_.processNextMidiEvent(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::processAllNotesOff(const MidiMessage& midi_message, int sample_position, int channel) {
|
||||||
|
if (isMpeChannelMasterLowerZone(channel))
|
||||||
|
engine_->allNotesOffRange(sample_position, lowerZoneStartChannel(), lowerZoneEndChannel());
|
||||||
|
else if (isMpeChannelMasterUpperZone(channel))
|
||||||
|
engine_->allNotesOffRange(sample_position, upperZoneStartChannel(), upperZoneEndChannel());
|
||||||
|
else
|
||||||
|
engine_->allNotesOff(sample_position, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::processAllSoundsOff() {
|
||||||
|
engine_->allSoundsOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::processSustain(const MidiMessage& midi_message, int sample_position, int channel) {
|
||||||
|
bool on = midi_message.isSustainPedalOn();
|
||||||
|
if (isMpeChannelMasterLowerZone(channel)) {
|
||||||
|
if (on)
|
||||||
|
engine_->sustainOnRange(lowerZoneStartChannel(), lowerZoneEndChannel());
|
||||||
|
else
|
||||||
|
engine_->sustainOffRange(sample_position, lowerZoneStartChannel(), lowerZoneEndChannel());
|
||||||
|
}
|
||||||
|
else if (isMpeChannelMasterUpperZone(channel)) {
|
||||||
|
if (on)
|
||||||
|
engine_->sustainOnRange(upperZoneStartChannel(), upperZoneEndChannel());
|
||||||
|
else
|
||||||
|
engine_->sustainOffRange(sample_position, upperZoneStartChannel(), upperZoneEndChannel());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (on)
|
||||||
|
engine_->sustainOn(channel);
|
||||||
|
else
|
||||||
|
engine_->sustainOff(sample_position, channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::processSostenuto(const MidiMessage& midi_message, int sample_position, int channel) {
|
||||||
|
bool on = midi_message.isSostenutoPedalOn();
|
||||||
|
if (isMpeChannelMasterLowerZone(channel)) {
|
||||||
|
if (on)
|
||||||
|
engine_->sostenutoOnRange(lowerZoneStartChannel(), lowerZoneEndChannel());
|
||||||
|
else
|
||||||
|
engine_->sostenutoOffRange(sample_position, lowerZoneStartChannel(), lowerZoneEndChannel());
|
||||||
|
}
|
||||||
|
else if (isMpeChannelMasterUpperZone(channel)) {
|
||||||
|
if (on)
|
||||||
|
engine_->sostenutoOnRange(upperZoneStartChannel(), upperZoneEndChannel());
|
||||||
|
else
|
||||||
|
engine_->sostenutoOffRange(sample_position, upperZoneStartChannel(), upperZoneEndChannel());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (on)
|
||||||
|
engine_->sostenutoOn(channel);
|
||||||
|
else
|
||||||
|
engine_->sostenutoOff(sample_position, channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::processPitchBend(const MidiMessage& midi_message, int sample_position, int channel) {
|
||||||
|
vital::mono_float percent = midi_message.getPitchWheelValue() / kHighResolutionMax;
|
||||||
|
vital::mono_float value = 2 * percent - 1.0f;
|
||||||
|
|
||||||
|
if (isMpeChannelMasterLowerZone(channel)) {
|
||||||
|
engine_->setZonedPitchWheel(value, lowerMasterChannel(), lowerMasterChannel() + 1);
|
||||||
|
engine_->setZonedPitchWheel(value, lowerZoneStartChannel(), lowerZoneEndChannel());
|
||||||
|
listener_->pitchWheelMidiChanged(value);
|
||||||
|
}
|
||||||
|
else if (isMpeChannelMasterUpperZone(channel)) {
|
||||||
|
engine_->setZonedPitchWheel(value, upperMasterChannel(), upperMasterChannel() + 1);
|
||||||
|
engine_->setZonedPitchWheel(value, upperZoneStartChannel(), upperZoneEndChannel());
|
||||||
|
listener_->pitchWheelMidiChanged(value);
|
||||||
|
}
|
||||||
|
else if (mpe_enabled_)
|
||||||
|
engine_->setPitchWheel(value, channel);
|
||||||
|
else {
|
||||||
|
engine_->setZonedPitchWheel(value, channel, channel);
|
||||||
|
listener_->pitchWheelMidiChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::processPressure(const MidiMessage& midi_message, int sample_position, int channel) {
|
||||||
|
vital::mono_float value = toHighResolutionValue(msb_pressure_values_[channel], lsb_pressure_values_[channel]);
|
||||||
|
if (isMpeChannelMasterLowerZone(channel))
|
||||||
|
engine_->setChannelRangeAftertouch(lowerZoneStartChannel(), lowerZoneEndChannel(), value, 0);
|
||||||
|
else if (isMpeChannelMasterUpperZone(channel))
|
||||||
|
engine_->setChannelRangeAftertouch(upperZoneStartChannel(), upperZoneEndChannel(), value, 0);
|
||||||
|
else
|
||||||
|
engine_->setChannelAftertouch(channel, value, sample_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::processSlide(const MidiMessage& midi_message, int sample_position, int channel) {
|
||||||
|
vital::mono_float value = toHighResolutionValue(msb_slide_values_[channel], lsb_slide_values_[channel]);
|
||||||
|
if (isMpeChannelMasterLowerZone(channel))
|
||||||
|
engine_->setChannelRangeSlide(value, lowerZoneStartChannel(), lowerZoneEndChannel(), 0);
|
||||||
|
else if (isMpeChannelMasterUpperZone(channel))
|
||||||
|
engine_->setChannelRangeSlide(value, upperZoneStartChannel(), upperZoneEndChannel(), 0);
|
||||||
|
else
|
||||||
|
engine_->setChannelSlide(channel, value, sample_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline bool MidiManager::isMpeChannelMasterLowerZone(int channel) {
|
||||||
|
return mpe_enabled_ && mpe_zone_layout_.getLowerZone().isActive() && lowerMasterChannel() == channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline bool MidiManager::isMpeChannelMasterUpperZone(int channel) {
|
||||||
|
return mpe_enabled_ && mpe_zone_layout_.getUpperZone().isActive() && upperMasterChannel() == channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::processMidiMessage(const MidiMessage& midi_message, int sample_position) {
|
||||||
|
if (midi_message.isController())
|
||||||
|
readMpeMessage(midi_message);
|
||||||
|
|
||||||
|
int channel = midi_message.getChannel() - 1;
|
||||||
|
MidiMainType type = static_cast<MidiMainType>(midi_message.getRawData()[0] & 0xf0);
|
||||||
|
switch (type) {
|
||||||
|
case kProgramChange:
|
||||||
|
return;
|
||||||
|
case kNoteOn: {
|
||||||
|
uint8 velocity = midi_message.getVelocity();
|
||||||
|
if (velocity)
|
||||||
|
engine_->noteOn(midi_message.getNoteNumber(), velocity / kControlMax, sample_position, channel);
|
||||||
|
else
|
||||||
|
engine_->noteOff(midi_message.getNoteNumber(), velocity / kControlMax, sample_position, channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case kNoteOff: {
|
||||||
|
vital::mono_float velocity = midi_message.getVelocity() / kControlMax;
|
||||||
|
engine_->noteOff(midi_message.getNoteNumber(), velocity, sample_position, channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case kAftertouch: {
|
||||||
|
int note = midi_message.getNoteNumber();
|
||||||
|
vital::mono_float value = midi_message.getAfterTouchValue() / kControlMax;
|
||||||
|
engine_->setAftertouch(note, value, sample_position, channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case kChannelPressure: {
|
||||||
|
msb_pressure_values_[channel] = midi_message.getChannelPressureValue();
|
||||||
|
processPressure(midi_message, sample_position, channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case kPitchWheel: {
|
||||||
|
processPitchBend(midi_message, sample_position, channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case kController: {
|
||||||
|
MidiSecondaryType secondary_type = static_cast<MidiSecondaryType>(midi_message.getControllerNumber());
|
||||||
|
switch (secondary_type) {
|
||||||
|
case kSlide: {
|
||||||
|
msb_slide_values_[channel] = midi_message.getControllerValue();
|
||||||
|
processSlide(midi_message, sample_position, channel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kLsbPressure: {
|
||||||
|
lsb_pressure_values_[channel] = midi_message.getControllerValue();
|
||||||
|
processPressure(midi_message, sample_position, channel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kLsbSlide: {
|
||||||
|
lsb_slide_values_[channel] = midi_message.getControllerValue();
|
||||||
|
processSlide(midi_message, sample_position, channel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kSustainPedal: {
|
||||||
|
processSustain(midi_message, sample_position, channel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kSostenutoPedal: {
|
||||||
|
processSostenuto(midi_message, sample_position, channel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kSoftPedalOn: // TODO
|
||||||
|
break;
|
||||||
|
case kModWheel: {
|
||||||
|
vital::mono_float percent = (1.0f * midi_message.getControllerValue()) / kControlMax;
|
||||||
|
engine_->setModWheel(percent, channel);
|
||||||
|
listener_->modWheelMidiChanged(percent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kAllNotesOff:
|
||||||
|
case kAllControllersOff:
|
||||||
|
processAllNotesOff(midi_message, sample_position, channel);
|
||||||
|
return;
|
||||||
|
case kAllSoundsOff:
|
||||||
|
processAllSoundsOff();
|
||||||
|
break;
|
||||||
|
case kBankSelect:
|
||||||
|
current_bank_ = midi_message.getControllerValue();
|
||||||
|
return;
|
||||||
|
case kFolderSelect:
|
||||||
|
current_folder_ = midi_message.getControllerValue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
midiInput(midi_message.getControllerNumber(), midi_message.getControllerValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::handleIncomingMidiMessage(MidiInput* source, const MidiMessage &midi_message) {
|
||||||
|
midi_collector_.addMessageToQueue(midi_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiManager::replaceKeyboardMessages(MidiBuffer& buffer, int num_samples) {
|
||||||
|
keyboard_state_->processNextMidiBuffer(buffer, 0, num_samples, true);
|
||||||
|
}
|
||||||
169
src/common/midi_manager.h
Normal file
169
src/common/midi_manager.h
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#if !defined(JUCE_AUDIO_DEVICES_H_INCLUDED)
|
||||||
|
|
||||||
|
class MidiInput { };
|
||||||
|
|
||||||
|
class MidiInputCallback {
|
||||||
|
public:
|
||||||
|
virtual ~MidiInputCallback() { }
|
||||||
|
virtual void handleIncomingMidiMessage(MidiInput *source, const MidiMessage &midi_message) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
class MidiMessageCollector {
|
||||||
|
public:
|
||||||
|
void reset(int sample_rate) { }
|
||||||
|
void removeNextBlockOfMessages(MidiBuffer& buffer, int num_samples) { }
|
||||||
|
void addMessageToQueue(const MidiMessage &midi_message) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class SynthBase;
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
class SoundEngine;
|
||||||
|
struct ValueDetails;
|
||||||
|
} // namespace vital
|
||||||
|
|
||||||
|
class MidiManager : public MidiInputCallback {
|
||||||
|
public:
|
||||||
|
typedef std::map<int, std::map<std::string, const vital::ValueDetails*>> midi_map;
|
||||||
|
|
||||||
|
enum MidiMainType {
|
||||||
|
kNoteOff = 0x80,
|
||||||
|
kNoteOn = 0x90,
|
||||||
|
kAftertouch = 0xa0,
|
||||||
|
kController = 0xb0,
|
||||||
|
kProgramChange = 0xc0,
|
||||||
|
kChannelPressure = 0xd0,
|
||||||
|
kPitchWheel = 0xe0,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MidiSecondaryType {
|
||||||
|
kBankSelect = 0x00,
|
||||||
|
kModWheel = 0x01,
|
||||||
|
kFolderSelect = 0x20,
|
||||||
|
kSustainPedal = 0x40,
|
||||||
|
kSostenutoPedal = 0x42,
|
||||||
|
kSoftPedalOn = 0x43,
|
||||||
|
kSlide = 0x4a,
|
||||||
|
kLsbPressure = 0x66,
|
||||||
|
kLsbSlide = 0x6a,
|
||||||
|
kAllSoundsOff = 0x78,
|
||||||
|
kAllControllersOff = 0x79,
|
||||||
|
kAllNotesOff = 0x7b,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
virtual ~Listener() { }
|
||||||
|
virtual void valueChangedThroughMidi(const std::string& name, vital::mono_float value) = 0;
|
||||||
|
virtual void pitchWheelMidiChanged(vital::mono_float value) = 0;
|
||||||
|
virtual void modWheelMidiChanged(vital::mono_float value) = 0;
|
||||||
|
virtual void presetChangedThroughMidi(File preset) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
MidiManager(SynthBase* synth, MidiKeyboardState* keyboard_state,
|
||||||
|
std::map<std::string, String>* gui_state, Listener* listener = nullptr);
|
||||||
|
virtual ~MidiManager();
|
||||||
|
|
||||||
|
void armMidiLearn(std::string name);
|
||||||
|
void cancelMidiLearn();
|
||||||
|
void clearMidiLearn(const std::string& name);
|
||||||
|
void midiInput(int control, vital::mono_float value);
|
||||||
|
void processMidiMessage(const MidiMessage &midi_message, int sample_position = 0);
|
||||||
|
bool isMidiMapped(const std::string& name) const;
|
||||||
|
|
||||||
|
void setSampleRate(double sample_rate);
|
||||||
|
void removeNextBlockOfMessages(MidiBuffer& buffer, int num_samples);
|
||||||
|
void replaceKeyboardMessages(MidiBuffer& buffer, int num_samples);
|
||||||
|
|
||||||
|
void processAllNotesOff(const MidiMessage& midi_message, int sample_position, int channel);
|
||||||
|
void processAllSoundsOff();
|
||||||
|
void processSustain(const MidiMessage& midi_message, int sample_position, int channel);
|
||||||
|
void processSostenuto(const MidiMessage& midi_message, int sample_position, int channel);
|
||||||
|
void processPitchBend(const MidiMessage& midi_message, int sample_position, int channel);
|
||||||
|
void processPressure(const MidiMessage& midi_message, int sample_position, int channel);
|
||||||
|
void processSlide(const MidiMessage& midi_message, int sample_position, int channel);
|
||||||
|
|
||||||
|
bool isMpeChannelMasterLowerZone(int channel);
|
||||||
|
bool isMpeChannelMasterUpperZone(int channel);
|
||||||
|
|
||||||
|
force_inline int lowerZoneStartChannel() { return mpe_zone_layout_.getLowerZone().getFirstMemberChannel() - 1; }
|
||||||
|
force_inline int upperZoneStartChannel() { return mpe_zone_layout_.getUpperZone().getLastMemberChannel() - 1; }
|
||||||
|
force_inline int lowerZoneEndChannel() { return mpe_zone_layout_.getLowerZone().getLastMemberChannel() - 1; }
|
||||||
|
force_inline int upperZoneEndChannel() { return mpe_zone_layout_.getUpperZone().getFirstMemberChannel() - 1; }
|
||||||
|
force_inline int lowerMasterChannel() { return mpe_zone_layout_.getLowerZone().getMasterChannel() - 1; }
|
||||||
|
force_inline int upperMasterChannel() { return mpe_zone_layout_.getUpperZone().getMasterChannel() - 1; }
|
||||||
|
|
||||||
|
void setMpeEnabled(bool enabled) { mpe_enabled_ = enabled; }
|
||||||
|
|
||||||
|
midi_map getMidiLearnMap() { return midi_learn_map_; }
|
||||||
|
void setMidiLearnMap(const midi_map& midi_learn_map) { midi_learn_map_ = midi_learn_map; }
|
||||||
|
|
||||||
|
// MidiInputCallback
|
||||||
|
void handleIncomingMidiMessage(MidiInput *source, const MidiMessage &midi_message) override;
|
||||||
|
|
||||||
|
struct PresetLoadedCallback : public CallbackMessage {
|
||||||
|
PresetLoadedCallback(Listener* lis, File pre) : listener(lis), preset(std::move(pre)) { }
|
||||||
|
|
||||||
|
void messageCallback() override {
|
||||||
|
if (listener)
|
||||||
|
listener->presetChangedThroughMidi(preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Listener* listener;
|
||||||
|
File preset;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void readMpeMessage(const MidiMessage& message);
|
||||||
|
|
||||||
|
SynthBase* synth_;
|
||||||
|
vital::SoundEngine* engine_;
|
||||||
|
MidiKeyboardState* keyboard_state_;
|
||||||
|
MidiMessageCollector midi_collector_;
|
||||||
|
std::map<std::string, String>* gui_state_;
|
||||||
|
Listener* listener_;
|
||||||
|
int current_bank_;
|
||||||
|
int current_folder_;
|
||||||
|
int current_preset_;
|
||||||
|
|
||||||
|
const vital::ValueDetails* armed_value_;
|
||||||
|
midi_map midi_learn_map_;
|
||||||
|
|
||||||
|
int msb_pressure_values_[vital::kNumMidiChannels];
|
||||||
|
int lsb_pressure_values_[vital::kNumMidiChannels];
|
||||||
|
int msb_slide_values_[vital::kNumMidiChannels];
|
||||||
|
int lsb_slide_values_[vital::kNumMidiChannels];
|
||||||
|
|
||||||
|
bool mpe_enabled_;
|
||||||
|
MPEZoneLayout mpe_zone_layout_;
|
||||||
|
MidiRPNDetector rpn_detector_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiManager)
|
||||||
|
};
|
||||||
|
|
||||||
38
src/common/startup.cpp
Normal file
38
src/common/startup.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "startup.h"
|
||||||
|
#include "load_save.h"
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "synth_base.h"
|
||||||
|
|
||||||
|
void Startup::doStartupChecks(MidiManager* midi_manager, vital::StringLayout* layout) {
|
||||||
|
if (!LoadSave::isInstalled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (LoadSave::wasUpgraded())
|
||||||
|
LoadSave::saveVersionConfig();
|
||||||
|
|
||||||
|
LoadSave::loadConfig(midi_manager, layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Startup::isComputerCompatible() {
|
||||||
|
#if defined(__ARM_NEON__)
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return SystemStats::hasSSE2() || SystemStats::hasAVX2();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
39
src/common/startup.h
Normal file
39
src/common/startup.h
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
class SynthBase;
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
class StringLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MidiManager;
|
||||||
|
|
||||||
|
class Startup {
|
||||||
|
public:
|
||||||
|
static void doStartupChecks(MidiManager* midi_manager, vital::StringLayout* layout = nullptr);
|
||||||
|
static bool isComputerCompatible();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Startup() = delete;
|
||||||
|
};
|
||||||
|
|
||||||
770
src/common/synth_base.cpp
Normal file
770
src/common/synth_base.cpp
Normal file
|
|
@ -0,0 +1,770 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "synth_base.h"
|
||||||
|
|
||||||
|
#include "sample_source.h"
|
||||||
|
#include "sound_engine.h"
|
||||||
|
#include "load_save.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "modulation_connection_processor.h"
|
||||||
|
#include "startup.h"
|
||||||
|
#include "synth_gui_interface.h"
|
||||||
|
#include "synth_parameters.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
SynthBase::SynthBase() : expired_(false) {
|
||||||
|
expired_ = LoadSave::isExpired();
|
||||||
|
self_reference_ = std::make_shared<SynthBase*>();
|
||||||
|
*self_reference_ = this;
|
||||||
|
|
||||||
|
engine_ = std::make_unique<vital::SoundEngine>();
|
||||||
|
engine_->setTuning(&tuning_);
|
||||||
|
|
||||||
|
mod_connections_.reserve(vital::kMaxModulationConnections);
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::kNumOscillators; ++i) {
|
||||||
|
vital::Wavetable* wavetable = engine_->getWavetable(i);
|
||||||
|
if (wavetable) {
|
||||||
|
wavetable_creators_[i] = std::make_unique<WavetableCreator>(wavetable);
|
||||||
|
wavetable_creators_[i]->init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboard_state_ = std::make_unique<MidiKeyboardState>();
|
||||||
|
midi_manager_ = std::make_unique<MidiManager>(this, keyboard_state_.get(), &save_info_, this);
|
||||||
|
|
||||||
|
last_played_note_ = 0.0f;
|
||||||
|
last_num_pressed_ = 0;
|
||||||
|
audio_memory_ = std::make_unique<vital::StereoMemory>(vital::kAudioMemorySamples);
|
||||||
|
memset(oscilloscope_memory_, 0, 2 * vital::kOscilloscopeMemoryResolution * sizeof(vital::poly_float));
|
||||||
|
memset(oscilloscope_memory_write_, 0, 2 * vital::kOscilloscopeMemoryResolution * sizeof(vital::poly_float));
|
||||||
|
memory_reset_period_ = vital::kOscilloscopeMemoryResolution;
|
||||||
|
memory_input_offset_ = 0;
|
||||||
|
memory_index_ = 0;
|
||||||
|
|
||||||
|
controls_ = engine_->getControls();
|
||||||
|
|
||||||
|
Startup::doStartupChecks(midi_manager_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthBase::~SynthBase() { }
|
||||||
|
|
||||||
|
void SynthBase::valueChanged(const std::string& name, vital::mono_float value) {
|
||||||
|
controls_[name]->set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::valueChangedInternal(const std::string& name, vital::mono_float value) {
|
||||||
|
valueChanged(name, value);
|
||||||
|
setValueNotifyHost(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::valueChangedThroughMidi(const std::string& name, vital::mono_float value) {
|
||||||
|
controls_[name]->set(value);
|
||||||
|
ValueChangedCallback* callback = new ValueChangedCallback(self_reference_, name, value);
|
||||||
|
setValueNotifyHost(name, value);
|
||||||
|
callback->post();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::pitchWheelMidiChanged(vital::mono_float value) {
|
||||||
|
ValueChangedCallback* callback = new ValueChangedCallback(self_reference_, "pitch_wheel", value);
|
||||||
|
callback->post();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::modWheelMidiChanged(vital::mono_float value) {
|
||||||
|
ValueChangedCallback* callback = new ValueChangedCallback(self_reference_, "mod_wheel", value);
|
||||||
|
callback->post();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::pitchWheelGuiChanged(vital::mono_float value) {
|
||||||
|
engine_->setZonedPitchWheel(value, 0, vital::kNumMidiChannels - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::modWheelGuiChanged(vital::mono_float value) {
|
||||||
|
engine_->setModWheelAllChannels(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::presetChangedThroughMidi(File preset) {
|
||||||
|
SynthGuiInterface* gui_interface = getGuiInterface();
|
||||||
|
if (gui_interface) {
|
||||||
|
gui_interface->updateFullGui();
|
||||||
|
gui_interface->notifyFresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::valueChangedExternal(const std::string& name, vital::mono_float value) {
|
||||||
|
valueChanged(name, value);
|
||||||
|
if (name == "mod_wheel")
|
||||||
|
engine_->setModWheelAllChannels(value);
|
||||||
|
else if (name == "pitch_wheel")
|
||||||
|
engine_->setZonedPitchWheel(value, 0, vital::kNumMidiChannels - 1);
|
||||||
|
|
||||||
|
ValueChangedCallback* callback = new ValueChangedCallback(self_reference_, name, value);
|
||||||
|
callback->post();
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::ModulationConnection* SynthBase::getConnection(const std::string& source, const std::string& destination) {
|
||||||
|
for (vital::ModulationConnection* connection : mod_connections_) {
|
||||||
|
if (connection->source_name == source && connection->destination_name == destination)
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SynthBase::getConnectionIndex(const std::string& source, const std::string& destination) {
|
||||||
|
vital::ModulationConnectionBank& modulation_bank = getModulationBank();
|
||||||
|
for (int i = 0; i < vital::kMaxModulationConnections; ++i) {
|
||||||
|
vital::ModulationConnection* connection = modulation_bank.atIndex(i);
|
||||||
|
if (connection->source_name == source && connection->destination_name == destination)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::modulation_change SynthBase::createModulationChange(vital::ModulationConnection* connection) {
|
||||||
|
vital::modulation_change change;
|
||||||
|
change.source = engine_->getModulationSource(connection->source_name);
|
||||||
|
change.mono_destination = engine_->getMonoModulationDestination(connection->destination_name);
|
||||||
|
change.mono_modulation_switch = engine_->getMonoModulationSwitch(connection->destination_name);
|
||||||
|
VITAL_ASSERT(change.source != nullptr);
|
||||||
|
VITAL_ASSERT(change.mono_destination != nullptr);
|
||||||
|
VITAL_ASSERT(change.mono_modulation_switch != nullptr);
|
||||||
|
|
||||||
|
change.destination_scale = vital::Parameters::getParameterRange(connection->destination_name);
|
||||||
|
change.poly_modulation_switch = engine_->getPolyModulationSwitch(connection->destination_name);
|
||||||
|
change.poly_destination = engine_->getPolyModulationDestination(connection->destination_name);
|
||||||
|
change.modulation_processor = connection->modulation_processor.get();
|
||||||
|
|
||||||
|
int num_audio_rate = 0;
|
||||||
|
vital::ModulationConnectionBank& modulation_bank = getModulationBank();
|
||||||
|
for (int i = 0; i < vital::kMaxModulationConnections; ++i) {
|
||||||
|
if (modulation_bank.atIndex(i)->source_name == connection->source_name &&
|
||||||
|
modulation_bank.atIndex(i)->destination_name != connection->destination_name &&
|
||||||
|
!modulation_bank.atIndex(i)->modulation_processor->isControlRate()) {
|
||||||
|
num_audio_rate++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
change.num_audio_rate = num_audio_rate;
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthBase::isInvalidConnection(const vital::modulation_change& change) {
|
||||||
|
return change.poly_destination && change.poly_destination->router() == change.modulation_processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::connectModulation(vital::ModulationConnection* connection) {
|
||||||
|
vital::modulation_change change = createModulationChange(connection);
|
||||||
|
if (isInvalidConnection(change)) {
|
||||||
|
connection->destination_name = "";
|
||||||
|
connection->source_name = "";
|
||||||
|
}
|
||||||
|
else if (mod_connections_.count(connection) == 0) {
|
||||||
|
change.disconnecting = false;
|
||||||
|
mod_connections_.push_back(connection);
|
||||||
|
modulation_change_queue_.enqueue(change);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthBase::connectModulation(const std::string& source, const std::string& destination) {
|
||||||
|
vital::ModulationConnection* connection = getConnection(source, destination);
|
||||||
|
bool create = connection == nullptr;
|
||||||
|
if (create)
|
||||||
|
connection = getModulationBank().createConnection(source, destination);
|
||||||
|
|
||||||
|
if (connection)
|
||||||
|
connectModulation(connection);
|
||||||
|
|
||||||
|
return create;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::disconnectModulation(vital::ModulationConnection* connection) {
|
||||||
|
if (mod_connections_.count(connection) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
vital::modulation_change change = createModulationChange(connection);
|
||||||
|
connection->source_name = "";
|
||||||
|
connection->destination_name = "";
|
||||||
|
|
||||||
|
mod_connections_.remove(connection);
|
||||||
|
change.disconnecting = true;
|
||||||
|
modulation_change_queue_.enqueue(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::disconnectModulation(const std::string& source, const std::string& destination) {
|
||||||
|
vital::ModulationConnection* connection = getConnection(source, destination);
|
||||||
|
if (connection)
|
||||||
|
disconnectModulation(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::clearModulations() {
|
||||||
|
clearModulationQueue();
|
||||||
|
|
||||||
|
while (mod_connections_.size()) {
|
||||||
|
vital::ModulationConnection* connection = *mod_connections_.begin();
|
||||||
|
mod_connections_.remove(connection);
|
||||||
|
vital::modulation_change change = createModulationChange(connection);
|
||||||
|
change.disconnecting = true;
|
||||||
|
engine_->disconnectModulation(change);
|
||||||
|
connection->source_name = "";
|
||||||
|
connection->destination_name = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_connections = static_cast<int>(getModulationBank().numConnections());
|
||||||
|
for (int i = 0; i < num_connections; ++i)
|
||||||
|
getModulationBank().atIndex(i)->modulation_processor->lineMapGenerator()->initLinear();
|
||||||
|
|
||||||
|
engine_->disableUnnecessaryModSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::forceShowModulation(const std::string& source, bool force) {
|
||||||
|
if (force)
|
||||||
|
engine_->enableModSource(source);
|
||||||
|
else if (!isSourceConnected(source))
|
||||||
|
engine_->disableModSource(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthBase::isModSourceEnabled(const std::string& source) {
|
||||||
|
return engine_->isModSourceEnabled(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SynthBase::getNumModulations(const std::string& destination) {
|
||||||
|
int connections = 0;
|
||||||
|
for (vital::ModulationConnection* connection : mod_connections_) {
|
||||||
|
if (connection->destination_name == destination)
|
||||||
|
connections++;
|
||||||
|
}
|
||||||
|
return connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<vital::ModulationConnection*> SynthBase::getSourceConnections(const std::string& source) {
|
||||||
|
std::vector<vital::ModulationConnection*> connections;
|
||||||
|
for (vital::ModulationConnection* connection : mod_connections_) {
|
||||||
|
if (connection->source_name == source)
|
||||||
|
connections.push_back(connection);
|
||||||
|
}
|
||||||
|
return connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthBase::isSourceConnected(const std::string& source) {
|
||||||
|
for (vital::ModulationConnection* connection : mod_connections_) {
|
||||||
|
if (connection->source_name == source)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<vital::ModulationConnection*> SynthBase::getDestinationConnections(const std::string& destination) {
|
||||||
|
std::vector<vital::ModulationConnection*> connections;
|
||||||
|
for (vital::ModulationConnection* connection : mod_connections_) {
|
||||||
|
if (connection->destination_name == destination)
|
||||||
|
connections.push_back(connection);
|
||||||
|
}
|
||||||
|
return connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vital::StatusOutput* SynthBase::getStatusOutput(const std::string& name) {
|
||||||
|
return engine_->getStatusOutput(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::Wavetable* SynthBase::getWavetable(int index) {
|
||||||
|
return engine_->getWavetable(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableCreator* SynthBase::getWavetableCreator(int index) {
|
||||||
|
return wavetable_creators_[index].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::Sample* SynthBase::getSample() {
|
||||||
|
return engine_->getSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
LineGenerator* SynthBase::getLfoSource(int index) {
|
||||||
|
return engine_->getLfoSource(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
json SynthBase::saveToJson() {
|
||||||
|
return LoadSave::stateToJson(this, getCriticalSection());
|
||||||
|
}
|
||||||
|
|
||||||
|
int SynthBase::getSampleRate() {
|
||||||
|
return engine_->getSampleRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::initEngine() {
|
||||||
|
clearModulations();
|
||||||
|
if (getWavetableCreator(0)) {
|
||||||
|
for (int i = 0; i < vital::kNumOscillators; ++i)
|
||||||
|
getWavetableCreator(i)->init();
|
||||||
|
|
||||||
|
engine_->getSample()->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::kNumLfos; ++i)
|
||||||
|
getLfoSource(i)->initTriangle();
|
||||||
|
|
||||||
|
vital::control_map controls = engine_->getControls();
|
||||||
|
for (auto& control : controls) {
|
||||||
|
vital::ValueDetails details = vital::Parameters::getDetails(control.first);
|
||||||
|
control.second->set(details.default_value);
|
||||||
|
}
|
||||||
|
checkOversampling();
|
||||||
|
|
||||||
|
clearActiveFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::loadTuningFile(const File& file) {
|
||||||
|
tuning_.loadFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::loadInitPreset() {
|
||||||
|
pauseProcessing(true);
|
||||||
|
engine_->allSoundsOff();
|
||||||
|
initEngine();
|
||||||
|
LoadSave::initSaveInfo(save_info_);
|
||||||
|
pauseProcessing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthBase::loadFromJson(const json& data) {
|
||||||
|
pauseProcessing(true);
|
||||||
|
engine_->allSoundsOff();
|
||||||
|
try {
|
||||||
|
bool result = LoadSave::jsonToState(this, save_info_, data);
|
||||||
|
pauseProcessing(false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (const json::exception& e) {
|
||||||
|
pauseProcessing(false);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthBase::loadFromFile(File preset, std::string& error) {
|
||||||
|
if (!preset.exists())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json parsed_json_state = json::parse(preset.loadFileAsString().toStdString(), nullptr);
|
||||||
|
if (!loadFromJson(parsed_json_state)) {
|
||||||
|
error = "Preset was created with a newer version.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
active_file_ = preset;
|
||||||
|
}
|
||||||
|
catch (const json::exception& e) {
|
||||||
|
error = "Preset file is corrupted.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPresetName(preset.getFileNameWithoutExtension());
|
||||||
|
|
||||||
|
SynthGuiInterface* gui_interface = getGuiInterface();
|
||||||
|
if (gui_interface) {
|
||||||
|
gui_interface->updateFullGui();
|
||||||
|
gui_interface->notifyFresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::renderAudioToFile(File file, float seconds, float bpm, std::vector<int> notes, bool render_images) {
|
||||||
|
static constexpr int kSampleRate = 44100;
|
||||||
|
static constexpr int kPreProcessSamples = 44100;
|
||||||
|
static constexpr int kFadeSamples = 200;
|
||||||
|
static constexpr int kBufferSize = 64;
|
||||||
|
static constexpr int kVideoRate = 30;
|
||||||
|
static constexpr int kImageNumberPlaces = 3;
|
||||||
|
static constexpr int kImageWidth = 500;
|
||||||
|
static constexpr int kImageHeight = 250;
|
||||||
|
static constexpr int kOscilloscopeResolution = 512;
|
||||||
|
static constexpr float kFadeRatio = 0.3f;
|
||||||
|
|
||||||
|
ScopedLock lock(getCriticalSection());
|
||||||
|
|
||||||
|
processModulationChanges();
|
||||||
|
engine_->setSampleRate(kSampleRate);
|
||||||
|
engine_->setBpm(bpm);
|
||||||
|
engine_->updateAllModulationSwitches();
|
||||||
|
|
||||||
|
double sample_time = 1.0 / getSampleRate();
|
||||||
|
double current_time = -kPreProcessSamples * sample_time;
|
||||||
|
|
||||||
|
for (int samples = 0; samples < kPreProcessSamples; samples += kBufferSize) {
|
||||||
|
engine_->correctToTime(current_time);
|
||||||
|
current_time += kBufferSize * sample_time;
|
||||||
|
engine_->process(kBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int note : notes)
|
||||||
|
engine_->noteOn(note, 0.7f, 0, 0);
|
||||||
|
|
||||||
|
file.deleteFile();
|
||||||
|
std::unique_ptr<FileOutputStream> file_stream = file.createOutputStream();
|
||||||
|
WavAudioFormat wav_format;
|
||||||
|
std::unique_ptr<AudioFormatWriter> writer(wav_format.createWriterFor(file_stream.get(), kSampleRate, 2, 16, {}, 0));
|
||||||
|
|
||||||
|
int on_samples = seconds * kSampleRate;
|
||||||
|
int total_samples = on_samples + seconds * kSampleRate * kFadeRatio;
|
||||||
|
std::unique_ptr<float[]> left_buffer = std::make_unique<float[]>(kBufferSize);
|
||||||
|
std::unique_ptr<float[]> right_buffer = std::make_unique<float[]>(kBufferSize);
|
||||||
|
float* buffers[2] = { left_buffer.get(), right_buffer.get() };
|
||||||
|
const vital::mono_float* engine_output = (const vital::mono_float*)engine_->output(0)->buffer;
|
||||||
|
|
||||||
|
#if JUCE_MODULE_AVAILABLE_juce_graphics
|
||||||
|
int current_image_index = -1;
|
||||||
|
PNGImageFormat png;
|
||||||
|
File images_folder = File::getCurrentWorkingDirectory().getChildFile("images");
|
||||||
|
if (!images_folder.exists() && render_images)
|
||||||
|
images_folder.createDirectory();
|
||||||
|
const vital::poly_float* memory = getOscilloscopeMemory();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (int samples = 0; samples < total_samples; samples += kBufferSize) {
|
||||||
|
engine_->correctToTime(current_time);
|
||||||
|
current_time += kBufferSize * sample_time;
|
||||||
|
engine_->process(kBufferSize);
|
||||||
|
updateMemoryOutput(kBufferSize, engine_->output(0)->buffer);
|
||||||
|
|
||||||
|
if (on_samples > samples && on_samples <= samples + kBufferSize) {
|
||||||
|
for (int note : notes)
|
||||||
|
engine_->noteOff(note, 0.5f, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < kBufferSize; ++i) {
|
||||||
|
vital::mono_float t = (total_samples - samples) / (1.0f * kFadeSamples);
|
||||||
|
t = vital::utils::min(t, 1.0f);
|
||||||
|
left_buffer[i] = t * engine_output[vital::poly_float::kSize * i];
|
||||||
|
right_buffer[i] = t * engine_output[vital::poly_float::kSize * i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
writer->writeFromFloatArrays(buffers, 2, kBufferSize);
|
||||||
|
|
||||||
|
#if JUCE_MODULE_AVAILABLE_juce_graphics
|
||||||
|
int image_index = (samples * kVideoRate) / kSampleRate;
|
||||||
|
if (image_index > current_image_index && render_images) {
|
||||||
|
current_image_index = image_index;
|
||||||
|
String number(image_index);
|
||||||
|
while (number.length() < kImageNumberPlaces)
|
||||||
|
number = "0" + number;
|
||||||
|
|
||||||
|
File image_file = images_folder.getChildFile("rendered_image" + number + ".png");
|
||||||
|
FileOutputStream image_file_stream(image_file);
|
||||||
|
Image image(Image::RGB, kImageWidth, kImageHeight, true);
|
||||||
|
Graphics g(image);
|
||||||
|
g.fillAll(Colour(0xff1d2125));
|
||||||
|
|
||||||
|
Path left_path;
|
||||||
|
Path right_path;
|
||||||
|
left_path.startNewSubPath(-2.0f, kImageHeight / 2);
|
||||||
|
right_path.startNewSubPath(-2.0f, kImageHeight / 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < kOscilloscopeResolution; ++i) {
|
||||||
|
float t = i / (kOscilloscopeResolution - 1.0f);
|
||||||
|
float memory_spot = (1.0f * i * vital::kOscilloscopeMemoryResolution) / kOscilloscopeResolution;
|
||||||
|
int memory_index = memory_spot;
|
||||||
|
float remainder = memory_spot - memory_index;
|
||||||
|
vital::poly_float from = memory[memory_index];
|
||||||
|
vital::poly_float to = memory[memory_index + 1];
|
||||||
|
vital::poly_float y = -vital::utils::interpolate(from, to, remainder) * kImageHeight / 2.0f + kImageHeight / 2;
|
||||||
|
left_path.lineTo(t * kImageWidth, y[0]);
|
||||||
|
right_path.lineTo(t * kImageWidth, y[1]);
|
||||||
|
}
|
||||||
|
left_path.lineTo(kImageWidth + 2.0f, kImageHeight / 2.0f);
|
||||||
|
right_path.lineTo(kImageWidth + 2.0f, kImageHeight / 2.0f);
|
||||||
|
|
||||||
|
g.setColour(Colour(0x64aa88ff));
|
||||||
|
g.fillPath(left_path);
|
||||||
|
g.fillPath(right_path);
|
||||||
|
|
||||||
|
g.setColour(Colour(0xffaa88ff));
|
||||||
|
g.strokePath(left_path, PathStrokeType(2.0f, PathStrokeType::curved, PathStrokeType::rounded));
|
||||||
|
g.strokePath(right_path, PathStrokeType(2.0f, PathStrokeType::curved, PathStrokeType::rounded));
|
||||||
|
|
||||||
|
png.writeImageToStream(image, image_file_stream);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
writer->flush();
|
||||||
|
file_stream->flush();
|
||||||
|
|
||||||
|
writer = nullptr;
|
||||||
|
file_stream.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::renderAudioForResynthesis(float* data, int samples, int note) {
|
||||||
|
static constexpr int kPreProcessSamples = 44100;
|
||||||
|
static constexpr int kBufferSize = 64;
|
||||||
|
|
||||||
|
ScopedLock lock(getCriticalSection());
|
||||||
|
|
||||||
|
double sample_time = 1.0 / getSampleRate();
|
||||||
|
double current_time = -kPreProcessSamples * sample_time;
|
||||||
|
|
||||||
|
engine_->allSoundsOff();
|
||||||
|
for (int s = 0; s < kPreProcessSamples; s += kBufferSize) {
|
||||||
|
engine_->correctToTime(current_time);
|
||||||
|
current_time += kBufferSize * sample_time;
|
||||||
|
engine_->process(kBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_->noteOn(note, 0.7f, 0, 0);
|
||||||
|
const vital::poly_float* engine_output = engine_->output(0)->buffer;
|
||||||
|
float max_value = 0.01f;
|
||||||
|
for (int s = 0; s < samples; s += kBufferSize) {
|
||||||
|
int num_samples = std::min(samples - s, kBufferSize);
|
||||||
|
engine_->correctToTime(current_time);
|
||||||
|
current_time += num_samples * sample_time;
|
||||||
|
engine_->process(num_samples);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_samples; ++i) {
|
||||||
|
float sample = engine_output[i][0];
|
||||||
|
data[s + i] = sample;
|
||||||
|
max_value = std::max(max_value, fabsf(sample));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float scale = 1.0f / max_value;
|
||||||
|
for (int s = 0; s < samples; ++s)
|
||||||
|
data[s] *= scale;
|
||||||
|
|
||||||
|
engine_->allSoundsOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthBase::saveToFile(File preset) {
|
||||||
|
preset = preset.withFileExtension(String(vital::kPresetExtension));
|
||||||
|
|
||||||
|
File parent = preset.getParentDirectory();
|
||||||
|
if (!parent.exists()) {
|
||||||
|
if (!parent.createDirectory().wasOk() || !parent.hasWriteAccess())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPresetName(preset.getFileNameWithoutExtension());
|
||||||
|
|
||||||
|
SynthGuiInterface* gui_interface = getGuiInterface();
|
||||||
|
if (gui_interface)
|
||||||
|
gui_interface->notifyFresh();
|
||||||
|
|
||||||
|
if (preset.replaceWithText(saveToJson().dump())) {
|
||||||
|
active_file_ = preset;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthBase::saveToActiveFile() {
|
||||||
|
if (!active_file_.exists() || !active_file_.hasWriteAccess())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return saveToFile(active_file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::setMpeEnabled(bool enabled) {
|
||||||
|
midi_manager_->setMpeEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::processAudio(AudioSampleBuffer* buffer, int channels, int samples, int offset) {
|
||||||
|
if (expired_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
engine_->process(samples);
|
||||||
|
writeAudio(buffer, channels, samples, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::processAudioWithInput(AudioSampleBuffer* buffer, const vital::poly_float* input_buffer,
|
||||||
|
int channels, int samples, int offset) {
|
||||||
|
if (expired_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
engine_->processWithInput(input_buffer, samples);
|
||||||
|
writeAudio(buffer, channels, samples, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::writeAudio(AudioSampleBuffer* buffer, int channels, int samples, int offset) {
|
||||||
|
const vital::mono_float* engine_output = (const vital::mono_float*)engine_->output(0)->buffer;
|
||||||
|
for (int channel = 0; channel < channels; ++channel) {
|
||||||
|
float* channel_data = buffer->getWritePointer(channel, offset);
|
||||||
|
|
||||||
|
for (int i = 0; i < samples; ++i) {
|
||||||
|
channel_data[i] = engine_output[vital::poly_float::kSize * i + channel];
|
||||||
|
VITAL_ASSERT(std::isfinite(channel_data[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMemoryOutput(samples, engine_->output(0)->buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::processMidi(MidiBuffer& midi_messages, int start_sample, int end_sample) {
|
||||||
|
bool process_all = end_sample == 0;
|
||||||
|
for (const MidiMessageMetadata message : midi_messages) {
|
||||||
|
int midi_sample = message.samplePosition;
|
||||||
|
if (process_all || (midi_sample >= start_sample && midi_sample < end_sample))
|
||||||
|
midi_manager_->processMidiMessage(message.getMessage(), midi_sample - start_sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::processKeyboardEvents(MidiBuffer& buffer, int num_samples) {
|
||||||
|
midi_manager_->replaceKeyboardMessages(buffer, num_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::processModulationChanges() {
|
||||||
|
vital::modulation_change change;
|
||||||
|
while (getNextModulationChange(change)) {
|
||||||
|
if (change.disconnecting)
|
||||||
|
engine_->disconnectModulation(change);
|
||||||
|
else
|
||||||
|
engine_->connectModulation(change);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::updateMemoryOutput(int samples, const vital::poly_float* audio) {
|
||||||
|
for (int i = 0; i < samples; ++i)
|
||||||
|
audio_memory_->push(audio[i]);
|
||||||
|
|
||||||
|
vital::mono_float last_played = engine_->getLastActiveNote();
|
||||||
|
last_played = vital::utils::clamp(last_played, kOutputWindowMinNote, kOutputWindowMaxNote);
|
||||||
|
|
||||||
|
int num_pressed = engine_->getNumPressedNotes();
|
||||||
|
int output_inc = std::max<int>(1, engine_->getSampleRate() / vital::kOscilloscopeMemorySampleRate);
|
||||||
|
int oscilloscope_samples = 2 * vital::kOscilloscopeMemoryResolution;
|
||||||
|
|
||||||
|
if (last_played && (last_played_note_ != last_played || num_pressed > last_num_pressed_)) {
|
||||||
|
last_played_note_ = last_played;
|
||||||
|
|
||||||
|
vital::mono_float frequency = vital::utils::midiNoteToFrequency(last_played_note_);
|
||||||
|
vital::mono_float period = engine_->getSampleRate() / frequency;
|
||||||
|
int window_length = output_inc * vital::kOscilloscopeMemoryResolution;
|
||||||
|
|
||||||
|
memory_reset_period_ = period;
|
||||||
|
while (memory_reset_period_ < window_length)
|
||||||
|
memory_reset_period_ += memory_reset_period_;
|
||||||
|
|
||||||
|
memory_reset_period_ = std::min(memory_reset_period_, 2.0f * window_length);
|
||||||
|
memory_index_ = 0;
|
||||||
|
vital::utils::copyBuffer(oscilloscope_memory_, oscilloscope_memory_write_, oscilloscope_samples);
|
||||||
|
}
|
||||||
|
last_num_pressed_ = num_pressed;
|
||||||
|
|
||||||
|
for (; memory_input_offset_ < samples; memory_input_offset_ += output_inc) {
|
||||||
|
int input_index = vital::utils::iclamp(memory_input_offset_, 0, samples);
|
||||||
|
memory_index_ = vital::utils::iclamp(memory_index_, 0, oscilloscope_samples - 1);
|
||||||
|
VITAL_ASSERT(input_index >= 0);
|
||||||
|
VITAL_ASSERT(input_index < samples);
|
||||||
|
VITAL_ASSERT(memory_index_ >= 0);
|
||||||
|
VITAL_ASSERT(memory_index_ < oscilloscope_samples);
|
||||||
|
oscilloscope_memory_write_[memory_index_++] = audio[input_index];
|
||||||
|
|
||||||
|
if (memory_index_ * output_inc >= memory_reset_period_) {
|
||||||
|
memory_input_offset_ += memory_reset_period_ - memory_index_ * output_inc;
|
||||||
|
memory_index_ = 0;
|
||||||
|
vital::utils::copyBuffer(oscilloscope_memory_, oscilloscope_memory_write_, oscilloscope_samples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memory_input_offset_ -= samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::armMidiLearn(const std::string& name) {
|
||||||
|
midi_manager_->armMidiLearn(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::cancelMidiLearn() {
|
||||||
|
midi_manager_->cancelMidiLearn();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::clearMidiLearn(const std::string& name) {
|
||||||
|
midi_manager_->clearMidiLearn(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynthBase::isMidiMapped(const std::string& name) {
|
||||||
|
return midi_manager_->isMidiMapped(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::setAuthor(const String& author) {
|
||||||
|
save_info_["author"] = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::setComments(const String& comments) {
|
||||||
|
save_info_["comments"] = comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::setStyle(const String& style) {
|
||||||
|
save_info_["style"] = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::setPresetName(const String& preset_name) {
|
||||||
|
save_info_["preset_name"] = preset_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::setMacroName(int index, const String& macro_name) {
|
||||||
|
save_info_["macro" + std::to_string(index + 1)] = macro_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
String SynthBase::getAuthor() {
|
||||||
|
return save_info_["author"];
|
||||||
|
}
|
||||||
|
|
||||||
|
String SynthBase::getComments() {
|
||||||
|
return save_info_["comments"];
|
||||||
|
}
|
||||||
|
|
||||||
|
String SynthBase::getStyle() {
|
||||||
|
return save_info_["style"];
|
||||||
|
}
|
||||||
|
|
||||||
|
String SynthBase::getPresetName() {
|
||||||
|
return save_info_["preset_name"];
|
||||||
|
}
|
||||||
|
|
||||||
|
String SynthBase::getMacroName(int index) {
|
||||||
|
String name = save_info_["macro" + std::to_string(index + 1)];
|
||||||
|
if (name.trim().isEmpty())
|
||||||
|
return "MACRO " + String(index + 1);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vital::StereoMemory* SynthBase::getEqualizerMemory() {
|
||||||
|
if (engine_)
|
||||||
|
return engine_->getEqualizerMemory();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::ModulationConnectionBank& SynthBase::getModulationBank() {
|
||||||
|
return engine_->getModulationBank();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::notifyOversamplingChanged() {
|
||||||
|
pauseProcessing(true);
|
||||||
|
engine_->allSoundsOff();
|
||||||
|
checkOversampling();
|
||||||
|
pauseProcessing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::checkOversampling() {
|
||||||
|
return engine_->checkOversampling();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthBase::ValueChangedCallback::messageCallback() {
|
||||||
|
if (auto synth_base = listener.lock()) {
|
||||||
|
SynthGuiInterface* gui_interface = (*synth_base)->getGuiInterface();
|
||||||
|
if (gui_interface) {
|
||||||
|
gui_interface->updateGuiControl(control_name, value);
|
||||||
|
if (control_name != "pitch_wheel")
|
||||||
|
gui_interface->notifyChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
211
src/common/synth_base.h
Normal file
211
src/common/synth_base.h
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "concurrentqueue/concurrentqueue.h"
|
||||||
|
#include "line_generator.h"
|
||||||
|
#include "synth_constants.h"
|
||||||
|
#include "synth_types.h"
|
||||||
|
#include "midi_manager.h"
|
||||||
|
#include "tuning.h"
|
||||||
|
#include "wavetable_creator.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
class SoundEngine;
|
||||||
|
struct Output;
|
||||||
|
class StatusOutput;
|
||||||
|
class StereoMemory;
|
||||||
|
class Sample;
|
||||||
|
class WaveFrame;
|
||||||
|
class Wavetable;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SynthGuiInterface;
|
||||||
|
|
||||||
|
class SynthBase : public MidiManager::Listener {
|
||||||
|
public:
|
||||||
|
static constexpr float kOutputWindowMinNote = 16.0f;
|
||||||
|
static constexpr float kOutputWindowMaxNote = 128.0f;
|
||||||
|
|
||||||
|
SynthBase();
|
||||||
|
virtual ~SynthBase();
|
||||||
|
|
||||||
|
void valueChanged(const std::string& name, vital::mono_float value);
|
||||||
|
void valueChangedThroughMidi(const std::string& name, vital::mono_float value) override;
|
||||||
|
void pitchWheelMidiChanged(vital::mono_float value) override;
|
||||||
|
void modWheelMidiChanged(vital::mono_float value) override;
|
||||||
|
void pitchWheelGuiChanged(vital::mono_float value);
|
||||||
|
void modWheelGuiChanged(vital::mono_float value);
|
||||||
|
void presetChangedThroughMidi(File preset) override;
|
||||||
|
void valueChangedExternal(const std::string& name, vital::mono_float value);
|
||||||
|
void valueChangedInternal(const std::string& name, vital::mono_float value);
|
||||||
|
bool connectModulation(const std::string& source, const std::string& destination);
|
||||||
|
void connectModulation(vital::ModulationConnection* connection);
|
||||||
|
void disconnectModulation(const std::string& source, const std::string& destination);
|
||||||
|
void disconnectModulation(vital::ModulationConnection* connection);
|
||||||
|
void clearModulations();
|
||||||
|
void forceShowModulation(const std::string& source, bool force);
|
||||||
|
bool isModSourceEnabled(const std::string& source);
|
||||||
|
int getNumModulations(const std::string& destination);
|
||||||
|
int getConnectionIndex(const std::string& source, const std::string& destination);
|
||||||
|
vital::CircularQueue<vital::ModulationConnection*> getModulationConnections() { return mod_connections_; }
|
||||||
|
std::vector<vital::ModulationConnection*> getSourceConnections(const std::string& source);
|
||||||
|
bool isSourceConnected(const std::string& source);
|
||||||
|
std::vector<vital::ModulationConnection*> getDestinationConnections(const std::string& destination);
|
||||||
|
|
||||||
|
const vital::StatusOutput* getStatusOutput(const std::string& name);
|
||||||
|
|
||||||
|
vital::Wavetable* getWavetable(int index);
|
||||||
|
WavetableCreator* getWavetableCreator(int index);
|
||||||
|
vital::Sample* getSample();
|
||||||
|
LineGenerator* getLfoSource(int index);
|
||||||
|
|
||||||
|
int getSampleRate();
|
||||||
|
void initEngine();
|
||||||
|
void loadTuningFile(const File& file);
|
||||||
|
void loadInitPreset();
|
||||||
|
bool loadFromFile(File preset, std::string& error);
|
||||||
|
void renderAudioToFile(File file, float seconds, float bpm, std::vector<int> notes, bool render_images);
|
||||||
|
void renderAudioForResynthesis(float* data, int samples, int note);
|
||||||
|
bool saveToFile(File preset);
|
||||||
|
bool saveToActiveFile();
|
||||||
|
void clearActiveFile() { active_file_ = File(); }
|
||||||
|
File getActiveFile() { return active_file_; }
|
||||||
|
|
||||||
|
void setMpeEnabled(bool enabled);
|
||||||
|
virtual void beginChangeGesture(const std::string& name) { }
|
||||||
|
virtual void endChangeGesture(const std::string& name) { }
|
||||||
|
virtual void setValueNotifyHost(const std::string& name, vital::mono_float value) { }
|
||||||
|
|
||||||
|
void armMidiLearn(const std::string& name);
|
||||||
|
void cancelMidiLearn();
|
||||||
|
void clearMidiLearn(const std::string& name);
|
||||||
|
bool isMidiMapped(const std::string& name);
|
||||||
|
|
||||||
|
void setAuthor(const String& author);
|
||||||
|
void setComments(const String& comments);
|
||||||
|
void setStyle(const String& comments);
|
||||||
|
void setPresetName(const String& preset_name);
|
||||||
|
void setMacroName(int index, const String& macro_name);
|
||||||
|
String getAuthor();
|
||||||
|
String getComments();
|
||||||
|
String getStyle();
|
||||||
|
String getPresetName();
|
||||||
|
String getMacroName(int index);
|
||||||
|
|
||||||
|
vital::control_map& getControls() { return controls_; }
|
||||||
|
vital::SoundEngine* getEngine() { return engine_.get(); }
|
||||||
|
MidiKeyboardState* getKeyboardState() { return keyboard_state_.get(); }
|
||||||
|
const vital::poly_float* getOscilloscopeMemory() { return oscilloscope_memory_; }
|
||||||
|
const vital::StereoMemory* getAudioMemory() { return audio_memory_.get(); }
|
||||||
|
const vital::StereoMemory* getEqualizerMemory();
|
||||||
|
vital::ModulationConnectionBank& getModulationBank();
|
||||||
|
void notifyOversamplingChanged();
|
||||||
|
void checkOversampling();
|
||||||
|
virtual const CriticalSection& getCriticalSection() = 0;
|
||||||
|
virtual void pauseProcessing(bool pause) = 0;
|
||||||
|
Tuning* getTuning() { return &tuning_; }
|
||||||
|
|
||||||
|
struct ValueChangedCallback : public CallbackMessage {
|
||||||
|
ValueChangedCallback(std::shared_ptr<SynthBase*> listener, std::string name, vital::mono_float val) :
|
||||||
|
listener(listener), control_name(std::move(name)), value(val) { }
|
||||||
|
|
||||||
|
void messageCallback() override;
|
||||||
|
|
||||||
|
std::weak_ptr<SynthBase*> listener;
|
||||||
|
std::string control_name;
|
||||||
|
vital::mono_float value;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
vital::modulation_change createModulationChange(vital::ModulationConnection* connection);
|
||||||
|
bool isInvalidConnection(const vital::modulation_change& change);
|
||||||
|
virtual SynthGuiInterface* getGuiInterface() = 0;
|
||||||
|
json saveToJson();
|
||||||
|
bool loadFromJson(const json& state);
|
||||||
|
vital::ModulationConnection* getConnection(const std::string& source, const std::string& destination);
|
||||||
|
|
||||||
|
inline bool getNextModulationChange(vital::modulation_change& change) {
|
||||||
|
return modulation_change_queue_.try_dequeue_non_interleaved(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void clearModulationQueue() {
|
||||||
|
vital::modulation_change change;
|
||||||
|
while (modulation_change_queue_.try_dequeue_non_interleaved(change))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void processAudio(AudioSampleBuffer* buffer, int channels, int samples, int offset);
|
||||||
|
void processAudioWithInput(AudioSampleBuffer* buffer, const vital::poly_float* input_buffer,
|
||||||
|
int channels, int samples, int offset);
|
||||||
|
void writeAudio(AudioSampleBuffer* buffer, int channels, int samples, int offset);
|
||||||
|
void processMidi(MidiBuffer& buffer, int start_sample = 0, int end_sample = 0);
|
||||||
|
void processKeyboardEvents(MidiBuffer& buffer, int num_samples);
|
||||||
|
void processModulationChanges();
|
||||||
|
void updateMemoryOutput(int samples, const vital::poly_float* audio);
|
||||||
|
|
||||||
|
std::unique_ptr<vital::SoundEngine> engine_;
|
||||||
|
std::unique_ptr<MidiManager> midi_manager_;
|
||||||
|
std::unique_ptr<MidiKeyboardState> keyboard_state_;
|
||||||
|
|
||||||
|
std::unique_ptr<WavetableCreator> wavetable_creators_[vital::kNumOscillators];
|
||||||
|
std::shared_ptr<SynthBase*> self_reference_;
|
||||||
|
|
||||||
|
File active_file_;
|
||||||
|
vital::poly_float oscilloscope_memory_[2 * vital::kOscilloscopeMemoryResolution];
|
||||||
|
vital::poly_float oscilloscope_memory_write_[2 * vital::kOscilloscopeMemoryResolution];
|
||||||
|
std::unique_ptr<vital::StereoMemory> audio_memory_;
|
||||||
|
vital::mono_float last_played_note_;
|
||||||
|
int last_num_pressed_;
|
||||||
|
vital::mono_float memory_reset_period_;
|
||||||
|
vital::mono_float memory_input_offset_;
|
||||||
|
int memory_index_;
|
||||||
|
bool expired_;
|
||||||
|
|
||||||
|
std::map<std::string, String> save_info_;
|
||||||
|
vital::control_map controls_;
|
||||||
|
vital::CircularQueue<vital::ModulationConnection*> mod_connections_;
|
||||||
|
moodycamel::ConcurrentQueue<vital::control_change> value_change_queue_;
|
||||||
|
moodycamel::ConcurrentQueue<vital::modulation_change> modulation_change_queue_;
|
||||||
|
Tuning tuning_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SynthBase)
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeadlessSynth : public SynthBase {
|
||||||
|
public:
|
||||||
|
virtual const CriticalSection& getCriticalSection() override {
|
||||||
|
return critical_section_;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void pauseProcessing(bool pause) override {
|
||||||
|
if (pause)
|
||||||
|
critical_section_.enter();
|
||||||
|
else
|
||||||
|
critical_section_.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual SynthGuiInterface* getGuiInterface() override { return nullptr; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
CriticalSection critical_section_;
|
||||||
|
};
|
||||||
172
src/common/synth_constants.h
Normal file
172
src/common/synth_constants.h
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "value.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
|
||||||
|
constexpr int kNumLfos = 8;
|
||||||
|
constexpr int kNumOscillators = 3;
|
||||||
|
constexpr int kNumOscillatorWaveFrames = 257;
|
||||||
|
constexpr int kNumEnvelopes = 6;
|
||||||
|
constexpr int kNumRandomLfos = 4;
|
||||||
|
constexpr int kNumMacros = 4;
|
||||||
|
constexpr int kNumFilters = 2;
|
||||||
|
constexpr int kNumFormants = 4;
|
||||||
|
constexpr int kNumChannels = 2;
|
||||||
|
constexpr int kMaxPolyphony = 33;
|
||||||
|
constexpr int kMaxActivePolyphony = 32;
|
||||||
|
constexpr int kLfoDataResolution = 2048;
|
||||||
|
constexpr int kMaxModulationConnections = 64;
|
||||||
|
|
||||||
|
constexpr int kOscilloscopeMemorySampleRate = 22000;
|
||||||
|
constexpr int kOscilloscopeMemoryResolution = 512;
|
||||||
|
constexpr int kAudioMemorySamples = 1 << 15;
|
||||||
|
constexpr int kDefaultWindowWidth = 1400;
|
||||||
|
constexpr int kDefaultWindowHeight = 820;
|
||||||
|
constexpr int kMinWindowWidth = 350;
|
||||||
|
constexpr int kMinWindowHeight = 205;
|
||||||
|
|
||||||
|
constexpr int kDefaultKeyboardOffset = 48;
|
||||||
|
constexpr wchar_t kDefaultKeyboardOctaveUp = 'x';
|
||||||
|
constexpr wchar_t kDefaultKeyboardOctaveDown = 'z';
|
||||||
|
const std::wstring kDefaultKeyboard = L"awsedftgyhujkolp;'";
|
||||||
|
|
||||||
|
const std::string kPresetExtension = "vital";
|
||||||
|
const std::string kWavetableExtension = "vitaltable";
|
||||||
|
const std::string kWavetableExtensionsList = "*." + vital::kWavetableExtension + ";*.wav;*.flac";
|
||||||
|
const std::string kSampleExtensionsList = "*.wav;*.flac";
|
||||||
|
const std::string kSkinExtension = "vitalskin";
|
||||||
|
const std::string kLfoExtension = "vitallfo";
|
||||||
|
const std::string kBankExtension = "vitalbank";
|
||||||
|
|
||||||
|
namespace constants {
|
||||||
|
enum SourceDestination {
|
||||||
|
kFilter1,
|
||||||
|
kFilter2,
|
||||||
|
kDualFilters,
|
||||||
|
kEffects,
|
||||||
|
kDirectOut,
|
||||||
|
kNumSourceDestinations
|
||||||
|
};
|
||||||
|
|
||||||
|
static SourceDestination toggleFilter1(SourceDestination current_destination, bool on) {
|
||||||
|
if (on) {
|
||||||
|
if (current_destination == vital::constants::kFilter2)
|
||||||
|
return vital::constants::kDualFilters;
|
||||||
|
else
|
||||||
|
return vital::constants::kFilter1;
|
||||||
|
}
|
||||||
|
else if (current_destination == vital::constants::kDualFilters)
|
||||||
|
return vital::constants::kFilter2;
|
||||||
|
else if (current_destination == vital::constants::kFilter1)
|
||||||
|
return vital::constants::kEffects;
|
||||||
|
|
||||||
|
return current_destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SourceDestination toggleFilter2(SourceDestination current_destination, bool on) {
|
||||||
|
if (on) {
|
||||||
|
if (current_destination == vital::constants::kFilter1)
|
||||||
|
return vital::constants::kDualFilters;
|
||||||
|
else
|
||||||
|
return vital::constants::kFilter2;
|
||||||
|
}
|
||||||
|
else if (current_destination == vital::constants::kDualFilters)
|
||||||
|
return vital::constants::kFilter1;
|
||||||
|
else if (current_destination == vital::constants::kFilter2)
|
||||||
|
return vital::constants::kEffects;
|
||||||
|
|
||||||
|
return current_destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Effect {
|
||||||
|
kChorus,
|
||||||
|
kCompressor,
|
||||||
|
kDelay,
|
||||||
|
kDistortion,
|
||||||
|
kEq,
|
||||||
|
kFilterFx,
|
||||||
|
kFlanger,
|
||||||
|
kPhaser,
|
||||||
|
kReverb,
|
||||||
|
kNumEffects
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FilterModel {
|
||||||
|
kAnalog,
|
||||||
|
kDirty,
|
||||||
|
kLadder,
|
||||||
|
kDigital,
|
||||||
|
kDiode,
|
||||||
|
kFormant,
|
||||||
|
kComb,
|
||||||
|
kPhase,
|
||||||
|
kNumFilterModels
|
||||||
|
};
|
||||||
|
|
||||||
|
enum RetriggerStyle {
|
||||||
|
kFree,
|
||||||
|
kRetrigger,
|
||||||
|
kSyncToPlayHead,
|
||||||
|
kNumRetriggerStyles,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int kNumSyncedFrequencyRatios = 13;
|
||||||
|
constexpr vital::mono_float kSyncedFrequencyRatios[kNumSyncedFrequencyRatios] = {
|
||||||
|
0.0f,
|
||||||
|
1.0f / 128.0f,
|
||||||
|
1.0f / 64.0f,
|
||||||
|
1.0f / 32.0f,
|
||||||
|
1.0f / 16.0f,
|
||||||
|
1.0f / 8.0f,
|
||||||
|
1.0f / 4.0f,
|
||||||
|
1.0f / 2.0f,
|
||||||
|
1.0f,
|
||||||
|
2.0f,
|
||||||
|
4.0f,
|
||||||
|
8.0f,
|
||||||
|
16.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
const poly_float kLeftOne(1.0f, 0.0f);
|
||||||
|
const poly_float kRightOne(0.0f, 1.0f);
|
||||||
|
const poly_float kFirstVoiceOne(1.0f, 1.0f, 0.0f, 0.0f);
|
||||||
|
const poly_float kSecondVoiceOne(0.0f, 0.0f, 1.0f, 1.0f);
|
||||||
|
const poly_float kStereoSplit = kLeftOne - kRightOne;
|
||||||
|
const poly_float kPolySqrt2 = kSqrt2;
|
||||||
|
const poly_mask kFullMask = poly_float::equal(0.0f, 0.0f);
|
||||||
|
const poly_mask kLeftMask = poly_float::equal(kLeftOne, 1.0f);
|
||||||
|
const poly_mask kRightMask = poly_float::equal(kRightOne, 1.0f);
|
||||||
|
const poly_mask kFirstMask = poly_float::equal(kFirstVoiceOne, 1.0f);
|
||||||
|
const poly_mask kSecondMask = poly_float::equal(kSecondVoiceOne, 1.0f);
|
||||||
|
|
||||||
|
const cr::Value kValueZero(0.0f);
|
||||||
|
const cr::Value kValueOne(1.0f);
|
||||||
|
const cr::Value kValueTwo(2.0f);
|
||||||
|
const cr::Value kValueHalf(0.5f);
|
||||||
|
const cr::Value kValueFifth(0.2f);
|
||||||
|
const cr::Value kValueTenth(0.1f);
|
||||||
|
const cr::Value kValuePi(kPi);
|
||||||
|
const cr::Value kValue2Pi(2.0f * kPi);
|
||||||
|
const cr::Value kValueSqrt2(kSqrt2);
|
||||||
|
const cr::Value kValueNegOne(-1.0f);
|
||||||
|
} // namespace constants
|
||||||
|
} // namespace vital
|
||||||
220
src/common/synth_gui_interface.cpp
Normal file
220
src/common/synth_gui_interface.cpp
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "synth_gui_interface.h"
|
||||||
|
#include "authentication.h"
|
||||||
|
#include "modulation_connection_processor.h"
|
||||||
|
#include "sound_engine.h"
|
||||||
|
#include "load_save.h"
|
||||||
|
#include "synth_base.h"
|
||||||
|
|
||||||
|
SynthGuiData::SynthGuiData(SynthBase* synth_base) : synth(synth_base) {
|
||||||
|
controls = synth->getControls();
|
||||||
|
mono_modulations = synth->getEngine()->getMonoModulations();
|
||||||
|
poly_modulations = synth->getEngine()->getPolyModulations();
|
||||||
|
modulation_sources = synth->getEngine()->getModulationSources();
|
||||||
|
for (int i = 0; i < vital::kNumOscillators; ++i)
|
||||||
|
wavetable_creators[i] = synth->getWavetableCreator(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HEADLESS
|
||||||
|
|
||||||
|
SynthGuiInterface::SynthGuiInterface(SynthBase* synth, bool use_gui) : synth_(synth) { }
|
||||||
|
SynthGuiInterface::~SynthGuiInterface() { }
|
||||||
|
void SynthGuiInterface::updateFullGui() { }
|
||||||
|
void SynthGuiInterface::updateGuiControl(const std::string& name, vital::mono_float value) { }
|
||||||
|
vital::mono_float SynthGuiInterface::getControlValue(const std::string& name) { return 0.0f; }
|
||||||
|
void SynthGuiInterface::connectModulation(std::string source, std::string destination) { }
|
||||||
|
void SynthGuiInterface::connectModulation(vital::ModulationConnection* connection) { }
|
||||||
|
void SynthGuiInterface::setModulationValues(const std::string& source, const std::string& destination,
|
||||||
|
vital::mono_float amount, bool bipolar, bool stereo, bool bypass) { }
|
||||||
|
void SynthGuiInterface::disconnectModulation(std::string source, std::string destination) { }
|
||||||
|
void SynthGuiInterface::disconnectModulation(vital::ModulationConnection* connection) { }
|
||||||
|
void SynthGuiInterface::setFocus() { }
|
||||||
|
void SynthGuiInterface::notifyChange() { }
|
||||||
|
void SynthGuiInterface::notifyFresh() { }
|
||||||
|
void SynthGuiInterface::openSaveDialog() { }
|
||||||
|
void SynthGuiInterface::externalPresetLoaded(File preset) { }
|
||||||
|
void SynthGuiInterface::setGuiSize(float scale) { }
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include "default_look_and_feel.h"
|
||||||
|
#include "full_interface.h"
|
||||||
|
|
||||||
|
SynthGuiInterface::SynthGuiInterface(SynthBase* synth, bool use_gui) : synth_(synth) {
|
||||||
|
if (use_gui) {
|
||||||
|
LineGenerator* lfo_sources[vital::kNumLfos];
|
||||||
|
for (int i = 0; i < vital::kNumLfos; ++i)
|
||||||
|
lfo_sources[i] = synth->getLfoSource(i);
|
||||||
|
SynthGuiData synth_data(synth_);
|
||||||
|
gui_ = std::make_unique<FullInterface>(&synth_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthGuiInterface::~SynthGuiInterface() { }
|
||||||
|
|
||||||
|
void SynthGuiInterface::updateFullGui() {
|
||||||
|
if (gui_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gui_->setAllValues(synth_->getControls());
|
||||||
|
gui_->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::updateGuiControl(const std::string& name, vital::mono_float value) {
|
||||||
|
if (gui_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gui_->setValue(name, value, NotificationType::dontSendNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::mono_float SynthGuiInterface::getControlValue(const std::string& name) {
|
||||||
|
return synth_->getControls()[name]->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::notifyModulationsChanged() {
|
||||||
|
gui_->modulationChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::notifyModulationValueChanged(int index) {
|
||||||
|
gui_->modulationValueChanged(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::connectModulation(std::string source, std::string destination) {
|
||||||
|
bool created = synth_->connectModulation(source, destination);
|
||||||
|
if (created)
|
||||||
|
initModulationValues(source, destination);
|
||||||
|
notifyModulationsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::connectModulation(vital::ModulationConnection* connection) {
|
||||||
|
synth_->connectModulation(connection);
|
||||||
|
notifyModulationsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::initModulationValues(const std::string& source, const std::string& destination) {
|
||||||
|
int connection_index = synth_->getConnectionIndex(source, destination);
|
||||||
|
if (connection_index < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
vital::ModulationConnection* connection = synth_->getModulationBank().atIndex(connection_index);
|
||||||
|
LineGenerator* map_generator = connection->modulation_processor->lineMapGenerator();
|
||||||
|
map_generator->initLinear();
|
||||||
|
|
||||||
|
std::string power_name = "modulation_" + std::to_string(connection_index + 1) + "_power";
|
||||||
|
synth_->valueChanged(power_name, 0.0f);
|
||||||
|
gui_->setValue(power_name, 0.0f, NotificationType::dontSendNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::setModulationValues(const std::string& source, const std::string& destination,
|
||||||
|
vital::mono_float amount, bool bipolar, bool stereo, bool bypass) {
|
||||||
|
int connection_index = synth_->getConnectionIndex(source, destination);
|
||||||
|
if (connection_index < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string number = std::to_string(connection_index + 1);
|
||||||
|
std::string amount_name = "modulation_" + number + "_amount";
|
||||||
|
std::string bipolar_name = "modulation_" + number + "_bipolar";
|
||||||
|
std::string stereo_name = "modulation_" + number + "_stereo";
|
||||||
|
std::string bypass_name = "modulation_" + number + "_bypass";
|
||||||
|
|
||||||
|
float bipolar_amount = bipolar ? 1.0f : 0.0f;
|
||||||
|
float stereo_amount = stereo ? 1.0f : 0.0f;
|
||||||
|
float bypass_amount = bypass ? 1.0f : 0.0f;
|
||||||
|
|
||||||
|
synth_->valueChanged(amount_name, amount);
|
||||||
|
synth_->valueChanged(bipolar_name, bipolar_amount);
|
||||||
|
synth_->valueChanged(stereo_name, stereo_amount);
|
||||||
|
synth_->valueChanged(bypass_name, bypass_amount);
|
||||||
|
gui_->setValue(amount_name, amount, NotificationType::dontSendNotification);
|
||||||
|
gui_->setValue(bipolar_name, bipolar_amount, NotificationType::dontSendNotification);
|
||||||
|
gui_->setValue(stereo_name, stereo_amount, NotificationType::dontSendNotification);
|
||||||
|
gui_->setValue(bypass_name, bypass_amount, NotificationType::dontSendNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::disconnectModulation(std::string source, std::string destination) {
|
||||||
|
synth_->disconnectModulation(source, destination);
|
||||||
|
notifyModulationsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::disconnectModulation(vital::ModulationConnection* connection) {
|
||||||
|
synth_->disconnectModulation(connection);
|
||||||
|
notifyModulationsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::setFocus() {
|
||||||
|
if (gui_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gui_->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::notifyChange() {
|
||||||
|
if (gui_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gui_->notifyChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::notifyFresh() {
|
||||||
|
if (gui_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gui_->notifyFresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::openSaveDialog() {
|
||||||
|
if (gui_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gui_->openSaveDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::externalPresetLoaded(File preset) {
|
||||||
|
if (gui_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gui_->externalPresetLoaded(preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthGuiInterface::setGuiSize(float scale) {
|
||||||
|
if (gui_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Point<int> position = gui_->getScreenBounds().getCentre();
|
||||||
|
const Displays::Display& display = Desktop::getInstance().getDisplays().findDisplayForPoint(position);
|
||||||
|
|
||||||
|
Rectangle<int> display_area = Desktop::getInstance().getDisplays().getTotalBounds(true);
|
||||||
|
ComponentPeer* peer = gui_->getPeer();
|
||||||
|
if (peer)
|
||||||
|
peer->getFrameSize().subtractFrom(display_area);
|
||||||
|
|
||||||
|
float window_size = scale / display.scale;
|
||||||
|
window_size = std::min(window_size, display_area.getWidth() * 1.0f / vital::kDefaultWindowWidth);
|
||||||
|
window_size = std::min(window_size, display_area.getHeight() * 1.0f / vital::kDefaultWindowHeight);
|
||||||
|
LoadSave::saveWindowSize(window_size);
|
||||||
|
|
||||||
|
int width = std::round(window_size * vital::kDefaultWindowWidth);
|
||||||
|
int height = std::round(window_size * vital::kDefaultWindowHeight);
|
||||||
|
|
||||||
|
Rectangle<int> bounds = gui_->getBounds();
|
||||||
|
bounds.setWidth(width);
|
||||||
|
bounds.setHeight(height);
|
||||||
|
gui_->getParentComponent()->setBounds(bounds);
|
||||||
|
gui_->redoBackground();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
79
src/common/synth_gui_interface.h
Normal file
79
src/common/synth_gui_interface.h
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "synth_base.h"
|
||||||
|
|
||||||
|
#if HEADLESS
|
||||||
|
|
||||||
|
class FullInterface { };
|
||||||
|
class AudioDeviceManager { };
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class FullInterface;
|
||||||
|
class Authentication;
|
||||||
|
|
||||||
|
struct SynthGuiData {
|
||||||
|
SynthGuiData(SynthBase* synth_base);
|
||||||
|
|
||||||
|
vital::control_map controls;
|
||||||
|
vital::output_map mono_modulations;
|
||||||
|
vital::output_map poly_modulations;
|
||||||
|
vital::output_map modulation_sources;
|
||||||
|
WavetableCreator* wavetable_creators[vital::kNumOscillators];
|
||||||
|
SynthBase* synth;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SynthGuiInterface {
|
||||||
|
public:
|
||||||
|
SynthGuiInterface(SynthBase* synth, bool use_gui = true);
|
||||||
|
virtual ~SynthGuiInterface();
|
||||||
|
|
||||||
|
virtual AudioDeviceManager* getAudioDeviceManager() { return nullptr; }
|
||||||
|
SynthBase* getSynth() { return synth_; }
|
||||||
|
virtual void updateFullGui();
|
||||||
|
virtual void updateGuiControl(const std::string& name, vital::mono_float value);
|
||||||
|
vital::mono_float getControlValue(const std::string& name);
|
||||||
|
|
||||||
|
void notifyModulationsChanged();
|
||||||
|
void notifyModulationValueChanged(int index);
|
||||||
|
void connectModulation(std::string source, std::string destination);
|
||||||
|
void connectModulation(vital::ModulationConnection* connection);
|
||||||
|
void setModulationValues(const std::string& source, const std::string& destination,
|
||||||
|
vital::mono_float amount, bool bipolar, bool stereo, bool bypass);
|
||||||
|
void initModulationValues(const std::string& source, const std::string& destination);
|
||||||
|
void disconnectModulation(std::string source, std::string destination);
|
||||||
|
void disconnectModulation(vital::ModulationConnection* connection);
|
||||||
|
|
||||||
|
void setFocus();
|
||||||
|
void notifyChange();
|
||||||
|
void notifyFresh();
|
||||||
|
void openSaveDialog();
|
||||||
|
void externalPresetLoaded(File preset);
|
||||||
|
void setGuiSize(float scale);
|
||||||
|
FullInterface* getGui() { return gui_.get(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SynthBase* synth_;
|
||||||
|
|
||||||
|
std::unique_ptr<FullInterface> gui_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SynthGuiInterface)
|
||||||
|
};
|
||||||
|
|
||||||
621
src/common/synth_parameters.cpp
Normal file
621
src/common/synth_parameters.cpp
Normal file
|
|
@ -0,0 +1,621 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "synth_parameters.h"
|
||||||
|
|
||||||
|
#include "compressor.h"
|
||||||
|
#include "distortion.h"
|
||||||
|
#include "digital_svf.h"
|
||||||
|
#include "synth_constants.h"
|
||||||
|
#include "random_lfo.h"
|
||||||
|
#include "synth_lfo.h"
|
||||||
|
#include "synth_oscillator.h"
|
||||||
|
#include "synth_strings.h"
|
||||||
|
#include "voice_handler.h"
|
||||||
|
#include "wavetable.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
|
||||||
|
bool compareValueDetails(const ValueDetails* a, const ValueDetails* b) {
|
||||||
|
if (a->version_added != b->version_added)
|
||||||
|
return a->version_added < b->version_added;
|
||||||
|
|
||||||
|
return a->name.compare(b->name) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace constants;
|
||||||
|
static const std::string kIdDelimiter = "_";
|
||||||
|
static const std::string kEnvIdPrefix = "env";
|
||||||
|
static const std::string kLfoIdPrefix = "lfo";
|
||||||
|
static const std::string kRandomIdPrefix = "random";
|
||||||
|
static const std::string kOscIdPrefix = "osc";
|
||||||
|
static const std::string kFilterIdPrefix = "filter";
|
||||||
|
static const std::string kModulationIdPrefix = "modulation";
|
||||||
|
static const std::string kNameDelimiter = " ";
|
||||||
|
static const std::string kEnvNamePrefix = "Envelope";
|
||||||
|
static const std::string kLfoNamePrefix = "LFO";
|
||||||
|
static const std::string kRandomNamePrefix = "Random LFO";
|
||||||
|
static const std::string kOscNamePrefix = "Oscillator";
|
||||||
|
static const std::string kFilterNamePrefix = "Filter";
|
||||||
|
static const std::string kModulationNamePrefix = "Modulation";
|
||||||
|
|
||||||
|
const ValueDetails ValueDetailsLookup::parameter_list[] = {
|
||||||
|
{ "bypass", 0x000702, 0.0, 1.0, 0.0, 0.0, 60.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Bypass", nullptr },
|
||||||
|
{ "beats_per_minute", 0x000000, 0.333333333, 5.0, 2.0, 0.0, 60.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Beats Per Minute", nullptr },
|
||||||
|
{ "delay_dry_wet", 0x000000, 0.0, 1.0, 0.3334, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Delay Mix", nullptr },
|
||||||
|
{ "delay_feedback", 0x000000, -1.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Delay Feedback", nullptr },
|
||||||
|
{ "delay_frequency", 0x000000, -2.0, 9.0, 2.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, true, " secs", "Delay Frequency", nullptr },
|
||||||
|
{ "delay_aux_frequency", 0x000507, -2.0, 9.0, 2.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, true, " secs", "Delay Frequency 2", nullptr },
|
||||||
|
{ "delay_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Delay Switch", strings::kOffOnNames },
|
||||||
|
{ "delay_style", 0x000000, 0.0, 3.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Delay Style", strings::kDelayStyleNames },
|
||||||
|
{ "delay_filter_cutoff", 0x000000, 8.0, 136.0, 60.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Delay Filter Cutoff", nullptr },
|
||||||
|
{ "delay_filter_spread", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Delay Filter Spread", nullptr },
|
||||||
|
{ "delay_sync", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Delay Sync", strings::kFrequencySyncNames },
|
||||||
|
{ "delay_tempo", 0x000000, 4.0, 12.0, 9.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Delay Tempo", strings::kSyncedFrequencyNames },
|
||||||
|
{ "delay_aux_sync", 0x000507, 0.0, 3.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Delay Sync 2", strings::kFrequencySyncNames },
|
||||||
|
{ "delay_aux_tempo", 0x000507, 4.0, 12.0, 9.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Delay Tempo 2", strings::kSyncedFrequencyNames },
|
||||||
|
{ "distortion_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Distortion Switch", strings::kOffOnNames },
|
||||||
|
{ "distortion_type", 0x000000, 0.0, 5.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Distortion Type", strings::kDistortionTypeNames },
|
||||||
|
{ "distortion_drive", 0x000000, Distortion::kMinDrive, Distortion::kMaxDrive, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Distortion Drive", nullptr },
|
||||||
|
{ "distortion_mix", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Distortion Mix", nullptr },
|
||||||
|
{ "distortion_filter_order", 0x000000, 0.0, 2.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Distortion Filter Order", strings::kDistortionFilterOrderNames },
|
||||||
|
{ "distortion_filter_cutoff", 0x000000, 8.0, 136.0, 80.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Distortion Filter Cutoff", nullptr },
|
||||||
|
{ "distortion_filter_resonance", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Distortion Filter Resonance", nullptr },
|
||||||
|
{ "distortion_filter_blend", 0x000000, 0.0, 2.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Distortion Filter Blend", nullptr },
|
||||||
|
{ "legato", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Legato", strings::kOffOnNames },
|
||||||
|
{ "macro_control_1", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Macro 1", nullptr },
|
||||||
|
{ "macro_control_2", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Macro 2", nullptr },
|
||||||
|
{ "macro_control_3", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Macro 3", nullptr },
|
||||||
|
{ "macro_control_4", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Macro 4", nullptr },
|
||||||
|
{ "pitch_bend_range", 0x000000, 0.0, 48.0, 2.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, " semitones", "Pitch Bend Range", nullptr },
|
||||||
|
{ "polyphony", 0x000000, 1.0, kMaxPolyphony - 1, 8.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, " voices", "Polyphony", nullptr },
|
||||||
|
{ "voice_tune", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, " cents", "Voice Tune", nullptr },
|
||||||
|
{ "voice_transpose", 0x000604, -48.0, 48.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Voice Transpose", nullptr },
|
||||||
|
{ "voice_amplitude", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Voice Amplitude", nullptr },
|
||||||
|
{ "stereo_routing", 0x000000, 0.0, 1.0, 1.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Stereo Routing", nullptr },
|
||||||
|
{ "stereo_mode", 0x000605, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Stereo Mode", strings::kStereoModeNames },
|
||||||
|
{ "portamento_time", 0x000000, -10.0, 4.0, -10.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, false, " secs", "Portamento Time", nullptr },
|
||||||
|
{ "portamento_slope", 0x000000, -8.0, 8.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Portamento Slope", nullptr },
|
||||||
|
{ "portamento_force", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Portamento Force", strings::kOffOnNames },
|
||||||
|
{ "portamento_scale", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Portamento Scale", strings::kOffOnNames },
|
||||||
|
{ "reverb_pre_low_cutoff", 0x000000, 0.0, 128.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Reverb Pre Low Cutoff", nullptr },
|
||||||
|
{ "reverb_pre_high_cutoff", 0x000000, 0.0, 128.0, 110.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Reverb Pre High Cutoff", nullptr },
|
||||||
|
{ "reverb_low_shelf_cutoff", 0x000000, 0.0, 128.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Reverb Low Cutoff", nullptr },
|
||||||
|
{ "reverb_low_shelf_gain", 0x000000, -6.0, 0.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Reverb Low Gain", nullptr },
|
||||||
|
{ "reverb_high_shelf_cutoff", 0x000000, 0.0, 128.0, 90.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Reverb High Cutoff", nullptr },
|
||||||
|
{ "reverb_high_shelf_gain", 0x000000, -6.0, 0.0, -1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Reverb High Gain", nullptr },
|
||||||
|
{ "reverb_dry_wet", 0x000000, 0.0, 1.0, 0.25, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Reverb Mix", nullptr },
|
||||||
|
{ "reverb_delay", 0x000609, 0.0, 0.3, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " secs", "Reverb Delay", nullptr },
|
||||||
|
{ "reverb_decay_time", 0x000000, -6.0, 6.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, false, " secs", "Reverb Decay Time", nullptr },
|
||||||
|
{ "reverb_size", 0x000506, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Reverb Size", nullptr },
|
||||||
|
{ "reverb_chorus_amount", 0x000000, 0.0, 1.0, 0.223607, 0.0, 100.0,
|
||||||
|
ValueDetails::kQuadratic, false, "%", "Reverb Chorus Amount", nullptr },
|
||||||
|
{ "reverb_chorus_frequency", 0x000000, -8.0, 3.0, -2.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, false, " Hz", "Reverb Chorus Frequency", nullptr },
|
||||||
|
{ "reverb_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Reverb Switch", strings::kOffOnNames },
|
||||||
|
{ "sub_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sub Switch", strings::kOffOnNames },
|
||||||
|
{ "sub_direct_out", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sub Direct Out", nullptr },
|
||||||
|
{ "sub_transpose", 0x000000, -48.0, 48.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sub Transpose", nullptr },
|
||||||
|
{ "sub_transpose_quantize", 0x000000, 0.0, 8191.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sub Transpose Quantize", nullptr },
|
||||||
|
{ "sub_tune", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Sub Tune", nullptr },
|
||||||
|
{ "sub_level", 0x000000, 0.0, 1.0, 0.70710678119, 0.0, 1.0,
|
||||||
|
ValueDetails::kQuadratic, false, "", "Sub Level", nullptr },
|
||||||
|
{ "sub_pan", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Sub Pan", nullptr },
|
||||||
|
{ "sub_waveform", 0x000000, 0.0, PredefinedWaveFrames::kNumShapes - 1, 2.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sub Osc Waveform", nullptr },
|
||||||
|
{ "sample_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sample Switch", strings::kOffOnNames },
|
||||||
|
{ "sample_random_phase", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sample Random Phase", strings::kOffOnNames },
|
||||||
|
{ "sample_keytrack", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sample Keytrack", strings::kOffOnNames },
|
||||||
|
{ "sample_loop", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sample Loop", strings::kOffOnNames },
|
||||||
|
{ "sample_bounce", 0x000603, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sample Bounce", strings::kOffOnNames },
|
||||||
|
{ "sample_transpose", 0x000000, -48.0, 48.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sample Transpose", nullptr },
|
||||||
|
{ "sample_transpose_quantize", 0x000000, 0.0, 8191.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sample Transpose Quantize", nullptr },
|
||||||
|
{ "sample_tune", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Sample Tune", nullptr },
|
||||||
|
{ "sample_level", 0x000000, 0.0, 1.0, 0.70710678119, 0.0, 1.0,
|
||||||
|
ValueDetails::kQuadratic, false, "", "Sample Level", nullptr },
|
||||||
|
{ "sample_destination", 0x000500, 0.0, constants::kNumSourceDestinations + constants::kNumEffects, 3.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sample Destination", strings::kDestinationNames },
|
||||||
|
{ "sample_pan", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Sample Pan", nullptr },
|
||||||
|
{ "velocity_track", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Velocity Track", nullptr },
|
||||||
|
{ "volume", 0x000000, 0.0, 7399.4404, 5473.0404, -80, 1.0,
|
||||||
|
ValueDetails::kSquareRoot, false, "dB", "Volume", nullptr },
|
||||||
|
{ "phaser_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Phaser Switch", strings::kOffOnNames },
|
||||||
|
{ "phaser_dry_wet", 0x000000, 0.0, 1.0, 1.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Phaser Mix", nullptr },
|
||||||
|
{ "phaser_feedback", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Phaser Feedback", nullptr },
|
||||||
|
{ "phaser_frequency", 0x000000, -5.0, 2.0, -3.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, true, " secs", "Phaser Frequency", nullptr },
|
||||||
|
{ "phaser_sync", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Phaser Sync",strings::kFrequencySyncNames },
|
||||||
|
{ "phaser_tempo", 0x000000, 0.0, 10.0, 3.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Phaser Tempo", strings::kSyncedFrequencyNames },
|
||||||
|
{ "phaser_center", 0x000000, 8.0, 136.0, 80.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Phaser Center", nullptr },
|
||||||
|
{ "phaser_blend", 0x000509, 0.0, 2.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Phaser Blend", nullptr },
|
||||||
|
{ "phaser_mod_depth", 0x000000, 0.0, 48.0, 24.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Phaser Mod Depth", nullptr },
|
||||||
|
{ "phaser_phase_offset", 0x000000, 0, 1.0, 0.33333333, 0.0, kDegreesPerCycle,
|
||||||
|
ValueDetails::kLinear, false, "", "Phaser Phase Offset", nullptr },
|
||||||
|
{ "flanger_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Flanger Switch", strings::kOffOnNames },
|
||||||
|
{ "flanger_dry_wet", 0x000000, 0.0, 0.5, 0.5, 0.0, 200.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Flanger Mix", nullptr },
|
||||||
|
{ "flanger_feedback", 0x000000, -1.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Flanger Feedback", nullptr },
|
||||||
|
{ "flanger_frequency", 0x000000, -5.0, 2.0, 2.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, true, " secs", "Flanger Frequency", nullptr },
|
||||||
|
{ "flanger_sync", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Flanger Sync", strings::kFrequencySyncNames },
|
||||||
|
{ "flanger_tempo", 0x000000, 0.0, 10.0, 4.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Flanger Tempo", strings::kSyncedFrequencyNames },
|
||||||
|
{ "flanger_center", 0x000505, 8.0, 136.0, 64.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Flanger Center", nullptr },
|
||||||
|
{ "flanger_mod_depth", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Flanger Mod Depth", nullptr },
|
||||||
|
{ "flanger_phase_offset", 0x000000, 0, 1.0, 0.33333333, 0.0, kDegreesPerCycle,
|
||||||
|
ValueDetails::kLinear, false, "", "Flanger Phase Offset", nullptr },
|
||||||
|
{ "chorus_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Chorus Switch", strings::kOffOnNames },
|
||||||
|
{ "chorus_dry_wet", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Chorus Mix", nullptr },
|
||||||
|
{ "chorus_feedback", 0x000000, -0.95, 0.95, 0.4, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Chorus Feedback", nullptr },
|
||||||
|
{ "chorus_cutoff", 0x000000, 8.0, 136.0, 60.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Chorus Filter Cutoff", nullptr },
|
||||||
|
{ "chorus_spread", 0x000607, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Chorus Filter Spread", nullptr },
|
||||||
|
{ "chorus_voices", 0x000508, 1.0, 4.0, 4.0, 0.0, 4.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Chorus Voices", nullptr },
|
||||||
|
{ "chorus_frequency", 0x000000, -6.0, 3.0, -3.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, true, " secs", "Chorus Frequency", nullptr },
|
||||||
|
{ "chorus_sync", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Chorus Sync", strings::kFrequencySyncNames },
|
||||||
|
{ "chorus_tempo", 0x000000, 0.0, 10.0, 4.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Chorus Tempo", strings::kSyncedFrequencyNames },
|
||||||
|
{ "chorus_mod_depth", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Chorus Mod Depth", nullptr },
|
||||||
|
{ "chorus_delay_1", 0x000000, -10.0, -5.64386, -9.0, 0.0, 1000.0,
|
||||||
|
ValueDetails::kExponential, false, "ms", "Chorus Delay 1", nullptr },
|
||||||
|
{ "chorus_delay_2", 0x000000, -10.0, -5.64386, -7.0, 0.0, 1000.0,
|
||||||
|
ValueDetails::kExponential, false, " ms", "Chorus Delay 2", nullptr },
|
||||||
|
{ "compressor_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Compressor Switch", strings::kOffOnNames },
|
||||||
|
{ "compressor_low_upper_threshold", 0x000000, -80.0, 0.0, -28.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Low Upper Threshold", nullptr },
|
||||||
|
{ "compressor_band_upper_threshold", 0x000000, -80.0, 0.0, -25.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Band Upper Threshold", nullptr },
|
||||||
|
{ "compressor_high_upper_threshold", 0x000000, -80.0, 0.0, -30.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "High Upper Threshold", nullptr },
|
||||||
|
{ "compressor_low_lower_threshold", 0x000000, -80.0, 0.0, -35.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Low Lower Threshold", nullptr },
|
||||||
|
{ "compressor_band_lower_threshold", 0x000000, -80.0, 0.0, -36.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Band Lower Threshold", nullptr },
|
||||||
|
{ "compressor_high_lower_threshold", 0x000000, -80.0, 0.0, -35.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "High Lower Threshold", nullptr },
|
||||||
|
{ "compressor_low_upper_ratio", 0x000000, 0.0, 1.0, 0.9, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Low Upper Ratio", nullptr },
|
||||||
|
{ "compressor_band_upper_ratio", 0x000000, 0.0, 1.0, 0.857, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Band Upper Ratio", nullptr },
|
||||||
|
{ "compressor_high_upper_ratio", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "High Upper Ratio", nullptr },
|
||||||
|
{ "compressor_low_lower_ratio", 0x000000, -1.0, 1.0, 0.8, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Low Lower Ratio", nullptr },
|
||||||
|
{ "compressor_band_lower_ratio", 0x000000, -1.0, 1.0, 0.8, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Band Lower Ratio", nullptr },
|
||||||
|
{ "compressor_high_lower_ratio", 0x000000, -1.0, 1.0, 0.8, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "High Lower Ratio", nullptr },
|
||||||
|
{ "compressor_low_gain", 0x000000, -30.0, 30.0, 16.3, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Compressor Low Gain", nullptr },
|
||||||
|
{ "compressor_band_gain", 0x000000, -30.0, 30.0, 11.7, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Compressor Band Gain", nullptr },
|
||||||
|
{ "compressor_high_gain", 0x000000, -30.0, 30.0, 16.3, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "Compressor High Gain", nullptr },
|
||||||
|
{ "compressor_attack", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Compressor Attack", nullptr },
|
||||||
|
{ "compressor_release", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Compressor Release", nullptr },
|
||||||
|
{ "compressor_enabled_bands", 0x000000, 0.0, vital::MultibandCompressor::kNumBandOptions - 1, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Compressor Enabled Bands", strings::kCompressorBandNames },
|
||||||
|
{ "compressor_mix", 0x000602, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Compressor Mix", nullptr },
|
||||||
|
{ "compressor_low_band_unused", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Compressor Unused", nullptr },
|
||||||
|
{ "eq_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "EQ Switch", strings::kOffOnNames },
|
||||||
|
{ "eq_low_mode", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "EQ Low Mode", strings::kEqLowModeNames },
|
||||||
|
{ "eq_low_cutoff", 0x000000, 8.0, 136.0, 40.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "EQ Low Cutoff", nullptr },
|
||||||
|
{ "eq_low_gain", 0x000000, DigitalSvf::kMinGain, DigitalSvf::kMaxGain, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "EQ Low Gain", nullptr },
|
||||||
|
{ "eq_low_resonance", 0x000000, 0.0, 1.0, 0.3163, 0.0, 100.0,
|
||||||
|
ValueDetails::kQuadratic, false, "%", "EQ Low Resonance", nullptr },
|
||||||
|
{ "eq_band_mode", 0x000506, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "EQ Band Mode", strings::kEqBandModeNames },
|
||||||
|
{ "eq_band_cutoff", 0x000000, 8.0, 136.0, 80.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "EQ Band Cutoff", nullptr },
|
||||||
|
{ "eq_band_gain", 0x000000, DigitalSvf::kMinGain, DigitalSvf::kMaxGain, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "EQ Band Gain", nullptr },
|
||||||
|
{ "eq_band_resonance", 0x000000, 0.0, 1.0, 0.4473, 0.0, 100.0,
|
||||||
|
ValueDetails::kQuadratic, false, "", "EQ Band Resonance", nullptr },
|
||||||
|
{ "eq_high_mode", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "EQ High Mode", strings::kEqHighModeNames },
|
||||||
|
{ "eq_high_cutoff", 0x000000, 8.0, 136.0, 100.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "EQ High Cutoff", nullptr },
|
||||||
|
{ "eq_high_gain", 0x000000, DigitalSvf::kMinGain, DigitalSvf::kMaxGain, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " dB", "EQ High Gain", nullptr },
|
||||||
|
{ "eq_high_resonance", 0x000000, 0.0, 1.0, 0.3163, 0.0, 100.0,
|
||||||
|
ValueDetails::kQuadratic, false, "", "EQ High Resonance", nullptr },
|
||||||
|
{ "effect_chain_order", 0x000000, 0.0, vital::utils::factorial(vital::kNumEffects) - 1, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Effect Chain Order", nullptr },
|
||||||
|
{ "voice_priority", 0x000000, 0.0, VoiceHandler::kNumVoicePriorities - 1, VoiceHandler::kRoundRobin, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Voice Priority", strings::kVoicePriorityNames },
|
||||||
|
{ "voice_override", 0x000700, 0.0, VoiceHandler::kNumVoiceOverrides - 1, VoiceHandler::kKill, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Voice Override", strings::kVoiceOverrideNames },
|
||||||
|
{ "oversampling", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Oversampling", strings::kOversamplingNames },
|
||||||
|
{ "pitch_wheel", 0x000400, -1.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Pitch Wheel", nullptr },
|
||||||
|
{ "mod_wheel", 0x000400, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Mod Wheel", nullptr },
|
||||||
|
{ "mpe_enabled", 0x000501, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "MPE Enabled", strings::kOffOnNames },
|
||||||
|
{ "view_spectrogram", 0x000803, 0.0, 2.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "View Spectrogram", strings::kOffOnNames },
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueDetails ValueDetailsLookup::env_parameter_list[] = {
|
||||||
|
{ "delay", 0x000503, 0.0, 1.4142135624, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kQuartic, false, " secs", "Delay", nullptr },
|
||||||
|
{ "attack", 0x000000, 0.0, 2.37842, 0.1495, 0.0, 1.0,
|
||||||
|
ValueDetails::kQuartic, false, " secs", "Attack", nullptr },
|
||||||
|
{ "hold", 0x000504, 0.0, 1.4142135624, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kQuartic, false, " secs", "Hold", nullptr },
|
||||||
|
{ "decay", 0x000000, 0.0, 2.37842, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kQuartic, false, " secs", "Decay", nullptr },
|
||||||
|
{ "release", 0x000000, 0.0, 2.37842, 0.5476, 0.0, 1.0,
|
||||||
|
ValueDetails::kQuartic, false, " secs", "Release", nullptr },
|
||||||
|
{ "attack_power", 0x000000, -20.0, 20.0, 0.0f, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Attack Power", nullptr },
|
||||||
|
{ "decay_power", 0x000000, -20.0, 20.0, -2.0f, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Decay Power", nullptr },
|
||||||
|
{ "release_power", 0x000000, -20.0, 20.0, -2.0f, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Release Power", nullptr },
|
||||||
|
{ "sustain", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Sustain", nullptr },
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueDetails ValueDetailsLookup::lfo_parameter_list[] = {
|
||||||
|
{ "phase", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Phase", nullptr },
|
||||||
|
{ "sync_type", 0x000000, 0.0, SynthLfo::kNumSyncTypes - 1, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sync Type", strings::kSyncNames },
|
||||||
|
{ "frequency", 0x000000, -7.0, 9.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, true, " secs", "Frequency", nullptr },
|
||||||
|
{ "sync", 0x000000, 0.0, SynthLfo::kNumSyncOptions - 1, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sync", strings::kFrequencySyncNames },
|
||||||
|
{ "tempo", 0x000000, 0.0, 12.0, 7.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Tempo", strings::kSyncedFrequencyNames },
|
||||||
|
{ "fade_time", 0x000000, 0.0, 8.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " secs", "Fade In", nullptr },
|
||||||
|
{ "smooth_mode", 0x000801, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Smooth Mode", strings::kOffOnNames },
|
||||||
|
{ "smooth_time", 0x000801, -10.0, 4.0, -7.5, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, false, " secs", "Smooth Time", nullptr },
|
||||||
|
{ "delay_time", 0x000000, 0.0, 4.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " secs", "Delay", nullptr },
|
||||||
|
{ "stereo", 0x000406, -0.5, 0.5, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Stereo", nullptr },
|
||||||
|
{ "keytrack_transpose", 0x000704, -60.0, 36.0, -12.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Transpose", nullptr },
|
||||||
|
{ "keytrack_tune", 0x000704, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Tune", nullptr },
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueDetails ValueDetailsLookup::random_lfo_parameter_list[] = {
|
||||||
|
{ "style", 0x000401, 0.0, RandomLfo::kNumStyles - 1, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Style", strings::kRandomNames },
|
||||||
|
{ "frequency", 0x000401, -7.0, 9.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kExponential, true, " secs", "Frequency", nullptr },
|
||||||
|
{ "sync", 0x000401, 0.0, SynthLfo::kNumSyncOptions - 1, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sync", strings::kFrequencySyncNames },
|
||||||
|
{ "tempo", 0x000401, 0.0, 12.0, 8.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Tempo", strings::kSyncedFrequencyNames },
|
||||||
|
{ "stereo", 0x000401, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Stereo", strings::kOffOnNames },
|
||||||
|
{ "sync_type", 0x000600, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Sync Type", strings::kOffOnNames },
|
||||||
|
{ "keytrack_transpose", 0x000704, -60.0, 36.0, -12.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Transpose", nullptr },
|
||||||
|
{ "keytrack_tune", 0x000704, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Tune", nullptr },
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueDetails ValueDetailsLookup::filter_parameter_list[] = {
|
||||||
|
{ "mix", 0x000000, 0.0f, 1.0f, 1.0f, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Mix", nullptr },
|
||||||
|
{ "cutoff", 0x000000, 8.0, 136.0, 60.0, -60.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Cutoff", nullptr },
|
||||||
|
{ "resonance", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Resonance", nullptr },
|
||||||
|
{ "drive", 0x000000, 0.0, 20.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "dB", "Drive", nullptr },
|
||||||
|
{ "blend", 0x000000, 0.0, 2.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Blend", nullptr },
|
||||||
|
{ "style", 0x000000, 0.0, 9.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Style", strings::kFilterStyleNames },
|
||||||
|
{ "model", 0x000000, 0.0, kNumFilterModels - 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Model", strings::kFilterModelNames },
|
||||||
|
{ "on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Switch", strings::kOffOnNames },
|
||||||
|
{ "blend_transpose", 0x000000, 0.0, 84.0, 42.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, " semitones", "Comb Blend Offset", nullptr },
|
||||||
|
{ "keytrack", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Key Track", nullptr },
|
||||||
|
{ "formant_x", 0x000000, 0.0, 1.0, 0.5, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Formant X", nullptr },
|
||||||
|
{ "formant_y", 0x000000, 0.0, 1.0, 0.5, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Formant Y", nullptr },
|
||||||
|
{ "formant_transpose", 0x000000, -12.0, 12.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Formant Transpose", nullptr },
|
||||||
|
{ "formant_resonance", 0x000000, 0.3, 1.0, 0.85, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Formant Resonance", nullptr },
|
||||||
|
{ "formant_spread", 0x000707, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Formant Spread", nullptr },
|
||||||
|
{ "osc1_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "OSC 1 Input", strings::kOffOnNames },
|
||||||
|
{ "osc2_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "OSC 2 Input", strings::kOffOnNames },
|
||||||
|
{ "osc3_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "OSC 3 Input", strings::kOffOnNames },
|
||||||
|
{ "sample_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "SAMPLE Input", strings::kOffOnNames },
|
||||||
|
{ "filter_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "FILTER Input", strings::kOffOnNames },
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueDetails ValueDetailsLookup::osc_parameter_list[] = {
|
||||||
|
{ "on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Switch", strings::kOffOnNames },
|
||||||
|
{ "transpose", 0x000000, -48.0, 48.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Transpose", nullptr },
|
||||||
|
{ "transpose_quantize", 0x000000, 0.0, 8191.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Transpose Quantize", nullptr },
|
||||||
|
{ "tune", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Tune", nullptr },
|
||||||
|
{ "pan", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Pan", nullptr },
|
||||||
|
{ "stack_style", 0x000000, 0.0, SynthOscillator::kNumUnisonStackTypes - 1, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Stack Style", strings::kUnisonStackNames },
|
||||||
|
{ "unison_detune", 0x000000, 0.0, 10.0, 4.472135955, 0.0, 1.0,
|
||||||
|
ValueDetails::kQuadratic, false, "%", "Unison Detune", nullptr },
|
||||||
|
{ "unison_voices", 0x000000, 1.0, 16.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "v", "Unison Voices", nullptr },
|
||||||
|
{ "unison_blend", 0x000000, 0.0, 1.0, 0.8, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Blend", nullptr },
|
||||||
|
{ "detune_power", 0x000000, -5.0, 5.0, 1.5, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Detune Power", nullptr },
|
||||||
|
{ "detune_range", 0x000000, 0.0, 48.0, 2.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Detune Range", nullptr },
|
||||||
|
{ "level", 0x000000, 0.0, 1.0, 0.70710678119, 0.0, 1.0,
|
||||||
|
ValueDetails::kQuadratic, false, "", "Level", nullptr },
|
||||||
|
{ "midi_track", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Midi Track", strings::kOffOnNames },
|
||||||
|
{ "smooth_interpolation", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Smooth Interpolation", strings::kOffOnNames },
|
||||||
|
{ "spectral_unison", 0x000500, 0.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Spectral Unison", strings::kOffOnNames },
|
||||||
|
{ "wave_frame", 0x000000, 0.0, kNumOscillatorWaveFrames - 1, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Wave Frame", nullptr },
|
||||||
|
{ "frame_spread", 0x000000, -kNumOscillatorWaveFrames / 2, kNumOscillatorWaveFrames / 2, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Unison Frame Spread", nullptr },
|
||||||
|
{ "stereo_spread", 0x000000, 0.0, 1.0, 1.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Stereo Spread", nullptr },
|
||||||
|
{ "phase", 0x000000, 0.0, 1.0, 0.5, 0.0, 360.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Phase", nullptr },
|
||||||
|
{ "distortion_phase", 0x000000, 0.0, 1.0, 0.5, 0.0, 360.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Distortion Phase", nullptr },
|
||||||
|
{ "random_phase", 0x000000, 0.0, 1.0, 1.0, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Phase Randomization", nullptr },
|
||||||
|
{ "distortion_type", 0x000000, 0.0, SynthOscillator::kNumDistortionTypes - 1, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Distortion Type", strings::kPhaseDistortionNames },
|
||||||
|
{ "distortion_amount", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Distortion Amount", nullptr },
|
||||||
|
{ "distortion_spread", 0x000000, -0.5, 0.5, 0.0, 0.0, 200.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Distortion Spread", nullptr },
|
||||||
|
{ "spectral_morph_type", 0x000407, 0.0, SynthOscillator::kNumSpectralMorphTypes - 1, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Frequency Morph Type", strings::kSpectralMorphNames },
|
||||||
|
{ "spectral_morph_amount", 0x000407, 0.0, 1.0, 0.5, 0.0, 100.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Frequency Morph Amount", nullptr },
|
||||||
|
{ "spectral_morph_spread", 0x000407, -0.5, 0.5, 0.0, 0.0, 200.0,
|
||||||
|
ValueDetails::kLinear, false, "%", "Frequency Morph Spread", nullptr },
|
||||||
|
{ "destination", 0x000500, 0.0, constants::kNumSourceDestinations + constants::kNumEffects, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Destination", strings::kDestinationNames },
|
||||||
|
{ "view_2d", 0x000402, 0.0, 2.0, 1.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "View 2D", strings::kOffOnNames },
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueDetails ValueDetailsLookup::mod_parameter_list[] = {
|
||||||
|
{ "amount", 0x000000, -1.0, 1.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Amount", nullptr },
|
||||||
|
{ "power", 0x000000, -10.0, 10.0, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kLinear, false, "", "Power", nullptr },
|
||||||
|
{ "bipolar", 0x000000, 0.0, 1.0f, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Bipolar", strings::kOffOnNames },
|
||||||
|
{ "stereo", 0x000000, 0.0, 1.0f, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Stereo", strings::kOffOnNames },
|
||||||
|
{ "bypass", 0x000000, 0.0, 1.0f, 0.0, 0.0, 1.0,
|
||||||
|
ValueDetails::kIndexed, false, "", "Bypass", strings::kOffOnNames }
|
||||||
|
};
|
||||||
|
|
||||||
|
ValueDetailsLookup::ValueDetailsLookup() {
|
||||||
|
static constexpr int kNumOscillatorsOld = 2;
|
||||||
|
static constexpr int kNewOscillatorVersion = 0x000500;
|
||||||
|
static constexpr int kOldMaxModulations = 32;
|
||||||
|
static constexpr int kNewModulationVersion = 0x000601;
|
||||||
|
|
||||||
|
int num_parameters = sizeof(parameter_list) / sizeof(ValueDetails);
|
||||||
|
for (int i = 0; i < num_parameters; ++i) {
|
||||||
|
details_lookup_[parameter_list[i].name] = parameter_list[i];
|
||||||
|
details_list_.push_back(¶meter_list[i]);
|
||||||
|
|
||||||
|
VITAL_ASSERT(parameter_list[i].default_value <= parameter_list[i].max);
|
||||||
|
VITAL_ASSERT(parameter_list[i].default_value >= parameter_list[i].min);
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_env_parameters = sizeof(env_parameter_list) / sizeof(ValueDetails);
|
||||||
|
for (int env = 0; env < kNumEnvelopes; ++env)
|
||||||
|
addParameterGroup(env_parameter_list, num_env_parameters, env, kEnvIdPrefix, kEnvNamePrefix);
|
||||||
|
|
||||||
|
int num_lfo_parameters = sizeof(lfo_parameter_list) / sizeof(ValueDetails);
|
||||||
|
for (int lfo = 0; lfo < kNumLfos; ++lfo)
|
||||||
|
addParameterGroup(lfo_parameter_list, num_lfo_parameters, lfo, kLfoIdPrefix, kLfoNamePrefix);
|
||||||
|
|
||||||
|
int num_random_lfo_parameters = sizeof(random_lfo_parameter_list) / sizeof(ValueDetails);
|
||||||
|
for (int lfo = 0; lfo < kNumRandomLfos; ++lfo)
|
||||||
|
addParameterGroup(random_lfo_parameter_list, num_random_lfo_parameters, lfo, kRandomIdPrefix, kRandomNamePrefix);
|
||||||
|
|
||||||
|
int num_osc_parameters = sizeof(osc_parameter_list) / sizeof(ValueDetails);
|
||||||
|
for (int osc = 0; osc < kNumOscillatorsOld; ++osc)
|
||||||
|
addParameterGroup(osc_parameter_list, num_osc_parameters, osc, kOscIdPrefix, kOscNamePrefix);
|
||||||
|
for (int osc = kNumOscillatorsOld; osc < kNumOscillators; ++osc) {
|
||||||
|
addParameterGroup(osc_parameter_list, num_osc_parameters, osc, kOscIdPrefix,
|
||||||
|
kOscNamePrefix, kNewOscillatorVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_filter_parameters = sizeof(filter_parameter_list) / sizeof(ValueDetails);
|
||||||
|
for (int filter = 0; filter < kNumFilters; ++filter)
|
||||||
|
addParameterGroup(filter_parameter_list, num_filter_parameters, filter, kFilterIdPrefix, kFilterNamePrefix);
|
||||||
|
|
||||||
|
addParameterGroup(filter_parameter_list, num_filter_parameters, "fx", kFilterIdPrefix, kFilterNamePrefix);
|
||||||
|
|
||||||
|
int num_mod_parameters = sizeof(mod_parameter_list) / sizeof(ValueDetails);
|
||||||
|
for (int modulation = 0; modulation < kOldMaxModulations; ++modulation) {
|
||||||
|
addParameterGroup(mod_parameter_list, num_mod_parameters, modulation,
|
||||||
|
kModulationIdPrefix, kModulationNamePrefix);
|
||||||
|
}
|
||||||
|
for (int modulation = kOldMaxModulations; modulation < kMaxModulationConnections; ++modulation) {
|
||||||
|
addParameterGroup(mod_parameter_list, num_mod_parameters, modulation,
|
||||||
|
kModulationIdPrefix, kModulationNamePrefix, kNewModulationVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
details_lookup_["osc_1_on"].default_value = 1.0f;
|
||||||
|
details_lookup_["osc_2_destination"].default_value = 1.0f;
|
||||||
|
details_lookup_["osc_3_destination"].default_value = 3.0f;
|
||||||
|
details_lookup_["filter_1_osc1_input"].default_value = 1.0f;
|
||||||
|
details_lookup_["filter_2_osc2_input"].default_value = 1.0f;
|
||||||
|
|
||||||
|
std::sort(details_list_.begin(), details_list_.end(), compareValueDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ValueDetailsLookup::addParameterGroup(const ValueDetails* list, int num_parameters, int index,
|
||||||
|
std::string id_prefix, std::string name_prefix, int version) {
|
||||||
|
std::string string_num = std::to_string(index + 1);
|
||||||
|
addParameterGroup(list, num_parameters, string_num, id_prefix, name_prefix, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ValueDetailsLookup::addParameterGroup(const ValueDetails* list, int num_parameters, std::string id,
|
||||||
|
std::string id_prefix, std::string name_prefix, int version) {
|
||||||
|
std::string id_start = id_prefix + kIdDelimiter + id + kIdDelimiter;
|
||||||
|
std::string name_start = name_prefix + kNameDelimiter + id + kNameDelimiter;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_parameters; ++i) {
|
||||||
|
ValueDetails details = list[i];
|
||||||
|
if (version > details.version_added)
|
||||||
|
details.version_added = version;
|
||||||
|
|
||||||
|
details.name = id_start + details.name;
|
||||||
|
details.local_description = details.display_name;
|
||||||
|
details.display_name = name_start + details.display_name;
|
||||||
|
details_lookup_[details.name] = details;
|
||||||
|
details_list_.push_back(&details_lookup_[details.name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueDetailsLookup Parameters::lookup_;
|
||||||
|
|
||||||
|
} // namespace vital
|
||||||
147
src/common/synth_parameters.h
Normal file
147
src/common/synth_parameters.h
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
|
||||||
|
struct ValueDetails {
|
||||||
|
enum ValueScale {
|
||||||
|
kIndexed,
|
||||||
|
kLinear,
|
||||||
|
kQuadratic,
|
||||||
|
kCubic,
|
||||||
|
kQuartic,
|
||||||
|
kSquareRoot,
|
||||||
|
kExponential
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
int version_added = 0;
|
||||||
|
mono_float min = 0.0f;
|
||||||
|
mono_float max = 1.0f;
|
||||||
|
mono_float default_value = 0.0f;
|
||||||
|
|
||||||
|
// post_offset used to offset quadratic and exponential scaling.
|
||||||
|
mono_float post_offset = 0.0f;
|
||||||
|
|
||||||
|
mono_float display_multiply = 1.0f;
|
||||||
|
ValueScale value_scale = kLinear;
|
||||||
|
bool display_invert = false;
|
||||||
|
std::string display_units;
|
||||||
|
std::string display_name;
|
||||||
|
const std::string* string_lookup = nullptr;
|
||||||
|
std::string local_description;
|
||||||
|
} typedef ValueDetails;
|
||||||
|
|
||||||
|
class ValueDetailsLookup {
|
||||||
|
public:
|
||||||
|
ValueDetailsLookup();
|
||||||
|
const bool isParameter(const std::string& name) const {
|
||||||
|
return details_lookup_.count(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ValueDetails& getDetails(const std::string& name) const {
|
||||||
|
auto details = details_lookup_.find(name);
|
||||||
|
VITAL_ASSERT(details != details_lookup_.end());
|
||||||
|
return details->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ValueDetails* getDetails(int index) const {
|
||||||
|
return details_list_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getDisplayName(const std::string& name) const {
|
||||||
|
return getDetails(name).display_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getNumParameters() const {
|
||||||
|
return static_cast<int>(details_list_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
mono_float getParameterRange(const std::string& name) const {
|
||||||
|
auto details = details_lookup_.find(name);
|
||||||
|
VITAL_ASSERT(details != details_lookup_.end());
|
||||||
|
return details->second.max - details->second.min;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, ValueDetails> getAllDetails() const {
|
||||||
|
return details_lookup_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addParameterGroup(const ValueDetails* list, int num_parameters, int index,
|
||||||
|
std::string id_prefix, std::string name_prefix, int version = -1);
|
||||||
|
|
||||||
|
void addParameterGroup(const ValueDetails* list, int num_parameters, std::string id,
|
||||||
|
std::string id_prefix, std::string name_prefix, int version = -1);
|
||||||
|
|
||||||
|
static const ValueDetails parameter_list[];
|
||||||
|
static const ValueDetails env_parameter_list[];
|
||||||
|
static const ValueDetails lfo_parameter_list[];
|
||||||
|
static const ValueDetails random_lfo_parameter_list[];
|
||||||
|
static const ValueDetails filter_parameter_list[];
|
||||||
|
static const ValueDetails osc_parameter_list[];
|
||||||
|
static const ValueDetails mod_parameter_list[];
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, ValueDetails> details_lookup_;
|
||||||
|
std::vector<const ValueDetails*> details_list_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ValueDetailsLookup)
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parameters {
|
||||||
|
public:
|
||||||
|
static const ValueDetails& getDetails(const std::string& name) {
|
||||||
|
return lookup_.getDetails(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getNumParameters() {
|
||||||
|
return lookup_.getNumParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ValueDetails* getDetails(int index) {
|
||||||
|
return lookup_.getDetails(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string getDisplayName(const std::string& name) {
|
||||||
|
return lookup_.getDisplayName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const mono_float getParameterRange(const std::string& name) {
|
||||||
|
return lookup_.getParameterRange(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const bool isParameter(const std::string& name) {
|
||||||
|
return lookup_.isParameter(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::map<std::string, ValueDetails> getAllDetails() {
|
||||||
|
return lookup_.getAllDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
static ValueDetailsLookup lookup_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Parameters() { }
|
||||||
|
};
|
||||||
|
} // namespace vital
|
||||||
|
|
||||||
72
src/common/synth_types.cpp
Normal file
72
src/common/synth_types.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "synth_types.h"
|
||||||
|
|
||||||
|
#include "synth_constants.h"
|
||||||
|
#include "modulation_connection_processor.h"
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
namespace {
|
||||||
|
const std::string kModulationSourceDelimiter = "_";
|
||||||
|
const std::set<std::string> kBipolarModulationSourcePrefixes = {
|
||||||
|
"lfo",
|
||||||
|
"stereo",
|
||||||
|
"random",
|
||||||
|
"pitch"
|
||||||
|
};
|
||||||
|
|
||||||
|
force_inline bool isConnectionAvailable(ModulationConnection* connection) {
|
||||||
|
return connection->source_name.empty() && connection->destination_name.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModulationConnection::ModulationConnection(int index, std::string from, std::string to) :
|
||||||
|
source_name(std::move(from)), destination_name(std::move(to)) {
|
||||||
|
modulation_processor = std::make_unique<ModulationConnectionProcessor>(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModulationConnection::~ModulationConnection() { }
|
||||||
|
|
||||||
|
bool ModulationConnection::isModulationSourceDefaultBipolar(const std::string& source) {
|
||||||
|
std::size_t pos = source.find(kModulationSourceDelimiter);
|
||||||
|
std::string prefix = source.substr(0, pos);
|
||||||
|
return kBipolarModulationSourcePrefixes.count(prefix) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModulationConnectionBank::ModulationConnectionBank() {
|
||||||
|
for (int i = 0; i < kMaxModulationConnections; ++i) {
|
||||||
|
std::unique_ptr<ModulationConnection> connection = std::make_unique<ModulationConnection>(i);
|
||||||
|
all_connections_.push_back(std::move(connection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModulationConnectionBank::~ModulationConnectionBank() { }
|
||||||
|
|
||||||
|
ModulationConnection* ModulationConnectionBank::createConnection(const std::string& from, const std::string& to) {
|
||||||
|
int index = 1;
|
||||||
|
for (auto& connection : all_connections_) {
|
||||||
|
std::string invalid_connection = "modulation_" + std::to_string(index++) + "_amount";
|
||||||
|
if (to != invalid_connection && isConnectionAvailable(connection.get())) {
|
||||||
|
connection->resetConnection(from, to);
|
||||||
|
connection->modulation_processor->setBipolar(ModulationConnection::isModulationSourceDefaultBipolar(from));
|
||||||
|
return connection.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
} // namespace vital
|
||||||
105
src/common/synth_types.h
Normal file
105
src/common/synth_types.h
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "circular_queue.h"
|
||||||
|
#include "synth_parameters.h"
|
||||||
|
#include "operators.h"
|
||||||
|
#include "value.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
|
||||||
|
class ValueSwitch;
|
||||||
|
class ModulationConnectionProcessor;
|
||||||
|
|
||||||
|
struct ModulationConnection {
|
||||||
|
ModulationConnection(int index) : ModulationConnection(index, "", "") { }
|
||||||
|
|
||||||
|
ModulationConnection(int index, std::string from, std::string to);
|
||||||
|
|
||||||
|
~ModulationConnection();
|
||||||
|
|
||||||
|
static bool isModulationSourceDefaultBipolar(const std::string& source);
|
||||||
|
|
||||||
|
void resetConnection(const std::string& from, const std::string& to) {
|
||||||
|
source_name = from;
|
||||||
|
destination_name = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string source_name;
|
||||||
|
std::string destination_name;
|
||||||
|
std::unique_ptr<ModulationConnectionProcessor> modulation_processor;
|
||||||
|
|
||||||
|
private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModulationConnection)
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModulationConnectionBank {
|
||||||
|
public:
|
||||||
|
ModulationConnectionBank();
|
||||||
|
~ModulationConnectionBank();
|
||||||
|
ModulationConnection* createConnection(const std::string& from, const std::string& to);
|
||||||
|
|
||||||
|
ModulationConnection* atIndex(int index) { return all_connections_[index].get(); }
|
||||||
|
size_t numConnections() { return all_connections_.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<ModulationConnection>> all_connections_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModulationConnectionBank)
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringLayout {
|
||||||
|
public:
|
||||||
|
StringLayout() : up_key_(0), down_key_(0) { }
|
||||||
|
std::wstring getLayout() { return layout_; }
|
||||||
|
void setLayout(const std::wstring& layout) { layout_ = layout; }
|
||||||
|
|
||||||
|
wchar_t getUpKey() { return up_key_; }
|
||||||
|
void setUpKey(wchar_t up_key) { up_key_ = up_key; }
|
||||||
|
|
||||||
|
wchar_t getDownKey() { return down_key_; }
|
||||||
|
void setDownKey(wchar_t down_key) { down_key_ = down_key; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::wstring layout_;
|
||||||
|
int up_key_;
|
||||||
|
int down_key_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(StringLayout)
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Output* source;
|
||||||
|
Processor* mono_destination;
|
||||||
|
Processor* poly_destination;
|
||||||
|
mono_float destination_scale;
|
||||||
|
ValueSwitch* mono_modulation_switch;
|
||||||
|
ValueSwitch* poly_modulation_switch;
|
||||||
|
ModulationConnectionProcessor* modulation_processor;
|
||||||
|
bool disconnecting;
|
||||||
|
int num_audio_rate;
|
||||||
|
} modulation_change;
|
||||||
|
|
||||||
|
typedef std::map<std::string, Value*> control_map;
|
||||||
|
typedef std::pair<Value*, mono_float> control_change;
|
||||||
|
typedef std::map<std::string, Processor*> input_map;
|
||||||
|
typedef std::map<std::string, Output*> output_map;
|
||||||
|
} // namespace vital
|
||||||
|
|
||||||
433
src/common/tuning.cpp
Normal file
433
src/common/tuning.cpp
Normal file
|
|
@ -0,0 +1,433 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tuning.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr char kScalaFileExtension[] = ".scl";
|
||||||
|
constexpr char kKeyboardMapExtension[] = ".kbm";
|
||||||
|
constexpr char kTunFileExtension[] = ".tun";
|
||||||
|
constexpr int kDefaultMidiReference = 60;
|
||||||
|
constexpr char kScalaKbmComment = '!';
|
||||||
|
constexpr char kTunComment = ';';
|
||||||
|
|
||||||
|
enum ScalaReadingState {
|
||||||
|
kDescription,
|
||||||
|
kScaleLength,
|
||||||
|
kScaleRatios
|
||||||
|
};
|
||||||
|
|
||||||
|
enum KbmPositions {
|
||||||
|
kMapSizePosition,
|
||||||
|
kStartMidiMapPosition,
|
||||||
|
kEndMidiMapPosition,
|
||||||
|
kMidiMapMiddlePosition,
|
||||||
|
kReferenceNotePosition,
|
||||||
|
kReferenceFrequencyPosition,
|
||||||
|
kScaleDegreePosition,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TunReadingState {
|
||||||
|
kScanningForSection,
|
||||||
|
kTuning,
|
||||||
|
kExactTuning
|
||||||
|
};
|
||||||
|
|
||||||
|
String extractFirstToken(const String& source) {
|
||||||
|
StringArray tokens;
|
||||||
|
tokens.addTokens(source, false);
|
||||||
|
return tokens[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
float readCentsToTranspose(const String& cents) {
|
||||||
|
return cents.getFloatValue() / vital::kCentsPerNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
float readRatioToTranspose(const String& ratio) {
|
||||||
|
StringArray tokens;
|
||||||
|
tokens.addTokens(ratio, "/", "");
|
||||||
|
float value = tokens[0].getIntValue();
|
||||||
|
|
||||||
|
if (tokens.size() == 2)
|
||||||
|
value /= tokens[1].getIntValue();
|
||||||
|
|
||||||
|
return vital::utils::ratioToMidiTranspose(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
String readTunSection(const String& line) {
|
||||||
|
return line.substring(1, line.length() - 1).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isBaseFrequencyAssignment(const String& line) {
|
||||||
|
return line.upToFirstOccurrenceOf("=", false, true).toLowerCase().trim() == "basefreq";
|
||||||
|
}
|
||||||
|
|
||||||
|
int getNoteAssignmentIndex(const String& line) {
|
||||||
|
String variable = line.upToFirstOccurrenceOf("=", false, true);
|
||||||
|
StringArray tokens;
|
||||||
|
tokens.addTokens(variable, false);
|
||||||
|
if (tokens.size() <= 1 || tokens[0].toLowerCase() != "note")
|
||||||
|
return -1;
|
||||||
|
int index = tokens[1].getIntValue();
|
||||||
|
if (index < 0 || index >= vital::kMidiSize)
|
||||||
|
return -1;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getAssignmentValue(const String& line) {
|
||||||
|
String value = line.fromLastOccurrenceOf("=", false, true).trim();
|
||||||
|
return value.getFloatValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String Tuning::allFileExtensions() {
|
||||||
|
return String("*") + kScalaFileExtension + String(";") +
|
||||||
|
String("*") + kKeyboardMapExtension + String(";") +
|
||||||
|
String("*") + kTunFileExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Tuning::noteToMidiKey(const String& note_text) {
|
||||||
|
constexpr int kNotesInScale = 7;
|
||||||
|
constexpr int kOctaveStart = -1;
|
||||||
|
constexpr int kScale[kNotesInScale] = { -3, -1, 0, 2, 4, 5, 7 };
|
||||||
|
|
||||||
|
String text = note_text.toLowerCase().removeCharacters(" ");
|
||||||
|
if (note_text.length() < 2)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
char note_in_scale = text[0] - 'a';
|
||||||
|
if (note_in_scale < 0 || note_in_scale >= kNotesInScale)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int offset = kScale[note_in_scale];
|
||||||
|
text = text.substring(1);
|
||||||
|
if (text[0] == '#') {
|
||||||
|
text = text.substring(1);
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
else if (text[0] == 'b') {
|
||||||
|
text = text.substring(1);
|
||||||
|
offset--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.length() == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
bool negative = false;
|
||||||
|
if (text[0] == '-') {
|
||||||
|
text = text.substring(1);
|
||||||
|
negative = true;
|
||||||
|
if (text.length() == 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int octave = text[0] - '0';
|
||||||
|
if (negative)
|
||||||
|
octave = -octave;
|
||||||
|
octave = octave - kOctaveStart;
|
||||||
|
return vital::kNotesPerOctave * octave + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuning Tuning::getTuningForFile(File file) {
|
||||||
|
return Tuning(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::loadFile(File file) {
|
||||||
|
String extension = file.getFileExtension().toLowerCase();
|
||||||
|
if (extension == String(kScalaFileExtension))
|
||||||
|
loadScalaFile(file);
|
||||||
|
else if (extension == String(kTunFileExtension))
|
||||||
|
loadTunFile(file);
|
||||||
|
else if (extension == String(kKeyboardMapExtension))
|
||||||
|
loadKeyboardMapFile(file);
|
||||||
|
|
||||||
|
default_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::loadScalaFile(const StringArray& scala_lines) {
|
||||||
|
ScalaReadingState state = kDescription;
|
||||||
|
|
||||||
|
int scale_length = 1;
|
||||||
|
std::vector<float> scale;
|
||||||
|
scale.push_back(0.0f);
|
||||||
|
|
||||||
|
for (const String& line : scala_lines) {
|
||||||
|
String trimmed_line = line.trim();
|
||||||
|
if (trimmed_line.length() > 0 && trimmed_line[0] == kScalaKbmComment)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (scale.size() >= scale_length + 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case kDescription:
|
||||||
|
state = kScaleLength;
|
||||||
|
break;
|
||||||
|
case kScaleLength:
|
||||||
|
scale_length = extractFirstToken(trimmed_line).getIntValue();
|
||||||
|
state = kScaleRatios;
|
||||||
|
break;
|
||||||
|
case kScaleRatios: {
|
||||||
|
String tuning = extractFirstToken(trimmed_line);
|
||||||
|
if (tuning.contains("."))
|
||||||
|
scale.push_back(readCentsToTranspose(tuning));
|
||||||
|
else
|
||||||
|
scale.push_back(readRatioToTranspose(tuning));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboard_mapping_.clear();
|
||||||
|
for (int i = 0; i < scale.size() - 1; ++i)
|
||||||
|
keyboard_mapping_.push_back(i);
|
||||||
|
scale_start_midi_note_ = kDefaultMidiReference;
|
||||||
|
reference_midi_note_ = 0;
|
||||||
|
|
||||||
|
loadScale(scale);
|
||||||
|
default_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::loadScalaFile(File scala_file) {
|
||||||
|
StringArray lines;
|
||||||
|
scala_file.readLines(lines);
|
||||||
|
loadScalaFile(lines);
|
||||||
|
tuning_name_ = scala_file.getFileNameWithoutExtension().toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::loadKeyboardMapFile(File kbm_file) {
|
||||||
|
static constexpr int kHeaderSize = 7;
|
||||||
|
|
||||||
|
StringArray lines;
|
||||||
|
kbm_file.readLines(lines);
|
||||||
|
|
||||||
|
float header_data[kHeaderSize];
|
||||||
|
memset(header_data, 0, kHeaderSize * sizeof(float));
|
||||||
|
int header_position = 0;
|
||||||
|
int map_size = 0;
|
||||||
|
int last_scale_value = 0;
|
||||||
|
keyboard_mapping_.clear();
|
||||||
|
|
||||||
|
for (const String& line : lines) {
|
||||||
|
String trimmed_line = line.trim();
|
||||||
|
if (trimmed_line.length() > 0 && trimmed_line[0] == kScalaKbmComment)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (header_position >= kHeaderSize) {
|
||||||
|
String token = extractFirstToken(trimmed_line);
|
||||||
|
if (token.toLowerCase()[0] != 'x')
|
||||||
|
last_scale_value = token.getIntValue();
|
||||||
|
|
||||||
|
keyboard_mapping_.push_back(last_scale_value);
|
||||||
|
|
||||||
|
if (keyboard_mapping_.size() >= map_size)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
header_data[header_position] = extractFirstToken(trimmed_line).getFloatValue();
|
||||||
|
if (header_position == kMapSizePosition)
|
||||||
|
map_size = header_data[header_position];
|
||||||
|
header_position++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setStartMidiNote(header_data[kMidiMapMiddlePosition]);
|
||||||
|
setReferenceNoteFrequency(header_data[kReferenceNotePosition], header_data[kReferenceFrequencyPosition]);
|
||||||
|
loadScale(scale_);
|
||||||
|
|
||||||
|
mapping_name_ = kbm_file.getFileNameWithoutExtension().toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::loadTunFile(File tun_file) {
|
||||||
|
keyboard_mapping_.clear();
|
||||||
|
|
||||||
|
TunReadingState state = kScanningForSection;
|
||||||
|
StringArray lines;
|
||||||
|
tun_file.readLines(lines);
|
||||||
|
|
||||||
|
int last_read_note = 0;
|
||||||
|
float base_frequency = vital::kMidi0Frequency;
|
||||||
|
std::vector<float> scale;
|
||||||
|
for (int i = 0; i < vital::kMidiSize; ++i)
|
||||||
|
scale.push_back(i);
|
||||||
|
|
||||||
|
for (const String& line : lines) {
|
||||||
|
String trimmed_line = line.trim();
|
||||||
|
if (trimmed_line.length() == 0 || trimmed_line[0] == kTunComment)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (trimmed_line[0] == '[') {
|
||||||
|
String section = readTunSection(trimmed_line);
|
||||||
|
if (section == "tuning")
|
||||||
|
state = kTuning;
|
||||||
|
else if (section == "exact tuning")
|
||||||
|
state = kExactTuning;
|
||||||
|
else
|
||||||
|
state = kScanningForSection;
|
||||||
|
}
|
||||||
|
else if (state == kTuning || state == kExactTuning) {
|
||||||
|
if (isBaseFrequencyAssignment(trimmed_line))
|
||||||
|
base_frequency = getAssignmentValue(trimmed_line);
|
||||||
|
else {
|
||||||
|
int index = getNoteAssignmentIndex(trimmed_line);
|
||||||
|
last_read_note = std::max(last_read_note, index);
|
||||||
|
if (index >= 0)
|
||||||
|
scale[index] = getAssignmentValue(trimmed_line) / vital::kCentsPerNote;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scale.resize(last_read_note + 1);
|
||||||
|
|
||||||
|
loadScale(scale);
|
||||||
|
setStartMidiNote(0);
|
||||||
|
setReferenceFrequency(base_frequency);
|
||||||
|
tuning_name_ = tun_file.getFileNameWithoutExtension().toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuning::Tuning() : default_(true) {
|
||||||
|
scale_start_midi_note_ = kDefaultMidiReference;
|
||||||
|
reference_midi_note_ = 0;
|
||||||
|
|
||||||
|
setDefaultTuning();
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuning::Tuning(File file) : Tuning() {
|
||||||
|
loadFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::loadScale(std::vector<float> scale) {
|
||||||
|
scale_ = scale;
|
||||||
|
if (scale.size() <= 1) {
|
||||||
|
setConstantTuning(kDefaultMidiReference);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int scale_size = static_cast<int>(scale.size() - 1);
|
||||||
|
int mapping_size = scale_size;
|
||||||
|
if (keyboard_mapping_.size())
|
||||||
|
mapping_size = static_cast<int>(keyboard_mapping_.size());
|
||||||
|
|
||||||
|
float octave_offset = scale[scale_size];
|
||||||
|
int start_octave = -kTuningCenter / mapping_size - 1;
|
||||||
|
int mapping_position = -kTuningCenter - start_octave * mapping_size;
|
||||||
|
|
||||||
|
float current_offset = start_octave * octave_offset;
|
||||||
|
for (int i = 0; i < kTuningSize; ++i) {
|
||||||
|
if (mapping_position >= mapping_size) {
|
||||||
|
current_offset += octave_offset;
|
||||||
|
mapping_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int note_in_scale = mapping_position;
|
||||||
|
if (keyboard_mapping_.size())
|
||||||
|
note_in_scale = keyboard_mapping_[mapping_position];
|
||||||
|
|
||||||
|
tuning_[i] = current_offset + scale[note_in_scale];
|
||||||
|
mapping_position++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::setConstantTuning(float note) {
|
||||||
|
for (int i = 0; i < kTuningSize; ++i)
|
||||||
|
tuning_[i] = note;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::setDefaultTuning() {
|
||||||
|
for (int i = 0; i < kTuningSize; ++i)
|
||||||
|
tuning_[i] = i - kTuningCenter;
|
||||||
|
|
||||||
|
scale_.clear();
|
||||||
|
for (int i = 0; i <= vital::kNotesPerOctave; ++i)
|
||||||
|
scale_.push_back(i);
|
||||||
|
|
||||||
|
keyboard_mapping_.clear();
|
||||||
|
|
||||||
|
default_ = true;
|
||||||
|
tuning_name_ = "";
|
||||||
|
mapping_name_ = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::mono_float Tuning::convertMidiNote(int note) const {
|
||||||
|
int scale_offset = note - scale_start_midi_note_;
|
||||||
|
return tuning_[kTuningCenter + scale_offset] + scale_start_midi_note_ + reference_midi_note_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::setReferenceFrequency(float frequency) {
|
||||||
|
setReferenceNoteFrequency(0, frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::setReferenceNoteFrequency(int midi_note, float frequency) {
|
||||||
|
reference_midi_note_ = vital::utils::frequencyToMidiNote(frequency) - midi_note;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::setReferenceRatio(float ratio) {
|
||||||
|
reference_midi_note_ = vital::utils::ratioToMidiTranspose(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
json Tuning::stateToJson() const {
|
||||||
|
json data;
|
||||||
|
data["scale_start_midi_note"] = scale_start_midi_note_;
|
||||||
|
data["reference_midi_note"] = reference_midi_note_;
|
||||||
|
data["tuning_name"] = tuning_name_;
|
||||||
|
data["mapping_name"] = mapping_name_;
|
||||||
|
data["default"] = default_;
|
||||||
|
|
||||||
|
json scale_data;
|
||||||
|
for (float scale_value : scale_)
|
||||||
|
scale_data.push_back(scale_value);
|
||||||
|
data["scale"] = scale_data;
|
||||||
|
|
||||||
|
if (keyboard_mapping_.size()) {
|
||||||
|
json mapping_data;
|
||||||
|
for (int mapping_value : keyboard_mapping_)
|
||||||
|
mapping_data.push_back(mapping_value);
|
||||||
|
data["mapping"] = mapping_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::jsonToState(const json& data) {
|
||||||
|
scale_start_midi_note_ = data["scale_start_midi_note"];
|
||||||
|
reference_midi_note_ = data["reference_midi_note"];
|
||||||
|
std::string tuning_name = data["tuning_name"];
|
||||||
|
tuning_name_ = tuning_name;
|
||||||
|
std::string mapping_name = data["mapping_name"];
|
||||||
|
mapping_name_ = mapping_name;
|
||||||
|
|
||||||
|
if (data.count("default"))
|
||||||
|
default_ = data["default"];
|
||||||
|
|
||||||
|
json scale_data = data["scale"];
|
||||||
|
scale_.clear();
|
||||||
|
for (json& value : scale_data) {
|
||||||
|
float scale_value = value;
|
||||||
|
scale_.push_back(scale_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboard_mapping_.clear();
|
||||||
|
if (data.count("mapping")) {
|
||||||
|
json mapping_data = data["mapping"];
|
||||||
|
for (json& value : mapping_data) {
|
||||||
|
int keyboard_value = value;
|
||||||
|
keyboard_mapping_.push_back(keyboard_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadScale(scale_);
|
||||||
|
}
|
||||||
82
src/common/tuning.h
Normal file
82
src/common/tuning.h
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "json/json.h"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
class Tuning {
|
||||||
|
public:
|
||||||
|
static constexpr int kTuningSize = 2 * vital::kMidiSize;
|
||||||
|
static constexpr int kTuningCenter = vital::kMidiSize;
|
||||||
|
static Tuning getTuningForFile(File file);
|
||||||
|
|
||||||
|
static String allFileExtensions();
|
||||||
|
static int noteToMidiKey(const String& note);
|
||||||
|
|
||||||
|
Tuning();
|
||||||
|
Tuning(File file);
|
||||||
|
|
||||||
|
void loadScale(std::vector<float> scale);
|
||||||
|
void loadFile(File file);
|
||||||
|
void setConstantTuning(float note);
|
||||||
|
void setDefaultTuning();
|
||||||
|
vital::mono_float convertMidiNote(int note) const;
|
||||||
|
void setStartMidiNote(int start_midi_note) { scale_start_midi_note_ = start_midi_note; }
|
||||||
|
void setReferenceNote(int reference_note) { reference_midi_note_ = reference_note; }
|
||||||
|
void setReferenceFrequency(float frequency);
|
||||||
|
void setReferenceNoteFrequency(int midi_note, float frequency);
|
||||||
|
void setReferenceRatio(float ratio);
|
||||||
|
std::string getName() const {
|
||||||
|
if (mapping_name_.size() == 0)
|
||||||
|
return tuning_name_;
|
||||||
|
if (tuning_name_.size() == 0)
|
||||||
|
return mapping_name_;
|
||||||
|
return tuning_name_ + " / " + mapping_name_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setName(const std::string& name) {
|
||||||
|
mapping_name_ = "";
|
||||||
|
tuning_name_ = name;
|
||||||
|
}
|
||||||
|
bool isDefault() const { return default_; }
|
||||||
|
|
||||||
|
json stateToJson() const;
|
||||||
|
void jsonToState(const json& data);
|
||||||
|
void loadScalaFile(const StringArray& scala_lines);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void loadScalaFile(File scala_file);
|
||||||
|
void loadKeyboardMapFile(File kbm_file);
|
||||||
|
void loadTunFile(File tun_file);
|
||||||
|
|
||||||
|
int scale_start_midi_note_;
|
||||||
|
float reference_midi_note_;
|
||||||
|
std::vector<float> scale_;
|
||||||
|
std::vector<int> keyboard_mapping_;
|
||||||
|
vital::mono_float tuning_[kTuningSize];
|
||||||
|
std::string tuning_name_;
|
||||||
|
std::string mapping_name_;
|
||||||
|
bool default_;
|
||||||
|
|
||||||
|
JUCE_LEAK_DETECTOR(Tuning)
|
||||||
|
};
|
||||||
|
|
||||||
400
src/common/wavetable/file_source.cpp
Normal file
400
src/common/wavetable/file_source.cpp
Normal file
|
|
@ -0,0 +1,400 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "file_source.h"
|
||||||
|
|
||||||
|
FileSource::FileSourceKeyframe::FileSourceKeyframe(SampleBuffer* sample_buffer) {
|
||||||
|
sample_buffer_ = sample_buffer;
|
||||||
|
start_position_ = 0.0f;
|
||||||
|
window_fade_ = 1.0f;
|
||||||
|
window_size_ = vital::WaveFrame::kWaveformSize;
|
||||||
|
fade_style_ = kWaveBlend;
|
||||||
|
phase_style_ = kNone;
|
||||||
|
overridden_phase_ = nullptr;
|
||||||
|
interpolate_from_frame_ = nullptr;
|
||||||
|
interpolate_to_frame_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::FileSourceKeyframe::copy(const WavetableKeyframe* keyframe) {
|
||||||
|
const FileSourceKeyframe* source = dynamic_cast<const FileSourceKeyframe*>(keyframe);
|
||||||
|
VITAL_ASSERT(source);
|
||||||
|
start_position_ = source->start_position_;
|
||||||
|
window_fade_ = source->window_fade_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::FileSourceKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
float t) {
|
||||||
|
const FileSourceKeyframe* from = dynamic_cast<const FileSourceKeyframe*>(from_keyframe);
|
||||||
|
const FileSourceKeyframe* to = dynamic_cast<const FileSourceKeyframe*>(to_keyframe);
|
||||||
|
VITAL_ASSERT(from);
|
||||||
|
VITAL_ASSERT(to);
|
||||||
|
|
||||||
|
start_position_ = linearTween(from->start_position_, to->start_position_, t);
|
||||||
|
window_fade_ = linearTween(from->window_fade_, to->window_fade_, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline float FileSource::FileSourceKeyframe::getScaledInterpolatedSample(float position) {
|
||||||
|
const float* buffer = getCubicInterpolationBuffer();
|
||||||
|
float clamped_position = vital::utils::clamp(position, 0.0f, sample_buffer_->size - 1);
|
||||||
|
int start_index = clamped_position;
|
||||||
|
float t = clamped_position - start_index;
|
||||||
|
|
||||||
|
vital::matrix interpolation_matrix = vital::utils::getCatmullInterpolationMatrix(t);
|
||||||
|
vital::matrix value_matrix = vital::utils::getValueMatrix(buffer, start_index);
|
||||||
|
value_matrix.transpose();
|
||||||
|
|
||||||
|
return interpolation_matrix.multiplyAndSumRows(value_matrix)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
float FileSource::FileSourceKeyframe::getNormalizationScale() {
|
||||||
|
const float* buffer = getDataBuffer();
|
||||||
|
if (buffer == nullptr)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
double cycles_in = start_position_ / window_size_;
|
||||||
|
int cycle = cycles_in;
|
||||||
|
|
||||||
|
double start_index = cycle * window_size_;
|
||||||
|
|
||||||
|
float max = 0.0f;
|
||||||
|
float min = 0.0f;
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
|
||||||
|
double position = std::min<double>(start_index + t * window_size_, sample_buffer_->size - 1);
|
||||||
|
int from_index = position;
|
||||||
|
int to_index = std::min(sample_buffer_->size - 1, from_index + 1);
|
||||||
|
|
||||||
|
VITAL_ASSERT(from_index >= 0 && from_index < sample_buffer_->size);
|
||||||
|
VITAL_ASSERT(to_index >= 0 && to_index < sample_buffer_->size);
|
||||||
|
|
||||||
|
float from_sample = buffer[from_index];
|
||||||
|
float to_sample = buffer[to_index];
|
||||||
|
max = std::max(from_sample, max);
|
||||||
|
max = std::max(to_sample, max);
|
||||||
|
min = std::min(from_sample, min);
|
||||||
|
min = std::min(to_sample, min);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 2.0f / std::max(max - min, 0.001f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::FileSourceKeyframe::render(vital::WaveFrame* wave_frame) {
|
||||||
|
if (sample_buffer_->size <= 0) {
|
||||||
|
wave_frame->clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fade_style_ == kWaveBlend)
|
||||||
|
renderWaveBlend(wave_frame);
|
||||||
|
else if (fade_style_ == kNoInterpolate)
|
||||||
|
renderNoInterpolate(wave_frame);
|
||||||
|
else if (fade_style_ == kTimeInterpolate)
|
||||||
|
renderTimeInterpolate(wave_frame);
|
||||||
|
else if (fade_style_ == kFreqInterpolate)
|
||||||
|
renderFreqInterpolate(wave_frame);
|
||||||
|
|
||||||
|
if (phase_style_ == kClear || phase_style_ == kVocode) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
float amplitude = std::abs(wave_frame->frequency_domain[i]);
|
||||||
|
wave_frame->frequency_domain[i] = std::polar(amplitude, overridden_phase_[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wave_frame->toTimeDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::FileSourceKeyframe::renderWaveBlend(vital::WaveFrame* wave_frame) {
|
||||||
|
double window_ratio = window_size_ / vital::WaveFrame::kWaveformSize;
|
||||||
|
int waveform_middle = vital::WaveFrame::kWaveformSize / 2;
|
||||||
|
int start_index = start_position_ / window_ratio + window_size_ / 2.0f + waveform_middle;
|
||||||
|
start_index = start_index % vital::WaveFrame::kWaveformSize;
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
|
||||||
|
double position = start_position_ + t * window_size_;
|
||||||
|
int write_index = (start_index + i) % vital::WaveFrame::kWaveformSize;
|
||||||
|
wave_frame->time_domain[write_index] = getScaledInterpolatedSample(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fade_samples = window_fade_ * vital::WaveFrame::kWaveformSize;
|
||||||
|
double fade_size = fade_samples * window_ratio;
|
||||||
|
for (int i = 0; i < fade_samples; ++i) {
|
||||||
|
double t = i / (fade_samples - 1.0f);
|
||||||
|
double fade = 0.5 + 0.5 * cos(vital::kPi * t);
|
||||||
|
|
||||||
|
int write_index = (start_index + i) % vital::WaveFrame::kWaveformSize;
|
||||||
|
double position = start_position_ + window_size_ + t * fade_size;
|
||||||
|
double existing_value = wave_frame->time_domain[write_index];
|
||||||
|
double fade_value = getScaledInterpolatedSample(position);
|
||||||
|
wave_frame->time_domain[write_index] = linearTween(existing_value, fade_value, fade);
|
||||||
|
}
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::FileSourceKeyframe::renderNoInterpolate(vital::WaveFrame* wave_frame) {
|
||||||
|
double cycles_in = start_position_ / window_size_;
|
||||||
|
int cycle = cycles_in;
|
||||||
|
|
||||||
|
double start_index = cycle * window_size_;
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
|
||||||
|
double position = start_index + t * window_size_;
|
||||||
|
wave_frame->time_domain[i] = getScaledInterpolatedSample(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::FileSourceKeyframe::renderTimeInterpolate(vital::WaveFrame* wave_frame) {
|
||||||
|
double cycles_in = start_position_ / window_size_;
|
||||||
|
int from_cycle = cycles_in;
|
||||||
|
int to_cycle = from_cycle + 1;
|
||||||
|
float transition = cycles_in - from_cycle;
|
||||||
|
|
||||||
|
double start_index_from = from_cycle * window_size_;
|
||||||
|
double start_index_to = to_cycle * window_size_;
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
|
||||||
|
double from_position = start_index_from + t * window_size_;
|
||||||
|
double to_position = start_index_to + t * window_size_;
|
||||||
|
float from_sample = getScaledInterpolatedSample(from_position);
|
||||||
|
float to_sample = getScaledInterpolatedSample(to_position);
|
||||||
|
wave_frame->time_domain[i] = vital::utils::interpolate(from_sample, to_sample, transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::FileSourceKeyframe::renderFreqInterpolate(vital::WaveFrame* wave_frame) {
|
||||||
|
double cycles_in = start_position_ / window_size_;
|
||||||
|
int from_cycle = cycles_in;
|
||||||
|
int to_cycle = from_cycle + 1;
|
||||||
|
float transition = cycles_in - from_cycle;
|
||||||
|
|
||||||
|
double start_index_from = from_cycle * window_size_;
|
||||||
|
double start_index_to = to_cycle * window_size_;
|
||||||
|
|
||||||
|
vital::WaveFrame* from_wave_frame = interpolate_from_frame_->wave_frame();
|
||||||
|
vital::WaveFrame* to_wave_frame = interpolate_to_frame_->wave_frame();
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
|
||||||
|
double from_position = start_index_from + t * window_size_;
|
||||||
|
double to_position = start_index_to + t * window_size_;
|
||||||
|
from_wave_frame->time_domain[i] = getScaledInterpolatedSample(from_position);
|
||||||
|
to_wave_frame->time_domain[i] = getScaledInterpolatedSample(to_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
from_wave_frame->toFrequencyDomain();
|
||||||
|
to_wave_frame->toFrequencyDomain();
|
||||||
|
interpolate_from_frame_->linearFrequencyInterpolate(from_wave_frame, to_wave_frame, transition);
|
||||||
|
wave_frame->copy(interpolate_from_frame_->wave_frame());
|
||||||
|
}
|
||||||
|
|
||||||
|
json FileSource::FileSourceKeyframe::stateToJson() {
|
||||||
|
json data = WavetableKeyframe::stateToJson();
|
||||||
|
data["start_position"] = start_position_;
|
||||||
|
data["window_fade"] = window_fade_;
|
||||||
|
data["window_size"] = window_size_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::FileSourceKeyframe::jsonToState(json data) {
|
||||||
|
WavetableKeyframe::jsonToState(data);
|
||||||
|
start_position_ = data["start_position"];
|
||||||
|
window_fade_ = data["window_fade"];
|
||||||
|
window_size_ = data["window_size"];
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSource::FileSource() : compute_frame_(&sample_buffer_), overridden_phase_(),
|
||||||
|
fade_style_(kWaveBlend), phase_style_(kNone),
|
||||||
|
normalize_gain_(false), normalize_mult_(false),
|
||||||
|
random_generator_(-vital::kPi, vital::kPi) {
|
||||||
|
window_size_ = vital::WaveFrame::kWaveformSize;
|
||||||
|
random_seed_ = random_generator_.next() * (INT_MAX / vital::kPi);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableKeyframe* FileSource::createKeyframe(int position) {
|
||||||
|
FileSourceKeyframe* keyframe = new FileSourceKeyframe(&sample_buffer_);
|
||||||
|
interpolate(keyframe, position);
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
if (sample_buffer_.data == nullptr)
|
||||||
|
wave_frame->clear();
|
||||||
|
else {
|
||||||
|
interpolate(&compute_frame_, position);
|
||||||
|
compute_frame_.setWindowSize(window_size_);
|
||||||
|
compute_frame_.setFadeStyle(fade_style_);
|
||||||
|
compute_frame_.setPhaseStyle(phase_style_);
|
||||||
|
compute_frame_.setInterpolateFromFrame(&interpolate_from_frame_);
|
||||||
|
compute_frame_.setInterpolateToFrame(&interpolate_to_frame_);
|
||||||
|
compute_frame_.setOverriddenPhaseBuffer(overridden_phase_);
|
||||||
|
compute_frame_.render(wave_frame);
|
||||||
|
wave_frame->setFrequencyRatio(window_size_ / vital::WaveFrame::kWaveformSize);
|
||||||
|
wave_frame->setSampleRate(sample_buffer_.sample_rate);
|
||||||
|
if (normalize_mult_)
|
||||||
|
wave_frame->normalize(normalize_gain_);
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType FileSource::getType() {
|
||||||
|
return WavetableComponentFactory::kFileSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
json FileSource::stateToJson() {
|
||||||
|
double max_position = 0;
|
||||||
|
for (int i = 0; i < numFrames(); ++i)
|
||||||
|
max_position = std::max(max_position, getKeyframe(i)->getStartPosition());
|
||||||
|
|
||||||
|
json data = WavetableComponent::stateToJson();
|
||||||
|
data["normalize_gain"] = normalize_gain_;
|
||||||
|
data["normalize_mult"] = normalize_mult_;
|
||||||
|
data["window_size"] = window_size_;
|
||||||
|
data["fade_style"] = fade_style_;
|
||||||
|
data["phase_style"] = phase_style_;
|
||||||
|
data["random_seed"] = random_seed_;
|
||||||
|
data["audio_sample_rate"] = sample_buffer_.sample_rate;
|
||||||
|
|
||||||
|
int save_samples = max_position + 2 * window_size_ + kExtraSaveSamples;
|
||||||
|
int num_samples = std::min(sample_buffer_.size, save_samples);
|
||||||
|
String encoded = "";
|
||||||
|
if (getDataBuffer()) {
|
||||||
|
std::unique_ptr<int16_t[]> pcm_data = std::make_unique<int16_t[]>(num_samples);
|
||||||
|
vital::utils::floatToPcmData(pcm_data.get(), getDataBuffer(), num_samples);
|
||||||
|
encoded = Base64::toBase64(pcm_data.get(), num_samples * sizeof(int16_t));
|
||||||
|
}
|
||||||
|
data["audio_file"] = encoded.toStdString();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::jsonToState(json data) {
|
||||||
|
normalize_gain_ = data["normalize_gain"];
|
||||||
|
if (data.count("normalize_mult"))
|
||||||
|
normalize_mult_ = data["normalize_mult"];
|
||||||
|
else
|
||||||
|
normalize_mult_ = true;
|
||||||
|
window_size_ = data["window_size"];
|
||||||
|
fade_style_ = kWaveBlend;
|
||||||
|
if (data.count("fade_style"))
|
||||||
|
fade_style_ = data["fade_style"];
|
||||||
|
|
||||||
|
phase_style_ = kNone;
|
||||||
|
if (data.count("phase_style"))
|
||||||
|
phase_style_ = data["phase_style"];
|
||||||
|
|
||||||
|
if (data.count("random_seed"))
|
||||||
|
random_seed_ = data["random_seed"];
|
||||||
|
|
||||||
|
writePhaseOverrideBuffer();
|
||||||
|
|
||||||
|
WavetableComponent::jsonToState(data);
|
||||||
|
|
||||||
|
int sample_rate = vital::kDefaultSampleRate;
|
||||||
|
if (data.count("audio_sample_rate"))
|
||||||
|
sample_rate = data["audio_sample_rate"];
|
||||||
|
|
||||||
|
MemoryOutputStream decoded;
|
||||||
|
std::string audio_data = data["audio_file"];
|
||||||
|
Base64::convertFromBase64(decoded, audio_data);
|
||||||
|
|
||||||
|
int size = static_cast<int>(decoded.getDataSize()) / sizeof(int16_t);
|
||||||
|
std::unique_ptr<float[]> float_data = std::make_unique<float[]>(size);
|
||||||
|
vital::utils::pcmToFloatData(float_data.get(), (int16_t*)decoded.getData(), size);
|
||||||
|
loadBuffer(float_data.get(), size, sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSource::FileSourceKeyframe* FileSource::getKeyframe(int index) {
|
||||||
|
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
|
||||||
|
return dynamic_cast<FileSource::FileSourceKeyframe*>(wavetable_keyframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::setPhaseStyle(PhaseStyle phase_style) {
|
||||||
|
if (phase_style_ == phase_style)
|
||||||
|
return;
|
||||||
|
|
||||||
|
phase_style_ = phase_style;
|
||||||
|
if (phase_style_ == kVocode)
|
||||||
|
random_seed_++;
|
||||||
|
|
||||||
|
writePhaseOverrideBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::writePhaseOverrideBuffer() {
|
||||||
|
if (phase_style_ == kClear) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize / 2; ++i) {
|
||||||
|
overridden_phase_[2 * i] = -0.5f * vital::kPi;
|
||||||
|
overridden_phase_[2 * i + 1] = 0.5f * vital::kPi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (phase_style_ == kVocode) {
|
||||||
|
random_generator_.seed(random_seed_);
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
|
||||||
|
overridden_phase_[i] = random_generator_.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::loadBuffer(const float* buffer, int size, int sample_rate) {
|
||||||
|
sample_buffer_.sample_rate = sample_rate;
|
||||||
|
sample_buffer_.size = size;
|
||||||
|
sample_buffer_.data = std::make_unique<float[]>(size + kExtraBufferSamples);
|
||||||
|
memcpy(sample_buffer_.data.get() + 1, buffer, size * sizeof(float));
|
||||||
|
|
||||||
|
sample_buffer_.data[0] = sample_buffer_.data[1];
|
||||||
|
|
||||||
|
for (int i = 1; i < kExtraBufferSamples; ++i)
|
||||||
|
sample_buffer_.data[sample_buffer_.size + i] = sample_buffer_.data[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::detectPitch(int max_period) {
|
||||||
|
int start = (sample_buffer_.size - kPitchDetectMaxPeriod) / 3;
|
||||||
|
pitch_detector_.loadSignal(getDataBuffer() + start, kPitchDetectMaxPeriod);
|
||||||
|
float period = pitch_detector_.matchPeriod(max_period);
|
||||||
|
setWindowSize(period);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSource::detectWaveEditTable() {
|
||||||
|
static constexpr int kWaveEditFrameLength = 256;
|
||||||
|
static constexpr int kFrequencyDomainTotals = 8;
|
||||||
|
static constexpr int kWaveEditNumFrames = 64;
|
||||||
|
if (sample_buffer_.size != kWaveEditFrameLength * kWaveEditNumFrames)
|
||||||
|
return;
|
||||||
|
|
||||||
|
vital::WaveFrame wave_frame;
|
||||||
|
const float* buffer = getDataBuffer();
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
|
||||||
|
wave_frame.time_domain[i] = buffer[i];
|
||||||
|
|
||||||
|
wave_frame.toFrequencyDomain();
|
||||||
|
|
||||||
|
int size_mult = vital::WaveFrame::kWaveformSize / kWaveEditFrameLength;
|
||||||
|
std::unique_ptr<float[]> totals = std::make_unique<float[]>(size_mult);
|
||||||
|
for (int i = 0; i < size_mult; ++i) {
|
||||||
|
for (int j = 0; j < kFrequencyDomainTotals; ++j)
|
||||||
|
totals[i] += std::abs(wave_frame.frequency_domain[i + 1 + j * size_mult]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < size_mult - 1; ++i) {
|
||||||
|
if (totals[i] > totals[size_mult - 1])
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWindowSize(kWaveEditFrameLength);
|
||||||
|
}
|
||||||
174
src/common/wavetable/file_source.h
Normal file
174
src/common/wavetable/file_source.h
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "pitch_detector.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "wave_source.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
class FileSource : public WavetableComponent {
|
||||||
|
public:
|
||||||
|
static constexpr float kMaxFileSourceSamples = 176400;
|
||||||
|
static constexpr int kExtraSaveSamples = 4;
|
||||||
|
static constexpr int kExtraBufferSamples = 4;
|
||||||
|
static constexpr int kPitchDetectMaxPeriod = 8096;
|
||||||
|
|
||||||
|
enum FadeStyle {
|
||||||
|
kWaveBlend,
|
||||||
|
kNoInterpolate,
|
||||||
|
kTimeInterpolate,
|
||||||
|
kFreqInterpolate,
|
||||||
|
kNumFadeStyles
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PhaseStyle {
|
||||||
|
kNone,
|
||||||
|
kClear,
|
||||||
|
kVocode,
|
||||||
|
kNumPhaseStyles
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SampleBuffer {
|
||||||
|
SampleBuffer() : size(0), sample_rate(0) { }
|
||||||
|
std::unique_ptr<float[]> data;
|
||||||
|
int size;
|
||||||
|
int sample_rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileSourceKeyframe : public WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
FileSourceKeyframe(SampleBuffer* sample_buffer);
|
||||||
|
virtual ~FileSourceKeyframe() { }
|
||||||
|
|
||||||
|
void copy(const WavetableKeyframe* keyframe) override;
|
||||||
|
void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) override;
|
||||||
|
|
||||||
|
float getNormalizationScale();
|
||||||
|
|
||||||
|
void render(vital::WaveFrame* wave_frame) override;
|
||||||
|
void renderWaveBlend(vital::WaveFrame* wave_frame);
|
||||||
|
void renderNoInterpolate(vital::WaveFrame* wave_frame);
|
||||||
|
void renderTimeInterpolate(vital::WaveFrame* wave_frame);
|
||||||
|
void renderFreqInterpolate(vital::WaveFrame* wave_frame);
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
double getStartPosition() { return start_position_; }
|
||||||
|
double getWindowSize() { return window_size_; }
|
||||||
|
double getWindowFade() { return window_fade_; }
|
||||||
|
double getWindowFadeSamples() { return window_fade_ * window_size_; }
|
||||||
|
int getSamplesNeeded() { return getWindowSize() + getWindowFadeSamples(); }
|
||||||
|
|
||||||
|
force_inline void setStartPosition(double start_position) { start_position_ = start_position; }
|
||||||
|
force_inline void setWindowFade(double window_fade) { window_fade_ = window_fade; }
|
||||||
|
force_inline void setWindowSize(double window_size) { window_size_ = window_size; }
|
||||||
|
force_inline void setFadeStyle(FadeStyle fade_style) { fade_style_ = fade_style; }
|
||||||
|
force_inline void setPhaseStyle(PhaseStyle phase_style) { phase_style_ = phase_style; }
|
||||||
|
force_inline void setOverriddenPhaseBuffer(const float* buffer) { overridden_phase_ = buffer; }
|
||||||
|
force_inline const float* getDataBuffer() {
|
||||||
|
if (sample_buffer_ == nullptr || sample_buffer_->data == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
return sample_buffer_->data.get() + 1;
|
||||||
|
}
|
||||||
|
force_inline const float* getCubicInterpolationBuffer() {
|
||||||
|
if (sample_buffer_ == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
return sample_buffer_->data.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
float getScaledInterpolatedSample(float time);
|
||||||
|
|
||||||
|
void setInterpolateFromFrame(WaveSourceKeyframe* frame) {
|
||||||
|
interpolate_from_frame_ = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInterpolateToFrame(WaveSourceKeyframe* frame) {
|
||||||
|
interpolate_to_frame_ = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SampleBuffer* sample_buffer_;
|
||||||
|
const float* overridden_phase_;
|
||||||
|
WaveSourceKeyframe* interpolate_from_frame_;
|
||||||
|
WaveSourceKeyframe* interpolate_to_frame_;
|
||||||
|
|
||||||
|
double start_position_;
|
||||||
|
double window_fade_;
|
||||||
|
double window_size_;
|
||||||
|
FadeStyle fade_style_;
|
||||||
|
PhaseStyle phase_style_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FileSourceKeyframe)
|
||||||
|
};
|
||||||
|
|
||||||
|
FileSource();
|
||||||
|
virtual ~FileSource() { }
|
||||||
|
|
||||||
|
WavetableKeyframe* createKeyframe(int position) override;
|
||||||
|
void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
FileSourceKeyframe* getKeyframe(int index);
|
||||||
|
const SampleBuffer* buffer() const { return &sample_buffer_; }
|
||||||
|
FadeStyle getFadeStyle() { return fade_style_; }
|
||||||
|
PhaseStyle getPhaseStyle() { return phase_style_; }
|
||||||
|
bool getNormalizeGain() { return normalize_gain_; }
|
||||||
|
|
||||||
|
void setNormalizeGain(bool normalize_gain) { normalize_gain_ = normalize_gain; }
|
||||||
|
void setWindowSize(double window_size) { window_size_ = window_size; }
|
||||||
|
void setFadeStyle(FadeStyle fade_style) { fade_style_ = fade_style; }
|
||||||
|
void setPhaseStyle(PhaseStyle phase_style);
|
||||||
|
void writePhaseOverrideBuffer();
|
||||||
|
double getWindowSize() { return window_size_; }
|
||||||
|
|
||||||
|
void loadBuffer(const float* buffer, int size, int sample_rate);
|
||||||
|
void detectPitch(int max_period = vital::WaveFrame::kWaveformSize);
|
||||||
|
void detectWaveEditTable();
|
||||||
|
|
||||||
|
force_inline const float* getDataBuffer() {
|
||||||
|
if (sample_buffer_.data == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
return sample_buffer_.data.get() + 1;
|
||||||
|
}
|
||||||
|
force_inline const float* getCubicInterpolationBuffer() { return sample_buffer_.data.get(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FileSourceKeyframe compute_frame_;
|
||||||
|
WaveSourceKeyframe interpolate_from_frame_;
|
||||||
|
WaveSourceKeyframe interpolate_to_frame_;
|
||||||
|
|
||||||
|
SampleBuffer sample_buffer_;
|
||||||
|
float overridden_phase_[vital::WaveFrame::kWaveformSize];
|
||||||
|
FadeStyle fade_style_;
|
||||||
|
PhaseStyle phase_style_;
|
||||||
|
bool normalize_gain_;
|
||||||
|
bool normalize_mult_;
|
||||||
|
double window_size_;
|
||||||
|
|
||||||
|
int random_seed_;
|
||||||
|
vital::utils::RandomGenerator random_generator_;
|
||||||
|
PitchDetector pitch_detector_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FileSource)
|
||||||
|
};
|
||||||
|
|
||||||
148
src/common/wavetable/frequency_filter_modifier.cpp
Normal file
148
src/common/wavetable/frequency_filter_modifier.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "frequency_filter_modifier.h"
|
||||||
|
|
||||||
|
#include "futils.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr float kMinPower = -9.0f;
|
||||||
|
constexpr float kMaxPower = 9.0f;
|
||||||
|
constexpr int kMaxSlopeReach = 128;
|
||||||
|
|
||||||
|
force_inline double powerScale(double value, double power) {
|
||||||
|
static constexpr float kMinPower = 0.01f;
|
||||||
|
if (fabs(power) < kMinPower)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
double abs_value = fabs(value);
|
||||||
|
|
||||||
|
double numerator = exp(power * abs_value) - 1.0f;
|
||||||
|
double denominator = exp(power) - 1.0f;
|
||||||
|
if (value >= 0.0f)
|
||||||
|
return numerator / denominator;
|
||||||
|
return -numerator / denominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline float combWave(float t, float power) {
|
||||||
|
float range = t - floorf(t);
|
||||||
|
return 2.0f * powerScale(1.0f - fabsf(2.0f * range - 1.0f), power);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
FrequencyFilterModifier::FrequencyFilterModifierKeyframe::FrequencyFilterModifierKeyframe() {
|
||||||
|
cutoff_ = 4.0f;
|
||||||
|
shape_ = 0.5f;
|
||||||
|
style_ = kLowPass;
|
||||||
|
normalize_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrequencyFilterModifier::FrequencyFilterModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
|
||||||
|
const FrequencyFilterModifierKeyframe* source = dynamic_cast<const FrequencyFilterModifierKeyframe*>(keyframe);
|
||||||
|
shape_ = source->shape_;
|
||||||
|
cutoff_ = source->cutoff_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrequencyFilterModifier::FrequencyFilterModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
float t) {
|
||||||
|
const FrequencyFilterModifierKeyframe* from = dynamic_cast<const FrequencyFilterModifierKeyframe*>(from_keyframe);
|
||||||
|
const FrequencyFilterModifierKeyframe* to = dynamic_cast<const FrequencyFilterModifierKeyframe*>(to_keyframe);
|
||||||
|
|
||||||
|
shape_ = linearTween(from->shape_, to->shape_, t);
|
||||||
|
cutoff_ = linearTween(from->cutoff_, to->cutoff_, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrequencyFilterModifier::FrequencyFilterModifierKeyframe::render(vital::WaveFrame* wave_frame) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kNumRealComplex; ++i)
|
||||||
|
wave_frame->frequency_domain[i] *= getMultiplier(i);
|
||||||
|
|
||||||
|
wave_frame->toTimeDomain();
|
||||||
|
|
||||||
|
if (normalize_) {
|
||||||
|
wave_frame->normalize(true);
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json FrequencyFilterModifier::FrequencyFilterModifierKeyframe::stateToJson() {
|
||||||
|
json data = WavetableKeyframe::stateToJson();
|
||||||
|
data["cutoff"] = cutoff_;
|
||||||
|
data["shape"] = shape_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrequencyFilterModifier::FrequencyFilterModifierKeyframe::jsonToState(json data) {
|
||||||
|
WavetableKeyframe::jsonToState(data);
|
||||||
|
cutoff_ = data["cutoff"];
|
||||||
|
shape_ = data["shape"];
|
||||||
|
}
|
||||||
|
|
||||||
|
float FrequencyFilterModifier::FrequencyFilterModifierKeyframe::getMultiplier(float index) {
|
||||||
|
float cutoff_index = std::pow(2.0f, cutoff_);
|
||||||
|
float cutoff_delta = index - cutoff_index;
|
||||||
|
|
||||||
|
float slope = 1.0f / vital::utils::interpolate(1.0f, kMaxSlopeReach, shape_ * shape_);
|
||||||
|
float power = vital::utils::interpolate(kMinPower, kMaxPower, shape_);
|
||||||
|
|
||||||
|
if (style_ == kLowPass)
|
||||||
|
return vital::utils::clamp(1.0f - slope * cutoff_delta, 0.0f, 1.0f);
|
||||||
|
if (style_ == kBandPass)
|
||||||
|
return vital::utils::clamp(1.0f - fabsf(slope * cutoff_delta), 0.0f, 1.0f);
|
||||||
|
if (style_ == kHighPass)
|
||||||
|
return vital::utils::clamp(1.0f + slope * cutoff_delta, 0.0f, 1.0f);
|
||||||
|
if (style_ == kComb)
|
||||||
|
return combWave(index / (cutoff_index * 2.0f), power);
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableKeyframe* FrequencyFilterModifier::createKeyframe(int position) {
|
||||||
|
FrequencyFilterModifierKeyframe* keyframe = new FrequencyFilterModifierKeyframe();
|
||||||
|
interpolate(keyframe, position);
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrequencyFilterModifier::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
interpolate(&compute_frame_, position);
|
||||||
|
compute_frame_.setStyle(style_);
|
||||||
|
compute_frame_.setNormalize(normalize_);
|
||||||
|
compute_frame_.render(wave_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType FrequencyFilterModifier::getType() {
|
||||||
|
return WavetableComponentFactory::kFrequencyFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
json FrequencyFilterModifier::stateToJson() {
|
||||||
|
json data = WavetableComponent::stateToJson();
|
||||||
|
data["style"] = style_;
|
||||||
|
data["normalize"] = normalize_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrequencyFilterModifier::jsonToState(json data) {
|
||||||
|
WavetableComponent::jsonToState(data);
|
||||||
|
style_ = data["style"];
|
||||||
|
normalize_ = data["normalize"];
|
||||||
|
}
|
||||||
|
|
||||||
|
FrequencyFilterModifier::FrequencyFilterModifierKeyframe* FrequencyFilterModifier::getKeyframe(int index) {
|
||||||
|
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
|
||||||
|
return dynamic_cast<FrequencyFilterModifier::FrequencyFilterModifierKeyframe*>(wavetable_keyframe);
|
||||||
|
}
|
||||||
87
src/common/wavetable/frequency_filter_modifier.h
Normal file
87
src/common/wavetable/frequency_filter_modifier.h
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
class FrequencyFilterModifier : public WavetableComponent {
|
||||||
|
public:
|
||||||
|
enum FilterStyle {
|
||||||
|
kLowPass,
|
||||||
|
kBandPass,
|
||||||
|
kHighPass,
|
||||||
|
kComb,
|
||||||
|
kNumFilterStyles
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrequencyFilterModifierKeyframe : public WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
FrequencyFilterModifierKeyframe();
|
||||||
|
virtual ~FrequencyFilterModifierKeyframe() { }
|
||||||
|
|
||||||
|
void copy(const WavetableKeyframe* keyframe) override;
|
||||||
|
void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) override;
|
||||||
|
void render(vital::WaveFrame* wave_frame) override;
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
float getMultiplier(float index);
|
||||||
|
|
||||||
|
float getCutoff() { return cutoff_; }
|
||||||
|
float getShape() { return shape_; }
|
||||||
|
|
||||||
|
void setStyle(FilterStyle style) { style_ = style; }
|
||||||
|
void setCutoff(float cutoff) { cutoff_ = cutoff; }
|
||||||
|
void setShape(float shape) { shape_ = shape; }
|
||||||
|
void setNormalize(bool normalize) { normalize_ = normalize; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FilterStyle style_;
|
||||||
|
bool normalize_;
|
||||||
|
float cutoff_;
|
||||||
|
float shape_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FrequencyFilterModifierKeyframe)
|
||||||
|
};
|
||||||
|
|
||||||
|
FrequencyFilterModifier() : style_(kLowPass), normalize_(true) { }
|
||||||
|
virtual ~FrequencyFilterModifier() { }
|
||||||
|
|
||||||
|
virtual WavetableKeyframe* createKeyframe(int position) override;
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
virtual json stateToJson() override;
|
||||||
|
virtual void jsonToState(json data) override;
|
||||||
|
|
||||||
|
FrequencyFilterModifierKeyframe* getKeyframe(int index);
|
||||||
|
|
||||||
|
FilterStyle getStyle() { return style_; }
|
||||||
|
bool getNormalize() { return normalize_; }
|
||||||
|
|
||||||
|
void setStyle(FilterStyle style) { style_ = style; }
|
||||||
|
void setNormalize(bool normalize) { normalize_ = normalize; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FilterStyle style_;
|
||||||
|
bool normalize_;
|
||||||
|
FrequencyFilterModifierKeyframe compute_frame_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FrequencyFilterModifier)
|
||||||
|
};
|
||||||
|
|
||||||
127
src/common/wavetable/phase_modifier.cpp
Normal file
127
src/common/wavetable/phase_modifier.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "phase_modifier.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "wavetable_component_factory.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::complex<float> multiplyAndMix(std::complex<float> value, std::complex<float> mult, float mix) {
|
||||||
|
std::complex<float> result = value * mult;
|
||||||
|
return mix * result + (1.0f - mix) * value;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PhaseModifier::PhaseModifierKeyframe::PhaseModifierKeyframe() : phase_(0.0f), mix_(1.0f), phase_style_(kNormal) { }
|
||||||
|
|
||||||
|
void PhaseModifier::PhaseModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
|
||||||
|
const PhaseModifierKeyframe* source = dynamic_cast<const PhaseModifierKeyframe*>(keyframe);
|
||||||
|
phase_ = source->phase_;
|
||||||
|
mix_ = source->mix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhaseModifier::PhaseModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
float t) {
|
||||||
|
const PhaseModifierKeyframe* from = dynamic_cast<const PhaseModifierKeyframe*>(from_keyframe);
|
||||||
|
const PhaseModifierKeyframe* to = dynamic_cast<const PhaseModifierKeyframe*>(to_keyframe);
|
||||||
|
|
||||||
|
phase_ = linearTween(from->phase_, to->phase_, t);
|
||||||
|
mix_ = linearTween(from->mix_, to->mix_, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhaseModifier::PhaseModifierKeyframe::render(vital::WaveFrame* wave_frame) {
|
||||||
|
std::complex<float> phase_shift = std::polar(1.0f, -phase_);
|
||||||
|
if (phase_style_ == kHarmonic) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
|
||||||
|
wave_frame->frequency_domain[i] = multiplyAndMix(wave_frame->frequency_domain[i], phase_shift, mix_);
|
||||||
|
}
|
||||||
|
else if (phase_style_ == kHarmonicEvenOdd) {
|
||||||
|
std::complex<float> odd_shift = 1.0f / phase_shift;
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; i += 2) {
|
||||||
|
wave_frame->frequency_domain[i] = multiplyAndMix(wave_frame->frequency_domain[i], phase_shift, mix_);
|
||||||
|
wave_frame->frequency_domain[i + 1] = multiplyAndMix(wave_frame->frequency_domain[i + 1], odd_shift, mix_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (phase_style_ == kNormal) {
|
||||||
|
std::complex<float> current_phase_shift = 1.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
wave_frame->frequency_domain[i] = multiplyAndMix(wave_frame->frequency_domain[i], current_phase_shift, mix_);
|
||||||
|
current_phase_shift *= phase_shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (phase_style_ == kEvenOdd) {
|
||||||
|
std::complex<float> current_phase_shift = 1.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; i += 2) {
|
||||||
|
wave_frame->frequency_domain[i] = multiplyAndMix(wave_frame->frequency_domain[i], current_phase_shift, mix_);
|
||||||
|
std::complex<float> odd_shift = 1.0f / (current_phase_shift * phase_shift);
|
||||||
|
wave_frame->frequency_domain[i + 1] = multiplyAndMix(wave_frame->frequency_domain[i + 1], odd_shift, mix_);
|
||||||
|
current_phase_shift *= phase_shift * phase_shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (phase_style_ == kClear) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
|
||||||
|
wave_frame->frequency_domain[i] = std::abs(wave_frame->frequency_domain[i]);
|
||||||
|
}
|
||||||
|
wave_frame->toTimeDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
json PhaseModifier::PhaseModifierKeyframe::stateToJson() {
|
||||||
|
json data = WavetableKeyframe::stateToJson();
|
||||||
|
data["phase"] = phase_;
|
||||||
|
data["mix"] = mix_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhaseModifier::PhaseModifierKeyframe::jsonToState(json data) {
|
||||||
|
WavetableKeyframe::jsonToState(data);
|
||||||
|
phase_ = data["phase"];
|
||||||
|
mix_ = data["mix"];
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableKeyframe* PhaseModifier::createKeyframe(int position) {
|
||||||
|
PhaseModifierKeyframe* keyframe = new PhaseModifierKeyframe();
|
||||||
|
interpolate(keyframe, position);
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhaseModifier::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
compute_frame_.setPhaseStyle(phase_style_);
|
||||||
|
interpolate(&compute_frame_, position);
|
||||||
|
compute_frame_.render(wave_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType PhaseModifier::getType() {
|
||||||
|
return WavetableComponentFactory::kPhaseModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
json PhaseModifier::stateToJson() {
|
||||||
|
json data = WavetableComponent::stateToJson();
|
||||||
|
data["style"] = phase_style_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhaseModifier::jsonToState(json data) {
|
||||||
|
WavetableComponent::jsonToState(data);
|
||||||
|
phase_style_ = data["style"];
|
||||||
|
}
|
||||||
|
|
||||||
|
PhaseModifier::PhaseModifierKeyframe* PhaseModifier::getKeyframe(int index) {
|
||||||
|
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
|
||||||
|
return dynamic_cast<PhaseModifier::PhaseModifierKeyframe*>(wavetable_keyframe);
|
||||||
|
}
|
||||||
79
src/common/wavetable/phase_modifier.h
Normal file
79
src/common/wavetable/phase_modifier.h
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
class PhaseModifier : public WavetableComponent {
|
||||||
|
public:
|
||||||
|
enum PhaseStyle {
|
||||||
|
kNormal,
|
||||||
|
kEvenOdd,
|
||||||
|
kHarmonic,
|
||||||
|
kHarmonicEvenOdd,
|
||||||
|
kClear,
|
||||||
|
kNumPhaseStyles
|
||||||
|
};
|
||||||
|
|
||||||
|
class PhaseModifierKeyframe : public WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
PhaseModifierKeyframe();
|
||||||
|
virtual ~PhaseModifierKeyframe() { }
|
||||||
|
|
||||||
|
void copy(const WavetableKeyframe* keyframe) override;
|
||||||
|
void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) override;
|
||||||
|
void render(vital::WaveFrame* wave_frame) override;
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
float getPhase() { return phase_; }
|
||||||
|
float getMix() { return mix_; }
|
||||||
|
void setPhase(float phase) { phase_ = phase; }
|
||||||
|
void setMix(float mix) { mix_ = mix; }
|
||||||
|
void setPhaseStyle(PhaseStyle style) { phase_style_ = style; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float phase_;
|
||||||
|
float mix_;
|
||||||
|
PhaseStyle phase_style_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PhaseModifierKeyframe)
|
||||||
|
};
|
||||||
|
|
||||||
|
PhaseModifier() : phase_style_(kNormal) { }
|
||||||
|
virtual ~PhaseModifier() = default;
|
||||||
|
|
||||||
|
virtual WavetableKeyframe* createKeyframe(int position) override;
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
virtual json stateToJson() override;
|
||||||
|
virtual void jsonToState(json data) override;
|
||||||
|
|
||||||
|
PhaseModifierKeyframe* getKeyframe(int index);
|
||||||
|
|
||||||
|
void setPhaseStyle(PhaseStyle style) { phase_style_ = style; }
|
||||||
|
PhaseStyle getPhaseStyle() const { return phase_style_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PhaseModifierKeyframe compute_frame_;
|
||||||
|
PhaseStyle phase_style_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PhaseModifier)
|
||||||
|
};
|
||||||
|
|
||||||
103
src/common/wavetable/pitch_detector.cpp
Normal file
103
src/common/wavetable/pitch_detector.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pitch_detector.h"
|
||||||
|
#include "synth_constants.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
PitchDetector::PitchDetector() {
|
||||||
|
size_ = 0;
|
||||||
|
signal_data_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PitchDetector::loadSignal(const float* signal, int size) {
|
||||||
|
size_ = size;
|
||||||
|
signal_data_ = std::make_unique<float[]>(size);
|
||||||
|
|
||||||
|
memcpy(signal_data_.get(), signal, sizeof(float) * size);
|
||||||
|
}
|
||||||
|
|
||||||
|
float PitchDetector::getPeriodError(float period) {
|
||||||
|
static constexpr float kDcDeltaErrorMultiplier = 0.015f;
|
||||||
|
float error = 0.0f;
|
||||||
|
int waves = size_ / period - 1;
|
||||||
|
VITAL_ASSERT(waves > 0);
|
||||||
|
int points = kNumPoints / waves;
|
||||||
|
for (int w = 0; w < waves; ++w) {
|
||||||
|
float total_from = 0.0f;
|
||||||
|
float total_to = 0.0f;
|
||||||
|
for (int i = 0; i < points; ++i) {
|
||||||
|
float first_position = w * period + i * period / points;
|
||||||
|
float second_position = (w + 1) * period + i * period / points;
|
||||||
|
|
||||||
|
int first_index = first_position;
|
||||||
|
float first_t = first_position - first_index;
|
||||||
|
float first_from = signal_data_[first_index];
|
||||||
|
float first_to = signal_data_[first_index + 1];
|
||||||
|
float first_value = vital::utils::interpolate(first_from, first_to, first_t);
|
||||||
|
total_from += first_value;
|
||||||
|
|
||||||
|
int second_index = second_position;
|
||||||
|
float second_t = second_position - second_index;
|
||||||
|
float second_from = signal_data_[second_index];
|
||||||
|
float second_to = signal_data_[second_index + 1];
|
||||||
|
float second_value = vital::utils::interpolate(second_from, second_to, second_t);
|
||||||
|
total_to += second_value;
|
||||||
|
|
||||||
|
float delta = first_value - second_value;
|
||||||
|
error += delta * delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
float total_diff = total_from - total_to;
|
||||||
|
error += total_diff * total_diff * kDcDeltaErrorMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
float PitchDetector::findYinPeriod(int max_period) {
|
||||||
|
constexpr float kMinLength = 300.0f;
|
||||||
|
|
||||||
|
float max_length = std::min<float>(size_ / 2.0f, max_period);
|
||||||
|
|
||||||
|
float best_error = INT_MAX;
|
||||||
|
float match = kMinLength;
|
||||||
|
|
||||||
|
for (float length = kMinLength; length < max_length; length += 1.0f) {
|
||||||
|
float error = getPeriodError(length);
|
||||||
|
if (error < best_error) {
|
||||||
|
best_error = error;
|
||||||
|
match = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float best_match = match;
|
||||||
|
for (float length = match - 1.0f; length <= match + 1.0f; length += 0.1f) {
|
||||||
|
float error = getPeriodError(length);
|
||||||
|
if (error < best_error) {
|
||||||
|
best_error = error;
|
||||||
|
best_match = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
float PitchDetector::matchPeriod(int max_period) {
|
||||||
|
return findYinPeriod(max_period);
|
||||||
|
}
|
||||||
42
src/common/wavetable/pitch_detector.h
Normal file
42
src/common/wavetable/pitch_detector.h
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
class PitchDetector {
|
||||||
|
public:
|
||||||
|
static constexpr int kNumPoints = 2520;
|
||||||
|
|
||||||
|
PitchDetector();
|
||||||
|
|
||||||
|
void setSize(int size) { size_ = size; }
|
||||||
|
void loadSignal(const float* signal, int size);
|
||||||
|
|
||||||
|
float getPeriodError(float period);
|
||||||
|
float findYinPeriod(int max_period);
|
||||||
|
float matchPeriod(int max_period);
|
||||||
|
|
||||||
|
const float* data() const { return signal_data_.get(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int size_;
|
||||||
|
std::unique_ptr<float[]> signal_data_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PitchDetector)
|
||||||
|
};
|
||||||
|
|
||||||
48
src/common/wavetable/shepard_tone_source.cpp
Normal file
48
src/common/wavetable/shepard_tone_source.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "shepard_tone_source.h"
|
||||||
|
#include "wavetable_component_factory.h"
|
||||||
|
|
||||||
|
ShepardToneSource::ShepardToneSource() {
|
||||||
|
loop_frame_ = std::make_unique<WaveSourceKeyframe>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ShepardToneSource::~ShepardToneSource() { }
|
||||||
|
|
||||||
|
void ShepardToneSource::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
if (numFrames() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
WaveSourceKeyframe* keyframe = getKeyframe(0);
|
||||||
|
vital::WaveFrame* key_wave_frame = keyframe->wave_frame();
|
||||||
|
vital::WaveFrame* loop_wave_frame = loop_frame_->wave_frame();
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize / 2; ++i) {
|
||||||
|
loop_wave_frame->frequency_domain[i * 2] = key_wave_frame->frequency_domain[i];
|
||||||
|
loop_wave_frame->frequency_domain[i * 2 + 1] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_wave_frame->toTimeDomain();
|
||||||
|
|
||||||
|
compute_frame_->setInterpolationMode(interpolation_mode_);
|
||||||
|
compute_frame_->interpolate(keyframe, loop_frame_.get(), position / (vital::kNumOscillatorWaveFrames - 1.0f));
|
||||||
|
wave_frame->copy(compute_frame_->wave_frame());
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType ShepardToneSource::getType() {
|
||||||
|
return WavetableComponentFactory::kShepardToneSource;
|
||||||
|
}
|
||||||
35
src/common/wavetable/shepard_tone_source.h
Normal file
35
src/common/wavetable/shepard_tone_source.h
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wave_source.h"
|
||||||
|
|
||||||
|
class ShepardToneSource : public WaveSource {
|
||||||
|
public:
|
||||||
|
ShepardToneSource();
|
||||||
|
virtual ~ShepardToneSource();
|
||||||
|
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
virtual bool hasKeyframes() override { return false; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<WaveSourceKeyframe> loop_frame_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ShepardToneSource)
|
||||||
|
};
|
||||||
94
src/common/wavetable/slew_limit_modifier.cpp
Normal file
94
src/common/wavetable/slew_limit_modifier.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "slew_limit_modifier.h"
|
||||||
|
|
||||||
|
#include "wave_frame.h"
|
||||||
|
|
||||||
|
SlewLimitModifier::SlewLimitModifierKeyframe::SlewLimitModifierKeyframe() {
|
||||||
|
slew_down_run_rise_ = 0.0f;
|
||||||
|
slew_up_run_rise_ = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SlewLimitModifier::SlewLimitModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
|
||||||
|
const SlewLimitModifierKeyframe* source = dynamic_cast<const SlewLimitModifierKeyframe*>(keyframe);
|
||||||
|
slew_down_run_rise_ = source->slew_down_run_rise_;
|
||||||
|
slew_up_run_rise_ = source->slew_up_run_rise_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SlewLimitModifier::SlewLimitModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
float t) {
|
||||||
|
const SlewLimitModifierKeyframe* from = dynamic_cast<const SlewLimitModifierKeyframe*>(from_keyframe);
|
||||||
|
const SlewLimitModifierKeyframe* to = dynamic_cast<const SlewLimitModifierKeyframe*>(to_keyframe);
|
||||||
|
|
||||||
|
slew_down_run_rise_ = linearTween(from->slew_down_run_rise_, to->slew_down_run_rise_, t);
|
||||||
|
slew_up_run_rise_ = linearTween(from->slew_up_run_rise_, to->slew_up_run_rise_, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SlewLimitModifier::SlewLimitModifierKeyframe::render(vital::WaveFrame* wave_frame) {
|
||||||
|
float min_slew_limit = 1.0f / vital::WaveFrame::kWaveformSize;
|
||||||
|
float max_up_delta = (2.0f / vital::WaveFrame::kWaveformSize) / std::max(slew_up_run_rise_, min_slew_limit);
|
||||||
|
float max_down_delta = (2.0f / vital::WaveFrame::kWaveformSize) / std::max(slew_down_run_rise_, min_slew_limit);
|
||||||
|
|
||||||
|
float current_value = wave_frame->time_domain[0];
|
||||||
|
for (int i = 1; i < 2 * vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
int index = i % vital::WaveFrame::kWaveformSize;
|
||||||
|
float target_value = wave_frame->time_domain[index];
|
||||||
|
float delta = target_value - current_value;
|
||||||
|
|
||||||
|
if (delta > 0.0f)
|
||||||
|
current_value += std::min(delta, max_up_delta);
|
||||||
|
else
|
||||||
|
current_value -= std::min(-delta, max_down_delta);
|
||||||
|
|
||||||
|
wave_frame->time_domain[index] = current_value;
|
||||||
|
}
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
json SlewLimitModifier::SlewLimitModifierKeyframe::stateToJson() {
|
||||||
|
json data = WavetableKeyframe::stateToJson();
|
||||||
|
data["up_run_rise"] = slew_up_run_rise_;
|
||||||
|
data["down_run_rise"] = slew_down_run_rise_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SlewLimitModifier::SlewLimitModifierKeyframe::jsonToState(json data) {
|
||||||
|
WavetableKeyframe::jsonToState(data);
|
||||||
|
slew_up_run_rise_ = data["up_run_rise"];
|
||||||
|
slew_down_run_rise_ = data["down_run_rise"];
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableKeyframe* SlewLimitModifier::createKeyframe(int position) {
|
||||||
|
SlewLimitModifierKeyframe* keyframe = new SlewLimitModifierKeyframe();
|
||||||
|
interpolate(keyframe, position);
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SlewLimitModifier::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
interpolate(&compute_frame_, position);
|
||||||
|
compute_frame_.render(wave_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType SlewLimitModifier::getType() {
|
||||||
|
return WavetableComponentFactory::kSlewLimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlewLimitModifier::SlewLimitModifierKeyframe* SlewLimitModifier::getKeyframe(int index) {
|
||||||
|
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
|
||||||
|
return dynamic_cast<SlewLimitModifier::SlewLimitModifierKeyframe*>(wavetable_keyframe);
|
||||||
|
}
|
||||||
63
src/common/wavetable/slew_limit_modifier.h
Normal file
63
src/common/wavetable/slew_limit_modifier.h
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
class SlewLimitModifier : public WavetableComponent {
|
||||||
|
public:
|
||||||
|
class SlewLimitModifierKeyframe : public WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
SlewLimitModifierKeyframe();
|
||||||
|
virtual ~SlewLimitModifierKeyframe() { }
|
||||||
|
|
||||||
|
void copy(const WavetableKeyframe* keyframe) override;
|
||||||
|
void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) override;
|
||||||
|
void render(vital::WaveFrame* wave_frame) override;
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
float getSlewUpLimit() { return slew_up_run_rise_; }
|
||||||
|
float getSlewDownLimit() { return slew_down_run_rise_; }
|
||||||
|
|
||||||
|
void setSlewUpLimit(float slew_up_limit) { slew_up_run_rise_ = slew_up_limit; }
|
||||||
|
void setSlewDownLimit(float slew_down_limit) { slew_down_run_rise_ = slew_down_limit; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float slew_up_run_rise_;
|
||||||
|
float slew_down_run_rise_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SlewLimitModifierKeyframe)
|
||||||
|
};
|
||||||
|
|
||||||
|
SlewLimitModifier() { }
|
||||||
|
virtual ~SlewLimitModifier() { }
|
||||||
|
|
||||||
|
virtual WavetableKeyframe* createKeyframe(int position) override;
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
|
||||||
|
SlewLimitModifierKeyframe* getKeyframe(int index);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SlewLimitModifierKeyframe compute_frame_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SlewLimitModifier)
|
||||||
|
};
|
||||||
|
|
||||||
81
src/common/wavetable/wave_fold_modifier.cpp
Normal file
81
src/common/wavetable/wave_fold_modifier.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wave_fold_modifier.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
|
||||||
|
WaveFoldModifier::WaveFoldModifierKeyframe::WaveFoldModifierKeyframe() {
|
||||||
|
wave_fold_boost_ = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveFoldModifier::WaveFoldModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
|
||||||
|
const WaveFoldModifierKeyframe* source = dynamic_cast<const WaveFoldModifierKeyframe*>(keyframe);
|
||||||
|
wave_fold_boost_ = source->wave_fold_boost_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveFoldModifier::WaveFoldModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
float t) {
|
||||||
|
const WaveFoldModifierKeyframe* from = dynamic_cast<const WaveFoldModifierKeyframe*>(from_keyframe);
|
||||||
|
const WaveFoldModifierKeyframe* to = dynamic_cast<const WaveFoldModifierKeyframe*>(to_keyframe);
|
||||||
|
|
||||||
|
wave_fold_boost_ = linearTween(from->wave_fold_boost_, to->wave_fold_boost_, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveFoldModifier::WaveFoldModifierKeyframe::render(vital::WaveFrame* wave_frame) {
|
||||||
|
float max_value = std::max(1.0f, wave_frame->getMaxZeroOffset());
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
float value = vital::utils::clamp(wave_frame->time_domain[i] / max_value, -1.0f, 1.0f);
|
||||||
|
float adjusted_value = max_value * wave_fold_boost_ * asinf(value);
|
||||||
|
|
||||||
|
wave_frame->time_domain[i] = sinf(adjusted_value);
|
||||||
|
}
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
json WaveFoldModifier::WaveFoldModifierKeyframe::stateToJson() {
|
||||||
|
json data = WavetableKeyframe::stateToJson();
|
||||||
|
data["fold_boost"] = wave_fold_boost_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveFoldModifier::WaveFoldModifierKeyframe::jsonToState(json data) {
|
||||||
|
WavetableKeyframe::jsonToState(data);
|
||||||
|
wave_fold_boost_ = data["fold_boost"];
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableKeyframe* WaveFoldModifier::createKeyframe(int position) {
|
||||||
|
WaveFoldModifierKeyframe* keyframe = new WaveFoldModifierKeyframe();
|
||||||
|
interpolate(keyframe, position);
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveFoldModifier::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
interpolate(&compute_frame_, position);
|
||||||
|
compute_frame_.render(wave_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType WaveFoldModifier::getType() {
|
||||||
|
return WavetableComponentFactory::kWaveFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveFoldModifier::WaveFoldModifierKeyframe* WaveFoldModifier::getKeyframe(int index) {
|
||||||
|
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
|
||||||
|
return dynamic_cast<WaveFoldModifier::WaveFoldModifierKeyframe*>(wavetable_keyframe);
|
||||||
|
}
|
||||||
59
src/common/wavetable/wave_fold_modifier.h
Normal file
59
src/common/wavetable/wave_fold_modifier.h
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
class WaveFoldModifier : public WavetableComponent {
|
||||||
|
public:
|
||||||
|
class WaveFoldModifierKeyframe : public WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
WaveFoldModifierKeyframe();
|
||||||
|
virtual ~WaveFoldModifierKeyframe() { }
|
||||||
|
|
||||||
|
void copy(const WavetableKeyframe* keyframe) override;
|
||||||
|
void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) override;
|
||||||
|
void render(vital::WaveFrame* wave_frame) override;
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
float getWaveFoldBoost() { return wave_fold_boost_; }
|
||||||
|
void setWaveFoldBoost(float boost) { wave_fold_boost_ = boost; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float wave_fold_boost_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveFoldModifierKeyframe)
|
||||||
|
};
|
||||||
|
|
||||||
|
WaveFoldModifier() { }
|
||||||
|
virtual ~WaveFoldModifier() { }
|
||||||
|
|
||||||
|
virtual WavetableKeyframe* createKeyframe(int position) override;
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
|
||||||
|
WaveFoldModifierKeyframe* getKeyframe(int index);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
WaveFoldModifierKeyframe compute_frame_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveFoldModifier)
|
||||||
|
};
|
||||||
|
|
||||||
126
src/common/wavetable/wave_line_source.cpp
Normal file
126
src/common/wavetable/wave_line_source.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wave_line_source.h"
|
||||||
|
|
||||||
|
#include "futils.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "poly_utils.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "wavetable_component_factory.h"
|
||||||
|
|
||||||
|
WaveLineSource::WaveLineSourceKeyframe::WaveLineSourceKeyframe() :
|
||||||
|
line_generator_(vital::WaveFrame::kWaveformSize) {
|
||||||
|
pull_power_ = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveLineSource::WaveLineSourceKeyframe::copy(const WavetableKeyframe* keyframe) {
|
||||||
|
const WaveLineSourceKeyframe* source = dynamic_cast<const WaveLineSourceKeyframe*>(keyframe);
|
||||||
|
|
||||||
|
const LineGenerator* source_generator = source->getLineGenerator();
|
||||||
|
line_generator_.setNumPoints(source_generator->getNumPoints());
|
||||||
|
line_generator_.setSmooth(source_generator->smooth());
|
||||||
|
for (int i = 0; i < line_generator_.getNumPoints(); ++i) {
|
||||||
|
line_generator_.setPoint(i, source_generator->getPoint(i));
|
||||||
|
line_generator_.setPower(i, source_generator->getPower(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveLineSource::WaveLineSourceKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
float t) {
|
||||||
|
const WaveLineSourceKeyframe* from = dynamic_cast<const WaveLineSourceKeyframe*>(from_keyframe);
|
||||||
|
const WaveLineSourceKeyframe* to = dynamic_cast<const WaveLineSourceKeyframe*>(to_keyframe);
|
||||||
|
VITAL_ASSERT(from->getNumPoints() == to->getNumPoints());
|
||||||
|
|
||||||
|
float relative_power = from->getPullPower() - to->getPullPower();
|
||||||
|
float adjusted_t = vital::futils::powerScale(t, relative_power);
|
||||||
|
|
||||||
|
const LineGenerator* from_generator = from->getLineGenerator();
|
||||||
|
const LineGenerator* to_generator = to->getLineGenerator();
|
||||||
|
int num_points = from_generator->getNumPoints();
|
||||||
|
line_generator_.setNumPoints(num_points);
|
||||||
|
line_generator_.setSmooth(from_generator->smooth());
|
||||||
|
|
||||||
|
for (int i = 0; i < num_points; ++i) {
|
||||||
|
std::pair<float, float> from_point = from_generator->getPoint(i);
|
||||||
|
std::pair<float, float> to_point = to_generator->getPoint(i);
|
||||||
|
line_generator_.setPoint(i, {
|
||||||
|
linearTween(from_point.first, to_point.first, adjusted_t),
|
||||||
|
linearTween(from_point.second, to_point.second, adjusted_t),
|
||||||
|
});
|
||||||
|
line_generator_.setPower(i, linearTween(from_generator->getPower(i), to_generator->getPower(i), adjusted_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveLineSource::WaveLineSourceKeyframe::render(vital::WaveFrame* wave_frame) {
|
||||||
|
line_generator_.render();
|
||||||
|
memcpy(wave_frame->time_domain, line_generator_.getBuffer(), vital::WaveFrame::kWaveformSize * sizeof(float));
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
|
||||||
|
wave_frame->time_domain[i] = wave_frame->time_domain[i] * 2.0f - 1.0f;
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
json WaveLineSource::WaveLineSourceKeyframe::stateToJson() {
|
||||||
|
json data = WavetableKeyframe::stateToJson();
|
||||||
|
data["pull_power"] = pull_power_;
|
||||||
|
data["line"] = line_generator_.stateToJson();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveLineSource::WaveLineSourceKeyframe::jsonToState(json data) {
|
||||||
|
WavetableKeyframe::jsonToState(data);
|
||||||
|
pull_power_ = 0.0f;
|
||||||
|
if (data.count("pull_power"))
|
||||||
|
pull_power_ = data["pull_power"];
|
||||||
|
if (data.count("line"))
|
||||||
|
line_generator_.jsonToState(data["line"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableKeyframe* WaveLineSource::createKeyframe(int position) {
|
||||||
|
WaveLineSourceKeyframe* keyframe = new WaveLineSourceKeyframe();
|
||||||
|
interpolate(keyframe, position);
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveLineSource::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
interpolate(&compute_frame_, position);
|
||||||
|
compute_frame_.render(wave_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType WaveLineSource::getType() {
|
||||||
|
return WavetableComponentFactory::kLineSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
json WaveLineSource::stateToJson() {
|
||||||
|
json data = WavetableComponent::stateToJson();
|
||||||
|
data["num_points"] = num_points_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveLineSource::jsonToState(json data) {
|
||||||
|
WavetableComponent::jsonToState(data);
|
||||||
|
setNumPoints(data["num_points"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveLineSource::setNumPoints(int num_points) {
|
||||||
|
num_points_ = num_points;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveLineSource::WaveLineSourceKeyframe* WaveLineSource::getKeyframe(int index) {
|
||||||
|
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
|
||||||
|
return dynamic_cast<WaveLineSource::WaveLineSourceKeyframe*>(wavetable_keyframe);
|
||||||
|
}
|
||||||
78
src/common/wavetable/wave_line_source.h
Normal file
78
src/common/wavetable/wave_line_source.h
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
class WaveLineSource : public WavetableComponent {
|
||||||
|
public:
|
||||||
|
static constexpr int kDefaultLinePoints = 4;
|
||||||
|
|
||||||
|
class WaveLineSourceKeyframe : public WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
WaveLineSourceKeyframe();
|
||||||
|
virtual ~WaveLineSourceKeyframe() = default;
|
||||||
|
|
||||||
|
void copy(const WavetableKeyframe* keyframe) override;
|
||||||
|
void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) override;
|
||||||
|
void render(vital::WaveFrame* wave_frame) override;
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
inline std::pair<float, float> getPoint(int index) const { return line_generator_.getPoint(index); }
|
||||||
|
inline float getPower(int index) const { return line_generator_.getPower(index); }
|
||||||
|
inline void setPoint(std::pair<float, float> point, int index) { line_generator_.setPoint(index, point); }
|
||||||
|
inline void setPower(float power, int index) { line_generator_.setPower(index, power); }
|
||||||
|
inline void removePoint(int index) { line_generator_.removePoint(index); }
|
||||||
|
inline void addMiddlePoint(int index) { line_generator_.addMiddlePoint(index); }
|
||||||
|
inline int getNumPoints() const { return line_generator_.getNumPoints(); }
|
||||||
|
inline void setSmooth(bool smooth) { line_generator_.setSmooth(smooth); }
|
||||||
|
|
||||||
|
void setPullPower(float power) { pull_power_ = power; }
|
||||||
|
float getPullPower() const { return pull_power_; }
|
||||||
|
const LineGenerator* getLineGenerator() const { return &line_generator_; }
|
||||||
|
LineGenerator* getLineGenerator() { return &line_generator_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LineGenerator line_generator_;
|
||||||
|
float pull_power_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveLineSourceKeyframe)
|
||||||
|
};
|
||||||
|
|
||||||
|
WaveLineSource() : num_points_(kDefaultLinePoints), compute_frame_() { }
|
||||||
|
virtual ~WaveLineSource() = default;
|
||||||
|
|
||||||
|
virtual WavetableKeyframe* createKeyframe(int position) override;
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
virtual json stateToJson() override;
|
||||||
|
virtual void jsonToState(json data) override;
|
||||||
|
|
||||||
|
void setNumPoints(int num_points);
|
||||||
|
int numPoints() { return num_points_; }
|
||||||
|
WaveLineSourceKeyframe* getKeyframe(int index);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int num_points_;
|
||||||
|
WaveLineSourceKeyframe compute_frame_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveLineSource)
|
||||||
|
};
|
||||||
|
|
||||||
208
src/common/wavetable/wave_source.cpp
Normal file
208
src/common/wavetable/wave_source.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wave_source.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "wavetable_component_factory.h"
|
||||||
|
|
||||||
|
WaveSource::WaveSource() {
|
||||||
|
compute_frame_ = std::make_unique<WaveSourceKeyframe>();
|
||||||
|
interpolation_mode_ = kFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveSource::~WaveSource() { }
|
||||||
|
|
||||||
|
WavetableKeyframe* WaveSource::createKeyframe(int position) {
|
||||||
|
WaveSourceKeyframe* keyframe = new WaveSourceKeyframe();
|
||||||
|
render(keyframe->wave_frame(), position);
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSource::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
compute_frame_->setInterpolationMode(interpolation_mode_);
|
||||||
|
interpolate(compute_frame_.get(), position);
|
||||||
|
wave_frame->copy(compute_frame_->wave_frame());
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType WaveSource::getType() {
|
||||||
|
return WavetableComponentFactory::kWaveSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
json WaveSource::stateToJson() {
|
||||||
|
json data = WavetableComponent::stateToJson();
|
||||||
|
data["interpolation"] = interpolation_mode_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSource::jsonToState(json data) {
|
||||||
|
WavetableComponent::jsonToState(data);
|
||||||
|
interpolation_mode_ = data["interpolation"];
|
||||||
|
compute_frame_->setInterpolationMode(interpolation_mode_);
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::WaveFrame* WaveSource::getWaveFrame(int index) {
|
||||||
|
return getKeyframe(index)->wave_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveSourceKeyframe* WaveSource::getKeyframe(int index) {
|
||||||
|
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
|
||||||
|
return dynamic_cast<WaveSourceKeyframe*>(wavetable_keyframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSourceKeyframe::copy(const WavetableKeyframe* keyframe) {
|
||||||
|
const WaveSourceKeyframe* source = dynamic_cast<const WaveSourceKeyframe*>(keyframe);
|
||||||
|
wave_frame_->copy(source->wave_frame_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSourceKeyframe::linearTimeInterpolate(const vital::WaveFrame* from, const vital::WaveFrame* to, float t) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
|
||||||
|
wave_frame_->time_domain[i] = linearTween(from->time_domain[i], to->time_domain[i], t);
|
||||||
|
wave_frame_->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSourceKeyframe::cubicTimeInterpolate(const vital::WaveFrame* prev, const vital::WaveFrame* from,
|
||||||
|
const vital::WaveFrame* to, const vital::WaveFrame* next,
|
||||||
|
float range_prev, float range, float range_next, float t) {
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
wave_frame_->time_domain[i] = cubicTween(prev->time_domain[i], from->time_domain[i],
|
||||||
|
to->time_domain[i], next->time_domain[i],
|
||||||
|
range_prev, range, range_next, t);
|
||||||
|
}
|
||||||
|
wave_frame_->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSourceKeyframe::linearFrequencyInterpolate(const vital::WaveFrame* from,
|
||||||
|
const vital::WaveFrame* to, float t) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kNumRealComplex; ++i) {
|
||||||
|
float amplitude_from = sqrtf(std::abs(from->frequency_domain[i]));
|
||||||
|
float amplitude_to = sqrtf(std::abs(to->frequency_domain[i]));
|
||||||
|
float amplitude = linearTween(amplitude_from, amplitude_to, t);
|
||||||
|
amplitude *= amplitude;
|
||||||
|
|
||||||
|
float phase_from = std::arg(from->frequency_domain[i]);
|
||||||
|
float phase_delta = std::arg(std::conj(from->frequency_domain[i]) * to->frequency_domain[i]);
|
||||||
|
float phase = phase_from + t * phase_delta;
|
||||||
|
if (amplitude_from == 0)
|
||||||
|
phase = std::arg(to->frequency_domain[i]);
|
||||||
|
wave_frame_->frequency_domain[i] = std::polar(amplitude, phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
float dc_from = from->frequency_domain[0].real();
|
||||||
|
float dc_to = to->frequency_domain[0].real();
|
||||||
|
wave_frame_->frequency_domain[0] = linearTween(dc_from, dc_to, t);
|
||||||
|
|
||||||
|
int last = vital::WaveFrame::kNumRealComplex - 1;
|
||||||
|
float last_harmonic_from = from->frequency_domain[last].real();
|
||||||
|
float last_harmonic_to = to->frequency_domain[last].real();
|
||||||
|
wave_frame_->frequency_domain[last] = linearTween(last_harmonic_from, last_harmonic_to, t);
|
||||||
|
|
||||||
|
wave_frame_->toTimeDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSourceKeyframe::cubicFrequencyInterpolate(const vital::WaveFrame* prev, const vital::WaveFrame* from,
|
||||||
|
const vital::WaveFrame* to, const vital::WaveFrame* next,
|
||||||
|
float range_prev, float range, float range_next, float t) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kNumRealComplex; ++i) {
|
||||||
|
float amplitude_prev = sqrtf(std::abs(prev->frequency_domain[i]));
|
||||||
|
float amplitude_from = sqrtf(std::abs(from->frequency_domain[i]));
|
||||||
|
float amplitude_to = sqrtf(std::abs(to->frequency_domain[i]));
|
||||||
|
float amplitude_next = sqrtf(std::abs(next->frequency_domain[i]));
|
||||||
|
float amplitude = cubicTween(amplitude_prev, amplitude_from, amplitude_to, amplitude_next,
|
||||||
|
range_prev, range, range_next, t);
|
||||||
|
amplitude *= amplitude;
|
||||||
|
|
||||||
|
float phase_delta_from = std::arg(std::conj(prev->frequency_domain[i]) * from->frequency_domain[i]);
|
||||||
|
float phase_delta_to = std::arg(std::conj(from->frequency_domain[i]) * to->frequency_domain[i]);
|
||||||
|
float phase_delta_next = std::arg(std::conj(to->frequency_domain[i]) * next->frequency_domain[i]);
|
||||||
|
float phase_prev = std::arg(prev->frequency_domain[i]);
|
||||||
|
float phase_from = phase_prev;
|
||||||
|
if (amplitude_from)
|
||||||
|
phase_from += phase_delta_from;
|
||||||
|
float phase_to = phase_from;
|
||||||
|
if (amplitude_to)
|
||||||
|
phase_to += phase_delta_to;
|
||||||
|
float phase_next = phase_to;
|
||||||
|
if (amplitude_next)
|
||||||
|
phase_next += phase_delta_next;
|
||||||
|
|
||||||
|
float phase = cubicTween(phase_prev, phase_from, phase_to, phase_next, range_prev, range, range_next, t);
|
||||||
|
wave_frame_->frequency_domain[i] = std::polar(amplitude, phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
float dc_prev = prev->frequency_domain[0].real();
|
||||||
|
float dc_from = from->frequency_domain[0].real();
|
||||||
|
float dc_to = to->frequency_domain[0].real();
|
||||||
|
float dc_next = next->frequency_domain[0].real();
|
||||||
|
wave_frame_->frequency_domain[0] = cubicTween(dc_prev, dc_from, dc_to, dc_next, range_prev, range, range_next, t);
|
||||||
|
|
||||||
|
int last = vital::WaveFrame::kNumRealComplex - 1;
|
||||||
|
float last_harmonic_prev = prev->frequency_domain[last].real();
|
||||||
|
float last_harmonic_from = from->frequency_domain[last].real();
|
||||||
|
float last_harmonic_to = to->frequency_domain[last].real();
|
||||||
|
float last_harmonic_next = next->frequency_domain[last].real();
|
||||||
|
wave_frame_->frequency_domain[last] = cubicTween(last_harmonic_prev, last_harmonic_from,
|
||||||
|
last_harmonic_to, last_harmonic_next,
|
||||||
|
range_prev, range, range_next, t);
|
||||||
|
wave_frame_->toTimeDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSourceKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) {
|
||||||
|
const WaveSourceKeyframe* from = dynamic_cast<const WaveSourceKeyframe*>(from_keyframe);
|
||||||
|
const WaveSourceKeyframe* to = dynamic_cast<const WaveSourceKeyframe*>(to_keyframe);
|
||||||
|
|
||||||
|
if (interpolation_mode_ == WaveSource::kFrequency)
|
||||||
|
linearFrequencyInterpolate(from->wave_frame_.get(), to->wave_frame_.get(), t);
|
||||||
|
else
|
||||||
|
linearTimeInterpolate(from->wave_frame_.get(), to->wave_frame_.get(), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSourceKeyframe::smoothInterpolate(const WavetableKeyframe* prev_keyframe,
|
||||||
|
const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
const WavetableKeyframe* next_keyframe, float t) {
|
||||||
|
const vital::WaveFrame* prev = dynamic_cast<const WaveSourceKeyframe*>(prev_keyframe)->wave_frame_.get();
|
||||||
|
const vital::WaveFrame* from = dynamic_cast<const WaveSourceKeyframe*>(from_keyframe)->wave_frame_.get();
|
||||||
|
const vital::WaveFrame* to = dynamic_cast<const WaveSourceKeyframe*>(to_keyframe)->wave_frame_.get();
|
||||||
|
const vital::WaveFrame* next = dynamic_cast<const WaveSourceKeyframe*>(next_keyframe)->wave_frame_.get();
|
||||||
|
|
||||||
|
float range_prev = from_keyframe->position() - prev_keyframe->position();
|
||||||
|
float range = to_keyframe->position() - from_keyframe->position();
|
||||||
|
float range_next = next_keyframe->position() - to_keyframe->position();
|
||||||
|
|
||||||
|
if (interpolation_mode_ == WaveSource::kFrequency)
|
||||||
|
cubicFrequencyInterpolate(prev, from, to, next, range_prev, range, range_next, t);
|
||||||
|
else
|
||||||
|
cubicTimeInterpolate(prev, from, to, next, range_prev, range, range_next, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
json WaveSourceKeyframe::stateToJson() {
|
||||||
|
String encoded = Base64::toBase64(wave_frame_->time_domain, sizeof(float) * vital::WaveFrame::kWaveformSize);
|
||||||
|
json data = WavetableKeyframe::stateToJson();
|
||||||
|
data["wave_data"] = encoded.toStdString();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveSourceKeyframe::jsonToState(json data) {
|
||||||
|
WavetableKeyframe::jsonToState(data);
|
||||||
|
|
||||||
|
MemoryOutputStream decoded(sizeof(float) * vital::WaveFrame::kWaveformSize);
|
||||||
|
std::string wave_data = data["wave_data"];
|
||||||
|
Base64::convertFromBase64(decoded, wave_data);
|
||||||
|
memcpy(wave_frame_->time_domain, decoded.getData(), sizeof(float) * vital::WaveFrame::kWaveformSize);
|
||||||
|
wave_frame_->toFrequencyDomain();
|
||||||
|
}
|
||||||
97
src/common/wavetable/wave_source.h
Normal file
97
src/common/wavetable/wave_source.h
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
|
||||||
|
class WaveSourceKeyframe;
|
||||||
|
|
||||||
|
class WaveSource : public WavetableComponent {
|
||||||
|
public:
|
||||||
|
enum InterpolationMode {
|
||||||
|
kTime,
|
||||||
|
kFrequency
|
||||||
|
};
|
||||||
|
|
||||||
|
WaveSource();
|
||||||
|
virtual ~WaveSource();
|
||||||
|
|
||||||
|
virtual WavetableKeyframe* createKeyframe(int position) override;
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
virtual json stateToJson() override;
|
||||||
|
virtual void jsonToState(json data) override;
|
||||||
|
|
||||||
|
vital::WaveFrame* getWaveFrame(int index);
|
||||||
|
WaveSourceKeyframe* getKeyframe(int index);
|
||||||
|
|
||||||
|
void setInterpolationMode(InterpolationMode mode) { interpolation_mode_ = mode; }
|
||||||
|
InterpolationMode getInterpolationMode() const { return interpolation_mode_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<WaveSourceKeyframe> compute_frame_;
|
||||||
|
InterpolationMode interpolation_mode_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
class WaveSourceKeyframe : public WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
WaveSourceKeyframe() : interpolation_mode_(WaveSource::kFrequency) {
|
||||||
|
wave_frame_ = std::make_unique<vital::WaveFrame>();
|
||||||
|
}
|
||||||
|
virtual ~WaveSourceKeyframe() { }
|
||||||
|
|
||||||
|
vital::WaveFrame* wave_frame() { return wave_frame_.get(); }
|
||||||
|
|
||||||
|
void copy(const WavetableKeyframe* keyframe) override;
|
||||||
|
void linearTimeInterpolate(const vital::WaveFrame* from, const vital::WaveFrame* to, float t);
|
||||||
|
|
||||||
|
void cubicTimeInterpolate(const vital::WaveFrame* prev, const vital::WaveFrame* from,
|
||||||
|
const vital::WaveFrame* to, const vital::WaveFrame* next,
|
||||||
|
float range_prev, float range, float range_next, float t);
|
||||||
|
void linearFrequencyInterpolate(const vital::WaveFrame* from, const vital::WaveFrame* to, float t);
|
||||||
|
|
||||||
|
void cubicFrequencyInterpolate(const vital::WaveFrame* prev, const vital::WaveFrame* from,
|
||||||
|
const vital::WaveFrame* to, const vital::WaveFrame* next,
|
||||||
|
float range_prev, float range, float range_next, float t);
|
||||||
|
|
||||||
|
void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) override;
|
||||||
|
void smoothInterpolate(const WavetableKeyframe* prev_keyframe,
|
||||||
|
const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
const WavetableKeyframe* next_keyframe, float t) override;
|
||||||
|
|
||||||
|
void render(vital::WaveFrame* wave_frame) override {
|
||||||
|
wave_frame->copy(wave_frame_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
void setInterpolationMode(WaveSource::InterpolationMode mode) { interpolation_mode_ = mode; }
|
||||||
|
WaveSource::InterpolationMode getInterpolationMode() const { return interpolation_mode_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<vital::WaveFrame> wave_frame_;
|
||||||
|
WaveSource::InterpolationMode interpolation_mode_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveSourceKeyframe)
|
||||||
|
};
|
||||||
136
src/common/wavetable/wave_warp_modifier.cpp
Normal file
136
src/common/wavetable/wave_warp_modifier.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wave_warp_modifier.h"
|
||||||
|
|
||||||
|
#include "futils.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
inline double highResPowerScale(float value, float power) {
|
||||||
|
static constexpr float kMinPower = 0.01f;
|
||||||
|
if (fabsf(power) < kMinPower)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
double abs_value = fabsf(value);
|
||||||
|
|
||||||
|
double numerator = exp(power * abs_value) - 1.0f;
|
||||||
|
double denominator = exp(power) - 1.0f;
|
||||||
|
if (value >= 0.0f)
|
||||||
|
return numerator / denominator;
|
||||||
|
return -numerator / denominator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveWarpModifier::WaveWarpModifierKeyframe::WaveWarpModifierKeyframe() {
|
||||||
|
horizontal_power_ = 0.0f;
|
||||||
|
vertical_power_ = 0.0f;
|
||||||
|
horizontal_asymmetric_ = false;
|
||||||
|
vertical_asymmetric_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWarpModifier::WaveWarpModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
|
||||||
|
const WaveWarpModifierKeyframe* source = dynamic_cast<const WaveWarpModifierKeyframe*>(keyframe);
|
||||||
|
horizontal_power_ = source->horizontal_power_;
|
||||||
|
vertical_power_ = source->vertical_power_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWarpModifier::WaveWarpModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
float t) {
|
||||||
|
const WaveWarpModifierKeyframe* from = dynamic_cast<const WaveWarpModifierKeyframe*>(from_keyframe);
|
||||||
|
const WaveWarpModifierKeyframe* to = dynamic_cast<const WaveWarpModifierKeyframe*>(to_keyframe);
|
||||||
|
|
||||||
|
horizontal_power_ = linearTween(from->horizontal_power_, to->horizontal_power_, t);
|
||||||
|
vertical_power_ = linearTween(from->vertical_power_, to->vertical_power_, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWarpModifier::WaveWarpModifierKeyframe::render(vital::WaveFrame* wave_frame) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
|
||||||
|
wave_frame->frequency_domain[i] = wave_frame->time_domain[i];
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
float horizontal = i / (vital::WaveFrame::kWaveformSize - 1.0f);
|
||||||
|
float warped_horizontal = 0.0f;
|
||||||
|
if (horizontal_asymmetric_)
|
||||||
|
warped_horizontal = highResPowerScale(horizontal, horizontal_power_);
|
||||||
|
else
|
||||||
|
warped_horizontal = 0.5f * highResPowerScale(2.0f * horizontal - 1.0f, horizontal_power_) + 0.5f;
|
||||||
|
|
||||||
|
float float_index = (vital::WaveFrame::kWaveformSize - 1) * warped_horizontal;
|
||||||
|
int index = float_index;
|
||||||
|
index = vital::utils::iclamp(index, 0, vital::WaveFrame::kWaveformSize - 2);
|
||||||
|
|
||||||
|
float vertical_from = wave_frame->frequency_domain[index].real();
|
||||||
|
float vertical_to = wave_frame->frequency_domain[index + 1].real();
|
||||||
|
float vertical = linearTween(vertical_from, vertical_to, float_index - index);
|
||||||
|
vertical = vital::utils::clamp(vertical, -1.0f, 1.0f);
|
||||||
|
if (vertical_asymmetric_)
|
||||||
|
wave_frame->time_domain[i] = 2.0f * highResPowerScale(0.5f * vertical + 0.5f, vertical_power_) - 1.0f;
|
||||||
|
else
|
||||||
|
wave_frame->time_domain[i] = highResPowerScale(vertical, vertical_power_);
|
||||||
|
}
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
json WaveWarpModifier::WaveWarpModifierKeyframe::stateToJson() {
|
||||||
|
json data = WavetableKeyframe::stateToJson();
|
||||||
|
data["horizontal_power"] = horizontal_power_;
|
||||||
|
data["vertical_power"] = vertical_power_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWarpModifier::WaveWarpModifierKeyframe::jsonToState(json data) {
|
||||||
|
WavetableKeyframe::jsonToState(data);
|
||||||
|
horizontal_power_ = data["horizontal_power"];
|
||||||
|
vertical_power_ = data["vertical_power"];
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableKeyframe* WaveWarpModifier::createKeyframe(int position) {
|
||||||
|
WaveWarpModifierKeyframe* keyframe = new WaveWarpModifierKeyframe();
|
||||||
|
interpolate(keyframe, position);
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWarpModifier::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
interpolate(&compute_frame_, position);
|
||||||
|
compute_frame_.setHorizontalAsymmetric(horizontal_asymmetric_);
|
||||||
|
compute_frame_.setVerticalAsymmetric(vertical_asymmetric_);
|
||||||
|
compute_frame_.render(wave_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType WaveWarpModifier::getType() {
|
||||||
|
return WavetableComponentFactory::kWaveWarp;
|
||||||
|
}
|
||||||
|
|
||||||
|
json WaveWarpModifier::stateToJson() {
|
||||||
|
json data = WavetableComponent::stateToJson();
|
||||||
|
data["horizontal_asymmetric"] = horizontal_asymmetric_;
|
||||||
|
data["vertical_asymmetric"] = vertical_asymmetric_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWarpModifier::jsonToState(json data) {
|
||||||
|
WavetableComponent::jsonToState(data);
|
||||||
|
horizontal_asymmetric_ = data["horizontal_asymmetric"];
|
||||||
|
vertical_asymmetric_ = data["vertical_asymmetric"];
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveWarpModifier::WaveWarpModifierKeyframe* WaveWarpModifier::getKeyframe(int index) {
|
||||||
|
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
|
||||||
|
return dynamic_cast<WaveWarpModifier::WaveWarpModifierKeyframe*>(wavetable_keyframe);
|
||||||
|
}
|
||||||
79
src/common/wavetable/wave_warp_modifier.h
Normal file
79
src/common/wavetable/wave_warp_modifier.h
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
class WaveWarpModifier : public WavetableComponent {
|
||||||
|
public:
|
||||||
|
class WaveWarpModifierKeyframe : public WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
WaveWarpModifierKeyframe();
|
||||||
|
virtual ~WaveWarpModifierKeyframe() { }
|
||||||
|
|
||||||
|
void copy(const WavetableKeyframe* keyframe) override;
|
||||||
|
void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) override;
|
||||||
|
void render(vital::WaveFrame* wave_frame) override;
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
float getHorizontalPower() { return horizontal_power_; }
|
||||||
|
float getVerticalPower() { return vertical_power_; }
|
||||||
|
|
||||||
|
void setHorizontalPower(float horizontal_power) { horizontal_power_ = horizontal_power; }
|
||||||
|
void setVerticalPower(float vertical_power) { vertical_power_ = vertical_power; }
|
||||||
|
|
||||||
|
void setHorizontalAsymmetric(bool horizontal_asymmetric) { horizontal_asymmetric_ = horizontal_asymmetric; }
|
||||||
|
void setVerticalAsymmetric(bool vertical_asymmetric) { vertical_asymmetric_ = vertical_asymmetric; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float horizontal_power_;
|
||||||
|
float vertical_power_;
|
||||||
|
|
||||||
|
bool horizontal_asymmetric_;
|
||||||
|
bool vertical_asymmetric_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveWarpModifierKeyframe)
|
||||||
|
};
|
||||||
|
|
||||||
|
WaveWarpModifier() : horizontal_asymmetric_(false), vertical_asymmetric_(false) { }
|
||||||
|
virtual ~WaveWarpModifier() = default;
|
||||||
|
|
||||||
|
virtual WavetableKeyframe* createKeyframe(int position) override;
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
virtual json stateToJson() override;
|
||||||
|
virtual void jsonToState(json data) override;
|
||||||
|
|
||||||
|
void setHorizontalAsymmetric(bool horizontal_asymmetric) { horizontal_asymmetric_ = horizontal_asymmetric; }
|
||||||
|
void setVerticalAsymmetric(bool vertical_asymmetric) { vertical_asymmetric_ = vertical_asymmetric; }
|
||||||
|
|
||||||
|
bool getHorizontalAsymmetric() const { return horizontal_asymmetric_; }
|
||||||
|
bool getVerticalAsymmetric() const { return vertical_asymmetric_; }
|
||||||
|
|
||||||
|
WaveWarpModifierKeyframe* getKeyframe(int index);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
WaveWarpModifierKeyframe compute_frame_;
|
||||||
|
bool horizontal_asymmetric_;
|
||||||
|
bool vertical_asymmetric_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveWarpModifier)
|
||||||
|
};
|
||||||
|
|
||||||
120
src/common/wavetable/wave_window_modifier.cpp
Normal file
120
src/common/wavetable/wave_window_modifier.cpp
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wave_frame.h"
|
||||||
|
|
||||||
|
#include "wave_window_modifier.h"
|
||||||
|
|
||||||
|
float WaveWindowModifier::applyWindow(WindowShape window_shape, float t) {
|
||||||
|
if (window_shape == kCos)
|
||||||
|
return 0.5f - 0.5f * cosf(vital::kPi * t);
|
||||||
|
if (window_shape == kHalfSin)
|
||||||
|
return sinf(vital::kPi * t / 2.0f);
|
||||||
|
if (window_shape == kSquare)
|
||||||
|
return t < 1.0f ? 0.0f : 1.0f;
|
||||||
|
if (window_shape == kWiggle)
|
||||||
|
return t * cosf(vital::kPi * (t * 1.5f + 0.5f));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveWindowModifier::WaveWindowModifierKeyframe::WaveWindowModifierKeyframe() {
|
||||||
|
static constexpr float kDefaultOffset = 0.25f;
|
||||||
|
left_position_ = kDefaultOffset;
|
||||||
|
right_position_ = 1.0f - kDefaultOffset;
|
||||||
|
window_shape_ = kCos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWindowModifier::WaveWindowModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
|
||||||
|
const WaveWindowModifierKeyframe* source = dynamic_cast<const WaveWindowModifierKeyframe*>(keyframe);
|
||||||
|
|
||||||
|
left_position_ = source->left_position_;
|
||||||
|
right_position_ = source->right_position_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWindowModifier::WaveWindowModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
float t) {
|
||||||
|
const WaveWindowModifierKeyframe* from = dynamic_cast<const WaveWindowModifierKeyframe*>(from_keyframe);
|
||||||
|
const WaveWindowModifierKeyframe* to = dynamic_cast<const WaveWindowModifierKeyframe*>(to_keyframe);
|
||||||
|
|
||||||
|
left_position_ = linearTween(from->left_position_, to->left_position_, t);
|
||||||
|
right_position_ = linearTween(from->right_position_, to->right_position_, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWindowModifier::WaveWindowModifierKeyframe::render(vital::WaveFrame* wave_frame) {
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
float t = i / (vital::WaveFrame::kWaveformSize - 1.0f);
|
||||||
|
if (t >= left_position_)
|
||||||
|
break;
|
||||||
|
|
||||||
|
wave_frame->time_domain[i] *= applyWindow(t / left_position_);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = vital::WaveFrame::kWaveformSize; i >= 0; --i) {
|
||||||
|
float t = i / (vital::WaveFrame::kWaveformSize - 1.0f);
|
||||||
|
if (t <= right_position_)
|
||||||
|
break;
|
||||||
|
|
||||||
|
wave_frame->time_domain[i] *= applyWindow((1.0f - t) / (1.0f - right_position_));
|
||||||
|
}
|
||||||
|
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
json WaveWindowModifier::WaveWindowModifierKeyframe::stateToJson() {
|
||||||
|
json data = WavetableKeyframe::stateToJson();
|
||||||
|
data["left_position"] = left_position_;
|
||||||
|
data["right_position"] = right_position_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWindowModifier::WaveWindowModifierKeyframe::jsonToState(json data) {
|
||||||
|
WavetableKeyframe::jsonToState(data);
|
||||||
|
left_position_ = data["left_position"];
|
||||||
|
right_position_ = data["right_position"];
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableKeyframe* WaveWindowModifier::createKeyframe(int position) {
|
||||||
|
WaveWindowModifierKeyframe* keyframe = new WaveWindowModifierKeyframe();
|
||||||
|
interpolate(keyframe, position);
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWindowModifier::render(vital::WaveFrame* wave_frame, float position) {
|
||||||
|
interpolate(&compute_frame_, position);
|
||||||
|
compute_frame_.setWindowShape(window_shape_);
|
||||||
|
compute_frame_.render(wave_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponentFactory::ComponentType WaveWindowModifier::getType() {
|
||||||
|
return WavetableComponentFactory::kWaveWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
json WaveWindowModifier::stateToJson() {
|
||||||
|
json data = WavetableComponent::stateToJson();
|
||||||
|
data["window_shape"] = window_shape_;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveWindowModifier::jsonToState(json data) {
|
||||||
|
WavetableComponent::jsonToState(data);
|
||||||
|
window_shape_ = data["window_shape"];
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveWindowModifier::WaveWindowModifierKeyframe* WaveWindowModifier::getKeyframe(int index) {
|
||||||
|
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
|
||||||
|
return dynamic_cast<WaveWindowModifier::WaveWindowModifierKeyframe*>(wavetable_keyframe);
|
||||||
|
}
|
||||||
84
src/common/wavetable/wave_window_modifier.h
Normal file
84
src/common/wavetable/wave_window_modifier.h
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
class WaveWindowModifier : public WavetableComponent {
|
||||||
|
public:
|
||||||
|
enum WindowShape {
|
||||||
|
kCos,
|
||||||
|
kHalfSin,
|
||||||
|
kLinear,
|
||||||
|
kSquare,
|
||||||
|
kWiggle,
|
||||||
|
kNumWindowShapes
|
||||||
|
};
|
||||||
|
|
||||||
|
static float applyWindow(WindowShape window_shape, float t);
|
||||||
|
|
||||||
|
class WaveWindowModifierKeyframe : public WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
WaveWindowModifierKeyframe();
|
||||||
|
virtual ~WaveWindowModifierKeyframe() { }
|
||||||
|
|
||||||
|
void copy(const WavetableKeyframe* keyframe) override;
|
||||||
|
void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) override;
|
||||||
|
void render(vital::WaveFrame* wave_frame) override;
|
||||||
|
json stateToJson() override;
|
||||||
|
void jsonToState(json data) override;
|
||||||
|
|
||||||
|
void setLeft(float left) { left_position_ = left; }
|
||||||
|
void setRight(float right) { right_position_ = right; }
|
||||||
|
float getLeft() { return left_position_; }
|
||||||
|
float getRight() { return right_position_; }
|
||||||
|
|
||||||
|
void setWindowShape(WindowShape window_shape) { window_shape_ = window_shape; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
inline float applyWindow(float t) { return WaveWindowModifier::applyWindow(window_shape_, t); }
|
||||||
|
|
||||||
|
float left_position_;
|
||||||
|
float right_position_;
|
||||||
|
WindowShape window_shape_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveWindowModifierKeyframe)
|
||||||
|
};
|
||||||
|
|
||||||
|
WaveWindowModifier() : window_shape_(kCos) { }
|
||||||
|
virtual ~WaveWindowModifier() { }
|
||||||
|
|
||||||
|
virtual WavetableKeyframe* createKeyframe(int position) override;
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) override;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() override;
|
||||||
|
virtual json stateToJson() override;
|
||||||
|
virtual void jsonToState(json data) override;
|
||||||
|
|
||||||
|
WaveWindowModifierKeyframe* getKeyframe(int index);
|
||||||
|
|
||||||
|
void setWindowShape(WindowShape window_shape) { window_shape_ = window_shape; }
|
||||||
|
WindowShape getWindowShape() { return window_shape_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
WaveWindowModifierKeyframe compute_frame_;
|
||||||
|
WindowShape window_shape_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveWindowModifier)
|
||||||
|
};
|
||||||
|
|
||||||
134
src/common/wavetable/wavetable_component.cpp
Normal file
134
src/common/wavetable/wavetable_component.cpp
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
WavetableKeyframe* WavetableComponent::insertNewKeyframe(int position) {
|
||||||
|
VITAL_ASSERT(position >= 0 && position < vital::kNumOscillatorWaveFrames);
|
||||||
|
|
||||||
|
WavetableKeyframe* keyframe = createKeyframe(position);
|
||||||
|
keyframe->setOwner(this);
|
||||||
|
keyframe->setPosition(position);
|
||||||
|
|
||||||
|
int index = getIndexFromPosition(position);
|
||||||
|
keyframes_.insert(keyframes_.begin() + index, std::unique_ptr<WavetableKeyframe>(keyframe));
|
||||||
|
return keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableComponent::reposition(WavetableKeyframe* keyframe) {
|
||||||
|
int start_index = indexOf(keyframe);
|
||||||
|
keyframes_[start_index].release();
|
||||||
|
keyframes_.erase(keyframes_.begin() + start_index);
|
||||||
|
|
||||||
|
int new_index = getIndexFromPosition(keyframe->position());
|
||||||
|
keyframes_.insert(keyframes_.begin() + new_index, std::unique_ptr<WavetableKeyframe>(keyframe));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableComponent::remove(WavetableKeyframe* keyframe) {
|
||||||
|
int start_index = indexOf(keyframe);
|
||||||
|
keyframes_.erase(keyframes_.begin() + start_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableComponent::jsonToState(json data) {
|
||||||
|
keyframes_.clear();
|
||||||
|
for (json json_keyframe : data["keyframes"]) {
|
||||||
|
WavetableKeyframe* keyframe = insertNewKeyframe(json_keyframe["position"]);
|
||||||
|
keyframe->jsonToState(json_keyframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.count("interpolation_style"))
|
||||||
|
interpolation_style_ = data["interpolation_style"];
|
||||||
|
}
|
||||||
|
|
||||||
|
json WavetableComponent::stateToJson() {
|
||||||
|
json keyframes_data;
|
||||||
|
for (int i = 0; i < keyframes_.size(); ++i)
|
||||||
|
keyframes_data.emplace_back(keyframes_[i]->stateToJson());
|
||||||
|
|
||||||
|
return {
|
||||||
|
{ "keyframes", keyframes_data },
|
||||||
|
{ "type", WavetableComponentFactory::getComponentName(getType()) },
|
||||||
|
{ "interpolation_style", interpolation_style_ }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableComponent::reset() {
|
||||||
|
keyframes_.clear();
|
||||||
|
insertNewKeyframe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableComponent::interpolate(WavetableKeyframe* dest, float position) {
|
||||||
|
if (numFrames() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = getIndexFromPosition(position) - 1;
|
||||||
|
int clamped_index = std::min(std::max(index, 0), numFrames() - 1);
|
||||||
|
WavetableKeyframe* from_frame = keyframes_[clamped_index].get();
|
||||||
|
|
||||||
|
if (index < 0 || index >= numFrames() - 1 || interpolation_style_ == kNone)
|
||||||
|
dest->copy(from_frame);
|
||||||
|
else if (interpolation_style_ == kLinear) {
|
||||||
|
WavetableKeyframe* to_frame = keyframes_[index + 1].get();
|
||||||
|
int from_position = keyframes_[index]->position();
|
||||||
|
int to_position = keyframes_[index + 1]->position();
|
||||||
|
float t = (1.0f * position - from_position) / (to_position - from_position);
|
||||||
|
dest->interpolate(from_frame, to_frame, t);
|
||||||
|
}
|
||||||
|
else if (interpolation_style_ == kCubic) {
|
||||||
|
int next_index = index + 2;
|
||||||
|
int prev_index = index - 1;
|
||||||
|
if (next_index >= numFrames())
|
||||||
|
next_index = index;
|
||||||
|
if (prev_index < 0)
|
||||||
|
prev_index = index + 1;
|
||||||
|
|
||||||
|
WavetableKeyframe* to_frame = keyframes_[index + 1].get();
|
||||||
|
WavetableKeyframe* next_frame = keyframes_[next_index].get();
|
||||||
|
WavetableKeyframe* prev_frame = keyframes_[prev_index].get();
|
||||||
|
|
||||||
|
int from_position = keyframes_[index]->position();
|
||||||
|
int to_position = keyframes_[index + 1]->position();
|
||||||
|
float t = (1.0f * position - from_position) / (to_position - from_position);
|
||||||
|
dest->smoothInterpolate(prev_frame, from_frame, to_frame, next_frame, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int WavetableComponent::getIndexFromPosition(int position) const {
|
||||||
|
int index = 0;
|
||||||
|
for (auto& keyframe : keyframes_) {
|
||||||
|
if (position < keyframe->position())
|
||||||
|
break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableKeyframe* WavetableComponent::getFrameAtPosition(int position) {
|
||||||
|
int index = getIndexFromPosition(position);
|
||||||
|
if (index < 0 || index >= keyframes_.size())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return keyframes_[index].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
int WavetableComponent::getLastKeyframePosition() {
|
||||||
|
if (keyframes_.size() == 0)
|
||||||
|
return 0;
|
||||||
|
if (!hasKeyframes())
|
||||||
|
return vital::kNumOscillatorWaveFrames - 1;
|
||||||
|
return keyframes_[keyframes_.size() - 1]->position();
|
||||||
|
}
|
||||||
78
src/common/wavetable/wavetable_component.h
Normal file
78
src/common/wavetable/wavetable_component.h
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wavetable_keyframe.h"
|
||||||
|
#include "wavetable_component_factory.h"
|
||||||
|
#include "json/json.h"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
class WaveFrame;
|
||||||
|
} // namespace vital
|
||||||
|
|
||||||
|
class WavetableComponent {
|
||||||
|
public:
|
||||||
|
enum InterpolationStyle {
|
||||||
|
kNone,
|
||||||
|
kLinear,
|
||||||
|
kCubic,
|
||||||
|
kNumInterpolationStyles
|
||||||
|
};
|
||||||
|
|
||||||
|
WavetableComponent() : interpolation_style_(kLinear) { }
|
||||||
|
virtual ~WavetableComponent() { }
|
||||||
|
|
||||||
|
virtual WavetableKeyframe* createKeyframe(int position) = 0;
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame, float position) = 0;
|
||||||
|
virtual WavetableComponentFactory::ComponentType getType() = 0;
|
||||||
|
virtual json stateToJson();
|
||||||
|
virtual void jsonToState(json data);
|
||||||
|
virtual void prerender() { }
|
||||||
|
virtual bool hasKeyframes() { return true; }
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
void interpolate(WavetableKeyframe* dest, float position);
|
||||||
|
WavetableKeyframe* insertNewKeyframe(int position);
|
||||||
|
void reposition(WavetableKeyframe* keyframe);
|
||||||
|
void remove(WavetableKeyframe* keyframe);
|
||||||
|
|
||||||
|
inline int numFrames() const { return static_cast<int>(keyframes_.size()); }
|
||||||
|
inline int indexOf(WavetableKeyframe* keyframe) const {
|
||||||
|
for (int i = 0; i < keyframes_.size(); ++i) {
|
||||||
|
if (keyframes_[i].get() == keyframe)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
inline WavetableKeyframe* getFrameAt(int index) const { return keyframes_[index].get(); }
|
||||||
|
int getIndexFromPosition(int position) const;
|
||||||
|
WavetableKeyframe* getFrameAtPosition(int position);
|
||||||
|
int getLastKeyframePosition();
|
||||||
|
|
||||||
|
void setInterpolationStyle(InterpolationStyle type) { interpolation_style_ = type; }
|
||||||
|
InterpolationStyle getInterpolationStyle() const { return interpolation_style_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::unique_ptr<WavetableKeyframe>> keyframes_;
|
||||||
|
InterpolationStyle interpolation_style_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableComponent)
|
||||||
|
};
|
||||||
|
|
||||||
109
src/common/wavetable/wavetable_component_factory.cpp
Normal file
109
src/common/wavetable/wavetable_component_factory.cpp
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wavetable_component_factory.h"
|
||||||
|
#include "file_source.h"
|
||||||
|
#include "frequency_filter_modifier.h"
|
||||||
|
#include "phase_modifier.h"
|
||||||
|
#include "shepard_tone_source.h"
|
||||||
|
#include "slew_limit_modifier.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "wave_fold_modifier.h"
|
||||||
|
#include "wave_line_source.h"
|
||||||
|
#include "wave_source.h"
|
||||||
|
#include "wave_warp_modifier.h"
|
||||||
|
#include "wave_window_modifier.h"
|
||||||
|
|
||||||
|
WavetableComponent* WavetableComponentFactory::createComponent(ComponentType type) {
|
||||||
|
switch (type) {
|
||||||
|
case kWaveSource:
|
||||||
|
return new WaveSource();
|
||||||
|
case kLineSource:
|
||||||
|
return new WaveLineSource();
|
||||||
|
case kFileSource:
|
||||||
|
return new FileSource();
|
||||||
|
case kShepardToneSource:
|
||||||
|
return new ShepardToneSource();
|
||||||
|
case kPhaseModifier:
|
||||||
|
return new PhaseModifier();
|
||||||
|
case kWaveWindow:
|
||||||
|
return new WaveWindowModifier();
|
||||||
|
case kFrequencyFilter:
|
||||||
|
return new FrequencyFilterModifier();
|
||||||
|
case kSlewLimiter:
|
||||||
|
return new SlewLimitModifier();
|
||||||
|
case kWaveFolder:
|
||||||
|
return new WaveFoldModifier();
|
||||||
|
case kWaveWarp:
|
||||||
|
return new WaveWarpModifier();
|
||||||
|
default:
|
||||||
|
VITAL_ASSERT(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WavetableComponent* WavetableComponentFactory::createComponent(const std::string& type) {
|
||||||
|
if (type == "Wave Source")
|
||||||
|
return new WaveSource();
|
||||||
|
if (type == "Line Source")
|
||||||
|
return new WaveLineSource();
|
||||||
|
if (type == "Audio File Source")
|
||||||
|
return new FileSource();
|
||||||
|
if (type == "Shepard Tone Source")
|
||||||
|
return new ShepardToneSource();
|
||||||
|
if (type == "Phase Shift")
|
||||||
|
return new PhaseModifier();
|
||||||
|
if (type == "Wave Window")
|
||||||
|
return new WaveWindowModifier();
|
||||||
|
if (type == "Frequency Filter")
|
||||||
|
return new FrequencyFilterModifier();
|
||||||
|
if (type == "Slew Limiter")
|
||||||
|
return new SlewLimitModifier();
|
||||||
|
if (type == "Wave Folder")
|
||||||
|
return new WaveFoldModifier();
|
||||||
|
if (type == "Wave Warp")
|
||||||
|
return new WaveWarpModifier();
|
||||||
|
|
||||||
|
VITAL_ASSERT(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WavetableComponentFactory::getComponentName(ComponentType type) {
|
||||||
|
switch (type) {
|
||||||
|
case kWaveSource:
|
||||||
|
return "Wave Source";
|
||||||
|
case kLineSource:
|
||||||
|
return "Line Source";
|
||||||
|
case kFileSource:
|
||||||
|
return "Audio File Source";
|
||||||
|
case kShepardToneSource:
|
||||||
|
return "Shepard Tone Source";
|
||||||
|
case kPhaseModifier:
|
||||||
|
return "Phase Shift";
|
||||||
|
case kWaveWindow:
|
||||||
|
return "Wave Window";
|
||||||
|
case kFrequencyFilter:
|
||||||
|
return "Frequency Filter";
|
||||||
|
case kSlewLimiter:
|
||||||
|
return "Slew Limiter";
|
||||||
|
case kWaveFolder:
|
||||||
|
return "Wave Folder";
|
||||||
|
case kWaveWarp:
|
||||||
|
return "Wave Warp";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/common/wavetable/wavetable_component_factory.h
Normal file
57
src/common/wavetable/wavetable_component_factory.h
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
class WavetableComponent;
|
||||||
|
|
||||||
|
class WavetableComponentFactory {
|
||||||
|
public:
|
||||||
|
enum ComponentType {
|
||||||
|
kWaveSource,
|
||||||
|
kLineSource,
|
||||||
|
kFileSource,
|
||||||
|
kNumSourceTypes,
|
||||||
|
kShepardToneSource = kNumSourceTypes, // Deprecated
|
||||||
|
|
||||||
|
kBeginModifierTypes = kNumSourceTypes + 1,
|
||||||
|
kPhaseModifier = kBeginModifierTypes,
|
||||||
|
kWaveWindow,
|
||||||
|
kFrequencyFilter,
|
||||||
|
kSlewLimiter,
|
||||||
|
kWaveFolder,
|
||||||
|
kWaveWarp,
|
||||||
|
kNumComponentTypes
|
||||||
|
};
|
||||||
|
|
||||||
|
static int numComponentTypes() { return kNumComponentTypes; }
|
||||||
|
static int numSourceTypes() { return kNumSourceTypes; }
|
||||||
|
static int numModifierTypes() { return kNumComponentTypes - kBeginModifierTypes; }
|
||||||
|
|
||||||
|
static WavetableComponent* createComponent(ComponentType type);
|
||||||
|
static WavetableComponent* createComponent(const std::string& type);
|
||||||
|
static std::string getComponentName(ComponentType type);
|
||||||
|
static ComponentType getSourceType(int type) { return static_cast<ComponentType>(type); }
|
||||||
|
static ComponentType getModifierType(int type) {
|
||||||
|
return (ComponentType)(type + kBeginModifierTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WavetableComponentFactory() { }
|
||||||
|
};
|
||||||
|
|
||||||
516
src/common/wavetable/wavetable_creator.cpp
Normal file
516
src/common/wavetable/wavetable_creator.cpp
Normal file
|
|
@ -0,0 +1,516 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wavetable_creator.h"
|
||||||
|
#include "line_generator.h"
|
||||||
|
#include "load_save.h"
|
||||||
|
#include "synth_constants.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "wave_line_source.h"
|
||||||
|
#include "wave_source.h"
|
||||||
|
#include "wavetable.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int getFirstNonZeroSample(const float* audio_buffer, int num_samples) {
|
||||||
|
for (int i = 0; i < num_samples; ++i) {
|
||||||
|
if (audio_buffer[i])
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int WavetableCreator::getGroupIndex(WavetableGroup* group) {
|
||||||
|
for (int i = 0; i < groups_.size(); ++i) {
|
||||||
|
if (groups_[i].get() == group)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::moveUp(int index) {
|
||||||
|
if (index <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
groups_[index].swap(groups_[index - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::moveDown(int index) {
|
||||||
|
if (index < 0 || index >= groups_.size() - 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
groups_[index].swap(groups_[index + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::removeGroup(int index) {
|
||||||
|
if (index < 0 || index >= groups_.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::unique_ptr<WavetableGroup> group = std::move(groups_[index]);
|
||||||
|
groups_.erase(groups_.begin() + index);
|
||||||
|
}
|
||||||
|
|
||||||
|
float WavetableCreator::render(int position) {
|
||||||
|
compute_frame_combine_.clear();
|
||||||
|
compute_frame_combine_.index = position;
|
||||||
|
compute_frame_.index = position;
|
||||||
|
|
||||||
|
for (auto& group : groups_) {
|
||||||
|
group->render(&compute_frame_, position);
|
||||||
|
compute_frame_combine_.addFrom(&compute_frame_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groups_.size() > 1)
|
||||||
|
compute_frame_combine_.multiply(1.0f / groups_.size());
|
||||||
|
|
||||||
|
if (remove_all_dc_)
|
||||||
|
compute_frame_combine_.removedDc();
|
||||||
|
|
||||||
|
float max_value = 0.0f;
|
||||||
|
float min_value = 0.0f;
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
max_value = std::max(compute_frame_combine_.time_domain[i], max_value);
|
||||||
|
min_value = std::min(compute_frame_combine_.time_domain[i], min_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
wavetable_->loadWaveFrame(&compute_frame_combine_);
|
||||||
|
return max_value - min_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::render() {
|
||||||
|
int last_waveframe = 0;
|
||||||
|
bool shepard = groups_.size() > 0;
|
||||||
|
for (auto& group : groups_) {
|
||||||
|
group->prerender();
|
||||||
|
last_waveframe = std::max(last_waveframe, group->getLastKeyframePosition());
|
||||||
|
shepard = shepard && group->isShepardTone();
|
||||||
|
}
|
||||||
|
|
||||||
|
wavetable_->setNumFrames(last_waveframe + 1);
|
||||||
|
wavetable_->setShepardTable(shepard);
|
||||||
|
float max_span = 0.0f;
|
||||||
|
for (int i = 0; i < last_waveframe + 1; ++i)
|
||||||
|
max_span = std::max(render(i), max_span);
|
||||||
|
wavetable_->setFrequencyRatio(compute_frame_.frequency_ratio);
|
||||||
|
wavetable_->setSampleRate(compute_frame_.sample_rate);
|
||||||
|
|
||||||
|
postRender(max_span);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::postRender(float max_span) {
|
||||||
|
if (full_normalize_)
|
||||||
|
wavetable_->postProcess(max_span);
|
||||||
|
else
|
||||||
|
wavetable_->postProcess(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::renderToBuffer(float* buffer, int num_frames, int frame_size) {
|
||||||
|
int total_samples = num_frames * frame_size;
|
||||||
|
for (int i = 0; i < num_frames; ++i) {
|
||||||
|
float position = (1.0f * i * vital::kNumOscillatorWaveFrames) / num_frames;
|
||||||
|
compute_frame_combine_.clear();
|
||||||
|
compute_frame_combine_.index = position;
|
||||||
|
compute_frame_.index = position;
|
||||||
|
|
||||||
|
for (auto& group : groups_) {
|
||||||
|
group->render(&compute_frame_, position);
|
||||||
|
compute_frame_combine_.addFrom(&compute_frame_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float* output_buffer = buffer + (i * frame_size);
|
||||||
|
|
||||||
|
if (frame_size != vital::WaveFrame::kWaveformSize)
|
||||||
|
VITAL_ASSERT(false); // TODO: support different waveframe size.
|
||||||
|
else {
|
||||||
|
for (int s = 0; s < vital::WaveFrame::kWaveformSize; ++s)
|
||||||
|
output_buffer[s] = compute_frame_combine_.time_domain[s];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float max_value = 1.0f;
|
||||||
|
for (int i = 0; i < total_samples; ++i)
|
||||||
|
max_value = std::max(max_value, fabsf(buffer[i]));
|
||||||
|
|
||||||
|
float scale = 1.0f / max_value;
|
||||||
|
for (int i = 0; i < total_samples; ++i)
|
||||||
|
buffer[i] *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::init() {
|
||||||
|
clear();
|
||||||
|
loadDefaultCreator();
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::clear() {
|
||||||
|
groups_.clear();
|
||||||
|
remove_all_dc_ = true;
|
||||||
|
full_normalize_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::loadDefaultCreator() {
|
||||||
|
wavetable_->setName("Init");
|
||||||
|
WavetableGroup* new_group = new WavetableGroup();
|
||||||
|
new_group->loadDefaultGroup();
|
||||||
|
addGroup(new_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::initPredefinedWaves() {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
WavetableGroup* new_group = new WavetableGroup();
|
||||||
|
WaveSource* wave_source = new WaveSource();
|
||||||
|
|
||||||
|
int num_shapes = vital::PredefinedWaveFrames::kNumShapes;
|
||||||
|
for (int i = 0; i < num_shapes; ++i) {
|
||||||
|
int position = (vital::kNumOscillatorWaveFrames * i) / num_shapes;
|
||||||
|
wave_source->insertNewKeyframe(position);
|
||||||
|
WaveSourceKeyframe* keyframe = wave_source->getKeyframe(i);
|
||||||
|
|
||||||
|
vital::PredefinedWaveFrames::Shape shape = static_cast<vital::PredefinedWaveFrames::Shape>(i);
|
||||||
|
keyframe->wave_frame()->copy(vital::PredefinedWaveFrames::getWaveFrame(shape));
|
||||||
|
}
|
||||||
|
wave_source->setInterpolationStyle(WaveSource::kNone);
|
||||||
|
full_normalize_ = false;
|
||||||
|
remove_all_dc_ = false;
|
||||||
|
|
||||||
|
new_group->addComponent(wave_source);
|
||||||
|
addGroup(new_group);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::initFromAudioFile(const float* audio_buffer, int num_samples, int sample_rate,
|
||||||
|
AudioFileLoadStyle load_style, FileSource::FadeStyle fade_style) {
|
||||||
|
int beginning_sample = getFirstNonZeroSample(audio_buffer, num_samples);
|
||||||
|
int shortened_num_samples = num_samples - beginning_sample;
|
||||||
|
if (load_style == kVocoded)
|
||||||
|
initFromVocodedAudioFile(audio_buffer + beginning_sample, shortened_num_samples, sample_rate, false);
|
||||||
|
else if (load_style == kTtwt)
|
||||||
|
initFromVocodedAudioFile(audio_buffer + beginning_sample, shortened_num_samples, sample_rate, true);
|
||||||
|
else if (load_style == kPitched)
|
||||||
|
initFromPitchedAudioFile(audio_buffer + beginning_sample, shortened_num_samples, sample_rate);
|
||||||
|
else
|
||||||
|
initFromSplicedAudioFile(audio_buffer, num_samples, sample_rate, fade_style);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::initFromSplicedAudioFile(const float* audio_buffer, int num_samples, int sample_rate,
|
||||||
|
FileSource::FadeStyle fade_style) {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
WavetableGroup* new_group = new WavetableGroup();
|
||||||
|
FileSource* file_source = new FileSource();
|
||||||
|
|
||||||
|
file_source->loadBuffer(audio_buffer, num_samples, sample_rate);
|
||||||
|
file_source->setFadeStyle(fade_style);
|
||||||
|
file_source->setPhaseStyle(FileSource::PhaseStyle::kNone);
|
||||||
|
file_source->insertNewKeyframe(0);
|
||||||
|
file_source->detectWaveEditTable();
|
||||||
|
|
||||||
|
double window_size = file_source->getWindowSize();
|
||||||
|
if (fade_style == FileSource::kNoInterpolate) {
|
||||||
|
int num_cycles = std::max<int>(1, num_samples / window_size);
|
||||||
|
int buffer_frames = vital::kNumOscillatorWaveFrames / num_cycles;
|
||||||
|
file_source->insertNewKeyframe(std::max(0, vital::kNumOscillatorWaveFrames - 1 - buffer_frames));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
file_source->insertNewKeyframe(vital::kNumOscillatorWaveFrames - 1);
|
||||||
|
|
||||||
|
file_source->getKeyframe(0)->setStartPosition(0);
|
||||||
|
int last_sample_position = num_samples - window_size;
|
||||||
|
last_sample_position = std::min<int>(file_source->getKeyframe(1)->position() * window_size, last_sample_position);
|
||||||
|
file_source->getKeyframe(1)->setStartPosition(std::max(0, last_sample_position));
|
||||||
|
|
||||||
|
new_group->addComponent(file_source);
|
||||||
|
addGroup(new_group);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::initFromVocodedAudioFile(const float* audio_buffer, int num_samples,
|
||||||
|
int sample_rate, bool ttwt) {
|
||||||
|
static constexpr float kMaxTTWTPeriod = .02f;
|
||||||
|
clear();
|
||||||
|
|
||||||
|
WavetableGroup* new_group = new WavetableGroup();
|
||||||
|
FileSource* file_source = new FileSource();
|
||||||
|
|
||||||
|
file_source->loadBuffer(audio_buffer, num_samples, sample_rate);
|
||||||
|
if (ttwt)
|
||||||
|
file_source->detectPitch(kMaxTTWTPeriod * sample_rate);
|
||||||
|
else
|
||||||
|
file_source->detectPitch();
|
||||||
|
|
||||||
|
file_source->setFadeStyle(FileSource::FadeStyle::kWaveBlend);
|
||||||
|
file_source->setPhaseStyle(FileSource::PhaseStyle::kVocode);
|
||||||
|
file_source->insertNewKeyframe(0);
|
||||||
|
file_source->insertNewKeyframe(vital::kNumOscillatorWaveFrames - 1);
|
||||||
|
file_source->getKeyframe(0)->setStartPosition(0);
|
||||||
|
int samples_needed = file_source->getKeyframe(1)->getSamplesNeeded();
|
||||||
|
file_source->getKeyframe(1)->setStartPosition(num_samples - samples_needed);
|
||||||
|
|
||||||
|
new_group->addComponent(file_source);
|
||||||
|
addGroup(new_group);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::initFromPitchedAudioFile(const float* audio_buffer, int num_samples, int sample_rate) {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
WavetableGroup* new_group = new WavetableGroup();
|
||||||
|
FileSource* file_source = new FileSource();
|
||||||
|
|
||||||
|
file_source->loadBuffer(audio_buffer, num_samples, sample_rate);
|
||||||
|
file_source->detectPitch();
|
||||||
|
file_source->setFadeStyle(FileSource::FadeStyle::kWaveBlend);
|
||||||
|
file_source->insertNewKeyframe(0);
|
||||||
|
file_source->insertNewKeyframe(vital::kNumOscillatorWaveFrames - 1);
|
||||||
|
file_source->getKeyframe(0)->setStartPosition(0);
|
||||||
|
int samples_needed = file_source->getKeyframe(1)->getSamplesNeeded();
|
||||||
|
file_source->getKeyframe(1)->setStartPosition(num_samples - samples_needed);
|
||||||
|
|
||||||
|
new_group->addComponent(file_source);
|
||||||
|
addGroup(new_group);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::initFromLineGenerator(LineGenerator* line_generator) {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
wavetable_->setName(line_generator->getName());
|
||||||
|
WavetableGroup* new_group = new WavetableGroup();
|
||||||
|
|
||||||
|
WaveLineSource* line_source = new WaveLineSource();
|
||||||
|
line_source->insertNewKeyframe(0);
|
||||||
|
WaveLineSource::WaveLineSourceKeyframe* keyframe = line_source->getKeyframe(0);
|
||||||
|
keyframe->getLineGenerator()->jsonToState(line_generator->stateToJson());
|
||||||
|
|
||||||
|
new_group->addComponent(line_source);
|
||||||
|
addGroup(new_group);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WavetableCreator::isValidJson(json data) {
|
||||||
|
if (LineGenerator::isValidJson(data))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!data.count("version") || !data.count("groups") || !data.count("name"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
json groups_data = data["groups"];
|
||||||
|
return groups_data.is_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
json WavetableCreator::updateJson(json data) {
|
||||||
|
std::string version = "0.0.0";
|
||||||
|
if (data.count("version")) {
|
||||||
|
std::string ver = data["version"];
|
||||||
|
version = ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoadSave::compareVersionStrings(version, "0.3.3") < 0) {
|
||||||
|
const std::string kOldOrder[] = {
|
||||||
|
"Wave Source", "Line Source", "Audio File Source", "Phase Shift", "Wave Window",
|
||||||
|
"Frequency Filter", "Slew Limiter", "Wave Folder", "Wave Warp"
|
||||||
|
};
|
||||||
|
json json_groups = data["groups"];
|
||||||
|
json new_groups;
|
||||||
|
for (json json_group : json_groups) {
|
||||||
|
json json_components = json_group["components"];
|
||||||
|
json new_components;
|
||||||
|
for (json json_component : json_components) {
|
||||||
|
int int_type = json_component["type"];
|
||||||
|
json_component["type"] = kOldOrder[int_type];
|
||||||
|
|
||||||
|
new_components.push_back(json_component);
|
||||||
|
}
|
||||||
|
json_group["components"] = new_components;
|
||||||
|
new_groups.push_back(json_group);
|
||||||
|
}
|
||||||
|
data["groups"] = new_groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoadSave::compareVersionStrings(version, "0.3.7") < 0) {
|
||||||
|
json json_groups = data["groups"];
|
||||||
|
json new_groups;
|
||||||
|
for (json json_group : json_groups) {
|
||||||
|
json json_components = json_group["components"];
|
||||||
|
json new_components;
|
||||||
|
for (json json_component : json_components) {
|
||||||
|
std::string type = json_component["type"];
|
||||||
|
if (type == "Audio File Source")
|
||||||
|
LoadSave::convertBufferToPcm(json_component, "audio_file");
|
||||||
|
|
||||||
|
new_components.push_back(json_component);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_group["components"] = new_components;
|
||||||
|
new_groups.push_back(json_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
data["groups"] = new_groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoadSave::compareVersionStrings(version, "0.3.8") < 0)
|
||||||
|
data["remove_all_dc"] = false;
|
||||||
|
|
||||||
|
if (LoadSave::compareVersionStrings(version, "0.3.9") < 0 && LoadSave::compareVersionStrings(version, "0.3.7") >= 0) {
|
||||||
|
json json_groups = data["groups"];
|
||||||
|
json new_groups;
|
||||||
|
for (json json_group : json_groups) {
|
||||||
|
json json_components = json_group["components"];
|
||||||
|
json new_components;
|
||||||
|
for (json json_component : json_components) {
|
||||||
|
std::string type = json_component["type"];
|
||||||
|
if (type == "Wave Source" || type == "Shepard Tone Source") {
|
||||||
|
json old_keyframes = json_component["keyframes"];
|
||||||
|
json new_keyframes;
|
||||||
|
for (json json_keyframe : old_keyframes) {
|
||||||
|
LoadSave::convertPcmToFloatBuffer(json_keyframe, "wave_data");
|
||||||
|
new_keyframes.push_back(json_keyframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_component["keyframes"] = new_keyframes;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_components.push_back(json_component);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_group["components"] = new_components;
|
||||||
|
new_groups.push_back(json_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
data["groups"] = new_groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoadSave::compareVersionStrings(version, "0.4.7") < 0)
|
||||||
|
data["full_normalize"] = false;
|
||||||
|
|
||||||
|
if (LoadSave::compareVersionStrings(version, "0.7.7") < 0) {
|
||||||
|
LineGenerator line_converter;
|
||||||
|
|
||||||
|
json json_groups = data["groups"];
|
||||||
|
json new_groups;
|
||||||
|
for (json json_group : json_groups) {
|
||||||
|
json json_components = json_group["components"];
|
||||||
|
json new_components;
|
||||||
|
for (json json_component : json_components) {
|
||||||
|
std::string type = json_component["type"];
|
||||||
|
if (type == "Line Source") {
|
||||||
|
json old_keyframes = json_component["keyframes"];
|
||||||
|
int num_points = json_component["num_points"];
|
||||||
|
json_component["num_points"] = num_points + 2;
|
||||||
|
line_converter.setNumPoints(num_points + 2);
|
||||||
|
|
||||||
|
json new_keyframes;
|
||||||
|
for (json json_keyframe : old_keyframes) {
|
||||||
|
json point_data = json_keyframe["points"];
|
||||||
|
json power_data = json_keyframe["powers"];
|
||||||
|
for (int i = 0; i < num_points; ++i) {
|
||||||
|
float x = point_data[2 * i];
|
||||||
|
float y = point_data[2 * i + 1];
|
||||||
|
line_converter.setPoint(i + 1, { x, y * 0.5f + 0.5f });
|
||||||
|
line_converter.setPower(i + 1, power_data[i]);
|
||||||
|
}
|
||||||
|
float start_x = point_data[0];
|
||||||
|
float start_y = point_data[1];
|
||||||
|
float end_x = point_data[2 * (num_points - 1)];
|
||||||
|
float end_y = point_data[2 * (num_points - 1) + 1];
|
||||||
|
|
||||||
|
float range_x = start_x - end_x + 1.0f;
|
||||||
|
float y = end_y;
|
||||||
|
if (range_x < 0.001f)
|
||||||
|
y = 0.5f * (start_y + end_y);
|
||||||
|
else {
|
||||||
|
float t = (1.0f - end_x) / range_x;
|
||||||
|
y = vital::utils::interpolate(end_y, start_y, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
line_converter.setPoint(0, { 0.0f, y * 0.5f + 0.5f });
|
||||||
|
line_converter.setPoint(num_points + 1, { 1.0f, y * 0.5f + 0.5f });
|
||||||
|
line_converter.setPower(0, 0.0f);
|
||||||
|
line_converter.setPower(num_points + 1, 0.0f);
|
||||||
|
|
||||||
|
json_keyframe["line"] = line_converter.stateToJson();
|
||||||
|
new_keyframes.push_back(json_keyframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_component["keyframes"] = new_keyframes;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_components.push_back(json_component);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_group["components"] = new_components;
|
||||||
|
new_groups.push_back(json_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
data["groups"] = new_groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
json WavetableCreator::stateToJson() {
|
||||||
|
json json_groups;
|
||||||
|
for (auto& group : groups_)
|
||||||
|
json_groups.push_back(group->stateToJson());
|
||||||
|
|
||||||
|
return {
|
||||||
|
{ "groups", json_groups },
|
||||||
|
{ "name", wavetable_->getName() },
|
||||||
|
{ "author", wavetable_->getAuthor() },
|
||||||
|
{ "version", ProjectInfo::versionString },
|
||||||
|
{ "remove_all_dc", remove_all_dc_ },
|
||||||
|
{ "full_normalize", full_normalize_ },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableCreator::jsonToState(json data) {
|
||||||
|
if (LineGenerator::isValidJson(data)) {
|
||||||
|
LineGenerator generator(vital::WaveFrame::kWaveformSize);
|
||||||
|
generator.jsonToState(data);
|
||||||
|
initFromLineGenerator(&generator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear();
|
||||||
|
data = updateJson(data);
|
||||||
|
|
||||||
|
std::string name = "";
|
||||||
|
if (data.count("name"))
|
||||||
|
name = data["name"].get<std::string>();
|
||||||
|
wavetable_->setName(name);
|
||||||
|
|
||||||
|
std::string author = "";
|
||||||
|
if (data.count("author"))
|
||||||
|
author = data["author"].get<std::string>();
|
||||||
|
wavetable_->setAuthor(author);
|
||||||
|
|
||||||
|
if (data.count("remove_all_dc"))
|
||||||
|
remove_all_dc_ = data["remove_all_dc"];
|
||||||
|
if (data.count("full_normalize"))
|
||||||
|
full_normalize_ = data["full_normalize"];
|
||||||
|
else
|
||||||
|
full_normalize_ = false;
|
||||||
|
|
||||||
|
json json_groups = data["groups"];
|
||||||
|
for (const json& json_group : json_groups) {
|
||||||
|
WavetableGroup* new_group = new WavetableGroup();
|
||||||
|
new_group->jsonToState(json_group);
|
||||||
|
addGroup(new_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
}
|
||||||
96
src/common/wavetable/wavetable_creator.h
Normal file
96
src/common/wavetable/wavetable_creator.h
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "wavetable_group.h"
|
||||||
|
#include "file_source.h"
|
||||||
|
#include "json/json.h"
|
||||||
|
#include "wavetable.h"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
class LineGenerator;
|
||||||
|
|
||||||
|
class WavetableCreator {
|
||||||
|
public:
|
||||||
|
enum AudioFileLoadStyle {
|
||||||
|
kNone,
|
||||||
|
kWavetableSplice,
|
||||||
|
kVocoded,
|
||||||
|
kTtwt,
|
||||||
|
kPitched,
|
||||||
|
kNumDragLoadStyles
|
||||||
|
};
|
||||||
|
|
||||||
|
WavetableCreator(vital::Wavetable* wavetable) : wavetable_(wavetable),
|
||||||
|
full_normalize_(true), remove_all_dc_(true) { }
|
||||||
|
|
||||||
|
int getGroupIndex(WavetableGroup* group);
|
||||||
|
void addGroup(WavetableGroup* group) { groups_.push_back(std::unique_ptr<WavetableGroup>(group)); }
|
||||||
|
void removeGroup(int index);
|
||||||
|
void moveUp(int index);
|
||||||
|
void moveDown(int index);
|
||||||
|
|
||||||
|
int numGroups() const { return static_cast<int>(groups_.size()); }
|
||||||
|
WavetableGroup* getGroup(int index) const { return groups_[index].get(); }
|
||||||
|
float render(int position);
|
||||||
|
void render();
|
||||||
|
void postRender(float max_span);
|
||||||
|
void renderToBuffer(float* buffer, int num_frames, int frame_size);
|
||||||
|
void init();
|
||||||
|
void clear();
|
||||||
|
void loadDefaultCreator();
|
||||||
|
|
||||||
|
void initPredefinedWaves();
|
||||||
|
void initFromAudioFile(const float* audio_buffer, int num_samples, int sample_rate,
|
||||||
|
AudioFileLoadStyle load_style, FileSource::FadeStyle fade_style);
|
||||||
|
|
||||||
|
void setName(const std::string& name) { wavetable_->setName(name); }
|
||||||
|
void setAuthor(const std::string& author) { wavetable_->setAuthor(author); }
|
||||||
|
void setFileLoaded(const std::string& path) { last_file_loaded_ = path; }
|
||||||
|
std::string getName() const { return wavetable_->getName(); }
|
||||||
|
std::string getAuthor() const { return wavetable_->getAuthor(); }
|
||||||
|
std::string getLastFileLoaded() { return last_file_loaded_; }
|
||||||
|
|
||||||
|
static bool isValidJson(json data);
|
||||||
|
json updateJson(json data);
|
||||||
|
json stateToJson();
|
||||||
|
void jsonToState(json data);
|
||||||
|
|
||||||
|
vital::Wavetable* getWavetable() { return wavetable_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void initFromSplicedAudioFile(const float* audio_buffer, int num_samples, int sample_rate,
|
||||||
|
FileSource::FadeStyle fade_style);
|
||||||
|
void initFromVocodedAudioFile(const float* audio_buffer, int num_samples, int sample_rate, bool ttwt);
|
||||||
|
void initFromPitchedAudioFile(const float* audio_buffer, int num_samples, int sample_rate);
|
||||||
|
void initFromLineGenerator(LineGenerator* line_generator);
|
||||||
|
|
||||||
|
vital::WaveFrame compute_frame_combine_;
|
||||||
|
vital::WaveFrame compute_frame_;
|
||||||
|
std::vector<std::unique_ptr<WavetableGroup>> groups_;
|
||||||
|
|
||||||
|
std::string last_file_loaded_;
|
||||||
|
vital::Wavetable* wavetable_;
|
||||||
|
bool full_normalize_;
|
||||||
|
bool remove_all_dc_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableCreator)
|
||||||
|
};
|
||||||
|
|
||||||
132
src/common/wavetable/wavetable_group.cpp
Normal file
132
src/common/wavetable/wavetable_group.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wavetable_group.h"
|
||||||
|
#include "synth_constants.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "wave_source.h"
|
||||||
|
#include "wavetable.h"
|
||||||
|
|
||||||
|
int WavetableGroup::getComponentIndex(WavetableComponent* component) {
|
||||||
|
for (int i = 0; i < components_.size(); ++i) {
|
||||||
|
if (components_[i].get() == component)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableGroup::moveUp(int index) {
|
||||||
|
if (index <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
components_[index].swap(components_[index - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableGroup::moveDown(int index) {
|
||||||
|
if (index < 0 || index >= components_.size() - 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
components_[index].swap(components_[index + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableGroup::removeComponent(int index) {
|
||||||
|
if (index < 0 || index >= components_.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::unique_ptr<WavetableComponent> component = std::move(components_[index]);
|
||||||
|
components_.erase(components_.begin() + index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableGroup::reset() {
|
||||||
|
components_.clear();
|
||||||
|
loadDefaultGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableGroup::prerender() {
|
||||||
|
for (auto& component : components_)
|
||||||
|
component->prerender();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WavetableGroup::isShepardTone() {
|
||||||
|
for (auto& component : components_) {
|
||||||
|
if (component->getType() != WavetableComponentFactory::kShepardToneSource)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableGroup::render(vital::WaveFrame* wave_frame, float position) const {
|
||||||
|
wave_frame->index = position;
|
||||||
|
|
||||||
|
for (auto& component : components_)
|
||||||
|
component->render(wave_frame, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableGroup::renderTo(vital::Wavetable* wavetable) {
|
||||||
|
for (int i = 0; i < vital::kNumOscillatorWaveFrames; ++i) {
|
||||||
|
compute_frame_.index = i;
|
||||||
|
|
||||||
|
for (auto& component : components_)
|
||||||
|
component->render(&compute_frame_, i);
|
||||||
|
|
||||||
|
wavetable->loadWaveFrame(&compute_frame_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableGroup::loadDefaultGroup() {
|
||||||
|
WaveSource* wave_source = new WaveSource();
|
||||||
|
wave_source->insertNewKeyframe(0);
|
||||||
|
vital::WaveFrame* wave_frame = wave_source->getWaveFrame(0);
|
||||||
|
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
|
||||||
|
float t = i / (vital::WaveFrame::kWaveformSize - 1.0f);
|
||||||
|
int half_shift = (i + vital::WaveFrame::kWaveformSize / 2) % vital::WaveFrame::kWaveformSize;
|
||||||
|
wave_frame->time_domain[half_shift] = 1.0f - 2.0f * t;
|
||||||
|
}
|
||||||
|
wave_frame->toFrequencyDomain();
|
||||||
|
|
||||||
|
addComponent(wave_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
int WavetableGroup::getLastKeyframePosition() {
|
||||||
|
int last_position = 0;
|
||||||
|
for (auto& component : components_)
|
||||||
|
last_position = std::max(last_position, component->getLastKeyframePosition());
|
||||||
|
|
||||||
|
return last_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
json WavetableGroup::stateToJson() {
|
||||||
|
json json_components;
|
||||||
|
for (auto& component : components_) {
|
||||||
|
json json_component = component->stateToJson();
|
||||||
|
json_components.push_back(json_component);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { { "components", json_components } };
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableGroup::jsonToState(json data) {
|
||||||
|
components_.clear();
|
||||||
|
|
||||||
|
json json_components = data["components"];
|
||||||
|
for (json json_component : json_components) {
|
||||||
|
std::string type = json_component["type"];
|
||||||
|
WavetableComponent* component = WavetableComponentFactory::createComponent(type);
|
||||||
|
component->jsonToState(json_component);
|
||||||
|
addComponent(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/common/wavetable/wavetable_group.h
Normal file
58
src/common/wavetable/wavetable_group.h
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "wave_frame.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
class Wavetable;
|
||||||
|
} // namespace vital
|
||||||
|
|
||||||
|
class WavetableGroup {
|
||||||
|
public:
|
||||||
|
WavetableGroup() { }
|
||||||
|
|
||||||
|
int getComponentIndex(WavetableComponent* component);
|
||||||
|
void addComponent(WavetableComponent* component) {
|
||||||
|
components_.push_back(std::unique_ptr< WavetableComponent>(component));
|
||||||
|
}
|
||||||
|
void removeComponent(int index);
|
||||||
|
void moveUp(int index);
|
||||||
|
void moveDown(int index);
|
||||||
|
void reset();
|
||||||
|
void prerender();
|
||||||
|
|
||||||
|
int numComponents() const { return static_cast<int>(components_.size()); }
|
||||||
|
WavetableComponent* getComponent(int index) const { return components_[index].get(); }
|
||||||
|
bool isShepardTone();
|
||||||
|
void render(vital::WaveFrame* wave_frame, float position) const;
|
||||||
|
void renderTo(vital::Wavetable* wavetable);
|
||||||
|
void loadDefaultGroup();
|
||||||
|
int getLastKeyframePosition();
|
||||||
|
|
||||||
|
json stateToJson();
|
||||||
|
void jsonToState(json data);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
vital::WaveFrame compute_frame_;
|
||||||
|
std::vector<std::unique_ptr<WavetableComponent>> components_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableGroup)
|
||||||
|
};
|
||||||
|
|
||||||
51
src/common/wavetable/wavetable_keyframe.cpp
Normal file
51
src/common/wavetable/wavetable_keyframe.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wavetable_keyframe.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
#include "wavetable_component.h"
|
||||||
|
|
||||||
|
float WavetableKeyframe::linearTween(float point_from, float point_to, float t) {
|
||||||
|
return vital::utils::interpolate(point_from, point_to, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float WavetableKeyframe::cubicTween(float point_prev, float point_from, float point_to, float point_next,
|
||||||
|
float range_prev, float range, float range_next, float t) {
|
||||||
|
float slope_from = 0.0f;
|
||||||
|
float slope_to = 0.0f;
|
||||||
|
if (range_prev > 0.0f)
|
||||||
|
slope_from = (point_to - point_prev) / (1.0f + range_prev / range);
|
||||||
|
if (range_next > 0.0f)
|
||||||
|
slope_to = (point_next - point_from) / (1.0f + range_next / range);
|
||||||
|
float delta = point_to - point_from;
|
||||||
|
|
||||||
|
float movement = linearTween(point_from, point_to, t);
|
||||||
|
float smooth = t * (1.0f - t) * ((1.0f - t) * (slope_from - delta) + t * (delta - slope_to));
|
||||||
|
return movement + smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WavetableKeyframe::index() {
|
||||||
|
return owner()->indexOf(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
json WavetableKeyframe::stateToJson() {
|
||||||
|
return { { "position", position_ } };
|
||||||
|
}
|
||||||
|
|
||||||
|
void WavetableKeyframe::jsonToState(json data) {
|
||||||
|
position_ = data["position"];
|
||||||
|
}
|
||||||
68
src/common/wavetable/wavetable_keyframe.h
Normal file
68
src/common/wavetable/wavetable_keyframe.h
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "json/json.h"
|
||||||
|
#include "synth_constants.h"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
class WavetableComponent;
|
||||||
|
|
||||||
|
namespace vital {
|
||||||
|
class WaveFrame;
|
||||||
|
} // namespace vital
|
||||||
|
|
||||||
|
class WavetableKeyframe {
|
||||||
|
public:
|
||||||
|
static float linearTween(float point_from, float point_to, float t);
|
||||||
|
static float cubicTween(float point_prev, float point_from, float point_to, float point_next,
|
||||||
|
float range_prev, float range, float range_next, float t);
|
||||||
|
|
||||||
|
WavetableKeyframe() : position_(0), owner_(nullptr) { }
|
||||||
|
virtual ~WavetableKeyframe() { }
|
||||||
|
|
||||||
|
int index();
|
||||||
|
int position() const { return position_; }
|
||||||
|
void setPosition(int position) {
|
||||||
|
VITAL_ASSERT(position >= 0 && position < vital::kNumOscillatorWaveFrames);
|
||||||
|
position_ = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void copy(const WavetableKeyframe* keyframe) = 0;
|
||||||
|
virtual void interpolate(const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe, float t) = 0;
|
||||||
|
virtual void smoothInterpolate(const WavetableKeyframe* prev_keyframe,
|
||||||
|
const WavetableKeyframe* from_keyframe,
|
||||||
|
const WavetableKeyframe* to_keyframe,
|
||||||
|
const WavetableKeyframe* next_keyframe, float t) { }
|
||||||
|
|
||||||
|
virtual void render(vital::WaveFrame* wave_frame) = 0;
|
||||||
|
virtual json stateToJson();
|
||||||
|
virtual void jsonToState(json data);
|
||||||
|
|
||||||
|
WavetableComponent* owner() { return owner_; }
|
||||||
|
void setOwner(WavetableComponent* owner) { owner_ = owner; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int position_;
|
||||||
|
WavetableComponent* owner_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableKeyframe)
|
||||||
|
};
|
||||||
|
|
||||||
143
src/headless/main.cpp
Normal file
143
src/headless/main.cpp
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "load_save.h"
|
||||||
|
#include "tuning.h"
|
||||||
|
#include "synth_base.h"
|
||||||
|
|
||||||
|
String getArgumentValue(int argc, const char* argv[], const String& flag, const String& full_flag) {
|
||||||
|
for (int i = 0; i < argc - 1; ++i) {
|
||||||
|
std::string arg = argv[i];
|
||||||
|
if (arg == flag || arg == full_flag)
|
||||||
|
return argv[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasFlag(int argc, const char* argv[], const String& flag, const String& full_flag) {
|
||||||
|
for (int i = 0; i < argc - 1; ++i) {
|
||||||
|
std::string arg = argv[i];
|
||||||
|
if (arg == flag || arg == full_flag)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getRenderLength(int argc, const char* argv[]) {
|
||||||
|
static constexpr float kDefaultRenderLength = 5.0f;
|
||||||
|
static constexpr float kMaxRenderLength = 15.0f;
|
||||||
|
|
||||||
|
String string_length = getArgumentValue(argc, argv, "-l", "--length");
|
||||||
|
float length = kDefaultRenderLength;
|
||||||
|
if (string_length.isEmpty())
|
||||||
|
return kDefaultRenderLength;
|
||||||
|
|
||||||
|
float float_val = string_length.getFloatValue();
|
||||||
|
if (float_val > 0.0f)
|
||||||
|
length = std::min(float_val, kMaxRenderLength);
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> getRenderMidiNotes(int argc, const char* argv[]) {
|
||||||
|
static constexpr int kDefaultMidiNote = 48;
|
||||||
|
|
||||||
|
String string_midi = getArgumentValue(argc, argv, "-m", "--midi");
|
||||||
|
std::vector<int> midi_notes;
|
||||||
|
if (!string_midi.isEmpty()) {
|
||||||
|
StringArray midi_tokens;
|
||||||
|
midi_tokens.addTokens(string_midi, ",", "");
|
||||||
|
|
||||||
|
for (const String& midi_token : midi_tokens) {
|
||||||
|
int midi = Tuning::noteToMidiKey(midi_token);
|
||||||
|
if (midi >= 0)
|
||||||
|
midi_notes.push_back(midi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (midi_notes.empty())
|
||||||
|
midi_notes.push_back(kDefaultMidiNote);
|
||||||
|
|
||||||
|
return midi_notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getRenderBpm(int argc, const char* argv[]) {
|
||||||
|
static constexpr float kDefaultBpm = 120.0f;
|
||||||
|
static constexpr float kMinBpm = 5.0f;
|
||||||
|
static constexpr float kMaxBpm = 900.0f;
|
||||||
|
|
||||||
|
String string_length = getArgumentValue(argc, argv, "-b", "--bpm");
|
||||||
|
float bpm = kDefaultBpm;
|
||||||
|
if (string_length.isEmpty())
|
||||||
|
return kDefaultBpm;
|
||||||
|
|
||||||
|
bpm = std::min(string_length.getFloatValue(), kMaxBpm);
|
||||||
|
return std::max(bpm, kMinBpm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void doRenderToFile(HeadlessSynth& headless_synth, int argc, const char* argv[]) {
|
||||||
|
String string_output_file = getArgumentValue(argc, argv, "-o", "--output");
|
||||||
|
bool render_images = hasFlag(argc, argv, "-i", "--render-images");
|
||||||
|
|
||||||
|
if (string_output_file.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!string_output_file.startsWith("/"))
|
||||||
|
string_output_file = "./" + string_output_file;
|
||||||
|
|
||||||
|
File output_file(string_output_file);
|
||||||
|
if (!output_file.hasWriteAccess()) {
|
||||||
|
std::cout << "Error: Don't have permission to write output file." << newLine;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float length = getRenderLength(argc, argv);
|
||||||
|
float bpm = getRenderBpm(argc, argv);
|
||||||
|
std::vector<int> midi_notes = getRenderMidiNotes(argc, argv);
|
||||||
|
|
||||||
|
headless_synth.renderAudioToFile(output_file, length, bpm, midi_notes, render_images);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadFromCommandLine(HeadlessSynth& synth, const String& command_line) {
|
||||||
|
String file_path = command_line;
|
||||||
|
if (file_path[0] == '"' && file_path[file_path.length() - 1] == '"')
|
||||||
|
file_path = command_line.substring(1, command_line.length() - 1);
|
||||||
|
File file = File::getCurrentWorkingDirectory().getChildFile(file_path);
|
||||||
|
if (!file.exists())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string error;
|
||||||
|
synth.loadFromFile(file, error);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[]) {
|
||||||
|
HeadlessSynth headless_synth;
|
||||||
|
|
||||||
|
bool last_arg_was_option = false;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
std::string arg = argv[i];
|
||||||
|
if (arg != "" && arg[0] != '-' && !last_arg_was_option && loadFromCommandLine(headless_synth, arg))
|
||||||
|
break;
|
||||||
|
|
||||||
|
last_arg_was_option = arg[0] == '-' && arg != "--headless";
|
||||||
|
}
|
||||||
|
|
||||||
|
doRenderToFile(headless_synth, argc, argv);
|
||||||
|
}
|
||||||
74
src/interface/editor_components/audio_file_drop_source.h
Normal file
74
src/interface/editor_components/audio_file_drop_source.h
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
class AudioFileDropSource : public FileDragAndDropTarget {
|
||||||
|
public:
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
virtual ~Listener() { }
|
||||||
|
virtual void audioFileLoaded(const File& file) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioFileDropSource() {
|
||||||
|
format_manager_.registerBasicFormats();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInterestedInFileDrag(const StringArray& files) override {
|
||||||
|
if (files.size() != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String file = files[0];
|
||||||
|
StringArray wildcards;
|
||||||
|
wildcards.addTokens(getExtensions(), ";", "\"");
|
||||||
|
for (const String& wildcard : wildcards) {
|
||||||
|
if (file.matchesWildcard(wildcard, true))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void filesDropped(const StringArray& files, int x, int y) override {
|
||||||
|
if (files.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
File file(files[0]);
|
||||||
|
audioFileLoaded(file);
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->audioFileLoaded(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void audioFileLoaded(const File& file) = 0;
|
||||||
|
void addListener(Listener* listener) { listeners_.push_back(listener); }
|
||||||
|
|
||||||
|
String getExtensions() { return format_manager_.getWildcardForAllFormats(); }
|
||||||
|
|
||||||
|
AudioFormatManager& formatManager() { return format_manager_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioFormatManager format_manager_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Listener*> listeners_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioFileDropSource)
|
||||||
|
};
|
||||||
|
|
||||||
236
src/interface/editor_components/bar_renderer.cpp
Normal file
236
src/interface/editor_components/bar_renderer.cpp
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bar_renderer.h"
|
||||||
|
|
||||||
|
#include "skin.h"
|
||||||
|
#include "shaders.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
BarRenderer::BarRenderer(int num_points, bool vertical) :
|
||||||
|
shader_(nullptr), vertical_(vertical), additive_blending_(true), display_scale_(1.0f), power_scale_(false),
|
||||||
|
square_scale_(false), dirty_(false), num_points_(num_points), total_points_(num_points) {
|
||||||
|
addRoundedCorners();
|
||||||
|
|
||||||
|
scale_ = 1.0f;
|
||||||
|
offset_ = 0.0f;
|
||||||
|
bar_data_ = std::make_unique<float[]>(kFloatsPerBar * total_points_);
|
||||||
|
bar_indices_ = std::make_unique<int[]>(kTriangleIndicesPerBar * total_points_);
|
||||||
|
bar_corner_data_ = std::make_unique<float[]>(kCornerFloatsPerBar * total_points_);
|
||||||
|
bar_buffer_ = 0;
|
||||||
|
bar_corner_buffer_ = 0;
|
||||||
|
bar_indices_buffer_ = 0;
|
||||||
|
bar_width_ = 1.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < total_points_; ++i) {
|
||||||
|
float t = i / (total_points_ * 1.0f);
|
||||||
|
int index = i * kFloatsPerBar;
|
||||||
|
|
||||||
|
for (int v = 0; v < kVerticesPerBar; ++v) {
|
||||||
|
int vertex_index = v * kFloatsPerVertex + index;
|
||||||
|
bar_data_[vertex_index] = 2.0f * t - 1.0f;
|
||||||
|
bar_data_[vertex_index + 1] = -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bar_index = i * kTriangleIndicesPerBar;
|
||||||
|
int bar_vertex_index = i * kVerticesPerBar;
|
||||||
|
bar_indices_[bar_index] = bar_vertex_index;
|
||||||
|
bar_indices_[bar_index + 1] = bar_vertex_index + 1;
|
||||||
|
bar_indices_[bar_index + 2] = bar_vertex_index + 2;
|
||||||
|
bar_indices_[bar_index + 3] = bar_vertex_index + 1;
|
||||||
|
bar_indices_[bar_index + 4] = bar_vertex_index + 2;
|
||||||
|
bar_indices_[bar_index + 5] = bar_vertex_index + 3;
|
||||||
|
|
||||||
|
int corner_index = i * kCornerFloatsPerBar;
|
||||||
|
bar_corner_data_[corner_index] = 0.0f;
|
||||||
|
bar_corner_data_[corner_index + 1] = 1.0f;
|
||||||
|
bar_corner_data_[corner_index + 2] = 1.0f;
|
||||||
|
bar_corner_data_[corner_index + 3] = 1.0f;
|
||||||
|
bar_corner_data_[corner_index + 4] = 0.0f;
|
||||||
|
bar_corner_data_[corner_index + 5] = 0.0f;
|
||||||
|
bar_corner_data_[corner_index + 6] = 1.0f;
|
||||||
|
bar_corner_data_[corner_index + 7] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BarRenderer::~BarRenderer() { }
|
||||||
|
|
||||||
|
void BarRenderer::init(OpenGlWrapper& open_gl) {
|
||||||
|
OpenGlComponent::init(open_gl);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glGenBuffers(1, &bar_buffer_);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_buffer_);
|
||||||
|
|
||||||
|
GLsizeiptr vert_size = static_cast<GLsizeiptr>(kFloatsPerBar * total_points_ * sizeof(float));
|
||||||
|
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, bar_data_.get(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glGenBuffers(1, &bar_corner_buffer_);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_corner_buffer_);
|
||||||
|
|
||||||
|
GLsizeiptr corner_size = static_cast<GLsizeiptr>(kCornerFloatsPerBar * total_points_ * sizeof(float));
|
||||||
|
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, corner_size, bar_corner_data_.get(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glGenBuffers(1, &bar_indices_buffer_);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bar_indices_buffer_);
|
||||||
|
|
||||||
|
GLsizeiptr bar_size = static_cast<GLsizeiptr>(kTriangleIndicesPerBar * total_points_ * sizeof(int));
|
||||||
|
open_gl.context.extensions.glBufferData(GL_ELEMENT_ARRAY_BUFFER, bar_size, bar_indices_.get(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
if (vertical_)
|
||||||
|
shader_ = open_gl.shaders->getShaderProgram(Shaders::kBarVerticalVertex, Shaders::kBarFragment);
|
||||||
|
else
|
||||||
|
shader_ = open_gl.shaders->getShaderProgram(Shaders::kBarHorizontalVertex, Shaders::kBarFragment);
|
||||||
|
|
||||||
|
shader_->use();
|
||||||
|
color_uniform_ = getUniform(open_gl, *shader_, "color");
|
||||||
|
dimensions_uniform_ = getUniform(open_gl, *shader_, "dimensions");
|
||||||
|
offset_uniform_ = getUniform(open_gl, *shader_, "offset");
|
||||||
|
scale_uniform_ = getUniform(open_gl, *shader_, "scale");
|
||||||
|
width_percent_uniform_ = getUniform(open_gl, *shader_, "width_percent");
|
||||||
|
position_ = getAttribute(open_gl, *shader_, "position");
|
||||||
|
corner_ = getAttribute(open_gl, *shader_, "corner");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarRenderer::drawBars(OpenGlWrapper& open_gl) {
|
||||||
|
if (!setViewPort(open_gl))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (shader_ == nullptr)
|
||||||
|
init(open_gl);
|
||||||
|
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glEnable(GL_SCISSOR_TEST);
|
||||||
|
if (additive_blending_)
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
||||||
|
else
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
if (dirty_) {
|
||||||
|
dirty_ = false;
|
||||||
|
setBarSizes();
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_buffer_);
|
||||||
|
|
||||||
|
GLsizeiptr vert_size = static_cast<GLsizeiptr>(kFloatsPerBar * total_points_ * sizeof(float));
|
||||||
|
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, bar_data_.get(), GL_STATIC_DRAW);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
shader_->use();
|
||||||
|
|
||||||
|
color_uniform_->set(color_.getFloatRed(), color_.getFloatGreen(),
|
||||||
|
color_.getFloatBlue(), color_.getFloatAlpha());
|
||||||
|
dimensions_uniform_->set(getWidth(), getHeight());
|
||||||
|
offset_uniform_->set(offset_);
|
||||||
|
scale_uniform_->set(scale_);
|
||||||
|
float min_width = 4.0f / getWidth();
|
||||||
|
width_percent_uniform_->set(std::max(min_width, bar_width_ * scale_ * 2.0f / num_points_));
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_buffer_);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bar_indices_buffer_);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glVertexAttribPointer(position_->attributeID, kFloatsPerVertex, GL_FLOAT,
|
||||||
|
GL_FALSE, kFloatsPerVertex * sizeof(float), nullptr);
|
||||||
|
open_gl.context.extensions.glEnableVertexAttribArray(position_->attributeID);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_corner_buffer_);
|
||||||
|
open_gl.context.extensions.glVertexAttribPointer(corner_->attributeID, kCornerFloatsPerVertex, GL_FLOAT,
|
||||||
|
GL_FALSE, kCornerFloatsPerVertex * sizeof(float), nullptr);
|
||||||
|
open_gl.context.extensions.glEnableVertexAttribArray(corner_->attributeID);
|
||||||
|
|
||||||
|
glDrawElements(GL_TRIANGLES, kTriangleIndicesPerBar * total_points_, GL_UNSIGNED_INT, nullptr);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glDisableVertexAttribArray(position_->attributeID);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarRenderer::render(OpenGlWrapper& open_gl, bool animate) {
|
||||||
|
drawBars(open_gl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarRenderer::destroy(OpenGlWrapper& open_gl) {
|
||||||
|
OpenGlComponent::destroy(open_gl);
|
||||||
|
|
||||||
|
shader_ = nullptr;
|
||||||
|
position_ = nullptr;
|
||||||
|
corner_ = nullptr;
|
||||||
|
color_uniform_ = nullptr;
|
||||||
|
dimensions_uniform_ = nullptr;
|
||||||
|
offset_uniform_ = nullptr;
|
||||||
|
scale_uniform_ = nullptr;
|
||||||
|
width_percent_uniform_ = nullptr;
|
||||||
|
open_gl.context.extensions.glDeleteBuffers(1, &bar_buffer_);
|
||||||
|
open_gl.context.extensions.glDeleteBuffers(1, &bar_indices_buffer_);
|
||||||
|
|
||||||
|
bar_buffer_ = 0;
|
||||||
|
bar_indices_buffer_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarRenderer::setBarSizes() {
|
||||||
|
if (vertical_) {
|
||||||
|
for (int i = 0; i < total_points_; ++i) {
|
||||||
|
int index = i * kFloatsPerBar;
|
||||||
|
float height = fabsf(yAt(i) - bottomAt(i)) * 0.5f * display_scale_;
|
||||||
|
|
||||||
|
bar_data_[index + 2] = height;
|
||||||
|
bar_data_[index + kFloatsPerVertex + 2] = height;
|
||||||
|
bar_data_[index + 2 * kFloatsPerVertex + 2] = height;
|
||||||
|
bar_data_[index + 3 * kFloatsPerVertex + 2] = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int i = 0; i < total_points_; ++i) {
|
||||||
|
int index = i * kFloatsPerBar;
|
||||||
|
float width = fabsf(xAt(i) - rightAt(i)) * 0.5f * display_scale_;
|
||||||
|
|
||||||
|
bar_data_[index + 2] = width;
|
||||||
|
bar_data_[index + kFloatsPerVertex + 2] = width;
|
||||||
|
bar_data_[index + 2 * kFloatsPerVertex + 2] = width;
|
||||||
|
bar_data_[index + 3 * kFloatsPerVertex + 2] = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarRenderer::setPowerScale(bool power_scale) {
|
||||||
|
if (power_scale == power_scale_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool old_power_scale = power_scale_;
|
||||||
|
for (int i = 1; i < num_points_; ++i) {
|
||||||
|
power_scale_ = old_power_scale;
|
||||||
|
float old_y = scaledYAt(i);
|
||||||
|
power_scale_ = power_scale;
|
||||||
|
setScaledY(i, old_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarRenderer::setSquareScale(bool square_scale) {
|
||||||
|
if (square_scale == square_scale_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool old_square_scale = square_scale_;
|
||||||
|
for (int i = 0; i < num_points_; ++i) {
|
||||||
|
square_scale_ = old_square_scale;
|
||||||
|
float old_y = scaledYAt(i);
|
||||||
|
square_scale_ = square_scale;
|
||||||
|
setScaledY(i, old_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
150
src/interface/editor_components/bar_renderer.h
Normal file
150
src/interface/editor_components/bar_renderer.h
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "open_gl_component.h"
|
||||||
|
#include "open_gl_multi_quad.h"
|
||||||
|
|
||||||
|
class BarRenderer : public OpenGlComponent {
|
||||||
|
public:
|
||||||
|
static constexpr float kScaleConstant = 5.0f;
|
||||||
|
static constexpr int kFloatsPerVertex = 3;
|
||||||
|
static constexpr int kVerticesPerBar = 4;
|
||||||
|
static constexpr int kFloatsPerBar = kVerticesPerBar * kFloatsPerVertex;
|
||||||
|
static constexpr int kTriangleIndicesPerBar = 6;
|
||||||
|
static constexpr int kCornerFloatsPerVertex = 2;
|
||||||
|
static constexpr int kCornerFloatsPerBar = kVerticesPerBar * kCornerFloatsPerVertex;
|
||||||
|
|
||||||
|
BarRenderer(int num_points, bool vertical = true);
|
||||||
|
virtual ~BarRenderer();
|
||||||
|
|
||||||
|
virtual void init(OpenGlWrapper& open_gl) override;
|
||||||
|
virtual void render(OpenGlWrapper& open_gl, bool animate) override;
|
||||||
|
virtual void destroy(OpenGlWrapper& open_gl) override;
|
||||||
|
|
||||||
|
void setColor(const Colour& color) { color_ = color; }
|
||||||
|
void setScale(float scale) { scale_ = scale; }
|
||||||
|
void setOffset(float offset) { offset_ = offset; }
|
||||||
|
void setBarWidth(float bar_width) { bar_width_ = bar_width; }
|
||||||
|
void setNumPoints(int num_points) { num_points_ = num_points; }
|
||||||
|
float getBarWidth() { return bar_width_; }
|
||||||
|
|
||||||
|
inline float xAt(int index) { return bar_data_[kFloatsPerBar * index]; }
|
||||||
|
inline float rightAt(int index) { return bar_data_[kFloatsPerBar * index + kFloatsPerVertex]; }
|
||||||
|
inline float yAt(int index) { return bar_data_[kFloatsPerBar * index + 1]; }
|
||||||
|
inline float bottomAt(int index) { return bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex + 1]; }
|
||||||
|
|
||||||
|
force_inline void setX(int index, float val) {
|
||||||
|
bar_data_[kFloatsPerBar * index] = val;
|
||||||
|
bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex] = val;
|
||||||
|
bar_data_[kFloatsPerBar * index + kFloatsPerVertex] = val;
|
||||||
|
bar_data_[kFloatsPerBar * index + 3 * kFloatsPerVertex] = val;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline void setY(int index, float val) {
|
||||||
|
bar_data_[kFloatsPerBar * index + 1] = val;
|
||||||
|
bar_data_[kFloatsPerBar * index + kFloatsPerVertex + 1] = val;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline void setBottom(int index, float val) {
|
||||||
|
bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex + 1] = val;
|
||||||
|
bar_data_[kFloatsPerBar * index + 3 * kFloatsPerVertex + 1] = val;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void positionBar(int index, float x, float y, float width, float height) {
|
||||||
|
bar_data_[kFloatsPerBar * index] = x;
|
||||||
|
bar_data_[kFloatsPerBar * index + 1] = y;
|
||||||
|
|
||||||
|
bar_data_[kFloatsPerBar * index + kFloatsPerVertex] = x + width;
|
||||||
|
bar_data_[kFloatsPerBar * index + kFloatsPerVertex + 1] = y;
|
||||||
|
|
||||||
|
bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex] = x;
|
||||||
|
bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex + 1] = y + height;
|
||||||
|
|
||||||
|
bar_data_[kFloatsPerBar * index + 3 * kFloatsPerVertex] = x + width;
|
||||||
|
bar_data_[kFloatsPerBar * index + 3 * kFloatsPerVertex + 1] = y + height;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBarSizes();
|
||||||
|
|
||||||
|
void setPowerScale(bool scale);
|
||||||
|
void setSquareScale(bool scale);
|
||||||
|
|
||||||
|
force_inline float scaledYAt(int index) {
|
||||||
|
float value = yAt(index) * 0.5f + 0.5f;
|
||||||
|
if (square_scale_)
|
||||||
|
value *= value;
|
||||||
|
if (power_scale_)
|
||||||
|
value /= std::max(index, 1) / kScaleConstant;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
force_inline void setScaledY(int index, float val) {
|
||||||
|
float value = val;
|
||||||
|
|
||||||
|
if (power_scale_)
|
||||||
|
value *= std::max(index, 1) / kScaleConstant;
|
||||||
|
if (square_scale_)
|
||||||
|
value = sqrtf(value);
|
||||||
|
|
||||||
|
setY(index, 2.0f * value - 1.0f);
|
||||||
|
}
|
||||||
|
force_inline void setAdditiveBlending(bool additive_blending) { additive_blending_ = additive_blending; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void drawBars(OpenGlWrapper& open_gl);
|
||||||
|
|
||||||
|
OpenGLShaderProgram* shader_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> color_uniform_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> dimensions_uniform_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> offset_uniform_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> scale_uniform_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> width_percent_uniform_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Attribute> position_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Attribute> corner_;
|
||||||
|
|
||||||
|
Colour color_;
|
||||||
|
bool vertical_;
|
||||||
|
float scale_;
|
||||||
|
float offset_;
|
||||||
|
float bar_width_;
|
||||||
|
bool additive_blending_;
|
||||||
|
float display_scale_;
|
||||||
|
|
||||||
|
bool power_scale_;
|
||||||
|
bool square_scale_;
|
||||||
|
bool dirty_;
|
||||||
|
int num_points_;
|
||||||
|
int total_points_;
|
||||||
|
std::unique_ptr<float[]> bar_data_;
|
||||||
|
std::unique_ptr<float[]> bar_corner_data_;
|
||||||
|
std::unique_ptr<int[]> bar_indices_;
|
||||||
|
GLuint bar_buffer_;
|
||||||
|
GLuint bar_corner_buffer_;
|
||||||
|
GLuint bar_indices_buffer_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(BarRenderer)
|
||||||
|
};
|
||||||
|
|
||||||
724
src/interface/editor_components/compressor_editor.cpp
Normal file
724
src/interface/editor_components/compressor_editor.cpp
Normal file
|
|
@ -0,0 +1,724 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "compressor_editor.h"
|
||||||
|
|
||||||
|
#include "skin.h"
|
||||||
|
#include "shaders.h"
|
||||||
|
#include "synth_gui_interface.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
float getOpenGlYForDb(float db) {
|
||||||
|
float t = (db - CompressorEditor::kMinDb) / (CompressorEditor::kMaxDb - CompressorEditor::kMinDb);
|
||||||
|
return 2.0f * t - 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::poly_float getOpenGlYForDb(vital::poly_float db) {
|
||||||
|
vital::poly_float t = (db - CompressorEditor::kMinDb) / (CompressorEditor::kMaxDb - CompressorEditor::kMinDb);
|
||||||
|
return vital::utils::clamp(t * 2.0f - 1.0f, -1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::poly_float getOpenGlYForMagnitude(vital::poly_float magnitude) {
|
||||||
|
vital::poly_float db = vital::utils::magnitudeToDb(vital::utils::max(0.0001f, magnitude));
|
||||||
|
return getOpenGlYForDb(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setQuadIfRatioMatch(OpenGlMultiQuad& quads, float ratio, float ratio_match,
|
||||||
|
int index, float x, float y, float w, float h) {
|
||||||
|
if (ratio == ratio_match || (ratio > 0.0f && ratio_match > 0.0f) || (ratio < 0.0f && ratio_match < 0.0f))
|
||||||
|
quads.setQuad(index, x, y, w, h);
|
||||||
|
else
|
||||||
|
quads.setQuad(index, -2.0f, -2.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string formatString(float value, std::string suffix) {
|
||||||
|
static constexpr int kMaxDecimalPlaces = 4;
|
||||||
|
String format = String(value, kMaxDecimalPlaces);
|
||||||
|
|
||||||
|
int display_characters = kMaxDecimalPlaces;
|
||||||
|
if (format[0] == '-')
|
||||||
|
display_characters += 1;
|
||||||
|
|
||||||
|
format = format.substring(0, display_characters);
|
||||||
|
if (format.getLastCharacter() == '.')
|
||||||
|
format = format.removeCharacters(".");
|
||||||
|
|
||||||
|
return format.toStdString() + suffix;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CompressorEditor::CompressorEditor() : hover_quad_(Shaders::kColorFragment),
|
||||||
|
input_dbs_(kNumChannels, Shaders::kColorFragment),
|
||||||
|
output_dbs_(kNumChannels, Shaders::kRoundedRectangleFragment),
|
||||||
|
thresholds_(kNumChannels, Shaders::kColorFragment),
|
||||||
|
ratio_lines_(kTotalRatioLines, Shaders::kFadeSquareFragment) {
|
||||||
|
addRoundedCorners();
|
||||||
|
addAndMakeVisible(hover_quad_);
|
||||||
|
addAndMakeVisible(input_dbs_);
|
||||||
|
addAndMakeVisible(output_dbs_);
|
||||||
|
addAndMakeVisible(thresholds_);
|
||||||
|
addAndMakeVisible(ratio_lines_);
|
||||||
|
|
||||||
|
parent_ = nullptr;
|
||||||
|
section_parent_ = nullptr;
|
||||||
|
hover_ = kNone;
|
||||||
|
animate_ = false;
|
||||||
|
high_band_active_ = true;
|
||||||
|
low_band_active_ = true;
|
||||||
|
size_ratio_ = 1.0f;
|
||||||
|
active_ = true;
|
||||||
|
|
||||||
|
low_upper_threshold_ = kMaxDb;
|
||||||
|
band_upper_threshold_ = kMaxDb;
|
||||||
|
high_upper_threshold_ = kMaxDb;
|
||||||
|
low_lower_threshold_ = kMinDb;
|
||||||
|
band_lower_threshold_ = kMinDb;
|
||||||
|
high_lower_threshold_ = kMinDb;
|
||||||
|
low_upper_ratio_ = 0.0f;
|
||||||
|
band_upper_ratio_ = 0.0f;
|
||||||
|
high_upper_ratio_ = 0.0f;
|
||||||
|
low_lower_ratio_ = 0.0f;
|
||||||
|
band_lower_ratio_ = 0.0f;
|
||||||
|
high_lower_ratio_ = 0.0f;
|
||||||
|
|
||||||
|
low_input_ms_ = nullptr;
|
||||||
|
band_input_ms_ = nullptr;
|
||||||
|
high_input_ms_ = nullptr;
|
||||||
|
|
||||||
|
low_output_ms_ = nullptr;
|
||||||
|
band_output_ms_ = nullptr;
|
||||||
|
high_output_ms_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompressorEditor::~CompressorEditor() { }
|
||||||
|
|
||||||
|
CompressorEditor::DragPoint CompressorEditor::getHoverPoint(const MouseEvent& e) {
|
||||||
|
float low_band_high_position = 3.0f * e.position.x / ((1.0f - kCompressorAreaBuffer) * getWidth());
|
||||||
|
int index = low_band_high_position;
|
||||||
|
float local_position = low_band_high_position - index;
|
||||||
|
if (index >= 3 || index < 0 || local_position < 3.0f * kCompressorAreaBuffer)
|
||||||
|
return kNone;
|
||||||
|
|
||||||
|
if (index == 0 && !low_band_active_)
|
||||||
|
index = 1;
|
||||||
|
if (index == 2 && !high_band_active_)
|
||||||
|
index = 1;
|
||||||
|
|
||||||
|
float upper_threshold_values[] = { low_upper_threshold_, band_upper_threshold_, high_upper_threshold_ };
|
||||||
|
float lower_threshold_values[] = { low_lower_threshold_, band_lower_threshold_, high_lower_threshold_ };
|
||||||
|
DragPoint upper_threshold_points[] = { kLowUpperThreshold, kBandUpperThreshold, kHighUpperThreshold };
|
||||||
|
DragPoint lower_threshold_points[] = { kLowLowerThreshold, kBandLowerThreshold, kHighLowerThreshold };
|
||||||
|
DragPoint upper_ratio_points[] = { kLowUpperRatio, kBandUpperRatio, kHighUpperRatio };
|
||||||
|
DragPoint lower_ratio_points[] = { kLowLowerRatio, kBandLowerRatio, kHighLowerRatio };
|
||||||
|
|
||||||
|
float grab_radius = kGrabRadius * size_ratio_;
|
||||||
|
int upper_handle_y = std::max(grab_radius, getYForDb(upper_threshold_values[index]));
|
||||||
|
int lower_handle_y = std::min(getHeight() - grab_radius, getYForDb(lower_threshold_values[index]));
|
||||||
|
|
||||||
|
float delta_upper = e.position.y - upper_handle_y;
|
||||||
|
float delta_lower = e.position.y - lower_handle_y;
|
||||||
|
if (fabsf(delta_upper) <= grab_radius && fabsf(delta_upper) < fabsf(delta_lower))
|
||||||
|
return upper_threshold_points[index];
|
||||||
|
if (fabsf(delta_lower) <= grab_radius)
|
||||||
|
return lower_threshold_points[index];
|
||||||
|
if (delta_upper < 0.0f)
|
||||||
|
return upper_ratio_points[index];
|
||||||
|
if (delta_lower > 0.0f)
|
||||||
|
return lower_ratio_points[index];
|
||||||
|
return kNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::mouseDown(const MouseEvent& e) {
|
||||||
|
last_mouse_position_ = e.getPosition();
|
||||||
|
mouseDrag(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::mouseDoubleClick(const MouseEvent& e) {
|
||||||
|
if (isRatio(hover_)) {
|
||||||
|
switch (hover_) {
|
||||||
|
case kLowUpperRatio:
|
||||||
|
setLowUpperRatio(0.0f);
|
||||||
|
break;
|
||||||
|
case kBandUpperRatio:
|
||||||
|
setBandUpperRatio(0.0f);
|
||||||
|
break;
|
||||||
|
case kHighUpperRatio:
|
||||||
|
setHighUpperRatio(0.0f);
|
||||||
|
break;
|
||||||
|
case kLowLowerRatio:
|
||||||
|
setLowLowerRatio(0.0f);
|
||||||
|
break;
|
||||||
|
case kBandLowerRatio:
|
||||||
|
setBandLowerRatio(0.0f);
|
||||||
|
break;
|
||||||
|
case kHighLowerRatio:
|
||||||
|
setHighLowerRatio(0.0f);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::mouseMove(const MouseEvent& e) {
|
||||||
|
hover_ = getHoverPoint(e);
|
||||||
|
|
||||||
|
if (hover_ != kNone)
|
||||||
|
setMouseCursor(MouseCursor::BottomEdgeResizeCursor);
|
||||||
|
else
|
||||||
|
setMouseCursor(MouseCursor::NormalCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::mouseDrag(const MouseEvent& e) {
|
||||||
|
if (hover_ == kNone || parent_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float delta = (e.getPosition().y - last_mouse_position_.y) * kMouseMultiplier / getHeight();
|
||||||
|
float delta_db_value = (kMinDb - kMaxDb) * delta;
|
||||||
|
|
||||||
|
last_mouse_position_ = e.getPosition();
|
||||||
|
float delta_ratio = delta * kRatioEditMultiplier;
|
||||||
|
|
||||||
|
if (e.mods.isShiftDown()) {
|
||||||
|
setLowUpperThreshold(low_upper_threshold_ + delta_db_value, false);
|
||||||
|
setBandUpperThreshold(band_upper_threshold_ + delta_db_value, false);
|
||||||
|
setHighUpperThreshold(high_upper_threshold_ + delta_db_value, false);
|
||||||
|
setLowLowerThreshold(low_lower_threshold_ + delta_db_value, false);
|
||||||
|
setBandLowerThreshold(band_lower_threshold_ + delta_db_value, false);
|
||||||
|
setHighLowerThreshold(high_lower_threshold_ + delta_db_value, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (hover_) {
|
||||||
|
case kLowUpperThreshold:
|
||||||
|
setLowUpperThreshold(low_upper_threshold_ + delta_db_value, true);
|
||||||
|
break;
|
||||||
|
case kLowUpperRatio:
|
||||||
|
setLowUpperRatio(low_upper_ratio_ + delta_ratio);
|
||||||
|
break;
|
||||||
|
case kBandUpperThreshold:
|
||||||
|
setBandUpperThreshold(band_upper_threshold_ + delta_db_value, true);
|
||||||
|
break;
|
||||||
|
case kBandUpperRatio:
|
||||||
|
setBandUpperRatio(band_upper_ratio_ + delta_ratio);
|
||||||
|
break;
|
||||||
|
case kHighUpperThreshold:
|
||||||
|
setHighUpperThreshold(high_upper_threshold_ + delta_db_value, true);
|
||||||
|
break;
|
||||||
|
case kHighUpperRatio:
|
||||||
|
setHighUpperRatio(high_upper_ratio_ + delta_ratio);
|
||||||
|
break;
|
||||||
|
case kLowLowerThreshold:
|
||||||
|
setLowLowerThreshold(low_lower_threshold_ + delta_db_value, true);
|
||||||
|
break;
|
||||||
|
case kLowLowerRatio:
|
||||||
|
setLowLowerRatio(low_lower_ratio_ - delta_ratio);
|
||||||
|
break;
|
||||||
|
case kBandLowerThreshold:
|
||||||
|
setBandLowerThreshold(band_lower_threshold_ + delta_db_value, true);
|
||||||
|
break;
|
||||||
|
case kBandLowerRatio:
|
||||||
|
setBandLowerRatio(band_lower_ratio_ - delta_ratio);
|
||||||
|
break;
|
||||||
|
case kHighLowerThreshold:
|
||||||
|
setHighLowerThreshold(high_lower_threshold_ + delta_db_value, true);
|
||||||
|
break;
|
||||||
|
case kHighLowerRatio:
|
||||||
|
setHighLowerRatio(high_lower_ratio_ - delta_ratio);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::mouseUp(const MouseEvent& e) {
|
||||||
|
if (isRatio(hover_))
|
||||||
|
setMouseCursor(MouseCursor::BottomEdgeResizeCursor);
|
||||||
|
|
||||||
|
section_parent_->hidePopupDisplay(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::mouseExit(const MouseEvent& e) {
|
||||||
|
setMouseCursor(MouseCursor::NormalCursor);
|
||||||
|
hover_ = kNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::paintBackground(Graphics& g) {
|
||||||
|
OpenGlComponent::paintBackground(g);
|
||||||
|
|
||||||
|
g.setColour(findColour(Skin::kLightenScreen, true));
|
||||||
|
for (int i = 1; i < kDbLineSections; ++i) {
|
||||||
|
float t = (i * 1.0f) / kDbLineSections;
|
||||||
|
int y = getHeight() * t;
|
||||||
|
g.fillRect(0, y, getWidth(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::resized() {
|
||||||
|
OpenGlComponent::resized();
|
||||||
|
hover_quad_.setBounds(getLocalBounds());
|
||||||
|
input_dbs_.setBounds(getLocalBounds());
|
||||||
|
output_dbs_.setBounds(getLocalBounds());
|
||||||
|
thresholds_.setBounds(getLocalBounds());
|
||||||
|
ratio_lines_.setBounds(getLocalBounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::init(OpenGlWrapper& open_gl) {
|
||||||
|
OpenGlComponent::init(open_gl);
|
||||||
|
hover_quad_.init(open_gl);
|
||||||
|
input_dbs_.init(open_gl);
|
||||||
|
output_dbs_.init(open_gl);
|
||||||
|
thresholds_.init(open_gl);
|
||||||
|
ratio_lines_.init(open_gl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::render(OpenGlWrapper& open_gl, bool animate) {
|
||||||
|
renderCompressor(open_gl, animate);
|
||||||
|
renderCorners(open_gl, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setThresholdPositions(int low_start, int low_end, int band_start, int band_end,
|
||||||
|
int high_start, int high_end, float ratio_match) {
|
||||||
|
thresholds_.setColor(getColorForRatio(ratio_match));
|
||||||
|
float width = getWidth();
|
||||||
|
|
||||||
|
float low_start_x = low_start * 2.0f / width - 1.0f;
|
||||||
|
float low_width = (low_end - low_start) * 2.0f / width;
|
||||||
|
float band_start_x = band_start * 2.0f / width - 1.0f;
|
||||||
|
float band_width = (band_end - band_start) * 2.0f / width;
|
||||||
|
float high_start_x = high_start * 2.0f / width - 1.0f;
|
||||||
|
float high_width = (high_end - high_start) * 2.0f / width;
|
||||||
|
|
||||||
|
setQuadIfRatioMatch(thresholds_, -low_lower_ratio_, ratio_match, 0,
|
||||||
|
low_start_x, -1.0f, low_width, getOpenGlYForDb(low_lower_threshold_) + 1.0f);
|
||||||
|
setQuadIfRatioMatch(thresholds_, low_upper_ratio_, ratio_match, 1,
|
||||||
|
low_start_x, 1.0f, low_width, getOpenGlYForDb(low_upper_threshold_) - 1.0f);
|
||||||
|
setQuadIfRatioMatch(thresholds_, -band_lower_ratio_, ratio_match, 2,
|
||||||
|
band_start_x, -1.0f, band_width, getOpenGlYForDb(band_lower_threshold_) + 1.0f);
|
||||||
|
setQuadIfRatioMatch(thresholds_, band_upper_ratio_, ratio_match, 3,
|
||||||
|
band_start_x, 1.0f, band_width, getOpenGlYForDb(band_upper_threshold_) - 1.0f);
|
||||||
|
setQuadIfRatioMatch(thresholds_, -high_lower_ratio_, ratio_match, 4,
|
||||||
|
high_start_x, -1.0f, high_width, getOpenGlYForDb(high_lower_threshold_) + 1.0f);
|
||||||
|
setQuadIfRatioMatch(thresholds_, high_upper_ratio_, ratio_match, 5,
|
||||||
|
high_start_x, 1.0f, high_width, getOpenGlYForDb(high_upper_threshold_) - 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setRatioLines(int start_index, int start_x, int end_x,
|
||||||
|
float threshold, float ratio, bool upper, bool hover) {
|
||||||
|
|
||||||
|
float db_position = kDbLineSections * (threshold - kMinDb) / (kMaxDb - kMinDb);
|
||||||
|
int db_index = db_position;
|
||||||
|
float db_change = -(kMaxDb - kMinDb) / kDbLineSections;
|
||||||
|
if (upper) {
|
||||||
|
db_change = (kMaxDb - kMinDb) / kDbLineSections;
|
||||||
|
db_index = ceil(db_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
float width = getWidth();
|
||||||
|
float x = start_x * 2.0f / width - 1.0f;
|
||||||
|
float ratio_width = (end_x - start_x) * 2.0f / width;
|
||||||
|
float ratio_height = 4.0f / getHeight();
|
||||||
|
|
||||||
|
float mult = hover ? 5.0f : 2.5f;
|
||||||
|
|
||||||
|
float db = db_index * (kMaxDb - kMinDb) / kDbLineSections + kMinDb;
|
||||||
|
for (int i = 0; i < kRatioDbLines; ++i) {
|
||||||
|
float adjusted_db = getCompressedDb(db, threshold, ratio, threshold, ratio);
|
||||||
|
ratio_lines_.setQuad(start_index + i, x, getOpenGlYForDb(adjusted_db) - ratio_height * 0.5f,
|
||||||
|
ratio_width, ratio_height);
|
||||||
|
ratio_lines_.setShaderValue(start_index + i, (kRatioDbLines - i) * mult / kRatioDbLines);
|
||||||
|
db += db_change;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setRatioLinePositions(int low_start, int low_end, int band_start, int band_end,
|
||||||
|
int high_start, int high_end) {
|
||||||
|
setRatioLines(0, low_start, low_end, low_upper_threshold_, low_upper_ratio_,
|
||||||
|
true, hover_ == kLowUpperRatio);
|
||||||
|
setRatioLines(kRatioDbLines, low_start, low_end, low_lower_threshold_, low_lower_ratio_,
|
||||||
|
false, hover_ == kLowLowerRatio);
|
||||||
|
setRatioLines(2 * kRatioDbLines, band_start, band_end, band_upper_threshold_, band_upper_ratio_,
|
||||||
|
true, hover_ == kBandUpperRatio);
|
||||||
|
setRatioLines(3 * kRatioDbLines, band_start, band_end, band_lower_threshold_, band_lower_ratio_,
|
||||||
|
false, hover_ == kBandLowerRatio);
|
||||||
|
setRatioLines(4 * kRatioDbLines, high_start, high_end, high_upper_threshold_, high_upper_ratio_,
|
||||||
|
true, hover_ == kHighUpperRatio);
|
||||||
|
setRatioLines(5 * kRatioDbLines, high_start, high_end, high_lower_threshold_, high_lower_ratio_,
|
||||||
|
false, hover_ == kHighLowerRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::renderHover(OpenGlWrapper& open_gl, int low_start, int low_end,
|
||||||
|
int band_start, int band_end, int high_start, int high_end) {
|
||||||
|
if (hover_ == kNone)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float width = getWidth();
|
||||||
|
float height = getHeight();
|
||||||
|
float threshold_height = 2.0f / height;
|
||||||
|
|
||||||
|
float low_start_x = low_start * 2.0f / width - 1.0f;
|
||||||
|
float low_width = (low_end - low_start) * 2.0f / width;
|
||||||
|
float band_start_x = band_start * 2.0f / width - 1.0f;
|
||||||
|
float band_width = (band_end - band_start) * 2.0f / width;
|
||||||
|
float high_start_x = high_start * 2.0f / width - 1.0f;
|
||||||
|
float high_width = (high_end - high_start) * 2.0f / width;
|
||||||
|
|
||||||
|
switch (hover_) {
|
||||||
|
case kLowUpperThreshold:
|
||||||
|
hover_quad_.setQuad(0, low_start_x, getOpenGlYForDb(low_upper_threshold_) - 0.5f * threshold_height,
|
||||||
|
low_width, threshold_height);
|
||||||
|
break;
|
||||||
|
case kBandUpperThreshold:
|
||||||
|
hover_quad_.setQuad(0, band_start_x, getOpenGlYForDb(band_upper_threshold_) - 0.5f * threshold_height,
|
||||||
|
band_width, threshold_height);
|
||||||
|
break;
|
||||||
|
case kHighUpperThreshold:
|
||||||
|
hover_quad_.setQuad(0, high_start_x, getOpenGlYForDb(high_upper_threshold_) - 0.5f * threshold_height,
|
||||||
|
high_width, threshold_height);
|
||||||
|
break;
|
||||||
|
case kLowLowerThreshold:
|
||||||
|
hover_quad_.setQuad(0, low_start_x, getOpenGlYForDb(low_lower_threshold_) - 0.5f * threshold_height,
|
||||||
|
low_width, threshold_height);
|
||||||
|
break;
|
||||||
|
case kBandLowerThreshold:
|
||||||
|
hover_quad_.setQuad(0, band_start_x, getOpenGlYForDb(band_lower_threshold_) - 0.5f * threshold_height,
|
||||||
|
band_width, threshold_height);
|
||||||
|
break;
|
||||||
|
case kHighLowerThreshold:
|
||||||
|
hover_quad_.setQuad(0, high_start_x, getOpenGlYForDb(high_lower_threshold_) - 0.5f * threshold_height,
|
||||||
|
high_width, threshold_height);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRatio(hover_))
|
||||||
|
hover_quad_.setColor(findColour(Skin::kLightenScreen, true));
|
||||||
|
else
|
||||||
|
hover_quad_.setColor(findColour(Skin::kWidgetCenterLine, true));
|
||||||
|
|
||||||
|
hover_quad_.render(open_gl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::renderCompressor(OpenGlWrapper& open_gl, bool animate) {
|
||||||
|
static constexpr float kOutputBarHeight = 2.2f;
|
||||||
|
if (low_input_ms_ == nullptr || band_input_ms_ == nullptr || high_input_ms_ == nullptr ||
|
||||||
|
low_output_ms_ == nullptr || band_output_ms_ == nullptr || high_output_ms_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::poly_float low_rms = vital::utils::sqrt(low_input_ms_->value());
|
||||||
|
vital::poly_float low_input_y = getOpenGlYForMagnitude(low_rms);
|
||||||
|
|
||||||
|
vital::poly_float scaled_low_rms = vital::utils::sqrt(low_output_ms_->value());
|
||||||
|
vital::poly_float low_output_y = getOpenGlYForMagnitude(scaled_low_rms);
|
||||||
|
|
||||||
|
vital::poly_float band_rms = vital::utils::sqrt(band_input_ms_->value());
|
||||||
|
vital::poly_float band_input_y = getOpenGlYForMagnitude(band_rms);
|
||||||
|
|
||||||
|
vital::poly_float scaled_band_rms = vital::utils::sqrt(band_output_ms_->value());
|
||||||
|
vital::poly_float band_output_y = getOpenGlYForMagnitude(scaled_band_rms);
|
||||||
|
|
||||||
|
vital::poly_float high_rms = vital::utils::sqrt(high_input_ms_->value());
|
||||||
|
vital::poly_float high_input_y = getOpenGlYForMagnitude(high_rms);
|
||||||
|
|
||||||
|
vital::poly_float scaled_high_rms = vital::utils::sqrt(high_output_ms_->value());
|
||||||
|
vital::poly_float high_output_y = getOpenGlYForMagnitude(scaled_high_rms);
|
||||||
|
|
||||||
|
int width = getWidth();
|
||||||
|
float active_area = 1.0f - 4.0f * kCompressorAreaBuffer;
|
||||||
|
float active_section_width = active_area / kMaxBands;
|
||||||
|
|
||||||
|
int low_start = std::round(kCompressorAreaBuffer * width);
|
||||||
|
int low_end = std::round((kCompressorAreaBuffer + active_section_width) * width);
|
||||||
|
int band_start = std::round((2.0f * kCompressorAreaBuffer + active_section_width) * width);
|
||||||
|
int band_end = width - band_start;
|
||||||
|
int high_start = width - low_end;
|
||||||
|
int high_end = width - low_start;
|
||||||
|
|
||||||
|
if (!low_band_active_) {
|
||||||
|
band_start = low_start;
|
||||||
|
low_start = low_end = -width;
|
||||||
|
}
|
||||||
|
if (!high_band_active_) {
|
||||||
|
band_end = high_end;
|
||||||
|
high_start = high_end = -width;
|
||||||
|
}
|
||||||
|
|
||||||
|
setThresholdPositions(low_start, low_end, band_start, band_end, high_start, high_end, 1.0f);
|
||||||
|
thresholds_.render(open_gl, true);
|
||||||
|
|
||||||
|
setThresholdPositions(low_start, low_end, band_start, band_end, high_start, high_end, 0.0f);
|
||||||
|
thresholds_.render(open_gl, true);
|
||||||
|
|
||||||
|
setThresholdPositions(low_start, low_end, band_start, band_end, high_start, high_end, -1.0f);
|
||||||
|
thresholds_.render(open_gl, true);
|
||||||
|
|
||||||
|
setRatioLinePositions(low_start, low_end, band_start, band_end, high_start, high_end);
|
||||||
|
ratio_lines_.setColor(findColour(Skin::kLightenScreen, true));
|
||||||
|
ratio_lines_.render(open_gl, true);
|
||||||
|
|
||||||
|
renderHover(open_gl, low_start, low_end, band_start, band_end, high_start, high_end);
|
||||||
|
|
||||||
|
int bar_width = kBarWidth * active_section_width * width;
|
||||||
|
int low_middle = (low_start + low_end) * 0.5f;
|
||||||
|
int band_middle = (band_start + band_end) * 0.5f;
|
||||||
|
int high_middle = (high_start + high_end) * 0.5f;
|
||||||
|
|
||||||
|
float gl_bar_width = bar_width * 2.0f / width;
|
||||||
|
float low_left = (low_middle - bar_width) * 2.0f / width - 1.0f;
|
||||||
|
float low_right = (low_middle + 1) * 2.0f / width - 1.0f;
|
||||||
|
float band_left = (band_middle - bar_width) * 2.0f / width - 1.0f;
|
||||||
|
float band_right = (band_middle + 1) * 2.0f / width - 1.0f;
|
||||||
|
float high_left = (high_middle - bar_width) * 2.0f / width - 1.0f;
|
||||||
|
float high_right = (high_middle + 1) * 2.0f / width - 1.0f;
|
||||||
|
output_dbs_.setQuad(0, low_left, low_output_y[0] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
|
||||||
|
output_dbs_.setQuad(1, low_right, low_output_y[1] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
|
||||||
|
output_dbs_.setQuad(2, band_left, band_output_y[0] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
|
||||||
|
output_dbs_.setQuad(3, band_right, band_output_y[1] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
|
||||||
|
output_dbs_.setQuad(4, high_left, high_output_y[0] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
|
||||||
|
output_dbs_.setQuad(5, high_right, high_output_y[1] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
|
||||||
|
|
||||||
|
float input_height = 2.0f / getHeight();
|
||||||
|
input_dbs_.setQuad(0, low_left, low_input_y[0] - 0.5f * input_height, gl_bar_width, input_height);
|
||||||
|
input_dbs_.setQuad(1, low_right, low_input_y[1] - 0.5f * input_height, gl_bar_width, input_height);
|
||||||
|
input_dbs_.setQuad(2, band_left, band_input_y[0] - 0.5f * input_height, gl_bar_width, input_height);
|
||||||
|
input_dbs_.setQuad(3, band_right, band_input_y[1] - 0.5f * input_height, gl_bar_width, input_height);
|
||||||
|
input_dbs_.setQuad(4, high_left, high_input_y[0] - 0.5f * input_height, gl_bar_width, input_height);
|
||||||
|
input_dbs_.setQuad(5, high_right, high_input_y[1] - 0.5f * input_height, gl_bar_width, input_height);
|
||||||
|
|
||||||
|
output_dbs_.setColor(findColour(Skin::kWidgetPrimary1, true));
|
||||||
|
output_dbs_.render(open_gl, animate);
|
||||||
|
|
||||||
|
input_dbs_.setColor(findColour(Skin::kWidgetPrimary2, true));
|
||||||
|
input_dbs_.render(open_gl, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::destroy(OpenGlWrapper& open_gl) {
|
||||||
|
OpenGlComponent::destroy(open_gl);
|
||||||
|
hover_quad_.destroy(open_gl);
|
||||||
|
input_dbs_.destroy(open_gl);
|
||||||
|
output_dbs_.destroy(open_gl);
|
||||||
|
thresholds_.destroy(open_gl);
|
||||||
|
ratio_lines_.destroy(open_gl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setAllValues(vital::control_map& controls) {
|
||||||
|
low_upper_threshold_ = controls["compressor_low_upper_threshold"]->value();
|
||||||
|
band_upper_threshold_ = controls["compressor_band_upper_threshold"]->value();
|
||||||
|
high_upper_threshold_ = controls["compressor_high_upper_threshold"]->value();
|
||||||
|
low_lower_threshold_ = controls["compressor_low_lower_threshold"]->value();
|
||||||
|
band_lower_threshold_ = controls["compressor_band_lower_threshold"]->value();
|
||||||
|
high_lower_threshold_ = controls["compressor_high_lower_threshold"]->value();
|
||||||
|
low_upper_ratio_ = controls["compressor_low_upper_ratio"]->value();
|
||||||
|
band_upper_ratio_ = controls["compressor_band_upper_ratio"]->value();
|
||||||
|
high_upper_ratio_ = controls["compressor_high_upper_ratio"]->value();
|
||||||
|
low_lower_ratio_ = controls["compressor_low_lower_ratio"]->value();
|
||||||
|
band_lower_ratio_ = controls["compressor_band_lower_ratio"]->value();
|
||||||
|
high_lower_ratio_ = controls["compressor_high_lower_ratio"]->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setLowUpperThreshold(float db, bool clamp) {
|
||||||
|
low_upper_threshold_ = db;
|
||||||
|
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
if (clamp)
|
||||||
|
low_upper_threshold_ = db;
|
||||||
|
synth->valueChangedInternal("compressor_low_upper_threshold", db);
|
||||||
|
if (low_upper_threshold_ < low_lower_threshold_ && clamp)
|
||||||
|
setLowLowerThreshold(db, clamp);
|
||||||
|
|
||||||
|
section_parent_->showPopupDisplay(this, formatString(low_upper_threshold_, " dB"), BubbleComponent::below, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setBandUpperThreshold(float db, bool clamp) {
|
||||||
|
band_upper_threshold_ = db;
|
||||||
|
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
if (clamp)
|
||||||
|
band_upper_threshold_ = db;
|
||||||
|
synth->valueChangedInternal("compressor_band_upper_threshold", db);
|
||||||
|
if (band_upper_threshold_ < band_lower_threshold_ && clamp)
|
||||||
|
setBandLowerThreshold(db, clamp);
|
||||||
|
|
||||||
|
section_parent_->showPopupDisplay(this, formatString(band_upper_threshold_, " dB"), BubbleComponent::below, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setHighUpperThreshold(float db, bool clamp) {
|
||||||
|
high_upper_threshold_ = db;
|
||||||
|
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
if (clamp)
|
||||||
|
high_upper_threshold_ = db;
|
||||||
|
synth->valueChangedInternal("compressor_high_upper_threshold", db);
|
||||||
|
if (high_upper_threshold_ < high_lower_threshold_ && clamp)
|
||||||
|
setHighLowerThreshold(db, clamp);
|
||||||
|
|
||||||
|
section_parent_->showPopupDisplay(this, formatString(high_upper_threshold_, " dB"), BubbleComponent::below, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setLowLowerThreshold(float db, bool clamp) {
|
||||||
|
low_lower_threshold_ = db;
|
||||||
|
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
if (clamp)
|
||||||
|
low_lower_threshold_ = db;
|
||||||
|
synth->valueChangedInternal("compressor_low_lower_threshold", db);
|
||||||
|
if (low_lower_threshold_ > low_upper_threshold_ && clamp)
|
||||||
|
setLowUpperThreshold(db, clamp);
|
||||||
|
|
||||||
|
section_parent_->showPopupDisplay(this, formatString(low_lower_threshold_, " dB"), BubbleComponent::below, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setBandLowerThreshold(float db, bool clamp) {
|
||||||
|
band_lower_threshold_ = db;
|
||||||
|
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
if (clamp)
|
||||||
|
band_lower_threshold_ = db;
|
||||||
|
synth->valueChangedInternal("compressor_band_lower_threshold", db);
|
||||||
|
if (band_lower_threshold_ > band_upper_threshold_ && clamp)
|
||||||
|
setBandUpperThreshold(db, clamp);
|
||||||
|
|
||||||
|
section_parent_->showPopupDisplay(this, formatString(band_lower_threshold_, " dB"), BubbleComponent::below, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setHighLowerThreshold(float db, bool clamp ) {
|
||||||
|
high_lower_threshold_ = db;
|
||||||
|
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
if (clamp)
|
||||||
|
high_lower_threshold_ = db;
|
||||||
|
synth->valueChangedInternal("compressor_high_lower_threshold", db);
|
||||||
|
if (high_lower_threshold_ > high_upper_threshold_ && clamp)
|
||||||
|
setHighUpperThreshold(db, clamp);
|
||||||
|
|
||||||
|
section_parent_->showPopupDisplay(this, formatString(high_lower_threshold_, " dB"), BubbleComponent::below, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setLowUpperRatio(float ratio) {
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
low_upper_ratio_ = vital::utils::clamp(ratio, kMinUpperRatio, kMaxUpperRatio);
|
||||||
|
synth->valueChangedInternal("compressor_low_upper_ratio", ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setBandUpperRatio(float ratio) {
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
band_upper_ratio_ = vital::utils::clamp(ratio, kMinUpperRatio, kMaxUpperRatio);
|
||||||
|
synth->valueChangedInternal("compressor_band_upper_ratio", ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setHighUpperRatio(float ratio) {
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
high_upper_ratio_ = vital::utils::clamp(ratio, kMinUpperRatio, kMaxUpperRatio);
|
||||||
|
synth->valueChangedInternal("compressor_high_upper_ratio", ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setLowLowerRatio(float ratio) {
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
low_lower_ratio_ = vital::utils::clamp(ratio, kMinLowerRatio, kMaxLowerRatio);
|
||||||
|
synth->valueChangedInternal("compressor_low_lower_ratio", ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setBandLowerRatio(float ratio) {
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
band_lower_ratio_ = vital::utils::clamp(ratio, kMinLowerRatio, kMaxLowerRatio);
|
||||||
|
synth->valueChangedInternal("compressor_band_lower_ratio", ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::setHighLowerRatio(float ratio) {
|
||||||
|
SynthBase* synth = parent_->getSynth();
|
||||||
|
|
||||||
|
high_lower_ratio_ = vital::utils::clamp(ratio, kMinLowerRatio, kMaxLowerRatio);
|
||||||
|
synth->valueChangedInternal("compressor_high_lower_ratio", ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
String CompressorEditor::formatValue(float value) {
|
||||||
|
static constexpr int number_length = 5;
|
||||||
|
static constexpr int max_decimals = 3;
|
||||||
|
|
||||||
|
String format = String(value, max_decimals);
|
||||||
|
format = format.substring(0, number_length);
|
||||||
|
int spaces = number_length - format.length();
|
||||||
|
|
||||||
|
for (int i = 0; i < spaces; ++i)
|
||||||
|
format = " " + format;
|
||||||
|
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorEditor::parentHierarchyChanged() {
|
||||||
|
if (parent_ == nullptr)
|
||||||
|
parent_ = findParentComponentOfClass<SynthGuiInterface>();
|
||||||
|
|
||||||
|
if (section_parent_ == nullptr)
|
||||||
|
section_parent_ = findParentComponentOfClass<SynthSection>();
|
||||||
|
|
||||||
|
if (parent_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (low_input_ms_ == nullptr)
|
||||||
|
low_input_ms_ = parent_->getSynth()->getStatusOutput("compressor_low_input");
|
||||||
|
if (band_input_ms_ == nullptr)
|
||||||
|
band_input_ms_ = parent_->getSynth()->getStatusOutput("compressor_band_input");
|
||||||
|
if (high_input_ms_ == nullptr)
|
||||||
|
high_input_ms_ = parent_->getSynth()->getStatusOutput("compressor_high_input");
|
||||||
|
if (low_output_ms_ == nullptr)
|
||||||
|
low_output_ms_ = parent_->getSynth()->getStatusOutput("compressor_low_output");
|
||||||
|
if (band_output_ms_ == nullptr)
|
||||||
|
band_output_ms_ = parent_->getSynth()->getStatusOutput("compressor_band_output");
|
||||||
|
if (high_output_ms_ == nullptr)
|
||||||
|
high_output_ms_ = parent_->getSynth()->getStatusOutput("compressor_high_output");
|
||||||
|
|
||||||
|
OpenGlComponent::parentHierarchyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
float CompressorEditor::getYForDb(float db) {
|
||||||
|
return getHeight() * (1.0f - getOpenGlYForDb(vital::utils::clamp(db, kMinDb, kMaxDb))) * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float CompressorEditor::getCompressedDb(float input_db, float upper_threshold, float upper_ratio,
|
||||||
|
float lower_threshold, float lower_ratio) {
|
||||||
|
if (input_db < lower_threshold)
|
||||||
|
return vital::utils::interpolate(input_db, lower_threshold, lower_ratio);
|
||||||
|
if (input_db > upper_threshold)
|
||||||
|
return vital::utils::interpolate(input_db, upper_threshold, upper_ratio);
|
||||||
|
return input_db;
|
||||||
|
}
|
||||||
|
|
||||||
|
Colour CompressorEditor::getColorForRatio(float ratio) {
|
||||||
|
if (!active_)
|
||||||
|
return findColour(Skin::kWidgetSecondaryDisabled, true);
|
||||||
|
|
||||||
|
if (ratio > 0.0f)
|
||||||
|
return findColour(Skin::kWidgetSecondary1, true);
|
||||||
|
if (ratio < 0.0f)
|
||||||
|
return findColour(Skin::kWidgetSecondary2, true);
|
||||||
|
|
||||||
|
return findColour(Skin::kWidgetSecondaryDisabled, true);
|
||||||
|
}
|
||||||
172
src/interface/editor_components/compressor_editor.h
Normal file
172
src/interface/editor_components/compressor_editor.h
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
#include "line_generator.h"
|
||||||
|
#include "open_gl_component.h"
|
||||||
|
#include "open_gl_multi_quad.h"
|
||||||
|
#include "synth_slider.h"
|
||||||
|
#include "synth_module.h"
|
||||||
|
|
||||||
|
class SynthGuiInterface;
|
||||||
|
|
||||||
|
class CompressorEditor : public OpenGlComponent, public SynthSlider::SliderListener {
|
||||||
|
public:
|
||||||
|
static constexpr float kGrabRadius = 8.0f;
|
||||||
|
static constexpr float kMinDb = -80.0f;
|
||||||
|
static constexpr float kMaxDb = 0.0f;
|
||||||
|
static constexpr float kDbEditBuffer = 1.0f;
|
||||||
|
static constexpr float kMinEditDb = kMinDb + kDbEditBuffer;
|
||||||
|
static constexpr float kMaxEditDb = kMaxDb - kDbEditBuffer;
|
||||||
|
static constexpr float kMinLowerRatio = -1.0f;
|
||||||
|
static constexpr float kMaxLowerRatio = 1.0f;
|
||||||
|
static constexpr float kMinUpperRatio = 0.0f;
|
||||||
|
static constexpr float kMaxUpperRatio = 1.0f;
|
||||||
|
static constexpr float kRatioEditMultiplier = 0.6f;
|
||||||
|
static constexpr float kCompressorAreaBuffer = 0.05f;
|
||||||
|
static constexpr float kBarWidth = 1.0f / 5.0f;
|
||||||
|
static constexpr float kInputLineRadius = 0.02f;
|
||||||
|
static constexpr float kMouseMultiplier = 1.0f;
|
||||||
|
static constexpr int kMaxBands = 3;
|
||||||
|
static constexpr int kNumChannels = kMaxBands * 2;
|
||||||
|
static constexpr int kDbLineSections = 8;
|
||||||
|
static constexpr int kExtraDbLines = 6;
|
||||||
|
static constexpr int kRatioDbLines = kDbLineSections + kExtraDbLines;
|
||||||
|
static constexpr int kTotalRatioLines = kRatioDbLines * kNumChannels;
|
||||||
|
|
||||||
|
CompressorEditor();
|
||||||
|
virtual ~CompressorEditor();
|
||||||
|
|
||||||
|
void paintBackground(Graphics& g) override;
|
||||||
|
void resized() override;
|
||||||
|
|
||||||
|
void mouseDown(const MouseEvent& e) override;
|
||||||
|
void mouseDoubleClick(const MouseEvent& e) override;
|
||||||
|
void mouseMove(const MouseEvent& e) override;
|
||||||
|
void mouseDrag(const MouseEvent& e) override;
|
||||||
|
void mouseUp(const MouseEvent& e) override;
|
||||||
|
void mouseExit(const MouseEvent& e) override;
|
||||||
|
void parentHierarchyChanged() override;
|
||||||
|
|
||||||
|
void init(OpenGlWrapper& open_gl) override;
|
||||||
|
void render(OpenGlWrapper& open_gl, bool animate) override;
|
||||||
|
void renderCompressor(OpenGlWrapper& open_gl, bool animate);
|
||||||
|
void destroy(OpenGlWrapper& open_gl) override;
|
||||||
|
void setSizeRatio(float ratio) { size_ratio_ = ratio; }
|
||||||
|
void setAllValues(vital::control_map& controls);
|
||||||
|
|
||||||
|
void setHighBandActive(bool active) { high_band_active_ = active; }
|
||||||
|
void setLowBandActive(bool active) { low_band_active_ = active; }
|
||||||
|
void setActive(bool active) { active_ = active; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum DragPoint {
|
||||||
|
kNone,
|
||||||
|
kLowUpperThreshold,
|
||||||
|
kBandUpperThreshold,
|
||||||
|
kHighUpperThreshold,
|
||||||
|
kLowLowerThreshold,
|
||||||
|
kBandLowerThreshold,
|
||||||
|
kHighLowerThreshold,
|
||||||
|
kLowUpperRatio,
|
||||||
|
kBandUpperRatio,
|
||||||
|
kHighUpperRatio,
|
||||||
|
kLowLowerRatio,
|
||||||
|
kBandLowerRatio,
|
||||||
|
kHighLowerRatio,
|
||||||
|
kNumDragPoints
|
||||||
|
};
|
||||||
|
|
||||||
|
bool isRatio(DragPoint drag_point) {
|
||||||
|
return drag_point == kLowLowerRatio || drag_point == kBandLowerRatio || drag_point == kHighLowerRatio ||
|
||||||
|
drag_point == kLowUpperRatio || drag_point == kBandUpperRatio || drag_point == kHighUpperRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setThresholdPositions(int low_start, int low_end, int band_start, int band_end,
|
||||||
|
int high_start, int high_end, float ratio_match);
|
||||||
|
void setRatioLines(int start_index, int start_x, int end_x, float threshold, float ratio, bool upper, bool hover);
|
||||||
|
void setRatioLinePositions(int low_start, int low_end, int band_start, int band_end,
|
||||||
|
int high_start, int high_end);
|
||||||
|
void renderHover(OpenGlWrapper& open_gl, int low_start, int low_end,
|
||||||
|
int band_start, int band_end, int high_start, int high_end);
|
||||||
|
|
||||||
|
void setLowUpperThreshold(float db, bool clamp);
|
||||||
|
void setBandUpperThreshold(float db, bool clamp);
|
||||||
|
void setHighUpperThreshold(float db, bool clamp);
|
||||||
|
void setLowLowerThreshold(float db, bool clamp);
|
||||||
|
void setBandLowerThreshold(float db, bool clamp);
|
||||||
|
void setHighLowerThreshold(float db, bool clamp);
|
||||||
|
|
||||||
|
void setLowUpperRatio(float ratio);
|
||||||
|
void setBandUpperRatio(float ratio);
|
||||||
|
void setHighUpperRatio(float ratio);
|
||||||
|
void setLowLowerRatio(float ratio);
|
||||||
|
void setBandLowerRatio(float ratio);
|
||||||
|
void setHighLowerRatio(float ratio);
|
||||||
|
|
||||||
|
String formatValue(float value);
|
||||||
|
|
||||||
|
DragPoint getHoverPoint(const MouseEvent& e);
|
||||||
|
float getYForDb(float db);
|
||||||
|
float getCompressedDb(float input_db, float upper_threshold, float upper_ratio,
|
||||||
|
float lower_threshold, float lower_ratio);
|
||||||
|
Colour getColorForRatio(float ratio);
|
||||||
|
|
||||||
|
SynthGuiInterface* parent_;
|
||||||
|
SynthSection* section_parent_;
|
||||||
|
|
||||||
|
DragPoint hover_;
|
||||||
|
Point<int> last_mouse_position_;
|
||||||
|
|
||||||
|
OpenGlQuad hover_quad_;
|
||||||
|
OpenGlMultiQuad input_dbs_;
|
||||||
|
OpenGlMultiQuad output_dbs_;
|
||||||
|
OpenGlMultiQuad thresholds_;
|
||||||
|
OpenGlMultiQuad ratio_lines_;
|
||||||
|
|
||||||
|
float low_upper_threshold_;
|
||||||
|
float band_upper_threshold_;
|
||||||
|
float high_upper_threshold_;
|
||||||
|
float low_lower_threshold_;
|
||||||
|
float band_lower_threshold_;
|
||||||
|
float high_lower_threshold_;
|
||||||
|
float low_upper_ratio_;
|
||||||
|
float band_upper_ratio_;
|
||||||
|
float high_upper_ratio_;
|
||||||
|
float low_lower_ratio_;
|
||||||
|
float band_lower_ratio_;
|
||||||
|
float high_lower_ratio_;
|
||||||
|
|
||||||
|
const vital::StatusOutput* low_input_ms_;
|
||||||
|
const vital::StatusOutput* band_input_ms_;
|
||||||
|
const vital::StatusOutput* high_input_ms_;
|
||||||
|
|
||||||
|
const vital::StatusOutput* low_output_ms_;
|
||||||
|
const vital::StatusOutput* band_output_ms_;
|
||||||
|
const vital::StatusOutput* high_output_ms_;
|
||||||
|
|
||||||
|
float size_ratio_;
|
||||||
|
bool animate_;
|
||||||
|
bool active_;
|
||||||
|
bool high_band_active_;
|
||||||
|
bool low_band_active_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CompressorEditor)
|
||||||
|
};
|
||||||
|
|
||||||
311
src/interface/editor_components/drag_drop_effect_order.cpp
Normal file
311
src/interface/editor_components/drag_drop_effect_order.cpp
Normal file
|
|
@ -0,0 +1,311 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "drag_drop_effect_order.h"
|
||||||
|
|
||||||
|
#include "skin.h"
|
||||||
|
#include "fonts.h"
|
||||||
|
#include "paths.h"
|
||||||
|
#include "synth_button.h"
|
||||||
|
#include "synth_gui_interface.h"
|
||||||
|
#include "synth_strings.h"
|
||||||
|
#include "text_look_and_feel.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Path getPathForEffect(String effect) {
|
||||||
|
if (effect == "compressor")
|
||||||
|
return Paths::compressor();
|
||||||
|
if (effect == "chorus")
|
||||||
|
return Paths::chorus();
|
||||||
|
if (effect == "delay")
|
||||||
|
return Paths::delay();
|
||||||
|
if (effect == "distortion")
|
||||||
|
return Paths::distortion();
|
||||||
|
if (effect == "eq")
|
||||||
|
return Paths::equalizer();
|
||||||
|
if (effect == "filter")
|
||||||
|
return Paths::effectsFilter();
|
||||||
|
if (effect == "flanger")
|
||||||
|
return Paths::flanger();
|
||||||
|
if (effect == "phaser")
|
||||||
|
return Paths::phaser();
|
||||||
|
if (effect == "reverb")
|
||||||
|
return Paths::reverb();
|
||||||
|
|
||||||
|
return Path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DraggableEffect::DraggableEffect(const String& name, int order) : SynthSection(name), order_(order), hover_(false) {
|
||||||
|
setInterceptsMouseClicks(false, true);
|
||||||
|
|
||||||
|
StringArray tokens;
|
||||||
|
tokens.addTokens(getName(), "_", "");
|
||||||
|
icon_ = getPathForEffect(tokens[0].toLowerCase());
|
||||||
|
|
||||||
|
background_ = std::make_unique<OpenGlImageComponent>("background");
|
||||||
|
addOpenGlComponent(background_.get());
|
||||||
|
background_->setComponent(this);
|
||||||
|
background_->paintEntireComponent(false);
|
||||||
|
|
||||||
|
enable_ = std::make_unique<SynthButton>(name + "_on");
|
||||||
|
enable_->setPowerButton();
|
||||||
|
addButton(enable_.get());
|
||||||
|
enable_->setButtonText("");
|
||||||
|
enable_->getGlComponent()->setAlwaysOnTop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DraggableEffect::paint(Graphics& g) {
|
||||||
|
static constexpr float kLeftPadding = 0.07f;
|
||||||
|
static constexpr float kIconSize = 0.6f;
|
||||||
|
static constexpr int kTextureRows = 2;
|
||||||
|
static constexpr int kTextureColumns = 3;
|
||||||
|
static constexpr float kTextureYStart = 0.13f;
|
||||||
|
static constexpr float kTexturePadding = 0.45f;
|
||||||
|
static constexpr float kTextureCircleRadiusPercent = 0.25f;
|
||||||
|
|
||||||
|
g.setColour(getParentComponent()->findColour(Skin::kBody, true));
|
||||||
|
int round_amount = findValue(Skin::kBodyRounding);
|
||||||
|
g.fillRoundedRectangle(0, 0, getWidth(), getHeight(), round_amount);
|
||||||
|
|
||||||
|
if (enable_->getToggleState())
|
||||||
|
g.setColour(findColour(Skin::kPowerButtonOn, true));
|
||||||
|
else
|
||||||
|
g.setColour(findColour(Skin::kPowerButtonOff, true));
|
||||||
|
g.drawRoundedRectangle(0.5f, 0.5f, getWidth() - 1.0f, getHeight() - 1.0f, round_amount, 1.0f);
|
||||||
|
|
||||||
|
g.setFont(Fonts::instance()->proportional_regular().withPointHeight(size_ratio_ * 12.0f));
|
||||||
|
float text_x = getWidth() * kLeftPadding;
|
||||||
|
StringArray tokens;
|
||||||
|
tokens.addTokens(getName(), "_", "");
|
||||||
|
String text = tokens[0];
|
||||||
|
text = text.substring(0, 1).toUpperCase() + text.substring(1);
|
||||||
|
g.drawText(text, text_x, 0, getWidth() - text_x, getHeight(), Justification::centredLeft, true);
|
||||||
|
|
||||||
|
float icon_width = kIconSize * getHeight();
|
||||||
|
float icon_x = getWidth() / 2.0f + (getWidth() / 2.0f - icon_width) / 2.0f;
|
||||||
|
float icon_y = (getHeight() - icon_width) / 2.0f;
|
||||||
|
Rectangle<float> icon_bounds(icon_x, icon_y, icon_width, icon_width);
|
||||||
|
g.fillPath(icon_, icon_.getTransformToScaleToFit(icon_bounds, true));
|
||||||
|
|
||||||
|
if (hover_) {
|
||||||
|
g.setColour(findColour(Skin::kLightenScreen, true).withMultipliedAlpha(1.5f));
|
||||||
|
|
||||||
|
int width = getWidth();
|
||||||
|
int height = getHeight();
|
||||||
|
float spacing = width * (1.0f - 2.0f * kTexturePadding) / (kTextureColumns - 1.0f);
|
||||||
|
float radius = spacing * kTextureCircleRadiusPercent;
|
||||||
|
float x = width * kTexturePadding;
|
||||||
|
float y = height * kTextureYStart;
|
||||||
|
for (int c = 0; c < kTextureColumns; ++c) {
|
||||||
|
for (int r = 0; r < kTextureRows; ++r) {
|
||||||
|
g.fillEllipse(x + spacing * c - radius, y + spacing * r - radius, 2.0f * radius, 2.0f * radius);
|
||||||
|
g.fillEllipse(x + spacing * c - radius, height - y - spacing * r - radius, 2.0f * radius, 2.0f * radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DraggableEffect::resized() {
|
||||||
|
int width = getTitleWidth();
|
||||||
|
enable_->setBounds(0, 0, width, width);
|
||||||
|
background_->redrawImage(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DraggableEffect::buttonClicked(Button* clicked_button) {
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->effectEnabledChanged(this, clicked_button->getToggleState());
|
||||||
|
|
||||||
|
background_->redrawImage(true);
|
||||||
|
SynthSection::buttonClicked(clicked_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DraggableEffect::hover(bool hover) {
|
||||||
|
if (hover_ != hover) {
|
||||||
|
hover_ = hover;
|
||||||
|
background_->redrawImage(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DragDropEffectOrder::DragDropEffectOrder(String name) : SynthSection(name) {
|
||||||
|
currently_dragged_ = nullptr;
|
||||||
|
currently_hovered_ = nullptr;
|
||||||
|
last_dragged_index_ = 0;
|
||||||
|
mouse_down_y_ = 0;
|
||||||
|
dragged_starting_y_ = 0;
|
||||||
|
for (int i = 0; i < vital::constants::kNumEffects; ++i) {
|
||||||
|
effect_order_[i] = i;
|
||||||
|
effect_list_.push_back(std::make_unique<DraggableEffect>(strings::kEffectOrder[i], i));
|
||||||
|
addSubSection(effect_list_[i].get());
|
||||||
|
effect_list_[i]->addListener(this);
|
||||||
|
effect_list_[i]->setSkinOverride(static_cast<Skin::SectionOverride>(Skin::kAllEffects + 1 + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DragDropEffectOrder::~DragDropEffectOrder() { }
|
||||||
|
|
||||||
|
void DragDropEffectOrder::resized() {
|
||||||
|
float padding = size_ratio_ * kEffectPadding;
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::constants::kNumEffects; ++i) {
|
||||||
|
Component* effect = getEffect(i);
|
||||||
|
int from_y = getEffectY(i);
|
||||||
|
int to_y = getEffectY(i + 1);
|
||||||
|
effect->setBounds(0, from_y, getWidth(), to_y - from_y - padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::paintBackground(Graphics& g) {
|
||||||
|
static constexpr float kConnectionWidth = 0.1f;
|
||||||
|
g.setColour(findColour(Skin::kLightenScreen, true));
|
||||||
|
int width = getWidth() * kConnectionWidth;
|
||||||
|
int center = getWidth() / 2.0f + findValue(Skin::kWidgetRoundedCorner);
|
||||||
|
g.fillRect(center - width / 2, 0, width, getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::renderOpenGlComponents(OpenGlWrapper& open_gl, bool animate) {
|
||||||
|
SynthSection::renderOpenGlComponents(open_gl, animate);
|
||||||
|
|
||||||
|
DraggableEffect* current = currently_dragged_;
|
||||||
|
if (current)
|
||||||
|
current->renderOpenGlComponents(open_gl, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::mouseMove(const MouseEvent& e) {
|
||||||
|
int current_index = getEffectIndexFromY(e.y);
|
||||||
|
DraggableEffect* hover = effect_list_[effect_order_[current_index]].get();
|
||||||
|
if (hover != currently_hovered_) {
|
||||||
|
if (currently_hovered_)
|
||||||
|
currently_hovered_->hover(false);
|
||||||
|
if (hover)
|
||||||
|
hover->hover(true);
|
||||||
|
currently_hovered_ = hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::mouseDown(const MouseEvent& e) {
|
||||||
|
mouse_down_y_ = e.y;
|
||||||
|
last_dragged_index_ = getEffectIndexFromY(e.y);
|
||||||
|
currently_dragged_ = effect_list_[effect_order_[last_dragged_index_]].get();
|
||||||
|
dragged_starting_y_ = currently_dragged_->getY();
|
||||||
|
|
||||||
|
currently_dragged_->setAlwaysOnTop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::mouseDrag(const MouseEvent& e) {
|
||||||
|
if (currently_dragged_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int delta_y = e.y - mouse_down_y_;
|
||||||
|
int clamped_y = vital::utils::iclamp(dragged_starting_y_ + delta_y, 0,
|
||||||
|
getHeight() - currently_dragged_->getHeight());
|
||||||
|
currently_dragged_->setTopLeftPosition(currently_dragged_->getX(), clamped_y);
|
||||||
|
|
||||||
|
int next_index = getEffectIndexFromY(e.y);
|
||||||
|
if (next_index != last_dragged_index_) {
|
||||||
|
moveEffect(last_dragged_index_, next_index);
|
||||||
|
last_dragged_index_ = next_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::mouseUp(const MouseEvent& e) {
|
||||||
|
if (currently_dragged_)
|
||||||
|
currently_dragged_->setAlwaysOnTop(false);
|
||||||
|
|
||||||
|
currently_dragged_ = nullptr;
|
||||||
|
setStationaryEffectPosition(last_dragged_index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::mouseExit(const MouseEvent& e) {
|
||||||
|
if (currently_hovered_) {
|
||||||
|
currently_hovered_->hover(false);
|
||||||
|
currently_hovered_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::effectEnabledChanged(DraggableEffect* effect, bool enabled) {
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->effectEnabledChanged(effect->order(), enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::setAllValues(vital::control_map& controls) {
|
||||||
|
SynthSection::setAllValues(controls);
|
||||||
|
float order = controls[getName().toStdString()]->value();
|
||||||
|
vital::utils::decodeFloatToOrder(effect_order_, order, vital::constants::kNumEffects);
|
||||||
|
|
||||||
|
for (int i = 0; i < vital::constants::kNumEffects; ++i)
|
||||||
|
setStationaryEffectPosition(i);
|
||||||
|
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->orderChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::moveEffect(int start_index, int end_index) {
|
||||||
|
int delta_index = end_index - start_index;
|
||||||
|
if (delta_index == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int moving = effect_order_[start_index];
|
||||||
|
|
||||||
|
int direction = delta_index / abs(delta_index);
|
||||||
|
for (int i = start_index; i != end_index; i += direction) {
|
||||||
|
effect_order_[i] = effect_order_[i + direction];
|
||||||
|
setStationaryEffectPosition(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
effect_order_[end_index] = moving;
|
||||||
|
|
||||||
|
float order = vital::utils::encodeOrderToFloat(effect_order_, vital::constants::kNumEffects);
|
||||||
|
SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();
|
||||||
|
if (parent)
|
||||||
|
parent->getSynth()->valueChangedInternal(getName().toStdString(), order);
|
||||||
|
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->orderChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragDropEffectOrder::setStationaryEffectPosition(int index) {
|
||||||
|
float padding = size_ratio_ * kEffectPadding;
|
||||||
|
Component* effect = getEffect(index);
|
||||||
|
int from_y = getEffectY(index);
|
||||||
|
int to_y = getEffectY(index + 1);
|
||||||
|
effect->setBounds(0, from_y, getWidth(), to_y - from_y - padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DragDropEffectOrder::getEffectIndex(int index) const {
|
||||||
|
int i = vital::utils::iclamp(index, 0, vital::constants::kNumEffects - 1);
|
||||||
|
return effect_order_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
Component* DragDropEffectOrder::getEffect(int index) const {
|
||||||
|
return effect_list_[getEffectIndex(index)].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DragDropEffectOrder::effectEnabled(int index) const {
|
||||||
|
return effect_list_[getEffectIndex(index)]->enabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
int DragDropEffectOrder::getEffectIndexFromY(float y) const {
|
||||||
|
float padding = size_ratio_ * kEffectPadding;
|
||||||
|
int index = vital::constants::kNumEffects * (y + padding / 2.0f) / (getHeight() + padding);
|
||||||
|
return vital::utils::iclamp(index, 0, vital::constants::kNumEffects - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DragDropEffectOrder::getEffectY(int index) const {
|
||||||
|
int padding = size_ratio_ * kEffectPadding;
|
||||||
|
return std::round((1.0f * index * (getHeight() + padding)) / vital::constants::kNumEffects);
|
||||||
|
}
|
||||||
111
src/interface/editor_components/drag_drop_effect_order.h
Normal file
111
src/interface/editor_components/drag_drop_effect_order.h
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "synth_section.h"
|
||||||
|
#include "synth_constants.h"
|
||||||
|
#include "synth_button.h"
|
||||||
|
#include "synth_slider.h"
|
||||||
|
#include "open_gl_image_component.h"
|
||||||
|
|
||||||
|
class DraggableEffect : public SynthSection {
|
||||||
|
public:
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
virtual ~Listener() { }
|
||||||
|
virtual void effectEnabledChanged(DraggableEffect* effect, bool enabled) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
DraggableEffect(const String& name, int order);
|
||||||
|
void paint(Graphics& g) override;
|
||||||
|
void paintBackground(Graphics& g) override { }
|
||||||
|
|
||||||
|
void renderOpenGlComponents(OpenGlWrapper& open_gl, bool animate) override {
|
||||||
|
SynthSection::renderOpenGlComponents(open_gl, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resized() override;
|
||||||
|
void buttonClicked(Button* clicked_button) override;
|
||||||
|
void addListener(Listener* listener) { listeners_.push_back(listener); }
|
||||||
|
bool enabled() const { return enable_->getToggleState(); }
|
||||||
|
int order() const { return order_; }
|
||||||
|
|
||||||
|
void hover(bool hover);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Path icon_;
|
||||||
|
int order_;
|
||||||
|
bool hover_;
|
||||||
|
std::unique_ptr<SynthButton> enable_;
|
||||||
|
std::unique_ptr<OpenGlImageComponent> background_;
|
||||||
|
std::vector<Listener*> listeners_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DragDropEffectOrder : public SynthSection, public DraggableEffect::Listener {
|
||||||
|
public:
|
||||||
|
static constexpr int kEffectPadding = 6;
|
||||||
|
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
virtual ~Listener() { }
|
||||||
|
virtual void orderChanged(DragDropEffectOrder* order) = 0;
|
||||||
|
virtual void effectEnabledChanged(int order_index, bool enabled) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
DragDropEffectOrder(String name);
|
||||||
|
virtual ~DragDropEffectOrder();
|
||||||
|
|
||||||
|
void resized() override;
|
||||||
|
void paintBackground(Graphics& g) override;
|
||||||
|
|
||||||
|
void renderOpenGlComponents(OpenGlWrapper& open_gl, bool animate) override;
|
||||||
|
|
||||||
|
void mouseMove(const MouseEvent& e) override;
|
||||||
|
void mouseDown(const MouseEvent& e) override;
|
||||||
|
void mouseDrag(const MouseEvent& e) override;
|
||||||
|
void mouseUp(const MouseEvent& e) override;
|
||||||
|
void mouseExit(const MouseEvent& e) override;
|
||||||
|
|
||||||
|
void effectEnabledChanged(DraggableEffect* effect, bool enabled) override;
|
||||||
|
void setAllValues(vital::control_map& controls) override;
|
||||||
|
|
||||||
|
void moveEffect(int start_index, int end_index);
|
||||||
|
void setStationaryEffectPosition(int index);
|
||||||
|
void addListener(Listener* listener) { listeners_.push_back(listener); }
|
||||||
|
|
||||||
|
int getEffectIndex(int index) const;
|
||||||
|
Component* getEffect(int index) const;
|
||||||
|
bool effectEnabled(int index) const;
|
||||||
|
int getEffectIndexFromY(float y) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int getEffectY(int index) const;
|
||||||
|
|
||||||
|
std::vector<Listener*> listeners_;
|
||||||
|
DraggableEffect* currently_dragged_;
|
||||||
|
DraggableEffect* currently_hovered_;
|
||||||
|
|
||||||
|
int last_dragged_index_;
|
||||||
|
int mouse_down_y_;
|
||||||
|
int dragged_starting_y_;
|
||||||
|
std::vector<std::unique_ptr<DraggableEffect>> effect_list_;
|
||||||
|
int effect_order_[vital::constants::kNumEffects];
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DragDropEffectOrder)
|
||||||
|
};
|
||||||
|
|
||||||
1120
src/interface/editor_components/envelope_editor.cpp
Normal file
1120
src/interface/editor_components/envelope_editor.cpp
Normal file
File diff suppressed because it is too large
Load diff
230
src/interface/editor_components/envelope_editor.h
Normal file
230
src/interface/editor_components/envelope_editor.h
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "skin.h"
|
||||||
|
#include "open_gl_image.h"
|
||||||
|
#include "open_gl_line_renderer.h"
|
||||||
|
#include "synth_module.h"
|
||||||
|
#include "synth_slider.h"
|
||||||
|
|
||||||
|
class EnvelopeEditor : public OpenGlLineRenderer, public SynthSlider::SliderListener {
|
||||||
|
public:
|
||||||
|
static constexpr float kMarkerWidth = 9.0f;
|
||||||
|
static constexpr float kRingThickness = 0.45f;
|
||||||
|
static constexpr float kPowerMarkerWidth = 7.0f;
|
||||||
|
static constexpr float kMarkerHoverRadius = 12.0f;
|
||||||
|
static constexpr float kMarkerGrabRadius = 20.0f;
|
||||||
|
static constexpr float kTailDecay = 0.965f;
|
||||||
|
static constexpr float kPaddingX = 0.018f;
|
||||||
|
static constexpr float kPaddingY = 0.06f;
|
||||||
|
static constexpr float kMinPointDistanceForPower = 3.0f;
|
||||||
|
static constexpr float kPowerMouseMultiplier = 0.06f;
|
||||||
|
static constexpr float kTimeDisplaySize = 0.05f;
|
||||||
|
|
||||||
|
static constexpr int kRulerDivisionSize = 4;
|
||||||
|
static constexpr int kMaxGridLines = 36;
|
||||||
|
static constexpr int kMaxTimesShown = 24;
|
||||||
|
static constexpr int kNumPointsPerSection = 98;
|
||||||
|
static constexpr int kNumSections = 4;
|
||||||
|
static constexpr int kTotalPoints = kNumSections * kNumPointsPerSection + 1;
|
||||||
|
|
||||||
|
EnvelopeEditor(const String& prefix,
|
||||||
|
const vital::output_map& mono_modulations, const vital::output_map& poly_modulations);
|
||||||
|
~EnvelopeEditor();
|
||||||
|
|
||||||
|
void paintBackground(Graphics& g) override;
|
||||||
|
|
||||||
|
void resized() override {
|
||||||
|
OpenGlLineRenderer::resized();
|
||||||
|
drag_circle_.setBounds(getLocalBounds());
|
||||||
|
hover_circle_.setBounds(getLocalBounds());
|
||||||
|
grid_lines_.setBounds(getLocalBounds());
|
||||||
|
sub_grid_lines_.setBounds(getLocalBounds());
|
||||||
|
position_circle_.setBounds(getLocalBounds());
|
||||||
|
point_circles_.setBounds(getLocalBounds());
|
||||||
|
power_circles_.setBounds(getLocalBounds());
|
||||||
|
|
||||||
|
float font_height = kTimeDisplaySize * getHeight();
|
||||||
|
for (int i = 0; i < kMaxTimesShown; ++i)
|
||||||
|
times_[i]->setTextSize(font_height);
|
||||||
|
|
||||||
|
setTimePositions();
|
||||||
|
reset_positions_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetEnvelopeLine(int index);
|
||||||
|
void guiChanged(SynthSlider* slider) override;
|
||||||
|
|
||||||
|
void setDelaySlider(SynthSlider* delay_slider);
|
||||||
|
void setAttackSlider(SynthSlider* attack_slider);
|
||||||
|
void setAttackPowerSlider(SynthSlider* attack_slider);
|
||||||
|
void setHoldSlider(SynthSlider* hold_slider);
|
||||||
|
void setDecaySlider(SynthSlider* decay_slider);
|
||||||
|
void setDecayPowerSlider(SynthSlider* decay_slider);
|
||||||
|
void setSustainSlider(SynthSlider* sustain_slider);
|
||||||
|
void setReleaseSlider(SynthSlider* release_slider);
|
||||||
|
void setReleasePowerSlider(SynthSlider* release_slider);
|
||||||
|
void setSizeRatio(float ratio) { size_ratio_ = ratio; }
|
||||||
|
|
||||||
|
void parentHierarchyChanged() override;
|
||||||
|
void pickHoverPosition(Point<float> position);
|
||||||
|
void mouseMove(const MouseEvent& e) override;
|
||||||
|
void mouseExit(const MouseEvent& e) override;
|
||||||
|
void mouseDown(const MouseEvent& e) override;
|
||||||
|
void mouseDrag(const MouseEvent& e) override;
|
||||||
|
void mouseDoubleClick(const MouseEvent& e) override;
|
||||||
|
void mouseUp(const MouseEvent& e) override;
|
||||||
|
void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override;
|
||||||
|
|
||||||
|
void magnifyZoom(Point<float> delta);
|
||||||
|
void magnifyReset();
|
||||||
|
|
||||||
|
void init(OpenGlWrapper& open_gl) override;
|
||||||
|
void render(OpenGlWrapper& open_gl, bool animate) override;
|
||||||
|
void destroy(OpenGlWrapper& open_gl) override;
|
||||||
|
void resetPositions() { reset_positions_ = true; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setEditingCircleBounds();
|
||||||
|
void setGridPositions();
|
||||||
|
void setTimePositions();
|
||||||
|
void setPointPositions();
|
||||||
|
void setGlPositions();
|
||||||
|
void setColors();
|
||||||
|
void zoom(float amount);
|
||||||
|
|
||||||
|
static std::pair<vital::Output*, vital::Output*> getOutputs(const vital::output_map& mono_modulations,
|
||||||
|
const vital::output_map& poly_modulations,
|
||||||
|
const String& name) {
|
||||||
|
return {
|
||||||
|
mono_modulations.at(name.toStdString()),
|
||||||
|
poly_modulations.at(name.toStdString())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::poly_float getOutputsTotal(std::pair<vital::Output*, vital::Output*> outputs,
|
||||||
|
vital::poly_float default_value);
|
||||||
|
void drawPosition(OpenGlWrapper& open_gl, int index);
|
||||||
|
std::pair<float, float> getPosition(int index);
|
||||||
|
|
||||||
|
float padX(float x);
|
||||||
|
float padY(float y);
|
||||||
|
float unpadX(float x);
|
||||||
|
float unpadY(float y);
|
||||||
|
float padOpenGlX(float x);
|
||||||
|
float padOpenGlY(float y);
|
||||||
|
|
||||||
|
float getSliderDelayX();
|
||||||
|
float getSliderAttackX();
|
||||||
|
float getSliderHoldX();
|
||||||
|
float getSliderDecayX();
|
||||||
|
float getSliderSustainY();
|
||||||
|
float getSliderReleaseX();
|
||||||
|
float getDelayTime(int index);
|
||||||
|
float getAttackTime(int index);
|
||||||
|
float getHoldTime(int index);
|
||||||
|
float getDecayTime(int index);
|
||||||
|
float getReleaseTime(int index);
|
||||||
|
float getDelayX(int index);
|
||||||
|
float getAttackX(int index);
|
||||||
|
float getHoldX(int index);
|
||||||
|
float getDecayX(int index);
|
||||||
|
float getSustainY(int index);
|
||||||
|
float getReleaseX(int index);
|
||||||
|
|
||||||
|
float getBackupPhase(float phase, int index);
|
||||||
|
vital::poly_float getBackupPhase(vital::poly_float phase);
|
||||||
|
float getEnvelopeValue(float t, float power, float start, float end);
|
||||||
|
float getSliderAttackValue(float t);
|
||||||
|
float getSliderDecayValue(float t);
|
||||||
|
float getSliderReleaseValue(float t);
|
||||||
|
float getAttackValue(float t, int index);
|
||||||
|
float getDecayValue(float t, int index);
|
||||||
|
float getReleaseValue(float t, int index);
|
||||||
|
|
||||||
|
void setDelayX(float x);
|
||||||
|
void setAttackX(float x);
|
||||||
|
void setHoldX(float x);
|
||||||
|
void setDecayX(float x);
|
||||||
|
void setSustainY(float y);
|
||||||
|
void setReleaseX(float x);
|
||||||
|
|
||||||
|
void setPower(SynthSlider* slider, float power);
|
||||||
|
void setAttackPower(float power);
|
||||||
|
void setDecayPower(float power);
|
||||||
|
void setReleasePower(float power);
|
||||||
|
|
||||||
|
SynthGuiInterface* parent_;
|
||||||
|
bool delay_hover_;
|
||||||
|
bool attack_hover_;
|
||||||
|
bool hold_hover_;
|
||||||
|
bool sustain_hover_;
|
||||||
|
bool release_hover_;
|
||||||
|
bool attack_power_hover_;
|
||||||
|
bool decay_power_hover_;
|
||||||
|
bool release_power_hover_;
|
||||||
|
bool mouse_down_;
|
||||||
|
Point<float> last_edit_position_;
|
||||||
|
|
||||||
|
bool animate_;
|
||||||
|
float size_ratio_;
|
||||||
|
float window_time_;
|
||||||
|
|
||||||
|
vital::poly_float current_position_alpha_;
|
||||||
|
vital::poly_float last_phase_;
|
||||||
|
|
||||||
|
Colour line_left_color_;
|
||||||
|
Colour line_right_color_;
|
||||||
|
Colour line_center_color_;
|
||||||
|
Colour fill_left_color_;
|
||||||
|
Colour fill_right_color_;
|
||||||
|
Colour background_color_;
|
||||||
|
Colour time_color_;
|
||||||
|
|
||||||
|
bool reset_positions_;
|
||||||
|
OpenGlQuad drag_circle_;
|
||||||
|
OpenGlQuad hover_circle_;
|
||||||
|
OpenGlMultiQuad grid_lines_;
|
||||||
|
OpenGlMultiQuad sub_grid_lines_;
|
||||||
|
OpenGlQuad position_circle_;
|
||||||
|
OpenGlMultiQuad point_circles_;
|
||||||
|
OpenGlMultiQuad power_circles_;
|
||||||
|
std::unique_ptr<PlainTextComponent> times_[kMaxTimesShown];
|
||||||
|
|
||||||
|
const vital::StatusOutput* envelope_phase_;
|
||||||
|
|
||||||
|
SynthSlider* delay_slider_;
|
||||||
|
SynthSlider* attack_slider_;
|
||||||
|
SynthSlider* hold_slider_;
|
||||||
|
SynthSlider* attack_power_slider_;
|
||||||
|
SynthSlider* decay_slider_;
|
||||||
|
SynthSlider* decay_power_slider_;
|
||||||
|
SynthSlider* sustain_slider_;
|
||||||
|
SynthSlider* release_slider_;
|
||||||
|
SynthSlider* release_power_slider_;
|
||||||
|
|
||||||
|
std::pair<vital::Output*, vital::Output*> delay_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> attack_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> hold_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> decay_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> sustain_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> release_outputs_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EnvelopeEditor)
|
||||||
|
};
|
||||||
581
src/interface/editor_components/equalizer_response.cpp
Normal file
581
src/interface/editor_components/equalizer_response.cpp
Normal file
|
|
@ -0,0 +1,581 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "equalizer_response.h"
|
||||||
|
|
||||||
|
#include "shaders.h"
|
||||||
|
#include "skin.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
EqualizerResponse::EqualizerResponse() : OpenGlLineRenderer(kResolution),
|
||||||
|
unselected_points_(2, Shaders::kRingFragment),
|
||||||
|
selected_point_(Shaders::kCircleFragment),
|
||||||
|
dragging_point_(Shaders::kCircleFragment), shader_(nullptr) {
|
||||||
|
unselected_points_.setThickness(1.0f);
|
||||||
|
setFill(true);
|
||||||
|
|
||||||
|
addAndMakeVisible(unselected_points_);
|
||||||
|
addAndMakeVisible(selected_point_);
|
||||||
|
addAndMakeVisible(dragging_point_);
|
||||||
|
|
||||||
|
low_cutoff_ = nullptr;
|
||||||
|
low_resonance_ = nullptr;
|
||||||
|
low_gain_ = nullptr;
|
||||||
|
band_cutoff_ = nullptr;
|
||||||
|
band_resonance_ = nullptr;
|
||||||
|
band_gain_ = nullptr;
|
||||||
|
high_cutoff_ = nullptr;
|
||||||
|
high_resonance_ = nullptr;
|
||||||
|
high_gain_ = nullptr;
|
||||||
|
|
||||||
|
low_cutoff_output_ = nullptr;
|
||||||
|
low_resonance_output_ = nullptr;
|
||||||
|
low_gain_output_ = nullptr;
|
||||||
|
band_cutoff_output_ = nullptr;
|
||||||
|
band_resonance_output_ = nullptr;
|
||||||
|
band_gain_output_ = nullptr;
|
||||||
|
high_cutoff_output_ = nullptr;
|
||||||
|
high_resonance_output_ = nullptr;
|
||||||
|
high_gain_output_ = nullptr;
|
||||||
|
|
||||||
|
current_cutoff_ = nullptr;
|
||||||
|
current_gain_ = nullptr;
|
||||||
|
|
||||||
|
low_filter_.setSampleRate(kViewSampleRate);
|
||||||
|
band_filter_.setSampleRate(kViewSampleRate);
|
||||||
|
high_filter_.setSampleRate(kViewSampleRate);
|
||||||
|
low_filter_.setDriveCompensation(false);
|
||||||
|
high_filter_.setDriveCompensation(false);
|
||||||
|
|
||||||
|
active_ = false;
|
||||||
|
high_pass_ = false;
|
||||||
|
notch_ = false;
|
||||||
|
low_pass_ = false;
|
||||||
|
animate_ = true;
|
||||||
|
draw_frequency_lines_ = true;
|
||||||
|
selected_band_ = 0;
|
||||||
|
|
||||||
|
line_data_ = std::make_unique<float[]>(kResolution);
|
||||||
|
line_buffer_ = 0;
|
||||||
|
response_buffer_ = 0;
|
||||||
|
vertex_array_object_ = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < kResolution; ++i) {
|
||||||
|
float t = i / (kResolution - 1.0f);
|
||||||
|
line_data_[i] = 2.0f * t - 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_buffer_ratio_ = kDefaultDbBufferRatio;
|
||||||
|
min_db_ = 0.0f;
|
||||||
|
max_db_ = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
EqualizerResponse::~EqualizerResponse() = default;
|
||||||
|
|
||||||
|
void EqualizerResponse::initEq(const vital::output_map& mono_modulations) {
|
||||||
|
low_cutoff_output_ = mono_modulations.at("eq_low_cutoff");
|
||||||
|
low_resonance_output_ = mono_modulations.at("eq_low_resonance");
|
||||||
|
low_gain_output_ = mono_modulations.at("eq_low_gain");
|
||||||
|
band_cutoff_output_ = mono_modulations.at("eq_band_cutoff");
|
||||||
|
band_resonance_output_ = mono_modulations.at("eq_band_resonance");
|
||||||
|
band_gain_output_ = mono_modulations.at("eq_band_gain");
|
||||||
|
high_cutoff_output_ = mono_modulations.at("eq_high_cutoff");
|
||||||
|
high_resonance_output_ = mono_modulations.at("eq_high_resonance");
|
||||||
|
high_gain_output_ = mono_modulations.at("eq_high_gain");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::initReverb(const vital::output_map& mono_modulations) {
|
||||||
|
low_cutoff_output_ = mono_modulations.at("reverb_low_shelf_cutoff");
|
||||||
|
low_gain_output_ = mono_modulations.at("reverb_low_shelf_gain");
|
||||||
|
high_cutoff_output_ = mono_modulations.at("reverb_high_shelf_cutoff");
|
||||||
|
high_gain_output_ = mono_modulations.at("reverb_high_shelf_gain");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::init(OpenGlWrapper& open_gl) {
|
||||||
|
OpenGlLineRenderer::init(open_gl);
|
||||||
|
|
||||||
|
unselected_points_.init(open_gl);
|
||||||
|
selected_point_.init(open_gl);
|
||||||
|
dragging_point_.init(open_gl);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glGenVertexArrays(1, &vertex_array_object_);
|
||||||
|
open_gl.context.extensions.glBindVertexArray(vertex_array_object_);
|
||||||
|
|
||||||
|
GLsizeiptr vert_size = static_cast<GLsizeiptr>(kResolution * sizeof(float));
|
||||||
|
open_gl.context.extensions.glGenBuffers(1, &line_buffer_);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, line_buffer_);
|
||||||
|
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, line_data_.get(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glGenBuffers(1, &response_buffer_);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, response_buffer_);
|
||||||
|
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, nullptr, GL_STATIC_READ);
|
||||||
|
|
||||||
|
const GLchar* varyings[] = { "response_out" };
|
||||||
|
shader_ = open_gl.shaders->getShaderProgram(Shaders::kEqFilterResponseVertex, Shaders::kColorFragment, varyings);
|
||||||
|
shader_->use();
|
||||||
|
|
||||||
|
position_attribute_ = getAttribute(open_gl, *shader_, "position");
|
||||||
|
midi_cutoff_uniform_ = getUniform(open_gl, *shader_, "midi_cutoff");
|
||||||
|
resonance_uniform_ = getUniform(open_gl, *shader_, "resonance");
|
||||||
|
low_amount_uniform_ = getUniform(open_gl, *shader_, "low_amount");
|
||||||
|
band_amount_uniform_ = getUniform(open_gl, *shader_, "band_amount");
|
||||||
|
high_amount_uniform_ = getUniform(open_gl, *shader_, "high_amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::drawResponse(OpenGlWrapper& open_gl, int index) {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
setLineWidth(findValue(Skin::kWidgetLineWidth));
|
||||||
|
setFillCenter(1.0f - 2.0f * max_db_ / (max_db_ - min_db_));
|
||||||
|
|
||||||
|
Colour color_line = findColour(Skin::kWidgetPrimary1, true);
|
||||||
|
Colour color_fill_to = findColour(Skin::kWidgetSecondary1, true);
|
||||||
|
|
||||||
|
if (!active_) {
|
||||||
|
color_line = findColour(Skin::kWidgetPrimaryDisabled, true);
|
||||||
|
color_fill_to = findColour(Skin::kWidgetSecondaryDisabled, true);
|
||||||
|
}
|
||||||
|
else if (index) {
|
||||||
|
color_line = findColour(Skin::kWidgetPrimary2, true);
|
||||||
|
color_fill_to = findColour(Skin::kWidgetSecondary2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
setColor(color_line);
|
||||||
|
float fill_fade = findValue(Skin::kWidgetFillFade);
|
||||||
|
setFillColors(color_fill_to.withMultipliedAlpha(1.0f - fill_fade), color_fill_to);
|
||||||
|
|
||||||
|
shader_->use();
|
||||||
|
open_gl.context.extensions.glBindVertexArray(vertex_array_object_);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, line_buffer_);
|
||||||
|
open_gl.context.extensions.glVertexAttribPointer(position_attribute_->attributeID, 1, GL_FLOAT, GL_FALSE,
|
||||||
|
sizeof(float), nullptr);
|
||||||
|
open_gl.context.extensions.glEnableVertexAttribArray(position_attribute_->attributeID);
|
||||||
|
open_gl.context.extensions.glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, response_buffer_);
|
||||||
|
|
||||||
|
midi_cutoff_uniform_->set(low_filter_.getMidiCutoff()[index],
|
||||||
|
band_filter_.getMidiCutoff()[index],
|
||||||
|
high_filter_.getMidiCutoff()[index]);
|
||||||
|
resonance_uniform_->set(low_filter_.getResonance()[index],
|
||||||
|
band_filter_.getResonance()[index],
|
||||||
|
high_filter_.getResonance()[index]);
|
||||||
|
|
||||||
|
low_amount_uniform_->set(low_filter_.getLowAmount()[index],
|
||||||
|
band_filter_.getLowAmount()[index],
|
||||||
|
high_filter_.getLowAmount()[index]);
|
||||||
|
band_amount_uniform_->set(low_filter_.getBandAmount()[index],
|
||||||
|
band_filter_.getBandAmount()[index],
|
||||||
|
high_filter_.getBandAmount()[index]);
|
||||||
|
high_amount_uniform_->set(low_filter_.getHighAmount()[index],
|
||||||
|
band_filter_.getHighAmount()[index],
|
||||||
|
high_filter_.getHighAmount()[index]);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glBeginTransformFeedback(GL_POINTS);
|
||||||
|
glDrawArrays(GL_POINTS, 0, kResolution);
|
||||||
|
open_gl.context.extensions.glEndTransformFeedback();
|
||||||
|
|
||||||
|
void* buffer = open_gl.context.extensions.glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
|
||||||
|
kResolution * sizeof(float), GL_MAP_READ_BIT);
|
||||||
|
|
||||||
|
float* response_data = (float*)buffer;
|
||||||
|
float width = getWidth();
|
||||||
|
float range = max_db_ - min_db_;
|
||||||
|
float y_mult = getHeight() / range;
|
||||||
|
for (int i = 0; i < kResolution; ++i) {
|
||||||
|
setXAt(i, i * width / (kResolution - 1.0f));
|
||||||
|
setYAt(i, (max_db_ - response_data[i]) * y_mult);
|
||||||
|
}
|
||||||
|
|
||||||
|
open_gl.context.extensions.glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
|
||||||
|
|
||||||
|
OpenGlLineRenderer::render(open_gl, animate_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::render(OpenGlWrapper& open_gl, bool animate) {
|
||||||
|
animate_ = animate;
|
||||||
|
computeFilterCoefficients();
|
||||||
|
if (active_ && animate_)
|
||||||
|
drawResponse(open_gl, 1);
|
||||||
|
drawResponse(open_gl, 0);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glDisableVertexAttribArray(position_attribute_->attributeID);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
open_gl.context.extensions.glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
|
||||||
|
|
||||||
|
checkGlError();
|
||||||
|
|
||||||
|
drawControlPoints(open_gl);
|
||||||
|
renderCorners(open_gl, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::destroy(OpenGlWrapper& open_gl) {
|
||||||
|
OpenGlLineRenderer::destroy(open_gl);
|
||||||
|
|
||||||
|
unselected_points_.destroy(open_gl);
|
||||||
|
selected_point_.destroy(open_gl);
|
||||||
|
dragging_point_.destroy(open_gl);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glDeleteBuffers(1, &line_buffer_);
|
||||||
|
open_gl.context.extensions.glDeleteBuffers(1, &response_buffer_);
|
||||||
|
line_buffer_ = 0;
|
||||||
|
response_buffer_ = 0;
|
||||||
|
|
||||||
|
shader_ = nullptr;
|
||||||
|
position_attribute_ = nullptr;
|
||||||
|
midi_cutoff_uniform_ = nullptr;
|
||||||
|
resonance_uniform_ = nullptr;
|
||||||
|
low_amount_uniform_ = nullptr;
|
||||||
|
band_amount_uniform_ = nullptr;
|
||||||
|
high_amount_uniform_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::setControlPointBounds(float selected_x, float selected_y,
|
||||||
|
float unselected_x1, float unselected_y1,
|
||||||
|
float unselected_x2, float unselected_y2) {
|
||||||
|
static constexpr float kHandleRadius = 0.06f;
|
||||||
|
static constexpr float kDraggingRadius = 0.18f;
|
||||||
|
|
||||||
|
float width = getWidth();
|
||||||
|
float height = getHeight();
|
||||||
|
float handle_radius = kHandleRadius * height;
|
||||||
|
float handle_width = handle_radius * 4.0f / width;
|
||||||
|
float handle_height = handle_radius * 4.0f / height;
|
||||||
|
|
||||||
|
float dragging_radius = kDraggingRadius * height;
|
||||||
|
float dragging_width = dragging_radius * 4.0f / width;
|
||||||
|
float dragging_height = dragging_radius * 4.0f / height;
|
||||||
|
|
||||||
|
selected_point_.setQuad(0, selected_x - handle_width * 0.5f, selected_y - handle_height * 0.5f,
|
||||||
|
handle_width, handle_height);
|
||||||
|
dragging_point_.setQuad(0, selected_x - dragging_width * 0.5f, selected_y - dragging_height * 0.5f,
|
||||||
|
dragging_width, dragging_height);
|
||||||
|
unselected_points_.setQuad(0, unselected_x1 - handle_width * 0.5f, unselected_y1 - handle_height * 0.5f,
|
||||||
|
handle_width, handle_height);
|
||||||
|
unselected_points_.setQuad(1, unselected_x2 - handle_width * 0.5f, unselected_y2 - handle_height * 0.5f,
|
||||||
|
handle_width, handle_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::drawControlPoints(OpenGlWrapper& open_gl) {
|
||||||
|
|
||||||
|
Point<float> low_position = getLowPosition();
|
||||||
|
Point<float> band_position = getBandPosition();
|
||||||
|
Point<float> high_position = getHighPosition();
|
||||||
|
|
||||||
|
float width = getWidth();
|
||||||
|
float height = getHeight();
|
||||||
|
|
||||||
|
float low_x = 2.0f * low_position.x / width - 1.0f;
|
||||||
|
float band_x = 2.0f * band_position.x / width - 1.0f;
|
||||||
|
float high_x = 2.0f * high_position.x / width - 1.0f;
|
||||||
|
float low_y = 1.0f - 2.0f * low_position.y / height;
|
||||||
|
float band_y = 1.0f - 2.0f * band_position.y / height;
|
||||||
|
float high_y = 1.0f - 2.0f * high_position.y / height;
|
||||||
|
|
||||||
|
if (band_cutoff_output_ == nullptr)
|
||||||
|
band_x = -2.0f;
|
||||||
|
|
||||||
|
if (selected_band_ == 0)
|
||||||
|
setControlPointBounds(low_x, low_y, band_x, band_y, high_x, high_y);
|
||||||
|
else if (band_cutoff_output_ && selected_band_ == 1)
|
||||||
|
setControlPointBounds(band_x, band_y, low_x, low_y, high_x, high_y);
|
||||||
|
else if (selected_band_ == 2)
|
||||||
|
setControlPointBounds(high_x, high_y, low_x, low_y, band_x, band_y);
|
||||||
|
|
||||||
|
dragging_point_.setColor(findColour(Skin::kLightenScreen, true));
|
||||||
|
if (current_cutoff_ && current_gain_)
|
||||||
|
dragging_point_.render(open_gl, true);
|
||||||
|
selected_point_.setColor(findColour(Skin::kWidgetPrimary1, true));
|
||||||
|
selected_point_.render(open_gl, true);
|
||||||
|
unselected_points_.setColor(findColour(Skin::kWidgetPrimary1, true));
|
||||||
|
unselected_points_.render(open_gl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::paintBackground(Graphics& g) {
|
||||||
|
static constexpr int kLineSpacing = 10;
|
||||||
|
|
||||||
|
OpenGlLineRenderer::paintBackground(g);
|
||||||
|
if (!draw_frequency_lines_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float min_frequency = vital::utils::midiNoteToFrequency(low_cutoff_->getMinimum());
|
||||||
|
float max_frequency = vital::utils::midiNoteToFrequency(low_cutoff_->getMaximum());
|
||||||
|
|
||||||
|
int height = getHeight();
|
||||||
|
float max_octave = log2f(max_frequency / min_frequency);
|
||||||
|
g.setColour(findColour(Skin::kLightenScreen, true).withMultipliedAlpha(0.5f));
|
||||||
|
float frequency = 0.0f;
|
||||||
|
float increment = 1.0f;
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
while (frequency < max_frequency) {
|
||||||
|
for (int i = 0; i < kLineSpacing; ++i) {
|
||||||
|
frequency += increment;
|
||||||
|
float t = log2f(frequency / min_frequency) / max_octave;
|
||||||
|
x = std::round(t * getWidth());
|
||||||
|
g.fillRect(x, 0, 1, height);
|
||||||
|
}
|
||||||
|
g.fillRect(x, 0, 1, height);
|
||||||
|
increment *= kLineSpacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) {
|
||||||
|
int band = getHoveredBand(e);
|
||||||
|
|
||||||
|
if (band == 0 && low_resonance_)
|
||||||
|
low_resonance_->mouseWheelMove(e, wheel);
|
||||||
|
else if (band == 1 && band_resonance_)
|
||||||
|
band_resonance_->mouseWheelMove(e, wheel);
|
||||||
|
else if (band == 2 && high_resonance_)
|
||||||
|
high_resonance_->mouseWheelMove(e, wheel);
|
||||||
|
else
|
||||||
|
OpenGlComponent::mouseWheelMove(e, wheel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::mouseDown(const MouseEvent& e) {
|
||||||
|
selected_band_ = getHoveredBand(e);
|
||||||
|
|
||||||
|
if (selected_band_ == 0) {
|
||||||
|
current_cutoff_ = low_cutoff_;
|
||||||
|
current_gain_ = low_gain_;
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->lowBandSelected();
|
||||||
|
}
|
||||||
|
else if (selected_band_ == 1) {
|
||||||
|
current_cutoff_ = band_cutoff_;
|
||||||
|
current_gain_ = band_gain_;
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->midBandSelected();
|
||||||
|
}
|
||||||
|
else if (selected_band_ == 2) {
|
||||||
|
current_cutoff_ = high_cutoff_;
|
||||||
|
current_gain_ = high_gain_;
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->highBandSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenGlLineRenderer::mouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::mouseDrag(const MouseEvent& e) {
|
||||||
|
moveFilterSettings(e.position);
|
||||||
|
OpenGlLineRenderer::mouseDrag(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::mouseUp(const MouseEvent& e) {
|
||||||
|
if (current_cutoff_ == nullptr && current_gain_ == nullptr) {
|
||||||
|
OpenGlLineRenderer::mouseUp(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_cutoff_ = nullptr;
|
||||||
|
current_gain_ = nullptr;
|
||||||
|
OpenGlLineRenderer::mouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::mouseExit(const MouseEvent& e) {
|
||||||
|
if (low_cutoff_) {
|
||||||
|
low_cutoff_->hidePopup(true);
|
||||||
|
low_cutoff_->hidePopup(false);
|
||||||
|
}
|
||||||
|
OpenGlLineRenderer::mouseExit(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
int EqualizerResponse::getHoveredBand(const MouseEvent& e) {
|
||||||
|
const float grab_distance = 0.06f * getWidth();
|
||||||
|
|
||||||
|
float delta_low = e.position.getDistanceSquaredFrom(getLowPosition());
|
||||||
|
float delta_band = e.position.getDistanceSquaredFrom(getBandPosition());
|
||||||
|
float delta_high = e.position.getDistanceSquaredFrom(getHighPosition());
|
||||||
|
|
||||||
|
float min_sqr_dist = grab_distance * grab_distance;
|
||||||
|
float min = std::min(std::min(min_sqr_dist, delta_low), delta_high);
|
||||||
|
if (band_cutoff_output_)
|
||||||
|
min = std::min(min, delta_band);
|
||||||
|
|
||||||
|
if (delta_low <= min)
|
||||||
|
return 0;
|
||||||
|
if (band_cutoff_output_ && delta_band <= min)
|
||||||
|
return 1;
|
||||||
|
if (delta_high <= min)
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point<float> EqualizerResponse::getLowPosition() {
|
||||||
|
float cutoff_range = low_cutoff_->getMaximum() - low_cutoff_->getMinimum();
|
||||||
|
float min_cutoff = low_cutoff_->getMinimum();
|
||||||
|
float gain_range = max_db_ - min_db_;
|
||||||
|
|
||||||
|
float low_x = getWidth() * (low_cutoff_->getValue() - min_cutoff) / cutoff_range;
|
||||||
|
float low_y = getHeight() * (max_db_ - low_gain_->getValue()) / gain_range;
|
||||||
|
return Point<float>(low_x, low_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Point<float> EqualizerResponse::getBandPosition() {
|
||||||
|
if (band_cutoff_ == nullptr)
|
||||||
|
return Point<float>(0.0f, 0.0f);
|
||||||
|
|
||||||
|
float cutoff_range = band_cutoff_->getMaximum() - band_cutoff_->getMinimum();
|
||||||
|
float min_cutoff = band_cutoff_->getMinimum();
|
||||||
|
float gain_range = max_db_ - min_db_;
|
||||||
|
|
||||||
|
float band_x = getWidth() * (band_cutoff_->getValue() - min_cutoff) / cutoff_range;
|
||||||
|
float band_y = getHeight() * (max_db_ - band_gain_->getValue()) / gain_range;
|
||||||
|
return Point<float>(band_x, band_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Point<float> EqualizerResponse::getHighPosition() {
|
||||||
|
float cutoff_range = high_cutoff_->getMaximum() - high_cutoff_->getMinimum();
|
||||||
|
float min_cutoff = high_cutoff_->getMinimum();
|
||||||
|
float gain_range = max_db_ - min_db_;
|
||||||
|
|
||||||
|
float high_x = getWidth() * (high_cutoff_->getValue() - min_cutoff) / cutoff_range;
|
||||||
|
float high_y = getHeight() * (max_db_ - high_gain_->getValue()) / gain_range;
|
||||||
|
return Point<float>(high_x, high_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::computeFilterCoefficients() {
|
||||||
|
low_filter_state_.midi_cutoff = getOutputTotal(low_cutoff_output_, low_cutoff_);
|
||||||
|
low_filter_state_.resonance_percent = getOutputTotal(low_resonance_output_, low_resonance_);
|
||||||
|
low_filter_state_.gain = getOutputTotal(low_gain_output_, low_gain_);
|
||||||
|
if (high_pass_) {
|
||||||
|
low_filter_state_.style = vital::DigitalSvf::k12Db;
|
||||||
|
low_filter_state_.pass_blend = 2.0f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
low_filter_state_.style = vital::DigitalSvf::kShelving;
|
||||||
|
low_filter_state_.pass_blend = 0.0f;
|
||||||
|
}
|
||||||
|
low_filter_.setupFilter(low_filter_state_);
|
||||||
|
|
||||||
|
band_filter_state_.midi_cutoff = getOutputTotal(band_cutoff_output_, band_cutoff_);
|
||||||
|
band_filter_state_.resonance_percent = getOutputTotal(band_resonance_output_, band_resonance_);
|
||||||
|
band_filter_state_.gain = getOutputTotal(band_gain_output_, band_gain_);
|
||||||
|
band_filter_state_.pass_blend = 1.0f;
|
||||||
|
if (notch_)
|
||||||
|
band_filter_state_.style = vital::DigitalSvf::kNotchPassSwap;
|
||||||
|
else
|
||||||
|
band_filter_state_.style = vital::DigitalSvf::kShelving;
|
||||||
|
band_filter_.setupFilter(band_filter_state_);
|
||||||
|
|
||||||
|
high_filter_state_.midi_cutoff = getOutputTotal(high_cutoff_output_, high_cutoff_);
|
||||||
|
high_filter_state_.resonance_percent = getOutputTotal(high_resonance_output_, high_resonance_);
|
||||||
|
high_filter_state_.gain = getOutputTotal(high_gain_output_, high_gain_);
|
||||||
|
if (low_pass_) {
|
||||||
|
high_filter_state_.style = vital::DigitalSvf::k12Db;
|
||||||
|
high_filter_state_.pass_blend = 0.0f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
high_filter_state_.style = vital::DigitalSvf::kShelving;
|
||||||
|
high_filter_state_.pass_blend = 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
high_filter_.setupFilter(high_filter_state_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::moveFilterSettings(Point<float> position) {
|
||||||
|
if (current_cutoff_) {
|
||||||
|
float ratio = vital::utils::clamp(position.x / getWidth(), 0.0f, 1.0f);
|
||||||
|
float min = current_cutoff_->getMinimum();
|
||||||
|
float max = current_cutoff_->getMaximum();
|
||||||
|
float new_cutoff = ratio * (max - min) + min;
|
||||||
|
current_cutoff_->showPopup(true);
|
||||||
|
current_cutoff_->setValue(new_cutoff);
|
||||||
|
}
|
||||||
|
if (current_gain_) {
|
||||||
|
float local_position = position.y - 0.5f * db_buffer_ratio_ * getHeight();
|
||||||
|
float ratio = vital::utils::clamp(local_position / ((1.0f - db_buffer_ratio_) * getHeight()), 0.0f, 1.0f);
|
||||||
|
float min = current_gain_->getMinimum();
|
||||||
|
float max = current_gain_->getMaximum();
|
||||||
|
float new_db = ratio * (min - max) + max;
|
||||||
|
current_gain_->setValue(new_db);
|
||||||
|
current_gain_->showPopup(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
low_gain_->hidePopup(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::setLowSliders(SynthSlider *cutoff,
|
||||||
|
SynthSlider *resonance, SynthSlider *gain) {
|
||||||
|
float buffer = gain->getRange().getLength() * db_buffer_ratio_;
|
||||||
|
min_db_ = gain->getMinimum() - buffer;
|
||||||
|
max_db_ = gain->getMaximum() + buffer;
|
||||||
|
low_cutoff_ = cutoff;
|
||||||
|
low_resonance_ = resonance;
|
||||||
|
low_gain_ = gain;
|
||||||
|
low_cutoff_->addSliderListener(this);
|
||||||
|
if (low_resonance_)
|
||||||
|
low_resonance_->addSliderListener(this);
|
||||||
|
low_gain_->addSliderListener(this);
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::setBandSliders(SynthSlider *cutoff,
|
||||||
|
SynthSlider *resonance, SynthSlider *gain) {
|
||||||
|
band_cutoff_ = cutoff;
|
||||||
|
band_resonance_ = resonance;
|
||||||
|
band_gain_ = gain;
|
||||||
|
band_cutoff_->addSliderListener(this);
|
||||||
|
if (band_resonance_)
|
||||||
|
band_resonance_->addSliderListener(this);
|
||||||
|
band_gain_->addSliderListener(this);
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::setHighSliders(SynthSlider *cutoff,
|
||||||
|
SynthSlider *resonance, SynthSlider *gain) {
|
||||||
|
high_cutoff_ = cutoff;
|
||||||
|
high_resonance_ = resonance;
|
||||||
|
high_gain_ = gain;
|
||||||
|
high_cutoff_->addSliderListener(this);
|
||||||
|
if (high_resonance_)
|
||||||
|
high_resonance_->addSliderListener(this);
|
||||||
|
high_gain_->addSliderListener(this);
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::setSelectedBand(int selected_band) {
|
||||||
|
selected_band_ = selected_band;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::setActive(bool active) {
|
||||||
|
active_ = active;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::setHighPass(bool high_pass) {
|
||||||
|
high_pass_ = high_pass;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::setNotch(bool notch) {
|
||||||
|
notch_ = notch;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EqualizerResponse::setLowPass(bool low_pass) {
|
||||||
|
low_pass_ = low_pass;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::poly_float EqualizerResponse::getOutputTotal(vital::Output* output, Slider* slider) {
|
||||||
|
if (output == nullptr || slider == nullptr)
|
||||||
|
return 0.0f;
|
||||||
|
if (!active_ || !animate_ || !output->owner->enabled())
|
||||||
|
return slider->getValue();
|
||||||
|
return output->trigger_value;
|
||||||
|
}
|
||||||
162
src/interface/editor_components/equalizer_response.h
Normal file
162
src/interface/editor_components/equalizer_response.h
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "digital_svf.h"
|
||||||
|
#include "open_gl_line_renderer.h"
|
||||||
|
#include "open_gl_multi_quad.h"
|
||||||
|
#include "synth_slider.h"
|
||||||
|
|
||||||
|
class EqualizerResponse : public OpenGlLineRenderer, SynthSlider::SliderListener {
|
||||||
|
public:
|
||||||
|
static constexpr int kResolution = 128;
|
||||||
|
static constexpr int kViewSampleRate = 100000;
|
||||||
|
static constexpr float kDefaultDbBufferRatio = 0.2f;
|
||||||
|
static constexpr float kMouseMultiplier = 0.3f;
|
||||||
|
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
virtual ~Listener() = default;
|
||||||
|
|
||||||
|
virtual void lowBandSelected() = 0;
|
||||||
|
virtual void midBandSelected() = 0;
|
||||||
|
virtual void highBandSelected() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
EqualizerResponse();
|
||||||
|
~EqualizerResponse();
|
||||||
|
|
||||||
|
void initEq(const vital::output_map& mono_modulations);
|
||||||
|
void initReverb(const vital::output_map& mono_modulations);
|
||||||
|
|
||||||
|
virtual void init(OpenGlWrapper& open_gl) override;
|
||||||
|
virtual void render(OpenGlWrapper& open_gl, bool animate) override;
|
||||||
|
virtual void destroy(OpenGlWrapper& open_gl) override;
|
||||||
|
|
||||||
|
void setControlPointBounds(float selected_x, float selected_y,
|
||||||
|
float unselected_x1, float unselected_y1,
|
||||||
|
float unselected_x2, float unselected_y2);
|
||||||
|
void drawControlPoints(OpenGlWrapper& open_gl);
|
||||||
|
void drawResponse(OpenGlWrapper& open_gl, int index);
|
||||||
|
|
||||||
|
void computeFilterCoefficients();
|
||||||
|
void moveFilterSettings(Point<float> position);
|
||||||
|
|
||||||
|
void setLowSliders(SynthSlider* cutoff, SynthSlider* resonance, SynthSlider* gain);
|
||||||
|
void setBandSliders(SynthSlider* cutoff, SynthSlider* resonance, SynthSlider* gain);
|
||||||
|
void setHighSliders(SynthSlider* cutoff, SynthSlider* resonance, SynthSlider* gain);
|
||||||
|
void setSelectedBand(int selected_band);
|
||||||
|
|
||||||
|
Point<float> getLowPosition();
|
||||||
|
Point<float> getBandPosition();
|
||||||
|
Point<float> getHighPosition();
|
||||||
|
|
||||||
|
void resized() override {
|
||||||
|
OpenGlLineRenderer::resized();
|
||||||
|
|
||||||
|
unselected_points_.setBounds(getLocalBounds());
|
||||||
|
selected_point_.setBounds(getLocalBounds());
|
||||||
|
dragging_point_.setBounds(getLocalBounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
void paintBackground(Graphics& g) override;
|
||||||
|
void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override;
|
||||||
|
void mouseDown(const MouseEvent& e) override;
|
||||||
|
void mouseDrag(const MouseEvent& e) override;
|
||||||
|
void mouseUp(const MouseEvent& e) override;
|
||||||
|
void mouseExit(const MouseEvent& e) override;
|
||||||
|
|
||||||
|
int getHoveredBand(const MouseEvent& e);
|
||||||
|
|
||||||
|
void setActive(bool active);
|
||||||
|
void setHighPass(bool high_pass);
|
||||||
|
void setNotch(bool notch);
|
||||||
|
void setLowPass(bool high_pass);
|
||||||
|
void setDbBufferRatio(float ratio) { db_buffer_ratio_ = ratio; }
|
||||||
|
void setDrawFrequencyLines(bool draw_lines) { draw_frequency_lines_ = draw_lines; }
|
||||||
|
|
||||||
|
void addListener(Listener* listener) { listeners_.push_back(listener); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
vital::poly_float getOutputTotal(vital::Output* output, Slider* slider);
|
||||||
|
|
||||||
|
int resolution_;
|
||||||
|
bool active_;
|
||||||
|
bool high_pass_;
|
||||||
|
bool notch_;
|
||||||
|
bool low_pass_;
|
||||||
|
bool animate_;
|
||||||
|
bool draw_frequency_lines_;
|
||||||
|
int selected_band_;
|
||||||
|
float db_buffer_ratio_;
|
||||||
|
float min_db_;
|
||||||
|
float max_db_;
|
||||||
|
|
||||||
|
OpenGlMultiQuad unselected_points_;
|
||||||
|
OpenGlQuad selected_point_;
|
||||||
|
OpenGlQuad dragging_point_;
|
||||||
|
|
||||||
|
vital::DigitalSvf low_filter_;
|
||||||
|
vital::DigitalSvf band_filter_;
|
||||||
|
vital::DigitalSvf high_filter_;
|
||||||
|
|
||||||
|
vital::SynthFilter::FilterState low_filter_state_;
|
||||||
|
vital::SynthFilter::FilterState band_filter_state_;
|
||||||
|
vital::SynthFilter::FilterState high_filter_state_;
|
||||||
|
|
||||||
|
SynthSlider* low_cutoff_;
|
||||||
|
SynthSlider* low_resonance_;
|
||||||
|
SynthSlider* low_gain_;
|
||||||
|
SynthSlider* band_cutoff_;
|
||||||
|
SynthSlider* band_resonance_;
|
||||||
|
SynthSlider* band_gain_;
|
||||||
|
SynthSlider* high_cutoff_;
|
||||||
|
SynthSlider* high_resonance_;
|
||||||
|
SynthSlider* high_gain_;
|
||||||
|
|
||||||
|
vital::Output* low_cutoff_output_;
|
||||||
|
vital::Output* low_resonance_output_;
|
||||||
|
vital::Output* low_gain_output_;
|
||||||
|
vital::Output* band_cutoff_output_;
|
||||||
|
vital::Output* band_resonance_output_;
|
||||||
|
vital::Output* band_gain_output_;
|
||||||
|
vital::Output* high_cutoff_output_;
|
||||||
|
vital::Output* high_resonance_output_;
|
||||||
|
vital::Output* high_gain_output_;
|
||||||
|
|
||||||
|
SynthSlider* current_cutoff_;
|
||||||
|
SynthSlider* current_gain_;
|
||||||
|
|
||||||
|
std::unique_ptr<float[]> line_data_;
|
||||||
|
OpenGLShaderProgram* shader_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Attribute> position_attribute_;
|
||||||
|
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> midi_cutoff_uniform_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> resonance_uniform_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> low_amount_uniform_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> band_amount_uniform_;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> high_amount_uniform_;
|
||||||
|
|
||||||
|
GLuint vertex_array_object_;
|
||||||
|
GLuint line_buffer_;
|
||||||
|
GLuint response_buffer_;
|
||||||
|
std::vector<Listener*> listeners_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EqualizerResponse)
|
||||||
|
};
|
||||||
|
|
||||||
635
src/interface/editor_components/filter_response.cpp
Normal file
635
src/interface/editor_components/filter_response.cpp
Normal file
|
|
@ -0,0 +1,635 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "filter_response.h"
|
||||||
|
|
||||||
|
#include "comb_filter.h"
|
||||||
|
#include "skin.h"
|
||||||
|
#include "synth_constants.h"
|
||||||
|
#include "shaders.h"
|
||||||
|
#include "synth_slider.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
FilterResponse::FilterShader getShaderForModel(vital::constants::FilterModel model, int style) {
|
||||||
|
switch (model) {
|
||||||
|
case vital::constants::kAnalog:
|
||||||
|
return FilterResponse::kAnalog;
|
||||||
|
case vital::constants::kComb: {
|
||||||
|
vital::CombFilter::FeedbackStyle feedback_style = vital::CombFilter::getFeedbackStyle(style);
|
||||||
|
if (feedback_style == vital::CombFilter::kComb)
|
||||||
|
return FilterResponse::kComb;
|
||||||
|
if (feedback_style == vital::CombFilter::kPositiveFlange)
|
||||||
|
return FilterResponse::kPositiveFlange;
|
||||||
|
return FilterResponse::kNegativeFlange;
|
||||||
|
}
|
||||||
|
case vital::constants::kDiode:
|
||||||
|
return FilterResponse::kDiode;
|
||||||
|
case vital::constants::kDirty:
|
||||||
|
return FilterResponse::kDirty;
|
||||||
|
case vital::constants::kLadder:
|
||||||
|
return FilterResponse::kLadder;
|
||||||
|
case vital::constants::kPhase:
|
||||||
|
return FilterResponse::kPhase;
|
||||||
|
case vital::constants::kFormant:
|
||||||
|
return FilterResponse::kFormant;
|
||||||
|
case vital::constants::kDigital:
|
||||||
|
return FilterResponse::kDigital;
|
||||||
|
default:
|
||||||
|
VITAL_ASSERT(false);
|
||||||
|
return FilterResponse::kNumFilterShaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shaders::VertexShader getVertexShader(FilterResponse::FilterShader shader) {
|
||||||
|
switch (shader) {
|
||||||
|
case FilterResponse::kAnalog:
|
||||||
|
return Shaders::kAnalogFilterResponseVertex;
|
||||||
|
case FilterResponse::kComb:
|
||||||
|
return Shaders::kCombFilterResponseVertex;
|
||||||
|
case FilterResponse::kPositiveFlange:
|
||||||
|
return Shaders::kPositiveFlangeFilterResponseVertex;
|
||||||
|
case FilterResponse::kNegativeFlange:
|
||||||
|
return Shaders::kNegativeFlangeFilterResponseVertex;
|
||||||
|
case FilterResponse::kDiode:
|
||||||
|
return Shaders::kDiodeFilterResponseVertex;
|
||||||
|
case FilterResponse::kDirty:
|
||||||
|
return Shaders::kDirtyFilterResponseVertex;
|
||||||
|
case FilterResponse::kLadder:
|
||||||
|
return Shaders::kLadderFilterResponseVertex;
|
||||||
|
case FilterResponse::kPhase:
|
||||||
|
return Shaders::kPhaserFilterResponseVertex;
|
||||||
|
case FilterResponse::kFormant:
|
||||||
|
return Shaders::kFormantFilterResponseVertex;
|
||||||
|
case FilterResponse::kDigital:
|
||||||
|
return Shaders::kDigitalFilterResponseVertex;
|
||||||
|
default:
|
||||||
|
VITAL_ASSERT(false);
|
||||||
|
return Shaders::kNumVertexShaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<vital::Output*, vital::Output*> getOutputs(const vital::output_map& mono_modulations,
|
||||||
|
const std::string& name) {
|
||||||
|
return {
|
||||||
|
mono_modulations.at(name),
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<vital::Output*, vital::Output*> getOutputs(const vital::output_map& mono_modulations,
|
||||||
|
const vital::output_map& poly_modulations,
|
||||||
|
const std::string& name) {
|
||||||
|
return {
|
||||||
|
mono_modulations.at(name),
|
||||||
|
poly_modulations.at(name)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
FilterResponse::FilterResponse() : OpenGlLineRenderer(kResolution), phaser_filter_(false) {
|
||||||
|
setFill(true);
|
||||||
|
setFillCenter(-1.0f);
|
||||||
|
active_ = false;
|
||||||
|
animate_ = false;
|
||||||
|
mix_ = 1.0f;
|
||||||
|
current_resonance_value_ = 0.0;
|
||||||
|
current_cutoff_value_ = 0.0;
|
||||||
|
current_formant_x_value_ = 0.0;
|
||||||
|
current_formant_y_value_ = 0.0;
|
||||||
|
|
||||||
|
cutoff_slider_ = nullptr;
|
||||||
|
resonance_slider_ = nullptr;
|
||||||
|
formant_x_slider_ = nullptr;
|
||||||
|
formant_y_slider_ = nullptr;
|
||||||
|
filter_mix_slider_ = nullptr;
|
||||||
|
blend_slider_ = nullptr;
|
||||||
|
transpose_slider_ = nullptr;
|
||||||
|
formant_transpose_slider_ = nullptr;
|
||||||
|
formant_resonance_slider_ = nullptr;
|
||||||
|
formant_spread_slider_ = nullptr;
|
||||||
|
last_filter_style_ = 0;
|
||||||
|
last_filter_model_ = static_cast<vital::constants::FilterModel>(0);
|
||||||
|
filter_model_ = static_cast<vital::constants::FilterModel>(0);
|
||||||
|
|
||||||
|
line_data_ = std::make_unique<float[]>(2 * kResolution);
|
||||||
|
line_buffer_ = 0;
|
||||||
|
response_buffer_ = 0;
|
||||||
|
vertex_array_object_ = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < kResolution; ++i) {
|
||||||
|
float t = i / (kResolution - 1.0f);
|
||||||
|
line_data_[2 * i] = 2.0f * t - 1.0f;
|
||||||
|
line_data_[2 * i + 1] = (i / kCombAlternatePeriod) % 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
analog_filter_.setSampleRate(kDefaultVisualSampleRate);
|
||||||
|
comb_filter_.setSampleRate(kDefaultVisualSampleRate);
|
||||||
|
digital_filter_.setSampleRate(kDefaultVisualSampleRate);
|
||||||
|
diode_filter_.setSampleRate(kDefaultVisualSampleRate);
|
||||||
|
dirty_filter_.setSampleRate(kDefaultVisualSampleRate);
|
||||||
|
formant_filter_.setSampleRate(kDefaultVisualSampleRate);
|
||||||
|
ladder_filter_.setSampleRate(kDefaultVisualSampleRate);
|
||||||
|
phaser_filter_.setSampleRate(kDefaultVisualSampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterResponse::FilterResponse(String suffix, const vital::output_map& mono_modulations) : FilterResponse() {
|
||||||
|
std::string prefix = std::string("filter_") + suffix.toStdString() +"_";
|
||||||
|
|
||||||
|
filter_mix_outputs_ = getOutputs(mono_modulations, prefix + "mix");
|
||||||
|
midi_cutoff_outputs_ = getOutputs(mono_modulations, prefix + "cutoff");
|
||||||
|
resonance_outputs_ = getOutputs(mono_modulations, prefix + "resonance");
|
||||||
|
blend_outputs_ = getOutputs(mono_modulations, prefix + "blend");
|
||||||
|
transpose_outputs_ = getOutputs(mono_modulations, prefix + "blend_transpose");
|
||||||
|
interpolate_x_outputs_ = getOutputs(mono_modulations, prefix + "formant_x");
|
||||||
|
interpolate_y_outputs_ = getOutputs(mono_modulations, prefix + "formant_y");
|
||||||
|
formant_transpose_outputs_ = getOutputs(mono_modulations, prefix + "formant_transpose");
|
||||||
|
formant_resonance_outputs_ = getOutputs(mono_modulations, prefix + "formant_resonance");
|
||||||
|
formant_spread_outputs_ = getOutputs(mono_modulations, prefix + "formant_spread");
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterResponse::FilterResponse(int index, const vital::output_map& mono_modulations,
|
||||||
|
const vital::output_map& poly_modulations) : FilterResponse() {
|
||||||
|
std::string prefix = std::string("filter_") + std::to_string(index) + "_";
|
||||||
|
|
||||||
|
filter_mix_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "mix");
|
||||||
|
midi_cutoff_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "cutoff");
|
||||||
|
resonance_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "resonance");
|
||||||
|
blend_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "blend");
|
||||||
|
transpose_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "blend_transpose");
|
||||||
|
interpolate_x_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_x");
|
||||||
|
interpolate_y_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_y");
|
||||||
|
formant_transpose_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_transpose");
|
||||||
|
formant_resonance_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_resonance");
|
||||||
|
formant_spread_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_spread");
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterResponse::~FilterResponse() { }
|
||||||
|
|
||||||
|
void FilterResponse::init(OpenGlWrapper& open_gl) {
|
||||||
|
OpenGlLineRenderer::init(open_gl);
|
||||||
|
|
||||||
|
const GLchar* varyings[] = { "response_out" };
|
||||||
|
open_gl.context.extensions.glGenVertexArrays(1, &vertex_array_object_);
|
||||||
|
open_gl.context.extensions.glBindVertexArray(vertex_array_object_);
|
||||||
|
|
||||||
|
GLsizeiptr data_size = static_cast<GLsizeiptr>(kResolution * sizeof(float));
|
||||||
|
open_gl.context.extensions.glGenBuffers(1, &line_buffer_);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, line_buffer_);
|
||||||
|
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, 2 * data_size, line_data_.get(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glGenBuffers(1, &response_buffer_);
|
||||||
|
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, response_buffer_);
|
||||||
|
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, data_size, nullptr, GL_STATIC_READ);
|
||||||
|
|
||||||
|
for (int i = 0; i < kNumFilterShaders; ++i) {
|
||||||
|
Shaders::VertexShader vertex_shader = getVertexShader(static_cast<FilterShader>(i));
|
||||||
|
OpenGLShaderProgram* shader = open_gl.shaders->getShaderProgram(vertex_shader, Shaders::kColorFragment, varyings);
|
||||||
|
shaders_[i].shader = shader;
|
||||||
|
|
||||||
|
shader->use();
|
||||||
|
shaders_[i].position = getAttribute(open_gl, *shader, "position");
|
||||||
|
|
||||||
|
shaders_[i].mix = getUniform(open_gl, *shader, "mix");
|
||||||
|
shaders_[i].midi_cutoff = getUniform(open_gl, *shader, "midi_cutoff");
|
||||||
|
shaders_[i].resonance = getUniform(open_gl, *shader, "resonance");
|
||||||
|
shaders_[i].drive = getUniform(open_gl, *shader, "drive");
|
||||||
|
shaders_[i].db24 = getUniform(open_gl, *shader, "db24");
|
||||||
|
|
||||||
|
shaders_[i].formant_cutoff = getUniform(open_gl, *shader, "formant_cutoff");
|
||||||
|
shaders_[i].formant_resonance = getUniform(open_gl, *shader, "formant_resonance");
|
||||||
|
shaders_[i].formant_spread = getUniform(open_gl, *shader, "formant_spread");
|
||||||
|
shaders_[i].formant_low = getUniform(open_gl, *shader, "low");
|
||||||
|
shaders_[i].formant_band = getUniform(open_gl, *shader, "band");
|
||||||
|
shaders_[i].formant_high = getUniform(open_gl, *shader, "high");
|
||||||
|
|
||||||
|
for (int s = 0; s < FilterResponseShader::kMaxStages; ++s) {
|
||||||
|
String stage = String("stage") + String(s);
|
||||||
|
shaders_[i].stages[s] = getUniform(open_gl, *shader, stage.toRawUTF8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::render(OpenGlWrapper& open_gl, bool animate) {
|
||||||
|
animate_ = animate;
|
||||||
|
drawFilterResponse(open_gl);
|
||||||
|
renderCorners(open_gl, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::destroy(OpenGlWrapper& open_gl) {
|
||||||
|
OpenGlLineRenderer::destroy(open_gl);
|
||||||
|
|
||||||
|
open_gl.context.extensions.glDeleteBuffers(1, &line_buffer_);
|
||||||
|
open_gl.context.extensions.glDeleteBuffers(1, &response_buffer_);
|
||||||
|
|
||||||
|
vertex_array_object_ = 0;
|
||||||
|
line_buffer_ = 0;
|
||||||
|
response_buffer_ = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < kNumFilterShaders; ++i) {
|
||||||
|
shaders_[i].shader = nullptr;
|
||||||
|
shaders_[i].position = nullptr;
|
||||||
|
|
||||||
|
shaders_[i].mix = nullptr;
|
||||||
|
shaders_[i].midi_cutoff = nullptr;
|
||||||
|
shaders_[i].resonance = nullptr;
|
||||||
|
shaders_[i].drive = nullptr;
|
||||||
|
shaders_[i].db24 = nullptr;
|
||||||
|
|
||||||
|
shaders_[i].formant_cutoff = nullptr;
|
||||||
|
shaders_[i].formant_resonance = nullptr;
|
||||||
|
shaders_[i].formant_spread = nullptr;
|
||||||
|
shaders_[i].formant_low = nullptr;
|
||||||
|
shaders_[i].formant_band = nullptr;
|
||||||
|
shaders_[i].formant_high = nullptr;
|
||||||
|
|
||||||
|
for (int s = 0; s < FilterResponseShader::kMaxStages; ++s)
|
||||||
|
shaders_[i].stages[s] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::paintBackground(Graphics& g) {
|
||||||
|
g.fillAll(findColour(Skin::kWidgetBackground, true));
|
||||||
|
|
||||||
|
line_left_color_ = findColour(Skin::kWidgetPrimary1, true);
|
||||||
|
line_right_color_ = findColour(Skin::kWidgetPrimary2, true);
|
||||||
|
line_disabled_color_ = findColour(Skin::kWidgetPrimaryDisabled, true);
|
||||||
|
fill_left_color_ = findColour(Skin::kWidgetSecondary1, true);
|
||||||
|
fill_right_color_ = findColour(Skin::kWidgetSecondary2, true);
|
||||||
|
fill_disabled_color_ = findColour(Skin::kWidgetSecondaryDisabled, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::setFilterSettingsFromPosition(Point<int> position) {
|
||||||
|
Point<int> delta = position - last_mouse_position_;
|
||||||
|
last_mouse_position_ = position;
|
||||||
|
double width = getWidth();
|
||||||
|
double height = getHeight();
|
||||||
|
current_cutoff_value_ += delta.x * cutoff_slider_->getRange().getLength() / width;
|
||||||
|
current_formant_x_value_ += delta.x * formant_x_slider_->getRange().getLength() / width;
|
||||||
|
current_resonance_value_ -= delta.y * resonance_slider_->getRange().getLength() / height;
|
||||||
|
current_formant_y_value_ -= delta.y * formant_y_slider_->getRange().getLength() / height;
|
||||||
|
current_cutoff_value_ = cutoff_slider_->getRange().clipValue(current_cutoff_value_);
|
||||||
|
current_formant_x_value_ = formant_x_slider_->getRange().clipValue(current_formant_x_value_);
|
||||||
|
current_resonance_value_ = resonance_slider_->getRange().clipValue(current_resonance_value_);
|
||||||
|
current_formant_y_value_ = formant_y_slider_->getRange().clipValue(current_formant_y_value_);
|
||||||
|
|
||||||
|
if (filter_model_ == vital::constants::kFormant) {
|
||||||
|
formant_x_slider_->setValue(current_formant_x_value_);
|
||||||
|
formant_x_slider_->showPopup(true);
|
||||||
|
formant_y_slider_->setValue(current_formant_y_value_);
|
||||||
|
formant_y_slider_->showPopup(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cutoff_slider_->setValue(current_cutoff_value_);
|
||||||
|
cutoff_slider_->showPopup(true);
|
||||||
|
resonance_slider_->setValue(current_resonance_value_);
|
||||||
|
resonance_slider_->showPopup(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::mouseDown(const MouseEvent& e) {
|
||||||
|
last_mouse_position_ = e.getPosition();
|
||||||
|
current_resonance_value_ = resonance_slider_->getValue();
|
||||||
|
current_cutoff_value_ = cutoff_slider_->getValue();
|
||||||
|
current_formant_x_value_ = formant_x_slider_->getValue();
|
||||||
|
current_formant_y_value_ = formant_y_slider_->getValue();
|
||||||
|
|
||||||
|
if (filter_model_ == vital::constants::kFormant) {
|
||||||
|
formant_x_slider_->showPopup(true);
|
||||||
|
formant_y_slider_->showPopup(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cutoff_slider_->showPopup(true);
|
||||||
|
resonance_slider_->showPopup(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::mouseDrag(const MouseEvent& e) {
|
||||||
|
setFilterSettingsFromPosition(e.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::mouseExit(const MouseEvent& e) {
|
||||||
|
cutoff_slider_->hidePopup(true);
|
||||||
|
resonance_slider_->hidePopup(false);
|
||||||
|
OpenGlLineRenderer::mouseExit(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) {
|
||||||
|
MouseWheelDetails horizontal_details = wheel;
|
||||||
|
horizontal_details.deltaY = 0.0f;
|
||||||
|
MouseWheelDetails vertical_details = wheel;
|
||||||
|
vertical_details.deltaX = 0.0f;
|
||||||
|
|
||||||
|
if (filter_model_ == vital::constants::kFormant) {
|
||||||
|
formant_x_slider_->mouseWheelMove(e, horizontal_details);
|
||||||
|
formant_y_slider_->mouseWheelMove(e, vertical_details);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cutoff_slider_->mouseWheelMove(e, horizontal_details);
|
||||||
|
resonance_slider_->mouseWheelMove(e, vertical_details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline vital::poly_float FilterResponse::getOutputsTotal(
|
||||||
|
std::pair<vital::Output*, vital::Output*> outputs, vital::poly_float default_value) {
|
||||||
|
if (!active_ || !animate_ || !outputs.first->owner->enabled())
|
||||||
|
return default_value;
|
||||||
|
if (outputs.second == nullptr || num_voices_readout_ == nullptr || num_voices_readout_->value()[0] <= 0.0f)
|
||||||
|
return outputs.first->trigger_value;
|
||||||
|
return outputs.first->trigger_value + outputs.second->trigger_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FilterResponse::setupFilterState(vital::constants::FilterModel model) {
|
||||||
|
vital::poly_float midi_cutoff = getOutputsTotal(midi_cutoff_outputs_, cutoff_slider_->getValue());
|
||||||
|
midi_cutoff = vital::utils::max(midi_cutoff, 0.0f);
|
||||||
|
vital::poly_float mix = getOutputsTotal(filter_mix_outputs_, filter_mix_slider_->getValue());
|
||||||
|
mix = vital::utils::clamp(mix, 0.0f, 1.0f);
|
||||||
|
vital::poly_float resonance_percent = getOutputsTotal(resonance_outputs_, resonance_slider_->getValue());
|
||||||
|
vital::poly_float pass_blend = getOutputsTotal(blend_outputs_, blend_slider_->getValue());
|
||||||
|
pass_blend = vital::utils::clamp(pass_blend, 0.0f, 2.0f);
|
||||||
|
vital::poly_float transpose = getOutputsTotal(transpose_outputs_, transpose_slider_->getValue());
|
||||||
|
vital::poly_float interpolate_x = getOutputsTotal(interpolate_x_outputs_, formant_x_slider_->getValue());
|
||||||
|
vital::poly_float interpolate_y = getOutputsTotal(interpolate_y_outputs_, formant_y_slider_->getValue());
|
||||||
|
|
||||||
|
if (model == vital::constants::kFormant) {
|
||||||
|
transpose = getOutputsTotal(formant_transpose_outputs_, formant_transpose_slider_->getValue());
|
||||||
|
resonance_percent = getOutputsTotal(formant_resonance_outputs_, formant_resonance_slider_->getValue());
|
||||||
|
pass_blend = getOutputsTotal(formant_spread_outputs_, formant_spread_slider_->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::poly_mask equal = vital::constants::kFullMask;
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.midi_cutoff, midi_cutoff);
|
||||||
|
equal = equal & vital::poly_float::equal(mix_, mix);
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.resonance_percent, resonance_percent);
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.pass_blend, pass_blend);
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.transpose, transpose);
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.interpolate_x, interpolate_x);
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.interpolate_y, interpolate_y);
|
||||||
|
|
||||||
|
filter_state_.midi_cutoff = midi_cutoff;
|
||||||
|
mix_ = mix;
|
||||||
|
filter_state_.resonance_percent = resonance_percent;
|
||||||
|
filter_state_.pass_blend = pass_blend;
|
||||||
|
filter_state_.transpose = transpose;
|
||||||
|
filter_state_.interpolate_x = interpolate_x;
|
||||||
|
filter_state_.interpolate_y = interpolate_y;
|
||||||
|
|
||||||
|
bool new_type = last_filter_model_ != model || last_filter_style_ != filter_state_.style;
|
||||||
|
last_filter_style_ = filter_state_.style;
|
||||||
|
last_filter_model_ = model;
|
||||||
|
|
||||||
|
return (~equal).anyMask() || new_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FilterResponse::isStereoState() {
|
||||||
|
vital::poly_mask equal = vital::constants::kFullMask;
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.midi_cutoff,
|
||||||
|
vital::utils::swapStereo(filter_state_.midi_cutoff));
|
||||||
|
equal = equal & vital::poly_float::equal(mix_, vital::utils::swapStereo(mix_));
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.resonance_percent,
|
||||||
|
vital::utils::swapStereo(filter_state_.resonance_percent));
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.pass_blend,
|
||||||
|
vital::utils::swapStereo(filter_state_.pass_blend));
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.transpose,
|
||||||
|
vital::utils::swapStereo(filter_state_.transpose));
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.interpolate_x,
|
||||||
|
vital::utils::swapStereo(filter_state_.interpolate_x));
|
||||||
|
equal = equal & vital::poly_float::equal(filter_state_.interpolate_y,
|
||||||
|
vital::utils::swapStereo(filter_state_.interpolate_y));
|
||||||
|
|
||||||
|
return (~equal).anyMask();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::loadShader(FilterShader shader, vital::constants::FilterModel model, int index) {
|
||||||
|
if (model == vital::constants::kAnalog) {
|
||||||
|
analog_filter_.setupFilter(filter_state_);
|
||||||
|
shaders_[shader].shader->use();
|
||||||
|
float resonance = vital::utils::clamp(analog_filter_.getResonance()[index], 0.0f, 2.0f);
|
||||||
|
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
|
||||||
|
shaders_[shader].resonance->set(resonance);
|
||||||
|
shaders_[shader].drive->set(analog_filter_.getDrive()[index]);
|
||||||
|
shaders_[shader].db24->set(filter_state_.style != vital::SynthFilter::k12Db ? 1.0f : 0.0f);
|
||||||
|
|
||||||
|
shaders_[shader].stages[0]->set(analog_filter_.getLowAmount()[index]);
|
||||||
|
shaders_[shader].stages[1]->set(analog_filter_.getBandAmount()[index]);
|
||||||
|
shaders_[shader].stages[2]->set(analog_filter_.getHighAmount()[index]);
|
||||||
|
shaders_[shader].stages[3]->set(analog_filter_.getLowAmount24(filter_state_.style)[index]);
|
||||||
|
shaders_[shader].stages[4]->set(analog_filter_.getHighAmount24(filter_state_.style)[index]);
|
||||||
|
}
|
||||||
|
else if (model == vital::constants::kComb) {
|
||||||
|
comb_filter_.setupFilter(filter_state_);
|
||||||
|
shaders_[shader].shader->use();
|
||||||
|
float resonance = vital::utils::clamp(comb_filter_.getResonance()[index], -0.99f, 0.99f);
|
||||||
|
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
|
||||||
|
shaders_[shader].resonance->set(resonance);
|
||||||
|
shaders_[shader].drive->set(comb_filter_.getDrive()[index]);
|
||||||
|
|
||||||
|
shaders_[shader].stages[0]->set(comb_filter_.getLowAmount()[index]);
|
||||||
|
shaders_[shader].stages[1]->set(comb_filter_.getHighAmount()[index]);
|
||||||
|
shaders_[shader].stages[2]->set(comb_filter_.getFilterMidiCutoff()[index]);
|
||||||
|
shaders_[shader].stages[3]->set(comb_filter_.getFilter2MidiCutoff()[index]);
|
||||||
|
}
|
||||||
|
else if (model == vital::constants::kDigital) {
|
||||||
|
digital_filter_.setupFilter(filter_state_);
|
||||||
|
shaders_[shader].shader->use();
|
||||||
|
float resonance = vital::utils::clamp(digital_filter_.getResonance()[index], 0.0f, 2.0f);
|
||||||
|
shaders_[shader].midi_cutoff->set(digital_filter_.getMidiCutoff()[index]);
|
||||||
|
shaders_[shader].resonance->set(resonance);
|
||||||
|
shaders_[shader].drive->set(digital_filter_.getDrive()[index]);
|
||||||
|
shaders_[shader].db24->set(filter_state_.style != vital::SynthFilter::k12Db ? 1.0f : 0.0f);
|
||||||
|
|
||||||
|
shaders_[shader].stages[0]->set(digital_filter_.getLowAmount()[index]);
|
||||||
|
shaders_[shader].stages[1]->set(digital_filter_.getBandAmount()[index]);
|
||||||
|
shaders_[shader].stages[2]->set(digital_filter_.getHighAmount()[index]);
|
||||||
|
shaders_[shader].stages[3]->set(digital_filter_.getLowAmount24(filter_state_.style)[index]);
|
||||||
|
shaders_[shader].stages[4]->set(digital_filter_.getHighAmount24(filter_state_.style)[index]);
|
||||||
|
}
|
||||||
|
else if (model == vital::constants::kDiode) {
|
||||||
|
diode_filter_.setupFilter(filter_state_);
|
||||||
|
shaders_[shader].shader->use();
|
||||||
|
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
|
||||||
|
shaders_[shader].resonance->set(diode_filter_.getResonance()[index]);
|
||||||
|
shaders_[shader].drive->set(diode_filter_.getDrive()[index]);
|
||||||
|
shaders_[shader].db24->set(diode_filter_.getHighPassAmount()[index]);
|
||||||
|
shaders_[shader].stages[0]->set(diode_filter_.getHighPassRatio()[index]);
|
||||||
|
}
|
||||||
|
else if (model == vital::constants::kDirty) {
|
||||||
|
dirty_filter_.setupFilter(filter_state_);
|
||||||
|
shaders_[shader].shader->use();
|
||||||
|
float resonance = vital::utils::clamp(dirty_filter_.getResonance()[index], 0.0f, 2.0f);
|
||||||
|
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
|
||||||
|
shaders_[shader].resonance->set(resonance);
|
||||||
|
shaders_[shader].drive->set(dirty_filter_.getDrive()[index]);
|
||||||
|
shaders_[shader].db24->set(filter_state_.style != vital::SynthFilter::k12Db ? 1.0f : 0.0f);
|
||||||
|
|
||||||
|
shaders_[shader].stages[0]->set(dirty_filter_.getLowAmount()[index]);
|
||||||
|
shaders_[shader].stages[1]->set(dirty_filter_.getBandAmount()[index]);
|
||||||
|
shaders_[shader].stages[2]->set(dirty_filter_.getHighAmount()[index]);
|
||||||
|
shaders_[shader].stages[3]->set(dirty_filter_.getLowAmount24(filter_state_.style)[index]);
|
||||||
|
shaders_[shader].stages[4]->set(dirty_filter_.getHighAmount24(filter_state_.style)[index]);
|
||||||
|
}
|
||||||
|
else if (model == vital::constants::kFormant) {
|
||||||
|
shaders_[shader].shader->use();
|
||||||
|
|
||||||
|
vital::DigitalSvf* formant0 = formant_filter_.getFormant(0);
|
||||||
|
vital::DigitalSvf* formant1 = formant_filter_.getFormant(1);
|
||||||
|
vital::DigitalSvf* formant2 = formant_filter_.getFormant(2);
|
||||||
|
vital::DigitalSvf* formant3 = formant_filter_.getFormant(3);
|
||||||
|
|
||||||
|
formant_filter_.setupFilter(filter_state_);
|
||||||
|
shaders_[shader].formant_cutoff->set(formant0->getMidiCutoff()[index], formant1->getMidiCutoff()[index],
|
||||||
|
formant2->getMidiCutoff()[index], formant3->getMidiCutoff()[index]);
|
||||||
|
shaders_[shader].formant_resonance->set(formant0->getResonance()[index], formant1->getResonance()[index],
|
||||||
|
formant2->getResonance()[index], formant3->getResonance()[index]);
|
||||||
|
vital::poly_float drive0 = formant0->getDrive();
|
||||||
|
vital::poly_float drive1 = formant1->getDrive();
|
||||||
|
vital::poly_float drive2 = formant2->getDrive();
|
||||||
|
vital::poly_float drive3 = formant3->getDrive();
|
||||||
|
vital::poly_float low0 = formant0->getLowAmount() * drive0;
|
||||||
|
vital::poly_float low1 = formant1->getLowAmount() * drive1;
|
||||||
|
vital::poly_float low2 = formant2->getLowAmount() * drive2;
|
||||||
|
vital::poly_float low3 = formant3->getLowAmount() * drive3;
|
||||||
|
vital::poly_float band0 = formant0->getBandAmount() * drive0;
|
||||||
|
vital::poly_float band1 = formant1->getBandAmount() * drive1;
|
||||||
|
vital::poly_float band2 = formant2->getBandAmount() * drive2;
|
||||||
|
vital::poly_float band3 = formant3->getBandAmount() * drive3;
|
||||||
|
vital::poly_float high0 = formant0->getHighAmount() * drive0;
|
||||||
|
vital::poly_float high1 = formant1->getHighAmount() * drive1;
|
||||||
|
vital::poly_float high2 = formant2->getHighAmount() * drive2;
|
||||||
|
vital::poly_float high3 = formant3->getHighAmount() * drive3;
|
||||||
|
|
||||||
|
shaders_[shader].formant_low->set(low0[index], low1[index], low2[index], low3[index]);
|
||||||
|
shaders_[shader].formant_band->set(band0[index], band1[index], band2[index], band3[index]);
|
||||||
|
shaders_[shader].formant_high->set(high0[index], high1[index], high2[index], high3[index]);
|
||||||
|
}
|
||||||
|
else if (model == vital::constants::kLadder) {
|
||||||
|
ladder_filter_.setupFilter(filter_state_);
|
||||||
|
shaders_[shader].shader->use();
|
||||||
|
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
|
||||||
|
shaders_[shader].resonance->set(ladder_filter_.getResonance()[index]);
|
||||||
|
shaders_[shader].drive->set(ladder_filter_.getDrive()[index]);
|
||||||
|
|
||||||
|
for (int s = 0; s < FilterResponseShader::kMaxStages; ++s)
|
||||||
|
shaders_[shader].stages[s]->set(ladder_filter_.getStageScale(s)[index]);
|
||||||
|
}
|
||||||
|
else if (model == vital::constants::kPhase) {
|
||||||
|
phaser_filter_.setupFilter(filter_state_);
|
||||||
|
shaders_[shader].shader->use();
|
||||||
|
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
|
||||||
|
shaders_[shader].resonance->set(phaser_filter_.getResonance()[index]);
|
||||||
|
shaders_[shader].db24->set(filter_state_.style != vital::SynthFilter::k12Db ? 1.0f : 0.0f);
|
||||||
|
|
||||||
|
shaders_[shader].stages[0]->set(phaser_filter_.getPeak1Amount()[index]);
|
||||||
|
shaders_[shader].stages[1]->set(phaser_filter_.getPeak3Amount()[index]);
|
||||||
|
shaders_[shader].stages[2]->set(phaser_filter_.getPeak5Amount()[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
shaders_[shader].mix->set(mix_[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::bind(FilterShader shader, OpenGLContext& open_gl_context) {
|
||||||
|
open_gl_context.extensions.glBindVertexArray(vertex_array_object_);
|
||||||
|
open_gl_context.extensions.glBindBuffer(GL_ARRAY_BUFFER, line_buffer_);
|
||||||
|
|
||||||
|
OpenGLShaderProgram::Attribute* position = shaders_[shader].position.get();
|
||||||
|
open_gl_context.extensions.glVertexAttribPointer(position->attributeID, 2, GL_FLOAT,
|
||||||
|
GL_FALSE, 2 * sizeof(float), nullptr);
|
||||||
|
open_gl_context.extensions.glEnableVertexAttribArray(position->attributeID);
|
||||||
|
|
||||||
|
open_gl_context.extensions.glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, response_buffer_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::unbind(FilterShader shader, OpenGLContext& open_gl_context) {
|
||||||
|
OpenGLShaderProgram::Attribute* position = shaders_[shader].position.get();
|
||||||
|
open_gl_context.extensions.glDisableVertexAttribArray(position->attributeID);
|
||||||
|
open_gl_context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
open_gl_context.extensions.glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::drawFilterResponse(OpenGlWrapper& open_gl) {
|
||||||
|
vital::constants::FilterModel model = filter_model_;
|
||||||
|
bool new_response = setupFilterState(model);
|
||||||
|
new_response = new_response || isStereoState();
|
||||||
|
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
setViewPort(open_gl);
|
||||||
|
|
||||||
|
Colour color_line = line_right_color_;
|
||||||
|
Colour color_fill_to = fill_right_color_;
|
||||||
|
float fill_fade = findValue(Skin::kWidgetFillFade);
|
||||||
|
Colour color_fill_from = color_fill_to.withMultipliedAlpha(1.0f - fill_fade);
|
||||||
|
|
||||||
|
setLineWidth(findValue(Skin::kWidgetLineWidth));
|
||||||
|
setFillCenter(findValue(Skin::kWidgetFillCenter));
|
||||||
|
|
||||||
|
FilterShader shader = getShaderForModel(model, filter_state_.style);
|
||||||
|
|
||||||
|
if (active_) {
|
||||||
|
if (new_response) {
|
||||||
|
bind(shader, open_gl.context);
|
||||||
|
loadShader(shader, model, 1);
|
||||||
|
renderLineResponse(open_gl);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFillColors(color_fill_from, color_fill_to);
|
||||||
|
setColor(color_line);
|
||||||
|
OpenGlLineRenderer::render(open_gl, animate_);
|
||||||
|
}
|
||||||
|
|
||||||
|
color_line = line_left_color_;
|
||||||
|
color_fill_to = fill_left_color_;
|
||||||
|
if (!active_) {
|
||||||
|
color_line = line_disabled_color_;
|
||||||
|
color_fill_to = fill_disabled_color_;
|
||||||
|
}
|
||||||
|
color_fill_from = color_fill_to.withMultipliedAlpha(1.0f - fill_fade);
|
||||||
|
|
||||||
|
if (new_response) {
|
||||||
|
bind(shader, open_gl.context);
|
||||||
|
loadShader(shader, model, 0);
|
||||||
|
renderLineResponse(open_gl);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFillColors(color_fill_from, color_fill_to);
|
||||||
|
setColor(color_line);
|
||||||
|
OpenGlLineRenderer::render(open_gl, animate_);
|
||||||
|
|
||||||
|
unbind(shader, open_gl.context);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
checkGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterResponse::renderLineResponse(OpenGlWrapper& open_gl) {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
open_gl.context.extensions.glBeginTransformFeedback(GL_POINTS);
|
||||||
|
glDrawArrays(GL_POINTS, 0, kResolution);
|
||||||
|
open_gl.context.extensions.glEndTransformFeedback();
|
||||||
|
|
||||||
|
void* buffer = open_gl.context.extensions.glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
|
||||||
|
kResolution * sizeof(float), GL_MAP_READ_BIT);
|
||||||
|
|
||||||
|
float* response_data = (float*)buffer;
|
||||||
|
float width = getWidth();
|
||||||
|
float y_adjust = getHeight() / 2.0f;
|
||||||
|
for (int i = 0; i < kResolution; ++i) {
|
||||||
|
setXAt(i, width * i / (kResolution - 1.0f));
|
||||||
|
setYAt(i, y_adjust * (1.0f - response_data[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
open_gl.context.extensions.glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
}
|
||||||
182
src/interface/editor_components/filter_response.h
Normal file
182
src/interface/editor_components/filter_response.h
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "open_gl_line_renderer.h"
|
||||||
|
|
||||||
|
#include "skin.h"
|
||||||
|
#include "comb_filter.h"
|
||||||
|
#include "digital_svf.h"
|
||||||
|
#include "diode_filter.h"
|
||||||
|
#include "dirty_filter.h"
|
||||||
|
#include "formant_filter.h"
|
||||||
|
#include "ladder_filter.h"
|
||||||
|
#include "phaser_filter.h"
|
||||||
|
#include "sallen_key_filter.h"
|
||||||
|
#include "synth_types.h"
|
||||||
|
|
||||||
|
class SynthSlider;
|
||||||
|
|
||||||
|
class FilterResponse : public OpenGlLineRenderer {
|
||||||
|
public:
|
||||||
|
static constexpr int kResolution = 512;
|
||||||
|
static constexpr int kDefaultVisualSampleRate = 200000;
|
||||||
|
static constexpr int kCombAlternatePeriod = 3;
|
||||||
|
|
||||||
|
static constexpr double kMouseSensitivityX = 0.3;
|
||||||
|
static constexpr double kMouseSensitivityY = 0.3;
|
||||||
|
|
||||||
|
enum FilterShader {
|
||||||
|
kAnalog,
|
||||||
|
kDirty,
|
||||||
|
kLadder,
|
||||||
|
kDigital,
|
||||||
|
kDiode,
|
||||||
|
kFormant,
|
||||||
|
kComb,
|
||||||
|
kPositiveFlange,
|
||||||
|
kNegativeFlange,
|
||||||
|
kPhase,
|
||||||
|
kNumFilterShaders
|
||||||
|
};
|
||||||
|
|
||||||
|
FilterResponse(String suffix, const vital::output_map& mono_modulations);
|
||||||
|
FilterResponse(int index, const vital::output_map& mono_modulations, const vital::output_map& poly_modulations);
|
||||||
|
virtual ~FilterResponse();
|
||||||
|
|
||||||
|
void init(OpenGlWrapper& open_gl) override;
|
||||||
|
void render(OpenGlWrapper& open_gl, bool animate) override;
|
||||||
|
void destroy(OpenGlWrapper& open_gl) override;
|
||||||
|
void paintBackground(Graphics& g) override;
|
||||||
|
|
||||||
|
void setCutoffSlider(SynthSlider* slider) { cutoff_slider_ = slider; }
|
||||||
|
void setResonanceSlider(SynthSlider* slider) { resonance_slider_ = slider; }
|
||||||
|
void setFormantXSlider(SynthSlider* slider) { formant_x_slider_ = slider; }
|
||||||
|
void setFormantYSlider(SynthSlider* slider) { formant_y_slider_ = slider; }
|
||||||
|
void setFilterMixSlider(SynthSlider* slider) { filter_mix_slider_ = slider; }
|
||||||
|
void setBlendSlider(SynthSlider* slider) { blend_slider_ = slider; }
|
||||||
|
void setTransposeSlider(SynthSlider* slider) { transpose_slider_ = slider; }
|
||||||
|
void setFormantTransposeSlider(SynthSlider* slider) { formant_transpose_slider_ = slider; }
|
||||||
|
void setFormantResonanceSlider(SynthSlider* slider) { formant_resonance_slider_ = slider; }
|
||||||
|
void setFormantSpreadSlider(SynthSlider* slider) { formant_spread_slider_ = slider; }
|
||||||
|
|
||||||
|
void mouseDown(const MouseEvent& e) override;
|
||||||
|
void mouseDrag(const MouseEvent& e) override;
|
||||||
|
void mouseExit(const MouseEvent& e) override;
|
||||||
|
void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override;
|
||||||
|
|
||||||
|
void setActive(bool active) { active_ = active; }
|
||||||
|
void setModel(vital::constants::FilterModel model) { filter_model_ = model; }
|
||||||
|
void setStyle(int style) { filter_state_.style = style; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct FilterResponseShader {
|
||||||
|
static constexpr int kMaxStages = 5;
|
||||||
|
OpenGLShaderProgram* shader;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Attribute> position;
|
||||||
|
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> mix;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> midi_cutoff;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> resonance;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> drive;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> db24;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> stages[kMaxStages];
|
||||||
|
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_cutoff;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_resonance;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_spread;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_low;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_band;
|
||||||
|
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_high;
|
||||||
|
};
|
||||||
|
|
||||||
|
FilterResponse();
|
||||||
|
|
||||||
|
void setFilterSettingsFromPosition(Point<int> position);
|
||||||
|
|
||||||
|
void drawFilterResponse(OpenGlWrapper& open_gl);
|
||||||
|
vital::poly_float getOutputsTotal(std::pair<vital::Output*, vital::Output*> outputs,
|
||||||
|
vital::poly_float default_value);
|
||||||
|
|
||||||
|
bool setupFilterState(vital::constants::FilterModel model);
|
||||||
|
bool isStereoState();
|
||||||
|
void loadShader(FilterShader shader, vital::constants::FilterModel model, int index);
|
||||||
|
void bind(FilterShader shader, OpenGLContext& open_gl_context);
|
||||||
|
void unbind(FilterShader shader, OpenGLContext& open_gl_context);
|
||||||
|
void renderLineResponse(OpenGlWrapper& open_gl);
|
||||||
|
|
||||||
|
bool active_;
|
||||||
|
bool animate_;
|
||||||
|
Point<int> last_mouse_position_;
|
||||||
|
double current_resonance_value_;
|
||||||
|
double current_cutoff_value_;
|
||||||
|
double current_formant_x_value_;
|
||||||
|
double current_formant_y_value_;
|
||||||
|
|
||||||
|
Colour line_left_color_;
|
||||||
|
Colour line_right_color_;
|
||||||
|
Colour line_disabled_color_;
|
||||||
|
Colour fill_left_color_;
|
||||||
|
Colour fill_right_color_;
|
||||||
|
Colour fill_disabled_color_;
|
||||||
|
|
||||||
|
vital::SallenKeyFilter analog_filter_;
|
||||||
|
vital::CombFilter comb_filter_;
|
||||||
|
vital::DigitalSvf digital_filter_;
|
||||||
|
vital::DiodeFilter diode_filter_;
|
||||||
|
vital::DirtyFilter dirty_filter_;
|
||||||
|
vital::FormantFilter formant_filter_;
|
||||||
|
vital::LadderFilter ladder_filter_;
|
||||||
|
vital::PhaserFilter phaser_filter_;
|
||||||
|
|
||||||
|
int last_filter_style_;
|
||||||
|
vital::constants::FilterModel last_filter_model_;
|
||||||
|
vital::constants::FilterModel filter_model_;
|
||||||
|
vital::SynthFilter::FilterState filter_state_;
|
||||||
|
vital::poly_float mix_;
|
||||||
|
|
||||||
|
SynthSlider* cutoff_slider_;
|
||||||
|
SynthSlider* resonance_slider_;
|
||||||
|
SynthSlider* formant_x_slider_;
|
||||||
|
SynthSlider* formant_y_slider_;
|
||||||
|
SynthSlider* filter_mix_slider_;
|
||||||
|
SynthSlider* blend_slider_;
|
||||||
|
SynthSlider* transpose_slider_;
|
||||||
|
SynthSlider* formant_transpose_slider_;
|
||||||
|
SynthSlider* formant_resonance_slider_;
|
||||||
|
SynthSlider* formant_spread_slider_;
|
||||||
|
|
||||||
|
std::pair<vital::Output*, vital::Output*> filter_mix_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> midi_cutoff_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> resonance_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> blend_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> transpose_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> interpolate_x_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> interpolate_y_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> formant_resonance_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> formant_spread_outputs_;
|
||||||
|
std::pair<vital::Output*, vital::Output*> formant_transpose_outputs_;
|
||||||
|
|
||||||
|
FilterResponseShader shaders_[kNumFilterShaders];
|
||||||
|
std::unique_ptr<float[]> line_data_;
|
||||||
|
GLuint vertex_array_object_;
|
||||||
|
GLuint line_buffer_;
|
||||||
|
GLuint response_buffer_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FilterResponse)
|
||||||
|
};
|
||||||
|
|
||||||
103
src/interface/editor_components/incrementer_buttons.h
Normal file
103
src/interface/editor_components/incrementer_buttons.h
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
#include "skin.h"
|
||||||
|
|
||||||
|
class IncrementerButtons : public Component, public Button::Listener {
|
||||||
|
public:
|
||||||
|
IncrementerButtons(Slider* slider) : slider_(slider), active_(true) {
|
||||||
|
increment_ = std::make_unique<ShapeButton>("Increment", Colours::black, Colours::black, Colours::black);
|
||||||
|
addAndMakeVisible(increment_.get());
|
||||||
|
increment_->addListener(this);
|
||||||
|
Path increment_shape;
|
||||||
|
increment_shape.startNewSubPath(Point<float>(0.5f, 0.1f));
|
||||||
|
increment_shape.lineTo(Point<float>(0.2f, 0.45f));
|
||||||
|
increment_shape.lineTo(Point<float>(0.8f, 0.45f));
|
||||||
|
increment_shape.closeSubPath();
|
||||||
|
|
||||||
|
increment_shape.startNewSubPath(Point<float>(0.0f, 0.0f));
|
||||||
|
increment_shape.closeSubPath();
|
||||||
|
increment_shape.startNewSubPath(Point<float>(1.0f, 0.5f));
|
||||||
|
increment_shape.closeSubPath();
|
||||||
|
|
||||||
|
increment_shape.addLineSegment(Line<float>(0.0f, 0.0f, 0.0f, 0.0f), 0.2f);
|
||||||
|
increment_shape.addLineSegment(Line<float>(0.5f, 0.5f, 0.5f, 0.5f), 0.2f);
|
||||||
|
increment_->setShape(increment_shape, true, true, false);
|
||||||
|
|
||||||
|
decrement_ = std::make_unique<ShapeButton>("Increment", Colours::black, Colours::black, Colours::black);
|
||||||
|
addAndMakeVisible(decrement_.get());
|
||||||
|
decrement_->addListener(this);
|
||||||
|
Path decrement_shape;
|
||||||
|
decrement_shape.startNewSubPath(Point<float>(0.5f, 0.4f));
|
||||||
|
decrement_shape.lineTo(Point<float>(0.2f, 0.05f));
|
||||||
|
decrement_shape.lineTo(Point<float>(0.8f, 0.05f));
|
||||||
|
decrement_shape.closeSubPath();
|
||||||
|
|
||||||
|
decrement_shape.startNewSubPath(Point<float>(0.0f, 0.0f));
|
||||||
|
decrement_shape.closeSubPath();
|
||||||
|
decrement_shape.startNewSubPath(Point<float>(1.0f, 0.5f));
|
||||||
|
decrement_shape.closeSubPath();
|
||||||
|
|
||||||
|
decrement_shape.addLineSegment(Line<float>(0.0f, 0.0f, 0.0f, 0.0f), 0.2f);
|
||||||
|
decrement_shape.addLineSegment(Line<float>(0.5f, 0.5f, 0.5f, 0.5f), 0.2f);
|
||||||
|
decrement_->setShape(decrement_shape, true, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setActive(bool active) {
|
||||||
|
active_ = active;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void resized() override {
|
||||||
|
Rectangle<int> increment_bounds = getLocalBounds();
|
||||||
|
Rectangle<int> decrement_bounds = increment_bounds.removeFromBottom(getHeight() / 2);
|
||||||
|
increment_->setBounds(increment_bounds);
|
||||||
|
decrement_->setBounds(decrement_bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint(Graphics& g) override {
|
||||||
|
setColors();
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonClicked(Button* clicked_button) override {
|
||||||
|
double value = slider_->getValue();
|
||||||
|
if (clicked_button == increment_.get())
|
||||||
|
slider_->setValue(value + 1.0);
|
||||||
|
else if (clicked_button == decrement_.get())
|
||||||
|
slider_->setValue(value - 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setColors() {
|
||||||
|
Colour normal = findColour(Skin::kIconButtonOff, true);
|
||||||
|
Colour hover = findColour(Skin::kIconButtonOffHover, true);
|
||||||
|
Colour down = findColour(Skin::kIconButtonOffPressed, true);
|
||||||
|
increment_->setColours(normal, hover, down);
|
||||||
|
decrement_->setColours(normal, hover, down);
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider* slider_;
|
||||||
|
bool active_;
|
||||||
|
|
||||||
|
std::unique_ptr<ShapeButton> increment_;
|
||||||
|
std::unique_ptr<ShapeButton> decrement_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IncrementerButtons)
|
||||||
|
};
|
||||||
|
|
||||||
248
src/interface/editor_components/lfo_editor.cpp
Normal file
248
src/interface/editor_components/lfo_editor.cpp
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "lfo_editor.h"
|
||||||
|
|
||||||
|
#include "default_look_and_feel.h"
|
||||||
|
#include "skin.h"
|
||||||
|
#include "shaders.h"
|
||||||
|
#include "synth_gui_interface.h"
|
||||||
|
#include "synth_section.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
LfoEditor::LfoEditor(LineGenerator* lfo_source, String prefix,
|
||||||
|
const vital::output_map& mono_modulations,
|
||||||
|
const vital::output_map& poly_modulations) : LineEditor(lfo_source) {
|
||||||
|
parent_ = nullptr;
|
||||||
|
wave_phase_ = nullptr;
|
||||||
|
frequency_ = nullptr;
|
||||||
|
last_phase_ = 0.0f;
|
||||||
|
|
||||||
|
setFill(true);
|
||||||
|
setFillCenter(-1.0f);
|
||||||
|
setName(prefix);
|
||||||
|
|
||||||
|
last_voice_ = -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
LfoEditor::~LfoEditor() { }
|
||||||
|
|
||||||
|
void LfoEditor::parentHierarchyChanged() {
|
||||||
|
parent_ = findParentComponentOfClass<SynthGuiInterface>();
|
||||||
|
|
||||||
|
if (wave_phase_ == nullptr && parent_)
|
||||||
|
wave_phase_ = parent_->getSynth()->getStatusOutput(getName().toStdString() + "_phase");
|
||||||
|
|
||||||
|
if (frequency_ == nullptr && parent_)
|
||||||
|
frequency_ = parent_->getSynth()->getStatusOutput(getName().toStdString() + "_frequency");
|
||||||
|
|
||||||
|
LineEditor::parentHierarchyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LfoEditor::mouseDown(const MouseEvent& e) {
|
||||||
|
if (e.mods.isPopupMenu()) {
|
||||||
|
PopupItems options;
|
||||||
|
|
||||||
|
int active_point = getActivePoint();
|
||||||
|
if (active_point >= 0) {
|
||||||
|
options.addItem(kSetPhaseToPoint, "Set Start Point");
|
||||||
|
if (active_point >= 1 && active_point < getModel()->getNumPoints() - 1) {
|
||||||
|
options.addItem(kRemovePoint, "Remove Point");
|
||||||
|
options.addItem(kEnterPhase, "Enter Point Phase");
|
||||||
|
}
|
||||||
|
|
||||||
|
options.addItem(kEnterValue, "Enter Point Value");
|
||||||
|
options.addItem(-1, "");
|
||||||
|
}
|
||||||
|
else if (getActivePower() >= 0) {
|
||||||
|
options.addItem(kSetPhaseToPower, "Set Start Point");
|
||||||
|
options.addItem(kResetPower, "Reset Power");
|
||||||
|
options.addItem(-1, "");
|
||||||
|
}
|
||||||
|
else if (getActiveGridSection() >= 0)
|
||||||
|
options.addItem(kSetPhaseToGrid, "Set Start Point");
|
||||||
|
|
||||||
|
options.addItem(kCopy, "Copy");
|
||||||
|
if (hasMatchingSystemClipboard())
|
||||||
|
options.addItem(kPaste, "Paste");
|
||||||
|
|
||||||
|
options.addItem(kSave, "Save to LFOs");
|
||||||
|
|
||||||
|
options.addItem(kFlipHorizontal, "Flip Horizontal");
|
||||||
|
options.addItem(kFlipVertical, "Flip Vertical");
|
||||||
|
|
||||||
|
options.addItem(kImportLfo, "Import LFO");
|
||||||
|
options.addItem(kExportLfo, "Export LFO");
|
||||||
|
|
||||||
|
SynthSection* parent = findParentComponentOfClass<SynthSection>();
|
||||||
|
int point = active_point;
|
||||||
|
int power = getActivePower();
|
||||||
|
parent->showPopupSelector(this, e.getPosition(), options,
|
||||||
|
[=](int selection) { respondToCallback(point, power, selection); });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LineEditor::mouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LfoEditor::mouseDoubleClick(const MouseEvent& e) {
|
||||||
|
if (!e.mods.isPopupMenu())
|
||||||
|
LineEditor::mouseDoubleClick(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LfoEditor::mouseUp(const MouseEvent& e) {
|
||||||
|
if (!e.mods.isPopupMenu())
|
||||||
|
LineEditor::mouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LfoEditor::respondToCallback(int point, int power, int result) {
|
||||||
|
if (result == kSetPhaseToPoint) {
|
||||||
|
if (point >= 0 && point < numPoints())
|
||||||
|
setPhase(getModel()->getPoint(point).first);
|
||||||
|
}
|
||||||
|
else if (result == kSetPhaseToPower) {
|
||||||
|
if (power >= 0 && power < numPoints() - 1) {
|
||||||
|
float from = getModel()->getPoint(power).first;
|
||||||
|
float to = getModel()->getPoint(power + 1).first;
|
||||||
|
setPhase((from + to) / 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (result == kSetPhaseToGrid) {
|
||||||
|
int section = getActiveGridSection();
|
||||||
|
int grid_size = getGridSizeX();
|
||||||
|
if (section >= 0 && grid_size > 0)
|
||||||
|
setPhase(section * 1.0f / grid_size);
|
||||||
|
}
|
||||||
|
else if (result == kImportLfo) {
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->importLfo();
|
||||||
|
}
|
||||||
|
else if (result == kExportLfo) {
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->exportLfo();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LineEditor::respondToCallback(point, power, result);
|
||||||
|
|
||||||
|
clearActiveMouseActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LfoEditor::setPhase(float phase) {
|
||||||
|
for (Listener* listener : listeners_)
|
||||||
|
listener->setPhase(phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LfoEditor::render(OpenGlWrapper& open_gl, bool animate) {
|
||||||
|
static constexpr float kBackupTime = 1.0f / 50.0f;
|
||||||
|
|
||||||
|
setGlPositions();
|
||||||
|
renderGrid(open_gl, animate);
|
||||||
|
|
||||||
|
vital::poly_float encoded_phase = wave_phase_->value();
|
||||||
|
vital::poly_mask inactive_mask = 0;
|
||||||
|
if (wave_phase_->isClearValue(encoded_phase)) {
|
||||||
|
encoded_phase = 0.0f;
|
||||||
|
inactive_mask = vital::constants::kFullMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
vital::poly_float frequency = frequency_->value();
|
||||||
|
if (frequency_->isClearValue(frequency))
|
||||||
|
frequency = 0.0f;
|
||||||
|
|
||||||
|
std::pair<vital::poly_float, vital::poly_float> decoded = vital::utils::decodePhaseAndVoice(encoded_phase);
|
||||||
|
vital::poly_float phase = decoded.first;
|
||||||
|
vital::poly_float voice = decoded.second;
|
||||||
|
|
||||||
|
vital::poly_float phase_delta = vital::poly_float::abs(phase - last_phase_);
|
||||||
|
vital::poly_float decay = vital::poly_float(1.0f) - phase_delta * kSpeedDecayMult;
|
||||||
|
decay = vital::utils::clamp(decay, kBoostDecay, 1.0f);
|
||||||
|
decay = vital::utils::maskLoad(decay, kBoostDecay, inactive_mask);
|
||||||
|
decayBoosts(decay);
|
||||||
|
|
||||||
|
vital::poly_mask switch_mask = vital::poly_float::notEqual(voice, last_voice_) | inactive_mask;
|
||||||
|
vital::poly_float phase_reset = vital::utils::max(0.0f, phase - frequency * kBackupTime);
|
||||||
|
last_phase_ = vital::utils::maskLoad(last_phase_, phase_reset, switch_mask);
|
||||||
|
|
||||||
|
bool animating = animate;
|
||||||
|
if (parent_)
|
||||||
|
animating = animating && parent_->getSynth()->isModSourceEnabled(getName().toStdString());
|
||||||
|
|
||||||
|
if (animating)
|
||||||
|
boostRange(adjustBoostPhase(last_phase_), adjustBoostPhase(phase), kNumWrapPoints, decay);
|
||||||
|
else
|
||||||
|
decayBoosts(0.0f);
|
||||||
|
|
||||||
|
last_phase_ = phase;
|
||||||
|
last_voice_ = voice;
|
||||||
|
|
||||||
|
setLineWidth(findValue(Skin::kWidgetLineWidth));
|
||||||
|
setFillCenter(findValue(Skin::kWidgetFillCenter));
|
||||||
|
|
||||||
|
Colour fill_color = findColour(Skin::kWidgetSecondary1, true);
|
||||||
|
float fill_fade = findValue(Skin::kWidgetFillFade);
|
||||||
|
Colour fill_color_fade = fill_color.withMultipliedAlpha(1.0f - fill_fade);
|
||||||
|
Colour position_color = findColour(Skin::kWidgetPrimary1, true);
|
||||||
|
|
||||||
|
Colour fill_color_stereo = findColour(Skin::kWidgetSecondary2, true);
|
||||||
|
Colour fill_color_stereo_fade = fill_color_stereo.withMultipliedAlpha(1.0f - fill_fade);
|
||||||
|
Colour position_color_stereo = findColour(Skin::kWidgetPrimary2, true);
|
||||||
|
|
||||||
|
if (animating) {
|
||||||
|
setFill(true);
|
||||||
|
setBoostAmount(findValue(Skin::kWidgetLineBoost));
|
||||||
|
setFillBoostAmount(findValue(Skin::kWidgetFillBoost));
|
||||||
|
|
||||||
|
setIndex(1);
|
||||||
|
setColor(findColour(Skin::kWidgetPrimary2, true));
|
||||||
|
setFillColors(fill_color_stereo_fade, fill_color_stereo);
|
||||||
|
drawLines(open_gl, false);
|
||||||
|
|
||||||
|
setIndex(0);
|
||||||
|
setColor(findColour(Skin::kWidgetPrimary1, true));
|
||||||
|
setFillColors(fill_color_fade, fill_color);
|
||||||
|
drawLines(open_gl, anyBoostValue());
|
||||||
|
|
||||||
|
setBoostAmount(0.0f);
|
||||||
|
setFill(false);
|
||||||
|
setColor(findColour(Skin::kWidgetCenterLine, true));
|
||||||
|
drawLines(open_gl, anyBoostValue());
|
||||||
|
|
||||||
|
setViewPort(open_gl);
|
||||||
|
if (switch_mask.sum() == 0) {
|
||||||
|
drawPosition(open_gl, position_color_stereo, phase[1]);
|
||||||
|
drawPosition(open_gl, position_color, phase[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setBoostAmount(0.0f);
|
||||||
|
setFillBoostAmount(0.0f);
|
||||||
|
setFill(true);
|
||||||
|
|
||||||
|
setColor(findColour(Skin::kWidgetPrimary2, true));
|
||||||
|
setFillColors(fill_color_stereo_fade, fill_color_stereo);
|
||||||
|
drawLines(open_gl, false);
|
||||||
|
|
||||||
|
setColor(findColour(Skin::kWidgetPrimary1, true));
|
||||||
|
setFillColors(fill_color_fade, fill_color);
|
||||||
|
drawLines(open_gl, false);
|
||||||
|
|
||||||
|
setFill(false);
|
||||||
|
setColor(findColour(Skin::kWidgetCenterLine, true));
|
||||||
|
drawLines(open_gl, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPoints(open_gl, animate);
|
||||||
|
renderCorners(open_gl, animate);
|
||||||
|
}
|
||||||
66
src/interface/editor_components/lfo_editor.h
Normal file
66
src/interface/editor_components/lfo_editor.h
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
#include "line_generator.h"
|
||||||
|
#include "open_gl_image.h"
|
||||||
|
#include "line_editor.h"
|
||||||
|
#include "synth_lfo.h"
|
||||||
|
#include "synth_module.h"
|
||||||
|
|
||||||
|
class SynthGuiInterface;
|
||||||
|
|
||||||
|
class LfoEditor : public LineEditor {
|
||||||
|
public:
|
||||||
|
static constexpr float kBoostDecay = 0.9f;
|
||||||
|
static constexpr float kSpeedDecayMult = 5.0f;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
kSetPhaseToPoint = kNumMenuOptions,
|
||||||
|
kSetPhaseToPower,
|
||||||
|
kSetPhaseToGrid,
|
||||||
|
kImportLfo,
|
||||||
|
kExportLfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
LfoEditor(LineGenerator* lfo_source, String prefix,
|
||||||
|
const vital::output_map& mono_modulations, const vital::output_map& poly_modulations);
|
||||||
|
virtual ~LfoEditor();
|
||||||
|
|
||||||
|
void parentHierarchyChanged() override;
|
||||||
|
virtual void mouseDown(const MouseEvent& e) override;
|
||||||
|
virtual void mouseDoubleClick(const MouseEvent& e) override;
|
||||||
|
virtual void mouseUp(const MouseEvent& e) override;
|
||||||
|
|
||||||
|
void respondToCallback(int point, int power, int result) override;
|
||||||
|
void setPhase(float phase);
|
||||||
|
|
||||||
|
void render(OpenGlWrapper& open_gl, bool animate) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SynthGuiInterface* parent_;
|
||||||
|
|
||||||
|
const vital::StatusOutput* wave_phase_;
|
||||||
|
const vital::StatusOutput* frequency_;
|
||||||
|
vital::poly_float last_phase_;
|
||||||
|
vital::poly_float last_voice_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LfoEditor)
|
||||||
|
};
|
||||||
|
|
||||||
1106
src/interface/editor_components/line_editor.cpp
Normal file
1106
src/interface/editor_components/line_editor.cpp
Normal file
File diff suppressed because it is too large
Load diff
236
src/interface/editor_components/line_editor.h
Normal file
236
src/interface/editor_components/line_editor.h
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
#include "line_generator.h"
|
||||||
|
#include "open_gl_image_component.h"
|
||||||
|
#include "open_gl_multi_image.h"
|
||||||
|
#include "open_gl_multi_quad.h"
|
||||||
|
#include "open_gl_line_renderer.h"
|
||||||
|
#include "synth_lfo.h"
|
||||||
|
#include "synth_module.h"
|
||||||
|
|
||||||
|
class LineEditor : public OpenGlLineRenderer, public TextEditor::Listener {
|
||||||
|
public:
|
||||||
|
static constexpr float kPositionWidth = 9.0f;
|
||||||
|
static constexpr float kPowerWidth = 7.0f;
|
||||||
|
static constexpr float kRingThickness = 0.45f;
|
||||||
|
static constexpr float kGrabRadius = 12.0f;
|
||||||
|
static constexpr float kDragRadius = 20.0f;
|
||||||
|
static constexpr int kResolution = 64;
|
||||||
|
static constexpr int kNumWrapPoints = 8;
|
||||||
|
static constexpr int kDrawPoints = kResolution + LineGenerator::kMaxPoints;
|
||||||
|
static constexpr int kTotalPoints = kDrawPoints + 2 * kNumWrapPoints;
|
||||||
|
static constexpr int kMaxGridSizeX = 32;
|
||||||
|
static constexpr int kMaxGridSizeY = 24;
|
||||||
|
static constexpr float kPaddingY = 6.0f;
|
||||||
|
static constexpr float kPaddingX = 0.0f;
|
||||||
|
static constexpr float kPowerMouseMultiplier = 9.0f;
|
||||||
|
static constexpr float kMinPointDistanceForPower = 3.0f;
|
||||||
|
|
||||||
|
enum MenuOptions {
|
||||||
|
kCancel,
|
||||||
|
kCopy,
|
||||||
|
kPaste,
|
||||||
|
kSave,
|
||||||
|
kEnterPhase,
|
||||||
|
kEnterValue,
|
||||||
|
kResetPower,
|
||||||
|
kRemovePoint,
|
||||||
|
kInit,
|
||||||
|
kFlipHorizontal,
|
||||||
|
kFlipVertical,
|
||||||
|
kNumMenuOptions
|
||||||
|
};
|
||||||
|
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
virtual ~Listener() { }
|
||||||
|
virtual void setPhase(float phase) = 0;
|
||||||
|
virtual void lineEditorScrolled(const MouseEvent& e, const MouseWheelDetails& wheel) = 0;
|
||||||
|
virtual void togglePaintMode(bool enabled, bool temporary_switch) = 0;
|
||||||
|
virtual void fileLoaded() = 0;
|
||||||
|
virtual void importLfo() = 0;
|
||||||
|
virtual void exportLfo() = 0;
|
||||||
|
virtual void pointChanged(int index, Point<float> position, bool mouse_up) { }
|
||||||
|
virtual void powersChanged(bool mouse_up) { }
|
||||||
|
virtual void pointAdded(int index, Point<float> position) { }
|
||||||
|
virtual void pointRemoved(int index) { }
|
||||||
|
virtual void pointsAdded(int index, int num_points_added) { }
|
||||||
|
virtual void pointsRemoved(int index, int num_points_removed) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
LineEditor(LineGenerator* line_source);
|
||||||
|
virtual ~LineEditor();
|
||||||
|
|
||||||
|
void resetWavePath();
|
||||||
|
void resized() override {
|
||||||
|
OpenGlLineRenderer::resized();
|
||||||
|
drag_circle_.setBounds(getLocalBounds());
|
||||||
|
hover_circle_.setBounds(getLocalBounds());
|
||||||
|
grid_lines_.setBounds(getLocalBounds());
|
||||||
|
position_circle_.setBounds(getLocalBounds());
|
||||||
|
point_circles_.setBounds(getLocalBounds());
|
||||||
|
power_circles_.setBounds(getLocalBounds());
|
||||||
|
resetPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
float padY(float y);
|
||||||
|
float unpadY(float y);
|
||||||
|
|
||||||
|
float padX(float x);
|
||||||
|
float unpadX(float x);
|
||||||
|
|
||||||
|
virtual void mouseDown(const MouseEvent& e) override;
|
||||||
|
virtual void mouseDoubleClick(const MouseEvent& e) override;
|
||||||
|
virtual void mouseMove(const MouseEvent& e) override;
|
||||||
|
virtual void mouseDrag(const MouseEvent& e) override;
|
||||||
|
virtual void mouseUp(const MouseEvent& e) override;
|
||||||
|
|
||||||
|
virtual void respondToCallback(int point, int power, int option);
|
||||||
|
bool hasMatchingSystemClipboard();
|
||||||
|
void paintLine(const MouseEvent& e);
|
||||||
|
|
||||||
|
void drawDown(const MouseEvent& e);
|
||||||
|
void drawDrag(const MouseEvent& e);
|
||||||
|
void drawUp(const MouseEvent& e);
|
||||||
|
|
||||||
|
void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override;
|
||||||
|
void mouseExit(const MouseEvent& e) override;
|
||||||
|
void clearActiveMouseActions();
|
||||||
|
|
||||||
|
void renderGrid(OpenGlWrapper& open_gl, bool animate);
|
||||||
|
void renderPoints(OpenGlWrapper& open_gl, bool animate);
|
||||||
|
void init(OpenGlWrapper& open_gl) override;
|
||||||
|
void render(OpenGlWrapper& open_gl, bool animate) override;
|
||||||
|
void destroy(OpenGlWrapper& open_gl) override;
|
||||||
|
void setSizeRatio(float ratio) { size_ratio_ = ratio; }
|
||||||
|
float sizeRatio() const { return size_ratio_; }
|
||||||
|
|
||||||
|
void setLoop(bool loop) { loop_ = loop; }
|
||||||
|
void setSmooth(bool smooth) { model_->setSmooth(smooth); resetPositions(); }
|
||||||
|
bool getSmooth() const { return model_->smooth(); }
|
||||||
|
void setPaint(bool paint);
|
||||||
|
void setPaintPattern(std::vector<std::pair<float, float>> pattern) { paint_pattern_ = pattern; }
|
||||||
|
|
||||||
|
virtual void setGridSizeX(int size) { grid_size_x_ = size; setGridPositions(); }
|
||||||
|
virtual void setGridSizeY(int size) { grid_size_y_ = size; setGridPositions(); }
|
||||||
|
int getGridSizeX() { return grid_size_x_; }
|
||||||
|
int getGridSizeY() { return grid_size_y_; }
|
||||||
|
|
||||||
|
void setModel(LineGenerator* model) { model_ = model; resetPositions(); }
|
||||||
|
LineGenerator* getModel() { return model_; }
|
||||||
|
void showTextEntry();
|
||||||
|
void hideTextEntry();
|
||||||
|
void textEditorReturnKeyPressed(TextEditor& editor) override;
|
||||||
|
void textEditorFocusLost(TextEditor& editor) override;
|
||||||
|
void textEditorEscapeKeyPressed(TextEditor& editor) override;
|
||||||
|
void setSliderPositionFromText();
|
||||||
|
void setAllowFileLoading(bool allow) { allow_file_loading_ = allow; }
|
||||||
|
|
||||||
|
void addListener(Listener* listener) { listeners_.push_back(listener); }
|
||||||
|
void setActive(bool active) { active_ = active; }
|
||||||
|
force_inline void resetPositions() { reset_positions_ = true; }
|
||||||
|
OpenGlComponent* getTextEditorComponent() {
|
||||||
|
if (value_entry_)
|
||||||
|
return value_entry_->getImageComponent();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void drawPosition(OpenGlWrapper& open_gl, Colour color, float fraction_x);
|
||||||
|
void setEditingCircleBounds();
|
||||||
|
void setGridPositions();
|
||||||
|
void setPointPositions();
|
||||||
|
void setGlPositions();
|
||||||
|
int getActivePoint() { return active_point_; }
|
||||||
|
int getActivePower() { return active_power_; }
|
||||||
|
int getActiveGridSection() { return active_grid_section_; }
|
||||||
|
bool isPainting() { return paint_ != temporary_paint_toggle_; }
|
||||||
|
bool isPaintEnabled() { return paint_; }
|
||||||
|
vital::poly_float adjustBoostPhase(vital::poly_float phase);
|
||||||
|
virtual void enableTemporaryPaintToggle(bool toggle);
|
||||||
|
|
||||||
|
bool active_;
|
||||||
|
std::vector<Listener*> listeners_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float adjustBoostPhase(float phase);
|
||||||
|
|
||||||
|
static std::pair<vital::Output*, vital::Output*> getOutputs(const vital::output_map& mono_modulations,
|
||||||
|
const vital::output_map& poly_modulations,
|
||||||
|
String name) {
|
||||||
|
return {
|
||||||
|
mono_modulations.at(name.toStdString()),
|
||||||
|
poly_modulations.at(name.toStdString())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
int getHoverPoint(Point<float> position);
|
||||||
|
int getHoverPower(Point<float> position);
|
||||||
|
float getSnapRadiusX();
|
||||||
|
float getSnapRadiusY();
|
||||||
|
float getSnappedX(float x);
|
||||||
|
float getSnappedY(float y);
|
||||||
|
void addPointAt(Point<float> position);
|
||||||
|
void movePoint(int index, Point<float> position, bool snap);
|
||||||
|
void movePower(int index, Point<float> position, bool all, bool alternate);
|
||||||
|
void removePoint(int index);
|
||||||
|
float getMinX(int index);
|
||||||
|
float getMaxX(int index);
|
||||||
|
|
||||||
|
Point<float> valuesToOpenGlPosition(float x, float y);
|
||||||
|
Point<float> getPowerPosition(int index);
|
||||||
|
bool powerActive(int index);
|
||||||
|
|
||||||
|
LineGenerator* model_;
|
||||||
|
int active_point_;
|
||||||
|
int active_power_;
|
||||||
|
int active_grid_section_;
|
||||||
|
bool dragging_;
|
||||||
|
bool reset_positions_;
|
||||||
|
bool allow_file_loading_;
|
||||||
|
Point<float> last_mouse_position_;
|
||||||
|
int last_model_render_;
|
||||||
|
bool loop_;
|
||||||
|
int grid_size_x_;
|
||||||
|
int grid_size_y_;
|
||||||
|
bool paint_;
|
||||||
|
|
||||||
|
bool temporary_paint_toggle_;
|
||||||
|
std::vector<std::pair<float, float>> paint_pattern_;
|
||||||
|
|
||||||
|
vital::poly_float last_phase_;
|
||||||
|
vital::poly_float last_voice_;
|
||||||
|
vital::poly_float last_last_voice_;
|
||||||
|
float size_ratio_;
|
||||||
|
|
||||||
|
OpenGlQuad drag_circle_;
|
||||||
|
OpenGlQuad hover_circle_;
|
||||||
|
OpenGlMultiQuad grid_lines_;
|
||||||
|
OpenGlQuad position_circle_;
|
||||||
|
OpenGlMultiQuad point_circles_;
|
||||||
|
OpenGlMultiQuad power_circles_;
|
||||||
|
std::unique_ptr<OpenGlTextEditor> value_entry_;
|
||||||
|
bool entering_phase_;
|
||||||
|
int entering_index_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LineEditor)
|
||||||
|
};
|
||||||
|
|
||||||
129
src/interface/editor_components/line_map_editor.cpp
Normal file
129
src/interface/editor_components/line_map_editor.cpp
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "line_map_editor.h"
|
||||||
|
|
||||||
|
#include "skin.h"
|
||||||
|
#include "shaders.h"
|
||||||
|
#include "synth_gui_interface.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
LineMapEditor::LineMapEditor(LineGenerator* line_source, String name) : LineEditor(line_source) {
|
||||||
|
animate_ = true;
|
||||||
|
raw_input_ = nullptr;
|
||||||
|
last_phase_ = 0.0f;
|
||||||
|
|
||||||
|
setFill(true);
|
||||||
|
setFillCenter(-1.0f);
|
||||||
|
setLoop(false);
|
||||||
|
setName(name);
|
||||||
|
setBoostAmount(0.0f);
|
||||||
|
setFillBoostAmount(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
LineMapEditor::~LineMapEditor() { }
|
||||||
|
|
||||||
|
void LineMapEditor::parentHierarchyChanged() {
|
||||||
|
SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();
|
||||||
|
|
||||||
|
if (parent && raw_input_ == nullptr)
|
||||||
|
raw_input_ = parent->getSynth()->getStatusOutput(getName().toStdString());
|
||||||
|
|
||||||
|
LineEditor::parentHierarchyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineMapEditor::render(OpenGlWrapper& open_gl, bool animate) {
|
||||||
|
setGlPositions();
|
||||||
|
renderGrid(open_gl, animate);
|
||||||
|
|
||||||
|
setLineWidth(findValue(Skin::kWidgetLineWidth));
|
||||||
|
|
||||||
|
Colour envelope_graph_fill = findColour(Skin::kWidgetSecondary1, true);
|
||||||
|
Colour envelope_graph_fill_stereo = findColour(Skin::kWidgetSecondary2, true);
|
||||||
|
|
||||||
|
float fill_fade = findValue(Skin::kWidgetFillFade);
|
||||||
|
if (!active_) {
|
||||||
|
envelope_graph_fill = findColour(Skin::kWidgetSecondaryDisabled, true);
|
||||||
|
envelope_graph_fill_stereo = envelope_graph_fill;
|
||||||
|
}
|
||||||
|
Colour envelope_graph_fill_fade = envelope_graph_fill.withMultipliedAlpha(1.0f - fill_fade);
|
||||||
|
Colour envelope_graph_fill_stereo_fade = envelope_graph_fill_stereo.withMultipliedAlpha(1.0f - fill_fade);
|
||||||
|
|
||||||
|
Colour position_color = findColour(Skin::kWidgetPrimary1, true);
|
||||||
|
Colour position_color_stereo = findColour(Skin::kWidgetPrimary2, true);
|
||||||
|
Colour center = findColour(Skin::kWidgetCenterLine, true);
|
||||||
|
if (!active_) {
|
||||||
|
position_color = findColour(Skin::kWidgetPrimaryDisabled, true);
|
||||||
|
center = position_color;
|
||||||
|
position_color_stereo = position_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animate && animate_) {
|
||||||
|
decayBoosts(kTailDecay);
|
||||||
|
|
||||||
|
vital::poly_float phase = raw_input_->value();
|
||||||
|
if (!raw_input_->isClearValue(phase)) {
|
||||||
|
vital::poly_float adjusted_phase = adjustBoostPhase(phase);
|
||||||
|
boostRange(last_phase_, adjusted_phase, kNumWrapPoints, kTailDecay);
|
||||||
|
last_phase_ = adjusted_phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFill(true);
|
||||||
|
setBoostAmount(findValue(Skin::kWidgetLineBoost));
|
||||||
|
setFillBoostAmount(findValue(Skin::kWidgetFillBoost));
|
||||||
|
|
||||||
|
setIndex(1);
|
||||||
|
setColor(position_color_stereo);
|
||||||
|
setFillColors(envelope_graph_fill_stereo_fade, envelope_graph_fill_stereo);
|
||||||
|
drawLines(open_gl, false);
|
||||||
|
|
||||||
|
setIndex(0);
|
||||||
|
setColor(position_color);
|
||||||
|
setFillColors(envelope_graph_fill_fade, envelope_graph_fill);
|
||||||
|
drawLines(open_gl, true);
|
||||||
|
|
||||||
|
setFill(false);
|
||||||
|
setBoostAmount(0.0f);
|
||||||
|
setFillBoostAmount(0.0f);
|
||||||
|
setColor(center);
|
||||||
|
LineEditor::render(open_gl, false);
|
||||||
|
|
||||||
|
setViewPort(open_gl);
|
||||||
|
drawPosition(open_gl, position_color_stereo, phase[1]);
|
||||||
|
drawPosition(open_gl, position_color, phase[0]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setBoostAmount(0.0f);
|
||||||
|
setFillBoostAmount(0.0f);
|
||||||
|
decayBoosts(0.0f);
|
||||||
|
|
||||||
|
setFill(true);
|
||||||
|
setColor(position_color_stereo);
|
||||||
|
setFillColors(envelope_graph_fill_stereo_fade, envelope_graph_fill_stereo);
|
||||||
|
drawLines(open_gl, false);
|
||||||
|
|
||||||
|
setColor(position_color);
|
||||||
|
setFillColors(envelope_graph_fill_fade, envelope_graph_fill);
|
||||||
|
drawLines(open_gl, true);
|
||||||
|
|
||||||
|
setFill(false);
|
||||||
|
setColor(center);
|
||||||
|
drawLines(open_gl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPoints(open_gl, animate);
|
||||||
|
renderCorners(open_gl, animate);
|
||||||
|
}
|
||||||
46
src/interface/editor_components/line_map_editor.h
Normal file
46
src/interface/editor_components/line_map_editor.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* Copyright 2013-2019 Matt Tytel
|
||||||
|
*
|
||||||
|
* vital is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* vital is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "JuceHeader.h"
|
||||||
|
|
||||||
|
#include "line_generator.h"
|
||||||
|
#include "open_gl_image.h"
|
||||||
|
#include "line_editor.h"
|
||||||
|
#include "synth_lfo.h"
|
||||||
|
#include "synth_module.h"
|
||||||
|
|
||||||
|
class LineMapEditor : public LineEditor {
|
||||||
|
public:
|
||||||
|
static constexpr float kTailDecay = 0.93f;
|
||||||
|
|
||||||
|
LineMapEditor(LineGenerator* line_source, String name);
|
||||||
|
virtual ~LineMapEditor();
|
||||||
|
|
||||||
|
void parentHierarchyChanged() override;
|
||||||
|
|
||||||
|
virtual void render(OpenGlWrapper& open_gl, bool animate) override;
|
||||||
|
void setAnimate(bool animate) { animate_ = animate; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const vital::StatusOutput* raw_input_;
|
||||||
|
bool animate_;
|
||||||
|
vital::poly_float last_phase_;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LineMapEditor)
|
||||||
|
};
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue