From 559a2d81c1a07020180d4bab429f3836c8669b19 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 12 Apr 2020 12:46:40 -0400 Subject: [PATCH] Baby step: starts trying to output the raw FM carrier, no modulation, no ADSR. --- Components/OPL2/OPL2.cpp | 56 +++++++++++++++++++++++++- Components/OPL2/OPL2.hpp | 38 +++++++++++++---- Machines/MasterSystem/MasterSystem.cpp | 11 ++--- 3 files changed, 91 insertions(+), 14 deletions(-) diff --git a/Components/OPL2/OPL2.cpp b/Components/OPL2/OPL2.cpp index e500a8ba0..1985e9f56 100644 --- a/Components/OPL2/OPL2.cpp +++ b/Components/OPL2/OPL2.cpp @@ -116,7 +116,11 @@ template class Yamaha::OPL::OPLBase; template class Yamaha::OPL::OPLBase; -OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, bool is_vrc7): OPLBase(task_queue) { +OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider, bool is_vrc7): OPLBase(task_queue), audio_divider_(audio_divider) { + // Due to the way that sound mixing works on the OPLL, the audio divider may not + // be larger than 2. + assert(audio_divider <= 2); + // Install fixed instruments. const uint8_t *patch_set = is_vrc7 ? vrc7_patch_set : opll_patch_set; for(int c = 0; c < 15; ++c) { @@ -128,13 +132,63 @@ OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, bool is_vrc7): OPLB for(int c = 0; c < 3; ++c) { setup_fixed_instrument(c+16, &percussion_patch_set[c * 8]); } + + // Set default modulators. + for(int c = 0; c < 9; ++c) { + channels_[c].modulator = &operators_[0]; + } } bool OPLL::is_zero_level() { + for(int c = 0; c < 9; ++c) { + if(channels_[c].is_audible()) return false; + } return true; } void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) { + // Both the OPLL and the OPL2 divide the input clock by 72 to get the base tick frequency; + // unlike the OPL2 the OPLL time-divides the output for 'mixing'. + + const int update_period = 72 / audio_divider_; + const int channel_output_period = 8 / audio_divider_; + + // Fill in any leftover from the previous session. + if(audio_offset_) { + while(audio_offset_ < update_period && number_of_samples) { + *target = int16_t(channels_[audio_offset_ / channel_output_period].level); + ++target; + ++audio_offset_; + --number_of_samples; + } + audio_offset_ = 0; + } + + // End now if that provided everything that was asked for. + if(!number_of_samples) return; + + int total_updates = int(number_of_samples) / update_period; + number_of_samples %= size_t(update_period); + audio_offset_ = int(number_of_samples); + + while(total_updates--) { + update_all_chanels(); + + for(int c = 0; c < update_period; ++c) { + *target = int16_t(channels_[c / channel_output_period].level); + ++target; + } + } + + // If there are any other spots remaining, fill them. + if(number_of_samples) { + update_all_chanels(); + + for(int c = 0; c < int(number_of_samples); ++c) { + *target = int16_t(channels_[c / channel_output_period].level); + ++target; + } + } } void OPLL::set_sample_volume_range(std::int16_t range) { diff --git a/Components/OPL2/OPL2.hpp b/Components/OPL2/OPL2.hpp index 4bcfc729f..71b10f067 100644 --- a/Components/OPL2/OPL2.hpp +++ b/Components/OPL2/OPL2.hpp @@ -13,6 +13,8 @@ #include "../../Concurrency/AsyncTaskQueue.hpp" #include "../../Numeric/LFSR.hpp" +#include + namespace Yamaha { @@ -64,14 +66,14 @@ class Operator { public: /// Sets this operator's attack rate as the top nibble of @c value, its decay rate as the bottom nibble. void set_attack_decay(uint8_t value) { - attack_rate = value >> 4; - decay_rate = value & 0xf; + attack_rate = (value & 0xf0) >> 2; + decay_rate = (value & 0x0f) << 2; } /// Sets this operator's sustain level as the top nibble of @c value, its release rate as the bottom nibble. void set_sustain_release(uint8_t value) { - sustain_level = value >> 4; - release_rate = value & 0xf; + sustain_level = (value & 0xf0) >> 2; + release_rate = (value & 0x0f) << 2; } /// Sets this operator's key scale level as the top two bits of @c value, its total output level as the low six bits. @@ -160,7 +162,8 @@ class Operator { /// Selects attenuation that is applied as a function of interval. Cf. p14. int scaling_level = 0; - /// Sets the ADSR rates. + /// Sets the ADSR rates. These all provide the top four bits of a six-bit number; + /// the bottom two bits... are 'RL'? int attack_rate = 0; int decay_rate = 0; int sustain_level = 0; @@ -207,10 +210,20 @@ class Channel { use_fm_synthesis = value & 1; } - // This should be called at a rate of around 49,716 Hz. - void update(Operator *carrier, Operator *modulator) { + /// 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) { modulator->update(modulator_state_, frequency, octave); carrier->update(carrier_state_, frequency, octave); + + // TODO: almost everything else. This is a quick test. + if(!key_on) return 0; + return int(sin(float(carrier_state_.phase) / 1024.0) * 2048.0); + } + + /// @returns @c true if this channel is currently producing any audio; @c false otherwise; + bool is_audible() { + return key_on; // TODO: this is a temporary hack in lieu of ADSR. Fix. } private: @@ -289,7 +302,7 @@ struct OPL2: public OPLBase { struct OPLL: public OPLBase { public: // Creates a new OPLL or VRC7. - OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, bool is_vrc7 = false); + OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false); /// As per ::SampleSource; provides a broadphase test for silence. bool is_zero_level(); @@ -312,13 +325,22 @@ struct OPLL: public OPLBase { struct Channel: public ::Yamaha::OPL::Channel { Operator *modulator; // Implicitly, the carrier is modulator+1. OperatorOverrides overrides; + int level = 0; }; + void update_all_chanels() { + for(int c = 0; c < 6; ++ c) { // Don't do anything with channels that might be percussion for now. + channels_[c].level = channels_[c].update(channels_[c].modulator, channels_[c].modulator + 1); + } + } Channel channels_[9]; void setup_fixed_instrument(int number, const uint8_t *data); uint8_t custom_instrument_[8]; void write_register(uint8_t address, uint8_t value); + + const int audio_divider_ = 1; + int audio_offset_ = 0; }; } diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index f82690a66..7fa1828ce 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -32,7 +32,7 @@ #include namespace { -constexpr int sn76489_divider = 2; +constexpr int audio_divider = 2; } namespace Sega { @@ -98,14 +98,14 @@ class ConcreteMachine: sn76489_( (target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS, audio_queue_, - sn76489_divider), - opll_(audio_queue_), + audio_divider), + opll_(audio_queue_, audio_divider), mixer_(sn76489_, opll_), speaker_(mixer_), keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}, {}) { // Pick the clock rate based on the region. const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0; - speaker_.set_input_rate(static_cast(clock_rate / sn76489_divider)); + speaker_.set_input_rate(static_cast(clock_rate / audio_divider)); set_clock_rate(clock_rate); // Instantiate the joysticks. @@ -321,6 +321,7 @@ class ConcreteMachine: if(has_fm_audio_) { switch(address & 0xff) { case 0xf0: case 0xf1: + update_audio(); opll_.write(address, *cycle.value); break; case 0xf2: @@ -441,7 +442,7 @@ class ConcreteMachine: } inline void update_audio() { - speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); + speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(audio_divider))); } using Target = Analyser::Static::Sega::Target;