VOGONS


Reply 40 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++

Manually running the title.exe of Links with cycles set to 6000 seems to work a bit. Display works, but it's very slow. A bit of sound at the first 1-2 seconds? Some tone or sweep? After that it's quiet?

Edit: I think that the PIT output is probably correct(according to 1 cycle/instruction and osdev description of modes). I think the problem is in the 'decoding' thaf output and converting it into PCM samples.

//PC speaker output!
speaker_ticktiming += timepassed; //Get the amount of time passed for the PC speaker (current emulated time passed according to set speed)!
if (speaker_ticktiming >= speaker_tick) //Enough time passed to render?
{
length = (uint_32)SAFEDIV(speaker_ticktiming, speaker_tick); //How many ticks to tick?
speaker_ticktiming -= (length*speaker_tick); //Rest the amount of ticks!

if (!FIFOBUFFER_LOCK) //Not locked?
{
lockaudio(); //Lock the audio!
}

//Ticks the speaker when needed!
i = 0; //Init counter!
if (PCSpeakerPort & 2) //Speaker is turned on?
{
short s; //Set the channels! We generate 1 sample of output here!
sword sample; //Current sample!
//Generate the samples from the output signal!
for (;;) //Generate samples!
{
//Average our input ticks!
PITchannels[2].samplesleft += ticklength; //Add our time to the one-shot samples!
tempf = floorf(PITchannels[2].samplesleft); //Take the rounded number of samples to process!
PITchannels[2].samplesleft -= tempf; //Take off the samples we've processed!
render_ticks = (uint_32)tempf; //The ticks to render!

//render_ticks contains the samples to process! Calculate the duty cycle and use it to generate a sample!
dutycycle = 0; //Start with nothing!
for (dutycyclei = render_ticks;dutycyclei;)
{
if (!readfifobuffer(PITchannels[2].rawsignal, &currentsample)) break; //Failed to read the sample? Stop counting!
sample = currentsample?SHRT_MAX:SHRT_MIN; //Convert sample to 16-bit!
applySpeakerLowpassFilter(&sample); //Low pass filter the signal!
dutycycle += sample; //Add the sample to the duty cycle!
--dutycyclei; //Decrease!
}

if (!render_ticks) //Invalid?
{
s = 0; //Nothing to process!
}
else
{
s = (short)(((float)dutycycle)/((float)render_ticks)); //Convert duty cycle to full average factor!
}

//Add the result to our buffer!
writefifobuffer16(PITchannels[2].buffer, s); //Write the sample to the buffer (mono buffer)!
if (++i == length) //Fully rendered?
{
if (!FIFOBUFFER_LOCK) //Not locked?
{
unlockaudio(); //Unlock the audio!
}
return; //Next item!
}
}
}
else //Not on? Generate quiet signal!
Show last 20 lines
		{
//Generate a quiet signal!
for (;;) //Generate samples!
{
writefifobuffer16(PITchannels[2].buffer, 0); //Write the sample to the buffer (mono buffer)!
if (++i == length)
{
if (!FIFOBUFFER_LOCK) //Not locked?
{
unlockaudio(); //Lock the audio!
}
return; //Next item!
}
}
}
if (!FIFOBUFFER_LOCK) //Not locked?
{
unlockaudio(); //Unlock the audio!
}
}

Currently the speaker itself is operating at 44100Hz.
Also:

const float ticklength = (1.0f / SPEAKER_RATE)*TIME_RATE; //Length of one shot samples to read every sample!

Where SPEAKER_RATE is 44100.0f and TIME_RATE is 1193182.0f . So ticklength contains the amount of PIT samples to average for every 44100Hz sample. Is this done correctly? Or is this response sample average different(perhaps actually a different samplerate, like 1000000.0f/60.0f(one sample based on 60us of PIT data). What does all software expect? How long does this period to average need to be? Osdev says 60us, but different average input sample counts give different average values=correct output or junk results, depending on what software expects of the speaker.

Last edited by superfury on 2016-01-20, 09:04. Edited 1 time in total.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 41 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++

Looking at Dosbox-X pcspeaker.cpp: Check if we have to slide the volume.

It seems it creates the frequency response by shifting the signal little bits towards 1 or 0 for those samples, instead of averaging the values. This will create a different output than simply averaging?
Thus the ramp to 1 or 0 is a linear value (0-1 and 1-0 in 60us if I understand correctly. Now I only still need to know how much the speaker moves to 0 or 1 each PIT sample (1.19MHz sample). Anyone knows the formula to calculate the EXACT value? Assuming TIME_RATE is the frequency of the PIT samples.

Edit: according to my calculations, it's:
60us = full movement of the PC speaker from OFF to ON and ON to OFF.
TIME_RATE = PIT ticks per second.

(60/1000000)*TIME_RATE=PIT samples per full movement.
1/((60/1000000*TIME_RATE))=Movement per PIT sample.
(1/((60/1000000)*TIME_RATE))*USHRT_MAX=16-bit movement per PIT sample.
Simplified:
(USHRT_MAX/(60/1000000*TIME_RATE))=16-bit movement per PIT sample.

Of course, this uses the fact that the PC speaker moves to 1 or 0 at a linear speed.

Anyone can confirm this?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 42 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've implemented the new handling of the PC speaker:
https://bitbucket.org/superfury/x86emu/src/c9 … pit.c?at=master

I notice that when debugging the emulator while running the links' title.exe, it will run, but PC speaker is reloaded too fast. It tries executing the countdown, but it never finishes (the PIT0 timer always expires before the Interrupt on Terminal Count (mode 0 set on channel 2, mode 2 set on channel 0). Channel 0 expires every 72 ticks (which matches 60us used for PWM). Channel 2 keeps changing to high numbers that never will expire in 72 PIT0 ticks (The high byte is unchanged, the low byte keeps getting updated).

Does the software have a bug that it's not updating the high byte of channel 2 correctly? Or is there an error in my emulator updating the counter (at the CPU in8253/out8253 functions of the PIT related to counter reading/writing)? Is it supposed to update the divisor set earlier or use the current count value when updating the low/high 8 bits of the reload register? The high byte of the reload register is set to a value higher than 0x00. Thus it will never time out?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 43 of 57, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

If the mode is set to load LSB only or MSB only, then it loads only one byte, and the other byte is zero.
So in load LSB only mode, you can only load values 0 to 255 (although 0 would still mean 65536 I think..).

This is convenient because in PWM mode you really don't want count larger than 255 anyway because it would mean sampling rate lower than 4679 Hz.

Reply 44 of 57, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

It seems it creates the frequency response by shifting the signal little bits towards 1 or 0 for those samples, instead of averaging the values. This will create a different output than simply averaging?

That sounds like it integrates the square wave into triangle wave at 1.19MHz, and it still has to somehow determine what would be the PCM value when sampled at 44kHz. I think both are theoretically wrong, averaging it directly or integrating like this. It might still sound somewhat OK but if one wants to cheat, why not just use the value loaded into PIT channel 2 as the PCM sample value directly? The proper way is to filter the 1.19MHz stream with a low pass filter that removes frequency content over 22kHz and then every 27th sample can be taken as PCM output as 44kHz (rest 26 samples can be discarded) because there is no frequency content that would get aliased.

superfury wrote:

Thus the ramp to 1 or 0 is a linear value (0-1 and 1-0 in 60us if I understand correctly. Now I only still need to know how much the speaker moves to 0 or 1 each PIT sample (1.19MHz sample). Anyone knows the formula to calculate the EXACT value? Assuming TIME_RATE is the frequency of the PIT samples.

I bet there is no exact value for this. The speaker is most likely not even driven with a symmetrical push-pull stage that would move it in and out with same speed.

Reply 45 of 57, by Scali

User metadata
Rank l33t
Rank
l33t
Jepael wrote:

It might still sound somewhat OK but if one wants to cheat, why not just use the value loaded into PIT channel 2 as the PCM sample value directly?

Getting the sample value is one part of the problem.
Another part is the sample rate.
You can't assume anything about the sample rate (you could look at PIT0, but 8088 MPH doesn't use a timer, yet it plays PWM data... so it is not a given that an application performing PWM audio will use PIT0 for its sample rate).
What you'd need to do then is to measure the number of PIT ticks between each value loaded into PIT channel 2.
Then your PWM-to-PCM routine needs to be able to handle changes in sample rate at any given sample, and you're good to go.
It's not actually an emulation though, because you're not actually performing PWM, and you will not get the characteristic sound of PWM (such as the carrier wave, or the rather 'grainy' sound).
So downfiltering a 1.19 MHz 1-bit sample buffer to a PCM buffer is the way to go for accurate emulation.

I don't think there's any point in trying to emulate the specific physical characteristics of the speaker though. These probably differ widely from one PC to the next anyway (using different speakers, even different technology, such as piezo vs cone, different circuit to drive the speaker etc). Yet it doesn't seem to affect PWM audio a lot on real hardware.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 46 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++
switch (pitcommand[pit]&0x30) //What input mode currently?
{
case 0x10: //Lo mode?
pitdivisor[pit] = (pitdivisor[pit] & 0xFF00) + (value & 0xFF);
break;
case 0x20: //Hi mode?
pitdivisor[pit] = (pitdivisor[pit] & 0xFF) + ((value & 0xFF)<<8);
break;

So this shouldn't load that low or high value from the reload register(pitdivisor[channel] in my emulation), setting that part to zero always? So low mode can only set reloads to 0-255 and high mode can only set the high bits(values XX00 being set)?

The signal is being filtered after determining the correct sample from PIT data(16-bits value, first low pass of 20kHz, then high pass of 18.2Hz.

The speaker moving up and down like now should be the correct way, according to wikipedia(see it's article on PWM). It should be able to run correctly(Links Golf) when I fix above counter bug?

Edit: Changed the above code to set the high(in low mode) and low(in high mode) byte to zero instead of using it again. It now gives some constant soundstream. It sounds kind of like a helicopter getting further away and returning etc. Also low pass filter set to 22050Hz.

https://bitbucket.org/superfury/x86emu/src/5d … pit.c?at=master

Last edited by superfury on 2016-01-20, 22:24. Edited 1 time in total.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 47 of 57, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
Scali wrote:
Getting the sample value is one part of the problem. Another part is the sample rate. You can't assume anything about the sample […]
Show full quote
Jepael wrote:

It might still sound somewhat OK but if one wants to cheat, why not just use the value loaded into PIT channel 2 as the PCM sample value directly?

Getting the sample value is one part of the problem.
Another part is the sample rate.
You can't assume anything about the sample rate (you could look at PIT0, but 8088 MPH doesn't use a timer, yet it plays PWM data... so it is not a given that an application performing PWM audio will use PIT0 for its sample rate).
What you'd need to do then is to measure the number of PIT ticks between each value loaded into PIT channel 2.

Yes, it would be the same issue with Covox DAC - there is no fixed sampling rate, it might be loaded in a timer interrupt determined by PIT 0 channel, or free running. And it's running at CPU speed, or ISA bus speed, and there will be some ticks of jitter. So something is loaded to the DAC port when it is. It just has to be that value, at some rate, until next value is loaded.

Scali wrote:

Then your PWM-to-PCM routine needs to be able to handle changes in sample rate at any given sample, and you're good to go.
It's not actually an emulation though, because you're not actually performing PWM, and you will not get the characteristic sound of PWM (such as the carrier wave, or the rather 'grainy' sound).
So downfiltering a 1.19 MHz 1-bit sample buffer to a PCM buffer is the way to go for accurate emulation.

Yes, I totally agree this is the most theoretically correct way to go. In the "PIT-renderer" I made for my friend's PC speaker song, I deliberately wanted to import the actual 1.19MHz 1-bit square wave into audio editor so I could experiment there and skip all the hard parts (filtering and downsampling in the renderer).

Scali wrote:

I don't think there's any point in trying to emulate the specific physical characteristics of the speaker though. These probably differ widely from one PC to the next anyway (using different speakers, even different technology, such as piezo vs cone, different circuit to drive the speaker etc). Yet it doesn't seem to affect PWM audio a lot on real hardware.

This I also agree. I do have to recommend to cut enough low and high frequencies instead of passing ideally frequencies from 0 to 22kHz because it sounds too sterile, no PC speaker has that kind of range. Even in the lowest 18.2 Hz tone, that is just sharp edges of square waves happening every 36.4 times per second, it does not mean there is actually 18.2 Hz sine wave or even a sawtooth baseband. And also very little of 16kHz square wave can be heard.

Reply 48 of 57, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

So this shouldn't load that low or high value from the reload register(pitdivisor[channel] in my emulation), setting that part to zero always? So low mode can only set reloads to 0-255 and high mode can only set the high bits(values XX00 being set)?

That's what the datasheet says. Either 00XXh or XX00h.

superfury wrote:

The signal is being filtered after determining the correct sample from PIT data(16-bits value, first low pass of 20kHz, then high pass of 18.2Hz.

Is that first order "RC" filter simulation? If so, consider it only attenuates 3dB per octave, so it may not remove much at all at 22050 Hz if you set the cutoff frequency to 20000 Hz. But it should be good enough for testing.

superfury wrote:

The speaker moving up and down like now should be the correct way, according to wikipedia(see it's article on PWM). It should be able to run correctly(Links Golf) when I fix above counter bug?

Which part of the article specifically you are referring to? Delta PCM? I would not suggest emulating the cone movement by +1/-1 based on the square wave being 1/0. Just filter the square wave. But yes, you should finally hear something when you fix the load LSB only mode.

Reply 49 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++

It should be generating the Delta PCM signal now. The blue line in the image to be exact. Only the values increase/decrease up to 32767/-32768(16-bit range converted from the Delta PCM signal floating point value, which is sampled using floating point calculations for accuracy(like all synchronization in hardware and CPU). Positive input increases with delta constant up to limit, zero decreases down to limit using the constant. The value of the constant is the result of the calculation a few posts back.

const float speakerMovement = (USHRT_MAX/((60.0f/1000000.0f)*TIME_RATE)); //Speaker movement (in 16-bits) for every positive/negative PIT sample(linear movement)!

Where positive(1) is added and negative(0) is substracted. And capped at 16-bits signed word of course(short/int16_t/sword). This floating point sample finally is converted to short, lowpass filtered(22050Hz), highpass filtered(18.2Hz) and added to the output buffer(which locked using a semaphore, is read by the audiothread at 44.1kHz to be mixed and returned to SDL for playback).

It kind of sounds like a chopper in the rhythm of the song(drums?). Rest is noise in the background? Sounds like a wave sweep of a low frequency.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 50 of 57, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

But this is not Delta, is it? I mean, I think the PC speaker stream is not defined to be the derivative of the actual signal so integrating it into speaker position is not how it should work.

In delta modulation, a 50% duty means "stop moving, stay where you are", 0% duty means "start moving towards negative at full speed", etc. See how the duty is 50% at both positive and negative peaks of the sine wave. So if you look at the article closer, Delta PWM modulation is the means to generate the PWM stream that is derivative from original signal. This is fig.3 in Wikipedia article.

In normal PWM modulation, 50% duty means "just go to the centre and stay there", 0% duty means "just go to most negative position and stay there", so it is not change in signal (delta or derivative) but the actual setpoint. In real life, this is how PWM normally is used to get something analog (after filtering). This is fig.2 in Wikipedia article.

Reply 51 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++

One problem: duty cycle is taken over a period of samples (from the 1.19MHz generator). If the period of the duty cycle is unknown (according to what I read it could be 60us like osdev notes, but also lower or higher according to a bit further in the article (setting PIT0 to sampling frequency and outputting one cycle each PIT0 tick?). So obviously that example uses the PIT0 to specify the duty period to average. But software that doesn't use PIT0 (like 8088 MPH) will fail if PIT0 is used to time output samples. Also, simply using the rate at which PIT2 is updated will destroy normal PC speaker mode 3 square wave generation(since it's only set once and never updated until the next time it's loaded, so you will get 0V over the period of the playback (square wave output entirely averaged, until either the counter is reloaded or port 0x60 bits 0/1 is/are updated.).

Edit: Looking at the OSDev article again, it seems to have a mistake. First it talks about this:

The PC Speaker takes approximately 60 millionths of a second to change positions

The rest of the article talks about 60 milliseconds. So that's 1000 times as long as said directly after "How It Works". Is this an error in the article? Is it 60us or 60ms? I think 60ms would be a little small (~16 2/3 samples a second), 60us has 16 2/3kHz.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 52 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've updated the filter order (first highpass, then lowpass). Also PCM generation using averaged 60us signal has been remade (including support for 'shared samples': Samples that overlap between the current PWM-modulated value and the next one, essentially leftovers with samples processed higher than processable in whole samples(e.g. 18.2 samples with 18 samples processed and 0.2 sample left to process. This is used in the next calculation (starting with the current sample read and the count started at 0.2 instead of 0.)).

https://bitbucket.org/superfury/x86emu/src/a8 … pit.c?at=master

Is this correct?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 53 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've fixed the PWM audio emulation. It's a bit slow to work with (very laggy, probably the CPU emulation itself or the rendering semaphore). I currently still have the .wav file output enabled, which seems without problems.

https://bitbucket.org/superfury/x86emu/src/c8 … pit.c?at=master

Anyone knows if it's simply the semaphore that makes this whole emulation so slow?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 54 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++

Listening to the recording of the PC speaker output:

The attachment x86emu_pcspeaker.zip is no longer available

I don't know if it's allowed to place such an recording here though. I think it is OK, as I found some ogg files in an older post here on the forums that still exists (I believe it was something to do with the PC speaker emulation too).

It seems that the PC speaker emulation now actually works. The only problem left is that:
1. The CPU emulation is too slow to render the PIT&PC speaker for the renderer (which results in empty buffers).
2. The PC speaker emulation or the semaphore locking the output buffer (from PIT to renderer) slows down the emulator too much.

The semaphore enabled in the buffer locks and unlocks the buffer each sample (so does the renderer, which reads the generated buffer).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 55 of 57, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've managed to fix the speed problems that appeared. Also the emulator has been optimized and runs quite fast now (probably realtime) due to the optimizations to speed up emulation (CPU, VGA, PIT, Keyboard and various others have been optimized).

Look on my x86EMU emulator release thread for the latest version (build 2016/01/22 03:31 atm).

Now software is fast again due to fixes and optimizations. CPU clock cycles are 1(Dosbox style with limit set) or 4(When using the Default setting, which also supplies an accurate ~4.77MHz clock(Which when divided by 4 is equal to the PIT clock), both based on the ~14MHz clock.

- 8088MPH PWM (works, but music is on the fast side due to inaccurate CPU cycles(4 cycles/instruction instead of instruction dependent)).
- Supaplex PWM works.
- Links Golf PWM works.

I haven't tested with other software yet though.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 56 of 57, by Scali

User metadata
Rank l33t
Rank
l33t
Jepael wrote:

I would not suggest emulating the cone movement by +1/-1 based on the square wave being 1/0. Just filter the square wave.

I agree with that. If you use a filter to filter out the high frequency components, you're effectively 'smoothing' the square signal into something more of a triangle or sine-like wave. Which is basically what the inertia of the speaker cone would do as well.
The filter would also emulate the fact that the speaker frequency response is not linear, so higher frequencies are softer or not even audible at all over a certain frequency.
The PC speaker itself is the 'filter' on real hardware.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 57 of 57, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

I think it's great you have it working now. I should say that more often 😀