mirror of https://github.com/bsnes-emu/bsnes.git
Update to v102r13 release.
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.
This commit is contained in:
parent
4c3f9b93e7
commit
0bf2c9d4e1
|
@ -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/";
|
||||
|
|
|
@ -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 ""; }
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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},
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue