Browse Source

Initial commit for vitalium

Signed-off-by: falkTX <falktx@falktx.com>
tags/2021-03-15
falkTX 3 years ago
parent
commit
60228728e7
100 changed files with 28994 additions and 0 deletions
  1. +1
    -0
      meson_options.txt
  2. +1
    -0
      ports/meson.build
  3. +1
    -0
      ports/vitalium/.gitignore
  4. +9376
    -0
      ports/vitalium/BinaryData.cpp
  5. +108
    -0
      ports/vitalium/BinaryData.h
  6. +19
    -0
      ports/vitalium/JuceHeader.h
  7. +42
    -0
      ports/vitalium/JucePluginCharacteristics.h
  8. +27
    -0
      ports/vitalium/fetch-upstream-code.sh
  9. +53
    -0
      ports/vitalium/meson.build
  10. +103
    -0
      ports/vitalium/source/common/authentication.h
  11. +66
    -0
      ports/vitalium/source/common/border_bounds_constrainer.cpp
  12. +44
    -0
      ports/vitalium/source/common/border_bounds_constrainer.h
  13. +187
    -0
      ports/vitalium/source/common/fourier_transform.h
  14. +308
    -0
      ports/vitalium/source/common/line_generator.cpp
  15. +126
    -0
      ports/vitalium/source/common/line_generator.h
  16. +1956
    -0
      ports/vitalium/source/common/load_save.cpp
  17. +183
    -0
      ports/vitalium/source/common/load_save.h
  18. +324
    -0
      ports/vitalium/source/common/midi_manager.cpp
  19. +169
    -0
      ports/vitalium/source/common/midi_manager.h
  20. +38
    -0
      ports/vitalium/source/common/startup.cpp
  21. +39
    -0
      ports/vitalium/source/common/startup.h
  22. +770
    -0
      ports/vitalium/source/common/synth_base.cpp
  23. +211
    -0
      ports/vitalium/source/common/synth_base.h
  24. +172
    -0
      ports/vitalium/source/common/synth_constants.h
  25. +220
    -0
      ports/vitalium/source/common/synth_gui_interface.cpp
  26. +79
    -0
      ports/vitalium/source/common/synth_gui_interface.h
  27. +621
    -0
      ports/vitalium/source/common/synth_parameters.cpp
  28. +147
    -0
      ports/vitalium/source/common/synth_parameters.h
  29. +72
    -0
      ports/vitalium/source/common/synth_types.cpp
  30. +105
    -0
      ports/vitalium/source/common/synth_types.h
  31. +433
    -0
      ports/vitalium/source/common/tuning.cpp
  32. +82
    -0
      ports/vitalium/source/common/tuning.h
  33. +400
    -0
      ports/vitalium/source/common/wavetable/file_source.cpp
  34. +174
    -0
      ports/vitalium/source/common/wavetable/file_source.h
  35. +148
    -0
      ports/vitalium/source/common/wavetable/frequency_filter_modifier.cpp
  36. +87
    -0
      ports/vitalium/source/common/wavetable/frequency_filter_modifier.h
  37. +127
    -0
      ports/vitalium/source/common/wavetable/phase_modifier.cpp
  38. +79
    -0
      ports/vitalium/source/common/wavetable/phase_modifier.h
  39. +103
    -0
      ports/vitalium/source/common/wavetable/pitch_detector.cpp
  40. +42
    -0
      ports/vitalium/source/common/wavetable/pitch_detector.h
  41. +48
    -0
      ports/vitalium/source/common/wavetable/shepard_tone_source.cpp
  42. +35
    -0
      ports/vitalium/source/common/wavetable/shepard_tone_source.h
  43. +94
    -0
      ports/vitalium/source/common/wavetable/slew_limit_modifier.cpp
  44. +63
    -0
      ports/vitalium/source/common/wavetable/slew_limit_modifier.h
  45. +81
    -0
      ports/vitalium/source/common/wavetable/wave_fold_modifier.cpp
  46. +59
    -0
      ports/vitalium/source/common/wavetable/wave_fold_modifier.h
  47. +126
    -0
      ports/vitalium/source/common/wavetable/wave_line_source.cpp
  48. +78
    -0
      ports/vitalium/source/common/wavetable/wave_line_source.h
  49. +208
    -0
      ports/vitalium/source/common/wavetable/wave_source.cpp
  50. +97
    -0
      ports/vitalium/source/common/wavetable/wave_source.h
  51. +136
    -0
      ports/vitalium/source/common/wavetable/wave_warp_modifier.cpp
  52. +79
    -0
      ports/vitalium/source/common/wavetable/wave_warp_modifier.h
  53. +120
    -0
      ports/vitalium/source/common/wavetable/wave_window_modifier.cpp
  54. +84
    -0
      ports/vitalium/source/common/wavetable/wave_window_modifier.h
  55. +134
    -0
      ports/vitalium/source/common/wavetable/wavetable_component.cpp
  56. +78
    -0
      ports/vitalium/source/common/wavetable/wavetable_component.h
  57. +109
    -0
      ports/vitalium/source/common/wavetable/wavetable_component_factory.cpp
  58. +57
    -0
      ports/vitalium/source/common/wavetable/wavetable_component_factory.h
  59. +516
    -0
      ports/vitalium/source/common/wavetable/wavetable_creator.cpp
  60. +96
    -0
      ports/vitalium/source/common/wavetable/wavetable_creator.h
  61. +132
    -0
      ports/vitalium/source/common/wavetable/wavetable_group.cpp
  62. +58
    -0
      ports/vitalium/source/common/wavetable/wavetable_group.h
  63. +51
    -0
      ports/vitalium/source/common/wavetable/wavetable_keyframe.cpp
  64. +68
    -0
      ports/vitalium/source/common/wavetable/wavetable_keyframe.h
  65. +74
    -0
      ports/vitalium/source/interface/editor_components/audio_file_drop_source.h
  66. +236
    -0
      ports/vitalium/source/interface/editor_components/bar_renderer.cpp
  67. +150
    -0
      ports/vitalium/source/interface/editor_components/bar_renderer.h
  68. +724
    -0
      ports/vitalium/source/interface/editor_components/compressor_editor.cpp
  69. +172
    -0
      ports/vitalium/source/interface/editor_components/compressor_editor.h
  70. +311
    -0
      ports/vitalium/source/interface/editor_components/drag_drop_effect_order.cpp
  71. +111
    -0
      ports/vitalium/source/interface/editor_components/drag_drop_effect_order.h
  72. +1120
    -0
      ports/vitalium/source/interface/editor_components/envelope_editor.cpp
  73. +230
    -0
      ports/vitalium/source/interface/editor_components/envelope_editor.h
  74. +581
    -0
      ports/vitalium/source/interface/editor_components/equalizer_response.cpp
  75. +162
    -0
      ports/vitalium/source/interface/editor_components/equalizer_response.h
  76. +635
    -0
      ports/vitalium/source/interface/editor_components/filter_response.cpp
  77. +182
    -0
      ports/vitalium/source/interface/editor_components/filter_response.h
  78. +103
    -0
      ports/vitalium/source/interface/editor_components/incrementer_buttons.h
  79. +248
    -0
      ports/vitalium/source/interface/editor_components/lfo_editor.cpp
  80. +66
    -0
      ports/vitalium/source/interface/editor_components/lfo_editor.h
  81. +1106
    -0
      ports/vitalium/source/interface/editor_components/line_editor.cpp
  82. +236
    -0
      ports/vitalium/source/interface/editor_components/line_editor.h
  83. +129
    -0
      ports/vitalium/source/interface/editor_components/line_map_editor.cpp
  84. +46
    -0
      ports/vitalium/source/interface/editor_components/line_map_editor.h
  85. +256
    -0
      ports/vitalium/source/interface/editor_components/midi_keyboard.cpp
  86. +120
    -0
      ports/vitalium/source/interface/editor_components/midi_keyboard.h
  87. +343
    -0
      ports/vitalium/source/interface/editor_components/modulation_button.cpp
  88. +121
    -0
      ports/vitalium/source/interface/editor_components/modulation_button.h
  89. +214
    -0
      ports/vitalium/source/interface/editor_components/modulation_meter.cpp
  90. +73
    -0
      ports/vitalium/source/interface/editor_components/modulation_meter.h
  91. +185
    -0
      ports/vitalium/source/interface/editor_components/modulation_tab_selector.cpp
  92. +82
    -0
      ports/vitalium/source/interface/editor_components/modulation_tab_selector.h
  93. +149
    -0
      ports/vitalium/source/interface/editor_components/open_gl_background.cpp
  94. +62
    -0
      ports/vitalium/source/interface/editor_components/open_gl_background.h
  95. +193
    -0
      ports/vitalium/source/interface/editor_components/open_gl_component.cpp
  96. +114
    -0
      ports/vitalium/source/interface/editor_components/open_gl_component.h
  97. +143
    -0
      ports/vitalium/source/interface/editor_components/open_gl_image.cpp
  98. +95
    -0
      ports/vitalium/source/interface/editor_components/open_gl_image.h
  99. +86
    -0
      ports/vitalium/source/interface/editor_components/open_gl_image_component.cpp
  100. +286
    -0
      ports/vitalium/source/interface/editor_components/open_gl_image_component.h

+ 1
- 0
meson_options.txt View File

@@ -70,6 +70,7 @@ option('plugins',
'tal-vocoder-2',
'temper',
'vex',
'vitalium',
'wolpertinger',
],
)

+ 1
- 0
ports/meson.build View File

@@ -6,6 +6,7 @@ if linux_embed
else
plugins = [
'chow',
'vitalium',
]
endif



+ 1
- 0
ports/vitalium/.gitignore View File

@@ -0,0 +1 @@
vital-git

+ 9376
- 0
ports/vitalium/BinaryData.cpp
File diff suppressed because it is too large
View File


+ 108
- 0
ports/vitalium/BinaryData.h View File

@@ -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);
}

+ 19
- 0
ports/vitalium/JuceHeader.h View File

@@ -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

+ 42
- 0
ports/vitalium/JucePluginCharacteristics.h View File

@@ -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

+ 27
- 0
ports/vitalium/fetch-upstream-code.sh View File

@@ -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/

+ 53
- 0
ports/vitalium/meson.build View File

@@ -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

###############################################################################

+ 103
- 0
ports/vitalium/source/common/authentication.h View 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
- 0
ports/vitalium/source/common/border_bounds_constrainer.cpp View 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
- 0
ports/vitalium/source/common/border_bounds_constrainer.h View 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
- 0
ports/vitalium/source/common/fourier_transform.h View 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
- 0
ports/vitalium/source/common/line_generator.cpp View 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
- 0
ports/vitalium/source/common/line_generator.h View 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
- 0
ports/vitalium/source/common/load_save.cpp
File diff suppressed because it is too large
View File


+ 183
- 0
ports/vitalium/source/common/load_save.h View 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
- 0
ports/vitalium/source/common/midi_manager.cpp View 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
- 0
ports/vitalium/source/common/midi_manager.h View 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
- 0
ports/vitalium/source/common/startup.cpp View 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
- 0
ports/vitalium/source/common/startup.h View 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
- 0
ports/vitalium/source/common/synth_base.cpp View 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()) {
nu