• Please review our updated Terms and Rules here

What I'm working on 2, Sound

It would be more difficult to update the screen at the full framerate (ie. 60Hz) using your methodology
While it would be near impossible to use one codebase to generate a 20, 24 and 30fps video framerates with a fixed 120hz audio update simultaneously without the risk of either-or interrupting each-other at undesired intervals.

Basically what I'm doing is running 120hz audio updates, and slicing in the code for everything-else in-between them. I don't see how a flat execution model can handle that with any degree of grace or accuracy -- hell it's why so many CGA-era games have jittery video and buggy sounding audio is they didn't bother.

Even if I synced it flat, I'd then have to add the overhead of fixed point or floating math to the ghost updates making it far more complicated -- OR I'd have to put the audio updates in the timer ISR meaning multiple DIFFERENT timer ISR's or constantly changing what subroutines the ISR is calling depending on if it's in the menu vs. in the game, which siren is in use, which effects are being played or maintained, etc, etc... Are you basically saying that doing that would be SIMPLER than just profiling timeslices? doesnae make any sense to me...

... and honestly at 320x200 or less anything over 30fps is a waste of time. (just as at 640x400 or higher anything over 60hz is a waste of time unless compensating for raster decay)

If that's the only change then I don't see why it shouldn't work.
Yeah, I'm not sure why that's not working either.

Though I did notice something I didn't see before;
Actually, should be WORD sized... and that's declared in the macro as countInc and countDec are macro's.

BTW, what is countISR, countInc and countDec anyway? Can you describe the timer code in more detail?
I'm running 120hz for the audio updates (I really should consider bumping it back to 240hz) both for speaker arpeggio's and realtime audio updates like the siren frequency; the time-slicing means I don't have the headache of having multiple different versions of the timer ISR or making some dozen different calls for the ISR to check against; but 120hz is NOT an even multiple of the 65536 timer intervals used by the normal 18.2hz clock. If I simply used the next nearest whole number the system timer would skew way off the longer the game ran. (as if it's not bad enough the jitter switching timer freq introduces on entrance/exit).

My solution is to simply use fractional math. Keeps it integer, keeps it fast.

Code:
%define countInc WORD 0x4000
%define countDec WORD 0x09B9

remember the PIT runs that 4.77mhz, and the system clock is updated roughly 18.2 times a second? Well, that 18.2 is actually 4,772,727 / 65,536 (+/- 0.027%) -- that 4.77mhz being 4:3 ratio the 315/88 colour burst crystal clock... (though I never really understood how you get a clock FASTER than the feed source?)

Every 65536 ticks on the PIT the system clock ISR should be called. Since I'm running the ISR every 9956 ticks (which actually works out to EXACTLY twice the vertical retrace of 59.902khz) there is no accurate integer value of 65536 / 9956...

So I fraction reduce it by four.

65536 / 4 = 16384 (0x4000)
9956 / 4 = 2489 (0x9B9)

so counter - 2489 until negative, when it goes negative add 16384 to it, and while there is timer "jitter" it is over a long period of time accurate. Every now and then the delay is a hair longer, but the average is accurate without resorting to floating point.

Basically it's the same math that Bresenham used in his line drawing algorithm. You need to get from point A to point B in integer numbers, without using floating point, accurately.. when X and Y do not cross-divide to an integer value.

Assuming distanceX is greater than distanceY:

Code:
void lineX(int x, int y, int distanceX, int distanceY) {

int
	endX = x + distanceX,
	count = (distanceX / 2); // I divide by two so the ends match

do {
	plot(x, y);
	x++;
	count -= distanceY;
	if (count < 0) {
		y++;
		count += distanceX;
	}
} while (x < endX)

Same general idea. Every so often there will be 'jitter' where the delay is longer or shorter between them, but over long periods of time it remains accurate as at the end it all adds up to the same value. Ever heard people talk about "timer jitter"? Well this is often the "why" of that when you want a timer interval that is NOT an integer quotient of the master timer you're trying to divide off of.

... and knowing how to do that can be REALLY handy; particularly if you have multiple events that need to run at different rates; like audio, video, user input and clock NOT running at the same flat frequency. Becomes an even bigger issue in real time programming in industrial settings -- where sensor inputs and motor control outputs often have wildly varying data rates. In a RTOS (real-time operating system) you'll often have dozens or even hundreds of these running off a single master timer -- particularly if trying to maintain realtime I/O on a single threaded processor. (see QNX)

In that way I'm applying a lot of motor control and realtime programming concepts to a game -- I find it easier that way and far simpler code-wise given what I want as a result. YMMV.
 
Last edited:
Basically what I'm doing is running 120hz audio updates, and slicing in the code for everything-else in-between them.

That's a curious decision since the original game didn't do that; it had everything on a VBLANK interrupt firing at ~60Hz. Unless you're planning on replacing the pac-man music with alternate, higher-resolution music, there's no point.

Both you and the original are doing similar things; there is a task handler in the original ROM where different subroutines are called on different iterations of the handler. But the original game only had two tasks firing during main gameplay, so the internal game calcs were effectively ~30Hz. You're doing way more work than you need to.
 
Both you and the original are doing similar things
The original wasn't supporting a dozen different sound cards of different capabilities. The original was supporting a higher resolution where off-interval movement wasn't as obvious so they could get by using that same fractional math to handle element movement.

Remember, I'm at 3/8ths the resolution for tiles and 5/16ths for sprites, so I had to ditch the idea of the different ghosts going at different speeds as it made the animation jerky and in general look like crap. It's FAR easier to change the game frame rate for level speed than it is to make the already painfully complex back-buffer, front-buffer and tile-buffer calculations even MORE complex... since remember I also don't have the convenience of a true sprite engine or a 1:1 back-buffer to screen buffer or a perfect exponent of 2 tile buffer to back/screen buffer.

original pac-man? Hardware sprites with no backbuffer, 28x31 grid of 8x8 tiles with 16x16 sprites

Paku Paku 2.x? Software sprites using a 32x31 pixel packed backbuffer being blitted to an interlaced 80x100 by two pixels screen, playmap 28x31 grid of 3x3 tiles with 5x5 sprites ALL done from software. NONE of the buffers needed to run the game even operate on the same dimensions. and if they did, I'd have a 200k+ memory footprint and be a third the speed, making it not viable on a 8088 even running flat out and throwing level speed concepts out the window.

There's a LOT that the video and audio hardware handles for them I'm having to brute-force code in realtime.

It's also why I don't have "cornering" -- there's not enough pixels to implement that so instead I give the player a 1px boot in the patoot at the corners to kind-of fake it... letting the player just pull away from Ghosts as long as they're not Elroys.

so the internal game calcs were effectively ~30Hz.
Their siren was programmed onto the sound chip, that's NOT an option on PC speaker. I update the siren frequency at 30hz, you can hear the steps since that would work out to ~40 cents shift each interval. At 120hz it's ~10 cents so the 'slide' sounds smooth. A 40 cent at 33ms is VERY audible, a 10 cent shift at 8.33ms isn't.

A LOT of what their sound chip was providing for them I'm having to basically bit-bang to the majority of PC configurations. About the only place I could do what they were doing is on Adlib, but with Yamaha FM being tinny weak crappy sound and it being the ONLY platform that would work on... (well, original Covox Sound master could do it as well). It would be like trying to play digital audio one byte per timer tick at 240hz, the result isn't good.

Remember, it's not just "music" -- it's every blasted sound in the game I have to manipulate the output frequencies for. That now includes a two-voice priority driven arpeggio for PC speaker... and I don't have the magical perfect sprite and audio engine to work with.

Hell, I didn't even have the perfect sprite and audio on the C64, a laugh since it has reasonably capable sprite and audio capabilities.

A LOT of the early arcade games are harder to get right on other platforms because they are so tied to the hardware they were built on -- EVEN if you are moving it to a platform with the same or even a more capable CPU. See why a 6x6 EGA resolution version of this would have a 386/16 as it's minimum spec (mostly due to it's planar nature); though laughably VGA mode 13 or tandy/jr 320x200 would be 8mhz 8088, but also far more memory hungry.

OF COURSE the arcade version could do a lower timer -- they had a higher resolution and the grunt-work done in hardware. Were that I could distribute a card with the game that had a custom 3 voice 4bit WSG on it that could also play samples via DMA, and added a sprite overlay, then what you are saying would be viable.
 
Last edited:
The fact that NASM doesn't force you to specify the size is one of my biggest gripes with it.
Oops, I just now checked and it seems they have fixed that actually.

Yeah, I'm not sure why that's not working either.
I think you're mixing up the segments somehow, that's the only explanation I can think of.

BTW, if you keep the variables used by the timer ISR in the code segment (together with the handler) then you can use CS segment overrides in the handler and avoid the mess of changing DS. As it is now, you're using 10 bytes where 3 segment overrides would suffice. I don't know how well this works with TP though.

Also, would it not be better to hook into the IRQ0 handler (INT 08h) instead of the system timer tick interrupt (INT 1Ch)?
 
Do any of the sound devices you support require data sent to them 120 times a second to function properly?

Hum, perhaps for some values of 'require'?
It reminds me a bit of 'multispeed SID' music, such as this: http://youtu.be/JsvYKxOrdOc
'Regular' SID music will just run once per frame, so that is normally at 50 Hz (nearly all music is made for PAL).
However, by updating the SID registers multiple times per frame, you can get more finegrained pitch/filter control, allowing for sounds you've never heard before on SID. I believe the maximum multispeed tune is 16x, which would be 800 Hz.
 
Do any of the sound devices you support require data sent to them 120 times a second to function properly?

For what I'm doing -- PC Speaker, Tandy/Jr, CMS, Innova... and pretty much all the MIDI devices. Otherwise there's frequency jitter you can hear. Again 10 cent steps vs. 40 cent steps over a 1 second siren siren up/down is a pretty big deal; the difference between it sounding like an analog siren and an arpeggio. Honestly even with Adlib I'd need that since I'm NOT using pre-programmed sounds there (in fact I'm doing my damnedest to get it to just output a clean sine wave, which of course that pile of shit FM synthesis can't actually accomplish)

It's even noticeable when not chomping on speaker WITH the arpeggio, but that's because it's priority based. (when the second voice is not playing, it doesn't arpeggio it plays the tone of the other voice straight).

Since again, I'm either setting a direct frequency NOT a pre-programmed sound, or I'm using pitch-bender with a range of 0..127 over and octave.

It's not what the devices "require", its' what the sound effects I'm playing through them require. The comparison to multispeed SID is a good one, there's a reason I'm running the C64 version at twice the system clock there too.
 
It's not what the devices "require", its' what the sound effects I'm playing through them require. The comparison to multispeed SID is a good one, there's a reason I'm running the C64 version at twice the system clock there too.

The original game didn't require that resolution; it ran at 60Hz. So, I'll assume from now on that this is a choice you are intentionally making to improve upon the original game's audio.
 
(in fact I'm doing my damnedest to get it to just output a clean sine wave, which of course that pile of shit FM synthesis can't actually accomplish)
I'm curious about this statement - you should be able to set an OPL2 voice to function as two unmodulated output operators rather than a modulator-carrier pair, so unless your problem is with the quality of the chip's internal sine-wave representation, this shouldn't be an issue.
 
The original game didn't require that resolution; it ran at 60Hz. So, I'll assume from now on that this is a choice you are intentionally making to improve upon the original game's audio.

Alright, I'm failing to understand why you aren't grasping that the original game's CUSTOM audio chip could do things most sound chips for the PC could not -- like the auto-generated siren that only required a base tone. Why is that so hard for you to get?

JUST the siren ALONE -- again 30hz would be 15th notes, 60hz 30th notes -- and while 32th notes are very hard to play, they are audible. you need at least twice that to smooth it out so you don't hear the transitions.

Much less sound FX that have ADSR frequencies or complex shifting frequencies that run a quarter note or less in length. four (30hz) to eight (60hz) frequencies over a one beat at 120bpm playtime is a JOKE for anythign remotely resembling a complex sound.

Those are things the real game does IN HARDWARE that things like Tandy, Jr, Speaker, CMS simply do not provide and has to be done software side -- WHAT THE **** ABOUT THAT AREN'T YOU GRASPING?!?!? Are you just being intentionally thick-headed at this point?!? It's a miracle at 120hz I can come CLOSE to just generating that siren; and honestly, I'm tempted to go back to 240hz just to truly accomplish it "properly" and get the damned latency under control.

Again why if I even wanted to THINK about flat execution for everything else, I'd have to code every sound effect into the damned ISR, and that's FAR more complex than what I'm doing.

I'm curious about this statement - you should be able to set an OPL2 voice to function as two unmodulated output operators rather than a modulator-carrier pair, so unless your problem is with the quality of the chip's internal sine-wave representation, this shouldn't be an issue.
I don't know if it's the internal sign wave itself being crap, or the way it puts operators together -- I've NEVER heard Yamaha FM sound that didn't have that and sound like utter and complete trash; unable to generate anything even remotely resembling bass, unable to generate anything without this ~12khz "hiss" atop everything, unable to generate a tone with a sharp/proper "attack"... and to my ears even the CMS sounds better for generating an actual tone, even if the voices of those tones aren't as "fancy" they at least don't sound like muddy, hissy ear splitting annoying ****!

It's actually quite an accomplishment making it muddy, ear splitting AND weak sounding all at once; but that just seems to be what OPL sound is.

But to keep that in perspective I say the same thing about SID and most chiptunes, which is probably why I have zero use and am usually NOT impressed with 99%+ of what the "demo scene" vomits up with the C64. There's just something about the sound or on top of the sound or... I dunno what, that makes me want to jab hot pokers in my ears. Basically the same reaction I have to modern "chiptunes" -- or as the kids who know nothing about music call it "dubstep" -- see the pimp slap I deliver every time some nitwit says "Bass Drop"; or as Buckley so rightly points out, a sub-genre of a sub-genre of an actual genre...
 
Last edited:
Ah. I think the issue there is more the super-junky output on a lot of SB/Adlib-compatibles (not to mention the original SB and Adlib.) They don't provide proper output filtering at all and sound generally tinny as hell, unfortunately - but anybody who claims that FM as a whole sounds like crap and can't do bass is welcome to meet me at dawn for DX7s at ten paces.
 
but anybody who claims that FM as a whole sounds like crap and can't do bass is welcome to meet me at dawn for DX7s at ten paces.
While to me the DX-7 has EXACTLY that sound I'm complaining about -- weak, tinny, like someone took a moog and cut off it's balls. There's like this annoying out-of-tune hiss on every sound that makes it not really any better sounding than a Casio-100. It's like there's a ring modulator turned up to Dalek involved somewhere.

Would it be wrong to show up to a DX-7 gunfight with a Roland D-20, Moog 3P or even better, ARP 2600? You know, in Sergio Leone terms, "what happens when a man with a pistol meats a man with a rifle?"... or in Edgar Winter terms "Are you ready for the monster?"

Admittedly, I'm a musician with perfect pitch -- and with most people seeming to be tone deaf, beat deaf or suffering from amusia...

I love the sound of old analog, I love the sound of LA/newer... but there's something about FM synths that to my ears make it sound to me like someone wiped their ass after a double-bean burrito, sandwiched it between two sheets of vinyl, and called it a double album. It's weak, it's tinny, it hisses, and does NOT sound the least bit pleasant; it's about as pleasant as Phil Collins voice. (which has been known to trigger seizure disorders LIKE MINE.)

-- edit -- Though for some reason, the "crippled" OPLL -- aka YM2413, sounds just fine to me. Addition vs. Multiplexing?
 
Last edited:
Man, you'll never see me turn up my nose at a Moog or a D-50, but I just can't fathom how someone could dismiss the DX7 so completely...if you've only ever heard the Standard-Issue '80s Ballad Piano patches, sure, but there's a world of excellent stuff outside that narrow window, and while I won't deny that the DX7 is noisy and displays weird artifacting at high frequencies, to me those things just add to the charm...

As far as Collins is concerned, as a mega-successful pop singer he's a very decent drummer.
 
As far as Collins is concerned, as a mega-successful pop singer he's a very decent drummer.
For me he triggers my vestibular hyperacusis -- laughably "auto-tune" triggers it the same way and there are very FEW things that are triggers for me. Phil Collins, auto-tune, and FM synthesis tops the list... and it's not ALL Phil Collins, Sussudio and the early stuff for example is fine - but from "but Seriously" onward I get the same problem. (I really pity Hamster for what Jezza and James they did to his Fiat in Turkey...)

... said problem LITERALLY falling into three reactions:

1) Irritability - I know, I know... like you could tell the difference :p

2) Headache like an icepick in the temples, dizziness, nausea, and after prolonged exposure projectile vomiting Linda Blair style.

3) Actual full-on seizures at particularly high volumes.

... which is GREAT as a musician in the age of every halfwit who THINKS they can sing diving for the autotune pretty much proving they can't :D That auto-tune also triggers it makes me wonder if Collins invented his own auto-tune two and a half decades ago and then kept it to himself.

Some smells can also trigger the same reaction in me; my mother's baked stuffed green peppers (my father's favorite food), anything with molasses in it while baking. (I'm ok once it's done, but during cooking? OUCH)... ANY of the food at Subway or Pizza Hut...

Of course, I'm in that wonderful minority where I have hyperacusis without tinnitus, but at least it's not misophonia. (not to be confused with misohornia, a close cousin to "pop culture tourettes")

Goes well with my photophobia, degenerative neuropracy, paraneoplasticism (the symptoms of post cancer recovery without a diagnosed cancer) and medication induced parkinsonism (due to some quack treating diabetes and hyperacusis as epilepsy with their "here try this" attitude toward prescriptions)

... and yet I'm in the best health of my life

FM synths don't trigger me as badly, but there's just something WRONG with how they sound. Same for SID and a few other methods of making sound. Again, SAA1099 sounds better than OPL to me so... I've never heard ANYTHING done on Adlib / Yamaha FM that sounded any better than the Orchestra 80 did a decade earlier.
 
Last edited:
Doesn't anyone like Adam Ant and the Thompson Twins anymore? :rolleyes:
Wouldn't "any more" imply people actually liking it in the first place? Never was much of one for bubblegum pop.

... admittedly at the peak of those bands I was listening to Motorhead, AC/DC, Omar and the Howlers, Robert Cray, Albert Collins, Brecker Brothers, Steps Ahead...

Nope, no conflicts there...
 
In other news:

Code:
simpleTimerISR:
	inc   WORD [cs : tickCounter]
	sub   [cs : countISR], countDec
	js    .oldCall
	iret
.oldCall:
	add   [cs : countISR], countInc
	jmp   FAR [cs : oldISR]

WORKS -- putting EVERYTHING into CS so I don't have multiple push/pop is nice, but a wash code-size and likely execution time wise... still not having to push/pop TWO registers is a definite bonus.

It also means that the speakerISR can now simply cascade into the simple one instead of using two separate instances of the same basic code.

Code:
checkSpeakerVoice:
; ACCEPTS
;   DS:BX   offset into speaker voices
; CORRUPTS
;   AX, CX
; RETURNS
;   ZF set == no playback, unset == playback
	mov  cx, WORD [cs : bx + voices]
	or   cx, cx
	jz   .done
	mov  al, 0xB6
	out  0x43, al
	mov  al, cl
	out  0x42, al
	mov  al, ch
	out  0x42, al
	in   al, 0x61
	or   al, 3
	out  0x61, al
.done:
	ret

speakerTimerISR:
	push  ax
	push  bx
	push  cx
		; uncomment next two lines to slow the arpeggio 50%
		;	xor   [cs : speakerFlags], BYTE 0x80
		;	jns   .tick
	mov   bx, [cs : currentVoice]
	xor   bl, 2
	mov   [cs : currentVoice], bx
	call  checkSpeakerVoice
	jnz   .played
	xor   bl, 2
	call  checkSpeakerVoice
	jnz   .played
.speakerOff:
	in    al, 0x61
	and   al, 0xFC
	out   0x61, al
.played:
	pop   cx
	pop   bx
	pop   ax
	
simpleTimerISR:
	inc   WORD [cs : tickCounter]
	sub   [cs : countISR], countDec
	js    .oldCall
	iret
.oldCall:
	add   [cs : countISR], countInc
	jmp   FAR [cs : oldISR]

So that is a bit of an improvement.

So you think it's all bubble gum do you?
Thank you for proving my point :p
 
The games with DS is definitely what was messing with it. I now have the ISR running as this:

Code:
simpleTimerISR:
	inc   WORD [cs : tickCounter]
	sub   [cs : countISR], countDec
	js    .oldCall
	iret
.oldCall:
	add   [cs : countISR], countInc
	db   0xEA
directISR:
	dd   0x00000000

With this as the setup:

Code:
	mov   ax, 0x351C
	int   0x21
	mov   [cs : directISR], bx
	mov   [cs : directISR + 2], es

Don't know why pushing/popping DS was a problem, but hey, whatever.
 
Back
Top