From 9d2691d1d28b128e89a94a9878ee0b1a133b0d53 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 1 May 2020 23:46:42 -0400 Subject: [PATCH] Taking it as given that outstanding deficiencies are mostly due to poor design, starts breaking out the envelope and phase generators. --- .../OPL2/Implementation/EnvelopeGenerator.hpp | 113 ++++++++++++++++++ .../OPL2/Implementation/PhaseGenerator.hpp | 108 +++++++++++++++++ Components/OPL2/OPL2.cpp | 3 + .../Clock Signal.xcodeproj/project.pbxproj | 4 + 4 files changed, 228 insertions(+) create mode 100644 Components/OPL2/Implementation/EnvelopeGenerator.hpp create mode 100644 Components/OPL2/Implementation/PhaseGenerator.hpp diff --git a/Components/OPL2/Implementation/EnvelopeGenerator.hpp b/Components/OPL2/Implementation/EnvelopeGenerator.hpp new file mode 100644 index 000000000..42f54dfe4 --- /dev/null +++ b/Components/OPL2/Implementation/EnvelopeGenerator.hpp @@ -0,0 +1,113 @@ +// +// EnvelopeGenerator.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/05/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef EnvelopeGenerator_h +#define EnvelopeGenerator_h + +#include +#include + +namespace Yamaha { +namespace OPL { + +/*! + Models an OPL-style envelope generator. + + Damping is optional; if damping is enabled then if there is a transition to key-on while + attenuation is less than maximum then attenuation will be quickly transitioned to maximum + before the attack phase can begin. + + in real hardware damping is used by the envelope generators associated with + carriers, with phases being reset upon the transition from damping to attack. + + This code considers application of tremolo to be a function of the envelope generator; + this is largely for logical conformity with the phase generator that necessarily has to + apply vibrato. +*/ +template class EnvelopeGenerator { + public: + /*! + Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator. + */ + void update(const LowFrequencyOscillator &oscillator); + + /*! + @returns The current attenuation from this envelope generator. + */ + int attenuation() const; + + /*! + Enables or disables damping on this envelope generator. If damping is enabled then this envelope generator will + use the damping phase when necessary (i.e. when transitioning to key on if attenuation is not already at maximum) + and in any case will call @c will_attack before transitioning from any other state to attack. + + @param will_attack Supply a will_attack callback to enable damping mode; supply nullopt to disable damping mode. + */ + void set_should_damp(const std::optional> &will_attack); + + /*! + Sets the current state of the key-on input. + */ + void set_key_on(bool); + + /*! + Sets the attack rate, which should be in the range 0–15. + */ + void set_attack_rate(int); + + /*! + Sets the decay rate, which should be in the range 0–15. + */ + void set_decay_rate(int); + + /*! + Sets the release rate, which should be in the range 0–15. + */ + void set_release_rate(int); + + /*! + Sets the sustain level, which should be in the range 0–15. + */ + void set_sustain_level(int); + + /*! + Enables or disables use of the sustain level. If this is disabled, the envelope proceeds + directly from decay to release. + */ + void set_use_sustain_level(bool); + + /*! + Enables or disables key-rate scaling. + */ + void set_key_rate_scaling_enabled(bool enabled); + + /*! + Enables or disables application of the low-frequency oscillator's tremolo. + */ + void set_tremolo_enabled(bool enabled); + + /*! + Sets the current period associated with the channel that owns this envelope generator; + this is used to select a key scaling rate if key-rate scaling is enabled. + */ + void set_period(int period, int octave); + + private: + enum class ADSRPhase { + Attack, Decay, Sustain, Release, Damp + } adsr_phase_ = ADSRPhase::Attack; + int adsr_attenuation_ = 511; + + bool key_on_ = false; + std::optional> will_attack_; +}; + +} +} + +#endif /* EnvelopeGenerator_h */ diff --git a/Components/OPL2/Implementation/PhaseGenerator.hpp b/Components/OPL2/Implementation/PhaseGenerator.hpp new file mode 100644 index 000000000..9f85689a2 --- /dev/null +++ b/Components/OPL2/Implementation/PhaseGenerator.hpp @@ -0,0 +1,108 @@ +// +// PhaseGenerator.h +// Clock Signal +// +// Created by Thomas Harte on 30/04/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef PhaseGenerator_h +#define PhaseGenerator_h + +#include +#include "LowFrequencyOscillator.hpp" +#include "Tables.hpp" + +namespace Yamaha { +namespace OPL { + +/*! + Models an OPL-style phase generator of templated precision; having been told its period ('f-num'), octave ('block') and + multiple, and whether to apply vibrato, this will then appropriately update and return phase. +*/ +template class PhaseGenerator { + public: + /*! + Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator. + */ + void update(const LowFrequencyOscillator &oscillator) { + constexpr int vibrato_shifts[8] = {3, 1, 0, 1, 3, 1, 0, 1}; + constexpr int vibrato_signs[2] = {1, -1}; + + // Get just the top three bits of the period_. + const int top_freq = period_ >> (precision - 3); + + // Cacluaute applicable vibrato as a function of (i) the top three bits of the + // oscillator period; (ii) the current low-frequency oscillator vibrato output; and + // (iii) whether vibrato is enabled. + const int vibrato = (top_freq >> vibrato_shifts[oscillator.vibrato]) * vibrato_signs[oscillator.vibrato >> 2] * enable_vibrato_; + + // Apply phase update with vibrato from the low-frequency oscillator. + raw_phase_ += multiple_ * (period_ + vibrato) << octave_; + } + + + /*! + @returns Current phase; real hardware provides only the low ten bits of this result. + */ + int phase() const { + // My table if multipliers is multiplied by two, so shift by one more + // than the stated precision. + return raw_phase_ >> precision_shift; + } + + /*! + 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. + */ + void set_multiple(int multiple) { + // This encodes the MUL -> multiple table given on page 12, + // multiplied by two. + constexpr int multipliers[] = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 + }; + assert(multiple < 16); + multiple_ = multipliers[multiple]; + } + + /*! + Sets the period of this generator, along with its current octave. + + Yamaha tends to refer to the period as the 'f-number', and used both 'octave' and 'block' for octave. + */ + void set_period(int period, int octave) { + period_ = period; + octave_ = octave; + + assert(octave_ < 8); + assert(period_ < (1 << precision)); + } + + /*! + Enables or disables vibrato. + */ + void set_vibrato_enabled(bool enabled) { + enable_vibrato_ = int(enabled); + } + + enum class Waveform { + Sine, HalfSine, AbsSine, PulseSine + }; + + private: + static constexpr int precision_shift = 1 + precision; + + int raw_phase_ = 0; + + int multiple_ = 0; + int period_ = 0; + int octave_ = 0; + int enable_vibrato_ = 0; + + Waveform waveform_ = Waveform::Sine; +}; + +} +} + +#endif /* PhaseGenerator_h */ diff --git a/Components/OPL2/OPL2.cpp b/Components/OPL2/OPL2.cpp index a945a39e5..da480c8c6 100644 --- a/Components/OPL2/OPL2.cpp +++ b/Components/OPL2/OPL2.cpp @@ -11,6 +11,9 @@ #include #include +#include "Implementation/PhaseGenerator.hpp" +#include "Implementation/EnvelopeGenerator.hpp" + using namespace Yamaha::OPL; template diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 62a4a224e..e81862b03 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1151,6 +1151,8 @@ 4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Microdisc.hpp; path = Oric/Microdisc.hpp; sourceTree = ""; }; 4B61908E24526E640013F202 /* LowFrequencyOscillator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = LowFrequencyOscillator.cpp; sourceTree = ""; }; 4B61908F24526E640013F202 /* LowFrequencyOscillator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LowFrequencyOscillator.hpp; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; @@ -3532,6 +3534,8 @@ 4BC0CB3A2447ECAE00A79DBB /* Tables.hpp */, 4B61908E24526E640013F202 /* LowFrequencyOscillator.cpp */, 4B61908F24526E640013F202 /* LowFrequencyOscillator.hpp */, + 4B619092245BC1000013F202 /* PhaseGenerator.hpp */, + 4B619093245CD63E0013F202 /* EnvelopeGenerator.hpp */, ); path = Implementation; sourceTree = "";