Browse Source

Initial commit for vitalium

Signed-off-by: falkTX <falktx@falktx.com>
tags/2021-03-15
falkTX 4 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', 'tal-vocoder-2',
'temper', 'temper',
'vex', 'vex',
'vitalium',
'wolpertinger', 'wolpertinger',
], ],
) )

+ 1
- 0
ports/meson.build View File

@@ -6,6 +6,7 @@ if linux_embed
else else
plugins = [ plugins = [
'chow', 'chow',
'vitalium',
] ]
endif 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()) {
num_audio_rate++;
}
}
change.num_audio_rate = num_audio_rate;
return change;
}

bool SynthBase::isInvalidConnection(const vital::modulation_change& change) {
return change.poly_destination && change.poly_destination->router() == change.modulation_processor;
}

void SynthBase::connectModulation(vital::ModulationConnection* connection) {
vital::modulation_change change = createModulationChange(connection);
if (isInvalidConnection(change)) {
connection->destination_name = "";
connection->source_name = "";
}
else if (mod_connections_.count(connection) == 0) {
change.disconnecting = false;
mod_connections_.push_back(connection);
modulation_change_queue_.enqueue(change);
}
}

bool SynthBase::connectModulation(const std::string& source, const std::string& destination) {
vital::ModulationConnection* connection = getConnection(source, destination);
bool create = connection == nullptr;
if (create)
connection = getModulationBank().createConnection(source, destination);

if (connection)
connectModulation(connection);

return create;
}

void SynthBase::disconnectModulation(vital::ModulationConnection* connection) {
if (mod_connections_.count(connection) == 0)
return;

vital::modulation_change change = createModulationChange(connection);
connection->source_name = "";
connection->destination_name = "";

mod_connections_.remove(connection);
change.disconnecting = true;
modulation_change_queue_.enqueue(change);
}

void SynthBase::disconnectModulation(const std::string& source, const std::string& destination) {
vital::ModulationConnection* connection = getConnection(source, destination);
if (connection)
disconnectModulation(connection);
}

void SynthBase::clearModulations() {
clearModulationQueue();
while (mod_connections_.size()) {
vital::ModulationConnection* connection = *mod_connections_.begin();
mod_connections_.remove(connection);
vital::modulation_change change = createModulationChange(connection);
change.disconnecting = true;
engine_->disconnectModulation(change);
connection->source_name = "";
connection->destination_name = "";
}

int num_connections = static_cast<int>(getModulationBank().numConnections());
for (int i = 0; i < num_connections; ++i)
getModulationBank().atIndex(i)->modulation_processor->lineMapGenerator()->initLinear();

engine_->disableUnnecessaryModSources();
}

void SynthBase::forceShowModulation(const std::string& source, bool force) {
if (force)
engine_->enableModSource(source);
else if (!isSourceConnected(source))
engine_->disableModSource(source);
}

bool SynthBase::isModSourceEnabled(const std::string& source) {
return engine_->isModSourceEnabled(source);
}

int SynthBase::getNumModulations(const std::string& destination) {
int connections = 0;
for (vital::ModulationConnection* connection : mod_connections_) {
if (connection->destination_name == destination)
connections++;
}
return connections;
}

std::vector<vital::ModulationConnection*> SynthBase::getSourceConnections(const std::string& source) {
std::vector<vital::ModulationConnection*> connections;
for (vital::ModulationConnection* connection : mod_connections_) {
if (connection->source_name == source)
connections.push_back(connection);
}
return connections;
}

bool SynthBase::isSourceConnected(const std::string& source) {
for (vital::ModulationConnection* connection : mod_connections_) {
if (connection->source_name == source)
return true;
}
return false;
}

std::vector<vital::ModulationConnection*> SynthBase::getDestinationConnections(const std::string& destination) {
std::vector<vital::ModulationConnection*> connections;
for (vital::ModulationConnection* connection : mod_connections_) {
if (connection->destination_name == destination)
connections.push_back(connection);
}
return connections;
}

const vital::StatusOutput* SynthBase::getStatusOutput(const std::string& name) {
return engine_->getStatusOutput(name);
}

vital::Wavetable* SynthBase::getWavetable(int index) {
return engine_->getWavetable(index);
}

WavetableCreator* SynthBase::getWavetableCreator(int index) {
return wavetable_creators_[index].get();
}

vital::Sample* SynthBase::getSample() {
return engine_->getSample();
}

LineGenerator* SynthBase::getLfoSource(int index) {
return engine_->getLfoSource(index);
}

json SynthBase::saveToJson() {
return LoadSave::stateToJson(this, getCriticalSection());
}

int SynthBase::getSampleRate() {
return engine_->getSampleRate();
}

void SynthBase::initEngine() {
clearModulations();
if (getWavetableCreator(0)) {
for (int i = 0; i < vital::kNumOscillators; ++i)
getWavetableCreator(i)->init();

engine_->getSample()->init();
}

for (int i = 0; i < vital::kNumLfos; ++i)
getLfoSource(i)->initTriangle();

vital::control_map controls = engine_->getControls();
for (auto& control : controls) {
vital::ValueDetails details = vital::Parameters::getDetails(control.first);
control.second->set(details.default_value);
}
checkOversampling();

clearActiveFile();
}

void SynthBase::loadTuningFile(const File& file) {
tuning_.loadFile(file);
}

void SynthBase::loadInitPreset() {
pauseProcessing(true);
engine_->allSoundsOff();
initEngine();
LoadSave::initSaveInfo(save_info_);
pauseProcessing(false);
}

bool SynthBase::loadFromJson(const json& data) {
pauseProcessing(true);
engine_->allSoundsOff();
try {
bool result = LoadSave::jsonToState(this, save_info_, data);
pauseProcessing(false);
return result;
}
catch (const json::exception& e) {
pauseProcessing(false);
throw e;
}
}

bool SynthBase::loadFromFile(File preset, std::string& error) {
if (!preset.exists())
return false;
try {
json parsed_json_state = json::parse(preset.loadFileAsString().toStdString(), nullptr);
if (!loadFromJson(parsed_json_state)) {
error = "Preset was created with a newer version.";
return false;
}

active_file_ = preset;
}
catch (const json::exception& e) {
error = "Preset file is corrupted.";
return false;
}
setPresetName(preset.getFileNameWithoutExtension());

SynthGuiInterface* gui_interface = getGuiInterface();
if (gui_interface) {
gui_interface->updateFullGui();
gui_interface->notifyFresh();
}

return true;
}

void SynthBase::renderAudioToFile(File file, float seconds, float bpm, std::vector<int> notes, bool render_images) {
static constexpr int kSampleRate = 44100;
static constexpr int kPreProcessSamples = 44100;
static constexpr int kFadeSamples = 200;
static constexpr int kBufferSize = 64;
static constexpr int kVideoRate = 30;
static constexpr int kImageNumberPlaces = 3;
static constexpr int kImageWidth = 500;
static constexpr int kImageHeight = 250;
static constexpr int kOscilloscopeResolution = 512;
static constexpr float kFadeRatio = 0.3f;

ScopedLock lock(getCriticalSection());

processModulationChanges();
engine_->setSampleRate(kSampleRate);
engine_->setBpm(bpm);
engine_->updateAllModulationSwitches();

double sample_time = 1.0 / getSampleRate();
double current_time = -kPreProcessSamples * sample_time;

for (int samples = 0; samples < kPreProcessSamples; samples += kBufferSize) {
engine_->correctToTime(current_time);
current_time += kBufferSize * sample_time;
engine_->process(kBufferSize);
}

for (int note : notes)
engine_->noteOn(note, 0.7f, 0, 0);

file.deleteFile();
std::unique_ptr<FileOutputStream> file_stream = file.createOutputStream();
WavAudioFormat wav_format;
std::unique_ptr<AudioFormatWriter> writer(wav_format.createWriterFor(file_stream.get(), kSampleRate, 2, 16, {}, 0));

int on_samples = seconds * kSampleRate;
int total_samples = on_samples + seconds * kSampleRate * kFadeRatio;
std::unique_ptr<float[]> left_buffer = std::make_unique<float[]>(kBufferSize);
std::unique_ptr<float[]> right_buffer = std::make_unique<float[]>(kBufferSize);
float* buffers[2] = { left_buffer.get(), right_buffer.get() };
const vital::mono_float* engine_output = (const vital::mono_float*)engine_->output(0)->buffer;

#if JUCE_MODULE_AVAILABLE_juce_graphics
int current_image_index = -1;
PNGImageFormat png;
File images_folder = File::getCurrentWorkingDirectory().getChildFile("images");
if (!images_folder.exists() && render_images)
images_folder.createDirectory();
const vital::poly_float* memory = getOscilloscopeMemory();
#endif

for (int samples = 0; samples < total_samples; samples += kBufferSize) {
engine_->correctToTime(current_time);
current_time += kBufferSize * sample_time;
engine_->process(kBufferSize);
updateMemoryOutput(kBufferSize, engine_->output(0)->buffer);

if (on_samples > samples && on_samples <= samples + kBufferSize) {
for (int note : notes)
engine_->noteOff(note, 0.5f, 0, 0);
}

for (int i = 0; i < kBufferSize; ++i) {
vital::mono_float t = (total_samples - samples) / (1.0f * kFadeSamples);
t = vital::utils::min(t, 1.0f);
left_buffer[i] = t * engine_output[vital::poly_float::kSize * i];
right_buffer[i] = t * engine_output[vital::poly_float::kSize * i + 1];
}

writer->writeFromFloatArrays(buffers, 2, kBufferSize);

#if JUCE_MODULE_AVAILABLE_juce_graphics
int image_index = (samples * kVideoRate) / kSampleRate;
if (image_index > current_image_index && render_images) {
current_image_index = image_index;
String number(image_index);
while (number.length() < kImageNumberPlaces)
number = "0" + number;

File image_file = images_folder.getChildFile("rendered_image" + number + ".png");
FileOutputStream image_file_stream(image_file);
Image image(Image::RGB, kImageWidth, kImageHeight, true);
Graphics g(image);
g.fillAll(Colour(0xff1d2125));

Path left_path;
Path right_path;
left_path.startNewSubPath(-2.0f, kImageHeight / 2);
right_path.startNewSubPath(-2.0f, kImageHeight / 2);

for (int i = 0; i < kOscilloscopeResolution; ++i) {
float t = i / (kOscilloscopeResolution - 1.0f);
float memory_spot = (1.0f * i * vital::kOscilloscopeMemoryResolution) / kOscilloscopeResolution;
int memory_index = memory_spot;
float remainder = memory_spot - memory_index;
vital::poly_float from = memory[memory_index];
vital::poly_float to = memory[memory_index + 1];
vital::poly_float y = -vital::utils::interpolate(from, to, remainder) * kImageHeight / 2.0f + kImageHeight / 2;
left_path.lineTo(t * kImageWidth, y[0]);
right_path.lineTo(t * kImageWidth, y[1]);
}
left_path.lineTo(kImageWidth + 2.0f, kImageHeight / 2.0f);
right_path.lineTo(kImageWidth + 2.0f, kImageHeight / 2.0f);

g.setColour(Colour(0x64aa88ff));
g.fillPath(left_path);
g.fillPath(right_path);

g.setColour(Colour(0xffaa88ff));
g.strokePath(left_path, PathStrokeType(2.0f, PathStrokeType::curved, PathStrokeType::rounded));
g.strokePath(right_path, PathStrokeType(2.0f, PathStrokeType::curved, PathStrokeType::rounded));

png.writeImageToStream(image, image_file_stream);
}
#endif
}

writer->flush();
file_stream->flush();

writer = nullptr;
file_stream.release();
}

void SynthBase::renderAudioForResynthesis(float* data, int samples, int note) {
static constexpr int kPreProcessSamples = 44100;
static constexpr int kBufferSize = 64;

ScopedLock lock(getCriticalSection());

double sample_time = 1.0 / getSampleRate();
double current_time = -kPreProcessSamples * sample_time;

engine_->allSoundsOff();
for (int s = 0; s < kPreProcessSamples; s += kBufferSize) {
engine_->correctToTime(current_time);
current_time += kBufferSize * sample_time;
engine_->process(kBufferSize);
}

engine_->noteOn(note, 0.7f, 0, 0);
const vital::poly_float* engine_output = engine_->output(0)->buffer;
float max_value = 0.01f;
for (int s = 0; s < samples; s += kBufferSize) {
int num_samples = std::min(samples - s, kBufferSize);
engine_->correctToTime(current_time);
current_time += num_samples * sample_time;
engine_->process(num_samples);

for (int i = 0; i < num_samples; ++i) {
float sample = engine_output[i][0];
data[s + i] = sample;
max_value = std::max(max_value, fabsf(sample));
}
}

float scale = 1.0f / max_value;
for (int s = 0; s < samples; ++s)
data[s] *= scale;

engine_->allSoundsOff();
}

bool SynthBase::saveToFile(File preset) {
preset = preset.withFileExtension(String(vital::kPresetExtension));

File parent = preset.getParentDirectory();
if (!parent.exists()) {
if (!parent.createDirectory().wasOk() || !parent.hasWriteAccess())
return false;
}

setPresetName(preset.getFileNameWithoutExtension());

SynthGuiInterface* gui_interface = getGuiInterface();
if (gui_interface)
gui_interface->notifyFresh();

if (preset.replaceWithText(saveToJson().dump())) {
active_file_ = preset;
return true;
}
return false;
}

bool SynthBase::saveToActiveFile() {
if (!active_file_.exists() || !active_file_.hasWriteAccess())
return false;

return saveToFile(active_file_);
}

void SynthBase::setMpeEnabled(bool enabled) {
midi_manager_->setMpeEnabled(enabled);
}

void SynthBase::processAudio(AudioSampleBuffer* buffer, int channels, int samples, int offset) {
if (expired_)
return;

engine_->process(samples);
writeAudio(buffer, channels, samples, offset);
}

void SynthBase::processAudioWithInput(AudioSampleBuffer* buffer, const vital::poly_float* input_buffer,
int channels, int samples, int offset) {
if (expired_)
return;

engine_->processWithInput(input_buffer, samples);
writeAudio(buffer, channels, samples, offset);
}

void SynthBase::writeAudio(AudioSampleBuffer* buffer, int channels, int samples, int offset) {
const vital::mono_float* engine_output = (const vital::mono_float*)engine_->output(0)->buffer;
for (int channel = 0; channel < channels; ++channel) {
float* channel_data = buffer->getWritePointer(channel, offset);

for (int i = 0; i < samples; ++i) {
channel_data[i] = engine_output[vital::poly_float::kSize * i + channel];
VITAL_ASSERT(std::isfinite(channel_data[i]));
}
}

updateMemoryOutput(samples, engine_->output(0)->buffer);
}

void SynthBase::processMidi(MidiBuffer& midi_messages, int start_sample, int end_sample) {
bool process_all = end_sample == 0;
for (const MidiMessageMetadata message : midi_messages) {
int midi_sample = message.samplePosition;
if (process_all || (midi_sample >= start_sample && midi_sample < end_sample))
midi_manager_->processMidiMessage(message.getMessage(), midi_sample - start_sample);
}
}

void SynthBase::processKeyboardEvents(MidiBuffer& buffer, int num_samples) {
midi_manager_->replaceKeyboardMessages(buffer, num_samples);
}

void SynthBase::processModulationChanges() {
vital::modulation_change change;
while (getNextModulationChange(change)) {
if (change.disconnecting)
engine_->disconnectModulation(change);
else
engine_->connectModulation(change);
}
}

void SynthBase::updateMemoryOutput(int samples, const vital::poly_float* audio) {
for (int i = 0; i < samples; ++i)
audio_memory_->push(audio[i]);

vital::mono_float last_played = engine_->getLastActiveNote();
last_played = vital::utils::clamp(last_played, kOutputWindowMinNote, kOutputWindowMaxNote);

int num_pressed = engine_->getNumPressedNotes();
int output_inc = std::max<int>(1, engine_->getSampleRate() / vital::kOscilloscopeMemorySampleRate);
int oscilloscope_samples = 2 * vital::kOscilloscopeMemoryResolution;

if (last_played && (last_played_note_ != last_played || num_pressed > last_num_pressed_)) {
last_played_note_ = last_played;
vital::mono_float frequency = vital::utils::midiNoteToFrequency(last_played_note_);
vital::mono_float period = engine_->getSampleRate() / frequency;
int window_length = output_inc * vital::kOscilloscopeMemoryResolution;

memory_reset_period_ = period;
while (memory_reset_period_ < window_length)
memory_reset_period_ += memory_reset_period_;

memory_reset_period_ = std::min(memory_reset_period_, 2.0f * window_length);
memory_index_ = 0;
vital::utils::copyBuffer(oscilloscope_memory_, oscilloscope_memory_write_, oscilloscope_samples);
}
last_num_pressed_ = num_pressed;

for (; memory_input_offset_ < samples; memory_input_offset_ += output_inc) {
int input_index = vital::utils::iclamp(memory_input_offset_, 0, samples);
memory_index_ = vital::utils::iclamp(memory_index_, 0, oscilloscope_samples - 1);
VITAL_ASSERT(input_index >= 0);
VITAL_ASSERT(input_index < samples);
VITAL_ASSERT(memory_index_ >= 0);
VITAL_ASSERT(memory_index_ < oscilloscope_samples);
oscilloscope_memory_write_[memory_index_++] = audio[input_index];

if (memory_index_ * output_inc >= memory_reset_period_) {
memory_input_offset_ += memory_reset_period_ - memory_index_ * output_inc;
memory_index_ = 0;
vital::utils::copyBuffer(oscilloscope_memory_, oscilloscope_memory_write_, oscilloscope_samples);
}
}

memory_input_offset_ -= samples;
}

void SynthBase::armMidiLearn(const std::string& name) {
midi_manager_->armMidiLearn(name);
}

void SynthBase::cancelMidiLearn() {
midi_manager_->cancelMidiLearn();
}

void SynthBase::clearMidiLearn(const std::string& name) {
midi_manager_->clearMidiLearn(name);
}

bool SynthBase::isMidiMapped(const std::string& name) {
return midi_manager_->isMidiMapped(name);
}

void SynthBase::setAuthor(const String& author) {
save_info_["author"] = author;
}

void SynthBase::setComments(const String& comments) {
save_info_["comments"] = comments;
}

void SynthBase::setStyle(const String& style) {
save_info_["style"] = style;
}

void SynthBase::setPresetName(const String& preset_name) {
save_info_["preset_name"] = preset_name;
}

void SynthBase::setMacroName(int index, const String& macro_name) {
save_info_["macro" + std::to_string(index + 1)] = macro_name;
}

String SynthBase::getAuthor() {
return save_info_["author"];
}

String SynthBase::getComments() {
return save_info_["comments"];
}

String SynthBase::getStyle() {
return save_info_["style"];
}

String SynthBase::getPresetName() {
return save_info_["preset_name"];
}

String SynthBase::getMacroName(int index) {
String name = save_info_["macro" + std::to_string(index + 1)];
if (name.trim().isEmpty())
return "MACRO " + String(index + 1);
return name;
}

const vital::StereoMemory* SynthBase::getEqualizerMemory() {
if (engine_)
return engine_->getEqualizerMemory();
return nullptr;
}

vital::ModulationConnectionBank& SynthBase::getModulationBank() {
return engine_->getModulationBank();
}

void SynthBase::notifyOversamplingChanged() {
pauseProcessing(true);
engine_->allSoundsOff();
checkOversampling();
pauseProcessing(false);
}

void SynthBase::checkOversampling() {
return engine_->checkOversampling();
}

void SynthBase::ValueChangedCallback::messageCallback() {
if (auto synth_base = listener.lock()) {
SynthGuiInterface* gui_interface = (*synth_base)->getGuiInterface();
if (gui_interface) {
gui_interface->updateGuiControl(control_name, value);
if (control_name != "pitch_wheel")
gui_interface->notifyChange();
}
}
}

+ 211
- 0
ports/vitalium/source/common/synth_base.h View File

@@ -0,0 +1,211 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "concurrentqueue/concurrentqueue.h"
#include "line_generator.h"
#include "synth_constants.h"
#include "synth_types.h"
#include "midi_manager.h"
#include "tuning.h"
#include "wavetable_creator.h"

#include <set>
#include <string>

namespace vital {
class SoundEngine;
struct Output;
class StatusOutput;
class StereoMemory;
class Sample;
class WaveFrame;
class Wavetable;
}

class SynthGuiInterface;

class SynthBase : public MidiManager::Listener {
public:
static constexpr float kOutputWindowMinNote = 16.0f;
static constexpr float kOutputWindowMaxNote = 128.0f;

SynthBase();
virtual ~SynthBase();

void valueChanged(const std::string& name, vital::mono_float value);
void valueChangedThroughMidi(const std::string& name, vital::mono_float value) override;
void pitchWheelMidiChanged(vital::mono_float value) override;
void modWheelMidiChanged(vital::mono_float value) override;
void pitchWheelGuiChanged(vital::mono_float value);
void modWheelGuiChanged(vital::mono_float value);
void presetChangedThroughMidi(File preset) override;
void valueChangedExternal(const std::string& name, vital::mono_float value);
void valueChangedInternal(const std::string& name, vital::mono_float value);
bool connectModulation(const std::string& source, const std::string& destination);
void connectModulation(vital::ModulationConnection* connection);
void disconnectModulation(const std::string& source, const std::string& destination);
void disconnectModulation(vital::ModulationConnection* connection);
void clearModulations();
void forceShowModulation(const std::string& source, bool force);
bool isModSourceEnabled(const std::string& source);
int getNumModulations(const std::string& destination);
int getConnectionIndex(const std::string& source, const std::string& destination);
vital::CircularQueue<vital::ModulationConnection*> getModulationConnections() { return mod_connections_; }
std::vector<vital::ModulationConnection*> getSourceConnections(const std::string& source);
bool isSourceConnected(const std::string& source);
std::vector<vital::ModulationConnection*> getDestinationConnections(const std::string& destination);

const vital::StatusOutput* getStatusOutput(const std::string& name);

vital::Wavetable* getWavetable(int index);
WavetableCreator* getWavetableCreator(int index);
vital::Sample* getSample();
LineGenerator* getLfoSource(int index);

int getSampleRate();
void initEngine();
void loadTuningFile(const File& file);
void loadInitPreset();
bool loadFromFile(File preset, std::string& error);
void renderAudioToFile(File file, float seconds, float bpm, std::vector<int> notes, bool render_images);
void renderAudioForResynthesis(float* data, int samples, int note);
bool saveToFile(File preset);
bool saveToActiveFile();
void clearActiveFile() { active_file_ = File(); }
File getActiveFile() { return active_file_; }

void setMpeEnabled(bool enabled);
virtual void beginChangeGesture(const std::string& name) { }
virtual void endChangeGesture(const std::string& name) { }
virtual void setValueNotifyHost(const std::string& name, vital::mono_float value) { }

void armMidiLearn(const std::string& name);
void cancelMidiLearn();
void clearMidiLearn(const std::string& name);
bool isMidiMapped(const std::string& name);

void setAuthor(const String& author);
void setComments(const String& comments);
void setStyle(const String& comments);
void setPresetName(const String& preset_name);
void setMacroName(int index, const String& macro_name);
String getAuthor();
String getComments();
String getStyle();
String getPresetName();
String getMacroName(int index);

vital::control_map& getControls() { return controls_; }
vital::SoundEngine* getEngine() { return engine_.get(); }
MidiKeyboardState* getKeyboardState() { return keyboard_state_.get(); }
const vital::poly_float* getOscilloscopeMemory() { return oscilloscope_memory_; }
const vital::StereoMemory* getAudioMemory() { return audio_memory_.get(); }
const vital::StereoMemory* getEqualizerMemory();
vital::ModulationConnectionBank& getModulationBank();
void notifyOversamplingChanged();
void checkOversampling();
virtual const CriticalSection& getCriticalSection() = 0;
virtual void pauseProcessing(bool pause) = 0;
Tuning* getTuning() { return &tuning_; }

struct ValueChangedCallback : public CallbackMessage {
ValueChangedCallback(std::shared_ptr<SynthBase*> listener, std::string name, vital::mono_float val) :
listener(listener), control_name(std::move(name)), value(val) { }

void messageCallback() override;

std::weak_ptr<SynthBase*> listener;
std::string control_name;
vital::mono_float value;
};

protected:
vital::modulation_change createModulationChange(vital::ModulationConnection* connection);
bool isInvalidConnection(const vital::modulation_change& change);
virtual SynthGuiInterface* getGuiInterface() = 0;
json saveToJson();
bool loadFromJson(const json& state);
vital::ModulationConnection* getConnection(const std::string& source, const std::string& destination);

inline bool getNextModulationChange(vital::modulation_change& change) {
return modulation_change_queue_.try_dequeue_non_interleaved(change);
}
inline void clearModulationQueue() {
vital::modulation_change change;
while (modulation_change_queue_.try_dequeue_non_interleaved(change))
;
}

void processAudio(AudioSampleBuffer* buffer, int channels, int samples, int offset);
void processAudioWithInput(AudioSampleBuffer* buffer, const vital::poly_float* input_buffer,
int channels, int samples, int offset);
void writeAudio(AudioSampleBuffer* buffer, int channels, int samples, int offset);
void processMidi(MidiBuffer& buffer, int start_sample = 0, int end_sample = 0);
void processKeyboardEvents(MidiBuffer& buffer, int num_samples);
void processModulationChanges();
void updateMemoryOutput(int samples, const vital::poly_float* audio);

std::unique_ptr<vital::SoundEngine> engine_;
std::unique_ptr<MidiManager> midi_manager_;
std::unique_ptr<MidiKeyboardState> keyboard_state_;

std::unique_ptr<WavetableCreator> wavetable_creators_[vital::kNumOscillators];
std::shared_ptr<SynthBase*> self_reference_;

File active_file_;
vital::poly_float oscilloscope_memory_[2 * vital::kOscilloscopeMemoryResolution];
vital::poly_float oscilloscope_memory_write_[2 * vital::kOscilloscopeMemoryResolution];
std::unique_ptr<vital::StereoMemory> audio_memory_;
vital::mono_float last_played_note_;
int last_num_pressed_;
vital::mono_float memory_reset_period_;
vital::mono_float memory_input_offset_;
int memory_index_;
bool expired_;

std::map<std::string, String> save_info_;
vital::control_map controls_;
vital::CircularQueue<vital::ModulationConnection*> mod_connections_;
moodycamel::ConcurrentQueue<vital::control_change> value_change_queue_;
moodycamel::ConcurrentQueue<vital::modulation_change> modulation_change_queue_;
Tuning tuning_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SynthBase)
};

class HeadlessSynth : public SynthBase {
public:
virtual const CriticalSection& getCriticalSection() override {
return critical_section_;
}

virtual void pauseProcessing(bool pause) override {
if (pause)
critical_section_.enter();
else
critical_section_.exit();
}

protected:
virtual SynthGuiInterface* getGuiInterface() override { return nullptr; }

private:
CriticalSection critical_section_;
};

+ 172
- 0
ports/vitalium/source/common/synth_constants.h View File

@@ -0,0 +1,172 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "value.h"

#include <string>

namespace vital {

constexpr int kNumLfos = 8;
constexpr int kNumOscillators = 3;
constexpr int kNumOscillatorWaveFrames = 257;
constexpr int kNumEnvelopes = 6;
constexpr int kNumRandomLfos = 4;
constexpr int kNumMacros = 4;
constexpr int kNumFilters = 2;
constexpr int kNumFormants = 4;
constexpr int kNumChannels = 2;
constexpr int kMaxPolyphony = 33;
constexpr int kMaxActivePolyphony = 32;
constexpr int kLfoDataResolution = 2048;
constexpr int kMaxModulationConnections = 64;

constexpr int kOscilloscopeMemorySampleRate = 22000;
constexpr int kOscilloscopeMemoryResolution = 512;
constexpr int kAudioMemorySamples = 1 << 15;
constexpr int kDefaultWindowWidth = 1400;
constexpr int kDefaultWindowHeight = 820;
constexpr int kMinWindowWidth = 350;
constexpr int kMinWindowHeight = 205;

constexpr int kDefaultKeyboardOffset = 48;
constexpr wchar_t kDefaultKeyboardOctaveUp = 'x';
constexpr wchar_t kDefaultKeyboardOctaveDown = 'z';
const std::wstring kDefaultKeyboard = L"awsedftgyhujkolp;'";

const std::string kPresetExtension = "vital";
const std::string kWavetableExtension = "vitaltable";
const std::string kWavetableExtensionsList = "*." + vital::kWavetableExtension + ";*.wav;*.flac";
const std::string kSampleExtensionsList = "*.wav;*.flac";
const std::string kSkinExtension = "vitalskin";
const std::string kLfoExtension = "vitallfo";
const std::string kBankExtension = "vitalbank";

namespace constants {
enum SourceDestination {
kFilter1,
kFilter2,
kDualFilters,
kEffects,
kDirectOut,
kNumSourceDestinations
};

static SourceDestination toggleFilter1(SourceDestination current_destination, bool on) {
if (on) {
if (current_destination == vital::constants::kFilter2)
return vital::constants::kDualFilters;
else
return vital::constants::kFilter1;
}
else if (current_destination == vital::constants::kDualFilters)
return vital::constants::kFilter2;
else if (current_destination == vital::constants::kFilter1)
return vital::constants::kEffects;

return current_destination;
}

static SourceDestination toggleFilter2(SourceDestination current_destination, bool on) {
if (on) {
if (current_destination == vital::constants::kFilter1)
return vital::constants::kDualFilters;
else
return vital::constants::kFilter2;
}
else if (current_destination == vital::constants::kDualFilters)
return vital::constants::kFilter1;
else if (current_destination == vital::constants::kFilter2)
return vital::constants::kEffects;

return current_destination;
}

enum Effect {
kChorus,
kCompressor,
kDelay,
kDistortion,
kEq,
kFilterFx,
kFlanger,
kPhaser,
kReverb,
kNumEffects
};

enum FilterModel {
kAnalog,
kDirty,
kLadder,
kDigital,
kDiode,
kFormant,
kComb,
kPhase,
kNumFilterModels
};

enum RetriggerStyle {
kFree,
kRetrigger,
kSyncToPlayHead,
kNumRetriggerStyles,
};

constexpr int kNumSyncedFrequencyRatios = 13;
constexpr vital::mono_float kSyncedFrequencyRatios[kNumSyncedFrequencyRatios] = {
0.0f,
1.0f / 128.0f,
1.0f / 64.0f,
1.0f / 32.0f,
1.0f / 16.0f,
1.0f / 8.0f,
1.0f / 4.0f,
1.0f / 2.0f,
1.0f,
2.0f,
4.0f,
8.0f,
16.0f
};

const poly_float kLeftOne(1.0f, 0.0f);
const poly_float kRightOne(0.0f, 1.0f);
const poly_float kFirstVoiceOne(1.0f, 1.0f, 0.0f, 0.0f);
const poly_float kSecondVoiceOne(0.0f, 0.0f, 1.0f, 1.0f);
const poly_float kStereoSplit = kLeftOne - kRightOne;
const poly_float kPolySqrt2 = kSqrt2;
const poly_mask kFullMask = poly_float::equal(0.0f, 0.0f);
const poly_mask kLeftMask = poly_float::equal(kLeftOne, 1.0f);
const poly_mask kRightMask = poly_float::equal(kRightOne, 1.0f);
const poly_mask kFirstMask = poly_float::equal(kFirstVoiceOne, 1.0f);
const poly_mask kSecondMask = poly_float::equal(kSecondVoiceOne, 1.0f);

const cr::Value kValueZero(0.0f);
const cr::Value kValueOne(1.0f);
const cr::Value kValueTwo(2.0f);
const cr::Value kValueHalf(0.5f);
const cr::Value kValueFifth(0.2f);
const cr::Value kValueTenth(0.1f);
const cr::Value kValuePi(kPi);
const cr::Value kValue2Pi(2.0f * kPi);
const cr::Value kValueSqrt2(kSqrt2);
const cr::Value kValueNegOne(-1.0f);
} // namespace constants
} // namespace vital

+ 220
- 0
ports/vitalium/source/common/synth_gui_interface.cpp View File

@@ -0,0 +1,220 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "synth_gui_interface.h"
#include "authentication.h"
#include "modulation_connection_processor.h"
#include "sound_engine.h"
#include "load_save.h"
#include "synth_base.h"

SynthGuiData::SynthGuiData(SynthBase* synth_base) : synth(synth_base) {
controls = synth->getControls();
mono_modulations = synth->getEngine()->getMonoModulations();
poly_modulations = synth->getEngine()->getPolyModulations();
modulation_sources = synth->getEngine()->getModulationSources();
for (int i = 0; i < vital::kNumOscillators; ++i)
wavetable_creators[i] = synth->getWavetableCreator(i);
}

#if HEADLESS

SynthGuiInterface::SynthGuiInterface(SynthBase* synth, bool use_gui) : synth_(synth) { }
SynthGuiInterface::~SynthGuiInterface() { }
void SynthGuiInterface::updateFullGui() { }
void SynthGuiInterface::updateGuiControl(const std::string& name, vital::mono_float value) { }
vital::mono_float SynthGuiInterface::getControlValue(const std::string& name) { return 0.0f; }
void SynthGuiInterface::connectModulation(std::string source, std::string destination) { }
void SynthGuiInterface::connectModulation(vital::ModulationConnection* connection) { }
void SynthGuiInterface::setModulationValues(const std::string& source, const std::string& destination,
vital::mono_float amount, bool bipolar, bool stereo, bool bypass) { }
void SynthGuiInterface::disconnectModulation(std::string source, std::string destination) { }
void SynthGuiInterface::disconnectModulation(vital::ModulationConnection* connection) { }
void SynthGuiInterface::setFocus() { }
void SynthGuiInterface::notifyChange() { }
void SynthGuiInterface::notifyFresh() { }
void SynthGuiInterface::openSaveDialog() { }
void SynthGuiInterface::externalPresetLoaded(File preset) { }
void SynthGuiInterface::setGuiSize(float scale) { }

#else

#include "default_look_and_feel.h"
#include "full_interface.h"

SynthGuiInterface::SynthGuiInterface(SynthBase* synth, bool use_gui) : synth_(synth) {
if (use_gui) {
LineGenerator* lfo_sources[vital::kNumLfos];
for (int i = 0; i < vital::kNumLfos; ++i)
lfo_sources[i] = synth->getLfoSource(i);
SynthGuiData synth_data(synth_);
gui_ = std::make_unique<FullInterface>(&synth_data);
}
}

SynthGuiInterface::~SynthGuiInterface() { }

void SynthGuiInterface::updateFullGui() {
if (gui_ == nullptr)
return;

gui_->setAllValues(synth_->getControls());
gui_->reset();
}

void SynthGuiInterface::updateGuiControl(const std::string& name, vital::mono_float value) {
if (gui_ == nullptr)
return;

gui_->setValue(name, value, NotificationType::dontSendNotification);
}

vital::mono_float SynthGuiInterface::getControlValue(const std::string& name) {
return synth_->getControls()[name]->value();
}

void SynthGuiInterface::notifyModulationsChanged() {
gui_->modulationChanged();
}

void SynthGuiInterface::notifyModulationValueChanged(int index) {
gui_->modulationValueChanged(index);
}

void SynthGuiInterface::connectModulation(std::string source, std::string destination) {
bool created = synth_->connectModulation(source, destination);
if (created)
initModulationValues(source, destination);
notifyModulationsChanged();
}

void SynthGuiInterface::connectModulation(vital::ModulationConnection* connection) {
synth_->connectModulation(connection);
notifyModulationsChanged();
}

void SynthGuiInterface::initModulationValues(const std::string& source, const std::string& destination) {
int connection_index = synth_->getConnectionIndex(source, destination);
if (connection_index < 0)
return;

vital::ModulationConnection* connection = synth_->getModulationBank().atIndex(connection_index);
LineGenerator* map_generator = connection->modulation_processor->lineMapGenerator();
map_generator->initLinear();

std::string power_name = "modulation_" + std::to_string(connection_index + 1) + "_power";
synth_->valueChanged(power_name, 0.0f);
gui_->setValue(power_name, 0.0f, NotificationType::dontSendNotification);
}

void SynthGuiInterface::setModulationValues(const std::string& source, const std::string& destination,
vital::mono_float amount, bool bipolar, bool stereo, bool bypass) {
int connection_index = synth_->getConnectionIndex(source, destination);
if (connection_index < 0)
return;

std::string number = std::to_string(connection_index + 1);
std::string amount_name = "modulation_" + number + "_amount";
std::string bipolar_name = "modulation_" + number + "_bipolar";
std::string stereo_name = "modulation_" + number + "_stereo";
std::string bypass_name = "modulation_" + number + "_bypass";

float bipolar_amount = bipolar ? 1.0f : 0.0f;
float stereo_amount = stereo ? 1.0f : 0.0f;
float bypass_amount = bypass ? 1.0f : 0.0f;

synth_->valueChanged(amount_name, amount);
synth_->valueChanged(bipolar_name, bipolar_amount);
synth_->valueChanged(stereo_name, stereo_amount);
synth_->valueChanged(bypass_name, bypass_amount);
gui_->setValue(amount_name, amount, NotificationType::dontSendNotification);
gui_->setValue(bipolar_name, bipolar_amount, NotificationType::dontSendNotification);
gui_->setValue(stereo_name, stereo_amount, NotificationType::dontSendNotification);
gui_->setValue(bypass_name, bypass_amount, NotificationType::dontSendNotification);
}

void SynthGuiInterface::disconnectModulation(std::string source, std::string destination) {
synth_->disconnectModulation(source, destination);
notifyModulationsChanged();
}

void SynthGuiInterface::disconnectModulation(vital::ModulationConnection* connection) {
synth_->disconnectModulation(connection);
notifyModulationsChanged();
}

void SynthGuiInterface::setFocus() {
if (gui_ == nullptr)
return;

gui_->setFocus();
}

void SynthGuiInterface::notifyChange() {
if (gui_ == nullptr)
return;

gui_->notifyChange();
}

void SynthGuiInterface::notifyFresh() {
if (gui_ == nullptr)
return;

gui_->notifyFresh();
}

void SynthGuiInterface::openSaveDialog() {
if (gui_ == nullptr)
return;

gui_->openSaveDialog();
}

void SynthGuiInterface::externalPresetLoaded(File preset) {
if (gui_ == nullptr)
return;

gui_->externalPresetLoaded(preset);
}

void SynthGuiInterface::setGuiSize(float scale) {
if (gui_ == nullptr)
return;

Point<int> position = gui_->getScreenBounds().getCentre();
const Displays::Display& display = Desktop::getInstance().getDisplays().findDisplayForPoint(position);

Rectangle<int> display_area = Desktop::getInstance().getDisplays().getTotalBounds(true);
ComponentPeer* peer = gui_->getPeer();
if (peer)
peer->getFrameSize().subtractFrom(display_area);

float window_size = scale / display.scale;
window_size = std::min(window_size, display_area.getWidth() * 1.0f / vital::kDefaultWindowWidth);
window_size = std::min(window_size, display_area.getHeight() * 1.0f / vital::kDefaultWindowHeight);
LoadSave::saveWindowSize(window_size);

int width = std::round(window_size * vital::kDefaultWindowWidth);
int height = std::round(window_size * vital::kDefaultWindowHeight);

Rectangle<int> bounds = gui_->getBounds();
bounds.setWidth(width);
bounds.setHeight(height);
gui_->getParentComponent()->setBounds(bounds);
gui_->redoBackground();
}
#endif

+ 79
- 0
ports/vitalium/source/common/synth_gui_interface.h View File

@@ -0,0 +1,79 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "synth_base.h"

#if HEADLESS

class FullInterface { };
class AudioDeviceManager { };

#endif

class FullInterface;
class Authentication;

struct SynthGuiData {
SynthGuiData(SynthBase* synth_base);

vital::control_map controls;
vital::output_map mono_modulations;
vital::output_map poly_modulations;
vital::output_map modulation_sources;
WavetableCreator* wavetable_creators[vital::kNumOscillators];
SynthBase* synth;
};

class SynthGuiInterface {
public:
SynthGuiInterface(SynthBase* synth, bool use_gui = true);
virtual ~SynthGuiInterface();

virtual AudioDeviceManager* getAudioDeviceManager() { return nullptr; }
SynthBase* getSynth() { return synth_; }
virtual void updateFullGui();
virtual void updateGuiControl(const std::string& name, vital::mono_float value);
vital::mono_float getControlValue(const std::string& name);

void notifyModulationsChanged();
void notifyModulationValueChanged(int index);
void connectModulation(std::string source, std::string destination);
void connectModulation(vital::ModulationConnection* connection);
void setModulationValues(const std::string& source, const std::string& destination,
vital::mono_float amount, bool bipolar, bool stereo, bool bypass);
void initModulationValues(const std::string& source, const std::string& destination);
void disconnectModulation(std::string source, std::string destination);
void disconnectModulation(vital::ModulationConnection* connection);

void setFocus();
void notifyChange();
void notifyFresh();
void openSaveDialog();
void externalPresetLoaded(File preset);
void setGuiSize(float scale);
FullInterface* getGui() { return gui_.get(); }

protected:
SynthBase* synth_;

std::unique_ptr<FullInterface> gui_;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SynthGuiInterface)
};


+ 621
- 0
ports/vitalium/source/common/synth_parameters.cpp View File

@@ -0,0 +1,621 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "synth_parameters.h"

#include "compressor.h"
#include "distortion.h"
#include "digital_svf.h"
#include "synth_constants.h"
#include "random_lfo.h"
#include "synth_lfo.h"
#include "synth_oscillator.h"
#include "synth_strings.h"
#include "voice_handler.h"
#include "wavetable.h"
#include "utils.h"

#include <cfloat>

namespace vital {

bool compareValueDetails(const ValueDetails* a, const ValueDetails* b) {
if (a->version_added != b->version_added)
return a->version_added < b->version_added;
return a->name.compare(b->name) < 0;
}

using namespace constants;
static const std::string kIdDelimiter = "_";
static const std::string kEnvIdPrefix = "env";
static const std::string kLfoIdPrefix = "lfo";
static const std::string kRandomIdPrefix = "random";
static const std::string kOscIdPrefix = "osc";
static const std::string kFilterIdPrefix = "filter";
static const std::string kModulationIdPrefix = "modulation";
static const std::string kNameDelimiter = " ";
static const std::string kEnvNamePrefix = "Envelope";
static const std::string kLfoNamePrefix = "LFO";
static const std::string kRandomNamePrefix = "Random LFO";
static const std::string kOscNamePrefix = "Oscillator";
static const std::string kFilterNamePrefix = "Filter";
static const std::string kModulationNamePrefix = "Modulation";

const ValueDetails ValueDetailsLookup::parameter_list[] = {
{ "bypass", 0x000702, 0.0, 1.0, 0.0, 0.0, 60.0,
ValueDetails::kIndexed, false, "", "Bypass", nullptr },
{ "beats_per_minute", 0x000000, 0.333333333, 5.0, 2.0, 0.0, 60.0,
ValueDetails::kLinear, false, "", "Beats Per Minute", nullptr },
{ "delay_dry_wet", 0x000000, 0.0, 1.0, 0.3334, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Delay Mix", nullptr },
{ "delay_feedback", 0x000000, -1.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Delay Feedback", nullptr },
{ "delay_frequency", 0x000000, -2.0, 9.0, 2.0, 0.0, 1.0,
ValueDetails::kExponential, true, " secs", "Delay Frequency", nullptr },
{ "delay_aux_frequency", 0x000507, -2.0, 9.0, 2.0, 0.0, 1.0,
ValueDetails::kExponential, true, " secs", "Delay Frequency 2", nullptr },
{ "delay_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Delay Switch", strings::kOffOnNames },
{ "delay_style", 0x000000, 0.0, 3.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Delay Style", strings::kDelayStyleNames },
{ "delay_filter_cutoff", 0x000000, 8.0, 136.0, 60.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Delay Filter Cutoff", nullptr },
{ "delay_filter_spread", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Delay Filter Spread", nullptr },
{ "delay_sync", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Delay Sync", strings::kFrequencySyncNames },
{ "delay_tempo", 0x000000, 4.0, 12.0, 9.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Delay Tempo", strings::kSyncedFrequencyNames },
{ "delay_aux_sync", 0x000507, 0.0, 3.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Delay Sync 2", strings::kFrequencySyncNames },
{ "delay_aux_tempo", 0x000507, 4.0, 12.0, 9.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Delay Tempo 2", strings::kSyncedFrequencyNames },
{ "distortion_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Distortion Switch", strings::kOffOnNames },
{ "distortion_type", 0x000000, 0.0, 5.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Distortion Type", strings::kDistortionTypeNames },
{ "distortion_drive", 0x000000, Distortion::kMinDrive, Distortion::kMaxDrive, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Distortion Drive", nullptr },
{ "distortion_mix", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Distortion Mix", nullptr },
{ "distortion_filter_order", 0x000000, 0.0, 2.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Distortion Filter Order", strings::kDistortionFilterOrderNames },
{ "distortion_filter_cutoff", 0x000000, 8.0, 136.0, 80.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Distortion Filter Cutoff", nullptr },
{ "distortion_filter_resonance", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Distortion Filter Resonance", nullptr },
{ "distortion_filter_blend", 0x000000, 0.0, 2.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Distortion Filter Blend", nullptr },
{ "legato", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Legato", strings::kOffOnNames },
{ "macro_control_1", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Macro 1", nullptr },
{ "macro_control_2", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Macro 2", nullptr },
{ "macro_control_3", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Macro 3", nullptr },
{ "macro_control_4", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Macro 4", nullptr },
{ "pitch_bend_range", 0x000000, 0.0, 48.0, 2.0, 0.0, 1.0,
ValueDetails::kIndexed, false, " semitones", "Pitch Bend Range", nullptr },
{ "polyphony", 0x000000, 1.0, kMaxPolyphony - 1, 8.0, 0.0, 1.0,
ValueDetails::kIndexed, false, " voices", "Polyphony", nullptr },
{ "voice_tune", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, " cents", "Voice Tune", nullptr },
{ "voice_transpose", 0x000604, -48.0, 48.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Voice Transpose", nullptr },
{ "voice_amplitude", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Voice Amplitude", nullptr },
{ "stereo_routing", 0x000000, 0.0, 1.0, 1.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Stereo Routing", nullptr },
{ "stereo_mode", 0x000605, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Stereo Mode", strings::kStereoModeNames },
{ "portamento_time", 0x000000, -10.0, 4.0, -10.0, 0.0, 1.0,
ValueDetails::kExponential, false, " secs", "Portamento Time", nullptr },
{ "portamento_slope", 0x000000, -8.0, 8.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Portamento Slope", nullptr },
{ "portamento_force", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Portamento Force", strings::kOffOnNames },
{ "portamento_scale", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Portamento Scale", strings::kOffOnNames },
{ "reverb_pre_low_cutoff", 0x000000, 0.0, 128.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Reverb Pre Low Cutoff", nullptr },
{ "reverb_pre_high_cutoff", 0x000000, 0.0, 128.0, 110.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Reverb Pre High Cutoff", nullptr },
{ "reverb_low_shelf_cutoff", 0x000000, 0.0, 128.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Reverb Low Cutoff", nullptr },
{ "reverb_low_shelf_gain", 0x000000, -6.0, 0.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Reverb Low Gain", nullptr },
{ "reverb_high_shelf_cutoff", 0x000000, 0.0, 128.0, 90.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Reverb High Cutoff", nullptr },
{ "reverb_high_shelf_gain", 0x000000, -6.0, 0.0, -1.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Reverb High Gain", nullptr },
{ "reverb_dry_wet", 0x000000, 0.0, 1.0, 0.25, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Reverb Mix", nullptr },
{ "reverb_delay", 0x000609, 0.0, 0.3, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " secs", "Reverb Delay", nullptr },
{ "reverb_decay_time", 0x000000, -6.0, 6.0, 0.0, 0.0, 1.0,
ValueDetails::kExponential, false, " secs", "Reverb Decay Time", nullptr },
{ "reverb_size", 0x000506, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Reverb Size", nullptr },
{ "reverb_chorus_amount", 0x000000, 0.0, 1.0, 0.223607, 0.0, 100.0,
ValueDetails::kQuadratic, false, "%", "Reverb Chorus Amount", nullptr },
{ "reverb_chorus_frequency", 0x000000, -8.0, 3.0, -2.0, 0.0, 1.0,
ValueDetails::kExponential, false, " Hz", "Reverb Chorus Frequency", nullptr },
{ "reverb_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Reverb Switch", strings::kOffOnNames },
{ "sub_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sub Switch", strings::kOffOnNames },
{ "sub_direct_out", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sub Direct Out", nullptr },
{ "sub_transpose", 0x000000, -48.0, 48.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sub Transpose", nullptr },
{ "sub_transpose_quantize", 0x000000, 0.0, 8191.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sub Transpose Quantize", nullptr },
{ "sub_tune", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "", "Sub Tune", nullptr },
{ "sub_level", 0x000000, 0.0, 1.0, 0.70710678119, 0.0, 1.0,
ValueDetails::kQuadratic, false, "", "Sub Level", nullptr },
{ "sub_pan", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Sub Pan", nullptr },
{ "sub_waveform", 0x000000, 0.0, PredefinedWaveFrames::kNumShapes - 1, 2.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sub Osc Waveform", nullptr },
{ "sample_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sample Switch", strings::kOffOnNames },
{ "sample_random_phase", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sample Random Phase", strings::kOffOnNames },
{ "sample_keytrack", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sample Keytrack", strings::kOffOnNames },
{ "sample_loop", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sample Loop", strings::kOffOnNames },
{ "sample_bounce", 0x000603, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sample Bounce", strings::kOffOnNames },
{ "sample_transpose", 0x000000, -48.0, 48.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sample Transpose", nullptr },
{ "sample_transpose_quantize", 0x000000, 0.0, 8191.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sample Transpose Quantize", nullptr },
{ "sample_tune", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "", "Sample Tune", nullptr },
{ "sample_level", 0x000000, 0.0, 1.0, 0.70710678119, 0.0, 1.0,
ValueDetails::kQuadratic, false, "", "Sample Level", nullptr },
{ "sample_destination", 0x000500, 0.0, constants::kNumSourceDestinations + constants::kNumEffects, 3.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sample Destination", strings::kDestinationNames },
{ "sample_pan", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Sample Pan", nullptr },
{ "velocity_track", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Velocity Track", nullptr },
{ "volume", 0x000000, 0.0, 7399.4404, 5473.0404, -80, 1.0,
ValueDetails::kSquareRoot, false, "dB", "Volume", nullptr },
{ "phaser_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Phaser Switch", strings::kOffOnNames },
{ "phaser_dry_wet", 0x000000, 0.0, 1.0, 1.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Phaser Mix", nullptr },
{ "phaser_feedback", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Phaser Feedback", nullptr },
{ "phaser_frequency", 0x000000, -5.0, 2.0, -3.0, 0.0, 1.0,
ValueDetails::kExponential, true, " secs", "Phaser Frequency", nullptr },
{ "phaser_sync", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Phaser Sync",strings::kFrequencySyncNames },
{ "phaser_tempo", 0x000000, 0.0, 10.0, 3.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Phaser Tempo", strings::kSyncedFrequencyNames },
{ "phaser_center", 0x000000, 8.0, 136.0, 80.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Phaser Center", nullptr },
{ "phaser_blend", 0x000509, 0.0, 2.0, 1.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Phaser Blend", nullptr },
{ "phaser_mod_depth", 0x000000, 0.0, 48.0, 24.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Phaser Mod Depth", nullptr },
{ "phaser_phase_offset", 0x000000, 0, 1.0, 0.33333333, 0.0, kDegreesPerCycle,
ValueDetails::kLinear, false, "", "Phaser Phase Offset", nullptr },
{ "flanger_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Flanger Switch", strings::kOffOnNames },
{ "flanger_dry_wet", 0x000000, 0.0, 0.5, 0.5, 0.0, 200.0,
ValueDetails::kLinear, false, "%", "Flanger Mix", nullptr },
{ "flanger_feedback", 0x000000, -1.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Flanger Feedback", nullptr },
{ "flanger_frequency", 0x000000, -5.0, 2.0, 2.0, 0.0, 1.0,
ValueDetails::kExponential, true, " secs", "Flanger Frequency", nullptr },
{ "flanger_sync", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Flanger Sync", strings::kFrequencySyncNames },
{ "flanger_tempo", 0x000000, 0.0, 10.0, 4.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Flanger Tempo", strings::kSyncedFrequencyNames },
{ "flanger_center", 0x000505, 8.0, 136.0, 64.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Flanger Center", nullptr },
{ "flanger_mod_depth", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Flanger Mod Depth", nullptr },
{ "flanger_phase_offset", 0x000000, 0, 1.0, 0.33333333, 0.0, kDegreesPerCycle,
ValueDetails::kLinear, false, "", "Flanger Phase Offset", nullptr },
{ "chorus_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Chorus Switch", strings::kOffOnNames },
{ "chorus_dry_wet", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Chorus Mix", nullptr },
{ "chorus_feedback", 0x000000, -0.95, 0.95, 0.4, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Chorus Feedback", nullptr },
{ "chorus_cutoff", 0x000000, 8.0, 136.0, 60.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Chorus Filter Cutoff", nullptr },
{ "chorus_spread", 0x000607, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Chorus Filter Spread", nullptr },
{ "chorus_voices", 0x000508, 1.0, 4.0, 4.0, 0.0, 4.0,
ValueDetails::kIndexed, false, "", "Chorus Voices", nullptr },
{ "chorus_frequency", 0x000000, -6.0, 3.0, -3.0, 0.0, 1.0,
ValueDetails::kExponential, true, " secs", "Chorus Frequency", nullptr },
{ "chorus_sync", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Chorus Sync", strings::kFrequencySyncNames },
{ "chorus_tempo", 0x000000, 0.0, 10.0, 4.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Chorus Tempo", strings::kSyncedFrequencyNames },
{ "chorus_mod_depth", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Chorus Mod Depth", nullptr },
{ "chorus_delay_1", 0x000000, -10.0, -5.64386, -9.0, 0.0, 1000.0,
ValueDetails::kExponential, false, "ms", "Chorus Delay 1", nullptr },
{ "chorus_delay_2", 0x000000, -10.0, -5.64386, -7.0, 0.0, 1000.0,
ValueDetails::kExponential, false, " ms", "Chorus Delay 2", nullptr },
{ "compressor_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Compressor Switch", strings::kOffOnNames },
{ "compressor_low_upper_threshold", 0x000000, -80.0, 0.0, -28.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Low Upper Threshold", nullptr },
{ "compressor_band_upper_threshold", 0x000000, -80.0, 0.0, -25.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Band Upper Threshold", nullptr },
{ "compressor_high_upper_threshold", 0x000000, -80.0, 0.0, -30.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "High Upper Threshold", nullptr },
{ "compressor_low_lower_threshold", 0x000000, -80.0, 0.0, -35.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Low Lower Threshold", nullptr },
{ "compressor_band_lower_threshold", 0x000000, -80.0, 0.0, -36.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Band Lower Threshold", nullptr },
{ "compressor_high_lower_threshold", 0x000000, -80.0, 0.0, -35.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "High Lower Threshold", nullptr },
{ "compressor_low_upper_ratio", 0x000000, 0.0, 1.0, 0.9, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Low Upper Ratio", nullptr },
{ "compressor_band_upper_ratio", 0x000000, 0.0, 1.0, 0.857, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Band Upper Ratio", nullptr },
{ "compressor_high_upper_ratio", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "High Upper Ratio", nullptr },
{ "compressor_low_lower_ratio", 0x000000, -1.0, 1.0, 0.8, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Low Lower Ratio", nullptr },
{ "compressor_band_lower_ratio", 0x000000, -1.0, 1.0, 0.8, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Band Lower Ratio", nullptr },
{ "compressor_high_lower_ratio", 0x000000, -1.0, 1.0, 0.8, 0.0, 1.0,
ValueDetails::kLinear, false, "", "High Lower Ratio", nullptr },
{ "compressor_low_gain", 0x000000, -30.0, 30.0, 16.3, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Compressor Low Gain", nullptr },
{ "compressor_band_gain", 0x000000, -30.0, 30.0, 11.7, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Compressor Band Gain", nullptr },
{ "compressor_high_gain", 0x000000, -30.0, 30.0, 16.3, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "Compressor High Gain", nullptr },
{ "compressor_attack", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Compressor Attack", nullptr },
{ "compressor_release", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Compressor Release", nullptr },
{ "compressor_enabled_bands", 0x000000, 0.0, vital::MultibandCompressor::kNumBandOptions - 1, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Compressor Enabled Bands", strings::kCompressorBandNames },
{ "compressor_mix", 0x000602, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Compressor Mix", nullptr },
{ "compressor_low_band_unused", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Compressor Unused", nullptr },
{ "eq_on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "EQ Switch", strings::kOffOnNames },
{ "eq_low_mode", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "EQ Low Mode", strings::kEqLowModeNames },
{ "eq_low_cutoff", 0x000000, 8.0, 136.0, 40.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "EQ Low Cutoff", nullptr },
{ "eq_low_gain", 0x000000, DigitalSvf::kMinGain, DigitalSvf::kMaxGain, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "EQ Low Gain", nullptr },
{ "eq_low_resonance", 0x000000, 0.0, 1.0, 0.3163, 0.0, 100.0,
ValueDetails::kQuadratic, false, "%", "EQ Low Resonance", nullptr },
{ "eq_band_mode", 0x000506, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "EQ Band Mode", strings::kEqBandModeNames },
{ "eq_band_cutoff", 0x000000, 8.0, 136.0, 80.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "EQ Band Cutoff", nullptr },
{ "eq_band_gain", 0x000000, DigitalSvf::kMinGain, DigitalSvf::kMaxGain, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "EQ Band Gain", nullptr },
{ "eq_band_resonance", 0x000000, 0.0, 1.0, 0.4473, 0.0, 100.0,
ValueDetails::kQuadratic, false, "", "EQ Band Resonance", nullptr },
{ "eq_high_mode", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "EQ High Mode", strings::kEqHighModeNames },
{ "eq_high_cutoff", 0x000000, 8.0, 136.0, 100.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "EQ High Cutoff", nullptr },
{ "eq_high_gain", 0x000000, DigitalSvf::kMinGain, DigitalSvf::kMaxGain, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " dB", "EQ High Gain", nullptr },
{ "eq_high_resonance", 0x000000, 0.0, 1.0, 0.3163, 0.0, 100.0,
ValueDetails::kQuadratic, false, "", "EQ High Resonance", nullptr },
{ "effect_chain_order", 0x000000, 0.0, vital::utils::factorial(vital::kNumEffects) - 1, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Effect Chain Order", nullptr },
{ "voice_priority", 0x000000, 0.0, VoiceHandler::kNumVoicePriorities - 1, VoiceHandler::kRoundRobin, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Voice Priority", strings::kVoicePriorityNames },
{ "voice_override", 0x000700, 0.0, VoiceHandler::kNumVoiceOverrides - 1, VoiceHandler::kKill, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Voice Override", strings::kVoiceOverrideNames },
{ "oversampling", 0x000000, 0.0, 3.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Oversampling", strings::kOversamplingNames },
{ "pitch_wheel", 0x000400, -1.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Pitch Wheel", nullptr },
{ "mod_wheel", 0x000400, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Mod Wheel", nullptr },
{ "mpe_enabled", 0x000501, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "MPE Enabled", strings::kOffOnNames },
{ "view_spectrogram", 0x000803, 0.0, 2.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "View Spectrogram", strings::kOffOnNames },
};

const ValueDetails ValueDetailsLookup::env_parameter_list[] = {
{ "delay", 0x000503, 0.0, 1.4142135624, 0.0, 0.0, 1.0,
ValueDetails::kQuartic, false, " secs", "Delay", nullptr },
{ "attack", 0x000000, 0.0, 2.37842, 0.1495, 0.0, 1.0,
ValueDetails::kQuartic, false, " secs", "Attack", nullptr },
{ "hold", 0x000504, 0.0, 1.4142135624, 0.0, 0.0, 1.0,
ValueDetails::kQuartic, false, " secs", "Hold", nullptr },
{ "decay", 0x000000, 0.0, 2.37842, 1.0, 0.0, 1.0,
ValueDetails::kQuartic, false, " secs", "Decay", nullptr },
{ "release", 0x000000, 0.0, 2.37842, 0.5476, 0.0, 1.0,
ValueDetails::kQuartic, false, " secs", "Release", nullptr },
{ "attack_power", 0x000000, -20.0, 20.0, 0.0f, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Attack Power", nullptr },
{ "decay_power", 0x000000, -20.0, 20.0, -2.0f, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Decay Power", nullptr },
{ "release_power", 0x000000, -20.0, 20.0, -2.0f, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Release Power", nullptr },
{ "sustain", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Sustain", nullptr },
};

const ValueDetails ValueDetailsLookup::lfo_parameter_list[] = {
{ "phase", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Phase", nullptr },
{ "sync_type", 0x000000, 0.0, SynthLfo::kNumSyncTypes - 1, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sync Type", strings::kSyncNames },
{ "frequency", 0x000000, -7.0, 9.0, 1.0, 0.0, 1.0,
ValueDetails::kExponential, true, " secs", "Frequency", nullptr },
{ "sync", 0x000000, 0.0, SynthLfo::kNumSyncOptions - 1, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sync", strings::kFrequencySyncNames },
{ "tempo", 0x000000, 0.0, 12.0, 7.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Tempo", strings::kSyncedFrequencyNames },
{ "fade_time", 0x000000, 0.0, 8.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " secs", "Fade In", nullptr },
{ "smooth_mode", 0x000801, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Smooth Mode", strings::kOffOnNames },
{ "smooth_time", 0x000801, -10.0, 4.0, -7.5, 0.0, 1.0,
ValueDetails::kExponential, false, " secs", "Smooth Time", nullptr },
{ "delay_time", 0x000000, 0.0, 4.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, " secs", "Delay", nullptr },
{ "stereo", 0x000406, -0.5, 0.5, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Stereo", nullptr },
{ "keytrack_transpose", 0x000704, -60.0, 36.0, -12.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Transpose", nullptr },
{ "keytrack_tune", 0x000704, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "", "Tune", nullptr },
};

const ValueDetails ValueDetailsLookup::random_lfo_parameter_list[] = {
{ "style", 0x000401, 0.0, RandomLfo::kNumStyles - 1, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Style", strings::kRandomNames },
{ "frequency", 0x000401, -7.0, 9.0, 1.0, 0.0, 1.0,
ValueDetails::kExponential, true, " secs", "Frequency", nullptr },
{ "sync", 0x000401, 0.0, SynthLfo::kNumSyncOptions - 1, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sync", strings::kFrequencySyncNames },
{ "tempo", 0x000401, 0.0, 12.0, 8.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Tempo", strings::kSyncedFrequencyNames },
{ "stereo", 0x000401, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Stereo", strings::kOffOnNames },
{ "sync_type", 0x000600, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Sync Type", strings::kOffOnNames },
{ "keytrack_transpose", 0x000704, -60.0, 36.0, -12.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Transpose", nullptr },
{ "keytrack_tune", 0x000704, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "", "Tune", nullptr },
};

const ValueDetails ValueDetailsLookup::filter_parameter_list[] = {
{ "mix", 0x000000, 0.0f, 1.0f, 1.0f, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Mix", nullptr },
{ "cutoff", 0x000000, 8.0, 136.0, 60.0, -60.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Cutoff", nullptr },
{ "resonance", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Resonance", nullptr },
{ "drive", 0x000000, 0.0, 20.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "dB", "Drive", nullptr },
{ "blend", 0x000000, 0.0, 2.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Blend", nullptr },
{ "style", 0x000000, 0.0, 9.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Style", strings::kFilterStyleNames },
{ "model", 0x000000, 0.0, kNumFilterModels - 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Model", strings::kFilterModelNames },
{ "on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Switch", strings::kOffOnNames },
{ "blend_transpose", 0x000000, 0.0, 84.0, 42.0, 0.0, 1.0,
ValueDetails::kLinear, false, " semitones", "Comb Blend Offset", nullptr },
{ "keytrack", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Key Track", nullptr },
{ "formant_x", 0x000000, 0.0, 1.0, 0.5, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Formant X", nullptr },
{ "formant_y", 0x000000, 0.0, 1.0, 0.5, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Formant Y", nullptr },
{ "formant_transpose", 0x000000, -12.0, 12.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Formant Transpose", nullptr },
{ "formant_resonance", 0x000000, 0.3, 1.0, 0.85, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Formant Resonance", nullptr },
{ "formant_spread", 0x000707, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Formant Spread", nullptr },
{ "osc1_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "OSC 1 Input", strings::kOffOnNames },
{ "osc2_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "OSC 2 Input", strings::kOffOnNames },
{ "osc3_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "OSC 3 Input", strings::kOffOnNames },
{ "sample_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "SAMPLE Input", strings::kOffOnNames },
{ "filter_input", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "FILTER Input", strings::kOffOnNames },
};

const ValueDetails ValueDetailsLookup::osc_parameter_list[] = {
{ "on", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Switch", strings::kOffOnNames },
{ "transpose", 0x000000, -48.0, 48.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Transpose", nullptr },
{ "transpose_quantize", 0x000000, 0.0, 8191.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Transpose Quantize", nullptr },
{ "tune", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "", "Tune", nullptr },
{ "pan", 0x000000, -1.0, 1.0, 0.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Pan", nullptr },
{ "stack_style", 0x000000, 0.0, SynthOscillator::kNumUnisonStackTypes - 1, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Stack Style", strings::kUnisonStackNames },
{ "unison_detune", 0x000000, 0.0, 10.0, 4.472135955, 0.0, 1.0,
ValueDetails::kQuadratic, false, "%", "Unison Detune", nullptr },
{ "unison_voices", 0x000000, 1.0, 16.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "v", "Unison Voices", nullptr },
{ "unison_blend", 0x000000, 0.0, 1.0, 0.8, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Blend", nullptr },
{ "detune_power", 0x000000, -5.0, 5.0, 1.5, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Detune Power", nullptr },
{ "detune_range", 0x000000, 0.0, 48.0, 2.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Detune Range", nullptr },
{ "level", 0x000000, 0.0, 1.0, 0.70710678119, 0.0, 1.0,
ValueDetails::kQuadratic, false, "", "Level", nullptr },
{ "midi_track", 0x000000, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Midi Track", strings::kOffOnNames },
{ "smooth_interpolation", 0x000000, 0.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Smooth Interpolation", strings::kOffOnNames },
{ "spectral_unison", 0x000500, 0.0, 1.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Spectral Unison", strings::kOffOnNames },
{ "wave_frame", 0x000000, 0.0, kNumOscillatorWaveFrames - 1, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Wave Frame", nullptr },
{ "frame_spread", 0x000000, -kNumOscillatorWaveFrames / 2, kNumOscillatorWaveFrames / 2, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Unison Frame Spread", nullptr },
{ "stereo_spread", 0x000000, 0.0, 1.0, 1.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Stereo Spread", nullptr },
{ "phase", 0x000000, 0.0, 1.0, 0.5, 0.0, 360.0,
ValueDetails::kLinear, false, "", "Phase", nullptr },
{ "distortion_phase", 0x000000, 0.0, 1.0, 0.5, 0.0, 360.0,
ValueDetails::kLinear, false, "", "Distortion Phase", nullptr },
{ "random_phase", 0x000000, 0.0, 1.0, 1.0, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Phase Randomization", nullptr },
{ "distortion_type", 0x000000, 0.0, SynthOscillator::kNumDistortionTypes - 1, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Distortion Type", strings::kPhaseDistortionNames },
{ "distortion_amount", 0x000000, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Distortion Amount", nullptr },
{ "distortion_spread", 0x000000, -0.5, 0.5, 0.0, 0.0, 200.0,
ValueDetails::kLinear, false, "%", "Distortion Spread", nullptr },
{ "spectral_morph_type", 0x000407, 0.0, SynthOscillator::kNumSpectralMorphTypes - 1, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Frequency Morph Type", strings::kSpectralMorphNames },
{ "spectral_morph_amount", 0x000407, 0.0, 1.0, 0.5, 0.0, 100.0,
ValueDetails::kLinear, false, "%", "Frequency Morph Amount", nullptr },
{ "spectral_morph_spread", 0x000407, -0.5, 0.5, 0.0, 0.0, 200.0,
ValueDetails::kLinear, false, "%", "Frequency Morph Spread", nullptr },
{ "destination", 0x000500, 0.0, constants::kNumSourceDestinations + constants::kNumEffects, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Destination", strings::kDestinationNames },
{ "view_2d", 0x000402, 0.0, 2.0, 1.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "View 2D", strings::kOffOnNames },
};

const ValueDetails ValueDetailsLookup::mod_parameter_list[] = {
{ "amount", 0x000000, -1.0, 1.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Amount", nullptr },
{ "power", 0x000000, -10.0, 10.0, 0.0, 0.0, 1.0,
ValueDetails::kLinear, false, "", "Power", nullptr },
{ "bipolar", 0x000000, 0.0, 1.0f, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Bipolar", strings::kOffOnNames },
{ "stereo", 0x000000, 0.0, 1.0f, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Stereo", strings::kOffOnNames },
{ "bypass", 0x000000, 0.0, 1.0f, 0.0, 0.0, 1.0,
ValueDetails::kIndexed, false, "", "Bypass", strings::kOffOnNames }
};

ValueDetailsLookup::ValueDetailsLookup() {
static constexpr int kNumOscillatorsOld = 2;
static constexpr int kNewOscillatorVersion = 0x000500;
static constexpr int kOldMaxModulations = 32;
static constexpr int kNewModulationVersion = 0x000601;

int num_parameters = sizeof(parameter_list) / sizeof(ValueDetails);
for (int i = 0; i < num_parameters; ++i) {
details_lookup_[parameter_list[i].name] = parameter_list[i];
details_list_.push_back(&parameter_list[i]);

VITAL_ASSERT(parameter_list[i].default_value <= parameter_list[i].max);
VITAL_ASSERT(parameter_list[i].default_value >= parameter_list[i].min);
}

int num_env_parameters = sizeof(env_parameter_list) / sizeof(ValueDetails);
for (int env = 0; env < kNumEnvelopes; ++env)
addParameterGroup(env_parameter_list, num_env_parameters, env, kEnvIdPrefix, kEnvNamePrefix);

int num_lfo_parameters = sizeof(lfo_parameter_list) / sizeof(ValueDetails);
for (int lfo = 0; lfo < kNumLfos; ++lfo)
addParameterGroup(lfo_parameter_list, num_lfo_parameters, lfo, kLfoIdPrefix, kLfoNamePrefix);

int num_random_lfo_parameters = sizeof(random_lfo_parameter_list) / sizeof(ValueDetails);
for (int lfo = 0; lfo < kNumRandomLfos; ++lfo)
addParameterGroup(random_lfo_parameter_list, num_random_lfo_parameters, lfo, kRandomIdPrefix, kRandomNamePrefix);

int num_osc_parameters = sizeof(osc_parameter_list) / sizeof(ValueDetails);
for (int osc = 0; osc < kNumOscillatorsOld; ++osc)
addParameterGroup(osc_parameter_list, num_osc_parameters, osc, kOscIdPrefix, kOscNamePrefix);
for (int osc = kNumOscillatorsOld; osc < kNumOscillators; ++osc) {
addParameterGroup(osc_parameter_list, num_osc_parameters, osc, kOscIdPrefix,
kOscNamePrefix, kNewOscillatorVersion);
}

int num_filter_parameters = sizeof(filter_parameter_list) / sizeof(ValueDetails);
for (int filter = 0; filter < kNumFilters; ++filter)
addParameterGroup(filter_parameter_list, num_filter_parameters, filter, kFilterIdPrefix, kFilterNamePrefix);

addParameterGroup(filter_parameter_list, num_filter_parameters, "fx", kFilterIdPrefix, kFilterNamePrefix);

int num_mod_parameters = sizeof(mod_parameter_list) / sizeof(ValueDetails);
for (int modulation = 0; modulation < kOldMaxModulations; ++modulation) {
addParameterGroup(mod_parameter_list, num_mod_parameters, modulation,
kModulationIdPrefix, kModulationNamePrefix);
}
for (int modulation = kOldMaxModulations; modulation < kMaxModulationConnections; ++modulation) {
addParameterGroup(mod_parameter_list, num_mod_parameters, modulation,
kModulationIdPrefix, kModulationNamePrefix, kNewModulationVersion);
}

details_lookup_["osc_1_on"].default_value = 1.0f;
details_lookup_["osc_2_destination"].default_value = 1.0f;
details_lookup_["osc_3_destination"].default_value = 3.0f;
details_lookup_["filter_1_osc1_input"].default_value = 1.0f;
details_lookup_["filter_2_osc2_input"].default_value = 1.0f;

std::sort(details_list_.begin(), details_list_.end(), compareValueDetails);
}

void ValueDetailsLookup::addParameterGroup(const ValueDetails* list, int num_parameters, int index,
std::string id_prefix, std::string name_prefix, int version) {
std::string string_num = std::to_string(index + 1);
addParameterGroup(list, num_parameters, string_num, id_prefix, name_prefix, version);
}

void ValueDetailsLookup::addParameterGroup(const ValueDetails* list, int num_parameters, std::string id,
std::string id_prefix, std::string name_prefix, int version) {
std::string id_start = id_prefix + kIdDelimiter + id + kIdDelimiter;
std::string name_start = name_prefix + kNameDelimiter + id + kNameDelimiter;

for (int i = 0; i < num_parameters; ++i) {
ValueDetails details = list[i];
if (version > details.version_added)
details.version_added = version;

details.name = id_start + details.name;
details.local_description = details.display_name;
details.display_name = name_start + details.display_name;
details_lookup_[details.name] = details;
details_list_.push_back(&details_lookup_[details.name]);
}
}

ValueDetailsLookup Parameters::lookup_;

} // namespace vital

+ 147
- 0
ports/vitalium/source/common/synth_parameters.h View File

@@ -0,0 +1,147 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "common.h"

#include <map>
#include <string>

namespace vital {

struct ValueDetails {
enum ValueScale {
kIndexed,
kLinear,
kQuadratic,
kCubic,
kQuartic,
kSquareRoot,
kExponential
};

std::string name;
int version_added = 0;
mono_float min = 0.0f;
mono_float max = 1.0f;
mono_float default_value = 0.0f;

// post_offset used to offset quadratic and exponential scaling.
mono_float post_offset = 0.0f;

mono_float display_multiply = 1.0f;
ValueScale value_scale = kLinear;
bool display_invert = false;
std::string display_units;
std::string display_name;
const std::string* string_lookup = nullptr;
std::string local_description;
} typedef ValueDetails;

class ValueDetailsLookup {
public:
ValueDetailsLookup();
const bool isParameter(const std::string& name) const {
return details_lookup_.count(name);
}

const ValueDetails& getDetails(const std::string& name) const {
auto details = details_lookup_.find(name);
VITAL_ASSERT(details != details_lookup_.end());
return details->second;
}

const ValueDetails* getDetails(int index) const {
return details_list_[index];
}

std::string getDisplayName(const std::string& name) const {
return getDetails(name).display_name;
}

int getNumParameters() const {
return static_cast<int>(details_list_.size());
}

mono_float getParameterRange(const std::string& name) const {
auto details = details_lookup_.find(name);
VITAL_ASSERT(details != details_lookup_.end());
return details->second.max - details->second.min;
}

std::map<std::string, ValueDetails> getAllDetails() const {
return details_lookup_;
}

void addParameterGroup(const ValueDetails* list, int num_parameters, int index,
std::string id_prefix, std::string name_prefix, int version = -1);

void addParameterGroup(const ValueDetails* list, int num_parameters, std::string id,
std::string id_prefix, std::string name_prefix, int version = -1);

static const ValueDetails parameter_list[];
static const ValueDetails env_parameter_list[];
static const ValueDetails lfo_parameter_list[];
static const ValueDetails random_lfo_parameter_list[];
static const ValueDetails filter_parameter_list[];
static const ValueDetails osc_parameter_list[];
static const ValueDetails mod_parameter_list[];

private:
std::map<std::string, ValueDetails> details_lookup_;
std::vector<const ValueDetails*> details_list_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ValueDetailsLookup)
};

class Parameters {
public:
static const ValueDetails& getDetails(const std::string& name) {
return lookup_.getDetails(name);
}

static int getNumParameters() {
return lookup_.getNumParameters();
}

static const ValueDetails* getDetails(int index) {
return lookup_.getDetails(index);
}

static std::string getDisplayName(const std::string& name) {
return lookup_.getDisplayName(name);
}

static const mono_float getParameterRange(const std::string& name) {
return lookup_.getParameterRange(name);
}

static const bool isParameter(const std::string& name) {
return lookup_.isParameter(name);
}

static std::map<std::string, ValueDetails> getAllDetails() {
return lookup_.getAllDetails();
}

static ValueDetailsLookup lookup_;

private:
Parameters() { }
};
} // namespace vital


+ 72
- 0
ports/vitalium/source/common/synth_types.cpp View File

@@ -0,0 +1,72 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "synth_types.h"

#include "synth_constants.h"
#include "modulation_connection_processor.h"

namespace vital {
namespace {
const std::string kModulationSourceDelimiter = "_";
const std::set<std::string> kBipolarModulationSourcePrefixes = {
"lfo",
"stereo",
"random",
"pitch"
};

force_inline bool isConnectionAvailable(ModulationConnection* connection) {
return connection->source_name.empty() && connection->destination_name.empty();
}
}

ModulationConnection::ModulationConnection(int index, std::string from, std::string to) :
source_name(std::move(from)), destination_name(std::move(to)) {
modulation_processor = std::make_unique<ModulationConnectionProcessor>(index);
}

ModulationConnection::~ModulationConnection() { }

bool ModulationConnection::isModulationSourceDefaultBipolar(const std::string& source) {
std::size_t pos = source.find(kModulationSourceDelimiter);
std::string prefix = source.substr(0, pos);
return kBipolarModulationSourcePrefixes.count(prefix) > 0;
}

ModulationConnectionBank::ModulationConnectionBank() {
for (int i = 0; i < kMaxModulationConnections; ++i) {
std::unique_ptr<ModulationConnection> connection = std::make_unique<ModulationConnection>(i);
all_connections_.push_back(std::move(connection));
}
}

ModulationConnectionBank::~ModulationConnectionBank() { }

ModulationConnection* ModulationConnectionBank::createConnection(const std::string& from, const std::string& to) {
int index = 1;
for (auto& connection : all_connections_) {
std::string invalid_connection = "modulation_" + std::to_string(index++) + "_amount";
if (to != invalid_connection && isConnectionAvailable(connection.get())) {
connection->resetConnection(from, to);
connection->modulation_processor->setBipolar(ModulationConnection::isModulationSourceDefaultBipolar(from));
return connection.get();
}
}

return nullptr;
}
} // namespace vital

+ 105
- 0
ports/vitalium/source/common/synth_types.h View File

@@ -0,0 +1,105 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "circular_queue.h"
#include "synth_parameters.h"
#include "operators.h"
#include "value.h"

#include <map>
#include <string>

namespace vital {

class ValueSwitch;
class ModulationConnectionProcessor;

struct ModulationConnection {
ModulationConnection(int index) : ModulationConnection(index, "", "") { }

ModulationConnection(int index, std::string from, std::string to);

~ModulationConnection();

static bool isModulationSourceDefaultBipolar(const std::string& source);

void resetConnection(const std::string& from, const std::string& to) {
source_name = from;
destination_name = to;
}

std::string source_name;
std::string destination_name;
std::unique_ptr<ModulationConnectionProcessor> modulation_processor;

private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModulationConnection)
};

class ModulationConnectionBank {
public:
ModulationConnectionBank();
~ModulationConnectionBank();
ModulationConnection* createConnection(const std::string& from, const std::string& to);

ModulationConnection* atIndex(int index) { return all_connections_[index].get(); }
size_t numConnections() { return all_connections_.size(); }

private:
std::vector<std::unique_ptr<ModulationConnection>> all_connections_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModulationConnectionBank)
};

class StringLayout {
public:
StringLayout() : up_key_(0), down_key_(0) { }
std::wstring getLayout() { return layout_; }
void setLayout(const std::wstring& layout) { layout_ = layout; }

wchar_t getUpKey() { return up_key_; }
void setUpKey(wchar_t up_key) { up_key_ = up_key; }

wchar_t getDownKey() { return down_key_; }
void setDownKey(wchar_t down_key) { down_key_ = down_key; }

protected:
std::wstring layout_;
int up_key_;
int down_key_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(StringLayout)
};

typedef struct {
Output* source;
Processor* mono_destination;
Processor* poly_destination;
mono_float destination_scale;
ValueSwitch* mono_modulation_switch;
ValueSwitch* poly_modulation_switch;
ModulationConnectionProcessor* modulation_processor;
bool disconnecting;
int num_audio_rate;
} modulation_change;

typedef std::map<std::string, Value*> control_map;
typedef std::pair<Value*, mono_float> control_change;
typedef std::map<std::string, Processor*> input_map;
typedef std::map<std::string, Output*> output_map;
} // namespace vital


+ 433
- 0
ports/vitalium/source/common/tuning.cpp View File

@@ -0,0 +1,433 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "tuning.h"

#include "utils.h"

namespace {
constexpr char kScalaFileExtension[] = ".scl";
constexpr char kKeyboardMapExtension[] = ".kbm";
constexpr char kTunFileExtension[] = ".tun";
constexpr int kDefaultMidiReference = 60;
constexpr char kScalaKbmComment = '!';
constexpr char kTunComment = ';';

enum ScalaReadingState {
kDescription,
kScaleLength,
kScaleRatios
};

enum KbmPositions {
kMapSizePosition,
kStartMidiMapPosition,
kEndMidiMapPosition,
kMidiMapMiddlePosition,
kReferenceNotePosition,
kReferenceFrequencyPosition,
kScaleDegreePosition,
};

enum TunReadingState {
kScanningForSection,
kTuning,
kExactTuning
};

String extractFirstToken(const String& source) {
StringArray tokens;
tokens.addTokens(source, false);
return tokens[0];
}

float readCentsToTranspose(const String& cents) {
return cents.getFloatValue() / vital::kCentsPerNote;
}

float readRatioToTranspose(const String& ratio) {
StringArray tokens;
tokens.addTokens(ratio, "/", "");
float value = tokens[0].getIntValue();

if (tokens.size() == 2)
value /= tokens[1].getIntValue();

return vital::utils::ratioToMidiTranspose(value);
}

String readTunSection(const String& line) {
return line.substring(1, line.length() - 1).toLowerCase();
}

bool isBaseFrequencyAssignment(const String& line) {
return line.upToFirstOccurrenceOf("=", false, true).toLowerCase().trim() == "basefreq";
}

int getNoteAssignmentIndex(const String& line) {
String variable = line.upToFirstOccurrenceOf("=", false, true);
StringArray tokens;
tokens.addTokens(variable, false);
if (tokens.size() <= 1 || tokens[0].toLowerCase() != "note")
return -1;
int index = tokens[1].getIntValue();
if (index < 0 || index >= vital::kMidiSize)
return -1;
return index;
}

float getAssignmentValue(const String& line) {
String value = line.fromLastOccurrenceOf("=", false, true).trim();
return value.getFloatValue();
}
}

String Tuning::allFileExtensions() {
return String("*") + kScalaFileExtension + String(";") +
String("*") + kKeyboardMapExtension + String(";") +
String("*") + kTunFileExtension;
}

int Tuning::noteToMidiKey(const String& note_text) {
constexpr int kNotesInScale = 7;
constexpr int kOctaveStart = -1;
constexpr int kScale[kNotesInScale] = { -3, -1, 0, 2, 4, 5, 7 };

String text = note_text.toLowerCase().removeCharacters(" ");
if (note_text.length() < 2)
return -1;

char note_in_scale = text[0] - 'a';
if (note_in_scale < 0 || note_in_scale >= kNotesInScale)
return -1;

int offset = kScale[note_in_scale];
text = text.substring(1);
if (text[0] == '#') {
text = text.substring(1);
offset++;
}
else if (text[0] == 'b') {
text = text.substring(1);
offset--;
}

if (text.length() == 0)
return -1;

bool negative = false;
if (text[0] == '-') {
text = text.substring(1);
negative = true;
if (text.length() == 0)
return -1;
}
int octave = text[0] - '0';
if (negative)
octave = -octave;
octave = octave - kOctaveStart;
return vital::kNotesPerOctave * octave + offset;
}

Tuning Tuning::getTuningForFile(File file) {
return Tuning(file);
}

void Tuning::loadFile(File file) {
String extension = file.getFileExtension().toLowerCase();
if (extension == String(kScalaFileExtension))
loadScalaFile(file);
else if (extension == String(kTunFileExtension))
loadTunFile(file);
else if (extension == String(kKeyboardMapExtension))
loadKeyboardMapFile(file);

default_ = false;
}

void Tuning::loadScalaFile(const StringArray& scala_lines) {
ScalaReadingState state = kDescription;

int scale_length = 1;
std::vector<float> scale;
scale.push_back(0.0f);

for (const String& line : scala_lines) {
String trimmed_line = line.trim();
if (trimmed_line.length() > 0 && trimmed_line[0] == kScalaKbmComment)
continue;

if (scale.size() >= scale_length + 1)
break;

switch (state) {
case kDescription:
state = kScaleLength;
break;
case kScaleLength:
scale_length = extractFirstToken(trimmed_line).getIntValue();
state = kScaleRatios;
break;
case kScaleRatios: {
String tuning = extractFirstToken(trimmed_line);
if (tuning.contains("."))
scale.push_back(readCentsToTranspose(tuning));
else
scale.push_back(readRatioToTranspose(tuning));
break;
}
}
}

keyboard_mapping_.clear();
for (int i = 0; i < scale.size() - 1; ++i)
keyboard_mapping_.push_back(i);
scale_start_midi_note_ = kDefaultMidiReference;
reference_midi_note_ = 0;

loadScale(scale);
default_ = false;
}

void Tuning::loadScalaFile(File scala_file) {
StringArray lines;
scala_file.readLines(lines);
loadScalaFile(lines);
tuning_name_ = scala_file.getFileNameWithoutExtension().toStdString();
}

void Tuning::loadKeyboardMapFile(File kbm_file) {
static constexpr int kHeaderSize = 7;

StringArray lines;
kbm_file.readLines(lines);

float header_data[kHeaderSize];
memset(header_data, 0, kHeaderSize * sizeof(float));
int header_position = 0;
int map_size = 0;
int last_scale_value = 0;
keyboard_mapping_.clear();

for (const String& line : lines) {
String trimmed_line = line.trim();
if (trimmed_line.length() > 0 && trimmed_line[0] == kScalaKbmComment)
continue;

if (header_position >= kHeaderSize) {
String token = extractFirstToken(trimmed_line);
if (token.toLowerCase()[0] != 'x')
last_scale_value = token.getIntValue();
keyboard_mapping_.push_back(last_scale_value);

if (keyboard_mapping_.size() >= map_size)
break;
}
else {
header_data[header_position] = extractFirstToken(trimmed_line).getFloatValue();
if (header_position == kMapSizePosition)
map_size = header_data[header_position];
header_position++;
}
}

setStartMidiNote(header_data[kMidiMapMiddlePosition]);
setReferenceNoteFrequency(header_data[kReferenceNotePosition], header_data[kReferenceFrequencyPosition]);
loadScale(scale_);

mapping_name_ = kbm_file.getFileNameWithoutExtension().toStdString();
}

void Tuning::loadTunFile(File tun_file) {
keyboard_mapping_.clear();

TunReadingState state = kScanningForSection;
StringArray lines;
tun_file.readLines(lines);

int last_read_note = 0;
float base_frequency = vital::kMidi0Frequency;
std::vector<float> scale;
for (int i = 0; i < vital::kMidiSize; ++i)
scale.push_back(i);

for (const String& line : lines) {
String trimmed_line = line.trim();
if (trimmed_line.length() == 0 || trimmed_line[0] == kTunComment)
continue;

if (trimmed_line[0] == '[') {
String section = readTunSection(trimmed_line);
if (section == "tuning")
state = kTuning;
else if (section == "exact tuning")
state = kExactTuning;
else
state = kScanningForSection;
}
else if (state == kTuning || state == kExactTuning) {
if (isBaseFrequencyAssignment(trimmed_line))
base_frequency = getAssignmentValue(trimmed_line);
else {
int index = getNoteAssignmentIndex(trimmed_line);
last_read_note = std::max(last_read_note, index);
if (index >= 0)
scale[index] = getAssignmentValue(trimmed_line) / vital::kCentsPerNote;
}
}
}

scale.resize(last_read_note + 1);
loadScale(scale);
setStartMidiNote(0);
setReferenceFrequency(base_frequency);
tuning_name_ = tun_file.getFileNameWithoutExtension().toStdString();
}

Tuning::Tuning() : default_(true) {
scale_start_midi_note_ = kDefaultMidiReference;
reference_midi_note_ = 0;

setDefaultTuning();
}

Tuning::Tuning(File file) : Tuning() {
loadFile(file);
}

void Tuning::loadScale(std::vector<float> scale) {
scale_ = scale;
if (scale.size() <= 1) {
setConstantTuning(kDefaultMidiReference);
return;
}

int scale_size = static_cast<int>(scale.size() - 1);
int mapping_size = scale_size;
if (keyboard_mapping_.size())
mapping_size = static_cast<int>(keyboard_mapping_.size());

float octave_offset = scale[scale_size];
int start_octave = -kTuningCenter / mapping_size - 1;
int mapping_position = -kTuningCenter - start_octave * mapping_size;

float current_offset = start_octave * octave_offset;
for (int i = 0; i < kTuningSize; ++i) {
if (mapping_position >= mapping_size) {
current_offset += octave_offset;
mapping_position = 0;
}

int note_in_scale = mapping_position;
if (keyboard_mapping_.size())
note_in_scale = keyboard_mapping_[mapping_position];

tuning_[i] = current_offset + scale[note_in_scale];
mapping_position++;
}
}

void Tuning::setConstantTuning(float note) {
for (int i = 0; i < kTuningSize; ++i)
tuning_[i] = note;
}

void Tuning::setDefaultTuning() {
for (int i = 0; i < kTuningSize; ++i)
tuning_[i] = i - kTuningCenter;

scale_.clear();
for (int i = 0; i <= vital::kNotesPerOctave; ++i)
scale_.push_back(i);

keyboard_mapping_.clear();

default_ = true;
tuning_name_ = "";
mapping_name_ = "";
}

vital::mono_float Tuning::convertMidiNote(int note) const {
int scale_offset = note - scale_start_midi_note_;
return tuning_[kTuningCenter + scale_offset] + scale_start_midi_note_ + reference_midi_note_;
}

void Tuning::setReferenceFrequency(float frequency) {
setReferenceNoteFrequency(0, frequency);
}

void Tuning::setReferenceNoteFrequency(int midi_note, float frequency) {
reference_midi_note_ = vital::utils::frequencyToMidiNote(frequency) - midi_note;
}

void Tuning::setReferenceRatio(float ratio) {
reference_midi_note_ = vital::utils::ratioToMidiTranspose(ratio);
}

json Tuning::stateToJson() const {
json data;
data["scale_start_midi_note"] = scale_start_midi_note_;
data["reference_midi_note"] = reference_midi_note_;
data["tuning_name"] = tuning_name_;
data["mapping_name"] = mapping_name_;
data["default"] = default_;

json scale_data;
for (float scale_value : scale_)
scale_data.push_back(scale_value);
data["scale"] = scale_data;

if (keyboard_mapping_.size()) {
json mapping_data;
for (int mapping_value : keyboard_mapping_)
mapping_data.push_back(mapping_value);
data["mapping"] = mapping_data;
}

return data;
}

void Tuning::jsonToState(const json& data) {
scale_start_midi_note_ = data["scale_start_midi_note"];
reference_midi_note_ = data["reference_midi_note"];
std::string tuning_name = data["tuning_name"];
tuning_name_ = tuning_name;
std::string mapping_name = data["mapping_name"];
mapping_name_ = mapping_name;
if (data.count("default"))
default_ = data["default"];

json scale_data = data["scale"];
scale_.clear();
for (json& value : scale_data) {
float scale_value = value;
scale_.push_back(scale_value);
}

keyboard_mapping_.clear();
if (data.count("mapping")) {
json mapping_data = data["mapping"];
for (json& value : mapping_data) {
int keyboard_value = value;
keyboard_mapping_.push_back(keyboard_value);
}
}

loadScale(scale_);
}

+ 82
- 0
ports/vitalium/source/common/tuning.h View File

@@ -0,0 +1,82 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

#include "common.h"
#include "json/json.h"

using json = nlohmann::json;

class Tuning {
public:
static constexpr int kTuningSize = 2 * vital::kMidiSize;
static constexpr int kTuningCenter = vital::kMidiSize;
static Tuning getTuningForFile(File file);

static String allFileExtensions();
static int noteToMidiKey(const String& note);

Tuning();
Tuning(File file);

void loadScale(std::vector<float> scale);
void loadFile(File file);
void setConstantTuning(float note);
void setDefaultTuning();
vital::mono_float convertMidiNote(int note) const;
void setStartMidiNote(int start_midi_note) { scale_start_midi_note_ = start_midi_note; }
void setReferenceNote(int reference_note) { reference_midi_note_ = reference_note; }
void setReferenceFrequency(float frequency);
void setReferenceNoteFrequency(int midi_note, float frequency);
void setReferenceRatio(float ratio);
std::string getName() const {
if (mapping_name_.size() == 0)
return tuning_name_;
if (tuning_name_.size() == 0)
return mapping_name_;
return tuning_name_ + " / " + mapping_name_;
}

void setName(const std::string& name) {
mapping_name_ = "";
tuning_name_ = name;
}
bool isDefault() const { return default_; }

json stateToJson() const;
void jsonToState(const json& data);
void loadScalaFile(const StringArray& scala_lines);

private:
void loadScalaFile(File scala_file);
void loadKeyboardMapFile(File kbm_file);
void loadTunFile(File tun_file);

int scale_start_midi_note_;
float reference_midi_note_;
std::vector<float> scale_;
std::vector<int> keyboard_mapping_;
vital::mono_float tuning_[kTuningSize];
std::string tuning_name_;
std::string mapping_name_;
bool default_;

JUCE_LEAK_DETECTOR(Tuning)
};


+ 400
- 0
ports/vitalium/source/common/wavetable/file_source.cpp View File

@@ -0,0 +1,400 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "file_source.h"

FileSource::FileSourceKeyframe::FileSourceKeyframe(SampleBuffer* sample_buffer) {
sample_buffer_ = sample_buffer;
start_position_ = 0.0f;
window_fade_ = 1.0f;
window_size_ = vital::WaveFrame::kWaveformSize;
fade_style_ = kWaveBlend;
phase_style_ = kNone;
overridden_phase_ = nullptr;
interpolate_from_frame_ = nullptr;
interpolate_to_frame_ = nullptr;
}

void FileSource::FileSourceKeyframe::copy(const WavetableKeyframe* keyframe) {
const FileSourceKeyframe* source = dynamic_cast<const FileSourceKeyframe*>(keyframe);
VITAL_ASSERT(source);
start_position_ = source->start_position_;
window_fade_ = source->window_fade_;
}

void FileSource::FileSourceKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
float t) {
const FileSourceKeyframe* from = dynamic_cast<const FileSourceKeyframe*>(from_keyframe);
const FileSourceKeyframe* to = dynamic_cast<const FileSourceKeyframe*>(to_keyframe);
VITAL_ASSERT(from);
VITAL_ASSERT(to);

start_position_ = linearTween(from->start_position_, to->start_position_, t);
window_fade_ = linearTween(from->window_fade_, to->window_fade_, t);
}

force_inline float FileSource::FileSourceKeyframe::getScaledInterpolatedSample(float position) {
const float* buffer = getCubicInterpolationBuffer();
float clamped_position = vital::utils::clamp(position, 0.0f, sample_buffer_->size - 1);
int start_index = clamped_position;
float t = clamped_position - start_index;

vital::matrix interpolation_matrix = vital::utils::getCatmullInterpolationMatrix(t);
vital::matrix value_matrix = vital::utils::getValueMatrix(buffer, start_index);
value_matrix.transpose();
return interpolation_matrix.multiplyAndSumRows(value_matrix)[0];
}

float FileSource::FileSourceKeyframe::getNormalizationScale() {
const float* buffer = getDataBuffer();
if (buffer == nullptr)
return 1.0f;

double cycles_in = start_position_ / window_size_;
int cycle = cycles_in;

double start_index = cycle * window_size_;

float max = 0.0f;
float min = 0.0f;
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
double position = std::min<double>(start_index + t * window_size_, sample_buffer_->size - 1);
int from_index = position;
int to_index = std::min(sample_buffer_->size - 1, from_index + 1);

VITAL_ASSERT(from_index >= 0 && from_index < sample_buffer_->size);
VITAL_ASSERT(to_index >= 0 && to_index < sample_buffer_->size);

float from_sample = buffer[from_index];
float to_sample = buffer[to_index];
max = std::max(from_sample, max);
max = std::max(to_sample, max);
min = std::min(from_sample, min);
min = std::min(to_sample, min);
}

return 2.0f / std::max(max - min, 0.001f);
}

void FileSource::FileSourceKeyframe::render(vital::WaveFrame* wave_frame) {
if (sample_buffer_->size <= 0) {
wave_frame->clear();
return;
}

if (fade_style_ == kWaveBlend)
renderWaveBlend(wave_frame);
else if (fade_style_ == kNoInterpolate)
renderNoInterpolate(wave_frame);
else if (fade_style_ == kTimeInterpolate)
renderTimeInterpolate(wave_frame);
else if (fade_style_ == kFreqInterpolate)
renderFreqInterpolate(wave_frame);

if (phase_style_ == kClear || phase_style_ == kVocode) {
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
float amplitude = std::abs(wave_frame->frequency_domain[i]);
wave_frame->frequency_domain[i] = std::polar(amplitude, overridden_phase_[i]);
}
}

wave_frame->toTimeDomain();
}

void FileSource::FileSourceKeyframe::renderWaveBlend(vital::WaveFrame* wave_frame) {
double window_ratio = window_size_ / vital::WaveFrame::kWaveformSize;
int waveform_middle = vital::WaveFrame::kWaveformSize / 2;
int start_index = start_position_ / window_ratio + window_size_ / 2.0f + waveform_middle;
start_index = start_index % vital::WaveFrame::kWaveformSize;

for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
double position = start_position_ + t * window_size_;
int write_index = (start_index + i) % vital::WaveFrame::kWaveformSize;
wave_frame->time_domain[write_index] = getScaledInterpolatedSample(position);
}

int fade_samples = window_fade_ * vital::WaveFrame::kWaveformSize;
double fade_size = fade_samples * window_ratio;
for (int i = 0; i < fade_samples; ++i) {
double t = i / (fade_samples - 1.0f);
double fade = 0.5 + 0.5 * cos(vital::kPi * t);

int write_index = (start_index + i) % vital::WaveFrame::kWaveformSize;
double position = start_position_ + window_size_ + t * fade_size;
double existing_value = wave_frame->time_domain[write_index];
double fade_value = getScaledInterpolatedSample(position);
wave_frame->time_domain[write_index] = linearTween(existing_value, fade_value, fade);
}
wave_frame->toFrequencyDomain();
}

void FileSource::FileSourceKeyframe::renderNoInterpolate(vital::WaveFrame* wave_frame) {
double cycles_in = start_position_ / window_size_;
int cycle = cycles_in;

double start_index = cycle * window_size_;

for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
double position = start_index + t * window_size_;
wave_frame->time_domain[i] = getScaledInterpolatedSample(position);
}

wave_frame->toFrequencyDomain();
}

void FileSource::FileSourceKeyframe::renderTimeInterpolate(vital::WaveFrame* wave_frame) {
double cycles_in = start_position_ / window_size_;
int from_cycle = cycles_in;
int to_cycle = from_cycle + 1;
float transition = cycles_in - from_cycle;

double start_index_from = from_cycle * window_size_;
double start_index_to = to_cycle * window_size_;

for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
double from_position = start_index_from + t * window_size_;
double to_position = start_index_to + t * window_size_;
float from_sample = getScaledInterpolatedSample(from_position);
float to_sample = getScaledInterpolatedSample(to_position);
wave_frame->time_domain[i] = vital::utils::interpolate(from_sample, to_sample, transition);
}

wave_frame->toFrequencyDomain();
}

void FileSource::FileSourceKeyframe::renderFreqInterpolate(vital::WaveFrame* wave_frame) {
double cycles_in = start_position_ / window_size_;
int from_cycle = cycles_in;
int to_cycle = from_cycle + 1;
float transition = cycles_in - from_cycle;

double start_index_from = from_cycle * window_size_;
double start_index_to = to_cycle * window_size_;

vital::WaveFrame* from_wave_frame = interpolate_from_frame_->wave_frame();
vital::WaveFrame* to_wave_frame = interpolate_to_frame_->wave_frame();
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
double t = i / (vital::WaveFrame::kWaveformSize * 1.0);
double from_position = start_index_from + t * window_size_;
double to_position = start_index_to + t * window_size_;
from_wave_frame->time_domain[i] = getScaledInterpolatedSample(from_position);
to_wave_frame->time_domain[i] = getScaledInterpolatedSample(to_position);
}

from_wave_frame->toFrequencyDomain();
to_wave_frame->toFrequencyDomain();
interpolate_from_frame_->linearFrequencyInterpolate(from_wave_frame, to_wave_frame, transition);
wave_frame->copy(interpolate_from_frame_->wave_frame());
}

json FileSource::FileSourceKeyframe::stateToJson() {
json data = WavetableKeyframe::stateToJson();
data["start_position"] = start_position_;
data["window_fade"] = window_fade_;
data["window_size"] = window_size_;
return data;
}

void FileSource::FileSourceKeyframe::jsonToState(json data) {
WavetableKeyframe::jsonToState(data);
start_position_ = data["start_position"];
window_fade_ = data["window_fade"];
window_size_ = data["window_size"];
}

FileSource::FileSource() : compute_frame_(&sample_buffer_), overridden_phase_(),
fade_style_(kWaveBlend), phase_style_(kNone),
normalize_gain_(false), normalize_mult_(false),
random_generator_(-vital::kPi, vital::kPi) {
window_size_ = vital::WaveFrame::kWaveformSize;
random_seed_ = random_generator_.next() * (INT_MAX / vital::kPi);
}

WavetableKeyframe* FileSource::createKeyframe(int position) {
FileSourceKeyframe* keyframe = new FileSourceKeyframe(&sample_buffer_);
interpolate(keyframe, position);
return keyframe;
}

void FileSource::render(vital::WaveFrame* wave_frame, float position) {
if (sample_buffer_.data == nullptr)
wave_frame->clear();
else {
interpolate(&compute_frame_, position);
compute_frame_.setWindowSize(window_size_);
compute_frame_.setFadeStyle(fade_style_);
compute_frame_.setPhaseStyle(phase_style_);
compute_frame_.setInterpolateFromFrame(&interpolate_from_frame_);
compute_frame_.setInterpolateToFrame(&interpolate_to_frame_);
compute_frame_.setOverriddenPhaseBuffer(overridden_phase_);
compute_frame_.render(wave_frame);
wave_frame->setFrequencyRatio(window_size_ / vital::WaveFrame::kWaveformSize);
wave_frame->setSampleRate(sample_buffer_.sample_rate);
if (normalize_mult_)
wave_frame->normalize(normalize_gain_);
wave_frame->toFrequencyDomain();
}
}

WavetableComponentFactory::ComponentType FileSource::getType() {
return WavetableComponentFactory::kFileSource;
}

json FileSource::stateToJson() {
double max_position = 0;
for (int i = 0; i < numFrames(); ++i)
max_position = std::max(max_position, getKeyframe(i)->getStartPosition());

json data = WavetableComponent::stateToJson();
data["normalize_gain"] = normalize_gain_;
data["normalize_mult"] = normalize_mult_;
data["window_size"] = window_size_;
data["fade_style"] = fade_style_;
data["phase_style"] = phase_style_;
data["random_seed"] = random_seed_;
data["audio_sample_rate"] = sample_buffer_.sample_rate;

int save_samples = max_position + 2 * window_size_ + kExtraSaveSamples;
int num_samples = std::min(sample_buffer_.size, save_samples);
String encoded = "";
if (getDataBuffer()) {
std::unique_ptr<int16_t[]> pcm_data = std::make_unique<int16_t[]>(num_samples);
vital::utils::floatToPcmData(pcm_data.get(), getDataBuffer(), num_samples);
encoded = Base64::toBase64(pcm_data.get(), num_samples * sizeof(int16_t));
}
data["audio_file"] = encoded.toStdString();
return data;
}

void FileSource::jsonToState(json data) {
normalize_gain_ = data["normalize_gain"];
if (data.count("normalize_mult"))
normalize_mult_ = data["normalize_mult"];
else
normalize_mult_ = true;
window_size_ = data["window_size"];
fade_style_ = kWaveBlend;
if (data.count("fade_style"))
fade_style_ = data["fade_style"];

phase_style_ = kNone;
if (data.count("phase_style"))
phase_style_ = data["phase_style"];

if (data.count("random_seed"))
random_seed_ = data["random_seed"];

writePhaseOverrideBuffer();

WavetableComponent::jsonToState(data);

int sample_rate = vital::kDefaultSampleRate;
if (data.count("audio_sample_rate"))
sample_rate = data["audio_sample_rate"];

MemoryOutputStream decoded;
std::string audio_data = data["audio_file"];
Base64::convertFromBase64(decoded, audio_data);

int size = static_cast<int>(decoded.getDataSize()) / sizeof(int16_t);
std::unique_ptr<float[]> float_data = std::make_unique<float[]>(size);
vital::utils::pcmToFloatData(float_data.get(), (int16_t*)decoded.getData(), size);
loadBuffer(float_data.get(), size, sample_rate);
}

FileSource::FileSourceKeyframe* FileSource::getKeyframe(int index) {
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
return dynamic_cast<FileSource::FileSourceKeyframe*>(wavetable_keyframe);
}

void FileSource::setPhaseStyle(PhaseStyle phase_style) {
if (phase_style_ == phase_style)
return;

phase_style_ = phase_style;
if (phase_style_ == kVocode)
random_seed_++;

writePhaseOverrideBuffer();
}

void FileSource::writePhaseOverrideBuffer() {
if (phase_style_ == kClear) {
for (int i = 0; i < vital::WaveFrame::kWaveformSize / 2; ++i) {
overridden_phase_[2 * i] = -0.5f * vital::kPi;
overridden_phase_[2 * i + 1] = 0.5f * vital::kPi;
}
}
else if (phase_style_ == kVocode) {
random_generator_.seed(random_seed_);
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
overridden_phase_[i] = random_generator_.next();
}
}

void FileSource::loadBuffer(const float* buffer, int size, int sample_rate) {
sample_buffer_.sample_rate = sample_rate;
sample_buffer_.size = size;
sample_buffer_.data = std::make_unique<float[]>(size + kExtraBufferSamples);
memcpy(sample_buffer_.data.get() + 1, buffer, size * sizeof(float));

sample_buffer_.data[0] = sample_buffer_.data[1];

for (int i = 1; i < kExtraBufferSamples; ++i)
sample_buffer_.data[sample_buffer_.size + i] = sample_buffer_.data[size];
}

void FileSource::detectPitch(int max_period) {
int start = (sample_buffer_.size - kPitchDetectMaxPeriod) / 3;
pitch_detector_.loadSignal(getDataBuffer() + start, kPitchDetectMaxPeriod);
float period = pitch_detector_.matchPeriod(max_period);
setWindowSize(period);
}

void FileSource::detectWaveEditTable() {
static constexpr int kWaveEditFrameLength = 256;
static constexpr int kFrequencyDomainTotals = 8;
static constexpr int kWaveEditNumFrames = 64;
if (sample_buffer_.size != kWaveEditFrameLength * kWaveEditNumFrames)
return;

vital::WaveFrame wave_frame;
const float* buffer = getDataBuffer();
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
wave_frame.time_domain[i] = buffer[i];

wave_frame.toFrequencyDomain();

int size_mult = vital::WaveFrame::kWaveformSize / kWaveEditFrameLength;
std::unique_ptr<float[]> totals = std::make_unique<float[]>(size_mult);
for (int i = 0; i < size_mult; ++i) {
for (int j = 0; j < kFrequencyDomainTotals; ++j)
totals[i] += std::abs(wave_frame.frequency_domain[i + 1 + j * size_mult]);
}

for (int i = 0; i < size_mult - 1; ++i) {
if (totals[i] > totals[size_mult - 1])
return;
}

setWindowSize(kWaveEditFrameLength);
}

+ 174
- 0
ports/vitalium/source/common/wavetable/file_source.h View File

@@ -0,0 +1,174 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "pitch_detector.h"
#include "wavetable_component.h"
#include "wave_frame.h"
#include "wave_source.h"
#include "utils.h"

class FileSource : public WavetableComponent {
public:
static constexpr float kMaxFileSourceSamples = 176400;
static constexpr int kExtraSaveSamples = 4;
static constexpr int kExtraBufferSamples = 4;
static constexpr int kPitchDetectMaxPeriod = 8096;

enum FadeStyle {
kWaveBlend,
kNoInterpolate,
kTimeInterpolate,
kFreqInterpolate,
kNumFadeStyles
};

enum PhaseStyle {
kNone,
kClear,
kVocode,
kNumPhaseStyles
};
struct SampleBuffer {
SampleBuffer() : size(0), sample_rate(0) { }
std::unique_ptr<float[]> data;
int size;
int sample_rate;
};

class FileSourceKeyframe : public WavetableKeyframe {
public:
FileSourceKeyframe(SampleBuffer* sample_buffer);
virtual ~FileSourceKeyframe() { }

void copy(const WavetableKeyframe* keyframe) override;
void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) override;

float getNormalizationScale();

void render(vital::WaveFrame* wave_frame) override;
void renderWaveBlend(vital::WaveFrame* wave_frame);
void renderNoInterpolate(vital::WaveFrame* wave_frame);
void renderTimeInterpolate(vital::WaveFrame* wave_frame);
void renderFreqInterpolate(vital::WaveFrame* wave_frame);
json stateToJson() override;
void jsonToState(json data) override;

double getStartPosition() { return start_position_; }
double getWindowSize() { return window_size_; }
double getWindowFade() { return window_fade_; }
double getWindowFadeSamples() { return window_fade_ * window_size_; }
int getSamplesNeeded() { return getWindowSize() + getWindowFadeSamples(); }

force_inline void setStartPosition(double start_position) { start_position_ = start_position; }
force_inline void setWindowFade(double window_fade) { window_fade_ = window_fade; }
force_inline void setWindowSize(double window_size) { window_size_ = window_size; }
force_inline void setFadeStyle(FadeStyle fade_style) { fade_style_ = fade_style; }
force_inline void setPhaseStyle(PhaseStyle phase_style) { phase_style_ = phase_style; }
force_inline void setOverriddenPhaseBuffer(const float* buffer) { overridden_phase_ = buffer; }
force_inline const float* getDataBuffer() {
if (sample_buffer_ == nullptr || sample_buffer_->data == nullptr)
return nullptr;
return sample_buffer_->data.get() + 1;
}
force_inline const float* getCubicInterpolationBuffer() {
if (sample_buffer_ == nullptr)
return nullptr;
return sample_buffer_->data.get();
}

float getScaledInterpolatedSample(float time);

void setInterpolateFromFrame(WaveSourceKeyframe* frame) {
interpolate_from_frame_ = frame;
}

void setInterpolateToFrame(WaveSourceKeyframe* frame) {
interpolate_to_frame_ = frame;
}

protected:
SampleBuffer* sample_buffer_;
const float* overridden_phase_;
WaveSourceKeyframe* interpolate_from_frame_;
WaveSourceKeyframe* interpolate_to_frame_;
double start_position_;
double window_fade_;
double window_size_;
FadeStyle fade_style_;
PhaseStyle phase_style_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FileSourceKeyframe)
};

FileSource();
virtual ~FileSource() { }

WavetableKeyframe* createKeyframe(int position) override;
void render(vital::WaveFrame* wave_frame, float position) override;
WavetableComponentFactory::ComponentType getType() override;
json stateToJson() override;
void jsonToState(json data) override;

FileSourceKeyframe* getKeyframe(int index);
const SampleBuffer* buffer() const { return &sample_buffer_; }
FadeStyle getFadeStyle() { return fade_style_; }
PhaseStyle getPhaseStyle() { return phase_style_; }
bool getNormalizeGain() { return normalize_gain_; }

void setNormalizeGain(bool normalize_gain) { normalize_gain_ = normalize_gain; }
void setWindowSize(double window_size) { window_size_ = window_size; }
void setFadeStyle(FadeStyle fade_style) { fade_style_ = fade_style; }
void setPhaseStyle(PhaseStyle phase_style);
void writePhaseOverrideBuffer();
double getWindowSize() { return window_size_; }
void loadBuffer(const float* buffer, int size, int sample_rate);
void detectPitch(int max_period = vital::WaveFrame::kWaveformSize);
void detectWaveEditTable();

force_inline const float* getDataBuffer() {
if (sample_buffer_.data == nullptr)
return nullptr;
return sample_buffer_.data.get() + 1;
}
force_inline const float* getCubicInterpolationBuffer() { return sample_buffer_.data.get(); }

protected:
FileSourceKeyframe compute_frame_;
WaveSourceKeyframe interpolate_from_frame_;
WaveSourceKeyframe interpolate_to_frame_;

SampleBuffer sample_buffer_;
float overridden_phase_[vital::WaveFrame::kWaveformSize];
FadeStyle fade_style_;
PhaseStyle phase_style_;
bool normalize_gain_;
bool normalize_mult_;
double window_size_;

int random_seed_;
vital::utils::RandomGenerator random_generator_;
PitchDetector pitch_detector_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FileSource)
};


+ 148
- 0
ports/vitalium/source/common/wavetable/frequency_filter_modifier.cpp View File

@@ -0,0 +1,148 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "frequency_filter_modifier.h"

#include "futils.h"
#include "utils.h"
#include "wave_frame.h"

namespace {
constexpr float kMinPower = -9.0f;
constexpr float kMaxPower = 9.0f;
constexpr int kMaxSlopeReach = 128;

force_inline double powerScale(double value, double power) {
static constexpr float kMinPower = 0.01f;
if (fabs(power) < kMinPower)
return value;

double abs_value = fabs(value);

double numerator = exp(power * abs_value) - 1.0f;
double denominator = exp(power) - 1.0f;
if (value >= 0.0f)
return numerator / denominator;
return -numerator / denominator;
}

force_inline float combWave(float t, float power) {
float range = t - floorf(t);
return 2.0f * powerScale(1.0f - fabsf(2.0f * range - 1.0f), power);
}
} // namespace

FrequencyFilterModifier::FrequencyFilterModifierKeyframe::FrequencyFilterModifierKeyframe() {
cutoff_ = 4.0f;
shape_ = 0.5f;
style_ = kLowPass;
normalize_ = true;
}

void FrequencyFilterModifier::FrequencyFilterModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
const FrequencyFilterModifierKeyframe* source = dynamic_cast<const FrequencyFilterModifierKeyframe*>(keyframe);
shape_ = source->shape_;
cutoff_ = source->cutoff_;
}

void FrequencyFilterModifier::FrequencyFilterModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
float t) {
const FrequencyFilterModifierKeyframe* from = dynamic_cast<const FrequencyFilterModifierKeyframe*>(from_keyframe);
const FrequencyFilterModifierKeyframe* to = dynamic_cast<const FrequencyFilterModifierKeyframe*>(to_keyframe);

shape_ = linearTween(from->shape_, to->shape_, t);
cutoff_ = linearTween(from->cutoff_, to->cutoff_, t);
}

void FrequencyFilterModifier::FrequencyFilterModifierKeyframe::render(vital::WaveFrame* wave_frame) {
for (int i = 0; i < vital::WaveFrame::kNumRealComplex; ++i)
wave_frame->frequency_domain[i] *= getMultiplier(i);

wave_frame->toTimeDomain();

if (normalize_) {
wave_frame->normalize(true);
wave_frame->toFrequencyDomain();
}
}

json FrequencyFilterModifier::FrequencyFilterModifierKeyframe::stateToJson() {
json data = WavetableKeyframe::stateToJson();
data["cutoff"] = cutoff_;
data["shape"] = shape_;
return data;
}

void FrequencyFilterModifier::FrequencyFilterModifierKeyframe::jsonToState(json data) {
WavetableKeyframe::jsonToState(data);
cutoff_ = data["cutoff"];
shape_ = data["shape"];
}

float FrequencyFilterModifier::FrequencyFilterModifierKeyframe::getMultiplier(float index) {
float cutoff_index = std::pow(2.0f, cutoff_);
float cutoff_delta = index - cutoff_index;

float slope = 1.0f / vital::utils::interpolate(1.0f, kMaxSlopeReach, shape_ * shape_);
float power = vital::utils::interpolate(kMinPower, kMaxPower, shape_);

if (style_ == kLowPass)
return vital::utils::clamp(1.0f - slope * cutoff_delta, 0.0f, 1.0f);
if (style_ == kBandPass)
return vital::utils::clamp(1.0f - fabsf(slope * cutoff_delta), 0.0f, 1.0f);
if (style_ == kHighPass)
return vital::utils::clamp(1.0f + slope * cutoff_delta, 0.0f, 1.0f);
if (style_ == kComb)
return combWave(index / (cutoff_index * 2.0f), power);

return 0.0f;
}

WavetableKeyframe* FrequencyFilterModifier::createKeyframe(int position) {
FrequencyFilterModifierKeyframe* keyframe = new FrequencyFilterModifierKeyframe();
interpolate(keyframe, position);
return keyframe;
}

void FrequencyFilterModifier::render(vital::WaveFrame* wave_frame, float position) {
interpolate(&compute_frame_, position);
compute_frame_.setStyle(style_);
compute_frame_.setNormalize(normalize_);
compute_frame_.render(wave_frame);
}

WavetableComponentFactory::ComponentType FrequencyFilterModifier::getType() {
return WavetableComponentFactory::kFrequencyFilter;
}

json FrequencyFilterModifier::stateToJson() {
json data = WavetableComponent::stateToJson();
data["style"] = style_;
data["normalize"] = normalize_;
return data;
}

void FrequencyFilterModifier::jsonToState(json data) {
WavetableComponent::jsonToState(data);
style_ = data["style"];
normalize_ = data["normalize"];
}

FrequencyFilterModifier::FrequencyFilterModifierKeyframe* FrequencyFilterModifier::getKeyframe(int index) {
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
return dynamic_cast<FrequencyFilterModifier::FrequencyFilterModifierKeyframe*>(wavetable_keyframe);
}

+ 87
- 0
ports/vitalium/source/common/wavetable/frequency_filter_modifier.h View File

@@ -0,0 +1,87 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wavetable_component.h"

class FrequencyFilterModifier : public WavetableComponent {
public:
enum FilterStyle {
kLowPass,
kBandPass,
kHighPass,
kComb,
kNumFilterStyles
};

class FrequencyFilterModifierKeyframe : public WavetableKeyframe {
public:
FrequencyFilterModifierKeyframe();
virtual ~FrequencyFilterModifierKeyframe() { }

void copy(const WavetableKeyframe* keyframe) override;
void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) override;
void render(vital::WaveFrame* wave_frame) override;
json stateToJson() override;
void jsonToState(json data) override;

float getMultiplier(float index);

float getCutoff() { return cutoff_; }
float getShape() { return shape_; }

void setStyle(FilterStyle style) { style_ = style; }
void setCutoff(float cutoff) { cutoff_ = cutoff; }
void setShape(float shape) { shape_ = shape; }
void setNormalize(bool normalize) { normalize_ = normalize; }

protected:
FilterStyle style_;
bool normalize_;
float cutoff_;
float shape_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FrequencyFilterModifierKeyframe)
};

FrequencyFilterModifier() : style_(kLowPass), normalize_(true) { }
virtual ~FrequencyFilterModifier() { }

virtual WavetableKeyframe* createKeyframe(int position) override;
virtual void render(vital::WaveFrame* wave_frame, float position) override;
virtual WavetableComponentFactory::ComponentType getType() override;
virtual json stateToJson() override;
virtual void jsonToState(json data) override;

FrequencyFilterModifierKeyframe* getKeyframe(int index);

FilterStyle getStyle() { return style_; }
bool getNormalize() { return normalize_; }

void setStyle(FilterStyle style) { style_ = style; }
void setNormalize(bool normalize) { normalize_ = normalize; }

protected:
FilterStyle style_;
bool normalize_;
FrequencyFilterModifierKeyframe compute_frame_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FrequencyFilterModifier)
};


+ 127
- 0
ports/vitalium/source/common/wavetable/phase_modifier.cpp View File

@@ -0,0 +1,127 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "phase_modifier.h"
#include "wave_frame.h"
#include "wavetable_component_factory.h"

namespace {
std::complex<float> multiplyAndMix(std::complex<float> value, std::complex<float> mult, float mix) {
std::complex<float> result = value * mult;
return mix * result + (1.0f - mix) * value;
}
} // namespace

PhaseModifier::PhaseModifierKeyframe::PhaseModifierKeyframe() : phase_(0.0f), mix_(1.0f), phase_style_(kNormal) { }

void PhaseModifier::PhaseModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
const PhaseModifierKeyframe* source = dynamic_cast<const PhaseModifierKeyframe*>(keyframe);
phase_ = source->phase_;
mix_ = source->mix_;
}

void PhaseModifier::PhaseModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
float t) {
const PhaseModifierKeyframe* from = dynamic_cast<const PhaseModifierKeyframe*>(from_keyframe);
const PhaseModifierKeyframe* to = dynamic_cast<const PhaseModifierKeyframe*>(to_keyframe);

phase_ = linearTween(from->phase_, to->phase_, t);
mix_ = linearTween(from->mix_, to->mix_, t);
}

void PhaseModifier::PhaseModifierKeyframe::render(vital::WaveFrame* wave_frame) {
std::complex<float> phase_shift = std::polar(1.0f, -phase_);
if (phase_style_ == kHarmonic) {
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
wave_frame->frequency_domain[i] = multiplyAndMix(wave_frame->frequency_domain[i], phase_shift, mix_);
}
else if (phase_style_ == kHarmonicEvenOdd) {
std::complex<float> odd_shift = 1.0f / phase_shift;
for (int i = 0; i < vital::WaveFrame::kWaveformSize; i += 2) {
wave_frame->frequency_domain[i] = multiplyAndMix(wave_frame->frequency_domain[i], phase_shift, mix_);
wave_frame->frequency_domain[i + 1] = multiplyAndMix(wave_frame->frequency_domain[i + 1], odd_shift, mix_);
}
}
else if (phase_style_ == kNormal) {
std::complex<float> current_phase_shift = 1.0f;

for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
wave_frame->frequency_domain[i] = multiplyAndMix(wave_frame->frequency_domain[i], current_phase_shift, mix_);
current_phase_shift *= phase_shift;
}
}
else if (phase_style_ == kEvenOdd) {
std::complex<float> current_phase_shift = 1.0f;

for (int i = 0; i < vital::WaveFrame::kWaveformSize; i += 2) {
wave_frame->frequency_domain[i] = multiplyAndMix(wave_frame->frequency_domain[i], current_phase_shift, mix_);
std::complex<float> odd_shift = 1.0f / (current_phase_shift * phase_shift);
wave_frame->frequency_domain[i + 1] = multiplyAndMix(wave_frame->frequency_domain[i + 1], odd_shift, mix_);
current_phase_shift *= phase_shift * phase_shift;
}
}
else if (phase_style_ == kClear) {
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
wave_frame->frequency_domain[i] = std::abs(wave_frame->frequency_domain[i]);
}
wave_frame->toTimeDomain();
}

json PhaseModifier::PhaseModifierKeyframe::stateToJson() {
json data = WavetableKeyframe::stateToJson();
data["phase"] = phase_;
data["mix"] = mix_;
return data;
}

void PhaseModifier::PhaseModifierKeyframe::jsonToState(json data) {
WavetableKeyframe::jsonToState(data);
phase_ = data["phase"];
mix_ = data["mix"];
}

WavetableKeyframe* PhaseModifier::createKeyframe(int position) {
PhaseModifierKeyframe* keyframe = new PhaseModifierKeyframe();
interpolate(keyframe, position);
return keyframe;
}

void PhaseModifier::render(vital::WaveFrame* wave_frame, float position) {
compute_frame_.setPhaseStyle(phase_style_);
interpolate(&compute_frame_, position);
compute_frame_.render(wave_frame);
}

WavetableComponentFactory::ComponentType PhaseModifier::getType() {
return WavetableComponentFactory::kPhaseModifier;
}

json PhaseModifier::stateToJson() {
json data = WavetableComponent::stateToJson();
data["style"] = phase_style_;
return data;
}

void PhaseModifier::jsonToState(json data) {
WavetableComponent::jsonToState(data);
phase_style_ = data["style"];
}

PhaseModifier::PhaseModifierKeyframe* PhaseModifier::getKeyframe(int index) {
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
return dynamic_cast<PhaseModifier::PhaseModifierKeyframe*>(wavetable_keyframe);
}

+ 79
- 0
ports/vitalium/source/common/wavetable/phase_modifier.h View File

@@ -0,0 +1,79 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wavetable_component.h"

class PhaseModifier : public WavetableComponent {
public:
enum PhaseStyle {
kNormal,
kEvenOdd,
kHarmonic,
kHarmonicEvenOdd,
kClear,
kNumPhaseStyles
};

class PhaseModifierKeyframe : public WavetableKeyframe {
public:
PhaseModifierKeyframe();
virtual ~PhaseModifierKeyframe() { }

void copy(const WavetableKeyframe* keyframe) override;
void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) override;
void render(vital::WaveFrame* wave_frame) override;
json stateToJson() override;
void jsonToState(json data) override;

float getPhase() { return phase_; }
float getMix() { return mix_; }
void setPhase(float phase) { phase_ = phase; }
void setMix(float mix) { mix_ = mix; }
void setPhaseStyle(PhaseStyle style) { phase_style_ = style; }

protected:
float phase_;
float mix_;
PhaseStyle phase_style_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PhaseModifierKeyframe)
};

PhaseModifier() : phase_style_(kNormal) { }
virtual ~PhaseModifier() = default;

virtual WavetableKeyframe* createKeyframe(int position) override;
virtual void render(vital::WaveFrame* wave_frame, float position) override;
virtual WavetableComponentFactory::ComponentType getType() override;
virtual json stateToJson() override;
virtual void jsonToState(json data) override;

PhaseModifierKeyframe* getKeyframe(int index);

void setPhaseStyle(PhaseStyle style) { phase_style_ = style; }
PhaseStyle getPhaseStyle() const { return phase_style_; }

protected:
PhaseModifierKeyframe compute_frame_;
PhaseStyle phase_style_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PhaseModifier)
};


+ 103
- 0
ports/vitalium/source/common/wavetable/pitch_detector.cpp 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/>.
*/

#include "pitch_detector.h"
#include "synth_constants.h"
#include "wave_frame.h"

#include <climits>

PitchDetector::PitchDetector() {
size_ = 0;
signal_data_ = nullptr;
}

void PitchDetector::loadSignal(const float* signal, int size) {
size_ = size;
signal_data_ = std::make_unique<float[]>(size);

memcpy(signal_data_.get(), signal, sizeof(float) * size);
}

float PitchDetector::getPeriodError(float period) {
static constexpr float kDcDeltaErrorMultiplier = 0.015f;
float error = 0.0f;
int waves = size_ / period - 1;
VITAL_ASSERT(waves > 0);
int points = kNumPoints / waves;
for (int w = 0; w < waves; ++w) {
float total_from = 0.0f;
float total_to = 0.0f;
for (int i = 0; i < points; ++i) {
float first_position = w * period + i * period / points;
float second_position = (w + 1) * period + i * period / points;

int first_index = first_position;
float first_t = first_position - first_index;
float first_from = signal_data_[first_index];
float first_to = signal_data_[first_index + 1];
float first_value = vital::utils::interpolate(first_from, first_to, first_t);
total_from += first_value;

int second_index = second_position;
float second_t = second_position - second_index;
float second_from = signal_data_[second_index];
float second_to = signal_data_[second_index + 1];
float second_value = vital::utils::interpolate(second_from, second_to, second_t);
total_to += second_value;

float delta = first_value - second_value;
error += delta * delta;
}

float total_diff = total_from - total_to;
error += total_diff * total_diff * kDcDeltaErrorMultiplier;
}

return error;
}

float PitchDetector::findYinPeriod(int max_period) {
constexpr float kMinLength = 300.0f;

float max_length = std::min<float>(size_ / 2.0f, max_period);

float best_error = INT_MAX;
float match = kMinLength;

for (float length = kMinLength; length < max_length; length += 1.0f) {
float error = getPeriodError(length);
if (error < best_error) {
best_error = error;
match = length;
}
}

float best_match = match;
for (float length = match - 1.0f; length <= match + 1.0f; length += 0.1f) {
float error = getPeriodError(length);
if (error < best_error) {
best_error = error;
best_match = length;
}
}

return best_match;
}

float PitchDetector::matchPeriod(int max_period) {
return findYinPeriod(max_period);
}

+ 42
- 0
ports/vitalium/source/common/wavetable/pitch_detector.h View File

@@ -0,0 +1,42 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

class PitchDetector {
public:
static constexpr int kNumPoints = 2520;

PitchDetector();

void setSize(int size) { size_ = size; }
void loadSignal(const float* signal, int size);

float getPeriodError(float period);
float findYinPeriod(int max_period);
float matchPeriod(int max_period);

const float* data() const { return signal_data_.get(); }

protected:
int size_;
std::unique_ptr<float[]> signal_data_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PitchDetector)
};


+ 48
- 0
ports/vitalium/source/common/wavetable/shepard_tone_source.cpp View File

@@ -0,0 +1,48 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "shepard_tone_source.h"
#include "wavetable_component_factory.h"

ShepardToneSource::ShepardToneSource() {
loop_frame_ = std::make_unique<WaveSourceKeyframe>();
}

ShepardToneSource::~ShepardToneSource() { }

void ShepardToneSource::render(vital::WaveFrame* wave_frame, float position) {
if (numFrames() == 0)
return;

WaveSourceKeyframe* keyframe = getKeyframe(0);
vital::WaveFrame* key_wave_frame = keyframe->wave_frame();
vital::WaveFrame* loop_wave_frame = loop_frame_->wave_frame();

for (int i = 0; i < vital::WaveFrame::kWaveformSize / 2; ++i) {
loop_wave_frame->frequency_domain[i * 2] = key_wave_frame->frequency_domain[i];
loop_wave_frame->frequency_domain[i * 2 + 1] = 0.0f;
}

loop_wave_frame->toTimeDomain();

compute_frame_->setInterpolationMode(interpolation_mode_);
compute_frame_->interpolate(keyframe, loop_frame_.get(), position / (vital::kNumOscillatorWaveFrames - 1.0f));
wave_frame->copy(compute_frame_->wave_frame());
}

WavetableComponentFactory::ComponentType ShepardToneSource::getType() {
return WavetableComponentFactory::kShepardToneSource;
}

+ 35
- 0
ports/vitalium/source/common/wavetable/shepard_tone_source.h View File

@@ -0,0 +1,35 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wave_source.h"

class ShepardToneSource : public WaveSource {
public:
ShepardToneSource();
virtual ~ShepardToneSource();

virtual void render(vital::WaveFrame* wave_frame, float position) override;
virtual WavetableComponentFactory::ComponentType getType() override;
virtual bool hasKeyframes() override { return false; }

protected:
std::unique_ptr<WaveSourceKeyframe> loop_frame_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ShepardToneSource)
};

+ 94
- 0
ports/vitalium/source/common/wavetable/slew_limit_modifier.cpp View File

@@ -0,0 +1,94 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "slew_limit_modifier.h"

#include "wave_frame.h"

SlewLimitModifier::SlewLimitModifierKeyframe::SlewLimitModifierKeyframe() {
slew_down_run_rise_ = 0.0f;
slew_up_run_rise_ = 0.0f;
}

void SlewLimitModifier::SlewLimitModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
const SlewLimitModifierKeyframe* source = dynamic_cast<const SlewLimitModifierKeyframe*>(keyframe);
slew_down_run_rise_ = source->slew_down_run_rise_;
slew_up_run_rise_ = source->slew_up_run_rise_;
}

void SlewLimitModifier::SlewLimitModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
float t) {
const SlewLimitModifierKeyframe* from = dynamic_cast<const SlewLimitModifierKeyframe*>(from_keyframe);
const SlewLimitModifierKeyframe* to = dynamic_cast<const SlewLimitModifierKeyframe*>(to_keyframe);

slew_down_run_rise_ = linearTween(from->slew_down_run_rise_, to->slew_down_run_rise_, t);
slew_up_run_rise_ = linearTween(from->slew_up_run_rise_, to->slew_up_run_rise_, t);
}

void SlewLimitModifier::SlewLimitModifierKeyframe::render(vital::WaveFrame* wave_frame) {
float min_slew_limit = 1.0f / vital::WaveFrame::kWaveformSize;
float max_up_delta = (2.0f / vital::WaveFrame::kWaveformSize) / std::max(slew_up_run_rise_, min_slew_limit);
float max_down_delta = (2.0f / vital::WaveFrame::kWaveformSize) / std::max(slew_down_run_rise_, min_slew_limit);

float current_value = wave_frame->time_domain[0];
for (int i = 1; i < 2 * vital::WaveFrame::kWaveformSize; ++i) {
int index = i % vital::WaveFrame::kWaveformSize;
float target_value = wave_frame->time_domain[index];
float delta = target_value - current_value;

if (delta > 0.0f)
current_value += std::min(delta, max_up_delta);
else
current_value -= std::min(-delta, max_down_delta);

wave_frame->time_domain[index] = current_value;
}
wave_frame->toFrequencyDomain();
}

json SlewLimitModifier::SlewLimitModifierKeyframe::stateToJson() {
json data = WavetableKeyframe::stateToJson();
data["up_run_rise"] = slew_up_run_rise_;
data["down_run_rise"] = slew_down_run_rise_;
return data;
}

void SlewLimitModifier::SlewLimitModifierKeyframe::jsonToState(json data) {
WavetableKeyframe::jsonToState(data);
slew_up_run_rise_ = data["up_run_rise"];
slew_down_run_rise_ = data["down_run_rise"];
}

WavetableKeyframe* SlewLimitModifier::createKeyframe(int position) {
SlewLimitModifierKeyframe* keyframe = new SlewLimitModifierKeyframe();
interpolate(keyframe, position);
return keyframe;
}

void SlewLimitModifier::render(vital::WaveFrame* wave_frame, float position) {
interpolate(&compute_frame_, position);
compute_frame_.render(wave_frame);
}

WavetableComponentFactory::ComponentType SlewLimitModifier::getType() {
return WavetableComponentFactory::kSlewLimiter;
}

SlewLimitModifier::SlewLimitModifierKeyframe* SlewLimitModifier::getKeyframe(int index) {
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
return dynamic_cast<SlewLimitModifier::SlewLimitModifierKeyframe*>(wavetable_keyframe);
}

+ 63
- 0
ports/vitalium/source/common/wavetable/slew_limit_modifier.h View File

@@ -0,0 +1,63 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wavetable_component.h"

class SlewLimitModifier : public WavetableComponent {
public:
class SlewLimitModifierKeyframe : public WavetableKeyframe {
public:
SlewLimitModifierKeyframe();
virtual ~SlewLimitModifierKeyframe() { }

void copy(const WavetableKeyframe* keyframe) override;
void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) override;
void render(vital::WaveFrame* wave_frame) override;
json stateToJson() override;
void jsonToState(json data) override;

float getSlewUpLimit() { return slew_up_run_rise_; }
float getSlewDownLimit() { return slew_down_run_rise_; }

void setSlewUpLimit(float slew_up_limit) { slew_up_run_rise_ = slew_up_limit; }
void setSlewDownLimit(float slew_down_limit) { slew_down_run_rise_ = slew_down_limit; }

protected:
float slew_up_run_rise_;
float slew_down_run_rise_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SlewLimitModifierKeyframe)
};

SlewLimitModifier() { }
virtual ~SlewLimitModifier() { }

virtual WavetableKeyframe* createKeyframe(int position) override;
virtual void render(vital::WaveFrame* wave_frame, float position) override;
virtual WavetableComponentFactory::ComponentType getType() override;

SlewLimitModifierKeyframe* getKeyframe(int index);

protected:
SlewLimitModifierKeyframe compute_frame_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SlewLimitModifier)
};


+ 81
- 0
ports/vitalium/source/common/wavetable/wave_fold_modifier.cpp View File

@@ -0,0 +1,81 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wave_fold_modifier.h"

#include "utils.h"
#include "wave_frame.h"

WaveFoldModifier::WaveFoldModifierKeyframe::WaveFoldModifierKeyframe() {
wave_fold_boost_ = 1.0f;
}

void WaveFoldModifier::WaveFoldModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
const WaveFoldModifierKeyframe* source = dynamic_cast<const WaveFoldModifierKeyframe*>(keyframe);
wave_fold_boost_ = source->wave_fold_boost_;
}

void WaveFoldModifier::WaveFoldModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
float t) {
const WaveFoldModifierKeyframe* from = dynamic_cast<const WaveFoldModifierKeyframe*>(from_keyframe);
const WaveFoldModifierKeyframe* to = dynamic_cast<const WaveFoldModifierKeyframe*>(to_keyframe);

wave_fold_boost_ = linearTween(from->wave_fold_boost_, to->wave_fold_boost_, t);
}

void WaveFoldModifier::WaveFoldModifierKeyframe::render(vital::WaveFrame* wave_frame) {
float max_value = std::max(1.0f, wave_frame->getMaxZeroOffset());

for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
float value = vital::utils::clamp(wave_frame->time_domain[i] / max_value, -1.0f, 1.0f);
float adjusted_value = max_value * wave_fold_boost_ * asinf(value);

wave_frame->time_domain[i] = sinf(adjusted_value);
}
wave_frame->toFrequencyDomain();
}

json WaveFoldModifier::WaveFoldModifierKeyframe::stateToJson() {
json data = WavetableKeyframe::stateToJson();
data["fold_boost"] = wave_fold_boost_;
return data;
}

void WaveFoldModifier::WaveFoldModifierKeyframe::jsonToState(json data) {
WavetableKeyframe::jsonToState(data);
wave_fold_boost_ = data["fold_boost"];
}

WavetableKeyframe* WaveFoldModifier::createKeyframe(int position) {
WaveFoldModifierKeyframe* keyframe = new WaveFoldModifierKeyframe();
interpolate(keyframe, position);
return keyframe;
}

void WaveFoldModifier::render(vital::WaveFrame* wave_frame, float position) {
interpolate(&compute_frame_, position);
compute_frame_.render(wave_frame);
}

WavetableComponentFactory::ComponentType WaveFoldModifier::getType() {
return WavetableComponentFactory::kWaveFolder;
}

WaveFoldModifier::WaveFoldModifierKeyframe* WaveFoldModifier::getKeyframe(int index) {
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
return dynamic_cast<WaveFoldModifier::WaveFoldModifierKeyframe*>(wavetable_keyframe);
}

+ 59
- 0
ports/vitalium/source/common/wavetable/wave_fold_modifier.h View File

@@ -0,0 +1,59 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wavetable_component.h"

class WaveFoldModifier : public WavetableComponent {
public:
class WaveFoldModifierKeyframe : public WavetableKeyframe {
public:
WaveFoldModifierKeyframe();
virtual ~WaveFoldModifierKeyframe() { }

void copy(const WavetableKeyframe* keyframe) override;
void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) override;
void render(vital::WaveFrame* wave_frame) override;
json stateToJson() override;
void jsonToState(json data) override;

float getWaveFoldBoost() { return wave_fold_boost_; }
void setWaveFoldBoost(float boost) { wave_fold_boost_ = boost; }

protected:
float wave_fold_boost_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveFoldModifierKeyframe)
};

WaveFoldModifier() { }
virtual ~WaveFoldModifier() { }

virtual WavetableKeyframe* createKeyframe(int position) override;
virtual void render(vital::WaveFrame* wave_frame, float position) override;
virtual WavetableComponentFactory::ComponentType getType() override;

WaveFoldModifierKeyframe* getKeyframe(int index);

protected:
WaveFoldModifierKeyframe compute_frame_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveFoldModifier)
};


+ 126
- 0
ports/vitalium/source/common/wavetable/wave_line_source.cpp 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/>.
*/

#include "wave_line_source.h"

#include "futils.h"
#include "utils.h"
#include "poly_utils.h"
#include "wave_frame.h"
#include "wavetable_component_factory.h"

WaveLineSource::WaveLineSourceKeyframe::WaveLineSourceKeyframe() :
line_generator_(vital::WaveFrame::kWaveformSize) {
pull_power_ = 0.0f;
}

void WaveLineSource::WaveLineSourceKeyframe::copy(const WavetableKeyframe* keyframe) {
const WaveLineSourceKeyframe* source = dynamic_cast<const WaveLineSourceKeyframe*>(keyframe);

const LineGenerator* source_generator = source->getLineGenerator();
line_generator_.setNumPoints(source_generator->getNumPoints());
line_generator_.setSmooth(source_generator->smooth());
for (int i = 0; i < line_generator_.getNumPoints(); ++i) {
line_generator_.setPoint(i, source_generator->getPoint(i));
line_generator_.setPower(i, source_generator->getPower(i));
}
}

void WaveLineSource::WaveLineSourceKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
float t) {
const WaveLineSourceKeyframe* from = dynamic_cast<const WaveLineSourceKeyframe*>(from_keyframe);
const WaveLineSourceKeyframe* to = dynamic_cast<const WaveLineSourceKeyframe*>(to_keyframe);
VITAL_ASSERT(from->getNumPoints() == to->getNumPoints());

float relative_power = from->getPullPower() - to->getPullPower();
float adjusted_t = vital::futils::powerScale(t, relative_power);

const LineGenerator* from_generator = from->getLineGenerator();
const LineGenerator* to_generator = to->getLineGenerator();
int num_points = from_generator->getNumPoints();
line_generator_.setNumPoints(num_points);
line_generator_.setSmooth(from_generator->smooth());

for (int i = 0; i < num_points; ++i) {
std::pair<float, float> from_point = from_generator->getPoint(i);
std::pair<float, float> to_point = to_generator->getPoint(i);
line_generator_.setPoint(i, {
linearTween(from_point.first, to_point.first, adjusted_t),
linearTween(from_point.second, to_point.second, adjusted_t),
});
line_generator_.setPower(i, linearTween(from_generator->getPower(i), to_generator->getPower(i), adjusted_t));
}
}

void WaveLineSource::WaveLineSourceKeyframe::render(vital::WaveFrame* wave_frame) {
line_generator_.render();
memcpy(wave_frame->time_domain, line_generator_.getBuffer(), vital::WaveFrame::kWaveformSize * sizeof(float));
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
wave_frame->time_domain[i] = wave_frame->time_domain[i] * 2.0f - 1.0f;
wave_frame->toFrequencyDomain();
}

json WaveLineSource::WaveLineSourceKeyframe::stateToJson() {
json data = WavetableKeyframe::stateToJson();
data["pull_power"] = pull_power_;
data["line"] = line_generator_.stateToJson();
return data;
}

void WaveLineSource::WaveLineSourceKeyframe::jsonToState(json data) {
WavetableKeyframe::jsonToState(data);
pull_power_ = 0.0f;
if (data.count("pull_power"))
pull_power_ = data["pull_power"];
if (data.count("line"))
line_generator_.jsonToState(data["line"]);
}

WavetableKeyframe* WaveLineSource::createKeyframe(int position) {
WaveLineSourceKeyframe* keyframe = new WaveLineSourceKeyframe();
interpolate(keyframe, position);
return keyframe;
}

void WaveLineSource::render(vital::WaveFrame* wave_frame, float position) {
interpolate(&compute_frame_, position);
compute_frame_.render(wave_frame);
}

WavetableComponentFactory::ComponentType WaveLineSource::getType() {
return WavetableComponentFactory::kLineSource;
}

json WaveLineSource::stateToJson() {
json data = WavetableComponent::stateToJson();
data["num_points"] = num_points_;
return data;
}

void WaveLineSource::jsonToState(json data) {
WavetableComponent::jsonToState(data);
setNumPoints(data["num_points"]);
}

void WaveLineSource::setNumPoints(int num_points) {
num_points_ = num_points;
}

WaveLineSource::WaveLineSourceKeyframe* WaveLineSource::getKeyframe(int index) {
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
return dynamic_cast<WaveLineSource::WaveLineSourceKeyframe*>(wavetable_keyframe);
}

+ 78
- 0
ports/vitalium/source/common/wavetable/wave_line_source.h View File

@@ -0,0 +1,78 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wavetable_component.h"

class WaveLineSource : public WavetableComponent {
public:
static constexpr int kDefaultLinePoints = 4;

class WaveLineSourceKeyframe : public WavetableKeyframe {
public:
WaveLineSourceKeyframe();
virtual ~WaveLineSourceKeyframe() = default;

void copy(const WavetableKeyframe* keyframe) override;
void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) override;
void render(vital::WaveFrame* wave_frame) override;
json stateToJson() override;
void jsonToState(json data) override;

inline std::pair<float, float> getPoint(int index) const { return line_generator_.getPoint(index); }
inline float getPower(int index) const { return line_generator_.getPower(index); }
inline void setPoint(std::pair<float, float> point, int index) { line_generator_.setPoint(index, point); }
inline void setPower(float power, int index) { line_generator_.setPower(index, power); }
inline void removePoint(int index) { line_generator_.removePoint(index); }
inline void addMiddlePoint(int index) { line_generator_.addMiddlePoint(index); }
inline int getNumPoints() const { return line_generator_.getNumPoints(); }
inline void setSmooth(bool smooth) { line_generator_.setSmooth(smooth); }

void setPullPower(float power) { pull_power_ = power; }
float getPullPower() const { return pull_power_; }
const LineGenerator* getLineGenerator() const { return &line_generator_; }
LineGenerator* getLineGenerator() { return &line_generator_; }

protected:
LineGenerator line_generator_;
float pull_power_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveLineSourceKeyframe)
};

WaveLineSource() : num_points_(kDefaultLinePoints), compute_frame_() { }
virtual ~WaveLineSource() = default;

virtual WavetableKeyframe* createKeyframe(int position) override;
virtual void render(vital::WaveFrame* wave_frame, float position) override;
virtual WavetableComponentFactory::ComponentType getType() override;
virtual json stateToJson() override;
virtual void jsonToState(json data) override;

void setNumPoints(int num_points);
int numPoints() { return num_points_; }
WaveLineSourceKeyframe* getKeyframe(int index);

protected:
int num_points_;
WaveLineSourceKeyframe compute_frame_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveLineSource)
};


+ 208
- 0
ports/vitalium/source/common/wavetable/wave_source.cpp View File

@@ -0,0 +1,208 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wave_source.h"
#include "wave_frame.h"
#include "wavetable_component_factory.h"

WaveSource::WaveSource() {
compute_frame_ = std::make_unique<WaveSourceKeyframe>();
interpolation_mode_ = kFrequency;
}

WaveSource::~WaveSource() { }

WavetableKeyframe* WaveSource::createKeyframe(int position) {
WaveSourceKeyframe* keyframe = new WaveSourceKeyframe();
render(keyframe->wave_frame(), position);
return keyframe;
}

void WaveSource::render(vital::WaveFrame* wave_frame, float position) {
compute_frame_->setInterpolationMode(interpolation_mode_);
interpolate(compute_frame_.get(), position);
wave_frame->copy(compute_frame_->wave_frame());
}

WavetableComponentFactory::ComponentType WaveSource::getType() {
return WavetableComponentFactory::kWaveSource;
}

json WaveSource::stateToJson() {
json data = WavetableComponent::stateToJson();
data["interpolation"] = interpolation_mode_;
return data;
}

void WaveSource::jsonToState(json data) {
WavetableComponent::jsonToState(data);
interpolation_mode_ = data["interpolation"];
compute_frame_->setInterpolationMode(interpolation_mode_);
}

vital::WaveFrame* WaveSource::getWaveFrame(int index) {
return getKeyframe(index)->wave_frame();
}

WaveSourceKeyframe* WaveSource::getKeyframe(int index) {
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
return dynamic_cast<WaveSourceKeyframe*>(wavetable_keyframe);
}

void WaveSourceKeyframe::copy(const WavetableKeyframe* keyframe) {
const WaveSourceKeyframe* source = dynamic_cast<const WaveSourceKeyframe*>(keyframe);
wave_frame_->copy(source->wave_frame_.get());
}

void WaveSourceKeyframe::linearTimeInterpolate(const vital::WaveFrame* from, const vital::WaveFrame* to, float t) {
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
wave_frame_->time_domain[i] = linearTween(from->time_domain[i], to->time_domain[i], t);
wave_frame_->toFrequencyDomain();
}

void WaveSourceKeyframe::cubicTimeInterpolate(const vital::WaveFrame* prev, const vital::WaveFrame* from,
const vital::WaveFrame* to, const vital::WaveFrame* next,
float range_prev, float range, float range_next, float t) {

for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
wave_frame_->time_domain[i] = cubicTween(prev->time_domain[i], from->time_domain[i],
to->time_domain[i], next->time_domain[i],
range_prev, range, range_next, t);
}
wave_frame_->toFrequencyDomain();
}

void WaveSourceKeyframe::linearFrequencyInterpolate(const vital::WaveFrame* from,
const vital::WaveFrame* to, float t) {
for (int i = 0; i < vital::WaveFrame::kNumRealComplex; ++i) {
float amplitude_from = sqrtf(std::abs(from->frequency_domain[i]));
float amplitude_to = sqrtf(std::abs(to->frequency_domain[i]));
float amplitude = linearTween(amplitude_from, amplitude_to, t);
amplitude *= amplitude;

float phase_from = std::arg(from->frequency_domain[i]);
float phase_delta = std::arg(std::conj(from->frequency_domain[i]) * to->frequency_domain[i]);
float phase = phase_from + t * phase_delta;
if (amplitude_from == 0)
phase = std::arg(to->frequency_domain[i]);
wave_frame_->frequency_domain[i] = std::polar(amplitude, phase);
}

float dc_from = from->frequency_domain[0].real();
float dc_to = to->frequency_domain[0].real();
wave_frame_->frequency_domain[0] = linearTween(dc_from, dc_to, t);

int last = vital::WaveFrame::kNumRealComplex - 1;
float last_harmonic_from = from->frequency_domain[last].real();
float last_harmonic_to = to->frequency_domain[last].real();
wave_frame_->frequency_domain[last] = linearTween(last_harmonic_from, last_harmonic_to, t);

wave_frame_->toTimeDomain();
}

void WaveSourceKeyframe::cubicFrequencyInterpolate(const vital::WaveFrame* prev, const vital::WaveFrame* from,
const vital::WaveFrame* to, const vital::WaveFrame* next,
float range_prev, float range, float range_next, float t) {
for (int i = 0; i < vital::WaveFrame::kNumRealComplex; ++i) {
float amplitude_prev = sqrtf(std::abs(prev->frequency_domain[i]));
float amplitude_from = sqrtf(std::abs(from->frequency_domain[i]));
float amplitude_to = sqrtf(std::abs(to->frequency_domain[i]));
float amplitude_next = sqrtf(std::abs(next->frequency_domain[i]));
float amplitude = cubicTween(amplitude_prev, amplitude_from, amplitude_to, amplitude_next,
range_prev, range, range_next, t);
amplitude *= amplitude;

float phase_delta_from = std::arg(std::conj(prev->frequency_domain[i]) * from->frequency_domain[i]);
float phase_delta_to = std::arg(std::conj(from->frequency_domain[i]) * to->frequency_domain[i]);
float phase_delta_next = std::arg(std::conj(to->frequency_domain[i]) * next->frequency_domain[i]);
float phase_prev = std::arg(prev->frequency_domain[i]);
float phase_from = phase_prev;
if (amplitude_from)
phase_from += phase_delta_from;
float phase_to = phase_from;
if (amplitude_to)
phase_to += phase_delta_to;
float phase_next = phase_to;
if (amplitude_next)
phase_next += phase_delta_next;

float phase = cubicTween(phase_prev, phase_from, phase_to, phase_next, range_prev, range, range_next, t);
wave_frame_->frequency_domain[i] = std::polar(amplitude, phase);
}

float dc_prev = prev->frequency_domain[0].real();
float dc_from = from->frequency_domain[0].real();
float dc_to = to->frequency_domain[0].real();
float dc_next = next->frequency_domain[0].real();
wave_frame_->frequency_domain[0] = cubicTween(dc_prev, dc_from, dc_to, dc_next, range_prev, range, range_next, t);

int last = vital::WaveFrame::kNumRealComplex - 1;
float last_harmonic_prev = prev->frequency_domain[last].real();
float last_harmonic_from = from->frequency_domain[last].real();
float last_harmonic_to = to->frequency_domain[last].real();
float last_harmonic_next = next->frequency_domain[last].real();
wave_frame_->frequency_domain[last] = cubicTween(last_harmonic_prev, last_harmonic_from,
last_harmonic_to, last_harmonic_next,
range_prev, range, range_next, t);
wave_frame_->toTimeDomain();
}

void WaveSourceKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) {
const WaveSourceKeyframe* from = dynamic_cast<const WaveSourceKeyframe*>(from_keyframe);
const WaveSourceKeyframe* to = dynamic_cast<const WaveSourceKeyframe*>(to_keyframe);

if (interpolation_mode_ == WaveSource::kFrequency)
linearFrequencyInterpolate(from->wave_frame_.get(), to->wave_frame_.get(), t);
else
linearTimeInterpolate(from->wave_frame_.get(), to->wave_frame_.get(), t);
}

void WaveSourceKeyframe::smoothInterpolate(const WavetableKeyframe* prev_keyframe,
const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
const WavetableKeyframe* next_keyframe, float t) {
const vital::WaveFrame* prev = dynamic_cast<const WaveSourceKeyframe*>(prev_keyframe)->wave_frame_.get();
const vital::WaveFrame* from = dynamic_cast<const WaveSourceKeyframe*>(from_keyframe)->wave_frame_.get();
const vital::WaveFrame* to = dynamic_cast<const WaveSourceKeyframe*>(to_keyframe)->wave_frame_.get();
const vital::WaveFrame* next = dynamic_cast<const WaveSourceKeyframe*>(next_keyframe)->wave_frame_.get();

float range_prev = from_keyframe->position() - prev_keyframe->position();
float range = to_keyframe->position() - from_keyframe->position();
float range_next = next_keyframe->position() - to_keyframe->position();

if (interpolation_mode_ == WaveSource::kFrequency)
cubicFrequencyInterpolate(prev, from, to, next, range_prev, range, range_next, t);
else
cubicTimeInterpolate(prev, from, to, next, range_prev, range, range_next, t);
}

json WaveSourceKeyframe::stateToJson() {
String encoded = Base64::toBase64(wave_frame_->time_domain, sizeof(float) * vital::WaveFrame::kWaveformSize);
json data = WavetableKeyframe::stateToJson();
data["wave_data"] = encoded.toStdString();
return data;
}

void WaveSourceKeyframe::jsonToState(json data) {
WavetableKeyframe::jsonToState(data);

MemoryOutputStream decoded(sizeof(float) * vital::WaveFrame::kWaveformSize);
std::string wave_data = data["wave_data"];
Base64::convertFromBase64(decoded, wave_data);
memcpy(wave_frame_->time_domain, decoded.getData(), sizeof(float) * vital::WaveFrame::kWaveformSize);
wave_frame_->toFrequencyDomain();
}

+ 97
- 0
ports/vitalium/source/common/wavetable/wave_source.h View File

@@ -0,0 +1,97 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wavetable_component.h"
#include "wave_frame.h"

class WaveSourceKeyframe;

class WaveSource : public WavetableComponent {
public:
enum InterpolationMode {
kTime,
kFrequency
};

WaveSource();
virtual ~WaveSource();

virtual WavetableKeyframe* createKeyframe(int position) override;
virtual void render(vital::WaveFrame* wave_frame, float position) override;
virtual WavetableComponentFactory::ComponentType getType() override;
virtual json stateToJson() override;
virtual void jsonToState(json data) override;

vital::WaveFrame* getWaveFrame(int index);
WaveSourceKeyframe* getKeyframe(int index);

void setInterpolationMode(InterpolationMode mode) { interpolation_mode_ = mode; }
InterpolationMode getInterpolationMode() const { return interpolation_mode_; }

protected:
std::unique_ptr<WaveSourceKeyframe> compute_frame_;
InterpolationMode interpolation_mode_;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveSource)
};

class WaveSourceKeyframe : public WavetableKeyframe {
public:
WaveSourceKeyframe() : interpolation_mode_(WaveSource::kFrequency) {
wave_frame_ = std::make_unique<vital::WaveFrame>();
}
virtual ~WaveSourceKeyframe() { }

vital::WaveFrame* wave_frame() { return wave_frame_.get(); }

void copy(const WavetableKeyframe* keyframe) override;
void linearTimeInterpolate(const vital::WaveFrame* from, const vital::WaveFrame* to, float t);

void cubicTimeInterpolate(const vital::WaveFrame* prev, const vital::WaveFrame* from,
const vital::WaveFrame* to, const vital::WaveFrame* next,
float range_prev, float range, float range_next, float t);
void linearFrequencyInterpolate(const vital::WaveFrame* from, const vital::WaveFrame* to, float t);

void cubicFrequencyInterpolate(const vital::WaveFrame* prev, const vital::WaveFrame* from,
const vital::WaveFrame* to, const vital::WaveFrame* next,
float range_prev, float range, float range_next, float t);
void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) override;
void smoothInterpolate(const WavetableKeyframe* prev_keyframe,
const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
const WavetableKeyframe* next_keyframe, float t) override;
void render(vital::WaveFrame* wave_frame) override {
wave_frame->copy(wave_frame_.get());
}

json stateToJson() override;
void jsonToState(json data) override;

void setInterpolationMode(WaveSource::InterpolationMode mode) { interpolation_mode_ = mode; }
WaveSource::InterpolationMode getInterpolationMode() const { return interpolation_mode_; }

protected:
std::unique_ptr<vital::WaveFrame> wave_frame_;
WaveSource::InterpolationMode interpolation_mode_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveSourceKeyframe)
};

+ 136
- 0
ports/vitalium/source/common/wavetable/wave_warp_modifier.cpp View File

@@ -0,0 +1,136 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wave_warp_modifier.h"

#include "futils.h"
#include "wave_frame.h"
#include "utils.h"

namespace {
inline double highResPowerScale(float value, float power) {
static constexpr float kMinPower = 0.01f;
if (fabsf(power) < kMinPower)
return value;

double abs_value = fabsf(value);

double numerator = exp(power * abs_value) - 1.0f;
double denominator = exp(power) - 1.0f;
if (value >= 0.0f)
return numerator / denominator;
return -numerator / denominator;
}
}

WaveWarpModifier::WaveWarpModifierKeyframe::WaveWarpModifierKeyframe() {
horizontal_power_ = 0.0f;
vertical_power_ = 0.0f;
horizontal_asymmetric_ = false;
vertical_asymmetric_ = false;
}

void WaveWarpModifier::WaveWarpModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
const WaveWarpModifierKeyframe* source = dynamic_cast<const WaveWarpModifierKeyframe*>(keyframe);
horizontal_power_ = source->horizontal_power_;
vertical_power_ = source->vertical_power_;
}

void WaveWarpModifier::WaveWarpModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
float t) {
const WaveWarpModifierKeyframe* from = dynamic_cast<const WaveWarpModifierKeyframe*>(from_keyframe);
const WaveWarpModifierKeyframe* to = dynamic_cast<const WaveWarpModifierKeyframe*>(to_keyframe);

horizontal_power_ = linearTween(from->horizontal_power_, to->horizontal_power_, t);
vertical_power_ = linearTween(from->vertical_power_, to->vertical_power_, t);
}

void WaveWarpModifier::WaveWarpModifierKeyframe::render(vital::WaveFrame* wave_frame) {
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i)
wave_frame->frequency_domain[i] = wave_frame->time_domain[i];

for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
float horizontal = i / (vital::WaveFrame::kWaveformSize - 1.0f);
float warped_horizontal = 0.0f;
if (horizontal_asymmetric_)
warped_horizontal = highResPowerScale(horizontal, horizontal_power_);
else
warped_horizontal = 0.5f * highResPowerScale(2.0f * horizontal - 1.0f, horizontal_power_) + 0.5f;

float float_index = (vital::WaveFrame::kWaveformSize - 1) * warped_horizontal;
int index = float_index;
index = vital::utils::iclamp(index, 0, vital::WaveFrame::kWaveformSize - 2);

float vertical_from = wave_frame->frequency_domain[index].real();
float vertical_to = wave_frame->frequency_domain[index + 1].real();
float vertical = linearTween(vertical_from, vertical_to, float_index - index);
vertical = vital::utils::clamp(vertical, -1.0f, 1.0f);
if (vertical_asymmetric_)
wave_frame->time_domain[i] = 2.0f * highResPowerScale(0.5f * vertical + 0.5f, vertical_power_) - 1.0f;
else
wave_frame->time_domain[i] = highResPowerScale(vertical, vertical_power_);
}
wave_frame->toFrequencyDomain();
}

json WaveWarpModifier::WaveWarpModifierKeyframe::stateToJson() {
json data = WavetableKeyframe::stateToJson();
data["horizontal_power"] = horizontal_power_;
data["vertical_power"] = vertical_power_;
return data;
}

void WaveWarpModifier::WaveWarpModifierKeyframe::jsonToState(json data) {
WavetableKeyframe::jsonToState(data);
horizontal_power_ = data["horizontal_power"];
vertical_power_ = data["vertical_power"];
}

WavetableKeyframe* WaveWarpModifier::createKeyframe(int position) {
WaveWarpModifierKeyframe* keyframe = new WaveWarpModifierKeyframe();
interpolate(keyframe, position);
return keyframe;
}

void WaveWarpModifier::render(vital::WaveFrame* wave_frame, float position) {
interpolate(&compute_frame_, position);
compute_frame_.setHorizontalAsymmetric(horizontal_asymmetric_);
compute_frame_.setVerticalAsymmetric(vertical_asymmetric_);
compute_frame_.render(wave_frame);
}

WavetableComponentFactory::ComponentType WaveWarpModifier::getType() {
return WavetableComponentFactory::kWaveWarp;
}

json WaveWarpModifier::stateToJson() {
json data = WavetableComponent::stateToJson();
data["horizontal_asymmetric"] = horizontal_asymmetric_;
data["vertical_asymmetric"] = vertical_asymmetric_;
return data;
}

void WaveWarpModifier::jsonToState(json data) {
WavetableComponent::jsonToState(data);
horizontal_asymmetric_ = data["horizontal_asymmetric"];
vertical_asymmetric_ = data["vertical_asymmetric"];
}

WaveWarpModifier::WaveWarpModifierKeyframe* WaveWarpModifier::getKeyframe(int index) {
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
return dynamic_cast<WaveWarpModifier::WaveWarpModifierKeyframe*>(wavetable_keyframe);
}

+ 79
- 0
ports/vitalium/source/common/wavetable/wave_warp_modifier.h View File

@@ -0,0 +1,79 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wavetable_component.h"

class WaveWarpModifier : public WavetableComponent {
public:
class WaveWarpModifierKeyframe : public WavetableKeyframe {
public:
WaveWarpModifierKeyframe();
virtual ~WaveWarpModifierKeyframe() { }

void copy(const WavetableKeyframe* keyframe) override;
void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) override;
void render(vital::WaveFrame* wave_frame) override;
json stateToJson() override;
void jsonToState(json data) override;

float getHorizontalPower() { return horizontal_power_; }
float getVerticalPower() { return vertical_power_; }

void setHorizontalPower(float horizontal_power) { horizontal_power_ = horizontal_power; }
void setVerticalPower(float vertical_power) { vertical_power_ = vertical_power; }

void setHorizontalAsymmetric(bool horizontal_asymmetric) { horizontal_asymmetric_ = horizontal_asymmetric; }
void setVerticalAsymmetric(bool vertical_asymmetric) { vertical_asymmetric_ = vertical_asymmetric; }

protected:
float horizontal_power_;
float vertical_power_;

bool horizontal_asymmetric_;
bool vertical_asymmetric_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveWarpModifierKeyframe)
};

WaveWarpModifier() : horizontal_asymmetric_(false), vertical_asymmetric_(false) { }
virtual ~WaveWarpModifier() = default;

virtual WavetableKeyframe* createKeyframe(int position) override;
virtual void render(vital::WaveFrame* wave_frame, float position) override;
virtual WavetableComponentFactory::ComponentType getType() override;
virtual json stateToJson() override;
virtual void jsonToState(json data) override;

void setHorizontalAsymmetric(bool horizontal_asymmetric) { horizontal_asymmetric_ = horizontal_asymmetric; }
void setVerticalAsymmetric(bool vertical_asymmetric) { vertical_asymmetric_ = vertical_asymmetric; }

bool getHorizontalAsymmetric() const { return horizontal_asymmetric_; }
bool getVerticalAsymmetric() const { return vertical_asymmetric_; }

WaveWarpModifierKeyframe* getKeyframe(int index);

protected:
WaveWarpModifierKeyframe compute_frame_;
bool horizontal_asymmetric_;
bool vertical_asymmetric_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveWarpModifier)
};


+ 120
- 0
ports/vitalium/source/common/wavetable/wave_window_modifier.cpp View File

@@ -0,0 +1,120 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wave_frame.h"

#include "wave_window_modifier.h"

float WaveWindowModifier::applyWindow(WindowShape window_shape, float t) {
if (window_shape == kCos)
return 0.5f - 0.5f * cosf(vital::kPi * t);
if (window_shape == kHalfSin)
return sinf(vital::kPi * t / 2.0f);
if (window_shape == kSquare)
return t < 1.0f ? 0.0f : 1.0f;
if (window_shape == kWiggle)
return t * cosf(vital::kPi * (t * 1.5f + 0.5f));
return t;
}

WaveWindowModifier::WaveWindowModifierKeyframe::WaveWindowModifierKeyframe() {
static constexpr float kDefaultOffset = 0.25f;
left_position_ = kDefaultOffset;
right_position_ = 1.0f - kDefaultOffset;
window_shape_ = kCos;
}

void WaveWindowModifier::WaveWindowModifierKeyframe::copy(const WavetableKeyframe* keyframe) {
const WaveWindowModifierKeyframe* source = dynamic_cast<const WaveWindowModifierKeyframe*>(keyframe);

left_position_ = source->left_position_;
right_position_ = source->right_position_;
}

void WaveWindowModifier::WaveWindowModifierKeyframe::interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
float t) {
const WaveWindowModifierKeyframe* from = dynamic_cast<const WaveWindowModifierKeyframe*>(from_keyframe);
const WaveWindowModifierKeyframe* to = dynamic_cast<const WaveWindowModifierKeyframe*>(to_keyframe);

left_position_ = linearTween(from->left_position_, to->left_position_, t);
right_position_ = linearTween(from->right_position_, to->right_position_, t);
}

void WaveWindowModifier::WaveWindowModifierKeyframe::render(vital::WaveFrame* wave_frame) {
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
float t = i / (vital::WaveFrame::kWaveformSize - 1.0f);
if (t >= left_position_)
break;

wave_frame->time_domain[i] *= applyWindow(t / left_position_);
}

for (int i = vital::WaveFrame::kWaveformSize; i >= 0; --i) {
float t = i / (vital::WaveFrame::kWaveformSize - 1.0f);
if (t <= right_position_)
break;

wave_frame->time_domain[i] *= applyWindow((1.0f - t) / (1.0f - right_position_));
}

wave_frame->toFrequencyDomain();
}

json WaveWindowModifier::WaveWindowModifierKeyframe::stateToJson() {
json data = WavetableKeyframe::stateToJson();
data["left_position"] = left_position_;
data["right_position"] = right_position_;
return data;
}

void WaveWindowModifier::WaveWindowModifierKeyframe::jsonToState(json data) {
WavetableKeyframe::jsonToState(data);
left_position_ = data["left_position"];
right_position_ = data["right_position"];
}

WavetableKeyframe* WaveWindowModifier::createKeyframe(int position) {
WaveWindowModifierKeyframe* keyframe = new WaveWindowModifierKeyframe();
interpolate(keyframe, position);
return keyframe;
}

void WaveWindowModifier::render(vital::WaveFrame* wave_frame, float position) {
interpolate(&compute_frame_, position);
compute_frame_.setWindowShape(window_shape_);
compute_frame_.render(wave_frame);
}

WavetableComponentFactory::ComponentType WaveWindowModifier::getType() {
return WavetableComponentFactory::kWaveWindow;
}

json WaveWindowModifier::stateToJson() {
json data = WavetableComponent::stateToJson();
data["window_shape"] = window_shape_;
return data;
}

void WaveWindowModifier::jsonToState(json data) {
WavetableComponent::jsonToState(data);
window_shape_ = data["window_shape"];
}

WaveWindowModifier::WaveWindowModifierKeyframe* WaveWindowModifier::getKeyframe(int index) {
WavetableKeyframe* wavetable_keyframe = keyframes_[index].get();
return dynamic_cast<WaveWindowModifier::WaveWindowModifierKeyframe*>(wavetable_keyframe);
}

+ 84
- 0
ports/vitalium/source/common/wavetable/wave_window_modifier.h View File

@@ -0,0 +1,84 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wavetable_component.h"

class WaveWindowModifier : public WavetableComponent {
public:
enum WindowShape {
kCos,
kHalfSin,
kLinear,
kSquare,
kWiggle,
kNumWindowShapes
};

static float applyWindow(WindowShape window_shape, float t);

class WaveWindowModifierKeyframe : public WavetableKeyframe {
public:
WaveWindowModifierKeyframe();
virtual ~WaveWindowModifierKeyframe() { }

void copy(const WavetableKeyframe* keyframe) override;
void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) override;
void render(vital::WaveFrame* wave_frame) override;
json stateToJson() override;
void jsonToState(json data) override;

void setLeft(float left) { left_position_ = left; }
void setRight(float right) { right_position_ = right; }
float getLeft() { return left_position_; }
float getRight() { return right_position_; }

void setWindowShape(WindowShape window_shape) { window_shape_ = window_shape; }

protected:
inline float applyWindow(float t) { return WaveWindowModifier::applyWindow(window_shape_, t); }

float left_position_;
float right_position_;
WindowShape window_shape_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveWindowModifierKeyframe)
};

WaveWindowModifier() : window_shape_(kCos) { }
virtual ~WaveWindowModifier() { }

virtual WavetableKeyframe* createKeyframe(int position) override;
virtual void render(vital::WaveFrame* wave_frame, float position) override;
virtual WavetableComponentFactory::ComponentType getType() override;
virtual json stateToJson() override;
virtual void jsonToState(json data) override;

WaveWindowModifierKeyframe* getKeyframe(int index);

void setWindowShape(WindowShape window_shape) { window_shape_ = window_shape; }
WindowShape getWindowShape() { return window_shape_; }

protected:
WaveWindowModifierKeyframe compute_frame_;
WindowShape window_shape_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveWindowModifier)
};


+ 134
- 0
ports/vitalium/source/common/wavetable/wavetable_component.cpp View File

@@ -0,0 +1,134 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wavetable_component.h"

WavetableKeyframe* WavetableComponent::insertNewKeyframe(int position) {
VITAL_ASSERT(position >= 0 && position < vital::kNumOscillatorWaveFrames);

WavetableKeyframe* keyframe = createKeyframe(position);
keyframe->setOwner(this);
keyframe->setPosition(position);

int index = getIndexFromPosition(position);
keyframes_.insert(keyframes_.begin() + index, std::unique_ptr<WavetableKeyframe>(keyframe));
return keyframe;
}

void WavetableComponent::reposition(WavetableKeyframe* keyframe) {
int start_index = indexOf(keyframe);
keyframes_[start_index].release();
keyframes_.erase(keyframes_.begin() + start_index);

int new_index = getIndexFromPosition(keyframe->position());
keyframes_.insert(keyframes_.begin() + new_index, std::unique_ptr<WavetableKeyframe>(keyframe));
}

void WavetableComponent::remove(WavetableKeyframe* keyframe) {
int start_index = indexOf(keyframe);
keyframes_.erase(keyframes_.begin() + start_index);
}

void WavetableComponent::jsonToState(json data) {
keyframes_.clear();
for (json json_keyframe : data["keyframes"]) {
WavetableKeyframe* keyframe = insertNewKeyframe(json_keyframe["position"]);
keyframe->jsonToState(json_keyframe);
}

if (data.count("interpolation_style"))
interpolation_style_ = data["interpolation_style"];
}

json WavetableComponent::stateToJson() {
json keyframes_data;
for (int i = 0; i < keyframes_.size(); ++i)
keyframes_data.emplace_back(keyframes_[i]->stateToJson());

return {
{ "keyframes", keyframes_data },
{ "type", WavetableComponentFactory::getComponentName(getType()) },
{ "interpolation_style", interpolation_style_ }
};
}

void WavetableComponent::reset() {
keyframes_.clear();
insertNewKeyframe(0);
}

void WavetableComponent::interpolate(WavetableKeyframe* dest, float position) {
if (numFrames() == 0)
return;

int index = getIndexFromPosition(position) - 1;
int clamped_index = std::min(std::max(index, 0), numFrames() - 1);
WavetableKeyframe* from_frame = keyframes_[clamped_index].get();

if (index < 0 || index >= numFrames() - 1 || interpolation_style_ == kNone)
dest->copy(from_frame);
else if (interpolation_style_ == kLinear) {
WavetableKeyframe* to_frame = keyframes_[index + 1].get();
int from_position = keyframes_[index]->position();
int to_position = keyframes_[index + 1]->position();
float t = (1.0f * position - from_position) / (to_position - from_position);
dest->interpolate(from_frame, to_frame, t);
}
else if (interpolation_style_ == kCubic) {
int next_index = index + 2;
int prev_index = index - 1;
if (next_index >= numFrames())
next_index = index;
if (prev_index < 0)
prev_index = index + 1;
WavetableKeyframe* to_frame = keyframes_[index + 1].get();
WavetableKeyframe* next_frame = keyframes_[next_index].get();
WavetableKeyframe* prev_frame = keyframes_[prev_index].get();
int from_position = keyframes_[index]->position();
int to_position = keyframes_[index + 1]->position();
float t = (1.0f * position - from_position) / (to_position - from_position);
dest->smoothInterpolate(prev_frame, from_frame, to_frame, next_frame, t);
}
}

int WavetableComponent::getIndexFromPosition(int position) const {
int index = 0;
for (auto& keyframe : keyframes_) {
if (position < keyframe->position())
break;
index++;
}

return index;
}

WavetableKeyframe* WavetableComponent::getFrameAtPosition(int position) {
int index = getIndexFromPosition(position);
if (index < 0 || index >= keyframes_.size())
return nullptr;

return keyframes_[index].get();
}

int WavetableComponent::getLastKeyframePosition() {
if (keyframes_.size() == 0)
return 0;
if (!hasKeyframes())
return vital::kNumOscillatorWaveFrames - 1;
return keyframes_[keyframes_.size() - 1]->position();
}

+ 78
- 0
ports/vitalium/source/common/wavetable/wavetable_component.h View File

@@ -0,0 +1,78 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wavetable_keyframe.h"
#include "wavetable_component_factory.h"
#include "json/json.h"

using json = nlohmann::json;

namespace vital {
class WaveFrame;
} // namespace vital

class WavetableComponent {
public:
enum InterpolationStyle {
kNone,
kLinear,
kCubic,
kNumInterpolationStyles
};

WavetableComponent() : interpolation_style_(kLinear) { }
virtual ~WavetableComponent() { }

virtual WavetableKeyframe* createKeyframe(int position) = 0;
virtual void render(vital::WaveFrame* wave_frame, float position) = 0;
virtual WavetableComponentFactory::ComponentType getType() = 0;
virtual json stateToJson();
virtual void jsonToState(json data);
virtual void prerender() { }
virtual bool hasKeyframes() { return true; }

void reset();
void interpolate(WavetableKeyframe* dest, float position);
WavetableKeyframe* insertNewKeyframe(int position);
void reposition(WavetableKeyframe* keyframe);
void remove(WavetableKeyframe* keyframe);

inline int numFrames() const { return static_cast<int>(keyframes_.size()); }
inline int indexOf(WavetableKeyframe* keyframe) const {
for (int i = 0; i < keyframes_.size(); ++i) {
if (keyframes_[i].get() == keyframe)
return i;
}
return -1;
}
inline WavetableKeyframe* getFrameAt(int index) const { return keyframes_[index].get(); }
int getIndexFromPosition(int position) const;
WavetableKeyframe* getFrameAtPosition(int position);
int getLastKeyframePosition();

void setInterpolationStyle(InterpolationStyle type) { interpolation_style_ = type; }
InterpolationStyle getInterpolationStyle() const { return interpolation_style_; }
protected:
std::vector<std::unique_ptr<WavetableKeyframe>> keyframes_;
InterpolationStyle interpolation_style_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableComponent)
};


+ 109
- 0
ports/vitalium/source/common/wavetable/wavetable_component_factory.cpp View File

@@ -0,0 +1,109 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wavetable_component_factory.h"
#include "file_source.h"
#include "frequency_filter_modifier.h"
#include "phase_modifier.h"
#include "shepard_tone_source.h"
#include "slew_limit_modifier.h"
#include "wave_frame.h"
#include "wave_fold_modifier.h"
#include "wave_line_source.h"
#include "wave_source.h"
#include "wave_warp_modifier.h"
#include "wave_window_modifier.h"

WavetableComponent* WavetableComponentFactory::createComponent(ComponentType type) {
switch (type) {
case kWaveSource:
return new WaveSource();
case kLineSource:
return new WaveLineSource();
case kFileSource:
return new FileSource();
case kShepardToneSource:
return new ShepardToneSource();
case kPhaseModifier:
return new PhaseModifier();
case kWaveWindow:
return new WaveWindowModifier();
case kFrequencyFilter:
return new FrequencyFilterModifier();
case kSlewLimiter:
return new SlewLimitModifier();
case kWaveFolder:
return new WaveFoldModifier();
case kWaveWarp:
return new WaveWarpModifier();
default:
VITAL_ASSERT(false);
return nullptr;
}
}

WavetableComponent* WavetableComponentFactory::createComponent(const std::string& type) {
if (type == "Wave Source")
return new WaveSource();
if (type == "Line Source")
return new WaveLineSource();
if (type == "Audio File Source")
return new FileSource();
if (type == "Shepard Tone Source")
return new ShepardToneSource();
if (type == "Phase Shift")
return new PhaseModifier();
if (type == "Wave Window")
return new WaveWindowModifier();
if (type == "Frequency Filter")
return new FrequencyFilterModifier();
if (type == "Slew Limiter")
return new SlewLimitModifier();
if (type == "Wave Folder")
return new WaveFoldModifier();
if (type == "Wave Warp")
return new WaveWarpModifier();

VITAL_ASSERT(false);
return nullptr;
}

std::string WavetableComponentFactory::getComponentName(ComponentType type) {
switch (type) {
case kWaveSource:
return "Wave Source";
case kLineSource:
return "Line Source";
case kFileSource:
return "Audio File Source";
case kShepardToneSource:
return "Shepard Tone Source";
case kPhaseModifier:
return "Phase Shift";
case kWaveWindow:
return "Wave Window";
case kFrequencyFilter:
return "Frequency Filter";
case kSlewLimiter:
return "Slew Limiter";
case kWaveFolder:
return "Wave Folder";
case kWaveWarp:
return "Wave Warp";
default:
return "";
}
}

+ 57
- 0
ports/vitalium/source/common/wavetable/wavetable_component_factory.h View File

@@ -0,0 +1,57 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

class WavetableComponent;

class WavetableComponentFactory {
public:
enum ComponentType {
kWaveSource,
kLineSource,
kFileSource,
kNumSourceTypes,
kShepardToneSource = kNumSourceTypes, // Deprecated

kBeginModifierTypes = kNumSourceTypes + 1,
kPhaseModifier = kBeginModifierTypes,
kWaveWindow,
kFrequencyFilter,
kSlewLimiter,
kWaveFolder,
kWaveWarp,
kNumComponentTypes
};

static int numComponentTypes() { return kNumComponentTypes; }
static int numSourceTypes() { return kNumSourceTypes; }
static int numModifierTypes() { return kNumComponentTypes - kBeginModifierTypes; }

static WavetableComponent* createComponent(ComponentType type);
static WavetableComponent* createComponent(const std::string& type);
static std::string getComponentName(ComponentType type);
static ComponentType getSourceType(int type) { return static_cast<ComponentType>(type); }
static ComponentType getModifierType(int type) {
return (ComponentType)(type + kBeginModifierTypes);
}

private:
WavetableComponentFactory() { }
};


+ 516
- 0
ports/vitalium/source/common/wavetable/wavetable_creator.cpp View File

@@ -0,0 +1,516 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wavetable_creator.h"
#include "line_generator.h"
#include "load_save.h"
#include "synth_constants.h"
#include "wave_frame.h"
#include "wave_line_source.h"
#include "wave_source.h"
#include "wavetable.h"

namespace {
int getFirstNonZeroSample(const float* audio_buffer, int num_samples) {
for (int i = 0; i < num_samples; ++i) {
if (audio_buffer[i])
return i;
}
return 0;
}
}

int WavetableCreator::getGroupIndex(WavetableGroup* group) {
for (int i = 0; i < groups_.size(); ++i) {
if (groups_[i].get() == group)
return i;
}
return -1;
}

void WavetableCreator::moveUp(int index) {
if (index <= 0)
return;
groups_[index].swap(groups_[index - 1]);
}

void WavetableCreator::moveDown(int index) {
if (index < 0 || index >= groups_.size() - 1)
return;

groups_[index].swap(groups_[index + 1]);
}

void WavetableCreator::removeGroup(int index) {
if (index < 0 || index >= groups_.size())
return;

std::unique_ptr<WavetableGroup> group = std::move(groups_[index]);
groups_.erase(groups_.begin() + index);
}

float WavetableCreator::render(int position) {
compute_frame_combine_.clear();
compute_frame_combine_.index = position;
compute_frame_.index = position;

for (auto& group : groups_) {
group->render(&compute_frame_, position);
compute_frame_combine_.addFrom(&compute_frame_);
}

if (groups_.size() > 1)
compute_frame_combine_.multiply(1.0f / groups_.size());

if (remove_all_dc_)
compute_frame_combine_.removedDc();

float max_value = 0.0f;
float min_value = 0.0f;
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
max_value = std::max(compute_frame_combine_.time_domain[i], max_value);
min_value = std::min(compute_frame_combine_.time_domain[i], min_value);
}

wavetable_->loadWaveFrame(&compute_frame_combine_);
return max_value - min_value;
}

void WavetableCreator::render() {
int last_waveframe = 0;
bool shepard = groups_.size() > 0;
for (auto& group : groups_) {
group->prerender();
last_waveframe = std::max(last_waveframe, group->getLastKeyframePosition());
shepard = shepard && group->isShepardTone();
}
wavetable_->setNumFrames(last_waveframe + 1);
wavetable_->setShepardTable(shepard);
float max_span = 0.0f;
for (int i = 0; i < last_waveframe + 1; ++i)
max_span = std::max(render(i), max_span);
wavetable_->setFrequencyRatio(compute_frame_.frequency_ratio);
wavetable_->setSampleRate(compute_frame_.sample_rate);

postRender(max_span);
}

void WavetableCreator::postRender(float max_span) {
if (full_normalize_)
wavetable_->postProcess(max_span);
else
wavetable_->postProcess(0.0f);
}

void WavetableCreator::renderToBuffer(float* buffer, int num_frames, int frame_size) {
int total_samples = num_frames * frame_size;
for (int i = 0; i < num_frames; ++i) {
float position = (1.0f * i * vital::kNumOscillatorWaveFrames) / num_frames;
compute_frame_combine_.clear();
compute_frame_combine_.index = position;
compute_frame_.index = position;

for (auto& group : groups_) {
group->render(&compute_frame_, position);
compute_frame_combine_.addFrom(&compute_frame_);
}

float* output_buffer = buffer + (i * frame_size);

if (frame_size != vital::WaveFrame::kWaveformSize)
VITAL_ASSERT(false); // TODO: support different waveframe size.
else {
for (int s = 0; s < vital::WaveFrame::kWaveformSize; ++s)
output_buffer[s] = compute_frame_combine_.time_domain[s];
}
}

float max_value = 1.0f;
for (int i = 0; i < total_samples; ++i)
max_value = std::max(max_value, fabsf(buffer[i]));

float scale = 1.0f / max_value;
for (int i = 0; i < total_samples; ++i)
buffer[i] *= scale;
}

void WavetableCreator::init() {
clear();
loadDefaultCreator();
render();
}

void WavetableCreator::clear() {
groups_.clear();
remove_all_dc_ = true;
full_normalize_ = true;
}

void WavetableCreator::loadDefaultCreator() {
wavetable_->setName("Init");
WavetableGroup* new_group = new WavetableGroup();
new_group->loadDefaultGroup();
addGroup(new_group);
}

void WavetableCreator::initPredefinedWaves() {
clear();

WavetableGroup* new_group = new WavetableGroup();
WaveSource* wave_source = new WaveSource();

int num_shapes = vital::PredefinedWaveFrames::kNumShapes;
for (int i = 0; i < num_shapes; ++i) {
int position = (vital::kNumOscillatorWaveFrames * i) / num_shapes;
wave_source->insertNewKeyframe(position);
WaveSourceKeyframe* keyframe = wave_source->getKeyframe(i);

vital::PredefinedWaveFrames::Shape shape = static_cast<vital::PredefinedWaveFrames::Shape>(i);
keyframe->wave_frame()->copy(vital::PredefinedWaveFrames::getWaveFrame(shape));
}
wave_source->setInterpolationStyle(WaveSource::kNone);
full_normalize_ = false;
remove_all_dc_ = false;

new_group->addComponent(wave_source);
addGroup(new_group);
render();
}

void WavetableCreator::initFromAudioFile(const float* audio_buffer, int num_samples, int sample_rate,
AudioFileLoadStyle load_style, FileSource::FadeStyle fade_style) {
int beginning_sample = getFirstNonZeroSample(audio_buffer, num_samples);
int shortened_num_samples = num_samples - beginning_sample;
if (load_style == kVocoded)
initFromVocodedAudioFile(audio_buffer + beginning_sample, shortened_num_samples, sample_rate, false);
else if (load_style == kTtwt)
initFromVocodedAudioFile(audio_buffer + beginning_sample, shortened_num_samples, sample_rate, true);
else if (load_style == kPitched)
initFromPitchedAudioFile(audio_buffer + beginning_sample, shortened_num_samples, sample_rate);
else
initFromSplicedAudioFile(audio_buffer, num_samples, sample_rate, fade_style);
}

void WavetableCreator::initFromSplicedAudioFile(const float* audio_buffer, int num_samples, int sample_rate,
FileSource::FadeStyle fade_style) {
clear();

WavetableGroup* new_group = new WavetableGroup();
FileSource* file_source = new FileSource();

file_source->loadBuffer(audio_buffer, num_samples, sample_rate);
file_source->setFadeStyle(fade_style);
file_source->setPhaseStyle(FileSource::PhaseStyle::kNone);
file_source->insertNewKeyframe(0);
file_source->detectWaveEditTable();

double window_size = file_source->getWindowSize();
if (fade_style == FileSource::kNoInterpolate) {
int num_cycles = std::max<int>(1, num_samples / window_size);
int buffer_frames = vital::kNumOscillatorWaveFrames / num_cycles;
file_source->insertNewKeyframe(std::max(0, vital::kNumOscillatorWaveFrames - 1 - buffer_frames));
}
else
file_source->insertNewKeyframe(vital::kNumOscillatorWaveFrames - 1);

file_source->getKeyframe(0)->setStartPosition(0);
int last_sample_position = num_samples - window_size;
last_sample_position = std::min<int>(file_source->getKeyframe(1)->position() * window_size, last_sample_position);
file_source->getKeyframe(1)->setStartPosition(std::max(0, last_sample_position));

new_group->addComponent(file_source);
addGroup(new_group);
render();
}

void WavetableCreator::initFromVocodedAudioFile(const float* audio_buffer, int num_samples,
int sample_rate, bool ttwt) {
static constexpr float kMaxTTWTPeriod = .02f;
clear();

WavetableGroup* new_group = new WavetableGroup();
FileSource* file_source = new FileSource();

file_source->loadBuffer(audio_buffer, num_samples, sample_rate);
if (ttwt)
file_source->detectPitch(kMaxTTWTPeriod * sample_rate);
else
file_source->detectPitch();

file_source->setFadeStyle(FileSource::FadeStyle::kWaveBlend);
file_source->setPhaseStyle(FileSource::PhaseStyle::kVocode);
file_source->insertNewKeyframe(0);
file_source->insertNewKeyframe(vital::kNumOscillatorWaveFrames - 1);
file_source->getKeyframe(0)->setStartPosition(0);
int samples_needed = file_source->getKeyframe(1)->getSamplesNeeded();
file_source->getKeyframe(1)->setStartPosition(num_samples - samples_needed);

new_group->addComponent(file_source);
addGroup(new_group);
render();
}

void WavetableCreator::initFromPitchedAudioFile(const float* audio_buffer, int num_samples, int sample_rate) {
clear();

WavetableGroup* new_group = new WavetableGroup();
FileSource* file_source = new FileSource();

file_source->loadBuffer(audio_buffer, num_samples, sample_rate);
file_source->detectPitch();
file_source->setFadeStyle(FileSource::FadeStyle::kWaveBlend);
file_source->insertNewKeyframe(0);
file_source->insertNewKeyframe(vital::kNumOscillatorWaveFrames - 1);
file_source->getKeyframe(0)->setStartPosition(0);
int samples_needed = file_source->getKeyframe(1)->getSamplesNeeded();
file_source->getKeyframe(1)->setStartPosition(num_samples - samples_needed);

new_group->addComponent(file_source);
addGroup(new_group);
render();
}

void WavetableCreator::initFromLineGenerator(LineGenerator* line_generator) {
clear();

wavetable_->setName(line_generator->getName());
WavetableGroup* new_group = new WavetableGroup();

WaveLineSource* line_source = new WaveLineSource();
line_source->insertNewKeyframe(0);
WaveLineSource::WaveLineSourceKeyframe* keyframe = line_source->getKeyframe(0);
keyframe->getLineGenerator()->jsonToState(line_generator->stateToJson());

new_group->addComponent(line_source);
addGroup(new_group);
render();
}

bool WavetableCreator::isValidJson(json data) {
if (LineGenerator::isValidJson(data))
return true;

if (!data.count("version") || !data.count("groups") || !data.count("name"))
return false;

json groups_data = data["groups"];
return groups_data.is_array();
}

json WavetableCreator::updateJson(json data) {
std::string version = "0.0.0";
if (data.count("version")) {
std::string ver = data["version"];
version = ver;
}

if (LoadSave::compareVersionStrings(version, "0.3.3") < 0) {
const std::string kOldOrder[] = {
"Wave Source", "Line Source", "Audio File Source", "Phase Shift", "Wave Window",
"Frequency Filter", "Slew Limiter", "Wave Folder", "Wave Warp"
};
json json_groups = data["groups"];
json new_groups;
for (json json_group : json_groups) {
json json_components = json_group["components"];
json new_components;
for (json json_component : json_components) {
int int_type = json_component["type"];
json_component["type"] = kOldOrder[int_type];

new_components.push_back(json_component);
}
json_group["components"] = new_components;
new_groups.push_back(json_group);
}
data["groups"] = new_groups;
}

if (LoadSave::compareVersionStrings(version, "0.3.7") < 0) {
json json_groups = data["groups"];
json new_groups;
for (json json_group : json_groups) {
json json_components = json_group["components"];
json new_components;
for (json json_component : json_components) {
std::string type = json_component["type"];
if (type == "Audio File Source")
LoadSave::convertBufferToPcm(json_component, "audio_file");

new_components.push_back(json_component);
}

json_group["components"] = new_components;
new_groups.push_back(json_group);
}

data["groups"] = new_groups;
}

if (LoadSave::compareVersionStrings(version, "0.3.8") < 0)
data["remove_all_dc"] = false;

if (LoadSave::compareVersionStrings(version, "0.3.9") < 0 && LoadSave::compareVersionStrings(version, "0.3.7") >= 0) {
json json_groups = data["groups"];
json new_groups;
for (json json_group : json_groups) {
json json_components = json_group["components"];
json new_components;
for (json json_component : json_components) {
std::string type = json_component["type"];
if (type == "Wave Source" || type == "Shepard Tone Source") {
json old_keyframes = json_component["keyframes"];
json new_keyframes;
for (json json_keyframe : old_keyframes) {
LoadSave::convertPcmToFloatBuffer(json_keyframe, "wave_data");
new_keyframes.push_back(json_keyframe);
}

json_component["keyframes"] = new_keyframes;
}

new_components.push_back(json_component);
}

json_group["components"] = new_components;
new_groups.push_back(json_group);
}

data["groups"] = new_groups;
}

if (LoadSave::compareVersionStrings(version, "0.4.7") < 0)
data["full_normalize"] = false;

if (LoadSave::compareVersionStrings(version, "0.7.7") < 0) {
LineGenerator line_converter;

json json_groups = data["groups"];
json new_groups;
for (json json_group : json_groups) {
json json_components = json_group["components"];
json new_components;
for (json json_component : json_components) {
std::string type = json_component["type"];
if (type == "Line Source") {
json old_keyframes = json_component["keyframes"];
int num_points = json_component["num_points"];
json_component["num_points"] = num_points + 2;
line_converter.setNumPoints(num_points + 2);

json new_keyframes;
for (json json_keyframe : old_keyframes) {
json point_data = json_keyframe["points"];
json power_data = json_keyframe["powers"];
for (int i = 0; i < num_points; ++i) {
float x = point_data[2 * i];
float y = point_data[2 * i + 1];
line_converter.setPoint(i + 1, { x, y * 0.5f + 0.5f });
line_converter.setPower(i + 1, power_data[i]);
}
float start_x = point_data[0];
float start_y = point_data[1];
float end_x = point_data[2 * (num_points - 1)];
float end_y = point_data[2 * (num_points - 1) + 1];

float range_x = start_x - end_x + 1.0f;
float y = end_y;
if (range_x < 0.001f)
y = 0.5f * (start_y + end_y);
else {
float t = (1.0f - end_x) / range_x;
y = vital::utils::interpolate(end_y, start_y, t);
}

line_converter.setPoint(0, { 0.0f, y * 0.5f + 0.5f });
line_converter.setPoint(num_points + 1, { 1.0f, y * 0.5f + 0.5f });
line_converter.setPower(0, 0.0f);
line_converter.setPower(num_points + 1, 0.0f);

json_keyframe["line"] = line_converter.stateToJson();
new_keyframes.push_back(json_keyframe);
}

json_component["keyframes"] = new_keyframes;
}

new_components.push_back(json_component);
}

json_group["components"] = new_components;
new_groups.push_back(json_group);
}

data["groups"] = new_groups;
}

return data;
}

json WavetableCreator::stateToJson() {
json json_groups;
for (auto& group : groups_)
json_groups.push_back(group->stateToJson());

return {
{ "groups", json_groups },
{ "name", wavetable_->getName() },
{ "author", wavetable_->getAuthor() },
{ "version", ProjectInfo::versionString },
{ "remove_all_dc", remove_all_dc_ },
{ "full_normalize", full_normalize_ },
};
}

void WavetableCreator::jsonToState(json data) {
if (LineGenerator::isValidJson(data)) {
LineGenerator generator(vital::WaveFrame::kWaveformSize);
generator.jsonToState(data);
initFromLineGenerator(&generator);
return;
}

clear();
data = updateJson(data);

std::string name = "";
if (data.count("name"))
name = data["name"].get<std::string>();
wavetable_->setName(name);

std::string author = "";
if (data.count("author"))
author = data["author"].get<std::string>();
wavetable_->setAuthor(author);

if (data.count("remove_all_dc"))
remove_all_dc_ = data["remove_all_dc"];
if (data.count("full_normalize"))
full_normalize_ = data["full_normalize"];
else
full_normalize_ = false;

json json_groups = data["groups"];
for (const json& json_group : json_groups) {
WavetableGroup* new_group = new WavetableGroup();
new_group->jsonToState(json_group);
addGroup(new_group);
}

render();
}

+ 96
- 0
ports/vitalium/source/common/wavetable/wavetable_creator.h View File

@@ -0,0 +1,96 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wave_frame.h"
#include "wavetable_group.h"
#include "file_source.h"
#include "json/json.h"
#include "wavetable.h"

using json = nlohmann::json;

class LineGenerator;

class WavetableCreator {
public:
enum AudioFileLoadStyle {
kNone,
kWavetableSplice,
kVocoded,
kTtwt,
kPitched,
kNumDragLoadStyles
};

WavetableCreator(vital::Wavetable* wavetable) : wavetable_(wavetable),
full_normalize_(true), remove_all_dc_(true) { }
int getGroupIndex(WavetableGroup* group);
void addGroup(WavetableGroup* group) { groups_.push_back(std::unique_ptr<WavetableGroup>(group)); }
void removeGroup(int index);
void moveUp(int index);
void moveDown(int index);

int numGroups() const { return static_cast<int>(groups_.size()); }
WavetableGroup* getGroup(int index) const { return groups_[index].get(); }
float render(int position);
void render();
void postRender(float max_span);
void renderToBuffer(float* buffer, int num_frames, int frame_size);
void init();
void clear();
void loadDefaultCreator();

void initPredefinedWaves();
void initFromAudioFile(const float* audio_buffer, int num_samples, int sample_rate,
AudioFileLoadStyle load_style, FileSource::FadeStyle fade_style);

void setName(const std::string& name) { wavetable_->setName(name); }
void setAuthor(const std::string& author) { wavetable_->setAuthor(author); }
void setFileLoaded(const std::string& path) { last_file_loaded_ = path; }
std::string getName() const { return wavetable_->getName(); }
std::string getAuthor() const { return wavetable_->getAuthor(); }
std::string getLastFileLoaded() { return last_file_loaded_; }

static bool isValidJson(json data);
json updateJson(json data);
json stateToJson();
void jsonToState(json data);

vital::Wavetable* getWavetable() { return wavetable_; }

protected:
void initFromSplicedAudioFile(const float* audio_buffer, int num_samples, int sample_rate,
FileSource::FadeStyle fade_style);
void initFromVocodedAudioFile(const float* audio_buffer, int num_samples, int sample_rate, bool ttwt);
void initFromPitchedAudioFile(const float* audio_buffer, int num_samples, int sample_rate);
void initFromLineGenerator(LineGenerator* line_generator);

vital::WaveFrame compute_frame_combine_;
vital::WaveFrame compute_frame_;
std::vector<std::unique_ptr<WavetableGroup>> groups_;

std::string last_file_loaded_;
vital::Wavetable* wavetable_;
bool full_normalize_;
bool remove_all_dc_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableCreator)
};


+ 132
- 0
ports/vitalium/source/common/wavetable/wavetable_group.cpp View File

@@ -0,0 +1,132 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wavetable_group.h"
#include "synth_constants.h"
#include "wave_frame.h"
#include "wave_source.h"
#include "wavetable.h"

int WavetableGroup::getComponentIndex(WavetableComponent* component) {
for (int i = 0; i < components_.size(); ++i) {
if (components_[i].get() == component)
return i;
}
return -1;
}

void WavetableGroup::moveUp(int index) {
if (index <= 0)
return;

components_[index].swap(components_[index - 1]);
}

void WavetableGroup::moveDown(int index) {
if (index < 0 || index >= components_.size() - 1)
return;

components_[index].swap(components_[index + 1]);
}

void WavetableGroup::removeComponent(int index) {
if (index < 0 || index >= components_.size())
return;

std::unique_ptr<WavetableComponent> component = std::move(components_[index]);
components_.erase(components_.begin() + index);
}

void WavetableGroup::reset() {
components_.clear();
loadDefaultGroup();
}

void WavetableGroup::prerender() {
for (auto& component : components_)
component->prerender();
}

bool WavetableGroup::isShepardTone() {
for (auto& component : components_) {
if (component->getType() != WavetableComponentFactory::kShepardToneSource)
return false;
}

return true;
}

void WavetableGroup::render(vital::WaveFrame* wave_frame, float position) const {
wave_frame->index = position;

for (auto& component : components_)
component->render(wave_frame, position);
}

void WavetableGroup::renderTo(vital::Wavetable* wavetable) {
for (int i = 0; i < vital::kNumOscillatorWaveFrames; ++i) {
compute_frame_.index = i;

for (auto& component : components_)
component->render(&compute_frame_, i);

wavetable->loadWaveFrame(&compute_frame_);
}
}

void WavetableGroup::loadDefaultGroup() {
WaveSource* wave_source = new WaveSource();
wave_source->insertNewKeyframe(0);
vital::WaveFrame* wave_frame = wave_source->getWaveFrame(0);
for (int i = 0; i < vital::WaveFrame::kWaveformSize; ++i) {
float t = i / (vital::WaveFrame::kWaveformSize - 1.0f);
int half_shift = (i + vital::WaveFrame::kWaveformSize / 2) % vital::WaveFrame::kWaveformSize;
wave_frame->time_domain[half_shift] = 1.0f - 2.0f * t;
}
wave_frame->toFrequencyDomain();

addComponent(wave_source);
}

int WavetableGroup::getLastKeyframePosition() {
int last_position = 0;
for (auto& component : components_)
last_position = std::max(last_position, component->getLastKeyframePosition());

return last_position;
}

json WavetableGroup::stateToJson() {
json json_components;
for (auto& component : components_) {
json json_component = component->stateToJson();
json_components.push_back(json_component);
}

return { { "components", json_components } };
}

void WavetableGroup::jsonToState(json data) {
components_.clear();

json json_components = data["components"];
for (json json_component : json_components) {
std::string type = json_component["type"];
WavetableComponent* component = WavetableComponentFactory::createComponent(type);
component->jsonToState(json_component);
addComponent(component);
}
}

+ 58
- 0
ports/vitalium/source/common/wavetable/wavetable_group.h View File

@@ -0,0 +1,58 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "wave_frame.h"
#include "wavetable_component.h"

namespace vital {
class Wavetable;
} // namespace vital

class WavetableGroup {
public:
WavetableGroup() { }

int getComponentIndex(WavetableComponent* component);
void addComponent(WavetableComponent* component) {
components_.push_back(std::unique_ptr< WavetableComponent>(component));
}
void removeComponent(int index);
void moveUp(int index);
void moveDown(int index);
void reset();
void prerender();

int numComponents() const { return static_cast<int>(components_.size()); }
WavetableComponent* getComponent(int index) const { return components_[index].get(); }
bool isShepardTone();
void render(vital::WaveFrame* wave_frame, float position) const;
void renderTo(vital::Wavetable* wavetable);
void loadDefaultGroup();
int getLastKeyframePosition();

json stateToJson();
void jsonToState(json data);

protected:
vital::WaveFrame compute_frame_;
std::vector<std::unique_ptr<WavetableComponent>> components_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableGroup)
};


+ 51
- 0
ports/vitalium/source/common/wavetable/wavetable_keyframe.cpp View File

@@ -0,0 +1,51 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wavetable_keyframe.h"

#include "utils.h"
#include "wavetable_component.h"

float WavetableKeyframe::linearTween(float point_from, float point_to, float t) {
return vital::utils::interpolate(point_from, point_to, t);
}

float WavetableKeyframe::cubicTween(float point_prev, float point_from, float point_to, float point_next,
float range_prev, float range, float range_next, float t) {
float slope_from = 0.0f;
float slope_to = 0.0f;
if (range_prev > 0.0f)
slope_from = (point_to - point_prev) / (1.0f + range_prev / range);
if (range_next > 0.0f)
slope_to = (point_next - point_from) / (1.0f + range_next / range);
float delta = point_to - point_from;

float movement = linearTween(point_from, point_to, t);
float smooth = t * (1.0f - t) * ((1.0f - t) * (slope_from - delta) + t * (delta - slope_to));
return movement + smooth;
}

int WavetableKeyframe::index() {
return owner()->indexOf(this);
}

json WavetableKeyframe::stateToJson() {
return { { "position", position_ } };
}

void WavetableKeyframe::jsonToState(json data) {
position_ = data["position"];
}

+ 68
- 0
ports/vitalium/source/common/wavetable/wavetable_keyframe.h View File

@@ -0,0 +1,68 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "json/json.h"
#include "synth_constants.h"

using json = nlohmann::json;

class WavetableComponent;

namespace vital {
class WaveFrame;
} // namespace vital

class WavetableKeyframe {
public:
static float linearTween(float point_from, float point_to, float t);
static float cubicTween(float point_prev, float point_from, float point_to, float point_next,
float range_prev, float range, float range_next, float t);

WavetableKeyframe() : position_(0), owner_(nullptr) { }
virtual ~WavetableKeyframe() { }

int index();
int position() const { return position_; }
void setPosition(int position) {
VITAL_ASSERT(position >= 0 && position < vital::kNumOscillatorWaveFrames);
position_ = position;
}

virtual void copy(const WavetableKeyframe* keyframe) = 0;
virtual void interpolate(const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe, float t) = 0;
virtual void smoothInterpolate(const WavetableKeyframe* prev_keyframe,
const WavetableKeyframe* from_keyframe,
const WavetableKeyframe* to_keyframe,
const WavetableKeyframe* next_keyframe, float t) { }

virtual void render(vital::WaveFrame* wave_frame) = 0;
virtual json stateToJson();
virtual void jsonToState(json data);

WavetableComponent* owner() { return owner_; }
void setOwner(WavetableComponent* owner) { owner_ = owner; }

protected:
int position_;
WavetableComponent* owner_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableKeyframe)
};


+ 74
- 0
ports/vitalium/source/interface/editor_components/audio_file_drop_source.h View File

@@ -0,0 +1,74 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

#include <climits>

class AudioFileDropSource : public FileDragAndDropTarget {
public:
class Listener {
public:
virtual ~Listener() { }
virtual void audioFileLoaded(const File& file) = 0;
};

AudioFileDropSource() {
format_manager_.registerBasicFormats();
}

bool isInterestedInFileDrag(const StringArray& files) override {
if (files.size() != 1)
return false;

String file = files[0];
StringArray wildcards;
wildcards.addTokens(getExtensions(), ";", "\"");
for (const String& wildcard : wildcards) {
if (file.matchesWildcard(wildcard, true))
return true;
}
return false;
}

void filesDropped(const StringArray& files, int x, int y) override {
if (files.size() == 0)
return;

File file(files[0]);
audioFileLoaded(file);
for (Listener* listener : listeners_)
listener->audioFileLoaded(file);
}

virtual void audioFileLoaded(const File& file) = 0;
void addListener(Listener* listener) { listeners_.push_back(listener); }

String getExtensions() { return format_manager_.getWildcardForAllFormats(); }

AudioFormatManager& formatManager() { return format_manager_; }

protected:
AudioFormatManager format_manager_;

private:
std::vector<Listener*> listeners_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioFileDropSource)
};


+ 236
- 0
ports/vitalium/source/interface/editor_components/bar_renderer.cpp View File

@@ -0,0 +1,236 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "bar_renderer.h"

#include "skin.h"
#include "shaders.h"
#include "utils.h"

BarRenderer::BarRenderer(int num_points, bool vertical) :
shader_(nullptr), vertical_(vertical), additive_blending_(true), display_scale_(1.0f), power_scale_(false),
square_scale_(false), dirty_(false), num_points_(num_points), total_points_(num_points) {
addRoundedCorners();

scale_ = 1.0f;
offset_ = 0.0f;
bar_data_ = std::make_unique<float[]>(kFloatsPerBar * total_points_);
bar_indices_ = std::make_unique<int[]>(kTriangleIndicesPerBar * total_points_);
bar_corner_data_ = std::make_unique<float[]>(kCornerFloatsPerBar * total_points_);
bar_buffer_ = 0;
bar_corner_buffer_ = 0;
bar_indices_buffer_ = 0;
bar_width_ = 1.0f;

for (int i = 0; i < total_points_; ++i) {
float t = i / (total_points_ * 1.0f);
int index = i * kFloatsPerBar;

for (int v = 0; v < kVerticesPerBar; ++v) {
int vertex_index = v * kFloatsPerVertex + index;
bar_data_[vertex_index] = 2.0f * t - 1.0f;
bar_data_[vertex_index + 1] = -1.0f;
}

int bar_index = i * kTriangleIndicesPerBar;
int bar_vertex_index = i * kVerticesPerBar;
bar_indices_[bar_index] = bar_vertex_index;
bar_indices_[bar_index + 1] = bar_vertex_index + 1;
bar_indices_[bar_index + 2] = bar_vertex_index + 2;
bar_indices_[bar_index + 3] = bar_vertex_index + 1;
bar_indices_[bar_index + 4] = bar_vertex_index + 2;
bar_indices_[bar_index + 5] = bar_vertex_index + 3;

int corner_index = i * kCornerFloatsPerBar;
bar_corner_data_[corner_index] = 0.0f;
bar_corner_data_[corner_index + 1] = 1.0f;
bar_corner_data_[corner_index + 2] = 1.0f;
bar_corner_data_[corner_index + 3] = 1.0f;
bar_corner_data_[corner_index + 4] = 0.0f;
bar_corner_data_[corner_index + 5] = 0.0f;
bar_corner_data_[corner_index + 6] = 1.0f;
bar_corner_data_[corner_index + 7] = 0.0f;
}
}

BarRenderer::~BarRenderer() { }

void BarRenderer::init(OpenGlWrapper& open_gl) {
OpenGlComponent::init(open_gl);

open_gl.context.extensions.glGenBuffers(1, &bar_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_buffer_);

GLsizeiptr vert_size = static_cast<GLsizeiptr>(kFloatsPerBar * total_points_ * sizeof(float));
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, bar_data_.get(), GL_STATIC_DRAW);

open_gl.context.extensions.glGenBuffers(1, &bar_corner_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_corner_buffer_);

GLsizeiptr corner_size = static_cast<GLsizeiptr>(kCornerFloatsPerBar * total_points_ * sizeof(float));
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, corner_size, bar_corner_data_.get(), GL_STATIC_DRAW);

open_gl.context.extensions.glGenBuffers(1, &bar_indices_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bar_indices_buffer_);

GLsizeiptr bar_size = static_cast<GLsizeiptr>(kTriangleIndicesPerBar * total_points_ * sizeof(int));
open_gl.context.extensions.glBufferData(GL_ELEMENT_ARRAY_BUFFER, bar_size, bar_indices_.get(), GL_STATIC_DRAW);

if (vertical_)
shader_ = open_gl.shaders->getShaderProgram(Shaders::kBarVerticalVertex, Shaders::kBarFragment);
else
shader_ = open_gl.shaders->getShaderProgram(Shaders::kBarHorizontalVertex, Shaders::kBarFragment);

shader_->use();
color_uniform_ = getUniform(open_gl, *shader_, "color");
dimensions_uniform_ = getUniform(open_gl, *shader_, "dimensions");
offset_uniform_ = getUniform(open_gl, *shader_, "offset");
scale_uniform_ = getUniform(open_gl, *shader_, "scale");
width_percent_uniform_ = getUniform(open_gl, *shader_, "width_percent");
position_ = getAttribute(open_gl, *shader_, "position");
corner_ = getAttribute(open_gl, *shader_, "corner");
}

void BarRenderer::drawBars(OpenGlWrapper& open_gl) {
if (!setViewPort(open_gl))
return;

if (shader_ == nullptr)
init(open_gl);

glEnable(GL_BLEND);
glEnable(GL_SCISSOR_TEST);
if (additive_blending_)
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
else
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

if (dirty_) {
dirty_ = false;
setBarSizes();
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_buffer_);

GLsizeiptr vert_size = static_cast<GLsizeiptr>(kFloatsPerBar * total_points_ * sizeof(float));
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, bar_data_.get(), GL_STATIC_DRAW);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
}

shader_->use();

color_uniform_->set(color_.getFloatRed(), color_.getFloatGreen(),
color_.getFloatBlue(), color_.getFloatAlpha());
dimensions_uniform_->set(getWidth(), getHeight());
offset_uniform_->set(offset_);
scale_uniform_->set(scale_);
float min_width = 4.0f / getWidth();
width_percent_uniform_->set(std::max(min_width, bar_width_ * scale_ * 2.0f / num_points_));
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bar_indices_buffer_);

open_gl.context.extensions.glVertexAttribPointer(position_->attributeID, kFloatsPerVertex, GL_FLOAT,
GL_FALSE, kFloatsPerVertex * sizeof(float), nullptr);
open_gl.context.extensions.glEnableVertexAttribArray(position_->attributeID);

open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, bar_corner_buffer_);
open_gl.context.extensions.glVertexAttribPointer(corner_->attributeID, kCornerFloatsPerVertex, GL_FLOAT,
GL_FALSE, kCornerFloatsPerVertex * sizeof(float), nullptr);
open_gl.context.extensions.glEnableVertexAttribArray(corner_->attributeID);

glDrawElements(GL_TRIANGLES, kTriangleIndicesPerBar * total_points_, GL_UNSIGNED_INT, nullptr);

open_gl.context.extensions.glDisableVertexAttribArray(position_->attributeID);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
}

void BarRenderer::render(OpenGlWrapper& open_gl, bool animate) {
drawBars(open_gl);
}

void BarRenderer::destroy(OpenGlWrapper& open_gl) {
OpenGlComponent::destroy(open_gl);

shader_ = nullptr;
position_ = nullptr;
corner_ = nullptr;
color_uniform_ = nullptr;
dimensions_uniform_ = nullptr;
offset_uniform_ = nullptr;
scale_uniform_ = nullptr;
width_percent_uniform_ = nullptr;
open_gl.context.extensions.glDeleteBuffers(1, &bar_buffer_);
open_gl.context.extensions.glDeleteBuffers(1, &bar_indices_buffer_);

bar_buffer_ = 0;
bar_indices_buffer_ = 0;
}

void BarRenderer::setBarSizes() {
if (vertical_) {
for (int i = 0; i < total_points_; ++i) {
int index = i * kFloatsPerBar;
float height = fabsf(yAt(i) - bottomAt(i)) * 0.5f * display_scale_;

bar_data_[index + 2] = height;
bar_data_[index + kFloatsPerVertex + 2] = height;
bar_data_[index + 2 * kFloatsPerVertex + 2] = height;
bar_data_[index + 3 * kFloatsPerVertex + 2] = height;
}
}
else {
for (int i = 0; i < total_points_; ++i) {
int index = i * kFloatsPerBar;
float width = fabsf(xAt(i) - rightAt(i)) * 0.5f * display_scale_;

bar_data_[index + 2] = width;
bar_data_[index + kFloatsPerVertex + 2] = width;
bar_data_[index + 2 * kFloatsPerVertex + 2] = width;
bar_data_[index + 3 * kFloatsPerVertex + 2] = width;
}
}
}

void BarRenderer::setPowerScale(bool power_scale) {
if (power_scale == power_scale_)
return;

bool old_power_scale = power_scale_;
for (int i = 1; i < num_points_; ++i) {
power_scale_ = old_power_scale;
float old_y = scaledYAt(i);
power_scale_ = power_scale;
setScaledY(i, old_y);
}

dirty_ = true;
}

void BarRenderer::setSquareScale(bool square_scale) {
if (square_scale == square_scale_)
return;

bool old_square_scale = square_scale_;
for (int i = 0; i < num_points_; ++i) {
square_scale_ = old_square_scale;
float old_y = scaledYAt(i);
square_scale_ = square_scale;
setScaledY(i, old_y);
}

dirty_ = true;
}

+ 150
- 0
ports/vitalium/source/interface/editor_components/bar_renderer.h View File

@@ -0,0 +1,150 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

#include "common.h"
#include "open_gl_component.h"
#include "open_gl_multi_quad.h"

class BarRenderer : public OpenGlComponent {
public:
static constexpr float kScaleConstant = 5.0f;
static constexpr int kFloatsPerVertex = 3;
static constexpr int kVerticesPerBar = 4;
static constexpr int kFloatsPerBar = kVerticesPerBar * kFloatsPerVertex;
static constexpr int kTriangleIndicesPerBar = 6;
static constexpr int kCornerFloatsPerVertex = 2;
static constexpr int kCornerFloatsPerBar = kVerticesPerBar * kCornerFloatsPerVertex;

BarRenderer(int num_points, bool vertical = true);
virtual ~BarRenderer();

virtual void init(OpenGlWrapper& open_gl) override;
virtual void render(OpenGlWrapper& open_gl, bool animate) override;
virtual void destroy(OpenGlWrapper& open_gl) override;

void setColor(const Colour& color) { color_ = color; }
void setScale(float scale) { scale_ = scale; }
void setOffset(float offset) { offset_ = offset; }
void setBarWidth(float bar_width) { bar_width_ = bar_width; }
void setNumPoints(int num_points) { num_points_ = num_points; }
float getBarWidth() { return bar_width_; }

inline float xAt(int index) { return bar_data_[kFloatsPerBar * index]; }
inline float rightAt(int index) { return bar_data_[kFloatsPerBar * index + kFloatsPerVertex]; }
inline float yAt(int index) { return bar_data_[kFloatsPerBar * index + 1]; }
inline float bottomAt(int index) { return bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex + 1]; }

force_inline void setX(int index, float val) {
bar_data_[kFloatsPerBar * index] = val;
bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex] = val;
bar_data_[kFloatsPerBar * index + kFloatsPerVertex] = val;
bar_data_[kFloatsPerBar * index + 3 * kFloatsPerVertex] = val;
dirty_ = true;
}

force_inline void setY(int index, float val) {
bar_data_[kFloatsPerBar * index + 1] = val;
bar_data_[kFloatsPerBar * index + kFloatsPerVertex + 1] = val;
dirty_ = true;
}

force_inline void setBottom(int index, float val) {
bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex + 1] = val;
bar_data_[kFloatsPerBar * index + 3 * kFloatsPerVertex + 1] = val;
dirty_ = true;
}

inline void positionBar(int index, float x, float y, float width, float height) {
bar_data_[kFloatsPerBar * index] = x;
bar_data_[kFloatsPerBar * index + 1] = y;

bar_data_[kFloatsPerBar * index + kFloatsPerVertex] = x + width;
bar_data_[kFloatsPerBar * index + kFloatsPerVertex + 1] = y;

bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex] = x;
bar_data_[kFloatsPerBar * index + 2 * kFloatsPerVertex + 1] = y + height;

bar_data_[kFloatsPerBar * index + 3 * kFloatsPerVertex] = x + width;
bar_data_[kFloatsPerBar * index + 3 * kFloatsPerVertex + 1] = y + height;
dirty_ = true;
}

void setBarSizes();

void setPowerScale(bool scale);
void setSquareScale(bool scale);

force_inline float scaledYAt(int index) {
float value = yAt(index) * 0.5f + 0.5f;
if (square_scale_)
value *= value;
if (power_scale_)
value /= std::max(index, 1) / kScaleConstant;

return value;
}

force_inline void setScaledY(int index, float val) {
float value = val;

if (power_scale_)
value *= std::max(index, 1) / kScaleConstant;
if (square_scale_)
value = sqrtf(value);

setY(index, 2.0f * value - 1.0f);
}
force_inline void setAdditiveBlending(bool additive_blending) { additive_blending_ = additive_blending; }

protected:
void drawBars(OpenGlWrapper& open_gl);

OpenGLShaderProgram* shader_;
std::unique_ptr<OpenGLShaderProgram::Uniform> color_uniform_;
std::unique_ptr<OpenGLShaderProgram::Uniform> dimensions_uniform_;
std::unique_ptr<OpenGLShaderProgram::Uniform> offset_uniform_;
std::unique_ptr<OpenGLShaderProgram::Uniform> scale_uniform_;
std::unique_ptr<OpenGLShaderProgram::Uniform> width_percent_uniform_;
std::unique_ptr<OpenGLShaderProgram::Attribute> position_;
std::unique_ptr<OpenGLShaderProgram::Attribute> corner_;

Colour color_;
bool vertical_;
float scale_;
float offset_;
float bar_width_;
bool additive_blending_;
float display_scale_;

bool power_scale_;
bool square_scale_;
bool dirty_;
int num_points_;
int total_points_;
std::unique_ptr<float[]> bar_data_;
std::unique_ptr<float[]> bar_corner_data_;
std::unique_ptr<int[]> bar_indices_;
GLuint bar_buffer_;
GLuint bar_corner_buffer_;
GLuint bar_indices_buffer_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(BarRenderer)
};


+ 724
- 0
ports/vitalium/source/interface/editor_components/compressor_editor.cpp View File

@@ -0,0 +1,724 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "compressor_editor.h"

#include "skin.h"
#include "shaders.h"
#include "synth_gui_interface.h"
#include "utils.h"

namespace {
float getOpenGlYForDb(float db) {
float t = (db - CompressorEditor::kMinDb) / (CompressorEditor::kMaxDb - CompressorEditor::kMinDb);
return 2.0f * t - 1.0f;
}

vital::poly_float getOpenGlYForDb(vital::poly_float db) {
vital::poly_float t = (db - CompressorEditor::kMinDb) / (CompressorEditor::kMaxDb - CompressorEditor::kMinDb);
return vital::utils::clamp(t * 2.0f - 1.0f, -1.0f, 1.0f);
}

vital::poly_float getOpenGlYForMagnitude(vital::poly_float magnitude) {
vital::poly_float db = vital::utils::magnitudeToDb(vital::utils::max(0.0001f, magnitude));
return getOpenGlYForDb(db);
}

void setQuadIfRatioMatch(OpenGlMultiQuad& quads, float ratio, float ratio_match,
int index, float x, float y, float w, float h) {
if (ratio == ratio_match || (ratio > 0.0f && ratio_match > 0.0f) || (ratio < 0.0f && ratio_match < 0.0f))
quads.setQuad(index, x, y, w, h);
else
quads.setQuad(index, -2.0f, -2.0f, 0.0f, 0.0f);
}

std::string formatString(float value, std::string suffix) {
static constexpr int kMaxDecimalPlaces = 4;
String format = String(value, kMaxDecimalPlaces);

int display_characters = kMaxDecimalPlaces;
if (format[0] == '-')
display_characters += 1;

format = format.substring(0, display_characters);
if (format.getLastCharacter() == '.')
format = format.removeCharacters(".");

return format.toStdString() + suffix;
}
} // namespace

CompressorEditor::CompressorEditor() : hover_quad_(Shaders::kColorFragment),
input_dbs_(kNumChannels, Shaders::kColorFragment),
output_dbs_(kNumChannels, Shaders::kRoundedRectangleFragment),
thresholds_(kNumChannels, Shaders::kColorFragment),
ratio_lines_(kTotalRatioLines, Shaders::kFadeSquareFragment) {
addRoundedCorners();
addAndMakeVisible(hover_quad_);
addAndMakeVisible(input_dbs_);
addAndMakeVisible(output_dbs_);
addAndMakeVisible(thresholds_);
addAndMakeVisible(ratio_lines_);

parent_ = nullptr;
section_parent_ = nullptr;
hover_ = kNone;
animate_ = false;
high_band_active_ = true;
low_band_active_ = true;
size_ratio_ = 1.0f;
active_ = true;

low_upper_threshold_ = kMaxDb;
band_upper_threshold_ = kMaxDb;
high_upper_threshold_ = kMaxDb;
low_lower_threshold_ = kMinDb;
band_lower_threshold_ = kMinDb;
high_lower_threshold_ = kMinDb;
low_upper_ratio_ = 0.0f;
band_upper_ratio_ = 0.0f;
high_upper_ratio_ = 0.0f;
low_lower_ratio_ = 0.0f;
band_lower_ratio_ = 0.0f;
high_lower_ratio_ = 0.0f;

low_input_ms_ = nullptr;
band_input_ms_ = nullptr;
high_input_ms_ = nullptr;

low_output_ms_ = nullptr;
band_output_ms_ = nullptr;
high_output_ms_ = nullptr;
}

CompressorEditor::~CompressorEditor() { }

CompressorEditor::DragPoint CompressorEditor::getHoverPoint(const MouseEvent& e) {
float low_band_high_position = 3.0f * e.position.x / ((1.0f - kCompressorAreaBuffer) * getWidth());
int index = low_band_high_position;
float local_position = low_band_high_position - index;
if (index >= 3 || index < 0 || local_position < 3.0f * kCompressorAreaBuffer)
return kNone;

if (index == 0 && !low_band_active_)
index = 1;
if (index == 2 && !high_band_active_)
index = 1;

float upper_threshold_values[] = { low_upper_threshold_, band_upper_threshold_, high_upper_threshold_ };
float lower_threshold_values[] = { low_lower_threshold_, band_lower_threshold_, high_lower_threshold_ };
DragPoint upper_threshold_points[] = { kLowUpperThreshold, kBandUpperThreshold, kHighUpperThreshold };
DragPoint lower_threshold_points[] = { kLowLowerThreshold, kBandLowerThreshold, kHighLowerThreshold };
DragPoint upper_ratio_points[] = { kLowUpperRatio, kBandUpperRatio, kHighUpperRatio };
DragPoint lower_ratio_points[] = { kLowLowerRatio, kBandLowerRatio, kHighLowerRatio };

float grab_radius = kGrabRadius * size_ratio_;
int upper_handle_y = std::max(grab_radius, getYForDb(upper_threshold_values[index]));
int lower_handle_y = std::min(getHeight() - grab_radius, getYForDb(lower_threshold_values[index]));

float delta_upper = e.position.y - upper_handle_y;
float delta_lower = e.position.y - lower_handle_y;
if (fabsf(delta_upper) <= grab_radius && fabsf(delta_upper) < fabsf(delta_lower))
return upper_threshold_points[index];
if (fabsf(delta_lower) <= grab_radius)
return lower_threshold_points[index];
if (delta_upper < 0.0f)
return upper_ratio_points[index];
if (delta_lower > 0.0f)
return lower_ratio_points[index];
return kNone;
}

void CompressorEditor::mouseDown(const MouseEvent& e) {
last_mouse_position_ = e.getPosition();
mouseDrag(e);
}

void CompressorEditor::mouseDoubleClick(const MouseEvent& e) {
if (isRatio(hover_)) {
switch (hover_) {
case kLowUpperRatio:
setLowUpperRatio(0.0f);
break;
case kBandUpperRatio:
setBandUpperRatio(0.0f);
break;
case kHighUpperRatio:
setHighUpperRatio(0.0f);
break;
case kLowLowerRatio:
setLowLowerRatio(0.0f);
break;
case kBandLowerRatio:
setBandLowerRatio(0.0f);
break;
case kHighLowerRatio:
setHighLowerRatio(0.0f);
break;
default:
break;
}
}
}

void CompressorEditor::mouseMove(const MouseEvent& e) {
hover_ = getHoverPoint(e);

if (hover_ != kNone)
setMouseCursor(MouseCursor::BottomEdgeResizeCursor);
else
setMouseCursor(MouseCursor::NormalCursor);
}

void CompressorEditor::mouseDrag(const MouseEvent& e) {
if (hover_ == kNone || parent_ == nullptr)
return;

float delta = (e.getPosition().y - last_mouse_position_.y) * kMouseMultiplier / getHeight();
float delta_db_value = (kMinDb - kMaxDb) * delta;

last_mouse_position_ = e.getPosition();
float delta_ratio = delta * kRatioEditMultiplier;

if (e.mods.isShiftDown()) {
setLowUpperThreshold(low_upper_threshold_ + delta_db_value, false);
setBandUpperThreshold(band_upper_threshold_ + delta_db_value, false);
setHighUpperThreshold(high_upper_threshold_ + delta_db_value, false);
setLowLowerThreshold(low_lower_threshold_ + delta_db_value, false);
setBandLowerThreshold(band_lower_threshold_ + delta_db_value, false);
setHighLowerThreshold(high_lower_threshold_ + delta_db_value, false);
}
else {
switch (hover_) {
case kLowUpperThreshold:
setLowUpperThreshold(low_upper_threshold_ + delta_db_value, true);
break;
case kLowUpperRatio:
setLowUpperRatio(low_upper_ratio_ + delta_ratio);
break;
case kBandUpperThreshold:
setBandUpperThreshold(band_upper_threshold_ + delta_db_value, true);
break;
case kBandUpperRatio:
setBandUpperRatio(band_upper_ratio_ + delta_ratio);
break;
case kHighUpperThreshold:
setHighUpperThreshold(high_upper_threshold_ + delta_db_value, true);
break;
case kHighUpperRatio:
setHighUpperRatio(high_upper_ratio_ + delta_ratio);
break;
case kLowLowerThreshold:
setLowLowerThreshold(low_lower_threshold_ + delta_db_value, true);
break;
case kLowLowerRatio:
setLowLowerRatio(low_lower_ratio_ - delta_ratio);
break;
case kBandLowerThreshold:
setBandLowerThreshold(band_lower_threshold_ + delta_db_value, true);
break;
case kBandLowerRatio:
setBandLowerRatio(band_lower_ratio_ - delta_ratio);
break;
case kHighLowerThreshold:
setHighLowerThreshold(high_lower_threshold_ + delta_db_value, true);
break;
case kHighLowerRatio:
setHighLowerRatio(high_lower_ratio_ - delta_ratio);
break;
default:
break;
}
}
}

void CompressorEditor::mouseUp(const MouseEvent& e) {
if (isRatio(hover_))
setMouseCursor(MouseCursor::BottomEdgeResizeCursor);

section_parent_->hidePopupDisplay(true);
}

void CompressorEditor::mouseExit(const MouseEvent& e) {
setMouseCursor(MouseCursor::NormalCursor);
hover_ = kNone;
}

void CompressorEditor::paintBackground(Graphics& g) {
OpenGlComponent::paintBackground(g);

g.setColour(findColour(Skin::kLightenScreen, true));
for (int i = 1; i < kDbLineSections; ++i) {
float t = (i * 1.0f) / kDbLineSections;
int y = getHeight() * t;
g.fillRect(0, y, getWidth(), 1);
}
}

void CompressorEditor::resized() {
OpenGlComponent::resized();
hover_quad_.setBounds(getLocalBounds());
input_dbs_.setBounds(getLocalBounds());
output_dbs_.setBounds(getLocalBounds());
thresholds_.setBounds(getLocalBounds());
ratio_lines_.setBounds(getLocalBounds());
}

void CompressorEditor::init(OpenGlWrapper& open_gl) {
OpenGlComponent::init(open_gl);
hover_quad_.init(open_gl);
input_dbs_.init(open_gl);
output_dbs_.init(open_gl);
thresholds_.init(open_gl);
ratio_lines_.init(open_gl);
}

void CompressorEditor::render(OpenGlWrapper& open_gl, bool animate) {
renderCompressor(open_gl, animate);
renderCorners(open_gl, animate);
}

void CompressorEditor::setThresholdPositions(int low_start, int low_end, int band_start, int band_end,
int high_start, int high_end, float ratio_match) {
thresholds_.setColor(getColorForRatio(ratio_match));
float width = getWidth();

float low_start_x = low_start * 2.0f / width - 1.0f;
float low_width = (low_end - low_start) * 2.0f / width;
float band_start_x = band_start * 2.0f / width - 1.0f;
float band_width = (band_end - band_start) * 2.0f / width;
float high_start_x = high_start * 2.0f / width - 1.0f;
float high_width = (high_end - high_start) * 2.0f / width;

setQuadIfRatioMatch(thresholds_, -low_lower_ratio_, ratio_match, 0,
low_start_x, -1.0f, low_width, getOpenGlYForDb(low_lower_threshold_) + 1.0f);
setQuadIfRatioMatch(thresholds_, low_upper_ratio_, ratio_match, 1,
low_start_x, 1.0f, low_width, getOpenGlYForDb(low_upper_threshold_) - 1.0f);
setQuadIfRatioMatch(thresholds_, -band_lower_ratio_, ratio_match, 2,
band_start_x, -1.0f, band_width, getOpenGlYForDb(band_lower_threshold_) + 1.0f);
setQuadIfRatioMatch(thresholds_, band_upper_ratio_, ratio_match, 3,
band_start_x, 1.0f, band_width, getOpenGlYForDb(band_upper_threshold_) - 1.0f);
setQuadIfRatioMatch(thresholds_, -high_lower_ratio_, ratio_match, 4,
high_start_x, -1.0f, high_width, getOpenGlYForDb(high_lower_threshold_) + 1.0f);
setQuadIfRatioMatch(thresholds_, high_upper_ratio_, ratio_match, 5,
high_start_x, 1.0f, high_width, getOpenGlYForDb(high_upper_threshold_) - 1.0f);
}

void CompressorEditor::setRatioLines(int start_index, int start_x, int end_x,
float threshold, float ratio, bool upper, bool hover) {

float db_position = kDbLineSections * (threshold - kMinDb) / (kMaxDb - kMinDb);
int db_index = db_position;
float db_change = -(kMaxDb - kMinDb) / kDbLineSections;
if (upper) {
db_change = (kMaxDb - kMinDb) / kDbLineSections;
db_index = ceil(db_position);
}

float width = getWidth();
float x = start_x * 2.0f / width - 1.0f;
float ratio_width = (end_x - start_x) * 2.0f / width;
float ratio_height = 4.0f / getHeight();

float mult = hover ? 5.0f : 2.5f;

float db = db_index * (kMaxDb - kMinDb) / kDbLineSections + kMinDb;
for (int i = 0; i < kRatioDbLines; ++i) {
float adjusted_db = getCompressedDb(db, threshold, ratio, threshold, ratio);
ratio_lines_.setQuad(start_index + i, x, getOpenGlYForDb(adjusted_db) - ratio_height * 0.5f,
ratio_width, ratio_height);
ratio_lines_.setShaderValue(start_index + i, (kRatioDbLines - i) * mult / kRatioDbLines);
db += db_change;
}
}

void CompressorEditor::setRatioLinePositions(int low_start, int low_end, int band_start, int band_end,
int high_start, int high_end) {
setRatioLines(0, low_start, low_end, low_upper_threshold_, low_upper_ratio_,
true, hover_ == kLowUpperRatio);
setRatioLines(kRatioDbLines, low_start, low_end, low_lower_threshold_, low_lower_ratio_,
false, hover_ == kLowLowerRatio);
setRatioLines(2 * kRatioDbLines, band_start, band_end, band_upper_threshold_, band_upper_ratio_,
true, hover_ == kBandUpperRatio);
setRatioLines(3 * kRatioDbLines, band_start, band_end, band_lower_threshold_, band_lower_ratio_,
false, hover_ == kBandLowerRatio);
setRatioLines(4 * kRatioDbLines, high_start, high_end, high_upper_threshold_, high_upper_ratio_,
true, hover_ == kHighUpperRatio);
setRatioLines(5 * kRatioDbLines, high_start, high_end, high_lower_threshold_, high_lower_ratio_,
false, hover_ == kHighLowerRatio);
}

void CompressorEditor::renderHover(OpenGlWrapper& open_gl, int low_start, int low_end,
int band_start, int band_end, int high_start, int high_end) {
if (hover_ == kNone)
return;

float width = getWidth();
float height = getHeight();
float threshold_height = 2.0f / height;

float low_start_x = low_start * 2.0f / width - 1.0f;
float low_width = (low_end - low_start) * 2.0f / width;
float band_start_x = band_start * 2.0f / width - 1.0f;
float band_width = (band_end - band_start) * 2.0f / width;
float high_start_x = high_start * 2.0f / width - 1.0f;
float high_width = (high_end - high_start) * 2.0f / width;

switch (hover_) {
case kLowUpperThreshold:
hover_quad_.setQuad(0, low_start_x, getOpenGlYForDb(low_upper_threshold_) - 0.5f * threshold_height,
low_width, threshold_height);
break;
case kBandUpperThreshold:
hover_quad_.setQuad(0, band_start_x, getOpenGlYForDb(band_upper_threshold_) - 0.5f * threshold_height,
band_width, threshold_height);
break;
case kHighUpperThreshold:
hover_quad_.setQuad(0, high_start_x, getOpenGlYForDb(high_upper_threshold_) - 0.5f * threshold_height,
high_width, threshold_height);
break;
case kLowLowerThreshold:
hover_quad_.setQuad(0, low_start_x, getOpenGlYForDb(low_lower_threshold_) - 0.5f * threshold_height,
low_width, threshold_height);
break;
case kBandLowerThreshold:
hover_quad_.setQuad(0, band_start_x, getOpenGlYForDb(band_lower_threshold_) - 0.5f * threshold_height,
band_width, threshold_height);
break;
case kHighLowerThreshold:
hover_quad_.setQuad(0, high_start_x, getOpenGlYForDb(high_lower_threshold_) - 0.5f * threshold_height,
high_width, threshold_height);
break;
default:
return;
}

if (isRatio(hover_))
hover_quad_.setColor(findColour(Skin::kLightenScreen, true));
else
hover_quad_.setColor(findColour(Skin::kWidgetCenterLine, true));

hover_quad_.render(open_gl, true);
}

void CompressorEditor::renderCompressor(OpenGlWrapper& open_gl, bool animate) {
static constexpr float kOutputBarHeight = 2.2f;
if (low_input_ms_ == nullptr || band_input_ms_ == nullptr || high_input_ms_ == nullptr ||
low_output_ms_ == nullptr || band_output_ms_ == nullptr || high_output_ms_ == nullptr) {
return;
}

vital::poly_float low_rms = vital::utils::sqrt(low_input_ms_->value());
vital::poly_float low_input_y = getOpenGlYForMagnitude(low_rms);

vital::poly_float scaled_low_rms = vital::utils::sqrt(low_output_ms_->value());
vital::poly_float low_output_y = getOpenGlYForMagnitude(scaled_low_rms);

vital::poly_float band_rms = vital::utils::sqrt(band_input_ms_->value());
vital::poly_float band_input_y = getOpenGlYForMagnitude(band_rms);

vital::poly_float scaled_band_rms = vital::utils::sqrt(band_output_ms_->value());
vital::poly_float band_output_y = getOpenGlYForMagnitude(scaled_band_rms);

vital::poly_float high_rms = vital::utils::sqrt(high_input_ms_->value());
vital::poly_float high_input_y = getOpenGlYForMagnitude(high_rms);

vital::poly_float scaled_high_rms = vital::utils::sqrt(high_output_ms_->value());
vital::poly_float high_output_y = getOpenGlYForMagnitude(scaled_high_rms);

int width = getWidth();
float active_area = 1.0f - 4.0f * kCompressorAreaBuffer;
float active_section_width = active_area / kMaxBands;

int low_start = std::round(kCompressorAreaBuffer * width);
int low_end = std::round((kCompressorAreaBuffer + active_section_width) * width);
int band_start = std::round((2.0f * kCompressorAreaBuffer + active_section_width) * width);
int band_end = width - band_start;
int high_start = width - low_end;
int high_end = width - low_start;

if (!low_band_active_) {
band_start = low_start;
low_start = low_end = -width;
}
if (!high_band_active_) {
band_end = high_end;
high_start = high_end = -width;
}

setThresholdPositions(low_start, low_end, band_start, band_end, high_start, high_end, 1.0f);
thresholds_.render(open_gl, true);

setThresholdPositions(low_start, low_end, band_start, band_end, high_start, high_end, 0.0f);
thresholds_.render(open_gl, true);

setThresholdPositions(low_start, low_end, band_start, band_end, high_start, high_end, -1.0f);
thresholds_.render(open_gl, true);

setRatioLinePositions(low_start, low_end, band_start, band_end, high_start, high_end);
ratio_lines_.setColor(findColour(Skin::kLightenScreen, true));
ratio_lines_.render(open_gl, true);

renderHover(open_gl, low_start, low_end, band_start, band_end, high_start, high_end);

int bar_width = kBarWidth * active_section_width * width;
int low_middle = (low_start + low_end) * 0.5f;
int band_middle = (band_start + band_end) * 0.5f;
int high_middle = (high_start + high_end) * 0.5f;

float gl_bar_width = bar_width * 2.0f / width;
float low_left = (low_middle - bar_width) * 2.0f / width - 1.0f;
float low_right = (low_middle + 1) * 2.0f / width - 1.0f;
float band_left = (band_middle - bar_width) * 2.0f / width - 1.0f;
float band_right = (band_middle + 1) * 2.0f / width - 1.0f;
float high_left = (high_middle - bar_width) * 2.0f / width - 1.0f;
float high_right = (high_middle + 1) * 2.0f / width - 1.0f;
output_dbs_.setQuad(0, low_left, low_output_y[0] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
output_dbs_.setQuad(1, low_right, low_output_y[1] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
output_dbs_.setQuad(2, band_left, band_output_y[0] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
output_dbs_.setQuad(3, band_right, band_output_y[1] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
output_dbs_.setQuad(4, high_left, high_output_y[0] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);
output_dbs_.setQuad(5, high_right, high_output_y[1] - kOutputBarHeight, gl_bar_width, kOutputBarHeight);

float input_height = 2.0f / getHeight();
input_dbs_.setQuad(0, low_left, low_input_y[0] - 0.5f * input_height, gl_bar_width, input_height);
input_dbs_.setQuad(1, low_right, low_input_y[1] - 0.5f * input_height, gl_bar_width, input_height);
input_dbs_.setQuad(2, band_left, band_input_y[0] - 0.5f * input_height, gl_bar_width, input_height);
input_dbs_.setQuad(3, band_right, band_input_y[1] - 0.5f * input_height, gl_bar_width, input_height);
input_dbs_.setQuad(4, high_left, high_input_y[0] - 0.5f * input_height, gl_bar_width, input_height);
input_dbs_.setQuad(5, high_right, high_input_y[1] - 0.5f * input_height, gl_bar_width, input_height);

output_dbs_.setColor(findColour(Skin::kWidgetPrimary1, true));
output_dbs_.render(open_gl, animate);

input_dbs_.setColor(findColour(Skin::kWidgetPrimary2, true));
input_dbs_.render(open_gl, animate);
}

void CompressorEditor::destroy(OpenGlWrapper& open_gl) {
OpenGlComponent::destroy(open_gl);
hover_quad_.destroy(open_gl);
input_dbs_.destroy(open_gl);
output_dbs_.destroy(open_gl);
thresholds_.destroy(open_gl);
ratio_lines_.destroy(open_gl);
}

void CompressorEditor::setAllValues(vital::control_map& controls) {
low_upper_threshold_ = controls["compressor_low_upper_threshold"]->value();
band_upper_threshold_ = controls["compressor_band_upper_threshold"]->value();
high_upper_threshold_ = controls["compressor_high_upper_threshold"]->value();
low_lower_threshold_ = controls["compressor_low_lower_threshold"]->value();
band_lower_threshold_ = controls["compressor_band_lower_threshold"]->value();
high_lower_threshold_ = controls["compressor_high_lower_threshold"]->value();
low_upper_ratio_ = controls["compressor_low_upper_ratio"]->value();
band_upper_ratio_ = controls["compressor_band_upper_ratio"]->value();
high_upper_ratio_ = controls["compressor_high_upper_ratio"]->value();
low_lower_ratio_ = controls["compressor_low_lower_ratio"]->value();
band_lower_ratio_ = controls["compressor_band_lower_ratio"]->value();
high_lower_ratio_ = controls["compressor_high_lower_ratio"]->value();
}

void CompressorEditor::setLowUpperThreshold(float db, bool clamp) {
low_upper_threshold_ = db;
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
SynthBase* synth = parent_->getSynth();

if (clamp)
low_upper_threshold_ = db;
synth->valueChangedInternal("compressor_low_upper_threshold", db);
if (low_upper_threshold_ < low_lower_threshold_ && clamp)
setLowLowerThreshold(db, clamp);

section_parent_->showPopupDisplay(this, formatString(low_upper_threshold_, " dB"), BubbleComponent::below, true);
}

void CompressorEditor::setBandUpperThreshold(float db, bool clamp) {
band_upper_threshold_ = db;
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
SynthBase* synth = parent_->getSynth();
if (clamp)
band_upper_threshold_ = db;
synth->valueChangedInternal("compressor_band_upper_threshold", db);
if (band_upper_threshold_ < band_lower_threshold_ && clamp)
setBandLowerThreshold(db, clamp);

section_parent_->showPopupDisplay(this, formatString(band_upper_threshold_, " dB"), BubbleComponent::below, true);
}

void CompressorEditor::setHighUpperThreshold(float db, bool clamp) {
high_upper_threshold_ = db;
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
SynthBase* synth = parent_->getSynth();
if (clamp)
high_upper_threshold_ = db;
synth->valueChangedInternal("compressor_high_upper_threshold", db);
if (high_upper_threshold_ < high_lower_threshold_ && clamp)
setHighLowerThreshold(db, clamp);

section_parent_->showPopupDisplay(this, formatString(high_upper_threshold_, " dB"), BubbleComponent::below, true);
}

void CompressorEditor::setLowLowerThreshold(float db, bool clamp) {
low_lower_threshold_ = db;
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
SynthBase* synth = parent_->getSynth();

if (clamp)
low_lower_threshold_ = db;
synth->valueChangedInternal("compressor_low_lower_threshold", db);
if (low_lower_threshold_ > low_upper_threshold_ && clamp)
setLowUpperThreshold(db, clamp);

section_parent_->showPopupDisplay(this, formatString(low_lower_threshold_, " dB"), BubbleComponent::below, true);
}

void CompressorEditor::setBandLowerThreshold(float db, bool clamp) {
band_lower_threshold_ = db;
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
SynthBase* synth = parent_->getSynth();

if (clamp)
band_lower_threshold_ = db;
synth->valueChangedInternal("compressor_band_lower_threshold", db);
if (band_lower_threshold_ > band_upper_threshold_ && clamp)
setBandUpperThreshold(db, clamp);

section_parent_->showPopupDisplay(this, formatString(band_lower_threshold_, " dB"), BubbleComponent::below, true);
}

void CompressorEditor::setHighLowerThreshold(float db, bool clamp ) {
high_lower_threshold_ = db;
db = vital::utils::clamp(db, kMinEditDb, kMaxEditDb);
SynthBase* synth = parent_->getSynth();

if (clamp)
high_lower_threshold_ = db;
synth->valueChangedInternal("compressor_high_lower_threshold", db);
if (high_lower_threshold_ > high_upper_threshold_ && clamp)
setHighUpperThreshold(db, clamp);

section_parent_->showPopupDisplay(this, formatString(high_lower_threshold_, " dB"), BubbleComponent::below, true);
}

void CompressorEditor::setLowUpperRatio(float ratio) {
SynthBase* synth = parent_->getSynth();

low_upper_ratio_ = vital::utils::clamp(ratio, kMinUpperRatio, kMaxUpperRatio);
synth->valueChangedInternal("compressor_low_upper_ratio", ratio);
}

void CompressorEditor::setBandUpperRatio(float ratio) {
SynthBase* synth = parent_->getSynth();

band_upper_ratio_ = vital::utils::clamp(ratio, kMinUpperRatio, kMaxUpperRatio);
synth->valueChangedInternal("compressor_band_upper_ratio", ratio);
}

void CompressorEditor::setHighUpperRatio(float ratio) {
SynthBase* synth = parent_->getSynth();

high_upper_ratio_ = vital::utils::clamp(ratio, kMinUpperRatio, kMaxUpperRatio);
synth->valueChangedInternal("compressor_high_upper_ratio", ratio);
}

void CompressorEditor::setLowLowerRatio(float ratio) {
SynthBase* synth = parent_->getSynth();

low_lower_ratio_ = vital::utils::clamp(ratio, kMinLowerRatio, kMaxLowerRatio);
synth->valueChangedInternal("compressor_low_lower_ratio", ratio);
}

void CompressorEditor::setBandLowerRatio(float ratio) {
SynthBase* synth = parent_->getSynth();

band_lower_ratio_ = vital::utils::clamp(ratio, kMinLowerRatio, kMaxLowerRatio);
synth->valueChangedInternal("compressor_band_lower_ratio", ratio);
}

void CompressorEditor::setHighLowerRatio(float ratio) {
SynthBase* synth = parent_->getSynth();

high_lower_ratio_ = vital::utils::clamp(ratio, kMinLowerRatio, kMaxLowerRatio);
synth->valueChangedInternal("compressor_high_lower_ratio", ratio);
}

String CompressorEditor::formatValue(float value) {
static constexpr int number_length = 5;
static constexpr int max_decimals = 3;

String format = String(value, max_decimals);
format = format.substring(0, number_length);
int spaces = number_length - format.length();

for (int i = 0; i < spaces; ++i)
format = " " + format;

return format;
}

void CompressorEditor::parentHierarchyChanged() {
if (parent_ == nullptr)
parent_ = findParentComponentOfClass<SynthGuiInterface>();

if (section_parent_ == nullptr)
section_parent_ = findParentComponentOfClass<SynthSection>();

if (parent_ == nullptr)
return;

if (low_input_ms_ == nullptr)
low_input_ms_ = parent_->getSynth()->getStatusOutput("compressor_low_input");
if (band_input_ms_ == nullptr)
band_input_ms_ = parent_->getSynth()->getStatusOutput("compressor_band_input");
if (high_input_ms_ == nullptr)
high_input_ms_ = parent_->getSynth()->getStatusOutput("compressor_high_input");
if (low_output_ms_ == nullptr)
low_output_ms_ = parent_->getSynth()->getStatusOutput("compressor_low_output");
if (band_output_ms_ == nullptr)
band_output_ms_ = parent_->getSynth()->getStatusOutput("compressor_band_output");
if (high_output_ms_ == nullptr)
high_output_ms_ = parent_->getSynth()->getStatusOutput("compressor_high_output");

OpenGlComponent::parentHierarchyChanged();
}

float CompressorEditor::getYForDb(float db) {
return getHeight() * (1.0f - getOpenGlYForDb(vital::utils::clamp(db, kMinDb, kMaxDb))) * 0.5f;
}

float CompressorEditor::getCompressedDb(float input_db, float upper_threshold, float upper_ratio,
float lower_threshold, float lower_ratio) {
if (input_db < lower_threshold)
return vital::utils::interpolate(input_db, lower_threshold, lower_ratio);
if (input_db > upper_threshold)
return vital::utils::interpolate(input_db, upper_threshold, upper_ratio);
return input_db;
}

Colour CompressorEditor::getColorForRatio(float ratio) {
if (!active_)
return findColour(Skin::kWidgetSecondaryDisabled, true);

if (ratio > 0.0f)
return findColour(Skin::kWidgetSecondary1, true);
if (ratio < 0.0f)
return findColour(Skin::kWidgetSecondary2, true);

return findColour(Skin::kWidgetSecondaryDisabled, true);
}

+ 172
- 0
ports/vitalium/source/interface/editor_components/compressor_editor.h View File

@@ -0,0 +1,172 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

#include "line_generator.h"
#include "open_gl_component.h"
#include "open_gl_multi_quad.h"
#include "synth_slider.h"
#include "synth_module.h"

class SynthGuiInterface;

class CompressorEditor : public OpenGlComponent, public SynthSlider::SliderListener {
public:
static constexpr float kGrabRadius = 8.0f;
static constexpr float kMinDb = -80.0f;
static constexpr float kMaxDb = 0.0f;
static constexpr float kDbEditBuffer = 1.0f;
static constexpr float kMinEditDb = kMinDb + kDbEditBuffer;
static constexpr float kMaxEditDb = kMaxDb - kDbEditBuffer;
static constexpr float kMinLowerRatio = -1.0f;
static constexpr float kMaxLowerRatio = 1.0f;
static constexpr float kMinUpperRatio = 0.0f;
static constexpr float kMaxUpperRatio = 1.0f;
static constexpr float kRatioEditMultiplier = 0.6f;
static constexpr float kCompressorAreaBuffer = 0.05f;
static constexpr float kBarWidth = 1.0f / 5.0f;
static constexpr float kInputLineRadius = 0.02f;
static constexpr float kMouseMultiplier = 1.0f;
static constexpr int kMaxBands = 3;
static constexpr int kNumChannels = kMaxBands * 2;
static constexpr int kDbLineSections = 8;
static constexpr int kExtraDbLines = 6;
static constexpr int kRatioDbLines = kDbLineSections + kExtraDbLines;
static constexpr int kTotalRatioLines = kRatioDbLines * kNumChannels;

CompressorEditor();
virtual ~CompressorEditor();

void paintBackground(Graphics& g) override;
void resized() override;

void mouseDown(const MouseEvent& e) override;
void mouseDoubleClick(const MouseEvent& e) override;
void mouseMove(const MouseEvent& e) override;
void mouseDrag(const MouseEvent& e) override;
void mouseUp(const MouseEvent& e) override;
void mouseExit(const MouseEvent& e) override;
void parentHierarchyChanged() override;

void init(OpenGlWrapper& open_gl) override;
void render(OpenGlWrapper& open_gl, bool animate) override;
void renderCompressor(OpenGlWrapper& open_gl, bool animate);
void destroy(OpenGlWrapper& open_gl) override;
void setSizeRatio(float ratio) { size_ratio_ = ratio; }
void setAllValues(vital::control_map& controls);

void setHighBandActive(bool active) { high_band_active_ = active; }
void setLowBandActive(bool active) { low_band_active_ = active; }
void setActive(bool active) { active_ = active; }

private:
enum DragPoint {
kNone,
kLowUpperThreshold,
kBandUpperThreshold,
kHighUpperThreshold,
kLowLowerThreshold,
kBandLowerThreshold,
kHighLowerThreshold,
kLowUpperRatio,
kBandUpperRatio,
kHighUpperRatio,
kLowLowerRatio,
kBandLowerRatio,
kHighLowerRatio,
kNumDragPoints
};

bool isRatio(DragPoint drag_point) {
return drag_point == kLowLowerRatio || drag_point == kBandLowerRatio || drag_point == kHighLowerRatio ||
drag_point == kLowUpperRatio || drag_point == kBandUpperRatio || drag_point == kHighUpperRatio;
}

void setThresholdPositions(int low_start, int low_end, int band_start, int band_end,
int high_start, int high_end, float ratio_match);
void setRatioLines(int start_index, int start_x, int end_x, float threshold, float ratio, bool upper, bool hover);
void setRatioLinePositions(int low_start, int low_end, int band_start, int band_end,
int high_start, int high_end);
void renderHover(OpenGlWrapper& open_gl, int low_start, int low_end,
int band_start, int band_end, int high_start, int high_end);

void setLowUpperThreshold(float db, bool clamp);
void setBandUpperThreshold(float db, bool clamp);
void setHighUpperThreshold(float db, bool clamp);
void setLowLowerThreshold(float db, bool clamp);
void setBandLowerThreshold(float db, bool clamp);
void setHighLowerThreshold(float db, bool clamp);

void setLowUpperRatio(float ratio);
void setBandUpperRatio(float ratio);
void setHighUpperRatio(float ratio);
void setLowLowerRatio(float ratio);
void setBandLowerRatio(float ratio);
void setHighLowerRatio(float ratio);

String formatValue(float value);

DragPoint getHoverPoint(const MouseEvent& e);
float getYForDb(float db);
float getCompressedDb(float input_db, float upper_threshold, float upper_ratio,
float lower_threshold, float lower_ratio);
Colour getColorForRatio(float ratio);

SynthGuiInterface* parent_;
SynthSection* section_parent_;

DragPoint hover_;
Point<int> last_mouse_position_;

OpenGlQuad hover_quad_;
OpenGlMultiQuad input_dbs_;
OpenGlMultiQuad output_dbs_;
OpenGlMultiQuad thresholds_;
OpenGlMultiQuad ratio_lines_;

float low_upper_threshold_;
float band_upper_threshold_;
float high_upper_threshold_;
float low_lower_threshold_;
float band_lower_threshold_;
float high_lower_threshold_;
float low_upper_ratio_;
float band_upper_ratio_;
float high_upper_ratio_;
float low_lower_ratio_;
float band_lower_ratio_;
float high_lower_ratio_;

const vital::StatusOutput* low_input_ms_;
const vital::StatusOutput* band_input_ms_;
const vital::StatusOutput* high_input_ms_;

const vital::StatusOutput* low_output_ms_;
const vital::StatusOutput* band_output_ms_;
const vital::StatusOutput* high_output_ms_;

float size_ratio_;
bool animate_;
bool active_;
bool high_band_active_;
bool low_band_active_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CompressorEditor)
};


+ 311
- 0
ports/vitalium/source/interface/editor_components/drag_drop_effect_order.cpp View File

@@ -0,0 +1,311 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "drag_drop_effect_order.h"

#include "skin.h"
#include "fonts.h"
#include "paths.h"
#include "synth_button.h"
#include "synth_gui_interface.h"
#include "synth_strings.h"
#include "text_look_and_feel.h"
#include "utils.h"

namespace {
Path getPathForEffect(String effect) {
if (effect == "compressor")
return Paths::compressor();
if (effect == "chorus")
return Paths::chorus();
if (effect == "delay")
return Paths::delay();
if (effect == "distortion")
return Paths::distortion();
if (effect == "eq")
return Paths::equalizer();
if (effect == "filter")
return Paths::effectsFilter();
if (effect == "flanger")
return Paths::flanger();
if (effect == "phaser")
return Paths::phaser();
if (effect == "reverb")
return Paths::reverb();

return Path();
}
}

DraggableEffect::DraggableEffect(const String& name, int order) : SynthSection(name), order_(order), hover_(false) {
setInterceptsMouseClicks(false, true);

StringArray tokens;
tokens.addTokens(getName(), "_", "");
icon_ = getPathForEffect(tokens[0].toLowerCase());

background_ = std::make_unique<OpenGlImageComponent>("background");
addOpenGlComponent(background_.get());
background_->setComponent(this);
background_->paintEntireComponent(false);

enable_ = std::make_unique<SynthButton>(name + "_on");
enable_->setPowerButton();
addButton(enable_.get());
enable_->setButtonText("");
enable_->getGlComponent()->setAlwaysOnTop(true);
}

void DraggableEffect::paint(Graphics& g) {
static constexpr float kLeftPadding = 0.07f;
static constexpr float kIconSize = 0.6f;
static constexpr int kTextureRows = 2;
static constexpr int kTextureColumns = 3;
static constexpr float kTextureYStart = 0.13f;
static constexpr float kTexturePadding = 0.45f;
static constexpr float kTextureCircleRadiusPercent = 0.25f;

g.setColour(getParentComponent()->findColour(Skin::kBody, true));
int round_amount = findValue(Skin::kBodyRounding);
g.fillRoundedRectangle(0, 0, getWidth(), getHeight(), round_amount);

if (enable_->getToggleState())
g.setColour(findColour(Skin::kPowerButtonOn, true));
else
g.setColour(findColour(Skin::kPowerButtonOff, true));
g.drawRoundedRectangle(0.5f, 0.5f, getWidth() - 1.0f, getHeight() - 1.0f, round_amount, 1.0f);

g.setFont(Fonts::instance()->proportional_regular().withPointHeight(size_ratio_ * 12.0f));
float text_x = getWidth() * kLeftPadding;
StringArray tokens;
tokens.addTokens(getName(), "_", "");
String text = tokens[0];
text = text.substring(0, 1).toUpperCase() + text.substring(1);
g.drawText(text, text_x, 0, getWidth() - text_x, getHeight(), Justification::centredLeft, true);

float icon_width = kIconSize * getHeight();
float icon_x = getWidth() / 2.0f + (getWidth() / 2.0f - icon_width) / 2.0f;
float icon_y = (getHeight() - icon_width) / 2.0f;
Rectangle<float> icon_bounds(icon_x, icon_y, icon_width, icon_width);
g.fillPath(icon_, icon_.getTransformToScaleToFit(icon_bounds, true));

if (hover_) {
g.setColour(findColour(Skin::kLightenScreen, true).withMultipliedAlpha(1.5f));

int width = getWidth();
int height = getHeight();
float spacing = width * (1.0f - 2.0f * kTexturePadding) / (kTextureColumns - 1.0f);
float radius = spacing * kTextureCircleRadiusPercent;
float x = width * kTexturePadding;
float y = height * kTextureYStart;
for (int c = 0; c < kTextureColumns; ++c) {
for (int r = 0; r < kTextureRows; ++r) {
g.fillEllipse(x + spacing * c - radius, y + spacing * r - radius, 2.0f * radius, 2.0f * radius);
g.fillEllipse(x + spacing * c - radius, height - y - spacing * r - radius, 2.0f * radius, 2.0f * radius);
}
}
}
}

void DraggableEffect::resized() {
int width = getTitleWidth();
enable_->setBounds(0, 0, width, width);
background_->redrawImage(true);
}

void DraggableEffect::buttonClicked(Button* clicked_button) {
for (Listener* listener : listeners_)
listener->effectEnabledChanged(this, clicked_button->getToggleState());

background_->redrawImage(true);
SynthSection::buttonClicked(clicked_button);
}

void DraggableEffect::hover(bool hover) {
if (hover_ != hover) {
hover_ = hover;
background_->redrawImage(true);
}
}

DragDropEffectOrder::DragDropEffectOrder(String name) : SynthSection(name) {
currently_dragged_ = nullptr;
currently_hovered_ = nullptr;
last_dragged_index_ = 0;
mouse_down_y_ = 0;
dragged_starting_y_ = 0;
for (int i = 0; i < vital::constants::kNumEffects; ++i) {
effect_order_[i] = i;
effect_list_.push_back(std::make_unique<DraggableEffect>(strings::kEffectOrder[i], i));
addSubSection(effect_list_[i].get());
effect_list_[i]->addListener(this);
effect_list_[i]->setSkinOverride(static_cast<Skin::SectionOverride>(Skin::kAllEffects + 1 + i));
}
}

DragDropEffectOrder::~DragDropEffectOrder() { }

void DragDropEffectOrder::resized() {
float padding = size_ratio_ * kEffectPadding;

for (int i = 0; i < vital::constants::kNumEffects; ++i) {
Component* effect = getEffect(i);
int from_y = getEffectY(i);
int to_y = getEffectY(i + 1);
effect->setBounds(0, from_y, getWidth(), to_y - from_y - padding);
}
}

void DragDropEffectOrder::paintBackground(Graphics& g) {
static constexpr float kConnectionWidth = 0.1f;
g.setColour(findColour(Skin::kLightenScreen, true));
int width = getWidth() * kConnectionWidth;
int center = getWidth() / 2.0f + findValue(Skin::kWidgetRoundedCorner);
g.fillRect(center - width / 2, 0, width, getHeight());
}

void DragDropEffectOrder::renderOpenGlComponents(OpenGlWrapper& open_gl, bool animate) {
SynthSection::renderOpenGlComponents(open_gl, animate);

DraggableEffect* current = currently_dragged_;
if (current)
current->renderOpenGlComponents(open_gl, animate);
}

void DragDropEffectOrder::mouseMove(const MouseEvent& e) {
int current_index = getEffectIndexFromY(e.y);
DraggableEffect* hover = effect_list_[effect_order_[current_index]].get();
if (hover != currently_hovered_) {
if (currently_hovered_)
currently_hovered_->hover(false);
if (hover)
hover->hover(true);
currently_hovered_ = hover;
}
}

void DragDropEffectOrder::mouseDown(const MouseEvent& e) {
mouse_down_y_ = e.y;
last_dragged_index_ = getEffectIndexFromY(e.y);
currently_dragged_ = effect_list_[effect_order_[last_dragged_index_]].get();
dragged_starting_y_ = currently_dragged_->getY();

currently_dragged_->setAlwaysOnTop(true);
}

void DragDropEffectOrder::mouseDrag(const MouseEvent& e) {
if (currently_dragged_ == nullptr)
return;

int delta_y = e.y - mouse_down_y_;
int clamped_y = vital::utils::iclamp(dragged_starting_y_ + delta_y, 0,
getHeight() - currently_dragged_->getHeight());
currently_dragged_->setTopLeftPosition(currently_dragged_->getX(), clamped_y);

int next_index = getEffectIndexFromY(e.y);
if (next_index != last_dragged_index_) {
moveEffect(last_dragged_index_, next_index);
last_dragged_index_ = next_index;
}
}

void DragDropEffectOrder::mouseUp(const MouseEvent& e) {
if (currently_dragged_)
currently_dragged_->setAlwaysOnTop(false);

currently_dragged_ = nullptr;
setStationaryEffectPosition(last_dragged_index_);
}

void DragDropEffectOrder::mouseExit(const MouseEvent& e) {
if (currently_hovered_) {
currently_hovered_->hover(false);
currently_hovered_ = nullptr;
}
}

void DragDropEffectOrder::effectEnabledChanged(DraggableEffect* effect, bool enabled) {
for (Listener* listener : listeners_)
listener->effectEnabledChanged(effect->order(), enabled);
}

void DragDropEffectOrder::setAllValues(vital::control_map& controls) {
SynthSection::setAllValues(controls);
float order = controls[getName().toStdString()]->value();
vital::utils::decodeFloatToOrder(effect_order_, order, vital::constants::kNumEffects);

for (int i = 0; i < vital::constants::kNumEffects; ++i)
setStationaryEffectPosition(i);

for (Listener* listener : listeners_)
listener->orderChanged(this);
}

void DragDropEffectOrder::moveEffect(int start_index, int end_index) {
int delta_index = end_index - start_index;
if (delta_index == 0)
return;

int moving = effect_order_[start_index];

int direction = delta_index / abs(delta_index);
for (int i = start_index; i != end_index; i += direction) {
effect_order_[i] = effect_order_[i + direction];
setStationaryEffectPosition(i);
}

effect_order_[end_index] = moving;

float order = vital::utils::encodeOrderToFloat(effect_order_, vital::constants::kNumEffects);
SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();
if (parent)
parent->getSynth()->valueChangedInternal(getName().toStdString(), order);

for (Listener* listener : listeners_)
listener->orderChanged(this);
}

void DragDropEffectOrder::setStationaryEffectPosition(int index) {
float padding = size_ratio_ * kEffectPadding;
Component* effect = getEffect(index);
int from_y = getEffectY(index);
int to_y = getEffectY(index + 1);
effect->setBounds(0, from_y, getWidth(), to_y - from_y - padding);
}

int DragDropEffectOrder::getEffectIndex(int index) const {
int i = vital::utils::iclamp(index, 0, vital::constants::kNumEffects - 1);
return effect_order_[i];
}

Component* DragDropEffectOrder::getEffect(int index) const {
return effect_list_[getEffectIndex(index)].get();
}

bool DragDropEffectOrder::effectEnabled(int index) const {
return effect_list_[getEffectIndex(index)]->enabled();
}

int DragDropEffectOrder::getEffectIndexFromY(float y) const {
float padding = size_ratio_ * kEffectPadding;
int index = vital::constants::kNumEffects * (y + padding / 2.0f) / (getHeight() + padding);
return vital::utils::iclamp(index, 0, vital::constants::kNumEffects - 1);
}

int DragDropEffectOrder::getEffectY(int index) const {
int padding = size_ratio_ * kEffectPadding;
return std::round((1.0f * index * (getHeight() + padding)) / vital::constants::kNumEffects);
}

+ 111
- 0
ports/vitalium/source/interface/editor_components/drag_drop_effect_order.h View File

@@ -0,0 +1,111 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "synth_section.h"
#include "synth_constants.h"
#include "synth_button.h"
#include "synth_slider.h"
#include "open_gl_image_component.h"

class DraggableEffect : public SynthSection {
public:
class Listener {
public:
virtual ~Listener() { }
virtual void effectEnabledChanged(DraggableEffect* effect, bool enabled) = 0;
};

DraggableEffect(const String& name, int order);
void paint(Graphics& g) override;
void paintBackground(Graphics& g) override { }

void renderOpenGlComponents(OpenGlWrapper& open_gl, bool animate) override {
SynthSection::renderOpenGlComponents(open_gl, animate);
}

void resized() override;
void buttonClicked(Button* clicked_button) override;
void addListener(Listener* listener) { listeners_.push_back(listener); }
bool enabled() const { return enable_->getToggleState(); }
int order() const { return order_; }

void hover(bool hover);

private:
Path icon_;
int order_;
bool hover_;
std::unique_ptr<SynthButton> enable_;
std::unique_ptr<OpenGlImageComponent> background_;
std::vector<Listener*> listeners_;
};

class DragDropEffectOrder : public SynthSection, public DraggableEffect::Listener {
public:
static constexpr int kEffectPadding = 6;

class Listener {
public:
virtual ~Listener() { }
virtual void orderChanged(DragDropEffectOrder* order) = 0;
virtual void effectEnabledChanged(int order_index, bool enabled) = 0;
};

DragDropEffectOrder(String name);
virtual ~DragDropEffectOrder();

void resized() override;
void paintBackground(Graphics& g) override;

void renderOpenGlComponents(OpenGlWrapper& open_gl, bool animate) override;

void mouseMove(const MouseEvent& e) override;
void mouseDown(const MouseEvent& e) override;
void mouseDrag(const MouseEvent& e) override;
void mouseUp(const MouseEvent& e) override;
void mouseExit(const MouseEvent& e) override;

void effectEnabledChanged(DraggableEffect* effect, bool enabled) override;
void setAllValues(vital::control_map& controls) override;

void moveEffect(int start_index, int end_index);
void setStationaryEffectPosition(int index);
void addListener(Listener* listener) { listeners_.push_back(listener); }

int getEffectIndex(int index) const;
Component* getEffect(int index) const;
bool effectEnabled(int index) const;
int getEffectIndexFromY(float y) const;

private:
int getEffectY(int index) const;

std::vector<Listener*> listeners_;
DraggableEffect* currently_dragged_;
DraggableEffect* currently_hovered_;

int last_dragged_index_;
int mouse_down_y_;
int dragged_starting_y_;
std::vector<std::unique_ptr<DraggableEffect>> effect_list_;
int effect_order_[vital::constants::kNumEffects];

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DragDropEffectOrder)
};


+ 1120
- 0
ports/vitalium/source/interface/editor_components/envelope_editor.cpp
File diff suppressed because it is too large
View File


+ 230
- 0
ports/vitalium/source/interface/editor_components/envelope_editor.h View File

@@ -0,0 +1,230 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "skin.h"
#include "open_gl_image.h"
#include "open_gl_line_renderer.h"
#include "synth_module.h"
#include "synth_slider.h"

class EnvelopeEditor : public OpenGlLineRenderer, public SynthSlider::SliderListener {
public:
static constexpr float kMarkerWidth = 9.0f;
static constexpr float kRingThickness = 0.45f;
static constexpr float kPowerMarkerWidth = 7.0f;
static constexpr float kMarkerHoverRadius = 12.0f;
static constexpr float kMarkerGrabRadius = 20.0f;
static constexpr float kTailDecay = 0.965f;
static constexpr float kPaddingX = 0.018f;
static constexpr float kPaddingY = 0.06f;
static constexpr float kMinPointDistanceForPower = 3.0f;
static constexpr float kPowerMouseMultiplier = 0.06f;
static constexpr float kTimeDisplaySize = 0.05f;

static constexpr int kRulerDivisionSize = 4;
static constexpr int kMaxGridLines = 36;
static constexpr int kMaxTimesShown = 24;
static constexpr int kNumPointsPerSection = 98;
static constexpr int kNumSections = 4;
static constexpr int kTotalPoints = kNumSections * kNumPointsPerSection + 1;

EnvelopeEditor(const String& prefix,
const vital::output_map& mono_modulations, const vital::output_map& poly_modulations);
~EnvelopeEditor();

void paintBackground(Graphics& g) override;

void resized() override {
OpenGlLineRenderer::resized();
drag_circle_.setBounds(getLocalBounds());
hover_circle_.setBounds(getLocalBounds());
grid_lines_.setBounds(getLocalBounds());
sub_grid_lines_.setBounds(getLocalBounds());
position_circle_.setBounds(getLocalBounds());
point_circles_.setBounds(getLocalBounds());
power_circles_.setBounds(getLocalBounds());
float font_height = kTimeDisplaySize * getHeight();
for (int i = 0; i < kMaxTimesShown; ++i)
times_[i]->setTextSize(font_height);
setTimePositions();
reset_positions_ = true;
}

void resetEnvelopeLine(int index);
void guiChanged(SynthSlider* slider) override;

void setDelaySlider(SynthSlider* delay_slider);
void setAttackSlider(SynthSlider* attack_slider);
void setAttackPowerSlider(SynthSlider* attack_slider);
void setHoldSlider(SynthSlider* hold_slider);
void setDecaySlider(SynthSlider* decay_slider);
void setDecayPowerSlider(SynthSlider* decay_slider);
void setSustainSlider(SynthSlider* sustain_slider);
void setReleaseSlider(SynthSlider* release_slider);
void setReleasePowerSlider(SynthSlider* release_slider);
void setSizeRatio(float ratio) { size_ratio_ = ratio; }

void parentHierarchyChanged() override;
void pickHoverPosition(Point<float> position);
void mouseMove(const MouseEvent& e) override;
void mouseExit(const MouseEvent& e) override;
void mouseDown(const MouseEvent& e) override;
void mouseDrag(const MouseEvent& e) override;
void mouseDoubleClick(const MouseEvent& e) override;
void mouseUp(const MouseEvent& e) override;
void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override;

void magnifyZoom(Point<float> delta);
void magnifyReset();

void init(OpenGlWrapper& open_gl) override;
void render(OpenGlWrapper& open_gl, bool animate) override;
void destroy(OpenGlWrapper& open_gl) override;
void resetPositions() { reset_positions_ = true; }

private:
void setEditingCircleBounds();
void setGridPositions();
void setTimePositions();
void setPointPositions();
void setGlPositions();
void setColors();
void zoom(float amount);

static std::pair<vital::Output*, vital::Output*> getOutputs(const vital::output_map& mono_modulations,
const vital::output_map& poly_modulations,
const String& name) {
return {
mono_modulations.at(name.toStdString()),
poly_modulations.at(name.toStdString())
};
}
vital::poly_float getOutputsTotal(std::pair<vital::Output*, vital::Output*> outputs,
vital::poly_float default_value);
void drawPosition(OpenGlWrapper& open_gl, int index);
std::pair<float, float> getPosition(int index);

float padX(float x);
float padY(float y);
float unpadX(float x);
float unpadY(float y);
float padOpenGlX(float x);
float padOpenGlY(float y);

float getSliderDelayX();
float getSliderAttackX();
float getSliderHoldX();
float getSliderDecayX();
float getSliderSustainY();
float getSliderReleaseX();
float getDelayTime(int index);
float getAttackTime(int index);
float getHoldTime(int index);
float getDecayTime(int index);
float getReleaseTime(int index);
float getDelayX(int index);
float getAttackX(int index);
float getHoldX(int index);
float getDecayX(int index);
float getSustainY(int index);
float getReleaseX(int index);

float getBackupPhase(float phase, int index);
vital::poly_float getBackupPhase(vital::poly_float phase);
float getEnvelopeValue(float t, float power, float start, float end);
float getSliderAttackValue(float t);
float getSliderDecayValue(float t);
float getSliderReleaseValue(float t);
float getAttackValue(float t, int index);
float getDecayValue(float t, int index);
float getReleaseValue(float t, int index);

void setDelayX(float x);
void setAttackX(float x);
void setHoldX(float x);
void setDecayX(float x);
void setSustainY(float y);
void setReleaseX(float x);

void setPower(SynthSlider* slider, float power);
void setAttackPower(float power);
void setDecayPower(float power);
void setReleasePower(float power);

SynthGuiInterface* parent_;
bool delay_hover_;
bool attack_hover_;
bool hold_hover_;
bool sustain_hover_;
bool release_hover_;
bool attack_power_hover_;
bool decay_power_hover_;
bool release_power_hover_;
bool mouse_down_;
Point<float> last_edit_position_;

bool animate_;
float size_ratio_;
float window_time_;

vital::poly_float current_position_alpha_;
vital::poly_float last_phase_;

Colour line_left_color_;
Colour line_right_color_;
Colour line_center_color_;
Colour fill_left_color_;
Colour fill_right_color_;
Colour background_color_;
Colour time_color_;

bool reset_positions_;
OpenGlQuad drag_circle_;
OpenGlQuad hover_circle_;
OpenGlMultiQuad grid_lines_;
OpenGlMultiQuad sub_grid_lines_;
OpenGlQuad position_circle_;
OpenGlMultiQuad point_circles_;
OpenGlMultiQuad power_circles_;
std::unique_ptr<PlainTextComponent> times_[kMaxTimesShown];

const vital::StatusOutput* envelope_phase_;

SynthSlider* delay_slider_;
SynthSlider* attack_slider_;
SynthSlider* hold_slider_;
SynthSlider* attack_power_slider_;
SynthSlider* decay_slider_;
SynthSlider* decay_power_slider_;
SynthSlider* sustain_slider_;
SynthSlider* release_slider_;
SynthSlider* release_power_slider_;

std::pair<vital::Output*, vital::Output*> delay_outputs_;
std::pair<vital::Output*, vital::Output*> attack_outputs_;
std::pair<vital::Output*, vital::Output*> hold_outputs_;
std::pair<vital::Output*, vital::Output*> decay_outputs_;
std::pair<vital::Output*, vital::Output*> sustain_outputs_;
std::pair<vital::Output*, vital::Output*> release_outputs_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EnvelopeEditor)
};

+ 581
- 0
ports/vitalium/source/interface/editor_components/equalizer_response.cpp View File

@@ -0,0 +1,581 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "equalizer_response.h"

#include "shaders.h"
#include "skin.h"
#include "utils.h"

EqualizerResponse::EqualizerResponse() : OpenGlLineRenderer(kResolution),
unselected_points_(2, Shaders::kRingFragment),
selected_point_(Shaders::kCircleFragment),
dragging_point_(Shaders::kCircleFragment), shader_(nullptr) {
unselected_points_.setThickness(1.0f);
setFill(true);

addAndMakeVisible(unselected_points_);
addAndMakeVisible(selected_point_);
addAndMakeVisible(dragging_point_);

low_cutoff_ = nullptr;
low_resonance_ = nullptr;
low_gain_ = nullptr;
band_cutoff_ = nullptr;
band_resonance_ = nullptr;
band_gain_ = nullptr;
high_cutoff_ = nullptr;
high_resonance_ = nullptr;
high_gain_ = nullptr;
low_cutoff_output_ = nullptr;
low_resonance_output_ = nullptr;
low_gain_output_ = nullptr;
band_cutoff_output_ = nullptr;
band_resonance_output_ = nullptr;
band_gain_output_ = nullptr;
high_cutoff_output_ = nullptr;
high_resonance_output_ = nullptr;
high_gain_output_ = nullptr;

current_cutoff_ = nullptr;
current_gain_ = nullptr;

low_filter_.setSampleRate(kViewSampleRate);
band_filter_.setSampleRate(kViewSampleRate);
high_filter_.setSampleRate(kViewSampleRate);
low_filter_.setDriveCompensation(false);
high_filter_.setDriveCompensation(false);

active_ = false;
high_pass_ = false;
notch_ = false;
low_pass_ = false;
animate_ = true;
draw_frequency_lines_ = true;
selected_band_ = 0;

line_data_ = std::make_unique<float[]>(kResolution);
line_buffer_ = 0;
response_buffer_ = 0;
vertex_array_object_ = 0;

for (int i = 0; i < kResolution; ++i) {
float t = i / (kResolution - 1.0f);
line_data_[i] = 2.0f * t - 1.0f;
}
db_buffer_ratio_ = kDefaultDbBufferRatio;
min_db_ = 0.0f;
max_db_ = 1.0f;
}

EqualizerResponse::~EqualizerResponse() = default;

void EqualizerResponse::initEq(const vital::output_map& mono_modulations) {
low_cutoff_output_ = mono_modulations.at("eq_low_cutoff");
low_resonance_output_ = mono_modulations.at("eq_low_resonance");
low_gain_output_ = mono_modulations.at("eq_low_gain");
band_cutoff_output_ = mono_modulations.at("eq_band_cutoff");
band_resonance_output_ = mono_modulations.at("eq_band_resonance");
band_gain_output_ = mono_modulations.at("eq_band_gain");
high_cutoff_output_ = mono_modulations.at("eq_high_cutoff");
high_resonance_output_ = mono_modulations.at("eq_high_resonance");
high_gain_output_ = mono_modulations.at("eq_high_gain");
}

void EqualizerResponse::initReverb(const vital::output_map& mono_modulations) {
low_cutoff_output_ = mono_modulations.at("reverb_low_shelf_cutoff");
low_gain_output_ = mono_modulations.at("reverb_low_shelf_gain");
high_cutoff_output_ = mono_modulations.at("reverb_high_shelf_cutoff");
high_gain_output_ = mono_modulations.at("reverb_high_shelf_gain");
}

void EqualizerResponse::init(OpenGlWrapper& open_gl) {
OpenGlLineRenderer::init(open_gl);

unselected_points_.init(open_gl);
selected_point_.init(open_gl);
dragging_point_.init(open_gl);

open_gl.context.extensions.glGenVertexArrays(1, &vertex_array_object_);
open_gl.context.extensions.glBindVertexArray(vertex_array_object_);

GLsizeiptr vert_size = static_cast<GLsizeiptr>(kResolution * sizeof(float));
open_gl.context.extensions.glGenBuffers(1, &line_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, line_buffer_);
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, line_data_.get(), GL_STATIC_DRAW);

open_gl.context.extensions.glGenBuffers(1, &response_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, response_buffer_);
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, nullptr, GL_STATIC_READ);

const GLchar* varyings[] = { "response_out" };
shader_ = open_gl.shaders->getShaderProgram(Shaders::kEqFilterResponseVertex, Shaders::kColorFragment, varyings);
shader_->use();

position_attribute_ = getAttribute(open_gl, *shader_, "position");
midi_cutoff_uniform_ = getUniform(open_gl, *shader_, "midi_cutoff");
resonance_uniform_ = getUniform(open_gl, *shader_, "resonance");
low_amount_uniform_ = getUniform(open_gl, *shader_, "low_amount");
band_amount_uniform_ = getUniform(open_gl, *shader_, "band_amount");
high_amount_uniform_ = getUniform(open_gl, *shader_, "high_amount");
}

void EqualizerResponse::drawResponse(OpenGlWrapper& open_gl, int index) {
glEnable(GL_BLEND);
setLineWidth(findValue(Skin::kWidgetLineWidth));
setFillCenter(1.0f - 2.0f * max_db_ / (max_db_ - min_db_));

Colour color_line = findColour(Skin::kWidgetPrimary1, true);
Colour color_fill_to = findColour(Skin::kWidgetSecondary1, true);

if (!active_) {
color_line = findColour(Skin::kWidgetPrimaryDisabled, true);
color_fill_to = findColour(Skin::kWidgetSecondaryDisabled, true);
}
else if (index) {
color_line = findColour(Skin::kWidgetPrimary2, true);
color_fill_to = findColour(Skin::kWidgetSecondary2, true);
}

setColor(color_line);
float fill_fade = findValue(Skin::kWidgetFillFade);
setFillColors(color_fill_to.withMultipliedAlpha(1.0f - fill_fade), color_fill_to);

shader_->use();
open_gl.context.extensions.glBindVertexArray(vertex_array_object_);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, line_buffer_);
open_gl.context.extensions.glVertexAttribPointer(position_attribute_->attributeID, 1, GL_FLOAT, GL_FALSE,
sizeof(float), nullptr);
open_gl.context.extensions.glEnableVertexAttribArray(position_attribute_->attributeID);
open_gl.context.extensions.glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, response_buffer_);

midi_cutoff_uniform_->set(low_filter_.getMidiCutoff()[index],
band_filter_.getMidiCutoff()[index],
high_filter_.getMidiCutoff()[index]);
resonance_uniform_->set(low_filter_.getResonance()[index],
band_filter_.getResonance()[index],
high_filter_.getResonance()[index]);

low_amount_uniform_->set(low_filter_.getLowAmount()[index],
band_filter_.getLowAmount()[index],
high_filter_.getLowAmount()[index]);
band_amount_uniform_->set(low_filter_.getBandAmount()[index],
band_filter_.getBandAmount()[index],
high_filter_.getBandAmount()[index]);
high_amount_uniform_->set(low_filter_.getHighAmount()[index],
band_filter_.getHighAmount()[index],
high_filter_.getHighAmount()[index]);

open_gl.context.extensions.glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, kResolution);
open_gl.context.extensions.glEndTransformFeedback();

void* buffer = open_gl.context.extensions.glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
kResolution * sizeof(float), GL_MAP_READ_BIT);

float* response_data = (float*)buffer;
float width = getWidth();
float range = max_db_ - min_db_;
float y_mult = getHeight() / range;
for (int i = 0; i < kResolution; ++i) {
setXAt(i, i * width / (kResolution - 1.0f));
setYAt(i, (max_db_ - response_data[i]) * y_mult);
}

open_gl.context.extensions.glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);

OpenGlLineRenderer::render(open_gl, animate_);
}

void EqualizerResponse::render(OpenGlWrapper& open_gl, bool animate) {
animate_ = animate;
computeFilterCoefficients();
if (active_ && animate_)
drawResponse(open_gl, 1);
drawResponse(open_gl, 0);

open_gl.context.extensions.glDisableVertexAttribArray(position_attribute_->attributeID);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
open_gl.context.extensions.glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);

checkGlError();

drawControlPoints(open_gl);
renderCorners(open_gl, animate);
}

void EqualizerResponse::destroy(OpenGlWrapper& open_gl) {
OpenGlLineRenderer::destroy(open_gl);

unselected_points_.destroy(open_gl);
selected_point_.destroy(open_gl);
dragging_point_.destroy(open_gl);

open_gl.context.extensions.glDeleteBuffers(1, &line_buffer_);
open_gl.context.extensions.glDeleteBuffers(1, &response_buffer_);
line_buffer_ = 0;
response_buffer_ = 0;

shader_ = nullptr;
position_attribute_ = nullptr;
midi_cutoff_uniform_ = nullptr;
resonance_uniform_ = nullptr;
low_amount_uniform_ = nullptr;
band_amount_uniform_ = nullptr;
high_amount_uniform_ = nullptr;
}

void EqualizerResponse::setControlPointBounds(float selected_x, float selected_y,
float unselected_x1, float unselected_y1,
float unselected_x2, float unselected_y2) {
static constexpr float kHandleRadius = 0.06f;
static constexpr float kDraggingRadius = 0.18f;

float width = getWidth();
float height = getHeight();
float handle_radius = kHandleRadius * height;
float handle_width = handle_radius * 4.0f / width;
float handle_height = handle_radius * 4.0f / height;

float dragging_radius = kDraggingRadius * height;
float dragging_width = dragging_radius * 4.0f / width;
float dragging_height = dragging_radius * 4.0f / height;

selected_point_.setQuad(0, selected_x - handle_width * 0.5f, selected_y - handle_height * 0.5f,
handle_width, handle_height);
dragging_point_.setQuad(0, selected_x - dragging_width * 0.5f, selected_y - dragging_height * 0.5f,
dragging_width, dragging_height);
unselected_points_.setQuad(0, unselected_x1 - handle_width * 0.5f, unselected_y1 - handle_height * 0.5f,
handle_width, handle_height);
unselected_points_.setQuad(1, unselected_x2 - handle_width * 0.5f, unselected_y2 - handle_height * 0.5f,
handle_width, handle_height);
}

void EqualizerResponse::drawControlPoints(OpenGlWrapper& open_gl) {

Point<float> low_position = getLowPosition();
Point<float> band_position = getBandPosition();
Point<float> high_position = getHighPosition();

float width = getWidth();
float height = getHeight();

float low_x = 2.0f * low_position.x / width - 1.0f;
float band_x = 2.0f * band_position.x / width - 1.0f;
float high_x = 2.0f * high_position.x / width - 1.0f;
float low_y = 1.0f - 2.0f * low_position.y / height;
float band_y = 1.0f - 2.0f * band_position.y / height;
float high_y = 1.0f - 2.0f * high_position.y / height;

if (band_cutoff_output_ == nullptr)
band_x = -2.0f;

if (selected_band_ == 0)
setControlPointBounds(low_x, low_y, band_x, band_y, high_x, high_y);
else if (band_cutoff_output_ && selected_band_ == 1)
setControlPointBounds(band_x, band_y, low_x, low_y, high_x, high_y);
else if (selected_band_ == 2)
setControlPointBounds(high_x, high_y, low_x, low_y, band_x, band_y);

dragging_point_.setColor(findColour(Skin::kLightenScreen, true));
if (current_cutoff_ && current_gain_)
dragging_point_.render(open_gl, true);
selected_point_.setColor(findColour(Skin::kWidgetPrimary1, true));
selected_point_.render(open_gl, true);
unselected_points_.setColor(findColour(Skin::kWidgetPrimary1, true));
unselected_points_.render(open_gl, true);
}

void EqualizerResponse::paintBackground(Graphics& g) {
static constexpr int kLineSpacing = 10;

OpenGlLineRenderer::paintBackground(g);
if (!draw_frequency_lines_)
return;

float min_frequency = vital::utils::midiNoteToFrequency(low_cutoff_->getMinimum());
float max_frequency = vital::utils::midiNoteToFrequency(low_cutoff_->getMaximum());

int height = getHeight();
float max_octave = log2f(max_frequency / min_frequency);
g.setColour(findColour(Skin::kLightenScreen, true).withMultipliedAlpha(0.5f));
float frequency = 0.0f;
float increment = 1.0f;

int x = 0;
while (frequency < max_frequency) {
for (int i = 0; i < kLineSpacing; ++i) {
frequency += increment;
float t = log2f(frequency / min_frequency) / max_octave;
x = std::round(t * getWidth());
g.fillRect(x, 0, 1, height);
}
g.fillRect(x, 0, 1, height);
increment *= kLineSpacing;
}
}

void EqualizerResponse::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) {
int band = getHoveredBand(e);

if (band == 0 && low_resonance_)
low_resonance_->mouseWheelMove(e, wheel);
else if (band == 1 && band_resonance_)
band_resonance_->mouseWheelMove(e, wheel);
else if (band == 2 && high_resonance_)
high_resonance_->mouseWheelMove(e, wheel);
else
OpenGlComponent::mouseWheelMove(e, wheel);
}

void EqualizerResponse::mouseDown(const MouseEvent& e) {
selected_band_ = getHoveredBand(e);

if (selected_band_ == 0) {
current_cutoff_ = low_cutoff_;
current_gain_ = low_gain_;
for (Listener* listener : listeners_)
listener->lowBandSelected();
}
else if (selected_band_ == 1) {
current_cutoff_ = band_cutoff_;
current_gain_ = band_gain_;
for (Listener* listener : listeners_)
listener->midBandSelected();
}
else if (selected_band_ == 2) {
current_cutoff_ = high_cutoff_;
current_gain_ = high_gain_;
for (Listener* listener : listeners_)
listener->highBandSelected();
}

OpenGlLineRenderer::mouseDown(e);
}

void EqualizerResponse::mouseDrag(const MouseEvent& e) {
moveFilterSettings(e.position);
OpenGlLineRenderer::mouseDrag(e);
}

void EqualizerResponse::mouseUp(const MouseEvent& e) {
if (current_cutoff_ == nullptr && current_gain_ == nullptr) {
OpenGlLineRenderer::mouseUp(e);
return;
}

current_cutoff_ = nullptr;
current_gain_ = nullptr;
OpenGlLineRenderer::mouseUp(e);
}

void EqualizerResponse::mouseExit(const MouseEvent& e) {
if (low_cutoff_) {
low_cutoff_->hidePopup(true);
low_cutoff_->hidePopup(false);
}
OpenGlLineRenderer::mouseExit(e);
}

int EqualizerResponse::getHoveredBand(const MouseEvent& e) {
const float grab_distance = 0.06f * getWidth();
float delta_low = e.position.getDistanceSquaredFrom(getLowPosition());
float delta_band = e.position.getDistanceSquaredFrom(getBandPosition());
float delta_high = e.position.getDistanceSquaredFrom(getHighPosition());

float min_sqr_dist = grab_distance * grab_distance;
float min = std::min(std::min(min_sqr_dist, delta_low), delta_high);
if (band_cutoff_output_)
min = std::min(min, delta_band);

if (delta_low <= min)
return 0;
if (band_cutoff_output_ && delta_band <= min)
return 1;
if (delta_high <= min)
return 2;

return -1;
}

Point<float> EqualizerResponse::getLowPosition() {
float cutoff_range = low_cutoff_->getMaximum() - low_cutoff_->getMinimum();
float min_cutoff = low_cutoff_->getMinimum();
float gain_range = max_db_ - min_db_;

float low_x = getWidth() * (low_cutoff_->getValue() - min_cutoff) / cutoff_range;
float low_y = getHeight() * (max_db_ - low_gain_->getValue()) / gain_range;
return Point<float>(low_x, low_y);
}

Point<float> EqualizerResponse::getBandPosition() {
if (band_cutoff_ == nullptr)
return Point<float>(0.0f, 0.0f);
float cutoff_range = band_cutoff_->getMaximum() - band_cutoff_->getMinimum();
float min_cutoff = band_cutoff_->getMinimum();
float gain_range = max_db_ - min_db_;

float band_x = getWidth() * (band_cutoff_->getValue() - min_cutoff) / cutoff_range;
float band_y = getHeight() * (max_db_ - band_gain_->getValue()) / gain_range;
return Point<float>(band_x, band_y);
}

Point<float> EqualizerResponse::getHighPosition() {
float cutoff_range = high_cutoff_->getMaximum() - high_cutoff_->getMinimum();
float min_cutoff = high_cutoff_->getMinimum();
float gain_range = max_db_ - min_db_;

float high_x = getWidth() * (high_cutoff_->getValue() - min_cutoff) / cutoff_range;
float high_y = getHeight() * (max_db_ - high_gain_->getValue()) / gain_range;
return Point<float>(high_x, high_y);
}

void EqualizerResponse::computeFilterCoefficients() {
low_filter_state_.midi_cutoff = getOutputTotal(low_cutoff_output_, low_cutoff_);
low_filter_state_.resonance_percent = getOutputTotal(low_resonance_output_, low_resonance_);
low_filter_state_.gain = getOutputTotal(low_gain_output_, low_gain_);
if (high_pass_) {
low_filter_state_.style = vital::DigitalSvf::k12Db;
low_filter_state_.pass_blend = 2.0f;
}
else {
low_filter_state_.style = vital::DigitalSvf::kShelving;
low_filter_state_.pass_blend = 0.0f;
}
low_filter_.setupFilter(low_filter_state_);

band_filter_state_.midi_cutoff = getOutputTotal(band_cutoff_output_, band_cutoff_);
band_filter_state_.resonance_percent = getOutputTotal(band_resonance_output_, band_resonance_);
band_filter_state_.gain = getOutputTotal(band_gain_output_, band_gain_);
band_filter_state_.pass_blend = 1.0f;
if (notch_)
band_filter_state_.style = vital::DigitalSvf::kNotchPassSwap;
else
band_filter_state_.style = vital::DigitalSvf::kShelving;
band_filter_.setupFilter(band_filter_state_);
high_filter_state_.midi_cutoff = getOutputTotal(high_cutoff_output_, high_cutoff_);
high_filter_state_.resonance_percent = getOutputTotal(high_resonance_output_, high_resonance_);
high_filter_state_.gain = getOutputTotal(high_gain_output_, high_gain_);
if (low_pass_) {
high_filter_state_.style = vital::DigitalSvf::k12Db;
high_filter_state_.pass_blend = 0.0f;
}
else {
high_filter_state_.style = vital::DigitalSvf::kShelving;
high_filter_state_.pass_blend = 2.0f;
}

high_filter_.setupFilter(high_filter_state_);
}

void EqualizerResponse::moveFilterSettings(Point<float> position) {
if (current_cutoff_) {
float ratio = vital::utils::clamp(position.x / getWidth(), 0.0f, 1.0f);
float min = current_cutoff_->getMinimum();
float max = current_cutoff_->getMaximum();
float new_cutoff = ratio * (max - min) + min;
current_cutoff_->showPopup(true);
current_cutoff_->setValue(new_cutoff);
}
if (current_gain_) {
float local_position = position.y - 0.5f * db_buffer_ratio_ * getHeight();
float ratio = vital::utils::clamp(local_position / ((1.0f - db_buffer_ratio_) * getHeight()), 0.0f, 1.0f);
float min = current_gain_->getMinimum();
float max = current_gain_->getMaximum();
float new_db = ratio * (min - max) + max;
current_gain_->setValue(new_db);
current_gain_->showPopup(false);
}
else
low_gain_->hidePopup(false);
}

void EqualizerResponse::setLowSliders(SynthSlider *cutoff,
SynthSlider *resonance, SynthSlider *gain) {
float buffer = gain->getRange().getLength() * db_buffer_ratio_;
min_db_ = gain->getMinimum() - buffer;
max_db_ = gain->getMaximum() + buffer;
low_cutoff_ = cutoff;
low_resonance_ = resonance;
low_gain_ = gain;
low_cutoff_->addSliderListener(this);
if (low_resonance_)
low_resonance_->addSliderListener(this);
low_gain_->addSliderListener(this);
repaint();
}

void EqualizerResponse::setBandSliders(SynthSlider *cutoff,
SynthSlider *resonance, SynthSlider *gain) {
band_cutoff_ = cutoff;
band_resonance_ = resonance;
band_gain_ = gain;
band_cutoff_->addSliderListener(this);
if (band_resonance_)
band_resonance_->addSliderListener(this);
band_gain_->addSliderListener(this);
repaint();
}

void EqualizerResponse::setHighSliders(SynthSlider *cutoff,
SynthSlider *resonance, SynthSlider *gain) {
high_cutoff_ = cutoff;
high_resonance_ = resonance;
high_gain_ = gain;
high_cutoff_->addSliderListener(this);
if (high_resonance_)
high_resonance_->addSliderListener(this);
high_gain_->addSliderListener(this);
repaint();
}

void EqualizerResponse::setSelectedBand(int selected_band) {
selected_band_ = selected_band;
repaint();
}

void EqualizerResponse::setActive(bool active) {
active_ = active;
repaint();
}

void EqualizerResponse::setHighPass(bool high_pass) {
high_pass_ = high_pass;
repaint();
}

void EqualizerResponse::setNotch(bool notch) {
notch_ = notch;
repaint();
}

void EqualizerResponse::setLowPass(bool low_pass) {
low_pass_ = low_pass;
repaint();
}

vital::poly_float EqualizerResponse::getOutputTotal(vital::Output* output, Slider* slider) {
if (output == nullptr || slider == nullptr)
return 0.0f;
if (!active_ || !animate_ || !output->owner->enabled())
return slider->getValue();
return output->trigger_value;
}

+ 162
- 0
ports/vitalium/source/interface/editor_components/equalizer_response.h View File

@@ -0,0 +1,162 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"
#include "digital_svf.h"
#include "open_gl_line_renderer.h"
#include "open_gl_multi_quad.h"
#include "synth_slider.h"

class EqualizerResponse : public OpenGlLineRenderer, SynthSlider::SliderListener {
public:
static constexpr int kResolution = 128;
static constexpr int kViewSampleRate = 100000;
static constexpr float kDefaultDbBufferRatio = 0.2f;
static constexpr float kMouseMultiplier = 0.3f;

class Listener {
public:
virtual ~Listener() = default;
virtual void lowBandSelected() = 0;
virtual void midBandSelected() = 0;
virtual void highBandSelected() = 0;
};

EqualizerResponse();
~EqualizerResponse();
void initEq(const vital::output_map& mono_modulations);
void initReverb(const vital::output_map& mono_modulations);

virtual void init(OpenGlWrapper& open_gl) override;
virtual void render(OpenGlWrapper& open_gl, bool animate) override;
virtual void destroy(OpenGlWrapper& open_gl) override;

void setControlPointBounds(float selected_x, float selected_y,
float unselected_x1, float unselected_y1,
float unselected_x2, float unselected_y2);
void drawControlPoints(OpenGlWrapper& open_gl);
void drawResponse(OpenGlWrapper& open_gl, int index);

void computeFilterCoefficients();
void moveFilterSettings(Point<float> position);

void setLowSliders(SynthSlider* cutoff, SynthSlider* resonance, SynthSlider* gain);
void setBandSliders(SynthSlider* cutoff, SynthSlider* resonance, SynthSlider* gain);
void setHighSliders(SynthSlider* cutoff, SynthSlider* resonance, SynthSlider* gain);
void setSelectedBand(int selected_band);

Point<float> getLowPosition();
Point<float> getBandPosition();
Point<float> getHighPosition();

void resized() override {
OpenGlLineRenderer::resized();

unselected_points_.setBounds(getLocalBounds());
selected_point_.setBounds(getLocalBounds());
dragging_point_.setBounds(getLocalBounds());
}

void paintBackground(Graphics& g) override;
void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override;
void mouseDown(const MouseEvent& e) override;
void mouseDrag(const MouseEvent& e) override;
void mouseUp(const MouseEvent& e) override;
void mouseExit(const MouseEvent& e) override;

int getHoveredBand(const MouseEvent& e);

void setActive(bool active);
void setHighPass(bool high_pass);
void setNotch(bool notch);
void setLowPass(bool high_pass);
void setDbBufferRatio(float ratio) { db_buffer_ratio_ = ratio; }
void setDrawFrequencyLines(bool draw_lines) { draw_frequency_lines_ = draw_lines; }

void addListener(Listener* listener) { listeners_.push_back(listener); }

private:
vital::poly_float getOutputTotal(vital::Output* output, Slider* slider);

int resolution_;
bool active_;
bool high_pass_;
bool notch_;
bool low_pass_;
bool animate_;
bool draw_frequency_lines_;
int selected_band_;
float db_buffer_ratio_;
float min_db_;
float max_db_;

OpenGlMultiQuad unselected_points_;
OpenGlQuad selected_point_;
OpenGlQuad dragging_point_;

vital::DigitalSvf low_filter_;
vital::DigitalSvf band_filter_;
vital::DigitalSvf high_filter_;

vital::SynthFilter::FilterState low_filter_state_;
vital::SynthFilter::FilterState band_filter_state_;
vital::SynthFilter::FilterState high_filter_state_;

SynthSlider* low_cutoff_;
SynthSlider* low_resonance_;
SynthSlider* low_gain_;
SynthSlider* band_cutoff_;
SynthSlider* band_resonance_;
SynthSlider* band_gain_;
SynthSlider* high_cutoff_;
SynthSlider* high_resonance_;
SynthSlider* high_gain_;

vital::Output* low_cutoff_output_;
vital::Output* low_resonance_output_;
vital::Output* low_gain_output_;
vital::Output* band_cutoff_output_;
vital::Output* band_resonance_output_;
vital::Output* band_gain_output_;
vital::Output* high_cutoff_output_;
vital::Output* high_resonance_output_;
vital::Output* high_gain_output_;

SynthSlider* current_cutoff_;
SynthSlider* current_gain_;

std::unique_ptr<float[]> line_data_;
OpenGLShaderProgram* shader_;
std::unique_ptr<OpenGLShaderProgram::Attribute> position_attribute_;

std::unique_ptr<OpenGLShaderProgram::Uniform> midi_cutoff_uniform_;
std::unique_ptr<OpenGLShaderProgram::Uniform> resonance_uniform_;
std::unique_ptr<OpenGLShaderProgram::Uniform> low_amount_uniform_;
std::unique_ptr<OpenGLShaderProgram::Uniform> band_amount_uniform_;
std::unique_ptr<OpenGLShaderProgram::Uniform> high_amount_uniform_;

GLuint vertex_array_object_;
GLuint line_buffer_;
GLuint response_buffer_;
std::vector<Listener*> listeners_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EqualizerResponse)
};


+ 635
- 0
ports/vitalium/source/interface/editor_components/filter_response.cpp View File

@@ -0,0 +1,635 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "filter_response.h"

#include "comb_filter.h"
#include "skin.h"
#include "synth_constants.h"
#include "shaders.h"
#include "synth_slider.h"
#include "utils.h"

namespace {
FilterResponse::FilterShader getShaderForModel(vital::constants::FilterModel model, int style) {
switch (model) {
case vital::constants::kAnalog:
return FilterResponse::kAnalog;
case vital::constants::kComb: {
vital::CombFilter::FeedbackStyle feedback_style = vital::CombFilter::getFeedbackStyle(style);
if (feedback_style == vital::CombFilter::kComb)
return FilterResponse::kComb;
if (feedback_style == vital::CombFilter::kPositiveFlange)
return FilterResponse::kPositiveFlange;
return FilterResponse::kNegativeFlange;
}
case vital::constants::kDiode:
return FilterResponse::kDiode;
case vital::constants::kDirty:
return FilterResponse::kDirty;
case vital::constants::kLadder:
return FilterResponse::kLadder;
case vital::constants::kPhase:
return FilterResponse::kPhase;
case vital::constants::kFormant:
return FilterResponse::kFormant;
case vital::constants::kDigital:
return FilterResponse::kDigital;
default:
VITAL_ASSERT(false);
return FilterResponse::kNumFilterShaders;
}
}

Shaders::VertexShader getVertexShader(FilterResponse::FilterShader shader) {
switch (shader) {
case FilterResponse::kAnalog:
return Shaders::kAnalogFilterResponseVertex;
case FilterResponse::kComb:
return Shaders::kCombFilterResponseVertex;
case FilterResponse::kPositiveFlange:
return Shaders::kPositiveFlangeFilterResponseVertex;
case FilterResponse::kNegativeFlange:
return Shaders::kNegativeFlangeFilterResponseVertex;
case FilterResponse::kDiode:
return Shaders::kDiodeFilterResponseVertex;
case FilterResponse::kDirty:
return Shaders::kDirtyFilterResponseVertex;
case FilterResponse::kLadder:
return Shaders::kLadderFilterResponseVertex;
case FilterResponse::kPhase:
return Shaders::kPhaserFilterResponseVertex;
case FilterResponse::kFormant:
return Shaders::kFormantFilterResponseVertex;
case FilterResponse::kDigital:
return Shaders::kDigitalFilterResponseVertex;
default:
VITAL_ASSERT(false);
return Shaders::kNumVertexShaders;
}
}

std::pair<vital::Output*, vital::Output*> getOutputs(const vital::output_map& mono_modulations,
const std::string& name) {
return {
mono_modulations.at(name),
nullptr
};
}

std::pair<vital::Output*, vital::Output*> getOutputs(const vital::output_map& mono_modulations,
const vital::output_map& poly_modulations,
const std::string& name) {
return {
mono_modulations.at(name),
poly_modulations.at(name)
};
}
} // namespace

FilterResponse::FilterResponse() : OpenGlLineRenderer(kResolution), phaser_filter_(false) {
setFill(true);
setFillCenter(-1.0f);
active_ = false;
animate_ = false;
mix_ = 1.0f;
current_resonance_value_ = 0.0;
current_cutoff_value_ = 0.0;
current_formant_x_value_ = 0.0;
current_formant_y_value_ = 0.0;

cutoff_slider_ = nullptr;
resonance_slider_ = nullptr;
formant_x_slider_ = nullptr;
formant_y_slider_ = nullptr;
filter_mix_slider_ = nullptr;
blend_slider_ = nullptr;
transpose_slider_ = nullptr;
formant_transpose_slider_ = nullptr;
formant_resonance_slider_ = nullptr;
formant_spread_slider_ = nullptr;
last_filter_style_ = 0;
last_filter_model_ = static_cast<vital::constants::FilterModel>(0);
filter_model_ = static_cast<vital::constants::FilterModel>(0);

line_data_ = std::make_unique<float[]>(2 * kResolution);
line_buffer_ = 0;
response_buffer_ = 0;
vertex_array_object_ = 0;

for (int i = 0; i < kResolution; ++i) {
float t = i / (kResolution - 1.0f);
line_data_[2 * i] = 2.0f * t - 1.0f;
line_data_[2 * i + 1] = (i / kCombAlternatePeriod) % 2;
}

analog_filter_.setSampleRate(kDefaultVisualSampleRate);
comb_filter_.setSampleRate(kDefaultVisualSampleRate);
digital_filter_.setSampleRate(kDefaultVisualSampleRate);
diode_filter_.setSampleRate(kDefaultVisualSampleRate);
dirty_filter_.setSampleRate(kDefaultVisualSampleRate);
formant_filter_.setSampleRate(kDefaultVisualSampleRate);
ladder_filter_.setSampleRate(kDefaultVisualSampleRate);
phaser_filter_.setSampleRate(kDefaultVisualSampleRate);
}

FilterResponse::FilterResponse(String suffix, const vital::output_map& mono_modulations) : FilterResponse() {
std::string prefix = std::string("filter_") + suffix.toStdString() +"_";

filter_mix_outputs_ = getOutputs(mono_modulations, prefix + "mix");
midi_cutoff_outputs_ = getOutputs(mono_modulations, prefix + "cutoff");
resonance_outputs_ = getOutputs(mono_modulations, prefix + "resonance");
blend_outputs_ = getOutputs(mono_modulations, prefix + "blend");
transpose_outputs_ = getOutputs(mono_modulations, prefix + "blend_transpose");
interpolate_x_outputs_ = getOutputs(mono_modulations, prefix + "formant_x");
interpolate_y_outputs_ = getOutputs(mono_modulations, prefix + "formant_y");
formant_transpose_outputs_ = getOutputs(mono_modulations, prefix + "formant_transpose");
formant_resonance_outputs_ = getOutputs(mono_modulations, prefix + "formant_resonance");
formant_spread_outputs_ = getOutputs(mono_modulations, prefix + "formant_spread");
}

FilterResponse::FilterResponse(int index, const vital::output_map& mono_modulations,
const vital::output_map& poly_modulations) : FilterResponse() {
std::string prefix = std::string("filter_") + std::to_string(index) + "_";

filter_mix_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "mix");
midi_cutoff_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "cutoff");
resonance_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "resonance");
blend_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "blend");
transpose_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "blend_transpose");
interpolate_x_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_x");
interpolate_y_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_y");
formant_transpose_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_transpose");
formant_resonance_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_resonance");
formant_spread_outputs_ = getOutputs(mono_modulations, poly_modulations, prefix + "formant_spread");
}

FilterResponse::~FilterResponse() { }

void FilterResponse::init(OpenGlWrapper& open_gl) {
OpenGlLineRenderer::init(open_gl);

const GLchar* varyings[] = { "response_out" };
open_gl.context.extensions.glGenVertexArrays(1, &vertex_array_object_);
open_gl.context.extensions.glBindVertexArray(vertex_array_object_);

GLsizeiptr data_size = static_cast<GLsizeiptr>(kResolution * sizeof(float));
open_gl.context.extensions.glGenBuffers(1, &line_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, line_buffer_);
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, 2 * data_size, line_data_.get(), GL_STATIC_DRAW);

open_gl.context.extensions.glGenBuffers(1, &response_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, response_buffer_);
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, data_size, nullptr, GL_STATIC_READ);

for (int i = 0; i < kNumFilterShaders; ++i) {
Shaders::VertexShader vertex_shader = getVertexShader(static_cast<FilterShader>(i));
OpenGLShaderProgram* shader = open_gl.shaders->getShaderProgram(vertex_shader, Shaders::kColorFragment, varyings);
shaders_[i].shader = shader;

shader->use();
shaders_[i].position = getAttribute(open_gl, *shader, "position");

shaders_[i].mix = getUniform(open_gl, *shader, "mix");
shaders_[i].midi_cutoff = getUniform(open_gl, *shader, "midi_cutoff");
shaders_[i].resonance = getUniform(open_gl, *shader, "resonance");
shaders_[i].drive = getUniform(open_gl, *shader, "drive");
shaders_[i].db24 = getUniform(open_gl, *shader, "db24");

shaders_[i].formant_cutoff = getUniform(open_gl, *shader, "formant_cutoff");
shaders_[i].formant_resonance = getUniform(open_gl, *shader, "formant_resonance");
shaders_[i].formant_spread = getUniform(open_gl, *shader, "formant_spread");
shaders_[i].formant_low = getUniform(open_gl, *shader, "low");
shaders_[i].formant_band = getUniform(open_gl, *shader, "band");
shaders_[i].formant_high = getUniform(open_gl, *shader, "high");

for (int s = 0; s < FilterResponseShader::kMaxStages; ++s) {
String stage = String("stage") + String(s);
shaders_[i].stages[s] = getUniform(open_gl, *shader, stage.toRawUTF8());
}
}
}

void FilterResponse::render(OpenGlWrapper& open_gl, bool animate) {
animate_ = animate;
drawFilterResponse(open_gl);
renderCorners(open_gl, animate);
}

void FilterResponse::destroy(OpenGlWrapper& open_gl) {
OpenGlLineRenderer::destroy(open_gl);

open_gl.context.extensions.glDeleteBuffers(1, &line_buffer_);
open_gl.context.extensions.glDeleteBuffers(1, &response_buffer_);

vertex_array_object_ = 0;
line_buffer_ = 0;
response_buffer_ = 0;

for (int i = 0; i < kNumFilterShaders; ++i) {
shaders_[i].shader = nullptr;
shaders_[i].position = nullptr;

shaders_[i].mix = nullptr;
shaders_[i].midi_cutoff = nullptr;
shaders_[i].resonance = nullptr;
shaders_[i].drive = nullptr;
shaders_[i].db24 = nullptr;

shaders_[i].formant_cutoff = nullptr;
shaders_[i].formant_resonance = nullptr;
shaders_[i].formant_spread = nullptr;
shaders_[i].formant_low = nullptr;
shaders_[i].formant_band = nullptr;
shaders_[i].formant_high = nullptr;

for (int s = 0; s < FilterResponseShader::kMaxStages; ++s)
shaders_[i].stages[s] = nullptr;
}
}

void FilterResponse::paintBackground(Graphics& g) {
g.fillAll(findColour(Skin::kWidgetBackground, true));

line_left_color_ = findColour(Skin::kWidgetPrimary1, true);
line_right_color_ = findColour(Skin::kWidgetPrimary2, true);
line_disabled_color_ = findColour(Skin::kWidgetPrimaryDisabled, true);
fill_left_color_ = findColour(Skin::kWidgetSecondary1, true);
fill_right_color_ = findColour(Skin::kWidgetSecondary2, true);
fill_disabled_color_ = findColour(Skin::kWidgetSecondaryDisabled, true);
}

void FilterResponse::setFilterSettingsFromPosition(Point<int> position) {
Point<int> delta = position - last_mouse_position_;
last_mouse_position_ = position;
double width = getWidth();
double height = getHeight();
current_cutoff_value_ += delta.x * cutoff_slider_->getRange().getLength() / width;
current_formant_x_value_ += delta.x * formant_x_slider_->getRange().getLength() / width;
current_resonance_value_ -= delta.y * resonance_slider_->getRange().getLength() / height;
current_formant_y_value_ -= delta.y * formant_y_slider_->getRange().getLength() / height;
current_cutoff_value_ = cutoff_slider_->getRange().clipValue(current_cutoff_value_);
current_formant_x_value_ = formant_x_slider_->getRange().clipValue(current_formant_x_value_);
current_resonance_value_ = resonance_slider_->getRange().clipValue(current_resonance_value_);
current_formant_y_value_ = formant_y_slider_->getRange().clipValue(current_formant_y_value_);

if (filter_model_ == vital::constants::kFormant) {
formant_x_slider_->setValue(current_formant_x_value_);
formant_x_slider_->showPopup(true);
formant_y_slider_->setValue(current_formant_y_value_);
formant_y_slider_->showPopup(false);
}
else {
cutoff_slider_->setValue(current_cutoff_value_);
cutoff_slider_->showPopup(true);
resonance_slider_->setValue(current_resonance_value_);
resonance_slider_->showPopup(false);
}
}

void FilterResponse::mouseDown(const MouseEvent& e) {
last_mouse_position_ = e.getPosition();
current_resonance_value_ = resonance_slider_->getValue();
current_cutoff_value_ = cutoff_slider_->getValue();
current_formant_x_value_ = formant_x_slider_->getValue();
current_formant_y_value_ = formant_y_slider_->getValue();

if (filter_model_ == vital::constants::kFormant) {
formant_x_slider_->showPopup(true);
formant_y_slider_->showPopup(false);
}
else {
cutoff_slider_->showPopup(true);
resonance_slider_->showPopup(false);
}
}

void FilterResponse::mouseDrag(const MouseEvent& e) {
setFilterSettingsFromPosition(e.getPosition());
}

void FilterResponse::mouseExit(const MouseEvent& e) {
cutoff_slider_->hidePopup(true);
resonance_slider_->hidePopup(false);
OpenGlLineRenderer::mouseExit(e);
}

void FilterResponse::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) {
MouseWheelDetails horizontal_details = wheel;
horizontal_details.deltaY = 0.0f;
MouseWheelDetails vertical_details = wheel;
vertical_details.deltaX = 0.0f;

if (filter_model_ == vital::constants::kFormant) {
formant_x_slider_->mouseWheelMove(e, horizontal_details);
formant_y_slider_->mouseWheelMove(e, vertical_details);
}
else {
cutoff_slider_->mouseWheelMove(e, horizontal_details);
resonance_slider_->mouseWheelMove(e, vertical_details);
}
}

inline vital::poly_float FilterResponse::getOutputsTotal(
std::pair<vital::Output*, vital::Output*> outputs, vital::poly_float default_value) {
if (!active_ || !animate_ || !outputs.first->owner->enabled())
return default_value;
if (outputs.second == nullptr || num_voices_readout_ == nullptr || num_voices_readout_->value()[0] <= 0.0f)
return outputs.first->trigger_value;
return outputs.first->trigger_value + outputs.second->trigger_value;
}

bool FilterResponse::setupFilterState(vital::constants::FilterModel model) {
vital::poly_float midi_cutoff = getOutputsTotal(midi_cutoff_outputs_, cutoff_slider_->getValue());
midi_cutoff = vital::utils::max(midi_cutoff, 0.0f);
vital::poly_float mix = getOutputsTotal(filter_mix_outputs_, filter_mix_slider_->getValue());
mix = vital::utils::clamp(mix, 0.0f, 1.0f);
vital::poly_float resonance_percent = getOutputsTotal(resonance_outputs_, resonance_slider_->getValue());
vital::poly_float pass_blend = getOutputsTotal(blend_outputs_, blend_slider_->getValue());
pass_blend = vital::utils::clamp(pass_blend, 0.0f, 2.0f);
vital::poly_float transpose = getOutputsTotal(transpose_outputs_, transpose_slider_->getValue());
vital::poly_float interpolate_x = getOutputsTotal(interpolate_x_outputs_, formant_x_slider_->getValue());
vital::poly_float interpolate_y = getOutputsTotal(interpolate_y_outputs_, formant_y_slider_->getValue());

if (model == vital::constants::kFormant) {
transpose = getOutputsTotal(formant_transpose_outputs_, formant_transpose_slider_->getValue());
resonance_percent = getOutputsTotal(formant_resonance_outputs_, formant_resonance_slider_->getValue());
pass_blend = getOutputsTotal(formant_spread_outputs_, formant_spread_slider_->getValue());
}

vital::poly_mask equal = vital::constants::kFullMask;
equal = equal & vital::poly_float::equal(filter_state_.midi_cutoff, midi_cutoff);
equal = equal & vital::poly_float::equal(mix_, mix);
equal = equal & vital::poly_float::equal(filter_state_.resonance_percent, resonance_percent);
equal = equal & vital::poly_float::equal(filter_state_.pass_blend, pass_blend);
equal = equal & vital::poly_float::equal(filter_state_.transpose, transpose);
equal = equal & vital::poly_float::equal(filter_state_.interpolate_x, interpolate_x);
equal = equal & vital::poly_float::equal(filter_state_.interpolate_y, interpolate_y);

filter_state_.midi_cutoff = midi_cutoff;
mix_ = mix;
filter_state_.resonance_percent = resonance_percent;
filter_state_.pass_blend = pass_blend;
filter_state_.transpose = transpose;
filter_state_.interpolate_x = interpolate_x;
filter_state_.interpolate_y = interpolate_y;

bool new_type = last_filter_model_ != model || last_filter_style_ != filter_state_.style;
last_filter_style_ = filter_state_.style;
last_filter_model_ = model;

return (~equal).anyMask() || new_type;
}

bool FilterResponse::isStereoState() {
vital::poly_mask equal = vital::constants::kFullMask;
equal = equal & vital::poly_float::equal(filter_state_.midi_cutoff,
vital::utils::swapStereo(filter_state_.midi_cutoff));
equal = equal & vital::poly_float::equal(mix_, vital::utils::swapStereo(mix_));
equal = equal & vital::poly_float::equal(filter_state_.resonance_percent,
vital::utils::swapStereo(filter_state_.resonance_percent));
equal = equal & vital::poly_float::equal(filter_state_.pass_blend,
vital::utils::swapStereo(filter_state_.pass_blend));
equal = equal & vital::poly_float::equal(filter_state_.transpose,
vital::utils::swapStereo(filter_state_.transpose));
equal = equal & vital::poly_float::equal(filter_state_.interpolate_x,
vital::utils::swapStereo(filter_state_.interpolate_x));
equal = equal & vital::poly_float::equal(filter_state_.interpolate_y,
vital::utils::swapStereo(filter_state_.interpolate_y));

return (~equal).anyMask();
}

void FilterResponse::loadShader(FilterShader shader, vital::constants::FilterModel model, int index) {
if (model == vital::constants::kAnalog) {
analog_filter_.setupFilter(filter_state_);
shaders_[shader].shader->use();
float resonance = vital::utils::clamp(analog_filter_.getResonance()[index], 0.0f, 2.0f);
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
shaders_[shader].resonance->set(resonance);
shaders_[shader].drive->set(analog_filter_.getDrive()[index]);
shaders_[shader].db24->set(filter_state_.style != vital::SynthFilter::k12Db ? 1.0f : 0.0f);

shaders_[shader].stages[0]->set(analog_filter_.getLowAmount()[index]);
shaders_[shader].stages[1]->set(analog_filter_.getBandAmount()[index]);
shaders_[shader].stages[2]->set(analog_filter_.getHighAmount()[index]);
shaders_[shader].stages[3]->set(analog_filter_.getLowAmount24(filter_state_.style)[index]);
shaders_[shader].stages[4]->set(analog_filter_.getHighAmount24(filter_state_.style)[index]);
}
else if (model == vital::constants::kComb) {
comb_filter_.setupFilter(filter_state_);
shaders_[shader].shader->use();
float resonance = vital::utils::clamp(comb_filter_.getResonance()[index], -0.99f, 0.99f);
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
shaders_[shader].resonance->set(resonance);
shaders_[shader].drive->set(comb_filter_.getDrive()[index]);

shaders_[shader].stages[0]->set(comb_filter_.getLowAmount()[index]);
shaders_[shader].stages[1]->set(comb_filter_.getHighAmount()[index]);
shaders_[shader].stages[2]->set(comb_filter_.getFilterMidiCutoff()[index]);
shaders_[shader].stages[3]->set(comb_filter_.getFilter2MidiCutoff()[index]);
}
else if (model == vital::constants::kDigital) {
digital_filter_.setupFilter(filter_state_);
shaders_[shader].shader->use();
float resonance = vital::utils::clamp(digital_filter_.getResonance()[index], 0.0f, 2.0f);
shaders_[shader].midi_cutoff->set(digital_filter_.getMidiCutoff()[index]);
shaders_[shader].resonance->set(resonance);
shaders_[shader].drive->set(digital_filter_.getDrive()[index]);
shaders_[shader].db24->set(filter_state_.style != vital::SynthFilter::k12Db ? 1.0f : 0.0f);

shaders_[shader].stages[0]->set(digital_filter_.getLowAmount()[index]);
shaders_[shader].stages[1]->set(digital_filter_.getBandAmount()[index]);
shaders_[shader].stages[2]->set(digital_filter_.getHighAmount()[index]);
shaders_[shader].stages[3]->set(digital_filter_.getLowAmount24(filter_state_.style)[index]);
shaders_[shader].stages[4]->set(digital_filter_.getHighAmount24(filter_state_.style)[index]);
}
else if (model == vital::constants::kDiode) {
diode_filter_.setupFilter(filter_state_);
shaders_[shader].shader->use();
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
shaders_[shader].resonance->set(diode_filter_.getResonance()[index]);
shaders_[shader].drive->set(diode_filter_.getDrive()[index]);
shaders_[shader].db24->set(diode_filter_.getHighPassAmount()[index]);
shaders_[shader].stages[0]->set(diode_filter_.getHighPassRatio()[index]);
}
else if (model == vital::constants::kDirty) {
dirty_filter_.setupFilter(filter_state_);
shaders_[shader].shader->use();
float resonance = vital::utils::clamp(dirty_filter_.getResonance()[index], 0.0f, 2.0f);
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
shaders_[shader].resonance->set(resonance);
shaders_[shader].drive->set(dirty_filter_.getDrive()[index]);
shaders_[shader].db24->set(filter_state_.style != vital::SynthFilter::k12Db ? 1.0f : 0.0f);

shaders_[shader].stages[0]->set(dirty_filter_.getLowAmount()[index]);
shaders_[shader].stages[1]->set(dirty_filter_.getBandAmount()[index]);
shaders_[shader].stages[2]->set(dirty_filter_.getHighAmount()[index]);
shaders_[shader].stages[3]->set(dirty_filter_.getLowAmount24(filter_state_.style)[index]);
shaders_[shader].stages[4]->set(dirty_filter_.getHighAmount24(filter_state_.style)[index]);
}
else if (model == vital::constants::kFormant) {
shaders_[shader].shader->use();

vital::DigitalSvf* formant0 = formant_filter_.getFormant(0);
vital::DigitalSvf* formant1 = formant_filter_.getFormant(1);
vital::DigitalSvf* formant2 = formant_filter_.getFormant(2);
vital::DigitalSvf* formant3 = formant_filter_.getFormant(3);

formant_filter_.setupFilter(filter_state_);
shaders_[shader].formant_cutoff->set(formant0->getMidiCutoff()[index], formant1->getMidiCutoff()[index],
formant2->getMidiCutoff()[index], formant3->getMidiCutoff()[index]);
shaders_[shader].formant_resonance->set(formant0->getResonance()[index], formant1->getResonance()[index],
formant2->getResonance()[index], formant3->getResonance()[index]);
vital::poly_float drive0 = formant0->getDrive();
vital::poly_float drive1 = formant1->getDrive();
vital::poly_float drive2 = formant2->getDrive();
vital::poly_float drive3 = formant3->getDrive();
vital::poly_float low0 = formant0->getLowAmount() * drive0;
vital::poly_float low1 = formant1->getLowAmount() * drive1;
vital::poly_float low2 = formant2->getLowAmount() * drive2;
vital::poly_float low3 = formant3->getLowAmount() * drive3;
vital::poly_float band0 = formant0->getBandAmount() * drive0;
vital::poly_float band1 = formant1->getBandAmount() * drive1;
vital::poly_float band2 = formant2->getBandAmount() * drive2;
vital::poly_float band3 = formant3->getBandAmount() * drive3;
vital::poly_float high0 = formant0->getHighAmount() * drive0;
vital::poly_float high1 = formant1->getHighAmount() * drive1;
vital::poly_float high2 = formant2->getHighAmount() * drive2;
vital::poly_float high3 = formant3->getHighAmount() * drive3;

shaders_[shader].formant_low->set(low0[index], low1[index], low2[index], low3[index]);
shaders_[shader].formant_band->set(band0[index], band1[index], band2[index], band3[index]);
shaders_[shader].formant_high->set(high0[index], high1[index], high2[index], high3[index]);
}
else if (model == vital::constants::kLadder) {
ladder_filter_.setupFilter(filter_state_);
shaders_[shader].shader->use();
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
shaders_[shader].resonance->set(ladder_filter_.getResonance()[index]);
shaders_[shader].drive->set(ladder_filter_.getDrive()[index]);

for (int s = 0; s < FilterResponseShader::kMaxStages; ++s)
shaders_[shader].stages[s]->set(ladder_filter_.getStageScale(s)[index]);
}
else if (model == vital::constants::kPhase) {
phaser_filter_.setupFilter(filter_state_);
shaders_[shader].shader->use();
shaders_[shader].midi_cutoff->set(filter_state_.midi_cutoff[index]);
shaders_[shader].resonance->set(phaser_filter_.getResonance()[index]);
shaders_[shader].db24->set(filter_state_.style != vital::SynthFilter::k12Db ? 1.0f : 0.0f);

shaders_[shader].stages[0]->set(phaser_filter_.getPeak1Amount()[index]);
shaders_[shader].stages[1]->set(phaser_filter_.getPeak3Amount()[index]);
shaders_[shader].stages[2]->set(phaser_filter_.getPeak5Amount()[index]);
}
shaders_[shader].mix->set(mix_[index]);
}

void FilterResponse::bind(FilterShader shader, OpenGLContext& open_gl_context) {
open_gl_context.extensions.glBindVertexArray(vertex_array_object_);
open_gl_context.extensions.glBindBuffer(GL_ARRAY_BUFFER, line_buffer_);

OpenGLShaderProgram::Attribute* position = shaders_[shader].position.get();
open_gl_context.extensions.glVertexAttribPointer(position->attributeID, 2, GL_FLOAT,
GL_FALSE, 2 * sizeof(float), nullptr);
open_gl_context.extensions.glEnableVertexAttribArray(position->attributeID);

open_gl_context.extensions.glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, response_buffer_);
}

void FilterResponse::unbind(FilterShader shader, OpenGLContext& open_gl_context) {
OpenGLShaderProgram::Attribute* position = shaders_[shader].position.get();
open_gl_context.extensions.glDisableVertexAttribArray(position->attributeID);
open_gl_context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
open_gl_context.extensions.glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
}

void FilterResponse::drawFilterResponse(OpenGlWrapper& open_gl) {
vital::constants::FilterModel model = filter_model_;
bool new_response = setupFilterState(model);
new_response = new_response || isStereoState();

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
setViewPort(open_gl);

Colour color_line = line_right_color_;
Colour color_fill_to = fill_right_color_;
float fill_fade = findValue(Skin::kWidgetFillFade);
Colour color_fill_from = color_fill_to.withMultipliedAlpha(1.0f - fill_fade);

setLineWidth(findValue(Skin::kWidgetLineWidth));
setFillCenter(findValue(Skin::kWidgetFillCenter));

FilterShader shader = getShaderForModel(model, filter_state_.style);

if (active_) {
if (new_response) {
bind(shader, open_gl.context);
loadShader(shader, model, 1);
renderLineResponse(open_gl);
}

setFillColors(color_fill_from, color_fill_to);
setColor(color_line);
OpenGlLineRenderer::render(open_gl, animate_);
}

color_line = line_left_color_;
color_fill_to = fill_left_color_;
if (!active_) {
color_line = line_disabled_color_;
color_fill_to = fill_disabled_color_;
}
color_fill_from = color_fill_to.withMultipliedAlpha(1.0f - fill_fade);

if (new_response) {
bind(shader, open_gl.context);
loadShader(shader, model, 0);
renderLineResponse(open_gl);
}

setFillColors(color_fill_from, color_fill_to);
setColor(color_line);
OpenGlLineRenderer::render(open_gl, animate_);

unbind(shader, open_gl.context);
glDisable(GL_BLEND);
checkGlError();
}

void FilterResponse::renderLineResponse(OpenGlWrapper& open_gl) {
glEnable(GL_BLEND);
open_gl.context.extensions.glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, kResolution);
open_gl.context.extensions.glEndTransformFeedback();

void* buffer = open_gl.context.extensions.glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
kResolution * sizeof(float), GL_MAP_READ_BIT);

float* response_data = (float*)buffer;
float width = getWidth();
float y_adjust = getHeight() / 2.0f;
for (int i = 0; i < kResolution; ++i) {
setXAt(i, width * i / (kResolution - 1.0f));
setYAt(i, y_adjust * (1.0f - response_data[i]));
}

open_gl.context.extensions.glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
glDisable(GL_BLEND);
}

+ 182
- 0
ports/vitalium/source/interface/editor_components/filter_response.h View File

@@ -0,0 +1,182 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "open_gl_line_renderer.h"

#include "skin.h"
#include "comb_filter.h"
#include "digital_svf.h"
#include "diode_filter.h"
#include "dirty_filter.h"
#include "formant_filter.h"
#include "ladder_filter.h"
#include "phaser_filter.h"
#include "sallen_key_filter.h"
#include "synth_types.h"

class SynthSlider;

class FilterResponse : public OpenGlLineRenderer {
public:
static constexpr int kResolution = 512;
static constexpr int kDefaultVisualSampleRate = 200000;
static constexpr int kCombAlternatePeriod = 3;

static constexpr double kMouseSensitivityX = 0.3;
static constexpr double kMouseSensitivityY = 0.3;

enum FilterShader {
kAnalog,
kDirty,
kLadder,
kDigital,
kDiode,
kFormant,
kComb,
kPositiveFlange,
kNegativeFlange,
kPhase,
kNumFilterShaders
};

FilterResponse(String suffix, const vital::output_map& mono_modulations);
FilterResponse(int index, const vital::output_map& mono_modulations, const vital::output_map& poly_modulations);
virtual ~FilterResponse();

void init(OpenGlWrapper& open_gl) override;
void render(OpenGlWrapper& open_gl, bool animate) override;
void destroy(OpenGlWrapper& open_gl) override;
void paintBackground(Graphics& g) override;

void setCutoffSlider(SynthSlider* slider) { cutoff_slider_ = slider; }
void setResonanceSlider(SynthSlider* slider) { resonance_slider_ = slider; }
void setFormantXSlider(SynthSlider* slider) { formant_x_slider_ = slider; }
void setFormantYSlider(SynthSlider* slider) { formant_y_slider_ = slider; }
void setFilterMixSlider(SynthSlider* slider) { filter_mix_slider_ = slider; }
void setBlendSlider(SynthSlider* slider) { blend_slider_ = slider; }
void setTransposeSlider(SynthSlider* slider) { transpose_slider_ = slider; }
void setFormantTransposeSlider(SynthSlider* slider) { formant_transpose_slider_ = slider; }
void setFormantResonanceSlider(SynthSlider* slider) { formant_resonance_slider_ = slider; }
void setFormantSpreadSlider(SynthSlider* slider) { formant_spread_slider_ = slider; }

void mouseDown(const MouseEvent& e) override;
void mouseDrag(const MouseEvent& e) override;
void mouseExit(const MouseEvent& e) override;
void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override;

void setActive(bool active) { active_ = active; }
void setModel(vital::constants::FilterModel model) { filter_model_ = model; }
void setStyle(int style) { filter_state_.style = style; }

private:
struct FilterResponseShader {
static constexpr int kMaxStages = 5;
OpenGLShaderProgram* shader;
std::unique_ptr<OpenGLShaderProgram::Attribute> position;

std::unique_ptr<OpenGLShaderProgram::Uniform> mix;
std::unique_ptr<OpenGLShaderProgram::Uniform> midi_cutoff;
std::unique_ptr<OpenGLShaderProgram::Uniform> resonance;
std::unique_ptr<OpenGLShaderProgram::Uniform> drive;
std::unique_ptr<OpenGLShaderProgram::Uniform> db24;
std::unique_ptr<OpenGLShaderProgram::Uniform> stages[kMaxStages];

std::unique_ptr<OpenGLShaderProgram::Uniform> formant_cutoff;
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_resonance;
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_spread;
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_low;
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_band;
std::unique_ptr<OpenGLShaderProgram::Uniform> formant_high;
};

FilterResponse();

void setFilterSettingsFromPosition(Point<int> position);

void drawFilterResponse(OpenGlWrapper& open_gl);
vital::poly_float getOutputsTotal(std::pair<vital::Output*, vital::Output*> outputs,
vital::poly_float default_value);

bool setupFilterState(vital::constants::FilterModel model);
bool isStereoState();
void loadShader(FilterShader shader, vital::constants::FilterModel model, int index);
void bind(FilterShader shader, OpenGLContext& open_gl_context);
void unbind(FilterShader shader, OpenGLContext& open_gl_context);
void renderLineResponse(OpenGlWrapper& open_gl);

bool active_;
bool animate_;
Point<int> last_mouse_position_;
double current_resonance_value_;
double current_cutoff_value_;
double current_formant_x_value_;
double current_formant_y_value_;

Colour line_left_color_;
Colour line_right_color_;
Colour line_disabled_color_;
Colour fill_left_color_;
Colour fill_right_color_;
Colour fill_disabled_color_;

vital::SallenKeyFilter analog_filter_;
vital::CombFilter comb_filter_;
vital::DigitalSvf digital_filter_;
vital::DiodeFilter diode_filter_;
vital::DirtyFilter dirty_filter_;
vital::FormantFilter formant_filter_;
vital::LadderFilter ladder_filter_;
vital::PhaserFilter phaser_filter_;

int last_filter_style_;
vital::constants::FilterModel last_filter_model_;
vital::constants::FilterModel filter_model_;
vital::SynthFilter::FilterState filter_state_;
vital::poly_float mix_;

SynthSlider* cutoff_slider_;
SynthSlider* resonance_slider_;
SynthSlider* formant_x_slider_;
SynthSlider* formant_y_slider_;
SynthSlider* filter_mix_slider_;
SynthSlider* blend_slider_;
SynthSlider* transpose_slider_;
SynthSlider* formant_transpose_slider_;
SynthSlider* formant_resonance_slider_;
SynthSlider* formant_spread_slider_;

std::pair<vital::Output*, vital::Output*> filter_mix_outputs_;
std::pair<vital::Output*, vital::Output*> midi_cutoff_outputs_;
std::pair<vital::Output*, vital::Output*> resonance_outputs_;
std::pair<vital::Output*, vital::Output*> blend_outputs_;
std::pair<vital::Output*, vital::Output*> transpose_outputs_;
std::pair<vital::Output*, vital::Output*> interpolate_x_outputs_;
std::pair<vital::Output*, vital::Output*> interpolate_y_outputs_;
std::pair<vital::Output*, vital::Output*> formant_resonance_outputs_;
std::pair<vital::Output*, vital::Output*> formant_spread_outputs_;
std::pair<vital::Output*, vital::Output*> formant_transpose_outputs_;

FilterResponseShader shaders_[kNumFilterShaders];
std::unique_ptr<float[]> line_data_;
GLuint vertex_array_object_;
GLuint line_buffer_;
GLuint response_buffer_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FilterResponse)
};


+ 103
- 0
ports/vitalium/source/interface/editor_components/incrementer_buttons.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

#include "JuceHeader.h"
#include "skin.h"

class IncrementerButtons : public Component, public Button::Listener {
public:
IncrementerButtons(Slider* slider) : slider_(slider), active_(true) {
increment_ = std::make_unique<ShapeButton>("Increment", Colours::black, Colours::black, Colours::black);
addAndMakeVisible(increment_.get());
increment_->addListener(this);
Path increment_shape;
increment_shape.startNewSubPath(Point<float>(0.5f, 0.1f));
increment_shape.lineTo(Point<float>(0.2f, 0.45f));
increment_shape.lineTo(Point<float>(0.8f, 0.45f));
increment_shape.closeSubPath();

increment_shape.startNewSubPath(Point<float>(0.0f, 0.0f));
increment_shape.closeSubPath();
increment_shape.startNewSubPath(Point<float>(1.0f, 0.5f));
increment_shape.closeSubPath();

increment_shape.addLineSegment(Line<float>(0.0f, 0.0f, 0.0f, 0.0f), 0.2f);
increment_shape.addLineSegment(Line<float>(0.5f, 0.5f, 0.5f, 0.5f), 0.2f);
increment_->setShape(increment_shape, true, true, false);

decrement_ = std::make_unique<ShapeButton>("Increment", Colours::black, Colours::black, Colours::black);
addAndMakeVisible(decrement_.get());
decrement_->addListener(this);
Path decrement_shape;
decrement_shape.startNewSubPath(Point<float>(0.5f, 0.4f));
decrement_shape.lineTo(Point<float>(0.2f, 0.05f));
decrement_shape.lineTo(Point<float>(0.8f, 0.05f));
decrement_shape.closeSubPath();

decrement_shape.startNewSubPath(Point<float>(0.0f, 0.0f));
decrement_shape.closeSubPath();
decrement_shape.startNewSubPath(Point<float>(1.0f, 0.5f));
decrement_shape.closeSubPath();

decrement_shape.addLineSegment(Line<float>(0.0f, 0.0f, 0.0f, 0.0f), 0.2f);
decrement_shape.addLineSegment(Line<float>(0.5f, 0.5f, 0.5f, 0.5f), 0.2f);
decrement_->setShape(decrement_shape, true, true, false);
}

void setActive(bool active) {
active_ = active;
repaint();
}

void resized() override {
Rectangle<int> increment_bounds = getLocalBounds();
Rectangle<int> decrement_bounds = increment_bounds.removeFromBottom(getHeight() / 2);
increment_->setBounds(increment_bounds);
decrement_->setBounds(decrement_bounds);
}

void paint(Graphics& g) override {
setColors();
}

void buttonClicked(Button* clicked_button) override {
double value = slider_->getValue();
if (clicked_button == increment_.get())
slider_->setValue(value + 1.0);
else if (clicked_button == decrement_.get())
slider_->setValue(value - 1.0);
}

private:
void setColors() {
Colour normal = findColour(Skin::kIconButtonOff, true);
Colour hover = findColour(Skin::kIconButtonOffHover, true);
Colour down = findColour(Skin::kIconButtonOffPressed, true);
increment_->setColours(normal, hover, down);
decrement_->setColours(normal, hover, down);
}

Slider* slider_;
bool active_;

std::unique_ptr<ShapeButton> increment_;
std::unique_ptr<ShapeButton> decrement_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IncrementerButtons)
};


+ 248
- 0
ports/vitalium/source/interface/editor_components/lfo_editor.cpp View File

@@ -0,0 +1,248 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "lfo_editor.h"

#include "default_look_and_feel.h"
#include "skin.h"
#include "shaders.h"
#include "synth_gui_interface.h"
#include "synth_section.h"
#include "utils.h"

LfoEditor::LfoEditor(LineGenerator* lfo_source, String prefix,
const vital::output_map& mono_modulations,
const vital::output_map& poly_modulations) : LineEditor(lfo_source) {
parent_ = nullptr;
wave_phase_ = nullptr;
frequency_ = nullptr;
last_phase_ = 0.0f;

setFill(true);
setFillCenter(-1.0f);
setName(prefix);

last_voice_ = -1.0f;
}

LfoEditor::~LfoEditor() { }

void LfoEditor::parentHierarchyChanged() {
parent_ = findParentComponentOfClass<SynthGuiInterface>();

if (wave_phase_ == nullptr && parent_)
wave_phase_ = parent_->getSynth()->getStatusOutput(getName().toStdString() + "_phase");

if (frequency_ == nullptr && parent_)
frequency_ = parent_->getSynth()->getStatusOutput(getName().toStdString() + "_frequency");

LineEditor::parentHierarchyChanged();
}

void LfoEditor::mouseDown(const MouseEvent& e) {
if (e.mods.isPopupMenu()) {
PopupItems options;

int active_point = getActivePoint();
if (active_point >= 0) {
options.addItem(kSetPhaseToPoint, "Set Start Point");
if (active_point >= 1 && active_point < getModel()->getNumPoints() - 1) {
options.addItem(kRemovePoint, "Remove Point");
options.addItem(kEnterPhase, "Enter Point Phase");
}

options.addItem(kEnterValue, "Enter Point Value");
options.addItem(-1, "");
}
else if (getActivePower() >= 0) {
options.addItem(kSetPhaseToPower, "Set Start Point");
options.addItem(kResetPower, "Reset Power");
options.addItem(-1, "");
}
else if (getActiveGridSection() >= 0)
options.addItem(kSetPhaseToGrid, "Set Start Point");

options.addItem(kCopy, "Copy");
if (hasMatchingSystemClipboard())
options.addItem(kPaste, "Paste");

options.addItem(kSave, "Save to LFOs");

options.addItem(kFlipHorizontal, "Flip Horizontal");
options.addItem(kFlipVertical, "Flip Vertical");

options.addItem(kImportLfo, "Import LFO");
options.addItem(kExportLfo, "Export LFO");

SynthSection* parent = findParentComponentOfClass<SynthSection>();
int point = active_point;
int power = getActivePower();
parent->showPopupSelector(this, e.getPosition(), options,
[=](int selection) { respondToCallback(point, power, selection); });
}
else
LineEditor::mouseDown(e);
}

void LfoEditor::mouseDoubleClick(const MouseEvent& e) {
if (!e.mods.isPopupMenu())
LineEditor::mouseDoubleClick(e);
}

void LfoEditor::mouseUp(const MouseEvent& e) {
if (!e.mods.isPopupMenu())
LineEditor::mouseUp(e);
}

void LfoEditor::respondToCallback(int point, int power, int result) {
if (result == kSetPhaseToPoint) {
if (point >= 0 && point < numPoints())
setPhase(getModel()->getPoint(point).first);
}
else if (result == kSetPhaseToPower) {
if (power >= 0 && power < numPoints() - 1) {
float from = getModel()->getPoint(power).first;
float to = getModel()->getPoint(power + 1).first;
setPhase((from + to) / 2.0f);
}
}
else if (result == kSetPhaseToGrid) {
int section = getActiveGridSection();
int grid_size = getGridSizeX();
if (section >= 0 && grid_size > 0)
setPhase(section * 1.0f / grid_size);
}
else if (result == kImportLfo) {
for (Listener* listener : listeners_)
listener->importLfo();
}
else if (result == kExportLfo) {
for (Listener* listener : listeners_)
listener->exportLfo();
}
else
LineEditor::respondToCallback(point, power, result);

clearActiveMouseActions();
}

void LfoEditor::setPhase(float phase) {
for (Listener* listener : listeners_)
listener->setPhase(phase);
}

void LfoEditor::render(OpenGlWrapper& open_gl, bool animate) {
static constexpr float kBackupTime = 1.0f / 50.0f;

setGlPositions();
renderGrid(open_gl, animate);

vital::poly_float encoded_phase = wave_phase_->value();
vital::poly_mask inactive_mask = 0;
if (wave_phase_->isClearValue(encoded_phase)) {
encoded_phase = 0.0f;
inactive_mask = vital::constants::kFullMask;
}

vital::poly_float frequency = frequency_->value();
if (frequency_->isClearValue(frequency))
frequency = 0.0f;

std::pair<vital::poly_float, vital::poly_float> decoded = vital::utils::decodePhaseAndVoice(encoded_phase);
vital::poly_float phase = decoded.first;
vital::poly_float voice = decoded.second;

vital::poly_float phase_delta = vital::poly_float::abs(phase - last_phase_);
vital::poly_float decay = vital::poly_float(1.0f) - phase_delta * kSpeedDecayMult;
decay = vital::utils::clamp(decay, kBoostDecay, 1.0f);
decay = vital::utils::maskLoad(decay, kBoostDecay, inactive_mask);
decayBoosts(decay);

vital::poly_mask switch_mask = vital::poly_float::notEqual(voice, last_voice_) | inactive_mask;
vital::poly_float phase_reset = vital::utils::max(0.0f, phase - frequency * kBackupTime);
last_phase_ = vital::utils::maskLoad(last_phase_, phase_reset, switch_mask);

bool animating = animate;
if (parent_)
animating = animating && parent_->getSynth()->isModSourceEnabled(getName().toStdString());

if (animating)
boostRange(adjustBoostPhase(last_phase_), adjustBoostPhase(phase), kNumWrapPoints, decay);
else
decayBoosts(0.0f);

last_phase_ = phase;
last_voice_ = voice;

setLineWidth(findValue(Skin::kWidgetLineWidth));
setFillCenter(findValue(Skin::kWidgetFillCenter));

Colour fill_color = findColour(Skin::kWidgetSecondary1, true);
float fill_fade = findValue(Skin::kWidgetFillFade);
Colour fill_color_fade = fill_color.withMultipliedAlpha(1.0f - fill_fade);
Colour position_color = findColour(Skin::kWidgetPrimary1, true);

Colour fill_color_stereo = findColour(Skin::kWidgetSecondary2, true);
Colour fill_color_stereo_fade = fill_color_stereo.withMultipliedAlpha(1.0f - fill_fade);
Colour position_color_stereo = findColour(Skin::kWidgetPrimary2, true);

if (animating) {
setFill(true);
setBoostAmount(findValue(Skin::kWidgetLineBoost));
setFillBoostAmount(findValue(Skin::kWidgetFillBoost));

setIndex(1);
setColor(findColour(Skin::kWidgetPrimary2, true));
setFillColors(fill_color_stereo_fade, fill_color_stereo);
drawLines(open_gl, false);

setIndex(0);
setColor(findColour(Skin::kWidgetPrimary1, true));
setFillColors(fill_color_fade, fill_color);
drawLines(open_gl, anyBoostValue());

setBoostAmount(0.0f);
setFill(false);
setColor(findColour(Skin::kWidgetCenterLine, true));
drawLines(open_gl, anyBoostValue());

setViewPort(open_gl);
if (switch_mask.sum() == 0) {
drawPosition(open_gl, position_color_stereo, phase[1]);
drawPosition(open_gl, position_color, phase[0]);
}
}
else {
setBoostAmount(0.0f);
setFillBoostAmount(0.0f);
setFill(true);

setColor(findColour(Skin::kWidgetPrimary2, true));
setFillColors(fill_color_stereo_fade, fill_color_stereo);
drawLines(open_gl, false);

setColor(findColour(Skin::kWidgetPrimary1, true));
setFillColors(fill_color_fade, fill_color);
drawLines(open_gl, false);

setFill(false);
setColor(findColour(Skin::kWidgetCenterLine, true));
drawLines(open_gl, false);
}

renderPoints(open_gl, animate);
renderCorners(open_gl, animate);
}

+ 66
- 0
ports/vitalium/source/interface/editor_components/lfo_editor.h 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/>.
*/

#pragma once

#include "JuceHeader.h"

#include "line_generator.h"
#include "open_gl_image.h"
#include "line_editor.h"
#include "synth_lfo.h"
#include "synth_module.h"

class SynthGuiInterface;

class LfoEditor : public LineEditor {
public:
static constexpr float kBoostDecay = 0.9f;
static constexpr float kSpeedDecayMult = 5.0f;

enum {
kSetPhaseToPoint = kNumMenuOptions,
kSetPhaseToPower,
kSetPhaseToGrid,
kImportLfo,
kExportLfo,
};

LfoEditor(LineGenerator* lfo_source, String prefix,
const vital::output_map& mono_modulations, const vital::output_map& poly_modulations);
virtual ~LfoEditor();

void parentHierarchyChanged() override;
virtual void mouseDown(const MouseEvent& e) override;
virtual void mouseDoubleClick(const MouseEvent& e) override;
virtual void mouseUp(const MouseEvent& e) override;

void respondToCallback(int point, int power, int result) override;
void setPhase(float phase);

void render(OpenGlWrapper& open_gl, bool animate) override;

private:
SynthGuiInterface* parent_;

const vital::StatusOutput* wave_phase_;
const vital::StatusOutput* frequency_;
vital::poly_float last_phase_;
vital::poly_float last_voice_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LfoEditor)
};


+ 1106
- 0
ports/vitalium/source/interface/editor_components/line_editor.cpp
File diff suppressed because it is too large
View File


+ 236
- 0
ports/vitalium/source/interface/editor_components/line_editor.h View File

@@ -0,0 +1,236 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

#include "line_generator.h"
#include "open_gl_image_component.h"
#include "open_gl_multi_image.h"
#include "open_gl_multi_quad.h"
#include "open_gl_line_renderer.h"
#include "synth_lfo.h"
#include "synth_module.h"

class LineEditor : public OpenGlLineRenderer, public TextEditor::Listener {
public:
static constexpr float kPositionWidth = 9.0f;
static constexpr float kPowerWidth = 7.0f;
static constexpr float kRingThickness = 0.45f;
static constexpr float kGrabRadius = 12.0f;
static constexpr float kDragRadius = 20.0f;
static constexpr int kResolution = 64;
static constexpr int kNumWrapPoints = 8;
static constexpr int kDrawPoints = kResolution + LineGenerator::kMaxPoints;
static constexpr int kTotalPoints = kDrawPoints + 2 * kNumWrapPoints;
static constexpr int kMaxGridSizeX = 32;
static constexpr int kMaxGridSizeY = 24;
static constexpr float kPaddingY = 6.0f;
static constexpr float kPaddingX = 0.0f;
static constexpr float kPowerMouseMultiplier = 9.0f;
static constexpr float kMinPointDistanceForPower = 3.0f;

enum MenuOptions {
kCancel,
kCopy,
kPaste,
kSave,
kEnterPhase,
kEnterValue,
kResetPower,
kRemovePoint,
kInit,
kFlipHorizontal,
kFlipVertical,
kNumMenuOptions
};

class Listener {
public:
virtual ~Listener() { }
virtual void setPhase(float phase) = 0;
virtual void lineEditorScrolled(const MouseEvent& e, const MouseWheelDetails& wheel) = 0;
virtual void togglePaintMode(bool enabled, bool temporary_switch) = 0;
virtual void fileLoaded() = 0;
virtual void importLfo() = 0;
virtual void exportLfo() = 0;
virtual void pointChanged(int index, Point<float> position, bool mouse_up) { }
virtual void powersChanged(bool mouse_up) { }
virtual void pointAdded(int index, Point<float> position) { }
virtual void pointRemoved(int index) { }
virtual void pointsAdded(int index, int num_points_added) { }
virtual void pointsRemoved(int index, int num_points_removed) { }
};

LineEditor(LineGenerator* line_source);
virtual ~LineEditor();

void resetWavePath();
void resized() override {
OpenGlLineRenderer::resized();
drag_circle_.setBounds(getLocalBounds());
hover_circle_.setBounds(getLocalBounds());
grid_lines_.setBounds(getLocalBounds());
position_circle_.setBounds(getLocalBounds());
point_circles_.setBounds(getLocalBounds());
power_circles_.setBounds(getLocalBounds());
resetPositions();
}

float padY(float y);
float unpadY(float y);

float padX(float x);
float unpadX(float x);

virtual void mouseDown(const MouseEvent& e) override;
virtual void mouseDoubleClick(const MouseEvent& e) override;
virtual void mouseMove(const MouseEvent& e) override;
virtual void mouseDrag(const MouseEvent& e) override;
virtual void mouseUp(const MouseEvent& e) override;

virtual void respondToCallback(int point, int power, int option);
bool hasMatchingSystemClipboard();
void paintLine(const MouseEvent& e);

void drawDown(const MouseEvent& e);
void drawDrag(const MouseEvent& e);
void drawUp(const MouseEvent& e);

void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override;
void mouseExit(const MouseEvent& e) override;
void clearActiveMouseActions();

void renderGrid(OpenGlWrapper& open_gl, bool animate);
void renderPoints(OpenGlWrapper& open_gl, bool animate);
void init(OpenGlWrapper& open_gl) override;
void render(OpenGlWrapper& open_gl, bool animate) override;
void destroy(OpenGlWrapper& open_gl) override;
void setSizeRatio(float ratio) { size_ratio_ = ratio; }
float sizeRatio() const { return size_ratio_; }

void setLoop(bool loop) { loop_ = loop; }
void setSmooth(bool smooth) { model_->setSmooth(smooth); resetPositions(); }
bool getSmooth() const { return model_->smooth(); }
void setPaint(bool paint);
void setPaintPattern(std::vector<std::pair<float, float>> pattern) { paint_pattern_ = pattern; }

virtual void setGridSizeX(int size) { grid_size_x_ = size; setGridPositions(); }
virtual void setGridSizeY(int size) { grid_size_y_ = size; setGridPositions(); }
int getGridSizeX() { return grid_size_x_; }
int getGridSizeY() { return grid_size_y_; }

void setModel(LineGenerator* model) { model_ = model; resetPositions(); }
LineGenerator* getModel() { return model_; }
void showTextEntry();
void hideTextEntry();
void textEditorReturnKeyPressed(TextEditor& editor) override;
void textEditorFocusLost(TextEditor& editor) override;
void textEditorEscapeKeyPressed(TextEditor& editor) override;
void setSliderPositionFromText();
void setAllowFileLoading(bool allow) { allow_file_loading_ = allow; }

void addListener(Listener* listener) { listeners_.push_back(listener); }
void setActive(bool active) { active_ = active; }
force_inline void resetPositions() { reset_positions_ = true; }
OpenGlComponent* getTextEditorComponent() {
if (value_entry_)
return value_entry_->getImageComponent();
return nullptr;
}

protected:
void drawPosition(OpenGlWrapper& open_gl, Colour color, float fraction_x);
void setEditingCircleBounds();
void setGridPositions();
void setPointPositions();
void setGlPositions();
int getActivePoint() { return active_point_; }
int getActivePower() { return active_power_; }
int getActiveGridSection() { return active_grid_section_; }
bool isPainting() { return paint_ != temporary_paint_toggle_; }
bool isPaintEnabled() { return paint_; }
vital::poly_float adjustBoostPhase(vital::poly_float phase);
virtual void enableTemporaryPaintToggle(bool toggle);

bool active_;
std::vector<Listener*> listeners_;

private:
float adjustBoostPhase(float phase);

static std::pair<vital::Output*, vital::Output*> getOutputs(const vital::output_map& mono_modulations,
const vital::output_map& poly_modulations,
String name) {
return {
mono_modulations.at(name.toStdString()),
poly_modulations.at(name.toStdString())
};
}

int getHoverPoint(Point<float> position);
int getHoverPower(Point<float> position);
float getSnapRadiusX();
float getSnapRadiusY();
float getSnappedX(float x);
float getSnappedY(float y);
void addPointAt(Point<float> position);
void movePoint(int index, Point<float> position, bool snap);
void movePower(int index, Point<float> position, bool all, bool alternate);
void removePoint(int index);
float getMinX(int index);
float getMaxX(int index);

Point<float> valuesToOpenGlPosition(float x, float y);
Point<float> getPowerPosition(int index);
bool powerActive(int index);

LineGenerator* model_;
int active_point_;
int active_power_;
int active_grid_section_;
bool dragging_;
bool reset_positions_;
bool allow_file_loading_;
Point<float> last_mouse_position_;
int last_model_render_;
bool loop_;
int grid_size_x_;
int grid_size_y_;
bool paint_;

bool temporary_paint_toggle_;
std::vector<std::pair<float, float>> paint_pattern_;

vital::poly_float last_phase_;
vital::poly_float last_voice_;
vital::poly_float last_last_voice_;
float size_ratio_;

OpenGlQuad drag_circle_;
OpenGlQuad hover_circle_;
OpenGlMultiQuad grid_lines_;
OpenGlQuad position_circle_;
OpenGlMultiQuad point_circles_;
OpenGlMultiQuad power_circles_;
std::unique_ptr<OpenGlTextEditor> value_entry_;
bool entering_phase_;
int entering_index_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LineEditor)
};


+ 129
- 0
ports/vitalium/source/interface/editor_components/line_map_editor.cpp View File

@@ -0,0 +1,129 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "line_map_editor.h"

#include "skin.h"
#include "shaders.h"
#include "synth_gui_interface.h"
#include "utils.h"

LineMapEditor::LineMapEditor(LineGenerator* line_source, String name) : LineEditor(line_source) {
animate_ = true;
raw_input_ = nullptr;
last_phase_ = 0.0f;

setFill(true);
setFillCenter(-1.0f);
setLoop(false);
setName(name);
setBoostAmount(0.0f);
setFillBoostAmount(0.0f);
}

LineMapEditor::~LineMapEditor() { }

void LineMapEditor::parentHierarchyChanged() {
SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();

if (parent && raw_input_ == nullptr)
raw_input_ = parent->getSynth()->getStatusOutput(getName().toStdString());

LineEditor::parentHierarchyChanged();
}

void LineMapEditor::render(OpenGlWrapper& open_gl, bool animate) {
setGlPositions();
renderGrid(open_gl, animate);

setLineWidth(findValue(Skin::kWidgetLineWidth));

Colour envelope_graph_fill = findColour(Skin::kWidgetSecondary1, true);
Colour envelope_graph_fill_stereo = findColour(Skin::kWidgetSecondary2, true);

float fill_fade = findValue(Skin::kWidgetFillFade);
if (!active_) {
envelope_graph_fill = findColour(Skin::kWidgetSecondaryDisabled, true);
envelope_graph_fill_stereo = envelope_graph_fill;
}
Colour envelope_graph_fill_fade = envelope_graph_fill.withMultipliedAlpha(1.0f - fill_fade);
Colour envelope_graph_fill_stereo_fade = envelope_graph_fill_stereo.withMultipliedAlpha(1.0f - fill_fade);

Colour position_color = findColour(Skin::kWidgetPrimary1, true);
Colour position_color_stereo = findColour(Skin::kWidgetPrimary2, true);
Colour center = findColour(Skin::kWidgetCenterLine, true);
if (!active_) {
position_color = findColour(Skin::kWidgetPrimaryDisabled, true);
center = position_color;
position_color_stereo = position_color;
}

if (animate && animate_) {
decayBoosts(kTailDecay);

vital::poly_float phase = raw_input_->value();
if (!raw_input_->isClearValue(phase)) {
vital::poly_float adjusted_phase = adjustBoostPhase(phase);
boostRange(last_phase_, adjusted_phase, kNumWrapPoints, kTailDecay);
last_phase_ = adjusted_phase;
}

setFill(true);
setBoostAmount(findValue(Skin::kWidgetLineBoost));
setFillBoostAmount(findValue(Skin::kWidgetFillBoost));

setIndex(1);
setColor(position_color_stereo);
setFillColors(envelope_graph_fill_stereo_fade, envelope_graph_fill_stereo);
drawLines(open_gl, false);

setIndex(0);
setColor(position_color);
setFillColors(envelope_graph_fill_fade, envelope_graph_fill);
drawLines(open_gl, true);

setFill(false);
setBoostAmount(0.0f);
setFillBoostAmount(0.0f);
setColor(center);
LineEditor::render(open_gl, false);

setViewPort(open_gl);
drawPosition(open_gl, position_color_stereo, phase[1]);
drawPosition(open_gl, position_color, phase[0]);
}
else {
setBoostAmount(0.0f);
setFillBoostAmount(0.0f);
decayBoosts(0.0f);

setFill(true);
setColor(position_color_stereo);
setFillColors(envelope_graph_fill_stereo_fade, envelope_graph_fill_stereo);
drawLines(open_gl, false);

setColor(position_color);
setFillColors(envelope_graph_fill_fade, envelope_graph_fill);
drawLines(open_gl, true);

setFill(false);
setColor(center);
drawLines(open_gl, true);
}

renderPoints(open_gl, animate);
renderCorners(open_gl, animate);
}

+ 46
- 0
ports/vitalium/source/interface/editor_components/line_map_editor.h View File

@@ -0,0 +1,46 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

#include "line_generator.h"
#include "open_gl_image.h"
#include "line_editor.h"
#include "synth_lfo.h"
#include "synth_module.h"

class LineMapEditor : public LineEditor {
public:
static constexpr float kTailDecay = 0.93f;

LineMapEditor(LineGenerator* line_source, String name);
virtual ~LineMapEditor();

void parentHierarchyChanged() override;

virtual void render(OpenGlWrapper& open_gl, bool animate) override;
void setAnimate(bool animate) { animate_ = animate; }

private:
const vital::StatusOutput* raw_input_;
bool animate_;
vital::poly_float last_phase_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LineMapEditor)
};


+ 256
- 0
ports/vitalium/source/interface/editor_components/midi_keyboard.cpp View File

@@ -0,0 +1,256 @@
/* 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_keyboard.h"

const float MidiKeyboard::kBlackKeyOffsets[kNumBlackKeysPerOctave] = {
1.0f - 0.6f * kBlackKeyWidthRatio,
2.0f - 0.4f * kBlackKeyWidthRatio,
4.0f - 0.7f * kBlackKeyWidthRatio,
5.0f - 0.5f * kBlackKeyWidthRatio,
6.0f - 0.3f * kBlackKeyWidthRatio,
};

const bool MidiKeyboard::kWhiteKeys[vital::kNotesPerOctave] = {
true, false, true, false, true, true, false, true, false, true, false, true
};

namespace {
int getBlackKeyOctaveOffset(int black_key_index) {
for (int i = 0; i < vital::kNotesPerOctave; ++i) {
if (!MidiKeyboard::kWhiteKeys[i]) {
if (black_key_index == 0)
return i;

black_key_index--;
}
}
return -1;
}

int getWhiteKeyOctaveOffset(int white_key_index) {
for (int i = 0; i < vital::kNotesPerOctave; ++i) {
if (MidiKeyboard::kWhiteKeys[i]) {
if (white_key_index == 0)
return i;

white_key_index--;
}
}
return -1;
}

int getBlackKeyIndexFromOffset(int note_offset) {
int black_key_index = 0;
for (int i = 0; i < note_offset; ++i) {
if (!MidiKeyboard::kWhiteKeys[i])
black_key_index++;
}
return black_key_index;
}

int getWhiteKeyIndexFromOffset(int note_offset) {
int white_key_index = 0;
for (int i = 0; i < note_offset; ++i) {
if (MidiKeyboard::kWhiteKeys[i])
white_key_index++;
}
return white_key_index;
}
}

MidiKeyboard::MidiKeyboard(MidiKeyboardState& state) :
OpenGlComponent("keyboard"), state_(state), midi_channel_(1), hover_note_(-1),
black_notes_(kNumBlackKeys, Shaders::kRoundedRectangleFragment),
white_pressed_notes_(kNumWhiteKeys, Shaders::kRoundedRectangleFragment),
black_pressed_notes_(kNumBlackKeys, Shaders::kRoundedRectangleFragment),
hover_note_quad_(Shaders::kRoundedRectangleFragment) {
black_notes_.setTargetComponent(this);
white_pressed_notes_.setTargetComponent(this);
black_pressed_notes_.setTargetComponent(this);
hover_note_quad_.setTargetComponent(this);
hover_note_quad_.setQuad(0, -2.0f, -2.0f, 0.0f, 0.0f);

int num_children = getNumChildComponents();

for (int i = 0; i < num_children; ++i) {
Component* child = getChildComponent(i);
child->setWantsKeyboardFocus(false);
}
}

void MidiKeyboard::paintBackground(Graphics& g) {
float width = getWidth();
int height = getHeight();
g.setColour(black_key_color_);
for (int i = 1; i < kNumWhiteKeys; ++i) {
int x = i * width / kNumWhiteKeys;
g.fillRect(x, 0, 1, height);
}
}

void MidiKeyboard::parentHierarchyChanged() {
setColors();
}

void MidiKeyboard::setColors() {
if (findParentComponentOfClass<SynthGuiInterface>() == nullptr)
return;
key_press_color_ = findColour(Skin::kWidgetPrimary1, true);
hover_color_ = findColour(Skin::kWidgetAccent2, true);
white_key_color_ = findColour(Skin::kWidgetSecondary1, true);
black_key_color_ = findColour(Skin::kWidgetSecondary2, true);
}

void MidiKeyboard::resized() {
OpenGlComponent::resized();
setColors();

float width = getWidth();
float height = getHeight();
float black_key_height = 2.0f * ((int)(height * kBlackKeyHeightRatio)) / height;
float black_key_y = 1.0f - black_key_height;
float white_key_width = 2.0f / kNumWhiteKeys;
float black_key_width = (((int)(kBlackKeyWidthRatio * white_key_width * width / 4.0f)) * 4.0f + 2.0f) / width;
float octave_width = kNumWhiteKeysPerOctave * white_key_width;
for (int i = 0; i < kNumBlackKeys; ++i) {
int octave = i / kNumBlackKeysPerOctave;
int index = i % kNumBlackKeysPerOctave;

float x = -1.0f + octave_width * octave + kBlackKeyOffsets[index] * white_key_width;
x = ((int)((x + 1.0f) * width / 2.0f)) * 2.0f / width - 1.0f;
black_notes_.setQuad(i, x, black_key_y, black_key_width, black_key_height + 0.5f);
}

float widget_rounding = findValue(Skin::kWidgetRoundedCorner);
black_notes_.setRounding(widget_rounding);
hover_note_quad_.setRounding(widget_rounding);
black_pressed_notes_.setRounding(widget_rounding);
}

int MidiKeyboard::getNoteAtPosition(Point<float> position) {
float white_key_position = kNumWhiteKeys * position.x / getWidth();
int octave = white_key_position / kNumWhiteKeysPerOctave;
float white_key_in_octave = white_key_position - octave * kNumWhiteKeysPerOctave;

if (isBlackKeyHeight(position)) {
for (int i = 0; i < kNumBlackKeysPerOctave; ++i) {
float note_offset = white_key_in_octave - kBlackKeyOffsets[i];
if (note_offset <= kBlackKeyWidthRatio && note_offset >= 0.0f) {
int note = octave * vital::kNotesPerOctave + getBlackKeyOctaveOffset(i);
return std::min(vital::kMidiSize - 1, std::max(note, 0));
}
}
}

int white_key_index = std::min<int>(kNumWhiteKeysPerOctave - 1, white_key_in_octave);
int note = octave * vital::kNotesPerOctave + getWhiteKeyOctaveOffset(white_key_index);
return std::min(vital::kMidiSize - 1, std::max(note, 0));
}

float MidiKeyboard::getVelocityForNote(int midi, Point<float> position) {
static constexpr float kMinVelocity = 1.0f / (vital::kMidiSize - 1);

float velocity = position.y / getHeight();
if (!isWhiteKey(midi))
velocity = position.y / (kBlackKeyHeightRatio * getHeight());

return std::max(kMinVelocity, std::min(1.0f, velocity));
}

void MidiKeyboard::render(OpenGlWrapper& open_gl, bool animate) {
setPressedKeyPositions();

hover_note_quad_.setColor(hover_color_);
int hover_note = hover_note_;

if (hover_note >= 0) {
int octave = hover_note / vital::kNotesPerOctave;
int note_offset = hover_note - octave * vital::kNotesPerOctave;
if (isWhiteKey(hover_note)) {
int index = octave * kNumWhiteKeysPerOctave + getWhiteKeyIndexFromOffset(note_offset);
setWhiteKeyQuad(&hover_note_quad_, 0, index);
hover_note_quad_.render(open_gl, animate);
}
else {
int index = octave * kNumBlackKeysPerOctave + getBlackKeyIndexFromOffset(note_offset);
setBlackKeyQuad(&hover_note_quad_, 0, index);
}
}

white_pressed_notes_.setColor(key_press_color_);
white_pressed_notes_.render(open_gl, animate);

black_notes_.setColor(black_key_color_);
black_notes_.render(open_gl, animate);

if (hover_note >= 0 && !isWhiteKey(hover_note))
hover_note_quad_.render(open_gl, animate);

black_pressed_notes_.setColor(key_press_color_);
black_pressed_notes_.render(open_gl, animate);
}

void MidiKeyboard::setPressedKeyPositions() {
int num_pressed_white_keys = 0;
int num_pressed_black_keys = 0;
int white_key_index = 0;
int black_key_index = 0;
for (int i = 0; i < vital::kMidiSize; ++i) {
bool white_key = isWhiteKey(i);
if (state_.isNoteOnForChannels(0xffff, i)) {
if (white_key) {
setWhiteKeyQuad(&white_pressed_notes_, num_pressed_white_keys, white_key_index);
num_pressed_white_keys++;
}
else {
setBlackKeyQuad(&black_pressed_notes_, num_pressed_black_keys, black_key_index);
num_pressed_black_keys++;
}
}

if (white_key)
white_key_index++;
else
black_key_index++;
}

white_pressed_notes_.setNumQuads(num_pressed_white_keys);
black_pressed_notes_.setNumQuads(num_pressed_black_keys);
}

void MidiKeyboard::setWhiteKeyQuad(OpenGlMultiQuad* quads, int quad_index, int white_key_index) {
float full_width = getWidth();
int start_x = white_key_index * full_width / kNumWhiteKeys + 1;
int end_x = (white_key_index + 1) * full_width / kNumWhiteKeys;
float x = 2.0f * start_x / full_width - 1.0f;
float width = 2.0f * (end_x - start_x) / full_width;

quads->setQuad(quad_index, x, -2.0f, width, 4.0f);
}

void MidiKeyboard::setBlackKeyQuad(OpenGlMultiQuad* quads, int quad_index, int black_key_index) {
float x = black_notes_.getQuadX(black_key_index);
float y = black_notes_.getQuadY(black_key_index);
float width = black_notes_.getQuadWidth(black_key_index);
float height = black_notes_.getQuadHeight(black_key_index);
float border = 2.0f / getWidth();
float y_adjust = 2.0f / getHeight();

quads->setQuad(quad_index, x + border, y + y_adjust, width - 2.0f * border, height);
}

+ 120
- 0
ports/vitalium/source/interface/editor_components/midi_keyboard.h View File

@@ -0,0 +1,120 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

#include "open_gl_multi_quad.h"
#include "synth_gui_interface.h"

class MidiKeyboard : public OpenGlComponent {
public:
static const float kBlackKeyOffsets[];
static const bool kWhiteKeys[];

static constexpr int kNumWhiteKeys = 75;
static constexpr int kNumWhiteKeysPerOctave = 7;
static constexpr int kNumBlackKeys = vital::kMidiSize - kNumWhiteKeys;
static constexpr int kNumBlackKeysPerOctave = vital::kNotesPerOctave - kNumWhiteKeysPerOctave;
static constexpr float kBlackKeyHeightRatio = 0.7f;
static constexpr float kBlackKeyWidthRatio = 0.8f;

force_inline static bool isWhiteKey(int midi) {
return kWhiteKeys[midi % vital::kNotesPerOctave];
}

MidiKeyboard(MidiKeyboardState& state);

void paintBackground(Graphics& g) override;
void parentHierarchyChanged() override;
void resized() override;
int getNoteAtPosition(Point<float> position);
bool isBlackKeyHeight(Point<float> position) { return position.y / getHeight() < kBlackKeyHeightRatio; }
float getVelocityForNote(int midi, Point<float> position);

void init(OpenGlWrapper& open_gl) override {
black_notes_.init(open_gl);
white_pressed_notes_.init(open_gl);
black_pressed_notes_.init(open_gl);
}
void render(OpenGlWrapper& open_gl, bool animate) override;
void setPressedKeyPositions();

void destroy(OpenGlWrapper& open_gl) override {
black_notes_.destroy(open_gl);
white_pressed_notes_.destroy(open_gl);
black_pressed_notes_.destroy(open_gl);
}

void mouseDown(const MouseEvent& e) override {
hover_note_ = getNoteAtPosition(e.position);
state_.noteOn(midi_channel_, hover_note_, getVelocityForNote(hover_note_, e.position));
}

void mouseUp(const MouseEvent& e) override {
state_.noteOff(midi_channel_, hover_note_, 0.0f);
hover_note_ = getNoteAtPosition(e.position);
}

void mouseEnter(const MouseEvent& e) override {
hover_note_ = getNoteAtPosition(e.position);
}

void mouseExit(const MouseEvent& e) override {
hover_note_ = -1;
}

void mouseDrag(const MouseEvent& e) override {
int note = getNoteAtPosition(e.position);
if (note == hover_note_)
return;

state_.noteOff(midi_channel_, hover_note_, 0.0f);
state_.noteOn(midi_channel_, note, getVelocityForNote(note, e.position));
hover_note_ = note;
}

void mouseMove(const MouseEvent& e) override {
hover_note_ = getNoteAtPosition(e.position);
}

void setMidiChannel(int channel) { midi_channel_ = channel; }
void setColors();

private:
void setWhiteKeyQuad(OpenGlMultiQuad* quads, int quad_index, int white_key_index);
void setBlackKeyQuad(OpenGlMultiQuad* quads, int quad_index, int black_key_index);

MidiKeyboardState& state_;

int midi_channel_;
int hover_note_;

OpenGlMultiQuad black_notes_;
OpenGlMultiQuad white_pressed_notes_;
OpenGlMultiQuad black_pressed_notes_;
OpenGlQuad hover_note_quad_;

Colour key_press_color_;
Colour hover_color_;
Colour white_key_color_;
Colour black_key_color_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiKeyboard)
};


+ 343
- 0
ports/vitalium/source/interface/editor_components/modulation_button.cpp View File

@@ -0,0 +1,343 @@
/* 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 "modulation_button.h"

#include "default_look_and_feel.h"
#include "modulation_matrix.h"
#include "synth_gui_interface.h"
#include "fonts.h"
#include "skin.h"

ModulationButton::ModulationButton(String name) : PlainShapeComponent(std::move(name)), parent_(nullptr),
mouse_state_(kNone), selected_(false), connect_right_(false),
draw_border_(false), active_modulation_(false), font_size_(12.0f),
show_drag_drop_(false), drag_drop_alpha_(0.0f) {
setWantsKeyboardFocus(true);
Path shape = Paths::dragDropArrows();
shape.addLineSegment(Line<float>(-50.0f, -50.0f, -50.0f, -50.0f), 0.2f);
setShape(Paths::dragDropArrows());
setComponent(&drag_drop_area_);
setActive(false);
setUseAlpha(true);
setInterceptsMouseClicks(true, false);
addAndMakeVisible(drag_drop_area_);
drag_drop_area_.setInterceptsMouseClicks(false, false);
setColor(Colours::transparentWhite);
}

ModulationButton::~ModulationButton() {
if (parent_)
parent_->getSynth()->forceShowModulation(getName().toStdString(), false);
}

bool ModulationButton::hasAnyModulation() {
if (parent_)
return parent_->getSynth()->isSourceConnected(getName().toStdString());
return false;
}

Rectangle<int> ModulationButton::getModulationAmountBounds(int index, int total) {
int columns = kModulationKnobColumns;

int row = index / columns;
int column = index % columns;

Rectangle<int> all_bounds = getModulationAreaBounds();
int x = all_bounds.getX() + (all_bounds.getWidth() * column) / columns;
int right = all_bounds.getX() + (all_bounds.getWidth() * (column + 1)) / columns;
int width = right - x;
int y = all_bounds.getY() + all_bounds.getHeight() - width * (row + 1);
return Rectangle<int>(x, y, width, width);
}

Rectangle<int> ModulationButton::getMeterBounds() {
static constexpr int kMinMeterWidth = 4;

int width = getWidth();
int meter_width = std::max<int>(kMinMeterWidth, std::round(width * kMeterAreaRatio / 2.0f) * 2);
int meter_height = getHeight() - 2;
return Rectangle<int>(1, 1, meter_width, meter_height);
}

Rectangle<int> ModulationButton::getModulationAreaBounds() {
static constexpr int kMaxWidthHeightRatio = 3;

SynthSection* parent = findParentComponentOfClass<SynthSection>();
int widget_margin = 0;
if (parent)
widget_margin = parent->findValue(Skin::kWidgetMargin);

int width = getWidth() - getMeterBounds().getRight();
int height = getHeight();

int widget_width = width - 2 * widget_margin;
int knob_width = widget_width / kModulationKnobColumns;
widget_width = knob_width * kModulationKnobColumns;
int widget_x = getMeterBounds().getRight() + (width - widget_width) / 2;
int min_y = kFontAreaHeightRatio * width;
int max_widget_height = ceilf(widget_width * 2.0f / 3.0f);
int widget_y = std::max(min_y, height - widget_margin - max_widget_height);
int widget_height = height - widget_y - widget_margin;
int center_y = widget_y + widget_height / 2;
widget_height = std::max(widget_height, (widget_width + kMaxWidthHeightRatio - 1) / kMaxWidthHeightRatio);
widget_y = center_y - widget_height / 2;
return Rectangle<int>(widget_x, widget_y, widget_width, widget_height);
}

void ModulationButton::paintBackground(Graphics& g) {
static constexpr float kShadowArea = 0.04f;

if (getWidth() == 0 || getHeight() == 0)
return;

if (selected_)
g.setColour(findColour(Skin::kModulationButtonSelected, true));
else
g.setColour(findColour(Skin::kModulationButtonUnselected, true));

SynthSection* parent = findParentComponentOfClass<SynthSection>();
int rounding_amount = 0;
if (parent)
rounding_amount = parent->findValue(Skin::kBodyRounding);

Rectangle<float> meter_bounds = getMeterBounds().toFloat();
int width = getWidth();
int adjusted_width = connect_right_ ? width * 2 : width;
Rectangle<float> bounds(0, 0, adjusted_width, getHeight());
g.fillRoundedRectangle(bounds, rounding_amount);

g.setColour(findColour(Skin::kWidgetBackground, true));
g.fillRoundedRectangle(meter_bounds, meter_bounds.getWidth() / 2.0f);
float meter_width = meter_bounds.getWidth();
g.fillRect(meter_bounds.getX() + meter_width / 2.0f, meter_bounds.getY(), meter_width / 2, meter_bounds.getHeight());

if (draw_border_) {
g.setColour(findColour(Skin::kBorder, true));
g.drawRoundedRectangle(bounds.reduced(0.5f), rounding_amount, 1.0f);
}

int height = getHeight();
g.setColour(findColour(Skin::kBodyText, true));
g.setFont(Fonts::instance()->proportional_regular().withPointHeight(font_size_));
String text = text_override_;
if (text.isEmpty())
text = ModulationMatrix::getUiSourceDisplayName(getName());

int font_area_height = kFontAreaHeightRatio * width;
g.drawText(text, meter_bounds.getRight(), 0, width - meter_bounds.getRight(),
font_area_height, Justification::centred);

if (connect_right_ && !selected_) {
int shadow_width = width * kShadowArea;
Colour shadow_color = findColour(Skin::kShadow, true);
ColourGradient gradient(shadow_color, width, 0, shadow_color.withAlpha(0.0f), width - shadow_width, 0, false);
g.setGradientFill(gradient);
g.fillRect(width - shadow_width, 0, shadow_width, height);
}
}

void ModulationButton::parentHierarchyChanged() {
if (parent_ == nullptr) {
parent_ = findParentComponentOfClass<SynthGuiInterface>();
setForceEnableModulationSource();
}
}

void ModulationButton::resized() {
static constexpr float kBorder = 0.2f;

PlainShapeComponent::resized();
Rectangle<float> meter_bounds = getMeterBounds().toFloat();
int left = meter_bounds.getRight();
int width = getWidth() - left;
int font_area_height = kFontAreaHeightRatio * width;
int top = font_area_height - (font_area_height - font_size_) * 0.5f;
int height = getHeight() - top;

float size_mult = 1.0f - 2.0f * kBorder;
drag_drop_area_.setBounds(left + width * kBorder, top + height * kBorder,
width * size_mult, height * size_mult);
}

void ModulationButton::render(OpenGlWrapper& open_gl, bool animate) {
static constexpr float kDeltaAlpha = 0.15f;

float target = 0.0f;
if (show_drag_drop_) {
target = 1.0f;
if (mouse_state_ == kMouseDown || mouse_state_ == kMouseDragging)
target = 2.0f;
}

bool increase = drag_drop_alpha_ < target;
if (increase)
drag_drop_alpha_ = std::min(drag_drop_alpha_ + kDeltaAlpha, target);
else
drag_drop_alpha_ = std::max(drag_drop_alpha_ - kDeltaAlpha, target);

if (drag_drop_alpha_ <= 0.0f) {
drag_drop_alpha_ = 0.0f;
setActive(false);
}

setColor(drag_drop_color_.withMultipliedAlpha(drag_drop_alpha_));
PlainShapeComponent::render(open_gl, animate);
}

void ModulationButton::mouseDown(const MouseEvent& e) {
if (e.mods.isPopupMenu()) {
if (parent_ == nullptr)
return;

std::vector<vital::ModulationConnection*> connections =
parent_->getSynth()->getSourceConnections(getName().toStdString());

if (connections.empty())
return;

mouse_state_ = kNone;

PopupItems options;
std::string disconnect = "Disconnect from ";
for (int i = 0; i < connections.size(); ++i) {
std::string destination = vital::Parameters::getDisplayName(connections[i]->destination_name);
options.addItem(kModulationList + i, disconnect + destination);
}

if (connections.size() > 1)
options.addItem(kDisconnect, "Disconnect all");

SynthSection* parent = findParentComponentOfClass<SynthSection>();
parent->showPopupSelector(this, e.getPosition(), options, [=](int selection) { disconnectIndex(selection); });
}
else {
setActiveModulation(true);
mouse_state_ = kMouseDown;

for (Listener* listener : listeners_)
listener->modulationSelected(this);
}
}

void ModulationButton::mouseDrag(const MouseEvent& e) {
if (e.mods.isRightButtonDown())
return;

if (!getLocalBounds().contains(e.getPosition()) && mouse_state_ != kDraggingOut) {
for (Listener* listener : listeners_)
listener->startModulationMap(this, e);
mouse_state_ = kDraggingOut;
setMouseCursor(MouseCursor::DraggingHandCursor);
}

if (mouse_state_ == kDraggingOut) {
for (Listener* listener : listeners_)
listener->modulationDragged(e);
}
else if (mouse_state_ != kMouseDragging)
mouse_state_ = kMouseDragging;
}

void ModulationButton::mouseUp(const MouseEvent& e) {
if (!e.mods.isRightButtonDown() && mouse_state_ == kDraggingOut) {
for (Listener* listener : listeners_)
listener->endModulationMap();
}
else if (!e.mods.isRightButtonDown()) {
for (Listener* listener : listeners_)
listener->modulationClicked(this);
}
setMouseCursor(MouseCursor::ParentCursor);

mouse_state_ = kHover;
}

void ModulationButton::mouseEnter(const MouseEvent& e) {
mouse_state_ = kHover;
drag_drop_color_ = findColour(Skin::kLightenScreen, true);
show_drag_drop_ = parent_->getSynth()->getSourceConnections(getName().toStdString()).empty();
setActive(show_drag_drop_);
redrawImage(true);
}

void ModulationButton::mouseExit(const MouseEvent& e) {
mouse_state_ = kNone;
show_drag_drop_ = false;
}

void ModulationButton::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) {
for (Listener* listener : listeners_)
listener->modulationWheelMoved(e, wheel);
}

void ModulationButton::focusLost(FocusChangeType cause) {
for (Listener* listener : listeners_)
listener->modulationLostFocus(this);
}

void ModulationButton::addListener(Listener* listener) {
listeners_.push_back(listener);
}

void ModulationButton::disconnectIndex(int index) {
if (parent_ == nullptr)
return;

std::vector<vital::ModulationConnection*> connections =
parent_->getSynth()->getSourceConnections(getName().toStdString());

if (index == kDisconnect) {
for (vital::ModulationConnection* connection : connections)
disconnectModulation(connection);
}
else if (index >= kModulationList) {
int connection_index = index - kModulationList;
disconnectModulation(connections[connection_index]);
}
}

void ModulationButton::select(bool select) {
selected_ = select;
setForceEnableModulationSource();
}

void ModulationButton::setActiveModulation(bool active) {
active_modulation_ = active;
setForceEnableModulationSource();
}

void ModulationButton::setForceEnableModulationSource() {
if (parent_)
parent_->getSynth()->forceShowModulation(getName().toStdString(), active_modulation_);
}

void ModulationButton::disconnectModulation(vital::ModulationConnection* connection) {
int modulations_left = parent_->getSynth()->getNumModulations(connection->destination_name);

for (Listener* listener : listeners_) {
listener->modulationDisconnected(connection, modulations_left <= 1);
listener->modulationConnectionChanged();
}

parent_->disconnectModulation(connection);

if (modulations_left <= 1) {
for (Listener* listener : listeners_)
listener->modulationCleared();
}
}


+ 121
- 0
ports/vitalium/source/interface/editor_components/modulation_button.h View File

@@ -0,0 +1,121 @@
/* 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 "open_gl_image_component.h"

namespace vital {
struct ModulationConnection;
} // namespace vital

class SynthGuiInterface;

class ModulationButton : public PlainShapeComponent {
public:
static constexpr float kFontAreaHeightRatio = 0.3f;
static constexpr int kModulationKnobColumns = 3;
static constexpr int kModulationKnobRows = 2;
static constexpr int kMaxModulationKnobs = kModulationKnobRows * kModulationKnobColumns;
static constexpr float kMeterAreaRatio = 0.05f;

enum MenuId {
kCancel = 0,
kDisconnect,
kModulationList
};

enum MouseState {
kNone,
kHover,
kMouseDown,
kMouseDragging,
kDraggingOut
};

class Listener {
public:
virtual ~Listener() = default;

virtual void modulationConnectionChanged() { }
virtual void modulationDisconnected(vital::ModulationConnection* connection, bool last) { }
virtual void modulationSelected(ModulationButton* source) { }
virtual void modulationLostFocus(ModulationButton* source) { }
virtual void startModulationMap(ModulationButton* source, const MouseEvent& e) { }
virtual void modulationDragged(const MouseEvent& e) { }
virtual void modulationWheelMoved(const MouseEvent& e, const MouseWheelDetails& wheel) { }
virtual void endModulationMap() { }
virtual void modulationClicked(ModulationButton* source) { }
virtual void modulationCleared() { }
};
ModulationButton(String name);
virtual ~ModulationButton();

void paintBackground(Graphics& g) override;
void parentHierarchyChanged() override;
void resized() override;

virtual void render(OpenGlWrapper& open_gl, bool animate) override;

void mouseDown(const MouseEvent& e) override;
void mouseDrag(const MouseEvent& e) override;
void mouseUp(const MouseEvent& e) override;
void mouseEnter(const MouseEvent& e) override;
void mouseExit(const MouseEvent& e) override;
void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override;
void focusLost(FocusChangeType cause) override;
void addListener(Listener* listener);
void disconnectIndex(int index);

void select(bool select);
bool isSelected() const { return selected_; }
void setActiveModulation(bool active);
bool isActiveModulation() const { return active_modulation_; }

void setForceEnableModulationSource();
bool hasAnyModulation();
void setFontSize(float size) { font_size_ = size; }
Rectangle<int> getModulationAmountBounds(int index, int total);
Rectangle<int> getModulationAreaBounds();
Rectangle<int> getMeterBounds();
void setConnectRight(bool connect) { connect_right_ = connect; repaint(); }
void setDrawBorder(bool border) { draw_border_ = border; repaint(); }
void overrideText(String text) { text_override_ = std::move(text); repaint(); }

private:
void disconnectModulation(vital::ModulationConnection* connection);

String text_override_;
SynthGuiInterface* parent_;
std::vector<Listener*> listeners_;
MouseState mouse_state_;
bool selected_;
bool connect_right_;
bool draw_border_;
bool active_modulation_;
OpenGlImageComponent drag_drop_;
Component drag_drop_area_;
float font_size_;

Colour drag_drop_color_;
bool show_drag_drop_;
float drag_drop_alpha_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModulationButton)
};


+ 214
- 0
ports/vitalium/source/interface/editor_components/modulation_meter.cpp View File

@@ -0,0 +1,214 @@
/* 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 "modulation_meter.h"

#include "open_gl_multi_quad.h"
#include "synth_gui_interface.h"
#include "shaders.h"
#include "synth_section.h"
#include "synth_slider.h"
#include "text_look_and_feel.h"

ModulationMeter::ModulationMeter(const vital::Output* mono_total, const vital::Output* poly_total,
const SynthSlider* slider, OpenGlMultiQuad* quads, int index) :
mono_total_(mono_total), poly_total_(poly_total), destination_(slider),
quads_(quads), index_(index), current_value_(0.0), mod_percent_(0.0) {

rotary_ = destination_->isRotary() && !destination_->isTextOrCurve();

if (destination_->getSliderStyle() == Slider::LinearBarVertical || destination_->isTextOrCurve())
quads->setRotatedCoordinates(index, -1.0f, -1.0f, 2.0f, 2.0f);

setInterceptsMouseClicks(false, false);
updateDrawing(false);
}

ModulationMeter::~ModulationMeter() { }

void ModulationMeter::resized() {
SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();
if (parent) {
std::vector<vital::ModulationConnection*> connections;
connections = parent->getSynth()->getSourceConnections(getName().toStdString());
setModulated(!connections.empty());
}

if (isVisible())
setVertices();
else
collapseVertices();
}

void ModulationMeter::setActive(bool active) {
if (active)
setVertices();
else
collapseVertices();
}

Rectangle<float> ModulationMeter::getMeterBounds() {
float width = getWidth();
float height = getHeight();
if (!destination_->isRotary() && !destination_->isTextOrCurve()) {
SynthSection* parent = findParentComponentOfClass<SynthSection>();
int widget_margin = parent->getWidgetMargin();

int total_width = destination_->isHorizontal() ? destination_->getHeight() : destination_->getWidth();
int extra = total_width % 2;
int slider_width = std::floor(SynthSlider::kLinearWidthPercent * total_width * 0.5f) * 2.0f + extra;

int inner_area = (total_width - slider_width) / 2;
int outer_area = inner_area - widget_margin;
int meter_width = SynthSlider::kLinearModulationPercent * total_width;
int border = std::max<int>(1, (widget_margin - meter_width) * 0.5f);

if (destination_->isHorizontal())
return Rectangle<float>(0.0f, outer_area + border, width, inner_area - outer_area - 2.0f * border);
return Rectangle<float>(outer_area + border, 0.0f, inner_area - outer_area - 2.0f * border, height);
}
else if (!destination_->isTextOrCurve()) {
float knob_scale = destination_->getKnobSizeScale();
float meter_width = destination_->findValue(Skin::kKnobModMeterArcSize) * knob_scale;
meter_width += destination_->findValue(Skin::kKnobModMeterArcThickness) * (1.0f - knob_scale);
float offset = destination_->findValue(Skin::kKnobOffset);

float center_x = getWidth() * 0.5f;
float center_y = getHeight() * 0.5f;
return Rectangle<float>(center_x - meter_width * 0.5f, center_y - meter_width * 0.5f + offset,
meter_width, meter_width);
}

return getLocalBounds().toFloat();
}

void ModulationMeter::setVertices() {
Rectangle<int> parent_bounds = getParentComponent()->getBounds();
Rectangle<int> bounds = getBounds();
Rectangle<float> meter_bounds = getMeterBounds();
float left = bounds.getX() + meter_bounds.getX();
float right = bounds.getX() + meter_bounds.getRight();
float top = parent_bounds.getHeight() - (bounds.getY() + meter_bounds.getY());
float bottom = parent_bounds.getHeight() - (bounds.getY() + meter_bounds.getBottom());

left_ = 2.0f * left / parent_bounds.getWidth() - 1.0f;
right_ = 2.0f * right / parent_bounds.getWidth() - 1.0f;
top_ = 2.0f * top / parent_bounds.getHeight() - 1.0f;
bottom_ = 2.0f * bottom / parent_bounds.getHeight() - 1.0f;
quads_->setQuad(index_, left_, bottom_, right_ - left_, top_ - bottom_);
}

void ModulationMeter::collapseVertices() {
left_ = right_ = top_ = bottom_= 0.0f;

quads_->setQuad(index_, left_, bottom_, right_ - left_, top_ - bottom_);
mod_percent_ = 0.0f;
}

void ModulationMeter::setAmountQuadVertices(OpenGlQuad& quad) {
Rectangle<float> meter_bounds = getMeterBounds();
if (rotary_)
meter_bounds.expand(2.0f, 2.0f);

float width = getWidth();
float height = getHeight();
float left = 2.0f * meter_bounds.getX() / width - 1.0f;
float bottom = 1.0f - 2.0f * meter_bounds.getBottom() / height;

bool vertical_bar = destination_->getSliderStyle() == Slider::LinearBarVertical || destination_->isTextOrCurve();
if (vertical_bar)
quad.setRotatedCoordinates(0, -1.0f, -1.0f, 2.0f, 2.0f);
else
quad.setCoordinates(0, -1.0f, -1.0f, 2.0f, 2.0f);

if (rotary_)
quad.setQuad(0, left, bottom, 2.0f * meter_bounds.getWidth() / width, 2.0f * meter_bounds.getHeight() / height);
else if (vertical_bar) {
float thickness = 2.0f / width;
quad.setQuad(0, left, bottom, thickness, 2.0f * meter_bounds.getHeight() / height);
}
else {
float thickness = 2.0f / height;
quad.setQuad(0, left, bottom + 2.0f * meter_bounds.getHeight() / height - thickness,
2.0f * meter_bounds.getWidth() / width, thickness);
}
}

void ModulationMeter::updateDrawing(bool use_poly) {
if (mono_total_) {
current_value_ = mono_total_->trigger_value;
if (poly_total_ && use_poly)
current_value_ += poly_total_->trigger_value;
}

float range = destination_->getMaximum() - destination_->getMinimum();
vital::poly_float value = (current_value_ - destination_->getMinimum()) * (1.0f / range);
mod_percent_ = vital::utils::clamp(value, 0.0f, 1.0f);
float knob_percent = (destination_->getValue() - destination_->getMinimum()) / range;

vital::poly_float min_percent = vital::utils::min(mod_percent_, knob_percent);
vital::poly_float max_percent = vital::utils::max(mod_percent_, knob_percent);

quads_->setQuad(index_, left_, bottom_, right_ - left_, top_ - bottom_);

if (rotary_) {
if (&destination_->getLookAndFeel() == TextLookAndFeel::instance()) {
min_percent = vital::utils::interpolate(-vital::kPi, 0.0f, min_percent);
max_percent = vital::utils::interpolate(-vital::kPi, 0.0f, max_percent);
}
else {
float angle = SynthSlider::kRotaryAngle;
min_percent = vital::utils::interpolate(-angle, angle, min_percent);
max_percent = vital::utils::interpolate(-angle, angle, max_percent);
}
}

quads_->setShaderValue(index_, min_percent[0], 0);
quads_->setShaderValue(index_, max_percent[0], 1);
quads_->setShaderValue(index_, min_percent[1], 2);
quads_->setShaderValue(index_, max_percent[1], 3);
}

void ModulationMeter::setModulationAmountQuad(OpenGlQuad& quad, float amount, bool bipolar) {
float range = destination_->getMaximum() - destination_->getMinimum();
float knob_percent = (destination_->getValue() - destination_->getMinimum()) / range;

float min_percent = std::min(knob_percent + amount, knob_percent);
float max_percent = std::max(knob_percent + amount, knob_percent);
if (bipolar) {
min_percent = std::min(knob_percent + amount * 0.5f, knob_percent - amount * 0.5f);
max_percent = std::max(knob_percent + amount * 0.5f, knob_percent - amount * 0.5f);
}

if (rotary_) {
if (&destination_->getLookAndFeel() == TextLookAndFeel::instance()) {
min_percent = vital::utils::interpolate(-vital::kPi, 0.0f, min_percent);
max_percent = vital::utils::interpolate(-vital::kPi, 0.0f, max_percent);
}
else {
float angle = SynthSlider::kRotaryAngle;
min_percent = vital::utils::interpolate(-angle, angle, min_percent);
max_percent = vital::utils::interpolate(-angle, angle, max_percent);
min_percent = std::max(-angle, min_percent);
max_percent = std::min(angle, max_percent);
}
}

quad.setShaderValue(0, min_percent, 0);
quad.setShaderValue(0, max_percent, 1);
quad.setShaderValue(0, min_percent, 2);
quad.setShaderValue(0, max_percent, 3);
}

+ 73
- 0
ports/vitalium/source/interface/editor_components/modulation_meter.h View File

@@ -0,0 +1,73 @@
/* 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"

class OpenGlMultiQuad;
class SynthSlider;
namespace vital {
struct Output;
}

class ModulationMeter : public Component {
public:
ModulationMeter(const vital::Output* mono_total, const vital::Output* poly_total,
const SynthSlider* slider, OpenGlMultiQuad* quads, int index);
virtual ~ModulationMeter();

void resized() override;
void setActive(bool active);

void updateDrawing(bool use_poly);
void setModulationAmountQuad(OpenGlQuad& quad, float amount, bool bipolar);
void setAmountQuadVertices(OpenGlQuad& quad);

bool isModulated() const { return modulated_; }
bool isRotary() const { return rotary_; }
void setModulated(bool modulated) { modulated_ = modulated; }
vital::poly_float getModPercent() { return mod_percent_; }

const SynthSlider* destination() { return destination_; }

private:
ModulationMeter() = delete;

Rectangle<float> getMeterBounds();
void setVertices();
void collapseVertices();

const vital::Output* mono_total_;
const vital::Output* poly_total_;
const SynthSlider* destination_;

OpenGlMultiQuad* quads_;
int index_;

vital::poly_float current_value_;
vital::poly_float mod_percent_;

bool modulated_;
bool rotary_;

float left_, right_, top_, bottom_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModulationMeter)
};


+ 185
- 0
ports/vitalium/source/interface/editor_components/modulation_tab_selector.cpp View File

@@ -0,0 +1,185 @@
/* 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 "modulation_tab_selector.h"
#include "modulation_button.h"
#include "synth_section.h"
#include "skin.h"

ModulationTabSelector::ModulationTabSelector(std::string prefix, int number) :
SynthSection(prefix), vertical_(true), selections_enabled_(false), min_modulations_shown_(0), num_shown_(0) {
for (int i = 0; i < number; ++i) {
std::string name = prefix + "_" + std::to_string(i + 1);
modulation_buttons_.push_back(std::make_unique<ModulationButton>(name));
addOpenGlComponent(modulation_buttons_.back().get());
modulation_buttons_.back()->addListener(this);
}
}

ModulationTabSelector::ModulationTabSelector(String name, int number, const char** names) :
SynthSection(name), vertical_(true), selections_enabled_(false), min_modulations_shown_(0), num_shown_(0) {
for (int i = 0; i < number; ++i) {
modulation_buttons_.push_back(std::make_unique<ModulationButton>(names[i]));
addOpenGlComponent(modulation_buttons_.back().get());
modulation_buttons_.back()->addListener(this);
}
}

ModulationTabSelector::~ModulationTabSelector() = default;

void ModulationTabSelector::paintBackground(Graphics& g) {
int num_to_show = getNumModulationsToShow();
if (num_shown_ != num_to_show) {
checkNumShown(false);
num_shown_ = num_to_show;
}

g.fillAll(findColour(Skin::kBackground, true));
paintTabShadow(g);

for (auto& button : modulation_buttons_) {
if (button->isVisible()) {
g.saveState();
Rectangle<int> bounds = getLocalArea(button.get(), button->getLocalBounds());
g.reduceClipRegion(bounds);
g.setOrigin(bounds.getTopLeft());
button->paintBackground(g);
g.restoreState();
}
}
}

void ModulationTabSelector::paintTabShadow(Graphics& g) {
SynthSection* parent = findParentComponentOfClass<SynthSection>();
if (parent == nullptr)
return;

int rounding_amount = parent->findValue(Skin::kBodyRounding);
g.setColour(findColour(Skin::kShadow, true));
g.fillRoundedRectangle(getLocalBounds().toFloat(), rounding_amount);
}

void ModulationTabSelector::resized() {
checkNumShown(false);
}

void ModulationTabSelector::checkNumShown(bool should_repaint) {
int num_to_show = getNumModulationsToShow();

if (vertical_) {
float cell_height = float(getHeight() + 1) / num_to_show;
int y = 0;

for (int i = 0; i < num_to_show; ++i) {
int last_y = y;
y = std::round((i + 1) * cell_height);
modulation_buttons_[i]->setBounds(0.0f, last_y, getWidth(), y - last_y - 1);
modulation_buttons_[i]->setVisible(true);
}
}
else {
float cell_width = float(getWidth() + 1) / num_to_show;
int x = 0;

for (int i = 0; i < num_to_show; ++i) {
int last_x = x;
x = std::round((i + 1) * cell_width);
modulation_buttons_[i]->setBounds(last_x, 0, x - last_x - 1, getHeight());
modulation_buttons_[i]->setVisible(true);
}
}

for (int i = num_to_show; i < modulation_buttons_.size(); ++i)
modulation_buttons_[i]->setVisible(false);

if (num_to_show != num_shown_ && should_repaint)
repaintBackground();
}

void ModulationTabSelector::reset() {
for (auto& button : modulation_buttons_) {
button->select(false);
button->setActiveModulation(false);
}

modulation_buttons_[0]->select(selections_enabled_);

if (getNumModulationsToShow() != num_shown_)
checkNumShown(true);

modulation_buttons_[0]->select(selections_enabled_);
for (Listener *listener : listeners_)
listener->modulationSelected(this, 0);
}

void ModulationTabSelector::modulationClicked(ModulationButton* source) {
int index = getModulationIndex(source->getName());

if (selections_enabled_) {
for (int i = 0; i < modulation_buttons_.size(); ++i)
modulation_buttons_[i]->select(index == i);
}

repaintBackground();

for (Listener *listener : listeners_)
listener->modulationSelected(this, index);
}

void ModulationTabSelector::endModulationMap() {
if (getNumModulationsToShow() != num_shown_)
checkNumShown(true);
}

void ModulationTabSelector::modulationConnectionChanged() {
if (getNumModulationsToShow() != num_shown_)
checkNumShown(true);
}

void ModulationTabSelector::modulationCleared() {
if (getNumModulationsToShow() != num_shown_)
checkNumShown(true);
}

void ModulationTabSelector::registerModulationButtons(SynthSection* synth_section) {
for (auto& button : modulation_buttons_)
synth_section->addModulationButton(button.get(), false);
}

void ModulationTabSelector::setFontSize(float font_size) {
for (auto& button : modulation_buttons_)
button->setFontSize(font_size);
}

int ModulationTabSelector::getNumModulationsToShow() {
int num_to_show = static_cast<int>(modulation_buttons_.size());
if (min_modulations_shown_ > 0) {
num_to_show = min_modulations_shown_;
for (int i = min_modulations_shown_ - 1; i < modulation_buttons_.size(); ++i) {
if (modulation_buttons_[i]->hasAnyModulation())
num_to_show = i + 2;
}
}
return std::min(num_to_show, static_cast<int>(modulation_buttons_.size()));
}

int ModulationTabSelector::getModulationIndex(String name) {
for (int i = 0; i < modulation_buttons_.size(); ++i) {
if (name == modulation_buttons_[i]->getName())
return i;
}
return 0;
}

+ 82
- 0
ports/vitalium/source/interface/editor_components/modulation_tab_selector.h View File

@@ -0,0 +1,82 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "JuceHeader.h"

#include "modulation_button.h"
#include "modulation_button.h"

class SynthSection;

class ModulationTabSelector : public SynthSection, public ModulationButton::Listener {
public:
class Listener {
public:
virtual ~Listener() = default;
virtual void modulationSelected(ModulationTabSelector* selector, int index) = 0;
};

ModulationTabSelector(std::string prefix, int number);
ModulationTabSelector(String name, int number, const char** names);
virtual ~ModulationTabSelector();

void paintBackground(Graphics& g) override;
void paintTabShadow(Graphics& g) override;
void resized() override;
void checkNumShown(bool should_repaint);
void reset() override;

void modulationClicked(ModulationButton* source) override;
void modulationConnectionChanged() override;
void endModulationMap() override;
void modulationCleared() override;

void addListener(Listener* listener) { listeners_.push_back(listener); }
void registerModulationButtons(SynthSection* synth_section);
void setFontSize(float font_size);

void setVertical(bool vertical) { vertical_ = vertical; }
void enableSelections() { selections_enabled_ = true; modulation_buttons_[0]->select(true); }
void setMinModulationsShown(int num) { min_modulations_shown_ = num; }

void connectRight(bool connect) {
for (auto& modulation_button : modulation_buttons_)
modulation_button->setConnectRight(connect);
}

ModulationButton* getButton(int index) { return modulation_buttons_[index].get(); }

void drawBorders(bool draw) {
for (auto& button : modulation_buttons_)
button->setDrawBorder(draw);
}

private:
int getModulationIndex(String name);
int getNumModulationsToShow();

std::vector<std::unique_ptr<ModulationButton>> modulation_buttons_;
std::vector<Listener*> listeners_;
bool vertical_;
bool selections_enabled_;
int min_modulations_shown_;
int num_shown_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModulationTabSelector)
};


+ 149
- 0
ports/vitalium/source/interface/editor_components/open_gl_background.cpp View File

@@ -0,0 +1,149 @@
/* 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 "open_gl_background.h"

#include "open_gl_component.h"
#include "common.h"
#include "shaders.h"

OpenGlBackground::OpenGlBackground() : image_shader_(nullptr), vertices_() {
new_background_ = false;
vertex_buffer_ = 0;
triangle_buffer_ = 0;
}

OpenGlBackground::~OpenGlBackground() { }

void OpenGlBackground::init(OpenGlWrapper& open_gl) {
static const float vertices[] = {
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};

memcpy(vertices_, vertices, 16 * sizeof(float));

static const int triangles[] = {
0, 1, 2,
2, 3, 0
};

open_gl.context.extensions.glGenBuffers(1, &vertex_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);

GLsizeiptr vert_size = static_cast<GLsizeiptr>(static_cast<size_t>(sizeof(vertices)));
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, vertices_, GL_STATIC_DRAW);

open_gl.context.extensions.glGenBuffers(1, &triangle_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangle_buffer_);

GLsizeiptr tri_size = static_cast<GLsizeiptr>(static_cast<size_t>(sizeof(triangles)));
open_gl.context.extensions.glBufferData(GL_ELEMENT_ARRAY_BUFFER, tri_size, triangles, GL_STATIC_DRAW);

image_shader_ = open_gl.shaders->getShaderProgram(Shaders::kImageVertex, Shaders::kImageFragment);
image_shader_->use();
position_ = OpenGlComponent::getAttribute(open_gl, *image_shader_, "position");
texture_coordinates_ = OpenGlComponent::getAttribute(open_gl, *image_shader_, "tex_coord_in");
texture_uniform_ = OpenGlComponent::getUniform(open_gl, *image_shader_, "image");
}

void OpenGlBackground::destroy(OpenGlWrapper& open_gl) {
if (background_.getWidth())
background_.release();

image_shader_ = nullptr;
position_ = nullptr;
texture_coordinates_ = nullptr;
texture_uniform_ = nullptr;

open_gl.context.extensions.glDeleteBuffers(1, &vertex_buffer_);
open_gl.context.extensions.glDeleteBuffers(1, &triangle_buffer_);
}

void OpenGlBackground::bind(OpenGLContext& open_gl_context) {
open_gl_context.extensions.glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
open_gl_context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangle_buffer_);
background_.bind();
}

void OpenGlBackground::enableAttributes(OpenGLContext& open_gl_context) {
if (position_ != nullptr) {
open_gl_context.extensions.glVertexAttribPointer(position_->attributeID, 2, GL_FLOAT,
GL_FALSE, 4 * sizeof(float), nullptr);
open_gl_context.extensions.glEnableVertexAttribArray(position_->attributeID);
}
if (texture_coordinates_ != nullptr) {
open_gl_context.extensions.glVertexAttribPointer(texture_coordinates_->attributeID, 2, GL_FLOAT,
GL_FALSE, 4 * sizeof(float),
(GLvoid*)(2 * sizeof(float)));
open_gl_context.extensions.glEnableVertexAttribArray(texture_coordinates_->attributeID);
}
}

void OpenGlBackground::disableAttributes(OpenGLContext& open_gl_context) {
if (position_ != nullptr)
open_gl_context.extensions.glDisableVertexAttribArray(position_->attributeID);

if (texture_coordinates_ != nullptr)
open_gl_context.extensions.glDisableVertexAttribArray(texture_coordinates_->attributeID);
}

void OpenGlBackground::render(OpenGlWrapper& open_gl) {
mutex_.lock();
if ((new_background_ || background_.getWidth() == 0) && background_image_.getWidth() > 0) {
new_background_ = false;
background_.loadImage(background_image_);

float width_ratio = (1.0f * background_.getWidth()) / background_image_.getWidth();
float height_ratio = (1.0f * background_.getHeight()) / background_image_.getHeight();
float width_end = 2.0f * width_ratio - 1.0f;
float height_end = 1.0f - 2.0f * height_ratio;

vertices_[8] = vertices_[12] = width_end;
vertices_[5] = vertices_[9] = height_end;

open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
GLsizeiptr vert_size = static_cast<GLsizeiptr>(static_cast<size_t>(16 * sizeof(float)));
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, vertices_, GL_STATIC_DRAW);
}

glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);

image_shader_->use();
bind(open_gl.context);
open_gl.context.extensions.glActiveTexture(GL_TEXTURE0);

if (texture_uniform_ != nullptr && background_.getWidth())
texture_uniform_->set(0);

enableAttributes(open_gl.context);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
disableAttributes(open_gl.context);
background_.unbind();

open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

mutex_.unlock();
}

void OpenGlBackground::updateBackgroundImage(Image background) {
background_image_ = background;
new_background_ = true;
}

+ 62
- 0
ports/vitalium/source/interface/editor_components/open_gl_background.h View File

@@ -0,0 +1,62 @@
/* 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 "open_gl_component.h"

#include <mutex>

class OpenGlBackground {
public:
OpenGlBackground();
virtual ~OpenGlBackground();

void updateBackgroundImage(Image background);
virtual void init(OpenGlWrapper& open_gl);
virtual void render(OpenGlWrapper& open_gl);
virtual void destroy(OpenGlWrapper& open_gl);

void lock() { mutex_.lock(); }
void unlock() { mutex_.unlock(); }

OpenGLShaderProgram* shader() { return image_shader_; }
OpenGLShaderProgram::Uniform* texture_uniform() { return texture_uniform_.get(); }

void bind(OpenGLContext& open_gl_context);
void enableAttributes(OpenGLContext& open_gl_context);
void disableAttributes(OpenGLContext& open_gl_context);

private:
OpenGLShaderProgram* image_shader_;
std::unique_ptr<OpenGLShaderProgram::Uniform> texture_uniform_;
std::unique_ptr<OpenGLShaderProgram::Attribute> position_;
std::unique_ptr<OpenGLShaderProgram::Attribute> texture_coordinates_;

float vertices_[16];

std::mutex mutex_;
OpenGLTexture background_;
bool new_background_;
Image background_image_;

GLuint vertex_buffer_;
GLuint triangle_buffer_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OpenGlBackground)
};


+ 193
- 0
ports/vitalium/source/interface/editor_components/open_gl_component.cpp View File

@@ -0,0 +1,193 @@
/* 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 "open_gl_component.h"

#include "open_gl_multi_quad.h"
#include "full_interface.h"
#include "skin.h"

namespace {
Rectangle<int> getGlobalBounds(Component* component, Rectangle<int> bounds) {
Component* parent = component->getParentComponent();
while (parent && dynamic_cast<FullInterface*>(component) == nullptr) {
bounds = bounds + component->getPosition();
component = parent;
parent = component->getParentComponent();
}

return bounds;
}

Rectangle<int> getGlobalVisibleBounds(Component* component, Rectangle<int> visible_bounds) {
Component* parent = component->getParentComponent();
while (parent && dynamic_cast<FullInterface*>(parent) == nullptr) {
visible_bounds = visible_bounds + component->getPosition();
parent->getLocalBounds().intersectRectangle(visible_bounds);
component = parent;
parent = component->getParentComponent();
}

return visible_bounds + component->getPosition();
}
}

OpenGlComponent::OpenGlComponent(String name) : Component(name), only_bottom_corners_(false),
parent_(nullptr), skin_override_(Skin::kNone),
num_voices_readout_(nullptr) {
background_color_ = Colours::transparentBlack;
}

OpenGlComponent::~OpenGlComponent() { }

bool OpenGlComponent::setViewPort(Component* component, Rectangle<int> bounds, OpenGlWrapper& open_gl) {
FullInterface* top_level = component->findParentComponentOfClass<FullInterface>();
float scale = open_gl.display_scale;
float resize_scale = top_level->getResizingScale();
float render_scale = 1.0f;
if (scale == 1.0f)
render_scale *= open_gl.context.getRenderingScale();

float gl_scale = render_scale * resize_scale;

Rectangle<int> top_level_bounds = top_level->getBounds();
Rectangle<int> global_bounds = getGlobalBounds(component, bounds);
Rectangle<int> visible_bounds = getGlobalVisibleBounds(component, bounds);

glViewport(gl_scale * global_bounds.getX(),
std::ceil(scale * render_scale * top_level_bounds.getHeight()) - gl_scale * global_bounds.getBottom(),
gl_scale * global_bounds.getWidth(), gl_scale * global_bounds.getHeight());

if (visible_bounds.getWidth() <= 0 || visible_bounds.getHeight() <= 0)
return false;

glScissor(gl_scale * visible_bounds.getX(),
std::ceil(scale * render_scale * top_level_bounds.getHeight()) - gl_scale * visible_bounds.getBottom(),
gl_scale * visible_bounds.getWidth(), gl_scale * visible_bounds.getHeight());

return true;
}

bool OpenGlComponent::setViewPort(Component* component, OpenGlWrapper& open_gl) {
return setViewPort(component, component->getLocalBounds(), open_gl);
}

bool OpenGlComponent::setViewPort(OpenGlWrapper& open_gl) {
return setViewPort(this, open_gl);
}

void OpenGlComponent::setScissor(Component* component, OpenGlWrapper& open_gl) {
setScissorBounds(component, component->getLocalBounds(), open_gl);
}

void OpenGlComponent::setScissorBounds(Component* component, Rectangle<int> bounds, OpenGlWrapper& open_gl) {
if (component == nullptr)
return;

FullInterface* top_level = component->findParentComponentOfClass<FullInterface>();
float scale = open_gl.display_scale;
float resize_scale = top_level->getResizingScale();
float render_scale = 1.0f;
if (scale == 1.0f)
render_scale *= open_gl.context.getRenderingScale();

float gl_scale = render_scale * resize_scale;

Rectangle<int> top_level_bounds = top_level->getBounds();
Rectangle<int> visible_bounds = getGlobalVisibleBounds(component, bounds);

if (visible_bounds.getHeight() > 0 && visible_bounds.getWidth() > 0) {
glScissor(gl_scale * visible_bounds.getX(),
std::ceil(scale * render_scale * top_level_bounds.getHeight()) - gl_scale * visible_bounds.getBottom(),
gl_scale * visible_bounds.getWidth(), gl_scale * visible_bounds.getHeight());
}
}

void OpenGlComponent::paintBackground(Graphics& g) {
if (!isVisible())
return;

g.fillAll(findColour(Skin::kWidgetBackground, true));
}

void OpenGlComponent::repaintBackground() {
if (!isShowing())
return;

FullInterface* parent = findParentComponentOfClass<FullInterface>();
if (parent)
parent->repaintOpenGlBackground(this);
}

void OpenGlComponent::resized() {
if (corners_)
corners_->setBounds(getLocalBounds());

body_color_ = findColour(Skin::kBody, true);
}

void OpenGlComponent::parentHierarchyChanged() {
if (num_voices_readout_ == nullptr) {
SynthGuiInterface* parent = findParentComponentOfClass<SynthGuiInterface>();
if (parent)
num_voices_readout_ = parent->getSynth()->getStatusOutput("num_voices");
}

Component::parentHierarchyChanged();
}

void OpenGlComponent::addRoundedCorners() {
corners_ = std::make_unique<OpenGlCorners>();
addAndMakeVisible(corners_.get());
}

void OpenGlComponent::addBottomRoundedCorners() {
only_bottom_corners_ = true;
addRoundedCorners();
}

void OpenGlComponent::init(OpenGlWrapper& open_gl) {
if (corners_)
corners_->init(open_gl);
}

void OpenGlComponent::renderCorners(OpenGlWrapper& open_gl, bool animate, Colour color, float rounding) {
if (corners_) {
if (only_bottom_corners_)
corners_->setBottomCorners(getLocalBounds(), rounding);
else
corners_->setCorners(getLocalBounds(), rounding);
corners_->setColor(color);
corners_->render(open_gl, animate);
}
}

void OpenGlComponent::renderCorners(OpenGlWrapper& open_gl, bool animate) {
renderCorners(open_gl, animate, body_color_, findValue(Skin::kWidgetRoundedCorner));
}

void OpenGlComponent::destroy(OpenGlWrapper& open_gl) {
if (corners_)
corners_->destroy(open_gl);
}

float OpenGlComponent::findValue(Skin::ValueId value_id) {
if (parent_)
return parent_->findValue(value_id);

VITAL_ASSERT(false);
return 0.0f;
}

+ 114
- 0
ports/vitalium/source/interface/editor_components/open_gl_component.h View File

@@ -0,0 +1,114 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "common.h"
#include "shaders.h"
#include "skin.h"
#include "synth_module.h"

class SynthSection;
class OpenGlCorners;

class OpenGlComponent : public Component {
public:
static bool setViewPort(Component* component, Rectangle<int> bounds, OpenGlWrapper& open_gl);
static bool setViewPort(Component* component, OpenGlWrapper& open_gl);
static void setScissor(Component* component, OpenGlWrapper& open_gl);
static void setScissorBounds(Component* component, Rectangle<int> bounds, OpenGlWrapper& open_gl);

static std::unique_ptr<OpenGLShaderProgram::Uniform> getUniform(const OpenGlWrapper& open_gl,
const OpenGLShaderProgram& program,
const char* name) {
if (open_gl.context.extensions.glGetUniformLocation(program.getProgramID(), name) >= 0)
return std::make_unique<OpenGLShaderProgram::Uniform>(program, name);
return nullptr;
}

static std::unique_ptr<OpenGLShaderProgram::Attribute> getAttribute(const OpenGlWrapper& open_gl,
const OpenGLShaderProgram& program,
const char* name) {
if (open_gl.context.extensions.glGetAttribLocation(program.getProgramID(), name) >= 0)
return std::make_unique<OpenGLShaderProgram::Attribute>(program, name);
return nullptr;
}

OpenGlComponent(String name = "");
virtual ~OpenGlComponent();

virtual void resized() override;
virtual void parentHierarchyChanged() override;

void addRoundedCorners();
void addBottomRoundedCorners();
virtual void init(OpenGlWrapper& open_gl);
virtual void render(OpenGlWrapper& open_gl, bool animate) = 0;
void renderCorners(OpenGlWrapper& open_gl, bool animate, Colour color, float rounding);
void renderCorners(OpenGlWrapper& open_gl, bool animate);
virtual void destroy(OpenGlWrapper& open_gl);
virtual void paintBackground(Graphics& g);
void repaintBackground();

Colour getBodyColor() const { return body_color_; }

void setParent(const SynthSection* parent) { parent_ = parent; }
float findValue(Skin::ValueId value_id);
void setSkinValues(const Skin& skin) {
skin.setComponentColors(this, skin_override_, false);
}
void setSkinOverride(Skin::SectionOverride skin_override) { skin_override_ = skin_override; }

static inline String translateFragmentShader(const String& code) {
#if OPENGL_ES
return String("#version 300 es\n") + "out mediump vec4 fragColor;\n" +
code.replace("varying", "in").replace("texture2D", "texture").replace("gl_FragColor", "fragColor");
#else
return OpenGLHelpers::translateFragmentShaderToV3(code);
#endif
}

static inline String translateVertexShader(const String& code) {
#if OPENGL_ES
return String("#version 300 es\n") + code.replace("attribute", "in").replace("varying", "out");
#else
return OpenGLHelpers::translateVertexShaderToV3(code);
#endif
}

force_inline void checkGlError() {
#if DEBUG
int error = glGetError();
assert(error == GL_NO_ERROR);
#endif
}

void setBackgroundColor(const Colour& color) { background_color_ = color; }

protected:
bool setViewPort(OpenGlWrapper& open_gl);
std::unique_ptr<OpenGlCorners> corners_;
bool only_bottom_corners_;
Colour background_color_;
Colour body_color_;
const SynthSection* parent_;
Skin::SectionOverride skin_override_;
const vital::StatusOutput* num_voices_readout_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OpenGlComponent)
};


+ 143
- 0
ports/vitalium/source/interface/editor_components/open_gl_image.cpp View File

@@ -0,0 +1,143 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#include "open_gl_image.h"

#include "open_gl_component.h"
#include "shaders.h"
#include "utils.h"

namespace {
constexpr int kNumPositions = 16;
constexpr int kNumTriangleIndices = 6;
} // namespace

OpenGlImage::OpenGlImage() : dirty_(true), image_(nullptr), image_width_(0), image_height_(0),
additive_(false), use_alpha_(false), scissor_(false) {
position_vertices_ = std::make_unique<float[]>(kNumPositions);
float position_vertices[kNumPositions] = {
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, -1.0f, 0.0f, 0.0f,
0.1f, -1.0f, 1.0f, 0.0f,
0.1f, 1.0f, 1.0f, 1.0f
};
memcpy(position_vertices_.get(), position_vertices, kNumPositions * sizeof(float));

position_triangles_ = std::make_unique<int[]>(kNumTriangleIndices);
int position_triangles[kNumTriangleIndices] = {
0, 1, 2,
2, 3, 0
};
memcpy(position_triangles_.get(), position_triangles, kNumTriangleIndices * sizeof(int));
}

OpenGlImage::~OpenGlImage() { }

void OpenGlImage::init(OpenGlWrapper& open_gl) {
open_gl.context.extensions.glGenBuffers(1, &vertex_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);

GLsizeiptr vert_size = static_cast<GLsizeiptr>(static_cast<size_t>(kNumPositions * sizeof(float)));
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size,
position_vertices_.get(), GL_STATIC_DRAW);

open_gl.context.extensions.glGenBuffers(1, &triangle_buffer_);
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangle_buffer_);

GLsizeiptr tri_size = static_cast<GLsizeiptr>(static_cast<size_t>(kNumTriangleIndices * sizeof(float)));
open_gl.context.extensions.glBufferData(GL_ELEMENT_ARRAY_BUFFER, tri_size,
position_triangles_.get(), GL_STATIC_DRAW);

image_shader_ = open_gl.shaders->getShaderProgram(Shaders::kImageVertex, Shaders::kTintedImageFragment);

image_shader_->use();
image_color_ = OpenGlComponent::getUniform(open_gl, *image_shader_, "color");
image_position_ = OpenGlComponent::getAttribute(open_gl, *image_shader_, "position");
texture_coordinates_ = OpenGlComponent::getAttribute(open_gl, *image_shader_, "tex_coord_in");
}

void OpenGlImage::drawImage(OpenGlWrapper& open_gl) {
mutex_.lock();
if (image_) {
texture_.loadImage(*image_);
image_ = nullptr;
}
mutex_.unlock();

glEnable(GL_BLEND);
if (scissor_)
glEnable(GL_SCISSOR_TEST);
else
glDisable(GL_SCISSOR_TEST);

if (additive_)
glBlendFunc(GL_ONE, GL_ONE);
else if (use_alpha_)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
else
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
GLsizeiptr vert_size = static_cast<GLsizeiptr>(static_cast<size_t>(kNumPositions * sizeof(float)));

mutex_.lock();
if (dirty_)
open_gl.context.extensions.glBufferData(GL_ARRAY_BUFFER, vert_size, position_vertices_.get(), GL_STATIC_DRAW);
dirty_ = false;

open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangle_buffer_);
texture_.bind();
open_gl.context.extensions.glActiveTexture(GL_TEXTURE0);
mutex_.unlock();

image_shader_->use();

image_color_->set(color_.getFloatRed(), color_.getFloatGreen(), color_.getFloatBlue(), color_.getFloatAlpha());
open_gl.context.extensions.glVertexAttribPointer(image_position_->attributeID, 2, GL_FLOAT,
GL_FALSE, 4 * sizeof(float), nullptr);
open_gl.context.extensions.glEnableVertexAttribArray(image_position_->attributeID);
open_gl.context.extensions.glVertexAttribPointer(texture_coordinates_->attributeID, 2, GL_FLOAT,
GL_FALSE, 4 * sizeof(float),
(GLvoid*)(2 * sizeof(float)));
open_gl.context.extensions.glEnableVertexAttribArray(texture_coordinates_->attributeID);

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

open_gl.context.extensions.glDisableVertexAttribArray(image_position_->attributeID);
open_gl.context.extensions.glDisableVertexAttribArray(texture_coordinates_->attributeID);
texture_.unbind();

open_gl.context.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0);
open_gl.context.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
}

void OpenGlImage::destroy(OpenGlWrapper& open_gl) {
texture_.release();

image_shader_ = nullptr;
image_color_ = nullptr;
image_position_ = nullptr;
texture_coordinates_ = nullptr;

open_gl.context.extensions.glDeleteBuffers(1, &vertex_buffer_);
open_gl.context.extensions.glDeleteBuffers(1, &triangle_buffer_);
}

+ 95
- 0
ports/vitalium/source/interface/editor_components/open_gl_image.h View File

@@ -0,0 +1,95 @@
/* 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 "open_gl_component.h"

#include <mutex>

class OpenGlImage {
public:
OpenGlImage();
virtual ~OpenGlImage();

void init(OpenGlWrapper& open_gl);
void drawImage(OpenGlWrapper& open_gl);
void destroy(OpenGlWrapper& open_gl);

void lock() { mutex_.lock(); }
void unlock() { mutex_.unlock(); }

void setOwnImage(Image& image) {
mutex_.lock();
owned_image_ = std::make_unique<Image>(image);
setImage(owned_image_.get());
mutex_.unlock();
}

void setImage(Image* image) {
image_ = image;
image_width_ = image->getWidth();
image_height_ = image->getHeight();
}

void setColor(Colour color) { color_ = color; }

inline void setPosition(float x, float y, int index) {
position_vertices_[index] = x;
position_vertices_[index + 1] = y;
dirty_ = true;
}
inline void setTopLeft(float x, float y) { setPosition(x, y, 0); }
inline void setBottomLeft(float x, float y) { setPosition(x, y, 4); }
inline void setBottomRight(float x, float y) { setPosition(x, y, 8); }
inline void setTopRight(float x, float y) { setPosition(x, y, 12); }

int getImageWidth() { return image_width_; }
int getImageHeight() { return image_height_; }

void setAdditive(bool additive) { additive_ = additive; }
void setUseAlpha(bool use_alpha) { use_alpha_ = use_alpha; }
void setScissor(bool scissor) { scissor_ = scissor; }

private:
std::mutex mutex_;
bool dirty_;

Image* image_;
int image_width_;
int image_height_;
std::unique_ptr<Image> owned_image_;
Colour color_;
OpenGLTexture texture_;
bool additive_;
bool use_alpha_;
bool scissor_;

OpenGLShaderProgram* image_shader_;
std::unique_ptr<OpenGLShaderProgram::Uniform> image_color_;
std::unique_ptr<OpenGLShaderProgram::Attribute> image_position_;
std::unique_ptr<OpenGLShaderProgram::Attribute> texture_coordinates_;

std::unique_ptr<float[]> position_vertices_;
std::unique_ptr<int[]> position_triangles_;
GLuint vertex_buffer_;
GLuint triangle_buffer_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OpenGlImage)
};


+ 86
- 0
ports/vitalium/source/interface/editor_components/open_gl_image_component.cpp View File

@@ -0,0 +1,86 @@
/* 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 "open_gl_image_component.h"

OpenGlImageComponent::OpenGlImageComponent(String name) : OpenGlComponent(name), component_(nullptr),
active_(true), static_image_(false),
paint_entire_component_(true) {
image_.setTopLeft(-1.0f, 1.0f);
image_.setTopRight(1.0f, 1.0f);
image_.setBottomLeft(-1.0f, -1.0f);
image_.setBottomRight(1.0f, -1.0f);
image_.setColor(Colours::white);

if (name == "")
setInterceptsMouseClicks(false, false);
}

void OpenGlImageComponent::redrawImage(bool force) {
if (!active_)
return;

Component* component = component_ ? component_ : this;

int pixel_scale = Desktop::getInstance().getDisplays().findDisplayForPoint(getScreenPosition()).scale;
int width = component->getWidth() * pixel_scale;
int height = component->getHeight() * pixel_scale;
if (width <= 0 || height <= 0)
return;

bool new_image = draw_image_ == nullptr || draw_image_->getWidth() != width || draw_image_->getHeight() != height;
if (!new_image && (static_image_ || !force))
return;

image_.lock();

if (new_image)
draw_image_ = std::make_unique<Image>(Image::ARGB, width, height, false);

draw_image_->clear(Rectangle<int>(0, 0, width, height));
Graphics g(*draw_image_);
g.addTransform(AffineTransform::scale(pixel_scale));
paintToImage(g);
image_.setImage(draw_image_.get());

float gl_width = vital::utils::nextPowerOfTwo(width);
float gl_height = vital::utils::nextPowerOfTwo(height);
float width_ratio = gl_width / width;
float height_ratio = gl_height / height;

float right = -1.0f + 2.0f * width_ratio;
float bottom = 1.0f - 2.0f * height_ratio;
image_.setTopRight(right, 1.0f);
image_.setBottomLeft(-1.0f, bottom);
image_.setBottomRight(right, bottom);
image_.unlock();
}

void OpenGlImageComponent::init(OpenGlWrapper& open_gl) {
image_.init(open_gl);
}

void OpenGlImageComponent::render(OpenGlWrapper& open_gl, bool animate) {
Component* component = component_ ? component_ : this;
if (!active_ || !setViewPort(component, open_gl) || !component->isVisible())
return;

image_.drawImage(open_gl);
}

void OpenGlImageComponent::destroy(OpenGlWrapper& open_gl) {
image_.destroy(open_gl);
}

+ 286
- 0
ports/vitalium/source/interface/editor_components/open_gl_image_component.h View File

@@ -0,0 +1,286 @@
/* Copyright 2013-2019 Matt Tytel
*
* vital is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* vital is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with vital. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "open_gl_component.h"
#include "fonts.h"
#include "open_gl_image.h"

class SynthSection;

class OpenGlImageComponent : public OpenGlComponent {
public:
OpenGlImageComponent(String name = "");
virtual ~OpenGlImageComponent() = default;

virtual void paintBackground(Graphics& g) override {
redrawImage(false);
}

virtual void paintToImage(Graphics& g) {
Component* component = component_ ? component_ : this;
if (paint_entire_component_)
component->paintEntireComponent(g, false);
else
component->paint(g);
}

virtual void init(OpenGlWrapper& open_gl) override;
virtual void render(OpenGlWrapper& open_gl, bool animate) override;
virtual void destroy(OpenGlWrapper& open_gl) override;

virtual void redrawImage(bool force);
void setComponent(Component* component) { component_ = component; }
void setScissor(bool scissor) { image_.setScissor(scissor); }
void setUseAlpha(bool use_alpha) { image_.setUseAlpha(use_alpha); }
void setColor(Colour color) { image_.setColor(color); }
OpenGlImage& image() { return image_; }
void setActive(bool active) { active_ = active; }
void setStatic(bool static_image) { static_image_ = static_image; }
void paintEntireComponent(bool paint_entire_component) { paint_entire_component_ = paint_entire_component; }
bool isActive() const { return active_; }

protected:
Component* component_;
bool active_;
bool static_image_;
bool paint_entire_component_;
std::unique_ptr<Image> draw_image_;
OpenGlImage image_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OpenGlImageComponent)
};


template <class ComponentType>
class OpenGlAutoImageComponent : public ComponentType {
public:
using ComponentType::ComponentType;

virtual void mouseDown(const MouseEvent& e) override {
ComponentType::mouseDown(e);
redoImage();
}

virtual void mouseUp(const MouseEvent& e) override {
ComponentType::mouseUp(e);
redoImage();
}

virtual void mouseDoubleClick(const MouseEvent& e) override {
ComponentType::mouseDoubleClick(e);
redoImage();
}

virtual void mouseEnter(const MouseEvent& e) override {
ComponentType::mouseEnter(e);
redoImage();
}

virtual void mouseExit(const MouseEvent& e) override {
ComponentType::mouseExit(e);
redoImage();
}

virtual void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override {
ComponentType::mouseWheelMove(e, wheel);
redoImage();
}

OpenGlImageComponent* getImageComponent() { return &image_component_; }
virtual void redoImage() { image_component_.redrawImage(true); }

protected:
OpenGlImageComponent image_component_;
};

class OpenGlTextEditor : public OpenGlAutoImageComponent<TextEditor>, public TextEditor::Listener {
public:
OpenGlTextEditor(String name) : OpenGlAutoImageComponent(name) {
monospace_ = false;
image_component_.setComponent(this);
addListener(this);
}
OpenGlTextEditor(String name, wchar_t password_char) : OpenGlAutoImageComponent(name, password_char) {
monospace_ = false;
image_component_.setComponent(this);
addListener(this);
}

bool keyPressed(const KeyPress& key) override {
bool result = TextEditor::keyPressed(key);
redoImage();
return result;
}
void textEditorTextChanged(TextEditor&) override { redoImage(); }
void textEditorFocusLost(TextEditor&) override { redoImage(); }

virtual void mouseDrag(const MouseEvent& e) override {
TextEditor::mouseDrag(e);
redoImage();
}

void applyFont() {
Font font;
if (monospace_)
font = Fonts::instance()->monospace().withPointHeight(getHeight() / 2.0f);
else
font = Fonts::instance()->proportional_light().withPointHeight(getHeight() / 2.0f);

applyFontToAllText(font);
redoImage();
}

void visibilityChanged() override {
TextEditor::visibilityChanged();

if (isVisible() && !isMultiLine())
applyFont();
}

void resized() override {
TextEditor::resized();
if (isMultiLine()) {
float indent = image_component_.findValue(Skin::kLabelBackgroundRounding);
setIndents(indent, indent);
return;
}

if (monospace_)
setIndents(getHeight() * 0.2, getHeight() * 0.17);
else
setIndents(getHeight() * 0.2, getHeight() * 0.15);
if (isVisible())
applyFont();
}

void setMonospace() {
monospace_ = true;
}

private:
bool monospace_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OpenGlTextEditor)
};

class PlainTextComponent : public OpenGlImageComponent {
public:
enum FontType {
kTitle,
kLight,
kRegular,
kMono,
kNumFontTypes
};

PlainTextComponent(String name, String text) : OpenGlImageComponent(name), text_(std::move(text)),
text_size_(1.0f), font_type_(kRegular),
justification_(Justification::centred),
buffer_(0) {
setInterceptsMouseClicks(false, false);
}

void resized() override {
OpenGlImageComponent::resized();
redrawImage(true);
}

void setText(String text) {
if (text_ == text)
return;

text_ = text;
redrawImage(true);
}

String getText() const { return text_; }

void paintToImage(Graphics& g) override {
g.setColour(Colours::white);

if (font_type_ == kTitle)
g.setFont(Fonts::instance()->proportional_title().withPointHeight(text_size_));
else if (font_type_ == kLight)
g.setFont(Fonts::instance()->proportional_light().withPointHeight(text_size_));
else if (font_type_ == kRegular)
g.setFont(Fonts::instance()->proportional_regular().withPointHeight(text_size_));
else
g.setFont(Fonts::instance()->monospace().withPointHeight(text_size_));

Component* component = component_ ? component_ : this;

g.drawFittedText(text_, buffer_, 0, component->getWidth() - 2 * buffer_,
component->getHeight(), justification_, false);
}

void setTextSize(float size) {
text_size_ = size;
redrawImage(true);
}

void setFontType(FontType font_type) {
font_type_ = font_type;
}

void setJustification(Justification justification) {
justification_ = justification;
}

void setBuffer(int buffer) { buffer_ = buffer; }

private:
String text_;
float text_size_;
FontType font_type_;
Justification justification_;
int buffer_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PlainTextComponent)
};

class PlainShapeComponent : public OpenGlImageComponent {
public:
PlainShapeComponent(String name) : OpenGlImageComponent(name), justification_(Justification::centred) {
setInterceptsMouseClicks(false, false);
}

void paintToImage(Graphics& g) override {
Component* component = component_ ? component_ : this;
Rectangle<float> bounds = component->getLocalBounds().toFloat();
Path shape = shape_;
shape.applyTransform(shape.getTransformToScaleToFit(bounds, true, justification_));

g.setColour(Colours::white);
g.fillPath(shape);
}

void setShape(Path shape) {
shape_ = shape;
redrawImage(true);
}

void setJustification(Justification justification) { justification_ = justification; }

private:
Path shape_;
Justification justification_;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PlainShapeComponent)
};

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save