Author Topic: Playing 24bit WAV files (directly not supported)  (Read 2385 times)

0 Members and 1 Guest are viewing this topic.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
Playing 24bit WAV files (directly not supported)
« on: October 03, 2021, 09:19:49 am »
Hi people,

Still - and probably forever - I'm completely unable to remember working with AND due to bit operations. Therefore, I tried to rewrite my decoding functions, which used strings on the field principle. It work, but the speed is still desperate.

So, now about this thread. SNDOPEN cannot directly play WAV format 24bit. This format is used for various samples of musical instruments. I chose one of them for this program for a demonstration. It has been downloaded from the musicradar website, but it has been a long time. So here is a (slow) way to access and play this music format in QB64. End note - the variable n& on line 91 is estimated and may not give a completely accurate sound.

Try compare this output and your system media player output.

Code: QB64: [Select]
  1. 'how play 24bit wave?
  2. f$ = "24bit.wav"
  3. 'try standard method:
  4. PRINT "Opening "; f$
  5. sndf = _SNDOPEN(f$)
  6. PRINT "Playing "; f$
  7.  
  8.  
  9. PRINT "Nothing? Press any key..."
  10.  
  11. PRINT "Loading file to memory..."
  12. TYPE head24
  13.     chunk AS STRING * 4 '       4 bytes  (RIFF)
  14.     size AS LONG '              4 bytes  (file size)
  15.     fomat AS STRING * 4 '       4 bytes  (WAVE)
  16.     sub1 AS STRING * 4 '        4 bytes  (fmt )
  17.     subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  18.     format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  19.     channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  20.     rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  21.     ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  22.     Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  23.     Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  24.     subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  25.     lenght AS LONG '            4 bytes  Data block size
  26. END TYPE '                     44 bytes  total
  27. DIM H24 AS head24
  28.  
  29.  
  30. OPEN f$ FOR BINARY AS #ff
  31. GET #ff, , H24
  32.  
  33. 'PRINT H24.format
  34. 'PRINT H24.channels
  35. 'PRINT H24.rate
  36. 'PRINT H24.Block
  37. 'PRINT H24.Bits
  38. 'PRINT H24.lenght
  39.  
  40.  
  41. REDIM SHARED BinnaryL(0) AS _UNSIGNED _BYTE 'MUST BE 0
  42. REDIM SHARED BinnaryR(0) AS _UNSIGNED _BYTE 'MUST BE 0
  43. REDIM SHARED AS LONG FinalL(0), FinalR(0)
  44.  
  45.  
  46.  
  47. m = _MEMNEW(H24.lenght)
  48. f$ = SPACE$(H24.lenght)
  49.  
  50. GET #ff, , f$
  51. _MEMPUT m, m.OFFSET, f$
  52. f$ = ""
  53. PRINT "File loaded to memory, decoding it..."
  54.  
  55. DIM AS LONG L, R, D
  56. D& = 0
  57.  
  58. DO UNTIL D& = m.SIZE - 6
  59.  
  60.     FOR L = D& + 2 TO D& STEP -1
  61.         Vl = _MEMGET(m, m.OFFSET + L, _UNSIGNED _BYTE)
  62.         DecimalToBinary Vl, 1, BinnaryL()
  63.     NEXT L
  64.     '24 bites - left channel
  65.  
  66.     FOR R = D& + 5 TO D& + 3 STEP -1
  67.         Vr = _MEMGET(m, m.OFFSET + R, _UNSIGNED _BYTE)
  68.         DecimalToBinary Vr, 1, BinnaryR()
  69.     NEXT R
  70.     '24 bites - right channel
  71.  
  72.     Ul = UBOUND(FinalL)
  73.     REDIM _PRESERVE FinalL(Ul + 1) AS LONG 'higher type - 4-bytes than WAV used 3-bytes type
  74.     Ur = UBOUND(FinalR)
  75.     REDIM _PRESERVE FinalR(Ur + 1) AS LONG
  76.  
  77.     FinalL(Ul) = BinaryToDecimal(BinnaryL())
  78.     FinalR(Ur) = BinaryToDecimal(BinnaryR())
  79.  
  80.     REDIM BinnaryL(0) AS _UNSIGNED _BYTE 'erase binaries
  81.     REDIM BinnaryR(0) AS _UNSIGNED _BYTE
  82.     D& = D& + 6
  83. PRINT "Playing it..."
  84.  
  85. n& = 65535 * 128
  86.  
  87. FOR listen = 0 TO UBOUND(FinalL)
  88.     Ls = FinalL(listen) / n&
  89.     Rs = FinalR(listen) / n&
  90.     _SNDRAW Ls, Rs
  91.     DO UNTIL _SNDRAWLEN = 0: LOOP
  92. PRINT "Erasing memory..."
  93. PRINT "That's all"
  94.  
  95.  
  96.  
  97.  
  98.  
  99. SUB DecimalToBinary (v AS _UNSIGNED _BYTE, HowBytes, Binnary() AS _UNSIGNED _BYTE) 'vstup V prevede na pole o (8 * HowBytes) indexech (1 az HowBytes, nula se nepouziva) a vnich jsou ulozeny binarni hodnoty 0 nebo 1
  100.     u = UBOUND(binnary)
  101.     REDIM _PRESERVE Binnary(u + (8 * HowBytes)) AS _UNSIGNED _BYTE
  102.  
  103.     FOR rj = (8 * HowBytes) - 1 TO 0 STEP -1
  104.         u = u + 1
  105.         IF v AND 2 ^ rj THEN Binnary(u) = 1 ELSE Binnary(u) = 0
  106.     NEXT rj
  107.  
  108. FUNCTION BinaryToDecimal& (Binnary() AS _UNSIGNED _BYTE) ' pro cisla ktera jsou tvorena 3 bytes musi mit pole Binnary celkem 24 indexu (ubound = 24, index 0 se nepouziva)
  109.     DIM btd AS LONG, si AS LONG
  110.     u = UBOUND(binnary)
  111.     FOR si = 1 TO u
  112.         c = Binnary(si)
  113.         Sj = u - si
  114.         btd = btd + (c * 2 ^ Sj)
  115.     NEXT
  116.     BinaryToDecimal& = btd
  117.  

* 24bit.wav (Filesize: 1.23 MB, Downloads: 165)

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Playing 24bit WAV files (directly not supported)
« Reply #1 on: October 03, 2021, 10:56:05 am »
Hi @Petr

If that is not a cymbal strike then my speaker or something is messed up, otherwise it seems to work.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
Re: Playing 24bit WAV files (directly not supported)
« Reply #2 on: October 03, 2021, 12:28:31 pm »
@bplus

Yes, it's a blow to cymbals. The whole composition can then be composed of these and similar individual samples.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
Re: Playing 24bit WAV files (directly not supported)
« Reply #3 on: October 05, 2021, 11:39:45 am »
Found bug in DecimalToBinary SUB, here is upgrade:

Code: QB64: [Select]
  1. 'how play 24bit wave?
  2. f$ = "24bit.wav"
  3. 'try standard method:
  4. PRINT "Opening "; f$
  5. sndf = _SNDOPEN(f$)
  6. PRINT "Playing "; f$
  7.  
  8.  
  9. PRINT "Nothing? Press any key..."
  10.  
  11. PRINT "Loading file to memory..."
  12. TYPE head24
  13.     chunk AS STRING * 4 '       4 bytes  (RIFF)
  14.     size AS LONG '              4 bytes  (file size)
  15.     fomat AS STRING * 4 '       4 bytes  (WAVE)
  16.     sub1 AS STRING * 4 '        4 bytes  (fmt )
  17.     subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  18.     format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  19.     channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  20.     rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  21.     ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  22.     Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  23.     Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  24.     subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  25.     lenght AS LONG '            4 bytes  Data block size
  26. END TYPE '                     44 bytes  total
  27. DIM H24 AS head24
  28.  
  29.  
  30. OPEN f$ FOR BINARY AS #ff
  31. GET #ff, , H24
  32.  
  33. 'PRINT H24.format
  34. 'PRINT H24.channels
  35. 'PRINT H24.rate
  36. 'PRINT H24.Block
  37. 'PRINT H24.Bits
  38. 'PRINT H24.lenght
  39.  
  40.  
  41. REDIM SHARED BinnaryL(0) AS _UNSIGNED _BYTE 'MUST BE 0
  42. REDIM SHARED BinnaryR(0) AS _UNSIGNED _BYTE 'MUST BE 0
  43. REDIM SHARED AS LONG FinalL(0), FinalR(0)
  44.  
  45.  
  46.  
  47. m = _MEMNEW(H24.lenght)
  48. f$ = SPACE$(H24.lenght)
  49.  
  50. GET #ff, , f$
  51. _MEMPUT m, m.OFFSET, f$
  52. f$ = ""
  53. PRINT "File loaded to memory, decoding it..."
  54.  
  55. DIM AS LONG L, R, D
  56. D& = 0
  57.  
  58. DO UNTIL D& = m.SIZE - 6&
  59.  
  60.     FOR L& = D& + 2& TO D& STEP -1&
  61.         Vl = _MEMGET(m, m.OFFSET + L&, _UNSIGNED _BYTE)
  62.         DecimalToBinary Vl, 1, BinnaryL()
  63.     NEXT L
  64.     '24 bites - left channel
  65.  
  66.     FOR R& = D& + 5& TO D& + 3& STEP -1
  67.         Vr = _MEMGET(m, m.OFFSET + R&, _UNSIGNED _BYTE)
  68.         DecimalToBinary Vr, 1, BinnaryR()
  69.     NEXT R
  70.     '24 bites - right channel
  71.  
  72.     Ul = UBOUND(FinalL)
  73.     REDIM _PRESERVE FinalL(Ul + 1) AS LONG 'higher type - 4-bytes than WAV used 3-bytes type
  74.     Ur = UBOUND(FinalR)
  75.     REDIM _PRESERVE FinalR(Ur + 1) AS LONG
  76.  
  77.     FinalL(Ul) = BinaryToDecimal(BinnaryL())
  78.     FinalR(Ur) = BinaryToDecimal(BinnaryR())
  79.  
  80.     REDIM BinnaryL(0) AS _UNSIGNED _BYTE 'erase binaries
  81.     REDIM BinnaryR(0) AS _UNSIGNED _BYTE
  82.     D& = D& + 6
  83. PRINT "Playing it..."
  84.  
  85. n& = 65535 * 127
  86.  
  87. FOR listen = 0 TO UBOUND(FinalL)
  88.     Ls = FinalL(listen) / n&
  89.     Rs = FinalR(listen) / n&
  90.     _SNDRAW Ls, Rs
  91.     DO UNTIL _SNDRAWLEN = 0: LOOP
  92. PRINT "Erasing memory..."
  93. PRINT "That's all"
  94.  
  95.  
  96.  
  97.  
  98.  
  99. SUB DecimalToBinary (v AS _UNSIGNED _BYTE, HowBytes, Binnary() AS _UNSIGNED _BYTE) 'vstup V prevede na pole o (8 * HowBytes) indexech (1 az HowBytes, nula se nepouziva) a vnich jsou ulozeny binarni hodnoty 0 nebo 1
  100.     DIM AS LONG u, rj
  101.     u = UBOUND(binnary)
  102.     REDIM _PRESERVE Binnary(u + (8 * HowBytes)) AS _UNSIGNED _BYTE
  103.     rj = (8 * HowBytes) - 1
  104.  
  105.     DO UNTIL rj = 0
  106.         IF v AND 2 ^ rj THEN Binnary(u) = 1 ELSE Binnary(u) = 0
  107.         u = u + 1
  108.         rj = rj - 1
  109.     LOOP
  110.  
  111. FUNCTION BinaryToDecimal& (Binnary() AS _UNSIGNED _BYTE) ' pro cisla ktera jsou tvorena 3 bytes musi mit pole Binnary celkem 24 indexu (ubound = 24, index 0 se nepouziva)
  112.     DIM btd AS LONG, si AS LONG, c AS LONG, sj AS LONG, u AS LONG
  113.     u = UBOUND(binnary)
  114.     si = 1
  115.     DO UNTIL si = u
  116.         c = Binnary(si)
  117.         sj = u - si
  118.         btd = btd + (c * 2 ^ sj)
  119.         si = si + 1
  120.     LOOP
  121.  
  122.     BinaryToDecimal& = btd
  123.  

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
Re: Playing 24bit WAV files (directly not supported)
« Reply #4 on: October 05, 2021, 07:58:51 pm »
Hi @Petr,

according to your question in the Bin$ thread about omitting leading zeros and possible complications for reversing the values back to decimal I see no problem here. Your BinaryToDecimal&() function should perfectly handle it, given the Binnary() array is setup correctly with the valid bits in the right array indexes.

However, although your way was interresting to study, I've never seen a more complicated way of an arbitrary bitsize little to big endian data converter, so I couldn't resist to cut your code down to the essential loop part, no MEM stuff, no arrays.

The bad sound quality of your converter is not related to the bitwise approach you chose or any arithmetic complicatons because of bit errors. It's simply because you don't make a proper sign extension from 24bit to the 32bit LONGs, hence you only produce positive sample values between 0.0 and 1.0

Code: QB64: [Select]
  1. 'how play 24bit wave?
  2. f$ = "24bit.wav"
  3. 'try standard method:
  4. PRINT "Opening "; f$
  5. sndf = _SNDOPEN(f$)
  6. PRINT "Playing "; f$
  7.  
  8.  
  9. PRINT "Nothing? Press any key...": PRINT
  10.  
  11. PRINT "Loading file..."
  12. TYPE head24
  13.     chunk AS STRING * 4 '       4 bytes  (RIFF)
  14.     size AS LONG '              4 bytes  (file size)
  15.     fomat AS STRING * 4 '       4 bytes  (WAVE)
  16.     sub1 AS STRING * 4 '        4 bytes  (fmt )
  17.     subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  18.     format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  19.     channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  20.     rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  21.     ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  22.     Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  23.     Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  24.     subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  25.     lenght AS LONG '            4 bytes  Data block size
  26. END TYPE '                     44 bytes  total
  27. DIM H24 AS head24
  28.  
  29.  
  30. OPEN f$ FOR BINARY AS #ff
  31. GET #ff, , H24
  32. f$ = SPACE$(H24.lenght)
  33. GET #ff, , f$
  34.  
  35. PRINT "File loaded, decoding and playing it..."
  36.  
  37. FOR samp& = 0 TO (LEN(f$) / 6) - 1 'lenght / block align - 1
  38.     Ls& = 0: Rs& = 0 'clear samples
  39.     FOR i% = 3 TO 1 STEP -1
  40.         'loop trough little endian file data to build samples
  41.         Ls& = (Ls& * 256) + ASC(f$, (samp& * 6) + i%) 'shift current value 8 bits left and add next byte
  42.         Rs& = (Rs& * 256) + ASC(f$, (samp& * 6) + i% + 3)
  43.     NEXT i%
  44.     'if MSB (bit 23) is set (negative sample) then sign extend to LONG
  45.     IF (Ls& AND &H800000) THEN Ls& = (Ls& OR &HFF000000)
  46.     IF (Rs& AND &H800000) THEN Rs& = (Rs& OR &HFF000000)
  47.     'play normalized samples in range -1.0 to +1.0
  48.     _SNDRAW Ls& / 8388608#, Rs& / 8388608#
  49. NEXT samp&
  50.  
  51. PRINT "That's all"
  52.  
  53.  
  54.  
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
Re: Playing 24bit WAV files (directly not supported)
« Reply #5 on: October 06, 2021, 01:33:44 pm »
@RhoSigma

Hi,

I will say it quite openly. I am amazed by the simplicity and efficiency of your version. The reason why mine was complicated there has one main and essential reason and another irrelevant reason.

The main reason is that I'm not very familiar with these bit operations, and your way of writing a program is simply beyond my knowledge.

The irrelevant one is that I will continue to use the samples in another program, so I needed to store them in memory.

When I think about it in retrospect, I'm surprised that it didn't hit me in the eye and ear right away. I assumed the correct samples would play with (255 ^ 3/2) but it didn't work properly. So I was expecting _SNDRAW number 8290688, but it's not there. There is 8388608. I'll try to find out the reason.

I'll put it this way. Many thanks for looking at it and showing me this solution. Lines 45, 46, 49, 50 would not occur to me in any way to solve in this way, it is definitely exactly what I can't do. Yet.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
Re: Playing 24bit WAV files (directly not supported)
« Reply #6 on: October 06, 2021, 02:46:01 pm »
So I figured it out. I forgot zero again. Zero is also a value. Therefore it is of course correct: 256 ^ 3 / 2 = 8 388 608.

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
Re: Playing 24bit WAV files (directly not supported)
« Reply #7 on: October 06, 2021, 02:50:15 pm »
Hi @Petr,

Quote
The main reason is that I'm not very familiar with these bit operations, and your way of writing a program is simply beyond my knowledge.
Maybe a good topic for a tutorial, how to accomplish bit shifts by multiply/divide operations with powers of 2 and how to use AND/OR etc. to check for certain bit states and masking in/out certain bits.

Quote
I assumed the correct samples would play with (255 ^ 3/2) but it didn't work properly. So I was expecting _SNDRAW number 8290688, but it's not there. There is 8388608. I'll try to find out the reason.
Quote
So I figured it out. I forgot zero again. Zero is also a value. Therefore it is of course correct: 256 ^ 3 / 2 = 8 388 608.
Even more simple, it's a 24bit WAV, samples can be positive or negative to the same value, hence (2^24)/2 = 8388608.

Quote
I'll put it this way. Many thanks for looking at it and showing me this solution. Lines 45, 46, 49, 50 would not occur to me in any way to solve in this way, it is definitely exactly what I can't do. Yet.
That's why we are here, to share knowledge and to help others getting better.

My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack