AY-3-8910 / YM2149 emulation

Discussion of development of software for any "obsolete" computer or video game system. See the WSdev wiki and ObscureDev wiki for more information on certain platforms.
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

AY-3-8910 / YM2149 emulation

Post by Near »

Recently implemented this chip for an MSX core, but for some reason something else is wrong so I can't test it. Figured I'd ask here in case anyone was familiar with it.

I'm going off the official manual, plus notes from MAME's implementation:
https://github.com/mamedev/mame/blob/ma ... ay8910.cpp

It says the counters increment instead of decrement, easy enough:

Code: Select all

auto PSG::Tone::clock() -> void {
  if(++counter < period) return;
  counter = 0;
  phase ^= 1;
}

//note: lfsr.bit(n) == (lfsr >> n & 1)
auto PSG::Noise::clock() -> void {
  if(++counter < period) return;
  counter = 0;
  if(phase ^= 1) lfsr = (lfsr.bit(0) ^ lfsr.bit(3)) << 16 | lfsr >> 1;
}
The envelopes get tricky ... MAME is doing all this weird stuff to reduce cases:
https://github.com/mamedev/mame/blob/ma ... .cpp#L1129

Too evil. So I just went off of the official PDF instead and came up with this:

Code: Select all

auto PSG::Envelope::clock() -> void {
  if(holding) return;
  if(++counter < period) return;
  counter = 0;
  if(!attacking) {
    if(phase !=  0) return (void)phase--;
  } else {
    if(phase != 15) return (void)phase++;
  }
  if(!repeat) {
    phase = 0;
    holding = 1;
  } else if(hold) {
    if(alternate) phase ^= 15;
    holding = 1;
  } else if(alternate) {
    attacking ^= 1;
    if(!attacking) {
      phase--;
    } else {
      phase++;
    }
  } else {
    phase = !attacking ? 15 : 0;
  }
}

//called when writing to the envelope mode register
auto PSG::Envelope::reload() -> void {
  holding = 0;
  attacking = attack;
  phase = !attacking ? 15 : 0;
}
Idea was, if we are attacking, and we count from 0 to 15, we should allow the value to actually be 15 for a full cycle, rather than immediately load in a new value once incrementing to 15. I don't see how/if MAME is doing this, but it seems logical.

If we're not in continue mode (I call it repeat because continue is a C++ keyword), the PDF says the output drops to 0. So that'd give us:
0,1,2,3,...14,15,0,0,0,0,... or
15,14,13,...,2,1,0,0,0,0

Next, if it's hold mode, we freeze the envelope output (phase) indefinitely. And this also allows to alternate/invert the final output:
0,1,2,...,14,15,0,0,0 or
15,14,13,...,2,1,0,15,15,15...

Next, if it's just alternating, then we flip the attack value (which I've cached to attacking since you can read back the written register values), and so that it looks like a proper triangle wave instead of repeating, we force an increment/decrement after alternating between attack/decay, so:
0,1,2,...,13,14,15,14,13,12,...2,1,0,1,2,...
Whereas without the extra inc/dec we'd end up with:
0,1,2,...,13,14,15,15,14,13,12,...,2,1,0,0,1,2,...

Finally, the last case are the sawtooth style waves where it just keeps repeating:
0,1,2,...13,14,15,0,1,2,...,13,14,15,0,1,2,...

I think this is all correct. Not sure about I/O register writes:

Code: Select all

auto PSG::write(uint8 data) -> void {
  switch(io.register) {
  case  0:
    toneA.period.bits(0, 7) = data.bits(0,7);
    break;
  case  1:
    toneA.period.bits(8,11) = data.bits(0,3);
    toneA.unused.bits(0, 3) = data.bits(4,7);
    break;
  case  2:
    toneB.period.bits(0, 7) = data.bits(0,7);
    break;
  case  3:
    toneB.period.bits(8,11) = data.bits(0,3);
    toneB.unused.bits(0, 3) = data.bits(4,7);
    break;
  case  4:
    toneC.period.bits(0, 7) = data.bits(0,7);
    break;
  case  5:
    toneC.period.bits(8,11) = data.bits(0,3);
    toneC.unused.bits(0, 3) = data.bits(4,7);
    break;
  case  6:
    noise.period = data.bits(0,4);
    noise.unused = data.bits(5,7);
    break;
  case  7:
    channelA.tone  = data.bit(0);
    channelB.tone  = data.bit(1);
    channelC.tone  = data.bit(2);
    channelA.noise = data.bit(3);
    channelB.noise = data.bit(4);
    channelC.noise = data.bit(5);
    io.portA       = data.bit(6);
    io.portB       = data.bit(7);
    break;
  case  8:
    channelA.amplitude = data.bits(0,3);
    channelA.envelope  = data.bit (4);
    channelA.unused    = data.bits(5,7);
    break;
  case  9:
    channelB.amplitude = data.bits(0,3);
    channelB.envelope  = data.bit (4);
    channelB.unused    = data.bits(5,7);
    break;
  case 10:
    channelC.amplitude = data.bits(0,3);
    channelC.envelope  = data.bit (4);
    channelC.unused    = data.bits(5,7);
    break;
  case 11:
    envelope.period.bits(0, 7) = data.bits(0,7);
    break;
  case 12:
    envelope.period.bits(8,15) = data.bits(0,7);
    break;
  case 13:
    envelope.hold      = data.bit (0);
    envelope.alternate = data.bit (1);
    envelope.attack    = data.bit (2);
    envelope.repeat    = data.bit (3);
    envelope.unused    = data.bits(4,7);
    envelope.reload();
    break;
  }  //14,15, I/O ports not implemented yet
}
Do any of these register writes reset the counters to zero, eg for tone, noise, or envelope?

Next up is the mixing. The manual says you can enable tone and/or noise on each channel, so I came up with this:

Code: Select all

auto PSG::mix() -> double {
  double output = 0.0;
  if((toneA.phase && !channelA.tone) || (noise.lfsr.bit(0) && !channelA.noise)) {
    output += amplitudes[channelA.envelope ? envelope.phase : channelA.amplitude];
  }
  if((toneB.phase && !channelB.tone) || (noise.lfsr.bit(0) && !channelB.noise)) {
    output += amplitudes[channelB.envelope ? envelope.phase : channelB.amplitude];
  }
  if((toneC.phase && !channelC.tone) || (noise.lfsr.bit(0) && !channelC.noise)) {
    output += amplitudes[channelC.envelope ? envelope.phase : channelC.amplitude];
  }
  return output / 3.0;
}
However, MAME selects channels like this:

Code: Select all

m_vol_enabled[chan] = (m_output[chan] | TONE_ENABLEQ(chan)) & (NOISE_OUTPUT() | NOISE_ENABLEQ(chan));
In my code it'd be:

Code: Select all

(toneN.phase | channelN.tone) & (noise.lfsr.bit(0) | channelN.noise);
The enable bits are inverted, so I'm almost certain these two are equivalent, but I think my way is easier to understand, though I may rename the variables, so it's && !channelC.toneDisable or whatnot.

Finally, the last issue is that every last AY-3-8910 implementation has different logarithm values.

MAME has a hardware measurement table here:
https://github.com/mamedev/mame/blob/ma ... 10.cpp#L37

Then later it says:
vol(i) = exp(i/2-7.5)

Then later we have these measurements:
https://github.com/mamedev/mame/blob/ma ... 0.cpp#L722

Other AY-3-8910 cores have different volume scales as well. None of them match. Which values should I be using for the MSX1?

Now getting into MAME notes more:
The main difference between the AY-3-8910 and the YM2149 is, that the
AY-3-8910 datasheet mentions, that fixed volume level 0, which is set by
registers 8 to 10 is "channel off". The YM2149 mentions, that the generated
signal has a 2V DC component. This is confirmed by measurements. The approach
taken here is to assume the 2V DC offset for all outputs for the YM2149.
For the AY-3-8910, an offset is used if envelope is active for a channel.
This is backed by oscilloscope pictures from the datasheet. If a fixed volume
is set, i.e. envelope is disabled, the output voltage is set to 0V. Recordings
I found on the web for gyruss indicate, that the AY-3-8910 offset should
be around 0.2V. This will also make sound levels more compatible with
user observations for scramble.
I ... don't understand any of that.

So it's saying that whenever the fixed volume for the channels is set to 0, it means the channel is off.
What does that mean? The counters won't run? But they do on the YM2149?
MAME is only actually using this via zero_is_off in build_3D_table which looks ... insanely complicated.
Reminds me a bit of the NES where the volume of one channel biases the volume of other channels. Is that what this is?
And then the YM2149 has a 2V DC offset ... so a fixed amount to add/subtract by ... only for a fixed volume of zero, or for any volume?
How do I convert a 2VDC offset to an amount to add/subtract by? Is it positive or negative?
And then it's saying ... the AY-3-8910 only gets the offset if the envelope is enabled, and it's 0.2VDC here?
Should I even bother with this? My code runs a 20hz highpass filter on the final output to remove any DC bias anyway.

What is the actual frequency of this chip? The PDF says the counters run on a clock divider of 16, so I've just been doing 3.58MHz/16, and each step at that frequency, clocking the tones/noise/envelope by one.

Then there's this note:
Also, note that period = 0 is the same as period = 1. This is mentioned
in the YM2203 data sheets. However, this does NOT apply to the Envelope
period. In that case, period = 0 is half as period = 1.
As far as I see, MAME isn't doing anything different between the tone/noise/envelope periods, all three are:

Code: Select all

counter++;
if(counter >= period) ...
As a result, period 0 is the same as period 1 even for envelopes:
https://github.com/mamedev/mame/blob/ma ... .cpp#L1117

What am I missing there?

... finally, I feel it's best to separate the AY-3-8910 and YM2149 implementations, so for now mine is just strictly AY-3-8910.
It's only a hundred lines of code or so to duplicate, which seems more sensible to me than having m_step, m_env_step_mask, etc.
The big question I'll have for YM2149 is ... if the envelopes now have 32 states, then that means we need a 32-state logarithmic volume table. So, would anyone happen to have actual values for such a table?

Thanks in advance if anyone is able to answer any of these questions ^-^
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by tepples »

Here's my guess in Python:

Code: Select all

# for SN76489
num_steps = 16
decibels_per_step = 2.0

# for AY-3-8910
num_steps = 16
decibels_per_step = 3.0

# for YM2149
num_steps = 32
decibels_per_step = 1.5

# formula follows
full_scale = 8000
[0] + [int(round(full_scale * pow(10, x * decibels_per_step / 20.))) for x in range(2 - num_steps, 1)]

# result for YM2149
# [0, 45, 53, 64, 76, 90, 107, 127, 151, 179, 213, 253, 301, 357, 425, 505, 600, 713, 847, 1007, 1197, 1423, 1691, 2010, 2388, 2839, 3374, 4009, 4765, 5664, 6731, 8000]
Another angle is to ask NSFPlay maintainer rainwarrior about volumes in Sunsoft 5B, which contains a YM2149, either here or on the FamiTracker.org Discord server.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by rainwarrior »

I don't have all the details on instant recall, the best I could say is that NSFPlay's implementation was inherited from Mitsutaka Okazaki's original version, and subsequently modified to more closely match the 5B by myself. I've done no tests of an AY-3-8910 to compare, nor of a plain YM2149.

You made a lot of comments that don't look like a question (e.g. your envelope is probably fine, alternating mode should have 15,15 and 0,0 in it, etc.) so I might not comment much there.
byuu wrote:Do any of these register writes reset the counters to zero, eg for tone, noise, or envelope?
Only the envelope has a phase reset. That should be very explicitly described in its datasheet.

The rest are permanently in an indeterminate phase, though there's a "trick" of setting the period register to 0 briefly then setting back to an audible period, which will effectively "reset" the phase to either 0 or 180 degrees. Not sure how useful that is, but maybe a 50/50 sync would be better than nothing for some purpose.
byuu wrote:Finally, the last issue is that every last AY-3-8910 implementation has different logarithm values.
In my experience with the 5B, the output does seem to match the logarithmic curve described by the datasheet, but then followed by its own internal amplifier which has an unfortunate nonlinear curve. I was investigating that in this thread, though the 5B's amplifier is unique and unrelated to the underlying AY/YM component, AFAIK.

So... on another system hosting an AY/YM you might be dealing with a second amplifier that messes with the curve, which could account for people wanting to tweak the values like that.
byuu wrote:
The main difference between the AY-3-8910 and the YM2149 is, that the
AY-3-8910 datasheet mentions, that fixed volume level 0, which is set by
registers 8 to 10 is "channel off". The YM2149 mentions, that the generated
signal has a 2V DC component. This is confirmed by measurements. The approach
taken here is to assume the 2V DC offset for all outputs for the YM2149.
For the AY-3-8910, an offset is used if envelope is active for a channel.
This is backed by oscilloscope pictures from the datasheet. If a fixed volume
is set, i.e. envelope is disabled, the output voltage is set to 0V. Recordings
I found on the web for gyruss indicate, that the AY-3-8910 offset should
be around 0.2V. This will also make sound levels more compatible with
user observations for scramble.
I ... don't understand any of that.
I'm not certain how to interpret that either. Since I've only worked with the 5B and not a YM directly, its own amplifier would remove DC bias too. If the goal is to emulate audio, I don't know that it's relevant, since we can't hear DC offset (...and if you do your tests with audio hardware and not an oscilloscope like I usually do, that device will probably filter it off too).

In dealing with the logarithmic curve, to produce absolute values you need to set some arbitrary nonzero output for volume "0" that can be multiplied against. I don't know if that would be related to the DC bias, but it's something that I needed to account for in my model. I guess this is baked into the log table output used by the emulation.
byuu wrote:What is the actual frequency of this chip? The PDF says the counters run on a clock divider of 16, so I've just been doing 3.58MHz/16, and each step at that frequency, clocking the tones/noise/envelope by one.
It has an external clock, so the frequency depends where it's hosted. The YM2419 also has a second divider that can be turned on and off that halves the tone resolution but doubles the envelope resolution (the 5B operates this way, using the Famicom's CPU clock 1.79MHz). What I know is described here.

Edit: it maybe halves the envelope too. At current I can only state that the 5B works as described on the wiki. I don't know for certain what happens when SEL is high/nc on a YM2149.
byuu wrote:Then there's this note:
Also, note that period = 0 is the same as period = 1. This is mentioned
in the YM2203 data sheets. However, this does NOT apply to the Envelope
period. In that case, period = 0 is half as period = 1.
Yes, the 0=1 thing seems to be true. It is a bit bizarre to me. I have verified that 0 is not a halt and it does keep changing phase during.

The envelope I'm not sure about. MAME's implementation seems to suggest that what I have in NSFPlay is off by 1 for the envelopes. This is something I will have to test. I believe I tried to make sure the frequencies matched in my earlier tests, so I'd be very surprised to be off by 1, but it is something I need to test again, so I can't comment on that yet, sorry.
byuu wrote:The big question I'll have for YM2149 is ... if the envelopes now have 32 states, then that means we need a 32-state logarithmic volume table. So, would anyone happen to have actual values for such a table?
Like above, I think the table output may be some compromised combination of the true output + some external amplifier. Not sure though, as I only know about 5B really.
Last edited by rainwarrior on Sat Mar 30, 2019 6:52 pm, edited 1 time in total.
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Re: AY-3-8910 / YM2149 emulation

Post by Near »

Oh wow, I completely forgot the 5B was a YM2149F! I guess I might as well refactor that code to support the envelope now.

> You made a lot of comments that don't look like a question

Mostly just in case I made an obvious mistake someone might spot. Never know.

> (e.g. your envelope is probably fine, alternating mode should have 15,15 and 0,0 in it, etc.)

Wait, it *should* have that? I expected it to be 14,15,14 and 1,0,1. Not 14,15,15,14 and 1,0,0,1.

The former gives you an actual triangle, the latter will not.

> Only the envelope has a phase reset.

For the 16-bit period/counter as well, or for just the 4/5-bit output that counts after each period reload?

> In my experience with the 5B, the output does seem to match the logarithmic curve described by the datasheet, but then followed by its own internal amplifier which has an unfortunate nonlinear curve.

Yeah, I'm thinking my base class should just output per-channel 4/5-bit samples. Then each implementation can mix freely. Should also allow support Game Gear's SN76489 "stereo" mode.

> I don't know that it's relevant, since we can't hear DC offset

All it really does is distort other audio applications on your host system that happen to be mixed in. So even though your emulator audio appears silent, your MP3 player gets garbled because the emulator's DC offset crushes the available audio range. So in effect we really, really want to get rid of DC offset in emulators.

That's the limit of my understanding of it. I don't even understand how a high-pass filter removes it.

> The YM2419 also has a second divider that can be turned on and off that halves the tone resolution but doubles the envelope resolution

I mean, it's one or the other, right? You could think of it as halving the tone, or doubling the envelope resolution, but not both? Or have I misunderstood it?

> I can't comment on that yet, sorry.

No worries, and no rush. We can always fix things later. Thank you for your reply!
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: AY-3-8910 / YM2149 emulation

Post by lidnariq »

byuu wrote:Wait, it *should* have that? I expected it to be 14,15,14 and 1,0,1. Not 14,15,15,14 and 1,0,0,1.

The former gives you an actual triangle, the latter will not.
Pedantically they're both real triangles: the 14,15,15,14 waveform ("x" below) is a half-sample delay of a 14.5,15.5,14.5,13.5 ("y"):

x 14
y 14.5
x 15
y 15.5
x 15
y 14.5
x 14
y 13.5
That's the limit of my understanding of it. I don't even understand how a high-pass filter removes it.
The simplest highpass filter is subtracting the previously generated sample from the current one. This would be written y[n]=x[n]-x[n-1]

Say the generated audio as audible and DC components (x[n]=a[n]+d). The DC component can't vary with time, otherwise it would be audible. When you subtract the generated samples, the DC component is subtracted away from itself (a[n]+d-a[n-1]-d).
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by rainwarrior »

byuu wrote:Wait, it *should* have that? I expected it to be 14,15,14 and 1,0,1. Not 14,15,15,14 and 1,0,0,1.
Well, the alternative seems like it would be that one of the envelope modes has a very different period length than the others?
byuu wrote:The former gives you an actual triangle, the latter will not.
If it's any consolation, with the logarithmic output it's not really much of a triangle either way.
byuu wrote:
rainwarrior wrote:Only the envelope has a phase reset.
For the 16-bit period/counter as well, or for just the 4/5-bit output that counts after each period reload?
It should reset the whole envelope generator. I don't remember ever seeing any evidence that the first tick was shortened.
byuu wrote:All (DC offset) really does is distort other audio applications on your host system that happen to be mixed in. So even though your emulator audio appears silent, your MP3 player gets garbled because the emulator's DC offset crushes the available audio range. So in effect we really, really want to get rid of DC offset in emulators.
There are some other cases where a DC offset on an output will cause audible effects on some system it's being played through, but none of them have anything to do with emulating the sound of a chip like this.

If something has a constant DC offset, it should not be emulated at all. However, if it's a DC offset that can be turned on and off by some state of the chip, that will make an audible difference when it changes, and that's something that should be emulated. I couldn't quite tell from the description you pasted whether it's suggesting that the AY shifts its DC offset when you use envelopes? It's a bit muddled to me. In my experience with the 5B, envelope 0 seemed to be identical to volume 0, but it's possible that's not the case for AY, based on what that seems to be saying?
byuu wrote:That's the limit of my understanding of it. I don't even understand how a high-pass filter removes it.
A DC offset is the same as an infinitely low frequency, for the purposes of a high-pass filter.

The other way to think of a high pass filter is that it's a circuit that automatically counter-balances the incoming signal to return it to zero, but it has a delay (exponential decay) that makes it happen slowly. The slower it is, the weaker the filter, and fast changes will pass right through it before it can affect them much, but nothing's slower than a constant DC offset.

(Nothing really wrong with emulating a DC offset higher up in the chain as long as there's a highpass that removes it before the final output, but there's also no reason to add a constant if all you're doing to do is remove it.)
byuu wrote:
rainwarrior wrote:The YM2419 also has a second divider that can be turned on and off that halves the tone resolution but doubles the envelope resolution
I mean, it's one or the other, right? You could think of it as halving the tone, or doubling the envelope resolution, but not both? Or have I misunderstood it?
With this mode enabled, the tone/noise will run at half speed, and so will the envelope, but the envelope will be twice as smooth.

It's as if you were using an AY at half the clock rate, and the envelope had some magic internal 2x interpolation.

Edit: this might be wrong. The SEL might halve the envelope too. Perhaps the YM always has 32 steps? Not sure. The 5B has the clock relationship I described on the wiki, but I don't know what SEL high/nc does for sure.
Last edited by rainwarrior on Sat Mar 30, 2019 6:54 pm, edited 1 time in total.
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Re: AY-3-8910 / YM2149 emulation

Post by Near »

> Well, the alternative seems like it would be that one of the envelope modes has a very different period length than the others?

I mean, I don't actually know which it is. Does anyone know for sure which one it is? MAME's code is too clever with using a signed 8-bit envelope phase and using d6 as a toggle, so it's hard to reason about, but it looks to me like MAME is likely doing 14,15,15,14.

Whichever it is is fine, I'll emulate it how it's supposed to be emulated ^-^

> It should reset the whole envelope generator. I don't remember ever seeing any evidence that the first tick was shortened.

Very interesting! MAME doesn't emulate that in that case. It only resets the 5-bit phase:
https://github.com/mamedev/mame/blob/ma ... .cpp#L1006

> However, if it's a DC offset that can be turned on and off by some state of the chip, that will make an audible difference when it changes, and that's something that should be emulated

Oh boy, that's a good point. Even for my obsessiveness with accuracy, I'd have to make something like that an option. Not only does it wreck other audio playing on your system, it'd cause audible popping when you turn the sound chip on and off programmatically (which you have to do often with the Neo Geo Pocket to switch between PSG and DAC mode, for instance.)

> A DC offset is the same as an infinitely low frequency, for the purposes of a high-pass filter.

I mean, that makes sense for why the filter would work on that, and why even a high-pass of 5hz is enough, but it's not clear to me how outputting an 8-bit unsigned audio sample of 0x40 (so a 1.25V DC offset) always is equal to an infinitely low frequency. It's not any frequency at all, it's 0hz. It's unchanging. It's infinitely low in the same way that 0/1 has an infinite remainder, but division by zero is not infinity at all. It's just undefined.

Anyway it's not important I understand it, thank you both for explaining the concepts a bit though :D

I used to not understand audio stuff at all, and slowly but surely it's starting to make sense and I can implement PSG chips pretty easily, save for the fine details that aren't documented well. Hopefully one day I can do the same with FM chips.

> It's as if you were using an AY at half the clock rate, and the envelope had some magic internal 2x interpolation.

Got it, thanks. Yeah, I'm going to implement YM2149(F) separately. I think I have enough for the initial implementation.

A shame Gimmick doesn't use the envelopes. Would be a great test case.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by tepples »

byuu wrote:division by zero is not infinity at all. It's just undefined.
This is true in real numbers. However, division by zero in the projective reals produces a number called "infinity". This number appears, for example, as the tangent of tau/4 radians or 90 degrees. The extended reals, by contrast, have two "infinity" numbers, one at each end of the real line; IEEE 754 floating point infinity behavior models extended reals.
byuu wrote:A shame Gimmick doesn't use the envelopes. Would be a great test case.
There are plenty of other test cases among what MSX, ZX Spectrum 128K, Atari ST, and FamiTracker composers have done.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by rainwarrior »

byuu wrote:I mean, I don't actually know which it is. Does anyone know for sure which one it is? MAME's code is too clever with using a signed 8-bit envelope phase and using d6 as a toggle, so it's hard to reason about, but it looks to me like MAME is likely doing 14,15,15,14.
Yes, what you think MAME is likely doing is in fact the correct thing.
byuu wrote:MAME doesn't emulate that in that case. It only resets the 5-bit phase.
Thanks for pointing it out. That's not how I have it implemented, but I will make a note to test this later. (I would generally assume MAME to be correct here, their sources are newer than the implementation I was building on, but it never hurts to verify.)
byuu wrote:I mean, that makes sense for why the filter would work on that, and why even a high-pass of 5hz is enough, but it's not clear to me how outputting an 8-bit unsigned audio sample of 0x40 (so a 1.25V DC offset) always is equal to an infinitely low frequency. It's not any frequency at all, it's 0hz. It's unchanging. It's infinitely low in the same way that 0/1 has an infinite remainder, but division by zero is not infinity at all. It's just undefined.
Well, in the context of a high-pass filter, it's not really like division by zero. It's more like multiplying by zero. The attenuation at 0.00000000001Hz is going to be only very slightly different from the attenuation at "0Hz" (DC constant), and for a slice of time that's not long enough to see the value rising and falling, you wouldn't be able to tell the difference between the two. The transition all the way down to 0 is clean and continuous.

I think it works out conveniently like this in a bunch of related places like in a Fourier Transform the DC offset will end up described as 0Hz as well.
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Re: AY-3-8910 / YM2149 emulation

Post by Near »

Oh boy, the YM2149 is even more fun.

MAME sets m_step=2 for the AY, m_step=1 for the YM, then does:

Code: Select all

if(++m_ctr >= envelope_counter * m_step) {
  m_ctr=0;
  //step the envelope phase
}
Which means that envelopes will take twice as long to step one phase on the YM, not half as long. And now that there's 32 phases instead of 16, that would effectively make it four times slower. It doesn't change anything to do with the tone/noise periods in YM mode. Even if you clock the YM externally twice as often to compensate, that's still going to be twice as slow, not twice as fast. You'd have to clock it eight times faster to actually make the envelopes run compatibly (save for the smoother steps) to the AY.

I also get conflicting answers on whether the YM2149 adds 15 additional steps (so 0-30 or 1-31) or 16. Internet forum posts say 15, MAME says the steps are doubled, which would be 16. Logically, it needs to be doubled, otherwise it's not going to be directly backward compatible.

> There are plenty of other test cases among what MSX, ZX Spectrum 128K, Atari ST, and FamiTracker composers have done.

Writing a small partial VGM music player may well be a good idea, yeah.

> Yes, what you think MAME is likely doing is in fact the correct thing.

Wild. Okay, thank you.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by rainwarrior »

byuu wrote:Which means that envelopes will take twice as long to step one phase on the YM, not half as long. And now that there's 32 phases instead of 16, that would effectively make it four times slower. It doesn't change anything to do with the tone/noise periods in YM mode. Even if you clock the YM externally twice as often to compensate, that's still going to be twice as slow, not twice as fast. You'd have to clock it eight times faster to actually make the envelopes run compatibly (save for the smoother steps) to the AY.
I can't tell you how to read whatever MAME is doing, but that's not what the YM does. What I said above is correct, I don't think it's possible that I was off by a factor of 2 or 4 for this. That would have been noticed.

Though maybe worth saying that YM does have two modes of operation, which are controlled by a pin (SEL in the datasheet). With the pin high, it's supposed to be backward compatible with the AY with 16 step envelopes (...I think. Have not personally tested/verified.) With the SEL pin low, it's what I described. This probably wouldn't be software switchable (though theoretically the host system could build a latch/register for it), but there are two different behaviours, which the emulator might require two modes for.
byuu wrote:I also get conflicting answers on whether the YM2149 adds 15 additional steps (so 0-30 or 1-31) or 16. Internet forum posts say 15, MAME says the steps are doubled, which would be 16. Logically, it needs to be doubled, otherwise it's not going to be directly backward compatible.
It has 32 steps.
byuu wrote:
tepples wrote:There are plenty of other test cases among what MSX, ZX Spectrum 128K, Atari ST, and FamiTracker composers have done.
Writing a small partial VGM music player may well be a good idea, yeah.
When I was testing 5B in NSFPlay I ended up translating a bunch of YM music into NSF form to give myself additional tests. (There's a couple of different AY/related formats besides VGM. Dunno which is easiest to parse.)
Last edited by rainwarrior on Thu Mar 28, 2019 2:52 pm, edited 3 times in total.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by rainwarrior »

Might also be worth consulting another source on this. Ay Emul is one I've often compared against, and it's been continually maintained for a long time and is still current.
https://bulba.untergrund.net/main_e.htm

It also has a related Vortex Tracker II project that builds a tracker on top of that emulator, which might be handy for making tests, or just playing with how the chips sound interactively.
Last edited by rainwarrior on Wed Mar 27, 2019 10:01 am, edited 1 time in total.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by tepples »

If you can emulate a Zilog Z80, you can play a couple of these "AY/related formats". (The Z80 is very similar to the SM83 core in the LR35902 coprocessor that higan already emulates for Super Game Boy, except Z80 has two more 16-bit pointer registers for offset addressing (IX and IY) and a second pair of AFBCDEHL, and HRAM instructions instead access a separate I/O bus. I see quite a few Z80 machines among platforms that higan emulates.)

KSS format originates from the MSX community. It's similar to NSF, but it uses a Z80 instead of a second-source MOS 6502. Both KSS and NSF support YM2149 SSG. KSS maps the YM2149 to out [$A0] (command) and out [$A1] (data); NSF maps it to sta $C000 (command) and sta $E000 (data). KSS supports Konami SCC; NSF supports Namco 163, whose capability is similar. KSS supports YM2413 (OPLL); NSF supports VRC7 (OPLL with different patch set and only 6 usable voices).

SGC, from the Coleco and Sega scene, is another Z80-driven NSF-style format. It supports OPLL and TI SN76489 (and Yamaha's clone thereof), but not the AY-3-8910. The SN76489 is a bit more limited; it has a 1/16 duty pulse instead of envelope bass, and it has three hardcoded noise frequencies that can play at the same time as tone instead of mixing noise and tone.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by rainwarrior »

Okay, to follow up on a few questions I didn't have an answer to, I wrote a test (envelope_5b), and have attached a recording if you'd like to see how I verified it.

The following is true of the 5B in my Gimmick! cartridge:

1. The frequencies of the tone and envelope are exactly as currently described on our wiki.

The test plays tones and envelope against matching APU tones, and the wavelengths match perfectly. (They use the same clock, after all.) If the APU frequencies are described correctly, therefore so are the 5B's.

2. Envelope period 0 acts as if the envelope's period is 1.

I don't think I'd tested this before, but using a value of 0 for the envelope's period does not halt it. It will instead behave exactly as if its period value was 1. (I have been told that the square wave tones do this as well, but the resulting frequency is above audible and I haven't verified with a scope.) NSFPlay currently gets this wrong.

Edit: noise does not halt on 0 either, and as far as I can tell from audio recordings noise period 0 acts as period 1. I should make some longer recordings of it later and verify it has the expected LFSR period.

3. Writing to register $0D does reset both components of the envelope's phase.

In the recording I see the first step of the envelope given its full duration every time. It is never shorted. (This is what I expected, and it is what NSFPlay currently does.)
Attachments
envelope_5b.mp3
(834.94 KiB) Downloaded 575 times
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: AY-3-8910 / YM2149 emulation

Post by tepples »

rainwarrior wrote:I don't think I'd tested this before, but using a value of 0 for the envelope's period does not halt it. It will instead behave exactly as if its period value was 1. (I have been told that the square wave tones do this as well, but the resulting frequency is above audible and I haven't verified with a scope.)
Could you make a tone by bit banging the log volume of an ultrasonic (period 0 or 1) wave and then compare the volume of that tone to other volumes you can coax out of the chip?
Post Reply