VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

It gives some kind of high sounding signal sometimes with the 1Hz high pass filter and ticking sounds (~4-6/second) without the filter.

It is first generated(1.19MHz), downsampled in 60us intervals(PWM), finally skipped(downsampling it) to 44.1kHz for output.

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

The tickPIT handles this signal generation. 8088 MPH sounds fine when making sound using normal PC speaker output(sounding), ticking sounds instead of quiet times and incorrect noise-like sound during it's PWM output in the credits.

As far as I know the 1Hz high pass filter works, as does the low pass filter (verified using MIDI output). So the PC speaker PWM and/or PIT2 generation goes wrong here(PWM is the inner loop of the second part of tickPIT(line 431), which is already downsampled to 44.1kHz at line 445). Line 385+ handles IRQ0 ticks. Line 401 can clear the PIT1 buffer (not used, so ignored). Line 162+ generates raw PIT samples for all 3 channels (It adds binary samples to the 1.19MHz PIT output buffer, so the output of the signal lines)).

The channel status used in generating the 1.19MHz signal has some different basic states (dependant on the channel's mode).

Anyone knows what's going wrong with it's output or sound(PWM) generation?

Last edited by superfury on 2016-03-02, 20:15. 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 1 of 55, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

Well, imagine there is a program that could run ideally and using PWM technique it will play ideal midpoint value silence and thus loads a value of 36 into the timer every 72 timer clocks.

It means for 36 timer cycles the PIT output is high and for 36 timer cycles the PIT output is low. That is a square wave with frequency of 16.572kHz, meaning a period of 60.34 microseconds.

My question is how does your speaker handling reacts to that signal?

You talk about 60 microsecond averaging to get duty cycle, so for a 60 microsecond period, do you just count how many ones and zeroes comes out and then you have a duty cycle you use as PCM sample value, for the next 60 microseconds?

And since 60 microseconds is 71.59 timer ticks so it is not an integer amount of ticks, does it means that about 59% of the time you average together 72 PIT outputs, and about 41% of the time you average together 71 PIT outputs, and use that average as the PCM value for the next 71 or 72 PIT ticks?

Reply 2 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

My emulation averages up to the current rounded down samples of time, including the previous sample if it's left over.

So the first block will contain 71.59 samples. Thus 71 samples are processed into a PCM sample(averaged). The next block will start with the last sample and the amount set to 0.59 samples.

The second block will contain 70.59+0.59=71.18 samples when it's processed(1.0 sample added each time). 71 samples are processed into PWM(includes the previous shared sample), 0.18 samples and the current sample is carried over to the next block.

Etc.

So it will process like this:
1. Add an available sample it reads to the total.
2. Add 1.0 to the time for the sample. Then, if time overflows the sample(1.19MHz) equivalent of 60us(~72 samples) goto step 3. Else continue to step 1.
3. Calculate the new duty by dividing the total with the time.
4. Calculate the new time by taking the remainder of time(so effectively floating point modulo 1.0, e.g. 72.23456->0.23456).
5. Finally determine the new starting total to either zero(when time is now zero) or the last sample read from the PIT(when time is set to nonzero, thus a shared sample between the current and next PWM sample).
6. Goto step 1.

Essentially the speaker renderer does the above loop for as much time has passed between the current 44.1kHz(output rate) sample and the previous 44.1kHz sample, thus keeping processing ~72 samples at ~16kHz samplerate(1000000/60 Hz). Then downsampling them to 44.1kHz by skipping the PWM samples by executing above loop for the time between 44.1kHz samples(1000000us/44100 us).

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

Reply 3 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

Although your 'midpoint value' of 0V won't happen in my emulation, as average duty becomes mapped from 0.0 - 1.0 to 16-bit signed sample values 0 - 32767. Thus that midpoint value gets mapped to 2.5V on a scale of 5V(16-bit signed value +16384).

When the average is 1.0 it will give 5V. With average 0.0 it will give 0V.

The problem with this is that if I change this to full range (-32768 - +32767), it will give disabled PIT3(port 0x61 bit 2=0) as -32768, thus -2.5V DC, which will be caught by the high pass filter of 1Hz and attenuated if it's enabled(it's essentially 0Hz). Although it does fix the range to it's correct -2.5V - +2.5V range.

So either positive only with disabled speaker=0V.
Or full 16-bit signed range(positive and negative) with disabled speaker=-2.5V DC, which is cut off by the 1Hz high pass filter(or the OS. Else it blows up the speaker(s) by burning the coil with DC, thus it will be destroyed forever(coil is burned through by the produced heat) or until someone fixes it by soldering it back together, if it doesn't burn at many places at the same time or starts getting on fire(kinda an actual HACF(Halt and catch fire) effect, but with speakers, unless it's AC coupled(which the PSP might not be, cannot find anything about that)).

Last edited by superfury on 2016-03-01, 22:59. 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 4 of 55, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

That is what I suspected then.

Now, imagine the following bitstreams out of PIT during 72 samples:
(these are imaginary, most likely no program running under 4.77MHz CPU could play these, but just for the algorithm's sake)

a) 36 ones, 36 zeroes (basically, 16572 Hz square wave tone)
b) 36 zeroes, 36 ones (basically, 16572 Hz square wave tone, but opposite phase of a)
c) 18 ones, 36 zeroes, 18 ones (basically, 16572 Hz square wave tone, but 90 degrees phase to a and b)
d) 18 ones, 18 zeros, 18 ones, 18 zeros (basically, 33144 Hz square wave tone)
e) 1,0,1,0,1,0,.. etc (basically, 596591 Hz square wave tone)
f) 1high, 2 low, 3 high, 4 low, etc, .. 10 low, 11 high, 6 low
g) any 72 bit length string with 36 zeroes and 36 ones distributed in any way you would like

If I am right, your algorithm just counts ones during time of 72 (approx) bits and averages it, so each waveform I described would have identical output with duty of 50%. Now, if you think of the bitstreams, they are different, they have different frequencies, or different phase content between each other even if they have identical frequencies, so they must sound different.

So, what do you think about this, what could be wrong here?

Reply 5 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

Well, the principle of 72 each sample doesn't apply: Every time the 71 samples(rounded down 60us) is calculated it leaves a little rest. This causes the next set of 71 samples to be 70 new samples plus the last one of the first set(last sample of previous block). This continues until nothing is shared anymore between the current block and the next(total and time being zero). After that the next block will once again contain 71 fresh samples of PWM data with the final sample as a rest value and new total(like the first block). Thus the cycle repeats.

I've made a dump .wav file of the raw PIT output(converted to 0-32767) and duty output(before any filtering is applied and before downsampling). I don't hear any difference between them, except the duty output being a bit muted.

It's in the latest commit:
https://bitbucket.org/superfury/x86emu/src/78 … pit.c?at=master

Although the negative part isn't applied yet.

And to answer your question: Assuming pc speaker movement speed is constant, it would just be those averaged values(50%) for that one 60us period(72 samples assumed to exactly be 60us with no rest and shared samples applied, like my emulation does do). If the movement speed isn't constant(don't know any formula for that in the case if the PC Speaker), movement speed for 1s and 0s will vary according to position of the PC speaker, making the actual PWM sample pretty complicated to calculate easily(matter weight, current voltage, resistance, gravity etc.)

Also, concluding from my recordings having the same errors during playback, even the raw PIT stream played using Windows 10(whopping 1.5GB file for 15 minutes of audio of 8088 MPH) which is apparently filtered and PWM applied by Windows, as it only has two PCM values(0 and 32767) which sound from my monitor's speakers as the PWM duty version(even though it's a raw binary wave(2 different values only), which isn't PWM modulated).

So the PWM probably works. Though seeing as the raw stream from the PIT channel 2 gives the same invalid output both ways, there probably is an error in the samples(1.19MHz) the PIT itself is generating(based on the mode, counters, gate, speaker AND(port 0x61 bit 2) and/or processing input by the CPU)

Also, the PC speaker can't respond to those little periods of time it's on 1 or 0. I'm handling the 60us periods by averaging, but a real speaker moves a bit towards 1 or 0 each sample when not already there. Also the movement speed could increase with time(multiple 1s or 0s in a row), affecting the output in those cases. Again I don't have any formulas on that, nor do I see any in Dosbox(It also averages if I remember correctly, but emulates the pit in less small blocks(mine's essentially emulated 1:1 PIT clock accurate synchronized with the CPU)).

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

Reply 6 of 55, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

Well, the principle of 72 each sample doesn't apply: Every time the 71 samples(rounded down 60us) is calculated it leaves a little rest. This causes the next set of 71 samples to be 70 new samples plus the last one of the first set(last sample of previous block). This continues until nothing is shared anymore between the current block and the next(total and time being zero). After that the next block will once again contain 71 fresh samples of PWM data with the final sample as a rest value and new total(like the first block). Then the cycle repeats.

My point was not to emphasize the block being exactly 72 samples or not. My point was to raise discussion about if you think there's anything wrong with the way of downsampling by a factor of (about) 72 by taking the average of (about) 72 samples, as it appears it will mangle many different kind of PIT waveforms of the current block into same output, regardless of what the previous PIT output bits were.

Also the phasing effect you described somewhere earlier may come from the fact that 8088 MPH tries to reload the PWM value every 72 ticks, and let's assume we live in the ideal world and it does. But your averaging window is now either 71 or 72 ticks, so it is sliding along the actual point of reference phase (software reload).

Reply 7 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

So that means the response of the speaker isn't exactly 60us, but actually a bit longer? Thus setting it to the correct amount (72) instead should fix that problem, except when it's not always 72(being a fraction of it more/less in reality, like the speaker should(according to osdev) use 60us response time, not being fully divisable in PIT samples). Is it always 72 samples for all PWM samples?

The only problem that's left is the data generated by the PIT timers. Since there should be no difference in output between the duty and the result I'm hearing from the PIT 1.19MHz .wav file played through Windows 10's default media player(the yellow one, not Windows Media Player) which seems to filter and PWM in either the media player and/or physical speakers, still will give invalid output according to what I hear from it.

So the PIT itself still has a problem generating the timed 1.19MHz stream itself. As far as I know I fully implemented it according to the descriptions of the modes etc. (Except the, on the original IBM PC PIT 8254, unsupported 'read-back mode')
I don't think I've made any mistakes with that. Or have I made an error there?

I do remember removing a feature from mode 0 or 1 concerning reloading or starting documented on osdev, but crashing the IRQ0 making 8088 MPH hang with the first few characters displayed on the fake text CGA screen(the screen comparing the other PC with the IBM PC, Commodore I believe? Like X channels vs IBM PC 1 channel, colors etc.)

Edit: When displaying the text "Ho, what's this" it would stop and hang about halfway because the IRQ0 didn't fire anymore.

Edit: I've changed the PWM from 60us to 72 PIT samples. Now only the PIT itself would need to be fixed.

Edit: Just tested the new PWM setting to 72 PIT samples: The noise pattern at the credits still appears, but the noise at the normal PC speaker part is gone (the quiet moment's noise during normal PC speaker output(mode 3/7)). Output is still a bit softer in the duty version (compared against the raw recording).

BTW BCD mode also isn't supported yet.

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

Reply 8 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

Although the PC speaker is driven by 0V and 5V signals, so your silent signal of 50% duty cycle (over a period of 4 seconds) will produce 2.5V DC instead of 0V on a real PC speaker, thus it might burn the coil of the non-AC coupled PC speaker?

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

Reply 9 of 55, by Scali

User metadata
Rank l33t
Rank
l33t
superfury wrote:

Is it always 72 samples for all PWM samples?

No, it is just 72 ticks because 8088 MPH was designed to make each sample take exactly 288 CPU cycles. The CPU runs at 4x the speed of the PIT, and 288/4 = 72.
PWM can be played at any rate the CPU allows, and your emulation should make no assumptions about this.

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

Reply 10 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

But if the PWM doesn't know how many samples to use for PWM conversion to 44.1kHz, it will only work with 8088 MPH, but no other ticks amount. Also, 72 ticks is ~60us (speaker frequency response time), so if other software uses a different amount, PWM screws up it's samples generating wrong output. Also it's then impossible to build for any software (as any software uses any amount of ticks, thus making the processing impossible, except when set manually using the BIOS menu to a certain amount of PIT ticks)?

Isn't it that the CPU must generate the PWM sample(s) in 72 ticks always, but simply vary the rate the PWM value it stores (converted to the stream) to get the correct samplerate?

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

Reply 11 of 55, by Scali

User metadata
Rank l33t
Rank
l33t

I have no idea what 'speaker frequency response time' has to do with anything.
Reality is that PWM can be performed at any frequency. Look at some MOD players with PC speaker output for example (or eg the audio routines in Triton's Crystal Dream demos). You can select the mixing rate there, which is also the output rate of PWM data. The faster the CPU, the higher the rate you can choose.

There are two ways to support this:
1) The 'proper' way: generating the actual 1-bit output data at 1.19 MHz, then downsampling to 44.1 KHz 16-bit for output on a modern audio device.
2) Cheat: just create a timestamp for each time the CPU writes a new count to the PIT. The count value gives you the PWM value, and the difference between two timestamps gives you the duration. Now you can convert the raw PWM value back to PCM data.
Note that this can get somewhat tricky because you can't assume the CPU writes are at a fixed frequency. In theory the frequency can be different at every write.

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

Reply 12 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

So I'm already using that 'proper' method, but I downsample each 72 samples normally before downsampling the resulting signal to 44.1kHz by skipping samples(not interpolating them). So every sample calculates 44100/(~1.19MHz/72) PWM samples and takes the last one calculated as the active sample. As far as I can hear it works properly. Those resulting samples at 44.1kHz are low pass filtered with 20kHz, then sent to the sound module for mixing. The sound module finally mixes all hardware emulated and applies a 1Hz high pass filter before sending it to SDL for playback. SDL passes it to Windows, Windows mixes it with running programs, sends it to the sound card(integrated on the motherboard or not), which might filter again, sending it to the speakers used afaik.

So the filtering executed is required(low pass) and optional for speaker protection(1Hz high pass) preventing DC on the speakers depending on the AC coupling used.

I just still need to fix the PIT generating samples itself. The PC speaker sounds fine, but the PWM music sounds bad with the usual garbage noise mixed in with the music.

Anyone can see/hear what's going wrong there?

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

Reply 13 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

Also, yes, PWM itself can be at any frequency, it's simply longer and shorter duty cycles seperately programmable. But the PC speaker response(only moving a bit while getting 1 or 0, moving towards that value, is what makes the magic happen. If you execute a PWM slower than the frequency response(~60us or 72 PIT samples), then it will fail because you will get actual PWM as direct output(e.g. 50 ticks high, 50 ticks low won't make the speaker use half value(16-bit signed 0, but move towards 1 during 50 ticks, then back towards zero for 50 ticks. So 72 ticks high 72 ticks low won't result in 50% voltage, instead it will result in a full voltage 'sample', next an empty voltage 'sample' because of the response of the pc speaker)). This explains why it only works with correct speaker response (~60us or 72 ticks), else the whole PWM would fall apart?

So executing the PWM with a response(sample) time of 60us would give correct results with a PC speaker. But doing it too fast would result in not enough movement and doing it too slow (more than 60us) would result in too much movement. So if you would drive the samples at e.g. 30kHz(loading the timer each 1/30000th second) with the PC speaker, the first sample would be the correct PWM sample, while the others are stuck to a value of 1 (using the One-Shot mode). So instead of getting the PWM sample for the entire duration, it only lasts very short (60us) and after that become it's maximum voltage (the PIT is outputting 1s until the next timer reload). Thus the entire playback would fall apart when reloading at 30kHz speed.

It would work though to repeatedly load samples each 60us, but keep repeating the same samples to archieve your wanted samplerate (like 44100Hz). So you send each sample multiple times or less to archieve the sample rate (so effectively resampling to ~16666Hz and sending those as raw PWM samples to the PIT to archieve playback). That should work. Anything sent slower than 16666Hz would result in incorrect PWM samples.

Last edited by superfury on 2016-03-02, 17:02. 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 14 of 55, by Scali

User metadata
Rank l33t
Rank
l33t
superfury wrote:

So I'm already using that 'proper' method, but I downsample each 72 samples normally before downsampling the resulting signal to 44.1kHz by skipping samples(not interpolating them). So every sample calculates 44100/(~1.19MHz/72) PWM samples and takes the last one calculated as the active sample.

I don't think this will work properly, you'll get lots of aliasing if you just cut up the data in sets of 72 samples.
Especially since your CPU emulation is not cycle-exact, the code probably won't actually run at 72 PIT ticks per PWM sample, like it does on an actual IBM PC.

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

Reply 15 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

But the 72 samples is actually the frequency response time? It's the amount of samples from the PIT that actually move the cone from 0 to 1, or from 1 to 0, in that time. When you send samples faster than that, you will move the cone too less, sending it slower will move it too much.

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

Reply 17 of 55, by gdjacobs

User metadata
Rank l33t++
Rank
l33t++

The response characteristics of the speaker are going to be variable, depending on the cone mass, spring constant, coil, and magnet.

All hail the Great Capacitor Brand Finder

Reply 18 of 55, by Scali

User metadata
Rank l33t
Rank
l33t

Aside from the fact that his understanding of PWM seems to be incorrect. You aren't sending it 'samples' as such (it's not PCM)... You control the +5v going through the speaker. You either turn it on or off. The whole point of PWM is to send a signal in discrete pulses. You basically 'balance' the cone in a certain position with these pulses. In theory it doesn't matter that much whether you send 10 pulses of duration X, or 100 pulses of duration X/10. The average duty cycle will be exactly the same over time.
It's not an exact science, it's very crude, but it works well enough in practice, as long as the frequency is high enough. The ~16 KHz we use here gives reasonably clear ~5 bit digital audio. But even at ~10 KHz it should already sound somewhat recognizable. As I say, try some old demos or mod players that support PC speaker.

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

Reply 19 of 55, by superfury

User metadata
Rank l33t++
Rank
l33t++

Yes, just like I said earlier, although I don't know those factors on the top of my head.

But essentially around 72 PIT samples make the cone move enough to be able to process the movement from 0 to 1 or in reverse. If this wouldn't be true, any PWM generating PCM samples using that method would fail.

Although my emulator does have some noise in the resulting PWM samples(because of errors in the PIT 1.19MHz signal generation itself?), it does work. Also as far as I looked at Dosbox's PC speaker emulation, it does about the same thing as my emulation, only it processes it using that 'cheat' method Scali mentioned earlier, while my emulation uses the 'proper' method (generate 1.19MHz signal, downsample to 16666Hz using 72 PIT ticks per sample averaged for the PC speaker output signal, which is upsampled to 44.1kHz, then finally being sent through a 20kHz low pass filter to remove artifacts and finally added to the rendered buffer, which in turn is used by the sound renderer which is a little resampler, which resamples the 44.1kHz PC speaker output to the current playback rate (usually 44.1kHz, depending on the system, giving by SDL_OpenAudio), finally sending the mixed data (of PC speaker, adlib, MIDI, Sound Source, Covox Speech Thing etc.), which is high pass filtered with 1Hz to protect output from Direct Current(DC), to SDL which eventually ends up at your speakers(usually external, like in your monitor, normal speakers, physical PC speaker(don't think Windows 10 supports that one anymore) or any recording device you may have enabled)).

The stuff up to the sound renderer is still handled in the PC speaker emulation itself (except the low pass and high pass filters code being in the sound module, called by the PC speaker emulation).

At least, that has been my understanding how PC speaker PCM works (according to osdev's articles).

If I don't execute the conversion from raw samples to 44.1kHz I would essentially lose a lot of information.

Or should I actually process the raw PIT2 samples converted to 0 or 32768 as input, low pass filter them at 16666 2/3Hz(The 60us response), then hold the result until it's the time equivalent of the current 44.1kHz sample and use that sample as the current output for the 44.1kHz rendering stream? Although it would be heavier on the low pass filter, it would be more accurate conversion?

Edit: Applying that last thing in this message (raw PIT2 samples part), makes the PC speaker sound pretty change. The volume keeps going up and down (from 0dB to maximum dB back to 0dB etc., like a modulation volume envelope acting like a 100% attenuation using a LFO as the source). It's using a 16666 2/3Hz low pass filter.

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