It is currently Thu Aug 24, 2017 3:30 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 14 posts ] 
Author Message
PostPosted: Tue Feb 21, 2017 4:11 am 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1329
Reading through http://md.squee.co/PSG , we found:
Quote:
Note: Any writes to the Attenuators will reset the waveform to high level, as recently found out by TmEE.


But it's also my understanding that volume writes reload the period (tone) counter from what was written to said registers.

Yet if I implement a volume write as always doing that, the audio distortion is pretty bad.

For now, Cydrak came up with this that seems to work well:
Code:
    tone0.volume = data.bits(0,3);
    if(tone0.pitch < 2) {
      tone0.output = 1;
      tone0.counter = tone0.pitch;
    }


... but is that accurate? I'm thinking that maybe ... the counter isn't reloaded on volume writes?

Also, what about noise channel writes? Does that also raise the output to 1?

Next up, there is this: http://www.smspower.org/Development/SN7 ... ectSN76489

I'm not really entirely sure how to emulate this.

For now, Cydrak has a lowpass filter in place:

Code:
  lowpassLeft += (left - lowpassLeft) * 20.0 / 256.0;
  left = left * 2.0 / 6.0 + lowpassLeft * 3.0 / 4.0;
  left = sclamp<16>(left);
  //same for right channel ... Master System mode is of course monaural


But I doubt that's enough to simulate that waveform.

What's the best technique for creating more faithful reproduction of the pictured imperfection in square waves?

Lastly ... all of the documentation refers to the PSG running at 3.58MHz ... but unless I run it at 1/16th that speed, or 223.7KHz, then the pitch is way off and the audio sounds terrible.

Am I doing something wrong, or if not, why don't the docs mention there's a /16 divider for clocking the tone/noise channels?

Many thanks in advance to anyone who can help shed some light onto these remaining mysteries :)


Top
 Profile  
 
PostPosted: Tue Feb 21, 2017 5:10 am 
Offline
User avatar

Joined: Wed Feb 13, 2008 9:10 am
Posts: 562
Location: Estonia, Rapla city (50 and 60Hz compatible :P)
The note that volume writes reset waveform is not correct, I misinterpreted my readings (I just had got an oscilloscope at that time and didn't know how to use it properly yet).
I recall setting lowest freq (register value 0) and just spammed non changing attenuator value and didn't observe any change in signal, but I probably didn't look at slow enough timebase. That was really long ago, I will do new tests in not too far future, I got more work to do with VDP for now, there's plenty registers which exact points of taking effect aren't yet determined.

EDIT: Waveform coming out the chip is perfect squarewave, any deviations are from any analog circuitry preset (only some bandpass filtering, low end at few tens of Hz, high end at few tens of KHz, depending on particular hardware). You also shouldn't try to replicate it, your sound card output or other devices in the signal path already do that.

PSG has prescaler in it that divides the clock by 16. Ti sold chips with and without the prescaler. PSG integrated into the VDP (SMS, MD and SG-1000 II) always has prescaler present, and LFSR is different compared to real Ti chip (not sure about SG-1000 II). The last part matters for SG-1000 and SG-3000 vs SMS and MD, alternate mode on noise channel has 1/15% duty cycle on one and 1/16% duty cycle on other, causing things to be out of tune if not compensated for.

_________________
http://www.tmeeco.eu


Top
 Profile  
 
PostPosted: Sat Feb 25, 2017 1:11 am 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1329
Thanks very much for the info!

One more question if possible ...

I'm trying to emulate the noise channel properly. Does this code look correct?

Code:
auto PSG::Noise::run() -> void {
  if(--counter) return;

  if(rate == 0) counter = 0x10;
  if(rate == 1) counter = 0x20;
  if(rate == 2) counter = 0x40;
  if(rate == 3) counter = pitch;  //shared with tone2

  if(clock ^= 1) {  //0->1 transition
    output = lfsr & 1;  //docs say the shifted-out value goes to the output
    auto eor = enable ? ~lfsr >> 3 : 0;  //lfsr is a uint16 type
    lfsr = (lfsr ^ eor) << 15 | lfsr >> 1;
  }
}


run() is called 3.58MHz/16 times per second.

The value in output, when set, outputs the noise channel's volume, otherwise zero.

When you write to the Tone 2 register, I copy the value into the noise class.

For now, when writing to the Noise register, I'm not modifying the counter at all.

I'm also unsure if my LFSR computation is 100% correct. The docs go into testing parity and things like that, but that seems really overly complex. This implementation of the LFSR is courtesy of Cydrak, but I'm not sure where sie got it from.


Last edited by byuu on Mon Feb 27, 2017 7:28 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Mon Feb 27, 2017 9:17 am 
Offline

Joined: Thu Oct 05, 2006 6:29 am
Posts: 910
Why is there a bitwise NOT in there? All descriptions of the SN76489 noise channel I've seen have had the new bit15 set to bit0 XOR bit3 in "white noise" mode. Has someone made recordings where they concluded that it should in fact be bit0 XOR (NOT bit3) ?


Top
 Profile  
 
PostPosted: Mon Feb 27, 2017 7:33 pm 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1329
Cydrak spotted a really important bug in that I was saying if(--counter) return; instead of if(counter--) return;

Lots of games set a period of 0 and expect that to be really short, not as long as possible. Fixes noise in tons of games.

> Why is there a bitwise NOT in there? All descriptions of the SN76489 noise channel I've seen have had the new bit15 set to bit0 XOR bit3 in "white noise" mode.

Hmm, so would you say this is a better strategy?

Code:
bool bit0 = lfsr & 1;
bool bit3 = lfsr >> 3 & enable;  //enable = 1 or 0
lfsr = (bit0 ^ bit3) << 15 | lfsr >> 1;


Or technically with my integer class, I can say:
Code:
lfsr = (lfsr.bit(0) ^ (lfsr.bit(3) & enable)) << 15 | lfsr >> 1;


Top
 Profile  
 
PostPosted: Tue Feb 28, 2017 1:42 am 
Offline

Joined: Thu Oct 05, 2006 6:29 am
Posts: 910
That looks like what I'd expect. Though I haven't made any hardware tests to verify this, so I was curious about that NOT and whether it was based on some recent discovery. If it wasn't then I'd say leave it out for the time being.


Top
 Profile  
 
PostPosted: Wed Mar 01, 2017 8:09 am 
Offline
User avatar

Joined: Wed Feb 13, 2008 9:10 am
Posts: 562
Location: Estonia, Rapla city (50 and 60Hz compatible :P)
I have been testing hardware the entire yesterday and I've found and verified few things.
Things tested :
*SN76489AN in SC-3000
*315-5124 (SMS1 VDP)
*315-5246 (SMS2 VDP)
*315-5313-x (MD1 VDP)
*315-5476/5660/5700/5708/5960 (MD2 ASIC)
*315-6123 (Genesis 3 VA2 ASIC)
I would love to test out Sega SG-1000 II with 315-5066 chip which has VDP and PSG combined. It will be good to verify if it has vanilla PSG or what all other machines got, and also determine if the RGB output is same as SMS VDPs or different.

Freq of $000 acts as $400 on TI chip (MCLK / 32 / $400), producing lowest possible frequency. Decrement counter and then check for zero logic.
On Sega VDPs, freq of $000 acts same as $001, producing (MCLK / 32) output rate, highest possible frequency. Rest works exactly as on TI PSG. Freq of $000 is handled specially, which makes little sense from hardware perspective. I would have liked the extra low freq step, as the chip has very shit lower end freq range to begin with due to being clocked so high.

Noise phase is reset by frequency writes, volume writes have no effect. Noise always starts from low level. By spamming freq writes you can permanently keep noise output at low level.

Noise register is 15 bits on TI chip. Bits 0 and 1 are XORed and output to bit14 of left rotated register. Output is inversion of bit 0.
Upon phase reset, noise reg is reloaded with 100 0000 0000 0000.
Starting bit pattern of noise on TI chip : http://www.tmeeco.eu/SMS/TI%20PSG%20Noi ... ttern.flac

On Sega VDPs the register is 16 bits and bits 0 and 3 XORed and output to bit 15 of left rotated reg, output is still inversion of bit 0. Phase reset reloads noise reg with 1000 0000 0000 0000.
Starting bit pattern of noise on Sega VDP : http://www.tmeeco.eu/SMS/SEGA%20VDP%20P ... ttern.flac

Do notice the gap between start and first pulse, same thing also happens on hardware. Noise output is not instant, suggesting the way I do the noise register is correct.

Alternate noise mode produces 100/15 duty cycle square wave on TI chips and 100/16 duty cycle squares on Sega VDPs. Noise register is rotated left without any XOR magic and output is inversion of bit 0. The implication of this is that you need a new freq table to have noise in tune with tones on TI PSG, while on Sega VDPs noise is exactly 4 octaves lower allowing freq table to be reused.

Tone phase is not reset by either volume or frequency writes on TI chip or Sega VDPs. You can spam either to the chips and nothing happens as far as phase goes. Change applies immediately to current state. This has implications on PCM playback, only sane way to do it is by using highest possible frequency which then averages into something along the lines of DC level due to lowpass filters on the chip output path. This is also part of the reason why PCM playback is very quiet on actual hardware. If noise started at high level you could get reliable DC offset to exploit and get better results for PCM playback.

It is possible to detect TI PSG, as it has READY output which is connected to !WAIT line on Z80 in SC-3000 and if the chip is not ready, CPU is stalled until it is. You can spam writes to the chip and then determine if you have lagged behind or not. There is no such stall mechanism with Sega VDPs. Chip is able to accept one write every sample, I'm not right now sure if Sega VDP can accept data faster than TI chip or it misses the writes, I will have to do some explicit tests to determine that.

Outputs of TI chip behaves as expected, you have 4x log scaled channels that just get summed together. The output has tendency to oscillate at transitions from low to high and high to low, but that is fixed by placing a 150pF or smaller cap from Audio Out pin (9) to the Audio In / NC pin (7). TI chip is made of BJTs not MOS transistors.
Sega VDPs are made of NMOS transistors and require external current sinking resistor and this has some implications on what signals are like. The higher overall output level gets the less the resistor is able to sink current away and the more output flattens out, you do not get equal spacing of summed channels as current is running out. The resistor value used in SMS and MDs is 2.2Kohm, exception is SMS2 which uses an additional resistor to VCC on the output which really destroys 3 top levels of a channel causing totally messed up output :
http://www.tmeeco.eu/SMS/PSGbadLevels.jpg
this has 3 channels playing at the same time doing a volume ramp. The pic was taken a long time ago, my current digital scope is unable to show as clear result unfortunately as it has relatively low sample rate and things are full of aliasing artifacts. My other nice analog scope managed to develop dead tube. Normal, unmessed up output looks like this (just 2.2k resistor): http://www.tmeeco.eu/SMS/PSGgoodLevels.jpg
There is still some non linearity but it is much smaller. By using a lower value resistor (under 470ohm) you start getting output that starts reaching the ideal (like TI chip). NES for example uses 100ohm resistors on its audio outputs to get best possible linearity (albeit it still has problems due to varying DC offsets on different channels). TI, SMS1, SMS2 and MD chips all got differing DC offsets and peak-to-peak output levels. I will do accurate measuerements of all the levels in near future once I write out a good test program for that. It should be noted that while MD2 ASIC is CMOS, the PSG output is still NMOS based and requires sink resistor to function, while YM output is full CMOS based and will not function with sinking resistor in place (which are required for YM2612 which is NMOS).

_________________
http://www.tmeeco.eu


Top
 Profile  
 
PostPosted: Tue Mar 07, 2017 12:37 am 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1329
Fantastic information, thank you! I've made a few corrections, such as flipping bit 0 of the noise channel output now.

One last significant question ...

When it comes to the output bits from each channel, should they behave like this?

Code:
  int output = 0;
  if(tone0.output) output += levels[tone0.volume];
  if(tone1.output) output += levels[tone1.volume];
  if(tone2.output) output += levels[tone2.volume];
  if(noise.output) output += levels[noise.volume];


Or like this?
Code:
  int output = 0;
  output += levels[tone0.volume] * (tone0.output ? +1 : -1);
  output += levels[tone1.volume] * (tone1.output ? +1 : -1);
  output += levels[tone2.volume] * (tone2.output ? +1 : -1);
  output += levels[noise.volume] * (noise.output ? +1 : -1);


In other words, if the bit is low on the output for a channel, should we just not adjust the total output volume at all, or should we subtract from it by the volume level for said channel?

The former has a significant problem in that it produdes a strictly unsigned waveform. We can try to center the channel. Say the total maximum value for all channels is 0x3fff, then we can subtract 0x2000 to turn the range from 0000 - 3fff into -2000 - +1fff. But this is tricky as well, because total silence now ends up being -0x2000 instead of 0, and when mixing with the YM2612, it can lead to faster audio distortion.

The latter avoids this problem, but is very different in terms of operation. It also means the only way to produce silent output for a channel is to set the volume register to 0xf. And obviously it doubles the range of output, so we'd have to halve the volume levels[], or use +0.5 and -0.5 instead.

The thing is ... I've tried both, and while the latter sounds slightly better to my ears, I'd like to know definitively which one is the correct behavior.

So ... does anyone know for sure which way the PSG should work?


Top
 Profile  
 
PostPosted: Tue Mar 07, 2017 1:24 am 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 6047
Location: Seattle
Wikipedia links to this datasheet which says ... honestly, something kinda funny.

On page 7, there's the following schematic:
Attachment:
TI-Engineering-Staff-SN76489AN-p7-top.png
TI-Engineering-Staff-SN76489AN-p7-top.png [ 12.51 KiB | Viewed 954 times ]

Indicating that the output is 'unsigned' ("Internally Generated Sound Signal -160µA ≤ IINT ≤ 0"), but with a offset ("+1.5V (typical)").

In any case, almost all instantiations of this hardware will follow it with an analog highpass filter, so "just" emitting unsigned values isn't entirely accurate.


Top
 Profile  
 
PostPosted: Tue Mar 07, 2017 2:10 am 
Offline
User avatar

Joined: Wed Feb 13, 2008 9:10 am
Posts: 562
Location: Estonia, Rapla city (50 and 60Hz compatible :P)
The output of the chip is all "unsigned", going above some DC offset (never below), but there's always a highpass filter on the chip output which will give you "signed" result that swings around some voltage reference given later in the circuit.

Ideally you would implement a highpass filter in software, but you can cheat and make the chip output swing around 0 rather than always above. Technically it would be inaccurate but there's no audible difference and it takes less CPU too.

_________________
http://www.tmeeco.eu


Top
 Profile  
 
PostPosted: Tue Mar 07, 2017 6:43 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 18836
Location: NE Indiana, USA (NTSC)
Games using PCM, such as the "Say-Gah" sample before the titlescreen in some Sonic games, expect the unsigned behavior. Otherwise, they'll be inaudible or nearly so. For both 2A03 and SN76489 family, use unsigned output followed by a software band-pass filter.


Top
 Profile  
 
PostPosted: Tue Mar 07, 2017 10:40 am 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1329
> Ideally you would implement a highpass filter in software

I can easily add a highpass filter, but the million dollar question there is ... what cutoff frequency should it use?

I already have a lowpass filter with a 20KHz cutoff frequency (I output at >= 44100hz), in order to erase audio aliasing on resampling. I could go higher, but nobody would hear it anyway.

> you can cheat and make the chip output swing around 0 rather than always above.

How would I do that? Even if I do things per channel:

Code:
  int output = 0;
  if(tone0.output) output += center(levels[tone0.volume]);
  if(tone1.output) output += center(levels[tone1.volume]);
  if(tone2.output) output += center(levels[tone2.volume]);
  if(noise.output) output += center(levels[noise.volume]);


Where center() would be something like subtracting levels[0]>>1 (half the maximum range).

Then even still, it's not going to maintain a signed 0x0000 position for silent audio.

I don't see how we can keep 0x0000 the center unless we do the output += volume * +1/-1 per channel hack.

> Games using PCM, such as the "Say-Gah" sample before the titlescreen in some Sonic games, expect the unsigned behavior.

Of course I forgot to test the Sonic Game Gear version >_<
Thanks, I'll try that one with whatever I come up with next.


Top
 Profile  
 
PostPosted: Tue Mar 07, 2017 11:30 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 18836
Location: NE Indiana, USA (NTSC)
To simulate the Game Gear's internal speaker, you could pretend it's the same as a Game Boy Advance's speaker, which I've modeled as a third-order Butterworth highpass with corner frequency 800 Hz.

For general use with TV or headphones, you could use a filter that's more or less transparent in the audible band. Try a 20 Hz corner frequency because that's the canonical lower limit of human hearing.

Or you can use half the PSG's minimum continuous output frequency as the corner frequency so that it's essentially flat in the desired band. For the SN76489 family, the lowest frequency is close to 39375000/11/32768 = 109.2 Hz, so you can set the corner frequency at 54.6 Hz.


Top
 Profile  
 
PostPosted: Tue Mar 07, 2017 12:30 pm 
Offline
User avatar

Joined: Wed Feb 13, 2008 9:10 am
Posts: 562
Location: Estonia, Rapla city (50 and 60Hz compatible :P)
HPF cutoff freq is subsonic, like 10Hz or something (10µF caps and relatively weak loading on subsequent stages in the circuit).
LPFs are all cutting well into audible range, under 10KHz even on some hardware models (! ). I will experimentally determine the cutoff points later on when I make some freq sweep tests on various hardware.

Anyway this is what happens in the hardware : http://www.tmeeco.eu/SMD/AnalogSoundStuff.png
Simulating the analog effects is going to be a bit ugly on the dynamic range side of things, full scale 16bit output will not fit in 16bit datatype after HPF, due to half of Vpp signal going below GND at that point (not a problem if you use floats).

As far as cheating with PSG output goes, I jsut created a volume table with + values for high states and - values for low states and I sum stuff up. It is dirty but approximates output after the HPF well enough for just music (which is all I need in my music tool) but it should break down for PCM playback stuff. Doing it the proper way will work best there. Bear in mind that most games using PCM on SMS/GG sound very bad (like playing 4bit PCM without taking account the log scale of the chip).
Here's one PSG PCM test ROM I made a while ago : http://www.tmeeco.eu/BitShit/SMSPCM.SMS
Original track used : http://www.tmeeco.eu/BitShit/GreenHillsXGremixLOOP.flac

_________________
http://www.tmeeco.eu


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group