Recursive SPIN routine - USB thumbdrive file find
Ron Czapala
Posts: 2,418
I wanted to develop a routine for my VMUSIC2 project which would locate a particular MP3 file on a USB thumbdrive inserted into the VMUSIC2.
Of course, there can be many nested folders and it seemed that a recursive routine was the best approach.
Since the Parallax memory stick datalogger module is similar to the VMUSIC2 (both use the Vinculum VNC1L USB host controller), this code should work
with the datalogger.
I have many vbscripts that do this using the FileSystemObject in the Windows scripting runtime DLL but doing this in SPIN for a Propeller
would be a different scenario due to memory constraints, etc.
What I did was define a global byte array in the DAT section which would hold up to 500 thirteen byte top-level directory names i.e. directories immediately under the root.
Then I created recursive method which defines a local variable to store up to 10 sub-folder names. It calls itself to handle nested sub-folders.
It passes an index pointer, directory counter, and subfolder array address to the supporting methods.
As it works it way through the directory structure, it looks at each file name and compares it to the search filename.
I use a variable named dirlvl to keep track of the nesting level. It is also used to build a array of the directory names maintaining a path to the current folder.
Of course, as the routine calls itself, more memory is being allocated and then freed up when a directory has been processed
and processing resumes with the parent directory.
I am trying to figure out the best way to monitor available memory during the recursion.
One way would be to check the dirlvl variable, but there may be a better way.
I also want to see if I can speed it up...
Here is the terminal output using a some small folders - you can see the line "
> Found it!" when the file has been located.
Of course, there can be many nested folders and it seemed that a recursive routine was the best approach.
Since the Parallax memory stick datalogger module is similar to the VMUSIC2 (both use the Vinculum VNC1L USB host controller), this code should work
with the datalogger.
I have many vbscripts that do this using the FileSystemObject in the Windows scripting runtime DLL but doing this in SPIN for a Propeller
would be a different scenario due to memory constraints, etc.
What I did was define a global byte array in the DAT section which would hold up to 500 thirteen byte top-level directory names i.e. directories immediately under the root.
Then I created recursive method which defines a local variable to store up to 10 sub-folder names. It calls itself to handle nested sub-folders.
It passes an index pointer, directory counter, and subfolder array address to the supporting methods.
As it works it way through the directory structure, it looks at each file name and compares it to the search filename.
I use a variable named dirlvl to keep track of the nesting level. It is also used to build a array of the directory names maintaining a path to the current folder.
Of course, as the routine calls itself, more memory is being allocated and then freed up when a directory has been processed
and processing resumes with the parent directory.
I am trying to figure out the best way to monitor available memory during the recursion.
One way would be to check the dirlvl variable, but there may be a better way.
I also want to see if I can speed it up...
[SIZE=2]CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000[/SIZE] [SIZE=2]' ------- Pin assigments ------ vm_rts = 0 ' Request To Send --> VMusic2 pin 6 (CTS#) - green vm_rx = 1 ' Receive Data <-- VMusic2 pin 5 (TXD) - yellow vm_tx = 2 ' Transmit Data --> VMusic2 pin 4 (RXD) - orange vm_cts = 3 ' Clear To Send <-- VMusic2 pin 2 (RTS#) - brown[/SIZE] [SIZE=2]vm_port = 0 BAUD = 9_600 ' LCD baud rate maxdirs = 500 ' number of directory names to table maxpath = 10 ' max number of folder names in path for recursive routine maxsubdirs = 12 ' number of sub folders allowed under top level folder maxbuff = 60 ' size of buffer for parsing VM2 serial output VAR long useser ' USB serial connection detected T/F long debugsw ' control output to Parallax Terminal long diskchk ' "No Disk" check byte fname[13] ' file name playing byte rxbuff[maxbuff] ' work buffer byte mp3title[maxbuff] ' hold song title byte artist[maxbuff] ' hold artist byte buffptr[/SIZE] [SIZE=2]word dircntr 'directory counter long dirptr 'directory pointer byte dirlvl 'directory nesting level byte findsw '1=recursive search for filename long pathptr 'path pointer[/SIZE] [SIZE=2] OBJ uarts : "pcFullDuplexSerial4FC" '1 COG for 4 serial ports Debug : "FullDuplexSerial" '"SerialMirror" '"Extended_FDSerial" I2C : "Basic_I2C_Driver_1" ' strng : "ASCII0_STREngine_1" PUB Main | idx dira[vm_rts]~~ outa[vm_rts]~ ' Take Vinculum Out Of Reset - set low useser := false if ina[31] == 1 ' RX (pin 31) is high if USB is connected Debug.start(31, 30, 0, 57600) ' ignore tx echo on rx useser := true ' Debug serial connection is active debugsw := true else outa[30] := 0 ' Force Propeller Tx line LOW if USB not connected ''----------------------- ''override debugsw to disable terminal displays ' debugsw := false ''-----------------------[/SIZE] [SIZE=2]if debugsw == true Debug.str(string(16,"Please wait",13)) ' CLS, disp msg[/SIZE] [SIZE=2]I2C.Initialize(I2C#BootPin) ' initialize I2C object diskchk := 1[/SIZE] [SIZE=2]uarts.Init 'start four port serial cog ' uarts.AddPort(vm_port,vm_rx,vm_tx,vm_cts,vm_rts,UARTS#DEFAULTTHRESHOLD,UARTS#NOMODE,UARTS#BAUD9600) uarts.AddPort(vm_port,vm_rx,vm_tx,vm_cts,vm_rts,UARTS#DEFAULTTHRESHOLD,UARTS#INVERTCTS,UARTS#BAUD9600) uarts.Start Read_VMResp[/SIZE] [SIZE=2]waitcnt(clkfreq * 2 + cnt) uarts.str(vm_port, string("IPA",$0D)) 'monitor mode ascii Read_VMResp waitcnt(clkfreq / 2 + cnt) [/SIZE] [SIZE=2]uarts.str(vm_port, string("ECS",$0D)) 'extended command set Read_VMResp waitcnt(clkfreq / 2 + cnt) [/SIZE] [SIZE=2]uarts.str(vm_port, string("CD / ")) 'CD root Read_VMResp[/SIZE] [SIZE=2] { store directory names in dirs table }[/SIZE] [SIZE=2]ReadDirectory 'read and store directory names[/SIZE] [SIZE=2] bytemove(@fname, string("STARBU~1.MP3"),13) FindFile[/SIZE] [SIZE=2] repeat waitcnt(clkfreq * 5 + cnt) [/SIZE] [SIZE=2]{ store directory names in dirs table } PUB ReadDirectory | idx dircntr := 0 dirptr := @dirs ' debug.dec(@rxbuff) 'address of rxbuff ' debug.tx(13) debug.str(string("dirs addr: ")) debug.dec(dirptr) 'address of dirs debug.tx(13) buffptr := 0 uarts.str(vm_port, string("DIR",$0D)) 'directory list Read_Dirs[/SIZE] [SIZE=2]if debugsw == true repeat idx from 0 to dircntr-1 dirptr := @dirs + (idx * 13) debug.dec(dirptr) debug.tx(32) debug.str(dirptr) debug.tx(13)[/SIZE] [SIZE=2]buffptr := 0 waitcnt(clkfreq * 2 + cnt) [/SIZE] [SIZE=2]{ Read directory names (under root) and store into dirs byte array Each directory name takes 13 bytes } PUB Read_Dirs| iobyte iobyte:=0 repeat iobyte:=uarts.rxtime(vm_port,500) if iobyte < 0 quit else ' debug.tx(iobyte) if Process_Directory(iobyte) if debugsw == true debug.dec(dircntr) debug.str(string(" Directories",13)) PUB Process_Directory(datain) if datain == 13 'carriage return? rxbuff[buffptr++] := 0 if StoreDir return true buffptr :=0 ' bytefill(@rxbuff, 0, maxbuff) 'Clear Buff to 0 return else if buffptr < maxbuff - 1 rxbuff[buffptr++] := datain 'add byte to buffer[/SIZE] [SIZE=2] PUB StoreDir | dirlen if findString(@rxbuff,string("D:\>")) > 0 return true dirlen := findString(@rxbuff,string(" DIR")) - @rxbuff 'calc length of directory name if dirlen > 0 'length of directory name ' debug.dec(dirlen) ' debug.tx(13) bytemove(dirptr, @rxbuff, dirlen) dircntr++ dirptr := @dirs + (dircntr * 13) 'calc addr to store next dir name ' debug.dec(dirptr) ' debug.tx(13)[/SIZE] [SIZE=2] PUB FindFile | idx findsw := 1 'enable filename match debug.str(string("Searching for: ")) debug.str(@fname) debug.tx(13) 'loop thru top level directories repeat idx from 0 to dircntr-1 dirptr := @dirs + (idx * 13) dirlvl :=0 ' debug.str(dirptr) ' debug.tx(13) Recurse(dirptr) 'call recursive routine to search for file debug.str(string("Done Searching")) findsw := 0 'disable filename match PUB Recurse(parent)| fldrptr, fldrcntr, fldrs[(maxsubdirs * 13) / 4], idx, psize dirlvl++ ' if dirlvl == 1 ' bytefill(@dirpath,0,maxpath*13) pathptr := @dirpath + ((dirlvl-1) * 13) psize := strsize(parent) bytemove(pathptr, parent, psize) debug.str(string(13,"Path: ")) repeat idx from 0 to dirlvl-1 debug.tx("\") debug.str(@dirpath + (idx * 13)) debug.tx(13) { debug.tx(13) debug.str(string("- - - - - - - - - - - ")) debug.str(string("dir level: ")) debug.dec(dirlvl) debug.tx(13) } fldrptr := @fldrs fldrcntr := 0 longfill(@fldrs, 0, 39) { debug.str(string("fldrs addr: ")) debug.dec(fldrptr) debug.tx(13) } if findString(parent,string("/")) <> @parent { debug.str(string("CD ")) debug.str(fldr) debug.tx(13) } uarts.str(vm_port, string("CD ")) uarts.str(vm_port, parent) 'change directory uarts.tx(vm_port, $0D) 'carriage return Read_VMResp[/SIZE] [SIZE=2]{ buffptr := 0 if findsw == 1 uarts.str(vm_port, string("DIR ")) uarts.str(vm_port, @fname) 'dir 'filename' uarts.tx(vm_port,$0D) Read_VMResp } uarts.str(vm_port, string("DIR",$0D)) 'directory list Read_SubDirs(@fldrptr, @fldrcntr, @fldrs)[/SIZE] [SIZE=2]'for each sub folder if fldrcntr > 0 repeat idx from 0 to fldrcntr-1 fldrptr := @fldrs + (idx * 13) Recurse(fldrptr)[/SIZE] [SIZE=2]uarts.str(vm_port, string("CD ..",$0D)) 'change to parent directory buffptr := 0 pathptr := @dirpath + ((dirlvl-1) * 13) bytefill(pathptr,0,13) dirlvl--[/SIZE] [SIZE=2] PUB Read_SubDirs(fldrptr,fldrcntr,fldrs) | iobyte iobyte:=0 repeat iobyte:=uarts.rxtime(vm_port,500) if iobyte < 0 quit else ' debug.tx(iobyte) if Process_SubDirectory(iobyte, fldrptr,fldrcntr,fldrs) ' if debugsw == true ' debug.dec(fldrcntr) ' debug.str(string(" Directories",13)) PUB Process_SubDirectory(datain,fldrptr,fldrcntr,fldrs) ' debug.str(string("proc dir: ")) ' debug.dec(long[fldrptr]) ' debug.tx(32) ' debug.dec(long[fldrcntr]) ' debug.tx(13) if datain == 13 'carriage return? rxbuff[buffptr++] := 0 if StoreSubDir(fldrptr,fldrcntr,fldrs) return true buffptr :=0 ' bytefill(@rxbuff, 0, maxbuff) 'Clear Buff to 0 return else if buffptr < maxbuff - 1 rxbuff[buffptr++] := datain 'add byte to buffer[/SIZE] [SIZE=2]PUB StoreSubDir(fldrptr,fldrcntr,fldrs) | dirlen if findString(@rxbuff,string("D:\>")) > 0 return true if findString(@rxbuff, string(". DIR")) == @rxbuff return if findString(@rxbuff, string(".. DIR")) == @rxbuff return dirlen := findString(@rxbuff,string(" DIR")) - @rxbuff 'calc length of directory name if long[fldrcntr] > (maxsubdirs - 1) 'fldrs array is full debug.str(string("Too many sub folders")) debug.tx(13) return if dirlen > 0 'length of directory name ' debug.str(string("store dir: ")) ' debug.dec(long[fldrptr]) ' debug.tx(32) ' debug.dec(long[fldrcntr]) ' debug.tx(32) ' debug.dec(dirlen) ' debug.tx(13) bytemove(long[fldrptr], @rxbuff, dirlen) long[fldrcntr]++ long[fldrptr] := fldrs + (long[fldrcntr] * 13) 'calc addr to store next dir name else if byte[@rxbuff][0] <> 0 ' ignore blank line at top of dir listing debug.str(@rxbuff) ' file, not directory debug.tx(13) if findsw == 1 if findString(@rxbuff, @fname) == @rxbuff debug.str(string("------> Found it!")) debug.tx(13)[/SIZE] [SIZE=2]PUB Read_VMResp | iobyte iobyte:=0 repeat iobyte:=uarts.rxtime(vm_port,500) if iobyte < 0 quit else Buffer_datain(iobyte) ' if debugsw == true ' debug.tx(iobyte)[/SIZE] [SIZE=2]PUB Buffer_datain(datain) if datain == 13 'carriage return? rxbuff[buffptr++] := 0 if debugsw == true DisplayBuff buffptr :=0 return else if buffptr < maxbuff - 1 rxbuff[buffptr++] := datain 'add byte to buffer[/SIZE] [SIZE=2]PUB DisplayBuff | idx if strsize(@rxbuff) < 1 'suppress blank line after any dir command return true if findString(@rxbuff,string("D:\>")) > 0 'suppress standard response ' debug.tx(".") return true if findString(@rxbuff,string("Command Failed")) > 0 return true[/SIZE] [SIZE=2]if findsw == 1 if findString(@rxbuff, @fname) == @rxbuff debug.str(string("------> Found it!")) debug.tx(13) [/SIZE] [SIZE=2]debug.str(@rxbuff) debug.tx(13)[/SIZE] [SIZE=2]PUB findString(stringToSearch, stringToFind) | index, size ' 7 Stack Longs '// Author: Kwabena W. Agyeman ' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ' // Searches a string of characters for the first occurence of the specified string of characters. ' // ' // Returns the address of that string of characters if found and zero if not found. ' // ' // StringToSearch - A pointer to the string of characters to search. ' // StringToFind - A pointer to the string of characters to find in the string of characters to search. ' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////[/SIZE] [SIZE=2]size := strsize(stringToFind) if(size--)[/SIZE] [SIZE=2]repeat strsize(stringToSearch--) if(byte[++stringToSearch] == byte[stringToFind])[/SIZE] [SIZE=2]repeat index from 0 to size if(byte[stringToSearch][index] <> byte[stringToFind][index]) result := true quit[/SIZE] [SIZE=2]ifnot(result~) return stringToSearch DAT nodisk byte "No Disk",0 dirs byte $00[maxdirs*13] 'array of directory names under root dirpath byte $00[maxpath*13] 'allow ten deep directory path [/SIZE]
Here is the terminal output using a some small folders - you can see the line "
> Found it!" when the file has been located.
[SIZE=2]Please wait Ver 03.68VMSC1F On-Line: Device Detected P2 No Upgrade dirs addr: 96 3 Directories 96 JAZZ 109 VARIOU~1 122 PORTER Searching for: STARBU~1.MP3[/SIZE] [SIZE=2]Path: \JAZZ[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ1 BLOWIN~1.MP3 BLUEDA~1.MP3 BODYHE~1.MP3 HEARTS~1.MP3 HEAVEN.MP3 LOSTSU~1.MP3 NORTHE~1.MP3 REALLY~1.MP3 SEEYOU~1.MP3 SOUNDO~1.MP3 STROLL~1.MP3 WITHOU~1.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ2 CANYOU~1.MP3 DOYOUR~1.MP3 GOODLO~1.MP3 INNERC~1.MP3 JUSTCA~1.MP3 SLOMOT~1.MP3 SMOOTH~1.MP3 SOMUCH~1.MP3 SUMMER~1.MP3 TIMETO~1.MP3 WALKIN~1.MP3 WONDER~1.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ3 DON'TL~1.MP3 DOWNSO~1.MP3 DREAMS.MP3 LONDON~1.MP3 LONDON~2.MP3 LOSTIN~1.MP3 NEWDAW~1.MP3 NIGHTC~1.MP3 REDZON~1.MP3 STARCH~1.MP3 STILLT~1.MP3 TRIPPI~1.MP3 VENTUR~1.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ4 KEYS_4.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ5 KEYS_5.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ6 KEYS_6.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ7 KEYS_7.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ8 KEYS_8.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ9 KEYS_9.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ10 KEYS_10.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ11 KEYS_11.MP3[/SIZE] [SIZE=2]Path: \JAZZ\JAZZ12 KEYS_12.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1[/SIZE] [SIZE=2]Path: \VARIOU~1\THEBIG~1 AWHITE~1.MP3 DANCIN~1.MP3 GOODLO~1.MP3 ISECON~1.MP3 ITSTHE~1.MP3 NATURA~1.MP3 TELLHI~1.MP3 TOOMAN~1.MP3 TRACKS~1.MP3 WHAT'S~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\TEENAG~1 BOBBYS~1.MP3 BORNTO~1.MP3 CHANCE~1.MP3 CRYMEA~1.MP3 DEVILO~1.MP3 HOLDME~1.MP3 IT'SAL~1.MP3 IT'SON~1.MP3 MYHEAR~1.MP3 MYSPEC~1.MP3 PRETTY~1.MP3 SEAOFL~1.MP3 SEALED~1.MP3 TEENAN~1.MP3 TELLLA~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\ONEHIT~1 986~1.MP3 COMEON~1.MP3 DOMINI~1.MP3 JUNGLE~1.MP3 MONTEG~1.MP3 ONETIN~1.MP3 REFLEC~1.MP3 YOUDON~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\MOREPU~1 DOYOUR~1.MP3 HANDST~1.MP3 IKNOWT~1.MP3 I'VEDO~1.MP3 JEOPARDY.MP3 LOVEIS~1.MP3 RIO.MP3 ROCKME~1.MP3 SHOUT.MP3 THELOO~1.MP3 THEPOW~1.MP3 THESAF~1.MP3 WALKIN~1.MP3 WOULDI~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\HARDTO~1.6MO (DOWNA~1.MP3 (JUSTL~1.MP3 AKOOKI~1.MP3 BLACKI~1.MP3 EVERYD~1.MP3 EVERYB~1.MP3 GUANTA~1.MP3 HOLDME~1.MP3 I'MLEA~1.MP3 LAURIE~1.MP3 LIGHTN~1.MP3 MASTER~1.MP3 MYBOYL~1.MP3 NOTHIN~1.MP3 PATAPA~1.MP3 RUMORS.MP3 SAILOR~1.MP3 THEMOR~1.MP3 TOBACC~1.MP3 WALKAW~1.MP3 WITCHI~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\HARDTO~1.560 ANGELO~1.MP3 B-A-B-Y.MP3 DEEPPU~1.MP3 DON'TS~1.MP3 JOHNNY~1.MP3 LET'ST~1.MP3 NEXTPL~1.MP3 ONEBOY~1.MP3 SIXTEE~1.MP3 TAKEAL~1.MP3 TURNAR~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\HARDTO~1.219 BOBBY'~1.MP3 DIANE.MP3 DOMINI~1.MP3 ISAWLI~1.MP3 LITTLE~1.MP3 LITTLE~2.MP3 LOVERP~1.MP3 NAVYBL~1.MP3 PATCHES.MP3 POPSIC~1.MP3 SUGARS~1.MP3 WOODEN~1.MP3 YOUDON~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\DICKCL~1.2 CHAPEL~1.MP3 DUKEOF~1.MP3 EVERYB~1.MP3 HARLEM~1.MP3 LEADER~1.MP3 LETTHE~1.MP3 POETRY~1.MP3 THENYO~1.MP3 THOSEO~1.MP3 TILLIK~1.MP3 TOSSSI~1.MP3 VENUSI~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\DICKBA~1.2 ANGELO~1.MP3 CINNAMON.MP3 EVERLA~1.MP3 EXPRES~1.MP3 GIMMEG~1.MP3 LETITO~1.MP3 MORNIN~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\BILLBO~1 EASIER~1.MP3 FINGER~1.MP3 HE'SSO~1.MP3 LOUIE_~1.MP3 MYBOYF~1.MP3 SUGARS~1.MP3 SURFCI~1.MP3 SURFIN~1.MP3 WALKLI~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\BILLBO~2 ALLEY-~1.MP3 CATHY'~1.MP3 HANDYM~1.MP3 RUNNIN~1.MP3 SAVETH~1.MP3 WALK_D~1.MP3[/SIZE] [SIZE=2]Path: \VARIOU~1\AFTERN~1 CAMPBE~1.MP3 DAWN-K~1.MP3 GLADYS~1.MP3 HARRYN~1.MP3 JIMCRO~1.MP3 JOSEFE~1.MP3 STARBU~1.MP3 ------> Found it! STARLA~1.MP3 STEVEF~1.MP3 THEHOL~1.MP3 THELEM~1.MP3 THERAS~1.MP3[/SIZE] [SIZE=2]Path: \PORTER[/SIZE] [SIZE=2]Path: \PORTER\PORTONE CANDLE~1.MP3 DBBLUE~1.MP3 FLIGHT~1.MP3 FOREVE~1.MP3 JUSTWA~1.MP3 LAKESH~1.MP3 LAYYOU~1.MP3 ONEMOR~1.MP3 WEAREO~1.MP3 WISHFU~1.MP3 Done Searching[/SIZE]
Comments
If you're going to be looking up file names often, but changing the contents of the memory stick infrequently, you should consider having a separate program that recursively goes through the directory structure and builds a database that can be easily searched. This database could be stored on a root-level file. If you want to get fancy, you could use a Winbond SPI flash memory to hold the database. These come in sizes up to several megabytes and there's a driver in the ObEx that provides for named files and includes a program loader.
Thanks for the suggestions Mike. I thought about using a vbscript to create a text file with the filenames and paths to load onto the drive beforehand.
The reason behind this effort was that when playing songs randomly on the VMUSIC2, it informs you of the filename playing, but not the path. I wanted to be able to delete the MP3 currently playing but you have to stop playing it first. When you do that, the VMUSIC returns to the root directory so you don't know what folder the file is in.
EDIT: Of course, with the limiting DOS 8.3 filenames, you could end up with two different MP3s with the same name in different folders.
The Winbond flash memory sounds interesting too!
The Winbond flash memories come in sizes up to 8MB in PDIP and the ObEx driver handles up to that size.
I am very familiar with ID3Tag information and have written DLLs, code to modify tag information, and my own MP3 player in VB6 (using the Windows media player API).
I also wrote a vb program to delete unneccesay tag items (like the embedded album art,etc) and convert unicode tags to ascii (the VMUSIC2 won't handle unicode). Zip file (here)
Digikey has the 8Mb PDIP chips for $1.39 - very reasonable!
I attached a ZIP file with the vbscript and this readme.txt file:
I have over 1010 MP3s on my thumbdrive and the sorted list shows many files with the same shortname in different directories.
For example, the are eight different songs named "YOU'RE~1.MP3" - songs that begin with "You're".
It could be a little tricky for the VMUSIC2 to find the right song to delete...