Introduction
The SoundDMA module (Sound0) implements the lowest level of the RISC OS sound system, generating the interrupt-driven flow of sample data to the sound hardware. Its original interface is described in the Programmer's Reference Manual chapters on the sound system and on SoundDMA itself, and its first significant extension, adding 16-bit linear stereo output alongside the original 8-bit mu-law format, is described in the addendum "16-Bit Stereo Sound Support for RISC OS" (referred to below as the 16-bit stereo addendum).
Since that addendum was written, SoundDMA has continued to develop in two directions. First, the module gained a small self-describing capability query, and a set of notifications broadcast whenever the sound system starts, stops, or has its configuration changed, so that other software can react to these events without polling. Second, RISC OS has grown a family of machines with more than one sound controller (for example, more than one hardware audio path, or a machine capable of routing sound through different physical outputs); a HAL-based module, Sound0HAL, was introduced to support such hardware, sharing the same SWI chunk and the same Service_Sound reason-code space as SoundDMA, so that client software can use one consistent interface regardless of which of the two modules answers a given call.
This document is a second addendum to the SoundDMA chapters. It describes:
- the SWI Sound_ReadSysInfo SWI, and how client software should use it to discover which of the facilities below are actually available before relying on them;
- the new Service_Sound reason codes broadcast when the sound system starts, stops, or has its sample rate or buffering changed;
- the SWI numbers and reason codes for sound-controller enumeration and selection, as implemented by Sound0HAL; and
- a set of implementation decisions, seen in the RISC OS Select lineage of this module, concerning how the sound DMA buffers are obtained and mapped, which are useful background for anyone maintaining or re-implementing a compatible module.
Everything in this document is additional to, not a replacement for, the base PRM chapters and the 16-bit stereo addendum: the SWIs described there (SWI Sound_Configure, SWI Sound_Mode, SWI Sound_LinearHandler, SWI Sound_SampleRate, and so on) are unaffected by anything documented here.
Overview
SoundDMA occupies a fixed SWI chunk, and its module header additionally defines a block of Service_Sound sub-reason codes used to announce changes within the sound system. Historically, both of these number spaces grew only when SoundDMA itself gained new facilities. That changed when Sound0HAL was introduced: rather than allocate Sound0HAL its own, separate SWI chunk and service numbering, its SWI numbers and Service_Sound reason codes were defined to continue on directly from SoundDMA's own, and SoundDMA's own header is kept up to date with Sound0HAL's additions even where SoundDMA itself does not implement the corresponding facility. The result is that a client program can call, for example, SWI Sound_ReadSysInfo without needing to know or care whether the underlying sound driver is SoundDMA or Sound0HAL.
This matters in practice because SoundDMA (this module) targets classic VIDC- and IOMD-based sound hardware, which provides exactly one sound controller, whereas Sound0HAL supports hardware with a HAL-described choice of controllers. Client software that only ever expects a single controller can safely ignore the controller-selection facilities described below; that is precisely why they were made discoverable through SWI Sound_ReadSysInfo rather than assumed.
Sound0HAL does not assume its set of sound controllers is fixed at start-up. Each controller is a HAL device of the audio type, and Sound0HAL learns of one, and stops using one, through the same Service_Hardware mechanism used for HAL device claim and release generally, rather than through any facility specific to sound. Consequently controllers can appear or disappear while the machine is running, for example a removable audio interface being attached or detached, and the set reported by SWI Sound_EnumerateControllers reflects whatever is present at the moment of the call. If the controller currently selected as default disappears, Sound0HAL stops sound output and, if another suitable controller remains, switches to it automatically; it does not attempt to fall back to a previous controller if a newly selected one fails to start.
The notification service calls exist for a different reason: several parts of the desktop (loudness/mixer utilities, oscilloscope-style monitoring tools, and power-management code) need to know promptly when the sound system's operating state changes, and previously had no way to find out except by polling SWI Sound_Enable or SWI Sound_SampleRate repeatedly. The new Service_Sound reason codes let such software register once, via SWI OS_ServiceCall claim handling in the usual way, and be told as changes happen.
Terminology
- Sound controller: a distinct hardware sound-generation path (for example, a particular set of DACs and associated mixer), as enumerated by Sound0HAL on hardware that provides more than one. SoundDMA, on VIDC/IOMD hardware, always presents exactly one controller and does not itself implement enumeration.
- Sound0HAL: the HAL-based sound driver module that extends the numbering defined by SoundDMA to cover controller enumeration and selection, and implements the SWIs and reason codes that do so. Its internal handling of individual HAL audio devices (DMA scatter lists, interrupt handling, and the like) is outside the scope of this document; the SWI and service interface it shares with SoundDMA, and the sound-controller enumeration model built on top of it, are described here.
Technical details
Capability discovery
Client software that wants to use any facility described in this document should first call Reads which of the facilities described in this document are supported and inspect the returned flags, rather than infer support from the module's version number. This is because Sound0HAL, not SoundDMA, is what actually implements controller enumeration on hardware that needs it, and the two modules are versioned independently.
SoundDMA's SWI dispatcher does not raise an error for a reason code it does not recognise: it simply returns without altering any register. This applies both to whole SWIs beyond the end of the module's own jump table, and to reason codes of SWI Sound_ReadSysInfo that this module does not handle. Consequently, a call that appears to succeed is not by itself evidence that the requested facility is supported; a caller must check the returned flags (or, for Reads the identifier of the currently selected default sound controller, be aware that this module does not populate any result at all) rather than treat "the SWI didn't error" as confirmation. For this reason, reason codes of SWI Sound_ReadSysInfo are numbered from 1 rather than 0, so that a zeroed or otherwise uninitialised register cannot be mistaken for a valid reason code.
State-change notification
Four new Service_Sound reason codes report changes of state within the sound system: the sample rate or buffer size changing, and the sound system being enabled or disabled. Unlike the older, low-level Service_Sound 0-family notices, which invite a claimant to alter the parameters being set up and are documented as such in the base PRM, these new notices are issued once SoundDMA has already committed to the new state. They are broadcast purely for information: a recipient cannot use them to prevent or change the transition that has already taken place, and should not attempt to.
On hardware where Reads which of the facilities described in this document are supported reports the relevant feature bit clear, these four reason codes are never issued; client software that wants to rely on them should check for this first rather than assume they are always available on every machine SoundDMA can run on.
The SoundChannels module's automatic sample-rate tuning has been enabled or disabled is included in this group for numbering purposes, but is issued by the SoundChannels module, not by SoundDMA; see its own entry below for details.
Sound-controller enumeration and selection
Three further SWI numbers, immediately following SWI Sound_ReadSysInfo in the same chunk, are used to enumerate and select among sound controllers: SWI Sound_SelectDefaultController, SWI Sound_EnumerateControllers, and SWI Sound_ControllerInfo. A fourth reason code, Reads the identifier of the currently selected default sound controller, sits alongside them, to report which controller is currently the default.
These three SWI numbers, and the second SWI Sound_ReadSysInfo reason code, are not implemented by SoundDMA itself: on VIDC/IOMD hardware there is exactly one sound controller, so no enumeration is needed. They are implemented by Sound0HAL, and documented here, alongside the feature flag that announces their availability, so that:
- client software written against this chunk of SWI numbers keeps working unmodified on hardware where Sound0HAL, rather than SoundDMA, answers the call; and
- anyone implementing a compatible sound-controller driver for new hardware follows the same interface, rather than inventing a conflicting one.
A sound controller is identified, throughout all four calls, by an opaque, null-terminated identifier string, obtained from SWI Sound_EnumerateControllers or Reads the identifier of the currently selected default sound controller and passed back verbatim to select or query that controller. Client software should treat this string as an opaque token: it must be passed back exactly as returned, but nothing should be inferred from its content or length. Where a controller identifier is read by SoundDMA rather than written by it (as the input to SWI Sound_SelectDefaultController, SWI Sound_EnumerateControllers, and SWI Sound_ControllerInfo), it may be terminated by a space or any other control character (any byte value 32 or lower), not only by a null byte, so that a controller identifier can be embedded in a larger, space-separated command line without further quoting.
The three calls that return a controller identifier or other variable-length data - Reads the identifier of the currently selected default sound controller, SWI Sound_EnumerateControllers, and SWI Sound_ControllerInfo - share a common buffer convention: the caller supplies a buffer pointer and its length, and on exit the length is updated to the length of data actually available, whether or not the call succeeded. If the supplied buffer is too small, the call returns an error indicating a buffer overflow, but still updates the length register with the length that would have been required, so that a caller can allocate a larger buffer from this value and repeat the call, rather than having to guess a sufficiently large buffer size up front.
Sound DMA buffer allocation
The following implementation decisions are drawn from the RISC OS Select lineage of this module. They do not change any SWI or service call documented above or in the base PRM, but they are useful background for anyone maintaining, re-implementing, or closely interoperating with a compatible SoundDMA module, and one of them - read access to the DMA buffer memory - is directly observable by client software.
Rather than rely on a fixed area of low memory set aside by the kernel at boot, the module can instead create its own dynamic area, named "Sound hardware buffers", at initialisation, sized to hold its two DMA buffers, and release it again at finalisation. This removes the need for the kernel to reserve any fixed sound workspace address in advance; a module built this way holds the whole of its Level 0 workspace, including the DMA buffers, within memory it has allocated for itself.
The dynamic area's pages are obtained by requesting DMA-able memory specifically (using the "recommend pages" operation of SWI OS_Memory with the DMA-able-memory bit set), so that the buffers are always taken from genuine, on-board, DMA-capable RAM rather than from any memory that is unsuitable for hardware DMA transfer (such as video/Stealth memory when present). Because the sound hardware only supports a single page per buffer, the two buffers are allocated as contiguous pages, and the area's pre-grow handler maps them in one page at a time as the area is created.
The dynamic area is flagged for user-mode read access rather than for user faulting. This is a deliberate relaxation: there is little reason why a user-mode client - for example, a simple oscilloscope-style display, or a utility inspecting the raw sample stream - should be prevented from reading the buffer memory, and a number of such utilities exist. The area remains protected against being written from user mode, and against being paged out, cached, or made bufferable, so this does not weaken the guarantees SoundDMA itself depends on.
The value used to mark "no channel handler installed" or "no scheduler installed" in the Level 0/Level 1 data structures (documented in the base PRM) is a fixed sentinel constant rather than an address mask. Client code that installs or removes channel handlers should compare against the documented constant symbolically, rather than assume any particular bit pattern, since the exact value used for this sentinel is not guaranteed to remain the same across versions of the module.
Finally, a module built this way restricts its own initialisation to machines it recognises as suitable (RiscPC, A7000, A7000Plus, or a virtualised RiscPC), and exits without claiming any vectors on other hardware, rather than attempting to run and failing later. Anything that replaces or extends SoundDMA on new hardware should apply an equivalent check appropriate to that hardware, rather than assume the module will simply refuse gracefully by itself.
Audio HAL device interface
Sound0HAL drives each sound controller through a HAL device of the audio type, discovered as described above. Such a device begins with the fields common to every HAL device - whose layout is defined elsewhere and is outside the scope of this document - followed by an audio-specific extension that Sound0HAL uses directly to control the controller and move sample data to it.
Audio HAL device (audio-specific fields)
The audio-specific fields follow immediately after the generic HAL device header, in the order given below. Sound0HAL only uses a device that declares an API version between 1 and 3 inclusive; fields introduced by a later API version are not present, and must not be accessed, on a device declaring an earlier one.
| Field | Size (bytes) | Contents |
|---|---|---|
| AudioMixer | 4 | Pointer to the associated HAL mixer device, or 0 if the controller has no hardware mixer of its own; see Reads the mixer device associated with a sound controller. |
| AudioChannelsOut | 4 | Number of output channels the controller provides. |
| AudioChannelsIn | 4 | Number of input channels the controller provides. |
| AudioDMAParams | 20 | API 1 and later. Values of R0, R1, R2, R3, and R7 to supply to DMA_RegisterChannel when registering this controller's DMA channel. |
| AudioPreEnable | 4 | API 1 and later. Entry point called before DMA is enabled, with R1 set to the DMA buffer size. |
| AudioPostEnable | 4 | API 1 and later. Entry point called after DMA is enabled, with R1 set to the DMA buffer size. |
| AudioPreDisable | 4 | API 1 and later. Entry point called before DMA is disabled. |
| AudioPostDisable | 4 | API 1 and later. Entry point called after DMA is disabled. |
| AudioIRQHandle | 4 | API 1 and later. Interrupt handler entry point; returns, in R0, 0 for normal servicing or 1 to request that Sound0HAL reset the audio hardware. |
| AudioNumRates | 4 | API 1 and later. Number of entries in the sample rate table pointed to by AudioRateTable. |
| AudioRateTable | 4 | API 1 and later. Pointer to the sample rate table described below. |
| AudioSetRate | 4 | API 1 and later. Entry point called, with R1 set to the 0-based index into the sample rate table of the rate to select, to change the controller's sample rate. |
| AudioCustomDMAEnable | 4 | API 2 and later. Where present, replaces AudioPreEnable and AudioPostEnable for a controller that manages its own buffering rather than using DMAManager. Called with R1 = DMA buffer size, R2 and R3 = the two buffer addresses, and a callback parameter and function pointer passed on the stack. |
| AudioFlags | 4 | API 2 and later. Flags describing the controller's data requirements and behaviour, described below. |
| AudioMinBuffSize | 4 | API 2 and later. Minimum buffer size the controller accepts, in bytes. |
| AudioBuffAlign | 4 | API 2 and later. Required buffer alignment and size granularity, in bytes. |
Two flag bits are defined for AudioFlags:
| Bit(s) | Name | Meaning | |
|---|---|---|---|
| 0 | StereoReverse | Clear: | Sample data is supplied to this controller in the VIDC-style right-left channel order used elsewhere in this chapter. |
| Set: | Sample data must instead be supplied to this controller in left-right channel order. | ||
| 1 | Synchronous | Clear: | API 3 and later, when AudioCustomDMAEnable is in use: buffer-fill processing is deferred rather than performed directly from the callback function. |
| Set: | API 3 and later, when AudioCustomDMAEnable is in use: the controller's callback function performs buffer-fill processing synchronously, within the callback itself. | ||
Each entry in the sample rate table pointed to by AudioRateTable is 8 bytes long, and gives the rate in the same Hz×1024 units used elsewhere in this chapter (see The sound system's sample rate or buffer size has changed), followed by the corresponding sample period in microseconds.
Service calls
| R0 | = |
Reason code:
| ||||||||||||
| R1 - R9 | = | Dependent on reason code |
| R0 - R9 | preserved | |
Reason codes 8-11 are new since the 16-bit stereo addendum; see the individual entries below, and Technical details above, for how they are used.
| R0 | = | 8 (reason code) |
| R1 | = | 54 (Service_Sound) |
| R2 | = | new sample rate, in units of 1/1024 Hz |
| R3 | = | new number of samples per buffer |
| R0 - R3 | preserved | |
This reason code is broadcast once SoundDMA has already adopted a new sample rate and/or buffer size, whether that change was made through SWI Sound_SampleRate 3 or through an equivalent change made while producing compatibility mu-law output. It is not issued unless the new values actually differ from the previous ones.
Recipients cannot prevent or alter the change; it has already taken effect by the time this notice is broadcast. A recipient that needs to resynchronise buffered data or a rate-dependent display should treat R2 and R3 as informational and, if it needs authoritative current values, re-read them with SWI Sound_SampleRate 1.
This reason code is only issued on hardware where Reads which of the facilities described in this document are supported reports the relevant feature bit set.
| R0 | = | 9 (reason code) |
| R1 | = | 54 (Service_Sound) |
| R0 - R1 | preserved | |
Broadcast when sound output is enabled, for example by SWI Sound_Enable being called to turn the sound system on. As with the other new reason codes, this is a pure notification: the transition has already been made by the time it is broadcast, and it is only issued on hardware where Reads which of the facilities described in this document are supported reports the relevant feature bit set.
| R0 | = | 10 (reason code) |
| R1 | = | 54 (Service_Sound) |
| R0 - R1 | preserved | |
Broadcast when sound output is disabled, for example by SWI Sound_Enable being called to turn the sound system off. This is a pure notification, issued once output has already stopped, and only on hardware where Reads which of the facilities described in this document are supported reports the relevant feature bit set.
| R0 | = | 11 (reason code) |
| R1 | = | 54 (Service_Sound) |
| R0 - R1 | preserved | |
This reason code is issued by the SoundChannels module, not by SoundDMA: SoundChannels shares SoundDMA's Service_Sound reason-code space, and this number is allocated here to keep the numbering coordinated between the two modules. It is broadcast whenever SoundChannels' automatic tuning facility is switched on or off. SoundDMA defines no further register conventions for this reason code beyond the reason code itself; consult SoundChannels' own documentation for anything further it may pass.
SWI calls
| R0 | = |
Reason code:
|
| R0 - R2 | = | Dependent on reason code |
Reason codes are numbered from 1, not 0, because SoundDMA's SWI dispatcher does not raise an error for a reason code it does not recognise: it simply returns without touching any register. Starting the numbering at 1 means a caller can distinguish "this reason code is genuinely unsupported by the module answering the call" from "R0 happened to be left at 0" by the fact that reason code 0 is never assigned to anything.
Client software should call this SWI with reason code 1 before relying on any of the facilities described in this document, since not every reason code, and not every other new SWI in this chunk, is implemented by every module that answers calls in it.
| R0 | = | 1 (reason code) |
| R0 | = | 0 | ||||||||||||||||||||||||
| R1 | = |
Feature flags:
| ||||||||||||||||||||||||
On the classic VIDC/IOMD implementation of SoundDMA, bit 0 depends on the underlying hardware (it is only set on IOMD-based machines with VIDC20 sound hardware), and bit 1 is always clear, since that module supports only a single sound controller and does not implement controller enumeration. Hardware with more than one sound controller is instead served by Sound0HAL, which sets bit 1 when its controller-enumeration SWIs are available.
| R0 | = | 2 (reason code) |
| R1 | = | pointer to a buffer to receive the controller identifier |
| R2 | = | length of the buffer at R1, in bytes |
| R0 | = | 0, or an error if the buffer at R1 is too small |
| R1 | preserved | |
| R2 | = | the length of the identifier actually available, in bytes, including its terminating null; the buffer at R1 is filled in with the null-terminated identifier only if it was large enough |
This reason code is implemented by Sound0HAL, not by the classic VIDC/IOMD SoundDMA module documented elsewhere in this chapter: calling it there returns without altering any register (see SWI Sound_ReadSysInfo above), so a caller must first check Reads which of the facilities described in this document are supported to confirm controller selection is supported before relying on this reason code.
The identifier returned is the same opaque string SWI Sound_EnumerateControllers would return for the controller currently in use, and can be passed to SWI Sound_ControllerInfo to read further details about it, or back to SWI Sound_SelectDefaultController to select it again after some other controller has been selected.
| R0 | = | pointer to the null, space, or control-character terminated identifier of the controller to select |
| R0 | = | 0, or an error if no controller with this identifier is currently present |
This SWI is implemented by Sound0HAL, alongside SWI Sound_EnumerateControllers and SWI Sound_ControllerInfo, for selecting the default sound controller on hardware with more than one; it is not implemented by the classic VIDC/IOMD SoundDMA module documented elsewhere in this chapter, which supports only a single controller. Availability is announced by the ControllerSelection bit of Reads which of the facilities described in this document are supported.
If the identifier given names the controller already selected, this SWI does nothing further. Otherwise, sound output is stopped on the current controller, the new controller is started in its place, and sound is resumed if it was in use beforehand. If the new controller fails to start, this SWI returns the resulting error and does not attempt to fall back to the previous controller, which remains stopped.
| R0 | = | 0, or the null, space, or control-character terminated identifier of the controller to continue from, to list the next one |
| R1 | = | pointer to a buffer to receive the next identifier; may be the same address as R0 |
| R2 | = | length of the buffer at R1, in bytes |
| R0 | = | 0, or an error if the buffer at R1 is too small |
| R2 | = | the length of the identifier actually available, in bytes, including its terminating null; the buffer at R1 is filled in with the null-terminated identifier only if it was large enough |
This SWI is implemented by Sound0HAL, working alongside SWI Sound_SelectDefaultController and SWI Sound_ControllerInfo, for enumerating the sound controllers present on hardware with more than one; it is not implemented by the classic VIDC/IOMD SoundDMA module. Its availability is announced by the ControllerSelection bit of Reads which of the facilities described in this document are supported.
To list every controller present, a caller passes 0 (or a pointer to an empty string) in R0 on the first call, then repeats the call feeding the identifier just returned back in as R0 each time, until the buffer at R1 is filled in with an empty string, which marks the end of the list. Because R1 may be the same address as R0, a caller can step through the whole list using a single buffer. The list reflects the controllers present at the moment of each call; a controller that has appeared or disappeared between two calls may cause the remainder of the enumeration to change accordingly.
| R0 | = | pointer to the null, space, or control-character terminated identifier of the controller to read | ||||||||
| R1 | = | pointer to a buffer to receive the result | ||||||||
| R2 | = | length of the buffer at R1, in bytes | ||||||||
| R3 | = |
Reason code:
|
| R0 | = | 0, or an error if no controller with this identifier is currently present, the buffer at R1 is too small, or R3 is not one of the reason codes below |
| R2 | = | the length of the data actually available, in bytes; the buffer at R1 is filled in with it only if it was large enough |
This SWI is implemented by Sound0HAL for reading details of a particular sound controller identified by SWI Sound_EnumerateControllers, Reads the identifier of the currently selected default sound controller, or SWI Sound_SelectDefaultController; it is not implemented by the classic VIDC/IOMD SoundDMA module. Its availability is announced by the ControllerSelection bit of Reads which of the facilities described in this document are supported. The reason codes below record what each is for and the exact form of data each returns.
| R3 | = | 0 (reason code) |
| R1 | = | filled in with a null-terminated name suitable for presenting the controller to the user, for example in a Configure-style choice of output device; where a translation into the user's chosen language is available, it is returned in preference to the controller's own description |
Reads a name suitable for presenting a sound controller to the user. Defined by Sound0HAL; see SWI Sound_ControllerInfo above.
| R3 | = | 1 (reason code) |
| R2 | = | 4 |
| R1 | = | filled in with a single word: a pointer to the HAL mixer device associated with this controller. If the controller provides no hardware mixer of its own, this instead points to a software mixer device that Sound0HAL substitutes transparently, so that mixer-aware client software need not treat the two cases differently. |
Reads which mixer device is associated with a sound controller, for software that adjusts volume or routing independently of SoundDMA itself. Defined by Sound0HAL; see SWI Sound_ControllerInfo above. The layout of the mixer device itself is defined elsewhere and is outside the scope of this document.
| R3 | = | 2 (reason code) |
| R2 | = | the number of supported rates multiplied by 4 |
| R1 | = | filled in with one word per supported sample rate, each in the same Hz×1024 units used elsewhere in this chapter (see The sound system's sample rate or buffer size has changed) |
Reads which sample rates a given sound controller supports, complementing SWI Sound_SampleRate on hardware where different controllers may support different rates. Defined by Sound0HAL; see SWI Sound_ControllerInfo above.