How to emulate PSG and SCC in Windows applications.

Página 3/6
1 | 2 | | 4 | 5 | 6

Por ARTRAG

Enlighted (6923)

imagem de ARTRAG

13-04-2011, 08:18

thanks, but as the tracker will work only on ISR (20ms), I do not think this detail will lead to real audible differences (in my case! not on bluemsx!)
btw the proposed changes are quite simple, I'll implement them

Por ARTRAG

Enlighted (6923)

imagem de ARTRAG

13-04-2011, 22:13

new release, thanks to you all:



SHORT R01 = 0;
SHORT R23 = 0;
SHORT R45 = 0;

char R6 = 31;
char R7 = 0;

char R8 = 15;
char R9 = 15;
char R10= 15;

char R15 =0;

#define playback_frequency     44100.0
#define psg_master_frequency   (1.7897725e6/16.0)

unsigned char volume_table[] = {0,2,3,4,6,8,11,16,23,32,45,64,90,128,180,255};

void psg_generator(LPSTR data, int size)
{
    static float PhaseA,PhaseB,PhaseC;    // state counters for ch A, B and C
    static SHORT LA,LB,LC;                // output state for ch A, B and C

    static float PhaseN;                 // state counters for Noise Generator
    static SHORT LN;                     // state for Noise Generator

    SHORT VA = volume_table[R8 & 15];     // output volume for ch A, B and C
    SHORT VB = volume_table[R9 & 15];
    SHORT VC = volume_table[R10& 15];

    float hPeriodA = ((max(R01,1) & 0x0FFF)+0)*128.0*playback_frequency/psg_master_frequency;
    float hPeriodB = ((max(R23,1) & 0x0FFF)+0)*128.0*playback_frequency/psg_master_frequency;
    float hPeriodC = ((max(R45,1) & 0x0FFF)+0)*128.0*playback_frequency/psg_master_frequency;

    float PeriodN = ((max(R6,1) & 0x1F)+0)*256.0*playback_frequency/psg_master_frequency;


    PSHORT b = (PSHORT) data;

    for (int i=0;i<(int)size/sizeof(SHORT);i++)
    {
        SHORT Level = 0;

        PhaseN +=256;
        if (PhaseN >= PeriodN)
        {
            PhaseN -= PeriodN;			// 8 bit of fractional part
//            LN = ((rand()+1)>>1) & 1;
            LN = AY8910_NoiseGenerator() & 1;
        }

        PhaseA +=256;   //count UP instead down.PSG without fractional business would just add 1.
        if (PhaseA >= hPeriodA) 	//comparison with frequency register instead with 0
        {
            PhaseA -= hPeriodA;	//subtract instead add
                                //the subtract is needed because of fractional business, PSG would just set PhaseA to 0 here.
            if (hPeriodA < 256) {
                PhaseA = 0;	//special Phase Reset.PSG would not need this code, would already have set to 0 in code above
            }
            LA ^= 1;
        }
        PhaseB +=256;   
        if (PhaseB >= hPeriodB)
        {
            PhaseB -= hPeriodB;
                               
            if (hPeriodB < 256) {
                PhaseB = 0;	
            }
            LB^= 1;
        }
        PhaseC +=256;   
        if (PhaseC >= hPeriodC) 	
        {
            PhaseC -= hPeriodC;	

            if (hPeriodC < 256) {
                PhaseB = 0;	
            }
            LC^= 1;
        }

	// Calculate samples.
	// The 8910 has three outputs, each output is the mix of one of the
	// three tone generators and of the (single) noise generator. The two
	// are mixed BEFORE going into the DAC. The formula to mix each channel
	// is:
	//   (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable),
	//   where ToneOn and NoiseOn are the current generator state
	//   and ToneDisable and NoiseDisable come from the enable reg.
	// Note that this means that if both tone and noise are disabled, the
	// output is 1, not 0, and can be modulated by changing the volume.


        Level += (( ((R7 & 0x01)!=0) | LA) & ( ((R7 & 0x08)!=0) | LN)) * VA;
        Level += (( ((R7 & 0x02)!=0) | LB) & ( ((R7 & 0x10)!=0) | LN)) * VB;
        Level += (( ((R7 & 0x04)!=0) | LC) & ( ((R7 & 0x20)!=0) | LN)) * VC;

        b[i] = Level*16;
    }
}

Por hit9918

Prophet (2927)

imagem de hit9918

14-04-2011, 00:35

thanks, but as the tracker will work only on ISR (20ms), I do not think this detail will lead to real audible differences (in my case! not on bluemsx!)
btw the proposed changes are quite simple, I'll implement them

@ARTRAG: yes it's simple, use the increment counting and all that stuff is fixed.

But do not underestimate the issue: Lowest PSG sound is 1.78Mhz / 16 / 4096 = 27Hz . A hz needs two bitflips, so it is 56 flips per second, so with buggy PSG emu the worst case response time is 18 milliseconds, WOW!

This means that when touching PSG only at ISR rate things may already sound wrong on buggy emu.

Por ARTRAG

Enlighted (6923)

imagem de ARTRAG

14-04-2011, 08:53

Just to be sure: can you confirm that for channels A,B and C if the period register = 0 or 1 the output flips at max frequency?
Can you confirm that for noise, if R6 is 0 or 1 the output flips at max frequency?

PS
Small fix, tested and working



SHORT R01 = 0;
SHORT R23 = 0;
SHORT R45 = 0;

char R6 = 31;
char R7 = 0;

char R8 = 15;
char R9 = 15;
char R10= 15;

char R15 =0;

#define playback_frequency     44100.0
#define psg_master_frequency   (1.7897725e6/16.0)

unsigned char volume_table[] = {0,2,3,4,6,8,11,16,23,32,45,64,90,128,180,255};

void psg_generator(LPSTR data, int size)
{
    static float PhaseA,PhaseB,PhaseC;    // state counters for ch A, B and C
    static SHORT LA,LB,LC;                // output state for ch A, B and C

    static float PhaseN;                 // state counters for Noise Generator
    static SHORT LN;                     // state for Noise Generator

    SHORT VA = volume_table[R8 & 15];     // output volume for ch A, B and C
    SHORT VB = volume_table[R9 & 15];
    SHORT VC = volume_table[R10& 15];

    float hPeriodA = ((max(R01,1) & 0x0FFF)+0)*128.0*playback_frequency/psg_master_frequency;
    float hPeriodB = ((max(R23,1) & 0x0FFF)+0)*128.0*playback_frequency/psg_master_frequency;
    float hPeriodC = ((max(R45,1) & 0x0FFF)+0)*128.0*playback_frequency/psg_master_frequency;

    float PeriodN = ((max(R6,1) & 0x1F)+0)*256.0*playback_frequency/psg_master_frequency;


    PSHORT b = (PSHORT) data;

    for (int i=0;i<(int)size/sizeof(SHORT);i++)
    {
        SHORT Level = 0;

        PhaseN +=256;
        if (PhaseN >= PeriodN)
        {
            PhaseN -= PeriodN;			// 8 bit of fractional part
//            LN = ((rand()+1)>>1) & 1;
            LN = AY8910_NoiseGenerator() & 1;
        }

        PhaseA +=256;           //count UP instead down.PSG without fractional business would just add 1.
        if (PhaseA >= hPeriodA) //comparison with frequency register instead with 0
        {
            PhaseA -= hPeriodA;	//subtract instead add
                                //the subtract is needed because of fractional business, PSG would just set PhaseA to 0 here.
            if (hPeriodA < 256)
                PhaseA = 0;	    //special Phase Reset.PSG would not need this code, would already have set to 0 in code above

            LA ^= 1;
        }
        PhaseB +=256;
        if (PhaseB >= hPeriodB)
        {
            PhaseB -= hPeriodB;

            if (hPeriodB < 256)
                PhaseB = 0;

            LB^= 1;
        }
        PhaseC +=256;
        if (PhaseC >= hPeriodC)
        {
            PhaseC -= hPeriodC;

            if (hPeriodC < 256)
                PhaseC = 0;

            LC^= 1;
        }

	// Calculate samples.
	// The 8910 has three outputs, each output is the mix of one of the
	// three tone generators and of the (single) noise generator. The two
	// are mixed BEFORE going into the DAC. The formula to mix each channel
	// is:
	//   (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable),
	//   where ToneOn and NoiseOn are the current generator state
	//   and ToneDisable and NoiseDisable come from the enable reg.
	// Note that this means that if both tone and noise are disabled, the
	// output is 1, not 0, and can be modulated by changing the volume.


        Level += (((R7 & 0x01) || LA) & ((R7 & 0x08) || LN))*VA;
        Level += (((R7 & 0x02) || LB) & ((R7 & 0x10) || LN))*VB;
        Level += (((R7 & 0x04) || LC) & ((R7 & 0x20) || LN))*VC;

        b[i] = Level*16;
    }
}

Por wouter_

Champion (508)

imagem de wouter_

14-04-2011, 15:42

Just to be sure: can you confirm that for channels A,B and C if the period register = 0 or 1 the output flips at max frequency?
Can you confirm that for noise, if R6 is 0 or 1 the output flips at max frequency?

I can confirm both.

But frankly, with your current code you don't have to worry yet about such low values for the period register. Both values 0 and 1 are 'wrong'. Let me try to explain:

On a real AY8910 period-values 0 and 1 both generate a tone of about 112kHz (224k 'flips' per second). Your current code will generate a tone of 22kHz. Actually for all period-values <= 5 you will generate this same signal. Both signals are inaudible, so (depending on your application) this may not be a problem.

But now look at period-value = 7. A real AY8910 will generate a clean tone of about 18.6kHz. While your code will generate a signal that is (very approximate) 1 'tick' high and 2 'ticks' low. This is not even a (symmetrical) square wave anymore. Both signals also sound differently. Though 18.6kHz is only barely in the audible range. But this same problem (to a lesser degree) also exists for many other lower frequency tones. This is what I meant in one of my previous posts with 'aliasing effects'.

You have a few options:
- Ignore this problem. This is what most emulators I've seen do. And for many tunes this is acceptable.
- Emulate AY8910 at a higher frequency than 44kHz and 'average' successive samples to again produce a 44kHz signal. This is relatively easy to implement. This already has better audio-quality, but it can't eliminate all aliasing, though it may certainly be 'good enough'. This is more or less what blueMSX and meisei do.
- Emulate AY8910 at its native frequency and perform band-limited resampling to 44kHz. This is mathematically correct (can eliminate all aliasing), but it is also more computation-intensive (and more complex). This is what openMSX does (with an option to trade emulation-speed for audio-quality).

Por hit9918

Prophet (2927)

imagem de hit9918

14-04-2011, 21:19


But frankly, with your current code you don't have to worry yet about such low values for the period register

Logic issues versus sample rate issues should be kept separate!

You are talking about sample rate issues. However if one does change the setup of the fixpoint emu to render at AY clock. Then it will do like the real integer AY. If its logic is ok.

But you are right.

Quick summary of the below: These days you just do the AY emu integer and tell the API to play that 111khz sample. The quality difference is that the APIs resampling can do interpolation.

The only reason to go fixpoint was about keeping the cpu consumption low ages ago. At sampling rates like 8000 hz or so. But when you sample 44000, the fixpoint version might even be slower than an integer version at 111000 AY rate.

So the actual cost with the 111000 version is resampling to host rate. That is the cost of replaying at 44000 a .wav that was recorded at a different sample rate. Not worth mentioning.

Por ARTRAG

Enlighted (6923)

imagem de ARTRAG

15-04-2011, 08:40

Just to be sure: can you confirm that for channels A,B and C if the period register = 0 or 1 the output flips at max frequency?
Can you confirm that for noise, if R6 is 0 or 1 the output flips at max frequency?

I can confirm both.

But frankly, with your current code you don't have to worry yet about such low values for the period register. Both values 0 and 1 are 'wrong'. Let me try to explain:

On a real AY8910 period-values 0 and 1 both generate a tone of about 112kHz (224k 'flips' per second). Your current code will generate a tone of 22kHz. Actually for all period-values <= 5 you will generate this same signal. Both signals are inaudible, so (depending on your application) this may not be a problem.

But now look at period-value = 7. A real AY8910 will generate a clean tone of about 18.6kHz. While your code will generate a signal that is (very approximate) 1 'tick' high and 2 'ticks' low. This is not even a (symmetrical) square wave anymore. Both signals also sound differently. Though 18.6kHz is only barely in the audible range. But this same problem (to a lesser degree) also exists for many other lower frequency tones. This is what I meant in one of my previous posts with 'aliasing effects'.

You have a few options:
- Ignore this problem. This is what most emulators I've seen do. And for many tunes this is acceptable.
- Emulate AY8910 at a higher frequency than 44kHz and 'average' successive samples to again produce a 44kHz signal. This is relatively easy to implement. This already has better audio-quality, but it can't eliminate all aliasing, though it may certainly be 'good enough'. This is more or less what blueMSX and meisei do.
- Emulate AY8910 at its native frequency and perform band-limited resampling to 44kHz. This is mathematically correct (can eliminate all aliasing), but it is also more computation-intensive (and more complex). This is what openMSX does (with an option to trade emulation-speed for audio-quality).

thanks for the excellent explanation, I could do some antialiasing in the analog domain, as the sampling rate of 44,1KHz is already too low in order to represent the actual waveform.

Actually, as i'm on a music tracker, tones for music hardly go at such a high frequency
So i'm tempted to leave things as they are

the real trouble is noise, where low values of the R6 can happen
as matter of facts noise emulation now quite sounds inaccurate...

If I correctly understand, in a real AY8910, for R6=0 or 1, the noise output should have as base frequency 112KHz (repetition rate of one single squared pulse) thus all the above problems of aliasing stay the same (even if frequency is half)

Por wouter_

Champion (508)

imagem de wouter_

16-04-2011, 10:20

the real trouble is noise, where low values of the R6 can happen
as matter of facts noise emulation now quite sounds inaccurate...

If I correctly understand, in a real AY8910, for R6=0 or 1, the noise output should have as base frequency 112KHz (repetition rate of one single squared pulse) thus all the above problems of aliasing stay the same (even if frequency is half)

Actually I checked again, and it seems that compared to the tone-period registers, the value for the noise-period register should be doubled. This contradicts the information in the AY8919 data-sheet (at least the version I'm using). Both meisei and openMSX implement this doubling, blueMSX does not. (It was fixed in openMSX in jan 2007).

It's also easy to check whether aliasing is a problem for noise: if low-frequency noise sounds ok, but high-frequency does not, it's likely an aliasing problem. If both sound wrong, it's likely a different problem. What is the formula you use for the noise-sequence?

Por ARTRAG

Enlighted (6923)

imagem de ARTRAG

16-04-2011, 21:42

I ripped it from another AY implementation



int AY8910_NoiseGenerator(void)
{
    static long int random=1;
    static long int output=0;
	// noise output changes when (bit1 ^ bit0) == 1
	output ^= ((random + 1) & 2) >> 1;

	random = (random >> 1) ^ ((random & 1) << 14) ^ ((random & 1) << 16);
	return output;
}

Por ARTRAG

Enlighted (6923)

imagem de ARTRAG

16-04-2011, 23:22

@wouter_

I see that VTII is implementing the noise period equal to the tone period... (same as bluemsx)
In my code I doubled it, as I was expecting the behaviour you said but this leads to different results when playing pt3 files....

Is this aspect really tested on real HW ?
Why the bluemsx team does not fix the glitch?

Página 3/6
1 | 2 | | 4 | 5 | 6