From 0bf2c9d4e1c3ade80df292b6b7202e2ef565e135 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 2 Mar 2017 07:40:55 +1100 Subject: [PATCH] Update to v102r13 release. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit byuu says: Changelog: - removed Emulator::Interface::videoFrequency(), audioFrequency()¹ - (MS,GG,MD)/PSG: removed inversion on noise channel LFSR update [mic_] - MD/PSG: lowered volume to match YM2612 volume - MD/YM2612: added Cydrak's emulation of FM channels and LFO² ¹: These were no longer used by the UI. The video frequency is adaptive on many systems. And the audio frequency is meaningless due to Emulator::Audio always outputting a consistent frequency specified by the UI. Plus, take the Genesis where there's two sound chips running at different frequencies. So, these had to go. ²: Due to some lurking bugs, the audio is completely broken unfortunately. Will need to be debugged :( First pass looking for any typos didn't yield any obvious results. --- higan/emulator/emulator.hpp | 2 +- higan/emulator/interface.hpp | 4 - higan/fc/interface/interface.cpp | 8 - higan/fc/interface/interface.hpp | 3 - higan/gb/interface/interface.cpp | 8 - higan/gb/interface/interface.hpp | 3 - higan/gba/interface/interface.cpp | 8 - higan/gba/interface/interface.hpp | 3 - higan/md/interface/interface.cpp | 8 - higan/md/interface/interface.hpp | 3 - higan/md/psg/noise.cpp | 3 +- higan/md/psg/psg.cpp | 2 +- higan/md/ym2612/channel.cpp | 216 +++++++++++++++++++++++++++ higan/md/ym2612/constants.cpp | 53 +++++++ higan/md/ym2612/io.cpp | 151 +++++++++++++++++++ higan/md/ym2612/ym2612.cpp | 167 ++++++++++++++++++++- higan/md/ym2612/ym2612.hpp | 117 ++++++++++++++- higan/ms/interface/game-gear.cpp | 4 - higan/ms/interface/interface.cpp | 4 - higan/ms/interface/interface.hpp | 4 - higan/ms/interface/master-system.cpp | 4 - higan/ms/psg/noise.cpp | 3 +- higan/pce/interface/interface.cpp | 8 - higan/pce/interface/interface.hpp | 3 - higan/sfc/interface/interface.cpp | 11 -- higan/sfc/interface/interface.hpp | 3 - higan/ws/interface/interface.cpp | 8 - higan/ws/interface/interface.hpp | 3 - 28 files changed, 698 insertions(+), 116 deletions(-) create mode 100644 higan/md/ym2612/channel.cpp create mode 100644 higan/md/ym2612/constants.cpp diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 9c185fc0..d5ab5b6d 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "102.12"; + static const string Version = "102.13"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/emulator/interface.hpp b/higan/emulator/interface.hpp index c7276a65..621aa931 100644 --- a/higan/emulator/interface.hpp +++ b/higan/emulator/interface.hpp @@ -46,13 +46,9 @@ struct Interface { struct VideoSize { uint width, height; }; virtual auto videoSize() -> VideoSize = 0; virtual auto videoSize(uint width, uint height, bool arc) -> VideoSize = 0; - virtual auto videoFrequency() -> double = 0; virtual auto videoColors() -> uint32 = 0; virtual auto videoColor(uint32 color) -> uint64 = 0; - //audio information - virtual auto audioFrequency() -> double = 0; - //media interface virtual auto loaded() -> bool { return false; } virtual auto sha256() -> string { return ""; } diff --git a/higan/fc/interface/interface.cpp b/higan/fc/interface/interface.cpp index 9dc4602c..2071c439 100644 --- a/higan/fc/interface/interface.cpp +++ b/higan/fc/interface/interface.cpp @@ -55,10 +55,6 @@ auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize { return {w * m, h * m}; } -auto Interface::videoFrequency() -> double { - return 21477272.0 / (262.0 * 1364.0 - 4.0); -} - auto Interface::videoColors() -> uint32 { return 1 << 9; } @@ -114,10 +110,6 @@ auto Interface::videoColor(uint32 n) -> uint64 { return r << 32 | g << 16 | b << 0; } -auto Interface::audioFrequency() -> double { - return 21477272.0 / 12.0; -} - auto Interface::loaded() -> bool { return system.loaded(); } diff --git a/higan/fc/interface/interface.hpp b/higan/fc/interface/interface.hpp index 060fffe7..9e40734d 100644 --- a/higan/fc/interface/interface.hpp +++ b/higan/fc/interface/interface.hpp @@ -28,12 +28,9 @@ struct Interface : Emulator::Interface { auto videoSize() -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override; - auto videoFrequency() -> double override; auto videoColors() -> uint32 override; auto videoColor(uint32 color) -> uint64 override; - auto audioFrequency() -> double override; - auto loaded() -> bool override; auto sha256() -> string override; auto load(uint id) -> bool override; diff --git a/higan/gb/interface/interface.cpp b/higan/gb/interface/interface.cpp index af499c16..cf8ba67e 100644 --- a/higan/gb/interface/interface.cpp +++ b/higan/gb/interface/interface.cpp @@ -44,14 +44,6 @@ auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize { return {w * m, h * m}; } -auto Interface::videoFrequency() -> double { - return 4'194'304.0 / (154.0 * 456.0); -} - -auto Interface::audioFrequency() -> double { - return 4'194'304.0 / 2.0; -} - auto Interface::loaded() -> bool { return system.loaded(); } diff --git a/higan/gb/interface/interface.hpp b/higan/gb/interface/interface.hpp index b510fea1..5555da31 100644 --- a/higan/gb/interface/interface.hpp +++ b/higan/gb/interface/interface.hpp @@ -25,9 +25,6 @@ struct Interface : Emulator::Interface { auto videoSize() -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override; - auto videoFrequency() -> double override; - - auto audioFrequency() -> double override; auto loaded() -> bool override; auto sha256() -> string override; diff --git a/higan/gba/interface/interface.cpp b/higan/gba/interface/interface.cpp index efd7c328..0b20f54f 100644 --- a/higan/gba/interface/interface.cpp +++ b/higan/gba/interface/interface.cpp @@ -50,10 +50,6 @@ auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize { return {w * m, h * m}; } -auto Interface::videoFrequency() -> double { - return 16777216.0 / (228.0 * 1232.0); -} - auto Interface::videoColors() -> uint32 { return 1 << 15; } @@ -80,10 +76,6 @@ auto Interface::videoColor(uint32 color) -> uint64 { return r << 32 | g << 16 | b << 0; } -auto Interface::audioFrequency() -> double { - return 16777216.0 / 512.0; -} - auto Interface::loaded() -> bool { return system.loaded(); } diff --git a/higan/gba/interface/interface.hpp b/higan/gba/interface/interface.hpp index b55ac1c4..610c2303 100644 --- a/higan/gba/interface/interface.hpp +++ b/higan/gba/interface/interface.hpp @@ -25,12 +25,9 @@ struct Interface : Emulator::Interface { auto videoSize() -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override; - auto videoFrequency() -> double override; auto videoColors() -> uint32 override; auto videoColor(uint32 color) -> uint64 override; - auto audioFrequency() -> double override; - auto loaded() -> bool override; auto load(uint id) -> bool override; auto save() -> void override; diff --git a/higan/md/interface/interface.cpp b/higan/md/interface/interface.cpp index 4ad25fa0..f909ea63 100644 --- a/higan/md/interface/interface.cpp +++ b/higan/md/interface/interface.cpp @@ -66,10 +66,6 @@ auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize { return {w * m, h * m}; } -auto Interface::videoFrequency() -> double { - return 60.0; -} - auto Interface::videoColors() -> uint32 { return 1 << 9; } @@ -86,10 +82,6 @@ auto Interface::videoColor(uint32 color) -> uint64 { return r << 32 | g << 16 | b << 0; } -auto Interface::audioFrequency() -> double { - return 52'000.0; -} - auto Interface::loaded() -> bool { return system.loaded(); } diff --git a/higan/md/interface/interface.hpp b/higan/md/interface/interface.hpp index e4a4839a..79b8618e 100644 --- a/higan/md/interface/interface.hpp +++ b/higan/md/interface/interface.hpp @@ -28,12 +28,9 @@ struct Interface : Emulator::Interface { auto videoSize() -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override; - auto videoFrequency() -> double override; auto videoColors() -> uint32 override; auto videoColor(uint32 color) -> uint64 override; - auto audioFrequency() -> double override; - auto loaded() -> bool override; auto load(uint id) -> bool override; auto save() -> void override; diff --git a/higan/md/psg/noise.cpp b/higan/md/psg/noise.cpp index 128eec08..2cf5e78b 100644 --- a/higan/md/psg/noise.cpp +++ b/higan/md/psg/noise.cpp @@ -8,8 +8,7 @@ auto PSG::Noise::run() -> void { if(clock ^= 1) { //0->1 transition output = lfsr.bit(0); - auto eor = enable ? ~lfsr >> 3 : 0; - lfsr = (lfsr ^ eor) << 15 | lfsr >> 1; + lfsr = (lfsr.bit(0) ^ (lfsr.bit(3) & enable)) << 15 | lfsr >> 1; } } diff --git a/higan/md/psg/psg.cpp b/higan/md/psg/psg.cpp index 9d03ae7a..dc90ec75 100644 --- a/higan/md/psg/psg.cpp +++ b/higan/md/psg/psg.cpp @@ -44,7 +44,7 @@ auto PSG::power() -> void { select = 0; lowpass = 0; for(auto n : range(15)) { - levels[n] = 0x2000 * pow(2, n * -2.0 / 6.0) + 0.5; + levels[n] = 0x1000 * pow(2, n * -2.0 / 6.0) + 0.5; } levels[15] = 0; diff --git a/higan/md/ym2612/channel.cpp b/higan/md/ym2612/channel.cpp new file mode 100644 index 00000000..6e85a7d6 --- /dev/null +++ b/higan/md/ym2612/channel.cpp @@ -0,0 +1,216 @@ +auto YM2612::Channel::runEnvelope(uint2 index) -> void { + auto& op = operators[index]; + + int sustain = op.envelope.sustainLevel < 15 ? op.envelope.sustainLevel << 5 : 0x3f0; + if(ym2612.envelope.clock & (1 << op.envelope.divider) - 1) return; + + int value = ym2612.envelope.clock >> op.envelope.divider; + int step = op.envelope.steps >> ~value % 8 * 4 & 0xf; + if(op.ssg.enable) step <<= 2; //SSG results in a 4x faster envelope + + if(op.envelope.state == Attack) { + int next = op.envelope.value + (~uint16(op.envelope.value) * step >> 4) & 0x3ff; + if(next <= op.envelope.value) { + op.envelope.value = next; + } else { + op.envelope.value = 0; + op.envelope.state = op.envelope.value < sustain ? Decay : Sustain; + updateEnvelope(index); + } + } else if(!op.ssg.enable || op.envelope.value < 0x200) { + op.envelope.value = min(op.envelope.value + step, 0x3ff); + if(op.envelope.state == Decay && op.envelope.value >= sustain) { + op.envelope.state = Sustain; + updateEnvelope(index); + } + } + + updateLevel(index); +} + +auto YM2612::Channel::runPhase(uint2 index) -> void { + auto& op = operators[index]; + + op.phase.value += op.phase.delta; //advance wave position + if(!(op.ssg.enable && op.envelope.value >= 0x200)) return; //SSG loop check + + if(!op.ssg.hold && !op.ssg.pingPong) op.phase.value = 0; + + if(!op.ssg.hold || op.ssg.attack == op.ssg.invert) op.ssg.invert ^= op.ssg.pingPong; + + if(op.envelope.state == Attack) { + //do nothing; SSG is meant to skip the attack phase + } else if(op.envelope.state != Release && !op.ssg.hold) { + //if still looping, reset the envelope + op.envelope.state = Attack; + if(op.envelope.attackRate >= 62) { + op.envelope.value = 0; + op.envelope.state = op.envelope.sustainLevel ? Decay : Sustain; + } + updateEnvelope(index); + } else if(op.envelope.state == Release || (op.ssg.hold && op.ssg.attack == op.ssg.invert)) { + //clear envelope once finished + op.envelope.value = 0x3ff; + } + updateLevel(index); +} + +auto YM2612::Channel::trigger(uint2 index, bool keyOn) -> void { + auto& op = operators[index]; + if(op.keyOn == keyOn) return; //no change + + op.keyOn = keyOn; + op.envelope.state = Release; + updateEnvelope(index); + + if(keyOn) { + //restart phase and envelope generators + op.phase.value = 0; + op.envelope.state = Attack; + updateEnvelope(index); + + if(op.envelope.rate >= 62) { + //skip attack and possibly decay stages + op.envelope.value = 0; + op.envelope.state = op.envelope.sustainLevel ? Decay : Sustain; + updateEnvelope(index); + } + } else if(op.ssg.enable && op.ssg.attack != op.ssg.invert) { + //SSG-EG key-off + op.envelope.value = 0x200 - op.envelope.value; + } + + updateLevel(index); +} + +auto YM2612::Channel::updateEnvelope(uint2 index) -> void { + auto& op = operators[index]; + + int key = min(max((uint)op.pitch.value, 0x300), 0x4ff); + int ksr = op.octave.value * 4 + (key - 0x300) / 0x80; + int rate = 0; + + if(op.envelope.state == Attack) rate += (op.envelope.attackRate << 1); + if(op.envelope.state == Decay) rate += (op.envelope.decayRate << 1); + if(op.envelope.state == Sustain) rate += (op.envelope.sustainRate << 1); + if(op.envelope.state == Release) rate += (op.envelope.releaseRate << 1); + + rate += (ksr >> 3 - op.envelope.keyScale) * (rate > 0); + rate = min(rate, 63); + + auto& entry = envelopeRates[rate >> 2]; + op.envelope.rate = rate; + op.envelope.divider = entry.divider; + op.envelope.steps = entry.steps[rate & 3]; +} + +auto YM2612::Channel::updatePitch(uint2 index) -> void { + auto& op = operators[index]; + + op.pitch.value = mode ? op.pitch.reload : operators[0].pitch.reload; + op.octave.value = mode ? op.octave.reload : operators[0].octave.reload; + + updatePhase(index); + updateEnvelope(index); //due to key scaling +} + +auto YM2612::Channel::updatePhase(uint2 index) -> void { + auto& op = operators[index]; + + int ratio = op.multiple ? 2 * op.multiple : 1; + + int detune = op.detune & 3; + int key = min(max((uint)op.pitch.value, 0x300), 0x4ff); + int ksr = op.octave.value * 4 + (key - 0x300) / 0x80; + int tuning = detune ? detunes[detune - 1][ksr & 7] >> (3 - (ksr >> 3)) : 0; + + int lfo = ym2612.lfo.clock >> 2 & 0x1f; + int pm = 4 * vibratos[vibrato][lfo & 15] * (-lfo >> 4); + int msb = 10; + + while(msb > 4 && ~op.pitch.value & 1 << msb) msb--; + + op.phase.delta = op.pitch.value + (pm >> 10 - msb) << 6 >> 7 - op.octave.value; + + if(op.detune < 4) op.phase.delta += tuning; + else op.phase.delta -= tuning; + + op.phase.delta &= 0x1ffff; + op.phase.delta *= ratio; + op.phase.delta >>= 1; + op.phase.delta &= 0xfffff; +} + +auto YM2612::Channel::updateLevel(uint2 index) -> void { + auto& op = operators[index]; + + int lfo = ym2612.lfo.clock & 0x40 ? ym2612.lfo.clock & 0x3f : ~ym2612.lfo.clock & 0x3f; + int depth = tremolos[tremolo]; + + bool invert = op.ssg.attack != op.ssg.invert && op.envelope.state != Release; + uint10 envelope = op.ssg.enable && invert ? 0x200 - op.envelope.value : 0 + op.envelope.value; + + op.outputLevel = op.totalLevel << 3; + op.outputLevel += envelope; + op.outputLevel += (2 * lfo >> depth) * ym2612.lfo.enable; + op.outputLevel <<= 2 + 1; //sign bit +} + +auto YM2612::Channel::power() -> void { + leftEnable = true; + rightEnable = true; + + algorithm = 0; + feedback = 0; + vibrato = 0; + tremolo = 0; + + mode = 0; + + for(auto& op : operators) { + op.keyOn = false; + op.lfoEnable = false; + op.detune = 0; + op.multiple = 0; + op.totalLevel = 0; + + op.outputLevel = 0x1fff; + op.output = 0; + op.prior = 0; + + op.pitch.value = 0; + op.pitch.reload = 0; + op.pitch.latch = 0; + + op.octave.value = 0; + op.octave.reload = 0; + op.octave.latch = 0; + + op.phase.value = 0; + op.phase.delta = 0; + + op.envelope.state = Release; + op.envelope.rate = 0; + op.envelope.divider = 11; + op.envelope.steps = 0; + op.envelope.value = 0x3ff; + + op.envelope.keyScale = 0; + op.envelope.attackRate = 0; + op.envelope.decayRate = 0; + op.envelope.sustainRate = 0; + op.envelope.sustainLevel = 0; + op.envelope.releaseRate = 1; + + op.ssg.enable = false; + op.ssg.attack = false; + op.ssg.pingPong = false; + op.ssg.hold = false; + op.ssg.invert = false; + } + + for(auto index : range(4)) { + updatePitch(index); + updateLevel(index); + } +} diff --git a/higan/md/ym2612/constants.cpp b/higan/md/ym2612/constants.cpp new file mode 100644 index 00000000..0bcdc200 --- /dev/null +++ b/higan/md/ym2612/constants.cpp @@ -0,0 +1,53 @@ +const uint8_t YM2612::lfoDividers[8] = { + 108, + 77, + 71, + 67, + 62, + 44, + 8, + 5, +}; + +const uint8_t YM2612::vibratos[8][16] = { + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0}, + {0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0}, + {0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 1, 1, 0, 0}, + {0, 0, 1, 2, 2, 2, 3, 4, 4, 3, 2, 2, 2, 1, 0, 0}, + {0, 0, 2, 3, 4, 4, 5, 6, 6, 5, 4, 4, 3, 2, 0, 0}, + {0, 0, 4, 6, 8, 8,10,12,12,10, 8, 8, 6, 4, 0, 0}, + {0, 0, 8,12,16,16,20,24,24,20,16,16,12, 8, 0, 0}, +}; + +const uint8_t YM2612::tremolos[4] = { + 7, + 3, + 1, + 0, +}; + +const uint8_t YM2612::detunes[3][8] = { + { 5, 6, 6, 7, 8, 8, 9, 10}, + {11, 12, 13, 14, 16, 17, 18, 20}, + {16, 17, 19, 20, 22, 24, 26, 28}, +}; + +const YM2612::EnvelopeRate YM2612::envelopeRates[16] = { + 11, {0x00000000, 0x00000000, 0x01010101, 0x01010101}, + 10, {0x01010101, 0x01010101, 0x01110111, 0x01110111}, + 9, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 8, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 7, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 6, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 5, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 4, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 3, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 2, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 1, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 0, {0x01010101, 0x01011101, 0x01110111, 0x01111111}, + 0, {0x11111111, 0x11121112, 0x12121212, 0x12221222}, + 0, {0x22222222, 0x22242224, 0x24242424, 0x24442444}, + 0, {0x44444444, 0x44484448, 0x48484848, 0x48884888}, + 0, {0x88888888, 0x88888888, 0x88888888, 0x88888888}, +}; diff --git a/higan/md/ym2612/io.cpp b/higan/md/ym2612/io.cpp index 08c2477b..8a346858 100644 --- a/higan/md/ym2612/io.cpp +++ b/higan/md/ym2612/io.cpp @@ -10,6 +10,13 @@ auto YM2612::writeAddress(uint9 data) -> void { auto YM2612::writeData(uint8 data) -> void { switch(io.address) { + //LFO + case 0x022: { + lfo.rate = data.bits(0,2); + lfo.enable = data.bit(3); + break; + } + //timer A period (high) case 0x024: { timerA.period.bits(2,9) = data.bits(0,7); @@ -44,6 +51,25 @@ auto YM2612::writeData(uint8 data) -> void { if(data.bit(4)) timerA.line = 0; if(data.bit(5)) timerB.line = 0; + channels[2].mode = data.bits(6,7); + for(uint index : range(4)) channels[2].updatePitch(index); + + break; + } + + //key on/off + case 0x28: { + //0,1,2,4,5,6 => 0,1,2,3,4,5 + uint index = data.bits(0,2); + if(index == 3 || index == 7) break; + if(index >= 4) index--; + auto& channel = channels[index]; + + channel.trigger(0, data.bit(4)); + channel.trigger(1, data.bit(5)); + channel.trigger(2, data.bit(6)); + channel.trigger(3, data.bit(7)); + break; } @@ -60,4 +86,129 @@ auto YM2612::writeData(uint8 data) -> void { } } + + if(io.address % 0x100 < 0x30 || io.address % 0x100 >= 0xb8 || io.address % 4 == 3) return; + + uint voice = io.address % 4 + 3 * (io.address >> 8); + uint index = (voice * 4 + (io.address >> 3 & 1) + (io.address >> 1 & 2)) % 4; + + auto& channel = channels[voice]; + auto& op = channel.operators[index]; + + switch(io.address & 0x0f0) { + + //detune, multiple + case 0x030: { + op.multiple = data.bits(0,3); + op.detune = data.bits(4,6); + channel.updatePhase(index); + break; + } + + //total level + case 0x040: { + op.totalLevel = data.bits(0,6); + channel.updateLevel(index); + break; + } + + //key scaling, attack rate + case 0x050: { + op.envelope.attackRate = data.bits(0,4); + op.envelope.keyScale = data.bits(6,7); + channel.updateEnvelope(index); + channel.updatePhase(index); + break; + } + + //LFO enable, decay rate + case 0x060: { + op.envelope.decayRate = data.bits(0,4); + op.lfoEnable = data.bit(7); + channel.updateEnvelope(index); + channel.updateLevel(index); + break; + } + + //sustain rate + case 0x070: { + op.envelope.sustainRate = data.bits(0,4); + channel.updateEnvelope(index); + break; + } + + //sustain level, release rate + case 0x080: { + op.envelope.releaseRate = data.bits(0,3) << 1 | 1; + op.envelope.sustainLevel = data.bits(4,7); + channel.updateEnvelope(index); + break; + } + + //SSG-EG + case 0x090: { + op.ssg.hold = data.bit(0); + op.ssg.pingPong = data.bit(1); + op.ssg.attack = data.bit(2); + op.ssg.enable = data.bit(3); + break; + } + + } + + switch(io.address & 0x0fc) { + + //pitch (low) + case 0x0a0: { + channel.operators[0].pitch.value = channel.operators[0].pitch.latch | data; + channel.operators[0].octave.value = channel.operators[0].octave.latch; + for(auto index : range(4)) channel.updatePitch(index); + break; + } + + //pitch (high) + case 0x0a4: { + channel.operators[0].pitch.latch = data << 8; + channel.operators[0].octave.latch = data >> 3; + break; + } + + //... + case 0x0a8: { + if(voice > 2) break; + channels[2].operators[1 + voice].pitch.value = channels[2].operators[1 + voice].pitch.latch | data; + channels[2].operators[1 + voice].octave.value = channels[2].operators[1 + voice].octave.latch; + channels[2].updatePitch(1 + voice); + break; + } + + //... + case 0x0ac: { + if(voice > 2) break; + channels[2].operators[1 + voice].pitch.latch = data << 8; + channels[2].operators[1 + voice].octave.latch = data >> 3; + break; + } + + //algorithm, feedback + case 0x0b0: { + channel.algorithm = data.bits(0,2); + channel.feedback = data.bits(3,5); + break; + } + + //panning, tremolo, vibrato + case 0x0b4: { + channel.vibrato = data.bits(0,2); + channel.tremolo = data.bits(4,5); + channel.rightEnable = data.bit(6); + channel.leftEnable = data.bit(7); + for(auto index : range(4)) { + channel.updateLevel(index); + channel.updatePhase(index); + } + break; + } + + } } diff --git a/higan/md/ym2612/ym2612.cpp b/higan/md/ym2612/ym2612.cpp index 27c95b7d..c541ced9 100644 --- a/higan/md/ym2612/ym2612.cpp +++ b/higan/md/ym2612/ym2612.cpp @@ -5,23 +5,160 @@ namespace MegaDrive { YM2612 ym2612; #include "io.cpp" #include "timer.cpp" +#include "channel.cpp" +#include "constants.cpp" auto YM2612::Enter() -> void { while(true) scheduler.synchronize(), ym2612.main(); } auto YM2612::main() -> void { + sample(); + timerA.run(); timerB.run(); - int output = 0; - if(dac.enable) output += dac.sample; - output <<= 5; + if(lfo.enable && ++lfo.divider == lfoDividers[lfo.rate]) { + lfo.divider = 0; + lfo.clock++; + for(auto& channel : channels) { + for(auto index : range(4)) { + channel.updatePhase(index); //due to vibrato + channel.updateLevel(index); //due to tremolo + } + } + } + + if(++envelope.divider == 3) { + envelope.divider = 0; + envelope.clock++; + for(auto& channel : channels) { + for(auto index : range(4)) { + channel.runPhase(index); + channel.runEnvelope(index); + } + } + } else { + for(auto& channel : channels) { + for(auto index : range(4)) { + channel.runPhase(index); + } + } + } - stream->sample(output / 32768.0, output / 32768.0); step(1); } +auto YM2612::sample() -> void { + int left = 0; + int right = 0; + + for(auto& channel : channels) { + auto& op = channel.operators; + + const int modMask = -(1 << 1); + const int sumMask = -(1 << 5); + const int outMask = -(1 << 5); + + auto old = [&](uint n) -> int { return modMask & op[n].prior; }; + auto mod = [&](uint n) -> int { return modMask & op[n].output; }; + auto out = [&](uint n) -> int { return sumMask & op[n].output; }; + + auto wave = [&](uint n, int modulation) -> int { + int x = modulation / 2 + op[n].phase.value / 0x400; + int y = sine[x & 0x3ff] + op[n].outputLevel; + return y < 0x2000 ? pow2[y & 0x1ff] << 2 >> y / 0x200 : 0; + }; + + int feedback = modMask & op[0].output + op[0].prior >> 9 - channel.feedback; + int accumulator = 0; + + for(auto n : range(4)) op[n].prior = op[n].output; + + op[0].output = wave(0, feedback * (channel.feedback > 0)); + + if(channel.algorithm == 0) { + //0 -> 1 -> 2 -> 3 + op[1].output = wave(1, mod(0)); + op[2].output = wave(2, old(1)); + op[3].output = wave(3, mod(2)); + accumulator += out(3); + } + + if(channel.algorithm == 1) { + //(0 + 1) -> 2 -> 3 + op[1].output = wave(1, 0); + op[2].output = wave(2, mod(0) + old(1)); + op[3].output = wave(3, mod(2)); + accumulator += out(3); + } + + if(channel.algorithm == 2) { + //0 + (1 -> 2) -> 3 + op[1].output = wave(1, 0); + op[2].output = wave(2, old(1)); + op[3].output = wave(3, mod(0) + mod(2)); + accumulator += out(3); + } + + if(channel.algorithm == 3) { + //(0 -> 1) + 2 -> 3 + op[1].output = wave(1, mod(0)); + op[2].output = wave(2, 0); + op[3].output = wave(3, mod(1) + mod(2)); + accumulator += out(3); + } + + if(channel.algorithm == 4) { + //(0 -> 1) + (2 -> 3) + op[1].output = wave(1, mod(0)); + op[2].output = wave(2, 0); + op[3].output = wave(3, mod(2)); + accumulator += out(1) + out(3); + } + + if(channel.algorithm == 5) { + //0 -> (1 + 2 + 3) + op[1].output = wave(1, mod(0)); + op[2].output = wave(2, old(0)); + op[3].output = wave(3, mod(0)); + accumulator += out(1) + out(2) + out(3); + } + + if(channel.algorithm == 6) { + //(0 -> 1) + 2 + 3 + op[1].output = wave(1, mod(0)); + op[2].output = wave(2, 0); + op[3].output = wave(3, 0); + accumulator += out(1) + out(2) + out(3); + } + + if(channel.algorithm == 7) { + //0 + 1 + 2 + 3 + op[1].output = wave(1, 0); + op[2].output = wave(2, 0); + op[3].output = wave(3, 0); + accumulator += out(0) + out(1) + out(2) + out(3); + } + + int voiceData = outMask & min(max(accumulator, -0x1ffff), +0x1ffff); + if(dac.enable && (&channel == &channels[5])) voiceData = dac.sample - 0x80 << 5; + + if(channel.leftEnable ) left += voiceData; + if(channel.rightEnable) right += voiceData; + } + + int cutoff = 20; + + lpfLeft = (left - lpfLeft) * cutoff / 256; + lpfRight = (right - lpfRight) * cutoff / 256; + + left = left * 2 / 6 + lpfLeft * 3 / 4; + right = right * 2 / 6 + lpfRight * 3 / 4; + + stream->sample(left / 32768.0, right / 32768.0); +} + auto YM2612::step(uint clocks) -> void { Thread::step(clocks); synchronize(cpu); @@ -33,12 +170,30 @@ auto YM2612::power() -> void { stream = Emulator::audio.createStream(2, frequency()); memory::fill(&io, sizeof(IO)); + memory::fill(&lfo, sizeof(LFO)); + memory::fill(&dac, sizeof(DAC)); + memory::fill(&envelope, sizeof(Envelope)); timerA.power(); timerB.power(); + for(auto& channel : channels) channel.power(); - dac.enable = 0; - dac.sample = 0; + const int pos = 0; + const int neg = 1; + + for(int x = 0; x <= 0xff; x++) { + int y = int(-256 * log(sin((2 * x + 1) * Math::Pi / 1024)) / log(2) + 0.5); + sine[0x000 + x] = pos + (y << 1); + sine[0x1ff - x] = pos + (y << 1); + sine[0x200 + x] = neg + (y << 1); + sine[0x3ff - x] = neg + (y << 1); + } + + for(int y = 0; y <= 0xff; y++) { + int z = int(1024 * pow(2, (0xff - y) / 256.0) + 0.5); + pow2[pos + (y << 1)] = +z; + pow2[neg + (y << 1)] = ~z; //not -z + } } } diff --git a/higan/md/ym2612/ym2612.hpp b/higan/md/ym2612/ym2612.hpp index 63dbbae6..a748b4ed 100644 --- a/higan/md/ym2612/ym2612.hpp +++ b/higan/md/ym2612/ym2612.hpp @@ -5,6 +5,7 @@ struct YM2612 : Thread { static auto Enter() -> void; auto main() -> void; + auto sample() -> void; auto step(uint clocks) -> void; auto power() -> void; @@ -19,6 +20,23 @@ private: uint9 address; } io; + struct LFO { + uint1 enable; + uint3 rate; + uint32 clock; + uint32 divider; + } lfo; + + struct DAC { + uint1 enable; + uint8 sample; + } dac; + + struct Envelope { + uint32 clock; + uint32 divider; + } envelope; + struct TimerA { //timer.cpp auto run() -> void; @@ -44,10 +62,101 @@ private: uint4 divider; } timerB; - struct DAC { - uint1 enable; - uint8 sample; - } dac; + enum : uint { Attack, Decay, Sustain, Release }; + + struct Channel { + //channel.cpp + auto runEnvelope(uint2 index) -> void; + auto runPhase(uint2 index) -> void; + auto trigger(uint2 index, bool keyOn) -> void; + + auto updateEnvelope(uint2 index) -> void; + auto updatePitch(uint2 index) -> void; + auto updatePhase(uint2 index) -> void; + auto updateLevel(uint2 index) -> void; + + auto power() -> void; + + bool leftEnable; + bool rightEnable; + + uint3 algorithm; + uint3 feedback; + uint3 vibrato; + uint2 tremolo; + + uint2 mode; + + struct Operator { + bool keyOn; + bool lfoEnable; + uint3 detune; + uint4 multiple; + uint7 totalLevel; + + uint16 outputLevel; + int16 output; + int16 prior; + + struct Pitch { + uint11 value; + uint11 reload; + uint11 latch; + } pitch; + + struct Octave { + uint3 value; + uint3 reload; + uint3 latch; + } octave; + + struct Phase { + uint20 value; + uint20 delta; + } phase; + + struct Envelope { + uint state; + uint rate; + uint divider; + uint steps; + uint10 value; + + uint2 keyScale; + uint5 attackRate; + uint5 decayRate; + uint5 sustainRate; + uint4 sustainLevel; + uint5 releaseRate; + } envelope; + + struct SSG { + bool enable; + bool attack; + bool pingPong; + bool hold; + bool invert; + } ssg; + } operators[4]; + } channels[6]; + + uint16 sine[0x400]; + int16 pow2[0x200]; + + int lpfLeft; + int lpfRight; + + //constants.cpp + struct EnvelopeRate { + uint32_t divider; + uint32_t steps[4]; + }; + + static const uint8_t lfoDividers[8]; + static const uint8_t vibratos[8][16]; + static const uint8_t tremolos[4]; + static const uint8_t detunes[3][8]; + static const EnvelopeRate envelopeRates[16]; }; extern YM2612 ym2612; diff --git a/higan/ms/interface/game-gear.cpp b/higan/ms/interface/game-gear.cpp index b876084a..fa75feba 100644 --- a/higan/ms/interface/game-gear.cpp +++ b/higan/ms/interface/game-gear.cpp @@ -32,10 +32,6 @@ auto GameGearInterface::videoSize(uint width, uint height, bool arc) -> VideoSiz return {w * m, h * m}; } -auto GameGearInterface::videoFrequency() -> double { - return 60.0; -} - auto GameGearInterface::videoColors() -> uint32 { return 1 << 12; } diff --git a/higan/ms/interface/interface.cpp b/higan/ms/interface/interface.cpp index 9abd82d9..f9e24184 100644 --- a/higan/ms/interface/interface.cpp +++ b/higan/ms/interface/interface.cpp @@ -17,10 +17,6 @@ auto Interface::title() -> string { return cartridge.title(); } -auto Interface::audioFrequency() -> double { - return 44'100.0; //todo: not correct -} - auto Interface::loaded() -> bool { return system.loaded(); } diff --git a/higan/ms/interface/interface.hpp b/higan/ms/interface/interface.hpp index 8c206f0b..f7c39339 100644 --- a/higan/ms/interface/interface.hpp +++ b/higan/ms/interface/interface.hpp @@ -27,8 +27,6 @@ struct Interface : Emulator::Interface { auto manifest() -> string override; auto title() -> string override; - auto audioFrequency() -> double override; - auto loaded() -> bool override; auto save() -> void override; auto unload() -> void override; @@ -54,7 +52,6 @@ struct MasterSystemInterface : Interface { auto videoSize() -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override; - auto videoFrequency() -> double override; auto videoColors() -> uint32 override; auto videoColor(uint32 color) -> uint64 override; @@ -68,7 +65,6 @@ struct GameGearInterface : Interface { auto videoSize() -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override; - auto videoFrequency() -> double override; auto videoColors() -> uint32 override; auto videoColor(uint32 color) -> uint64 override; diff --git a/higan/ms/interface/master-system.cpp b/higan/ms/interface/master-system.cpp index 3d208e5d..12f3c1a3 100644 --- a/higan/ms/interface/master-system.cpp +++ b/higan/ms/interface/master-system.cpp @@ -48,10 +48,6 @@ auto MasterSystemInterface::videoSize(uint width, uint height, bool arc) -> Vide return {uint(w * a * m), uint(h * m)}; } -auto MasterSystemInterface::videoFrequency() -> double { - return 60.0; -} - auto MasterSystemInterface::videoColors() -> uint32 { return 1 << 6; } diff --git a/higan/ms/psg/noise.cpp b/higan/ms/psg/noise.cpp index a8ac734e..987676e1 100644 --- a/higan/ms/psg/noise.cpp +++ b/higan/ms/psg/noise.cpp @@ -8,8 +8,7 @@ auto PSG::Noise::run() -> void { if(clock ^= 1) { //0->1 transition output = lfsr.bit(0); - auto eor = enable ? ~lfsr >> 3 : 0; - lfsr = (lfsr ^ eor) << 15 | lfsr >> 1; + lfsr = (lfsr.bit(0) ^ (lfsr.bit(3) & enable)) << 15 | lfsr >> 1; } } diff --git a/higan/pce/interface/interface.cpp b/higan/pce/interface/interface.cpp index 5e47c2be..f0edf61b 100644 --- a/higan/pce/interface/interface.cpp +++ b/higan/pce/interface/interface.cpp @@ -51,10 +51,6 @@ auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize { return {uint(w * a * m), uint(h * m)}; } -auto Interface::videoFrequency() -> double { - return 60.0; -} - auto Interface::videoColors() -> uint32 { return 1 << 9; } @@ -71,10 +67,6 @@ auto Interface::videoColor(uint32 color) -> uint64 { return r << 32 | g << 16 | b << 0; } -auto Interface::audioFrequency() -> double { - return 315.0 / 88.0 * 1'000'000.0; //3.57MHz -} - auto Interface::loaded() -> bool { return system.loaded(); } diff --git a/higan/pce/interface/interface.hpp b/higan/pce/interface/interface.hpp index dc20b3e8..545a4162 100644 --- a/higan/pce/interface/interface.hpp +++ b/higan/pce/interface/interface.hpp @@ -25,12 +25,9 @@ struct Interface : Emulator::Interface { auto videoSize() -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override; - auto videoFrequency() -> double override; auto videoColors() -> uint32 override; auto videoColor(uint32 color) -> uint64 override; - auto audioFrequency() -> double override; - auto loaded() -> bool override; auto sha256() -> string override; auto save() -> void override; diff --git a/higan/sfc/interface/interface.cpp b/higan/sfc/interface/interface.cpp index a50c931d..5e2c3825 100644 --- a/higan/sfc/interface/interface.cpp +++ b/higan/sfc/interface/interface.cpp @@ -129,13 +129,6 @@ auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize { return {w * m, h * m}; } -auto Interface::videoFrequency() -> double { - switch(system.region()) { default: - case System::Region::NTSC: return (system.colorburst() * 6.0) / (262.0 * 1364.0 - 4.0); - case System::Region::PAL: return (system.colorburst() * 6.0) / (312.0 * 1364.0); - } -} - auto Interface::videoColors() -> uint32 { return 1 << 19; } @@ -166,10 +159,6 @@ auto Interface::videoColor(uint32 color) -> uint64 { return R << 32 | G << 16 | B << 0; } -auto Interface::audioFrequency() -> double { - return 32040.0; -} - auto Interface::loaded() -> bool { return system.loaded(); } diff --git a/higan/sfc/interface/interface.hpp b/higan/sfc/interface/interface.hpp index f7250b23..267c42ea 100644 --- a/higan/sfc/interface/interface.hpp +++ b/higan/sfc/interface/interface.hpp @@ -40,12 +40,9 @@ struct Interface : Emulator::Interface { auto videoSize() -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override; - auto videoFrequency() -> double override; auto videoColors() -> uint32 override; auto videoColor(uint32 color) -> uint64 override; - auto audioFrequency() -> double override; - auto loaded() -> bool override; auto sha256() -> string override; auto load(uint id) -> bool override; diff --git a/higan/ws/interface/interface.cpp b/higan/ws/interface/interface.cpp index 27192384..ed5ac26f 100644 --- a/higan/ws/interface/interface.cpp +++ b/higan/ws/interface/interface.cpp @@ -50,14 +50,6 @@ auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize { return {w * m, h * m}; } -auto Interface::videoFrequency() -> double { - return 3'072'000.0 / (159.0 * 256.0); //~75.47hz -} - -auto Interface::audioFrequency() -> double { - return 3'072'000.0; -} - auto Interface::loaded() -> bool { return system.loaded(); } diff --git a/higan/ws/interface/interface.hpp b/higan/ws/interface/interface.hpp index db09511d..2bb75f33 100644 --- a/higan/ws/interface/interface.hpp +++ b/higan/ws/interface/interface.hpp @@ -25,9 +25,6 @@ struct Interface : Emulator::Interface { auto videoSize() -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override; - auto videoFrequency() -> double override; - - auto audioFrequency() -> double override; auto loaded() -> bool override; auto sha256() -> string override;