John,
enclosed is a patch for the MIDIFileReader to handle RMID files correctly. If you don't know, RMID is M$ way of packing MIDI files in their own chunk structure. If you haven't seen any of these yet, look into your Windows\Media directory. There should be a couple of those (extension is ".rmi") .
Have fun, Andreas -----------------------------------------------------------------------------
'From Squeak 1.3 of Jan 16, 1998 on 27 January 1998 at 5:27:45 pm'!
!MIDIFileReader methodsFor: 'chunk reading' stamp: 'ar 1/27/98 17:25'! readHeaderChunk
| chunkType chunkSize division | chunkType _ self readChunkType. "AR hack - support RMID files" chunkType = 'RIFF' ifTrue:[chunkType _ self riffSkipToMidiChunk]. "AR endOfHack" chunkType = 'MThd' ifFalse: [^ self error: 'missing MIDI file header chunk']. chunkSize _ self readChunkSize. fileType _ self next16BitWord. trackCount _ self next16BitWord. division _ self next16BitWord. (division anyMask: 16r8000) ifTrue: [self error: 'SMPTE time formats are not yet supported'] ifFalse: [ticksPerQuarter _ division].
"sanity checks" chunkSize = 6 ifFalse: [self report: 'unusual MIDI header size ', chunkSize printString]. (#(0 1 2) includes: fileType) ifFalse: [self report: 'unusual MIDI file type ', fileType printString].
Transcript show: 'Reading Type ', fileType printString, ' MIDI File ('; show: trackCount printString, ' tracks, '; show: ticksPerQuarter printString, ' ticks per quarter note)'; cr. ! !
!MIDIFileReader methodsFor: 'private' stamp: 'ar 1/27/98 17:27'! next32BitWord: msbFirst "Read a 32-bit positive integer from the input stream." "Assume: Stream has at least four bytes left."
| n | n _ stream next: 4. ^msbFirst ifTrue:[((n at: 1) bitShift: 24) + ((n at: 2) bitShift: 16) + ((n at: 3) bitShift: 8) + (n at: 4)] ifFalse:[((n at: 4) bitShift: 24) + ((n at: 3) bitShift: 16) + ((n at: 2) bitShift: 8) + (n at: 1)] ! !
!MIDIFileReader methodsFor: 'private' stamp: 'ar 1/27/98 17:27'! riffSkipToMidiChunk "The file is a RIFF file which may (or may not) contain a MIDI chunk" | dwLength fourcc | "Read length of all data" dwLength := self next32BitWord: false. "Get RIFF contents type " fourcc := self readChunkType. fourcc = 'RMID' ifFalse:[^fourcc]. "We can only read RMID files here" "Search for data" [[fourcc := self readChunkType. dwLength := self next32BitWord: false. fourcc = 'data'] whileFalse:[ "Skip chunk - rounded to word boundary" stream skip: (dwLength + 1 bitAnd: 16rFFFFFFFE). stream atEnd ifTrue:[^'']]. "Data chunk is raw - look into if it contains MIDI data and skip if not" fourcc := self readChunkType. fourcc = 'MThd'] whileFalse:[ "Skip data (chunk - 4bytes) rounded to word boundary" stream skip: (dwLength - 3 bitAnd: 16rFFFFFFFE)]. ^fourcc! !
-----------------------------------------------------------------------------
John,
looks like today is file-format-day ;-) Since I was already investigating multi-media and associated stuff I quickly added a method for reading .wav files. It's a bit slow because SampledSound is mono by default and I had to split up the data stream into different buffer and mix them afterwards. Anyways, I hope you like it.
Have fun, Andreas
PS. Can you tell me how to load such a sound into the SoundSequencerMorph?!
------------------------------------------------------------------------
'From Squeak 1.3 of Jan 16, 1998 on 27 January 1998 at 11:57:55 pm'!
!SampledSound class methodsFor: 'instance creation' stamp: 'ar 1/27/98 23:55'! fromWaveFileNamed: fileName "(SampledSound fromWaveFileNamed: 'c:\windows\media\chimes.wav') play" "| snd fd | fd := FileDirectory on:'c:\windows\media'. fd fileNames do: [:n | (n asLowercase endsWith: '.wav') ifTrue: [ snd _ SampledSound fromWaveFileNamed: (fd pathName,n). snd play. SoundPlayer waitUntilDonePlaying: snd]]." | header stream data type channels samplingRate blockAlign bitsPerSample leftData rightData index dataWord | stream := FileStream oldFileNamed: fileName. header := self readWaveChunk:'fmt ' inRIFF: stream. data := self readWaveChunk: 'data' inRIFF: stream. stream close. stream := ReadStream on: header. type := self next16BitWord: false from: stream. type = 1 ifFalse:[^self error:'Unexpected wave format']. channels := self next16BitWord: false from: stream. (channels < 1 or:[channels > 2]) ifTrue:[^self error:'Unexpected number of wave channels']. samplingRate := self next32BitWord: false from: stream. stream skip: 4. "Skip average bytes per second" blockAlign := self next16BitWord: false from: stream. bitsPerSample := self next16BitWord: false from: stream. (bitsPerSample = 8 or:[bitsPerSample = 16]) ifFalse:[ "Recompute bits per sample" bitsPerSample := (blockAlign // channels) * 8. ]. bitsPerSample = 8 ifTrue:[ data := self convert8bitUnsignedTo16Bit: data. ]. channels = 2 ifTrue:[ leftData := SoundBuffer newMonoSampleCount: data size. rightData := SoundBuffer newMonoSampleCount: data size. stream := ReadStream on: data. index := 1. [stream atEnd] whileFalse:[ dataWord := self next16BitWord: false from: stream. dataWord > 16r8000 ifTrue:[dataWord := dataWord - 16r10000]. leftData at: index put: dataWord. dataWord := self next16BitWord: false from: stream. dataWord > 16r8000 ifTrue:[dataWord := dataWord - 16r10000]. rightData at: index put: dataWord. index := index + 1. ]. ^(MixedSound new) add: (self samples: leftData samplingRate: samplingRate) pan: 0.0; add: (self samples: rightData samplingRate: samplingRate) pan: 1.0; yourself ]. ^self samples: data samplingRate: samplingRate.! !
!SampledSound class methodsFor: 'utilities' stamp: 'ar 1/27/98 23:11'! convert8bitUnsignedTo16Bit: anArray "Convert the given array of samples--assumed to be 8-bit unsigned, linear data--into 16-bit signed samples. Return an array containing the resulting samples. Typically used to read uncompressed WAVE sound data."
| n samples s | n _ anArray size. samples _ SoundBuffer newMonoSampleCount: n. 1 to: n do: [:i | s _ anArray at: i. samples at: i put: (s - 128 * 256)]. ^ samples ! !
!SampledSound class methodsFor: 'WAV reading' stamp: 'ar 1/27/98 23:06'! next16BitWord: msbFirst from: stream "Read a 16-bit positive integer from the input stream." "Assume: Stream has at least two bytes left."
| n | n _ stream next: 2. ^msbFirst ifTrue:[(n at: 1) * 256 + (n at: 2)] ifFalse:[(n at: 2) * 256 + (n at: 1)] ! !
!SampledSound class methodsFor: 'WAV reading' stamp: 'ar 1/27/98 23:06'! next32BitWord: msbFirst from: stream "Read a 32-bit positive integer from the input stream." "Assume: Stream has at least four bytes left."
| n | n _ stream next: 4. ^msbFirst ifTrue:[(n at: 1) * 256 + (n at: 2) * 256 + (n at: 3) * 256 + (n at: 4)] ifFalse:[(n at: 4) * 256 + (n at: 3) * 256 + (n at: 2) * 256 + (n at: 1)] ! !
!SampledSound class methodsFor: 'WAV reading' stamp: 'ar 1/27/98 23:06'! readChunkTypeFrom: stream | s | s _ String new: 4. 1 to: 4 do: [:i | s at: i put: (stream next) asCharacter]. ^ s ! !
!SampledSound class methodsFor: 'WAV reading' stamp: 'ar 1/27/98 23:06'! readWaveChunk: chunkType inRIFF: stream "Search the stream for a format chunk and return its contents" | dwLength fourcc | "Get to binary and skip 'RIFF'" stream reset; binary; skip: 4. "Read length of all data" dwLength := self next32BitWord: false from: stream. "Get RIFF contents type " fourcc := self readChunkTypeFrom: stream. fourcc = 'WAVE' ifFalse:[^nil]. "We can only read WAVE files here" "Search for chunk" [fourcc := self readChunkTypeFrom: stream. dwLength := self next32BitWord: false from: stream. fourcc = chunkType] whileFalse:[ "Skip chunk - rounded to word boundary" stream skip: (dwLength + 1 bitAnd: 16rFFFFFFFE). stream atEnd ifTrue:[^'']]. "Return raw data" ^stream next: dwLength! !
------------------------------------------------------------------------
squeak-dev@lists.squeakfoundation.org