mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-30 19:30:17 +00:00
Adds tremolo support, switches to global timer for ADSR stages other than attack.
This commit is contained in:
parent
c7ad6b1b50
commit
9e3614066a
@ -33,16 +33,16 @@ void Channel::set_feedback_mode(uint8_t value) {
|
||||
use_fm_synthesis_ = value & 1;
|
||||
}
|
||||
|
||||
int Channel::update(Operator *modulator, Operator *carrier, OperatorOverrides *modulator_overrides, OperatorOverrides *carrier_overrides) {
|
||||
int Channel::update(const LowFrequencyOscillator &oscillator, Operator *modulator, Operator *carrier, OperatorOverrides *modulator_overrides, OperatorOverrides *carrier_overrides) {
|
||||
if(use_fm_synthesis_) {
|
||||
// Get modulator level, use that as a phase-adjusting input to the carrier and then return the carrier level.
|
||||
modulator->update(modulator_state_, key_on_, period_ << frequency_shift_, octave_, nullptr, modulator_overrides);
|
||||
carrier->update(carrier_state_, key_on_, period_ << frequency_shift_, octave_, &modulator_state_, carrier_overrides);
|
||||
modulator->update(modulator_state_, nullptr, oscillator, key_on_, period_ << frequency_shift_, octave_, modulator_overrides);
|
||||
carrier->update(carrier_state_, &modulator_state_, oscillator, key_on_, period_ << frequency_shift_, octave_, carrier_overrides);
|
||||
return carrier_state_.level();
|
||||
} else {
|
||||
// Get modulator and carrier levels separately, return their sum.
|
||||
modulator->update(modulator_state_, key_on_, period_ << frequency_shift_, octave_, nullptr, modulator_overrides);
|
||||
carrier->update(carrier_state_, key_on_, period_ << frequency_shift_, octave_, nullptr, carrier_overrides);
|
||||
modulator->update(modulator_state_, nullptr, oscillator, key_on_, period_ << frequency_shift_, octave_, modulator_overrides);
|
||||
carrier->update(carrier_state_, nullptr, oscillator, key_on_, period_ << frequency_shift_, octave_, carrier_overrides);
|
||||
return (modulator_state_.level() + carrier_state_.level());
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#ifndef Channel_hpp
|
||||
#define Channel_hpp
|
||||
|
||||
#include "LowFrequencyOscillator.hpp"
|
||||
#include "Operator.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
@ -40,7 +41,7 @@ class Channel {
|
||||
|
||||
/// This should be called at a rate of around 49,716 Hz; it returns the current output level
|
||||
/// level for this channel.
|
||||
int update(Operator *modulator, Operator *carrier, OperatorOverrides *modulator_overrides = nullptr, OperatorOverrides *carrier_overrides = nullptr);
|
||||
int update(const LowFrequencyOscillator &oscillator, Operator *modulator, Operator *carrier, OperatorOverrides *modulator_overrides = nullptr, OperatorOverrides *carrier_overrides = nullptr);
|
||||
|
||||
/// @returns @c true if this channel is currently producing any audio; @c false otherwise;
|
||||
bool is_audible(Operator *carrier, OperatorOverrides *carrier_overrides = nullptr);
|
||||
|
@ -0,0 +1,9 @@
|
||||
//
|
||||
// LowFrequencyOscillator.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/04/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "LowFrequencyOscillator.hpp"
|
54
Components/OPL2/Implementation/LowFrequencyOscillator.hpp
Normal file
54
Components/OPL2/Implementation/LowFrequencyOscillator.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// LowFrequencyOscillator.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/04/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef LowFrequencyOscillator_hpp
|
||||
#define LowFrequencyOscillator_hpp
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
|
||||
/*!
|
||||
Models the output of the OPL low-frequency oscillator, which provides a couple of optional fixed-frequency
|
||||
modifications to an operator: tremolo and vibrato. Also exposes a global time counter, which oscillators use
|
||||
as part of their ADSR envelope.
|
||||
*/
|
||||
class LowFrequencyOscillator {
|
||||
public:
|
||||
/// Current attenuation due to tremolo / amplitude modulation, between 0 and 26.
|
||||
int tremolo = 0;
|
||||
/// TODO
|
||||
int vibrato = 0;
|
||||
/// A counter of the number of operator update cycles (i.e. input clock / 72) since an arbitrary time.
|
||||
int counter = 0;
|
||||
|
||||
/// Updates the oscillator outputs
|
||||
void update() {
|
||||
++counter;
|
||||
|
||||
// Update tremolo.
|
||||
++tremolo_phase_;
|
||||
|
||||
// This produces output of:
|
||||
//
|
||||
// four instances of 0, four instances of 1... _three_ instances of 26,
|
||||
// four instances of 25, four instances of 24... _three_ instances of 0.
|
||||
//
|
||||
// ... advancing once every 64th update.
|
||||
const int tremolo_index = (tremolo_phase_ >> 6) % 210;
|
||||
const int tremolo_levels[2] = {tremolo_index >> 2, 52 - ((tremolo_index+1) >> 2)};
|
||||
tremolo = tremolo_levels[tremolo_index / 107];
|
||||
}
|
||||
|
||||
private:
|
||||
int tremolo_phase_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* LowFrequencyOscillator_hpp */
|
@ -62,7 +62,14 @@ bool Operator::is_audible(OperatorState &state, OperatorOverrides *overrides) {
|
||||
|
||||
// MARK: - Update logic.
|
||||
|
||||
void Operator::update(OperatorState &state, bool key_on, int channel_period, int channel_octave, OperatorState *phase_offset, OperatorOverrides *overrides) {
|
||||
void Operator::update(
|
||||
OperatorState &state,
|
||||
const OperatorState *phase_offset,
|
||||
const LowFrequencyOscillator &oscillator,
|
||||
bool key_on,
|
||||
int channel_period,
|
||||
int channel_octave,
|
||||
const OperatorOverrides *overrides) {
|
||||
// Per the documentation:
|
||||
//
|
||||
// Delta phase = ( [desired freq] * 2^19 / [input clock / 72] ) / 2 ^ (b - 1)
|
||||
@ -93,10 +100,9 @@ void Operator::update(OperatorState &state, bool key_on, int channel_period, int
|
||||
// On the leading edge of it becoming true, enter the attack state.
|
||||
if(!key_on) {
|
||||
state.adsr_phase_ = OperatorState::ADSRPhase::Release;
|
||||
state.time_in_phase_ = 0;
|
||||
} else if(!state.last_key_on_) {
|
||||
state.adsr_phase_ = OperatorState::ADSRPhase::Attack;
|
||||
state.time_in_phase_ = 0;
|
||||
state.attack_time_ = 0;
|
||||
}
|
||||
state.last_key_on_ = key_on;
|
||||
|
||||
@ -110,8 +116,7 @@ void Operator::update(OperatorState &state, bool key_on, int channel_period, int
|
||||
assert(key_scaling_rate < 16);
|
||||
assert((channel_period >> 9) < 2);
|
||||
|
||||
const auto current_phase = state.adsr_phase_;
|
||||
switch(current_phase) {
|
||||
switch(state.adsr_phase_) {
|
||||
case OperatorState::ADSRPhase::Attack: {
|
||||
const int attack_rate = attack_rate_ + key_scaling_rate;
|
||||
|
||||
@ -125,7 +130,7 @@ void Operator::update(OperatorState &state, bool key_on, int channel_period, int
|
||||
state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 2) - 1;
|
||||
} else {
|
||||
const int sample_length = 1 << (14 - (attack_rate >> 2)); // TODO: don't throw away KSR bits.
|
||||
if(!(state.time_in_phase_ & (sample_length - 1))) {
|
||||
if(!(state.attack_time_ & (sample_length - 1))) {
|
||||
state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 3) - 1;
|
||||
}
|
||||
}
|
||||
@ -162,7 +167,7 @@ void Operator::update(OperatorState &state, bool key_on, int channel_period, int
|
||||
case 2: state.adsr_attenuation_ += 16; break;
|
||||
default: {
|
||||
const int sample_length = 1 << ((decrease_rate >> 2) - 4);
|
||||
if(!(state.time_in_phase_ & (sample_length - 1))) {
|
||||
if(!(oscillator.counter & (sample_length - 1))) {
|
||||
state.adsr_attenuation_ += 8;
|
||||
}
|
||||
} break;
|
||||
@ -183,11 +188,7 @@ void Operator::update(OperatorState &state, bool key_on, int channel_period, int
|
||||
// Nothing to do.
|
||||
break;
|
||||
}
|
||||
if(state.adsr_phase_ == current_phase) {
|
||||
++state.time_in_phase_;
|
||||
} else {
|
||||
state.time_in_phase_ = 0;
|
||||
}
|
||||
++state.attack_time_;
|
||||
|
||||
// Calculate key-level scaling. Table is as per p14 of the YM3812 application manual,
|
||||
// converted into a fixed-point scheme. Compare with https://www.smspower.org/Development/RE12
|
||||
@ -221,4 +222,9 @@ void Operator::update(OperatorState &state, bool key_on, int channel_period, int
|
||||
// attenuations of 24db to 0.75db.
|
||||
state.attenuation.log += (state.adsr_attenuation_ << 3) + (attenuation_ << 5);
|
||||
}
|
||||
|
||||
// Add optional tremolo.
|
||||
state.attenuation.log += int(apply_amplitude_modulation_) * oscillator.tremolo << 4;
|
||||
}
|
||||
|
||||
// TODO: both the tremolo and ADSR envelopes should be half-resolution on an OPLL.
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include "Tables.hpp"
|
||||
#include "LowFrequencyOscillator.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
@ -30,7 +31,7 @@ struct OperatorState {
|
||||
enum class ADSRPhase {
|
||||
Attack, Decay, Sustain, Release
|
||||
} adsr_phase_ = ADSRPhase::Attack;
|
||||
int time_in_phase_ = 0;
|
||||
int attack_time_ = 0;
|
||||
int adsr_attenuation_ = 511;
|
||||
bool last_key_on_ = false;
|
||||
|
||||
@ -83,7 +84,14 @@ class Operator {
|
||||
void set_am_vibrato_hold_sustain_ksr_multiple(uint8_t value);
|
||||
|
||||
/// Provides one clock tick to the operator, along with the relevant parameters of its channel.
|
||||
void update(OperatorState &state, bool key_on, int channel_period, int channel_octave, OperatorState *phase_offset, OperatorOverrides *overrides = nullptr);
|
||||
void update(
|
||||
OperatorState &state,
|
||||
const OperatorState *phase_offset,
|
||||
const LowFrequencyOscillator &oscillator,
|
||||
bool key_on,
|
||||
int channel_period,
|
||||
int channel_octave,
|
||||
const OperatorOverrides *overrides = nullptr);
|
||||
|
||||
/// @returns @c true if this channel currently has a non-zero output; @c false otherwise.
|
||||
bool is_audible(OperatorState &state, OperatorOverrides *overrides = nullptr);
|
||||
|
@ -180,9 +180,12 @@ void OPLL::setup_fixed_instrument(int number, const uint8_t *data) {
|
||||
}
|
||||
|
||||
void OPLL::update_all_chanels() {
|
||||
// Update the LFO.
|
||||
oscillator_.update();
|
||||
|
||||
// Channels that are updated for melodic output regardless.
|
||||
for(int c = 0; c < 6; ++ c) {
|
||||
channels_[c].level = (channels_[c].update() * total_volume_) >> 11;
|
||||
channels_[c].level = (channels_[c].update(oscillator_) * total_volume_) >> 11;
|
||||
}
|
||||
|
||||
if(depth_rhythm_control_ & 0x20) {
|
||||
@ -190,7 +193,7 @@ void OPLL::update_all_chanels() {
|
||||
} else {
|
||||
// All melody, all the time.
|
||||
for(int c = 6; c < 9; ++ c) {
|
||||
channels_[c].level = (channels_[c].update() * total_volume_) >> 11;
|
||||
channels_[c].level = (channels_[c].update(oscillator_) * total_volume_) >> 11;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource
|
||||
OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
LowFrequencyOscillator oscillator_;
|
||||
|
||||
uint8_t depth_rhythm_control_;
|
||||
uint8_t csm_keyboard_split_;
|
||||
@ -92,9 +93,10 @@ struct OPLL: public OPLBase<OPLL> {
|
||||
// one user-configurable channel, 15 hard-coded channels, and
|
||||
// three channels configured for rhythm generation.
|
||||
|
||||
|
||||
struct Channel: public ::Yamaha::OPL::Channel {
|
||||
int update() {
|
||||
return Yamaha::OPL::Channel::update(modulator, modulator + 1, nullptr, &overrides);
|
||||
int update(const LowFrequencyOscillator &oscillator) {
|
||||
return Yamaha::OPL::Channel::update(oscillator, modulator, modulator + 1, nullptr, &overrides);
|
||||
}
|
||||
|
||||
bool is_audible() {
|
||||
|
@ -222,6 +222,8 @@
|
||||
4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; };
|
||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; };
|
||||
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */; };
|
||||
4B61909024526E640013F202 /* LowFrequencyOscillator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B61908E24526E640013F202 /* LowFrequencyOscillator.cpp */; };
|
||||
4B61909124526E640013F202 /* LowFrequencyOscillator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B61908E24526E640013F202 /* LowFrequencyOscillator.cpp */; };
|
||||
4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */; };
|
||||
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; };
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; };
|
||||
@ -1147,6 +1149,8 @@
|
||||
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileHolder.hpp; sourceTree = "<group>"; };
|
||||
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Microdisc.cpp; path = Oric/Microdisc.cpp; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -3526,6 +3530,8 @@
|
||||
4BC0CB352447EC9A00A79DBB /* Channel.cpp */,
|
||||
4BC0CB362447EC9A00A79DBB /* Channel.hpp */,
|
||||
4BC0CB3A2447ECAE00A79DBB /* Tables.hpp */,
|
||||
4B61908E24526E640013F202 /* LowFrequencyOscillator.cpp */,
|
||||
4B61908F24526E640013F202 /* LowFrequencyOscillator.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
@ -4423,6 +4429,7 @@
|
||||
4B055AB01FAE86070060FFFF /* PulseQueuedTape.cpp in Sources */,
|
||||
4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */,
|
||||
4BB307BC235001C300457D33 /* 6850.cpp in Sources */,
|
||||
4B61909124526E640013F202 /* LowFrequencyOscillator.cpp in Sources */,
|
||||
4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */,
|
||||
4B89451D201967B4007DE474 /* Disk.cpp in Sources */,
|
||||
4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
|
||||
@ -4743,6 +4750,7 @@
|
||||
4B1B88C0202E3DB200B67DFF /* MultiConfigurable.cpp in Sources */,
|
||||
4BFF1D3922337B0300838EA1 /* 68000Storage.cpp in Sources */,
|
||||
4B54C0BC1F8D8E790050900F /* KeyboardMachine.cpp in Sources */,
|
||||
4B61909024526E640013F202 /* LowFrequencyOscillator.cpp in Sources */,
|
||||
4BB244D522AABAF600BE20E5 /* z8530.cpp in Sources */,
|
||||
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */,
|
||||
4B894534201967B4007DE474 /* AddressMapper.cpp in Sources */,
|
||||
|
Loading…
Reference in New Issue
Block a user