mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
0bf2c9d4e1
commit
89d47914b9
|
@ -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/";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//TI SN76489
|
||||
//TI SN76489 (derivative)
|
||||
|
||||
struct PSG : Thread {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//TI SN76489
|
||||
//TI SN76489 (derivative)
|
||||
|
||||
struct PSG : Thread {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
|
Loading…
Reference in New Issue