16 April 2019, 01:27 | #1 |
Registered User
Join Date: Jul 2015
Location: The Netherlands
Posts: 3,438
|
Audio Mixing for Games example + source
Recently I've been inspired by stuff like Octamed and the many threads on here about Paula and it's abilities. So I decided to try my hand at making a real-time audio mixer that would be fast enough for use in A500 games.
I've managed to get a (IMHO anyway) pretty good result, with low CPU use and decent quality. Obviously I had to compromise and so err, I did - the mixer runs at a fixed sample rate. However, it does manage to mix three channels into a single one at less than 6% of a PAL frame (as measured on an A500). Edit: this perhaps wasn't clear in my original post, but the end result is six channel playback - three channels for music, three mixed ones for sound effects. There’s also a ‘high quality’ mixer included that is much slower, but results in much better output quality. This one is probably too slow for the A500, but will work nicely on an A1200 (where it uses about 7% CPU time per frame). Lastly, there are two executables: one optimised for the 68000 and one for the 68020 and up. Both should run on any Amiga with 512KB of memory. Personally, I'm rather happy with the result and so I wrote an article on my website and made a YouTube video showing the result. I've also included the full source code and wrote the mixer so that it should be useable in other projects with minimal changes. If you like it, use it as you see fit. Note that the module and samples I've used in the example are not by me and the startup code is by Photon of Scoopex. The ProTracker player used is the one by Frank Wille. Hope you guys find it useful or neat. The article/source can be found here: http://powerprograms.nl/amiga/audio-mixing.html Here's the YouTube Video: [ Show youtube player ] Last edited by roondar; 16 April 2019 at 10:50. |
16 April 2019, 09:41 | #2 |
Registered User
Join Date: Jan 2017
Location: London, UK
Posts: 433
|
My first game/demo work was writing audio code for the Amiga. I’ll have to have a look through my archives, but my playback routine would playback a protracker module on the first three audio channels, using the interrupts as normal to keep Paula fed with data. The fourth channel was my sfx channel, which would allow two samples to be played back, using a very simple “add and right shift” technique, for this channel the interrupt contounious played a relatively large buffer (~8khz IIRC), which was filled every VBL.
The sound designers (usually me), would then make sure all music mods were only 3 channels sfx were sampled at the correct frequency. When an sfx needed to be played it would be added to a simple event queue, that would then update the sfx playback buffer every VBL, the oldest sample would be removed if a newer one needed to played, as only 2 sfx were allowed. -edit- if I did it now, I would probably remove the quietest sample, but almost all sfx got quieter the longer they played, so the effect was the same. It worked really well on a mono TV, but sounded horrible if you listened to it in stereo. It would be a fun experiment to have done a proper stereo version of this. |
16 April 2019, 10:04 | #3 |
Registered User
Join Date: Jul 2015
Location: The Netherlands
Posts: 3,438
|
Interesting, that is similar to what I'm doing here - playing back 3 channel ProTracker and mixing SFX to the 4th channel using audio interrupts.
However, my version doesn't shift in real time - it divides* or limits ahead of time (well, for the standard mixer that is - the HQ one does real time limiting) so it's might be a bit faster. The example as is runs at a fixed rate of 11KHz (though this is configurable) and I feel it sounds well, good enough. Certainly a lot better than I expected *) Using signed divide. I do this because then a three channel mix only needs to be divided by three rather than four as is the case with shifts. |
16 April 2019, 10:27 | #4 |
Defendit numerus
Join Date: Mar 2017
Location: Crossing the Rubicon
Age: 54
Posts: 4,501
|
Hi roondar, when I find some time (I am totally overwhelmed by real work these days) I will take a look at your article.
But I'm sure it's written in a simple and clear way as usual I have several mixing routines with different features in my sources mess (I also remember that in the 80's I wrote a fast routine with linear interpolation in real time of which I was proud ). So I can compare with the job you did here. Cheers! |
16 April 2019, 10:48 | #5 | |
Registered User
Join Date: Jul 2015
Location: The Netherlands
Posts: 3,438
|
Quote:
Comparing notes is always useful. Do note that the one overarching goal here was to minimise overhead, so the mixing routines themselves limit the amount of tricks they use (though the High Quality mixer does try to keep samples as close to their 8-bit original values as much as possible even when mixed) To be honest I'm kind of hoping to help people 'see the light' here, mixing some extra channels at a reasonable quality isn't as hard or as processor intensive as it may seem (though you do have to accept a few compromises) and it can really change the way games sound. More so as I've recently noted (been playing some Mega Drive stuff ) myself that more channels but lower quality SFX generally seems to sound more impressive than fewer channels but higher quality SFX |
|
16 April 2019, 11:37 | #6 | |
Registered User
Join Date: Jan 2017
Location: London, UK
Posts: 433
|
Quote:
So if I needed two samples (for gunfire, or foot steps, or object interaction), then I could use two channel mode, and then switch it off for a loud sample (like a bomb going off, which might just be the same gun shot sample played loud to save on chip RAM ), that was actually quite effective. It was all about saving RAM and making it as flexible as possible |
|
16 April 2019, 11:42 | #7 |
Registered User
Join Date: Jun 2016
Location: UK
Posts: 428
|
Have you tried temporal mixing? As in if you have three samples you just do one sample from each after the other, like ABCABCABC?
As well as avoiding any arithmetic you might be able to use the blitter to do the interleaving. |
16 April 2019, 11:50 | #8 |
Registered User
Join Date: Jan 2017
Location: London, UK
Posts: 433
|
I don’t think the Amiga would be able to get the sample rate high enough to make that work effectively.
|
16 April 2019, 12:03 | #9 | ||
Registered User
Join Date: Jul 2015
Location: The Netherlands
Posts: 3,438
|
Quote:
The idea of masking quiet sounds with loud ones does sound interesting. My current mixer simply has an AddSampleToMix routine that just adds in an extra sample to the list of samples to play at the current time (unless no free slot exists, in which case it does nothing). I considered adding priorities, but these examples of mine are mostly about showing the effect and so I try to not make them too complicated. Quote:
This would mean even a fairly low frequency sample would need a very high sample rate. Plus, as I understand it, such mixing leads to a high-pitched whine being added to the sound (which can be fixed, but as I understand it only by increasing the sample rate even further). So I ultimately decided against it. |
||
16 April 2019, 12:23 | #10 | |||
Registered User
Join Date: Jan 2017
Location: London, UK
Posts: 433
|
Quote:
Quote:
Quote:
|
|||
16 April 2019, 16:50 | #11 |
Natteravn
Join Date: Nov 2009
Location: Herford / Germany
Posts: 2,553
|
That's a quite interesting piece of software! Thanks a lot for sharing it. I would have never considered audio mixing in games, but 6% might make me change my mind. It's still a lot of time, but all of my recent games could easily afford that.
You said that mixing the sound effects results in a lower volume, so you reduced the volume of the music channels. As sound effects are always much more important than the background music an alternative solution could be to mix the music instead, and play the sound effects on the hardware channels. Although I understand that this is much more difficult for you, because then the mixer must be integrated into the player routine. BTW, I just saw a comment in your Makefile that you have to do "make clean" whenever an include file (*.i) changes. There is a vasm-option to automatically emit all dependencies in Makefile-format: -depend=make (no code will be generated). |
16 April 2019, 17:37 | #12 | |||
Registered User
Join Date: Jul 2015
Location: The Netherlands
Posts: 3,438
|
Quote:
On the other hand, the 6% I mention is all CPU cycles. But as we know, the 68000 only uses about half the cycles of the system, so roughly half of these cycles can be used by other DMA such as the Blitter or bitplane DMA. I've not calculated how much of a difference that makes, but maybe the actual overhead can be slightly lower if (on average) some interleaving between DMA and CPU occurs. Quote:
The volume issue is improved by using the compressor/limiter option for pre-processing, but that does impact audio quality more than the divide/shift approach. One thing about using it for music would be having to deal with many different pitches (=period values). The main reason it's only 6% is that I don't do any resampling - the program just assumes all samples are (roughly) 11025Hz and never changes period values. Quote:
To be honest, the makefile is the thing I'm least proud of. I've used a rather lazy approach to keep it as short as possible. It mostly works, but indeed doesn't care much about dependencies. |
|||
16 April 2019, 23:26 | #13 |
Registered User
Join Date: May 2017
Location: AmigaLand
Posts: 459
|
Holy shit ! Roondar your work is stunning. 3 samples playing at the same time than music is a dream. It adds so much atmosphere to a game.
Do the sfx have to be played in the main program or does it need to be played under irq ? BTW, your article about fast bobs triggered an idea to make my bobs routine much much faster (or to be more precise : less slower). |
17 April 2019, 00:37 | #14 | |
Registered User
Join Date: Jul 2015
Location: The Netherlands
Posts: 3,438
|
Quote:
As for your question: The code as I wrote it uses the Amiga's audio interrupt (so it runs from an IRQ). However, you could, in theory, run the mixing routines without interrupts. That said, it's much easier to use audio interrupts. This is because Paula's period values (which are what govern the sample playback rate) only really support sample rates that do not neatly divide up into 50 or 60. This means that the buffer to play back & fill would vary in size from frame to frame when not using interrupts*. Using an audio interrupt means you don't have to consider this, which makes the code easier to follow and a bit faster to boot. Note that the code as I provide it can be used as is and has a routine that you can just call from inside of your main loop to add a sample to be played back. Note you might want to remove any pre-processor and mixing mode you don't use to save space. *) you could also wait the exact amount of time needed, but that would desynchronise your main loop from the display, which is probably not what you had in mind. Last edited by roondar; 17 April 2019 at 00:45. |
|
17 April 2019, 14:23 | #15 | ||
Natteravn
Join Date: Nov 2009
Location: Herford / Germany
Posts: 2,553
|
Quote:
Quote:
BTW, I only had a quick glance over the source, so I assume that you always allocate one fixed channel for mixing, also when no effects are played? Playing the mixing-buffer as a sound-effect only when there is something in it would be a great enhancement! |
||
17 April 2019, 15:02 | #16 | |
Registered User
Join Date: Jul 2015
Location: The Netherlands
Posts: 3,438
|
Quote:
Your idea would be a interesting improvement to consider. You could even combine that with your ProTracker with sound effects system - play 4 channels of music and interrupt that as needed for a sound channel from the mixer. There would be a mechanism needed to make sure that the sound effects don't skip from channel to channel mid-mix though, but I'm sure that could be made. So as always, there's some room for improvement here The primary reason for it being like this is to keep the example code as simple as possible (well that and the less choices the audio interrupt handler has to make, the smaller the overhead). The second reason is more of a personal preference, not allowing music on the mixer channel means it won't ever cut out music playback and that has been a standard critique of Amiga audio for years so I decided to simply play back 3 channels for music instead. That said, your ideas are certainly interesting and could potentially make this idea work even better |
|
17 April 2019, 15:07 | #17 | |
Defendit numerus
Join Date: Mar 2017
Location: Crossing the Rubicon
Age: 54
Posts: 4,501
|
Quote:
You must always remember that in Amiga there are no values like exactly 50 (or exactly 60) but everything is a multiple of the C2 clock (3546895Hz on PAL Amiga). So in frame you have 227 C2/line * 313/line (on default long frame) for a grand total of 71051 'slot'. Yes, ugly values (are even prime numbers..) but anyway both value are possible audio periods to 'sync'/subdivide with 71051. Ok, ok, Paula accept only values in word granularity but not a problem at all. PTR and LEN are buffered so in one frame you can setup (x+1)/2 in LEN, then (x-1)/2 and alternate. Every source that sync with video (VBI, Copper, even a polling to a fixed line) can be used. What values for x? Well, the reciprocals If you setup PER = 227 (15625.09 Hz) then LEN = (313+1)/2 = 157 and (313-1)/2 = 156. If you setup PER = 313 (11331.93Hz) then LEN = (227+1)/2 = 114 and (313-1)/2 = 113. It is therefore necessary to define the double buffer, in which to insert the mixed samples, of the right dimensions (2*LEN). Among other things, the value PER = 313 gives you a frequency that is practically indistinguishable from the 11025 you chose. So this can be a viable alternative: insert the mixing routine in VBI, together with the module player. Cheers! |
|
17 April 2019, 15:20 | #18 | |
Registered User
Join Date: Jul 2015
Location: The Netherlands
Posts: 3,438
|
Quote:
Your method would obviously also work and thus is fully valid, but to be honest I personally don't like the idea of switching between buffers of multiple sizes that much. Not because it doesn't work, but because it means a little bit of extra code to select between buffer sizes The Amiga (IMHO anyway) offers a much nicer solution to this - the Audio interrupts allow you to pick any buffer size and simply fill it as needed. No change in size ever needed. On the other hand, keeping it in the VBI does mean the VBI never gets delayed and my code does sometimes delay the VBI (because the audio interrupt has a higher priority). Personally, I still like this more than the alternative, but I do understand that's merely my opinion and yours might differ. So, if you really do not want to use audio interrupts, your solution is definitely an option *) the audio interrupt effectively runs slightly slower than 1/50th of a second. Last edited by roondar; 17 April 2019 at 15:26. |
|
17 April 2019, 15:23 | #19 | |
Registered User
Join Date: Nov 2006
Location: Stockholm, Sweden
Posts: 237
|
Quote:
(It assumes 227 buscycles per line, 312 lines during a short frame, 313 lines during a long frame. It counts samples using 32.32 fixed point, and looks at the LOF flag when determining how many bus cycles will pass during the upcoming frame.) Perhaps a tad overcomplicated though Switching between two buffers is probably more straightforward. Another approach that I have used in the past is to do mixing in the VBI, always mixing a few more samples than necessary, and use the end-of-audio interrupt to inform the VBI of when the audio hardware has reached the end of the buffer - and include throttling in the VBI, so it skips mixing if the end-of-audio interrupt is too delayed compared to the mixing. Last edited by Kalms; 17 April 2019 at 15:29. |
|
17 April 2019, 15:42 | #20 | ||||
Defendit numerus
Join Date: Mar 2017
Location: Crossing the Rubicon
Age: 54
Posts: 4,501
|
Quote:
Quote:
Quote:
I've a full working mixer for a 4channels->1channel module player that use Audio IRQs. When I find some time I also tell you how the mixing is done. Quote:
|
||||
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
Thread Tools | |
Similar Threads | ||||
Thread | Thread Starter | Forum | Replies | Last Post |
Using blitter for sound mixing? | idrougge | Coders. Asm / Hardware | 20 | 23 December 2022 16:13 |
CSPPC and Mixing UltraWide and Fast Scsi Devices | reflex | support.Hardware | 5 | 18 December 2018 13:26 |
Turrican 2 with replayer patched to use 28khz mixing? | Dr.Venom | project.WHDLoad | 3 | 05 December 2016 07:50 |
Games and examples with source available | TurboCrash | Coders. Blitz Basic | 3 | 06 January 2016 17:50 |
Mixing Retr0Bright safely without a blender? | mancalledSun | support.Hardware | 3 | 04 February 2010 19:45 |
|
|