@@ -70,6 +70,7 @@ option('plugins', | |||
'tal-vocoder-2', | |||
'temper', | |||
'vex', | |||
'vitalium', | |||
'wolpertinger', | |||
], | |||
) |
@@ -6,6 +6,7 @@ if linux_embed | |||
else | |||
plugins = [ | |||
'chow', | |||
'vitalium', | |||
] | |||
endif | |||
@@ -0,0 +1 @@ | |||
vital-git |
@@ -0,0 +1,108 @@ | |||
/* ========================================================================================= | |||
This is an auto-generated file: Any edits you make may be overwritten! | |||
*/ | |||
#pragma once | |||
namespace BinaryData | |||
{ | |||
extern const char* _5_Limit_scl; | |||
const int _5_Limit_sclSize = 87; | |||
extern const char* _7_Limit_scl; | |||
const int _7_Limit_sclSize = 85; | |||
extern const char* Pythagorean_scl; | |||
const int Pythagorean_sclSize = 108; | |||
extern const char* default_vitalskin; | |||
const int default_vitalskinSize = 10485; | |||
extern const char* DroidSansMono_ttf; | |||
const int DroidSansMono_ttfSize = 119380; | |||
extern const char* LatoLight_ttf; | |||
const int LatoLight_ttfSize = 77192; | |||
extern const char* LatoRegular_ttf; | |||
const int LatoRegular_ttfSize = 75136; | |||
extern const char* MontserratLight_otf; | |||
const int MontserratLight_otfSize = 228068; | |||
extern const char* MontserratRegular_ttf; | |||
const int MontserratRegular_ttfSize = 245708; | |||
extern const char* chorus_svg; | |||
const int chorus_svgSize = 12842; | |||
extern const char* cog_svg; | |||
const int cog_svgSize = 472; | |||
extern const char* compressor_svg; | |||
const int compressor_svgSize = 3175; | |||
extern const char* delay_svg; | |||
const int delay_svgSize = 3334; | |||
extern const char* distortion_svg; | |||
const int distortion_svgSize = 2015; | |||
extern const char* drag_drop_arrows_svg; | |||
const int drag_drop_arrows_svgSize = 3810; | |||
extern const char* effects_filter_svg; | |||
const int effects_filter_svgSize = 1495; | |||
extern const char* equalizer_svg; | |||
const int equalizer_svgSize = 2816; | |||
extern const char* flanger_svg; | |||
const int flanger_svgSize = 3334; | |||
extern const char* folder_svg; | |||
const int folder_svgSize = 154; | |||
extern const char* link_svg; | |||
const int link_svgSize = 317; | |||
extern const char* phaser_svg; | |||
const int phaser_svgSize = 1588; | |||
extern const char* reverb_svg; | |||
const int reverb_svgSize = 14394; | |||
extern const char* shuffle_svg; | |||
const int shuffle_svgSize = 382; | |||
extern const char* vital_ring_svg; | |||
const int vital_ring_svgSize = 2658; | |||
extern const char* vital_v_svg; | |||
const int vital_v_svgSize = 2658; | |||
extern const char* vital_word_svg; | |||
const int vital_word_svgSize = 3979; | |||
extern const char* vital_word_ring_svg; | |||
const int vital_word_ring_svgSize = 1888; | |||
// Number of elements in the namedResourceList and originalFileNames arrays. | |||
const int namedResourceListSize = 27; | |||
// Points to the start of a list of resource names. | |||
extern const char* namedResourceList[]; | |||
// Points to the start of a list of resource filenames. | |||
extern const char* originalFilenames[]; | |||
// If you provide the name of one of the binary resource variables above, this function will | |||
// return the corresponding data and its size (or a null pointer if the name isn't found). | |||
const char* getNamedResource (const char* resourceNameUTF8, int& dataSizeInBytes); | |||
// If you provide the name of one of the binary resource variables above, this function will | |||
// return the corresponding original, non-mangled filename (or a null pointer if the name isn't found). | |||
const char* getNamedResourceOriginalFilename (const char* resourceNameUTF8); | |||
} |
@@ -0,0 +1,19 @@ | |||
#ifndef __JUCE_HEADER_H__ | |||
#define __JUCE_HEADER_H__ | |||
#include "JucePluginMain.h" | |||
#include "BinaryData.h" | |||
using namespace juce; | |||
namespace ProjectInfo | |||
{ | |||
const char* const projectName = "Vitalium"; | |||
const char* const companyName = "DISTRHO"; | |||
const char* const versionString = "1.0.6"; | |||
const int versionNumber = 0x10006; | |||
} | |||
#endif |
@@ -0,0 +1,42 @@ | |||
#ifndef __JUCE_PLUGIN_CHARACTERISTICS_H__ | |||
#define __JUCE_PLUGIN_CHARACTERISTICS_H__ | |||
#define JucePlugin_Name "Vitalium" | |||
#define JucePlugin_Desc "Vitalium" | |||
#define JucePlugin_Manufacturer "DISTRHO" | |||
#define JucePlugin_ManufacturerCode 'DSTR' | |||
#define JucePlugin_ManufacturerEmail "info@kx.studio" | |||
#define JucePlugin_ManufacturerWebsite "vitalium.distrho.kx.studio" | |||
#define JucePlugin_PluginCode 'Vita' | |||
#define JucePlugin_VersionCode 0x10006 | |||
#define JucePlugin_VersionString "1.0.6" | |||
#define JucePlugin_MaxNumInputChannels 0 | |||
#define JucePlugin_MaxNumOutputChannels 2 | |||
#define JucePlugin_PreferredChannelConfigurations { 0, 2 } | |||
#define JucePlugin_IsSynth 1 | |||
#define JucePlugin_WantsMidiInput 1 | |||
#define JucePlugin_ProducesMidiOutput 0 | |||
#define JucePlugin_SilenceInProducesSilenceOut 0 | |||
#define JucePlugin_EditorRequiresKeyboardFocus 1 // pluginEditorRequiresKeys | |||
#define JucePlugin_IsMidiEffect 0 | |||
// aaxIdentifier="audio.vial.synth" | |||
// pluginAUExportPrefix="vial" | |||
// pluginRTASCategory="" | |||
// pluginAAXCategory="2048" | |||
#define JucePlugin_VSTUniqueID JucePlugin_PluginCode | |||
#define JucePlugin_VSTCategory kPlugCategSynth | |||
#define JucePlugin_LV2URI "urn:distrho:vitalium" | |||
#define JucePlugin_LV2Category "InstrumentPlugin" | |||
#define JucePlugin_WantsLV2Latency 0 | |||
#define JucePlugin_WantsLV2State 1 | |||
#define JucePlugin_WantsLV2TimePos 1 | |||
#define JucePlugin_WantsLV2FixedBlockSize 1 | |||
#define JucePlugin_WantsLV2Presets 0 | |||
#endif |
@@ -0,0 +1,27 @@ | |||
#!/bin/bash | |||
set -e | |||
cd $(dirname ${0}) | |||
# fetch git repo if not done yet | |||
if [ ! -d vital-git ]; then | |||
git clone --depth=1 --recursive git@github.com:mtytel/vital.git vital-git | |||
fi | |||
# clean and update git repo | |||
# pushd vital-git | |||
# git checkout . | |||
# git submodule update | |||
# git pull | |||
# git submodule update | |||
# popd | |||
# TODO generate binarydata ourselves | |||
cp vital-git/plugin/JuceLibraryCode/BinaryData.{cpp,h} . | |||
rm -rf source third_party | |||
mkdir source | |||
mkdir third_party | |||
cp -r vital-git/src/{common,interface,plugin,synthesis,unity_build} source/ | |||
cp -r vital-git/third_party/{concurrentqueue,json} third_party/ |
@@ -0,0 +1,53 @@ | |||
############################################################################### | |||
plugin_extra_build_flags = [ | |||
'-Wno-char-subscripts', | |||
'-Wno-deprecated-declarations', | |||
'-Wno-sign-compare', | |||
'-DNO_AUTH=1', | |||
] | |||
# -Ofast -flto | |||
# -ftree-vectorize -ftree-slp-vectorize -funroll-loops | |||
# -DREQUIRE_AUTH | |||
# "-DJUCE_DSP_USE_SHARED_FFTW=1" | |||
plugin_extra_include_dirs = include_directories([ | |||
'.', | |||
'source/common', | |||
'source/common/wavetable', | |||
'source/interface/editor_components', | |||
'source/interface/editor_sections', | |||
'source/interface/look_and_feel', | |||
'source/interface/wavetable', | |||
'source/interface/wavetable/editors', | |||
'source/interface/wavetable/overlays', | |||
'source/plugin', | |||
'source/synthesis/synth_engine', | |||
'source/synthesis/effects', | |||
'source/synthesis/filters', | |||
'source/synthesis/framework', | |||
'source/synthesis/lookups', | |||
'source/synthesis/modulators', | |||
'source/synthesis/modules', | |||
'source/synthesis/producers', | |||
'source/synthesis/utilities', | |||
'third_party', | |||
]) | |||
plugin_srcs = files([ | |||
'BinaryData.cpp', | |||
'source/unity_build/common.cpp', | |||
'source/unity_build/interface_editor_components.cpp', | |||
'source/unity_build/interface_editor_sections.cpp', | |||
'source/unity_build/interface_editor_sections2.cpp', | |||
'source/unity_build/interface_look_and_feel.cpp', | |||
'source/unity_build/interface_wavetable.cpp', | |||
'source/unity_build/plugin.cpp', | |||
'source/unity_build/synthesis.cpp', | |||
]) | |||
plugin_name = 'vitalium' | |||
plugin_uses_opengl = true | |||
############################################################################### |
@@ -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 |
@@ -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); | |||
} | |||
} |
@@ -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) | |||
}; | |||
@@ -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 |
@@ -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(); | |||
} |
@@ -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) | |||
}; | |||
@@ -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() { } | |||
}; | |||
@@ -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); | |||
} |
@@ -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) | |||
}; | |||
@@ -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 | |||
} |
@@ -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; | |||
}; | |||
@@ -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()) { | |||
nu |