Atlas - SDL_dosaudio_sb.c

Home / ext / SDL / src / audio / dos Lines: 1 | Size: 26720 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER 24 25#include "../../core/dos/SDL_dos.h" 26#include "../../core/dos/SDL_dos_scheduler.h" 27#include "SDL_dosaudio_sb.h" 28 29// Set to 1 to force 8-bit mono (pre-SB16) code path even on SB16 hardware. 30// Useful for testing in DOSBox which always emulates an SB16 (DSP 4.x). 31#define FORCE_SB_8BIT 0 32 33static int soundblaster_base_port = -1; 34static int soundblaster_irq = -1; 35static int soundblaster_dma_channel = -1; 36static int soundblaster_highdma_channel = -1; 37static int soundblaster_version = -1; 38static int soundblaster_version_minor = -1; 39static bool soundblaster_is_sb16 = false; // false when FORCE_SB_8BIT or DSP < 4 40static Uint8 soundblaster_silence_value = 0; 41 42static void ResetSoundBlasterDSP(void) 43{ 44 // reset the DSP. 45 const int reset_port = soundblaster_base_port + 0x6; 46 outportb(reset_port, 1); 47 SDL_DelayPrecise(3000); // wait at least 3 microseconds for hardware to see it. 48 outportb(reset_port, 0); 49} 50 51static bool ReadSoundBlasterReady(void) 52{ 53 const int ready_port = soundblaster_base_port + 0xE; 54 return ((inportb(ready_port) & (1 << 7)) != 0); 55} 56 57static void WriteSoundBlasterDSP(const Uint8 val) 58{ 59 const int port = soundblaster_base_port + 0xC; 60 int timeout = 100000; 61 while ((inportb(port) & (1 << 7)) && --timeout > 0) { /* spin until ready or timeout */ 62 } 63 outportb(port, val); 64} 65 66static Uint8 ReadSoundBlasterDSP(void) 67{ 68 const int query_port = soundblaster_base_port + 0xA; 69 int timeout = 100000; 70 while (!ReadSoundBlasterReady() && --timeout > 0) { /* spin until ready or timeout */ 71 } 72 return (Uint8)inportb(query_port); 73} 74 75// The ISR copies audio from a pre-allocated ring buffer directly into the 76// DMA half-buffer. The SDL audio thread fills the ring buffer cooperatively 77// (using the full SDL pipeline with all its allocations and mutexes), and 78// the ISR just does a memcpy (no SDL calls, no DPMI, no allocator). 79 80// Number of DMA half-buffers that fit in the ring. Must be a power of two. 81// 4 chunks is ~45 ms at 44100 Hz, enough headroom for 22 fps frame times. 82#define RING_BUFFER_CHUNKS 4 83 84// All of the following statics are memory-locked, making them safe to access 85// from the ISR without risking a page fault or DPMI re-entrance. 86 87// ISR-cached copies of device state (avoids chasing heap pointers in IRQ context). 88static volatile int isr_irq_ack_port = 0; 89 90// ISR-visible ring buffer state (all memory-locked). 91static volatile int isr_ring_read = 0; 92static volatile int isr_ring_write = 0; 93static int isr_ring_size = 0; // ring_size (power-of-2 bytes) 94static int isr_ring_mask = 0; // ring_size - 1 95static int isr_chunk_size = 0; // one DMA half-buffer, in bytes 96static Uint8 *isr_ring_buffer = NULL; // the ring itself (allocated and locked) 97static Uint8 *isr_dma_buffer = NULL; // pointer to the DMA double-buffer 98static int isr_dma_halfdma = 0; // half the DMA buffer size, in bytes 99static int isr_dma_channel = 0; 100static bool isr_is_16bit = false; 101static Uint8 isr_silence_value = 0; 102 103// Copy `len` bytes from the ring buffer at position `pos` into `dst`, 104// handling the power-of-2 wrap. All pointers are memory-locked. 105static void RingCopyOut(Uint8 *dst, int pos, int len) 106{ 107 const int mask = isr_ring_mask; 108 const int start = pos & mask; 109 const int first = (start + len <= isr_ring_size) ? len : (isr_ring_size - start); 110 SDL_memcpy(dst, isr_ring_buffer + start, first); 111 if (first < len) { 112 SDL_memcpy(dst + first, isr_ring_buffer, len - first); 113 } 114} 115static void RingCopyOut_End(void) {} 116 117// Determine which DMA half-buffer the hardware is NOT currently playing 118// (i.e. the one we should fill). Uses ISR-cached statics so we don't 119// chase any heap pointers. 120static Uint8 *ISR_GetDMAHalf(void) 121{ 122 int count; 123 if (isr_is_16bit) { 124 outportb(0xD8, 0x00); 125 count = (int)inportb(0xC0 + (isr_dma_channel - 4) * 4 + 2); 126 count += (int)inportb(0xC0 + (isr_dma_channel - 4) * 4 + 2) << 8; 127 return isr_dma_buffer + (count < (isr_dma_halfdma / 2) ? 0 : isr_dma_halfdma); 128 } else { 129 outportb(0x0C, 0x00); 130 count = (int)inportb(isr_dma_channel * 2 + 1); 131 count += (int)inportb(isr_dma_channel * 2 + 1) << 8; 132 return isr_dma_buffer + (count < isr_dma_halfdma ? 0 : isr_dma_halfdma); 133 } 134} 135static void ISR_GetDMAHalf_End(void) {} 136 137// The IRQ handler. Copies one chunk from the ring buffer into the DMA 138// half-buffer that the hardware isn't currently playing. If the ring is 139// empty it fills with silence (no stutter, just a brief gap). 140// 141// This function touches ONLY memory-locked data and does ONLY port I/O and 142// memcpy. No DPMI, no malloc, no mutex, no FPU. 143static void SoundBlasterIRQHandler(void) 144{ 145 // Acknowledge hardware first. 146 inportb(isr_irq_ack_port); 147 DOS_EndOfInterrupt(soundblaster_irq); 148 149 Uint8 *dma_dst = ISR_GetDMAHalf(); 150 151 // How many bytes are available in the ring? 152 const int avail = isr_ring_write - isr_ring_read; // both are monotonic 153 154 if (avail >= isr_chunk_size) { 155 RingCopyOut(dma_dst, isr_ring_read, isr_chunk_size); 156 isr_ring_read += isr_chunk_size; 157 } else { 158 // Ring underrun: fill with silence so we don't replay stale audio. 159 SDL_memset(dma_dst, isr_silence_value, isr_chunk_size); 160 } 161} 162static void SoundBlasterIRQHandler_End(void) {} 163 164// Wait until the ring buffer has room for one more chunk. 165// The audio thread keeps yielding so the game's main thread can run while 166// we wait. Because the ISR is steadily draining the ring, this only blocks 167// when the ring is completely full, which is a good problem to have. 168static bool DOSSOUNDBLASTER_WaitDevice(SDL_AudioDevice *device) 169{ 170 struct SDL_PrivateAudioData *hidden = device->hidden; 171 const int size = hidden->ring_size; 172 173 for (;;) { 174 // Available space = ring_size - (write - read). 175 // ring_write is ours (audio thread only), ring_read is advanced by 176 // the ISR. Read the ISR's copy so we see the latest drain position. 177 const int used = hidden->ring_write - isr_ring_read; 178 if ((size - used) >= hidden->chunk_size) { 179 return true; // room for at least one chunk 180 } 181 DOS_Yield(); 182 } 183} 184 185static bool DOSSOUNDBLASTER_OpenDevice(SDL_AudioDevice *device) 186{ 187 const bool is_sb16 = soundblaster_is_sb16; 188 189 if (is_sb16) { 190 // SB16 (DSP >= 4): 16-bit stereo signed 191 device->spec.format = SDL_AUDIO_S16LE; 192 device->spec.channels = 2; 193 } else if (soundblaster_version >= 3) { 194 // SB Pro (DSP 3.x): 8-bit stereo unsigned. 195 // Max 22050 Hz in stereo (hardware interleaves L/R at double the rate). 196 device->spec.format = SDL_AUDIO_U8; 197 device->spec.channels = 2; 198 } else { 199 // SB 2.0 (DSP 2.x) and SB 1.x: 8-bit mono unsigned. 200 device->spec.format = SDL_AUDIO_U8; 201 device->spec.channels = 1; 202 } 203 204 // Accept whatever frequency SDL3's audio layer passes in. For SB16 (DSP >= 4) 205 // the hardware supports 5000–44100 Hz via DSP command 0x41. For pre-SB16, 206 // clamp to hardware limits: 207 // SB 1.x: max ~23 kHz mono 208 // SB 2.0 (DSP 2.x): max 44100 Hz mono (high-speed), ~23 kHz normal 209 // SB Pro (DSP 3.x): max 22050 Hz stereo, max 44100 Hz mono 210 if (!is_sb16 && device->spec.freq > 22050) { 211 device->spec.freq = 22050; // clamp to safe max for pre-SB16 212 } 213 device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq); 214 215 // Calculate the final parameters for this audio specification 216 SDL_UpdatedAudioDeviceFormat(device); 217 218 SDL_Log("SOUNDBLASTER: Opening at %d Hz, %d channels, format 0x%X, %d sample frames (DSP %d.%d, %s)", 219 device->spec.freq, device->spec.channels, device->spec.format, device->sample_frames, 220 soundblaster_version, soundblaster_version_minor, is_sb16 ? "SB16" : "pre-SB16"); 221 222 if (device->buffer_size > (32 * 1024)) { 223 return SDL_SetError("Buffer size is too large (choose smaller audio format and/or fewer sample frames)"); // DMA buffer has to fit in 64K segment, so buffer_size has to be half that, as we double it. 224 } 225 226 // Initialize all variables that we clean on shutdown 227 struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); 228 if (!hidden) { 229 return false; 230 } 231 232 device->hidden = hidden; 233 hidden->is_16bit = is_sb16; 234 235 ResetSoundBlasterDSP(); 236 237 // allocate conventional memory for the DMA buffer. 238 hidden->dma_channel = is_sb16 ? soundblaster_highdma_channel : soundblaster_dma_channel; 239 if (hidden->dma_channel < 0) { 240 SDL_free(hidden); 241 return SDL_SetError("No %s DMA channel configured in BLASTER environment variable", 242 is_sb16 ? "high (16-bit)" : "low (8-bit)"); 243 } 244 hidden->dma_buflen = device->buffer_size * 2; 245 hidden->dma_buffer = (Uint8 *)DOS_AllocateDMAMemory(hidden->dma_buflen, &hidden->dma_seginfo); 246 if (!hidden->dma_buffer) { 247 return SDL_SetError("Couldn't allocate Sound Blaster DMA buffer!"); 248 } 249 250 SDL_Log("SOUNDBLASTER: Allocated %d bytes of conventional memory at segment %d (ptr=%p)", (int)hidden->dma_buflen, (int)hidden->dma_seginfo.rm_segment, hidden->dma_buffer); 251 252 // silence the DMA buffer to start 253 SDL_memset(hidden->dma_buffer, soundblaster_silence_value, hidden->dma_buflen); 254 255 // set up DMA controller. 256 const Uint32 physical = DOS_LinearToPhysical(hidden->dma_buffer); 257 const Uint8 physical_page = (physical >> 16) & 0xFF; 258 259 if (is_sb16) { 260 // High DMA (16-bit, channels 5-7): ports in 0xC0-0xDF range, counts in words. 261 const int dma_words = (hidden->dma_buflen / 2) - 1; 262 outportb(0xD4, 0x04 | hidden->dma_channel); // mask the DMA channel 263 outportb(0xD6, 0x58 | (hidden->dma_channel - 4)); // mode: single, read, auto-init 264 static const int high_page_ports[] = { 0, 0, 0, 0, 0, 0x8B, 0x89, 0x8A }; // DMA page register ports for channels 5-7 265 outportb(high_page_ports[hidden->dma_channel], physical_page); // page to transfer 266 outportb(0xD8, 0x00); // clear the flip-flop 267 outportb(0xC0 + (hidden->dma_channel - 4) * 4, (Uint8)((physical >> 1) & 0xFF)); // offset low (word address) 268 outportb(0xC0 + (hidden->dma_channel - 4) * 4, (Uint8)((physical >> 9) & 0xFF)); // offset high 269 outportb(0xD8, 0x00); // clear the flip-flop 270 outportb(0xC0 + (hidden->dma_channel - 4) * 4 + 2, (Uint8)(dma_words & 0xFF)); // count low 271 outportb(0xC0 + (hidden->dma_channel - 4) * 4 + 2, (Uint8)((dma_words >> 8) & 0xFF)); // count high 272 outportb(0xD4, hidden->dma_channel & ~4); // unmask the DMA channel 273 } else { 274 // Low DMA (8-bit, channels 0-3): ports in 0x00-0x0F range, counts in bytes. 275 static const int page_ports[] = { 0x87, 0x83, 0x81, 0x82 }; // DMA page register ports for channels 0-3 (yes, they're out of order — that's how the IBM PC DMA controller works) 276 const int dma_bytes = hidden->dma_buflen - 1; 277 outportb(0x0A, 0x04 | hidden->dma_channel); // mask the DMA channel 278 outportb(0x0B, 0x58 | hidden->dma_channel); // mode: single, read, auto-init 279 outportb(page_ports[hidden->dma_channel], physical_page); // page to transfer 280 outportb(0x0C, 0x00); // clear the flip-flop 281 outportb(hidden->dma_channel * 2, (Uint8)(physical & 0xFF)); // offset low (byte address) 282 outportb(hidden->dma_channel * 2, (Uint8)((physical >> 8) & 0xFF)); // offset high 283 outportb(0x0C, 0x00); // clear the flip-flop 284 outportb(hidden->dma_channel * 2 + 1, (Uint8)(dma_bytes & 0xFF)); // count low 285 outportb(hidden->dma_channel * 2 + 1, (Uint8)((dma_bytes >> 8) & 0xFF)); // count high 286 outportb(0x0A, hidden->dma_channel); // unmask the DMA channel (just the channel number, no bit 2) 287 } 288 289 // Cache the IRQ ack port so the ISR doesn't chase pointers. 290 isr_irq_ack_port = is_sb16 ? (soundblaster_base_port + 0x0F) : (soundblaster_base_port + 0x0E); 291 292 // Set up the IRQ-driven ring buffer. 293 hidden->chunk_size = device->buffer_size; // one DMA half-buffer 294 295 // Ring size must be a power of two and hold RING_BUFFER_CHUNKS chunks. 296 hidden->ring_size = hidden->chunk_size * RING_BUFFER_CHUNKS; 297 // Ensure power-of-two (chunk_size itself comes from SDL and may not be). 298 { 299 int rs = hidden->ring_size; 300 rs--; 301 rs |= rs >> 1; 302 rs |= rs >> 2; 303 rs |= rs >> 4; 304 rs |= rs >> 8; 305 rs |= rs >> 16; 306 rs++; 307 hidden->ring_size = rs; 308 } 309 310 hidden->ring_buffer = (Uint8 *)SDL_calloc(1, hidden->ring_size); 311 if (!hidden->ring_buffer) { 312 return SDL_SetError("Couldn't allocate ring buffer for IRQ-driven audio"); 313 } 314 hidden->staging_buffer = (Uint8 *)SDL_calloc(1, hidden->chunk_size); 315 if (!hidden->staging_buffer) { 316 return SDL_SetError("Couldn't allocate staging buffer for IRQ-driven audio"); 317 } 318 319 hidden->ring_read = 0; 320 hidden->ring_write = 0; 321 322 // Populate ISR-visible statics (all will be memory-locked below). 323 isr_ring_buffer = hidden->ring_buffer; 324 isr_ring_read = 0; 325 isr_ring_write = 0; 326 isr_ring_size = hidden->ring_size; 327 isr_ring_mask = hidden->ring_size - 1; 328 isr_chunk_size = hidden->chunk_size; 329 isr_dma_buffer = hidden->dma_buffer; 330 isr_dma_halfdma = hidden->dma_buflen / 2; 331 isr_dma_channel = hidden->dma_channel; 332 isr_is_16bit = is_sb16; 333 isr_silence_value = soundblaster_silence_value; 334 335 // Lock all ISR code and data to prevent page faults during interrupts. 336 DOS_LockCode(SoundBlasterIRQHandler, SoundBlasterIRQHandler_End); 337 DOS_LockCode(RingCopyOut, RingCopyOut_End); 338 DOS_LockCode(ISR_GetDMAHalf, ISR_GetDMAHalf_End); 339 DOS_LockData(*hidden->ring_buffer, hidden->ring_size); 340 DOS_LockVariable(isr_ring_read); 341 DOS_LockVariable(isr_ring_write); 342 DOS_LockVariable(isr_ring_size); 343 DOS_LockVariable(isr_ring_mask); 344 DOS_LockVariable(isr_chunk_size); 345 DOS_LockVariable(isr_ring_buffer); 346 DOS_LockVariable(isr_dma_buffer); 347 DOS_LockVariable(isr_dma_halfdma); 348 DOS_LockVariable(isr_dma_channel); 349 DOS_LockVariable(isr_is_16bit); 350 DOS_LockVariable(isr_silence_value); 351 DOS_LockVariable(isr_irq_ack_port); 352 DOS_LockVariable(soundblaster_irq); 353 354 DOS_HookInterrupt(soundblaster_irq, SoundBlasterIRQHandler, &hidden->interrupt_hook); 355 356 WriteSoundBlasterDSP(0xD1); // turn on the speaker 357 // The speaker-on command takes up to 112 ms to complete on real hardware. 358 // Poll the DSP write status port (bit 7 clears when the DSP is ready); 359 // in practice — and always in DOSBox — it completes almost instantly. 360 { 361 const int status_port = soundblaster_base_port + 0xC; 362 const Uint64 deadline = SDL_GetTicksNS() + SDL_MS_TO_NS(112); 363 while ((inportb(status_port) & 0x80) && (SDL_GetTicksNS() < deadline)) { 364 SDL_DelayPrecise(SDL_US_TO_NS(100)); // brief yield between polls 365 } 366 } 367 368 if (is_sb16) { 369 // SB16 (DSP >= 4): set output sample rate directly 370 WriteSoundBlasterDSP(0x41); // set output sampling rate 371 WriteSoundBlasterDSP((Uint8)(device->spec.freq >> 8)); 372 WriteSoundBlasterDSP((Uint8)(device->spec.freq & 0xFF)); 373 374 // start 16-bit auto-initialize DMA mode 375 // half the total buffer per transfer, then convert to samples (divide by 2 because they are 16-bits each). 376 const int block_size = ((hidden->dma_buflen / 2) / sizeof(Sint16)) - 1; // one less than samples to be transferred. 377 WriteSoundBlasterDSP(0xB6); // 16-bit output, auto-init, FIFO on 378 WriteSoundBlasterDSP(0x30); // 16-bit stereo signed PCM 379 WriteSoundBlasterDSP((Uint8)(block_size & 0xFF)); 380 WriteSoundBlasterDSP((Uint8)(block_size >> 8)); 381 } else { 382 // Pre-SB16 (DSP < 4): set sample rate via Time Constant 383 // Time Constant = 256 - (1000000 / (channels * freq)) 384 // In stereo mode the SB Pro interleaves L/R samples, so the effective 385 // hardware rate is channels * freq. 386 const int effective_rate = device->spec.channels * device->spec.freq; 387 const Uint8 time_constant = (Uint8)(256 - (1000000 / effective_rate)); 388 WriteSoundBlasterDSP(0x40); // set time constant 389 WriteSoundBlasterDSP(time_constant); 390 391 // SB Pro (DSP 3.x): enable or disable stereo via mixer register 0x0E 392 if (soundblaster_version >= 3) { 393 const int mixer_addr = soundblaster_base_port + 0x04; 394 const int mixer_data = soundblaster_base_port + 0x05; 395 outportb(mixer_addr, 0x0E); // select output/stereo register 396 if (device->spec.channels == 2) { 397 outportb(mixer_data, inportb(mixer_data) | 0x02); // set bit 1 = stereo 398 } else { 399 outportb(mixer_data, inportb(mixer_data) & ~0x02); // clear bit 1 = mono 400 } 401 } 402 403 // start 8-bit auto-initialize DMA mode 404 // block_size is in bytes for 8-bit, and it's the half-buffer size minus 1 405 const int block_size = (hidden->dma_buflen / 2) - 1; 406 WriteSoundBlasterDSP(0x48); // set DSP block transfer size 407 WriteSoundBlasterDSP((Uint8)(block_size & 0xFF)); 408 WriteSoundBlasterDSP((Uint8)(block_size >> 8)); 409 // NOTE: DSP 1.x does not support auto-init (0x1C). Those cards are extremely 410 // rare and would need single-cycle transfers re-triggered from the ISR. 411 // For now we use 0x1C anyway and hope for the best on DSP 1.x hardware. 412 WriteSoundBlasterDSP(0x1C); // 8-bit auto-init DMA playback 413 } 414 415 SDL_Log("SoundBlaster opened!"); 416 return true; 417} 418 419// Return the staging buffer. The SDL audio pipeline writes the mixed audio 420// here; PlayDevice then copies it into the ring buffer. 421static Uint8 *DOSSOUNDBLASTER_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) 422{ 423 struct SDL_PrivateAudioData *hidden = device->hidden; 424 (void)buffer_size; // unchanged, always one chunk 425 return hidden->staging_buffer; 426} 427 428// Commit the staging buffer into the ring buffer. 429// Called by SDL's audio thread after it has written a full chunk. 430static bool DOSSOUNDBLASTER_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) 431{ 432 struct SDL_PrivateAudioData *hidden = device->hidden; 433 const int mask = hidden->ring_size - 1; 434 const int pos = hidden->ring_write & mask; 435 const int first = (pos + buffer_size <= hidden->ring_size) ? buffer_size : (hidden->ring_size - pos); 436 437 SDL_memcpy(hidden->ring_buffer + pos, buffer, first); 438 if (first < buffer_size) { 439 SDL_memcpy(hidden->ring_buffer, buffer + first, buffer_size - first); 440 } 441 442 // Advance the write cursor. Interrupts are disabled around the store so 443 // the ISR never sees a torn write (not strictly necessary on x86 for an 444 // aligned int, but let's be safe). 445 DOS_DisableInterrupts(); 446 hidden->ring_write += buffer_size; 447 isr_ring_write = hidden->ring_write; 448 DOS_EnableInterrupts(); 449 450 return true; 451} 452 453static void DOSSOUNDBLASTER_CloseDevice(SDL_AudioDevice *device) 454{ 455 struct SDL_PrivateAudioData *hidden = device->hidden; 456 if (hidden) { 457 // Disable PCM. 458 if (hidden->is_16bit) { 459 WriteSoundBlasterDSP(0xDA); // exit 16-bit auto-init DMA 460 WriteSoundBlasterDSP(0xD3); // turn off the speaker 461 } else { 462 WriteSoundBlasterDSP(0xD0); // halt 8-bit DMA 463 WriteSoundBlasterDSP(0xDA); // exit auto-init DMA 464 WriteSoundBlasterDSP(0xD3); // turn off the speaker 465 466 // SB Pro: reset stereo bit in mixer register 0x0E 467 if (soundblaster_version >= 3) { 468 const int mixer_addr = soundblaster_base_port + 0x04; 469 const int mixer_data = soundblaster_base_port + 0x05; 470 outportb(mixer_addr, 0x0E); 471 outportb(mixer_data, inportb(mixer_data) & ~0x02); // clear stereo bit 472 } 473 } 474 475 DOS_UnhookInterrupt(&hidden->interrupt_hook, true); 476 477 // disable DMA — mask the appropriate DMA channel. 478 if (hidden->dma_buffer) { 479 if (hidden->is_16bit) { 480 outportb(0xD4, 0x04 | hidden->dma_channel); // mask high DMA channel (channels 5-7) 481 } else { 482 outportb(0x0A, 0x04 | hidden->dma_channel); // mask low DMA channel (channels 0-3) 483 } 484 DOS_FreeConventionalMemory(&hidden->dma_seginfo); 485 } 486 487 // Free ring buffer resources. 488 if (hidden->ring_buffer) { 489 SDL_free(hidden->ring_buffer); 490 } 491 if (hidden->staging_buffer) { 492 SDL_free(hidden->staging_buffer); 493 } 494 495 // Clear ISR-visible statics. 496 isr_ring_buffer = NULL; 497 isr_ring_read = 0; 498 isr_ring_write = 0; 499 isr_ring_size = 0; 500 isr_ring_mask = 0; 501 isr_chunk_size = 0; 502 isr_dma_buffer = NULL; 503 isr_dma_halfdma = 0; 504 isr_irq_ack_port = 0; 505 506 SDL_free(hidden); 507 } 508} 509 510static bool CheckForSoundBlaster(void) 511{ 512 ResetSoundBlasterDSP(); 513 514 // wait for the DSP to say it's ready. 515 bool ready = false; 516 for (int i = 0; i < 300; i++) { // may take up to 100msecs to initialize. We'll give it 300. 517 SDL_DelayPrecise(1000); 518 if (ReadSoundBlasterReady()) { 519 ready = true; 520 break; 521 } 522 } 523 524 if (!ready) { 525 return SDL_SetError("No SoundBlaster detected on port 0x%X", soundblaster_base_port); // either no SoundBlaster or it's on a different base port. 526 } else if (ReadSoundBlasterDSP() != 0xAA) { 527 return SDL_SetError("Not a SoundBlaster at port 0x%X", soundblaster_base_port); // either it's not a SoundBlaster or there's a problem. 528 } 529 return true; 530} 531 532static bool IsSoundBlasterPresent(void) 533{ 534 const char *env = SDL_getenv("BLASTER"); 535 if (!env) { 536 return SDL_SetError("No BLASTER environment variable to find Sound Blaster"); // definitely doesn't have a Sound Blaster (or they screwed up). 537 } 538 539 char *copy = SDL_strdup(env); 540 if (!copy) { 541 return false; // oh well. 542 } 543 544 char *str = copy; 545 char *saveptr = NULL; 546 547 char *token; 548 while ((token = SDL_strtok_r(str, " ", &saveptr)) != NULL) { 549 str = NULL; // must be NULL for future calls to tokenize the same string. 550 char *endp = NULL; 551 const int base = (SDL_toupper(*token) == 'A') ? 16 : 10; 552 const int num = (int)SDL_strtol(token + 1, &endp, base); 553 if ((token[1] == 0) || (*endp != 0)) { // bogus num 554 continue; 555 } else if (num < 0) { 556 continue; 557 } 558 559 switch (SDL_toupper(*token)) { 560 case 'A': // Base i/o port (in hex) 561 soundblaster_base_port = num; 562 break; 563 564 case 'I': // IRQ 565 soundblaster_irq = num; 566 break; 567 568 case 'D': // DMA channel 569 soundblaster_dma_channel = num; 570 break; 571 572 case 'H': // High DMA channel 573 soundblaster_highdma_channel = num; 574 break; 575 576 // don't care about these. 577 // case 'M': // mixer chip base port 578 // case 'P': // MPU-401 base port 579 // case 'T': // type of device 580 // case 'E': // EMU8000 base port: an AWE32 thing 581 default: 582 break; 583 } 584 } 585 SDL_free(copy); 586 587 if (soundblaster_base_port < 0 || soundblaster_irq < 0 || (soundblaster_dma_channel < 0 && soundblaster_highdma_channel < 0)) { 588 return SDL_SetError("BLASTER environment variable is incomplete or incorrect"); 589 } else if (!CheckForSoundBlaster()) { 590 return false; 591 } 592 593 WriteSoundBlasterDSP(0xE1); // query DSP version 594 soundblaster_version = (int)ReadSoundBlasterDSP(); 595 soundblaster_version_minor = (int)ReadSoundBlasterDSP(); 596 597 SDL_Log("SB: BLASTER env='%s'", env); 598 SDL_Log("SB: port=0x%X", soundblaster_base_port); 599 SDL_Log("SB: irq=%d", soundblaster_irq); 600 SDL_Log("SB: dma8=%d", soundblaster_dma_channel); 601 SDL_Log("SB: dma16=%d", soundblaster_highdma_channel); 602 SDL_Log("SB: version=%d.%d", soundblaster_version, soundblaster_version_minor); 603 604 soundblaster_is_sb16 = !FORCE_SB_8BIT && (soundblaster_version >= 4); 605 soundblaster_silence_value = soundblaster_is_sb16 ? 0x00 : 0x80; // S16LE silence is 0x00, U8 silence is 0x80 606 607 return true; 608} 609 610static bool DOSSOUNDBLASTER_Init(SDL_AudioDriverImpl *impl) 611{ 612 if (!IsSoundBlasterPresent()) { 613 return false; 614 } 615 616 impl->OpenDevice = DOSSOUNDBLASTER_OpenDevice; 617 impl->WaitDevice = DOSSOUNDBLASTER_WaitDevice; 618 impl->GetDeviceBuf = DOSSOUNDBLASTER_GetDeviceBuf; 619 impl->PlayDevice = DOSSOUNDBLASTER_PlayDevice; 620 impl->CloseDevice = DOSSOUNDBLASTER_CloseDevice; 621 622 impl->OnlyHasDefaultPlaybackDevice = true; 623 624 return true; 625} 626 627AudioBootStrap DOSSOUNDBLASTER_bootstrap = { 628 "soundblaster", "Sound Blaster", DOSSOUNDBLASTER_Init, false, false 629}; 630 631#endif // SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER 632
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.