Author Topic: Sound Question  (Read 4253 times)

0 Members and 1 Guest are viewing this topic.

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Sound Question
« on: October 31, 2019, 10:35:27 pm »
I'm 99% sure this is impossible in QB64, but is there a way to make a graphical display per ms of sound output? I'm talking about the same display some sound programs have like the old Winamp or others that show lines going up and down showing spikes of music volume and/or treble and bass output amounts. I looked into this deeply a few months ago but couldn't find this on the QB64.org Wiki page. I think all they have is the simple way to load, play, stop, FF, rewind, and pause. I started on a music player once that did these things but gave up because there's just so many better programs out there. I remember someone posting their sound player on my forum thread about it. I'm just wondering if there can be that graphical output. Thank you.
I wonder if a PORT command would work somehow? Although I do use a USB sound card.
« Last Edit: October 31, 2019, 10:37:51 pm by SierraKen »

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Sound Question
« Reply #1 on: October 31, 2019, 11:32:06 pm »
Petr has worked several programs which do this.  If you search the forums, you can probably find some nice examples of his stuff floating around a few of them.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: Sound Question
« Reply #2 on: November 01, 2019, 01:37:56 am »
Thanks but I couldn't find it. I think I've scoured the entire Google database just about as well. lol I've also tried _MEM with _MEMGET using the Wiki examples for memory blocks, but I'm guessing it doesn't use those addresses for sound. I also looked at the old fashioned way with OUT and INP but QB64 only uses INP for 2 addresses which I don't believe are sound ports. My USB sound card probably wouldn't use that anyway.
Petr does any of this sound familiar? Any help is always appreciated.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Sound Question
« Reply #3 on: November 01, 2019, 07:05:25 am »
Hi SierraKen,
You can program the visualization of the sound with QB64. For the function of the program I highly recommend first to write a program that without visualization plays the sound in uncompressed WAV format. This music format is very easy to read and play, unlike compressed MP3 music for example. (Playing WAV is as easy as changing a bulb in a chandelier, playing MP3 is fundamentally, much more complicated.)
For visualization, you need detailed knowledge of the internal structure of the WAV file and the _SNDRAW, _SNDRAWLEN commands. Nothing more. But, at the outset, I have two important things to point out to you. First: There is an unknown, uncorrected bug that causes SNDRAW to play very strange stereo, rather mono sound, and that SNDRAW contains an internal buffer that you have to watch for audio and video synchronization, and that is not easy if you want precise visualization . If you look at my YouTube channel, there are some visuals written in QB64.
Another thing to note before writing a visualization is that the 16-bit stereo record contains 44100 samples (numbers). If you write the program incorrectly and want to display each sample, then the program will hang. It is necessary to take the mathematical average for the time period and use it for visualization.
To load a WAV music file, I recommend wikipedia to find the internal structure of the WAV file (it's not complicated, really) and then you'll read it using the GET command.

I also wrote MP3 playback, but I used a third-party decoder that can save MP3 to RAW format. The RAW format is the same as the WAV format, but without the file header, which is absolutely essential for proper file content playback. This program works only with some MP3 formats, it decodes the MP3 header, which is written so that individual bits of specific bytes carry the necessary information. MP3 is a compression format, so even the header is written in the smallest possible way. The header of the program must be read to know how the content in the RAW file is decompressed (is it mono or stereo sound ?, is it 8 bit, 16 bit, 24 bit sound?) Each such file is read differently, according to this information. After decoding, playback is the same as WAV format.

Unfortunately, there is no function in QB64 that allows direct access to music samples that are sent to the sound card. MEM commands are used only for graphical and array operations, they cannot be used to obtain RAW audio data, so to visualize you need this path to read data from a file + or write your own audio decoder. Apparently this is because QB64 uses third-party music decoders and does not allow licenses. Or, no one thought anyone wanted access to RAW audio data.

This is my audio PLAYER for WAV files WITHOUT visualization:

Code: QB64: [Select]
  1. wav "7.wav" 'insert uncompressed wav audio filename here
  2.  
  3. SUB wav (file$)
  4.     TYPE head
  5.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  6.         size AS LONG '              4 bytes  (?E??)
  7.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  8.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  9.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  10.         format AS STRING * 2 '      2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  11.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  12.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  13.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  14.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  15.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  16.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  17.     END TYPE '                     40 bytes  total
  18.     DIM H AS head
  19.     ch = FREEFILE
  20.  
  21.     IF _FILEEXISTS(file$) THEN OPEN file$ FOR BINARY AS #ch ELSE PRINT file$; " not found": SLEEP 2: SYSTEM
  22.     GET #ch, , H
  23.  
  24.     block = H.Block
  25.     RATE = H.rate
  26.     chan = H.channels
  27.     bits = H.Bits
  28.  
  29.     DO WHILE NOT EOF(ch)
  30.         IF bits = 16 AND chan = 2 THEN
  31.             REDIM lefi AS INTEGER, righi AS INTEGER
  32.             GET #ch, , lefi
  33.             GET #ch, , righi
  34.             lef = lefi / RATE
  35.             righ = righi / RATE
  36.         END IF
  37.  
  38.         IF bits = 16 AND chan = 1 THEN
  39.             REDIM leftMono AS INTEGER
  40.             GET #ch, , leftMono
  41.             lef = leftMono / RATE
  42.             righ = leftMono / RATE
  43.         END IF
  44.  
  45.         IF bits = 8 AND chan = 2 THEN
  46.             REDIM lleft8 AS _UNSIGNED _BYTE, rright8 AS _UNSIGNED _BYTE
  47.             GET #ch, , lleft8
  48.             GET #ch, , rright8
  49.             lef = lleft8 / 256
  50.             righ = rright8 / 256
  51.         END IF
  52.  
  53.         IF bits = 8 AND chan = 1 THEN
  54.             REDIM mono8 AS _UNSIGNED _BYTE
  55.             GET #ch, , mono8
  56.             lef = mono8 / 256
  57.             righ = lef
  58.         END IF
  59.  
  60.         IF RATE > 44100 THEN frekvence = RATE ELSE frekvence = 44100
  61.         FOR plll = 1 TO frekvence / RATE
  62.             _SNDRAW lef, righ
  63.         NEXT plll
  64.  
  65.         DO WHILE _SNDRAWLEN > 0: LOOP: REM comment this
  66.     LOOP
  67.     CLOSE ch
  68.  


Also see here: https://www.qb64.org/forum/index.php?topic=1399.msg105829#msg105829
« Last Edit: November 01, 2019, 09:02:52 am by Petr »

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: Sound Question
« Reply #4 on: November 01, 2019, 07:59:06 pm »
Thanks Petr. But I am brand new with the GET command and I tried your WAV program, which works great by the way, but not knowing which variable to use or how to use the GET command, I just tried a bunch of things you had on there but couldn't get a variable that changed as the song went along. I also tried Wikipedia and other website about the .WAV file structure which is interesting, but without knowing how to use GET and what code to use, I'm at a loss. But it sure interests me a lot. It might be over my head, especially if I need to know what things to find within the .WAV file that I can't really see right now. I appreciate your help.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Sound Question
« Reply #5 on: November 02, 2019, 04:33:39 am »
Hi SierraKen. For drawing music curve use my source code and variables LEF and RIGH. This are sound values read from WAV file, always in range -1 to 1 (and -1 to 1 is also valid value for _SNDRAW). You can study my code, which show you how GET works, program read music samples from WAV so as is writed now.

I add small upgraded source code, now with easy visualisation. Do some experiments with it :)

Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(800, 600, 256)
  2.  
  3. wav "7.wav" 'insert uncompressed wav audio filename here
  4.  
  5. SUB wav (file$)
  6.     TYPE head
  7.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  8.         size AS LONG '              4 bytes  (?E??)
  9.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  10.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  11.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  12.         format AS STRING * 2 '      2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  13.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  14.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  15.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  16.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  17.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  18.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  19.     END TYPE '                     40 bytes  total
  20.     DIM H AS head
  21.     ch = FREEFILE
  22.  
  23.     IF _FILEEXISTS(file$) THEN OPEN file$ FOR BINARY AS #ch ELSE PRINT file$; " not found": SLEEP 2: SYSTEM
  24.     GET #ch, , H
  25.  
  26.     block = H.Block
  27.     RATE = H.rate
  28.     chan = H.channels
  29.     bits = H.Bits
  30.  
  31.  
  32.     DO WHILE NOT EOF(ch)
  33.  
  34.         vz = 44100 / 80
  35.         IF (SEEK(ch) - 40) MOD vz = 0 THEN
  36.             visual lefVis / vz, righVis / vz
  37.             lefVis = 0
  38.             righVis = 0
  39.         END IF
  40.         lefVis = lefVis + ABS(lef)
  41.         righVis = righVis + ABS(righ)
  42.  
  43.  
  44.  
  45.         IF bits = 16 AND chan = 2 THEN
  46.             REDIM lefi AS INTEGER, righi AS INTEGER
  47.             GET #ch, , lefi
  48.             GET #ch, , righi
  49.             lef = lefi / RATE
  50.             righ = righi / RATE
  51.         END IF
  52.  
  53.         IF bits = 16 AND chan = 1 THEN
  54.             REDIM leftMono AS INTEGER
  55.             GET #ch, , leftMono
  56.             lef = leftMono / RATE
  57.             righ = leftMono / RATE
  58.         END IF
  59.  
  60.         IF bits = 8 AND chan = 2 THEN
  61.             REDIM lleft8 AS _UNSIGNED _BYTE, rright8 AS _UNSIGNED _BYTE
  62.             GET #ch, , lleft8
  63.             GET #ch, , rright8
  64.             lef = lleft8 / 256
  65.             righ = rright8 / 256
  66.         END IF
  67.  
  68.         IF bits = 8 AND chan = 1 THEN
  69.             REDIM mono8 AS _UNSIGNED _BYTE
  70.             GET #ch, , mono8
  71.             lef = mono8 / 256
  72.             righ = lef
  73.         END IF
  74.  
  75.         IF RATE > 44100 THEN frekvence = RATE ELSE frekvence = 44100
  76.         FOR plll = 1 TO frekvence / RATE
  77.             _SNDRAW lef, righ
  78.         NEXT plll
  79.  
  80.         DO WHILE _SNDRAWLEN > 0: LOOP: REM By commenting this row you DAMAGE SOUND / VIDEO synchronizing. TRY IT.
  81.  
  82.  
  83.     LOOP
  84.     CLOSE ch
  85.  
  86. SUB visual (lef, righ)
  87.     SHARED x, oldx1, oldx2
  88.     STP = 5 ' TRY rewriting STP value to other
  89.     x1 = lef * 200
  90.     x2 = righ * 200
  91.  
  92.     IF oldx1 THEN
  93.         LINE (x, 0)-(x + STP, 400), 0, BF
  94.         LINE (x, 200 - x1)-(x - STP, 200 - oldx1), 15
  95.  
  96.         LINE (x, 100 - x2)-(x - STP, 100 - oldx2), 14
  97.  
  98.     END IF
  99.     oldx1 = x1
  100.     oldx2 = x2
  101.     x = x + STP: IF x > _WIDTH THEN x = 0
  102.  
  103.     LINE (100, 600)-(300, 300), 0, BF
  104.     LINE (100, 600)-(300, 600 - 500 * lef), 14, BF
  105.  
  106.     LINE (700, 600)-(500, 300), 0, BF
  107.     LINE (700, 600)-(500, 600 - 500 * righ), 3, BF
  108.  


Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: Sound Question
« Reply #6 on: November 02, 2019, 05:44:40 pm »
That is so awesome! Thanks Petr! I added 2 displays as well as yours and changed the way your line goes over the old line, I did a CLS as soon as it gets to the end of the screen.
Press the Space Bar to switch between displays.  :)

Code: QB64: [Select]
  1. 'Almost all of this was made by Petr from the QB64.org forum.
  2. 'Some of the displays added by SierraKen (Ken G.).
  3. _TITLE "WAV Music Player - Space Bar Changes Display When Playing. Esc to Quit."
  4. SCREEN _NEWIMAGE(800, 600, 256)
  5.     CLS
  6.     PRINT: PRINT: PRINT
  7.     INPUT "Type .WAV music filename here or only Enter to quit:", nm$
  8.     IF nm$ = "" THEN END
  9.     CLS
  10.     wav nm$ 'insert uncompressed wav audio filename here
  11. SUB wav (file$)
  12.     TYPE head
  13.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  14.         size AS LONG '              4 bytes  (?E??)
  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 STRING * 2 '      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.     END TYPE '                     40 bytes  total
  26.     DIM H AS head
  27.     ch = FREEFILE
  28.  
  29.     IF _FILEEXISTS(file$) THEN OPEN file$ FOR BINARY AS #ch ELSE PRINT file$; " not found": SLEEP 2: SYSTEM
  30.     GET #ch, , H
  31.  
  32.     block = H.Block
  33.     RATE = H.rate
  34.     chan = H.channels
  35.     bits = H.Bits
  36.  
  37.  
  38.     DO WHILE NOT EOF(ch)
  39.  
  40.         vz = 44100 / 80
  41.         IF (SEEK(ch) - 40) MOD vz = 0 THEN
  42.             visual lefVis / vz, righVis / vz
  43.             lefVis = 0
  44.             righVis = 0
  45.         END IF
  46.         lefVis = lefVis + ABS(lef)
  47.         righVis = righVis + ABS(righ)
  48.  
  49.  
  50.  
  51.         IF bits = 16 AND chan = 2 THEN
  52.             REDIM lefi AS INTEGER, righi AS INTEGER
  53.             GET #ch, , lefi
  54.             GET #ch, , righi
  55.             lef = lefi / RATE
  56.             righ = righi / RATE
  57.         END IF
  58.  
  59.         IF bits = 16 AND chan = 1 THEN
  60.             REDIM leftMono AS INTEGER
  61.             GET #ch, , leftMono
  62.             lef = leftMono / RATE
  63.             righ = leftMono / RATE
  64.         END IF
  65.  
  66.         IF bits = 8 AND chan = 2 THEN
  67.             REDIM lleft8 AS _UNSIGNED _BYTE, rright8 AS _UNSIGNED _BYTE
  68.             GET #ch, , lleft8
  69.             GET #ch, , rright8
  70.             lef = lleft8 / 256
  71.             righ = rright8 / 256
  72.         END IF
  73.  
  74.         IF bits = 8 AND chan = 1 THEN
  75.             REDIM mono8 AS _UNSIGNED _BYTE
  76.             GET #ch, , mono8
  77.             lef = mono8 / 256
  78.             righ = lef
  79.         END IF
  80.  
  81.         IF RATE > 44100 THEN frekvence = RATE ELSE frekvence = 44100
  82.         FOR plll = 1 TO frekvence / RATE
  83.             _SNDRAW lef, righ
  84.         NEXT plll
  85.  
  86.         DO WHILE _SNDRAWLEN > 0: LOOP: REM By commenting this row you DAMAGE SOUND / VIDEO synchronizing. TRY IT.
  87.  
  88.  
  89.     LOOP
  90.     CLOSE ch
  91.  
  92. SUB visual (lef, righ)
  93.     SHARED x, oldx1, oldx2, disp, tt
  94.     STP = 5 ' TRY rewriting STP value to other
  95.     x1 = lef * 200
  96.     x2 = righ * 200
  97.     a$ = INKEY$
  98.     IF a$ = " " THEN CLS: disp = disp + 1: tt = 0
  99.     IF a$ = CHR$(27) THEN END
  100.     IF disp > 2 THEN disp = 0
  101.  
  102.     IF oldx1 AND disp = 2 THEN
  103.         tt = tt + 1
  104.         IF tt < 5 THEN GOTO skip:
  105.         tt = 0
  106.         c1 = RND * 255
  107.         c2 = RND * 255
  108.         c3 = RND * 255
  109.         xx = RND * 800
  110.         yy = RND * 600
  111.         LINE (xx, yy)-((xx + x1) * 2, (yy + x2) * 2), _RGB32(c1, c2, c3), BF
  112.         skip:
  113.     END IF
  114.     IF oldx1 AND disp = 1 THEN
  115.         tt = tt + 1
  116.         IF tt < 5 THEN GOTO skip2:
  117.         tt = 0
  118.         c1 = RND * 255
  119.         c2 = RND * 255
  120.         c3 = RND * 255
  121.         xx = RND * 800
  122.         yy = RND * 600
  123.         CIRCLE (xx, yy), x1 * 2, _RGB32(c1, c2, c3)
  124.         PAINT (xx, yy), _RGB32(c1, c2, c3)
  125.         skip2:
  126.     END IF
  127.     IF oldx1 AND disp = 0 THEN
  128.         IF x = 800 THEN CLS: x = 0
  129.         LINE (x, 0)-(x + STP, 400), 0, BF
  130.         LINE (x, 200 - x1)-(x - STP, 200 - oldx1), 15
  131.  
  132.         LINE (x, 100 - x2)-(x - STP, 100 - oldx2), 14
  133.     END IF
  134.  
  135.     oldx1 = x1
  136.     oldx2 = x2
  137.     x = x + STP: IF x > _WIDTH THEN x = 0
  138.  
  139.     LINE (100, 600)-(300, 400), 0, BF
  140.     LINE (100, 600)-(300, 600 - 500 * lef), 14, BF
  141.  
  142.     LINE (700, 600)-(500, 400), 0, BF
  143.     LINE (700, 600)-(500, 600 - 500 * righ), 3, BF
  144.  

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: Sound Question
« Reply #7 on: November 03, 2019, 01:03:16 am »
OK I got 7 displays on it now. I'm going to make a new thread so people can see this better and will post it there.