From 64003fb004419a318eae8e532278f1885d7c8fbf Mon Sep 17 00:00:00 2001 From: Dalton Messmer Date: Mon, 20 Mar 2023 18:11:24 -0400 Subject: [PATCH] Fix FreeBoy CPU time bug (#6680) --- plugins/FreeBoy/CMakeLists.txt | 4 +- plugins/FreeBoy/FreeBoy.cpp | 216 +++++++++--------- plugins/FreeBoy/FreeBoy.h | 26 +-- .../{Gb_Apu_Buffer.cpp => GbApuWrapper.cpp} | 44 ++-- .../{Gb_Apu_Buffer.h => GbApuWrapper.h} | 36 +-- 5 files changed, 168 insertions(+), 158 deletions(-) rename plugins/FreeBoy/{Gb_Apu_Buffer.cpp => GbApuWrapper.cpp} (57%) rename plugins/FreeBoy/{Gb_Apu_Buffer.h => GbApuWrapper.h} (59%) diff --git a/plugins/FreeBoy/CMakeLists.txt b/plugins/FreeBoy/CMakeLists.txt index fb5093ee0..485ed3cc2 100644 --- a/plugins/FreeBoy/CMakeLists.txt +++ b/plugins/FreeBoy/CMakeLists.txt @@ -4,8 +4,8 @@ INCLUDE_DIRECTORIES(game-music-emu/gme) BUILD_PLUGIN(freeboy FreeBoy.cpp FreeBoy.h - Gb_Apu_Buffer.cpp - Gb_Apu_Buffer.h + GbApuWrapper.cpp + GbApuWrapper.h game-music-emu/gme/Gb_Apu.cpp game-music-emu/gme/Gb_Apu.h game-music-emu/gme/Gb_Oscs.cpp diff --git a/plugins/FreeBoy/FreeBoy.cpp b/plugins/FreeBoy/FreeBoy.cpp index 0d639d3a6..f9ef2c5aa 100644 --- a/plugins/FreeBoy/FreeBoy.cpp +++ b/plugins/FreeBoy/FreeBoy.cpp @@ -2,7 +2,7 @@ * FreeBoy.cpp - GameBoy papu based instrument * * Copyright (c) 2008 Attila Herman - * Csaba Hruska + * Csaba Hruska * * This file is part of LMMS - https://lmms.io * @@ -23,11 +23,12 @@ * */ -#include - -#include #include "FreeBoy.h" -#include "Gb_Apu_Buffer.h" + +#include +#include + +#include "GbApuWrapper.h" #include "base64.h" #include "InstrumentTrack.h" #include "Knob.h" @@ -45,8 +46,11 @@ namespace lmms { -const blip_time_t FRAME_LENGTH = 70224; -const long CLOCK_RATE = 4194304; +namespace +{ +constexpr blip_time_t FRAME_LENGTH = 70224; +constexpr long CLOCK_RATE = 4194304; +} extern "C" { @@ -118,9 +122,7 @@ FreeBoyInstrument::FreeBoyInstrument( InstrumentTrack * _instrument_track ) : m_trebleModel( -20.0f, -100.0f, 200.0f, 1.0f, this, tr( "Treble" ) ), m_bassModel( 461.0f, -1.0f, 600.0f, 1.0f, this, tr( "Bass" ) ), - m_graphModel( 0, 15, 32, this, false, 1 ), - - m_time(0) + m_graphModel( 0, 15, 32, this, false, 1 ) { } @@ -238,189 +240,189 @@ f_cnt_t FreeBoyInstrument::desiredReleaseFrames() const -void FreeBoyInstrument::playNote( NotePlayHandle * _n, - sampleFrame * _working_buffer ) +void FreeBoyInstrument::playNote(NotePlayHandle* nph, sampleFrame* workingBuffer) { - const f_cnt_t tfp = _n->totalFramesPlayed(); + const f_cnt_t tfp = nph->totalFramesPlayed(); const int samplerate = Engine::audioEngine()->processingSampleRate(); - const fpp_t frames = _n->framesLeftForCurrentPeriod(); - const f_cnt_t offset = _n->noteOffset(); + const fpp_t frames = nph->framesLeftForCurrentPeriod(); + const f_cnt_t offset = nph->noteOffset(); int data = 0; - int freq = _n->frequency(); + int freq = nph->frequency(); if ( tfp == 0 ) { - auto papu = new Gb_Apu_Buffer(); - papu->set_sample_rate( samplerate, CLOCK_RATE ); + auto papu = new GbApuWrapper{}; + papu->setSampleRate(samplerate, CLOCK_RATE); // Master sound circuitry power control - papu->write_register( fakeClock(), 0xff26, 0x80 ); + papu->writeRegister(0xff26, 0x80); data = m_ch1VolumeModel.value(); - data = data<<1; + data = data << 1; data += m_ch1VolSweepDirModel.value(); - data = data<<3; + data = data << 3; data += m_ch1SweepStepLengthModel.value(); - papu->write_register( fakeClock(), 0xff12, data ); + papu->writeRegister(0xff12, data); data = m_ch2VolumeModel.value(); - data = data<<1; + data = data << 1; data += m_ch2VolSweepDirModel.value(); - data = data<<3; + data = data << 3; data += m_ch2SweepStepLengthModel.value(); - papu->write_register( fakeClock(), 0xff17, data ); + papu->writeRegister(0xff17, data); //channel 4 - noise data = m_ch4VolumeModel.value(); - data = data<<1; + data = data << 1; data += m_ch4VolSweepDirModel.value(); - data = data<<3; + data = data << 3; data += m_ch4SweepStepLengthModel.value(); - papu->write_register( fakeClock(), 0xff21, data ); + papu->writeRegister(0xff21, data); - _n->m_pluginData = papu; + nph->m_pluginData = papu; } - auto papu = static_cast(_n->m_pluginData); + auto papu = static_cast(nph->m_pluginData); - papu->treble_eq( m_trebleModel.value() ); - papu->bass_freq( m_bassModel.value() ); + papu->trebleEq(m_trebleModel.value()); + papu->bassFreq(m_bassModel.value()); //channel 1 - square data = m_ch1SweepTimeModel.value(); - data = data<<1; + data = data << 1; data += m_ch1SweepDirModel.value(); data = data << 3; data += m_ch1SweepRtShiftModel.value(); - papu->write_register( fakeClock(), 0xff10, data ); + papu->writeRegister(0xff10, data); data = m_ch1WavePatternDutyModel.value(); - data = data<<6; - papu->write_register( fakeClock(), 0xff11, data ); - + data = data << 6; + papu->writeRegister(0xff11, data); //channel 2 - square data = m_ch2WavePatternDutyModel.value(); - data = data<<6; - papu->write_register( fakeClock(), 0xff16, data ); - + data = data << 6; + papu->writeRegister(0xff16, data); //channel 3 - wave - //data = m_ch3OnModel.value()?128:0; + //data = m_ch3OnModel.value() ? 128 : 0; data = 128; - papu->write_register( fakeClock(), 0xff1a, data ); + papu->writeRegister(0xff1a, data); auto ch3voldata = std::array{0, 3, 2, 1}; data = ch3voldata[(int)m_ch3VolumeModel.value()]; - data = data<<5; - papu->write_register( fakeClock(), 0xff1c, data ); - + data = data << 5; + papu->writeRegister(0xff1c, data); //controls data = m_so1VolumeModel.value(); - data = data<<4; + data = data << 4; data += m_so2VolumeModel.value(); - papu->write_register( fakeClock(), 0xff24, data ); + papu->writeRegister(0xff24, data); - data = m_ch4So2Model.value()?128:0; - data += m_ch3So2Model.value()?64:0; - data += m_ch2So2Model.value()?32:0; - data += m_ch1So2Model.value()?16:0; - data += m_ch4So1Model.value()?8:0; - data += m_ch3So1Model.value()?4:0; - data += m_ch2So1Model.value()?2:0; - data += m_ch1So1Model.value()?1:0; - papu->write_register( fakeClock(), 0xff25, data ); + data = m_ch4So2Model.value() ? 128 : 0; + data += m_ch3So2Model.value() ? 64 : 0; + data += m_ch2So2Model.value() ? 32 : 0; + data += m_ch1So2Model.value() ? 16 : 0; + data += m_ch4So1Model.value() ? 8 : 0; + data += m_ch3So1Model.value() ? 4 : 0; + data += m_ch2So1Model.value() ? 2 : 0; + data += m_ch1So1Model.value() ? 1 : 0; + papu->writeRegister(0xff25, data); - const float * wpm = m_graphModel.samples(); + const float* wpm = m_graphModel.samples(); - for( char i=0; i<16; i++ ) + for (char i = 0; i < 16; ++i) { - data = (int)floor(wpm[i*2]) << 4; - data += (int)floor(wpm[i*2+1]); - papu->write_register( fakeClock(), 0xff30 + i, data ); + data = static_cast(std::floor(wpm[i * 2])) << 4; + data += static_cast(std::floor(wpm[(i * 2) + 1])); + papu->writeRegister(0xff30 + i, data); } - if( ( freq >= 65 ) && ( freq <=4000 ) ) + if ((freq >= 65) && (freq <= 4000)) { - int initflag = (tfp==0)?128:0; - // Hz = 4194304 / ( ( 2048 - ( 11-bit-freq ) ) << 5 ) - data = 2048 - ( ( 4194304 / freq )>>5 ); - if( tfp==0 ) + int initFlag = (tfp == 0) ? 128 : 0; + // Hz = 4194304 / ((2048 - (11-bit-freq)) << 5) + data = 2048 - ((4194304 / freq) >> 5); + if (tfp == 0) { - papu->write_register( fakeClock(), 0xff13, data & 0xff ); - papu->write_register( fakeClock(), 0xff14, (data>>8) | initflag ); + papu->writeRegister(0xff13, data & 0xff); + papu->writeRegister(0xff14, (data >> 8) | initFlag); } - papu->write_register( fakeClock(), 0xff18, data & 0xff ); - papu->write_register( fakeClock(), 0xff19, (data>>8) | initflag ); - papu->write_register( fakeClock(), 0xff1d, data & 0xff ); - papu->write_register( fakeClock(), 0xff1e, (data>>8) | initflag ); + papu->writeRegister(0xff18, data & 0xff); + papu->writeRegister(0xff19, (data >> 8) | initFlag); + papu->writeRegister(0xff1d, data & 0xff); + papu->writeRegister(0xff1e, (data >> 8) | initFlag); } - if( tfp == 0 ) + if (tfp == 0) { //PRNG Frequency = (1048576 Hz / (ratio + 1)) / 2 ^ (shiftclockfreq + 1) - char sopt=0; - char ropt=1; - float fopt = 524288.0 / ( ropt * pow( 2.0, sopt + 1.0 ) ); + char sopt = 0; + char ropt = 1; + float fopt = 524288.0 / (ropt * std::pow(2.0, sopt + 1.0)); float f; - for ( char s=0; s<16; s++ ) - for ( char r=0; r<8; r++ ) { - f = 524288.0 / ( r * pow( 2.0, s + 1.0 ) ); - if( fabs( freq-fopt ) > fabs( freq-f ) ) { - fopt = f; - ropt = r; - sopt = s; + for (char s = 0; s < 16; ++s) + { + for (char r = 0; r < 8; ++r) + { + f = 524288.0 / (r * std::pow(2.0, s + 1.0)); + if (std::fabs(freq - fopt) > std::fabs(freq - f)) + { + fopt = f; + ropt = r; + sopt = s; + } } } + data = sopt; data = data << 1; data += m_ch4ShiftRegWidthModel.value(); data = data << 3; data += ropt; - papu->write_register( fakeClock(), 0xff22, data ); + papu->writeRegister(0xff22, data); //channel 4 init - papu->write_register( fakeClock(), 0xff23, 128 ); + papu->writeRegister(0xff23, 128); } - int const buf_size = 2048; - int framesleft = frames; - int datalen = 0; - auto buf = std::array{}; - while( framesleft > 0 ) + constexpr int bufSize = 2048; + int framesLeft = frames; + int dataLen = 0; + auto buf = std::array{}; + while (framesLeft > 0) { - int avail = papu->samples_avail(); - if( avail <= 0 ) + int avail = papu->samplesAvail(); + if (avail <= 0) { - m_time = 0; - papu->end_frame(FRAME_LENGTH); - avail = papu->samples_avail(); + papu->endFrame(FRAME_LENGTH); + avail = papu->samplesAvail(); } - datalen = framesleft>avail?avail:framesleft; - datalen = datalen>buf_size?buf_size:datalen; + dataLen = framesLeft > avail ? avail : framesLeft; + dataLen = dataLen > bufSize ? bufSize : dataLen; - long count = papu->read_samples(buf.data(), datalen * 2) / 2; + long count = papu->readSamples(buf.data(), dataLen * 2) / 2; - for( fpp_t frame = 0; frame < count; ++frame ) + for (fpp_t frame = 0; frame < count; ++frame) { - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) { - sample_t s = float(buf[frame*2+ch])/32768.0; - _working_buffer[frames-framesleft+frame+offset][ch] = s; + sample_t s = static_cast(buf[(frame * 2) + ch]) / 32768.0f; + workingBuffer[frames - framesLeft + frame + offset][ch] = s; } } - framesleft -= count; + framesLeft -= count; } - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); + instrumentTrack()->processAudioBuffer(workingBuffer, frames + offset, nph); } -void FreeBoyInstrument::deleteNotePluginData( NotePlayHandle * _n ) +void FreeBoyInstrument::deleteNotePluginData(NotePlayHandle* nph) { - delete static_cast( _n->m_pluginData ); + delete static_cast(nph->m_pluginData); } @@ -736,4 +738,4 @@ PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *m, void * ) } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/plugins/FreeBoy/FreeBoy.h b/plugins/FreeBoy/FreeBoy.h index aecd2b188..747305414 100644 --- a/plugins/FreeBoy/FreeBoy.h +++ b/plugins/FreeBoy/FreeBoy.h @@ -1,8 +1,8 @@ /* - * FreeBoyInstrument.h - GameBoy papu based instrument + * FreeBoy.h - GameBoy papu based instrument * - * Copyright (c) 2008 - * Csaba Hruska + * Copyright (c) 2008 Attila Herman + * Csaba Hruska * * This file is part of LMMS - https://lmms.io * @@ -23,8 +23,8 @@ * */ -#ifndef FREEBOY_H -#define FREEBOY_H +#ifndef LMMS_FREEBOY_H +#define LMMS_FREEBOY_H #include "AutomatableModel.h" #include "Blip_Buffer.h" @@ -54,10 +54,8 @@ public: FreeBoyInstrument( InstrumentTrack * _instrument_track ); ~FreeBoyInstrument() override = default; - void playNote( NotePlayHandle * _n, - sampleFrame * _working_buffer ) override; - void deleteNotePluginData( NotePlayHandle * _n ) override; - + void playNote(NotePlayHandle* nph, sampleFrame* workingBuffer) override; + void deleteNotePluginData(NotePlayHandle* nph) override; void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; void loadSettings( const QDomElement & _this ) override; @@ -112,12 +110,8 @@ private: graphModel m_graphModel; - // Fake CPU timing - blip_time_t m_time; - blip_time_t fakeClock() { return m_time += 4; } - friend class gui::FreeBoyInstrumentView; -} ; +}; namespace gui @@ -172,11 +166,11 @@ private: /*protected slots: void updateKnobHint(); void updateKnobToolTip();*/ -} ; +}; } // namespace gui } // namespace lmms -#endif +#endif // LMMS_FREEBOY_H diff --git a/plugins/FreeBoy/Gb_Apu_Buffer.cpp b/plugins/FreeBoy/GbApuWrapper.cpp similarity index 57% rename from plugins/FreeBoy/Gb_Apu_Buffer.cpp rename to plugins/FreeBoy/GbApuWrapper.cpp index ec1a36479..790cf96e2 100644 --- a/plugins/FreeBoy/Gb_Apu_Buffer.cpp +++ b/plugins/FreeBoy/GbApuWrapper.cpp @@ -1,7 +1,7 @@ /* - * Gb_Apu_Buffer.cpp - Gb_Apu subclass which allows direct buffer access + * GbApuWrapper.cpp - Gb_Apu subclass which allows direct buffer access * Copyright (c) 2017 Tres Finocchiaro - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -20,37 +20,45 @@ * Boston, MA 02110-1301 USA. * */ -#include "Gb_Apu_Buffer.h" + +#include "GbApuWrapper.h" namespace lmms { -void Gb_Apu_Buffer::end_frame(blip_time_t end_time) { - Gb_Apu::end_frame(end_time); - m_buf.end_frame(end_time); +// Sets specified sample rate and clock rate in Stereo_Buffer +blargg_err_t GbApuWrapper::setSampleRate(long sampleRate, long clockRate) +{ + Gb_Apu::output(m_buf.center(), m_buf.left(), m_buf.right()); + m_buf.clock_rate(clockRate); + return m_buf.set_sample_rate(sampleRate); } -// Sets specified sample rate and clock rate in Multi_Buffer -blargg_err_t Gb_Apu_Buffer::set_sample_rate(long sample_rate, long clock_rate) { - Gb_Apu_Buffer::output(m_buf.center(), m_buf.left(), m_buf.right()); - m_buf.clock_rate(clock_rate); - return m_buf.set_sample_rate(sample_rate); -} - -// Wrap Multi_Buffer::samples_avail() -long Gb_Apu_Buffer::samples_avail() const { +// Wrap Stereo_Buffer::samples_avail() +long GbApuWrapper::samplesAvail() const +{ return m_buf.samples_avail(); } -// Wrap Multi_Buffer::read_samples(...) -long Gb_Apu_Buffer::read_samples(sample_t* out, long count) { +// Wrap Stereo_Buffer::read_samples(...) +long GbApuWrapper::readSamples(blip_sample_t* out, long count) +{ return m_buf.read_samples(out, count); } -void Gb_Apu_Buffer::bass_freq(int freq) { +// Wrap Stereo_Buffer::bass_freq(...) +void GbApuWrapper::bassFreq(int freq) +{ m_buf.bass_freq(freq); } +void GbApuWrapper::endFrame(blip_time_t endTime) +{ + m_time = 0; + Gb_Apu::end_frame(endTime); + m_buf.end_frame(endTime); +} + } // namespace lmms diff --git a/plugins/FreeBoy/Gb_Apu_Buffer.h b/plugins/FreeBoy/GbApuWrapper.h similarity index 59% rename from plugins/FreeBoy/Gb_Apu_Buffer.h rename to plugins/FreeBoy/GbApuWrapper.h index 760e0920d..493a28731 100644 --- a/plugins/FreeBoy/Gb_Apu_Buffer.h +++ b/plugins/FreeBoy/GbApuWrapper.h @@ -1,7 +1,7 @@ /* - * Gb_Apu_Buffer.cpp - Gb_Apu subclass which allows direct buffer access + * GbApuWrapper.h - Gb_Apu subclass which allows direct buffer access * Copyright (c) 2017 Tres Finocchiaro - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -20,8 +20,9 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef GB_APU_BUFFER_H -#define GB_APU_BUFFER_H + +#ifndef LMMS_GB_APU_WRAPPER_H +#define LMMS_GB_APU_WRAPPER_H #include "Gb_Apu.h" #include "Multi_Buffer.h" @@ -31,25 +32,30 @@ namespace lmms { -class Gb_Apu_Buffer : public Gb_Apu { +class GbApuWrapper : private Gb_Apu +{ MM_OPERATORS public: - Gb_Apu_Buffer() = default; - ~Gb_Apu_Buffer() = default; + GbApuWrapper() = default; + ~GbApuWrapper() = default; - void end_frame(blip_time_t); + blargg_err_t setSampleRate(long sampleRate, long clockRate); + void writeRegister(unsigned addr, int data) { Gb_Apu::write_register(fakeClock(), addr, data); } + long samplesAvail() const; + long readSamples(blip_sample_t* out, long count); + void trebleEq(const blip_eq_t& eq) { Gb_Apu::treble_eq(eq); } + void bassFreq(int freq); + void endFrame(blip_time_t endTime); - blargg_err_t set_sample_rate(long sample_rate, long clock_rate); - long samples_avail() const; - using sample_t = blip_sample_t; - long read_samples(sample_t* out, long count); - void bass_freq(int freq); private: Stereo_Buffer m_buf; + + // Fake CPU timing + blip_time_t fakeClock() { return m_time += 4; } + blip_time_t m_time = 0; }; } // namespace lmms -#endif - +#endif // LMMS_GB_APU_WRAPPER_H