' VMusic2 Driver for Prism Project CON Cr = $0D ' New line / Return Lf = $0A ' Line feed Del = $7F ' Delete cmdChDir = $02 ' Change directory cmdOpen = $0E ' Open file for reading cmdRead = $04 ' Read file cmdClose = $0A ' Close file cmdDir = $01 ' Directory listing cmdPlay = $1D ' Play file cmdStop = $20 ' Stop plays maxLine = 80 ' Maximum line / pattern length maxBuff = 2048 ' Maximum file buffer size maxTime = 100 ' Maximum time for VMusic2 response maxFiles = 64 ' Maximum number of MP3 files in list OBJ ser : "FullDuplexSerialMod" ' Serial link to VMusic2 VAR long stack[50] ' Stack for VMusic2 cog long msgPtr ' Pending console message pointer long msgLock ' Lock number used for message long fileSize ' File size from directory command byte cog ' Cog number or zero if can't start one byte channel ' Sound channel for this cog (0 or 1) byte relays ' Start of relay pins for this cog byte line[maxLine] ' Input line buffer (from VMusic2) byte lineLen ' Current length of input line buffer byte buffer[maxBuff] ' Buffer for text file being read byte message[maxLine] ' Console message (zero-terminated) byte msgLen ' Current length of console message byte msgDspFile ' > 0 to display filename of value-1 byte fileCount ' Number of files in file list byte subdir[maxFiles*10] ' Subdirectory name for MP3 file byte filename[maxFiles*14] ' 8.3 (FAT16) name for MP3 file byte speaker[maxFiles] ' Speaker number to use for file byte seqFilename[16] ' Constructed name of sequence file DAT echoUp byte "E",Cr,0 ' Upper case echo command / response echoLow byte "e",Cr,0 ' Lower case echo command / response chkStat byte Cr,0 ' Check drive status command longSCS byte "SCS",Cr,0 ' Long form SCS command shortUp byte cmdChDir," ..",Cr,0 ' Short form change directory upwards shortOK byte ">",Cr,0 ' Short form normal response shortND byte "ND",Cr,0 ' Short form No Disk response shortCF byte "CF",Cr,0 ' Short form Command Failure response stopMsg byte "S>",Cr,0 ' Stop play message endFile byte "*** END ***",0 ' End of file marker PUB start(Chan,TxD,RxD,Bank) stop ' Stop if previously started channel := Chan ' Save channel # (0 or 1) bytemove(@seqFilename,string(cmdDir," SEQUENC"),9) seqFilename[9] := channel + "0" bytemove(@seqFilename+10,string(".TXT",Cr),6) relays := Bank ' Save relay pin start (0,-1,-2, ...) msgPtr := 0 msgLen := 0 ' Clear the message buffer and address msgDspFile := 0 ' and don't display file name first if (msgLock := locknew) == -1 return 0 ' Must have a lock to continue ser.start(TxD,RxD,%0000,9600) ' Start this cog's serial port ifnot cog := cognew(vmusic,@stack) + 1 ser.stop ' Stop serial port if COGNEW fails return cog PUB stop if msgLock <> -1 lockret(msgLock) if cog ' If cog has been started, cogstop(cog - 1) ' stop the cog and ser.stop ' stop the serial port PUB waitForMessage ' Locks message buffer for main cog repeat while lockset(msgLock) PUB freeMessage ' Frees message buffer for main cog lockclr(msgLock) PUB getMsgPtr ' Gets message pointer for main cog return msgPtr PUB getSubdir(subdirNo) ' Gets subdirectory text for main cog return @subdir + subdirNo * 10 PUB getFilename(fileNo) ' Gets filename text for main cog return @filename + fileNo * 14 PUB getSpeaker(fileNo) ' Gets speaker number for main cog return speaker[fileNo] PUB getFileNo ' Gets current file number for main cog return msgDspFile PRI vmusic | current, t outa[relays..relays-5] := %000000 ' Initialize relay outputs to low dira[relays..relays-5] := %111111 ser.str(string(cmdStop,Cr)) ' Try forcing a stop command repeat until cmdResp(@echoUp,@echoUp) ifnot cmdResp(@echoLow,@echoLow) ' Send "E" until we get a response newError ' then send "e" and check for echo errorMsg(string("Missed 'e'"),0) abort ifnot cmdResp(@longSCS,@shortOK) ' Put VMusic2 into short command mode newError errorMsg(string("SCS not OK"),0) abort if cmdResp(@chkStat,@shortND) ' See if a drive is present newError errorMsg(string("No Disk Present"),0) abort ifnot endsWith(@shortOK) newError errorMsg(string("Status Not OK"),0) abort repeat while cmdResp(@shortUp,@shortOK) ifnot endsWith(@shortCF) ' Make sure current directory is root newError errorMsg(string("Can't CD Root"),0) abort seqFilename[0] := cmdDir ifnot cmdResp(@seqFilename,@shortOK) ' Do DIR for SEQUENCn.TXT newError errorMsg(string("Failed DIR"),0) abort fileSize := endNumber(strsize(@shortOK)+1) if fileSize > maxBuff ' Check for valid file size newError errorMsg(string("File Too Big"),0) abort seqFilename[0] := cmdOpen ifnot cmdResp(@seqFilename,@shortOK) ' Open the playlist file newError errorMsg(string("Failed OPR"),0) abort ifnot readTheFile(fileSize,@buffer) ' Read the entire file into buffer newError errorMsg(string("Timeout RD"),0) abort ifnot endsWith(@shortOK) newError errorMsg(string("Failed RD"),0) abort seqFilename[0] := cmdClose ifnot cmdResp(@seqFilename,@shortOK) ' Close the playlist file newError errorMsg(string("Failed CLF"),0) abort clearLine ifnot parseTheFile ' Process the playlist newError errorMsg(string("Bad Playlist"),0) abort repeat current from 0 to fileCount-1 ifnot setDirectory(current) ' Change to requested directory newError errorMsg(string("CD File Error"),current+1) abort if (t := playTheFile(current)) <> true newError errStr(t) ' Play the current file & display time errorMsg(string(" Play Error"),current+1) abort ifnot setDirectory(-1) ' Change back to root directory newError errorMsg(string("CD Root Error"),current+1) abort PRI clearLine ' Clear input line lineLen := 0 PRI cmdResp(cmd,ans) clearLine ser.str(cmd) ' Transmit command repeat while bufferFill ' Fill buffer return endsWith(ans) ' Return match to response PRI endsWith(str) | s if lineLen == maxLine ' Is there room for a zero terminator? return false line[lineLen] := 0 ' Put it in s := strsize(str) ' Get length of pattern if s > lineLen ' Make sure input is long enough return false return strcomp(str,@line+lineLen-s) ' Return result of string compare PRI bufferFill | c ' Attempt to read a new input character if lineLen < maxLine ' Don't read a character if buffer full if (c := ser.rxtime(maxTime)) => 0 line[lineLen++] := c ' Save new character in buffer return true PRI endNumber(pos) ' Get 4 byte value relative to end of buffer repeat 4 result := result << 8 | line[lineLen-(++pos)] PRI playTheFile(fileNo) | time, timeout outa[relays-speaker[fileNo]+1] := 1 clearLine ser.str(string(cmdPlay," ")) ' Initiate playing of a track ser.str(@filename+fileNo*14) repeat while bufferFill ifnot endsWith(@fileName+fileNo*14) ' Response should end with track name outa[relays-speaker[fileNo]+1] := 0 return string("No Trk") clearLine newError errorMsg(string("0 seconds"),fileNo+1) clearLine timeout := 0 repeat while timeout++ < 20 ' Timeout if no response in 2 seconds repeat while bufferFill if endsWith(@stopMsg) ' At end of track ... "S>" outa[relays-speaker[fileNo]+1] := 0 return true if endsWith(string(Cr)) ' Otherwise, it's two bytes with time time := line[lineLen-2] << 8 | line[lineLen-3] clearLine newError errDec(time) ' Display the time if time == 1 errorMsg(string(" second"),fileNo+1) else errorMsg(string(" seconds"),fileNo+1) timeout := 0 ' Reset the timeout outa[relays-speaker[fileNo]+1] := 0 return string("Timeout") PRI setDirectory(subdirNo) clearLine ser.str(string(cmdChDir," ")) ' Change directory if subdirNo < 0 ser.str(string("..",Cr)) ' Either go up one level else ser.str(@subdir+subdirNo*10) ' Or use a specific directory repeat while bufferFill return endsWith(@shortOK) ' Make sure response is OK PRI readTheFile(size,ptr) | c clearLine seqFilename[0] := cmdRead ' Read the file into a buffer ser.str(@seqFilename) repeat size ' A timeout should not occur if (c := ser.rxtime(maxTime)) < 0 return false byte[ptr++] := c repeat while bufferFill ' Make sure response is OK return endsWith(@shortOK) PRI parseTheFile | bufPtr, dirPtr, filePtr, c, valid ' The format for the data: ' Each line consists of up to 6 characters for the directory / prefix name ' followed by a space, then a one or two digit track number and a space, ' then a single digit speaker number followed by the line terminator. ' This may be either a return, a linefeed, or both. The last line ' contains the data "*** END ***". Each line contains up to 11 characters. fileCount := 0 bufPtr := 0 repeat if chkEqual(@buffer+bufPtr,@endFile,11) return true ' Finished if end of file marker if fileCount == maxFiles return false ' Error if already at last file dirPtr := fileCount * 10 filePtr := fileCount * 14 repeat case c := buffer[bufPtr++] ' Copy file name to table and " ": ' convert lower case to upper quit ' Stop at first blank "0".."9", "A".."Z": subdir[dirPtr++] := c filename[filePtr++] := c "a".."z": c := c - "a" + "A" subdir[dirPtr++] := c filename[filePtr++] := c other: ' Invalid character return false bytemove(@subdir+dirPtr,string(Cr),2) repeat while (c := buffer[bufPtr++]) == " " repeat case c ' Now copy numeric value " ": quit ' Stop at first blank "0".."9": filename[filePtr++] := c other: ' Invalid character return false c := buffer[bufPtr++] bytemove(@filename+filePtr,string(".MP3",Cr),6) if buffer[bufPtr] < "1" or buffer[bufPtr] > "6" return false ' Single digit speaker number speaker[fileCount++] := buffer[bufPtr++] - "0" valid := false if buffer[bufPtr] == Cr ' End of line delimiter is bufPtr += 1 ' either a return or linefeed valid := true ' or both (in that order) if buffer[bufPtr] == Lf bufPtr += 1 valid := true ifnot valid ' Invalid delimiter return false PRI chkEqual(src1,src2,len) repeat len ' Fixed length string compare if byte[src1++] <> byte[src2++] return false return true PRI newError repeat while lockset(msgLock) ' Wait for message info to be free msgLen := 0 ' Clear the message buffer and address msgPtr := 0 msgDspFile := 0 ' Don't display file name first PRI errOut(c) ' Store a character in the message if msgLen+1 < maxLine ' Don't store past end of message space message[msgLen++] := c PRI errStr(s) | c ' Add a string to the message repeat while c := byte[s++] errOut(c) PRI errDec(value) | i ' Convert integer to string if value < 0 -value ' Take care of leading sign if negative errOut("-") i := 1_000_000_000 repeat 10 ' Output up to 10 digits suppressing if value => i ' leading zeros errOut(value / i + "0") value //= i result~~ elseif result or i == 1 errOut("0") i /= 10 PRI errHex(value, digits) ' Convert integer to hexadecimal value <<= (8 - digits) << 2 repeat digits errOut(lookupz((value <-= 4) & $F : "0".."9", "A".."F")) PRI errSendMsg(n) ' Send constructed message to console message[msgLen] := 0 ' Set new message terminator msgDspFile := n ' Optionally display file information msgPtr := @message ' Indicate that there's a string to go lockclr(msgLock) ' Release message buffer PRI errorMsg(s,n) | i errStr(s) ' Wait for message buffer & send string if lineLen errOut(":") ' Include any received information repeat i from 0 to lineLen-1 if line[i] => 32 and line[i] < 128 errOut(line[i]) else errOut("{") ' Translate to hex if not displayable errHex(line[i],2) errOut("}") errOut(":") errSendMsg(n) ' Transfer the message to the main cog