1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-19 10:29:08 +00:00

Adds KeyLevelScaler, implements EnvelopeGenerator, adds reset to PhaseGenerator.

This commit is contained in:
Thomas Harte 2020-05-03 16:24:55 -04:00
parent 9d2691d1d2
commit 1ff5ea0a6e
4 changed files with 230 additions and 24 deletions

View File

@ -29,17 +29,60 @@ namespace OPL {
this is largely for logical conformity with the phase generator that necessarily has to this is largely for logical conformity with the phase generator that necessarily has to
apply vibrato. apply vibrato.
*/ */
template <int precision> class EnvelopeGenerator { template <int envelope_precision, int period_precision> class EnvelopeGenerator {
public: public:
/*! /*!
Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator. Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/ */
void update(const LowFrequencyOscillator &oscillator); void update(const LowFrequencyOscillator &oscillator) {
// Apply tremolo, which is fairly easy.
tremolo_ = tremolo_enable_ * oscillator.tremolo << 4;
// Something something something...
const int key_scaling_rate = key_scale_rate_ >> key_scale_rate_shift_;
switch(phase_) {
case Phase::Damp:
update_decay(oscillator, 12);
if(attenuation_ == 511) {
will_attack_();
phase_ = Phase::Attack;
}
break;
case Phase::Attack:
update_attack(oscillator, attack_rate_ + key_scaling_rate);
// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached.
if((attack_rate_ + key_scaling_rate) > 60 || attenuation_ <= 0) {
attenuation_ = 0;
phase_ = Phase::Decay;
}
break;
case Phase::Decay:
update_decay(oscillator, decay_rate_ + key_scaling_rate);
if(attenuation_ >= sustain_level_) {
attenuation_ = sustain_level_;
phase_ = use_sustain_level_ ? Phase::Sustain : Phase::Release;
}
break;
case Phase::Sustain:
// Nothing to do.
break;
case Phase::Release:
update_decay(oscillator, release_rate_ + key_scaling_rate);
break;
}
}
/*! /*!
@returns The current attenuation from this envelope generator. @returns The current attenuation from this envelope generator.
*/ */
int attenuation() const; int attenuation() const {
return attenuation_ + tremolo_;
}
/*! /*!
Enables or disables damping on this envelope generator. If damping is enabled then this envelope generator will Enables or disables damping on this envelope generator. If damping is enabled then this envelope generator will
@ -48,63 +91,165 @@ template <int precision> class EnvelopeGenerator {
@param will_attack Supply a will_attack callback to enable damping mode; supply nullopt to disable damping mode. @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); void set_should_damp(const std::optional<std::function<void(void)>> &will_attack) {
will_attack_ = will_attack;
}
/*! /*!
Sets the current state of the key-on input. Sets the current state of the key-on input.
*/ */
void set_key_on(bool); void set_key_on(bool key_on) {
// Do nothing if this is not a leading or trailing edge.
if(key_on == key_on_) return;
key_on_ = key_on;
// Always transition to release upon a key off.
if(!key_on_) {
phase_ = Phase::Release;
return;
}
// On key on: if this is an envelope generator with damping, and damping is required,
// schedule that. If damping is not required, announce a pending attack now and
// transition to attack.
if(will_attack_) {
if(attenuation_ != 511) {
phase_ = Phase::Damp;
return;
}
will_attack_();
}
phase_ = Phase::Attack;
}
/*! /*!
Sets the attack rate, which should be in the range 015. Sets the attack rate, which should be in the range 015.
*/ */
void set_attack_rate(int); void set_attack_rate(int rate) {
attack_rate_ = rate << 2;
}
/*! /*!
Sets the decay rate, which should be in the range 015. Sets the decay rate, which should be in the range 015.
*/ */
void set_decay_rate(int); void set_decay_rate(int rate) {
decay_rate_ = rate << 2;
}
/*! /*!
Sets the release rate, which should be in the range 015. Sets the release rate, which should be in the range 015.
*/ */
void set_release_rate(int); void set_release_rate(int rate) {
release_rate_ = rate << 2;
}
/*! /*!
Sets the sustain level, which should be in the range 015. Sets the sustain level, which should be in the range 015.
*/ */
void set_sustain_level(int); void set_sustain_level(int level) {
sustain_level_ = level << 3;
// TODO: verify the shift level here. Especially re: precision.
}
/*! /*!
Enables or disables use of the sustain level. If this is disabled, the envelope proceeds Enables or disables use of the sustain level. If this is disabled, the envelope proceeds
directly from decay to release. directly from decay to release.
*/ */
void set_use_sustain_level(bool); void set_use_sustain_level(bool use) {
use_sustain_level_ = use;
}
/*! /*!
Enables or disables key-rate scaling. Enables or disables key-rate scaling.
*/ */
void set_key_rate_scaling_enabled(bool enabled); void set_key_scaling_rate_enabled(bool enabled) {
key_scale_rate_shift_ = int(enabled) * 2;
}
/*! /*!
Enables or disables application of the low-frequency oscillator's tremolo. Enables or disables application of the low-frequency oscillator's tremolo.
*/ */
void set_tremolo_enabled(bool enabled); void set_tremolo_enabled(bool enabled) {
tremolo_enable_ = int(enabled);
}
/*! /*!
Sets the current period associated with the channel that owns this envelope generator; 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. this is used to select a key scaling rate if key-rate scaling is enabled.
*/ */
void set_period(int period, int octave); void set_period(int period, int octave) {
key_scale_rate_ = (octave << 1) | (period >> (period_precision - 1));
}
private: private:
enum class ADSRPhase { enum class Phase {
Attack, Decay, Sustain, Release, Damp Attack, Decay, Sustain, Release, Damp
} adsr_phase_ = ADSRPhase::Attack; } phase_ = Phase::Attack;
int adsr_attenuation_ = 511; int attenuation_ = 511, tremolo_ = 0;
bool key_on_ = false; bool key_on_ = false;
std::optional<std::function<void(void)>> will_attack_; std::optional<std::function<void(void)>> will_attack_;
int key_scale_rate_ = 0;
int key_scale_rate_shift_ = 0;
int tremolo_enable_ = 0;
int attack_rate_ = 0;
int decay_rate_ = 0;
int release_rate_ = 0;
int sustain_level_ = 0;
bool use_sustain_level_ = false;
void update_attack(const LowFrequencyOscillator &oscillator, int rate) {
// Rules:
//
// An attack rate of '13' has 32 samples in the attack phase; a rate of '12' has the same 32 steps, but spread out over 64 samples, etc.
// An attack rate of '14' uses a divide by four instead of two.
// 15 is instantaneous.
if(rate >= 56) {
attenuation_ -= (attenuation_ >> 2) - 1;
} else {
const int sample_length = 1 << (14 - (rate >> 2)); // TODO: don't throw away KSR bits.
if(!(oscillator.counter & (sample_length - 1))) {
attenuation_ -= (attenuation_ >> 3) - 1;
}
}
}
void update_decay(const LowFrequencyOscillator &oscillator, int rate) {
// Rules:
//
// (relative to a 511 scale)
//
// A rate of 0 is no decay at all.
// A rate of 1 means increase 4 per cycle.
// A rate of 2 means increase 2 per cycle.
// A rate of 3 means increase 1 per cycle.
// A rate of 4 means increase 1 every other cycle.
// A rate of 5 means increase once every fourth cycle.
// etc.
// eighth, sixteenth, 32nd, 64th, 128th, 256th, 512th, 1024th, 2048th, 4096th, 8192th
if(rate) {
// TODO: don't throw away low two bits of the rate.
switch(rate >> 2) {
case 1: attenuation_ += 32; break;
case 2: attenuation_ += 16; break;
default: {
const int sample_length = 1 << ((rate >> 2) - 4);
if(!(oscillator.counter & (sample_length - 1))) {
attenuation_ += 8;
}
} break;
}
// Clamp to the proper range.
attenuation_ = std::min(attenuation_, 511);
}
}
}; };
} }

View File

@ -0,0 +1,58 @@
//
// KeyLevelScaler.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef KeyLevelScaler_h
#define KeyLevelScaler_h
namespace Yamaha {
namespace OPL {
template <int frequency_precision> class KeyLevelScaler {
public:
/*!
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) {
constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112};
constexpr int masks[2] = {~0, 0};
// A two's complement assumption is embedded below; the use of masks relies
// 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];
}
/*!
Enables or disables key-rate scaling.
*/
void set_key_scaling_level(int level) {
// '7' is just a number large enough to render all possible scaling coefficients as 0.
constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
shift_ = key_level_scale_shifts[level];
}
/*!
@returns The current attenuation level due to key-level scaling.
*/
int attenuation() const {
return level_ >> shift_;
}
private:
int level_ = 0;
int shift_ = 0;
};
}
}
#endif /* KeyLevelScaler_h */

View File

@ -38,7 +38,7 @@ template <int precision> class PhaseGenerator {
const int vibrato = (top_freq >> vibrato_shifts[oscillator.vibrato]) * vibrato_signs[oscillator.vibrato >> 2] * enable_vibrato_; 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. // Apply phase update with vibrato from the low-frequency oscillator.
raw_phase_ += multiple_ * (period_ + vibrato) << octave_; phase_ += multiple_ * (period_ + vibrato) << octave_;
} }
@ -48,7 +48,7 @@ template <int precision> class PhaseGenerator {
int phase() const { int phase() const {
// My table if multipliers is multiplied by two, so shift by one more // My table if multipliers is multiplied by two, so shift by one more
// than the stated precision. // than the stated precision.
return raw_phase_ >> precision_shift; return phase_ >> precision_shift;
} }
/*! /*!
@ -85,21 +85,22 @@ template <int precision> class PhaseGenerator {
enable_vibrato_ = int(enabled); enable_vibrato_ = int(enabled);
} }
enum class Waveform { /*!
Sine, HalfSine, AbsSine, PulseSine Resets the current phase.
}; */
void reset() {
phase_ = 0;
}
private: private:
static constexpr int precision_shift = 1 + precision; static constexpr int precision_shift = 1 + precision;
int raw_phase_ = 0; int phase_ = 0;
int multiple_ = 0; int multiple_ = 0;
int period_ = 0; int period_ = 0;
int octave_ = 0; int octave_ = 0;
int enable_vibrato_ = 0; int enable_vibrato_ = 0;
Waveform waveform_ = Waveform::Sine;
}; };
} }

View File

@ -1153,6 +1153,7 @@
4B61908F24526E640013F202 /* LowFrequencyOscillator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LowFrequencyOscillator.hpp; 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>"; }; 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>"; }; 4B619093245CD63E0013F202 /* EnvelopeGenerator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = EnvelopeGenerator.hpp; sourceTree = "<group>"; };
4B619094245E73B90013F202 /* KeyLevelScaler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyLevelScaler.hpp; sourceTree = "<group>"; };
4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DisplayMetrics.cpp; path = ../../Outputs/DisplayMetrics.cpp; 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>"; }; 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>"; }; 4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = "<group>"; };
@ -3536,6 +3537,7 @@
4B61908F24526E640013F202 /* LowFrequencyOscillator.hpp */, 4B61908F24526E640013F202 /* LowFrequencyOscillator.hpp */,
4B619092245BC1000013F202 /* PhaseGenerator.hpp */, 4B619092245BC1000013F202 /* PhaseGenerator.hpp */,
4B619093245CD63E0013F202 /* EnvelopeGenerator.hpp */, 4B619093245CD63E0013F202 /* EnvelopeGenerator.hpp */,
4B619094245E73B90013F202 /* KeyLevelScaler.hpp */,
); );
path = Implementation; path = Implementation;
sourceTree = "<group>"; sourceTree = "<group>";