home *** CD-ROM | disk | FTP | other *** search
- The Audio Device On The Amiga
- -----------------------------
- May, 1986, somebody asks: "Why couldn't they have made audio easier to use?"
-
- Is it really so difficult, I ask myself. After all, it seems pretty
- thoroughly documented. But then again, why are so few people using
- audio so far if it is supposed to be so good on the Amiga. But I'm
- really busy trying to document other things so I leave audio to the
- experts. As help is requested, it appears that the designer of the
- audio device is advising people in person. I guess that whoever
- needs help is getting directly to him.
-
- Fast forward to November, 1986. I am not at Amiga any more.
- I'm consulting for a couple of other places and doing the Programmers'
- Guide To The Amiga in whatever spare time I have left. Comes the
- time to do an audio chapter. Hmmm, the ROM Kernel stuff
- describes a lot of features, but demonstrates only a few of them.
- And it goes directly to the hardware. I don't want to do that!
-
- My editor recommends another Amiga book that seems to have an "inside look"
- at the audio. So I got it and, oh no, it goes directly to the hardware
- too! What I want to do is queue up several sounds, and have
- the the audio system play them sequentially while my task goes on to do
- something else. Going directly to the hardware meant the audio device
- will neither count cycles for me nor queue sounds for automatic
- play.
-
- Sound queueing could be done using the audio device command
- called CMD_WRITE, but at first I found no examples that used the
- CMD_WRITE command. Later, after several false starts (and after
- completing the audio chapter and most of the rest of the book) I
- finally discovered an example program on Usenet (reposted from BIX)
- that used CMD_WRITE. Some pieces of that program, along with what
- I developed subsequent to the audio chapter, appear here.
-
- I simply wanted my main program to look like this:
-
- main()
- {
- /* ... (program-stuff) ... */
-
- InitAudio(); /* initialize everything */
-
- channel = GetChannel(-1);
- if(channel != -1)
- {
- PlayNote(channel, waveform, note_no, volume, duration);
- /* ... (more PlayNotes) ... */
- }
- /* do non-audio things here */
-
- FreeChannel(channel);
- FinishAudio(); /* close everything down */
- }
-
- THAT seemed to make it simple, and it seemed to be what I wanted (and what
- others had been asking for). The details of device access and message passing
- are buried in a subroutine somewhere, where a person need not deal with it.
- After I created some of these routines I tested them on a few cases and
- they seem to be what people wanted, so here they are.
-
- The functions you see above are provided in
- this article, with other support functions for the audio device.
- By examining the source code I've provided, you will see
- just how to communicate with the audio device and you
- may be able to add your own enhancements to these techniques.
-
- At the end of the article, you'll find a list of some of the
- enhancements I expect to install by the time this article actually
- appears in print, as well as other features that people have requested.
-
- Note: If not otherwise stated, all parameters passed to the routines and
- passed back as return values are LONG integers (32-bits). Sometimes
- a pointer (also 32-bits) is used, and is shown as such.
-
- The Functions, Explained
- ------------------------
- 1. gotchannel = GetChannel(channel);
-
- channel - any number from 0 to 3, corresponding to a specific
- hardware channel on the Amiga. If you ask for channel
- number -1, it means get ANY channel that is available.
- The function GetChannel returns the channel number, or
- returns -1 if none are available to you.
-
- 2. PlayNote(channel,waveform,note_no,volume,duration,priority,message);
-
- channel - a channel that you already own. If you
- don't own it, the note simply will not play.
-
- waveform - a pointer to the start of a waveform table
- that contains 256 samples of a single wave
- of your sound. Sample values range from
- -128 to +127. The waveform table also includes
- copies of the same waveform, each having fewer
- and fewer samples in the table (128 samples,
- 64, 32 and so on).
-
- This waveform table lets us stay within
- the allowable limits of the Amiga audio hardware.
- In particular, period values of 127 through 500
- are the values that lets the Amiga output the
- best quality audio. To get an output that is
- of a high frequency, since the period values
- are limited, each wave of the waveform must be
- output more quickly. Thus the table with
- several copies of the waveform, each having
- different numbers of samples. See the source
- code for MakeWaves to see how the tables are built.
-
- Note Number - Notes are numbered from 0 to 95, structured
- as 8 octaves of 12 notes each. Each octave has
- its own waveform table entry having a length
- appropriate to that octave.
-
- Volume - Takes a value from 0 to 64 where 0 is minimum.
-
- Duration - specified in 1000ths of a second. Five Hundred
- thousandths of a second, for example, is one-half
- of a second. The audio device accepts a command
- to output a specific number of cycles of a waveform.
- I calculate the frequency (in cycles per second)
- from the note number, then multiply by Duration
- and divide by 1000, yielding the correct number
- of cycles for that frequency. Thus all notes
- play for the correct time.
-
- Priority - [NOT IMPLEMENTED YET]. If priority is 0,
- just queue the note. If less than 0, flush all
- current requests for this channel and start this
- note only. If greater than 0, do not flush...
- the priority value is only going to be used to
- identify the note number to you when the note
- begins to play.
-
- Message - [NOT IMPLEMENTED YET]. Audiotools can send
- you a message that contains an identifier of
- your choice (the priority value) to let your
- task know that this note has just begun to
- play. On receiving the message, your task
- must reply to it so that the audiotools can
- reuse or deallocate the message memory.
-
- 3. FreeChannel(channel) - frees a channel that you own to let another task
- (or your own task, later) use the channel.
-
- 4. InitAudio()
- 5. FinishAudio() - These functions take care of the background work,
- such as opening and closing the audio device.
-
- By using these routines, you do not have to deal with the audio device
- at all. You need not allocate and initialize message blocks and so on.
- All of this is built into the support routines and associated global variables.
-
- By using the routines, though, you add some additional overhead to
- accessing the audio device. If you are designing a high performance
- audio routine, you just may have to lock the channels and go directly
- to the hardware. For that kind of thing, you have the ROM Kernel
- examples to guide you.
-
- But for the rest of us, who just need to BEEP at somebody, these
- routines make the access just a bit simpler (and provide a
- functional jumping off point for further audio development).
- You could also get rid of the subroutine call overhead by copying
- appropriate portions of code directly into your main program.
-
- Note: PlayNote is asynchronous. This means that it queues up a note to be
- played by the audio device and then returns to the calling program
- immediately. (It does not wait for the note to be finished before
- it returns to the caller). ALL other functions in this article
- are synchronous. That is, the function is performed entirely before
- your program goes on to do something else.
-
-
- What The Sample (main) Program Does
- -----------------------------------
- Using the above functions, main simply plays a few notes through each
- channel, in each of the waveforms: sawtooth, triangle and square waves.
- All 4 channels are active at the same time. When all notes have completed,
- the program exits.
-
-
- Support Functions
- -----------------
- This audio library has the following support functions.
-
- 6. error = StopChannel(channel)
- 7. error = StartChannel(channel) - stop or start a specific channel.
-
- If a CMD_WRITE arrives at a stopped channel, it queues
- and waits for the channel to be started. A return value
- of 0 means no error. A return of -1 indicates low memory.
- Any other value is a direct return from io_Error. See
- devices/audio.h for meanings of other return values.
- StopChannel terminates any CMD_WRITE currently in progress.
-
- 8. error = FlushChannel(channel) - If there are CMD_WRITE's lined up
- to be played, return them all to the caller (flush input).
-
- 9. error = ResetChannel(channel) - reset it to its default values.
- Also means flush a channel's input queue.
-
- 10. stereopair = GetStereoPair(pair) -
- In the Amiga hardware, audio channels 0 and 3 are connected
- to the Left audio output; audio channels 1 and 2 are connected
- to the Right audio output. Thus to get a stereo pair, you
- need left-channel and one right-channel.
-
- Specify pair as -1 for ANY stereo pair,
- or specify 0 (for audio channels 0 and 1), 1 (for 0 and 2),
- 2 (for 1 and 3) or 3 (for 2 and 3). Return value is the pair
- that was available, or -1 if no stereo pair is available.
- FreeChannel(stereopair) works just as FreeChannel(channel).
-
- 11. error = SetPV(channel, period, volume) - set the period and volume of
- a note that is playing currently. Note that there is only
- a limited range available for the period (roughly 127 to 500)
- so is it more likely that you would use PlayNote instead since
- PlayNote can modify the waveform pointer as well as the other
- parameters.
-
-
- Internal Functions
- ------------------
- The following internal functions are used by the library functions
- shown earlier. Your programs may, at times, need these functions as well.
- These functions create, initialize and free audio device message blocks.
- (I call them IOBs, for I/O Blocks).
-
- 12. iob = GetIOB() - allocate or assign an IOAudio structure for use.
- Returns a value of 0 if system is too low on memory.
- If no IOB is available from a specified pool of IOB's,
- then dynamically allocate an IOB and pass back its address.
-
- Note: for more advanced system functions suggested by the users
- group members (shown later in this article), this structure
- may need to be extended to hold additional parameters.
- For now, though, the ExtIOB structure is identical
- to the normal IOAudio structure (created for now by a define
- statement). This allows us to define an extended version
- of the structure later, with little if any change to
- existing functions.
-
- 13. ReEmployIOB() - look at audio channel reply ports and see if any
- IOB's have returned (are now unemployed) and can therefore
- be reassigned or deallocated.
-
- 14. FreeIOB() - return a finished IOB to the free IOB pool or deallocate
- a dynamically allocated one.
-
- 15. InitBlock(iob,channel) - Initialize an IOB for communication with a
- specific channel, default command is CMD_WRITE. iob is a pointer
- to an IOAudio structure. Channel is the specific channel for
- which this block is to be initialized (allocation key is the
- critical item).
-
- 16. ExpandWave(waveform_pointer) - Takes a pointer to a waveform buffer
- that contains one cycle of a waveform, in 256 consecutive
- bytes, and expands the table to add the same waveform sampled
- 128 times at twice the sampling interval, 64 times at 4 times
- the sampling interval, 32 times/16 times/8 times and so on.
-
- NOTE: the wave tables MUST be in CHIP memory otherwise
- the audio device will be unable to play the notes!!!
-
- ExpandWave is associated with MakeWaves() that creates three
- tables total, one containing a sawtooth wave, one a triangle
- wave, and the third contains a square wave. ExpandWave
- completes the table entries for each waveform.
- All of the waveforms are left in contiguous memory after the
- first wave, in order of decending sizes (256, 128, 64, ...)
-
- MakeWaves, ExpandWave, SetPV and PlayNote are paraphrased versions of
- similar routines found in a posting to BIX by Steven A. Bennett.
- Thanks, Steven, for the inspiration on this project. Steven's posted
- article also provided the waveform and period tables I've used, as
- well as the excellent explanation of the period value calculation
- that I've quoted (slightly modified) below.
-
- As you examine the source code provided, you'll see that the
- audio device requires a period value rather than a frequency value.
- The period table contains the period value corresponding to the
- frequencies of the normal scale (12 notes per octave... see ABasiC
- manual, page 138). You could calculate period yourself from the
- formula:
-
- period = Clock / ( samples-per-wave * frequency)
-
- Clock rate is 3,579,545 cycles per second
-
- So if you are playing a wave table that contains 32 samples, and
- your selected output frequency is to be middle-A (440 hz),
- of the piano the period value must be
-
- 3579545 / (32 * 440) = 254.229.
-
- <SAB>: "But the audio device only accepts a whole number, so your result
- has to be rounded down to the nearest integer which can result
- in a maximum frequency error of about .25%, assuming one uses the
- octave for frequency between period 226 and period 428. (This comes
- out to be less than a twentieth step at the shortest period)."
-
- <SAB>: "Period values of less than 127 are illegal, as there aren't enough
- cycles set aside for audio DMA for anything less. period values of
- greater than 500 or so aren't recommended as the anti-aliasing
- filter isn't of much use then, and actually could cause a possible
- high pitched overtone, which I'm sure nobody wants. Thus I am
- only going to use SetPV to handle a single octave's range."
-
- <SAB>: "Changes of octave are accomplished by doubling or halving the
- number of samples in one cycle of the waveform, so one must, therefore,
- call PlayNote() instead of SetPV()."
-
-
- Additional Information About Internal Functions
- -----------------------------------------------
- For GetIOB, you can control how many structures are allocated for
- IOAudio use. How many audio ioblocks should the system have available for
- queueing up notes? If you want to queue up a whole song
- by using a whole bunch of PlayNote commands and go away to do
- something else, it could take a lot of memory! Once the system
- runs out of these preallocated structures, it must dynamically
- allocate and free memory... this can cause fragmentation of memory space.
- You might want to send parts of the song at a time instead of the
- whole song.
-
- Depending on the variable AUDBUFFERS, defined when the
- program is compiled, GetIOB either returns the address of
- a buffer in global memory space, named "global"
- (in the name field of the I/O message, node area) or
- named "dynamic" if GetIOB runs out of AUDBUFFERS global blocks
- to use. The number of dynamic blocks is limited only by the
- available system memory (FAST memory, that is non-CHIP memory
- is used for the I/O blocks).
-
- NOTE: To be able to use only a standard sized IOAudio structure
- for the message passing, I assigned the message mn_Length field
- to identify the global blocks. (As of 1.1 and 1.2, the mn_Length
- field is still available for anybody to decide what meaning it has).
- To be perfectly safe, as well as to handle the advanced functions
- that people have requested, an extended audio block should probably be
- used... with a LONG quantity appended to it as the identifier
- in place of using mn_Length, as well as a few other fields. This
- change is very likely to be made for the disk version of the tools.
- (The structure ExtIOB will be used as an extended version of IOAudio).
-
- The Audio Tools And A Test Program
- ----------------------------------
- Here are the listings that implement the tools described above.
- Following the listings are some improvements that have been
- suggested. Whatever I've been able to implement of those
- improvements, as well as this code, appears on the disk mentioned
- at the end of the article.
-
- I hope these help you to better understand the audio system of
- the Amiga.
-
- ========================================
-
- <Insert All Listings Here>
-
- ========================================
-
-
- What People Have Suggested
- --------------------------
- I did a chalk-talk at a developers' group meeting, and showed them what
- I was working on for this article. They suggested the following additions
- to what you see described above.
-
- 0. Add examples that use the stereo pair; check that
- all error conditions are properly reported ("bullet-proofing").
- (This is numbered "0" because it just has-to-be-done).
-
- 1. Implement the Priority and Message fields of PlayNote,
- just as described.
-
- 2. Add a PlaySong function that can take a pointer
- to a data structure that describes a song, with
- some of the parameters that PlayNote takes, and
- just play the song automatically.
-
- 3. Add a PlayWave function to handle sampled sounds, such as:
-
- PlayWave(channel,sample_addr,copy,period,repeats,priority,message)
-
- Global variables would be expanded to include a separate
- ReplyPort for the sampled sounds. A copy (TRUE/FALSE)
- parameter would specify (if TRUE) that the sampled_wave should be
- copied (into chip memory) before queueing it to be played.
- (Only CHIP memory waveforms can be played anyway).
- If FALSE, it would assume that sample_addr is in chip
- memory and that you will not change the contents of memory
- before the note has completed playing.
-
- 4. Add a PlayFreq function that takes a frequency value instead
- of a note number so that oriental music, for example, not
- based on the same scale we use for a piano, could be played.
- PlayFreq would take exactly the same parameters as PlayNote
- but substitute "frequency" for "note_no". It would calculate
- which is the longest waveform that can be used for the
- selected frequency and still leave the period value within
- the appropriate range of 127 to 500.
-
- 5. Add an implied-rest between notes so that it would sound
- more natural and avoid having to explicitly encode such
- rests into a song structure.
-
- 6. Add the ability to specify a slew rate for either volume
- or frequency or both so that notes instead of going
- directly from one setting to another can slide to
- the new setting at a specified rate. Or perhaps
- better still, add full ADSR capabilities.
-
- This last one is a little tricky. It could require
- software interrupts or perhaps even breaking into the
- audio interrupt vector itself. It will also require
- a data structure larger than the basic IOAudio structure
- to hold these new variables.
-
- This article is basically a report on current progress on a continuing
- project whose goal is to develop a freely distributable set of license-free
- routines that make it easier to use Amiga audio. I welcome suggestions
- as to additional enhancements that might be useful, or code samples
- that implement such enhancements.
-