Skip to content

Commit

Permalink
Add SpectralPhaser
Browse files Browse the repository at this point in the history
  • Loading branch information
ryukau committed Oct 22, 2024
1 parent 3d68e76 commit 4bbfa25
Show file tree
Hide file tree
Showing 30 changed files with 2,086 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ jobs:
- name: Get Release Notes
run: 'echo "$(git tag -l --format="%(contents:body)" $GITHUB_REF_NAME)" > RELEASE_NOTES'
- name: Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
body_path: RELEASE_NOTES
files: |
Expand Down
2 changes: 1 addition & 1 deletion DoubleLoopCymbal/source/editor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class Editor : public PlugEditor {
{
auto knob = new Knob<style>(
CRect(left, top, left + height, top + height), this, tag, palette);
knob->setSlitWidth(2.0);
knob->setArcWidth(2.0);
knob->setValueNormalized(controller->getParamNormalized(tag));
knob->setDefaultValue(param->getDefaultNormalized(tag));
frame->addView(knob);
Expand Down
2 changes: 1 addition & 1 deletion NarrowingDelay/source/editor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Editor : public PlugEditor {
{
auto knob = new Knob<style>(
CRect(left, top, left + height, top + height), this, tag, palette);
knob->setSlitWidth(2.0);
knob->setArcWidth(2.0);
knob->setValueNormalized(controller->getParamNormalized(tag));
knob->setDefaultValue(param->getDefaultNormalized(tag));
frame->addView(knob);
Expand Down
2 changes: 1 addition & 1 deletion OrdinaryPhaser/source/editor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Editor : public PlugEditor {
{
auto knob = new Knob<style>(
CRect(left, top, left + height, top + height), this, tag, palette);
knob->setSlitWidth(2.0);
knob->setArcWidth(2.0);
knob->setValueNormalized(controller->getParamNormalized(tag));
knob->setDefaultValue(param->getDefaultNormalized(tag));
frame->addView(knob);
Expand Down
2 changes: 1 addition & 1 deletion ParallelDetune/source/editor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Editor : public PlugEditor {
{
auto knob = new Knob<style>(
CRect(left, top, left + height, top + height), this, tag, palette);
knob->setSlitWidth(2.0);
knob->setArcWidth(2.0);
knob->setValueNormalized(controller->getParamNormalized(tag));
knob->setDefaultValue(param->getDefaultNormalized(tag));
frame->addView(knob);
Expand Down
19 changes: 19 additions & 0 deletions SpectralPhaser/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.20)

set(UHHYOU_USE_FFTW True)
include(../common/cmake/non_simd.cmake)

if(TEST_PLUGIN)
build_test("")
else()
# VST 3 source files.
set(plug_sources
source/dsp/spectralfilter.cpp
source/parameter.cpp
source/gui/splashdraw.cpp
source/plugprocessor.cpp
source/editor.cpp
source/plugfactory.cpp)

build_vst3("${plug_sources}")
endif()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions SpectralPhaser/resource/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSHumanReadableCopyright</key>
<string>2018 Steinberg Media Technologies</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>SpectralPhaser</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.steinberg.vst3.SpectralPhaser</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CSResourcesFileMapped</key>
<true />
</dict>
</plist>
44 changes: 44 additions & 0 deletions SpectralPhaser/resource/plug.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <windows.h>
#include "../source/version.hpp"

#define APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// Version
/////////////////////////////////////////////////////////////////////////////
VS_VERSION_INFO VERSIONINFO
FILEVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
PRODUCTVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040004e4"
BEGIN
VALUE "FileVersion", FULL_VERSION_STR"\0"
VALUE "ProductVersion", FULL_VERSION_STR"\0"
VALUE "OriginalFilename", stringOriginalFilename"\0"
VALUE "FileDescription", stringFileDescription"\0"
VALUE "InternalName", stringFileDescription"\0"
VALUE "ProductName", stringFileDescription"\0"
VALUE "CompanyName", stringCompanyName"\0"
VALUE "LegalCopyright", stringLegalCopyright"\0"
VALUE "LegalTrademarks", stringLegalTrademarks"\0"
//VALUE "PrivateBuild", " \0"
//VALUE "SpecialBuild", " \0"
//VALUE "Comments", " \0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x400, 1252
END
END
217 changes: 217 additions & 0 deletions SpectralPhaser/source/dsp/dspcore.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright Takamitsu Endo ([email protected]).
// SPDX-License-Identifier: GPL-3.0-only

#include "../../../lib/juce_ScopedNoDenormal.hpp"

#include "dspcore.hpp"

#include <algorithm>
#include <limits>
#include <numeric>

using Sample = DSPCore::Sample;

void DSPCore::setup(double sampleRate)
{
this->sampleRate = Sample(sampleRate);

SmootherCommon<Sample>::setSampleRate(sampleRate);

lfoSyncRate = EMAFilter<double>::secondToP(sampleRate, Sample(0.013));

reset();
startup();
}

size_t DSPCore::getLatency() { return spcParam.reportLatency ? spcParam.frmSize : 0; }

#define ASSIGN_PARAMETER(METHOD) \
using ID = ParameterID::ID; \
const auto &pv = param.value; \
\
SmootherCommon<Sample>::setTime(pv[ID::parameterSmoothingSecond]->getFloat()); \
\
spcParam.sideChain = pv[ID::sideChainSwitch]->getInt(); \
spcParam.reportLatency = pv[ID::reportLatency]->getInt(); \
spcParam.frameSizeLog2 = pv[ID::frameSize]->getInt() + maxFrameSizeStart; \
spcParam.frmSize = int(1) << spcParam.frameSizeLog2; \
spcParam.transform = static_cast<TransformType>(pv[ID::transform]->getInt()); \
spcParam.maskWaveform = static_cast<MaskWaveform>(pv[ID::maskWaveform]->getInt()); \
\
lfoWaveform = static_cast<LfoWaveform>(pv[ID::lfoWaveform]->getInt()); \
lfoWaveMod.METHOD(pv[ID::lfoWaveMod]->getFloat()); \
lfoRate.METHOD(pv[ID::lfoRate]->getFloat() / sampleRate); \
lfoStereoPhaseOffset.METHOD(pv[ID::lfoStereoPhaseOffset]->getFloat()); \
lfoInitialPhase.METHOD(pv[ID::lfoInitialPhase]->getFloat()); \
\
feedback.METHOD(pv[ID::feedback]->getFloat()); \
const auto spcShift = Sample(0.5) * pv[ID::spectralShift]->getFloat(); \
spectralShift.METHOD(spcShift - std::floor(spcShift)); \
octaveDown.METHOD(pv[ID::octaveDown]->getFloat()); \
\
constexpr Sample twopi = Sample(2) * std::numbers::pi_v<Sample>; \
maskMix.METHOD(pv[ID::maskMix]->getFloat()); \
maskPhase.METHOD(pv[ID::maskPhase]->getFloat()); \
maskFreq.METHOD(pv[ID::maskFreq]->getFloat() / spcParam.frmSize); \
maskThreshold.METHOD(pv[ID::maskThreshold]->getFloat()); \
maskRotation.METHOD(pv[ID::maskRotation]->getFloat() * twopi); \
lfoToMaskMix.METHOD(pv[ID::lfoToMaskMix]->getFloat()); \
lfoToMaskPhase.METHOD(pv[ID::lfoToMaskPhase]->getFloat()); \
lfoToMaskFreq.METHOD(pv[ID::lfoToMaskFreq]->getFloat()); \
lfoToMaskThreshold.METHOD(pv[ID::lfoToMaskThreshold]->getFloat()); \
lfoToMaskRotation.METHOD(pv[ID::lfoToMaskRotation]->getFloat() * twopi); \
lfoToSpectralShift.METHOD(pv[ID::lfoToSpectralShift]->getFloat() * Sample(2)); \
\
outputGain.METHOD(pv[ID::outputGain]->getFloat()); \
dryWetMix.METHOD(pv[ID::dryWetMix]->getFloat());

void DSPCore::reset()
{
ASSIGN_PARAMETER(reset);

lfoTargetFreq = getTempoSyncFrequency();
lfo.reset(lfoInitialPhase.getValue(), lfoTargetFreq);
for (auto &x : spc) x.reset(spcParam.frameSizeLog2);

startup();
}

void DSPCore::startup() {}

void DSPCore::setParameters() { ASSIGN_PARAMETER(push); }

// Output range is in [0, 1].
inline Sample phaseToWave(Sample phase, Sample mod, LfoWaveform waveform)
{
switch (waveform) {
case LfoWaveform::triSaw: {
constexpr Sample eps = std::numeric_limits<Sample>::epsilon();
const auto pw = mod * Sample(0.5) + Sample(0.5);
if (pw <= eps) return Sample(1) - phase;
if (pw >= Sample(1) - eps) return phase;
return phase < pw ? phase / pw : (Sample(1) - phase) / (Sample(1) - pw);
} break;
}
// LfoWaveform::sine
constexpr Sample twopi = Sample(2) * std::numbers::pi_v<Sample>;
return std::sin(twopi * (phase + mod * std::sin(twopi * phase)));
}

std::array<Sample, 2>
DSPCore::processFrame(int frameIndex, Sample in0, Sample in1, Sample side0, Sample side1)
{
lfoInitialPhase.process();

const auto processLfoSync = [&]() {
auto samplesElapsed = sampleRate * beatsElapsed * 60 / tempo;
auto targetPhase = Sample((samplesElapsed + frameIndex) * lfoTargetFreq);
targetPhase -= std::floor(targetPhase);
return lfo.processSync(
targetPhase + lfoInitialPhase.getValue(), lfoTargetFreq, lfoSyncRate);
};

auto modPhase0 = isPlaying ? processLfoSync() : lfo.process(lfoTargetFreq);
auto modPhase1 = modPhase0 + lfoStereoPhaseOffset.process();
modPhase1 -= std::floor(modPhase1);

lfoWaveMod.process();
auto mod0 = phaseToWave(modPhase0, lfoWaveMod.getValue(), lfoWaveform);
auto mod1 = phaseToWave(modPhase1, lfoWaveMod.getValue(), lfoWaveform);

spcParam.dryWetMix = dryWetMix.process();
spcParam.feedback = feedback.process();
spectralShift.process();
octaveDown.process();
maskMix.process();
maskPhase.process();
maskFreq.process();
maskThreshold.process();
maskRotation.process();
lfoToMaskMix.process();
lfoToMaskPhase.process();
lfoToMaskFreq.process();
lfoToMaskThreshold.process();
lfoToMaskRotation.process();
lfoToSpectralShift.process();
lfoToOctaveDown.process();

const auto modulateParam = [&](Sample unipoler) {
const auto bipoler = unipoler - Sample(0.5);

spcParam.spectralShift
= spectralShift.getValue() + lfoToSpectralShift.getValue() * bipoler;
spcParam.spectralShift = spcParam.spectralShift - std::floor(spcParam.spectralShift);
spcParam.octaveDown = octaveDown.getValue() + lfoToOctaveDown.getValue() * unipoler;
spcParam.octaveDown -= std::floor(spcParam.octaveDown);

spcParam.maskMix = std::clamp(
maskMix.getValue() + lfoToMaskMix.getValue() * bipoler, Sample(0), Sample(1));
spcParam.maskPhase = maskPhase.getValue() + lfoToMaskPhase.getValue() * unipoler;
spcParam.maskFreq
= maskFreq.getValue() * std::exp2(Sample(6) * lfoToMaskFreq.getValue() * unipoler);
spcParam.maskThreshold = std::clamp(
maskThreshold.getValue() + lfoToMaskThreshold.getValue() * unipoler, //
Sample(0), Sample(1));
spcParam.maskRotation
= maskRotation.getValue() + lfoToMaskRotation.getValue() * unipoler;
};

modulateParam(mod0);
auto sig0 = spc[0].process(in0, side0, spcParam);

modulateParam(mod1);
auto sig1 = spc[1].process(in1, side1, spcParam);

outputGain.process();
return {
outputGain.getValue() * sig0,
outputGain.getValue() * sig1,
};
}

void DSPCore::process(
const size_t length,
const float *in0,
const float *in1,
const float *side0,
const float *side1,
float *out0,
float *out1)
{
ScopedNoDenormals scopedDenormals;

using ID = ParameterID::ID;
const auto &pv = param.value;

SmootherCommon<Sample>::setBufferSize(Sample(length));
lfoTargetFreq = getTempoSyncFrequency();

for (int i = 0; i < length; ++i) {
auto frame = processFrame(
i, Sample(in0[i]), Sample(in1[i]), Sample(side0[i]), Sample(side1[i]));
out0[i] = float(frame[0]);
out1[i] = float(frame[1]);
}
}

Sample DSPCore::getTempoSyncFrequency()
{
using ID = ParameterID::ID;
const auto &pv = param.value;

auto lfoRate = pv[ID::lfoRate]->getDouble();
if (lfoRate > Scales::lfoRate.getMax()) return 0;

auto upper = (pv[ID::lfoTempoUpper]->getDouble() + double(1)) * timeSigLower;
auto lower = pv[ID::lfoTempoLower]->getDouble() + double(1);

//
// Simplified version of following expressions.
//
// ```
// syncBeat = upper / (lower * lfoRate);
// secondsPerBeat = 60 / tempoInBpm;
// lfoFreq = 1 / (syncBeat * secondPerBeat * sampleRate);
// ```
return Sample((tempo * lower * lfoRate) / (60 * upper * sampleRate));
}
Loading

0 comments on commit 4bbfa25

Please sign in to comment.