'=========================================================================== ' Subject: DMA PLAY 4 (WITH 32K BUFFER) Date: 01-14-98 (19:14) ' Author: Toshihiro Horie Code: QB, QBasic, PDS ' Origin: horie@ocf.berkeley.edu Packet: SOUND.ABC '=========================================================================== '------------------------------------------------------------------ ' DMA Play 4 (1998.1.14 version with 32K buffer - Internet) ' 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 ' Toshi gives special thanks to Ethan Brodsky, who helped immensely. ' It works without static now! It was just an alignment problem. ' Feel free to use, but please give credit to its authors in your code. ' Program downloaded from http://www.ocf.berkeley.edu/~horie/project.html '----------------------------------------------------------------- DECLARE FUNCTION DEC2HEX$ (longnum&) DECLARE SUB WavInfo (Length&, Freq&, StereoWav%) DECLARE FUNCTION int2ULong& (signedint%) DECLARE FUNCTION SpeakerStatus% () DECLARE FUNCTION DMAStatus% () DECLARE FUNCTION DMADone% (DMA16%, L&) DECLARE FUNCTION ResetDSP% () 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 FUNCTION ReadDSP% () DECLARE SUB WriteDAC (byte%) DECLARE SUB SpeakerState (OnOff%) DECLARE SUB DMAState (StopGo%) DECLARE FUNCTION ReadDAC% () DECLARE SUB DMAPlay (Segment&, Offset&, Length&, Freq&, StereoWav%) DECLARE SUB DMAPlay16 (Segment&, Offset&, Length&, Freq&, StereoWav%) DECLARE SUB DMARecord (Segment&, Offset&, Length&, Freq&) DECLARE SUB GetBLASTER () DECLARE FUNCTION DSPVersion! () COMMON SHARED Baseport%, LenPort%, DMA%, DMA16% TYPE WaveHeaderType RiffID AS STRING * 4 RiffLength AS LONG WavID AS STRING * 4 FmtID AS STRING * 4 FmtLength AS LONG wavformattag AS INTEGER Channels AS INTEGER SamplesPerSec AS INTEGER BytesPerSec AS INTEGER BlockAlign AS INTEGER FmtSpecific AS INTEGER Padding AS STRING * 4 DataID AS STRING * 4 DataLength AS LONG END TYPE DIM SHARED Wave(0) AS WaveHeaderType SCREEN 9: CLS 'WIDTH 80, 50 PRINT "DMAPlay 4" PRINT "By Mike Huff (SB, SBPro) and Toshi Horie (SB16)" 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" GetBLASTER ' Parses BLASTER environment PRINT STRING$(80, 196) IF ResetDSP% THEN 'resets DSP (returns true if sucessful) PRINT "DSP reset sucessfully!" ELSE PRINT "DSP failed to reset, try another port.": END END IF SpeakerState 1 'turn the speaker on PRINT "Sound Card DSP version:"; DSPVersion! MasterVolume Right%, Left%, -1 'this puts the mixer volumes in Right% and Left% PRINT "Master volume is set at: Right-"; Right%; " Left-"; Left% MasterVolume 7, 7, 0 '15,15,0 cranks the master volume all the way up. 'WavBuffer size MUST be divisible by 4 for stereo files DIM WavBuffer(0) AS STRING * 32764 'to make a ~32k buffer for file blocklen = 32764 'make this the same as the number above in STRING*____ ' 16-bit WAV files are OK if you have an SB16. 'Filename$ = "D:\SOUND\AOIUSAGI.WAV" Filename$ = "C:\WINDOWS\MEDIA\THEMIC~1.WAV" 'Filename$ = "C:\PROGRA~1\ICQ\SOUNDS\CHAT.WAV" 'Filename$ = "mock.wav" 'Filename$ = "ellens13.wav" 'Filename$ = "mono13.wav" 'Filename$ = "ellens13.raw" OPEN Filename$ FOR BINARY AS #1 HeaderSize = 45 'assume .WAV file (use 45) 'I think it's 32 for VOC, 0 for RAW files Length& = LOF(1) - HeaderSize: PRINT : PRINT "Playing " + Filename$ GET #1, 1, Wave(0) Freq& = 22000: 'default playback frequency WavInfo Length&, Freq&, StereoWav% SEEK #1, HeaderSize Buffseg% = VARSEG(WavBuffer(0)) Buffoff% = VARPTR(WavBuffer(0)) Bseg& = int2ULong&(Buffseg%) BOff& = int2ULong&(Buffoff%): 'should always be 0 in BASIC DO 'Get 32k from file (skip header on WAV) IF Length& >= blocklen THEN L& = blocklen ELSE L& = Length& END IF GET #1, , WavBuffer(0) CurrentBlk& = CurrentBlk& + 1 Length& = Length& - blocklen '............play block in the background....................... IF DMA16% AND StereoWav% THEN DMAPlay16 Bseg&, BOff&, L&, Freq&, StereoWav% 'PRINT "SB16 mode" ELSE DMAPlay Bseg&, BOff&, L&, Freq&, StereoWav% 'PRINT "SB mode" END IF '.............................................................. DO UNTIL DMADone%(DMA16%, L&) 'CPU is free to do graphics, etc. 'LOCATE 12, 1 'PRINT USING "Seconds elapsed: ###.###s"; (TIMER - t1#) LINE (RND * 640, RND * 290 + 60)-(RND * 640, RND * 290 + 60), FIX(RND * 16) LINE (RND * 640, RND * 290 + 60)-(RND * 640, RND * 290 + 60), FIX(RND * 16) IF INKEY$ > "" THEN stopflag = 1 LOOP IF stopflag THEN EXIT DO: 'stop here so it doesn't freeze the computer LOOP UNTIL EOF(1) '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& 'Notes: 16 bit mono files are incorrectly played in DMAPLAY instead of 'DMAPLAY16. This will be fixed as soon as I get more info on the 'wave file header. PRINT "DMA transfer completed!" DMAState 0: 'stop sound SpeakerState 0: 'turn the speaker off quit% = ResetDSP% END 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 FUNCTION DEC2HEX$ (longnum&) DEC2HEX$ = HEX$(longnum&) END FUNCTION FUNCTION DMADone% (DMA16%, L&) countlo% = INP(LenPort%) counthi% = INP(LenPort%) count& = CLNG(counthi% * 256&) + CLNG(countlo%) 'LOCATE 13, 1: PRINT count& 'if you have problems with L&-8, then use L&-1 IF count& > L& - 8 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. Length& = Length& - 1 page% = 0 addr& = Segment& * 16 + Offset& 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 IF StereoWav% 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 &HFFFF&) \ &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%, Length& AND &HFF: 'size of block to DMA controller -Low OUT LenPort%, (Length& AND &HFFFF&) \ &H100: '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& < 23000 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 INT(DSPVersion!) = 3 THEN 'high speed 8 bit output up to 44kHz mono or 22Khz stereo WriteDSP &H40: 'output sampling rate const WriteDSP TimeConst% WriteDSP &H48 WriteDSP (Length& AND &HFF) WriteDSP ((Length& AND &HFFFF&) \ &H100) WriteDSP &H91 ELSE PRINT "You need a Sound Blaster Pro to play at 8 bit high speed." EXIT SUB END IF END IF END SUB SUB DMAPlay16 (Segment&, Offset&, L&, Freq&, StereoWav%) ' Transfers and plays the contents of the buffer. ' Try only on an SoundBlaster 16 !! ' Don't see any more mistakes in code and it sounds almost perfect, ' other than some inherent pops and clicks due to the limitations of ' single-cycle DMA ' 1 page=128K in 16 bit mode ' DMA16% (16-bit DMA channel) passed implicitly IF DSPVersion! < 4 THEN PRINT "You need an SB16 for this mode!": END 'LOCATE 16, 1: PRINT "using SB16 DMA." L& = L& - 1: page% = 0 addr& = Segment& * 16 + Offset& 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 &HFFFF&) \ &H100) 'this may be wrong sometimes 'LOCATE 21, 1: PRINT "seg:"; dec2hex$(Segment&), 'PRINT "offset:"; dec2hex$(Offset&), "addr:"; dec2hex$(addr&) 'PRINT "off2:"; dec2hex$(Offset&), "page=addr\64K:"; dec2hex$(page%), 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 &HFFFF&) \ &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) FreqHi% = (Freq& AND &HFFFF&) \ &H100 FreqLo% = Freq& AND &HFF WriteDSP &H41: 'set output sampling rate WriteDSP FreqHi% WriteDSP FreqLo% WriteDSP &HB0: '16 bit DAC, single cycle, FIFO off (ok) 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 WriteDSP ((L& \ 2) AND &HFF) WriteDSP (((L& \ 2) AND &HFFFF&) \ &H100) 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% DSPVersion! = VAL(STR$(Temp%) + "." + STR$(Temp2%)) 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 settings ' implicitly using common shared variables Baseport%, DMA%,DMA16% IF LEN(ENVIRON$("BLASTER")) = 0 THEN PRINT "BLASTER environment variable not set.": EXIT SUB FOR index% = 1 TO LEN(ENVIRON$("BLASTER")) SELECT CASE MID$(ENVIRON$("BLASTER"), index%, 1) CASE "A" Baseport% = VAL("&H" + MID$(ENVIRON$("BLASTER"), index% + 1, 3)) CASE "I" IRQ% = VAL(MID$(ENVIRON$("BLASTER"), index% + 1, 1)) CASE "D" DMA% = VAL(MID$(ENVIRON$("BLASTER"), index% + 1, 1)) CASE "H" DMA16% = VAL(MID$(ENVIRON$("BLASTER"), index% + 1, 1)) END SELECT NEXT END SUB SUB InputSource (InputSrc%, GetSrc%) OUT Baseport% + 4, &HC IF GetSrc% THEN InputSrc% = INP(Baseport% + 5) AND 2 + INP(Baseport% + 5) AND 4 ELSE OUT Baseport% + 5, InputSrc% AND 7 END IF END SUB FUNCTION int2ULong& (signedint%) IF signedint% < 0 THEN int2ULong& = CLNG(signedint% + 65536) ELSE int2ULong& = CLNG(signedint%) END IF END FUNCTION SUB LineVolume (Right%, Left%, Getvol%) OUT Baseport% + 4, &H2E 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 MasterVolume (Right%, Left%, Getvol%) OUT Baseport% + 4, &H22 'PRINT BasePort% 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 MicVolume (Volume%, Getvol%) OUT Baseport% + 4, &HA IF Getvol% THEN Volume% = INP(Baseport% + 5) AND &HF EXIT SUB ELSE OUT Baseport% + 5, Volume% AND &HF END IF 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 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 WavInfo (Length&, Freq&, StereoWav%) 'PRE: Wave(0) array filled from WAV file header 'POST: Length&, Freq& set PRINT "RiffID:"; Wave(0).RiffID IF Wave(0).RiffID <> "RIFF" THEN PRINT "NOT A WAV FILE": END 'PRINT "RiffLength:"; Wave(0).RiffLength 'PRINT "WavID:"; Wave(0).WavID 'PRINT "FmtID:"; Wave(0).FmtID 'PRINT "FmtLength:"; Wave(0).FmtLength 'PRINT "wavformattag:"; Wave(0).wavformattag PRINT "Channels:"; Wave(0).Channels Freq& = int2ULong&(Wave(0).SamplesPerSec) IF Wave(0).Channels = 2 THEN StereoWav% = 1 END IF PRINT "SamplesPerSec:"; Freq& 'PRINT "BytesPerSec:"; Wave(0).BytesPerSec 'PRINT "BlockAlign:"; Wave(0).BlockAlign 'PRINT "FmtSpecific:"; Wave(0).FmtSpecific 'PRINT "Padding:"; Wave(0).Padding 'PRINT "DataID:"; Wave(0).DataID PRINT "DataLength:"; Wave(0).DataLength IF UCASE$(Wave(0).DataID) = "DATA" THEN Length& = Wave(0).DataLength END IF PRINT USING "Play Length ####.##s"; Length& / Freq& 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