DirectSound API Reference
Complete reference for the DirectSound subset implemented in free-direct, backed by SDL3 audio streams.
free-direct implements a read/write PCM buffer layer over SDL3. Hardware acceleration, 3D audio, EAX effects, DSBPLAY_LOOPING, and stereo panning are not functional. The implementation is designed to port legacy 2D game audio that uses simple mono/stereo PCM sounds played once.
Contents
- Design and internal architecture
- Global functions
- IDirectSound interface
- IDirectSoundBuffer interface
- Structs
- Flag constants
- Result codes
- Known limitations
Design and internal architecture
DirectSoundCreate()
│
▼
DirectSoundImpl ──── SharedAudioDevice (singleton)
│ │
│ SDL_AudioDeviceID (opened once on first Play)
│
▼
DirectSoundBufferImpl
├── std::vector<uint8_t> ← raw PCM storage (dwBufferBytes)
├── SDL_AudioStream* ← per-buffer stream (created on Play)
├── PCMWAVEFORMAT ← format snapshot from DSBUFFERDESC
├── long volume_cb ← centibels, default 0 (full volume)
├── long pan_cb ← centibels, stored but NOT applied
└── bool playing ← set by Play(), cleared by Stop()
Key design points:
- One SharedAudioDevice singleton is opened on the first
Play()call; all subsequent buffers share the same SDL audio device ID. - Each buffer owns a separate SDL_AudioStream created at play time. This stream is destroyed on
Stop()orRelease(). - Volume is converted from centibels to a linear gain:
gain = 10^(cb / 2000), applied withSDL_SetAudioStreamGain. - Stereo panning computes left/right gains using a constant-power curve but the result is
(void)-discarded — pan is stored but not sent to SDL. - The PCM format is read from
DSBUFFERDESC.lpwfxFormatcast toPCMWAVEFORMAT*to avoid LP64 struct-padding issues — see the PCMWAVEFORMAT section. - Multiple simultaneous buffers each have their own SDL_AudioStream; SDL mixes them at the device level.
Global functions
Creates a DirectSound object. Entry point for all DirectSound use.
HRESULT DirectSoundCreate(
LPGUID lpGuid,
LPDIRECTSOUND* ppDS,
IUnknown* pUnkOuter
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
lpGuid | in | LPGUID |
Device GUID. Pass nullptr to select the default audio device.
Any non-null value is accepted but ignored — there is always one audio device. |
ppDS | out | LPDIRECTSOUND* |
Receives the created IDirectSound pointer. Set to nullptr on failure. Must not be nullptr itself. |
pUnkOuter | in | IUnknown* |
COM aggregation outer object. Must be nullptr; any other value is silently ignored. |
DirectSoundCreate only allocates the IDirectSound object.
The SDL audio device is not opened until the first Play() call on any buffer.
| Return value | Condition |
|---|---|
DS_OK | Object created successfully. |
DSERR_INVALIDPARAM | ppDS is nullptr. |
IDirectSound interface
Obtained from DirectSoundCreate. Pointer typedef: LPDIRECTSOUND.
COM reference counting implemented with std::atomic<ULONG>.
Release() deletes the object when the count reaches zero.
Initial reference count after DirectSoundCreate is 1.
ULONG AddRef();
ULONG Release();
Both return the new reference count after the operation.
Sets the cooperative level for the DirectSound object. In free-direct this call
is accepted for compatibility but does not change any SDL audio behavior.
Audio is always operated at the equivalent of DSSCL_NORMAL.
HRESULT SetCooperativeLevel(
HWND hwnd,
DWORD dwLevel
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
hwnd | in | HWND |
Application window handle. Stored but not used — SDL audio does not require an HWND. |
dwLevel | in | DWORD |
Cooperative level. See DSSCL_* constants. Only DSSCL_NORMAL (0x00000001) is meaningful; all values are accepted without error. |
| Return value | Condition |
|---|---|
DS_OK | Always returned. |
Creates a sound buffer. free-direct creates a software PCM buffer backed by
std::vector<uint8_t>. Hardware buffers are not supported —
DSBCAPS_LOCHARDWARE is silently treated as software.
HRESULT CreateSoundBuffer(
LPCDSBUFFERDESC pcDSBufferDesc,
LPDIRECTSOUNDBUFFER* ppDSBuffer,
IUnknown* pUnkOuter
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
pcDSBufferDesc | in | LPCDSBUFFERDESC |
Pointer to a DSBUFFERDESC describing the buffer. Must not be nullptr. dwSize, dwBufferBytes, and lpwfxFormat must all be valid. |
ppDSBuffer | out | LPDIRECTSOUNDBUFFER* |
Receives the created buffer object on success. Set to nullptr on failure. Must not be nullptr itself. |
pUnkOuter | in | IUnknown* |
COM aggregation — must be nullptr; silently ignored. |
Accepted DSBUFFERDESC.dwFlags combinations and their effect:
| Flag | Support | Notes |
|---|---|---|
DSBCAPS_CTRLVOLUME | Functional | SetVolume applies gain via SDL. |
DSBCAPS_CTRLPAN | Stored only | SetPan stores the value; pan is not applied. |
DSBCAPS_CTRLFREQUENCY | No-op | Flag accepted; no frequency shifting. |
DSBCAPS_LOCSOFTWARE | Functional | All buffers are software. |
DSBCAPS_LOCHARDWARE | Treated as software | No hardware mixing available. |
DSBCAPS_STATIC | Accepted | No semantic difference from non-static. |
| Return value | Condition |
|---|---|
DS_OK | Buffer created successfully. |
DSERR_INVALIDPARAM | pcDSBufferDesc or ppDSBuffer is nullptr, or dwBufferBytes is 0, or lpwfxFormat is nullptr. |
DSERR_OUTOFMEMORY | Allocation of the buffer failed. |
IDirectSoundBuffer interface
Obtained from IDirectSound::CreateSoundBuffer. Pointer typedef: LPDIRECTSOUNDBUFFER.
COM reference counting with std::atomic<ULONG>.
Release() when count reaches zero: calls Stop(),
destroys the SDL_AudioStream, frees the PCM vector.
Initial reference count is 1.
ULONG AddRef();
ULONG Release();
Both return the new reference count.
Obtains write access to a region of the PCM buffer. Returns one or two pointers
directly into the internal std::vector<uint8_t>. No mutex or
synchronization is used — caller must not call Play() while locked.
HRESULT Lock(
DWORD dwWriteCursor,
DWORD dwWriteBytes,
LPVOID* ppvAudioPtr1,
LPDWORD pdwAudioBytes1,
LPVOID* ppvAudioPtr2,
LPDWORD pdwAudioBytes2,
DWORD dwFlags
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
dwWriteCursor | in | DWORD |
Byte offset from the start of the buffer at which to begin the lock.
Ignored when DSBLOCK_FROMWRITECURSOR is set in dwFlags. |
dwWriteBytes | in | DWORD |
Number of bytes to lock. Must be ≤ dwBufferBytes.
When DSBLOCK_ENTIREBUFFER is set this value is ignored and the entire buffer is locked. |
ppvAudioPtr1 | out | LPVOID* |
Receives a pointer to the first (and possibly only) segment of locked memory.
Points into the internal vector. Must not be nullptr. |
pdwAudioBytes1 | out | LPDWORD |
Receives the byte count of the first segment. Must not be nullptr. |
ppvAudioPtr2 | out | LPVOID* |
Receives a pointer to the second (wrap-around) segment, or nullptr
if no wrap occurred. May be nullptr (caller ignores wrap). |
pdwAudioBytes2 | out | LPDWORD |
Receives the byte count of the second segment, or 0 if none.
May be nullptr. |
dwFlags | in | DWORD |
Lock flags. See DSBLOCK_* constants. |
Wrap-around behaviour: The buffer is treated as circular.
If dwWriteCursor + dwWriteBytes > dwBufferBytes:
- Segment 1: from
dwWriteCursorto end-of-buffer → size =dwBufferBytes − dwWriteCursor. - Segment 2: from start of buffer → size =
dwWriteBytes − size1.
If no wrap, segment 2 pointer is nullptr and size is 0.
| Return value | Condition |
|---|---|
DS_OK | Lock succeeded. Pointers valid for writing. |
DSERR_INVALIDPARAM | ppvAudioPtr1 or pdwAudioBytes1 is nullptr. |
DSERR_INVALIDCALL | Requested region exceeds buffer size. |
Signals write completion. In free-direct this is a no-op — the vector is already in-place, no flush or copy is needed. Parameters are accepted for API compatibility and then ignored.
HRESULT Unlock(
LPVOID pvAudioPtr1,
DWORD dwAudioBytes1,
LPVOID pvAudioPtr2,
DWORD dwAudioBytes2
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
pvAudioPtr1 | in | LPVOID | First pointer from Lock. Ignored. |
dwAudioBytes1 | in | DWORD | Bytes written to first segment. Ignored. |
pvAudioPtr2 | in | LPVOID | Second pointer from Lock. Ignored. |
dwAudioBytes2 | in | DWORD | Bytes written to second segment. Ignored. |
| Return value | Condition |
|---|---|
DS_OK | Always returned. |
Starts playback. The entire PCM buffer is put into a new SDL_AudioStream and the
stream is bound to the shared device. The buffer plays once;
DSBPLAY_LOOPING is accepted but does not cause the buffer to repeat.
HRESULT Play(
DWORD dwReserved1,
DWORD dwPriority,
DWORD dwFlags
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
dwReserved1 | in | DWORD |
Reserved — must be 0. Ignored by free-direct. |
dwPriority | in | DWORD |
Buffer priority for voice stealing. Accepted but not used — all buffers have equal priority under SDL. |
dwFlags | in | DWORD |
Play flags. 0 = play once. DSBPLAY_LOOPING (0x00000001) = accepted but looping does not occur. |
Internal steps on Play():
- Open
SharedAudioDevicesingleton if not yet open (SDL_OpenAudioDevice). - Create a new
SDL_AudioStreamfor the buffer's PCM format (sample rate, channels, bit depth). - Apply current volume: compute
gain = 10^(volume_cb / 2000), callSDL_SetAudioStreamGain(stream, gain). - Put all PCM data into the stream:
SDL_PutAudioStreamData(stream, data.data(), data.size()). - Bind the stream to the audio device:
SDL_BindAudioStream(device, stream). - Set
playing = true.
The flag is accepted without error, but the audio plays once and stops.
To simulate looping, poll GetStatus and re-call Play()
when the buffer finishes — or wrap it in a helper class. See
Developer Notes for an implementation sketch.
| Return value | Condition |
|---|---|
DS_OK | Playback started successfully. |
DSERR_INVALIDCALL | SDL audio device could not be opened. |
DSERR_GENERIC | SDL_AudioStream creation or binding failed. |
Stops playback. Unbinds the SDL_AudioStream from the device and destroys it.
The PCM vector contents are preserved. The buffer can be played again with
Play(). Calling Stop on an already-stopped buffer is harmless (idempotent).
HRESULT Stop();
| Return value | Condition |
|---|---|
DS_OK | Always returned. |
Returns the current playback status. free-direct tracks a simple playing
boolean. It does not detect when SDL finishes consuming the stream,
so DSBSTATUS_PLAYING may remain set after audio has finished.
HRESULT GetStatus(
LPDWORD pdwStatus
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
pdwStatus | out | LPDWORD |
Receives a bitmask of DSBSTATUS_* flags. Set to 0 if the buffer is stopped. |
| Flag returned | Value | Meaning |
|---|---|---|
DSBSTATUS_PLAYING | 0x00000001 | Set after Play(); cleared only by Stop() or Release(). |
DSBSTATUS_LOOPING | 0x00000004 | Never set — looping is not implemented. |
DSBSTATUS_BUFFERLOST | 0x00000002 | Never set — CPU buffers are never lost. |
free-direct does not poll SDL to detect end-of-stream.
DSBSTATUS_PLAYING remains set until Stop() is called
explicitly. Do not rely on this flag alone to detect when audio playback ends.
| Return value | Condition |
|---|---|
DS_OK | Status written successfully. |
DSERR_INVALIDPARAM | pdwStatus is nullptr. |
Sets the playback volume. Volume is specified in centibels
(hundredths of a decibel). Converted to a linear gain and applied to the
SDL_AudioStream immediately if one is open; stored for future Play() calls otherwise.
HRESULT SetVolume(
LONG lVolume
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
lVolume | in | LONG |
Volume in centibels. Range: DSBVOLUME_MIN (−10000, ≈ silence) to DSBVOLUME_MAX (0, full volume). Values above 0 are not defined by the original API. |
| Value | dB | Linear gain | Meaning |
|---|---|---|---|
0 | 0 dB | 1.000 | Full volume (default) |
−500 | −5 dB | ≈ 0.562 | About half perceived loudness |
−1000 | −10 dB | ≈ 0.316 | Roughly one-third amplitude |
−2000 | −20 dB | ≈ 0.100 | One-tenth amplitude |
−10000 | −100 dB | ≈ 0.00001 | Essentially silent |
Conversion formula: float gain = powf(10.0f, lVolume / 2000.0f);
Applied with SDL_SetAudioStreamGain(stream, gain) if a stream is open.
| Return value | Condition |
|---|---|
DS_OK | Always returned. |
Sets the stereo pan position. The value and the computed left/right gains are
stored but the result is (void)-discarded before it reaches SDL.
Pan has no audible effect.
HRESULT SetPan(
LONG lPan
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
lPan | in | LONG |
Pan in centibels. −10000 = full left, 0 = center, +10000 = full right. Stored in the buffer but not applied to SDL. All audio is mixed at center. |
The implementation computes constant-power left/right gains but passes them to
(void)left; (void)right; — the computed values are discarded.
This is a known limitation; see Developer Notes
for the recommended path to add proper panning.
| Return value | Condition |
|---|---|
DS_OK | Always returned. |
Sets the playback start position. Stored in the buffer but not used to seek
within an active SDL stream (SDL_AudioStream does not support seeking).
Effective only before Play().
HRESULT SetCurrentPosition(
DWORD dwNewPosition
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
dwNewPosition | in | DWORD |
Byte offset from start of buffer. Values beyond the buffer size are clamped.
When Play() is called, the PCM data is fed from this offset if it has been set before Play. |
| Return value | Condition |
|---|---|
DS_OK | Always returned. |
Returns the current play and write cursor positions. free-direct always returns 0 for both — SDL_AudioStream does not expose a consumption position.
HRESULT GetCurrentPosition(
LPDWORD pdwCurrentPlayCursor,
LPDWORD pdwCurrentWriteCursor
);
| Parameter | Dir | Type | Description |
|---|---|---|---|
pdwCurrentPlayCursor | out | LPDWORD |
Receives the play cursor position. Always set to 0. May be nullptr (skip this output). |
pdwCurrentWriteCursor | out | LPDWORD |
Receives the safe write cursor position. Always set to 0. May be nullptr. |
| Return value | Condition |
|---|---|
DS_OK | Always returned. |
Structs
Describes a sound buffer. Passed to IDirectSound::CreateSoundBuffer.
typedef struct {
DWORD dwSize; // sizeof(DSBUFFERDESC)
DWORD dwFlags; // DSBCAPS_* capability flags
DWORD dwBufferBytes; // PCM buffer size in bytes
DWORD dwReserved; // must be 0
LPWAVEFORMATEX lpwfxFormat; // pointer to PCMWAVEFORMAT (see note)
} DSBUFFERDESC;
| Field | Dir | Type | Description |
|---|---|---|---|
dwSize | in | DWORD |
Structure size in bytes. Must be sizeof(DSBUFFERDESC) before passing to CreateSoundBuffer. |
dwFlags | in | DWORD |
Combination of DSBCAPS_* flags. Controls which features are enabled. DSBCAPS_CTRLVOLUME is the most useful flag in free-direct. |
dwBufferBytes | in | DWORD |
Total byte size of the PCM data area. A std::vector<uint8_t> of exactly this size is allocated. Must be > 0. |
dwReserved | in | DWORD |
Reserved — set to 0. Ignored by free-direct. |
lpwfxFormat | in | LPWAVEFORMATEX |
Pointer to a PCMWAVEFORMAT (typed as LPWAVEFORMATEX for API compatibility).
free-direct casts this to PCMWAVEFORMAT* — always fill a PCMWAVEFORMAT to avoid LP64 padding issues. Must not be nullptr. |
Base wave format structure. Embedded as the first member of PCMWAVEFORMAT.
typedef struct {
WORD wFormatTag; // audio format type
WORD nChannels; // number of channels
DWORD nSamplesPerSec; // sample rate in Hz
DWORD nAvgBytesPerSec; // nSamplesPerSec * nBlockAlign
WORD nBlockAlign; // bytes per sample frame
} WAVEFORMAT;
// sizeof(WAVEFORMAT) = 14 bytes on LP64 (no internal padding)
| Field | Byte offset | Type | Description |
|---|---|---|---|
wFormatTag | 0 | WORD | Format type. 1 = WAVE_FORMAT_PCM (only supported value). |
nChannels | 2 | WORD | Channel count. 1 = mono, 2 = stereo. Both are passed through to SDL. |
nSamplesPerSec | 4 | DWORD | Samples per second per channel. Common values: 8000, 11025, 22050, 44100. |
nAvgBytesPerSec | 8 | DWORD | Average byte transfer rate = nSamplesPerSec × nBlockAlign. Not used by free-direct for processing. |
nBlockAlign | 12 | WORD | Bytes per sample frame = nChannels × (wBitsPerSample / 8). E.g. stereo 16-bit = 4. |
PCM wave format descriptor. This is the correct struct to pass as
lpwfxFormat in free-direct.
typedef struct {
WAVEFORMAT wf; // base format (14 bytes)
WORD wBitsPerSample; // bits per sample (at byte offset 14)
} PCMWAVEFORMAT;
// sizeof(PCMWAVEFORMAT) = 16 bytes
| Field | Byte offset | Type | Description |
|---|---|---|---|
wf | 0 | WAVEFORMAT | Base format fields (see WAVEFORMAT). |
wBitsPerSample | 14 | WORD | Bits per sample per channel. 8 = unsigned 8-bit PCM, 16 = signed 16-bit PCM LE. free-direct reads this field at offset 14. |
On LP64 systems (Linux 64-bit, macOS 64-bit):
PCMWAVEFORMAT.wBitsPerSampleis at byte offset 14.WAVEFORMATEX.wBitsPerSampleis at byte offset 16 (due to DWORD alignment padding after the WORDnBlockAlignin some compiler layouts).
free-direct always reads wBitsPerSample at offset 14.
If you fill a WAVEFORMATEX and cast it, free-direct will read
padding bytes as the bit depth and open the SDL stream with incorrect parameters,
causing silence or noise.
// CORRECT: use PCMWAVEFORMAT
PCMWAVEFORMAT pcm = {};
pcm.wf.wFormatTag = 1; // WAVE_FORMAT_PCM
pcm.wf.nChannels = 1; // mono
pcm.wf.nSamplesPerSec = 22050;
pcm.wf.nBlockAlign = 2; // 1 ch * 2 bytes
pcm.wf.nAvgBytesPerSec = 44100;
pcm.wBitsPerSample = 16;
bufDesc.lpwfxFormat = (LPWAVEFORMATEX)&pcm;
Extended wave format. Defined in dsound.h for compile compatibility.
Do not use this struct directly as lpwfxFormat in free-direct
— use PCMWAVEFORMAT instead.
typedef struct {
WORD wFormatTag;
WORD nChannels;
DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec;
WORD nBlockAlign;
WORD wBitsPerSample; // at offset 16 on LP64 (NOT offset 14)
WORD cbSize; // extra bytes beyond this struct
} WAVEFORMATEX;
| Field | Type | Description |
|---|---|---|
wFormatTag | WORD | 1 = WAVE_FORMAT_PCM. |
nChannels | WORD | 1 = mono, 2 = stereo. |
nSamplesPerSec | DWORD | Sample rate in Hz. |
nAvgBytesPerSec | DWORD | nSamplesPerSec × nBlockAlign. |
nBlockAlign | WORD | nChannels × (wBitsPerSample / 8). |
wBitsPerSample | WORD | Bits per sample. At offset 16 on LP64, not offset 14. Do not use this field path with free-direct. |
cbSize | WORD | Extra data size appended after this struct. 0 for PCM. |
Flag constants
DSBCAPS_* — buffer capability flags
Passed in DSBUFFERDESC.dwFlags to CreateSoundBuffer.
| Constant | Value | Support | Description |
|---|---|---|---|
DSBCAPS_PRIMARYBUFFER | 0x00000001 | No | Primary mixer output buffer. Not implemented. |
DSBCAPS_STATIC | 0x00000002 | Yes | Buffer loaded once, played many times. Accepted — all buffers are effectively static. |
DSBCAPS_LOCHARDWARE | 0x00000004 | Treated as software | Force hardware mixing. No hardware mixing; silently treated as software. |
DSBCAPS_LOCSOFTWARE | 0x00000008 | Yes | Force software mixing. Default for all buffers. |
DSBCAPS_CTRL3D | 0x00000010 | No | 3D positional audio. Not implemented. |
DSBCAPS_CTRLFREQUENCY | 0x00000020 | No-op | Frequency (pitch) control. Accepted but SetFrequency not implemented. |
DSBCAPS_CTRLPAN | 0x00000040 | Stored only | Stereo pan control. SetPan stores the value but does not apply it. |
DSBCAPS_CTRLVOLUME | 0x00000080 | Yes | Volume control. SetVolume is fully functional via SDL_SetAudioStreamGain. |
DSBCAPS_CTRLDEFAULT | 0x000000E0 | Partial | Combined CTRLFREQUENCY|CTRLPAN|CTRLVOLUME. Volume works; pan stored only; frequency no-op. |
DSBCAPS_CTRLALL | 0x000001F0 | Partial | All controls. Only volume is functional. |
DSBCAPS_STICKYFOCUS | 0x00004000 | No | Continue when app loses focus. SDL audio is always running; no special handling. |
DSBCAPS_GLOBALFOCUS | 0x00008000 | No | Global audio focus. Not implemented. |
DSBLOCK_* — lock flags
Passed as dwFlags to IDirectSoundBuffer::Lock.
| Constant | Value | Support | Description |
|---|---|---|---|
DSBLOCK_FROMWRITECURSOR | 0x00000001 | Yes | Ignore dwWriteCursor and lock from the current write cursor (always 0 in free-direct — the buffer is always fully lockable from offset 0). |
DSBLOCK_ENTIREBUFFER | 0x00000002 | Yes | Lock the entire buffer. Overrides dwWriteBytes; equivalent to passing dwBufferBytes as the size. |
DSBSTATUS_* — status flags
Returned by IDirectSoundBuffer::GetStatus.
| Constant | Value | Support | Description |
|---|---|---|---|
DSBSTATUS_PLAYING | 0x00000001 | Partial | Buffer is playing. Set by Play(); cleared by Stop(). Does not auto-clear on stream end. |
DSBSTATUS_BUFFERLOST | 0x00000002 | Never set | Buffer memory lost. CPU-side buffers never become lost. |
DSBSTATUS_LOOPING | 0x00000004 | Never set | Buffer looping. Looping not implemented; never returned. |
DSSCL_* — cooperative level values
Passed to IDirectSound::SetCooperativeLevel.
| Constant | Value | Support | Description |
|---|---|---|---|
DSSCL_NORMAL | 0x00000001 | Yes | Normal shared access. The only level that has semantic meaning in free-direct (SDL audio is always shared). |
DSSCL_PRIORITY | 0x00000002 | Accepted | Priority access. Accepted without error; treated as DSSCL_NORMAL. |
DSSCL_EXCLUSIVE | 0x00000003 | Accepted | Exclusive access. Accepted; no exclusive device ownership occurs. |
DSSCL_WRITEPRIMARY | 0x00000004 | No | Write to primary buffer. Primary buffers not implemented. |
DSBPLAY_* — play flags
Passed as dwFlags to IDirectSoundBuffer::Play.
| Constant | Value | Support | Description |
|---|---|---|---|
DSBPLAY_LOOPING | 0x00000001 | Accepted, not functional | Loop playback continuously. Accepted without error but buffer plays only once. Re-call Play() to simulate looping. |
Result codes
Success
| Constant | Value | Description |
|---|---|---|
DS_OK | 0x00000000 | Operation succeeded. Equivalent to S_OK. |
Error codes actively returned
| Constant | Value | Returned by | Description |
|---|---|---|---|
DSERR_INVALIDPARAM | 0x80070057 | DirectSoundCreate, CreateSoundBuffer, Lock, GetStatus | A required pointer is nullptr, or a required field is zero or invalid. |
DSERR_OUTOFMEMORY | 0x80070008 | CreateSoundBuffer | Memory allocation for the buffer failed. |
DSERR_INVALIDCALL | 0x88780032 | Lock, Play | Lock: region exceeds buffer size. Play: SDL audio device could not be opened. |
DSERR_GENERIC | 0x88780081 | Play | SDL_AudioStream creation or binding to device failed. |
Compile-compatibility constants (defined, not returned)
These constants are defined in dsound.h to allow legacy code to compile. free-direct never returns them.
| Constant | Value |
|---|---|
DSERR_ALLOCATED | 0x8878000A |
DSERR_CONTROLUNAVAIL | 0x8878001E |
DSERR_BADFORMAT | 0x88780064 |
DSERR_NODRIVER | 0x88780078 |
DSERR_OTHERAPP | 0x88780082 |
DSERR_BUFFERLOST | 0x88780096 |
DSERR_UNINITIALIZED | 0x888700AA |
DSERR_UNSUPPORTED | 0x80004001 |
DSERR_PRIOLEVELNEEDED | 0x88780046 |
Known limitations
| Feature | Status | Notes |
|---|---|---|
| DSBPLAY_LOOPING | Not functional | Flag accepted; plays once. Re-call Play() to loop. |
| Stereo panning (SetPan) | Stored only | Gains computed but voided; all audio plays at center. |
| Frequency shifting | Not implemented | No SetFrequency; DSBCAPS_CTRLFREQUENCY is a no-op. |
| 3D positional audio | Not implemented | No IDirectSound3DBuffer or IDirectSound3DListener. |
| Primary buffer | Not implemented | DSBCAPS_PRIMARYBUFFER is not supported. |
| GetStatus completion detection | Incomplete | DSBSTATUS_PLAYING does not auto-clear on stream end. |
| GetCurrentPosition | Stub | Always returns 0 for both cursors. |
| SetCurrentPosition | Stored only | Value stored; not applied to an active SDL stream. |
| Multiple simultaneous buffers | Supported | Each buffer has its own SDL_AudioStream; SDL mixes at device level. |
| Mixed sample rates | Supported | SDL resamples each stream independently to the device rate. |
| 8-bit PCM | Supported | SDL_AUDIO_U8 format is passed through correctly. |
| 16-bit PCM | Supported | SDL_AUDIO_S16LE format is passed through correctly. |
| Hardware acceleration | Not available | All mixing is in SDL software; no hardware voice management. |
| EAX / DSP effects | Not implemented | No DirectSound effects processing of any kind. |
| WAVEFORMATEX as lpwfxFormat | Do not use | LP64 padding places wBitsPerSample at offset 16 instead of 14; free-direct reads offset 14. Always use PCMWAVEFORMAT. |
Since DSBPLAY_LOOPING is not functional, the recommended workaround
is to monitor playback from within the game loop and re-play the buffer.
A simple wrapper class that tracks elapsed time vs. buffer duration and calls
Play() again when needed is the cleanest approach.
See Developer Notes for an implementation sketch.