From 1223c99e0f244f1a60a5a20669c1d4bd78b37786 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 3 May 2020 21:38:20 -0400 Subject: [PATCH] Adds waveform generation logic to the new factoring. --- .../OPL2/Implementation/KeyLevelScaler.hpp | 2 +- .../OPL2/Implementation/PhaseGenerator.hpp | 7 ++ .../OPL2/Implementation/WaveformGenerator.hpp | 92 +++++++++++++++++++ Components/OPL2/OPL2.cpp | 2 + .../Clock Signal.xcodeproj/project.pbxproj | 2 + 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 Components/OPL2/Implementation/WaveformGenerator.hpp diff --git a/Components/OPL2/Implementation/KeyLevelScaler.hpp b/Components/OPL2/Implementation/KeyLevelScaler.hpp index bbd5b7531..a86995bdf 100644 --- a/Components/OPL2/Implementation/KeyLevelScaler.hpp +++ b/Components/OPL2/Implementation/KeyLevelScaler.hpp @@ -27,7 +27,7 @@ template class KeyLevelScaler { // on the sign bit to clamp to zero. level_ = key_level_scales[period >> (frequency_precision - 4)]; level_ -= 16 * (octave ^ 7); - level_ &= masks[(key_scale_rate_ >> ((sizeof(int) * 8) - 1)) & 1]; + level_ &= masks[(level_ >> ((sizeof(int) * 8) - 1)) & 1]; } /*! diff --git a/Components/OPL2/Implementation/PhaseGenerator.hpp b/Components/OPL2/Implementation/PhaseGenerator.hpp index 35ba2108e..aaef60683 100644 --- a/Components/OPL2/Implementation/PhaseGenerator.hpp +++ b/Components/OPL2/Implementation/PhaseGenerator.hpp @@ -51,6 +51,13 @@ template class PhaseGenerator { return phase_ >> precision_shift; } + /*! + @returns Current phase, scaled up by (1 << precision). + */ + int scaled_phase() const { + return phase_ >> 1; + } + /*! Sets the multiple for this phase generator, in the same terms as an OPL programmer, i.e. a 4-bit number that is used as a lookup into the internal multiples table. diff --git a/Components/OPL2/Implementation/WaveformGenerator.hpp b/Components/OPL2/Implementation/WaveformGenerator.hpp new file mode 100644 index 000000000..0008dd3cf --- /dev/null +++ b/Components/OPL2/Implementation/WaveformGenerator.hpp @@ -0,0 +1,92 @@ +// +// WaveformGenerator.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/05/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef WaveformGenerator_h +#define WaveformGenerator_h + +#include "Tables.hpp" +#include "LowFrequencyOscillator.hpp" + +namespace Yamaha { +namespace OPL { + +template class WaveformGenerator { + public: + enum class Waveform { + Sine, HalfSine, AbsSine, PulseSine + }; + + /*! + @returns The output of waveform @c form at [integral] phase @c phase. + */ + static constexpr LogSign wave(Waveform form, int phase) { + constexpr int waveforms[4][4] = { + {1023, 1023, 1023, 1023}, // Sine: don't mask in any quadrant. + {511, 511, 0, 0}, // Half sine: keep the first half intact, lock to 0 in the second half. + {511, 511, 511, 511}, // AbsSine: endlessly repeat the first half of the sine wave. + {255, 0, 255, 0}, // PulseSine: act as if the first quadrant is in the first and third; lock the other two to 0. + }; + return negative_log_sin(phase & waveforms[int(form)][(phase >> 8) & 3]); + } + + /*! + @returns The output of waveform @c form at [scaled] phase @c scaled_phase given the modulation input @c modulation. + */ + static constexpr LogSign wave(Waveform form, int scaled_phase, LogSign modulation) { + const int scaled_phase_offset = modulation.level(phase_precision); + const int phase = (scaled_phase + scaled_phase_offset) >> phase_precision; + return wave(form, phase); + } + + /*! + @returns Snare output, calculated from the current LFSR state as captured in @c oscillator and an operator's phase. + */ + static constexpr LogSign snare(const LowFrequencyOscillator &oscillator, int phase) { + // If noise is 0, output is positive. + // If noise is 1, output is negative. + // If (noise ^ sign) is 0, output is 0. Otherwise it is max. + const int sign = phase & 0x200; + const int level = ((phase >> 9) & 1) ^ oscillator.lfsr; + return negative_log_sin(sign + (level << 8)); + } + + /*! + @returns Cymbal output, calculated from an operator's phase and a modulator's phase. + */ + static constexpr LogSign cymbal(int carrier_phase, int modulator_phase) { + return negative_log_sin(256 + (phase_combination(carrier_phase, modulator_phase) << 9)); + } + + /*! + @returns High-hat output, calculated from the current LFSR state as captured in @c oscillator, an operator's phase and a modulator's phase. + */ + static constexpr LogSign high_hat(const LowFrequencyOscillator &oscillator, int carrier_phase, int modulator_phase) { + constexpr int angles[] = {0x234, 0xd0, 0x2d0, 0x34}; + return negative_log_sin(angles[ + phase_combination(carrier_phase, modulator_phase) | + (oscillator.lfsr << 1) + ]); + } + + private: + /*! + @returns The phase bit used for cymbal and high-hat generation, which is a function of two operators' phases. + */ + static constexpr int phase_combination(int carrier_phase, int modulator_phase) { + return ( + ((carrier_phase >> 5) ^ (carrier_phase >> 3)) & + ((modulator_phase >> 7) ^ (modulator_phase >> 2)) & + ((carrier_phase >> 5) ^ (modulator_phase >> 3)) + ) & 1; + } +}; + +} +} + +#endif /* WaveformGenerator_h */ diff --git a/Components/OPL2/OPL2.cpp b/Components/OPL2/OPL2.cpp index da480c8c6..b12af5b3b 100644 --- a/Components/OPL2/OPL2.cpp +++ b/Components/OPL2/OPL2.cpp @@ -13,6 +13,8 @@ #include "Implementation/PhaseGenerator.hpp" #include "Implementation/EnvelopeGenerator.hpp" +#include "Implementation/KeyLevelScaler.hpp" +#include "Implementation/WaveformGenerator.hpp" using namespace Yamaha::OPL; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 3a3c52701..f3a4930c6 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1154,6 +1154,7 @@ 4B619092245BC1000013F202 /* PhaseGenerator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PhaseGenerator.hpp; sourceTree = ""; }; 4B619093245CD63E0013F202 /* EnvelopeGenerator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = EnvelopeGenerator.hpp; sourceTree = ""; }; 4B619094245E73B90013F202 /* KeyLevelScaler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyLevelScaler.hpp; sourceTree = ""; }; + 4B619095245FA04B0013F202 /* WaveformGenerator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = WaveformGenerator.hpp; sourceTree = ""; }; 4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DisplayMetrics.cpp; path = ../../Outputs/DisplayMetrics.cpp; sourceTree = ""; }; 4B622AE4222E0AD5008B59F2 /* DisplayMetrics.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DisplayMetrics.hpp; path = ../../Outputs/DisplayMetrics.hpp; sourceTree = ""; }; 4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = ""; }; @@ -3538,6 +3539,7 @@ 4B619092245BC1000013F202 /* PhaseGenerator.hpp */, 4B619093245CD63E0013F202 /* EnvelopeGenerator.hpp */, 4B619094245E73B90013F202 /* KeyLevelScaler.hpp */, + 4B619095245FA04B0013F202 /* WaveformGenerator.hpp */, ); path = Implementation; sourceTree = "";