martedì 4 novembre 2008

Macro e Rock&Roll (2)

Come preannunciato nel post precedente, tenterò di fare qualche osservazione più approfondita sul primo video musicale su Excel. Se non fosse già chiaro, lo scopo finale di tutte queste elucubrazioni è quello di "tradurre" il codice originale VBA in StarBasic, in modo da ottenere il primo video musicale per OpenOffice.org :-)
Il file originale contiene alcuni moduli macro scritti ovviamente in VBA (Visual Basic for Applications):
  • FileExtractor
  • LoadModule
  • Module
  • Sheet1
  • ThisWorkbook
Quando il documento Excel viene aperto da OpenOffice.org Calc, il codice contenuto in tali moduli viene importato in forma commentata, ovvero il filtro di importazione antepone un'istruzione Rem a tutte le linee di codice. Inoltre, tutto il codice di ciascun modulo viene racchiuso in una routine che prende il nome del modulo stesso.
Con un cerca & sostituisci si possono eliminare velocemente tutte le istruzioni Rem, mentre le routine aggiunte dal filtro di importazione dovranno essere eliminate manualmente.
Dopo queste operazioni di pulizia, il codice non sarà comunque compatibile con Calc ma potremo osservarlo più comodamente.

Modulo FileExtractor
Questo modulo contiene una sola routine: ExtractWAV.
L'osservazione del codice ci rivela che il nostro documento Excel "nasconde" un file audio in formato wav. In effetti il file wav è stato accodato al file Excel.
Evidentemente gli sviluppatori non hanno trovato un metodo meno rozzo per nascondere un file alieno all'interno di un documento Excel.
Va sottolineato che OpenOffice.org permette di includere contenuti alieni nei documenti usando un approccio ben più raffinato (che magari vedremo in futuro)
Esaminando i contenuti del documento Excel, si nota anche un oggetto OLE di circa 1.9Mb che si rivela essere una copia dello stesso file wav accodato. Si tratta forse del residuo di qualche esperimento che non ha dato il risultato voluto e porta a raddoppiare inutilmente la dimensione del file da scaricare.
La routine ExtractFile viene chiamata durante l'apertura del documento e si occupa appunto di estrarre il file wav dalla coda del documento Excel. Il codice è abbastanza semplice e per lavorare con il file-system non usa oggetti API ma le semplici funzioni runtime offerte da VBA e che sono presenti anche i StarBasic.
Di fatto, la routine sarebbe quasi completamente compatibile con StarBasic a parte alcune semplici chiamate API
Vediamo dunque come "tradurre" il resto del codice in modo da estrarre il file wav.
La routine si può scomporre in tre blocchi:
Nel primo blocco il documento Excel con accodato il file wav viene interamente caricato in memoria sotto forma di array di bytes:
VBA:
tmpFileName = ThisWorkbook.Path & _
Application.PathSeparator & ThisWorkbook.Name
myFileId = FreeFile
Open tmpFileName For Binary As #myFileId
MyFileLen = LOF(myFileId)
ReDim myArr(MyFileLen - 1)
Get myFileId, , myArr()
Close myFileId

La prima linea di codice recupera il percorso del documento e la traduzione in StarBasic risulta particolarmente compatta:
tmpFileName = ThisComponent.URL
Per il resto si tratta di istruzioni che esistono anche in Starbasic, quindi il codice dovrebbe essere direttamente eseguibile.
Purtroppo, la versione Starbasic di Get funziona solo con le stringhe e non con gli arrays nonostante la documentazione sostenga il contrario. Si tratta indubbiamente di un bug, che segnalerò quanto prima.
Poco male perchè mediante l'API di OpenOffice.org è possibile utilizzare un approccio decisamente più elegante e potente:
StarBasic:
oUcb = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
oFileStream = oUcb.openFileRead(ThisComponent.URL)
iFileLen = oFileStream.Length
Dim myArr() As Byte
iResult = oFileStream.readBytes(myArr(), iFileLen)
Ora che abbiamo ottenuto un array contenente l'intero documento, occorre analizzarlo per estrarre la parte finale contenente il file wav. Questo viene effettuato nel secondo blocco di codice:
VBA:
Application.ScreenUpdating = False
i = 0
Do While i < MyFileLen
If myArr(i) = &H52 Then 'Looking for RIFF
If myArr(i + 1) = &H49 And _
myArr(i + 2) = &H46 And _
myArr(i + 3) = &H46 Then
FileLen = CLng(&H1000000) * myArr(i + 7) + _
CLng(&H10000) * myArr(i + 6) + _
CLng(&H100) * myArr(i + 5) + _
myArr(i + 4)
FileLen = FileLen + 8
ReDim fileArray(FileLen - 1)
For myIndex = 0 To FileLen - 1
fileArray(myIndex) = myArr(i + myIndex)
Next myIndex
Exit Do
Else
i = i + 4
End If
Else
i = i + 1
End If
Loop
La prima istruzione del blocco dovrebbe servire per inibire il refresh della finestra durante le elaborazioni successive, migliorando le prestazioni, ma pare che gli sviluppatori abbiano scordato di ripristinare lo stato della proprietà a True una volta terminate le elaborazioni.
L'equivalente StarBasic di questa tecnica è il seguente:
StarBasic:
ThisComponent.lockControllers
...(codice da eseguire)
ThisComponent.unlockControllers
Nel nostro caso il documento finale userà una tecnica differente per includere il file wav e useremo la routine che stiamo traducendo solo una volta, perciò possiamo tranquillamente omettere queste tecniche per migliorare le performances.
Tutte le rimanenti istruzioni del secondo blocco possono essere eseguite direttamente in StarBasic, senza nessuna modifica.
Come si può vedere, il codice effettua una scansione dell'array precedentemente creato alla ricerca della stringa "AIFF" che segna l'inizio del file wav da estrarre.
Tutti i bytes a partire da questa stringa vengono posti in un secondo array chiamato fileArray() che, una volta terminato il ciclo conterrà il file wav sotto forma di sequenza di bytes.
Il terzo ed ultimo blocco prende l'array creato nel blocco precedente e lo salva in un file:
VBA:
myFileId = FreeFile

tmpFileName = _
Left(tmpFileName, Len(tmpFileName) - 4) & ".wav"
Open tmpFileName For Binary As #myFileId
Put #myFileId, , fileArray
Close myFileId
Per il problema citato precedentemente con l'istruzione Get non possiamo usare queste linee di codice così come sono ma dovremo usare l'approccio basato sull'API di OpenOffice.org:
StarBasic:
sOutUrl = Left(ThisComponent.URL, Len(ThisComponent.URL) - 4) & ".wav"
oFileStream = oUcb.openFileWrite(sOutUrl)
oFileStream.writeBytes(fileArray())
oFileStream.flush
oFileStream.closeOutput
Bene! siamo giunti al termine di questo faticoso lavoro. Detto per inciso, la routine ottenuta, sebbene funzionante è ben lungi dall'essere ottimizzata, anzi, il codice VBA di partenza è particolarmente sprecone sia in termini di RAM che di processore, ma, dato che non siamo interessati ad usare le stesse tecniche nel nostro documento Calc, non vale la pena di correggere l'impostazione data dagli sviluppatori originali.
Mettendo assieme i pezzi e lanciando la routine otterremo (dopo paziente attesa) il file acdc.wav perfettamente funzionante e contenente circa 45 secondi di puro metallo pesante :-)
Mettiamolo da parte perchè nei prossimi post vedremo come utilizzarlo nella versione finale del primo video musicale per OpenOffice.org
A presto!

Nessun commento: