1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-10-02 19:54:35 +00:00

Taking it as given that outstanding deficiencies are mostly due to poor design, starts breaking out the envelope and phase generators.

This commit is contained in:
Thomas Harte 2020-05-01 23:46:42 -04:00
parent e4ef2c68bb
commit 9d2691d1d2
4 changed files with 228 additions and 0 deletions

View File

@ -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 <optional>
#include <functional>
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 <int precision> 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<std::function<void(void)>> &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 015.
*/
void set_attack_rate(int);
/*!
Sets the decay rate, which should be in the range 015.
*/
void set_decay_rate(int);
/*!
Sets the release rate, which should be in the range 015.
*/
void set_release_rate(int);
/*!
Sets the sustain level, which should be in the range 015.
*/
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<std::function<void(void)>> will_attack_;
};
}
}
#endif /* EnvelopeGenerator_h */

View File

@ -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 <cassert>
#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 <int precision> 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 */

View File

@ -11,6 +11,9 @@
#include <cassert>
#include <cmath>
#include "Implementation/PhaseGenerator.hpp"
#include "Implementation/EnvelopeGenerator.hpp"
using namespace Yamaha::OPL;
template <typename Child>

View File

@ -1151,6 +1151,8 @@
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Microdisc.hpp; path = Oric/Microdisc.hpp; sourceTree = "<group>"; };
4B61908E24526E640013F202 /* LowFrequencyOscillator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = LowFrequencyOscillator.cpp; sourceTree = "<group>"; };
4B61908F24526E640013F202 /* LowFrequencyOscillator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LowFrequencyOscillator.hpp; sourceTree = "<group>"; };
4B619092245BC1000013F202 /* PhaseGenerator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PhaseGenerator.hpp; sourceTree = "<group>"; };
4B619093245CD63E0013F202 /* EnvelopeGenerator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = EnvelopeGenerator.hpp; sourceTree = "<group>"; };
4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DisplayMetrics.cpp; path = ../../Outputs/DisplayMetrics.cpp; sourceTree = "<group>"; };
4B622AE4222E0AD5008B59F2 /* DisplayMetrics.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DisplayMetrics.hpp; path = ../../Outputs/DisplayMetrics.hpp; sourceTree = "<group>"; };
4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = "<group>"; };
@ -3532,6 +3534,8 @@
4BC0CB3A2447ECAE00A79DBB /* Tables.hpp */,
4B61908E24526E640013F202 /* LowFrequencyOscillator.cpp */,
4B61908F24526E640013F202 /* LowFrequencyOscillator.hpp */,
4B619092245BC1000013F202 /* PhaseGenerator.hpp */,
4B619093245CD63E0013F202 /* EnvelopeGenerator.hpp */,
);
path = Implementation;
sourceTree = "<group>";