Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion src/sound/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,56 @@ This folder contains the related files for all sound APIs.

## Documentation of sound design

**Fixme:** The sound design is not yet documented.
This describes how the code behaves today. It covers the device lifecycle and the threading rules
around the audio callback; the areas still missing are listed at the end.

Each platform provides one `CSound` class deriving from `CSoundBase`, and exactly one of the
subdirectories is compiled in: `asio/` (Windows), `coreaudio-mac/`, `coreaudio-ios/`, `oboe/`
(Android) and `jack/` (where JACK is enabled).

### Buffer size negotiation

`Init ( iNewPrefMonoBufferSize )` returns the mono buffer size the device actually accepted,
which may differ from the one requested. `CClient::Init()` uses that return value to find out
which sizes a device supports, so it calls `Init()` four times per invocation: once for each of
`FRAME_SIZE_FACTOR_PREFERRED`, `FRAME_SIZE_FACTOR_DEFAULT` and `FRAME_SIZE_FACTOR_SAFE` to fill
`bFraSiFactPrefSupported`, `bFraSiFactDefSupported` and `bFraSiFactSafeSupported`, then once with
the size selected in the settings. Those three flags drive the enabled state of the buffer delay
radio buttons, and the settings dialog polls them once a second; no signal runs from the sound
device to that dialog.

A driver may also change the buffer size on its own. `kAsioBufferSizeChange` in the ASIO backend

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd turn this around to state what can be done, how it can be done and then state which drivers currently do it (ideally, "and why" as well as "and why none of the others").

and JACK's buffer size callback both report that by calling
`EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT )`, which reaches
`CClient::OnSndCrdReinitRequest` and repeats the negotiation above.

### Start, stop and the audio callback

`Init()` is only ever entered with the device stopped. Callers that may be running stop it first
and restart it afterwards, which is the `bWasRunning` pattern throughout `client.cpp`.

The audio callback runs on a thread owned by the driver. `CSoundBase` inherits `QThread`, but
nothing here overrides `run()` or calls `start()`, so no such thread exists. Backends keep the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is worth investigating. If the architectural framework was put in place to put this on a thread, it's probably meant to be on a thread or the code should be cleaned up.

The architecture is meant to have one of the following:

  • GUI:
    • Qt main thread at normal priority
    • CClient with CSound (to/from the audio hardware) and CChannel (to/from the network) at raised "real time" priority
  • Headless:
    • CClient with CSound (to/from the audio hardware) and CChannel (to/from the network) at raised "real time" priority

callback away from a device that is being re-initialised in two ways, and the ASIO backend is the
exception to both:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I make that three ways, then. It should be written plainly.


| backend | audio callback | ignores the callback while stopped | takes `MutexAudioProcessCallback` |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth seeing why this variation exists, anyway.

|---|---|---|---|
| JACK | `process()` | yes, `IsRunning()` | yes |
| CoreAudio (macOS) | `callbackIO()` | yes, `bRun` | yes |
| CoreAudio (iOS) | `processBufferList()` | no | yes |
| Oboe | `onAudioReady()` | yes, `!bRun` | yes |
| ASIO | `bufferSwitch()` | no | no, it uses its own `ASIOMutex` |

`CSoundBase::Stop()` clears `bRun` and then takes `MutexAudioProcessCallback` to wait for a
callback that is already in flight. The ASIO backend never takes that mutex, so on Windows that
wait returns immediately and `CSound::Stop()` waits on `ASIOMutex` instead.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it clear whether ASIOMutex is owned by the ASIO code or by the Jamulus code.


### Not yet documented

- device enumeration, and what `SetDev()` does when a device cannot be used
- input and output channel selection, and the input channel mixing in the callbacks
- MIDI: device selection, controller mapping and `ParseMIDIMessage()`
- latency reporting via `GetInOutLatencyMs()`
- the sound card conversion buffer used when a device's buffer size is not a multiple of the
system frame size