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:
Tim Allen 2017-03-02 07:40:55 +11:00
parent 4c3f9b93e7
commit 0bf2c9d4e1
28 changed files with 698 additions and 116 deletions

View File

@ -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/";

View File

@ -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 ""; }

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

216
higan/md/ym2612/channel.cpp Normal file
View File

@ -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);
}
}

View File

@ -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},
};

View File

@ -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;
}
}
}

View File

@ -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
}
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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;