Update to v102r14 release.

byuu says:

Changelog:

  - (MS,GG,MD)/PSG: flip output bit from noise channel [TmEE]
  - MD/YM2612: rewrite YM2612::Channel functions to
    YM2612::Channel::Operator functions¹
  - MD/YM2612: pitch/octave I/O registers should set reload, not value
    (fixes sound in most games)
  - MD/YM2612: don't try to sign-extend raw PCM values (fixes Shining
    Force opening music)
  - MD/YM2612: various algorithm simplifications; conversions from
    `*`, `/`, `%` to `<<`, `>>`; etc.

Overall ... Sonic the Hedgehog sounds really, really great. Almost
perfect, but there's a bit of clamping going on in the special zones.
Langrisser II sounds really great. Shining Force sounds pretty much
perfect. Bare Knucles (Streets of Rage) does pretty badly ... punches
sound more like dinging a salad fork on a wine glass, heh. Altered Beast
is extremely broken ... no music at the title screen, very distorted
in-game music. I suspect a bug outside of the YM2612 is affecting this
game.

So, the YM2612 emulation isn't perfect, but it's a really good start to
the most complex sound chip in all of higan. Hopefully the VRC7 and
YM2413 will prove to be less ferocious ... not that I'm in any rush to
work on either. The former is going to need the NES mapper rewrite to be
done first, and the latter is cool but not very necessary since all
those games have fallbacks to the inferior PSG audio.

But really ... I can't thank Cydrak enough for doing this for me. It
would have probably taken me months to parse through all of the
documentation on this chip (most of which is in a 55-page thread on
spritesmind that is filled with wrong/outdated information at the start,
and corrections as you go deeper.) Not to mention, learning about what
the hell detuning, low-frequency oscillation, tremolo, vibrato, etc were
all about. Or how those algorithms to compute the final output work. Or
the dozens of special cases littered in there to make everything sound
good. Fierce, nasty chip that.

Now the last real problem is save states ... the Mega Drive is going to
be the trickiest of all to implement with libco. There are lots of areas
where one chip will deadlock another chip while it completes some
operation. We don't have a choice but to force those stalls to abort
anyway, in order to let libco reach the start of its entry point once
again. I don't know what kind of impact that'll have on states ... I
suspect they'll work almost as reliably as the SNES does, but I can't
know that until I implement it. It's going to be pretty nasty, though.

¹: this basically removes a lot of unnecessary op. prefixes and the
need to capture `auto& op = operators[index]` at the start of every
function.

I wanted to have subfunctions like
`YM2612::Channel::Operator::Envelope::run()`, etc but unfortunately,
pretty much all of the envelope, phase, pitch, level functions need to
access each other's state.
This commit is contained in:
Tim Allen 2017-03-03 21:45:07 +11:00
parent 0bf2c9d4e1
commit 89d47914b9
11 changed files with 203 additions and 222 deletions

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "102.13";
static const string Version = "102.14";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -7,7 +7,7 @@ auto PSG::Noise::run() -> void {
if(rate == 3) counter = pitch; //shared with tone2
if(clock ^= 1) { //0->1 transition
output = lfsr.bit(0);
output = !lfsr.bit(0);
lfsr = (lfsr.bit(0) ^ (lfsr.bit(3) & enable)) << 15 | lfsr >> 1;
}
}

View File

@ -23,8 +23,8 @@ auto PSG::main() -> void {
if(tone2.output) output += levels[tone2.volume];
if(noise.output) output += levels[noise.volume];
lowpass += (output - lowpass) * 20.0 / 256.0;
output = output * 2.0 / 6.0 + lowpass * 3.0 / 4.0;
lowpass += (output - lowpass) * 20 / 256;
output = output * 2 / 6 + lowpass * 3 / 4;
output = sclamp<16>(output - 32768);
stream->sample(output / 32768.0);

View File

@ -1,4 +1,4 @@
//TI SN76489
//TI SN76489 (derivative)
struct PSG : Thread {
shared_pointer<Emulator::Stream> stream;

View File

@ -1,159 +1,137 @@
auto YM2612::Channel::runEnvelope(uint2 index) -> void {
auto& op = operators[index];
auto YM2612::Channel::Operator::trigger(bool state) -> void {
if(keyOn == state) return; //no change
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);
keyOn = state;
envelope.state = Release;
updateEnvelope();
if(keyOn) {
//restart phase and envelope generators
op.phase.value = 0;
op.envelope.state = Attack;
updateEnvelope(index);
phase.value = 0;
ssg.invert = false;
envelope.state = Attack;
updateEnvelope();
if(op.envelope.rate >= 62) {
if(envelope.rate >= 62) {
//skip attack and possibly decay stages
op.envelope.value = 0;
op.envelope.state = op.envelope.sustainLevel ? Decay : Sustain;
updateEnvelope(index);
envelope.value = 0;
envelope.state = envelope.sustainLevel ? Decay : Sustain;
updateEnvelope();
}
} else if(op.ssg.enable && op.ssg.attack != op.ssg.invert) {
} else if(ssg.enable && ssg.attack != ssg.invert) {
//SSG-EG key-off
op.envelope.value = 0x200 - op.envelope.value;
envelope.value = 0x200 - envelope.value;
}
updateLevel(index);
updateLevel();
}
auto YM2612::Channel::updateEnvelope(uint2 index) -> void {
auto& op = operators[index];
auto YM2612::Channel::Operator::runEnvelope() -> void {
uint sustain = envelope.sustainLevel < 15 ? envelope.sustainLevel << 5 : 0x3f0;
if(ym2612.envelope.clock & (1 << envelope.divider) - 1) return;
int key = min(max((uint)op.pitch.value, 0x300), 0x4ff);
int ksr = op.octave.value * 4 + (key - 0x300) / 0x80;
int rate = 0;
uint value = ym2612.envelope.clock >> envelope.divider;
uint step = envelope.steps >> ((~value & 7) << 2) & 0xf;
if(ssg.enable) step <<= 2; //SSG results in a 4x faster envelope
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);
if(envelope.state == Attack) {
uint next = envelope.value + (~uint16(envelope.value) * step >> 4) & 0x3ff;
if(next <= envelope.value) {
envelope.value = next;
} else {
envelope.value = 0;
envelope.state = envelope.value < sustain ? Decay : Sustain;
updateEnvelope();
}
} else if(!ssg.enable || envelope.value < 0x200) {
envelope.value = min(envelope.value + step, 0x3ff);
if(envelope.state == Decay && envelope.value >= sustain) {
envelope.state = Sustain;
updateEnvelope();
}
}
rate += (ksr >> 3 - op.envelope.keyScale) * (rate > 0);
updateLevel();
}
auto YM2612::Channel::Operator::runPhase() -> void {
phase.value += phase.delta; //advance wave position
if(!(ssg.enable && envelope.value >= 0x200)) return; //SSG loop check
if(!ssg.hold && !ssg.alternate) phase.value = 0;
if(!ssg.hold || ssg.attack == ssg.invert) ssg.invert ^= ssg.alternate;
if(envelope.state == Attack) {
//do nothing; SSG is meant to skip the attack phase
} else if(envelope.state != Release && !ssg.hold) {
//if still looping, reset the envelope
envelope.state = Attack;
if(envelope.attackRate >= 62) {
envelope.value = 0;
envelope.state = envelope.sustainLevel ? Decay : Sustain;
}
updateEnvelope();
} else if(envelope.state == Release || (ssg.hold && ssg.attack == ssg.invert)) {
//clear envelope once finished
envelope.value = 0x3ff;
}
updateLevel();
}
auto YM2612::Channel::Operator::updateEnvelope() -> void {
uint key = min(max((uint)pitch.value, 0x300), 0x4ff);
uint ksr = (octave.value << 2) + ((key - 0x300) >> 7);
uint rate = 0;
if(envelope.state == Attack) rate += (envelope.attackRate << 1);
if(envelope.state == Decay) rate += (envelope.decayRate << 1);
if(envelope.state == Sustain) rate += (envelope.sustainRate << 1);
if(envelope.state == Release) rate += (envelope.releaseRate << 1);
rate += (ksr >> 3 - 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];
envelope.rate = rate;
envelope.divider = entry.divider;
envelope.steps = entry.steps[rate & 3];
}
auto YM2612::Channel::updatePitch(uint2 index) -> void {
auto& op = operators[index];
auto YM2612::Channel::Operator::updatePitch() -> void {
//only channel[2] allows per-operator frequencies
//implemented by forcing mode to zero (single frequency) for other channels
//in single frequency mode, operator[3] frequency is used for all operators
pitch.value = channel.mode ? pitch.reload : channel[3].pitch.reload;
octave.value = channel.mode ? octave.reload : channel[3].octave.reload;
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
updatePhase();
updateEnvelope(); //due to key scaling
}
auto YM2612::Channel::updatePhase(uint2 index) -> void {
auto& op = operators[index];
auto YM2612::Channel::Operator::updatePhase() -> void {
uint key = min(max((uint)pitch.value, 0x300), 0x4ff);
uint ksr = (octave.value << 2) + ((key - 0x300) >> 7);
uint tuning = detune & 3 ? detunes[(detune & 3) - 1][ksr & 7] >> (3 - (ksr >> 3)) : 0;
int ratio = op.multiple ? 2 * op.multiple : 1;
uint lfo = ym2612.lfo.clock >> 2 & 0x1f;
uint pm = 4 * vibratos[channel.vibrato][lfo & 15] * (-lfo >> 4);
uint msb = 10;
while(msb > 4 && ~pitch.value & 1 << msb) msb--;
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;
phase.delta = pitch.value + (pm >> 10 - msb) << 6 >> 7 - octave.value;
phase.delta = (!detune.bit(2) ? phase.delta + tuning : phase.delta - tuning) & 0x1ffff;
phase.delta = (multiple ? phase.delta * multiple : phase.delta >> 1) & 0xfffff;
}
auto YM2612::Channel::updateLevel(uint2 index) -> void {
auto& op = operators[index];
auto YM2612::Channel::Operator::updateLevel() -> void {
uint lfo = ym2612.lfo.clock & 0x40 ? ym2612.lfo.clock & 0x3f : ~ym2612.lfo.clock & 0x3f;
uint depth = tremolos[channel.tremolo];
int lfo = ym2612.lfo.clock & 0x40 ? ym2612.lfo.clock & 0x3f : ~ym2612.lfo.clock & 0x3f;
int depth = tremolos[tremolo];
bool invert = ssg.attack != ssg.invert && envelope.state != Release;
uint10 value = ssg.enable && invert ? 0x200 - envelope.value : 0 + envelope.value;
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
outputLevel = ((totalLevel << 3) + value + (lfoEnable ? lfo << 1 >> depth : 0)) << 3;
}
auto YM2612::Channel::power() -> void {
@ -204,13 +182,11 @@ auto YM2612::Channel::power() -> void {
op.ssg.enable = false;
op.ssg.attack = false;
op.ssg.pingPong = false;
op.ssg.alternate = false;
op.ssg.hold = false;
op.ssg.invert = false;
}
for(auto index : range(4)) {
updatePitch(index);
updateLevel(index);
op.updatePitch();
op.updateLevel();
}
}

View File

@ -1,5 +1,5 @@
auto YM2612::readStatus() -> uint8 {
//d7 = busy
//d7 = busy (not emulated, requires cycle timing accuracy)
return timerA.line << 0 | timerB.line << 1;
}
@ -52,7 +52,7 @@ auto YM2612::writeData(uint8 data) -> void {
if(data.bit(5)) timerB.line = 0;
channels[2].mode = data.bits(6,7);
for(uint index : range(4)) channels[2].updatePitch(index);
for(auto& op : channels[2].operators) op.updatePitch();
break;
}
@ -63,12 +63,11 @@ auto YM2612::writeData(uint8 data) -> void {
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));
channels[index][0].trigger(data.bit(4));
channels[index][1].trigger(data.bit(5));
channels[index][2].trigger(data.bit(6));
channels[index][3].trigger(data.bit(7));
break;
}
@ -87,10 +86,9 @@ 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;
if(io.address.bits(0,1) == 3) return;
uint3 voice = io.address.bit(8) * 3 + io.address.bits(0,1);
uint2 index = io.address.bits(2,3) >> 1 | io.address.bits(2,3) << 1; //0,1,2,3 => 0,2,1,3
auto& channel = channels[voice];
auto& op = channel.operators[index];
@ -101,14 +99,14 @@ auto YM2612::writeData(uint8 data) -> void {
case 0x030: {
op.multiple = data.bits(0,3);
op.detune = data.bits(4,6);
channel.updatePhase(index);
channel[index].updatePhase();
break;
}
//total level
case 0x040: {
op.totalLevel = data.bits(0,6);
channel.updateLevel(index);
channel[index].updateLevel();
break;
}
@ -116,8 +114,8 @@ auto YM2612::writeData(uint8 data) -> void {
case 0x050: {
op.envelope.attackRate = data.bits(0,4);
op.envelope.keyScale = data.bits(6,7);
channel.updateEnvelope(index);
channel.updatePhase(index);
channel[index].updateEnvelope();
channel[index].updatePhase();
break;
}
@ -125,15 +123,15 @@ auto YM2612::writeData(uint8 data) -> void {
case 0x060: {
op.envelope.decayRate = data.bits(0,4);
op.lfoEnable = data.bit(7);
channel.updateEnvelope(index);
channel.updateLevel(index);
channel[index].updateEnvelope();
channel[index].updateLevel();
break;
}
//sustain rate
case 0x070: {
op.envelope.sustainRate = data.bits(0,4);
channel.updateEnvelope(index);
channel[index].updateEnvelope();
break;
}
@ -141,14 +139,14 @@ auto YM2612::writeData(uint8 data) -> void {
case 0x080: {
op.envelope.releaseRate = data.bits(0,3) << 1 | 1;
op.envelope.sustainLevel = data.bits(4,7);
channel.updateEnvelope(index);
channel[index].updateEnvelope();
break;
}
//SSG-EG
case 0x090: {
op.ssg.hold = data.bit(0);
op.ssg.pingPong = data.bit(1);
op.ssg.alternate = data.bit(1);
op.ssg.attack = data.bit(2);
op.ssg.enable = data.bit(3);
break;
@ -160,33 +158,37 @@ auto YM2612::writeData(uint8 data) -> void {
//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);
channel[3].pitch.reload = channel[3].pitch.latch | data;
channel[3].octave.reload = channel[3].octave.latch;
for(auto index : range(4)) channel[index].updatePitch();
break;
}
//pitch (high)
case 0x0a4: {
channel.operators[0].pitch.latch = data << 8;
channel.operators[0].octave.latch = data >> 3;
channel[3].pitch.latch = data << 8;
channel[3].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);
if(io.address == 0x0a9) voice = 0;
if(io.address == 0x0aa) voice = 1;
if(io.address == 0x0a8) voice = 2;
channels[2][voice].pitch.value = channels[2][voice].pitch.latch | data;
channels[2][voice].octave.value = channels[2][voice].octave.latch;
channels[2][voice].updatePitch();
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;
if(io.address == 0x0ad) voice = 0;
if(io.address == 0x0ae) voice = 1;
if(io.address == 0x0ac) voice = 2;
channels[2][voice].pitch.latch = data << 8;
channels[2][voice].octave.latch = data >> 3;
break;
}
@ -204,8 +206,8 @@ auto YM2612::writeData(uint8 data) -> void {
channel.rightEnable = data.bit(6);
channel.leftEnable = data.bit(7);
for(auto index : range(4)) {
channel.updateLevel(index);
channel.updatePhase(index);
channel[index].updateLevel();
channel[index].updatePhase();
}
break;
}

View File

@ -22,9 +22,9 @@ auto YM2612::main() -> void {
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
for(auto& op : channel.operators) {
op.updatePhase(); //due to vibrato
op.updateLevel(); //due to tremolo
}
}
}
@ -32,17 +32,13 @@ auto YM2612::main() -> void {
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);
}
}
for(auto& channel : channels) {
for(auto& op : channel.operators) {
op.runPhase();
if(envelope.divider) continue;
op.runEnvelope();
}
}
@ -60,14 +56,14 @@ auto YM2612::sample() -> void {
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 old = [&](uint n) -> int { return op[n].prior & modMask; };
auto mod = [&](uint n) -> int { return op[n].output & modMask; };
auto out = [&](uint n) -> int { return op[n].output & sumMask; };
auto wave = [&](uint n, int modulation) -> int {
int x = modulation / 2 + op[n].phase.value / 0x400;
auto wave = [&](uint n, uint modulation) -> int {
int x = (modulation >> 1) + (op[n].phase.value >> 10);
int y = sine[x & 0x3ff] + op[n].outputLevel;
return y < 0x2000 ? pow2[y & 0x1ff] << 2 >> y / 0x200 : 0;
return y < 0x2000 ? pow2[y & 0x1ff] << 2 >> (y >> 9) : 0;
};
int feedback = modMask & op[0].output + op[0].prior >> 9 - channel.feedback;
@ -142,7 +138,7 @@ auto YM2612::sample() -> void {
}
int voiceData = outMask & min(max(accumulator, -0x1ffff), +0x1ffff);
if(dac.enable && (&channel == &channels[5])) voiceData = dac.sample - 0x80 << 5;
if(dac.enable && (&channel == &channels[5])) voiceData = dac.sample << 6;
if(channel.leftEnable ) left += voiceData;
if(channel.rightEnable) right += voiceData;
@ -178,21 +174,21 @@ auto YM2612::power() -> void {
timerB.power();
for(auto& channel : channels) channel.power();
const int pos = 0;
const int neg = 1;
const uint positive = 0;
const uint negative = 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);
int y = -256 * log(sin((2 * x + 1) * Math::Pi / 1024)) / log(2) + 0.5;
sine[0x000 + x] = positive + (y << 1);
sine[0x1ff - x] = positive + (y << 1);
sine[0x200 + x] = negative + (y << 1);
sine[0x3ff - x] = negative + (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
int z = 1024 * pow(2, (0xff - y) / 256.0) + 0.5;
pow2[positive + (y << 1)] = +z;
pow2[negative + (y << 1)] = ~z; //not -z
}
}

View File

@ -66,15 +66,6 @@ private:
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;
@ -88,6 +79,20 @@ private:
uint2 mode;
struct Operator {
Channel& channel;
Operator(Channel& channel) : channel(channel) {}
//channel.cpp
auto trigger(bool) -> void;
auto runEnvelope() -> void;
auto runPhase() -> void;
auto updateEnvelope() -> void;
auto updatePitch() -> void;
auto updatePhase() -> void;
auto updateLevel() -> void;
bool keyOn;
bool lfoEnable;
uint3 detune;
@ -117,9 +122,9 @@ private:
struct Envelope {
uint state;
uint rate;
uint divider;
uint steps;
int rate;
int divider;
uint32 steps;
uint10 value;
uint2 keyScale;
@ -133,11 +138,13 @@ private:
struct SSG {
bool enable;
bool attack;
bool pingPong;
bool alternate;
bool hold;
bool invert;
} ssg;
} operators[4];
} operators[4]{*this, *this, *this, *this};
auto operator[](uint2 index) -> Operator& { return operators[index]; }
} channels[6];
uint16 sine[0x400];

View File

@ -7,7 +7,7 @@ auto PSG::Noise::run() -> void {
if(rate == 3) counter = pitch; //shared with tone2
if(clock ^= 1) { //0->1 transition
output = lfsr.bit(0);
output = !lfsr.bit(0);
lfsr = (lfsr.bit(0) ^ (lfsr.bit(3) & enable)) << 15 | lfsr >> 1;
}
}

View File

@ -24,8 +24,8 @@ auto PSG::main() -> void {
if(tone2.output && tone2.left) left += levels[tone2.volume];
if(noise.output && noise.left) left += levels[noise.volume];
lowpassLeft += (left - lowpassLeft) * 20.0 / 256.0;
left = left * 2.0 / 6.0 + lowpassLeft * 3.0 / 4.0;
lowpassLeft += (left - lowpassLeft) * 20 / 256;
left = left * 2 / 6 + lowpassLeft * 3 / 4;
left = sclamp<16>(left - 32768);
int right = 0;
@ -34,8 +34,8 @@ auto PSG::main() -> void {
if(tone2.output && tone2.right) right += levels[tone2.volume];
if(noise.output && noise.right) right += levels[noise.volume];
lowpassRight += (right - lowpassRight) * 20.0 / 256.0;
right = right * 2.0 / 6.0 + lowpassRight * 3.0 / 4.0;
lowpassRight += (right - lowpassRight) * 20 / 256;
right = right * 2 / 6 + lowpassRight * 3 / 4;
right = sclamp<16>(right - 32768);
stream->sample(left / 32768.0, right / 32768.0);

View File

@ -1,4 +1,4 @@
//TI SN76489
//TI SN76489 (derivative)
struct PSG : Thread {
shared_pointer<Emulator::Stream> stream;