Advertisement
Guest User

Write a WAV file in VBA

a guest
Jun 3rd, 2020
72
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. Option Explicit
  2.  
  3. 'Write a stereo WAV file with arbitrary sample rate, 16-bit signed LPCM
  4. 'Note that since VBA does not support unsigned long (32 bit) values at all, some of the limits
  5. 'for our WAV files are half what the file format supports, but still much longer than we would
  6. 'ever realistically use. VBA writes all binary values to disk as little endian, which is convenient
  7. 'as this is what a WAV file requires. Except for the 4-byte ASCII values, which we write one
  8. 'byte/char at a time as big endian (or "no-endian")
  9. Sub write_wav_file(ByVal filename As String, samplesL() As Double, samplesR() As Double)
  10.    
  11.     Dim fileNumber As Integer
  12.     fileNumber = FreeFile
  13.     Open (ActiveWorkbook.Path & "\" & filename) For Binary Access Write As #fileNumber
  14.        
  15.     Dim fileSize As Long, dataSizeL As Long, dataSizeR As Long
  16.     dataSizeL = 2 * (UBound(samplesL) - (LBound(samplesL) - 1)) '1 sample = 2 bytes
  17.    dataSizeR = 2 * (UBound(samplesR) - (LBound(samplesR) - 1))
  18.     fileSize = dataSizeL + dataSizeR + 44 '44 is the magic number sum of all metadata bytes
  19.    
  20.     '===========
  21.    'RIFF HEADER
  22.    '===========
  23.    'ChunkID: The letters "RIFF" in ASCII, 0x52 49 46 46 big-endian
  24.    Put #fileNumber, , CByte(&H52)
  25.     Put #fileNumber, , CByte(&H49)
  26.     Put #fileNumber, , CByte(&H46)
  27.     Put #fileNumber, , CByte(&H46) '4
  28.    
  29.     'ChunkSize: little-endian, 4 bytes. Size of the entire file in bytes,
  30.    'minus 8 bytes for the first 4 bytes and these 4 bytes
  31.    Put #fileNumber, , CLng(fileSize - 8) '8
  32.    
  33.     'Format: The letters "WAVE" in ASCII, 0x57 41 56 45 big-endian
  34.    Put #fileNumber, , CByte(&H57)
  35.     Put #fileNumber, , CByte(&H41)
  36.     Put #fileNumber, , CByte(&H56)
  37.     Put #fileNumber, , CByte(&H45) '12
  38.    
  39.     '===========
  40.    'SUB CHUNK 1
  41.    '===========
  42.    'Subchunk1ID: The letters "fmt " (with space!) in ASCII, 0x66 6D 74 20 big-endian
  43.    Put #fileNumber, , CByte(&H66)
  44.     Put #fileNumber, , CByte(&H6D)
  45.     Put #fileNumber, , CByte(&H74)
  46.     Put #fileNumber, , CByte(&H20) '16
  47.    
  48.     'Subchunk1Size: little endian, 4 bytes. Size of sub chunk 1, not including the first 8 bytes
  49.    'It's always 16 for our WAV file
  50.    Put #fileNumber, , CLng(16) '20
  51.    
  52.     'AudioFormat: little endian. 2 bytes, 0x01 for PCM
  53.    Put #fileNumber, , CInt(1) '22
  54.    
  55.     'NumChannels: little endian. 2 bytes, 0x02 for stereo
  56.    Put #fileNumber, , CInt(2) '24
  57.    
  58.     'SampleRate: little endian, 4 bytes
  59.    Put #fileNumber, , CLng(SAMPLERATE) '28
  60.    
  61.     'ByteRate: little endian, 4 bytes. SampleRate * NumChannels * BytesPerSample
  62.    Put #fileNumber, , CLng(SAMPLERATE * 4) '32
  63.    
  64.     'BlockAlign: little endian, 2 bytes. Number of bytes for one sample including all channels
  65.    'NumChannels * BytesPerSample = 4 for us
  66.    Put #fileNumber, , CInt(4) '34
  67.    
  68.     'BitsPerSample: little endian, 2 bytes. Per channel, so 16 for us
  69.    Put #fileNumber, , CInt(16) '36
  70.    
  71.     '===========
  72.    'SUB CHUNK 2
  73.    '===========
  74.    'SubChunk2ID: The letters "data" in ASCII, 0x64 61 74 61 big-endian
  75.    Put #fileNumber, , CByte(&H64)
  76.     Put #fileNumber, , CByte(&H61)
  77.     Put #fileNumber, , CByte(&H74)
  78.     Put #fileNumber, , CByte(&H61) '40
  79.    
  80.     'Subchunk2Size: little endian, 4 bytes. The number of bytes in the data,
  81.    'not including the first 8 bytes of sub chunk 2
  82.    Put #fileNumber, , CLng(dataSizeL + dataSizeR) '44
  83.    
  84.     'The actual sound data, right channel samples first, then left channel samples
  85.    Dim i As Long
  86.     For i = LBound(samplesL) To UBound(samplesL)
  87.         Put #fileNumber, , double_to_lpcm(samplesL(i))
  88.         Put #fileNumber, , double_to_lpcm(samplesR(i))
  89.     Next i
  90.    
  91.     Close #fileNumber
  92. End Sub
  93.  
  94. 'Convert a 64-bit double to a 16-bit signed audio sample
  95. 'Double audio samples are typically in [-1, 1] which represents -inf db (0) to 0db (+/-1)
  96. 'We clip our input double samples to this range otherwise we get wraparound distortion, the
  97. 'worst possible distortion
  98. Function double_to_lpcm(sample As Double) As Integer
  99.     If sample > 1 Then sample = 1 Else If sample < (-1) Then sample = (-1)
  100.     sample = sample * CDbl(&H7FFF)
  101.     double_to_lpcm = CInt(sample)
  102. End Function
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement