From 839813d0f195d00146ed7d76b5f4cec7c81e8479 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 1 Jun 2016 08:29:36 +1000 Subject: [PATCH] Update to v098r13 release. byuu says: Changelog: - nall/dsp returns with new iir/biquad.hpp and resampler/cubic.hpp files - nall/queue.hpp added (simple ring buffer ... nall/vector wouldn't cause too many moves with FIFO) - audio streams now only buffer 20ms; so even if multiple audio streams desync, latency can never exceed 20ms - replaced blackman windwed sinc FIR hermite audio filter with transposed direct form II biquadratic sixth-order IIR butterworth filter (better attenuation of frequencies above 20KHz, faster, no need for decimation, less code) - put in experimental eight-tap echo filter (a lot better than what I had before, but still rather weak) - substantial cleanups to the SuperFX GSU processor core (slightly faster, 479KB->100KB object file, 42.7KB->33.4KB source code size, way less code duplication) We'll definitely want to test the whole SuperFX library (not many games) just to make sure there's no regressions caused by this one. Not sure what I want to do with audio processing effects yet. I've always really wanted lots of fun controls to customize audio, and now finally with this new biquad filter, I can finally start implementing real effects. For instance, an equalizer wouldn't be too complicated anymore. The new reverb effect is still a poor man's version. I need to find human readable source for implementing a comb-filter properly. I'm pretty sure I can already treat nall::queue as an all-pass filter since all that does is phase shift (fancy audio term for "delay audio"). What's really going to be hard is figuring out how to expose user-friendly settings for controlling it. It looks like you need a bunch of coprime coefficients, and I don't think casual users are going to be able to hand-enter coprime values to get the echo effect they want. I uh ... don't even know how to calculate coprime values dynamically right now >_> But we're going to have to, as they are correlated to the output sampling rate. We'll definitely want to make some audio profiles so that users can quickly select pre-configured themes that sound nice, but expose the underlying coefficients so that they can tweak stuff to their liking. This isn't just about higan, this is about me trying to learn digital signal processing, so please don't be too upset about feature creep or anything on this. Anyway ... I'm having some difficulties with my audio right now. When the reverb effect is enabled, there's a bunch of static on system reset for just a moment. But this should not be possible. nall::queue is initializing all previous reverb sample elements to 0.0. I don't understand where static is coming in from. Further, we have the same issue with both the windowed sinc and the biquad filters ... a bit of a popping sound when starting a game. Any help tracking this down would be appreciated. There's also one really annoying issue ... I can't seem to do reverb or volume adjustments with normalized samples. If I say "volume *= 0.5" in higan/audio/audio.cpp line 68, it doesn't just halve the volume, it adds a whole bunch of distortion. This makes absolutely zero sense to me. The sample values are between 0.0 (mute) and 1.0 (full volume) here, so multiplying a double by 0.5 shouldn't cause distortion. So right now, I'm doing these adjustments with less precision after denormalizing back to int16. Anyone ever see something like that? :/ --- higan/audio/audio.cpp | 44 +- higan/audio/audio.hpp | 33 +- higan/audio/stream.cpp | 128 +--- higan/emulator/emulator.hpp | 2 +- higan/processor/gsu/gsu.cpp | 2 +- higan/processor/gsu/gsu.hpp | 92 +-- higan/processor/gsu/instructions.cpp | 609 +++++------------- higan/processor/gsu/switch.cpp | 94 +++ higan/processor/gsu/table.cpp | 266 -------- higan/sfc/coprocessor/superfx/superfx.cpp | 5 +- .../configuration/configuration.cpp | 3 +- higan/target-tomoko/program/utility.cpp | 7 +- higan/target-tomoko/settings/audio.cpp | 14 +- higan/target-tomoko/settings/settings.hpp | 9 +- nall/dsp/iir/biquad.hpp | 146 +++++ nall/dsp/resampler/cubic.hpp | 56 ++ nall/nall.hpp | 1 + nall/queue.hpp | 101 +++ 18 files changed, 657 insertions(+), 955 deletions(-) create mode 100644 higan/processor/gsu/switch.cpp delete mode 100644 higan/processor/gsu/table.cpp create mode 100644 nall/dsp/iir/biquad.hpp create mode 100644 nall/dsp/resampler/cubic.hpp create mode 100644 nall/queue.hpp diff --git a/higan/audio/audio.cpp b/higan/audio/audio.cpp index 223b8ae9..cc441d0b 100644 --- a/higan/audio/audio.cpp +++ b/higan/audio/audio.cpp @@ -7,7 +7,18 @@ Audio audio; auto Audio::reset() -> void { streams.reset(); - setReverbDelay(reverbDelay); + reverb.reset(); + reverb.resize(2); + for(auto c : range(2)) { + reverb[c].resize(7); + reverb[c][0].resize(1229); + reverb[c][1].resize(1559); + reverb[c][2].resize(1907); + reverb[c][3].resize(4057); + reverb[c][4].resize(8117); + reverb[c][5].resize(8311); + reverb[c][6].resize(9931); + } } auto Audio::setInterface(Interface* interface) -> void { @@ -27,16 +38,8 @@ auto Audio::setBalance(double balance) -> void { this->balance = balance; } -auto Audio::setReverbDelay(uint reverbDelay) -> void { - this->reverbDelay = reverbDelay; - reverbLeft.resize(frequency * reverbDelay / 1000.0); - reverbRight.resize(frequency * reverbDelay / 1000.0); - memory::fill(reverbLeft.data(), reverbLeft.size() * sizeof(int16)); - memory::fill(reverbRight.data(), reverbRight.size() * sizeof(int16)); -} - -auto Audio::setReverbLevel(double reverbLevel) -> void { - this->reverbLevel = reverbLevel; +auto Audio::setReverb(bool enabled) -> void { + this->reverbEnable = enabled; } auto Audio::createStream(uint channels, double frequency) -> shared_pointer { @@ -70,16 +73,21 @@ auto Audio::poll() -> void { int ileft = (left * 65535.0) - 32768.0; int iright = (right * 65535.0) - 32768.0; + if(reverbEnable) { + ileft *= 0.125; + for(auto n : range(7)) ileft += 0.125 * reverb[0][n].last(); + for(auto n : range(7)) reverb[0][n].write(ileft); + ileft *= 8.000; + + iright *= 0.125; + for(auto n : range(7)) iright += 0.125 * reverb[1][n].last(); + for(auto n : range(7)) reverb[1][n].write(iright); + iright *= 8.000; + } + ileft *= volume; iright *= volume; - if(reverbDelay) { - reverbLeft.append(ileft); - reverbRight.append(iright); - ileft += reverbLeft.takeLeft() * reverbLevel; - iright += reverbRight.takeLeft() * reverbLevel; - } - interface->audioSample(sclamp<16>(ileft), sclamp<16>(iright)); } } diff --git a/higan/audio/audio.hpp b/higan/audio/audio.hpp index ccb33d9b..eb22049c 100644 --- a/higan/audio/audio.hpp +++ b/higan/audio/audio.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + namespace Emulator { struct Interface; @@ -13,8 +16,7 @@ struct Audio { auto setFrequency(double frequency) -> void; auto setVolume(double volume) -> void; auto setBalance(double balance) -> void; - auto setReverbDelay(uint milliseconds) -> void; - auto setReverbLevel(double level) -> void; + auto setReverb(bool enabled) -> void; auto createStream(uint channels, double frequency) -> shared_pointer; @@ -26,10 +28,9 @@ private: double frequency = 0.0; double volume = 1.0; double balance = 0.0; - uint reverbDelay = 0; //0 = disabled - double reverbLevel = 0.0; - vector reverbLeft; - vector reverbRight; + + bool reverbEnable = false; + vector>> reverb; friend class Stream; }; @@ -55,23 +56,9 @@ private: double outputFrequency = 0.0; double cutoffFrequency = 0.0; - vector taps; - - uint decimationRate = 0; - uint decimationOffset = 0; - - vector> input; - uint inputOffset = 0; - - double resamplerFrequency = 0.0; - double resamplerFraction = 0.0; - double resamplerStep = 0.0; - vector> queue; - - vector> output; - uint outputs = 0; - uint outputReadOffset = 0; - uint outputWriteOffset = 0; + const uint iirPasses = 3; //6th-order filter + vector> iir; + vector resampler; friend class Audio; }; diff --git a/higan/audio/stream.cpp b/higan/audio/stream.cpp index e5f20aed..3363126f 100644 --- a/higan/audio/stream.cpp +++ b/higan/audio/stream.cpp @@ -1,137 +1,49 @@ -//Emulator::Stream implements advanced audio resampling -//First, a lowpass sinc filter is used (with a Blackman window to reduce rippling) in order to remove aliasing -//Second, a decimator is used to reduce the CPU overhead of the sinc function -//Finally, a hermite resampler is used to resample to the exact requested output frequency -//Note: when the cutoff frequency is >= 0.5; only the hermite resampler is used - Stream::Stream(uint channels, double inputFrequency) : channels(channels), inputFrequency(inputFrequency) { } auto Stream::reset() -> void { - taps.reset(); - input.reset(); - queue.reset(); - output.reset(); + iir.reset(); + resampler.reset(); } auto Stream::setFrequency(double outputFrequency_) -> void { reset(); - const double pi = 3.141592; - auto sinc = [&](double x) -> double { - if(x == 0) return 1; - return sin(pi * x) / (pi * x); - }; - outputFrequency = outputFrequency_; cutoffFrequency = outputFrequency / inputFrequency; - if(cutoffFrequency < 0.5) { - double transitionBandwidth = 0.008; //lower = higher quality; more taps (slower) - taps.resize((uint)ceil(4.0 / transitionBandwidth) | 1); + iir.resize(channels); - double sum = 0.0; - for(uint t : range(taps)) { - //sinc filter - double s = sinc(2.0 * cutoffFrequency * (t - (taps.size() - 1) / 2.0)); - - //blackman window - double b = 0.42 - 0.5 * cos(2.0 * pi * t / (taps.size() - 1)) + 0.08 * cos(4.0 * pi * t / (taps.size() - 1)); - - taps[t] = s * b; - sum += taps[t]; + if(cutoffFrequency <= 0.5) { + for(auto c : range(channels)) { + iir[c].resize(iirPasses); + for(auto p : range(iirPasses)) { + //attenuates frequencies that exceed the limits of human hearing (20KHz) to prevent aliasing + iir[c][p].reset(DSP::IIR::Biquad::Type::LowPass, 20000.0 / inputFrequency); + } } - - //normalize so that the sum of all coefficients is 1.0 - for(auto& tap : taps) tap /= sum; - } else { - taps.resize(1); - taps[0] = 1.0; } - decimationRate = max(1, (uint)floor(inputFrequency / outputFrequency)); - decimationOffset = 0; - - input.resize(channels); - for(auto c : range(channels)) input[c].resize(taps.size() * 2); - inputOffset = 0; - - resamplerFrequency = inputFrequency / decimationRate; - resamplerFraction = 0.0; - resamplerStep = resamplerFrequency / outputFrequency; - queue.resize(channels); - for(auto c : range(channels)) queue[c].resize(4); - - output.resize(channels); - outputs = inputFrequency * 0.02; - for(auto c : range(channels)) output[c].resize(outputs); - outputReadOffset = 0; - outputWriteOffset = 0; + resampler.resize(channels); + for(auto c : range(channels)) { + resampler[c].reset(inputFrequency, outputFrequency); + } } auto Stream::pending() const -> bool { - return outputReadOffset != outputWriteOffset; + return resampler && resampler[0].pending(); } auto Stream::read(double* samples) -> void { - for(auto c : range(channels)) { - samples[c] = output[c][outputReadOffset]; - } + for(auto c : range(channels)) samples[c] = resampler[c].read(); if(channels == 1) samples[1] = samples[0]; //monaural->stereo hack - if(++outputReadOffset >= outputs) outputReadOffset = 0; } auto Stream::write(int16* samples) -> void { - inputOffset = !inputOffset ? taps.size() - 1 : inputOffset - 1; for(auto c : range(channels)) { - auto sample = (samples[c] + 32768.0) / 65535.0; //normalize - input[c][inputOffset] = input[c][inputOffset + taps.size()] = sample; + double sample = (samples[c] + 32768.0) / 65535.0; //normalize + for(auto& p : iir[c]) sample = p.process(sample); + resampler[c].write(sample); } - if(++decimationOffset >= decimationRate) { - decimationOffset = 0; - - for(auto c : range(channels)) { - double sample = 0.0; - for(auto t : range(taps)) sample += input[c][inputOffset + t] * taps[t]; - - auto& q = queue[c]; - q[0] = q[1]; - q[1] = q[2]; - q[2] = q[3]; - q[3] = sample; - } - - //4-tap hermite - auto& mu = resamplerFraction; - while(mu <= 1.0) { - for(auto c : range(channels)) { - auto& q = queue[c]; - - const double tension = 0.0; //-1 = low, 0 = normal, +1 = high - const double bias = 0.0; //-1 = left, 0 = even, +1 = right - - double mu1 = mu; - double mu2 = mu * mu; - double mu3 = mu * mu * mu; - - double m0 = (q[1] - q[0]) * (1.0 + bias) * (1.0 - tension) / 2.0 - + (q[2] - q[1]) * (1.0 - bias) * (1.0 - tension) / 2.0; - double m1 = (q[2] - q[1]) * (1.0 + bias) * (1.0 - tension) / 2.0 - + (q[3] - q[2]) * (1.0 - bias) * (1.0 - tension) / 2.0; - - double a0 = +2 * mu3 - 3 * mu2 + 1; - double a1 = mu3 - 2 * mu2 + mu1; - double a2 = mu3 - mu2; - double a3 = -2 * mu3 + 3 * mu2; - - output[c][outputWriteOffset] = (a0 * q[1]) + (a1 * m0) + (a2 * m1) + (a3 * q[2]); - } - - if(++outputWriteOffset >= outputs) outputWriteOffset = 0; - mu += resamplerStep; - audio.poll(); - } - - mu -= 1.0; - } + audio.poll(); } diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 09704f2f..be02c33e 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -9,7 +9,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "098.12"; + static const string Version = "098.13"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/processor/gsu/gsu.cpp b/higan/processor/gsu/gsu.cpp index 03bbb0d3..6210ba79 100644 --- a/higan/processor/gsu/gsu.cpp +++ b/higan/processor/gsu/gsu.cpp @@ -8,7 +8,7 @@ namespace Processor { #include "instructions.cpp" -#include "table.cpp" +#include "switch.cpp" #include "serialization.cpp" #include "disassembler.cpp" diff --git a/higan/processor/gsu/gsu.hpp b/higan/processor/gsu/gsu.hpp index 3c517b78..47e7601c 100644 --- a/higan/processor/gsu/gsu.hpp +++ b/higan/processor/gsu/gsu.hpp @@ -5,7 +5,7 @@ namespace Processor { struct GSU { #include "registers.hpp" - virtual auto step(unsigned clocks) -> void = 0; + virtual auto step(uint clocks) -> void = 0; virtual auto stop() -> void = 0; virtual auto color(uint8 source) -> uint8 = 0; @@ -28,91 +28,49 @@ struct GSU { auto reset() -> void; //instructions.cpp - template auto op_adc_i(); - template auto op_adc_r(); - template auto op_add_i(); - template auto op_add_r(); + auto op_add_adc(uint n); auto op_alt1(); auto op_alt2(); auto op_alt3(); - template auto op_and_i(); - template auto op_and_r(); - auto op_asr(); - auto op_bge(); - auto op_bcc(); - auto op_bcs(); - auto op_beq(); - template auto op_bic_i(); - template auto op_bic_r(); - auto op_blt(); - auto op_bmi(); - auto op_bne(); - auto op_bpl(); - auto op_bra(); - auto op_bvc(); - auto op_bvs(); + auto op_and_bic(uint n); + auto op_asr_div2(); + auto op_branch(bool c); auto op_cache(); - auto op_cmode(); - template auto op_cmp_r(); - auto op_color(); - template auto op_dec_r(); - auto op_div2(); - auto op_fmult(); - template auto op_from_r(); + auto op_color_cmode(); + auto op_dec(uint n); + auto op_fmult_lmult(); + auto op_from_moves(uint n); auto op_getb(); - auto op_getbl(); - auto op_getbh(); - auto op_getbs(); - auto op_getc(); + auto op_getc_ramb_romb(); auto op_hib(); - template auto op_ibt_r(); - template auto op_inc_r(); - template auto op_iwt_r(); - template auto op_jmp_r(); - template auto op_ldb_ir(); - template auto op_ldw_ir(); - template auto op_link(); - template auto op_ljmp_r(); - template auto op_lm_r(); - template auto op_lms_r(); - auto op_lmult(); + auto op_ibt_lms_sms(uint n); + auto op_inc(uint n); + auto op_iwt_lm_sm(uint n); + auto op_jmp_ljmp(uint n); + auto op_link(uint n); + auto op_load(uint n); auto op_lob(); auto op_loop(); auto op_lsr(); auto op_merge(); - template auto op_mult_i(); - template auto op_mult_r(); + auto op_mult_umult(uint n); auto op_nop(); auto op_not(); - template auto op_or_i(); - template auto op_or_r(); - auto op_plot(); - auto op_ramb(); + auto op_or_xor(uint n); + auto op_plot_rpix(); auto op_rol(); - auto op_romb(); auto op_ror(); - auto op_rpix(); - template auto op_sbc_r(); auto op_sbk(); auto op_sex(); - template auto op_sm_r(); - template auto op_sms_r(); - template auto op_stb_ir(); + auto op_store(uint n); auto op_stop(); - template auto op_stw_ir(); - template auto op_sub_i(); - template auto op_sub_r(); + auto op_sub_sbc_cmp(uint n); auto op_swap(); - template auto op_to_r(); - template auto op_umult_i(); - template auto op_umult_r(); - template auto op_with_r(); - template auto op_xor_i(); - template auto op_xor_r(); + auto op_to_move(uint n); + auto op_with(uint n); - //table.cpp - auto (GSU::*opcode_table[1024])() -> void; - auto initialize_opcode_table() -> void; + //switch.cpp + auto instruction(uint8 opcode) -> void; //serialization.cpp auto serialize(serializer&) -> void; diff --git a/higan/processor/gsu/instructions.cpp b/higan/processor/gsu/instructions.cpp index 4a525ea8..caa5ba8d 100644 --- a/higan/processor/gsu/instructions.cpp +++ b/higan/processor/gsu/instructions.cpp @@ -4,9 +4,8 @@ auto GSU::op_stop() { regs.sfr.irq = 1; stop(); } - regs.sfr.g = 0; - regs.pipeline = 0x01; + regs.pipeline = 0x01; //nop regs.reset(); } @@ -44,75 +43,25 @@ auto GSU::op_rol() { } //$05 bra e -auto GSU::op_bra() { - regs.r[15] += (int8)pipe(); -} - //$06 blt e -auto GSU::op_blt() { - int e = (int8)pipe(); - if((regs.sfr.s ^ regs.sfr.ov) == 0) regs.r[15] += e; -} - //$07 bge e -auto GSU::op_bge() { - int e = (int8)pipe(); - if((regs.sfr.s ^ regs.sfr.ov) == 1) regs.r[15] += e; -} - //$08 bne e -auto GSU::op_bne() { - int e = (int8)pipe(); - if(regs.sfr.z == 0) regs.r[15] += e; -} - //$09 beq e -auto GSU::op_beq() { - int e = (int8)pipe(); - if(regs.sfr.z == 1) regs.r[15] += e; -} - //$0a bpl e -auto GSU::op_bpl() { - int e = (int8)pipe(); - if(regs.sfr.s == 0) regs.r[15] += e; -} - //$0b bmi e -auto GSU::op_bmi() { - int e = (int8)pipe(); - if(regs.sfr.s == 1) regs.r[15] += e; -} - //$0c bcc e -auto GSU::op_bcc() { - int e = (int8)pipe(); - if(regs.sfr.cy == 0) regs.r[15] += e; -} - //$0d bcs e -auto GSU::op_bcs() { - int e = (int8)pipe(); - if(regs.sfr.cy == 1) regs.r[15] += e; -} - //$0e bvc e -auto GSU::op_bvc() { - int e = (int8)pipe(); - if(regs.sfr.ov == 0) regs.r[15] += e; -} - //$0f bvs e -auto GSU::op_bvs() { - int e = (int8)pipe(); - if(regs.sfr.ov == 1) regs.r[15] += e; +auto GSU::op_branch(bool c) { + auto d = (int8)pipe(); + if(c) regs.r[15] += d; } -//$10-1f(b0): to rN -//$10-1f(b1): move rN -template -auto GSU::op_to_r() { - if(regs.sfr.b == 0) { +//$10-1f(b0) to rN +//$10-1f(b1) move rN +auto GSU::op_to_move(uint n) { + if(!regs.sfr.b) { regs.dreg = n; } else { regs.r[n] = regs.sr(); @@ -120,28 +69,19 @@ auto GSU::op_to_r() { } } -//$20-2f: with rN -template -auto GSU::op_with_r() { +//$20-2f with rN +auto GSU::op_with(uint n) { regs.sreg = n; regs.dreg = n; regs.sfr.b = 1; } -//$30-3b(alt0): stw (rN) -template -auto GSU::op_stw_ir() { - regs.ramaddr = regs.r[n]; - rambuffer_write(regs.ramaddr ^ 0, regs.sr() >> 0); - rambuffer_write(regs.ramaddr ^ 1, regs.sr() >> 8); - regs.reset(); -} - -//$30-3b(alt1): stb (rN) -template -auto GSU::op_stb_ir() { +//$30-3b(alt0) stw (rN) +//$30-3b(alt1) stb (rN) +auto GSU::op_store(uint n) { regs.ramaddr = regs.r[n]; rambuffer_write(regs.ramaddr, regs.sr()); + if(!regs.sfr.alt1) rambuffer_write(regs.ramaddr ^ 1, regs.sr() >> 8); regs.reset(); } @@ -173,41 +113,30 @@ auto GSU::op_alt3() { regs.sfr.alt2 = 1; } -//$40-4b(alt0): ldw (rN) -template -auto GSU::op_ldw_ir() { - regs.ramaddr = regs.r[n]; - uint16_t data; - data = rambuffer_read(regs.ramaddr ^ 0) << 0; - data |= rambuffer_read(regs.ramaddr ^ 1) << 8; - regs.dr() = data; - regs.reset(); -} - -//$40-4b(alt1): ldb (rN) -template -auto GSU::op_ldb_ir() { +//$40-4b(alt0) ldw (rN) +//$40-4b(alt1) ldb (rN) +auto GSU::op_load(uint n) { regs.ramaddr = regs.r[n]; regs.dr() = rambuffer_read(regs.ramaddr); + if(!regs.sfr.alt1) regs.dr() |= rambuffer_read(regs.ramaddr ^ 1) << 8; regs.reset(); } -//$4c(alt0): plot -auto GSU::op_plot() { - plot(regs.r[1], regs.r[2]); - regs.r[1]++; +//$4c(alt0) plot +//$4c(alt1) rpix +auto GSU::op_plot_rpix() { + if(!regs.sfr.alt1) { + plot(regs.r[1], regs.r[2]); + regs.r[1]++; + } else { + regs.dr() = rpix(regs.r[1], regs.r[2]); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + } regs.reset(); } -//$4c(alt1): rpix -auto GSU::op_rpix() { - regs.dr() = rpix(regs.r[1], regs.r[2]); - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); -} - -//$4d: swap +//$4d swap auto GSU::op_swap() { regs.dr() = (regs.sr() >> 8) | (regs.sr() << 8); regs.sfr.s = (regs.dr() & 0x8000); @@ -215,19 +144,18 @@ auto GSU::op_swap() { regs.reset(); } -//$4e(alt0): color -auto GSU::op_color() { - regs.colr = color(regs.sr()); +//$4e(alt0) color +//$4e(alt1) cmode +auto GSU::op_color_cmode() { + if(!regs.sfr.alt1) { + regs.colr = color(regs.sr()); + } else { + regs.por = regs.sr(); + } regs.reset(); } -//$4e(alt1): cmode -auto GSU::op_cmode() { - regs.por = regs.sr(); - regs.reset(); -} - -//$4f: not +//$4f not auto GSU::op_not() { regs.dr() = ~regs.sr(); regs.sfr.s = (regs.dr() & 0x8000); @@ -235,102 +163,37 @@ auto GSU::op_not() { regs.reset(); } -//$50-5f(alt0): add rN -template -auto GSU::op_add_r() { - int r = regs.sr() + regs.r[n]; - regs.sfr.ov = ~(regs.sr() ^ regs.r[n]) & (regs.r[n] ^ r) & 0x8000; - regs.sfr.s = (r & 0x8000); - regs.sfr.cy = (r >= 0x10000); - regs.sfr.z = ((uint16_t)r == 0); - regs.dr() = r; - regs.reset(); -} - -//$50-5f(alt1): adc rN -template -auto GSU::op_adc_r() { - int r = regs.sr() + regs.r[n] + regs.sfr.cy; - regs.sfr.ov = ~(regs.sr() ^ regs.r[n]) & (regs.r[n] ^ r) & 0x8000; - regs.sfr.s = (r & 0x8000); - regs.sfr.cy = (r >= 0x10000); - regs.sfr.z = ((uint16_t)r == 0); - regs.dr() = r; - regs.reset(); -} - -//$50-5f(alt2): add #N -template -auto GSU::op_add_i() { - int r = regs.sr() + n; +//$50-5f(alt0) add rN +//$50-5f(alt1) adc rN +//$50-5f(alt2) add #N +//$50-5f(alt3) adc #N +auto GSU::op_add_adc(uint n) { + if(!regs.sfr.alt2) n = regs.r[n]; + int r = regs.sr() + n + (regs.sfr.alt1 ? regs.sfr.cy : 0); regs.sfr.ov = ~(regs.sr() ^ n) & (n ^ r) & 0x8000; regs.sfr.s = (r & 0x8000); regs.sfr.cy = (r >= 0x10000); - regs.sfr.z = ((uint16_t)r == 0); + regs.sfr.z = ((uint16)r == 0); regs.dr() = r; regs.reset(); } -//$50-5f(alt3): adc #N -template -auto GSU::op_adc_i() { - int r = regs.sr() + n + regs.sfr.cy; - regs.sfr.ov = ~(regs.sr() ^ n) & (n ^ r) & 0x8000; - regs.sfr.s = (r & 0x8000); - regs.sfr.cy = (r >= 0x10000); - regs.sfr.z = ((uint16_t)r == 0); - regs.dr() = r; - regs.reset(); -} - -//$60-6f(alt0): sub rN -template -auto GSU::op_sub_r() { - int r = regs.sr() - regs.r[n]; - regs.sfr.ov = (regs.sr() ^ regs.r[n]) & (regs.sr() ^ r) & 0x8000; - regs.sfr.s = (r & 0x8000); - regs.sfr.cy = (r >= 0); - regs.sfr.z = ((uint16_t)r == 0); - regs.dr() = r; - regs.reset(); -} - -//$60-6f(alt1): sbc rN -template -auto GSU::op_sbc_r() { - int r = regs.sr() - regs.r[n] - !regs.sfr.cy; - regs.sfr.ov = (regs.sr() ^ regs.r[n]) & (regs.sr() ^ r) & 0x8000; - regs.sfr.s = (r & 0x8000); - regs.sfr.cy = (r >= 0); - regs.sfr.z = ((uint16_t)r == 0); - regs.dr() = r; - regs.reset(); -} - -//$60-6f(alt2): sub #N -template -auto GSU::op_sub_i() { - int r = regs.sr() - n; +//$60-6f(alt0) sub rN +//$60-6f(alt1) sbc rN +//$60-6f(alt2) sub #N +//$60-6f(alt3) cmp rN +auto GSU::op_sub_sbc_cmp(uint n) { + if(!regs.sfr.alt2 || regs.sfr.alt1) n = regs.r[n]; + int r = regs.sr() - n - (!regs.sfr.alt2 && regs.sfr.alt1 ? !regs.sfr.cy : 0); regs.sfr.ov = (regs.sr() ^ n) & (regs.sr() ^ r) & 0x8000; regs.sfr.s = (r & 0x8000); regs.sfr.cy = (r >= 0); - regs.sfr.z = ((uint16_t)r == 0); - regs.dr() = r; + regs.sfr.z = ((uint16)r == 0); + if(!regs.sfr.alt2 || !regs.sfr.alt1) regs.dr() = r; regs.reset(); } -//$60-6f(alt3): cmp rN -template -auto GSU::op_cmp_r() { - int r = regs.sr() - regs.r[n]; - regs.sfr.ov = (regs.sr() ^ regs.r[n]) & (regs.sr() ^ r) & 0x8000; - regs.sfr.s = (r & 0x8000); - regs.sfr.cy = (r >= 0); - regs.sfr.z = ((uint16_t)r == 0); - regs.reset(); -} - -//$70: merge +//$70 merge auto GSU::op_merge() { regs.dr() = (regs.r[7] & 0xff00) | (regs.r[8] >> 8); regs.sfr.ov = (regs.dr() & 0xc0c0); @@ -340,97 +203,45 @@ auto GSU::op_merge() { regs.reset(); } -//$71-7f(alt0): and rN -template -auto GSU::op_and_r() { - regs.dr() = regs.sr() & regs.r[n]; +//$71-7f(alt0) and rN +//$71-7f(alt1) bic rN +//$71-7f(alt2) and #N +//$71-7f(alt3) bic #N +auto GSU::op_and_bic(uint n) { + if(!regs.sfr.alt2) n = regs.r[n]; + regs.dr() = regs.sr() & (regs.sfr.alt1 ? ~n : n); regs.sfr.s = (regs.dr() & 0x8000); regs.sfr.z = (regs.dr() == 0); regs.reset(); } -//$71-7f(alt1): bic rN -template -auto GSU::op_bic_r() { - regs.dr() = regs.sr() & ~regs.r[n]; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); -} - -//$71-7f(alt2): and #N -template -auto GSU::op_and_i() { - regs.dr() = regs.sr() & n; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); -} - -//$71-7f(alt3): bic #N -template -auto GSU::op_bic_i() { - regs.dr() = regs.sr() & ~n; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); -} - -//$80-8f(alt0): mult rN -template -auto GSU::op_mult_r() { - regs.dr() = (int8)regs.sr() * (int8)regs.r[n]; +//$80-8f(alt0) mult rN +//$80-8f(alt1) umult rN +//$80-8f(alt2) mult #N +//$80-8f(alt3) umult #N +auto GSU::op_mult_umult(uint n) { + if(!regs.sfr.alt2) n = regs.r[n]; + regs.dr() = (!regs.sfr.alt1 ? ((int8)regs.sr() * (int8)n) : ((uint8)regs.sr() * (uint8)n)); regs.sfr.s = (regs.dr() & 0x8000); regs.sfr.z = (regs.dr() == 0); regs.reset(); if(!regs.cfgr.ms0) step(regs.clsr ? 1 : 2); } -//$80-8f(alt1): umult rN -template -auto GSU::op_umult_r() { - regs.dr() = (uint8)regs.sr() * (uint8)regs.r[n]; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); - if(!regs.cfgr.ms0) step(regs.clsr ? 1 : 2); -} - -//$80-8f(alt2): mult #N -template -auto GSU::op_mult_i() { - regs.dr() = (int8)regs.sr() * (int8)n; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); - if(!regs.cfgr.ms0) step(regs.clsr ? 1 : 2); -} - -//$80-8f(alt3): umult #N -template -auto GSU::op_umult_i() { - regs.dr() = (uint8)regs.sr() * (uint8)n; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); - if(!regs.cfgr.ms0) step(regs.clsr ? 1 : 2); -} - -//$90: sbk +//$90 sbk auto GSU::op_sbk() { rambuffer_write(regs.ramaddr ^ 0, regs.sr() >> 0); rambuffer_write(regs.ramaddr ^ 1, regs.sr() >> 8); regs.reset(); } -//$91-94: link #N -template -auto GSU::op_link() { +//$91-94 link #N +auto GSU::op_link(uint n) { regs.r[11] = regs.r[15] + n; regs.reset(); } -//$95: sex +//$95 sex auto GSU::op_sex() { regs.dr() = (int8)regs.sr(); regs.sfr.s = (regs.dr() & 0x8000); @@ -438,25 +249,17 @@ auto GSU::op_sex() { regs.reset(); } -//$96(alt0): asr -auto GSU::op_asr() { +//$96(alt0) asr +//$96(alt1) div2 +auto GSU::op_asr_div2() { regs.sfr.cy = (regs.sr() & 1); - regs.dr() = (int16_t)regs.sr() >> 1; + regs.dr() = ((int16)regs.sr() >> 1) + (regs.sfr.alt1 ? ((regs.sr() + 1) >> 16) : 0); regs.sfr.s = (regs.dr() & 0x8000); regs.sfr.z = (regs.dr() == 0); regs.reset(); } -//$96(alt1): div2 -auto GSU::op_div2() { - regs.sfr.cy = (regs.sr() & 1); - regs.dr() = ((int16_t)regs.sr() >> 1) + ((regs.sr() + 1) >> 16); - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); -} - -//$97: ror +//$97 ror auto GSU::op_ror() { bool carry = (regs.sr() & 1); regs.dr() = (regs.sfr.cy << 15) | (regs.sr() >> 1); @@ -466,24 +269,21 @@ auto GSU::op_ror() { regs.reset(); } -//$98-9d(alt0): jmp rN -template -auto GSU::op_jmp_r() { - regs.r[15] = regs.r[n]; +//$98-9d(alt0) jmp rN +//$98-9d(alt1) ljmp rN +auto GSU::op_jmp_ljmp(uint n) { + if(!regs.sfr.alt1) { + regs.r[15] = regs.r[n]; + } else { + regs.pbr = regs.r[n] & 0x7f; + regs.r[15] = regs.sr(); + regs.cbr = regs.r[15] & 0xfff0; + cache_flush(); + } regs.reset(); } -//$98-9d(alt1): ljmp rN -template -auto GSU::op_ljmp_r() { - regs.pbr = regs.r[n] & 0x7f; - regs.r[15] = regs.sr(); - regs.cbr = regs.r[15] & 0xfff0; - cache_flush(); - regs.reset(); -} - -//$9e: lob +//$9e lob auto GSU::op_lob() { regs.dr() = regs.sr() & 0xff; regs.sfr.s = (regs.dr() & 0x80); @@ -491,9 +291,11 @@ auto GSU::op_lob() { regs.reset(); } -//$9f(alt0): fmult -auto GSU::op_fmult() { - uint32_t result = (int16_t)regs.sr() * (int16_t)regs.r[6]; +//$9f(alt0) fmult +//$9f(alt1) lmult +auto GSU::op_fmult_lmult() { + uint32 result = (int16)regs.sr() * (int16)regs.r[6]; + if(regs.sfr.alt1) regs.r[4] = result; regs.dr() = result >> 16; regs.sfr.s = (regs.dr() & 0x8000); regs.sfr.cy = (result & 0x8000); @@ -502,50 +304,28 @@ auto GSU::op_fmult() { step((regs.cfgr.ms0 ? 3 : 7) * (regs.clsr ? 1 : 2)); } -//$9f(alt1): lmult -auto GSU::op_lmult() { - uint32_t result = (int16_t)regs.sr() * (int16_t)regs.r[6]; - regs.r[4] = result; - regs.dr() = result >> 16; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.cy = (result & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); - step((regs.cfgr.ms0 ? 3 : 7) * (regs.clsr ? 1 : 2)); -} - -//$a0-af(alt0): ibt rN,#pp -template -auto GSU::op_ibt_r() { - regs.r[n] = (int8)pipe(); +//$a0-af(alt0) ibt rN,#pp +//$a0-af(alt1) lms rN,(yy) +//$a0-af(alt2) sms (yy),rN +auto GSU::op_ibt_lms_sms(uint n) { + if(regs.sfr.alt1) { + regs.ramaddr = pipe() << 1; + uint8 lo = rambuffer_read(regs.ramaddr ^ 0) << 0; + regs.r[n] = rambuffer_read(regs.ramaddr ^ 1) << 8 | lo; + } else if(regs.sfr.alt2) { + regs.ramaddr = pipe() << 1; + rambuffer_write(regs.ramaddr ^ 0, regs.r[n] >> 0); + rambuffer_write(regs.ramaddr ^ 1, regs.r[n] >> 8); + } else { + regs.r[n] = (int8)pipe(); + } regs.reset(); } -//$a0-af(alt1): lms rN,(yy) -template -auto GSU::op_lms_r() { - regs.ramaddr = pipe() << 1; - uint16_t data; - data = rambuffer_read(regs.ramaddr ^ 0) << 0; - data |= rambuffer_read(regs.ramaddr ^ 1) << 8; - regs.r[n] = data; - regs.reset(); -} - -//$a0-af(alt2): sms (yy),rN -template -auto GSU::op_sms_r() { - regs.ramaddr = pipe() << 1; - rambuffer_write(regs.ramaddr ^ 0, regs.r[n] >> 0); - rambuffer_write(regs.ramaddr ^ 1, regs.r[n] >> 8); - regs.reset(); -} - -//$b0-bf(b0): from rN -//$b0-bf(b1): moves rN -template -auto GSU::op_from_r() { - if(regs.sfr.b == 0) { +//$b0-bf(b0) from rN +//$b0-bf(b1) moves rN +auto GSU::op_from_moves(uint n) { + if(!regs.sfr.b) { regs.sreg = n; } else { regs.dr() = regs.r[n]; @@ -556,7 +336,7 @@ auto GSU::op_from_r() { } } -//$c0: hib +//$c0 hib auto GSU::op_hib() { regs.dr() = regs.sr() >> 8; regs.sfr.s = (regs.dr() & 0x80); @@ -564,132 +344,81 @@ auto GSU::op_hib() { regs.reset(); } -//$c1-cf(alt0): or rN -template -auto GSU::op_or_r() { - regs.dr() = regs.sr() | regs.r[n]; +//$c1-cf(alt0) or rN +//$c1-cf(alt1) xor rN +//$c1-cf(alt2) or #N +//$c1-cf(alt3) xor #N +auto GSU::op_or_xor(uint n) { + if(!regs.sfr.alt2) n = regs.r[n]; + regs.dr() = (!regs.sfr.alt1 ? (regs.sr() | n) : (regs.sr() ^ n)); regs.sfr.s = (regs.dr() & 0x8000); regs.sfr.z = (regs.dr() == 0); regs.reset(); } -//$c1-cf(alt1): xor rN -template -auto GSU::op_xor_r() { - regs.dr() = regs.sr() ^ regs.r[n]; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); -} - -//$c1-cf(alt2): or #N -template -auto GSU::op_or_i() { - regs.dr() = regs.sr() | n; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); -} - -//$c1-cf(alt3): xor #N -template -auto GSU::op_xor_i() { - regs.dr() = regs.sr() ^ n; - regs.sfr.s = (regs.dr() & 0x8000); - regs.sfr.z = (regs.dr() == 0); - regs.reset(); -} - -//$d0-de: inc rN -template -auto GSU::op_inc_r() { +//$d0-de inc rN +auto GSU::op_inc(uint n) { regs.r[n]++; regs.sfr.s = (regs.r[n] & 0x8000); regs.sfr.z = (regs.r[n] == 0); regs.reset(); } -//$df(alt0): getc -auto GSU::op_getc() { - regs.colr = color(rombuffer_read()); +//$df(alt0) getc +//$df(alt2) ramb +//$df(alt3) romb +auto GSU::op_getc_ramb_romb() { + if(!regs.sfr.alt2) { + regs.colr = color(rombuffer_read()); + } else if(!regs.sfr.alt1) { + rambuffer_sync(); + regs.rambr = regs.sr() & 0x01; + } else { + rombuffer_sync(); + regs.rombr = regs.sr() & 0x7f; + } regs.reset(); } -//$df(alt2): ramb -auto GSU::op_ramb() { - rambuffer_sync(); - regs.rambr = regs.sr() & 0x01; - regs.reset(); -} - -//$df(alt3): romb -auto GSU::op_romb() { - rombuffer_sync(); - regs.rombr = regs.sr() & 0x7f; - regs.reset(); -} - -//$e0-ee: dec rN -template -auto GSU::op_dec_r() { +//$e0-ee dec rN +auto GSU::op_dec(uint n) { regs.r[n]--; regs.sfr.s = (regs.r[n] & 0x8000); regs.sfr.z = (regs.r[n] == 0); regs.reset(); } -//$ef(alt0): getb +//$ef(alt0) getb +//$ef(alt1) getbh +//$ef(alt2) getbl +//$ef(alt3) getbs auto GSU::op_getb() { - regs.dr() = rombuffer_read(); + switch(regs.sfr.alt2 << 1 | regs.sfr.alt1 << 0) { + case 0: regs.dr() = rombuffer_read(); break; + case 1: regs.dr() = rombuffer_read() << 8 | (uint8)regs.sr(); break; + case 2: regs.dr() = (regs.sr() & 0xff00) | rombuffer_read(); break; + case 3: regs.dr() = (int8)rombuffer_read(); break; + } regs.reset(); } -//$ef(alt1): getbh -auto GSU::op_getbh() { - regs.dr() = (rombuffer_read() << 8) | (regs.sr() & 0x00ff); - regs.reset(); -} - -//$ef(alt2): getbl -auto GSU::op_getbl() { - regs.dr() = (regs.sr() & 0xff00) | (rombuffer_read() << 0); - regs.reset(); -} - -//$ef(alt3): getbs -auto GSU::op_getbs() { - regs.dr() = (int8)rombuffer_read(); - regs.reset(); -} - -//$f0-ff(alt0): iwt rN,#xx -template -auto GSU::op_iwt_r() { - uint16_t data; - data = pipe() << 0; - data |= pipe() << 8; - regs.r[n] = data; - regs.reset(); -} - -//$f0-ff(alt1): lm rN,(xx) -template -auto GSU::op_lm_r() { - regs.ramaddr = pipe() << 0; - regs.ramaddr |= pipe() << 8; - uint16_t data; - data = rambuffer_read(regs.ramaddr ^ 0) << 0; - data |= rambuffer_read(regs.ramaddr ^ 1) << 8; - regs.r[n] = data; - regs.reset(); -} - -//$f0-ff(alt2): sm (xx),rN -template -auto GSU::op_sm_r() { - regs.ramaddr = pipe() << 0; - regs.ramaddr |= pipe() << 8; - rambuffer_write(regs.ramaddr ^ 0, regs.r[n] >> 0); - rambuffer_write(regs.ramaddr ^ 1, regs.r[n] >> 8); +//$f0-ff(alt0) iwt rN,#xx +//$f0-ff(alt1) lm rN,(xx) +//$f0-ff(alt2) sm (xx),rN +auto GSU::op_iwt_lm_sm(uint n) { + if(regs.sfr.alt1) { + regs.ramaddr = pipe() << 0; + regs.ramaddr |= pipe() << 8; + uint8 lo = rambuffer_read(regs.ramaddr ^ 0) << 0; + regs.r[n] = rambuffer_read(regs.ramaddr ^ 1) << 8 | lo; + } else if(regs.sfr.alt2) { + regs.ramaddr = pipe() << 0; + regs.ramaddr |= pipe() << 8; + rambuffer_write(regs.ramaddr ^ 0, regs.r[n] >> 0); + rambuffer_write(regs.ramaddr ^ 1, regs.r[n] >> 8); + } else { + uint8 lo = pipe(); + regs.r[n] = pipe() << 8 | lo; + } regs.reset(); } diff --git a/higan/processor/gsu/switch.cpp b/higan/processor/gsu/switch.cpp new file mode 100644 index 00000000..7aba9a5e --- /dev/null +++ b/higan/processor/gsu/switch.cpp @@ -0,0 +1,94 @@ +auto GSU::instruction(uint8 opcode) -> void { + #define op(id, name, ...) \ + case id: return op_##name(__VA_ARGS__); \ + + #define op4(id, name) \ + case id+ 0: return op_##name((uint4)opcode); \ + case id+ 1: return op_##name((uint4)opcode); \ + case id+ 2: return op_##name((uint4)opcode); \ + case id+ 3: return op_##name((uint4)opcode); \ + + #define op6(id, name) \ + op4(id, name) \ + case id+ 4: return op_##name((uint4)opcode); \ + case id+ 5: return op_##name((uint4)opcode); \ + + #define op12(id, name) \ + op6(id, name) \ + case id+ 6: return op_##name((uint4)opcode); \ + case id+ 7: return op_##name((uint4)opcode); \ + case id+ 8: return op_##name((uint4)opcode); \ + case id+ 9: return op_##name((uint4)opcode); \ + case id+10: return op_##name((uint4)opcode); \ + case id+11: return op_##name((uint4)opcode); \ + + #define op15(id, name) \ + op12(id, name) \ + case id+12: return op_##name((uint4)opcode); \ + case id+13: return op_##name((uint4)opcode); \ + case id+14: return op_##name((uint4)opcode); \ + + #define op16(id, name) \ + op15(id, name) \ + case id+15: return op_##name((uint4)opcode); \ + + switch(opcode) { + op (0x00, stop) + op (0x01, nop) + op (0x02, cache) + op (0x03, lsr) + op (0x04, rol) + op (0x05, branch, 1) //bra + op (0x06, branch, (regs.sfr.s ^ regs.sfr.ov) == 0) //blt + op (0x07, branch, (regs.sfr.s ^ regs.sfr.ov) == 1) //bge + op (0x08, branch, regs.sfr.z == 0) //bne + op (0x09, branch, regs.sfr.z == 1) //beq + op (0x0a, branch, regs.sfr.s == 0) //bpl + op (0x0b, branch, regs.sfr.s == 1) //bmi + op (0x0c, branch, regs.sfr.cy == 0) //bcc + op (0x0d, branch, regs.sfr.cy == 1) //bcs + op (0x0e, branch, regs.sfr.ov == 0) //bvc + op (0x0f, branch, regs.sfr.ov == 1) //bvs + op16(0x10, to_move) + op16(0x20, with) + op12(0x30, store) + op (0x3c, loop) + op (0x3d, alt1) + op (0x3e, alt2) + op (0x3f, alt3) + op12(0x40, load) + op (0x4c, plot_rpix) + op (0x4d, swap) + op (0x4e, color_cmode) + op (0x4f, not) + op16(0x50, add_adc) + op16(0x60, sub_sbc_cmp) + op (0x70, merge) + op15(0x71, and_bic) + op16(0x80, mult_umult) + op (0x90, sbk) + op4 (0x91, link) + op (0x95, sex) + op (0x96, asr_div2) + op (0x97, ror) + op6 (0x98, jmp_ljmp) + op (0x9e, lob) + op (0x9f, fmult_lmult) + op16(0xa0, ibt_lms_sms) + op16(0xb0, from_moves) + op (0xc0, hib) + op15(0xc1, or_xor) + op15(0xd0, inc) + op15(0xe0, dec) + op (0xdf, getc_ramb_romb) + op (0xef, getb) + op16(0xf0, iwt_lm_sm) + } + + #undef op + #undef op4 + #undef op6 + #undef op12 + #undef op15 + #undef op16 +} diff --git a/higan/processor/gsu/table.cpp b/higan/processor/gsu/table.cpp deleted file mode 100644 index 2bc51f05..00000000 --- a/higan/processor/gsu/table.cpp +++ /dev/null @@ -1,266 +0,0 @@ -auto GSU::initialize_opcode_table() -> void { - #define op4(id, name) \ - op(id+ 0, name< 1>) op(id+ 1, name< 2>) op(id+ 2, name< 3>) op(id+ 3, name< 4>) - - #define op6(id, name) \ - op(id+ 0, name< 8>) op(id+ 1, name< 9>) op(id+ 2, name<10>) op(id+ 3, name<11>) \ - op(id+ 4, name<12>) op(id+ 5, name<13>) - - #define op12(id, name) \ - op(id+ 0, name< 0>) op(id+ 1, name< 1>) op(id+ 2, name< 2>) op(id+ 3, name< 3>) \ - op(id+ 4, name< 4>) op(id+ 5, name< 5>) op(id+ 6, name< 6>) op(id+ 7, name< 7>) \ - op(id+ 8, name< 8>) op(id+ 9, name< 9>) op(id+10, name<10>) op(id+11, name<11>) - - #define op15l(id, name) \ - op(id+ 0, name< 0>) op(id+ 1, name< 1>) op(id+ 2, name< 2>) op(id+ 3, name< 3>) \ - op(id+ 4, name< 4>) op(id+ 5, name< 5>) op(id+ 6, name< 6>) op(id+ 7, name< 7>) \ - op(id+ 8, name< 8>) op(id+ 9, name< 9>) op(id+10, name<10>) op(id+11, name<11>) \ - op(id+12, name<12>) op(id+13, name<13>) op(id+14, name<14>) - - #define op15h(id, name) \ - op(id+ 0, name< 1>) op(id+ 1, name< 2>) op(id+ 2, name< 3>) op(id+ 3, name< 4>) \ - op(id+ 4, name< 5>) op(id+ 5, name< 6>) op(id+ 6, name< 7>) op(id+ 7, name< 8>) \ - op(id+ 8, name< 9>) op(id+ 9, name<10>) op(id+10, name<11>) op(id+11, name<12>) \ - op(id+12, name<13>) op(id+13, name<14>) op(id+14, name<15>) - - #define op16(id, name) \ - op(id+ 0, name< 0>) op(id+ 1, name< 1>) op(id+ 2, name< 2>) op(id+ 3, name< 3>) \ - op(id+ 4, name< 4>) op(id+ 5, name< 5>) op(id+ 6, name< 6>) op(id+ 7, name< 7>) \ - op(id+ 8, name< 8>) op(id+ 9, name< 9>) op(id+10, name<10>) op(id+11, name<11>) \ - op(id+12, name<12>) op(id+13, name<13>) op(id+14, name<14>) op(id+15, name<15>) - - //====== - // ALT0 - //====== - - #define op(id, name) opcode_table[ 0 + id] = &GSU::op_##name; - op (0x00, stop) - op (0x01, nop) - op (0x02, cache) - op (0x03, lsr) - op (0x04, rol) - op (0x05, bra) - op (0x06, blt) - op (0x07, bge) - op (0x08, bne) - op (0x09, beq) - op (0x0a, bpl) - op (0x0b, bmi) - op (0x0c, bcc) - op (0x0d, bcs) - op (0x0e, bvc) - op (0x0f, bvs) - op16 (0x10, to_r) - op16 (0x20, with_r) - op12 (0x30, stw_ir) - op (0x3c, loop) - op (0x3d, alt1) - op (0x3e, alt2) - op (0x3f, alt3) - op12 (0x40, ldw_ir) - op (0x4c, plot) - op (0x4d, swap) - op (0x4e, color) - op (0x4f, not) - op16 (0x50, add_r) - op16 (0x60, sub_r) - op (0x70, merge) - op15h(0x71, and_r) - op16 (0x80, mult_r) - op (0x90, sbk) - op4 (0x91, link) - op (0x95, sex) - op (0x96, asr) - op (0x97, ror) - op6 (0x98, jmp_r) - op (0x9e, lob) - op (0x9f, fmult) - op16 (0xa0, ibt_r) - op16 (0xb0, from_r) - op (0xc0, hib) - op15h(0xc1, or_r) - op15l(0xd0, inc_r) - op (0xdf, getc) - op15l(0xe0, dec_r) - op (0xef, getb) - op16 (0xf0, iwt_r) - #undef op - - //====== - // ALT1 - //====== - - #define op(id, name) opcode_table[256 + id] = &GSU::op_##name; - op (0x00, stop) - op (0x01, nop) - op (0x02, cache) - op (0x03, lsr) - op (0x04, rol) - op (0x05, bra) - op (0x06, blt) - op (0x07, bge) - op (0x08, bne) - op (0x09, beq) - op (0x0a, bpl) - op (0x0b, bmi) - op (0x0c, bcc) - op (0x0d, bcs) - op (0x0e, bvc) - op (0x0f, bvs) - op16 (0x10, to_r) - op16 (0x20, with_r) - op12 (0x30, stb_ir) - op (0x3c, loop) - op (0x3d, alt1) - op (0x3e, alt2) - op (0x3f, alt3) - op12 (0x40, ldb_ir) - op (0x4c, rpix) - op (0x4d, swap) - op (0x4e, cmode) - op (0x4f, not) - op16 (0x50, adc_r) - op16 (0x60, sbc_r) - op (0x70, merge) - op15h(0x71, bic_r) - op16 (0x80, umult_r) - op (0x90, sbk) - op4 (0x91, link) - op (0x95, sex) - op (0x96, div2) - op (0x97, ror) - op6 (0x98, ljmp_r) - op (0x9e, lob) - op (0x9f, lmult) - op16 (0xa0, lms_r) - op16 (0xb0, from_r) - op (0xc0, hib) - op15h(0xc1, xor_r) - op15l(0xd0, inc_r) - op (0xdf, getc) - op15l(0xe0, dec_r) - op (0xef, getbh) - op16 (0xf0, lm_r) - #undef op - - //====== - // ALT2 - //====== - - #define op(id, name) opcode_table[512 + id] = &GSU::op_##name; - op (0x00, stop) - op (0x01, nop) - op (0x02, cache) - op (0x03, lsr) - op (0x04, rol) - op (0x05, bra) - op (0x06, blt) - op (0x07, bge) - op (0x08, bne) - op (0x09, beq) - op (0x0a, bpl) - op (0x0b, bmi) - op (0x0c, bcc) - op (0x0d, bcs) - op (0x0e, bvc) - op (0x0f, bvs) - op16 (0x10, to_r) - op16 (0x20, with_r) - op12 (0x30, stw_ir) - op (0x3c, loop) - op (0x3d, alt1) - op (0x3e, alt2) - op (0x3f, alt3) - op12 (0x40, ldw_ir) - op (0x4c, plot) - op (0x4d, swap) - op (0x4e, color) - op (0x4f, not) - op16 (0x50, add_i) - op16 (0x60, sub_i) - op (0x70, merge) - op15h(0x71, and_i) - op16 (0x80, mult_i) - op (0x90, sbk) - op4 (0x91, link) - op (0x95, sex) - op (0x96, asr) - op (0x97, ror) - op6 (0x98, jmp_r) - op (0x9e, lob) - op (0x9f, fmult) - op16 (0xa0, sms_r) - op16 (0xb0, from_r) - op (0xc0, hib) - op15h(0xc1, or_i) - op15l(0xd0, inc_r) - op (0xdf, ramb) - op15l(0xe0, dec_r) - op (0xef, getbl) - op16 (0xf0, sm_r) - #undef op - - //====== - // ALT3 - //====== - - #define op(id, name) opcode_table[768 + id] = &GSU::op_##name; - op (0x00, stop) - op (0x01, nop) - op (0x02, cache) - op (0x03, lsr) - op (0x04, rol) - op (0x05, bra) - op (0x06, blt) - op (0x07, bge) - op (0x08, bne) - op (0x09, beq) - op (0x0a, bpl) - op (0x0b, bmi) - op (0x0c, bcc) - op (0x0d, bcs) - op (0x0e, bvc) - op (0x0f, bvs) - op16 (0x10, to_r) - op16 (0x20, with_r) - op12 (0x30, stb_ir) - op (0x3c, loop) - op (0x3d, alt1) - op (0x3e, alt2) - op (0x3f, alt3) - op12 (0x40, ldb_ir) - op (0x4c, rpix) - op (0x4d, swap) - op (0x4e, cmode) - op (0x4f, not) - op16 (0x50, adc_i) - op16 (0x60, cmp_r) - op (0x70, merge) - op15h(0x71, bic_i) - op16 (0x80, umult_i) - op (0x90, sbk) - op4 (0x91, link) - op (0x95, sex) - op (0x96, div2) - op (0x97, ror) - op6 (0x98, ljmp_r) - op (0x9e, lob) - op (0x9f, lmult) - op16 (0xa0, lms_r) - op16 (0xb0, from_r) - op (0xc0, hib) - op15h(0xc1, xor_i) - op15l(0xd0, inc_r) - op (0xdf, romb) - op15l(0xe0, dec_r) - op (0xef, getbs) - op16 (0xf0, lm_r) - #undef op - - #undef op4 - #undef op6 - #undef op12 - #undef op15l - #undef op15h - #undef op16 -} diff --git a/higan/sfc/coprocessor/superfx/superfx.cpp b/higan/sfc/coprocessor/superfx/superfx.cpp index 4d56f935..aecf7e4c 100644 --- a/higan/sfc/coprocessor/superfx/superfx.cpp +++ b/higan/sfc/coprocessor/superfx/superfx.cpp @@ -17,14 +17,11 @@ auto SuperFX::Enter() -> void { auto SuperFX::main() -> void { if(regs.sfr.g == 0) return step(6); - - uint opcode = regs.sfr.alt2 << 9 | regs.sfr.alt1 << 8 | peekpipe(); - (this->*opcode_table[opcode])(); + instruction(peekpipe()); if(!r15_modified) regs.r[15]++; } auto SuperFX::init() -> void { - initialize_opcode_table(); regs.r[14].modify = {&SuperFX::r14_modify, this}; regs.r[15].modify = {&SuperFX::r15_modify, this}; } diff --git a/higan/target-tomoko/configuration/configuration.cpp b/higan/target-tomoko/configuration/configuration.cpp index 7d680960..4e8c5544 100644 --- a/higan/target-tomoko/configuration/configuration.cpp +++ b/higan/target-tomoko/configuration/configuration.cpp @@ -38,8 +38,7 @@ Settings::Settings() { set("Audio/Mute", false); set("Audio/Volume", 100); set("Audio/Balance", 50); - set("Audio/Reverb/Delay", 0); - set("Audio/Reverb/Level", 0); + set("Audio/Reverb/Enable", false); set("Audio/Latency", 60); set("Audio/Resampler", "Sinc"); diff --git a/higan/target-tomoko/program/utility.cpp b/higan/target-tomoko/program/utility.cpp index afc50368..ecbecf29 100644 --- a/higan/target-tomoko/program/utility.cpp +++ b/higan/target-tomoko/program/utility.cpp @@ -87,9 +87,6 @@ auto Program::updateAudioEffects() -> void { auto balance = max(-1.0, min(1.0, (settings["Audio/Balance"].integer() - 50) / 50.0)); Emulator::audio.setBalance(balance); - auto reverbDelay = settings["Audio/Reverb/Delay"].natural(); - Emulator::audio.setReverbDelay(reverbDelay); - - auto reverbLevel = settings["Audio/Reverb/Level"].natural() / 100.0; - Emulator::audio.setReverbLevel(reverbLevel); + auto reverbEnable = settings["Audio/Reverb/Enable"].boolean(); + Emulator::audio.setReverb(reverbEnable); } diff --git a/higan/target-tomoko/settings/audio.cpp b/higan/target-tomoko/settings/audio.cpp index 5067c5e3..5c59207f 100644 --- a/higan/target-tomoko/settings/audio.cpp +++ b/higan/target-tomoko/settings/audio.cpp @@ -46,13 +46,7 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { balanceValue.setAlignment(0.5); balanceSlider.setLength(101).setPosition(settings["Audio/Balance"].natural()).onChange([&] { updateEffects(); }); - reverbDelayLabel.setText("Reverb Delay:"); - reverbDelayValue.setAlignment(0.5); - reverbDelaySlider.setLength(201).setPosition(settings["Audio/Reverb/Delay"].natural()).onChange([&] { updateEffects(); }); - - reverbLevelLabel.setText("Reverb Level:"); - reverbLevelValue.setAlignment(0.5); - reverbLevelSlider.setLength(101).setPosition(settings["Audio/Reverb/Level"].natural()).onChange([&] { updateEffects(); }); + reverbEnable.setText("Reverb").setChecked(settings["Audio/Reverb/Enable"].boolean()).onToggle([&] { updateEffects(); }); updateDriver(); updateEffects(); @@ -81,11 +75,7 @@ auto AudioSettings::updateEffects() -> void { settings["Audio/Balance"].setValue(balanceSlider.position()); balanceValue.setText({balanceSlider.position(), "%"}); - settings["Audio/Reverb/Delay"].setValue(reverbDelaySlider.position()); - reverbDelayValue.setText({reverbDelaySlider.position(), "ms"}); - - settings["Audio/Reverb/Level"].setValue(reverbLevelSlider.position()); - reverbLevelValue.setText({reverbLevelSlider.position(), "%"}); + settings["Audio/Reverb/Enable"].setValue(reverbEnable.checked()); program->updateAudioEffects(); } diff --git a/higan/target-tomoko/settings/settings.hpp b/higan/target-tomoko/settings/settings.hpp index f5432d4e..351ad696 100644 --- a/higan/target-tomoko/settings/settings.hpp +++ b/higan/target-tomoko/settings/settings.hpp @@ -51,14 +51,7 @@ struct AudioSettings : TabFrameItem { Label balanceLabel{&balanceLayout, Size{80, 0}}; Label balanceValue{&balanceLayout, Size{50, 0}}; HorizontalSlider balanceSlider{&balanceLayout, Size{~0, 0}}; - HorizontalLayout reverbDelayLayout{&layout, Size{~0, 0}}; - Label reverbDelayLabel{&reverbDelayLayout, Size{80, 0}}; - Label reverbDelayValue{&reverbDelayLayout, Size{50, 0}}; - HorizontalSlider reverbDelaySlider{&reverbDelayLayout, Size{~0, 0}}; - HorizontalLayout reverbLevelLayout{&layout, Size{~0, 0}}; - Label reverbLevelLabel{&reverbLevelLayout, Size{80, 0}}; - Label reverbLevelValue{&reverbLevelLayout, Size{50, 0}}; - HorizontalSlider reverbLevelSlider{&reverbLevelLayout, Size{~0, 0}}; + CheckLabel reverbEnable{&layout, Size{~0, 0}}; auto updateDriver() -> void; auto updateEffects() -> void; diff --git a/nall/dsp/iir/biquad.hpp b/nall/dsp/iir/biquad.hpp new file mode 100644 index 00000000..f20cedfa --- /dev/null +++ b/nall/dsp/iir/biquad.hpp @@ -0,0 +1,146 @@ +#pragma once + +//transposed direct form II biquadratic second-order IIR filter (butterworth) + +namespace nall { namespace DSP { namespace IIR { + +struct Biquad { + enum class Type : uint { + LowPass, + HighPass, + BandPass, + Notch, + Peak, + LowShelf, + HighShelf, + }; + + inline auto reset(Type type, double cutoff, double quality = 0.707107, double gain = 0.0) -> void; + inline auto process(double in) -> double; //normalized sample + +private: + Type type; //filter type + double cutoff; //frequency cutoff + double quality; //frequency response quality + double gain; //peak gain + double a0, a1, a2, b1, b2; //coefficients + double z1, z2; //second-order IIR +}; + +auto Biquad::reset(Type type, double cutoff, double quality, double gain) -> void { + this->type = type; + this->cutoff = cutoff; + this->quality = quality; + this->gain = gain; + + z1 = 0.0; + z2 = 0.0; + + double v = pow(10, fabs(gain) / 20.0); + double k = tan(3.141592 * cutoff); + double q = quality; + double n = 0.0; + + switch(type) { + + case Type::LowPass: + n = 1 / (1 + k / q + k * k); + a0 = k * k * n; + a1 = 2 * a0; + a2 = a0; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - k / q + k * k) * n; + break; + + case Type::HighPass: + n = 1 / (1 + k / q + k * k); + a0 = 1 * n; + a1 = -2 * a0; + a2 = a0; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - k / q + k * k) * n; + break; + + case Type::BandPass: + n = 1 / (1 + k / q + k * k); + a0 = k / q * n; + a1 = 0; + a2 = -a0; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - k / q + k * k) * n; + break; + + case Type::Notch: + n = 1 / (1 + k / q + k * k); + a0 = (1 + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = a0; + b1 = a1; + b2 = (1 - k / q + k * k) * n; + break; + + case Type::Peak: + if(gain >= 0) { + n = 1 / (1 + 1 / q * k + k * k); + a0 = (1 + v / q * k + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = (1 - v / q * k + k * k) * n; + b1 = a1; + b2 = (1 - 1 / q * k + k * k) * n; + } else { + n = 1 / (1 + v / q * k + k * k); + a0 = (1 + 1 / q * k + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = (1 - 1 / q * k + k * k) * n; + b1 = a1; + b2 = (1 - v / q * k + k * k) * n; + } + break; + + case Type::LowShelf: + if(gain >= 0) { + n = 1 / (1 + sqrt(2) * k + k * k); + a0 = (1 + sqrt(2 * v) * k + v * k * k) * n; + a1 = 2 * (v * k * k - 1) * n; + a2 = (1 - sqrt(2 * v) * k + v * k * k) * n; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - sqrt(2) * k + k * k) * n; + } else { + n = 1 / (1 + sqrt(2 * v) * k + v * k * k); + a0 = (1 + sqrt(2) * k + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = (1 - sqrt(2) * k + k * k) * n; + b1 = 2 * (v * k * k - 1) * n; + b2 = (1 - sqrt(2 * v) * k + v * k * k) * n; + } + break; + + case Type::HighShelf: + if(gain >= 0) { + n = 1 / (1 + sqrt(2) * k + k * k); + a0 = (v + sqrt(2 * v) * k + k * k) * n; + a1 = 2 * (k * k - v) * n; + a2 = (v - sqrt(2 * v) * k + k * k) * n; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - sqrt(2) * k + k * k) * n; + } else { + n = 1 / (v + sqrt(2 * v) * k + k * k); + a0 = (1 + sqrt(2) * k + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = (1 - sqrt(2) * k + k * k) * n; + b1 = 2 * (k * k - v) * n; + b2 = (v - sqrt(2 * v) * k + k * k) * n; + } + break; + + } +} + +auto Biquad::process(double in) -> double { + double out = in * a0 + z1; + z1 = in * a1 + z2 - b1 * out; + z2 = in * a2 - b2 * out; + return out; +} + +}}} diff --git a/nall/dsp/resampler/cubic.hpp b/nall/dsp/resampler/cubic.hpp new file mode 100644 index 00000000..df379776 --- /dev/null +++ b/nall/dsp/resampler/cubic.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include + +namespace nall { namespace DSP { namespace Resampler { + +struct Cubic { + inline auto reset(double inputFrequency, double outputFrequency, uint queueSize = 0) -> void; + inline auto pending() const -> bool { return samples.pending(); } + inline auto read() -> double { return samples.read(); } + inline auto write(double sample) -> void; + +private: + double inputFrequency; + double outputFrequency; + + double ratio; + double fraction; + double history[4]; + queue samples; +}; + +auto Cubic::reset(double inputFrequency, double outputFrequency, uint queueSize) -> void { + this->inputFrequency = inputFrequency; + this->outputFrequency = outputFrequency; + if(!queueSize) queueSize = outputFrequency * 0.02; //20ms + + ratio = inputFrequency / outputFrequency; + fraction = 0.0; + for(auto& sample: history) sample = 0.0; + samples.resize(queueSize); +} + +auto Cubic::write(double sample) -> void { + auto& mu = fraction; + auto& s = history; + + s[0] = s[1]; + s[1] = s[2]; + s[2] = s[3]; + s[3] = sample; + + while(mu <= 1.0) { + double A = s[3] - s[2] - s[0] + s[1]; + double B = s[0] - s[1] - A; + double C = s[2] - s[0]; + double D = s[1]; + + samples.write(A * mu * mu * mu + B * mu * mu + C * mu + D); + mu += ratio; + } + + mu -= 1.0; +} + +}}} diff --git a/nall/nall.hpp b/nall/nall.hpp index cdcb5cbb..b64ffe59 100644 --- a/nall/nall.hpp +++ b/nall/nall.hpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include diff --git a/nall/queue.hpp b/nall/queue.hpp new file mode 100644 index 00000000..244aec3a --- /dev/null +++ b/nall/queue.hpp @@ -0,0 +1,101 @@ +#pragma once + +//simple circular ring buffer + +namespace nall { + +template +struct queue { + queue() = default; + + queue(const queue& source) { + operator=(source); + } + + queue(queue&& source) { + operator=(move(source)); + } + + auto operator=(const queue& source) -> queue& { + if(this == &source) return *this; + reset(); + _size = source._size; + _data = new T[_size]; + for(auto n : range(_size)) _data[n] = source._data[n]; + _read = source._read; + _write = source._write; + return *this; + } + + auto operator=(queue&& source) -> queue& { + if(this == &source) return *this; + _data = source._data; + _size = source._size; + _read = source._read; + _write = source._write; + source._data = nullptr; + source.reset(); + } + + ~queue() { + reset(); + } + + explicit operator bool() const { + return _size; + } + + auto size() const -> uint { + return _size; + } + + auto data() -> T* { + return _data; + } + + auto data() const -> const T* { + return _data; + } + + auto reset() { + delete[] _data; + _data = nullptr; + _size = 0; + _read = 0; + _write = 0; + } + + auto resize(uint size, const T& value = {}) -> void { + reset(); + _size = size; + _data = new T[_size]; + for(auto n : range(_size)) _data[n] = value; + } + + auto pending() const -> bool { + return _read != _write; + } + + auto read() -> T { + T result = _data[_read]; + if(++_read >= _size) _read = 0; + return result; + } + + auto last() const -> T { + return _data[_write]; + } + + auto write(const T& value) -> void { + _data[_write] = value; + if(++_write >= _size) _write = 0; + } + +private: + T* _data = nullptr; + uint _size = 0; + uint _read = 0; + uint _write = 0; +}; + +}