'--------------------------------------------------------------------------- ' DMA Play 6.0 Beta Build 11.10.98: released for beta testing ONLY! ' a versatile DMA-based sound (.WAV) file player in QBasic and QB45. ' by Mike Huff (v1), Martin Rampersad (v2), Toshi Horie (v3,4,5,6) ' Realtime 16-bit to 8-bit stereo/mono conversions added up to 44Khz ' Realtime 16-bit and 8-bit mixing possible on Pentium 120Mhz+. ' Program downloaded from http://www.ocf.berkeley.edu/~horie/project.html ' ------------- programming notes ---------------- ' Thanks: * Ethan Brodsky, who made DMAPLAY4+ possible. ' * Angelo Mottola for his support and testing on SB16. ' * Michael Sorensen for his brilliant ideas and feedback. ' Features: * 8-bit and 16-bit 2 channel mixing. ' * Tested with an SB Pro and SB AWE32. ' * simplified calling scheme: PlayWave Filename$ ' * new 8-bit 'generic DMA' handler for SB16. ' * bugfix: Now all files tested finish playing. ' Future Plans* complete 8 channel mixer routine ' * EMS sound buffer swapping support ' * interrupt and autoinit mode support (thanks to Michael) ' Legal: * Toshihiro Horie will not be liable for any damages ' resulting from the use or misuse of this program. '--------------------------------------------------------------------------- TYPE WaveInfoType StereoWav AS INTEGER Freq AS LONG Length AS LONG playtime AS DOUBLE sixteenbit AS INTEGER END TYPE TYPE WaveHeaderType RiffID AS STRING * 4 'should be 'RIFF' RiffLength AS LONG 'rept. chunk id and size then chunk data WavID AS STRING * 4 'should be 'WAVE' FmtID AS STRING * 4 FmtLength AS LONG 'FMT ' chunk - common fields wavformattag AS INTEGER ' word - format category e.g. 0x0001=PCM Channels AS INTEGER ' word - number of Channels 1=mono 2=stereo SamplesPerSec AS LONG 'dword - sampling rate e.g. 44100Hz avgBytesPerSec AS LONG 'dword - to estimate buffer size blockalign AS INTEGER ' word - buffer size must be int. multiple of this FmtSpecific AS INTEGER ' word DataID AS STRING * 4 DataLength AS LONG END TYPE DECLARE SUB InitMixer8bit () DECLARE SUB Convert2FAST (Bseg%, Boff&, L%) DECLARE SUB InitConvert2FAST () DECLARE SUB PlayWave (Filename$) DECLARE SUB ConvertStereo (Freq&, Bseg%, Boff%, L%) DECLARE SUB Convert1 (Bseg%, Boff%, L%) DECLARE SUB Convert2 (Bseg%, Boff&, L%) DECLARE SUB Convert4 (Bseg%, Boff%, L%) DECLARE SUB Convert5 (Bseg%, Boff%, L%) DECLARE SUB Convert6 (Bseg%, Boff&, L%) DECLARE SUB Convert7 (Bseg%, Boff&, L%) DECLARE SUB Mix2Buffers (Bseg%, Boff&, Bseg2%, Boff2&, L%, sixteenbit%) DECLARE SUB Play2Waves (File1$, File2$) DECLARE SUB getWaveInfo () DECLARE FUNCTION DEC2HEX$ (longnum&) DECLARE FUNCTION int2ULong& (signedint%) DECLARE FUNCTION Ulong2int% (Ulong&) DECLARE FUNCTION SpeakerStatus% () DECLARE FUNCTION DMAStatus% () DECLARE FUNCTION DMADone% (DMA16%, L&) DECLARE FUNCTION ResetDSP% () DECLARE FUNCTION ReadDSP% () DECLARE FUNCTION ReadDAC% () DECLARE SUB FMVolume (Right%, Left%, Getvol%) DECLARE SUB VocVolume (Right%, Left%, Getvol%) DECLARE SUB MasterVolume (Right%, Left%, Getvol%) DECLARE SUB MicVolume (Gain%, Getvol%) DECLARE SUB LineVolume (Right%, Left%, Getvol%) DECLARE SUB CDVolume (Right%, Left%, Getvol%) DECLARE SUB InputSource (InputSrc%, GetSrc%) DECLARE SUB WriteDSP (byte%) DECLARE SUB SetStereo (OnOff%) DECLARE SUB WriteDAC (byte%) DECLARE SUB SpeakerState (OnOff%) DECLARE SUB DMAState (StopGo%) DECLARE SUB DMAPlay (Segment&, offset&, Length&, Freq&, StereoWav%) DECLARE SUB DMAPlay16 (Segment&, offset&, L&, Freq&, StereoWav%, sixteenbit%) DECLARE SUB DMARecord (Segment&, offset&, Length&, Freq&) DECLARE SUB GetBLASTER () DECLARE FUNCTION DSPVersion! () COMMON SHARED Baseport%, LenPort%, DMA%, DMA16%, cardversion% '$DYNAMIC: 'needed for compilation in QB45 Playerversion$ = "DMAPlay 6.0b" DIM SHARED Wave(0) AS WaveHeaderType DIM SHARED WaveInfo AS WaveInfoType DIM SHARED MixRoutine%(29) InitMixer8bit InitConvert2FAST SCREEN 0: CLS 'WIDTH 80, 50 PRINT Playerversion$ PRINT "By Mike Huff (SB, SBPro) and Toshi Horie (SB16, SBPro realtime conv/mixing)" PRINT "Modified By Martin Rampersad (To play entire file instead of first 32k)" PRINT "Comments, etc. can be sent to MHuff@gnn.com or to Martin_Rampersad@juno.com" Baseport% = &H220: IRQ% = 5: DMA% = 1: 'default GetBLASTER ' Parses BLASTER environment PRINT STRING$(80, 196) SpeakerState 1 MasterVolume 12, 12, 0 'VocVolume 12, 12, 0 MasterVolume Right%, Left%, 1 PRINT "Master volume is set at: Right-"; Right%; " Left-"; Left% 'WavBuffer size MUST be divisible by 4 for stereo files CONST blocklen = 32760 'create 32K buffer DIM SHARED WavBuffer(0 TO 1) AS STRING * blocklen CONST HeaderSize = 45 'assume .WAV file (use 45 because 1st byte ' is at offset 1 in QB.) 'I think it's 32 for VOC, 0 for RAW files. 'In order to properly play .VOC, .SAM, etc 'you need to modify SUB getWaveInfo() 'but you can bypass that by setting 'the default frequency, stereo, and 16-bit 'characteristics of the file in PlayWave. PlayWave "C:\WINDOWS\MEDIA\THEMIC~1.WAV" 'Play2Waves "C:\SOUND\CD_AUDIO\STEREO.WAV", "C:\SOUND\CD_AUDIO\S4416.WAV" CLOSE END REM $STATIC SUB CDVolume (Right%, Left%, Getvol%) OUT Baseport% + 4, &H28 IF Getvol% THEN Left% = INP(Baseport% + 5) \ 16 Right% = INP(Baseport% + 5) AND &HF EXIT SUB ELSE OUT Baseport% + 5, (Right% + Left% * 16) AND &HFF END IF END SUB SUB Convert1 (Bseg%, Boff%, L%) 'converts 16 bit signed stereo buffer 'to an 8 bit unsigned stereo buffer in place 'slower original routine 'PRE: BSeg:BOff points to a array buffer of length L& chars 'POST: L&\2 of the buffer is converted to 8bit, rest is 16-bit garbage DEF SEG = Bseg% FOR i = 0 TO L% - 1 STEP 2 Addr = i + Boff% lowbyte% = PEEK(Addr) hibyte% = PEEK(Addr + 1) v& = hibyte% * 256& + lowbyte% IF (v& AND &H8000) THEN v& = v& + &H8000 vi% = v& \ 256 ELSE vi% = v& \ 256 + &H80 END IF POKE Boff% + i \ 2, vi% NEXT END SUB SUB Convert2 (Bseg%, Boff&, L%) 'converts 44KHz stereo to 22Khz stereo 'optimized conversion routine playing every other sample. 'used for 22Khz or higher stereo waves on SBPro 'no more long integer calculations DEF SEG = Bseg% FOR i = 0 TO L% - 1 STEP 8 'LEFT CHANNEL Addy = i + Boff& hibyte% = PEEK(Addy + 1) 'i+1 POKE Boff% + i \ 4, hibyte% XOR &H80 'RIGHT CHANNEL hibyte% = PEEK(Addy + 3) 'i+4 POKE Boff& + i \ 4 + 1, hibyte% XOR &H80 NEXT END SUB SUB Convert2FAST (Bseg%, Boff&, L%) 'blazingly fast 16-bit to 8-bit conversion routine 'by Angelo Mottola. Wow, it's 40x faster than the QB4.5 version. '**************************************************************************** ' This is how to call the asm routine... ' The "L" parameter does not need to be decreased! DEF SEG = VARSEG(AsM$) CALL ABSOLUTE(BYVAL Bseg%, BYVAL Boff%, BYVAL L%, SADD(AsM$)) DEF SEG ' End of assembly call; now the array is filled with the converted values! END SUB SUB Convert3 (Bseg&, Boff&, L&) 'converts 16-bit 44Khz stereo to 8-bit 44Khz mono for SBPro 'not working yet! FOR i = 0 TO L& - 1 STEP 4 Addr = i + Boff& 'Left=Left>>2 Leftlo% = PEEK(Addr) \ 2 Lefthi% = PEEK(Addr + 1) \ 2 'Right=Right>>2 Rightlo% = PEEK(Addr + 2) \ 2 Righthi% = PEEK(Addr + 3) \ 2 mix& = Lefthi% * 256 + Leftlo% mix& = mix& + Righthi% * 256 + Rightlo% mixhi% = (mix& AND &HFF00) \ &H100 mixlo% = (mix& AND &HFF) POKE Boff& + i \ 4, mixlo% POKE Boff& + i \ 4 + 1, mixhi% NEXT i END SUB SUB Convert4 (Bseg%, Boff%, L%) 'convert 16 bit mono to 8 bit mono DEF SEG = Bseg% FOR i = 0 TO L% - 1 STEP 2 Addr = i + Boff% hibyte% = PEEK(Addr + 1) 'i+1 POKE Boff& + i \ 2, hibyte% + &H80 NEXT END SUB SUB Convert5 (Bseg%, Boff%, L%) 'takes 0L 0R 1L 1R 2L 2R 3L 3R 4L 4R... ' to 0L 0R 2L 2R 4L 4R... DEF SEG = Bseg% FOR i = 4 TO L% - 1 STEP 4 vleft = PEEK(i) vright = PEEK(i + 1) POKE i \ 2, vleft POKE i \ 2 + 1, vright NEXT i END SUB SUB Convert6 (Bseg%, Boff&, L%) 'finally a 44Khz 16bit stereo to 11Khz 8bit mono mixer for SB2.0/1.0 DEF SEG = Bseg% FOR i = 0 TO L% - 1 STEP 16 'LEFT CHANNEL Addy = i + Boff& hibyteL% = PEEK(Addy + 1) 'i+1 hibyteR% = PEEK(Addy + 3) 'i+4 v = ((hibyteR% XOR &H80) + (hibyteL% XOR &H80)) \ 2 POKE Boff% + i \ 16, v NEXT END SUB SUB Convert7 (Bseg%, Boff&, L%) 'finally a 44Khz 8bit stereo to 11Khz 8bit mono mixer for SB2.0/1.0 DEF SEG = Bseg% FOR i = 0 TO L% - 1 STEP 16 Addy = i + Boff& byteL% = PEEK(Addy) byteR% = PEEK(Addy + 1) v = (byteR% + byteL%) \ 2 POKE Boff% + i \ 16, v NEXT END SUB SUB ConvertStereo (Freq&, Bseg%, Boff%, L%) IF Freq& > 22050 THEN Convert2 Bseg%, Boff&, L% ELSE 'shouldn't need it unless using sb1.x-2.x 'Convert2 Bseg%, Boff&, L% END IF END SUB FUNCTION DEC2HEX$ (longnum&) DEC2HEX$ = HEX$(longnum&) END FUNCTION FUNCTION DMADone% (DMA16%, L&) countlo% = INP(LenPort%) counthi% = INP(LenPort%) IF counthi% = 255 AND countlo% = 255 THEN IF DMA16% THEN ack16% = INP(Baseport% + &HF) 'ack to SB 'OUT &HA0, &H20 'acknowledge SB interrupt 8-15 'OUT &H20, &H20 'acknowledge SB interrupt 1-15 ELSE ack% = INP(Baseport% + &HE) 'OUT &H20, &H20 'acknowledge SB interrupt 1-15 END IF DMADone% = -1 'SOUND 300, .4 END IF END FUNCTION SUB DMAPlay (Segment&, offset&, Length&, Freq&, StereoWav%) ' Transfers and plays the contents of the buffer. 'IF Freq& < 4000 THEN PRINT "Freq too low!": END 'LOCATE 21, 1 'SELECT CASE cardversion% ' CASE 1: ' IF Freq& > 11111 THEN PRINT "SB1.0 Freq"; Freq&; "too high!": END ' CASE 2: ' IF Freq& > 15151 THEN PRINT "SB2.0 Freq"; Freq&; " too high!": END ' CASE IS > 2: ' IF Freq& > 45454 THEN PRINT "SBPro Freq"; Freq&; " too high!": END 'END SELECT Length& = Length& - 1 page% = 0 Addr& = Segment& * 16 + offset& 'StartPage& = Addr& \ &H10000 'EndPage& = (Addr& + L&) \ &H10000 'IF StartPage& <> EndPage& THEN PRINT "Unable to allocate memory for DMA transfer.": STOP SELECT CASE DMA% CASE 0 PgPort% = &H87 AddPort% = &H0 LenPort% = &H1 ModeReg% = &H48 CASE 1 PgPort% = &H83 AddPort% = &H2 LenPort% = &H3 ModeReg% = &H49 CASE 2 PgPort% = &H81 AddPort% = &H4 LenPort% = &H5 ModeReg% = &H4A CASE 3 PgPort% = &H82 AddPort% = &H6 LenPort% = &H7 ModeReg% = &H4B CASE ELSE PRINT "8-bit DMA channels 0-3 only!": END EXIT SUB END SELECT Lengthlo% = Length& AND &HFF Lengthhi% = (Length& AND &HFF00&) \ &H100 IF StereoWav% AND cardversion% = 3 THEN SetStereo 1 OUT &HA, &H4 + DMA%: 'DMA channel to use (DRQ#) OUT &HC, &H0 OUT &HB, ModeReg% OUT AddPort%, Addr& AND &HFF: 'buffer address of sound data low byte OUT AddPort%, (Addr& AND &HFF00&) \ &H100: 'high byte IF (Addr& AND 65536) THEN page% = page% + 1: '64K pages for 8-bit DMA IF (Addr& AND 131072) THEN page% = page% + 2 IF (Addr& AND 262144) THEN page% = page% + 4 IF (Addr& AND 524288) THEN page% = page% + 8 OUT PgPort%, page%: 'output page of phys. addr of sample block OUT LenPort%, Lengthlo%: 'size of block to DMA controller -Low OUT LenPort%, Lengthhi%: 'high byte OUT &HA, DMA%: 'release DMA channel 'LOCATE 21, 1: PRINT "seg:"; DEC2HEX$(Segment&), 'PRINT "offset:"; DEC2HEX$(Offset&), "addr:"; DEC2HEX$(addr&) TimeConst% = 256 - 1000000 \ Freq& IF Freq& < 22728 THEN WriteDSP &H40 WriteDSP TimeConst% WriteDSP &H14: '8 bit output over DMA WriteDSP (Length& AND &HFF) WriteDSP ((Length& AND &HFFFF&) \ &H100) ELSE 'SBPro (DSP version 3.x) can play 8-bit mono/stereo wave files IF cardversion% > 2 THEN 'high speed 8 bit output up to 44kHz mono or 22Khz stereo WriteDSP &H40: 'output sampling rate const WriteDSP TimeConst% WriteDSP &H48 WriteDSP Lengthlo% WriteDSP Lengthhi% WriteDSP &H91 ELSE PRINT "Current Freq&="; Freq& PRINT "You need a Sound Blaster Pro to play at 8 bit high speed.": END END IF END IF END SUB SUB DMAPlay16 (Segment&, offset&, L&, Freq&, StereoWav%, sixteenbit%) ' Transfers and plays the contents of the buffer. ' Try only on an SoundBlaster 16 !! ' 1 page=128K in 16 bit mode ' DMA16% (16-bit DMA channel) passed implicitly IF cardversion% < 4 THEN PRINT "You need an SB16 for this mode!": END L& = L& - 1: page% = 0 Addr& = Segment& * 16 + offset& 'StartPage& = Addr& \ &H10000 'EndPage& = (Addr& + L&) \ &H10000 'IF StartPage& <> EndPage& THEN PRINT "Unable to allocate memory for DMA transfer.": STOP IF sixteenbit% THEN SELECT CASE DMA16% CASE 4 PgPort% = &H0 AddPort% = &HC0 LenPort% = &HC2 ModeReg% = &H48: '58h for autoinit/48h for not CASE 5 PgPort% = &H8B AddPort% = &HC4 LenPort% = &HC6 ModeReg% = &H49 CASE 6 PgPort% = &H89 AddPort% = &HC8 LenPort% = &HCA ModeReg% = &H4A CASE 7 PgPort% = &H8A 'ok AddPort% = &HCC 'ok LenPort% = &HCE 'ok ModeReg% = &H4B 'ok CASE ELSE PRINT "16 bit DMA channels 4-7 only!" EXIT SUB END SELECT page% = (Addr& \ 131072) * 2 Offset2& = (Addr& - (page% * 65536)) \ 2 Lengthlo% = ((L& \ 2) AND &HFF): 'number of words-1 Lengthhi% = (((L& \ 2) AND &HFF00&) \ &H100) 'LOCATE 21, 1: PRINT "seg:"; DEC2HEX$(Segment&), 'PRINT "offset:"; DEC2HEX$(Offset&), "addr:"; DEC2HEX$(addr&) OUT &HD8, 0: 'clear flip flop OUT &HD6, ModeReg%: 'write mode reg OUT AddPort%, (Offset2& AND &HFF): 'Buffer base offset lo OUT AddPort%, (Offset2& AND &HFF00&) \ &H100: 'Buffer base offset hi OUT PgPort%, page%: 'output page of phys. addr of sample block OUT LenPort%, Lengthlo%: 'DMA count = length of buffer OUT LenPort%, Lengthhi%: 'DMA count high byte OUT &HD4, DMA16% - 4: 'write single mask (select Channel16) ELSE SELECT CASE DMA% CASE 0 PgPort% = &H87 AddPort% = &H0 LenPort% = &H1 ModeReg% = &H48 CASE 1 PgPort% = &H83 AddPort% = &H2 LenPort% = &H3 ModeReg% = &H49 CASE 2 PgPort% = &H81 AddPort% = &H4 LenPort% = &H5 ModeReg% = &H4A CASE 3 PgPort% = &H82 AddPort% = &H6 LenPort% = &H7 ModeReg% = &H4B CASE ELSE PRINT "8-bit DMA channels 0-3 only!": END EXIT SUB END SELECT Lengthlo% = L& AND &HFF Lengthhi% = (L& AND &HFF00&) \ &H100 OUT &HA, &H4 + DMA%: 'DMA channel to use (DRQ#) OUT &HC, &H0 OUT &HB, ModeReg% OUT AddPort%, Addr& AND &HFF: 'buffer address of sound data low byte OUT AddPort%, (Addr& AND &HFF00&) \ &H100: 'high byte page% = (Addr& \ 65536) '64K pages for 8bit output OUT PgPort%, page%: 'output page of phys. addr of sample block OUT LenPort%, Lengthlo%: 'size of block to DMA controller -Low OUT LenPort%, Lengthhi%: 'high byte OUT &HA, DMA%: 'release DMA channel END IF FreqHi% = (Freq& AND &HFF00&) \ &H100 FreqLo% = Freq& AND &HFF WriteDSP &H41: 'set output sampling rate WriteDSP FreqHi% WriteDSP FreqLo% IF sixteenbit% THEN WriteDSP &HB0: '16 bit DAC, single cycle, FIFO off ELSE WriteDSP &HC0: '8 bit DAC, single cycle, FIFO off END IF IF sixteenbit% THEN IF StereoWav% THEN 'subtract 10h for unsigned WriteDSP &H30: '30h=Mode byte for 16 bit signed stereo ELSE WriteDSP &H10: '10h=Mode byte for 16 bit signed mono END IF ELSE IF StereoWav% THEN 'subtract 10h for unsigned WriteDSP &H20: '20h=Mode byte for 8 bit unsigned stereo ELSE WriteDSP &H0: '0h=Mode byte for 8 bit unsigned mono END IF END IF WriteDSP Lengthlo% WriteDSP Lengthhi% END SUB SUB DMARecord (Segment&, offset&, Length&, Freq&) Length& = Length& - 1 memloc& = Segment& * 16 + offset& page% = 0 SELECT CASE DMA% CASE 0 PgPort% = &H87 AddPort% = &H0 LenPort% = &H1 ModeReg% = &H44 CASE 1 PgPort% = &H83 AddPort% = &H2 LenPort% = &H3 ModeReg% = &H45 CASE 2 PgPort% = &H81 AddPort% = &H4 LenPort% = &H5 ModeReg% = &H46 CASE 3 PgPort% = &H82 AddPort% = &H6 LenPort% = &H7 ModeReg% = &H47 CASE ELSE EXIT SUB END SELECT OUT &HA, &H4 + DMA% OUT &HC, &H0 OUT &HB, ModeReg% OUT AddPort%, memloc& AND &HFF OUT AddPort%, (memloc& AND &HFFFF&) \ &H100 IF (LongByte& AND 65536) THEN page% = page% + 1 IF (LongByte& AND 131072) THEN page% = page% + 2 IF (LongByte& AND 262144) THEN page% = page% + 4 IF (LongByte& AND 524288) THEN page% = page% + 8 OUT PgPort%, page% OUT LenPort%, Length& AND &HFF OUT LenPort%, (Length& AND &HFFFF&) \ &H100 OUT &HA, DMA% IF Freq& <= 23000 THEN TimeConst% = 256 - 1000000 \ Freq& WriteDSP &H40 WriteDSP TimeConst% WriteDSP &H24 WriteDSP (Length& AND &HFF) WriteDSP ((Length& AND &HFFFF&) \ &H100) ELSE IF DSPVersion! >= 3 THEN TimeConst% = ((65536 - 256000000 / Freq&) AND &HFFFF&) \ &H100 WriteDSP &H40 WriteDSP TimeConst% WriteDSP (Length& AND &HFF) WriteDSP ((Length& AND &HFFFF&) \ &H100) WriteDSP &H99 ELSE PRINT "You need a Sound Blaster with a DSP 3.x+ to record at high speed." EXIT SUB END IF END IF END SUB SUB DMAState (StopGo%) ' Stops or continues DMA play. IF StopGo% THEN WriteDSP &HD4 ELSE WriteDSP &HD0 END SUB FUNCTION DSPVersion! ' Gets the DSP version. WriteDSP &HE1 Temp% = ReadDSP% temp2% = ReadDSP% IF temp2% < 10 THEN DSPVersion! = VAL(STR$(Temp%) + ".0" + STR$(temp2%)) ELSE DSPVersion! = VAL(STR$(Temp%) + "." + STR$(temp2%)) END IF 'MODEL VERSION 'SB 1.0 1.?? (1.05???, err=2.00) 'SB 1.5 1.?? (1.05???) 'SB 2.0 2.xx (2.01) 'SB Pro 3.00 (???) 'SB Pro 2 3.01+ (3.01, 3.02) 'SB 16 4.0x (4.04, 4.05) 'SB 16 SCSI-2 4.11 (4.11) 'SB AWE 32 4.12+ (4.12) END FUNCTION SUB FMVolume (Right%, Left%, Getvol%) OUT Baseport% + 4, &H26 IF Getvol% THEN Left% = INP(Baseport% + 5) \ 16 Right% = INP(Baseport% + 5) AND &HF EXIT SUB ELSE OUT Baseport% + 5, (Right% + Left% * 16) AND &HFF END IF END SUB SUB GetBLASTER ' This subroutine parses the BLASTER environment string ' and returns the sound card settings ' implicitly using COMMON variables Baseport%, DMA%, DMA16% blaster$ = ENVIRON$("BLASTER") IF LEN(blaster$) = 0 THEN PRINT "BLASTER environment variable not set." INPUT "Would you like to try the defaults? "; ck$ IF ck$ = "Y" OR ck$ = "y" THEN blaster$ = "A220 I5 D1 H5" ELSE PRINT "Goodbye." END END IF ELSE FOR index% = 1 TO LEN(blaster$) SELECT CASE MID$(UCASE$(blaster$), index%, 1) CASE "A" Baseport% = VAL("&H" + MID$(blaster$, index% + 1, 3)) CASE "I" IRQ% = VAL(MID$(blaster$, index% + 1, 1)) CASE "D" DMA% = VAL(MID$(blaster$, index% + 1, 1)) CASE "H" DMA16% = VAL(MID$(blaster$, index% + 1, 1)) END SELECT NEXT END IF IF ResetDSP% = 0 THEN 'resets DSP (returns true if sucessful) PRINT "Sound card NOT found at " + HEX$(Baseport%) + "H." PRINT "Either your card is not SB-compatible or it is set up wrong." END END IF PRINT "Sound Card DSP version:"; DSPVersion! cardversion% = INT(DSPVersion!) END SUB SUB getWaveInfo 'PRE: Wave(0) array filled from WAV file header 'POST: WaveInfo fields set IF UCASE$(Wave(0).RiffID) <> "RIFF" THEN PRINT "NOT A RIFF FMT FILE": END IF UCASE$(Wave(0).WavID) <> "WAVE" THEN PRINT "NOT A Microsoft WAVE FILE": END IF Wave(0).wavformattag <> 1 THEN PRINT "Not PCM format": END PRINT "Channels:"; Wave(0).Channels; WaveInfo.Freq = Wave(0).SamplesPerSec SELECT CASE Wave(0).Channels CASE 2 WaveInfo.StereoWav% = 1: PRINT "(Stereo)" CASE 1 WaveInfo.StereoWav% = 0: PRINT "(mono)" CASE ELSE WaveInfo.StereoWav% = -1: PRINT "no support for multichannel waves": END END SELECT PRINT "SamplesPerSec:"; WaveInfo.Freq& 'assume no weird sampling rate like 9bit/sec 'PRINT "should equal blockalign:"; (Wave(0).avgBytesPerSec / Freq&) PRINT "BlockAlign:"; Wave(0).blockalign IF (blocklen MOD Wave(0).blockalign) <> 0 THEN PRINT "Internal error: make blocklen=32752": END PRINT "FmtSpecific:"; Wave(0).FmtSpecific; "bits/sample" IF Wave(0).FmtSpecific = 16 THEN WaveInfo.sixteenbit = 1 'PRINT "DataID:"; Wave(0).DataID IF UCASE$(Wave(0).DataID) <> "DATA" THEN PRINT "Not Data chunk": END PRINT "DataLength:"; Wave(0).DataLength; "bytes" WaveInfo.Length& = Wave(0).DataLength WaveInfo.playtime# = WaveInfo.Length& / WaveInfo.Freq& / Wave(0).blockalign pmin = INT(WaveInfo.playtime#) \ 60 psec = INT(WaveInfo.playtime#) MOD 60 IF pmin > 0 THEN PRINT USING "Play Length: ##:"; pmin; PRINT USING "##"; psec ELSE PRINT USING "Play Length ##.##s"; WaveInfo.playtime# END IF 'PRINT "start of actual data:"; SEEK(1) END SUB SUB InitConvert2FAST AsM$ = "" AsM$ = AsM$ + CHR$(&H1E) ' PUSH DS AsM$ = AsM$ + CHR$(&H55) ' PUSH BP AsM$ = AsM$ + CHR$(&H89) + CHR$(&HE5) ' MOV BP,SP AsM$ = AsM$ + CHR$(&H8B) + CHR$(&H46) + CHR$(&HC) ' MOV AX,[BP+0C] AsM$ = AsM$ + CHR$(&H8E) + CHR$(&HD8) ' MOV DS,AX AsM$ = AsM$ + CHR$(&HBB) + CHR$(&H0) + CHR$(&H0) ' MOV BX,0000 AsM$ = AsM$ + CHR$(&H8B) + CHR$(&H76) + CHR$(&HA) ' Startloop: MOV SI,[BP+0A] AsM$ = AsM$ + CHR$(&H1) + CHR$(&HDE) ' ADD SI,BX AsM$ = AsM$ + CHR$(&H46) ' INC SI AsM$ = AsM$ + CHR$(&H8A) + CHR$(&H4) ' MOV AL,[SI] AsM$ = AsM$ + CHR$(&H34) + CHR$(&H80) ' XOR AL,80 AsM$ = AsM$ + CHR$(&H46) ' INC SI AsM$ = AsM$ + CHR$(&H46) ' INC SI AsM$ = AsM$ + CHR$(&H8A) + CHR$(&H24) ' MOV AH,[SI] AsM$ = AsM$ + CHR$(&H80) + CHR$(&HF4) + CHR$(&H80) ' XOR AH,80 AsM$ = AsM$ + CHR$(&H89) + CHR$(&HDF) ' MOV DI,BX AsM$ = AsM$ + CHR$(&HB1) + CHR$(&H2) ' MOV CL,02 AsM$ = AsM$ + CHR$(&HD3) + CHR$(&HEF) ' SHR DI,CL AsM$ = AsM$ + CHR$(&H3) + CHR$(&H7E) + CHR$(&HA) ' ADD DI,[BP+0A] AsM$ = AsM$ + CHR$(&H89) + CHR$(&H5) ' MOV [DI],AX AsM$ = AsM$ + CHR$(&H83) + CHR$(&HC3) + CHR$(&H8) ' ADD BX,+08 AsM$ = AsM$ + CHR$(&H3B) + CHR$(&H5E) + CHR$(&H8) ' CMP BX,[BP+08] AsM$ = AsM$ + CHR$(&H7C) + CHR$(&HDC) ' JL Startloop AsM$ = AsM$ + CHR$(&H5D) ' POP BP AsM$ = AsM$ + CHR$(&H1F) ' POP DS AsM$ = AsM$ + CHR$(&HCA) + CHR$(&H6) + CHR$(&H0) ' RETF 0006 END SUB DEFINT A-Z SUB InitMixer8bit 'programmed by Skywise 01/25/98 '8 bit mono wave files only 'CALL Absolute(BYVAL Bseg%, BYVAL off1%, BYVAL Bseg2%, BYVAL off2%, BYVAL L%, VARPTR(MixRoutine%(0))) ' [SP+10h] [sp+0E] [sp+0C] [sp+0A] [sp+08] [sp+06] ' ASSEMBLY INSTRUCTION : my interpretation a$ = "1E": 'push ds :save BASIC's DS = DEF SEG a$ = a$ + "55": 'push bp :save BASIC's BP a$ = a$ + "89" + "E5": 'mov bp,sp :bp=SP a$ = a$ + "8B" + "56" + "08": 'mov dx,[bp+08] :dx=L% a$ = a$ + "BB" + "00" + "00": 'mov bx,0000 :bx=0 a$ = a$ + "8C" + "DF": 'mov di,ds :di=DS 'LABEL 010C: a$ = a$ + "8B" + "46" + "0C": 'mov ax,[bp+0C] :ax=Bseg2% a$ = a$ + "8B" + "76" + "0A": 'mov si,[bp+0A] :si=off2% a$ = a$ + "01" + "DE": 'add si,bx :si=off2%+bx a$ = a$ + "8E" + "D8": 'mov ds,ax :DEF SEG=Bseg2% a$ = a$ + "8A" + "0C": 'mov cl,[si] :cl=Buffer2[off2%+c%] a$ = a$ + "8E" + "DF": 'mov ds,di :DEF SEG (to BASIC's codseg) a$ = a$ + "8B" + "46" + "10": 'mov ax,[bp+10] :ax=Bseg% a$ = a$ + "8B" + "76" + "0E": 'mov si,[bp+0E] :si=off1% a$ = a$ + "01" + "DE": 'add si,bx :si=off1% + c% a$ = a$ + "8E" + "D8": 'mov ds,ax :DEF SEG=Bseg% a$ = a$ + "8A" + "2C": 'mov ch,[si] :ch=sample1=Buffer1[off1%] a$ = a$ + "D0" + "ED": 'shr ch,1 :sample1=sample1 / 2 a$ = a$ + "D0" + "E9": 'shr cl,1 :sample2=sample2 / 2 a$ = a$ + "00" + "CD": 'add ch,cl :sample1=sample1+sample2 a$ = a$ + "88" + "2C": 'mov [si],ch :array[off1%]=sample1 a$ = a$ + "8E" + "DF": 'mov ds,di :DEF SEG (BASIC's codeseg) a$ = a$ + "43": 'inc bx :c%=c%+1 ' c% is bx a$ = a$ + "39" + "D3": 'cmp bx,dx :is bx 0 THEN PRINT "oops in mix2buffers": END IF sixteenbit% THEN '16 bit signed mixing 'sounds just as good in 8bit "cheat" mode on SBPro (clicks) 'this cheat mode can be converted to asm with only a few byte 'difference than the Mixer8bit code. change the inc bx to an add bx,2 'and adjust the jl 0106 and initially mov bx,0001. -from Toshi FOR i = 0 TO L% STEP 2 hibyte2% = PEEK(Boff2& + i + 1) hibyte1% = PEEK(i + 1) POKE i + 1, hibyte2% + hibyte1%: 'QB automatically casts to char NEXT 'real 16 bit mixing == SLOW because BASIC does not have unsigned ints 'FOR i = 0 TO L% STEP 2 ' hibyte2% = PEEK(Boff2& + i + 1) ' hibyte1% = PEEK(i + 1) ' lobyte2% = PEEK(Boff2& + i) ' lobyte1% = PEEK(i) ' v1 = Ulong2int(hibyte1% * 256& + lobyte1%) \ 2 ' v2 = Ulong2int(hibyte2% * 256& + lobyte2%) \ 2 ' v = v1 + v2 AND &HFFFF ' POKE i + 1, v \ 256 ' POKE i, v AND 255 'NEXT ELSE '8 bit unsigned mixing off1% = Ulong2int(Boff&) off2% = Ulong2int(Boff2&) 'OPEN "C:\QB45\mixer\MIXER.BIN" FOR BINARY AS #10 'GET #10, 1, MixRoutine 'CLOSE #10 DEF SEG = VARSEG(MixRoutine%(0)) CALL ABSOLUTE(BYVAL Bseg%, BYVAL off1%, BYVAL Bseg2%, BYVAL off2%, BYVAL L%, VARPTR(MixRoutine%(0))) DEF SEG 'QB version works fine too 'FOR i = 0 TO L% STEP 2 ' byte2a = PEEK(Boff2& + i) ' byte2b = PEEK(Boff2& + i + 1) ' byte1a = PEEK(i) ' byte1b = PEEK(i + 1) ' o1 = (byte1a + byte2a) \ 2 'For loud files, this sounds better ' o2 = (byte1b + byte2b) \ 2 'than adding samples then clipping. '' IF o1 AND 256 THEN o1 = 255 'clipping '' IF o2 AND 256 THEN o2 = 255 'clipping ' POKE i, o1 ' POKE i + 1, o2 'NEXT END IF END SUB SUB Play2Waves (File1$, File2$) 'this is so messy-- ugh. 'mixes two files in realtime 'files must be of the same type, sampling frequency, etc. 'likely to get some garbage on last sample filenum = FREEFILE OPEN File1$ FOR BINARY AS filenum filenum2 = FREEFILE OPEN File2$ FOR BINARY AS filenum2 IF LOF(filenum) = 0 THEN PRINT "**"; File1$; " doesn't exist.**" CLOSE : KILL File1$: END END IF IF LOF(filenum2) = 0 THEN PRINT "**"; File2$; " doesn't exist.**" CLOSE : KILL File2$: END END IF PRINT : PRINT "Playing mixed " + File1$; " and "; File2$ GET #filenum, 1, Wave(0) getWaveInfo RLength& = WaveInfo.Length&: 'RLength& is number of remaining bytes IF RLength& > (LOF(filenum) - HeaderSize + 1) THEN RLength& = LOF(filenum) - HeaderSize + 1 LOCATE 20, 1: PRINT "warning: Truncated .WAV " + File1$ + " detected." END IF Freq& = 22000: 'default playback frequency Freq& = WaveInfo.Freq Freq2& = Freq& * 2 StereoWav% = WaveInfo.StereoWav sixteenbit% = WaveInfo.sixteenbit 'compare two file types - must be the same type! GET #filenum2, 1, Wave(0) IF StereoWav% <> WaveInfo.StereoWav THEN PRINT "incompatible file types": END IF Freq& <> WaveInfo.Freq THEN PRINT "incompatible file types": END IF sixteenbit% <> WaveInfo.sixteenbit THEN PRINT "incompatible file types": END css28 = cardversion% = 2 AND NOT sixteenbit% AND StereoWav% 'on SB 2.0 8s->8mono css2 = cardversion% = 2 AND sixteenbit% AND StereoWav% 'on SB 2.0 16s->8mono css = cardversion% = 3 AND sixteenbit% AND StereoWav% '16s->8stereo on SBPro cmm = cardversion% = 3 AND sixteenbit% AND NOT StereoWav% '16mono->8mono cskip = cardversion% <= 3 AND NOT sixteenbit% AND StereoWav%: 'skip one 'cskip2 = cardversion% <= 2 AND NOT sixteenbit% AND NOT StereoWav% Bseg% = VARSEG(WavBuffer(0)) Boff% = VARPTR(WavBuffer(0)) Bseg& = int2ULong&(Bseg%) Boff& = int2ULong&(Boff%) Bseg2% = VARSEG(WavBuffer(1)) Boff2% = VARPTR(WavBuffer(1)) Bseg2& = int2ULong&(Bseg2%) Boff2& = int2ULong&(Boff2%) IF Bseg% <> Bseg2% THEN PRINT "Need more contiguous memory!": END firsttime = 1 SEEK #filenum, HeaderSize GET #filenum, HeaderSize, WavBuffer(0) 'fill first buffer SEEK #filenum2, HeaderSize GET #filenum2, HeaderSize, WavBuffer(1) 'fill first buffer IF RLength& >= blocklen THEN L& = blocklen L% = blocklen ELSE L& = RLength& L% = CINT(RLength&) END IF Mix2Buffers Bseg%, Boff&, Bseg2%, Boff2&, L%, sixteenbit% IF cskip OR cskip2 THEN Convert5 Bseg%, Boff%, L%: LOCATE 18, 55: PRINT "cskip" IF css THEN Convert2FAST Bseg%, Boff&, L%: LOCATE 18, 44: PRINT "css Pro" IF css2 THEN Convert6 Bseg%, Boff&, L%: LOCATE 18, 40: PRINT "css 2.0" IF css28 THEN Convert7 Bseg%, Boff&, L%: LOCATE 18, 56: PRINT "css 28" IF cmm THEN Convert4 Bseg%, Boff%, L%: LOCATE 18, 50: PRINT "cmm" DO IF RLength& <= 0 AND firsttime = 0 THEN EXIT SUB firsttime = 0 '............play block in the background....................... LOCATE 1, 13: COLOR 14 IF sixteenbit% THEN SELECT CASE cardversion% CASE 4 'SB16, AWE32, AWE64? PRINT "SB16 16bit mode" DMAPlay16 Bseg&, Boff&, L&, Freq&, StereoWav%, sixteenbit% CASE 3 'SBPro IF StereoWav% THEN PRINT "SBPro Realtime stereo" DMAPlay Bseg&, Boff&, L& \ 4, Freq&, StereoWav% ELSE 'mono 16 to 8 bit conversion PRINT "SBPro Realtime mono" DMAPlay Bseg&, Boff&, L& \ 2, Freq&, StereoWav% END IF CASE 2 'SB 2.0 IF StereoWav% THEN PRINT "SB2.0 RT Stereo16" DMAPlay Bseg&, Boff&, L& \ 16, Freq& \ 4, 0 ELSE LOCATE 22, 1 PRINT "sb 2.0 44Khz 16bit stereo to 8bit mono not done." END END IF CASE ELSE 'SB COLOR 14, 4 PRINT "SB Realtime conversions from stereo to mono not supported.": END END SELECT ELSE '8 bit IF StereoWav% THEN SELECT CASE cardversion% CASE 4 LOCATE 1, 14: COLOR 14: PRINT "SB16 8bit stereo"; Freq&; "Hz" DMAPlay16 Bseg&, Boff&, L&, Freq&, StereoWav%, sixteenbit% CASE 3 IF cskip THEN DMAPlay Bseg&, Boff&, L& \ 2, Freq&, StereoWav% ELSE DMAPlay Bseg&, Boff&, L&, Freq2&, StereoWav% END IF CASE ELSE 'convert to sb2.0 8bit mono PRINT "SBPro RT stereo8" DMAPlay Bseg&, Boff&, L& \ 2, Freq& \ 2, 0 END SELECT ELSE 'mono 8-bit (no error checking) SELECT CASE cardversion% CASE 4 LOCATE 1, 14: COLOR 14: PRINT "SB16 8bit mono"; Freq&; "Hz" DMAPlay16 Bseg&, Boff&, L&, Freq&, StereoWav%, sixteenbit% CASE IS <= 2 LOCATE 1, 14: COLOR 14: PRINT "SB mode"; Freq&; "Hz" DMAPlay Bseg&, Boff&, L& \ 2, Freq& \ 2, StereoWav% CASE ELSE DMAPlay Bseg&, Boff&, L&, Freq&, StereoWav% END SELECT END IF END IF '.............................................................. 'fill buffer IF RLength& >= blocklen THEN L& = blocklen L% = blocklen ELSE L& = RLength& L% = blocklen END IF GET #filenum, , WavBuffer(0) GET #filenum2, , WavBuffer(1) Mix2Buffers Bseg%, Boff&, Bseg2%, Boff2&, L%, sixteenbit% IF css THEN Convert2FAST Bseg%, Boff&, L% 'should be convertstereo!! IF cmm THEN Convert4 Bseg%, Boff%, L% IF css2 THEN Convert6 Bseg%, Boff&, L% IF css28 THEN Convert7 Bseg%, Boff&, L% IF (cskip OR cskip2) THEN Convert5 Bseg%, Boff%, L% RLength& = RLength& - L& 'update remaining length 'done filling buffer,,,,,,,,,,,,,, last2: IF INKEY$ > "" THEN stopflag = 1 DO UNTIL DMADone%(DMA16%, L&) ' 'now CPU is free to do graphics, etc. ' ' LOOP IF stopflag THEN EXIT DO: 'stop here so it doesn't freeze the computer LOOP UNTIL EOF(filenum) OR EOF(filenum2) LOCATE 23, 1: PRINT "DMA transfer completed!"; DMAState 0: 'stop sound quit% = ResetDSP% END SUB SUB PlayWave (Filename$) LOCATE 7, 1 filenum = FREEFILE OPEN Filename$ FOR BINARY AS filenum IF LOF(filenum) = 0 THEN PRINT "**"; Filename$; " doesn't exist.**" CLOSE : KILL Filename$: END END IF PRINT : PRINT "Playing " + Filename$ GET #filenum, 1, Wave(0): 'BASIC defines beginning of file as 1 Freq& = 22000: 'default playback frequency getWaveInfo Freq& = WaveInfo.Freq Freq2& = Freq& * 2 StereoWav% = WaveInfo.StereoWav sixteenbit% = WaveInfo.sixteenbit css28 = cardversion% = 2 AND NOT sixteenbit% AND StereoWav% 'on SB 2.0 8s->8mono css2 = cardversion% = 2 AND sixteenbit% AND StereoWav% 'on SB 2.0 16s->8mono css = cardversion% = 3 AND sixteenbit% AND StereoWav% '16s->8stereo on SBPro cmm = cardversion% = 3 AND sixteenbit% AND NOT StereoWav% '16mono->8mono cskip = cardversion% <= 3 AND NOT sixteenbit% AND StereoWav%: 'skip one 'cskip2 = cardversion% <= 2 AND NOT sixteenbit% AND NOT StereoWav% LOCATE 1, 65: PRINT "cycles free" Bseg% = VARSEG(WavBuffer(0)) 'Boff should always be 0 in QB. Boff% = VARPTR(WavBuffer(0)) Bseg& = int2ULong&(Bseg%) Boff& = int2ULong&(Boff%) vol = 10 'LOCATE 18, 1: PRINT "Buffer at " + HEX$(Bseg%); ":"; HEX$(Boff%) RLength& = WaveInfo.Length&: 'RLength& is number of remaining bytes IF RLength& > (LOF(filenum) - HeaderSize + 1) THEN RLength& = LOF(filenum) - HeaderSize + 1 LOCATE 20, 1: PRINT "warning: Truncated .WAV detected." END IF firsttime = 1 SEEK #filenum, HeaderSize GET #filenum, HeaderSize, WavBuffer(0) 'fill first buffer IF RLength& >= blocklen THEN L& = blocklen L% = blocklen ELSE L& = RLength& L% = CINT(RLength&) END IF IF cskip OR cskip2 THEN Convert5 Bseg%, Boff%, L% IF css THEN Convert2FAST Bseg%, Boff&, L% IF css2 THEN Convert6 Bseg%, Boff&, L% IF css28 THEN Convert7 Bseg%, Boff&, L% IF cmm THEN Convert4 Bseg%, Boff%, L% t1# = TIMER DO IF RLength& <= 0 AND firsttime = 0 THEN EXIT SUB firsttime = 0 '............play block in the background....................... LOCATE 1, 13: COLOR 14 IF sixteenbit% THEN SELECT CASE cardversion% CASE 4 'SB16, AWE32, AWE64? PRINT "SB16 16bit mode" DMAPlay16 Bseg&, Boff&, L&, Freq&, StereoWav%, sixteenbit% CASE 3 'SBPro IF StereoWav% THEN PRINT "SBPro Realtime stereo" DMAPlay Bseg&, Boff&, L& \ 4, Freq&, StereoWav% ELSE 'mono 16 to 8 bit conversion PRINT "SBPro Realtime mono" DMAPlay Bseg&, Boff&, L& \ 2, Freq&, StereoWav% END IF CASE 2 'SB 2.0 IF StereoWav% THEN PRINT "SB2.0 RT Stereo16" DMAPlay Bseg&, Boff&, L& \ 16, Freq& \ 4, 0 ELSE LOCATE 22, 1 PRINT "sb 2.0 44Khz 16bit stereo to 8bit mono not done." END END IF CASE ELSE 'SB COLOR 14, 4 PRINT "SB Realtime conversions from stereo to mono not supported.": END END SELECT ELSE '8 bit IF StereoWav% THEN SELECT CASE cardversion% CASE 4 LOCATE 1, 14: COLOR 14: PRINT "SB16 8bit stereo"; Freq&; "Hz" DMAPlay16 Bseg&, Boff&, L&, Freq&, StereoWav%, sixteenbit% CASE 3 IF cskip THEN DMAPlay Bseg&, Boff&, L& \ 2, Freq&, StereoWav% ELSE DMAPlay Bseg&, Boff&, L&, Freq2&, StereoWav% END IF CASE ELSE 'convert to sb2.0 8bit mono PRINT "SBPro RT stereo8" DMAPlay Bseg&, Boff&, L& \ 2, Freq& \ 2, 0 END SELECT ELSE 'mono 8-bit (no error checking) SELECT CASE cardversion% CASE 4 LOCATE 1, 14: COLOR 14: PRINT "SB16 8bit mono"; Freq&; "Hz" DMAPlay16 Bseg&, Boff&, L&, Freq&, StereoWav%, sixteenbit% CASE IS <= 2 LOCATE 1, 14: COLOR 14: PRINT "SB mode"; Freq&; "Hz" DMAPlay Bseg&, Boff&, L& \ 2, Freq& \ 2, StereoWav% CASE ELSE DMAPlay Bseg&, Boff&, L&, Freq&, StereoWav% END SELECT END IF END IF '.............................................................. 'fill buffer IF RLength& >= blocklen THEN L& = blocklen L% = blocklen ELSE L& = RLength& L% = blocklen END IF GET #1, , WavBuffer(0) ccc = ccc + 1 'LOCATE 19, 1: PRINT SEEK(filenum), LOF(filenum), ccc IF css THEN Convert2FAST Bseg%, Boff&, L%'should be convertstereo!! IF cmm THEN Convert4 Bseg%, Boff%, L% IF css2 THEN Convert6 Bseg%, Boff&, L% IF css28 THEN Convert7 Bseg%, Boff&, L% IF (cskip OR cskip2) THEN Convert5 Bseg%, Boff%, L% RLength& = RLength& - L& 'update remaining length 'done filling buffer,,,,,,,,,,,,,, last: LOCATE 1, 37: PRINT TIME$; " "; 100 - INT(RLength& / WaveInfo.Length& * 100); "% done "; cycles% 'LOCATE 19, 8: PRINT "[" + STRING$(vol, 254) + STRING$(15 - vol, 249) + "]" cycles% = 0 DO UNTIL DMADone%(DMA16%, L&) ' now CPU is free to do graphics, etc. ' ' LINE (RND * 640, RND * 350)-(RND * 640, RND * 350), RND * 16 ' ' put your graphics update calls here ' ' IF cycles% < 32767 THEN cycles% = cycles% + 1 'Polling to Status/DMADone port too fast causes 'jumpy sound - the print statement adds a delay :) LOCATE 23, 1 PRINT USING "###.##s"; (TIMER - t1#) IF INKEY$ > "" THEN stopflag = 1 LOOP IF stopflag THEN EXIT DO: 'stop here so it doesn't freeze the computer LOOP LOCATE 23, 1: PRINT "DMA transfer completed!"; DMAState 0: 'stop sound quit% = ResetDSP% END SUB FUNCTION ReadDAC% ' Reads a byte from the DAC. WriteDSP &H20 ReadDAC% = ReadDSP% END FUNCTION FUNCTION ReadDSP% WAIT (Baseport% + &HE), &H80: 'wait for bit 7 on pollport DO: DSPIn% = INP(Baseport% + 10): LOOP UNTIL DSPIn% <> &HAA ReadDSP% = DSPIn% END FUNCTION FUNCTION ResetDSP% ct = 0: stat = 0: ready = &HAA OUT Baseport% + &H6, 1 DO OUT Baseport% + &H6, 0 stat = INP(Baseport% + &HE) stat = INP(Baseport% + &HA) IF stat = ready THEN EXIT DO ct = ct + 1 LOOP WHILE ct < 100 'wait about 100 ms IF stat = ready THEN ResetDSP% = 1 ELSE ResetDSP% = 0 END FUNCTION SUB SetStereo (OnOff%) 'only needed on SBPro MixerReg% = Baseport% + 4 MixerData% = Baseport% + 5 OUT MixerReg%, &HE IF OnOff% THEN OUT MixerData%, 2 ELSE OUT MixerData%, 0 END IF END SUB SUB SpeakerState (OnOff%) ' Turns speaker on or off. IF OnOff% THEN WriteDSP &HD1 ELSE WriteDSP &HD3 END SUB FUNCTION SpeakerStatus% OUT Baseport% + 4, &HD8 IF INP(Baseport% + 5) = &HFF THEN SpeakerStatus% = -1 ELSE SpeakerStatus% = 0 END FUNCTION SUB TECHNICAL ' Speed benchmarks with SBPro, QB45, Cyrix P120+: ' ~1600 cycles free on 44Khz stereo realtime downsampled wave file ' 17590 cycles free on 22Khz stereo wave file ' 32767+ cycles free on 22Khz mono wave file ' realtime conversion requires Pentium 100 level computer with QB45 ' If you get stuttering and 0 cycles free, your computer is too slow ' This program runs about 10x faster compiled compared to QBasic 1.1 ' better error correction and autodetection ' WAV file type autodetection for mono 16bit WAV autodetect ' double buffer is added for better sound quality ' By Mike Huff (1996) * Now plays whole file in QB45 and Qbasic * ' Added WAVE file header reader to determine length and sampling freq. ' DMAPlay16 (stereo) actually completes transfer of 40 Megabyte song ' It works without static now! It was just an alignment problem. 'FMT - format-specific fields 'e.g. PCM-format-specific has BitsPerSample (word) ' for PCM data, ' wAvgBytesPerSec=RoundUp(wChannels*wBitsPerSec*wBitsPerSample/8) ' wBlockAlign=wBitsPerSample/8 ' assuming no FACT, CUE points, Playlist, Assoc. Data List chunks 'data section stored like this: colon means -or- ' -> { : } ' -> data( ) ' -> LIST( 'wavl' { : //wave samples ' }... ) //silence ' -> slnt( ) //count of silence samples ' // not necessarily 0. ' // use last data sample's value. 'PCM Data is stored as follows; 0123 are sample #s, L,R=left,right ' byte0 byte1 byte2 byte3 ' 8 bit mono - 0 1 2 3 ' 8 bit stereo - 0L 0R 1L 1R ' 16 bit mono - 0lo 0hi 1lo 1hi ' 16 bit stereo - 0Llo 0Lhi 0Rlo 0Rhi ' ___Sample Size_|__data fmt____|_____range_________________ ' 1 to 8 bits | Unsigned int | 0-0xFF ' 9 or more | signed int | most negative to most positive ' 'in PCM 'data' Chunk ' dwChunkStart - file pos. of 'data' chunk relative to start of data section in 'wavl' LIST chunk ' dwBlockStart - file pos. of cue point relative to ..... ' dwSampleOffs = 0 ' fccChunk - FOURCC value 'data' 'Use DMARecord to record in the background. 'and use DMAPlay to playback the same buffer you recorded to or you could 'even write the buffer to a file. 'DMARecord VARSEG(WavBuffer(0)), VARPTR(WavBuffer(0)), Length&, Freq& 'Filename$ = "C:\QB45\WHOOSH1.WAV" 'Filename$ = "C:\QB45\ARROWS.WAV" 'Filename$ = "C:\QB45\A4416.WAV" 'Filename$ = "C:\SOUND\AOIUSAGI.WAV" 'Filename$ = "C:\SOUND\CD_AUDIO\STEREO.WAV" 'SIXTEEN-BIT TEST SAMPLES 'Filename$ = "C:\SOUND\CD_AUDIO\S4416.WAV" 'Filename$ = "C:\SOUND\CD_AUDIO\S2216.WAV" 'Filename$ = "C:\SOUND\CD_AUDIO\S1116.WAV" 'Filename$ = "C:\SOUND\CD_AUDIO\M4416.WAV" 'Filename$ = "C:\SOUND\CD_AUDIO\M2216.WAV" 'Filename$ = "C:\SOUND\CD_AUDIO\M1116.WAV": 'DID NOT FINISH cmm ccc=1 'EIGHT-BIT TEST SAMPLES 'Filename$ = "C:\SOUND\CD_AUDIO\S4408.WAV": 'Filename$ = "C:\SOUND\CD_AUDIO\S2208.WAV" 'Filename$ = "C:\SOUND\CD_AUDIO\S1108.WAV": 'DID NOT FINISH cskip mode ccc=1 'Filename$ = "C:\SOUND\CD_AUDIO\M4408.WAV" 'Filename$ = "C:\SOUND\CD_AUDIO\M2208.WAV": 'DID NOT FINISH sb mode 22khz ccc=1 'Filename$ = "C:\SOUND\CD_AUDIO\M1108.WAV": Filename$ = "C:\SOUND\AOIUSAGI.WAV" 'Filename$ = "C:\SOUND\CD_AUDIO\STEREO.WAV" END SUB FUNCTION Ulong2int% (Ulong&) IF Ulong& > 32767 THEN Sint% = CINT(Ulong& - 65536) ELSE Sint% = CINT(Ulong&) END IF Ulong2int% = Sint% END FUNCTION SUB VocVolume (Right%, Left%, Getvol%) OUT Baseport% + 4, &H4 IF Getvol% THEN Left% = INP(Baseport% + 5) \ 16 Right% = INP(Baseport% + 5) AND &HF EXIT SUB ELSE OUT Baseport% + 5, (Right% + Left% * 16) AND &HFF END IF END SUB SUB WriteDAC (byte%) ' Writes a byte to the DAC. WriteDSP &H10 WriteDSP byte% END SUB SUB WriteDSP (byte%) ' Writes a byte to the DSP DO: LOOP WHILE INP(Baseport% + 12) AND &H80 OUT Baseport% + 12, byte% END SUB SUB WriteMixer (cmd%, value%) MixerReg% = Baseport% + 4 MixerData% = Baseport% + 5 OUT MixerReg%, cmd% OUT MixerData%, value% END SUB