diff --git a/GlitchSprinkler/source/dsp/dspcore.cpp b/GlitchSprinkler/source/dsp/dspcore.cpp index e4c99ea7..44e86a91 100644 --- a/GlitchSprinkler/source/dsp/dspcore.cpp +++ b/GlitchSprinkler/source/dsp/dspcore.cpp @@ -368,14 +368,16 @@ double getPitchRatioFromArray( } inline double -getPitchRatio(int semitone, int rootShift, double octave, double cent, Tuning tuning) +getPitchRatio(int semitone, int rootShift, double octave, double cent, Tuning tuningIndex) { - switch (tuning) { + switch (tuningIndex) { default: case Tuning::et12: return getPitchRatioFromArray(semitone, rootShift, octave, cent, tuningRatioEt12); case Tuning::et5: return getPitchRatioFromArray(semitone, rootShift, octave, cent, tuningRatioEt5); + case Tuning::et10: + return getPitchRatioFromArray(semitone, rootShift, octave, cent, tuningRatioEt10); case Tuning::just5Major: return getPitchRatioFromArray( semitone, rootShift, octave, cent, tuningRatioJust5Major); @@ -384,17 +386,49 @@ getPitchRatio(int semitone, int rootShift, double octave, double cent, Tuning tu semitone, rootShift, octave, cent, tuningRatioJust5Minor); case Tuning::just7: return getPitchRatioFromArray(semitone, rootShift, octave, cent, tuningRatioJust7); + case Tuning::mtPythagorean: + return getPitchRatioFromArray( + semitone, rootShift, octave, cent, tuningRatioMtPythagorean); + case Tuning::mtThirdComma: + return getPitchRatioFromArray( + semitone, rootShift, octave, cent, tuningRatioMtThirdComma); + case Tuning::mtQuarterComma: + return getPitchRatioFromArray( + semitone, rootShift, octave, cent, tuningRatioMtQuarterComma); } // Shouldn't reach here. } template -inline double getRandomPitch(Rng &rng, const Scale &scale, const Tuning &tuning) +inline double getRandomPitchFixed(Rng &rng, const Scale &scale, const Tuning &tuningTable) { std::uniform_int_distribution indexDist{0, scale.size() - 1}; const size_t index = scale[indexDist(rng)]; - const size_t octave = size_t(1) << (index / tuning.size()); - return octave * tuning[index % tuning.size()]; + const size_t octave = size_t(1) << (index / tuningTable.size()); + return octave * tuningTable[index % tuningTable.size()]; +} + +template +inline double getRandomPitch(Rng &rng, const Scale &scale, const Tuning tuningIndex) +{ + switch (tuningIndex) { + default: + case Tuning::et12: + return getRandomPitchFixed(rng, scale, tuningRatioEt12); + case Tuning::just5Major: + return getRandomPitchFixed(rng, scale, tuningRatioJust5Major); + case Tuning::just5Minor: + return getRandomPitchFixed(rng, scale, tuningRatioJust5Minor); + case Tuning::just7: + return getRandomPitchFixed(rng, scale, tuningRatioJust7); + case Tuning::mtPythagorean: + return getRandomPitchFixed(rng, scale, tuningRatioMtPythagorean); + case Tuning::mtThirdComma: + return getRandomPitchFixed(rng, scale, tuningRatioMtThirdComma); + case Tuning::mtQuarterComma: + return getRandomPitchFixed(rng, scale, tuningRatioMtQuarterComma); + } + // Shouldn't reach here. } void Voice::updateNote() @@ -473,41 +507,41 @@ void Voice::updateNote() if (arpeggioTimer != 0 || arpeggioLoopCounter != 0) { const auto arpeggioScale = pv[ID::arpeggioScale]->getInt(); if (arpeggioScale == PitchScale::et5Chromatic) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt5, tuningRatioEt5); + arpRatio *= getRandomPitchFixed(rngArpeggio, scaleEt5, tuningRatioEt5); } else if (arpeggioScale == PitchScale::et12ChurchC) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchC, tuningRatioJust5Major); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchC, tuning); } else if (arpeggioScale == PitchScale::et12ChurchD) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchD, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchD, tuning); } else if (arpeggioScale == PitchScale::et12ChurchE) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchE, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchE, tuning); } else if (arpeggioScale == PitchScale::et12ChurchF) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchF, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchF, tuning); } else if (arpeggioScale == PitchScale::et12ChurchG) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchG, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchG, tuning); } else if (arpeggioScale == PitchScale::et12ChurchA) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchA, tuningRatioJust5Minor); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchA, tuning); } else if (arpeggioScale == PitchScale::et12ChurchB) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchB, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12ChurchB, tuning); } else if (arpeggioScale == PitchScale::et12Sus2) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Sus2, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Sus2, tuning); } else if (arpeggioScale == PitchScale::et12Sus4) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Sus4, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Sus4, tuning); } else if (arpeggioScale == PitchScale::et12Maj7) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Maj7, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Maj7, tuning); } else if (arpeggioScale == PitchScale::et12Min7) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Min7, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Min7, tuning); } else if (arpeggioScale == PitchScale::et12MajExtended) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12MajExtended, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12MajExtended, tuning); } else if (arpeggioScale == PitchScale::et12MinExtended) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12MinExtended, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12MinExtended, tuning); } else if (arpeggioScale == PitchScale::et12WholeTone2) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12WholeTone2, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12WholeTone2, tuning); } else if (arpeggioScale == PitchScale::et12WholeTone3) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12WholeTone3, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12WholeTone3, tuning); } else if (arpeggioScale == PitchScale::et12WholeTone4) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12WholeTone4, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12WholeTone4, tuning); } else if (arpeggioScale == PitchScale::et12Blues) { - arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Blues, tuningRatioEt12); + arpRatio *= getRandomPitch(rngArpeggio, scaleEt12Blues, tuning); } else if (arpeggioScale == PitchScale::overtone32) { std::uniform_int_distribution dist{1, 32}; arpRatio *= double(dist(rngArpeggio)); @@ -565,14 +599,22 @@ void Voice::updateNote() // Filter. if (pv[ID::filterSwitch]->getInt()) { const auto cutoffBaseOctave = pv[ID::filterCutoffBaseOctave]->getDouble(); - const auto cutoffModOctave = pv[ID::filterCutoffBaseOctave]->getDouble(); - cutoffBase = std::clamp( - freqHz / core.sampleRate * std::exp2(cutoffBaseOctave), double(0), double(0.4999)); + const auto cutoffKeyFollow = pv[ID::filterCutoffKeyFollow]->getDouble(); + const auto baseFreq + = std::lerp(double(20), freqHz, cutoffKeyFollow) / core.sampleRate; + cutoffBase + = std::clamp(baseFreq * std::exp2(cutoffBaseOctave), double(0), double(0.4999)); + + const auto cutoffModOctave = pv[ID::filterCutoffModOctave]->getDouble(); cutoffMod = cutoffModOctave <= Scales::filterCutoffModOctave.getMin() ? double(0) - : std::min(cutoffBase * std::exp2(cutoffModOctave), double(0.4999) - cutoffBase); + : std::clamp( + cutoffBase * std::exp2(cutoffModOctave), double(0), + double(0.4999) - cutoffBase); + resonanceBase = pv[ID::filterResonanceBase]->getDouble(); resonanceMod = pv[ID::filterResonanceMod]->getDouble() * (double(1) - resonanceBase); + notchBase = std::exp2(pv[ID::filterNotchBaseOctave]->getDouble()); const auto notchModOctave = std::exp2(pv[ID::filterNotchModOctave]->getDouble()); notchMod = notchModOctave <= Scales::filterNotchModOctave.getMin() diff --git a/GlitchSprinkler/source/dsp/polynomial.hpp b/GlitchSprinkler/source/dsp/polynomial.hpp index f228cc11..bd0c124f 100644 --- a/GlitchSprinkler/source/dsp/polynomial.hpp +++ b/GlitchSprinkler/source/dsp/polynomial.hpp @@ -251,7 +251,7 @@ template class ResonantEmaLowpass1A1 { constexpr Sample pi = std::numbers::pi_v; const auto freq = pi * std::clamp(cutValue, Sample(0), Sample(0.4999)); - const auto s = 1 - std::cos(Sample(2) * freq); + const auto s = Sample(1) - std::cos(Sample(2) * freq); const auto c1 = std::sqrt(s * s + Sample(2) * s) - s; apsValue += interpRate * (apScale - apsValue); diff --git a/GlitchSprinkler/source/dsp/tuning.hpp b/GlitchSprinkler/source/dsp/tuning.hpp index 3aadef87..aecad763 100644 --- a/GlitchSprinkler/source/dsp/tuning.hpp +++ b/GlitchSprinkler/source/dsp/tuning.hpp @@ -18,6 +18,10 @@ /* This file can be shorter if the compiler supports C++26. C++26 provides constexpr everywhere for . + +- ET or Et : Equal temperament. +- MT or Mt : Meantone temperament. +- Just : Just intonation. */ #pragma once @@ -54,7 +58,7 @@ std::array scaleEt12Blues{0, 3, 5, 6, 7, 10}; /** ```javascript -var cents = [0, 1, 240, 241, 400, 480, 481, 720, 721, 900, 960, 961]; +var cents = [0, 0, 240, 240, 400, 480, 480, 720, 720, 900, 960, 960]; console.log(cents.map(v => 2**(v / 1200))); ``` */ @@ -74,12 +78,20 @@ std::array tuningRatioEt5{ }; std::array scaleEt5{0, 2, 5, 7, 10}; -// cents = [0, 120, 240, 300, 360, 480, 600, 720, 840, 960, 1020, 1080]; -std::array tuningRatioEt10Minor{ - double(1.0000000000000), double(1.0717734625362931), double(1.148698354997035), - double(1.189207115002721), double(1.2311444133449163), double(1.3195079107728942), - double(1.4142135623730951), double(1.515716566510398), double(1.624504792712471), - double(1.7411011265922482), double(1.8025009252216604), double(1.8660659830736148), +// cents = [0, 120, 240, 300, 360, 480, 600, 720, 840, 900, 960, 1080]; +std::array tuningRatioEt10{ + double(1), // 0: 0 + double(1.0717734625362931), // 1: 120 + double(1.148698354997035), // 2: 240 + double(1.189207115002721), // 3: 300 + double(1.2311444133449163), // 4: 360 + double(1.3195079107728942), // 5: 480 + double(1.4142135623730951), // 6: 600 + double(1.515716566510398), // 7: 720 + double(1.624504792712471), // 8: 840 + double(1.681792830507429), // 9: 900 + double(1.7411011265922482), // 10: 960 + double(1.8660659830736148), // 11: 1080 }; std::array tuningRatioJust5Major{ @@ -104,4 +116,54 @@ std::array tuningRatioJust7{ double(7) / double(4), }; +std::array tuningRatioMtPythagorean{ + double(1) / double(1), // 0: + double(256) / double(243), // 1: + double(9) / double(8), // 2: + double(32) / double(27), // 3: + double(81) / double(64), // 4: + double(4) / double(3), // 5: + // double(1024) / double(729), // 6: pairs with 1. + double(729) / double(512), // 6: pairs with 11. + double(3) / double(2), // 7: + double(128) / double(81), // 8: + double(27) / double(16), // 9: + double(16) / double(9), // 10: + double(243) / double(128), // 11: +}; + +// Commented numbers indicate pitches in 12 ET semitone. +std::array tuningRatioMtThirdComma{ + double(1.00000000000000), // 0.00000000000000 + double(1.07553713917372), // 1.26068811667588 + double(1.11572158347028), // 1.89572475332965 + double(1.20000000000000), // 3.15641287000552 + double(1.24483465182143), // 3.79144950665930 + double(1.33886590016434), // 5.05213762333518 + // double(1.38888888888889), // 5.68717425998895 + double(1.44000000000000), // 6.31282574001105 + double(1.49380158218572), // 6.94786237666482 + double(1.60663908019721), // 8.20855049334070 + double(1.66666666666667), // 8.84358712999448 + double(1.79256189862287), // 10.10427524667035 + double(1.85953597245047), // 10.73931188332413 +}; + +// Commented numbers indicate pitches in 12 ET semitone. +std::array tuningRatioMtQuarterComma{ + double(1.00000000000000), // 0.00000000000000 + double(1.06998448796228), // 1.17107857668956 + double(1.11803398874989), // 1.93156856932417 + double(1.19627902497698), // 3.10264714601374 + double(1.25000000000000), // 3.86313713864835 + double(1.33748060995284), // 5.03421571533791 + // double(1.39754248593737), // 5.79470570797252 + double(1.43108350559987), // 6.20529429202748 + double(1.49534878122122), // 6.96578428466209 + double(1.60000000000000), // 8.13686286135165 + double(1.67185076244106), // 8.89735285398626 + double(1.78885438199983), // 10.06843143067583 + double(1.86918597652653), // 10.82892142331043 +}; + } // namespace SomeDSP diff --git a/GlitchSprinkler/source/editor.cpp b/GlitchSprinkler/source/editor.cpp index bd28a536..14f9a71c 100644 --- a/GlitchSprinkler/source/editor.cpp +++ b/GlitchSprinkler/source/editor.cpp @@ -129,8 +129,9 @@ bool Editor::prepareUI() addLabel(mixLeft0, mixTop8, labelWidth, labelHeight, uiTextSize, "Tuning"); std::vector tuningItems{ - "ET 12", "ET 5", "Just 5 Major", "Just 5 Minor", "Just 7", - "Discrete 2", "Discrete 3", "Discrete 5", "Discrete 7", + "ET 12", "ET 5", "ET 10", "Just 5 Major", "Just 5 Minor", + "Just 7", "MT Pythagorean", "MT 1/3 Comma", "MT 1/4 Comma", "Discrete 2", + "Discrete 3", "Discrete 5", "Discrete 7", }; for (size_t idx = tuningItems.size(); idx <= Scales::tuningType.getMax(); ++idx) { tuningItems.push_back("- Reserved " + std::to_string(idx) + " -"); @@ -151,12 +152,13 @@ bool Editor::prepareUI() // Filter. constexpr auto filterLabelWidth = 100.0f; - constexpr auto filterTop0 = mixTop12 + labelY; + constexpr auto filterTop0 = mixTop11 + labelY; constexpr auto filterTop1 = filterTop0 + 1 * labelY; constexpr auto filterTop2 = filterTop0 + 2 * labelY; constexpr auto filterTop3 = filterTop0 + 3 * labelY; constexpr auto filterTop4 = filterTop0 + 4 * labelY; constexpr auto filterTop5 = filterTop0 + 5 * labelY; + constexpr auto filterTop6 = filterTop0 + 6 * labelY; constexpr auto filterLeft0 = left0; constexpr auto filterLeft1 = filterLeft0 + 1 * filterLabelWidth + 3 * margin; constexpr auto filterLeft2 = filterLeft0 + 2 * filterLabelWidth + 6 * margin; @@ -169,36 +171,40 @@ bool Editor::prepareUI() addTextKnob( filterLeft0 + labelWidth + 2 * margin, filterTop1, labelWidth, labelHeight, uiTextSize, ID::filterDecayRatio, Scales::filterDecayRatio, false, 5); + addLabel(filterLeft0, filterTop2, labelWidth, labelHeight, uiTextSize, "Key Follow"); + addTextKnob( + filterLeft0 + labelWidth + 2 * margin, filterTop2, labelWidth, labelHeight, + uiTextSize, ID::filterCutoffKeyFollow, Scales::defaultScale, false, 5); - addLabel(filterLeft1, filterTop2, filterLabelWidth, labelHeight, uiTextSize, "Base"); + addLabel(filterLeft1, filterTop3, filterLabelWidth, labelHeight, uiTextSize, "Base"); addLabel( - filterLeft2, filterTop2, filterLabelWidth, labelHeight, uiTextSize, "Env. Mod"); + filterLeft2, filterTop3, filterLabelWidth, labelHeight, uiTextSize, "Env. Mod"); addLabel( - filterLeft0, filterTop3, filterLabelWidth, labelHeight, uiTextSize, "Cutoff [oct.]"); + filterLeft0, filterTop4, filterLabelWidth, labelHeight, uiTextSize, "Cutoff [oct.]"); addTextKnob( - filterLeft1, filterTop3, filterLabelWidth, labelHeight, uiTextSize, + filterLeft1, filterTop4, filterLabelWidth, labelHeight, uiTextSize, ID::filterCutoffBaseOctave, Scales::filterCutoffBaseOctave, false, 5); addTextKnob( - filterLeft2, filterTop3, filterLabelWidth, labelHeight, uiTextSize, + filterLeft2, filterTop4, filterLabelWidth, labelHeight, uiTextSize, ID::filterCutoffModOctave, Scales::filterCutoffModOctave, false, 5); addLabel( - filterLeft0, filterTop4, filterLabelWidth, labelHeight, uiTextSize, "Resonance"); + filterLeft0, filterTop5, filterLabelWidth, labelHeight, uiTextSize, "Resonance"); addTextKnob( - filterLeft1, filterTop4, filterLabelWidth, labelHeight, uiTextSize, + filterLeft1, filterTop5, filterLabelWidth, labelHeight, uiTextSize, ID::filterResonanceBase, Scales::defaultScale, false, 5); addTextKnob( - filterLeft2, filterTop4, filterLabelWidth, labelHeight, uiTextSize, + filterLeft2, filterTop5, filterLabelWidth, labelHeight, uiTextSize, ID::filterResonanceMod, Scales::defaultScale, false, 5); addLabel( - filterLeft0, filterTop5, filterLabelWidth, labelHeight, uiTextSize, "Notch [oct.]"); + filterLeft0, filterTop6, filterLabelWidth, labelHeight, uiTextSize, "Notch [oct.]"); addTextKnob( - filterLeft1, filterTop5, filterLabelWidth, labelHeight, uiTextSize, + filterLeft1, filterTop6, filterLabelWidth, labelHeight, uiTextSize, ID::filterNotchBaseOctave, Scales::filterNotchBaseOctave, false, 5); addTextKnob( - filterLeft2, filterTop5, filterLabelWidth, labelHeight, uiTextSize, + filterLeft2, filterTop6, filterLabelWidth, labelHeight, uiTextSize, ID::filterNotchModOctave, Scales::filterNotchModOctave, false, 5); // Waveform. diff --git a/GlitchSprinkler/source/parameter.cpp b/GlitchSprinkler/source/parameter.cpp index c8705a89..45027f27 100644 --- a/GlitchSprinkler/source/parameter.cpp +++ b/GlitchSprinkler/source/parameter.cpp @@ -41,8 +41,8 @@ DecibelScale Scales::fmIndex(-60.0, 40.0, true); LinearScale Scales::randomizeFmIndex(0.0, 4.0); LinearScale Scales::filterDecayRatio(-10.0, 10.0); -LinearScale Scales::filterCutoffBaseOctave(0.0, 10.0); -LinearScale Scales::filterCutoffModOctave(-10, 10); +LinearScale Scales::filterCutoffBaseOctave(-8.0, 16.0); +LinearScale Scales::filterCutoffModOctave(-16, 16); LinearScale Scales::filterNotchBaseOctave(-4, 4); LinearScale Scales::filterNotchModOctave(-4, 4); diff --git a/GlitchSprinkler/source/parameter.hpp b/GlitchSprinkler/source/parameter.hpp index 71dd5abc..e79f4b69 100644 --- a/GlitchSprinkler/source/parameter.hpp +++ b/GlitchSprinkler/source/parameter.hpp @@ -45,11 +45,13 @@ namespace Synth { enum Tuning : uint32_t { et12, et5, - // et10Major, // TODO - // et10Minor, // TODO + et10, just5Major, just5Minor, just7, + mtPythagorean, + mtThirdComma, + mtQuarterComma, discrete2, discrete3, discrete5, @@ -110,6 +112,7 @@ enum ID { filterSwitch, filterDecayRatio, + filterCutoffKeyFollow, filterCutoffBaseOctave, filterCutoffModOctave, filterResonanceBase, @@ -239,6 +242,9 @@ struct GlobalParameter : public ParameterInterface { value[ID::filterDecayRatio] = std::make_unique( Scales::filterDecayRatio.invmap(0.0), Scales::filterDecayRatio, "filterDecayRatio", Info::kCanAutomate); + value[ID::filterCutoffKeyFollow] = std::make_unique( + Scales::defaultScale.invmap(1.0), Scales::defaultScale, "filterCutoffKeyFollow", + Info::kCanAutomate); value[ID::filterCutoffBaseOctave] = std::make_unique( Scales::filterCutoffBaseOctave.invmap(0.0), Scales::filterCutoffBaseOctave, "filterCutoffBaseOctave", Info::kCanAutomate);