├── .gitignore ├── LICENSE.md ├── README.md ├── cli.h ├── convert.cpp ├── convert.h ├── devds1.cpp ├── devds1.h ├── devgus.cpp ├── devgus.h ├── devhda.cpp ├── devhda.h ├── devhonk.cpp ├── devhonk.h ├── devhonka.asm ├── devpas.cpp ├── devpas.h ├── devsb.cpp ├── devsb.h ├── devwss.cpp ├── devwss.h ├── dma.cpp ├── dma.h ├── docs └── TODO.md ├── dpmi.cpp ├── dpmi.h ├── ds1defs.h ├── examples ├── imaplay │ ├── README.md │ ├── decode.asm │ ├── decode.h │ ├── imados.cpp │ ├── imados.h │ ├── imaplay.cpp │ ├── imaplay.h │ ├── makefile │ ├── test.cpp │ └── wavehdr.h ├── mp2play │ ├── README.md │ ├── fpstate.h │ ├── makefile │ ├── mp2dec │ │ ├── basedec.h │ │ ├── getbits.cpp │ │ ├── getbits.h │ │ ├── makefile │ │ ├── mp2dec.cpp │ │ ├── mp2dec.h │ │ ├── mp2dec_a.asm │ │ ├── rdsmp.cpp │ │ ├── transfrm.cpp │ │ └── transfrm.h │ ├── mp2dos.cpp │ ├── mp2dos.h │ ├── mp2play.cpp │ ├── mp2play.h │ └── test.cpp └── wavesine │ ├── makefile │ └── wavesine.cpp ├── hdadefs.h ├── irq.cpp ├── irq.h ├── logerror.cpp ├── logerror.h ├── makefile ├── mmio.h ├── snddefs.h ├── snddev.cpp ├── snddev.h ├── snderror.h ├── sndfmt.h ├── sndioctl.h ├── sndlib.cpp ├── sndlib.h ├── sndmisc.cpp ├── sndmisc.h ├── tinypci.cpp └── tinypci.h /.gitignore: -------------------------------------------------------------------------------- 1 | # preliminary .gitignore 2 | 3 | hdatest/* 4 | waveplay/* 5 | *.obj 6 | *.exe 7 | *.err 8 | *.lst 9 | *.map 10 | *.bak 11 | *.lib -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | sndlib library is licensed under the following terms of MIT license: 2 | 3 | ``` 4 | Copyright (c) 2022 Artem Vasilev - wbcbz7 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ``` 24 | 25 | in any way, you are free to use sndlib in any kind of software (including commercial) without any restrictions, make any modifications, etc., as long as if you plan to use this in your own productions, please don't forget to mention the copyright notice somewhere in the documentation. thanks in advance ;) 26 | 27 | -------------------------------------------------------------------------------- /cli.h: -------------------------------------------------------------------------------- 1 | #ifndef __CLI_H__ 2 | #define __CLI_H__ 3 | 4 | #include 5 | #include 6 | 7 | #if (defined(_DOS) || defined(__DOS__)) 8 | 9 | unsigned long pushf(); 10 | #pragma aux pushf = "pushfd" "pop eax" value [eax] modify [eax] 11 | 12 | void popf(unsigned long); 13 | #pragma aux popf = "push eax" "popfd" parm [eax] modify [eax] 14 | 15 | inline void _enable_if_enabled(unsigned long flags) { if (flags & 0x200) _disable(); } 16 | 17 | #else 18 | 19 | // multitasking systems will kick you for these tricks 20 | 21 | #error "pushf/popf are risky on non-DOS environments, so you are warned" 22 | 23 | #define pushf(a) {}; 24 | #define popf(a) {}; 25 | #define _enable_if_enabled(a) {}; 26 | 27 | #endif 28 | 29 | #endif -------------------------------------------------------------------------------- /convert.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "convert.h" 5 | #include "snddefs.h" 6 | 7 | #if 1 8 | // 16bit stereo -> 16bit stereo 9 | int __sndconvcall __declspec(naked) sndconv_memcpy(void *dst, void *src, uint32_t length, uint32_t div, uint32_t) { 10 | _asm { 11 | xchg ecx, edx 12 | shl edx, cl // ecx = length << div 13 | 14 | mov ecx, edx 15 | and edx, 3 16 | shr ecx, 2 17 | 18 | // copy by dwords 19 | rep movsd 20 | 21 | // copy remainder 22 | mov ecx, edx 23 | jcxz _end 24 | rep movsb 25 | _end: 26 | ret 27 | } 28 | } 29 | #else 30 | int __sndconvcall sndconv_memcpy(void *dst, void *src, uint32_t length, uint32_t div, uint32_t) { 31 | memcpy(dst, src, length << div); 32 | return 0; 33 | } 34 | #endif 35 | 36 | #ifdef SNDLIB_CONVERT_ENABLE_ARBITRARY 37 | 38 | // 8 bit any -> 16 bit any (for HD audio and other PCI devices) 39 | int __sndconvcall sndconv_8_16(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t postmul) { 40 | int8_t *p = (int8_t*)src; 41 | int16_t *v = (int16_t*)dst; 42 | 43 | length *= postmul; 44 | if (length == 0) return 0; 45 | do { 46 | *v++ = ((int16_t)(*p++ ^ xormask) << 8); 47 | } while (--length != 0); 48 | 49 | return 0; 50 | } 51 | 52 | // 16bit stereo -> 16bit mono SIGNED 53 | int __sndconvcall sndconv_16s_16m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t) { 54 | int16_t *p = (int16_t*)src; 55 | int16_t *v = (int16_t*)dst; 56 | 57 | if (length == 0) return 0; 58 | do { 59 | *v++ = ((*p >> 1) + (*(p+1) >> 1)) ^ xormask; p+=2; 60 | } while (--length != 0); 61 | 62 | return 0; 63 | } 64 | 65 | // 8bit stereo -> 8bit mono 66 | int __sndconvcall sndconv_8s_8m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t) { 67 | uint8_t *p = (uint8_t*)src; 68 | uint8_t *v = (uint8_t*)dst; 69 | 70 | if (length == 0) return 0; 71 | do { 72 | *v++ = (((int)*p + *(p + 1)) >> 1) ^ xormask; p += 2; 73 | } while (--length != 0); 74 | 75 | return 0; 76 | } 77 | 78 | // 8bit stereo -> 8bit mono UNSIGNED 79 | int __sndconvcall sndconv_8sus_8m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t) { 80 | uint8_t *p = (uint8_t*)src; 81 | uint8_t *v = (uint8_t*)dst; 82 | 83 | if (length == 0) return 0; 84 | do { 85 | *v++ = ((((int)(*p ^ 0x80)) + ((int)(*(p+1) ^ 0x80))) >> 1) ^ xormask; p += 2; 86 | } while (--length != 0); 87 | 88 | return 0; 89 | } 90 | 91 | // 16bit stereo -> 8 bit (un)signed stereo, no dither 92 | int __sndconvcall sndconv_16s_8s(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t) { 93 | uint32_t *p = (uint32_t*)src; 94 | uint32_t *v = (uint32_t*)dst; 95 | 96 | if (length >= 2) do { 97 | *v++ = (((*p >> 8) & 0x000000FF) | ((*p >> 16) & 0x0000FF00) | ((*(p+1) << 8) & 0x00FF0000) | ((*(p+1) & 0xFF000000))) ^ xormask; p += 2; length -= 2; 98 | } while (length > 1); 99 | if (length == 1) *(uint16_t*)v = (((*p >> 8) & 0x000000FF) | ((*p >> 16) & 0x0000FF00)) ^ xormask; 100 | 101 | return 0; 102 | } 103 | 104 | // 16bit stereo -> 8 bit (un)signed stereo, no dither 105 | int __sndconvcall sndconv_16m_8m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t) { 106 | uint32_t *p = (uint32_t*)src; 107 | uint32_t *v = (uint32_t*)dst; 108 | 109 | if (length >= 4) do { 110 | *v++ = (((*p >> 8) & 0x000000FF) | ((*p >> 16) & 0x0000FF00) | ((*(p+1) << 8) & 0x00FF0000) | ((*(p+1) & 0xFF000000))) ^ xormask; p += 2; length -= 4; 111 | } while (length > 3); 112 | 113 | uint16_t *p16 = (uint16_t*)p; 114 | uint8_t *v8 = (uint8_t*)v; 115 | while (length-- != 0) { 116 | *v8++ = (*p16++ >> 8) ^ xormask; 117 | }; 118 | 119 | return 0; 120 | } 121 | 122 | // 16bit stereo -> 8 bit (un)signed mono, no dither 123 | int __sndconvcall sndconv_16s_8m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t) { 124 | int16_t *p = (int16_t*)src; 125 | int8_t *v = (int8_t*)dst; 126 | if (length == 0) return 0; 127 | 128 | do { 129 | *v++ = (((*p >> 1) + (*(p+1) >> 1)) >> 8) ^ 0x80; p += 2; 130 | } while(--length != 0); 131 | 132 | return 0; 133 | } 134 | 135 | #endif 136 | 137 | #ifdef SNDLIB_CONVERT_ENABLE_PCSPEAKER 138 | 139 | // 8bit mono to 8bit xlated (for PC speaker) 140 | int __sndconvcall sndconv_8m_xlat (void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t xlatPtr) { 141 | uint8_t *p = (uint8_t*)src; uint8_t *v = (uint8_t*)dst; uint8_t *x = (uint8_t*)xlatPtr; xormask &= 0xFF; 142 | 143 | if (length == 0) return 0; 144 | do { 145 | *v++ = x[*p++ ^ xormask]; 146 | } while (--length != 0); 147 | 148 | return 0; 149 | } 150 | // 8bit stereo to 8bit xlated (for PC speaker) 151 | int __sndconvcall sndconv_8s_xlat (void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t xlatPtr) { 152 | uint8_t *p = (uint8_t*)src; uint8_t *v = (uint8_t*)dst; uint8_t *x = (uint8_t*)xlatPtr; xormask &= 0xFF; 153 | xormask = (int8_t)xormask; // sign-extend it 154 | 155 | if (length == 0) return 0; 156 | 157 | _asm { 158 | mov edi, [v] 159 | mov esi, [p] 160 | mov ebx, [x] 161 | mov ecx, [length] 162 | 163 | _loop: 164 | movsx eax, byte ptr [esi + 0] // fuck P5 165 | movsx edx, byte ptr [esi + 1] 166 | xor eax, [xormask] 167 | xor edx, [xormask] 168 | add eax, edx 169 | shr eax, 1 170 | and eax, 0xFF 171 | mov edx, [ebx + eax] 172 | mov [edi], edx 173 | 174 | add esi, 2 175 | add edi, 1 176 | dec ecx 177 | jnz _loop 178 | } 179 | 180 | /* 181 | do { 182 | uint8_t left = *(p+0) ^ xormask; 183 | uint8_t right = *(p+1) ^ xormask; 184 | 185 | *v++ = x[((left + right) >> 1) & 0xFF]; p += 2; 186 | } while (--length != 0); 187 | */ 188 | 189 | return 0; 190 | } 191 | // 16bit mono to 8bit xlated (for PC speaker) 192 | int __sndconvcall sndconv_16m_xlat (void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t xlatPtr) { 193 | uint16_t *p = (uint16_t*)src; uint8_t *v = (uint8_t*)dst; uint8_t *x = (uint8_t*)xlatPtr; xormask &= 0xFFFF; 194 | 195 | if (length == 0) return 0; 196 | do { 197 | *v++ = x[((*p++ ^ xormask) >> 8) & 0xFF]; 198 | } while (--length != 0); 199 | 200 | return 0; 201 | } 202 | // 16bit stereo to 8bit xlated (for PC speaker) 203 | int __sndconvcall sndconv_16s_xlat (void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t xlatPtr) { 204 | uint16_t *p = (uint16_t*)src; uint8_t *v = (uint8_t*)dst; uint8_t *x = (uint8_t*)xlatPtr; 205 | xormask = (int16_t)xormask; // sign-extend it 206 | 207 | if (length == 0) return 0; 208 | 209 | _asm { 210 | mov edi, [v] 211 | mov esi, [p] 212 | mov ebx, [x] 213 | mov ecx, [length] 214 | 215 | _loop: 216 | movsx eax, word ptr [esi + 0] // fuck P5 217 | movsx edx, word ptr [esi + 2] 218 | xor eax, [xormask] 219 | xor edx, [xormask] 220 | add eax, edx 221 | shr eax, 9 222 | and eax, 0xFF 223 | mov edx, [ebx + eax] 224 | mov [edi], edx 225 | 226 | add esi, 4 227 | add edi, 1 228 | dec ecx 229 | jnz _loop 230 | } 231 | 232 | /* 233 | do { 234 | uint16_t left = *(p+0) ^ xormask; 235 | uint16_t right = *(p+1) ^ xormask; 236 | 237 | *v++ = x[((left + right) >> 9) & 0xFF]; p += 2; 238 | } while (--length != 0); 239 | */ 240 | 241 | return 0; 242 | } 243 | 244 | #endif 245 | -------------------------------------------------------------------------------- /convert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "sndfmt.h" 5 | #include "snddefs.h" 6 | 7 | // convert.asm imports 8 | #pragma aux __sndconvcall "*_\\conv" parm caller [edi] [esi] [ecx] [edx] [ebx] \ 9 | value [eax] modify [eax ebx ecx edx esi edi] 10 | 11 | #define __sndconvcall __declspec(__pragma("__sndconvcall")) 12 | 13 | typedef int __sndconvcall (*soundFormatConverter) (void*, void*, uint32_t, uint32_t, uint32_t); 14 | 15 | struct soundFormatConverterInfo { 16 | soundFormat format; // current sound format 17 | soundFormatConverter proc; // converter procedure pointer 18 | uint32_t parm; // passed in edx while calling proc 19 | uint32_t parm2; // passed in ebx while calling proc 20 | uint32_t bytesPerSample; // bytes per each sample 21 | uint32_t sourceSampleRate; // requested sample rate 22 | uint32_t sampleRate; // actual sample rate 23 | }; 24 | 25 | // callback return codes 26 | enum soundDeviceCallbackResult { 27 | callbackOk, callbackComplete, callbackSkip, callbackAbort 28 | }; 29 | 30 | // callback definition 31 | typedef soundDeviceCallbackResult(*soundDeviceCallback)(void* userPtr, void* buffer, uint32_t bufferSamples, soundFormatConverterInfo *fmt, uint64_t bufferPos); 32 | 33 | int __sndconvcall sndconv_memcpy(void *dst, void *src, uint32_t length, uint32_t div, uint32_t); 34 | 35 | #ifdef SNDLIB_CONVERT_ENABLE_ARBITRARY 36 | int __sndconvcall sndconv_16s_16m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t); 37 | int __sndconvcall sndconv_8sus_8m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t); 38 | int __sndconvcall sndconv_8s_8m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t); 39 | int __sndconvcall sndconv_16s_8s(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t); 40 | int __sndconvcall sndconv_16m_8m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t); 41 | int __sndconvcall sndconv_16s_8m(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t); 42 | int __sndconvcall sndconv_8_16(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t lenmul); 43 | #endif 44 | 45 | #ifdef SNDLIB_CONVERT_ENABLE_PCSPEAKER 46 | int __sndconvcall sndconv_16m_xlat(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t xlatPtr); 47 | int __sndconvcall sndconv_8m_xlat (void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t xlatPtr); 48 | int __sndconvcall sndconv_16s_xlat(void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t xlatPtr); 49 | int __sndconvcall sndconv_8s_xlat (void *dst, void *src, uint32_t length, uint32_t xormask, uint32_t xlatPtr); 50 | #endif 51 | -------------------------------------------------------------------------------- /devds1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Yamaha DS-1 PCI (YMF724/744/754) driver 4 | // --wbcbz7 23.o6.2o22 5 | 6 | #include "snddefs.h" 7 | #ifdef SNDLIB_DEVICE_ENABLE_DS1 8 | 9 | #include 10 | #include "snddev.h" 11 | #include "convert.h" 12 | #include "sndmisc.h" 13 | 14 | #include "cli.h" 15 | #include "irq.h" 16 | #include "dma.h" 17 | #include "sndioctl.h" 18 | 19 | #include "ds1defs.h" 20 | 21 | class sndYamahaDS1 : public DmaBufferDevice { 22 | public: 23 | sndYamahaDS1(); 24 | 25 | // ------------- common device methods -------------------------------- 26 | // get device name 27 | //virtual const char *getName(); 28 | 29 | // get available resources for manual config, return -1 if autoconfig only, 0 if error, else length of info array 30 | //virtual const uint32_t getResourceInfo(const soundResourceInfo *info); 31 | 32 | // get supported formats, 0 if error, else length of info array 33 | //virtual const uint32_t getCaps(const soundFormatCapability* info); 34 | 35 | // detect (0 if found + if (res != NULL) *res filled with current config) 36 | virtual uint32_t detect(SoundDevice::deviceInfo *info = NULL); 37 | 38 | // select and init (use supplied *res info if (res != NULL)) 39 | virtual uint32_t init(SoundDevice::deviceInfo *info = NULL); 40 | 41 | // check if format is supported (0 if supported + passes pointer to converter procedure) 42 | //virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat fmt, soundFormatConverterInfo *conv); 43 | 44 | // return converter for current format 45 | //virtual uint32_t getConverter(soundFormat srcfmt, soundFormat dstfmt, soundFormatConverterInfo *conv); 46 | 47 | // return bytes per sample count 48 | //virtual uint32_t getBytesPerSample(soundFormat fmt); 49 | 50 | // init for playback 51 | virtual uint32_t open(uint32_t sampleRate, soundFormat fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void *userdata, soundFormatConverterInfo *conv); 52 | 53 | // start playback (won't return immediately, calls callback to fill DMA buffer) 54 | virtual uint32_t start(); 55 | 56 | // pause playback (start() or resume() for resume) 57 | virtual uint32_t pause(); 58 | 59 | // resume playback 60 | virtual uint32_t resume(); 61 | 62 | // get playback position in samples 63 | virtual int64_t getPos(); 64 | 65 | // ioctl 66 | virtual uint32_t ioctl(uint32_t function, void *data, uint32_t len); 67 | 68 | // stop playback 69 | virtual uint32_t stop(); 70 | 71 | // close playback 72 | virtual uint32_t close(); 73 | 74 | // deinit device 75 | virtual uint32_t done(); 76 | 77 | protected: 78 | 79 | // --------------------------- DS1 stuff -------------------- 80 | 81 | // PCI device info struct 82 | pciDeviceList pciInfo; 83 | sndlib::ymf_memoryAllocInfo ds1alloc; 84 | 85 | uint32_t sampleDelta; // ([sampleRate] << 28) / 48000 86 | uint32_t oldChanPlayPos; 87 | uint32_t oldSample48Count; // saved during pause()/resume() 88 | 89 | // init DMA buffer 90 | virtual uint32_t dmaBufferInit(uint32_t bufferSize, soundFormatConverterInfo *conv); 91 | 92 | // --------------------------- IRQ stuff -------------------- 93 | 94 | virtual bool irqProc(); 95 | 96 | // scan PCI configuration space, find suitable devices, fill info 97 | bool ds1Detect(SoundDevice::deviceInfo* info, bool manualDetect); 98 | 99 | // fill codec info, assumes info->membase is valid 100 | uint32_t fillCodecInfo(SoundDevice::deviceInfo* info); 101 | 102 | }; 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /devgus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Gravis Ultrasound (GF1) driver 4 | // --wbcbz7 21o27e8 5 | 6 | #include "snddefs.h" 7 | #ifdef SNDLIB_DEVICE_ENABLE_GUS 8 | 9 | #include 10 | #include "snddev.h" 11 | #include "convert.h" 12 | #include "sndmisc.h" 13 | 14 | #include "cli.h" 15 | #include "irq.h" 16 | #include "dma.h" 17 | #include "sndioctl.h" 18 | 19 | // register defines 20 | enum { 21 | GF1_REG_DMA_CTRL = 0x41, 22 | GF1_REG_DMA_ADDR = 0x42, 23 | GF1_REG_DRAM_ADDR_LOW = 0x43, 24 | GF1_REG_DRAM_ADDR_HIGH = 0x44, 25 | GF1_REG_TIMER_CTRL = 0x45, 26 | GF1_REG_TIMER1_COUNT = 0x46, 27 | GF1_REG_TIMER2_COUNT = 0x47, 28 | GF1_REG_SAMPLING_FREQ = 0x48, 29 | GF1_REG_SAMPLING_CTRL = 0x49, 30 | GF1_REG_JOY_TRIMDAC = 0x4B, 31 | GF1_REG_RESET = 0x4C, 32 | 33 | GF1_REG_CHAN_READ = 0x80, 34 | 35 | GF1_REG_CHAN_CTRL = 0x00, 36 | GF1_REG_CHAN_FREQ = 0x01, 37 | GF1_REG_CHAN_START_HIGH = 0x02, 38 | GF1_REG_CHAN_START_LOW = 0x03, 39 | GF1_REG_CHAN_END_HIGH = 0x04, 40 | GF1_REG_CHAN_END_LOW = 0x05, 41 | GF1_REG_CHAN_RAMP_RATE = 0x06, 42 | GF1_REG_CHAN_RAMP_START = 0x07, 43 | GF1_REG_CHAN_RAMP_END = 0x08, 44 | GF1_REG_CHAN_VOLUME = 0x09, 45 | GF1_REG_CHAN_POS_HIGH = 0x0A, 46 | GF1_REG_CHAN_POS_LOW = 0x0B, 47 | GF1_REG_CHAN_PAN = 0x0C, 48 | GF1_REG_CHAN_VOL_CTRL = 0x0D, 49 | 50 | GF1_REG_ACTIVE_CHANS = 0x0E, 51 | GF1_REG_IRQ_STATUS = 0x0F, 52 | }; 53 | 54 | // TODO: ioctls 55 | 56 | // base GUS driver 57 | class sndGravisUltrasound : public IsaDmaDevice { 58 | 59 | public: 60 | // constructor (nothing fancy here) 61 | sndGravisUltrasound(); 62 | 63 | // get device name 64 | // virtual const char *getName(); 65 | 66 | // detect (0 if found + if (res != NULL) *res filled with current config) 67 | virtual uint32_t detect(SoundDevice::deviceInfo *info = NULL); 68 | 69 | // select and init (use supplied *res info if (res != NULL)) 70 | virtual uint32_t init(SoundDevice::deviceInfo* info = NULL); 71 | 72 | /* 73 | // check if format is supported (0 if supported + passes pointer to converter procedure) 74 | virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat *fmt, soundFormatConverterInfo **conv); 75 | 76 | // return converter for current format 77 | soundFormatConverterInfo getFormatConverter(soundFormat *fmt); 78 | */ 79 | 80 | // init for playback 81 | virtual uint32_t open(uint32_t sampleRate, soundFormat fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void* userdata, soundFormatConverterInfo* conv); 82 | 83 | // start playback (won't return immediately, calls callback to fill DMA buffer) 84 | virtual uint32_t start(); 85 | 86 | // pause playback (start() for resume) 87 | virtual uint32_t pause(); 88 | 89 | // resume playback 90 | virtual uint32_t resume(); 91 | 92 | // get playback position in samples 93 | //virtual int64_t getPos(); 94 | 95 | // ioctl 96 | virtual uint32_t ioctl(uint32_t function, void* data, uint32_t len); 97 | 98 | // stop playback 99 | virtual uint32_t stop(); 100 | 101 | // close 102 | virtual uint32_t close(); 103 | 104 | // deinit device 105 | virtual uint32_t done(); 106 | 107 | protected: 108 | 109 | // detect GUS presence, fill deviceInfo, returns 1 if success 110 | bool gusDetect(SoundDevice::deviceInfo* info, bool manualDetect = false); 111 | 112 | // reset GF1, get memory size in bytes or 0 if not detected or no memory available 113 | virtual uint32_t gusReset(uint32_t iobase); 114 | 115 | // get DMA control flags 116 | virtual uint8_t getDMACtrl(soundFormat format); 117 | 118 | // probe environment 119 | bool readEnvironment(SoundDevice::deviceInfo *info); 120 | 121 | // convert and upload block of audio 122 | virtual uint32_t convertAndUpload(uint32_t samples, uint32_t gusofs, bool first); 123 | 124 | // returns play position within DMA buffer in bytes 125 | virtual int32_t getPlayPos(); 126 | 127 | // IRQ->callback caller 128 | virtual bool irqCallbackCaller(); 129 | 130 | // advance play/render pointers 131 | //virtual void irqAdvancePos(); 132 | 133 | // ------------------------- playback stuff ----------------- 134 | 135 | // GF1 DRAM size in bytes 136 | uint32_t dramsize; 137 | // offset of channel data in GF1 DRAM (in sample frames!) 138 | uint32_t channel_offset[2]; 139 | // last rendered sample count (for anticlick) 140 | uint32_t sample_bytes_to_render_last; 141 | // DMA buffer offsets 142 | uint32_t dmablk_offset[2]; 143 | // sample pointers for converters 144 | uint8_t *conv_buf_ptr[2]; 145 | // channel pitch 146 | uint16_t pitch; 147 | // DMACTRL (GF1 index 0x41) flags for format 148 | uint8_t dmactrl; 149 | // current buffer half 150 | uint8_t buffer_half; 151 | 152 | // --------------------------- IRQ stuff -------------------- 153 | 154 | virtual bool irqProc(); 155 | }; 156 | 157 | #endif -------------------------------------------------------------------------------- /devhda.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Intel High Definition Audio generic driver 4 | // --wbcbz7 23.o6.2o22 5 | 6 | #include "snddefs.h" 7 | #ifdef SNDLIB_DEVICE_ENABLE_HDA 8 | 9 | #include 10 | #include "snddev.h" 11 | #include "convert.h" 12 | #include "sndmisc.h" 13 | 14 | #include "cli.h" 15 | #include "irq.h" 16 | #include "dma.h" 17 | #include "sndioctl.h" 18 | 19 | // HDA ioctl definitions 20 | enum { 21 | SND_IOCTL_HDA_SPEAKEROUT_ENABLE_GET = SND_IOCTL_DEVICE_SPECIFIC, 22 | SND_IOCTL_HDA_SPEAKEROUT_ENABLE_SET, 23 | }; 24 | 25 | #include "hdadefs.h" 26 | 27 | // base HDA driver 28 | class sndHDAudio : public DmaBufferDevice { 29 | public: 30 | sndHDAudio(); 31 | 32 | // ------------- common device methods -------------------------------- 33 | // get device name 34 | //virtual const char *getName(); 35 | 36 | // get available resources for manual config, return -1 if autoconfig only, 0 if error, else length of info array 37 | //virtual const uint32_t getResourceInfo(const soundResourceInfo *info); 38 | 39 | // get supported formats, 0 if error, else length of info array 40 | //virtual const uint32_t getCaps(const soundFormatCapability* info); 41 | 42 | // detect (0 if found + if (res != NULL) *res filled with current config) 43 | virtual uint32_t detect(SoundDevice::deviceInfo *info = NULL); 44 | 45 | // select and init (use supplied *res info if (res != NULL)) 46 | virtual uint32_t init(SoundDevice::deviceInfo *info = NULL); 47 | 48 | // check if format is supported (0 if supported + passes pointer to converter procedure) 49 | //virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat fmt, soundFormatConverterInfo *conv); 50 | 51 | // return converter for current format 52 | //virtual uint32_t getConverter(soundFormat srcfmt, soundFormat dstfmt, soundFormatConverterInfo *conv); 53 | 54 | // return bytes per sample count 55 | //virtual uint32_t getBytesPerSample(soundFormat fmt); 56 | 57 | // init for playback 58 | virtual uint32_t open(uint32_t sampleRate, soundFormat fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void *userdata, soundFormatConverterInfo *conv); 59 | 60 | // start playback (won't return immediately, calls callback to fill DMA buffer) 61 | virtual uint32_t start(); 62 | 63 | // pause playback (start() or resume() for resume) 64 | virtual uint32_t pause(); 65 | 66 | // resume playback 67 | virtual uint32_t resume(); 68 | 69 | // get playback position in samples 70 | //virtual int64_t getPos(); 71 | 72 | // ioctl 73 | virtual uint32_t ioctl(uint32_t function, void *data, uint32_t len); 74 | 75 | // stop playback 76 | virtual uint32_t stop(); 77 | 78 | // close playback 79 | virtual uint32_t close(); 80 | 81 | // deinit device 82 | virtual uint32_t done(); 83 | 84 | protected: 85 | 86 | // --------------------------- HDA stuff -------------------- 87 | 88 | // stream format/tag 89 | sndlib::hda_streamFormat hdaStreamFormat; 90 | uint32_t hdaStreamTag; 91 | uint32_t hdaStreamIndex; 92 | 93 | // HDA codec graph struct 94 | sndlib::hda_codecInfo codecGraph; 95 | 96 | // PCI device info struct 97 | pciDeviceList pciInfo; 98 | 99 | // --------------------------- DMA stuff -------------------- 100 | 101 | // buffer descriptor list pointers 102 | struct { 103 | _dpmi_ptr dpmi; // allocated from DOS memory 104 | sndlib::hda_bufferDescriptor* ptr; // 128b aligned 105 | } bufferDescriptor; 106 | 107 | // init DMA buffer 108 | virtual uint32_t dmaBufferInit(uint32_t bufferSize, soundFormatConverterInfo *conv); 109 | 110 | // free DMA buffer 111 | virtual uint32_t dmaBufferFree(); 112 | 113 | // --------------------------- IRQ stuff -------------------- 114 | 115 | virtual bool irqProc(); 116 | 117 | // get play position in DMA buffer in bytes 118 | virtual int32_t getPlayPos(); 119 | 120 | // advance buffer position 121 | virtual void irqAdvancePos(); 122 | 123 | // scan PCI configuration space, find suitable HDA codecs, fill info 124 | bool hdaDetect(SoundDevice::deviceInfo* info, bool manualDetect); 125 | 126 | // scan codec DACs for supported formats, returns format mask 127 | uint32_t getCodecCaps(SoundDevice::deviceInfo* info); 128 | 129 | // fill codec info, assumes info->membase is valid 130 | uint32_t fillCodecInfo(SoundDevice::deviceInfo* info); 131 | 132 | // reset codec and controller, get info 133 | uint32_t resetCodec(SoundDevice::deviceInfo* info, bool setupPCI); 134 | }; 135 | 136 | #endif 137 | -------------------------------------------------------------------------------- /devpas.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Pro Audio Spectrum original/Plus/16 driver 4 | // --wbcbz7 l9o52o22 5 | 6 | #include "snddefs.h" 7 | #ifdef SNDLIB_DEVICE_ENABLE_PAS 8 | 9 | #include 10 | #include "snddev.h" 11 | #include "convert.h" 12 | #include "sndmisc.h" 13 | 14 | #include "cli.h" 15 | #include "irq.h" 16 | #include "dma.h" 17 | #include "sndioctl.h" 18 | 19 | // --------------------- MVSOUND.SYS related stuff --------------------- 20 | struct MVSoundShadowRegisters { 21 | uint8_t _sysspkrtmr; /* 42 System Speaker Timer Address */ 22 | uint8_t _systmrctlr; /* 43 System Timer Control */ 23 | uint8_t _sysspkrreg; /* 61 System Speaker Register */ 24 | uint8_t _joystick; /* 201 Joystick Register */ 25 | uint8_t _lfmaddr; /* 388 Left FM Synth Address */ 26 | uint8_t _lfmdata; /* 389 Left FM Synth Data */ 27 | uint8_t _rfmaddr; /* 38A Right FM Synth Address */ 28 | uint8_t _rfmdata; /* 38B Right FM Synth Data */ 29 | uint8_t _dfmaddr; /* 788 Dual FM Synth Address */ 30 | uint8_t _dfmdata; /* 789 Dual FM Synth Data */ 31 | uint8_t _RESRVD1[1]; /* reserved */ 32 | uint8_t _paudiomixr; /* 78B Paralllel Audio Mixer Control*/ 33 | uint8_t _audiomixr; /* B88 Audio Mixer Control */ 34 | uint8_t _intrctlrst; /* B89 Interrupt Status */ 35 | uint8_t _audiofilt; /* B8A Audio Filter Control */ 36 | uint8_t _intrctlr; /* B8B Interrupt Control */ 37 | uint8_t _pcmdata; /* F88 PCM Data I/O Register */ 38 | uint8_t _RESRVD2; /* reserved */ 39 | uint8_t _crosschannel; /* F8A Cross Channel */ 40 | uint8_t _RESRVD3; /* reserved */ 41 | uint16_t _samplerate; /* 1388 Sample Rate Timer */ 42 | uint16_t _samplecnt; /* 1389 Sample Count Register */ 43 | uint16_t _spkrtmr; /* 138A Shadow Speaker Timer Count */ 44 | uint8_t _tmrctlr; /* 138B Shadow Speaker Timer Control */ 45 | uint8_t _mdirqvect; /* 1788 MIDI IRQ Vector Register */ 46 | uint8_t _mdsysctlr; /* 1789 MIDI System Control Register */ 47 | uint8_t _mdsysstat; /* 178A MIDI IRQ Status Register */ 48 | uint8_t _mdirqclr; /* 178B MIDI IRQ Clear Register */ 49 | uint8_t _mdgroup1; /* 1B88 MIDI Group #1 Register */ 50 | uint8_t _mdgroup2; /* 1B89 MIDI Group #2 Register */ 51 | uint8_t _mdgroup3; /* 1B8A MIDI Group #3 Register */ 52 | uint8_t _mdgroup4; /* 1B8B MIDI Group #4 Register */ 53 | }; 54 | 55 | // base PAS driver 56 | class sndProAudioSpectrum : public IsaDmaDevice { 57 | 58 | public: 59 | // constructor (nothing fancy here) 60 | sndProAudioSpectrum(); 61 | 62 | // get device name 63 | // virtual const char *getName(); 64 | 65 | // detect (0 if found + if (res != NULL) *res filled with current config) 66 | virtual uint32_t detect(SoundDevice::deviceInfo *info = NULL); 67 | 68 | // select and init (use supplied *res info if (res != NULL)) 69 | virtual uint32_t init(SoundDevice::deviceInfo* info = NULL); 70 | 71 | /* 72 | // check if format is supported (0 if supported + passes pointer to converter procedure) 73 | virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat *fmt, soundFormatConverterInfo **conv); 74 | 75 | // return converter for current format 76 | soundFormatConverterInfo getFormatConverter(soundFormat *fmt); 77 | */ 78 | 79 | // init for playback 80 | virtual uint32_t open(uint32_t sampleRate, soundFormat fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void* userdata, soundFormatConverterInfo* conv); 81 | 82 | // start playback (won't return immediately, calls callback to fill DMA buffer) 83 | virtual uint32_t start(); 84 | 85 | // pause playback (start() for resume) 86 | virtual uint32_t pause(); 87 | 88 | // resume playback 89 | virtual uint32_t resume(); 90 | 91 | // get playback position in samples 92 | //virtual int64_t getPos(); 93 | 94 | // ioctl 95 | virtual uint32_t ioctl(uint32_t function, void* data, uint32_t len); 96 | 97 | // stop playback 98 | virtual uint32_t stop(); 99 | 100 | // deinit all this shit 101 | virtual uint32_t done(); 102 | 103 | protected: 104 | 105 | // detection flags 106 | uint32_t featureLevel; 107 | uint32_t timeConstant; 108 | bool isMvSoundPresent; 109 | 110 | // detect PAS presence, fill deviceInfo, returns 1 if success 111 | bool pasDetect(SoundDevice::deviceInfo* info, bool manualDetect = false); 112 | 113 | // reset codec 114 | virtual bool pasReset(SoundDevice::deviceInfo* info); 115 | 116 | // fill info according to DSP version 117 | virtual uint32_t fillCodecInfo(SoundDevice::deviceInfo* info); 118 | 119 | // --------------------------- IRQ stuff -------------------- 120 | 121 | virtual bool irqProc(); 122 | 123 | // --------------------- MVSOUND.SYS related stuff --------------------- 124 | 125 | MVSoundShadowRegisters *shadowPtr; 126 | MVSoundShadowRegisters localShadow; 127 | 128 | }; 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /devsb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "snddefs.h" 4 | #if (defined(SNDLIB_DEVICE_ENABLE_SB) || \ 5 | defined(SNDLIB_DEVICE_ENABLE_SB16) || \ 6 | defined(SNDLIB_DEVICE_ENABLE_ESS)) 7 | 8 | 9 | // Sound Blaster 1.x/2.x/Pro/16 and ESS AudioDrive driver 10 | // --wbcbz7 oloz7e5 11 | 12 | #include 13 | #include "snddev.h" 14 | #include "convert.h" 15 | #include "sndmisc.h" 16 | 17 | #include "cli.h" 18 | #include "irq.h" 19 | #include "dma.h" 20 | #include "sndioctl.h" 21 | 22 | // base SB driver 23 | class sndSBBase : public IsaDmaDevice { 24 | public: 25 | // constructor (nothing fancy here) 26 | sndSBBase(const char *name); 27 | 28 | // get device name 29 | virtual const char *getName(); 30 | 31 | /* 32 | // detect (0 if found + if (res != NULL) *res filled with current config) 33 | virtual uint32_t detect(SoundDevice::deviceInfo *info = NULL); 34 | */ 35 | // select and init (use supplied *res info if (res != NULL)) 36 | virtual uint32_t init(SoundDevice::deviceInfo *info = NULL); 37 | /* 38 | // check if format is supported (0 if supported + passes pointer to converter procedure) 39 | virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat *fmt, soundFormatConverterInfo **conv); 40 | 41 | // return converter for current format 42 | soundFormatConverterInfo getFormatConverter(soundFormat *fmt); 43 | 44 | // init for playback 45 | virtual uint32_t open(uint32_t sampleRate, soundFormat *fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void *userdata, soundFormatConverterInfo *conv); 46 | 47 | // start playback (won't return immediately, calls callback to fill DMA buffer) 48 | virtual uint32_t start(); 49 | 50 | // pause playback (start() for resume) 51 | virtual uint32_t pause(); 52 | */ 53 | 54 | // resume playback 55 | //virtual uint32_t resume(); 56 | 57 | // get playback position in samples 58 | //virtual int64_t getPos(); 59 | 60 | // ioctl 61 | virtual uint32_t ioctl(uint32_t function, void *data, uint32_t len); 62 | 63 | // stop playback 64 | //virtual uint32_t stop(); 65 | 66 | // close playback 67 | virtual uint32_t close(); 68 | 69 | // deinit device 70 | virtual uint32_t done(); 71 | 72 | protected: 73 | 74 | // DSP version 75 | uint32_t dspVersion; 76 | 77 | // SB base common open functions 78 | uint32_t openCommon(uint32_t sampleRate, soundFormat fmt, soundFormat newFormat, uint32_t bufferSize, soundDeviceCallback callback, void* userdata, soundFormatConverterInfo* conv); 79 | 80 | // detect SB presence, fill deviceInfo, returns dsp version 81 | uint32_t sbDetect(SoundDevice::deviceInfo *info, bool manualDetect = false); 82 | 83 | // detect and init extended features (SB16?/ESS) 84 | virtual uint32_t detectExt(SoundDevice::deviceInfo* info, uint32_t sbDspVersion); 85 | 86 | // get IRQ/DMA configuration 87 | virtual uint32_t fillIrqDma(SoundDevice::deviceInfo* info, uint32_t sbDspVersion); 88 | 89 | // fill info according to DSP version 90 | virtual uint32_t fillDspInfo(SoundDevice::deviceInfo *info, uint32_t sbDspVersion); 91 | 92 | // convert info to string 93 | virtual const char* devinfoToString(SoundDevice::deviceInfo * info, uint32_t sbDspVersion); 94 | 95 | // --------------------------- IRQ stuff -------------------- 96 | 97 | virtual bool irqProc(); 98 | 99 | // used during IRQ discovery only 100 | static void __interrupt sbDetectIrqProc(); 101 | 102 | }; 103 | 104 | #ifdef SNDLIB_DEVICE_ENABLE_SB 105 | 106 | // SB 1.x/2.x/Pro driver 107 | class sndSoundBlaster : public sndSBBase { 108 | 109 | public: 110 | // constructor (nothing fancy here) 111 | sndSoundBlaster(); 112 | 113 | // get device name 114 | //virtual const char *getName(); 115 | 116 | // detect (0 if found + if (res != NULL) *res filled with current config) 117 | virtual uint32_t detect(SoundDevice::deviceInfo *info = NULL); 118 | 119 | /* 120 | // select and init (use supplied *res info if (res != NULL)) 121 | virtual uint32_t init(SoundDevice::deviceInfo *info = NULL); 122 | 123 | // check if format is supported (0 if supported + passes pointer to converter procedure) 124 | //virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat fmt, soundFormatConverterInfo *conv); 125 | 126 | // return converter for current format 127 | //virtual uint32_t getConverter(soundFormat srcfmt, soundFormat dstfmt, soundFormatConverterInfo *conv); 128 | */ 129 | 130 | // init for playback 131 | virtual uint32_t open(uint32_t sampleRate, soundFormat fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void *userdata, soundFormatConverterInfo *conv); 132 | 133 | // start playback (won't return immediately, calls callback to fill DMA buffer) 134 | virtual uint32_t start(); 135 | 136 | // pause playback (start() for resume) 137 | virtual uint32_t pause(); 138 | 139 | // resume playback 140 | virtual uint32_t resume(); 141 | 142 | // get playback position in samples 143 | //virtual int64_t getPos(); 144 | 145 | // ioctl 146 | virtual uint32_t ioctl(uint32_t function, void *data, uint32_t len); 147 | 148 | // stop playback 149 | virtual uint32_t stop(); 150 | 151 | // deinit device 152 | //virtual uint32_t done(); 153 | 154 | private: 155 | 156 | enum playbackType { 157 | SingleCycle = 0, // SB 1.x only 158 | AutoInit, // SB 2.00+/Pro up to 11m/22s 159 | HighSpeed // SB 2.01+/Pro above 11m/22s 160 | }; 161 | 162 | // is highspeed? flag 163 | playbackType playbackType; 164 | 165 | // time constant 166 | uint32_t timeConstant; 167 | 168 | // fill info according to DSP version 169 | virtual uint32_t fillDspInfo(SoundDevice::deviceInfo *info, uint32_t sbDspVersion); 170 | 171 | virtual bool irqProc(); 172 | }; 173 | 174 | #endif 175 | 176 | #ifdef SNDLIB_DEVICE_ENABLE_SB16 177 | 178 | // SB16 driver 179 | class sndSoundBlaster16 : public sndSBBase { 180 | 181 | public: 182 | // constructor (nothing fancy here) 183 | sndSoundBlaster16(); 184 | 185 | // get device name 186 | //virtual const char *getName(); 187 | 188 | // detect (0 if found + if (res != NULL) *res filled with current config) 189 | virtual uint32_t detect(SoundDevice::deviceInfo *info = NULL); 190 | 191 | /* 192 | // select and init (use supplied *res info if (res != NULL)) 193 | virtual uint32_t init(SoundDevice::deviceInfo *info = NULL); 194 | 195 | // check if format is supported (0 if supported + passes pointer to converter procedure) 196 | virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat fmt, soundFormatConverterInfo *conv); 197 | 198 | // return converter for current format 199 | uint32_t getConverter(soundFormat srcfmt, soundFormat dstfmt, soundFormatConverterInfo *conv); 200 | */ 201 | 202 | // init for playback 203 | virtual uint32_t open(uint32_t sampleRate, soundFormat fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void *userdata, soundFormatConverterInfo *conv); 204 | 205 | // start playback (won't return immediately, calls callback to fill DMA buffer) 206 | virtual uint32_t start(); 207 | 208 | // pause playback (start() for resume) 209 | virtual uint32_t pause(); 210 | 211 | // resume playback 212 | virtual uint32_t resume(); 213 | 214 | // ioctl 215 | virtual uint32_t ioctl(uint32_t function, void *data, uint32_t len); 216 | 217 | // stop playback 218 | virtual uint32_t stop(); 219 | 220 | // deinit all this shit 221 | //virtual uint32_t done(); 222 | 223 | private: 224 | 225 | bool is16Bit; 226 | 227 | // fill info according to DSP version 228 | virtual uint32_t fillDspInfo(SoundDevice::deviceInfo *info, uint32_t sbDspVersion); 229 | 230 | // get IRQ/DMA configuration 231 | virtual uint32_t fillIrqDma(SoundDevice::deviceInfo* info, uint32_t sbDspVersion); 232 | 233 | // convert info to string 234 | virtual const char* devinfoToString(SoundDevice::deviceInfo * info, uint32_t sbDspVersion); 235 | 236 | virtual bool irqProc(); 237 | 238 | // get start command 239 | virtual uint32_t getStartCommand(soundFormatConverterInfo &conv); 240 | }; 241 | 242 | #endif 243 | 244 | #ifdef SNDLIB_DEVICE_ENABLE_ESS 245 | 246 | // ESS ioctl definitions 247 | enum { 248 | SND_IOCTL_ESS_DEMANDMODE_GET = SND_IOCTL_DEVICE_SPECIFIC, 249 | SND_IOCTL_ESS_DEMANDMODE_SET, 250 | }; 251 | 252 | // ESS AudioDrive driver 253 | class sndESSAudioDrive : public sndSBBase { 254 | 255 | public: 256 | // constructor (nothing fancy here) 257 | sndESSAudioDrive(); 258 | 259 | // get device name 260 | //virtual const char* getName(); 261 | 262 | // detect (0 if found + if (res != NULL) *res filled with current config) 263 | virtual uint32_t detect(SoundDevice::deviceInfo* info = NULL); 264 | 265 | /* 266 | // select and init (use supplied *res info if (res != NULL)) 267 | virtual uint32_t init(SoundDevice::deviceInfo* info = NULL); 268 | 269 | // check if format is supported (0 if supported + passes pointer to converter procedure) 270 | virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat fmt, soundFormatConverterInfo *conv); 271 | 272 | // return converter for current format 273 | uint32_t getConverter(soundFormat srcfmt, soundFormat dstfmt, soundFormatConverterInfo *conv); 274 | */ 275 | 276 | // init for playback 277 | virtual uint32_t open(uint32_t sampleRate, soundFormat fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void* userdata, soundFormatConverterInfo* conv); 278 | 279 | // start playback (won't return immediately, calls callback to fill DMA buffer) 280 | virtual uint32_t start(); 281 | 282 | // pause playback (start() for resume) 283 | virtual uint32_t pause(); 284 | 285 | // resume playback 286 | virtual uint32_t resume(); 287 | 288 | // ioctl 289 | virtual uint32_t ioctl(uint32_t function, void* data, uint32_t len); 290 | 291 | // stop playback 292 | virtual uint32_t stop(); 293 | 294 | // deinit all this shit 295 | //virtual uint32_t done(); 296 | 297 | private: 298 | 299 | uint32_t modelId; // read by DSP command 0xE7 300 | uint32_t modelNumber; // e.g 688/1868/1869 301 | 302 | uint32_t timeConstant; 303 | 304 | // fill info according to DSP version 305 | virtual uint32_t fillDspInfo(SoundDevice::deviceInfo* info, uint32_t sbDspVersion); 306 | 307 | // detect and init extended features (SB16?/ESS) 308 | virtual uint32_t detectExt(SoundDevice::deviceInfo* info, uint32_t sbDspVersion); 309 | 310 | // get IRQ/DMA configuration 311 | virtual uint32_t fillIrqDma(SoundDevice::deviceInfo* info, uint32_t sbDspVersion); 312 | 313 | // convert info to string 314 | virtual const char* devinfoToString(SoundDevice::deviceInfo * info, uint32_t sbDspVersion); 315 | 316 | virtual bool irqProc(); 317 | 318 | // enable demand mode 319 | bool demandModeEnable; 320 | uint32_t demandModeBurstLength; 321 | }; 322 | 323 | #endif 324 | 325 | #endif 326 | -------------------------------------------------------------------------------- /devwss.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Windows Sound System and GUS MAX/PnP codecs driver 4 | // --wbcbz7 oloz7e5 5 | 6 | #include "snddefs.h" 7 | #ifdef SNDLIB_DEVICE_ENABLE_WSS 8 | 9 | #include 10 | #include "snddev.h" 11 | #include "convert.h" 12 | #include "sndmisc.h" 13 | 14 | #include "cli.h" 15 | #include "irq.h" 16 | #include "dma.h" 17 | #include "sndioctl.h" 18 | 19 | // WSS ioctl definitions 20 | enum { 21 | SND_IOCTL_WSS_64KHZ_GET = SND_IOCTL_DEVICE_SPECIFIC, 22 | SND_IOCTL_WSS_64KHZ_SET, 23 | 24 | SND_IOCTL_WSS_VARIABLE_RATE_GET, 25 | SND_IOCTL_WSS_VARIABLE_RATE_SET, 26 | 27 | SND_IOCTL_WSS_FEATURELEVEL_GET, 28 | SND_IOCTL_WSS_FEATURELEVEL_SET, 29 | }; 30 | 31 | enum { 32 | WSS_INDEX_INIT_STATE = (1 << 7), 33 | WSS_INDEX_MODE_CHANGE = (1 << 6), 34 | WSS_INDEX_TRD = (1 << 5), 35 | }; 36 | 37 | enum { 38 | WSS_PORT_INDEX = 0, 39 | WSS_PORT_DATA, 40 | WSS_PORT_STATUS, 41 | WSS_PORT_PIO_DATA 42 | }; 43 | 44 | enum { 45 | WSS_REG_LEFT_INPUT_CTRL = 0, 46 | WSS_REG_RIGHT_INPUT_CTRL, 47 | WSS_REG_LEFT_AUX0_CTRL, 48 | WSS_REG_RIGHT_AUX0_CTRL, 49 | WSS_REG_LEFT_AUX1_CTRL, 50 | WSS_REG_RIGHT_AUX1_CTRL, 51 | WSS_REG_LEFT_DAC_CTRL, 52 | WSS_REG_RIGHT_DAC_CTRL, 53 | WSS_REG_CLK_DATA_FORMAT, 54 | WSS_REG_INTERFACE_CONFIG, 55 | WSS_REG_PIN_CTRL, 56 | WSS_REG_TEST_INIT, 57 | WSS_REG_MODE_ID, 58 | WSS_REG_DIGITAL_MIX_CTRL, 59 | WSS_REG_DMA_COUNT_HIGH, 60 | WSS_REG_DMA_COUNT_LOW, 61 | 62 | // CS4231+ extended registers 63 | WSS_REG_ALT_FEATURE1, 64 | WSS_REG_ALT_FEATURE2, 65 | WSS_REG_LEFT_LINE_CTL, 66 | WSS_REG_RIGHT_LINET_CTL, 67 | WSS_REG_TIMER_HIGH, 68 | WSS_REG_TIMER_LOW, 69 | WSS_REG_ALTERNATE_SAMPLE_RATE, // WSS_FEATURE_CS4236 only 70 | WSS_REG_ALT_FEATURE3 = 23, // WSS_FEATURE_CS4231 only 71 | WSS_REG_EXT_INDEX = 23, // WSS_FEATURE_CS4236 only 72 | WSS_REG_ALT_FEATURE_STATUS, 73 | WSS_REG_EXTENDED_ID, 74 | WSS_REG_MONO_CTRL, 75 | WSS_REG_LEFT_MASTER_OUT_CTRL, // WSS_FEATURE_CS4236 only 76 | WSS_REG_CAPTURE_FORMAT, 77 | WSS_REG_RIGHT_MASTER_OUT_CTRL, // WSS_FEATURE_CS4236 only 78 | WSS_REG_CAPTURE_COUNT_HIGH, 79 | WSS_REG_CAPTURE_COUNT_LOW, 80 | 81 | // AMD Interwave registers fixups 82 | IW_REG_LEFT_OUT_ATTENUATION = 25, 83 | IW_REG_RIGHT_OUT_ATTENUATION = 27, 84 | IW_REG_PLAYBACK_SAMPLE_RATE = 29, 85 | }; 86 | 87 | enum { 88 | WSS_FEATURE_AD1848, // 16 registers, mode 1 only, half-duplex? 89 | WSS_FEATURE_CS4231, // 32 registers, mode 1/2, full-duplex 90 | WSS_FEATURE_CS4236, // 32 + extra registers, mode 1/2/3, full-duplex, variable rate 91 | WSS_FEATURE_INTERWAVE, // like CS4231 non-A, mode 1/2 + own mode3, full-duplex, variable rate (22/32k limited) 92 | }; 93 | 94 | 95 | // base WSS driver 96 | class sndWindowsSoundSystem : public IsaDmaDevice { 97 | 98 | public: 99 | // constructor (nothing fancy here) 100 | sndWindowsSoundSystem(); 101 | 102 | // get device name 103 | // virtual const char *getName(); 104 | 105 | // detect (0 if found + if (res != NULL) *res filled with current config) 106 | virtual uint32_t detect(SoundDevice::deviceInfo *info = NULL); 107 | 108 | // select and init (use supplied *res info if (res != NULL)) 109 | virtual uint32_t init(SoundDevice::deviceInfo* info = NULL); 110 | 111 | /* 112 | // check if format is supported (0 if supported + passes pointer to converter procedure) 113 | virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat *fmt, soundFormatConverterInfo **conv); 114 | 115 | // return converter for current format 116 | soundFormatConverterInfo getFormatConverter(soundFormat *fmt); 117 | */ 118 | 119 | // init for playback 120 | virtual uint32_t open(uint32_t sampleRate, soundFormat fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void* userdata, soundFormatConverterInfo* conv); 121 | 122 | // start playback (won't return immediately, calls callback to fill DMA buffer) 123 | virtual uint32_t start(); 124 | 125 | // pause playback (start() for resume) 126 | virtual uint32_t pause(); 127 | 128 | // resume playback 129 | virtual uint32_t resume(); 130 | 131 | // get playback position in samples 132 | //virtual int64_t getPos(); 133 | 134 | // ioctl 135 | virtual uint32_t ioctl(uint32_t function, void* data, uint32_t len); 136 | 137 | // stop playback 138 | virtual uint32_t stop(); 139 | 140 | // close 141 | virtual uint32_t close(); 142 | 143 | // deinit device 144 | virtual uint32_t done(); 145 | 146 | protected: 147 | 148 | bool isGus; // is GUS? 149 | bool is64khz; 150 | bool isVariableSampleRate; // few codecs support this, i.e later Crystal codecs and AMD Interwave 151 | 152 | // detect WSS presence, fill deviceInfo, returns 1 if success 153 | bool wssDetect(SoundDevice::deviceInfo* info, bool manualDetect = false); 154 | 155 | // reset codec 156 | virtual bool wssReset(SoundDevice::deviceInfo* info, bool isGus); 157 | 158 | // set sample rate and format 159 | virtual uint32_t setFormat(SoundDevice::deviceInfo* info, uint32_t sampleRate, soundFormat format); 160 | 161 | // fill info according to DSP version 162 | virtual uint32_t fillCodecInfo(SoundDevice::deviceInfo* info); 163 | 164 | // get codec version 165 | virtual bool getCodecVersion(SoundDevice::deviceInfo* info); 166 | 167 | // probe environment 168 | bool readEnvironment(SoundDevice::deviceInfo *info); 169 | 170 | // kickstart WSS to enable probing 171 | bool kickstartProbingPlayback(SoundDevice::deviceInfo *info, uint32_t dmaChannel, ::dmaBlock &block, uint32_t probeLength, bool enableIrq); 172 | 173 | // identification info 174 | uint32_t featureLevel; // detected feature level 175 | uint32_t oldId; // I12 aka WSS_REG_MODE_ID 176 | uint32_t newId; // I25 aka WSS_REG_EXTENDED_ID 177 | uint32_t extId; // X25 178 | 179 | // --------------------------- IRQ stuff -------------------- 180 | 181 | virtual bool irqProc(); 182 | 183 | // used during IRQ discovery only 184 | static void __interrupt wssDetectIrqProc(); 185 | }; 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /dma.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cli.h" 5 | #include "dma.h" 6 | #include "dpmi.h" 7 | #include "logerror.h" 8 | 9 | dmaPorts dmaPorts[] = { 10 | { 0x00, 0x01, 0x87, 0x09, 0x0A, 0x0B, 0x0C, 0 }, // 0 11 | { 0x02, 0x03, 0x83, 0x09, 0x0A, 0x0B, 0x0C, 0 }, // 1 12 | { 0x04, 0x05, 0x81, 0x09, 0x0A, 0x0B, 0x0C, 0 }, // 2 13 | { 0x06, 0x07, 0x82, 0x09, 0x0A, 0x0B, 0x0C, 0 }, // 3 14 | 15 | { 0x00, 0x00, 0x00, 0xD2, 0xD4, 0xD6, 0xD8, 0 }, // 4 (unapplicable) 16 | { 0xC4, 0xC6, 0x8B, 0xD2, 0xD4, 0xD6, 0xD8, 0 }, // 5 17 | { 0xC8, 0xCA, 0x89, 0xD2, 0xD4, 0xD6, 0xD8, 0 }, // 5 18 | { 0xCC, 0xCE, 0x8A, 0xD2, 0xD4, 0xD6, 0xD8, 0 }, // 5 19 | }; 20 | 21 | bool dmaAllocUnaligned(size_t len, dmaBlock *blk) { 22 | // check block size 23 | if ((len == 0) || (len > 65536)) { 24 | logerr("length must be nonzero and less than 64k\n"); 25 | return false; 26 | } 27 | 28 | // allocate block "as-is" 29 | _dpmi_ptr dpmi_block; 30 | dpmi_getdosmem((len + 15) >> 4, &dpmi_block); 31 | if (dpmi_status) { 32 | logerr("unable to allocate memory for DMA buffer\n"); 33 | return false; 34 | } 35 | 36 | size_t linaddr = (dpmi_block.segment << 4); 37 | blk->ptr = (void*)linaddr; 38 | blk->dpmi = dpmi_block; 39 | return true; 40 | } 41 | 42 | bool dmaAlloc(size_t len, dmaBlock *blk) { 43 | // check block size 44 | if ((len == 0) || (len > 65536)) { 45 | logerr("length must be nonzero and less than 64k\n"); 46 | return false; 47 | } 48 | 49 | // allocate block "as-is" 50 | _dpmi_ptr dpmi_block; 51 | dpmi_getdosmem((len + 15) >> 4, &dpmi_block); 52 | if (dpmi_status) { 53 | logerr("unable to allocate memory for DMA buffer\n"); 54 | return false; 55 | } 56 | 57 | size_t linaddr = (dpmi_block.segment << 4); 58 | 59 | // test for 64k boundary crossing 60 | if (((linaddr + len - 1) & ~0xFFFF) != (linaddr & ~0xFFFF)) { 61 | // calculate block size needed for alignment 62 | size_t adjust = ((linaddr + len) & ~0xFFFF) - linaddr; 63 | size_t newsize = len + adjust; 64 | 65 | // realloc block 66 | dpmi_freedosmem(&dpmi_block); 67 | if (dpmi_status) { 68 | logerr("DPMI error\n"); 69 | return false; 70 | } 71 | 72 | dpmi_getdosmem((newsize + 15) >> 4, &dpmi_block); 73 | if (dpmi_status) { 74 | logerr("unable to allocate memory for DMA buffer\n"); 75 | return false; 76 | } 77 | 78 | // test again 79 | blk->ptr = (void*)(((dpmi_block.segment << 4) + adjust) & ~0xFFFF); 80 | 81 | // test again 82 | linaddr = (size_t)blk->ptr; 83 | if (((linaddr + len - 1) & ~0xFFFF) != (linaddr & ~0xFFFF)) { 84 | logerr("unable to allocate non-64K crossing DMA buffer\n"); 85 | return false; 86 | } 87 | } else { 88 | blk->ptr = (void*)linaddr; 89 | } 90 | 91 | blk->dpmi = dpmi_block; 92 | return true; 93 | } 94 | 95 | bool dmaFree(dmaBlock *blk) { 96 | dpmi_freedosmem(&blk->dpmi); 97 | if (dpmi_status) { 98 | logerr("unable to free DMA buffer\n"); 99 | return false; 100 | } 101 | 102 | return true; 103 | } 104 | 105 | bool dmaSetup(size_t chan, dmaBlock *blk, size_t len, unsigned char mode, uint32_t offset) { 106 | 107 | if ((chan == 4) || (chan > 7)) { 108 | logerr("channel number must be for 0 to 7 (excluding 4)\n"); 109 | return false; 110 | } 111 | 112 | if ((len == 0) || (len > 65536)) { 113 | logerr("length must be nonzero and less than 64k\n"); 114 | return false; 115 | } 116 | 117 | unsigned char rawchan = chan & 3; 118 | 119 | // disable interrupts! 120 | unsigned long flags = pushf(); 121 | _disable(); 122 | 123 | // mask channel 124 | outp(dmaPorts[chan].mask, 0x04 | rawchan); 125 | 126 | // clear flip-flop 127 | outp(dmaPorts[chan].clear, 0x00); 128 | 129 | // set mode 130 | outp(dmaPorts[chan].mode, mode | rawchan); 131 | 132 | // apparently high DMAs require some shifting to make it work 133 | size_t rawptr = (size_t)blk->ptr + offset; 134 | rawptr = rawptr >> (chan >= 4 ? 1 : 0); 135 | size_t rawlen = (len >> (chan >= 4 ? 1 : 0)) - 1; 136 | size_t rawpage =(size_t)blk->ptr >> 16; 137 | 138 | // set offset 139 | outp(dmaPorts[chan].address, rawptr & 0xFF); 140 | outp(dmaPorts[chan].address, (rawptr >> 8) & 0xFF); 141 | outp(dmaPorts[chan].page, rawpage & 0xFF); 142 | 143 | // clear flip-flop again 144 | outp(dmaPorts[chan].clear, 0x00); 145 | 146 | // set length 147 | outp(dmaPorts[chan].count, rawlen & 0xFF); 148 | outp(dmaPorts[chan].count, (rawlen >> 8) & 0xFF); 149 | 150 | // unmask channel 151 | outp(dmaPorts[chan].mask, 0x00 | rawchan); 152 | 153 | // enable interrupts 154 | //_enable_if_enabled(flags); 155 | _enable(); 156 | 157 | return true; 158 | } 159 | 160 | bool dmaPause(size_t chan) { 161 | #ifdef DEBUG 162 | if ((chan == 4) || (chan > 7)) { 163 | logerr("channel number must be for 0 to 7 (excluding 4)\n"); 164 | return true; 165 | } 166 | #endif 167 | 168 | // mask channel 169 | outp(dmaPorts[chan].mask, 0x04 | (chan & 3)); 170 | 171 | return true; 172 | }; 173 | 174 | bool dmaResume(size_t chan) { 175 | #ifdef DEBUG 176 | if ((chan == 4) || (chan > 7)) { 177 | logerr("channel number must be for 0 to 7 (excluding 4)\n"); 178 | return true; 179 | } 180 | #endif 181 | 182 | // unmask channel 183 | outp(dmaPorts[chan].mask, 0x00 | (chan & 3)); 184 | 185 | return true; 186 | }; 187 | 188 | bool dmaStop(size_t chan) { 189 | #ifdef DEBUG 190 | if ((chan == 4) || (chan > 7)) { 191 | logerr("channel number must be for 0 to 7 (excluding 4)\n"); 192 | return true; 193 | } 194 | #endif 195 | 196 | // mask channel 197 | outp(dmaPorts[chan].mask, 0x04 | (chan & 3)); 198 | 199 | // clear channel 200 | outp(dmaPorts[chan].clear, 0x00); 201 | 202 | return true; 203 | }; 204 | 205 | uint16_t dmaRead(unsigned short port); 206 | #pragma aux dmaRead = "in al, dx" "mov bl, al" "in al, dx" "mov bh, al" parm [dx] value [bx] modify [ax] 207 | 208 | // read DMA controller 16-bit register 209 | uint32_t dmaRead16bit(uint32_t reg, bool lockInts) { 210 | unsigned long flags; 211 | 212 | if (lockInts == true) { 213 | // disable interrupts! 214 | flags = pushf(); 215 | _disable(); 216 | } 217 | 218 | // i8237 does not feature latching current address/count, so high byte can change while reading low byte! 219 | // so read it until retrieving correct value 220 | size_t timeout = 20; // try at least 20 times 221 | volatile unsigned short oldpos, pos = dmaRead(reg); 222 | 223 | do { 224 | oldpos = pos; 225 | pos = dmaRead(reg); 226 | 227 | if ((oldpos & 0xFF00) == (pos & 0xFF00)) break; 228 | } while (--timeout); 229 | 230 | if (lockInts == true) { 231 | // enable interrupts 232 | _enable_if_enabled(flags); 233 | } 234 | 235 | return pos; 236 | } 237 | 238 | // return current address (A[15..0] for 8bit channels, A[16..1] for 16bit) 239 | uint32_t dmaGetCurrentAddress(uint32_t chan, bool lockInts) { 240 | // clear flip-flop 241 | outp(dmaPorts[chan].clear, 0x00); 242 | return dmaRead16bit(dmaPorts[chan].address, lockInts); 243 | } 244 | 245 | // return current count register value 246 | uint32_t dmaGetCurrentCount(uint32_t chan, bool lockInts) { 247 | // clear flip-flop 248 | outp(dmaPorts[chan].clear, 0x00); 249 | return dmaRead16bit(dmaPorts[chan].count, lockInts); 250 | } -------------------------------------------------------------------------------- /dma.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "dpmi.h" 6 | 7 | enum { 8 | ISA_DMA_MAX_BUFFER_SIZE = 65536 9 | }; 10 | 11 | // DMA block struct 12 | struct dmaBlock { 13 | void *ptr; // linear address 14 | _dpmi_ptr dpmi; // internal DPMI structs 15 | }; 16 | 17 | struct dmaPorts { 18 | unsigned char address; 19 | unsigned char count; 20 | unsigned char page; 21 | unsigned char request; 22 | unsigned char mask; 23 | unsigned char mode; 24 | unsigned char clear; 25 | unsigned char dummy; 26 | }; 27 | 28 | // mode consts 29 | enum { 30 | dmaModeDemand = 0x0, 31 | dmaModeSingle = 0x40, 32 | dmaModeBlock = 0x80, 33 | dmaModeCascade = 0xC0, 34 | 35 | dmaModeInc = 0x0, 36 | dmaModeDec = 0x20, 37 | 38 | dmaModeNoAutoInit = 0x0, 39 | dmaModeAutoInit = 0x10, 40 | 41 | dmaModeVerify = 0x0, 42 | dmaModeWrite = 0x4, 43 | dmaModeRead = 0x8, 44 | }; 45 | 46 | // allocate memory for DMA transfers 47 | bool dmaAlloc(size_t len, dmaBlock *blk); 48 | bool dmaAllocUnaligned(size_t len, dmaBlock *blk); 49 | bool dmaFree(dmaBlock *blk); 50 | 51 | // setup DMA for transfer and start it (TODO: fix API) 52 | bool dmaSetup(size_t chan, dmaBlock *blk, size_t len, unsigned char mode, uint32_t offset = 0); 53 | 54 | // pause/resume transfer 55 | bool dmaPause(size_t chan); 56 | bool dmaResume(size_t chan); 57 | 58 | // stop transfer 59 | bool dmaStop(size_t chan); 60 | 61 | // get current address and count 62 | uint32_t dmaGetCurrentAddress(uint32_t chan, bool lockInts = true); 63 | uint32_t dmaGetCurrentCount(uint32_t chan, bool lockInts = true); 64 | 65 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | ### quick glossary: 2 | 3 | * DMA block refers to contiguous memory region (i.e 4 kB) used for ISA DMA transfers, divided by two or more DMA buffers. The ISA DMA controller is programmed for transfer size of entire DMA block, and the soundcard is programmed for transfer size of one DMA buffer, thus we can track which current buffer is playing now and update other buffers in the background 4 | 5 | 6 | 7 | ### bugs: 8 | 9 | - SB 2.0/Pro driver: 10 | - hispeed mode: fix pause/resume (cmd 0xD4 doesn't work for hispeed transfers and 0x90 resets current transfer count, thus SB goes out of sync with i8237) 11 | possible solution: rewind i8237 to start of current DMA buffer, but it's complicated because if playing DMA buffer is not first in the DMA block (see glossary if your mind exploding right now :). we need to re-setup i8237 when the SB finishes to play last DMA buffer 12 | 13 | * position/buffer tracking code is a one big kludge - i8237 is so unreliable when it comes to reading current buffer position. seems to work for me, but if any issues occur, don't hesitate to report 14 | 15 | ### fixed: 16 | 17 | - high IRQs (8-15) doesn't work? 18 | - it was chipset blocking IRQs assigned to PCI devices from being triggered by ISA 19 | - EMM386: 20 | - pagefaults on exit *fixed - unlock every locked memory region on exit* 21 | - SB2/Pro driver sometimes can't detect my CT4520 (while SB16 driver does) *DSP reset fixed, I forgot to poll "data available" flag while waiting for 0xAA in the read buffer; this should also fix stuck 0xAA bug in the first sbDspRead() call* 22 | 23 | * REGRESSION: IRQ routine crashes under Win9x (stack fault in sound callback, since Windows calls ISR on separate stack segment with non-zero base) - *fixed by changing stack switch code so no local variables are used* 24 | * ymf715 - autodetect hangs if ULTRA16 var is empty - *fixed - messed up with caps and IRQ detection* 25 | 26 | ### won't fix 27 | 28 | - non-DMA ISR jitter issues (PC Speaker sounds like scratched vinyl record, while Covox sounds way better) 29 | - Windows XP NTVDM: 30 | - Dual Covox/Stereo-on-1 detection always succeeds (finding two LPT ports at 0x378 and 0x278, and selecting the latter) 31 | - SB 2.0 driver horribly stutters under Windows XP NTVDM, broken DMA position tracking - *use VDMSound or DOSBox. no, really* 32 | -------------------------------------------------------------------------------- /ds1defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "mmio.h" 5 | 6 | // Yamaha DS-1 PCI (YMF724/744/754) driver 7 | // wbcbz7 o7.o8.2o22 8 | 9 | #define YMF_REG_WRITE32(base, reg, value) (MMIO_WRITE32(((uint8_t*)(base) + (reg)), (value))) 10 | #define YMF_REG_WRITE16(base, reg, value) (MMIO_WRITE16(((uint8_t*)(base) + (reg)), (value))) 11 | #define YMF_REG_WRITE8(base, reg, value) (MMIO_WRITE8(((uint8_t*)(base) + (reg)), (value))) 12 | 13 | #define YMF_REG_READ32(base, reg) (MMIO_READ32(((uint8_t*)(base) + (reg)))) 14 | #define YMF_REG_READ16(base, reg) (MMIO_READ16(((uint8_t*)(base) + (reg)))) 15 | #define YMF_REG_READ8(base, reg) (MMIO_READ8(((uint8_t*)(base) + (reg)))) 16 | 17 | #define YMF_REG_MODIFY32(base, reg, mask, value) (MMIO_MODIFY32(((uint8_t*)(base) + (reg)), (mask), (value))) 18 | #define YMF_REG_MODIFY16(base, reg, mask, value) (MMIO_MODIFY16(((uint8_t*)(base) + (reg)), (mask), (value))) 19 | #define YMF_REG_MODIFY8(base, reg, mask, value) (MMIO_MODIFY8(((uint8_t*)(base) + (reg)), (mask), (value))) 20 | 21 | #define YMF_REG_FORCE_WRITE32(base, reg, value) (MMIO_FORCE_WRITE32(((uint8_t*)(base) + (reg)), (value))) 22 | #define YMF_REG_FORCE_WRITE16(base, reg, value) (MMIO_FORCE_WRITE16(((uint8_t*)(base) + (reg)), (value))) 23 | #define YMF_REG_FORCE_WRITE8(base, reg, value) (MMIO_FORCE_WRITE8(((uint8_t*)(base) + (reg)), (value))) 24 | 25 | #define YMF_REG_FORCE_READ32(base, reg) (MMIO_FORCE_READ32(((uint8_t*)(base) + (reg)))) 26 | #define YMF_REG_FORCE_READ16(base, reg) (MMIO_FORCE_READ16(((uint8_t*)(base) + (reg)))) 27 | #define YMF_REG_FORCE_READ8(base, reg) (MMIO_FORCE_READ8(((uint8_t*)(base) + (reg)))) 28 | 29 | #define YMF_REG_FORCE_MODIFY32(base, reg, mask, value) (MMIO_FORCE_MODIFY32(((uint8_t*)(base) + (reg)), (mask), (value))) 30 | #define YMF_REG_FORCE_MODIFY16(base, reg, mask, value) (MMIO_FORCE_MODIFY16(((uint8_t*)(base) + (reg)), (mask), (value))) 31 | #define YMF_REG_FORCE_MODIFY8(base, reg, mask, value) (MMIO_FORCE_MODIFY8(((uint8_t*)(base) + (reg)), (mask), (value))) 32 | 33 | 34 | // protect from leaking to root namespace 35 | namespace sndlib { 36 | 37 | #pragma pack(push, 1) 38 | 39 | // playback slot (aka channel) control data 40 | struct ymf_playSlotControlData { 41 | uint32_t format; 42 | uint32_t loopDefault; 43 | void* pgBase; // dword aligned, start/loop/loopEnd offsets are relative from the base 44 | uint32_t pgLoop; 45 | uint32_t pgLoopEnd; 46 | uint32_t pgLoopFrac; 47 | uint32_t pgDeltaEnd; 48 | uint32_t lpfKEnd; 49 | uint32_t egGainEnd; 50 | uint32_t leftGainEnd; 51 | uint32_t rightGainEnd; 52 | uint32_t fx1GainEnd; 53 | uint32_t fx2GainEnd; 54 | uint32_t fx3GainEnd; 55 | uint32_t lpfQ; 56 | uint32_t status; 57 | uint32_t numOfFrames; 58 | uint32_t loopCount; 59 | uint32_t pgStart; 60 | uint32_t pgStartFrac; 61 | uint32_t pgDelta; 62 | uint32_t lpfK; 63 | uint32_t egGain; 64 | uint32_t leftGain; 65 | uint32_t rightGain; 66 | uint32_t fx1Gain; 67 | uint32_t fx2Gain; 68 | uint32_t fx3Gain; 69 | uint32_t lpfD1; 70 | uint32_t lpfD2; 71 | }; 72 | 73 | // effect slot control data 74 | struct ymf_fxSlotControlData { 75 | uint32_t pgBase; // dword aligned, start/loop/loopEnd offsets are relative from the base 76 | uint32_t pgLoopEnd; 77 | uint32_t pgStart; 78 | uint32_t temp; 79 | }; 80 | 81 | struct ymf_playControlDataTable { 82 | uint32_t numOfPlay; 83 | ymf_playSlotControlData *slotBase[64]; 84 | }; 85 | 86 | // PCI configuration space registers 87 | enum { 88 | YMF_PCI_REG_MMIO_BASE = 0x0010, // BAR0 89 | YMF_PCI_REG_LEGACY_SB_BASE = 0x0014, // BAR1 90 | YMF_PCI_REG_LEGACY_JOY_BASE = 0x0018, // BAR2 91 | YMF_PCI_REG_LEGACY_AUDIO_CTRL = 0x0040, 92 | YMF_PCI_REG_LEGACY_AUDIO_EXT_CTRL= 0x0042, 93 | YMF_PCI_REG_SUBSYSTEM_ID_WRITE = 0x0044, 94 | YMF_PCI_REG_DS1_CTRL = 0x0048, 95 | YMF_PCI_REG_DS1_POWER_CTRL_1 = 0x004A, 96 | YMF_PCI_REG_DS1_POWER_CTRL_2 = 0x004C, 97 | YMF_PCI_REG_DDMA_CTRL = 0x004E, 98 | YMF_PCI_REG_POWER_MANAGEMENT = 0x0054, 99 | YMF_PCI_REG_ACPI_MODE = 0x0058, 100 | YMF_PCI_REG_AC97_SEC_POWER_CTRL = 0x005A, 101 | }; 102 | 103 | // MMIO registers 104 | enum { 105 | YMF_REG_REVISION = 0x0000, 106 | YMF_REG_INTERRUPT_FLAG = 0x0004, 107 | YMF_REG_ACTIVITY = 0x0006, 108 | YMF_REG_GLOBAL_CONTROL = 0x0008, 109 | YMF_REG_ZOOMEDVIDEO_CONTROL = 0x000A, 110 | YMF_REG_TIMER_CONTROL = 0x0010, 111 | YMF_REG_TIMER_COUNT = 0x0012, 112 | YMF_REG_SPDIF_OUT_CONTROL = 0x0018, 113 | YMF_REG_SPDIF_OUT_CHAN_CONTROL = 0x001C, 114 | YMF_REG_EEPROM_CONTROL = 0x002C, 115 | YMF_REG_SPDIF_IN_CONTROL = 0x0034, 116 | YMF_REG_SPDIF_OUT_CHAN_STATUS = 0x0038, 117 | YMF_REG_GPIO_INT_FLAG = 0x0050, 118 | YMF_REG_GPIO_INT_ENABLE = 0x0052, 119 | YMF_REG_GPIO_IN_STATUS = 0x0054, 120 | YMF_REG_GPIO_OUT_CONTROL = 0x0056, 121 | YMF_REG_GPIO_FUNCTION = 0x0058, 122 | YMF_REG_GPIO_IN_CONFIG = 0x005A, 123 | 124 | YMF_REG_AC97_CMD_DATA = 0x0060, 125 | YMF_REG_AC97_CMD_ADDRESS = 0x0062, 126 | YMF_REG_AC97_STATUS0_DATA = 0x0064, 127 | YMF_REG_AC97_STATUS0_ADDRESS = 0x0066, 128 | YMF_REG_AC97_STATUS1_DATA = 0x0068, 129 | YMF_REG_AC97_STATUS1_ADDRESS = 0x006A, 130 | YMF_REG_AC97_SECONDARY_CONFIG = 0x0070, 131 | 132 | // loword is left, hiword is right 133 | // [db] = 20 * log([volume]/16384), 14 bit, 0 - mute 134 | YMF_REG_VOL_LEGACY_OUT = 0x0080, 135 | YMF_REG_VOL_AUDIO_DAC_OUT = 0x0084, 136 | YMF_REG_VOL_ZV_OUT = 0x0088, // fuck putin! 137 | YMF_REG_VOL_AC97_SECONDARY_OUT = 0x008C, 138 | YMF_REG_VOL_ADC_OUT = 0x0090, 139 | YMF_REG_VOL_LEGACY_LOOPBACK = 0x0094, 140 | YMF_REG_VOL_AUDIO_DAC_LOOPBACK = 0x0098, 141 | YMF_REG_VOL_ZV_LOOPBACK = 0x009C, 142 | YMF_REG_VOL_AC97_SECONDARY_LOOPBACK = 0x00A0, 143 | YMF_REG_VOL_ADC_LOOPBACK = 0x00A4, 144 | YMF_REG_VOL_ADC_INPUT = 0x00A8, 145 | YMF_REG_VOL_REC_INPUT = 0x00AC, 146 | YMF_REG_VOL_P44_OUT = 0x00B0, 147 | YMF_REG_VOL_P44_LOOPBACK = 0x00B4, 148 | YMF_REG_VOL_SPDIF_IN_OUT = 0x00B8, 149 | YMF_REG_VOL_SPDIF_IN_LOOPBACK = 0x00BC, 150 | 151 | YMF_REG_ADC_SAMPLE_RATE = 0x00C0, 152 | YMF_REG_REC_SAMPLE_RATE = 0x00C4, 153 | YMF_REG_ADC_FORMAT = 0x00C8, 154 | YMF_REG_REC_FORMAT = 0x00CC, 155 | 156 | YMF_REG_AUDIO_STATUS = 0x0100, 157 | YMF_REG_AUDIO_CONTROLSELECT = 0x0104, 158 | YMF_REG_AUDIO_MODE = 0x0108, 159 | YMF_REG_AUDIO_SAMPLE_COUNT = 0x010C, 160 | YMF_REG_AUDIO_NUM_OF_SAMPLE = 0x0110, 161 | YMF_REG_AUDIO_CONFIG = 0x0114, 162 | 163 | // size of each bank in bytes 164 | YMF_REG_AUDIO_PLAY_CONTROL_SIZE = 0x0140, 165 | YMF_REG_AUDIO_REC_CONTROL_SIZE = 0x0144, 166 | YMF_REG_AUDIO_FX_CONTROL_SIZE = 0x0148, 167 | YMF_REG_AUDIO_WORK_SIZE = 0x014C, // used for p44 slots 168 | YMF_REG_AUDIO_MAP_OF_REC = 0x0150, 169 | YMF_REG_AUDIO_MAP_OF_FX = 0x0154, 170 | 171 | // physical pointer to struct, dword aligned 172 | YMF_REG_AUDIO_PLAY_CONTROL_BASE = 0x0158, 173 | YMF_REG_AUDIO_REC_CONTROL_BASE = 0x015C, 174 | YMF_REG_AUDIO_FX_CONTROL_BASE = 0x0160, 175 | YMF_REG_AUDIO_WORK_CONTROL_BASE = 0x0164, // used for p44 slots 176 | 177 | // DSP instruction RAM 178 | YMF_REG_DSP_INSTRUCTION_RAM = 0x1000, 179 | YMF_REG_CTRL_INSTRUCTION_RAM = 0x4000, 180 | }; 181 | 182 | void ds1_dspDisable(void *ds1regs); 183 | void ds1_dspEnable(void *ds1regs); 184 | void ds1_dspReset(void *ds1regs, bool enable = true); 185 | void ds1_dspUpload(void *ds1regs, uint8_t* dspCode, uint32_t dspCodeLength, uint8_t* ctrlCode, uint32_t ctrlCodeLength); 186 | 187 | // memory allocation structure: 188 | // for sound playback, 3 memory ppols are allocated: 189 | // - audio DMA block (classic double-buffer for sound data) 190 | // - play slot control data, 2 banks 191 | // - rest of DS1 buffers - play control data table, fx work buffer, p44slot work buffer 192 | 193 | struct ymf_memoryAllocInfo { 194 | // buffer size info 195 | uint32_t playCtrlSize; 196 | uint32_t recCtrlSize; 197 | uint32_t fxCtrlSize; 198 | uint32_t p44WorkSize; 199 | 200 | // DMA block for control arrays 201 | dmaBlock workBlk; 202 | 203 | // work buffer pointers 204 | uint8_t* p44WorkBuf; 205 | ymf_fxSlotControlData** fxCtrl; 206 | ymf_playControlDataTable* playCtrl; 207 | 208 | // DMA block for play slot data arrays 209 | dmaBlock playSlotBlk; 210 | 211 | // play slot control data for every active channel 212 | uint32_t playChannels; 213 | ymf_playSlotControlData** playPtrBuf; 214 | ymf_playSlotControlData** playSlot[2]; // for each bank 215 | 216 | // DMA block for effects control slots 217 | dmaBlock fxSlotBlk; 218 | ymf_playSlotControlData** fxPtrBuf; 219 | ymf_fxSlotControlData* fxSlot[2][5]; 220 | }; 221 | 222 | bool ds1_allocBuffers(void *ds1regs, ymf_memoryAllocInfo *alloc, uint32_t playChannels); 223 | bool ds1_freeBuffers (void *ds1regs, ymf_memoryAllocInfo *alloc); 224 | void ds1_setBuffers(void *ds1regs, ymf_memoryAllocInfo *alloc); 225 | void ds1_dspDisable(void *ds1regs); 226 | void ds1_dspEnable(void *ds1regs); 227 | void ds1_dspReset(void *ds1regs, bool enable); 228 | void ds1_dspUpload(void *ds1regs, uint8_t* dspCode, uint32_t dspCodeLength, uint8_t* ctrlCode, uint32_t ctrlCodeLength); 229 | 230 | #pragma pack(pop) 231 | 232 | } 233 | -------------------------------------------------------------------------------- /examples/imaplay/README.md: -------------------------------------------------------------------------------- 1 | # sndlib->ima adpcm wave player 2 | 3 | plays IMA ADPCM-compressed .wav files. designed for 486 and early Pentiums. 4 | 5 | currently in beta, plays mono and stereo files fine, seems to be stable enough. use at own risk :) Note that .wav file is loaded in memory at once, trackloading is not added yet. 6 | 7 | ~0.4% mono / 0.8% stereo CPU load on Pentium 100. my 486 motherboard is broken yet so I can't test on those machines. 8 | 9 | TODO: more documentation, see source/makefile for now. basically you need to add mp2play folder to include path and link with `imaplay.lib`. 10 | 11 | 12 | 13 | compile with `RASTER=1` for enabling debug callback rasterbars, and `USE_C_DECODERS=1` to enable debug C decoders instead of assembly. 14 | 15 | 16 | 17 | additional thanks to baze and other [Sizecoding Discord](https://discord.gg/pZE5rAQrHx) folks for help with decoder code optimization :) 18 | 19 | --wbcbz7 17.o8.2o22 -------------------------------------------------------------------------------- /examples/imaplay/decode.asm: -------------------------------------------------------------------------------- 1 | ; imaplay - asm decoding routines 2 | ; wbcbz7 18.o8.2o22 3 | 4 | segment .text 5 | align 16 6 | use32 7 | 8 | ; generated in player, 4kb each (could be optimized) 9 | ; low 8 bits - stepIndex << 1, high 4 bits - sample code 10 | extern _imaplay_nextstep_table 11 | extern _imaplay_diff_table 12 | 13 | %if USE_C_DECODERS != 1 14 | global __imaplay_decode_mono 15 | global __imaplay_decode_stereo 16 | %endif 17 | 18 | ; edi - out, esi - in, ecx - blockSize in samples, first sample aka predictor is extracted from header 19 | 20 | ;uint32_t imaplay_decode_mono(int16_t * out, uint8_t * in, uint32_t blockSize) 21 | __imaplay_decode_mono: 22 | pusha 23 | 24 | ; get first sample 25 | mov ax, [esi] 26 | mov [edi], ax 27 | 28 | ; get step index 29 | xor edx, edx 30 | mov dl, [esi + 2] 31 | add esi, 4 32 | 33 | ; shift step index 34 | add edx, edx 35 | add edi, 2 36 | 37 | .loop: 38 | ; get first 8 samples 39 | mov ebx, [esi] 40 | 41 | %assign smp 0 42 | %rep 8 43 | ; sample loop 44 | mov dh, bl 45 | shr ebx, 4 46 | and dh, 0xF 47 | add ax, word [_imaplay_diff_table + edx] 48 | jno .skip_overflow_%+ smp 49 | ; greeting to baze :) 50 | sbb ax, ax 51 | xor ax, 0x7FFF 52 | .skip_overflow_%+ smp: 53 | mov [edi + smp*2], ax 54 | mov dl, byte [_imaplay_nextstep_table + edx] 55 | %assign smp smp + 1 56 | %endrep 57 | 58 | add esi, 4 59 | add edi, 8*2 60 | 61 | sub ecx, 8 62 | jnz .loop 63 | 64 | popa 65 | ret 66 | 67 | __imaplay_decode_stereo: 68 | pusha 69 | push ecx 70 | 71 | ; eax - sample storage (4 samples per channel) 72 | ; ebx - lookup index (note we have to save index somewhere) 73 | ; ecx - (sample counter << 16) | index storage 74 | ; edx/ebp - predictors 75 | ; esi/edi - src/dst 76 | 77 | xor ebx, ebx 78 | xor edx, edx 79 | xor ebp, ebp 80 | 81 | ; get first sample 82 | mov dx, [esi] 83 | mov cl, [esi + 2] 84 | mov bp, [esi + 4] 85 | mov ch, [esi + 6] 86 | add cx, cx ; step_index in 0..88 range 87 | 88 | ; store first sample 89 | mov [edi], dx 90 | mov [edi + 2], bp 91 | 92 | add esi, 8 93 | add edi, 4 94 | 95 | .loop: 96 | mov eax, [esi] 97 | mov bl, cl 98 | 99 | ; do left channel (BL = CL - left index) 100 | %assign smp 0 101 | %rep 8 102 | mov bh, al 103 | and bh, 0xF 104 | add dx, word [_imaplay_diff_table + ebx] 105 | jno .skip_overflow_left_%+ smp 106 | ; greetings to baze :) 107 | sbb dx, dx 108 | xor dx, 0x7FFF 109 | .skip_overflow_left_%+ smp: 110 | shr eax, 4 111 | mov [edi + smp*4], dx 112 | mov bl, byte [_imaplay_nextstep_table + ebx] 113 | %assign smp smp + 1 114 | %endrep 115 | ; switch to right channel (BL = CH - right index) 116 | mov cl, bl 117 | mov bl, ch 118 | mov eax, [esi + 4] 119 | %assign smp 0 120 | %rep 8 121 | mov bh, al 122 | and bh, 0xF 123 | add bp, word [_imaplay_diff_table + ebx] 124 | jno .skip_overflow_right_%+ smp 125 | ; greetings to baze :) 126 | sbb bp, bp 127 | xor bp, 0x7FFF 128 | .skip_overflow_right_%+ smp: 129 | shr eax, 4 130 | mov [edi + smp*4 + 2], bp 131 | mov bl, byte [_imaplay_nextstep_table + ebx] 132 | %assign smp smp + 1 133 | %endrep 134 | ; save channels 135 | mov ch, bl 136 | add esi, 8 137 | 138 | add edi, 32 139 | 140 | sub dword [esp], 8 141 | jnz .loop 142 | pop eax 143 | 144 | popa 145 | ret 146 | -------------------------------------------------------------------------------- /examples/imaplay/decode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | extern int16_t imaplay_nextstep_table[16][128]; 5 | extern int16_t imaplay_diff_table[16][128]; 6 | 7 | // decode one IMA ADPCM block 8 | uint32_t imaplay_decode_mono(int16_t * out, uint8_t * in, uint32_t samples); 9 | uint32_t imaplay_decode_stereo(int16_t * out, uint8_t * in, uint32_t samples); 10 | 11 | #pragma aux imaplay_decode_mono "__*" parm caller [edi] [esi] [ecx] value [eax] modify [eax ebx ecx edx esi edi] 12 | #pragma aux imaplay_decode_stereo "__*" parm caller [edi] [esi] [ecx] value [eax] modify [eax ebx ecx edx esi edi] 13 | } 14 | -------------------------------------------------------------------------------- /examples/imaplay/imados.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "imaplay.h" 7 | #include "wavehdr.h" 8 | 9 | class imaplay_dos : public imaplay { 10 | 11 | public: 12 | imaplay_dos(); 13 | virtual ~imaplay_dos(); 14 | 15 | // init player 16 | // sets up [decodedPool] decoded frames ring buffer 17 | // bufferSamples sets primary (aka DMA) buffer length 18 | // autosetup = false for manual sound device selection 19 | virtual bool init(uint32_t decodedPool, uint32_t bufferSamples, uint32_t resampleMode, bool autosetup = true); 20 | 21 | // load mp2 from file/memory, predecode samples to ring buffer 22 | virtual bool load(const char *filename, uint32_t filebufsize = -1); 23 | virtual bool loadmem(void * ptr, uint32_t size); 24 | 25 | // play (pos in samples) 26 | virtual bool play(uint64_t pos = -1); 27 | 28 | // render frames (0 - fill entire ringbuffer) 29 | virtual bool decode(uint32_t frames); 30 | 31 | // pause 32 | virtual bool pause(); 33 | 34 | // resume 35 | virtual bool resume(); 36 | 37 | // get position in samples 38 | virtual int64_t getPos(); 39 | 40 | // get sample rate 41 | virtual uint32_t getSampleRate() { 42 | return fmtHeader->nSamplesPerSec; 43 | } 44 | 45 | // stop 46 | virtual bool stop(); 47 | 48 | // done 49 | virtual bool done(); 50 | 51 | private: 52 | // init tables 53 | bool calculateTables(); 54 | 55 | // allocate buffers 56 | bool allocateBuffers(); 57 | 58 | // wave file pointers 59 | fmt_ima_Header* fmtHeader; 60 | 61 | uint8_t *wavBuffer; 62 | uint8_t *wavBase; // start of audio data 63 | uint8_t *wavPos; // current wave pos 64 | uint32_t wavSize; // total size in bytes 65 | uint32_t wavTotalBlocks; 66 | uint32_t wavSamplesPerBlock; 67 | 68 | // temp buffer for storing decoded samples before format conversion 69 | int16_t *imaDecodedTempBuffer; 70 | 71 | // ring buffer stuff 72 | #pragma pack(push, 16) 73 | struct poolEntry { 74 | poolEntry* next; 75 | uint32_t length; // in BYTES 76 | void* samples; 77 | bool consumed; 78 | }; 79 | #pragma pack(pop) 80 | poolEntry *poolEntries; 81 | uint8_t *pool; // contains decoded samples 82 | 83 | struct PoolState { 84 | uint32_t poolSize; 85 | uint32_t poolAvail; 86 | uint32_t poolWatermark; // if (poolAvail <= poolWatermark) && (!isModifying), render blocks in callback 87 | uint32_t poolBytesPerEntry; 88 | 89 | struct { 90 | uint32_t index; 91 | uint32_t cursor; 92 | } first, last; 93 | 94 | bool isrFinished; 95 | bool modifyLock; 96 | } state; 97 | 98 | // more states! 99 | bool isInitialized; 100 | bool isPlaying; 101 | bool isPaused; 102 | uint32_t resampleMode; 103 | uint32_t dmaBufferSamples; 104 | 105 | // sample position 106 | uint64_t bufferStart; 107 | 108 | // sndlib stuff 109 | SoundDevice *dev; 110 | soundFormatConverterInfo convinfo; 111 | 112 | // callback 113 | static soundDeviceCallbackResult callbackBouncer(void* userPtr, void* buffer, uint32_t bufferSamples, soundFormatConverterInfo *fmt, uint64_t bufferPos); 114 | soundDeviceCallbackResult callback(void* buffer, uint32_t bufferSamples, soundFormatConverterInfo *fmt, uint64_t bufferPos); 115 | 116 | }; 117 | -------------------------------------------------------------------------------- /examples/imaplay/imaplay.cpp: -------------------------------------------------------------------------------- 1 | #include "imaplay.h" 2 | 3 | bool imaplay::init(uint32_t decodedPool, uint32_t bufferSamples, uint32_t resampleMode, bool autosetup) {return false;} 4 | 5 | bool imaplay::load(const char *filename, uint32_t) {return false;} 6 | bool imaplay::loadmem(void * ptr, uint32_t size) {return false;} 7 | 8 | bool imaplay::play(uint64_t pos) {return false;} 9 | 10 | bool imaplay::decode(uint32_t frames) {return false;} 11 | 12 | bool imaplay::pause() {return false;} 13 | 14 | bool imaplay::resume() {return false;} 15 | 16 | int64_t imaplay::getPos() {return 0;} 17 | 18 | uint32_t imaplay::getSampleRate() {return 0;} 19 | 20 | bool imaplay::stop() {return false;} 21 | 22 | bool imaplay::done() {return false;} -------------------------------------------------------------------------------- /examples/imaplay/imaplay.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class imaplay { 6 | 7 | public: 8 | imaplay() {}; 9 | virtual ~imaplay() {}; 10 | 11 | enum { 12 | IMA_PLAY_DOWNSAMPLE_2TO1 = (1 << 0), // i.e 44k -> 22k 13 | IMA_PLAY_UPSAMPLE_1TO2 = (1 << 1), // i.e 22k -> 44k 14 | IMA_PLAY_RESAMPLE = (1 << 2) 15 | }; 16 | 17 | // init player 18 | // sets up [decodedPool] decoded frames ring buffer 19 | // bufferSamples sets primary (aka DMA) buffer length 20 | // autosetup = false for manual sound device selection 21 | virtual bool init(uint32_t decodedPool, uint32_t bufferSamples, uint32_t resampleMode, bool autosetup = true); 22 | 23 | // load mp2 from file/memory, predecode samples to ring buffer 24 | virtual bool load(const char *filename, uint32_t filebufsize = -1); 25 | virtual bool loadmem(void * ptr, uint32_t size); 26 | 27 | // play (pos in samples) 28 | virtual bool play(uint64_t pos = -1); 29 | 30 | // render frames (0 - fill entire ringbuffer) 31 | virtual bool decode(uint32_t frames); 32 | 33 | // pause 34 | virtual bool pause(); 35 | 36 | // resume 37 | virtual bool resume(); 38 | 39 | // get position in samples 40 | virtual int64_t getPos(); 41 | 42 | // get sample rate 43 | virtual uint32_t getSampleRate(); 44 | 45 | // stop 46 | virtual bool stop(); 47 | 48 | // done 49 | virtual bool done(); 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /examples/imaplay/makefile: -------------------------------------------------------------------------------- 1 | TARGET = imaplay 2 | 3 | BUILD = dos 4 | 5 | # define as dos4g for DOS4GW, pmode for PMODE/W build 6 | SYSTEM = dos32a 7 | DEBUG = all 8 | 9 | # set debug information level 10 | DLEVEL = 0 11 | 12 | # set to 1 to enable callback debug rasterbars 13 | RASTER = 0 14 | 15 | # set to 1 to enable high-level IMA ADPCM decoder (debug only) 16 | USE_C_DECODERS = 0 17 | 18 | # path to sndlib 19 | SNDLIB_PATH = ../.. 20 | 21 | INCLUDE = 22 | 23 | O = obj 24 | C = cpp 25 | A = asm 26 | 27 | AS = nasm.exe 28 | CC = wpp386.exe 29 | LD = wlink.exe 30 | CFLAGS = -5r -zp16 -oneatx -s -d$(DLEVEL) -bt=$(BUILD) -I=$(SNDLIB_PATH);$(INCLUDE) -DDEBUG_RASTER=$(RASTER) -DUSE_C_DECODERS=$(USE_C_DECODERS) 31 | LFLAGS = 32 | AFLAGS = -f win32 -dUSE_C_DECODERS=$(USE_C_DECODERS) 33 | 34 | OBJS = imaplay.obj imados.obj decode.obj 35 | OBJSTR = file {$(OBJS)} 36 | 37 | TESTOBJ = test.obj 38 | TESTOBJSTR = file {$(TESTOBJ)} 39 | 40 | LIBS = imaplay.lib $(SNDLIB_PATH)\sndlib.lib 41 | LIBSTR = library {$(LIBS)} 42 | 43 | all: $(TARGET).lib $(TARGET).exe .symbolic 44 | 45 | $(TARGET).lib : $(OBJS) .symbolic 46 | %create $(TARGET).ls 47 | for %i in ($(OBJS)) do @%append $(TARGET).ls +%i 48 | 49 | wlib -n $(TARGET).lib 50 | wlib $(TARGET).lib @$(TARGET).ls 51 | del $(TARGET).ls 52 | 53 | $(TARGET).exe : $(TESTOBJ) .symbolic 54 | %write $(TARGET).lnk debug $(DEBUG) 55 | %write $(TARGET).lnk name $(TARGET) 56 | %write $(TARGET).lnk option map=$(TARGET).map 57 | %write $(TARGET).lnk option eliminate 58 | %write $(TARGET).lnk $(TESTOBJSTR) 59 | %write $(TARGET).lnk $(LIBSTR) 60 | %write $(TARGET).lnk system $(SYSTEM) 61 | $(LD) @$(TARGET).lnk $(LFLAGS) 62 | del $(TARGET).lnk 63 | 64 | .cpp.obj: 65 | $(CC) $< $(CFLAGS) 66 | 67 | .asm.obj: 68 | $(AS) $< $(AFLAGS) 69 | 70 | # clean all 71 | clean: .symbolic 72 | del *.$(O) 73 | del $(TARGET).exe 74 | del *.err -------------------------------------------------------------------------------- /examples/imaplay/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "imados.h" 6 | 7 | int main(int argc, char* argv[]) { 8 | imaplay_dos player; 9 | 10 | // parse options 11 | if (argc < 2) { 12 | printf("usage: imaplay.exe [filename.wav] [autosetup]\n"); 13 | return 1; 14 | } 15 | 16 | bool downmix = false, mono = false, force8bit = false, autosetup = false; 17 | 18 | for (size_t i = 2; i < argc; i++) { 19 | char a = toupper(*argv[i]); 20 | if (a == 'A') autosetup = true; 21 | if (a == 'D') downmix = true; 22 | if (a == 'M') mono = true; 23 | if (a == '8') force8bit = true; 24 | } 25 | 26 | // set 64 frames in ring buffer (ca. 1.5 seconds of pre-render), 1024 samples DMA buffer 27 | if (!player.init(64, 1024, 0, autosetup)) return 1; 28 | 29 | // load wav file 30 | if (!player.load(argv[1])) return 1; 31 | 32 | // start playback 33 | if (!player.play()) return 1; 34 | 35 | uint64_t pos = player.getPos(); 36 | bool isPause = false; 37 | printf("space to pause/resume, esc to exit\n"); 38 | 39 | // loop 40 | while (1) { 41 | 42 | if (kbhit()) { 43 | char a = getch(); 44 | if (a == ' ') { 45 | isPause = !isPause; 46 | if (isPause) player.pause(); else player.resume(); 47 | } 48 | else if (a == 0x1B) break; 49 | } 50 | 51 | // get current position in samples, convert to mm:ss:ms 52 | uint64_t pos = player.getPos(); 53 | uint64_t posInMilliseconds = (pos * 1000 / player.getSampleRate()); 54 | uint32_t minutes = (posInMilliseconds / (1000 * 60)); 55 | uint32_t seconds = (posInMilliseconds / 1000) % 60; 56 | uint32_t ms = (posInMilliseconds % 1000); 57 | 58 | printf("\rplaypos = %8llu samples, %02d:%02d.%03d", player.getPos(), minutes, seconds, ms); fflush(stdout); 59 | while (inp(0x3da) & 8); while (!(inp(0x3da) & 8)); 60 | 61 | // uncomment this to optionally decode wav in main thread 62 | //player.decode(0); // how many frames per call you have to decode, 0 - until ring buffer full 63 | }; 64 | 65 | // cleanup 66 | player.stop(); 67 | player.done(); 68 | 69 | return 0; 70 | } -------------------------------------------------------------------------------- /examples/imaplay/wavehdr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #pragma pack(push, 1) 5 | 6 | // RIFF header 7 | struct RIFF_Header { 8 | char id[4]; // "RIFF" 9 | uint32_t size; 10 | char fourcc[4]; // "WAVE" 11 | }; 12 | 13 | struct chunk_Header { 14 | char id[4]; 15 | uint32_t size; 16 | }; 17 | 18 | // wave format header 19 | struct fmt_Header { 20 | char id[4]; // "fmt " 21 | uint32_t size; // size of chunk! 22 | 23 | uint16_t wFormatTag; // Format code 24 | uint16_t nChannels; // Number of interleaved channels 25 | uint32_t nSamplesPerSec; // Sampling rate (blocks per second) 26 | uint32_t nAvgBytesPerSec; // Data rate 27 | uint16_t nBlockAlign; // Data block size (bytes) 28 | uint16_t wBitsPerSample; // Bits per sample 29 | 30 | // enough :) 31 | }; 32 | 33 | struct fmt_ima_Header { 34 | char id[4]; // "fmt " 35 | uint32_t size; // size of chunk! 36 | 37 | uint16_t wFormatTag; // Format code 38 | uint16_t nChannels; // Number of interleaved channels 39 | uint32_t nSamplesPerSec; // Sampling rate (blocks per second) 40 | uint32_t nAvgBytesPerSec; // Data rate 41 | uint16_t nBlockAlign; // Data block size (bytes) 42 | uint16_t wBitsPerSample; // Bits per sample 43 | 44 | uint16_t cbSize; // size of additional data 45 | uint16_t wSamplesPerBlock; // sample count per each ADPCM block 46 | }; 47 | 48 | // data format header 49 | typedef chunk_Header data_Header; 50 | 51 | struct ima_block_Header { 52 | int16_t firstSample; 53 | uint8_t stepIndex; 54 | uint8_t dummy; 55 | }; 56 | 57 | #pragma pack (pop) -------------------------------------------------------------------------------- /examples/mp2play/README.md: -------------------------------------------------------------------------------- 1 | # sndlib->mp2 player 2 | 3 | **NOTE:** it's MPEG 1 Audio Layer II (aka **MP2**) player, it WON'T play any **MP3** files! 4 | 5 | TODO: more documentation, see source/makefile for now. basically you need to add both mp2play and mp2dec folders to include path and link with both ` mp2play.lib` and `mp2dec\mp2dec.lib` 6 | 7 | needs FPU, mostly Pentium optimized, not optimized for AMD/Cyrix, think twice before trying it on 486 :) 8 | 9 | 3.6% mono / 6.7% stereo CPU load on Pentium 200, 1.8% mono / 3% stereo on Celeron 300A 10 | 11 | 12 | 13 | FDCT/windowing based on [amp](http://www.mp3-tech.org/programmer/sources/amp-0_7_6.tgz) sources ((C) tomislav uzelac 1996,1997), mp2 stream decoder and getbits stuff by me. 14 | 15 | 16 | 17 | TODO: license. basically all files except `mp2dec\transfrm.c` and `mp2dec\mp2dec_a.asm` are licensed under following terms of MIT license: 18 | 19 | ``` 20 | Copyright (c) 2022 Artem Vasilev - wbcbz7 21 | 22 | Permission is hereby granted, free of charge, to any person obtaining a copy 23 | of this software and associated documentation files (the "Software"), to deal 24 | in the Software without restriction, including without limitation the rights 25 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 26 | copies of the Software, and to permit persons to whom the Software is 27 | furnished to do so, subject to the following conditions: 28 | 29 | The above copyright notice and this permission notice shall be included in all 30 | copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 37 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 38 | SOFTWARE. 39 | ``` 40 | 41 | 42 | 43 | `mp2dec\transfrm.c` and `mp2dec\mp2dec_a.asm` are derived from amp player and additionally licensed under following terms: 44 | 45 | ``` 46 | This software can be used freely for any purpose. It can be distributed 47 | freely, as long as it is not sold commercially without permission from 48 | Tomislav Uzelac . However, including this software 49 | on CD_ROMs containing other free software is explicitly permitted even 50 | when a modest distribution fee is charged for the CD, as long as this 51 | software is not a primary selling argument for the CD. 52 | 53 | Building derived versions of this software is permitted, as long as they 54 | are not sold commercially without permission from Tomislav Uzelac 55 | . Any derived versions must be clearly marked as 56 | such, and must be called by a name other than amp. Any derived versions 57 | must retain this copyright notice. 58 | 59 | /* This license is itself copied from Tatu Ylonen's ssh package. It does 60 | * not mention being copyrighted itself :) 61 | */ 62 | 63 | THERE IS NO WARRANTY FOR THIS PROGRAM - whatsoever. You use it entirely 64 | at your risk, and neither Tomislav Uzelac, nor FER will be liable for 65 | any damages that might occur to your computer, software, etc. in 66 | consequence of you using this freeware program. 67 | ``` 68 | 69 | 70 | 71 | --wbcbz7 16.o8.2o22 -------------------------------------------------------------------------------- /examples/mp2play/fpstate.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | struct fpuState { 7 | uint32_t controlWord; 8 | uint32_t statusWord; 9 | uint32_t tagWord; 10 | uint32_t ip; 11 | uint32_t cs; 12 | uint32_t ptrOfs; 13 | uint32_t ptrSel; 14 | uint32_t fpuStack[20]; // 80 bits per extended float, 8 registers -> 80 bytes 15 | }; 16 | -------------------------------------------------------------------------------- /examples/mp2play/makefile: -------------------------------------------------------------------------------- 1 | TARGET = mp2play 2 | 3 | BUILD = dos 4 | 5 | # define as dos4g for DOS4GW, pmode for PMODE/W build 6 | SYSTEM = dos32a 7 | DEBUG = all 8 | 9 | # set debug information level 10 | DLEVEL = 0 11 | 12 | # set to 1 to enable callback debug rasterbars 13 | RASTER = 0 14 | 15 | # path to sndlib and MP2 decode library, respectively 16 | MP2DEC_PATH = mp2dec 17 | SNDLIB_PATH = ../.. 18 | 19 | INCLUDE = 20 | 21 | O = obj 22 | C = cpp 23 | A = asm 24 | 25 | AS = nasm.exe 26 | CC = wpp386.exe 27 | LD = wlink.exe 28 | CFLAGS = -5r -zp16 -oneatx -s -d$(DLEVEL) -bt=$(BUILD) -I=$(MP2DEC_PATH);$(SNDLIB_PATH) -DDEBUG_RASTER=$(RASTER) 29 | LFLAGS = 30 | AFLAGS = -f win32 31 | 32 | OBJS = mp2play.obj mp2dos.obj 33 | OBJSTR = file {$(OBJS)} 34 | 35 | TESTOBJ = test.obj 36 | TESTOBJSTR = file {$(TESTOBJ)} 37 | 38 | LIBS = mp2play.lib $(MP2DEC_PATH)\mp2dec.lib $(SNDLIB_PATH)\sndlib.lib 39 | LIBSTR = library {$(LIBS)} 40 | 41 | all: $(TARGET).lib $(TARGET).exe .symbolic 42 | 43 | $(TARGET).lib : $(OBJS) .symbolic 44 | %create $(TARGET).ls 45 | for %i in ($(OBJS)) do @%append $(TARGET).ls +%i 46 | 47 | wlib -n $(TARGET).lib 48 | wlib $(TARGET).lib @$(TARGET).ls 49 | del $(TARGET).ls 50 | 51 | $(TARGET).exe : $(TESTOBJ) .symbolic 52 | %write $(TARGET).lnk debug $(DEBUG) 53 | %write $(TARGET).lnk name $(TARGET) 54 | %write $(TARGET).lnk option map=$(TARGET).map 55 | %write $(TARGET).lnk option eliminate 56 | %write $(TARGET).lnk $(TESTOBJSTR) 57 | %write $(TARGET).lnk $(LIBSTR) 58 | %write $(TARGET).lnk system $(SYSTEM) 59 | $(LD) @$(TARGET).lnk $(LFLAGS) 60 | del $(TARGET).lnk 61 | 62 | .cpp.obj: 63 | $(CC) $< $(CFLAGS) 64 | 65 | .asm.obj: 66 | $(AS) $< $(AFLAGS) 67 | 68 | # clean all 69 | clean: .symbolic 70 | del *.$(O) 71 | del $(TARGET).exe 72 | del *.err -------------------------------------------------------------------------------- /examples/mp2play/mp2dec/basedec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class BaseDecoder { 5 | public: 6 | // constructor/destructor 7 | BaseDecoder() {}; 8 | virtual ~BaseDecoder() {}; 9 | 10 | // init 11 | virtual bool init() { return false; }; 12 | 13 | // decode one frame 14 | virtual unsigned long decode(uint8_t * frame, int16_t * out) { return 0; } 15 | }; 16 | -------------------------------------------------------------------------------- /examples/mp2play/mp2dec/getbits.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "getbits.h" 3 | 4 | // init 5 | BitReader& BitReader::init(uint8_t *buffer) { 6 | 7 | // set buffer 8 | bufferPos = buffer; 9 | 10 | // fill window with new bits 11 | window = (*bufferPos << 24) | (*(bufferPos + 1) << 16) | (*(bufferPos + 2) << 8) | (*(bufferPos + 3) << 0); 12 | bufferPos += 4; 13 | bitsInWindow = 32; 14 | 15 | return *this; 16 | } 17 | 18 | #if 0 19 | // get count bits 20 | uint32_t BitReader::getbits(uint32_t count) { 21 | if (count == 0) return 0; 22 | 23 | // extract bits from window 24 | uint32_t result = (window >> (bitsInWindow - count)) & ((1 << count) - 1); 25 | bitsInWindow -= count; 26 | 27 | // fill window with new bits 28 | while (bitsInWindow <= 24) { 29 | window = (window << 8) | *bufferPos++; 30 | bitsInWindow += 8; 31 | } 32 | 33 | return result; 34 | } 35 | 36 | // get count bits, do not refill 37 | uint32_t BitReader::popbits(uint32_t count) { 38 | if (count == 0) return 0; 39 | 40 | // extract bits from window 41 | uint32_t result = (window >> (bitsInWindow - count)) & ((1 << count) - 1); 42 | bitsInWindow -= count; 43 | 44 | return result; 45 | } 46 | 47 | // refill window (gurantees at least 24 bits available) 48 | void BitReader::refill() { 49 | // fill window with new bits 50 | while (bitsInWindow <= 24) { 51 | window = (window << 8) | *bufferPos++; 52 | bitsInWindow += 8; 53 | } 54 | } 55 | 56 | // deadcode... 57 | /* 58 | void initbits(getbits_data* data, uint8_t* buffer) { 59 | // set buffer 60 | data->bufferPos = buffer; 61 | 62 | // fill window with new bits 63 | data->window = (*data->bufferPos << 24) | (*(data->bufferPos + 1) << 16) | (*(data->bufferPos + 2) << 8) | (*(data->bufferPos + 3) << 0); 64 | data->bufferPos += 4; 65 | data->bitsInWindow = 32; 66 | }; 67 | */ 68 | /* 69 | uint32_t getbits(getbits_data* data, uint32_t count) { 70 | static uint32_t countmask[] = { 71 | 0x00000000, 0x00000001, 0x00000003, 0x00000007, 72 | 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F, 73 | 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, 74 | 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, 75 | 0x0000FFFF, 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 76 | 0x000FFFFF, 0x001FFFFF, 0x003FFFFF, 0x007FFFFF, 77 | 0x00FFFFFF, 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 78 | 0x0FFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF 79 | }; 80 | 81 | uint32_t rtn; 82 | _asm { 83 | mov eax, [count] 84 | mov ebx, [data] 85 | 86 | and eax, eax 87 | jz quit 88 | 89 | mov ecx, [ebx].bitsInWindow // ecx = bitsInWindow 90 | mov esi, [ebx].bufferPos // esi = bufferPos 91 | 92 | sub ecx, eax // ecx = bitsInWindow - count 93 | mov edx, edi // eax - saved window 94 | 95 | shr edi, cl // edi = window >> (bitsInWindow - count) 96 | 97 | and edi, [countmask + 4*eax] // edi = window >> (bitsInWindow - count) & ((1 << count) - 1); 98 | mov eax, [ebx].window // edi - window 99 | 100 | cmp ecx, 24 101 | ja quit 102 | 103 | refill: 104 | shl edx, 8 105 | inc edi 106 | 107 | mov dl, [edi - 1] 108 | add ecx, 8 109 | 110 | cmp ecx, 24 111 | jle refill 112 | 113 | mov [ebx].window, edx 114 | 115 | quit: 116 | mov [rtn], edi 117 | }; 118 | 119 | return rtn; 120 | } 121 | */ 122 | 123 | #endif -------------------------------------------------------------------------------- /examples/mp2play/mp2dec/getbits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | // bit stream reader 5 | class BitReader { 6 | protected: 7 | // buffer position 8 | uint8_t *bufferPos; 9 | 10 | // window buffer 11 | uint32_t window; 12 | 13 | // bits in window 14 | uint32_t bitsInWindow; 15 | 16 | public: 17 | 18 | BitReader() : bufferPos(NULL), window(0), bitsInWindow(0) {} 19 | ~BitReader() {}; 20 | 21 | // init constructor 22 | BitReader(uint8_t *buffer) { 23 | init(buffer); 24 | } 25 | 26 | // init 27 | BitReader& init(uint8_t *buffer); 28 | 29 | // get count bits 30 | uint32_t getbits(uint32_t count) { 31 | if (count == 0) return 0; 32 | 33 | // extract bits from window 34 | uint32_t result = (window >> (bitsInWindow - count)) & ((1 << count) - 1); 35 | bitsInWindow -= count; 36 | 37 | // fill window with new bits 38 | while (bitsInWindow <= 24) { 39 | window = (window << 8) | *bufferPos++; 40 | bitsInWindow += 8; 41 | } 42 | 43 | return result; 44 | } 45 | 46 | // get count pits, do not refill 47 | uint32_t popbits(uint32_t count) { 48 | if (count == 0) return 0; 49 | 50 | // extract bits from window 51 | uint32_t result = (window >> (bitsInWindow - count)) & ((1 << count) - 1); 52 | bitsInWindow -= count; 53 | 54 | return result; 55 | } 56 | 57 | // refill window (gurantees at least 24 bits available) 58 | void refill() { 59 | // fill window with new bits 60 | while (bitsInWindow <= 24) { 61 | window = (window << 8) | *bufferPos++; 62 | bitsInWindow += 8; 63 | } 64 | } 65 | }; -------------------------------------------------------------------------------- /examples/mp2play/mp2dec/makefile: -------------------------------------------------------------------------------- 1 | TARGET = mp2dec 2 | 3 | BUILD = dos 4 | 5 | SYSTEM = dos 6 | DEBUG = dwarf all 7 | DLEVEL = 0 8 | 9 | SYSDEF = WIN32 10 | 11 | INCLUDE = .\include 12 | 13 | O = obj 14 | C = cpp 15 | A = asm 16 | 17 | AS = nasm.exe 18 | CC = wpp386.exe 19 | LD = wlink.exe 20 | CFLAGS = -5r -fp5 -fpi87 -zp16 -oneatx -ol+ -s -d$(DLEVEL) -d_$(SYSDEF) -i=$(INCLUDE) -bt=$(BUILD) 21 | LFLAGS = 22 | AFLAGS = -f win32 23 | 24 | # add object files here 25 | OBJS = mp2dec.obj rdsmp.obj getbits.obj mp2dec_a.obj transfrm.obj 26 | 27 | OBJLIST = $(OBJS) 28 | OBJSTR = file {$(OBJLIST)} 29 | 30 | all: $(TARGET).lib .symbolic 31 | 32 | $(TARGET).lib : $(OBJS) .symbolic 33 | %create $(TARGET).ls 34 | for %i in ($(OBJS)) do @%append $(TARGET).ls +%i 35 | 36 | wlib -n $(TARGET).lib 37 | wlib $(TARGET).lib @$(TARGET).ls 38 | del $(TARGET).ls 39 | 40 | .c.obj: 41 | $(CC) $< $(CFLAGS) 42 | 43 | .cpp.obj: 44 | $(CC) $< $(CFLAGS) 45 | 46 | .asm.obj: 47 | $(AS) $< $(AFLAGS) 48 | -------------------------------------------------------------------------------- /examples/mp2play/mp2dec/mp2dec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "getbits.h" 7 | #include "basedec.h" 8 | #include "transfrm.h" 9 | 10 | class MP2Decoder : public BaseDecoder { 11 | 12 | public: 13 | 14 | // constructor/destructor 15 | MP2Decoder(); 16 | virtual ~MP2Decoder(); 17 | 18 | // init 19 | virtual bool init(); 20 | 21 | // MPEG header struct 22 | struct MPEGHeader { 23 | unsigned long sync; 24 | unsigned long versionId; 25 | unsigned long layerId; 26 | unsigned long crcPresent; 27 | 28 | unsigned long bitrate; 29 | unsigned long sampleRate; 30 | unsigned long padding; 31 | unsigned long privateBit; 32 | 33 | unsigned long stereoMode; 34 | unsigned long modeExtension; 35 | unsigned long copyright; 36 | unsigned long originalCopy; 37 | unsigned long emphasis; 38 | }; 39 | 40 | // mode enums 41 | enum { 42 | modeStereo = 0, modeJointStereo, modeDualChannel, modeMono, 43 | 44 | }; 45 | 46 | // decode one frame (ASSUMES you're passing valid buffer), returns frame length in bytes, tags are skipped, 0 if end of stream 47 | // note - can return either empty buffer or zero length for invalid bitrate/samplerate frames 48 | virtual unsigned long decode(uint8_t * frame, int16_t * out, bool mono = false, bool downmix2to1 = false) { 49 | 50 | unsigned long rtn = decodeBitstream(frame, out); 51 | decodeSound(out, mono, downmix2to1); 52 | 53 | return rtn; 54 | }; 55 | 56 | // decoe header and get frame info/length, returns frame length in bytes, tags are skipped, 0 if end of stream 57 | virtual unsigned long decodeHeader(uint8_t * frame, MPEGHeader & out); 58 | 59 | // decode frame bitstream only and store inside decoder data 60 | virtual unsigned long decodeBitstream(uint8_t * frame, int16_t * out); 61 | 62 | // decode sound data using decodeBitstream() data; pass (1152 * (mono ? 1 : 2)) / (downmix2to1 ? 2 : 1) int16_t's buffer 63 | virtual unsigned long decodeSound(int16_t * out, bool mono = false, bool downmix2to1 = false); 64 | 65 | private: 66 | 67 | // optimized sample readers 68 | // read samples, mono 69 | void readSamplesMono(BitReader& bits, uint32_t sbLimit); 70 | void readSamplesStereo(BitReader& bits, uint32_t sbLimit); 71 | void readSamplesJointStereo(BitReader& bits, uint32_t sbLimit, uint32_t bound); 72 | 73 | protected: 74 | 75 | // bit reader 76 | BitReader bits; 77 | 78 | // scale factor table (scalefactor[idx] = pow(2.0, 1 - (idx / 3.0))) 79 | static float scalefactor[64]; 80 | 81 | // grouped samples lookup 82 | static const size_t groupedLookupIdx[]; 83 | static uint16_t groupedLookup[3*3*3 + 5*5*5 + 9*9*9]; // packed as ((s1 << 8) | (s2 << 4) | s3) 84 | 85 | // ---------------------------------------------- 86 | // bit rates table 87 | static unsigned long bitRates[16]; 88 | 89 | // sample rates table 90 | static unsigned long sampleRates[4]; 91 | 92 | // valid format class table isNotMono samplerate bitrate 93 | static unsigned long formatTable[2] [4] [16]; 94 | 95 | // Layer II subband classes index table (unused subbands are zero)) 96 | struct formatClass { 97 | unsigned long subbands; 98 | unsigned long subbandClass[32]; 99 | }; 100 | 101 | static formatClass formatClasses[5]; 102 | 103 | // Layer II subband classes 104 | struct subbandClass { 105 | unsigned long bitAllocationLength; 106 | unsigned long quantizationClassIdx[16]; 107 | }; 108 | 109 | static subbandClass subbandClasses[7]; 110 | 111 | // Layer II quantization classes 112 | struct quantizationClass { 113 | unsigned long steps; 114 | bool grouping; 115 | unsigned long bitsPerCodeword; 116 | 117 | // calculated in constructor 118 | float normScale; 119 | signed long normBias; 120 | }; 121 | 122 | static quantizationClass quantizationClasses[18]; 123 | 124 | // scalefactor index lookup (faster than div by 12) 125 | static const size_t scalefactorLookup[]; 126 | 127 | // -------------------------------------- 128 | // subband stuff 129 | struct subband { 130 | quantizationClass *quantization; 131 | subbandClass *sbClass; 132 | union { 133 | unsigned long scalefactor[3]; // for each part of 12 samples 134 | float postscale[3]; 135 | }; 136 | unsigned long scfsi; 137 | }; 138 | 139 | // current format class index 140 | uint32_t currentFormatClass; 141 | uint32_t currentChannels; 142 | 143 | // channel data struct 144 | float samples[2][36][32]; 145 | subband subbands[32 * 2]; // separate 146 | 147 | // polydata 148 | char *alignbuf; 149 | mp2dec_poly_private_data *polydata; 150 | 151 | }; 152 | -------------------------------------------------------------------------------- /examples/mp2play/mp2dec/rdsmp.cpp: -------------------------------------------------------------------------------- 1 | // hack to keep $$%#%&#@Q watcom from inlining funcitons when it's absolutely not desired 2 | #include 3 | #include 4 | #include 5 | 6 | #include "mp2dec.h" 7 | #include "getbits.h" 8 | 9 | void MP2Decoder::readSamplesMono(BitReader& oldbits, uint32_t sbLimit) { 10 | BitReader bits = oldbits; 11 | 12 | // clear sample storage, only left channel! 13 | memset(&samples[0][0][0], 0, sizeof(float) * 32 * 36); 14 | 15 | // temp assigned variables 16 | MP2Decoder::subband* bandptr; 17 | MP2Decoder::quantizationClass* bandclass; 18 | uint32_t gr = 0, sb; int32_t rawsamples[3]; 19 | float *os = &samples[0][0][0]; 20 | do { 21 | bandptr = subbands; 22 | sb = sbLimit; 23 | do { 24 | bandclass = bandptr->quantization; 25 | if (bandclass->steps != 0) { 26 | if (bandclass->grouping) { 27 | // read grouped code 28 | unsigned long code = bits.getbits(bandclass->bitsPerCodeword); 29 | 30 | // get packed samples from table 31 | unsigned long samples = groupedLookup[groupedLookupIdx[(bandclass->steps - 3)] + code]; 32 | rawsamples[0] = (samples) & 0xF; samples >>= 4; 33 | rawsamples[1] = (samples) & 0xF; samples >>= 4; 34 | rawsamples[2] = (samples) & 0xF; 35 | } 36 | else { 37 | // ungrouped 38 | rawsamples[0] = bits.getbits(bandclass->bitsPerCodeword); 39 | rawsamples[1] = bits.getbits(bandclass->bitsPerCodeword); 40 | rawsamples[2] = bits.getbits(bandclass->bitsPerCodeword); 41 | } 42 | 43 | // renormalize 44 | signed long bias = bandclass->normBias; 45 | 46 | *(float*)((uint8_t*)os + 0*36*32*4 + 0*32*4) = (bias - rawsamples[0]) * bandptr[0].postscale[gr >> 2]; 47 | *(float*)((uint8_t*)os + 0*36*32*4 + 1*32*4) = (bias - rawsamples[1]) * bandptr[0].postscale[gr >> 2]; 48 | *(float*)((uint8_t*)os + 0*36*32*4 + 2*32*4) = (bias - rawsamples[2]) * bandptr[0].postscale[gr >> 2]; 49 | } 50 | bandptr++; os++; 51 | } while (--sb); 52 | os += 2*32 + 32 - sbLimit; 53 | } while (++gr != 12); 54 | 55 | } 56 | 57 | void MP2Decoder::readSamplesStereo(BitReader& oldbits, uint32_t sbLimit) { 58 | BitReader bits = oldbits; 59 | 60 | // clear sample storage 61 | memset(&samples[0][0][0], 0, sizeof(float) * 32 * 36 * 2); 62 | 63 | // temp assigned variables 64 | MP2Decoder::subband* bandptr; 65 | MP2Decoder::quantizationClass* bandclass; 66 | uint32_t gr = 0, sb; int32_t rawsamples[3]; uint32_t sbLimit2 = (sbLimit << 1); 67 | float *os = &samples[0][0][0]; 68 | uint32_t osofs = 0; // channel offset in sample buffer 69 | 70 | do { 71 | bandptr = subbands; 72 | sb = sbLimit2; 73 | do { 74 | // left channel 75 | bandclass = bandptr->quantization; 76 | if (bandclass->steps != 0) { 77 | if (bandclass->grouping) { 78 | // read grouped code 79 | unsigned long code = bits.getbits(bandclass->bitsPerCodeword); 80 | 81 | // get packed samples from table 82 | unsigned long samples = groupedLookup[groupedLookupIdx[(bandclass->steps - 3)] + code]; 83 | rawsamples[0] = (samples) & 0xF; samples >>= 4; 84 | rawsamples[1] = (samples) & 0xF; samples >>= 4; 85 | rawsamples[2] = (samples) & 0xF; 86 | } 87 | else { 88 | // ungrouped 89 | rawsamples[0] = bits.getbits(bandclass->bitsPerCodeword); 90 | rawsamples[1] = bits.getbits(bandclass->bitsPerCodeword); 91 | rawsamples[2] = bits.getbits(bandclass->bitsPerCodeword); 92 | } 93 | 94 | // renormalize 95 | signed long bias = bandclass->normBias; 96 | 97 | *(float*)((uint8_t*)os + osofs + 0*32*4) = (bias - rawsamples[0]) * bandptr->postscale[gr >> 2]; 98 | *(float*)((uint8_t*)os + osofs + 1*32*4) = (bias - rawsamples[1]) * bandptr->postscale[gr >> 2]; 99 | *(float*)((uint8_t*)os + osofs + 2*32*4) = (bias - rawsamples[2]) * bandptr->postscale[gr >> 2]; 100 | } 101 | bandptr++; osofs ^= 1*36*32*4; if (sb & 1) os++; 102 | } while (--sb); 103 | os += 2*32 + 32 - sbLimit; 104 | } while (++gr != 12); 105 | 106 | } 107 | 108 | void MP2Decoder::readSamplesJointStereo(BitReader& oldbits, uint32_t sbLimit, uint32_t bound) { 109 | BitReader bits = oldbits; 110 | 111 | // clear sample storage 112 | memset(&samples[0][0][0], 0, sizeof(float) * 32 * 36 * 2); 113 | 114 | // temp assigned variables 115 | MP2Decoder::subband* bandptr; 116 | MP2Decoder::quantizationClass* bandclass; 117 | uint32_t gr = 0, sb; int32_t rawsamples[3]; uint32_t sbLimit2 = (sbLimit << 1), bound2 = (bound << 1); 118 | float *os = &samples[0][0][0]; 119 | uint32_t osofs = 0; // channel offset in sample buffer 120 | 121 | do { 122 | // process bound 123 | bandptr = subbands; 124 | sb = bound2; 125 | do { 126 | bandclass = bandptr->quantization; 127 | if (bandclass->steps != 0) { 128 | if (bandclass->grouping) { 129 | // read grouped code 130 | unsigned long code = bits.getbits(bandclass->bitsPerCodeword); 131 | 132 | // get packed samples from table 133 | unsigned long samples = groupedLookup[groupedLookupIdx[(bandclass->steps - 3)] + code]; 134 | rawsamples[0] = (samples) & 0xF; samples >>= 4; 135 | rawsamples[1] = (samples) & 0xF; samples >>= 4; 136 | rawsamples[2] = (samples) & 0xF; 137 | } 138 | else { 139 | // ungrouped 140 | rawsamples[0] = bits.getbits(bandclass->bitsPerCodeword); 141 | rawsamples[1] = bits.getbits(bandclass->bitsPerCodeword); 142 | rawsamples[2] = bits.getbits(bandclass->bitsPerCodeword); 143 | } 144 | 145 | // renormalize 146 | signed long bias = bandclass->normBias; 147 | 148 | *(float*)((uint8_t*)os + osofs + 0*32*4) = (bias - rawsamples[0]) * bandptr->postscale[gr >> 2]; 149 | *(float*)((uint8_t*)os + osofs + 1*32*4) = (bias - rawsamples[1]) * bandptr->postscale[gr >> 2]; 150 | *(float*)((uint8_t*)os + osofs + 2*32*4) = (bias - rawsamples[2]) * bandptr->postscale[gr >> 2]; 151 | } 152 | bandptr++; osofs ^= 1*36*32*4; if (sb & 1) os++; 153 | } while (--sb); 154 | 155 | // process after bound 156 | sb = (sbLimit - bound); 157 | do { 158 | bandclass = bandptr->quantization; 159 | if (bandclass->steps != 0) { 160 | if (bandclass->grouping) { 161 | // read grouped code 162 | unsigned long code = bits.getbits(bandclass->bitsPerCodeword); 163 | 164 | // get packed samples from table 165 | unsigned long samples = groupedLookup[groupedLookupIdx[(bandclass->steps - 3)] + code]; 166 | rawsamples[0] = (samples) & 0xF; samples >>= 4; 167 | rawsamples[1] = (samples) & 0xF; samples >>= 4; 168 | rawsamples[2] = (samples) & 0xF; 169 | } 170 | else { 171 | // ungrouped 172 | rawsamples[0] = bits.getbits(bandclass->bitsPerCodeword); 173 | rawsamples[1] = bits.getbits(bandclass->bitsPerCodeword); 174 | rawsamples[2] = bits.getbits(bandclass->bitsPerCodeword); 175 | } 176 | 177 | // renormalize 178 | signed long bias = bandclass->normBias; 179 | 180 | *(float*)((uint8_t*)os + 0*36*32*4 + 0*32*4) = (bias - rawsamples[0]) * bandptr[0].postscale[gr >> 2]; 181 | *(float*)((uint8_t*)os + 0*36*32*4 + 1*32*4) = (bias - rawsamples[1]) * bandptr[0].postscale[gr >> 2]; 182 | *(float*)((uint8_t*)os + 0*36*32*4 + 2*32*4) = (bias - rawsamples[2]) * bandptr[0].postscale[gr >> 2]; 183 | *(float*)((uint8_t*)os + 1*36*32*4 + 0*32*4) = (bias - rawsamples[0]) * bandptr[1].postscale[gr >> 2]; 184 | *(float*)((uint8_t*)os + 1*36*32*4 + 1*32*4) = (bias - rawsamples[1]) * bandptr[1].postscale[gr >> 2]; 185 | *(float*)((uint8_t*)os + 1*36*32*4 + 2*32*4) = (bias - rawsamples[2]) * bandptr[1].postscale[gr >> 2]; 186 | } 187 | bandptr += 2; os++; 188 | } while (--sb); 189 | 190 | os += 2*32 + 32 - sbLimit; 191 | } while (++gr != 12); 192 | 193 | } 194 | -------------------------------------------------------------------------------- /examples/mp2play/mp2dec/transfrm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | extern float mp2dec_FDCTCoeffs[32]; 9 | extern float mp2dec_t_dewindow[17][32]; 10 | 11 | #pragma pack(push, 1) 12 | struct mp2dec_poly_private_data { 13 | float u[2][2][17][16]; /* no v[][], it's redundant */ 14 | int u_start[2]; /* first element of u[][] */ 15 | int u_div[2]; /* which part of u[][] is currently used */ 16 | }; 17 | #pragma pack(pop) 18 | 19 | // monofy samples 20 | void mp2dec_samples_monofy(float *left, float *right); 21 | // convert mono to stereo 22 | void mp2dec_samples_mono2stereo(int16_t* out); 23 | // feed FDCT pipeline 24 | #ifdef MP2DEC_TRANSFORM_C 25 | void _cdecl mp2dec_poly_fdct(mp2dec_poly_private_data *data, float *subbands, const int ch); 26 | #endif 27 | void _cdecl mp2dec_poly_fdct_p5(mp2dec_poly_private_data *data, float *subbands, const int ch); 28 | // dewindow and output samples 29 | #ifdef MP2DEC_TRANSFORM_C 30 | void _cdecl mp2dec_poly_window(mp2dec_poly_private_data *data, int16_t *samples); 31 | void _cdecl mp2dec_poly_window_mono(mp2dec_poly_private_data *data, int16_t *samples, const int ch = 0); 32 | #endif 33 | void _cdecl mp2dec_poly_window_mono_p5(mp2dec_poly_private_data *data, int16_t *samples, const int ch = 0, const int channels = 1); 34 | // advance pipeline 35 | void mp2dec_poly_next_granule(mp2dec_poly_private_data *data, const int ch); 36 | // prepare tables 37 | void mp2dec_poly_premultiply(); 38 | 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /examples/mp2play/mp2dos.cpp: -------------------------------------------------------------------------------- 1 | #include "mp2play.h" 2 | #include "mp2dos.h" 3 | 4 | #include 5 | #include "fpstate.h" 6 | 7 | 8 | unsigned long long rdtsc(); 9 | #pragma aux rdtsc = ".586" "rdtsc" value [edx eax] 10 | 11 | void fpuSave(fpuState *state); 12 | #pragma aux fpuSave = "fnsave [eax]" parm [eax] 13 | void fpuRestore(fpuState *state); 14 | #pragma aux fpuRestore = "frstor [eax]" parm [eax] 15 | 16 | #define arrayof(a) (sizeof(a) / sizeof(a[0])) 17 | 18 | soundDeviceCallbackResult mp2play_dos::callbackBouncer(void* userPtr, void* buffer, uint32_t bufferSamples, soundFormatConverterInfo *fmt, uint64_t bufferPos) 19 | { 20 | // save FPU state 21 | fpuState fpustate; 22 | fpuSave(&fpustate); 23 | 24 | // call callback 25 | soundDeviceCallbackResult rtn = ((mp2play_dos*)userPtr)->callback(buffer, bufferSamples, fmt, bufferPos); 26 | 27 | // restore FPU state 28 | fpuRestore(&fpustate); 29 | 30 | return rtn; 31 | } 32 | 33 | 34 | soundDeviceCallbackResult mp2play_dos::callback(void* buffer, uint32_t bufferSamples, soundFormatConverterInfo *fmt, uint64_t bufferPos) 35 | { 36 | /* 37 | this is pretty messy circular buffer handling, but at least it works somehow :) 38 | */ 39 | 40 | #if DEBUG_RASTER == 1 41 | outp(0x3c8, 0); outp(0x3c9, 0x1f); outp(0x3c9, 0x1f); outp(0x3c9, 0x1f); 42 | #endif 43 | 44 | uint32_t bytesToRender = bufferSamples * convinfo.bytesPerSample; 45 | uint8_t *dst = (uint8_t*)buffer; 46 | 47 | poolEntry* block = &poolEntries[state.firstIdx]; 48 | uint32_t startPos = state.firstRemainingPos; 49 | uint32_t blockRemainder = block->length - state.firstRemainingPos; // b 50 | uint32_t blockBytesToCopy = (bytesToRender < blockRemainder ? bytesToRender : blockRemainder); 51 | uint32_t blocksConsumed = 0; 52 | 53 | while (bytesToRender > 0) { 54 | 55 | // copy cur block 56 | memcpy((uint8_t*)dst, (uint8_t*)block->samples + startPos, blockBytesToCopy); 57 | dst += blockBytesToCopy; 58 | startPos += blockBytesToCopy; 59 | bytesToRender -= blockBytesToCopy; 60 | 61 | if (bytesToRender > 0) { 62 | block->consumed = true; 63 | blocksConsumed++; 64 | 65 | // jump to next block 66 | state.firstIdx++; if (state.firstIdx == state.poolSize) state.firstIdx = 0; 67 | block = &poolEntries[state.firstIdx]; 68 | startPos = 0; 69 | blockBytesToCopy = (bytesToRender < block->length ? bytesToRender : block->length); 70 | 71 | } 72 | } 73 | // save current state 74 | state.firstRemainingPos = startPos; 75 | state.firstEntry = block; 76 | state.poolAvail += blocksConsumed; 77 | 78 | // tell render to remove stuck blocks 79 | isIsrFinished = true; 80 | 81 | #if DEBUG_RASTER == 1 82 | outp(0x3c8, 0); outp(0x3c9, 0x1f); outp(0x3c9, 0x0); outp(0x3c9, 0x1f); 83 | #endif 84 | 85 | // render blocks if below buffer watermark 86 | if ((isModifying == false) && ((state.poolSize - state.poolAvail) <= state.poolWatermark)) 87 | decode(blocksConsumed); 88 | 89 | #if DEBUG_RASTER == 1 90 | outp(0x3c8, 0); outp(0x3c9, 0); outp(0x3c9, 0); outp(0x3c9, 0); 91 | #endif 92 | 93 | return callbackOk; 94 | } 95 | 96 | 97 | mp2play_dos::~mp2play_dos() 98 | { 99 | if (isPlaying) stop(); 100 | if (isInitialized) done(); 101 | } 102 | 103 | bool mp2play_dos::init(uint32_t decodedPool, uint32_t bufferSamples, bool autosetup, bool mono, bool downsample2to1) 104 | { 105 | if ((decodedPool == 0) || (bufferSamples == 0)) return false; 106 | 107 | // init sndlib 108 | if (sndlibInit() != SND_ERR_OK) return false; 109 | uint32_t rtn = SND_ERR_OK; 110 | 111 | // query, detect, create sound device 112 | rtn = sndlibCreateDevice(&dev, autosetup ? SND_CREATE_DEVICE_AUTO_DETECT : SND_CREATE_DEVICE_MANUAL_SELECT); 113 | if (rtn == SND_ERR_USEREXIT) { 114 | printf("user exit\n"); 115 | return false; 116 | } 117 | if (rtn != SND_ERR_OK) { 118 | printf("mp2play_dos error: no available sound devices found!\n"); 119 | return false; 120 | } 121 | 122 | // init device 123 | rtn = dev->init(); 124 | if (rtn != SND_ERR_OK) { 125 | printf("mp2play_dos::init() error: unable to init sound device (rtn = %d)\n", rtn); 126 | sndlibDestroyDevice(dev); 127 | return false; 128 | } 129 | 130 | printf("mp2play_dos::init(): %s init success\n", dev->getName()); 131 | 132 | // save other props 133 | isDownsampled = downsample2to1; 134 | isMono = mono; 135 | state.poolSize = decodedPool; 136 | state.bufferSamples = bufferSamples; 137 | 138 | // init decoder 139 | decoder.init(); 140 | 141 | return true; 142 | } 143 | 144 | bool mp2play_dos::load(const char * filename) 145 | { 146 | FILE *f = fopen(filename, "rb"); 147 | size_t fsize = 0; 148 | 149 | if (f) { 150 | // get filesize 151 | fseek(f, 0, SEEK_END); 152 | fsize = ftell(f); 153 | fseek(f, 0, SEEK_SET); 154 | 155 | // allocate memory 156 | mp2 = new uint8_t[fsize + 4]; // reserve extra word for end of stream 157 | if (mp2 == NULL) { 158 | printf("mp2play_dos::load() error: insufficient memory to load mp2 stream!\n"); 159 | return false; 160 | } 161 | 162 | // read file 163 | size_t rtn = fread(mp2, sizeof(uint8_t), fsize, f); 164 | if (rtn != fsize) { 165 | printf("mp2play_dos::load() error: expected %d bytes, read %d\n", fsize, rtn); 166 | return false; 167 | } 168 | // mark end of stream 169 | *(uint32_t*)(mp2 + fsize) = 0; 170 | 171 | fclose(f); 172 | } 173 | else { 174 | perror("mp2play_dos::load() error: unable to open mp2 file: "); 175 | return false; 176 | } 177 | 178 | // yeah :) 179 | mp2pos = mp2; 180 | mp2size = fsize; 181 | return allocateBuffers(); 182 | } 183 | 184 | bool mp2play_dos::loadmem(void * ptr, uint32_t size) 185 | { 186 | // doesn't check for validity 187 | mp2 = (uint8_t*)ptr; 188 | mp2size = size; 189 | return allocateBuffers(); 190 | } 191 | 192 | // load 2nd stage - open sound stream, get converter info 193 | bool mp2play_dos::allocateBuffers() 194 | { 195 | // parse header 196 | uint32_t rtn = decoder.decodeHeader(mp2pos, mp2header); 197 | 198 | if (rtn == 0) { 199 | printf("mp2play_dos::load() error: no MPEG frame found or unknown frame format\n"); 200 | return false; 201 | } 202 | 203 | // request format 204 | uint32_t samplerate = mp2header.sampleRate >> (isDownsampled ? 1 : 0); 205 | if (dev->isFormatSupported(samplerate, SND_FMT_DEPTH_MASK | SND_FMT_SIGN_MASK | (isMono ? SND_FMT_MONO : SND_FMT_STEREO), NULL) != SND_ERR_OK) { 206 | // try mono 207 | isMono = true; 208 | if (dev->isFormatSupported(samplerate, SND_FMT_DEPTH_MASK | SND_FMT_SIGN_MASK | SND_FMT_MONO, NULL) != SND_ERR_OK) { 209 | printf("mp2play_dos::load() error: unsupported sample rate or format!\n", rtn); 210 | } 211 | } 212 | uint32_t format = SND_FMT_INT16 | SND_FMT_SIGNED | (isMono ? SND_FMT_MONO : SND_FMT_STEREO); 213 | 214 | rtn = dev->open(samplerate, format, state.bufferSamples, 0, callbackBouncer, (void*)this, &convinfo); 215 | if (rtn != SND_ERR_OK) { 216 | printf("mp2play_dos::load() error: unable to open sound device (rtn = %d)\n", rtn); 217 | return false; 218 | } 219 | 220 | printf("mp2play_dos::load(): stream open success: %d->%d hz %sbit %s %s\n", 221 | convinfo.sourceSampleRate, convinfo.sampleRate, 222 | convinfo.format & SND_FMT_INT8 ? "8" : (convinfo.format & SND_FMT_INT16 ? "16" : "unk"), 223 | convinfo.format & SND_FMT_MONO ? "mono" : "stereo", 224 | convinfo.format & SND_FMT_SIGNED ? "signed" : "unsigned" 225 | ); 226 | 227 | // init decoded stream pool 228 | state.poolBytesPerEntry = 576 * (isDownsampled ? 1 : 2) * convinfo.bytesPerSample; 229 | 230 | #ifdef DEBUG 231 | printf("bytes per frame - %d\n", state.poolBytesPerEntry); 232 | #endif 233 | 234 | pool = new uint8_t[state.poolSize * state.poolBytesPerEntry]; 235 | poolEntries = new poolEntry[state.poolSize]; 236 | if (!pool || !poolEntries) { 237 | printf("mp2play_dos::load() error: not enough memory to initialize pool\n", rtn); 238 | return false; 239 | } 240 | 241 | memset(pool, 0, sizeof(state.poolSize * (state.poolBytesPerEntry))); 242 | memset(poolEntries, 0, sizeof(state.poolSize * (sizeof(poolEntry)))); 243 | 244 | // set pool head/tail 245 | state.firstCurrentPos = 0; 246 | state.firstIdx = 0; 247 | state.firstEntry = poolEntries; 248 | state.firstPos = 0; 249 | 250 | state.lastEntry = poolEntries; 251 | state.lastIdx = 0; 252 | state.lastPos = 0; 253 | 254 | state.poolAvail = state.poolSize; 255 | state.firstRemainingPos = 0; 256 | 257 | // update watermark 258 | state.poolWatermark = ((4 * state.bufferSamples * convinfo.bytesPerSample) / state.poolBytesPerEntry); 259 | 260 | isModifying = false; 261 | isInitialized = true; 262 | isIsrFinished = false; 263 | 264 | // request to fill pool 265 | decode(0); // fill until pool end 266 | 267 | return true; 268 | } 269 | 270 | bool mp2play_dos::decode(uint32_t frames) 271 | { 272 | int16_t decodeBuffer[1152 * 2]; 273 | 274 | // lock buffer tail 275 | isModifying = true; 276 | uint32_t availAtStart = state.poolAvail; 277 | 278 | // don't fill 279 | if ((frames == 0) || (frames >= availAtStart)) { 280 | frames = availAtStart - 1; // fill until end, leave 1 free "end" frame 281 | }; 282 | 283 | uint32_t currentAvail = availAtStart; 284 | uint32_t currentIdx = state.lastIdx; 285 | for (size_t block = 0; block < frames; block++) { 286 | uint32_t rtn = decoder.decode(mp2pos, &decodeBuffer[0], isMono); if (rtn == 0) return false; 287 | mp2pos += rtn; if ((mp2pos - mp2) >= mp2size) mp2pos = mp2; 288 | 289 | // totally crappy downsampler (TODO: implement half-rate mode in mp2 decoder library) 290 | if (isDownsampled == true) { 291 | if (isMono == true) { 292 | for (size_t i = 0; i < 576; i++) { 293 | decodeBuffer[i] = (decodeBuffer[2*i] + decodeBuffer[2*i]) >> 1; 294 | } 295 | } else { 296 | for (size_t i = 0; i < 576*2; i += 2) { 297 | decodeBuffer[i + 0] = (decodeBuffer[2*i + 0] + decodeBuffer[2*i + 2]) >> 1; 298 | decodeBuffer[i + 1] = (decodeBuffer[2*i + 1] + decodeBuffer[2*i + 3]) >> 1; 299 | } 300 | } 301 | } 302 | 303 | // convert 304 | uint8_t* samplePtr = (uint8_t*)pool + (state.poolBytesPerEntry * currentIdx); 305 | convinfo.proc(samplePtr, decodeBuffer, 576 * (isDownsampled ? 1 : 2), convinfo.parm, convinfo.parm2); // store preconverted samples (hi pc honker) 306 | 307 | // fill pool stuff 308 | poolEntries[currentIdx].consumed = false; 309 | poolEntries[currentIdx].length = state.poolBytesPerEntry; 310 | poolEntries[currentIdx].samples = samplePtr; 311 | poolEntries[currentIdx].next = NULL; 312 | 313 | currentIdx++; if (currentIdx == state.poolSize) currentIdx = 0; currentAvail--; state.lastPos += 1152; 314 | } 315 | 316 | // link tail 317 | state.lastIdx = currentIdx; 318 | state.lastEntry = &poolEntries[currentIdx]; 319 | 320 | if (isIsrFinished) { 321 | isIsrFinished = false; 322 | currentAvail += (state.poolAvail - availAtStart); 323 | } 324 | state.poolAvail = currentAvail; 325 | 326 | // release lock 327 | isModifying = false; 328 | 329 | return true; 330 | } 331 | 332 | bool mp2play_dos::play(uint64_t pos) 333 | { 334 | if (!isInitialized) return false; 335 | 336 | // set start position 337 | if (pos != -1) { 338 | mp2pos = mp2; 339 | for (uint64_t i = 0; i < pos; i++) { 340 | uint32_t rtn = decoder.decodeHeader(mp2pos, mp2header); 341 | if (rtn == 0) { 342 | printf("mp2play_dos::play() error: end of file reached\n"); 343 | } else mp2pos += rtn; 344 | } 345 | 346 | // flush entire ringbuffer 347 | state.firstCurrentPos = 0; 348 | state.firstIdx = 0; 349 | state.firstEntry = poolEntries; 350 | state.firstPos = 0; 351 | 352 | state.lastEntry = poolEntries; 353 | state.lastIdx = 0; 354 | state.lastPos = 0; 355 | 356 | state.poolAvail = state.poolSize; 357 | state.firstRemainingPos = 0; 358 | 359 | // refill 360 | decode(0); 361 | } 362 | 363 | // start for first time 364 | uint32_t rtn = dev->start(); 365 | if (rtn != SND_ERR_OK) { 366 | printf("mp2play_dos::play() error: unable to start stream (rtn = %d)\n", rtn); 367 | return false; 368 | } 369 | 370 | isPlaying = true; 371 | bufferPlay = 0; 372 | 373 | return true; 374 | } 375 | 376 | bool mp2play_dos::pause() 377 | { 378 | return (dev->pause() != SND_ERR_OK); 379 | } 380 | 381 | bool mp2play_dos::resume() 382 | { 383 | return (dev->resume() != SND_ERR_OK); 384 | } 385 | 386 | int64_t mp2play_dos::getPos() 387 | { 388 | uint64_t pos = dev->getPos() << (isDownsampled ? 1 : 0); 389 | bufferPlay = pos; 390 | return pos; 391 | } 392 | 393 | bool mp2play_dos::stop() 394 | { 395 | // start for first time 396 | uint32_t rtn = dev->stop(); 397 | if (rtn != SND_ERR_OK) { 398 | printf("mp2play_dos::stop() error: unable to stop stream (rtn = %d)\n", rtn); 399 | return false; 400 | } 401 | 402 | isPlaying = false; 403 | 404 | return true; 405 | } 406 | 407 | bool mp2play_dos::done() 408 | { 409 | if (isPlaying) stop(); 410 | 411 | dev->done(); 412 | 413 | // destroy device 414 | sndlibDestroyDevice(dev); 415 | 416 | // destroy pool 417 | delete[] pool; 418 | delete[] poolEntries; 419 | 420 | isInitialized = false; 421 | 422 | // cleanup sndlib 423 | if (sndlibDone() != SND_ERR_OK) return false; 424 | 425 | return true; 426 | } 427 | 428 | 429 | 430 | 431 | -------------------------------------------------------------------------------- /examples/mp2play/mp2dos.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "mp2dec.h" 6 | #include "mp2play.h" 7 | #include 8 | 9 | 10 | class mp2play_dos : public mp2play { 11 | 12 | public: 13 | mp2play_dos() : isInitialized(false), isPlaying(false), isPaused(false), isDownsampled(false), isMono(false), mp2(NULL), pool(NULL), poolEntries(NULL), decoder() {} 14 | virtual ~mp2play_dos(); 15 | 16 | // init player 17 | // sets up [decodedPool] decoded frames ring buffer 18 | // bufferSamples sets primary (aka DMA) buffer length 19 | // autosetup = false for manual sound device selection 20 | virtual bool init(uint32_t decodedPool, uint32_t bufferSamples, bool autosetup = true, bool mono = false, bool downsample2to1 = false); 21 | 22 | // load mp2 from file/memory, predecode samples to ring buffer 23 | virtual bool load(const char *filename); 24 | virtual bool loadmem(void * ptr, uint32_t size); 25 | 26 | // play (pos in frames == samples*1152) 27 | virtual bool play(uint64_t pos = -1); 28 | 29 | // render frames (0 - fill entire ringbuffer) 30 | virtual bool decode(uint32_t frames); 31 | 32 | // pause 33 | virtual bool pause(); 34 | 35 | // resume 36 | virtual bool resume(); 37 | 38 | // get position (+/- 20 samples accurate, i dunno, what about RTC interpolation?) 39 | virtual int64_t getPos(); 40 | 41 | // get sample rate 42 | virtual uint32_t getSampleRate() { 43 | return mp2header.sampleRate; 44 | } 45 | 46 | // stop 47 | virtual bool stop(); 48 | 49 | // done 50 | virtual bool done(); 51 | 52 | protected: 53 | 54 | MP2Decoder decoder; 55 | MP2Decoder::MPEGHeader mp2header; 56 | 57 | uint8_t *mp2; 58 | uint8_t *mp2pos; 59 | uint32_t mp2size; 60 | void *pool; 61 | 62 | char *stack; 63 | char *stackTop; 64 | 65 | #pragma pack(push, 16) 66 | struct poolEntry { 67 | poolEntry* next; 68 | uint32_t length; // in BYTES 69 | void* samples; 70 | bool consumed; 71 | }; 72 | #pragma pack(pop) 73 | 74 | poolEntry* poolEntries; 75 | 76 | // pool 77 | struct poolState { 78 | uint32_t poolSize; 79 | uint32_t poolBytesPerEntry; 80 | uint32_t bufferSamples; 81 | 82 | // head (fed to callback) 83 | poolEntry* firstEntry; // ptr 84 | uint32_t firstIdx; 85 | uint32_t firstIdxIsr; // сохраняется коллбеком 86 | uint64_t firstPos; // frame pos 87 | uint32_t firstRemainingPos; // семпол с которого следующий колбек нарежет звук когда его попросят 88 | uint64_t firstCurrentPos; 89 | 90 | // tail (fed by render()/decode()) 91 | poolEntry* lastEntry; 92 | uint32_t lastIdx; 93 | uint64_t lastPos; 94 | 95 | uint32_t poolAvail; 96 | uint32_t poolWatermark; // если (poolAvail <= poolWatermark) && (!isModifying), то не дожидаясь юзера прямо из колбека нарезаем ceil(bufferSamples*2/1152 + 1) фреймов в пул 97 | }; 98 | 99 | poolState state; 100 | 101 | uint64_t bufferPlay; 102 | uint64_t bufferStart; // updated each callback, used for getPos() interpolation 103 | uint64_t bufferOldStart; 104 | uint64_t bufferDelta; // same 105 | 106 | bool isMono; 107 | bool isDownsampled; 108 | bool isInitialized; 109 | bool isPlaying; 110 | bool isPaused; 111 | 112 | volatile bool isIsrFinished; 113 | // "hey we're rendering more frames don't peek us much" flag 114 | volatile bool isModifying; 115 | 116 | // sndlib stuff 117 | SoundDevice *dev; 118 | soundFormatConverterInfo convinfo; 119 | 120 | static soundDeviceCallbackResult callbackBouncer(void* userPtr, void* buffer, uint32_t bufferSamples, soundFormatConverterInfo *fmt, uint64_t bufferPos); 121 | public: 122 | soundDeviceCallbackResult callback(void* buffer, uint32_t bufferSamples, soundFormatConverterInfo *fmt, uint64_t bufferPos); 123 | 124 | bool allocateBuffers(); 125 | 126 | // downsampler 127 | 128 | }; -------------------------------------------------------------------------------- /examples/mp2play/mp2play.cpp: -------------------------------------------------------------------------------- 1 | #include "mp2play.h" 2 | 3 | bool mp2play::init(uint32_t decodedPool, uint32_t bufferSamples, bool autosetup, bool mono, bool downsample2to1) {return false;} 4 | 5 | bool mp2play::load(const char *filename) {return false;} 6 | bool mp2play::loadmem(void * ptr, uint32_t size) {return false;} 7 | 8 | bool mp2play::play(uint64_t pos) {return false;} 9 | 10 | bool mp2play::decode(uint32_t frames) {return false;} 11 | 12 | bool mp2play::pause() {return false;} 13 | bool mp2play::setPos(double pos) {return true;} 14 | 15 | bool mp2play::resume() {return false;} 16 | 17 | int64_t mp2play::getPos() {return 0;} 18 | 19 | uint32_t mp2play::getSampleRate() {return 0;} 20 | 21 | bool mp2play::stop() {return false;} 22 | 23 | bool mp2play::done() {return false;} -------------------------------------------------------------------------------- /examples/mp2play/mp2play.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class mp2play { 6 | 7 | public: 8 | mp2play() {}; 9 | virtual ~mp2play() {}; 10 | 11 | // init player 12 | // sets up [decodedPool] decoded frames ring buffer 13 | // bufferSamples sets primary (aka DMA) buffer length 14 | // autosetup = false for manual sound device selection 15 | virtual bool init(uint32_t decodedPool, uint32_t bufferSamples, bool autosetup = true, bool mono = false, bool downsample2to1 = false); 16 | 17 | // load mp2 from file/memory, predecode samples to ring buffer 18 | virtual bool load(const char *filename); 19 | virtual bool loadmem(void * ptr, uint32_t size); 20 | 21 | // play (pos in frames == samples*1152) 22 | virtual bool play(uint64_t pos = -1); 23 | 24 | // render frames (0 - fill entire ringbuffer) 25 | virtual bool decode(uint32_t frames); 26 | 27 | // pause 28 | virtual bool pause(); 29 | 30 | // resume 31 | virtual bool resume(); 32 | 33 | // get position (+/- 20 samples accurate, i dunno, what about RTC interpolation?) 34 | virtual int64_t getPos(); 35 | virtual bool setPos(double pos); 36 | 37 | // get sample rate 38 | virtual uint32_t getSampleRate(); 39 | 40 | // stop 41 | virtual bool stop(); 42 | 43 | // done 44 | virtual bool done(); 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /examples/mp2play/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "mp2dos.h" 6 | 7 | int main(int argc, char* argv[]) { 8 | mp2play_dos player; 9 | 10 | // parse options 11 | if (argc < 2) { 12 | printf("usage: mp2play.exe [filename.wav] [downmix] [mono] [8bit] [autosetup]\n"); 13 | return 1; 14 | } 15 | 16 | bool downmix = false, mono = false, force8bit = false, autosetup = false; 17 | 18 | for (size_t i = 2; i < argc; i++) { 19 | char a = toupper(*argv[i]); 20 | if (a == 'A') autosetup = true; 21 | if (a == 'D') downmix = true; 22 | if (a == 'M') mono = true; 23 | if (a == '8') force8bit = true; 24 | } 25 | 26 | // set 64 frames in ring buffer (ca. 1.5 seconds of pre-render), 1024s sample DMA buffer 27 | if (!player.init(64, 1024, autosetup, mono, downmix)) return 1; 28 | 29 | // load mp2 file 30 | if (!player.load(argv[1])) return 1; 31 | 32 | // start playback 33 | if (!player.play()) return 1; 34 | 35 | int64_t pos = player.getPos(); 36 | bool isPause = false; 37 | printf("space to pause/resume, esc to exit\n"); 38 | 39 | // loop 40 | while (1) { 41 | 42 | if (kbhit()) { 43 | char a = getch(); 44 | if (a == ' ') { 45 | isPause = !isPause; 46 | if (isPause) player.pause(); else player.resume(); 47 | } 48 | else if (a == 0x1B) break; 49 | } 50 | 51 | // get current position in samples, convert to mm:ss:ms 52 | int64_t pos = player.getPos(); 53 | int64_t posInMilliseconds = (pos * 1000 / player.getSampleRate()); 54 | int32_t minutes = (posInMilliseconds / (1000 * 60)); 55 | int32_t seconds = (posInMilliseconds / 1000) % 60; 56 | int32_t ms = (posInMilliseconds % 1000); 57 | 58 | printf("\rplaypos = %8llu samples, %02d:%02d.%03d", player.getPos(), minutes, seconds, ms); fflush(stdout); 59 | while (inp(0x3da) & 8); while (!(inp(0x3da) & 8)); 60 | 61 | // uncomment this to optionally decode mp2 in main thread 62 | //player.decode(0); // how many frames per call you have to decode, 0 - until ring buffer full 63 | }; 64 | 65 | // cleanup 66 | player.stop(); 67 | player.done(); 68 | 69 | return 0; 70 | } -------------------------------------------------------------------------------- /examples/wavesine/makefile: -------------------------------------------------------------------------------- 1 | TARGET = wavesine 2 | 3 | # leave this at "dos" 4 | BUILD = dos 5 | 6 | # define as dos4g for DOS4GW, pmode for PMODE/W build 7 | SYSTEM = dos32a 8 | 9 | # set to "none" to exclude debug inforrmation from the executable 10 | DEBUG = all 11 | 12 | # set debug information level 13 | DLEVEL = 0 14 | 15 | # set to 1 to enable callback debug rasterbars 16 | RASTER = 0 17 | 18 | # path to sndlib 19 | SNDLIB_PATH = ../.. 20 | 21 | # optional include paths 22 | INCLUDE = 23 | 24 | AS = nasm.exe 25 | CC = wpp386.exe 26 | LD = wlink.exe 27 | CFLAGS = -5r -zp16 -oneatx -s -d$(DLEVEL) -bt=$(BUILD) -I=$(SNDLIB_PATH);$(INCLUDE) -DDEBUG_RASTER=$(RASTER) 28 | LFLAGS = 29 | AFLAGS = -f win32 30 | 31 | OBJS = $(TARGET).obj 32 | OBJSTR = file {$(OBJS)} 33 | 34 | LIBS = $(SNDLIB_PATH)\sndlib.lib 35 | LIBSTR = library {$(LIBS)} 36 | 37 | all: $(TARGET).exe .symbolic 38 | 39 | $(TARGET).exe : $(OBJS) .symbolic 40 | %write $(TARGET).lnk debug $(DEBUG) 41 | %write $(TARGET).lnk name $(TARGET) 42 | %write $(TARGET).lnk option map=$(TARGET).map 43 | %write $(TARGET).lnk option eliminate 44 | %write $(TARGET).lnk $(OBJSTR) 45 | %write $(TARGET).lnk $(LIBSTR) 46 | %write $(TARGET).lnk system $(SYSTEM) 47 | $(LD) @$(TARGET).lnk $(LFLAGS) 48 | del $(TARGET).lnk 49 | 50 | .cpp.obj: 51 | $(CC) $< $(CFLAGS) 52 | 53 | .asm.obj: 54 | $(AS) $< $(AFLAGS) 55 | 56 | # clean all 57 | clean: .symbolic 58 | del *.obj 59 | del $(TARGET).exe 60 | del *.err -------------------------------------------------------------------------------- /examples/wavesine/wavesine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* 8 | advanced sine generator with manual format conversion - demonstrates simple sound synthesis in a callback 9 | */ 10 | 11 | // ------------------------------ 12 | // fixedpoint math helpers 13 | 14 | #ifndef min 15 | #define min(a, b) ((a) < (b) ? (a) : (b)) 16 | #endif 17 | #ifndef max 18 | #define max(a, b) ((a) > (b) ? (a) : (b)) 19 | #endif 20 | #ifndef sgn 21 | #define sgn(a) ((a) < (0) ? (-1) : ((a) > (0) ? (1) : (0))) 22 | #endif 23 | #ifndef clamp 24 | #define clamp(a, l, h) ((a) > (h) ? (h) : ((a) < (l) ? (l) : (a))) 25 | #endif 26 | 27 | // (x * y) / z 28 | int32_t imuldiv16(int32_t x, int32_t y, int32_t z); 29 | #pragma aux imuldiv16 = \ 30 | " imul edx "\ 31 | " idiv ebx "\ 32 | parm [eax] [edx] [ebx] modify exact [eax edx] value [eax] 33 | 34 | // (x * y) >> 16 35 | int32_t imul16(int32_t x, int32_t y); 36 | #pragma aux imul16 = \ 37 | " imul edx "\ 38 | " shrd eax,edx,16 "\ 39 | parm [eax] [edx] value [eax] 40 | 41 | // (x << 16) / y 42 | int32_t idiv16(int32_t x, int32_t y); 43 | #pragma aux idiv16 = \ 44 | " mov edx,eax "\ 45 | " sar edx,16 "\ 46 | " shl eax,16 "\ 47 | " idiv ebx "\ 48 | parm [eax] [ebx] modify exact [eax edx] value [eax] 49 | 50 | // (x * y) >> 8 51 | int32_t imul8(int32_t x, int32_t y); 52 | #pragma aux imul8 = \ 53 | " imul edx "\ 54 | " shrd eax,edx,8 "\ 55 | parm [eax] [edx] value [eax] 56 | 57 | // (x << 8) / y 58 | int32_t idiv8(int32_t x, int32_t y); 59 | #pragma aux idiv8 = \ 60 | " mov edx,eax "\ 61 | " sar edx,24 "\ 62 | " shl eax,8 "\ 63 | " idiv ebx "\ 64 | parm [eax] [ebx] modify exact [eax edx] value [eax] 65 | 66 | // ------------------------------ 67 | // sine table 68 | 69 | const size_t SINTAB_SIZE = 256; 70 | 71 | int32_t sintab[SINTAB_SIZE + 1]; // interpolation fixup 72 | 73 | // iterative sine generator from https://www.musicdsp.org/en/latest/Synthesis/10-fast-sine-and-cosine-calculation.html 74 | // c = (int)(65536.0*2.0*sin(PI/SINTAB_SIZE)), realculate it yourself for other SINTAB_SIZE 75 | void makeWavetable() { 76 | #if 1 77 | int32_t a = 32767, b = 0, c = 1608; 78 | for (size_t i = 0; i < SINTAB_SIZE; i++) { 79 | sintab[i] = b; 80 | a -= imul16(b,c); 81 | b += imul16(a,c); 82 | } 83 | #else 84 | for (size_t i = 0; i < SINTAB_SIZE; i++) { 85 | sintab[i] = ((65536 * i) / SINTAB_SIZE) - 32768; 86 | } 87 | #endif 88 | sintab[SINTAB_SIZE] = sintab[0]; 89 | } 90 | 91 | // ------------------------------ 92 | // callback context structure 93 | 94 | struct callbackContext { 95 | uint32_t channels; 96 | 97 | // sine synth variables 98 | struct { 99 | int32_t vol; // 16.16 fixedpoint 100 | int32_t pos; // 24.8 fixedpoint 101 | int32_t delta; // 24.8 fixedpoint 102 | } synth[2]; 103 | 104 | // xor mask for flipping the sign 105 | uint32_t xorval; 106 | }; 107 | 108 | // ------------------------------ 109 | // callback 110 | 111 | void renderpc(void* buffer, uint32_t samples, callbackContext *ctx, soundFormatConverterInfo *info) { 112 | for (size_t j = 0; j < ctx->channels; j++) { 113 | uint8_t *p = (uint8_t*)buffer + j; 114 | for (uint32_t i = 0; i < samples; i++) { 115 | int32_t intPos = ctx->synth[j].pos >> 8, fracPos = ctx->synth[j].pos & 0xFF; 116 | int32_t a = imul16( 117 | ((sintab[intPos] * (256 - fracPos)) + (sintab[intPos + 1] * (fracPos))), 118 | ctx->synth[j].vol) >> 16; 119 | a = clamp(a, -128, 127); 120 | *p = ((uint8_t*)info->parm2)[(uint8_t)a]; p += ctx->channels; 121 | ctx->synth[j].pos = (ctx->synth[j].pos + ctx->synth[j].delta) & (((SINTAB_SIZE - 1) << 8) + 0xFF); 122 | } 123 | } 124 | } 125 | 126 | void render8(void* buffer, uint32_t samples, callbackContext *ctx) { 127 | for (size_t j = 0; j < ctx->channels; j++) { 128 | uint8_t *p = (uint8_t*)buffer + j; 129 | for (uint32_t i = 0; i < samples; i++) { 130 | int32_t intPos = ctx->synth[j].pos >> 8, fracPos = ctx->synth[j].pos & 0xFF; 131 | int32_t a = imul16( 132 | ((sintab[intPos] * (256 - fracPos)) + (sintab[intPos + 1] * (fracPos))), 133 | ctx->synth[j].vol) >> 16; 134 | a = clamp(a, -128, 127); 135 | *p = (uint16_t)(a ^ ctx->xorval); p += ctx->channels; 136 | ctx->synth[j].pos = (ctx->synth[j].pos + ctx->synth[j].delta) & (((SINTAB_SIZE - 1) << 8) + 0xFF); 137 | } 138 | } 139 | } 140 | 141 | void render16(void* buffer, uint32_t samples, callbackContext *ctx) { 142 | for (size_t j = 0; j < ctx->channels; j++) { 143 | uint16_t *p = (uint16_t*)buffer + j; 144 | for (uint32_t i = 0; i < samples; i++) { 145 | int32_t intPos = ctx->synth[j].pos >> 8, fracPos = ctx->synth[j].pos & 0xFF; 146 | int32_t a = imul16( 147 | ((sintab[intPos] * (256 - fracPos)) + (sintab[intPos + 1] * (fracPos))), 148 | ctx->synth[j].vol) >> 8; 149 | a = clamp(a, -32768, 32767); 150 | *p = (uint16_t)(a ^ ctx->xorval); p += ctx->channels; 151 | ctx->synth[j].pos = (ctx->synth[j].pos + ctx->synth[j].delta) & (((SINTAB_SIZE - 1) << 8) + 0xFF); 152 | } 153 | } 154 | } 155 | 156 | soundDeviceCallbackResult callback(void* userPtr, void* buffer, uint32_t bufferSamples, soundFormatConverterInfo *fmt, uint64_t bufferPos) { 157 | // cast userPtr to callbackContext 158 | callbackContext *ctx = (callbackContext*)userPtr; 159 | 160 | #if DEBUG_RASTER == 1 161 | outp(0x3c8, 0); outp(0x3c9, 0x1f); outp(0x3c9, 0x0); outp(0x3c9, 0x1f); 162 | #endif 163 | 164 | // call appropriate synth 165 | switch (fmt->format & SND_FMT_DEPTH_MASK) { 166 | case SND_FMT_XLAT8: renderpc(buffer, bufferSamples, ctx, fmt); break; 167 | case SND_FMT_INT8: render8 (buffer, bufferSamples, ctx); break; 168 | case SND_FMT_INT16: render16(buffer, bufferSamples, ctx); break; 169 | default: return callbackAbort; 170 | } 171 | 172 | #if DEBUG_RASTER == 1 173 | outp(0x3c8, 0); outp(0x3c9, 0); outp(0x3c9, 0); outp(0x3c9, 0); 174 | #endif 175 | 176 | // and exit callback 177 | return callbackOk; 178 | } 179 | 180 | // ------------------------------ 181 | // static data structures 182 | 183 | SoundDevice *dev = NULL; 184 | callbackContext callbackContext; 185 | soundFormatConverterInfo convinfo; 186 | 187 | // ------------------------------ 188 | // initialization 189 | 190 | bool init(bool autosetup) { 191 | uint32_t rtn = SND_ERR_OK; 192 | 193 | // init sndlib 194 | if ((rtn = sndlibInit()) != SND_ERR_OK) { 195 | printf("error: unable to init sndlib!\n"); 196 | } 197 | 198 | // query, detect, create sound device 199 | rtn = sndlibCreateDevice(&dev, autosetup ? SND_CREATE_DEVICE_AUTO_DETECT : SND_CREATE_DEVICE_MANUAL_SELECT); 200 | if (rtn == SND_ERR_USEREXIT) { 201 | printf("user exit\n"); 202 | return false; 203 | } 204 | if (rtn != SND_ERR_OK) { 205 | printf("error: no available sound devices found!\n"); 206 | return false; 207 | } 208 | 209 | // init device 210 | rtn = dev->init(); 211 | if (rtn != SND_ERR_OK) { 212 | printf("error: unable to init sound device (rtn = %d)\n", rtn); 213 | sndlibDestroyDevice(dev); 214 | return false; 215 | } 216 | 217 | printf("%s init success\n", dev->getName()); 218 | 219 | return true; 220 | } 221 | 222 | bool open(uint32_t sampleRate, uint32_t bufferSamples, bool mono, bool use8bit) { 223 | soundFormat format = (mono ? SND_FMT_MONO : SND_FMT_STEREO) | (use8bit ? SND_FMT_INT8 : SND_FMT_INT16); 224 | memset(&convinfo, 0, sizeof(convinfo)); 225 | 226 | // check if format is supported 227 | if (dev->isFormatSupported(sampleRate == -1 ? SND_ISFORMATSUPPORTED_MAXSAMPLERATE : sampleRate, format | SND_FMT_SIGN_MASK, &convinfo) != SND_ERR_OK) { 228 | // downgrade the format 229 | format = (format & ~SND_FMT_DEPTH_MASK) | SND_FMT_INT8; 230 | if (dev->isFormatSupported(sampleRate == -1 ? SND_ISFORMATSUPPORTED_MAXSAMPLERATE : sampleRate, format | SND_FMT_SIGN_MASK, &convinfo) != SND_ERR_OK) { 231 | // downgrade it more :) 232 | format = (format & ~SND_FMT_CHANNELS_MASK) | SND_FMT_MONO; 233 | if (dev->isFormatSupported(sampleRate == -1 ? SND_ISFORMATSUPPORTED_MAXSAMPLERATE : sampleRate, format | SND_FMT_SIGN_MASK, &convinfo) != SND_ERR_OK) { 234 | // downgrade it EVEN more :) for PC speaker 235 | format = (format & ~SND_FMT_DEPTH_MASK) | SND_FMT_XLAT8; 236 | if (dev->isFormatSupported(sampleRate == -1 ? SND_ISFORMATSUPPORTED_MAXSAMPLERATE : sampleRate, format | SND_FMT_SIGN_MASK, &convinfo) != SND_ERR_OK) { 237 | // oops! 238 | printf("error: unsupported sample rate or format!\n"); 239 | return false; 240 | } 241 | } 242 | } 243 | } 244 | 245 | // get sample rate 246 | if (sampleRate == -1) sampleRate = convinfo.sampleRate; 247 | 248 | // get format sign 249 | format = (format & ~SND_FMT_SIGN_MASK) | (convinfo.format & SND_FMT_SIGNED ? SND_FMT_SIGNED : SND_FMT_UNSIGNED); 250 | 251 | // finally open the device! 252 | // note SND_OPEN_NOCONVERT flag, that tells to use exact requested format 253 | uint32_t rtn = dev->open(sampleRate, format, bufferSamples, SND_OPEN_NOCONVERT, callback, &callbackContext, &convinfo); 254 | if (rtn != SND_ERR_OK) { 255 | printf("error: unable to open sound device (rtn = %d)\n", rtn); 256 | return false; 257 | } 258 | 259 | printf("stream open success: %d->%d hz %sbit %s %s\n", 260 | convinfo.sourceSampleRate, convinfo.sampleRate, 261 | convinfo.format & SND_FMT_INT8 ? "8" : (convinfo.format & SND_FMT_INT16 ? "16" : "unk"), 262 | convinfo.format & SND_FMT_MONO ? "mono" : "stereo", 263 | convinfo.format & SND_FMT_SIGNED ? "signed" : "unsigned" 264 | ); 265 | 266 | // init synth 267 | // generate 500hz tone in left/mono channel and 1000hz in right channel 268 | for (size_t i = 0; i < 2; i++) { 269 | callbackContext.synth[i].vol = 0xC000; // 0.75 270 | callbackContext.synth[i].pos = 0; 271 | callbackContext.synth[i].delta = idiv8((SINTAB_SIZE * 500 * (i + 1)), convinfo.sampleRate); 272 | } 273 | callbackContext.channels = (convinfo.format & SND_FMT_STEREO ? 2 : 1); 274 | callbackContext.xorval = (convinfo.format & SND_FMT_UNSIGNED ? 0x80 : 0) << (convinfo.format & SND_FMT_INT16 ? 8 : 0); 275 | 276 | printf("synth init complete\n"); 277 | 278 | return true; 279 | } 280 | 281 | void close() { 282 | // stop and close device 283 | dev->stop(); 284 | dev->close(); 285 | 286 | // destroy device 287 | sndlibDestroyDevice(dev); 288 | 289 | // cleanup sndlib 290 | sndlibDone(); 291 | } 292 | 293 | 294 | void showHelp() { 295 | printf("usage: sine.exe [sampleRate] [mono] [8bit] [autosetup]\n"); 296 | } 297 | 298 | int main(int argc, char* argv[]) { 299 | // init sine table 300 | makeWavetable(); 301 | 302 | // options 303 | bool downmix = false, mono = false, use8bit = false, autosetup = false; 304 | uint32_t sampleRate = -1, rtn; 305 | 306 | for (size_t i = 1; i < argc; i++) { 307 | // first try to convert to sample rate 308 | uint32_t d = strtol(argv[i], NULL, 10); if (d >= 4000) {sampleRate = d; continue;} 309 | 310 | // else parse as option 311 | char a = toupper(*argv[i]); 312 | 313 | switch(a) { 314 | case '?': showHelp(); return 0; 315 | case 'A': autosetup = true; break; 316 | case 'M': mono = true; break; 317 | case '8': use8bit = true; break; 318 | default: break; 319 | } 320 | } 321 | 322 | // init and open sound device 323 | if (init(autosetup) == false) return 1; 324 | if (open(sampleRate, 1024, mono, use8bit) == false) {close(); return 1;} 325 | 326 | // start! 327 | if ((rtn = dev->start()) != SND_ERR_OK) { 328 | printf("error: unable to start sound device (rtn = %d)\n", rtn); 329 | close(); 330 | return 1; 331 | } 332 | 333 | bool isRunnning = true, isPause = false; 334 | printf("space to pause/resume, esc to exit\n"); 335 | printf("[q/w] to adjust left channel delta, [a/s] - right, [z/x] - volume\n"); 336 | 337 | // loop 338 | while (isRunnning) { 339 | 340 | // keyboard control 341 | if (kbhit()) { 342 | char a = toupper(getch()); 343 | switch (a) { 344 | case 'Q' : callbackContext.synth[0].delta = clamp(callbackContext.synth[0].delta - 0x10, 0, 0xFFFFFF); break; 345 | case 'W' : callbackContext.synth[0].delta = clamp(callbackContext.synth[0].delta + 0x10, 0, 0xFFFFFF); break; 346 | case 'A' : callbackContext.synth[1].delta = clamp(callbackContext.synth[1].delta - 0x10, 0, 0xFFFFFF); break; 347 | case 'S' : callbackContext.synth[1].delta = clamp(callbackContext.synth[1].delta + 0x10, 0, 0xFFFFFF); break; 348 | case 'Z' : callbackContext.synth[0].vol = clamp(callbackContext.synth[0].vol - 0x100, 0, 0xFFFF); 349 | callbackContext.synth[1].vol = clamp(callbackContext.synth[1].vol - 0x100, 0, 0xFFFF); break; 350 | case 'X' : callbackContext.synth[0].vol = clamp(callbackContext.synth[0].vol + 0x100, 0, 0xFFFF); 351 | callbackContext.synth[1].vol = clamp(callbackContext.synth[1].vol + 0x100, 0, 0xFFFF); break; 352 | case ' ' : 353 | isPause = !isPause; 354 | if (isPause) dev->pause(); else dev->resume(); 355 | break; 356 | case 0x1B: isRunnning = false; break; 357 | default: break; 358 | } 359 | } 360 | 361 | // get current position in samples, convert to mm:ss:ms 362 | uint64_t pos = dev->getPos(); 363 | uint64_t posInMilliseconds = (pos * 1000 / convinfo.sampleRate); 364 | uint32_t minutes = (posInMilliseconds / (1000 * 60)); 365 | uint32_t seconds = (posInMilliseconds / 1000) % 60; 366 | uint32_t ms = (posInMilliseconds % 1000); 367 | 368 | printf("\rplaypos = %8llu samples, %02d:%02d.%03d, synth delta: %04X/%04X, volume %04X/%04X", 369 | pos, minutes, seconds, ms, 370 | callbackContext.synth[0].delta, callbackContext.synth[1].delta, 371 | callbackContext.synth[0].vol, callbackContext.synth[1].vol); fflush(stdout); 372 | 373 | // wait for vertical retrace 374 | while (inp(0x3da) & 8); while (!(inp(0x3da) & 8)); 375 | 376 | }; 377 | 378 | // done :) cleanup 379 | close(); 380 | 381 | return 0; 382 | } -------------------------------------------------------------------------------- /irq.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "irq.h" 6 | #include "dpmi.h" 7 | #include "cli.h" 8 | #include "logerror.h" 9 | 10 | static irqInfo irqInfoTable[] = { 11 | { 0x08, 0x20, 0x01, 0 }, // 0 12 | { 0x09, 0x20, 0x02, 0 }, // 1 13 | { 0x0A, 0x20, 0x04, 0 }, // 2 14 | { 0x0B, 0x20, 0x08, 0 }, // 3 15 | { 0x0C, 0x20, 0x10, 0 }, // 4 16 | { 0x0D, 0x20, 0x20, 0 }, // 5 17 | { 0x0E, 0x20, 0x40, 0 }, // 6 18 | { 0x0F, 0x20, 0x80, IRQ_SPURIOUS }, // 7 19 | 20 | { 0x70, 0xA0, 0x01, IRQ_SECONDARYPIC }, // 8 21 | { 0x71, 0xA0, 0x02, IRQ_SECONDARYPIC }, // 9 22 | { 0x72, 0xA0, 0x04, IRQ_SECONDARYPIC }, // 10 23 | { 0x73, 0xA0, 0x08, IRQ_SECONDARYPIC }, // 11 24 | { 0x74, 0xA0, 0x10, IRQ_SECONDARYPIC }, // 12 25 | { 0x75, 0xA0, 0x20, IRQ_SECONDARYPIC }, // 13 26 | { 0x76, 0xA0, 0x40, IRQ_SECONDARYPIC }, // 14 27 | { 0x77, 0xA0, 0x80, IRQ_SECONDARYPIC | IRQ_SPURIOUS } // 15 28 | }; 29 | 30 | // install ISR for given interrupt 31 | // unmask == true - unmask IRQ channel after setup 32 | bool irqHook(unsigned long num, irqEntry *p, bool unmask) { 33 | if (num > 15) { 34 | logerr ("unable to hook IRQ %d (IRQ must be from 0 to 15)\n", num); return true;} 35 | if (p->handler == NULL) { 36 | logfatal("null irqEntry->handler pointer!\n"); return true;} 37 | if (p->hooked != 0) { 38 | logerr ("IRQ is already hooked\n"); return true; 39 | } 40 | 41 | // disable interrupts and setup handler 42 | //unsigned long flags = pushf(); 43 | _disable(); 44 | 45 | // hook real-mode handler if requested 46 | if ((p->flags & IRQENTRY_HOOK_REALMODE) == IRQENTRY_HOOK_REALMODE) { 47 | p->oldrmhandler = dpmi_getrmvect_ex(irqInfoTable[num].intnum); 48 | dpmi_setrmvect_ex(irqInfoTable[num].intnum, p->rmhandler); 49 | p->hooked = 1; 50 | } 51 | 52 | // hook protected mode handler 53 | if (p->handler != NULL) { 54 | p->oldhandler = _dos_getvect(irqInfoTable[num].intnum); 55 | _dos_setvect(irqInfoTable[num].intnum, p->handler); 56 | p->hooked = 1; 57 | } 58 | 59 | p->num = num; 60 | p->info = &irqInfoTable[num]; 61 | p->oldmask = inp(irqInfoTable[num].picbase + 1) & irqInfoTable[num].mask; 62 | 63 | // unmask if requested 64 | if (unmask) { 65 | outp(irqInfoTable[num].picbase + 1, (inp(irqInfoTable[num].picbase + 1) & ~irqInfoTable[num].mask)); 66 | } 67 | _enable(); 68 | //_enable_if_enabled(flags); 69 | 70 | p->hooked = 1; 71 | 72 | return false; 73 | } 74 | 75 | bool irqUnhook(irqEntry *p, bool mask) { 76 | if (p == NULL) { 77 | logfatal("null irqEntry pointer!\n"); return true;} 78 | if (p->num > 15) { 79 | logfatal("unable to unhook IRQ %d (IRQ must be from 0 to 15)\n", p->num); return true;} 80 | if (p->hooked == 0) { 81 | logerr ("IRQ is not hooked\n"); return true; 82 | } 83 | 84 | // disable interrupts and setup handler 85 | //unsigned long flags = pushf(); 86 | _disable(); 87 | 88 | if ((p->flags & IRQENTRY_HOOK_REALMODE) == IRQENTRY_HOOK_REALMODE) 89 | dpmi_setrmvect_ex(irqInfoTable[p->num].intnum, p->oldrmhandler); 90 | if (p->oldhandler != NULL) _dos_setvect(irqInfoTable[p->num].intnum, p->oldhandler); 91 | 92 | if (mask) { 93 | outp(irqInfoTable[p->num].picbase, 0x40); // nop 94 | outp(irqInfoTable[p->num].picbase + 1, ((inp(irqInfoTable[p->num].picbase + 1) & ~irqInfoTable[p->num].mask) | p->oldmask)); 95 | } 96 | _enable(); 97 | //_enable_if_enabled(flags); 98 | 99 | p->hooked = 0; 100 | 101 | return false; 102 | } 103 | 104 | 105 | -------------------------------------------------------------------------------- /irq.h: -------------------------------------------------------------------------------- 1 | #ifndef __IRQ_H__ 2 | #define __IRQ_H__ 3 | 4 | #if (defined(_DOS) || defined(__DOS__)) 5 | 6 | #include 7 | #include 8 | #include 9 | #include "dpmi.h" 10 | 11 | #define irqcall __interrupt __far 12 | 13 | enum { 14 | IRQ_SECONDARYPIC = 1 << 0, 15 | 16 | IRQ_SPURIOUS = 1 << 2, // irq 7 and 15, sanity check 17 | }; 18 | 19 | enum { 20 | IRQENTRY_HOOK_REALMODE = 1 << 0, // set if realmode ISR 21 | }; 22 | 23 | struct irqInfo { 24 | unsigned char intnum; 25 | unsigned char picbase; 26 | unsigned char mask; 27 | unsigned char flags; 28 | }; 29 | 30 | struct irqEntry { 31 | uint8_t hooked; 32 | uint8_t num; 33 | uint8_t flags; 34 | uint8_t oldmask; 35 | irqInfo *info; 36 | 37 | // real-mode old mode handler 38 | _dpmi_rmpointer rmhandler; 39 | _dpmi_rmpointer oldrmhandler; 40 | 41 | // protected-mode 42 | void (irqcall *oldhandler)(); 43 | void (irqcall *handler)(); 44 | }; 45 | 46 | // protected mode hook 47 | bool irqInit(); 48 | bool irqHook(unsigned long num, irqEntry *p, bool unmask); 49 | bool irqUnhook(irqEntry *p, bool mask); 50 | 51 | #else 52 | 53 | #error "irq stuff won't work on non-DOS environments" 54 | 55 | #endif 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /logerror.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "logerror.h" 6 | 7 | FILE *logfile = NULL; 8 | 9 | char *logerr_header = ">> ERROR [%s:%d]: ", *logfatal_header = ">> FATAL [%s:%d]: ", *logdebug_header = ">> DEBUG [%s:%d]: "; 10 | 11 | // somewhere from stack overflow :] 12 | static void format_time(FILE *f) { 13 | if (!f) return; 14 | 15 | time_t rawtime; 16 | struct tm * timeinfo; 17 | 18 | time(&rawtime ); 19 | timeinfo = localtime(&rawtime); 20 | 21 | fprintf(f, "[%02d:%02d:%02d] ", timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec); 22 | } 23 | 24 | void va_write(FILE *f, const char* format, ...) { 25 | if (!f) return; 26 | 27 | va_list args; 28 | 29 | // print current time 30 | format_time(f); 31 | 32 | // print log message itself 33 | va_start(args, format); 34 | vfprintf(f, format, args); 35 | va_end(args); 36 | } 37 | 38 | void loginit(char* fname) 39 | { 40 | FILE* f = fopen(fname, "w"); 41 | if (f != NULL) logfile = f; else printf("can't create logfile!"); 42 | } 43 | -------------------------------------------------------------------------------- /logerror.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** simple error handling stuff 4 | --wbcbz7 l8.ob.zol8 5 | **/ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef __WINDOWS_386__ 12 | #define WIN32_LEAN_AND_MEAN 13 | #include 14 | #endif 15 | 16 | extern FILE *logfile; 17 | 18 | void va_write(FILE *f, const char* format, ...); 19 | 20 | void loginit(char* fname); 21 | extern char *logerr_header, *logfatal_header, *logdebug_header; 22 | 23 | #ifdef DEBUG_LOG 24 | 25 | #ifdef __WINDOWS_386__ 26 | 27 | #define logerr(a, ...) { const char *text = a, *fname = __FILE__; \ 28 | va_write(stderr, logerr_header, fname, __LINE__); \ 29 | va_write(logfile, logerr_header, fname, __LINE__); \ 30 | fprintf(stderr, text, ##__VA_ARGS__); \ 31 | fprintf(logfile, text, ##__VA_ARGS__); } 32 | 33 | #define logfatal(a, ...) { const char *text = a, *fname = __FILE__; \ 34 | va_write(stderr, logfatal_header, fname, __LINE__); \ 35 | va_write(logfile, logfatal_header, fname, __LINE__); \ 36 | fprintf(stderr, text, ##__VA_ARGS__); \ 37 | fprintf(logfile, text, ##__VA_ARGS__); \ 38 | exit(1);} 39 | 40 | #else 41 | 42 | #define logerr(a, ...) { const char *text = a, *fname = __FILE__; \ 43 | va_write(stderr, logerr_header, fname, __LINE__); \ 44 | va_write(logfile, logerr_header, fname, __LINE__); \ 45 | fprintf(stderr, text, ##__VA_ARGS__); \ 46 | fprintf(logfile, text, ##__VA_ARGS__); } 47 | 48 | #define logfatal(a, ...) { const char *text = a, *fname = __FILE__; \ 49 | va_write(stderr, logfatal_header, fname, __LINE__); \ 50 | va_write(logfile, logfatal_header, fname, __LINE__); \ 51 | fprintf(stderr, text, ##__VA_ARGS__); \ 52 | fprintf(logfile, text, ##__VA_ARGS__); \ 53 | exit(1);} 54 | #endif 55 | 56 | 57 | #define logwrite(...) va_write(logfile, __VA_ARGS__); 58 | #define logprint(...) { va_write(stderr, __VA_ARGS__); \ 59 | va_write(logfile, __VA_ARGS__); \ 60 | } 61 | 62 | #define logdebug(a, ...) { const char *text = a, *fname = __FILE__; \ 63 | va_write(stderr, logdebug_header, fname, __LINE__); \ 64 | va_write(logfile, logdebug_header, fname, __LINE__); \ 65 | fprintf(stderr, text, ##__VA_ARGS__); \ 66 | fprintf(logfile, text, ##__VA_ARGS__); } 67 | 68 | #else 69 | #define logerr(a, ...) 70 | #define logfatal(a, ...) 71 | #define logwrite(...) 72 | #define logprint(...) 73 | #define logdebug(a, ...) 74 | #endif -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | TARGET = sndlib 2 | 3 | BUILD = dos 4 | 5 | SYSTEM = dos32a 6 | DEBUG = dwarf all 7 | DLEVEL = 0 8 | 9 | SYSDEF = DOS 10 | 11 | INCLUDE = .\include 12 | 13 | AS = nasm.exe 14 | CC = wpp386.exe 15 | LD = wlink.exe 16 | AFLAGS = -f win32 17 | CFLAGS = -5r -zp16 -onhasbmi -s -zv -d$(DLEVEL) -d_$(SYSDEF) -i=$(INCLUDE) -bt=$(BUILD) 18 | LFLAGS = 19 | 20 | # add object files here 21 | OBJS = sndlib.obj convert.obj dma.obj dpmi.obj tinypci.obj irq.obj logerror.obj sndmisc.obj 22 | OBJS = $(OBJS) snddev.obj devgus.obj devsb.obj devwss.obj devpas.obj devhda.obj devhonk.obj devhonka.obj devds1.obj 23 | 24 | OBJLIST = $(OBJS) 25 | OBJSTR = file {$(OBJLIST)} 26 | 27 | all: $(TARGET).lib .symbolic 28 | 29 | $(TARGET).lib : $(OBJS) .symbolic 30 | %create $(TARGET).ls 31 | for %i in ($(OBJS)) do @%append $(TARGET).ls +%i 32 | 33 | wlib -n $(TARGET).lib 34 | wlib $(TARGET).lib @$(TARGET).ls 35 | del $(TARGET).ls 36 | 37 | # custom rule to enable "option eliminate" 38 | dpmi.obj: 39 | $(CC) dpmi.cpp $(CFLAGS) -zm 40 | dma.obj: 41 | $(CC) dma.cpp $(CFLAGS) -zm 42 | tinypci.obj: 43 | $(CC) tinypci.cpp $(CFLAGS) -zm 44 | 45 | .cpp.obj: 46 | $(CC) $< $(CFLAGS) 47 | 48 | .asm.obj: 49 | $(AS) $< $(AFLAGS) -------------------------------------------------------------------------------- /mmio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // force MMIO read 4 | uint32_t MMIO_READ32_IMPL(void *addr); 5 | #pragma aux MMIO_READ32_IMPL = "mov eax, [edi]" parm [edi] value [eax] 6 | uint16_t MMIO_READ16_IMPL(void *addr); 7 | #pragma aux MMIO_READ16_IMPL = "mov ax, [edi]" parm [edi] value [ax] 8 | uint8_t MMIO_READ8_IMPL (void *addr); 9 | #pragma aux MMIO_READ8_IMPL = "mov al, [edi]" parm [edi] value [al] 10 | 11 | #define MMIO_FORCE_READ32(addr) (MMIO_READ32_IMPL((void*)addr)) 12 | #define MMIO_FORCE_READ16(addr) (MMIO_READ16_IMPL((void*)addr)) 13 | #define MMIO_FORCE_READ8(addr) (MMIO_READ8_IMPL ((void*)addr)) 14 | 15 | // force MMIO write 16 | uint32_t MMIO_WRITE32_IMPL(void *addr, uint32_t val); 17 | #pragma aux MMIO_WRITE32_IMPL = "mov [edi], eax" parm [edi] [eax] value [eax] 18 | uint16_t MMIO_WRITE16_IMPL(void *addr, uint16_t val); 19 | #pragma aux MMIO_WRITE16_IMPL = "mov [edi], ax" parm [edi] [ax] value [ax] 20 | uint8_t MMIO_WRITE8_IMPL (void *addr, uint8_t val); 21 | #pragma aux MMIO_WRITE8_IMPL = "mov [edi], al" parm [edi] [al] value [al] 22 | #define MMIO_FORCE_WRITE32(addr, val) (MMIO_WRITE32_IMPL((void*)addr, val)) 23 | #define MMIO_FORCE_WRITE16(addr, val) (MMIO_WRITE16_IMPL((void*)addr, val)) 24 | #define MMIO_FORCE_WRITE8(addr, val) (MMIO_WRITE8_IMPL ((void*)addr, val)) 25 | 26 | #define MMIO_FORCE_MODIFY32(addr, mask, val) MMIO_FORCE_WRITE32(addr, (MMIO_FORCE_READ32(addr) & (~mask)) | ((val) & (mask))) 27 | #define MMIO_FORCE_MODIFY16(addr, mask, val) MMIO_FORCE_WRITE16(addr, (MMIO_FORCE_READ16(addr) & (~mask)) | ((val) & (mask))) 28 | #define MMIO_FORCE_MODIFY8(addr, mask, val) MMIO_FORCE_WRITE8 (addr, (MMIO_FORCE_READ8 (addr) & (~mask)) | ((val) & (mask))) 29 | 30 | // instantinate MMIO read 31 | #ifdef MMIO_ALWAYS_FORCE 32 | 33 | #define MMIO_READ32(addr) MMIO_FORCE_READ32(addr) 34 | #define MMIO_READ16(addr) MMIO_FORCE_READ16(addr) 35 | #define MMIO_READ8(addr) MMIO_FORCE_READ8(addr) 36 | 37 | #define MMIO_WRITE32(addr, val) MMIO_FORCE_WRITE32(addr, val) 38 | #define MMIO_WRITE16(addr, val) MMIO_FORCE_WRITE16(addr, val) 39 | #define MMIO_WRITE8(addr, val) MMIO_FORCE_WRITE8(addr, val) 40 | 41 | #define MMIO_MODIFY32(addr, mask, val) MMIO_FORCE_MODIFY32(addr, mask, val) 42 | #define MMIO_MODIFY16(addr, mask, val) MMIO_FORCE_MODIFY16(addr, mask, val) 43 | #define MMIO_MODIFY8(addr, mask, val) MMIO_FORCE_MODIFY8(addr, mask, val) 44 | 45 | #else 46 | 47 | #define MMIO_READ32(addr) (*((uint32_t*)(addr))) 48 | #define MMIO_READ16(addr) (*((uint16_t*)(addr))) 49 | #define MMIO_READ8(addr) (*((uint8_t *) (addr))) 50 | 51 | #define MMIO_WRITE32(addr, val) (*((uint32_t*)(addr)) = (uint32_t)(val)) 52 | #define MMIO_WRITE16(addr, val) (*((uint16_t*)(addr)) = (uint16_t)(val)) 53 | #define MMIO_WRITE8(addr, val) (*((uint8_t *)(addr)) = (uint8_t )(val)) 54 | 55 | #define MMIO_MODIFY32(addr, mask, val) (*((uint32_t*)(addr)) = (uint32_t)(*((uint32_t*)(addr)) & (uint32_t)(~mask)) | (uint32_t)((val) & (mask))) 56 | #define MMIO_MODIFY16(addr, mask, val) (*((uint16_t*)(addr)) = (uint16_t)(*((uint16_t*)(addr)) & (uint16_t)(~mask)) | (uint16_t)((val) & (mask))) 57 | #define MMIO_MODIFY8(addr, mask, val) (*((uint8_t *)(addr)) = (uint8_t) (*((uint8_t *)(addr)) & (uint8_t )(~mask)) | (uint8_t )((val) & (mask))) 58 | 59 | #endif -------------------------------------------------------------------------------- /snddefs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | sndlib options setup include 5 | --wbcbz7 28.o6.2o22 6 | */ 7 | 8 | // ------------------------------ 9 | // main sndlib defines 10 | 11 | // enable manual setup 12 | #define SNDLIB_ENABLE_MANUAL_SETUP 13 | 14 | // enable debug logging 15 | #define SNDLIB_ENABLE_DEBUG_LOG 16 | 17 | // ------------------------------ 18 | // sound device defines 19 | 20 | // enable non-DMA devices 21 | #define SNDLIB_DEVICE_ENABLE_NONDMA 22 | 23 | // enable ISA DMA devices 24 | #define SNDLIB_DEVICE_ENABLE_ISADMA 25 | 26 | // enable PCI/PCIe bus master devices 27 | #define SNDLIB_DEVICE_ENABLE_PCI 28 | 29 | // individually enable devices 30 | #define SNDLIB_DEVICE_ENABLE_PC_SPEAKER 31 | #define SNDLIB_DEVICE_ENABLE_COVOX 32 | #define SNDLIB_DEVICE_ENABLE_DUAL_COVOX 33 | #define SNDLIB_DEVICE_ENABLE_STEREO_ON_1 34 | #define SNDLIB_DEVICE_ENABLE_SB 35 | #define SNDLIB_DEVICE_ENABLE_SB16 36 | #define SNDLIB_DEVICE_ENABLE_GUS 37 | #define SNDLIB_DEVICE_ENABLE_WSS 38 | #define SNDLIB_DEVICE_ENABLE_ESS 39 | #define SNDLIB_DEVICE_ENABLE_PAS 40 | #define SNDLIB_DEVICE_ENABLE_HDA 41 | //#define SNDLIB_DEVICE_ENABLE_DS1 // disabled until i make the driver work as intended 42 | 43 | // ------------------------------ 44 | // sound device custom properties 45 | 46 | // WSS: extended ID probing (CS4232 or newer, informational only, as sndlib uses AD1848 subset only) 47 | //#define SNDLIB_DEVICE_WSS_EXTENDED_ID_PROBING 48 | 49 | // HDA: enable buffer position workaround (still buggy :D) 50 | #define SNDLIB_DEVICE_HDA_BUFFER_POS_WORKAROUND 51 | 52 | // ESS: enable ES1869 features (accurate 48khz clock) 53 | #define SNDLIB_DEVICE_ESS_ENABLE_ES1869_FEATURES 54 | 55 | // ------------------------------ 56 | // sound format converter defines 57 | 58 | // enable arbitrary format converters 59 | // if not defined, source format must match one of device supported formats 60 | #define SNDLIB_CONVERT_ENABLE_ARBITRARY 61 | 62 | // enable 8/16bit to PC-Speaker format converters 63 | // automatically disabled if PC Speaker device is not enabled 64 | #define SNDLIB_CONVERT_ENABLE_PCSPEAKER 65 | 66 | 67 | // ----------------------------- 68 | // autogenerated defines 69 | 70 | #ifndef SNDLIB_DEVICE_ENABLE_NONDMA 71 | #undef SNDLIB_DEVICE_ENABLE_PC_SPEAKER 72 | #undef SNDLIB_DEVICE_ENABLE_COVOX 73 | #undef SNDLIB_DEVICE_ENABLE_DUAL_COVOX 74 | #undef SNDLIB_DEVICE_ENABLE_STEREO_ON_1 75 | #endif 76 | #ifndef SNDLIB_DEVICE_ENABLE_ISADMA 77 | #undef SNDLIB_DEVICE_ENABLE_SB 78 | #undef SNDLIB_DEVICE_ENABLE_SB16 79 | #undef SNDLIB_DEVICE_ENABLE_GUS 80 | #undef SNDLIB_DEVICE_ENABLE_WSS 81 | #undef SNDLIB_DEVICE_ENABLE_ESS 82 | #undef SNDLIB_DEVICE_ENABLE_PAS 83 | #endif 84 | #ifndef SNDLIB_DEVICE_ENABLE_PCI 85 | #undef SNDLIB_DEVICE_ENABLE_HDA 86 | #undef SNDLIB_DEVICE_ENABLE_DS1 87 | #endif 88 | 89 | #ifndef SNDLIB_DEVICE_ENABLE_PC_SPEAKER 90 | #undef SNDLIB_CONVERT_ENABLE_PCSPEAKER 91 | #endif 92 | -------------------------------------------------------------------------------- /snddev.h: -------------------------------------------------------------------------------- 1 | // WE'RE GONNA SCREW EVERYTHING 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "convert.h" 9 | #include "irq.h" 10 | #include "dma.h" 11 | #include "tinypci.h" 12 | 13 | #define SNDDEV_IRQ_PER_DEVICE 14 | 15 | 16 | // deviceResources::flags 17 | enum { 18 | SND_DEVICE_IRQ0 = (1 << 0), // timing based on IRQ0 (e.g. covox/pc speaker/sb non-dma) 19 | SND_DEVICE_CLOCKDRIFT = (1 << 1), // actual samplerate can differ from requested by >2% 20 | SND_DEVICE_CUSTOMFORMAT = (1 << 2), // custom format for samples (e.g. pc speaker) 21 | }; 22 | 23 | // open::flags 24 | enum { 25 | SND_OPEN_NOCONVERT = (1 << 0), // try setting direct format, don't perform conversion 26 | SND_OPEN_PCSPEAKER_GEN_XLAT = (1 << 1), // PC Speaker only: allow SND_FMT_INT8 even if SND_OPEN_NOCONVERT and generate xlat table 27 | }; 28 | 29 | // isFormatSupported::flage 30 | enum { 31 | SND_ISFORMATSUPPORTED_MAXSAMPLERATE = -1, 32 | }; 33 | 34 | enum { 35 | SND_RES_IOBASE, 36 | SND_RES_IOBASE2, 37 | SND_RES_IRQ, 38 | SND_RES_IRQ2, 39 | SND_RES_DMA, 40 | SND_RES_DMA2, 41 | SND_RES_PCI, 42 | }; 43 | 44 | struct soundResourceInfo { 45 | uint32_t resourceType; 46 | uint32_t length; 47 | const uint32_t* data; 48 | }; 49 | 50 | 51 | class SoundDevice { 52 | 53 | const char* name; // nothing fancy here 54 | 55 | public: 56 | struct deviceInfo { 57 | const char* name; // device name 58 | const char* version; // device version 59 | 60 | void* membase; // NULL if none 61 | uint32_t iobase; // -1 if none 62 | uint32_t iobase2; // secondary (hey gus), -1 if none 63 | uint32_t irq; // -1 if none 64 | uint32_t irq2; // -1 if none 65 | uint32_t dma; // -1 if none 66 | uint32_t dma2; // -1 if none 67 | pciAddress pci; // PCI device address, -1 if none 68 | 69 | uint32_t maxBufferSize; // maximum available buffer size in BYTES 70 | uint32_t flags; // as usual 71 | 72 | uint32_t capsLen; // caps list length 73 | const soundFormatCapability* caps; // caps list pointer 74 | 75 | // private buffer for copied strings/etc 76 | char *privateBuf; 77 | uint32_t privateBufSize; 78 | 79 | // clear struct 80 | void clear(); 81 | 82 | // fix private buffer pointers 83 | void privFixup(const deviceInfo& rhs); 84 | 85 | // regular constructor 86 | deviceInfo(uint32_t _privateBufSize = 64); 87 | 88 | // copy constructor 89 | deviceInfo(const deviceInfo& rhs); 90 | deviceInfo& operator=(const deviceInfo& rhs); 91 | 92 | // destructor 93 | ~deviceInfo(); 94 | }; 95 | 96 | public: 97 | // constructor (nothing fancy here) 98 | SoundDevice(const char* _name, uint32_t _infoPrivateBufSize = 64); 99 | virtual ~SoundDevice(); 100 | 101 | // get device name 102 | virtual const char *getName(); 103 | 104 | // get available resources for manual config, return -1 if autoconfig only, 0 if error, else length of info array 105 | virtual const uint32_t getResourceInfo(const soundResourceInfo *info); 106 | 107 | // get supported formats, 0 if error, else length of info array 108 | virtual const uint32_t getCaps(const soundFormatCapability* info); 109 | 110 | // detect (0 if found + if (res != NULL) *res filled with current config) 111 | virtual uint32_t detect(SoundDevice::deviceInfo *info = NULL); 112 | 113 | // get device information, NULL on error 114 | virtual const SoundDevice::deviceInfo* getDeviceInfo(); 115 | 116 | // select and init (use supplied *res info if (res != NULL)) 117 | virtual uint32_t init(SoundDevice::deviceInfo *info = NULL); 118 | 119 | // check if format is supported (0 if supported, fills conv->sampleRate/surceSampleRate fields) 120 | virtual uint32_t isFormatSupported(uint32_t sampleRate, soundFormat fmt, soundFormatConverterInfo *conv); 121 | 122 | // return converter for current format 123 | virtual uint32_t getConverter(soundFormat srcfmt, soundFormat dstfmt, soundFormatConverterInfo *conv); 124 | 125 | // return bytes per sample count 126 | virtual uint32_t getBytesPerSample(soundFormat fmt); 127 | 128 | // init for playback 129 | virtual uint32_t open(uint32_t sampleRate, soundFormat fmt, uint32_t bufferSize, uint32_t flags, soundDeviceCallback callback, void *userdata, soundFormatConverterInfo *conv); 130 | 131 | // start playback (won't return immediately, calls callback to fill DMA buffer) 132 | virtual uint32_t start(); 133 | 134 | // pause playback (start() or resume() for resume) 135 | virtual uint32_t pause(); 136 | 137 | // resume playback 138 | virtual uint32_t resume(); 139 | 140 | // get playback position in samples 141 | virtual int64_t getPos(); 142 | 143 | // ioctl 144 | virtual uint32_t ioctl(uint32_t function, void *data, uint32_t len); 145 | 146 | // stop playback 147 | virtual uint32_t stop(); 148 | 149 | // close playback 150 | virtual uint32_t close(); 151 | 152 | // deinit device 153 | virtual uint32_t done(); 154 | 155 | protected: 156 | // ---------- internal running state flags ---------- 157 | bool isDetected; // set by detect() 158 | bool isInitialised; // init() ................................ done() 159 | bool isOpened; // open() ................. close() 160 | bool isPlaying; // play() ... stop() 161 | bool isPaused; // pause() .. play() 162 | 163 | // ------------------ device info -------------------- 164 | SoundDevice::deviceInfo devinfo; 165 | 166 | // ------------ format and callback info ------------- 167 | // callback info 168 | soundDeviceCallback callback; 169 | void* userdata; 170 | soundFormatConverterInfo convinfo; 171 | 172 | public: 173 | // ---------------- interrupt stuff ------------------ 174 | 175 | // IRQ stuff info 176 | irqEntry irq; 177 | 178 | // irq procedure (returns true if needs to chain to old handler) 179 | virtual bool irqProc(); 180 | 181 | }; 182 | 183 | // ----------------------------------------------- 184 | // DMA-like circular buffer device (both ISA DMA and PCI bus-master devices) 185 | // ----------------------------------------------- 186 | class DmaBufferDevice : public SoundDevice { 187 | public: 188 | DmaBufferDevice(const char* _name, uint32_t _infoPrivateBufSize = 64); 189 | 190 | // get playback position in samples 191 | virtual int64_t getPos(); 192 | 193 | protected: 194 | 195 | // -------------------------- DMA stuff ------------------------ 196 | 197 | // getPos() previous values 198 | int64_t oldTotalPos; // previous total playback pos 199 | 200 | int64_t currentPos; // total playback pos 201 | int64_t renderPos; // total rendering pos 202 | uint32_t irqs; // total IRQs count 203 | 204 | // each block contains one or more buffers (2 in our case) 205 | 206 | uint32_t dmaChannel; // sometimes different formats require different DMA channels (hey SB16) 207 | 208 | dmaBlock dmaBlock; 209 | uint32_t dmaBlockSize; // size in bytes! 210 | uint32_t dmaBlockSamples; // size in samples 211 | 212 | uint32_t dmaBufferCount; // num of buffers inside one block 213 | uint32_t dmaBufferSize; // size of each buffer 214 | uint32_t dmaBufferSamples; // size of each buffer (in samples) 215 | 216 | int32_t dmaCurrentPtr; // points to current playing(!) buffer 217 | int32_t dmaRenderPtr; // points to current rendering buffer 218 | 219 | // init DMA buffer 220 | virtual uint32_t dmaBufferInit(uint32_t bufferSize, soundFormatConverterInfo *conv); 221 | 222 | // free DMA buffer 223 | virtual uint32_t dmaBufferFree(); 224 | 225 | // install IRQ routine 226 | virtual uint32_t installIrq(); 227 | 228 | // remove IRQ routine 229 | virtual uint32_t removeIrq(); 230 | 231 | // init start and prefill buffer 232 | virtual uint32_t prefill(); 233 | 234 | // advance play/render pointers 235 | virtual void irqAdvancePos(); 236 | 237 | // IRQ->callback caller 238 | virtual bool irqCallbackCaller(); 239 | 240 | // get play position in DMA buffer in bytes 241 | virtual int32_t getPlayPos(); 242 | 243 | }; 244 | 245 | // ISA DMA device 246 | class IsaDmaDevice : public DmaBufferDevice { 247 | public: 248 | IsaDmaDevice(const char* _name); 249 | 250 | protected: 251 | 252 | // get play position in DMA buffer in bytes 253 | virtual int32_t getPlayPos(); 254 | 255 | }; 256 | 257 | 258 | // IRQ handling stuff 259 | extern SoundDevice *snd_activeDevice[16]; 260 | #ifdef SNDDEV_IRQ_PER_DEVICE 261 | extern void __interrupt __far (*snd_irqProcTable[16])(); 262 | #endif 263 | extern "C" void __interrupt __far snd_irqStaticProc(INTPACK r); 264 | 265 | // device IRQ detection stuff 266 | struct IrqDetectInfo { 267 | bool found; 268 | uint32_t iobase; 269 | irqEntry *irq; 270 | }; 271 | extern volatile IrqDetectInfo snd_IrqDetectInfo; 272 | 273 | // protected mode ISR stack 274 | extern "C" { 275 | extern uint8_t snddev_bss_lock_start, snddev_bss_lock_end; // BSS lock start/end (not a variable) 276 | extern uint8_t snddev_pm_stack_in_use; // PM stack "in use" flag (0 - free) 277 | 278 | extern void __far *snddev_pm_stack_top; // PM stack top 279 | extern void __far *snddev_pm_stack; // PM stack bottom 280 | extern void __far *snddev_pm_old_stack; // PM saved stack pointer 281 | } 282 | 283 | -------------------------------------------------------------------------------- /snderror.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum { 4 | SND_ERR_OK = 0, 5 | SND_ERR_UNSUPPORTED, 6 | SND_ERR_INVALIDCONFIG, 7 | SND_ERR_MEMALLOC, 8 | SND_ERR_UNKNOWN_FORMAT, 9 | SND_ERR_NOTFOUND, 10 | SND_ERR_NULLPTR, 11 | SND_ERR_STUCK_IRQ, 12 | SND_ERR_DMA, 13 | SND_ERR_NO_DATA, 14 | SND_ERR_UNINITIALIZED, 15 | SND_ERR_RESUMED, // for SoundDevide::start() if playback is paused, not an error! 16 | SND_ERR_USEREXIT, // for sndlibCreateDevice() 17 | 18 | 19 | // ...more in future :) 20 | }; 21 | -------------------------------------------------------------------------------- /sndfmt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "convert.h" 4 | 5 | typedef uint32_t soundFormat; 6 | enum { 7 | SND_FMT_NULL = 0, 8 | SND_FMT_INT8 = (1 << 0), 9 | SND_FMT_INT16 = (1 << 1), 10 | SND_FMT_INT20 = (1 << 2), 11 | SND_FMT_INT24 = (1 << 3), 12 | SND_FMT_FLOAT = (1 << 4), 13 | SND_FMT_DOUBLE = (1 << 5), 14 | 15 | SND_FMT_XLAT8 = (1 << 7), 16 | SND_FMT_DEPTH_MASK = (1 << 8) - 1, 17 | 18 | SND_FMT_MONO = (1 << 8), 19 | SND_FMT_STEREO = (1 << 9), 20 | 21 | SND_FMT_CHANNELS_MASK = ((1 << (12 - 8 + 1)) - 1) << 8, 22 | 23 | SND_FMT_SIGNED = (1 << 15), 24 | SND_FMT_UNSIGNED = (1 << 14), 25 | 26 | SND_FMT_SIGN_MASK = ((1 << (15 - 14 + 1)) - 1) << 14, 27 | }; 28 | 29 | struct soundFormatCapability { 30 | soundFormat format; // combination of soundFormat flags 31 | int32_t ratesLength; // positive for fixed rates, -2 for rates range [rates[0]; rates[1]] 32 | const uint32_t *rates; // ratesLength length, NOT sorted! 33 | }; 34 | 35 | 36 | -------------------------------------------------------------------------------- /sndioctl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum { 4 | // range definitions 5 | SND_IOCTL_GENERAL = 0, 6 | 7 | // mixer controls 8 | SND_IOCTL_MIXER = 0x4000, 9 | 10 | SND_IOCTL_VOL_MAIN_GET, // 16.16 fx 2's complement, 0x10000 - 1 db, 0x80000000 - silence 11 | SND_IOCTL_VOL_MAIN_SET, 12 | 13 | SND_IOCTL_VOL_WAVE_GET, 14 | SND_IOCTL_VOL_WAVE_SET, 15 | 16 | // device-specific controls 17 | SND_IOCTL_DEVICE_SPECIFIC = 0x8000, 18 | }; 19 | 20 | // ioctl types 21 | enum { 22 | SND_IOCTRL_TYPE_BOOL, 23 | SND_IOCTRL_TYPE_INT32, 24 | SND_IOCTRL_TYPE_UINT32, 25 | SND_IOCTRL_TYPE_VOLUME, 26 | }; 27 | 28 | // ioctl import table 29 | struct SoundDeviceIoctlImport { 30 | uint32_t handle; 31 | uint32_t type; 32 | uint32_t minValue; 33 | uint32_t maxValue; 34 | const char* name; 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /sndlib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sndlib.h" 7 | #include "snddefs.h" 8 | #include "snddev.h" 9 | #include "sndfmt.h" 10 | #include "convert.h" 11 | #include "snderror.h" 12 | 13 | // init sound library internal resources 14 | uint32_t sndlibInit() { 15 | // init PM ISR stack 16 | snddev_pm_stack_top = &snddev_pm_stack_top; 17 | 18 | // init snddev_pm_old_stack so it doesn't dangle in the void 19 | snddev_pm_old_stack = NULL; 20 | 21 | // clear in use flag 22 | snddev_pm_stack_in_use = 0; 23 | 24 | // lock memory for stack/RMCB data 25 | dpmi_lockmemory(&snddev_bss_lock_start, (&snddev_bss_lock_end - &snddev_bss_lock_start)); 26 | 27 | // clear active device storage 28 | memset(snd_activeDevice, 0, sizeof(snd_activeDevice)); 29 | 30 | return SND_ERR_OK; 31 | } 32 | 33 | // close sound library and cleanup 34 | uint32_t sndlibDone() { 35 | // unlock memory for stack/RMCB data 36 | dpmi_lockmemory(&snddev_bss_lock_start, (&snddev_bss_lock_end - &snddev_bss_lock_start)); 37 | 38 | return SND_ERR_OK; 39 | } 40 | 41 | // detect for SBEMU, returns INT 2D multiplex or -1 if not found 42 | static uint32_t sndlib_sbemu_detect() { 43 | uint8_t* amis_appstring; 44 | uint32_t ret_seg = 0, ret_ofs = 0; 45 | 46 | // check for INT2D vector == NULL 47 | if (dpmi_getrmvect(0x2D) == NULL) return -1; 48 | 49 | // scan all multiplexes of INT 2D 50 | for (int mx = 0; mx < 256; mx++) { 51 | _asm { 52 | mov ah, byte ptr [mx] 53 | xor al, al 54 | int 0x2d 55 | cmp al, 0xFF // is this a free multiplex? 56 | jz _found 57 | xor dx, dx // it is, return NULL pointer 58 | xor di, di 59 | _found: 60 | mov word ptr [ret_seg], dx 61 | mov word ptr [ret_ofs], di 62 | } 63 | 64 | // check for SBEMU application string 65 | amis_appstring = (uint8_t*)((ret_seg << 4) + ret_ofs); // FIXME: assumes Watcom C zero-based flat model 66 | if ((amis_appstring != NULL) && (memcmp(amis_appstring+8, "SBEMU", 5) == 0)) return mx; 67 | } 68 | 69 | // TODO: implement 70 | return -1; 71 | } 72 | 73 | // this is SLOW (if not done via lookup), but it can't be helped right now 74 | SoundDevice* sndlibCreateSpecificDevice(uint32_t id) { 75 | switch(id) { 76 | // non-DMA devices... 77 | #ifdef SNDLIB_DEVICE_ENABLE_PC_SPEAKER 78 | case SND_CREATE_DEVICE_PC_SPEAKER: return new sndPcSpeaker(); 79 | #endif 80 | #ifdef SNDLIB_DEVICE_ENABLE_COVOX 81 | case SND_CREATE_DEVICE_COVOX : return new sndCovox(); 82 | #endif 83 | #ifdef SNDLIB_DEVICE_ENABLE_DUAL_COVOX 84 | case SND_CREATE_DEVICE_DUAL_COVOX: return new sndDualCovox(); 85 | #endif 86 | #ifdef SNDLIB_DEVICE_ENABLE_STEREO_ON_1 87 | case SND_CREATE_DEVICE_STEREO_ON_1: return new sndStereoOn1(); 88 | #endif 89 | 90 | // ISA DMA devices... 91 | #ifdef SNDLIB_DEVICE_ENABLE_SB 92 | case SND_CREATE_DEVICE_SB: return new sndSoundBlaster(); 93 | #endif 94 | #ifdef SNDLIB_DEVICE_ENABLE_SB16 95 | case SND_CREATE_DEVICE_SB16: return new sndSoundBlaster16(); 96 | #endif 97 | #ifdef SNDLIB_DEVICE_ENABLE_GUS 98 | case SND_CREATE_DEVICE_GUS: return new sndGravisUltrasound(); 99 | #endif 100 | #ifdef SNDLIB_DEVICE_ENABLE_WSS 101 | case SND_CREATE_DEVICE_WSS: return new sndWindowsSoundSystem(); 102 | #endif 103 | #ifdef SNDLIB_DEVICE_ENABLE_ESS 104 | case SND_CREATE_DEVICE_ESS: return new sndESSAudioDrive(); 105 | #endif 106 | #ifdef SNDLIB_DEVICE_ENABLE_PAS 107 | case SND_CREATE_DEVICE_PAS: return new sndProAudioSpectrum(); 108 | #endif 109 | 110 | // PCI/PCIe bus master devices... 111 | #ifdef SNDLIB_DEVICE_ENABLE_HDA 112 | case SND_CREATE_DEVICE_HDA: return new sndHDAudio(); 113 | #endif 114 | #ifdef SNDLIB_DEVICE_ENABLE_DS1 115 | case SND_CREATE_DEVICE_DS1: return new sndYamahaDS1(); 116 | #endif 117 | 118 | default: return NULL; 119 | } 120 | } 121 | 122 | #define arrayof(a) (sizeof(a) / sizeof(a[0])) 123 | 124 | const uint32_t SND_TOTAL_DEVICES = 125 | (SND_CREATE_DEVICE_NONDMA_LAST - SND_CREATE_DEVICE_NONDMA_FIRST) + 126 | (SND_CREATE_DEVICE_ISA_DMA_LAST - SND_CREATE_DEVICE_ISA_DMA_FIRST) + 127 | (SND_CREATE_DEVICE_PCI_LAST - SND_CREATE_DEVICE_PCI_FIRST); 128 | 129 | // detect, create device object and other stuff, y'know 130 | uint32_t sndlibCreateDevice(SoundDevice **device, uint32_t flags) { 131 | if (device == NULL) return SND_ERR_NULLPTR; 132 | 133 | // return status 134 | uint32_t rtn = SND_ERR_OK; 135 | 136 | // device flags 137 | uint32_t deviceFlags = flags & SND_CREATE_DEVICE_SPECIFIC; 138 | 139 | if (deviceFlags >= SND_CREATE_DEVICE_SPECIFIC) { 140 | // request specific device 141 | SoundDevice* dev = sndlibCreateSpecificDevice(deviceFlags); 142 | if (dev == NULL) return SND_ERR_MEMALLOC; 143 | 144 | // detect if requested 145 | if (((flags & SND_CREATE_SKIP_DETECTION) != 0) && ((rtn = dev->detect()) != SND_ERR_OK)) return rtn; 146 | 147 | // pass pointer to device and return success 148 | *device = dev; 149 | return SND_ERR_OK; 150 | } 151 | 152 | // else we have to query each driver for supported devices 153 | uint32_t selection = ((flags & SND_CREATE_DEVICE_MASK) == SND_CREATE_DEVICE_AUTO_DETECT) ? 0 : -1; 154 | 155 | // check for SBEMU running 156 | // in this case, do not probe PCI devices to avoid screwing things up 157 | uint32_t sbemu_multiplex = sndlib_sbemu_detect(); 158 | #ifdef DEBUG_LOG 159 | logdebug("SBEMU multiplex = %d\n", sbemu_multiplex); 160 | #endif 161 | 162 | // probe devices array 163 | SoundDevice * probeDevices[SND_TOTAL_DEVICES]; 164 | uint32_t probeDeviceIdx = 0; 165 | 166 | // device priority list 167 | static const uint32_t priorityList[] = { 168 | SND_CREATE_DEVICE_HDA, 169 | SND_CREATE_DEVICE_DS1, 170 | 171 | SND_CREATE_DEVICE_SB16, 172 | SND_CREATE_DEVICE_WSS, 173 | SND_CREATE_DEVICE_ESS, 174 | SND_CREATE_DEVICE_SB, 175 | SND_CREATE_DEVICE_GUS, 176 | SND_CREATE_DEVICE_PAS, 177 | 178 | SND_CREATE_DEVICE_STEREO_ON_1, 179 | SND_CREATE_DEVICE_DUAL_COVOX, 180 | SND_CREATE_DEVICE_COVOX, 181 | SND_CREATE_DEVICE_PC_SPEAKER, 182 | }; 183 | 184 | // probe each device 185 | for (uint32_t i = 0; i < arrayof(priorityList); i++) { 186 | uint32_t id = priorityList[i]; 187 | 188 | // filter out non-DMA devices if requested 189 | if ((flags & SND_CREATE_SKIP_NONDMA_DEVICES) && 190 | (id < SND_CREATE_DEVICE_NONDMA_LAST) && 191 | (id >= SND_CREATE_DEVICE_NONDMA_FIRST)) continue; 192 | 193 | // filter out PCI devices if SBEMU is running 194 | if ((sbemu_multiplex != -1) && 195 | (id >= SND_CREATE_DEVICE_PCI_FIRST) && 196 | (id < SND_CREATE_DEVICE_PCI_LAST)) continue; 197 | 198 | // request specific device 199 | probeDevices[probeDeviceIdx] = sndlibCreateSpecificDevice(id); 200 | 201 | // check if such device class is available 202 | if (probeDevices[probeDeviceIdx] == NULL) continue; 203 | 204 | // detect if requested 205 | if (((flags & SND_CREATE_SKIP_DETECTION) == 0) && (probeDevices[probeDeviceIdx]->detect() != SND_ERR_OK)) 206 | delete probeDevices[probeDeviceIdx]; 207 | else probeDeviceIdx++; 208 | } 209 | 210 | // no devices? 211 | if (probeDeviceIdx == 0) return SND_ERR_NOTFOUND; 212 | 213 | // show selector 214 | #ifdef SNDLIB_ENABLE_MANUAL_SETUP 215 | if ((flags & SND_CREATE_DEVICE_MASK) != SND_CREATE_DEVICE_AUTO_DETECT) { 216 | printf(" select available sound device: \n"); 217 | for (size_t i = 0; i < probeDeviceIdx; i++) { 218 | printf(" %c - %-25s (%s)\n", 219 | (i >= 10 ? (i - 10 + 'A') : (i + '0')), 220 | probeDevices[i]->getDeviceInfo()->name, 221 | probeDevices[i]->getDeviceInfo()->version 222 | ); 223 | } 224 | printf(" ------------------------------------------\n"); 225 | printf(" press [0 - %c] to select device, [ESC] to exit... \n", (probeDeviceIdx >= 11 ? (probeDeviceIdx - 11 + 'A') : (probeDeviceIdx - 1 + '0'))); 226 | 227 | // messy keyscan routine 228 | do { 229 | char ch = getch(); 230 | if (ch == 0x1B) { 231 | // user exit 232 | rtn = SND_ERR_USEREXIT; 233 | break; 234 | } 235 | if (ch < '0') continue; 236 | if ((ch <= 'z') && (ch >= 'a')) ch -= 32; 237 | selection = (ch >= 'A' ? (ch + 10 - 'A') : (ch - '0')); 238 | } while (selection >= probeDeviceIdx); 239 | } 240 | #endif 241 | 242 | // free unused devices 243 | for (uint32_t i = 0; i < probeDeviceIdx; i++) if (i != selection) delete probeDevices[i]; 244 | 245 | // fill user pointer with current device 246 | if (selection < probeDeviceIdx) *device = probeDevices[selection]; 247 | 248 | // done :) 249 | return rtn; 250 | } 251 | 252 | uint32_t sndlibDestroyDevice(SoundDevice *dev) { 253 | if (dev == NULL) return SND_ERR_NULLPTR; 254 | delete dev; 255 | return SND_ERR_OK; 256 | } -------------------------------------------------------------------------------- /sndlib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | sndlib main include 5 | --wbcbz7 28.o6.2o22 6 | */ 7 | 8 | // options setup 9 | #include "snddefs.h" 10 | 11 | // main sndlib includes 12 | #include "convert.h" 13 | #include "snderror.h" 14 | #include "sndfmt.h" 15 | #include "snddev.h" 16 | #include "sndmisc.h" 17 | 18 | // init sound library (called before any device using) 19 | uint32_t sndlibInit(); 20 | 21 | // done sound library (called at exit) 22 | uint32_t sndlibDone(); 23 | 24 | // create device object, optionally autodetect and select best device, request specific device driver, etc... 25 | uint32_t sndlibCreateDevice(SoundDevice **dev, uint32_t flags = 0); 26 | 27 | // destroy device 28 | uint32_t sndlibDestroyDevice(SoundDevice *dev); 29 | 30 | // sndlibCreateDevice() enums 31 | enum { 32 | SND_CREATE_DEVICE_MANUAL_SELECT = 0, // manual setup 33 | SND_CREATE_DEVICE_AUTO_DETECT = 1, // autodetect and select best available device 34 | SND_CREATE_DEVICE_SPECIFIC = 0x1000, // select specific device 35 | SND_CREATE_DEVICE_MASK = 0x1FFF, // 36 | 37 | SND_CREATE_SKIP_NONDMA_DEVICES = (1 << 24), // skip non-DMA devices 38 | SND_CREATE_SKIP_DETECTION = (1 << 25), // skip device detection 39 | 40 | /// ------------------------------------------ 41 | // device driver IDs 42 | // non-DMA devices... 43 | SND_CREATE_DEVICE_NONDMA_FIRST = 0x1000, 44 | SND_CREATE_DEVICE_PC_SPEAKER = SND_CREATE_DEVICE_NONDMA_FIRST, 45 | SND_CREATE_DEVICE_COVOX, 46 | SND_CREATE_DEVICE_DUAL_COVOX, 47 | SND_CREATE_DEVICE_STEREO_ON_1, 48 | SND_CREATE_DEVICE_NONDMA_LAST, 49 | 50 | // ISA DMA devices... 51 | SND_CREATE_DEVICE_ISA_DMA_FIRST = 0x1100, 52 | SND_CREATE_DEVICE_SB = SND_CREATE_DEVICE_ISA_DMA_FIRST, 53 | SND_CREATE_DEVICE_SB16, 54 | SND_CREATE_DEVICE_GUS, 55 | SND_CREATE_DEVICE_WSS, 56 | SND_CREATE_DEVICE_ESS, 57 | SND_CREATE_DEVICE_PAS, 58 | SND_CREATE_DEVICE_ISA_DMA_LAST, 59 | 60 | // PCI/PCIe bus master devices... 61 | SND_CREATE_DEVICE_PCI_FIRST = 0x1200, 62 | SND_CREATE_DEVICE_HDA = SND_CREATE_DEVICE_PCI_FIRST, 63 | SND_CREATE_DEVICE_DS1, 64 | SND_CREATE_DEVICE_PCI_LAST, 65 | }; 66 | 67 | // devices include 68 | #if (defined(SNDLIB_DEVICE_ENABLE_SB) || \ 69 | defined(SNDLIB_DEVICE_ENABLE_SB16) || \ 70 | defined(SNDLIB_DEVICE_ENABLE_ESS)) 71 | #include "devsb.h" 72 | #endif 73 | #if defined(SNDLIB_DEVICE_ENABLE_GUS) 74 | #include "devgus.h" 75 | #endif 76 | #if defined(SNDLIB_DEVICE_ENABLE_WSS) 77 | #include "devwss.h" 78 | #endif 79 | #if defined(SNDLIB_DEVICE_ENABLE_PAS) 80 | #include "devpas.h" 81 | #endif 82 | #if defined(SNDLIB_DEVICE_ENABLE_NONDMA) 83 | #include "devhonk.h" 84 | #endif 85 | #if defined(SNDLIB_DEVICE_ENABLE_HDA) 86 | #include "devhda.h" 87 | #endif 88 | #if defined(SNDLIB_DEVICE_ENABLE_DS1) 89 | #include "devds1.h" 90 | #endif -------------------------------------------------------------------------------- /sndmisc.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbcbz7/sndlib-watcom/dfe10be498886a6bb14e73292dbc4ceddfa1216a/sndmisc.cpp -------------------------------------------------------------------------------- /sndmisc.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbcbz7/sndlib-watcom/dfe10be498886a6bb14e73292dbc4ceddfa1216a/sndmisc.h -------------------------------------------------------------------------------- /tinypci.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "tinypci.h" 6 | 7 | //#define MORE_DEBUG 8 | 9 | namespace tinypci { 10 | // bus enumeration helpers 11 | // list is incremented each time new device is enumerated 12 | struct enumContext { 13 | pciDeviceList *list; 14 | uint32_t listSize; 15 | pciDeviceList listInfo; 16 | }; 17 | 18 | // recursive bus enumeraion 19 | bool enumerateBus(enumContext& ctx, uint32_t bus = 0); 20 | 21 | // enumerate device 22 | bool enumerateDevice(enumContext& ctx, uint32_t bus, uint32_t device); 23 | 24 | // enumerate device function 25 | bool enumerateFunction(enumContext& ctx, pciAddress addr); 26 | 27 | // convert address to CF8 port value 28 | uint32_t pciAddrToCF8(pciAddress addr, uint32_t index); 29 | } 30 | 31 | uint32_t tinypci::enumerateAll(pciDeviceList *list, uint32_t listSize) { 32 | pciDeviceList listInfo; memset(&listInfo, -1, sizeof(listInfo)); 33 | return enumerate(list, listSize, listInfo); 34 | } 35 | 36 | uint32_t tinypci::enumerateByAddress(pciDeviceList *list, uint32_t listSize, pciAddress addr) { 37 | pciDeviceList listInfo; memset(&listInfo, -1, sizeof(listInfo)); 38 | listInfo.address = addr; 39 | return enumerate(list, listSize, listInfo); // slooooowwww.... 40 | } 41 | 42 | uint32_t tinypci::enumerateByClass(pciDeviceList *list, uint32_t listSize, pciClass devclass) { 43 | pciDeviceList listInfo; memset(&listInfo, -1, sizeof(listInfo)); 44 | listInfo.deviceClass = devclass; 45 | return enumerate(list, listSize, listInfo); 46 | } 47 | 48 | uint32_t tinypci::enumerateByDeviceId(pciDeviceList *list, uint32_t listSize, uint32_t vendorId, uint32_t deviceId) { 49 | pciDeviceList listInfo; memset(&listInfo, -1, sizeof(listInfo)); 50 | listInfo.vendorId = vendorId; 51 | listInfo.deviceId = deviceId; 52 | return enumerate(list, listSize, listInfo); 53 | } 54 | 55 | uint32_t tinypci::enumerate(pciDeviceList *list, uint32_t listSize, pciDeviceList& listInfo) { 56 | if ((list == NULL) || (listSize == 0)) return 0; 57 | enumContext ctx; 58 | ctx.list = list; ctx.listSize = listSize; ctx.listInfo = listInfo; 59 | 60 | // start from bus 0 61 | pciAddress rootAddr = {0}; 62 | if (configReadWord(rootAddr, 0) == 0xFFFF) return 0; // no bus? 63 | 64 | // check if multifunction 65 | if ((configReadByte(rootAddr, 0xE) & 0x80) == 0) { 66 | // single bus 67 | enumerateBus(ctx, 0); 68 | } 69 | else { 70 | // multiple buses 71 | for (rootAddr.function = 0; rootAddr.function < 8; rootAddr.function++) { 72 | // better to read configspace directly :) 73 | if (configReadWord(rootAddr, 0) != 0xFFFF) enumerateBus(ctx, rootAddr.function); 74 | } 75 | } 76 | 77 | // return total count of device found 78 | return (listSize - ctx.listSize); 79 | } 80 | 81 | bool tinypci::enumerateBus(enumContext& ctx, uint32_t bus) { 82 | #ifdef MORE_DEBUG 83 | printf("enumerate bus %02X...\n", bus); 84 | #endif 85 | for (uint32_t device = 0; device < 32; device++) enumerateDevice(ctx, bus, device); 86 | return true; 87 | } 88 | 89 | bool tinypci::enumerateDevice(enumContext& ctx, uint32_t bus, uint32_t device) { 90 | pciAddress addr; addr.bus = bus; addr.device = device; addr.function = 0; 91 | #ifdef MORE_DEBUG 92 | printf("enumerate device %02X:%02X...\n", bus, device); 93 | #endif 94 | // test for device presence 95 | if ((configReadWord(addr, 0) == 0xFFFF) || (ctx.list == NULL) || (ctx.listSize == 0)) return false; 96 | 97 | // check for multifunction 98 | if (configReadByte(addr, 0xE) & 0x80) { 99 | // enumerate subfunctions 100 | for (addr.function = 0; addr.function < 8; addr.function++) enumerateFunction(ctx, addr); 101 | } else 102 | // enumerate single device 103 | enumerateFunction(ctx, addr); 104 | 105 | return true; 106 | } 107 | 108 | bool tinypci::enumerateFunction(enumContext& ctx, pciAddress addr) { 109 | #ifdef MORE_DEBUG 110 | printf("enumerate function %02X:%02X.%01X...\n", addr.bus, addr.device, addr.function); 111 | #endif 112 | // test for device presence 113 | if ((configReadWord(addr, 0) == 0xFFFF) || (ctx.list == NULL) || (ctx.listSize == 0)) return false; 114 | 115 | // prefill list info 116 | pciDeviceList list; 117 | list.address = addr; 118 | list.vendorId = configReadWord(addr, 0x0); 119 | list.deviceId = configReadWord(addr, 0x2); 120 | list.deviceClass.baseClass = configReadByte(addr, 0xB); 121 | list.deviceClass.subClass = configReadByte(addr, 0xA); 122 | list.deviceClass.progInterface = configReadByte(addr, 0x9); 123 | list.headerType = configReadByte(addr, 0xE); 124 | 125 | // check if PCI-PCI bridge 126 | if ((list.deviceClass.baseClass == 0x06) && (list.deviceClass.subClass == 0x04) && ((list.headerType & 0x7F) == 0x1)) { 127 | // enum secondary bus 128 | enumerateBus(ctx, configReadByte(addr, 0x19)); 129 | } 130 | 131 | #ifdef MORE_DEBUG 132 | printf("%x %x %x %x\n", ctx.listInfo.address.addr, ctx.listInfo.deviceClass.val, ctx.listInfo.deviceId, ctx.listInfo.vendorId); 133 | printf("%x %x %x %x\n", list.address.addr, list.deviceClass.val, list.deviceId, list.vendorId); 134 | printf("%x %x %x\n", ctx.listInfo.address.bus, ctx.listInfo.address.device, ctx.listInfo.address.function); 135 | #endif 136 | 137 | // filter by ctx criterias 138 | if ((ctx.listInfo.address.bus != 0xFF) && (ctx.listInfo.address.bus != list.address.bus)) return false; 139 | if ((ctx.listInfo.address.device != 0xFF) && (ctx.listInfo.address.device != list.address.device)) return false; 140 | if ((ctx.listInfo.address.function != 0xFF) && (ctx.listInfo.address.function != list.address.function)) return false; 141 | 142 | if ((ctx.listInfo.vendorId != 0xFFFF) && (ctx.listInfo.vendorId != list.vendorId)) return false; 143 | if ((ctx.listInfo.deviceId != 0xFFFF) && (ctx.listInfo.deviceId != list.deviceId)) return false; 144 | 145 | if ((ctx.listInfo.deviceClass.baseClass != 0xFF) && (ctx.listInfo.deviceClass.baseClass != list.deviceClass.baseClass)) return false; 146 | if ((ctx.listInfo.deviceClass.subClass != 0xFF) && (ctx.listInfo.deviceClass.subClass != list.deviceClass.subClass)) return false; 147 | if ((ctx.listInfo.deviceClass.progInterface != 0xFF) && (ctx.listInfo.deviceClass.progInterface != list.deviceClass.progInterface)) return false; 148 | 149 | // if passed the checks - put info to the list 150 | list.interruptLine = configReadByte (addr, 0x3C); 151 | list.bar0 = configReadDword(addr, 0x10); 152 | memcpy(ctx.list, &list, sizeof(list)); ctx.list++; ctx.listSize--; 153 | 154 | #ifdef MORE_DEBUG 155 | printf("ok\n"); 156 | #endif 157 | 158 | return true; 159 | } 160 | 161 | uint32_t tinypci::pciAddrToCF8(pciAddress addr, uint32_t index) { 162 | return 0x80000000 | ((addr.function & 0x7) << 8) | ((addr.device & 0x1F) << 11) | ((addr.bus & 0xFF) << 16) | (index & 0xFC) | ((index & 0x300) << 16); 163 | } 164 | 165 | // don't inline to prevent timing conflicts 166 | uint8_t tinypci::configReadByte(pciAddress addr, uint32_t index) 167 | { 168 | outpd(0xCF8, pciAddrToCF8(addr, index)); 169 | return (inpd(0xCFC) >> ((index & 3) << 3)) & 0xFF; 170 | } 171 | 172 | uint16_t tinypci::configReadWord(pciAddress addr, uint32_t index) 173 | { 174 | outpd(0xCF8, pciAddrToCF8(addr, index)); 175 | return (inpd(0xCFC) >> ((index & 2) << 3)) & 0xFFFF; 176 | } 177 | 178 | uint32_t tinypci::configReadDword(pciAddress addr, uint32_t index) 179 | { 180 | outpd(0xCF8, pciAddrToCF8(addr, index)); 181 | return inpd(0xCFC); 182 | } 183 | 184 | void tinypci::configWriteByte(pciAddress addr, uint32_t index, uint8_t data) 185 | { 186 | outpd(0xCF8, pciAddrToCF8(addr, index)); 187 | 188 | // read/modify/write 189 | uint32_t shift = (index & 3) << 3; 190 | uint32_t mask = 0xFF << shift; 191 | outpd(0xCFC, (inpd(0xCFC) & ~mask) | ((data << shift) & mask)); 192 | } 193 | 194 | void tinypci::configWriteWord(pciAddress addr, uint32_t index, uint16_t data) 195 | { 196 | outpd(0xCF8, pciAddrToCF8(addr, index)); 197 | 198 | // read/modify/write 199 | uint32_t shift = (index & 2) << 3; 200 | uint32_t mask = 0xFFFF << shift; 201 | outpd(0xCFC, (inpd(0xCFC) & ~mask) | ((data << shift) & mask)); 202 | } 203 | 204 | void tinypci::configWriteDword(pciAddress addr, uint32_t index, uint32_t data) 205 | { 206 | outpd(0xCF8, pciAddrToCF8(addr, index)); 207 | outpd(0xCFC, data); 208 | } 209 | -------------------------------------------------------------------------------- /tinypci.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | union pciAddress { 6 | struct { 7 | uint8_t function; 8 | uint8_t device; 9 | uint8_t bus; 10 | uint8_t dummy; 11 | }; 12 | uint32_t addr; 13 | }; 14 | 15 | union pciClass { 16 | struct { 17 | uint8_t revision; 18 | uint8_t progInterface; 19 | uint8_t subClass; 20 | uint8_t baseClass; 21 | }; 22 | uint32_t val; 23 | }; 24 | 25 | struct pciDeviceList { 26 | pciAddress address; 27 | pciClass deviceClass; 28 | 29 | // only the essential info stored here 30 | uint16_t vendorId, deviceId; 31 | uint32_t bar0; 32 | uint8_t headerType; 33 | uint8_t interruptLine; 34 | }; 35 | 36 | // full info with first 64 bytes of configuration space cached 37 | struct pciDeviceInfo { 38 | pciAddress address; 39 | union { 40 | 41 | struct { 42 | uint16_t vendorId; 43 | uint16_t deviceId; 44 | 45 | struct { 46 | uint16_t ioSpace : 1; 47 | uint16_t memSpace : 1; 48 | uint16_t busMaster : 1; 49 | uint16_t specialCycles : 1; 50 | uint16_t memWriteInvEnable : 1; 51 | uint16_t vgaSnoop : 1; 52 | uint16_t parityCheck : 1; 53 | uint16_t reserved1 : 1; 54 | uint16_t serrEnable : 1; 55 | uint16_t fastb2bEnable : 1; 56 | uint16_t intDisable : 1; 57 | uint16_t reserved2 : 5; 58 | } command; 59 | 60 | struct { 61 | uint16_t reserved1 : 3; 62 | uint16_t intStatus : 1; 63 | uint16_t capsList : 1; 64 | uint16_t cap66Mhz : 1; 65 | uint16_t reserved2 : 1; 66 | uint16_t capFastb2b : 1; 67 | uint16_t masterParityError : 1; 68 | uint16_t devselTiming : 2; 69 | uint16_t targetAbortSignaled : 1; 70 | uint16_t targetAbortRecieved : 1; 71 | uint16_t masterAbortRecieved : 1; 72 | uint16_t systemErrorSignaled : 1; 73 | uint16_t parityError : 1; 74 | } status; 75 | 76 | uint8_t revision; 77 | uint8_t progInterface; 78 | uint8_t subClass; 79 | uint8_t baseClass; 80 | 81 | uint8_t cacheLineSize; 82 | uint8_t latencyTimer; 83 | uint8_t headerType; // bit 7 - multifunctional device 84 | uint8_t bist; 85 | 86 | uint32_t bar[6]; 87 | uint32_t cardbusCIS; 88 | 89 | uint16_t subsysVendorId; 90 | uint16_t subsysDeviceId; 91 | 92 | uint32_t romBase; 93 | uint8_t capsPointer; 94 | uint8_t reserved[7]; 95 | 96 | uint8_t intLine; 97 | uint8_t intPin; 98 | uint8_t minGnt; 99 | uint8_t maxLat; 100 | } header; 101 | 102 | // only first 64 bytes of config space are stored here 103 | uint8_t cfg8[64]; 104 | uint16_t cfg16[32]; 105 | uint32_t cfg32[16]; 106 | }; 107 | }; 108 | 109 | namespace tinypci { 110 | // ------------------------ 111 | // enumeration methods, all return number of devices found and recorded to list 112 | 113 | // enumerate all devices 114 | uint32_t enumerateAll(pciDeviceList *list, uint32_t listSize); 115 | 116 | // enumerate by address 117 | uint32_t enumerateByAddress(pciDeviceList *list, uint32_t listSize, pciAddress addr); 118 | 119 | // enumerate by class 120 | uint32_t enumerateByClass(pciDeviceList *list, uint32_t listSize, pciClass devclass); 121 | 122 | // enumerate by vendor/device id 123 | uint32_t enumerateByDeviceId(pciDeviceList *list, uint32_t listSize, uint32_t vendorId, uint32_t deviceId = -1); 124 | 125 | // enumerate by all, don't care fields set to -1 126 | uint32_t enumerate(pciDeviceList *list, uint32_t listSize, pciDeviceList& listInfo); 127 | 128 | // read config space byte/word/dword 129 | uint8_t configReadByte (pciAddress addr, uint32_t index); 130 | uint16_t configReadWord (pciAddress addr, uint32_t index); 131 | uint32_t configReadDword(pciAddress addr, uint32_t index); 132 | 133 | // write config space byte/word/dword 134 | void configWriteByte (pciAddress addr, uint32_t index, uint8_t data); 135 | void configWriteWord (pciAddress addr, uint32_t index, uint16_t data); 136 | void configWriteDword(pciAddress addr, uint32_t index, uint32_t data); 137 | 138 | }; 139 | 140 | --------------------------------------------------------------------------------