1
0
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:
Thomas Harte 2020-04-23 23:55:49 -04:00
parent c7ad6b1b50
commit 9e3614066a
9 changed files with 115 additions and 24 deletions

View File

@ -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());
}
}

View File

@ -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);

View File

@ -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"

View 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 */

View File

@ -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.

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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 */,