How do I make background sound?

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Post Reply
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

How do I make background sound?

Post by dfzx »

I'm looking at making sound on the 48K, and although I can see how it basically works, I can't get my head around making sounds "in the background."

I get that toggling the speaker's bit in port 254 makes it go 'click', and that toggling it repeatedly with carefully controlled pauses between the clicks can make those clicks run at, say, 261Hz, which makes a middle C tone. But all the examples I've managed to find on the interwebs do exactly that - play tones using 100% of the CPU in a carefully controlled loop. That's not so much use in games. Even the squeak-and-pop examples I'm finding hog the CPU causing the game to stutter. i.e. if it's squeaking it can't be updating the screen. Yet many games have smooth graphics alongside the sounds. Some have music playing, admittedly with rather wobbly notes, but they're still notes that don't disturb game play.

So how is that done? Interrupts, somehow? Move a bit, beep a bit? If it's the latter I'd expect it to sound a bit stuttery, or crackly. So what's the approach? Initially I'd like a few pings and pops in my 50fps game, but background music is the ultimate aim.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
Ralf
Rick Dangerous
Posts: 2279
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: How do I make background sound?

Post by Ralf »

Well, it's all true that you write. To make sound from the beeper you have to flip the bit responsible for the sound between 0 and 1 and send this value to port 254. There is no other way.

Getting different sound effects depends on about precise caluclations when you should send 0 or 1 to the beeper

Quality tunes created for the beeper use 100% of CPU time, not allowing for anything else. In the games you have a lot of other stuff to do so you can't devote all CPU time to beeper. You have to deal with it in short pauses between doing other things.

Doing it right generally needs experimenting. Or analysing others' code. You may search old magazines for sound effects, there should be quite a lot of this stuff.
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: How do I make background sound?

Post by dfzx »

Ralf wrote: Wed Nov 14, 2018 11:58 am You may search old magazines for sound effects, there should be quite a lot of this stuff.
You'd think! But everything I can find describes techniques which play using 100% CPU time.

I was just trying to work out the JSW disassembly. It looks like each "note" of the background music is played for a very short duration. So I'm thinking that if I can work out how much of my frame time is left over after all the game logic, I can play a note that long. e.g. I have 1/50th of a second in total (20ms). If my game logic takes half of that (10ms) I could use the other 10ms to play a 10ms musical note. If my note has a duration of more than that I need to play enough 10ms notes sequentially to get the duration I need.

All of which makes sense, but it rather assumes my game logic always takes the same amount of time, leaving me a consistent amount with which to play the music. I don't think my game logic is that consistent. So I start thinking maybe I can play music until the interrupt fires, at which point I somehow stop playing music and go back to doing game logic. Or something like that. Seems... complicated!
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: How do I make background sound?

Post by Joefish »

To play a note of a tune in the background you need one of these routines to toggle the bit high/low with precise timing. And as you suggest, you run the game code for a bit, then play the note for a bit, and repeat. This is why the music in Manic Miner sounds so farty, like someone blowing a raspberry to the tune - because it's cutting in and out many times a second.

Joffa Smith mastered the technique you're suggesting by synching it with his screen update in games like Green Beret and Hysteria. The way it worked is the screen update started on an interrupt. It took a little less than 2 frames of the TV picture to refresh the game, then it went into the sound routine to play the current note. It was arranged so that the very next interrupt would break out of the sound routine and trigger the next game refresh cycle. An interrupt routine doesn't have to return to the function it interrupted. In the interrupt handler, just do a POP to pull the return address off the stack and throw it away, then jump to whatever code you want to run next. Remember you actually have to tell the Spectrum to 'EI' from one firing of the interrupt routine to the next, so just hold off doing 'EI' until you enter your sound routine. Do the keyboard checking in your own code; don't rely on the interrupt for anything except timing.

Some of the better tunes are the little ditties played in Head Over Heels when you collect significant objects. Note though that this technique works best with higher pitches, as then there are more cycles within the brief time the note is played for.

There is another method though, and I don't know of any game that uses it except my own. For sound effects, you don't need to be able to play precise notes. You just want a tone or noise you can play at different pitches, then ramp up for a laser 'pew' or ramp down for a 'thud'. So if you've already got a big loop function in your code (such as copying or clearing a screen) you can put a series of bytes in a buffer, then send them to OUT 254 one at a time at the end of each pass of your loop.
Then to make a high tone you can fill the buffer with a 0-1-0-1 pattern, 0-0-1-1-0-0-1-1 for a lower tone, random data for noise, etc. For better noise, fill a buffer twice as big as you need with random numbers and jiggle the start position randomly each time it's re-used. Or you could just take numbers from the ROM, they're random enough in most places to generate noise. And repeat the same number two or three times in the buffer to lower the frequency range of the noise.

You won't get a perfectly tuned middle C, but it might be more consistent and so louder than the previous method, particularly if your big program loop is already taking up most of the processing time in your game. This method is better for lower pitches and random noise. And if you genuinely want a squidgy splat, you can still turn the whole thing on/off every few frames to get that farty effect.
User avatar
utz
Microbot
Posts: 113
Joined: Wed Nov 15, 2017 9:04 am
Contact:

Re: How do I make background sound?

Post by utz »

Ahh, background beeper music, the holy grail of ZX 48K programming :mrgreen:

While the Joffa approach certainly works, it won't help much if you plan on making a 50 Hz or 25 Hz game. There is another solution, but it's so tedious that it has never been done.

Ok, so your idea about dedicating half a frame to logic and half a frame to music won't work, because this will give a loud 50Hz buzz on top of everything. What you do need to do is update the sound continually, which means you have to interleave it with the game logic. Which, like multicolor, needs precise timing. But unlike with multicolor, timing needs to be cycle-exact at all times, which means you basically can't have an interrupt running. It is ok to periodically spend a few extra cycles at a rate of <50Hz (but not too many) because that'll generate a tone outside of the audible range. You can also get away with interrupting sound generation for a longer period once in a while, as demonstrated by this demo. Another one you might want to look up is the credits part in my 2014 music disk, which features a 25fps scroller with BASIC BEEP style music. I can probably dig up introspec's code for that if you're interested.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2640
Joined: Mon Nov 13, 2017 3:16 pm

Re: How do I make background sound?

Post by Ast A. Moore »

The best I’ve seen recently is Dark Fusion. The music plays in the menu while the star field is being updated at 50 fps (plus the attribute flashing of the highlighted menu item). Pretty darned impressive.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: How do I make background sound?

Post by Joefish »

Joffa's scrolling games did all run at 25fps.
User avatar
utz
Microbot
Posts: 113
Joined: Wed Nov 15, 2017 9:04 am
Contact:

Re: How do I make background sound?

Post by utz »

And which of them have ingame music?
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: How do I make background sound?

Post by dfzx »

Having read the responses I decided I'm still a bit out of my depth with this sort of thing. :?

I decided to cheat and look at the background music code in a Manic Miner disassembly. The loop which plays a single note has its timing hardcoded - if my reading of it is correct it goes round 768 times. It takes 31,000 T-states, which, again based on my understanding, means each note is about 8.8ms. The data numbers only represent pitch, so it's quite simple.

I took that code and dropped it into my game, which is written in C and runs at 50fps. Manic Miner appears to run at maybe 12.5fps, so I call the play-a-note function once every 4 times round my loop. It sounds spot on!

In Manic Miner each frame lasts about 80ms, of which about 10% is taken by the CPU playing music. So in fact the game is actually about 90% silent, but because the sound is played frequently (4 times a second) it sounds fine (for an 80's micro). I'm not sure where this takes me, but it struck me as interesting. :)
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
Ralf
Rick Dangerous
Posts: 2279
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: How do I make background sound?

Post by Ralf »

And which of them have ingame music?
I suppose none :) But they have decent sound effects.

If you are interested in games with beeper music, check O.K. Yah:
https://spectrumcomputing.co.uk/index.p ... 96&id=3499

The game is crap in terms of gameplay but has quite a lot of action on the screen + ingame beeper music, better than Manic Miner I would say.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2640
Joined: Mon Nov 13, 2017 3:16 pm

Re: How do I make background sound?

Post by Ast A. Moore »

Found another interesting example: Moonlight Madness. It’s two-channel, naturally choppy, but not as grating as Manic Miner, and doesn’t seem to conflict with other sound effects.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
Cosmium
Microbot
Posts: 154
Joined: Tue Dec 04, 2018 10:20 pm
Location: USA

Re: How do I make background sound?

Post by Cosmium »

From what I can remember when I programmed the sound effects (not music) code on Quadron, I had the 50Hz interrupt handler checking a pointer variable that if non-0 would step through a list of encoded frequencies each frame. These 'frequencies' were really the delay introduced between turning the sound bit on and off and then OUTted to the sound port. Higher numbers meant bigger delays hence lower notes, and consequently a slight slow down of the rest of the game. But this was a lot better than calling the 100% CPU hogging BEEP routine!

The list of encoded frequencies were made by trial and error. I kind of got the feel for it after a while and got some quite varied sound effects in the end! To prepare them I think I had a simple BASIC program reading through a list with READ and DATA statements and POKEing them into memory to form the list ready for the interrupt routine. Doing it in BASIC made editing and changes easier.

Once I had all the sound effect lists ready it was just a case of saving them out with SAVE "sfx" CODE xxxxx,xxx so that it could be included by the assembler later.
User avatar
Turtle_Quality
Manic Miner
Posts: 502
Joined: Fri Dec 07, 2018 10:19 pm

Re: How do I make background sound?

Post by Turtle_Quality »

I've been wondering the same about background sound, I'm very slowly writing a game that will be in assembly (New Year's Resolution), and probably overcomplicating the whole thing for myself.

Back in the day I used nested loops to make differing sound effects, with increasing/falling pitch or weird combinations to make sound patterns -sirens, quacks (using a duck nested loop obviously) etc... I had some code (long lost) that would "sing" text by plotting the pixels of a word in a pseudo random way at the same time as sounding a note (or two). By maintaining the sound after a updating each byte on screen the sound was continous.

I'm guessing that most in game sound engines work by playing some sound on the interrupt before the graphics render, or play sound after the graphics render up until the interrupt, giving it a stuttered effect ; sound for half a frame then silent.

I'm wondering if it's feasible to embed very frequent calls to a continous sound engine, after each sprite row and other items in the game loop, maybe every 60 t states or thereabouts, to enable smooth sound fx. The continuous sound engine would then need to decide if it's time for a click and decide the next frequency. It could use the r register and a small delay loop to ensure consistent number of t states before a click. Just don't keep resetting the r register, not on a real speccy. Did the refresh occur when R = 127 ? Maybe set it to 127 then for a timer reset.

I'm still musing and doing the maths to see if this feasible. The game I'm working on should not be too CPU intensive on it's own so I reckon I can make 50fps with sound. And I'm talking about the beeper here obviously, not the AY chip. And I'm aiming just to make sound effects rather than backround music (although some of the best fx are musical and need a good pitch : the Penetrator bugle call before the game starts, a congratulatory "tada" effect etc..)
Definition of loop : see loop
Post Reply