Author Topic: Simple Piano  (Read 8916 times)

0 Members and 1 Guest are viewing this topic.

Offline TerryRitchie

  • Seasoned Forum Regular
  • Posts: 495
  • Semper Fidelis
    • View Profile
Re: Simple Piano
« Reply #15 on: March 26, 2020, 10:16:09 am »
For internal file commands like OPEN, image, sound and font, etc. QB64 does the conversion, so it doesn't matter.

For SHELL calls, they remain unchanged. It is advisable, if it's for SHELL calls, to create a precompiler block and define the separator:

Code: QB64: [Select]
  1. $IF WIN THEN
  2.     pathSep$ = "\"
  3.     pathSep$ = "/"

That's what I use in my code.

Thanks for the clarification. :-)
In order to understand recursion, one must first understand recursion.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Simple Piano
« Reply #16 on: March 26, 2020, 11:32:53 am »
Quote
Hmmm, after reading your request I thought "Yeah, that would be cool". But then a few seconds more thinking and ... "How the heck would I do that?" Does anyone have any suggestions as a starting point?

Hi
The solution exists. I'll take it nicely from the beginning and first lightly:
You must not use _SNDPLAY but _SNDRAW and a numeric data field (which is the actual WAV file data). Therefore, it is good to first convert OGG files to WAV format, you load them into one huge array, it will basically be a buffer temporary memory) and then you have to introduce one auxiliary array that will determine from which record to which record in the buffer field the data for one WAV sound.

example:
(1.WAV is array index 0 to 55632, 2.WAV is array index 55633 to 99596...)

After pressing the key, the required RAW audio data is read from the large buffer field. Let's say you press the "S" key and it will play a 2.wav file (2.ogg in your case) Before this is played, the audio data of this file must be written in another field, say "output sound". If another button is pressed at that time, the field must be analyzed as "output sound" and samples in that field that have not been played must be averaged along with the new samples.

What I mean. Suppose it plays a 2 second sound. At 0.5 seconds from the start of sound playback, you press another button for another sound in lenght 5 seconds. The program must mix 1.5 seconds of the original sound with the new sound and add new samples from the new 3.5 second sample at the same time. This creates a huge array of samples, which can then be easily played with the _SNDRAW statement and saved using my program.

Now i start to write small example.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Simple Piano
« Reply #17 on: March 26, 2020, 01:26:53 pm »
Soooo.... i am back :)

I had to modify the program to play WAV files and then search for my SaveSound. Well, on this forum is not, as I found out, apparently I put it on the old forum. Because I have a really awful mess on my hard drive, I had to plug in my brain and write at least a shortened version of SaveSound. So, this program stores audio in WAV, 16 bit, stereo format. The first thing he does is load both audio files into one field. These are then saved in a single audio file. You need the attached files to try. I'll add a sample of how to mix these sounds.

Code: QB64: [Select]
  1. TYPE Snd
  2.     Left AS SINGLE
  3.     Right AS SINGLE
  4. REDIM SHARED OutputSound(-1) AS Snd
  5.  
  6.  
  7. WAVtoRAW "1.wav"
  8. WAVtoRAW "30.wav"
  9. RAWPLAY OutputSound()
  10. SAVESOUND16S OutputSound(), "TwotoOne.wav"
  11.  
  12. PRINT "Try use Windows Media Player and run created file TwotoOne.wav"
  13.  
  14. SUB RAWPLAY (arr() AS Snd)
  15.     FOR p = 1 TO UBOUND(arr)
  16.         _SNDRAW arr(p).Left, arr(p).Right
  17.     NEXT
  18.  
  19.  
  20. SUB WAVtoRAW (file$)
  21.     TYPE head
  22.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  23.         size AS LONG '              4 bytes  (?E??)
  24.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  25.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  26.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  27.         format AS STRING * 2 '      2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  28.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  29.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  30.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  31.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  32.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  33.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  34.     END TYPE '                     40 bytes  total
  35.     DIM H AS head
  36.     ch = FREEFILE
  37.  
  38.     IF _FILEEXISTS(file$) THEN OPEN file$ FOR BINARY AS #ch ELSE PRINT file$; " not found": SLEEP 2: SYSTEM
  39.     GET #ch, , H
  40.  
  41.     block = H.Block
  42.     RATE = H.rate
  43.     chan = H.channels
  44.     bits = H.Bits
  45.  
  46.     SEEK #ch, 49
  47.     DO WHILE NOT EOF(ch)
  48.         '------------------------------------------ UPGRADE FOR SYNTH.WAV ------------------------------------------------------------------ IF STEREO
  49.         IF bits = 32 AND chan = 2 THEN
  50.             DO WHILE NOT EOF(ch)
  51.                 REDIM lefi32 AS SINGLE, righi32 AS SINGLE ' MAYBE! FOR 64 bit WAV here use DOUBLE TYPE. (NOT TESTED with 64 bit WAV)
  52.                 GET #ch, , lefi32
  53.                 GET #ch, , righi32
  54.                 _SNDRAW lefi32, righi32
  55.                 DO WHILE _SNDRAWLEN > 0: LOOP
  56.             LOOP
  57.             END
  58.         END IF
  59.         '------------------------------------------ UPGRADE FOR SYNTH.WAV ------------------------------------------------------------------ IF MONO
  60.         IF bits = 32 AND chan = 1 THEN
  61.             DO WHILE NOT EOF(ch)
  62.                 REDIM leftMono32 AS SINGLE
  63.                 GET #ch, , leftMono32
  64.                 _SNDRAW leftMono32, leftMono32
  65.                 DO WHILE _SNDRAWLEN > 0: LOOP
  66.             LOOP
  67.         END IF
  68.         '-------------------------------------------- CONTINUE AS OLD SOURCE ---------------------------------------------------------------
  69.  
  70.         IF bits = 16 AND chan = 2 THEN
  71.             REDIM lefi AS INTEGER, righi AS INTEGER
  72.             GET #ch, , lefi
  73.             GET #ch, , righi
  74.             lef = lefi / RATE
  75.             righ = righi / RATE
  76.         END IF
  77.  
  78.         IF bits = 16 AND chan = 1 THEN
  79.             REDIM leftMono AS INTEGER
  80.             GET #ch, , leftMono
  81.             lef = leftMono / RATE
  82.             righ = leftMono / RATE
  83.         END IF
  84.  
  85.         IF bits = 8 AND chan = 2 THEN
  86.             REDIM lleft8 AS _UNSIGNED _BYTE, rright8 AS _UNSIGNED _BYTE
  87.             GET #ch, , lleft8
  88.             GET #ch, , rright8
  89.             lef = lleft8 / 256
  90.             righ = rright8 / 256
  91.         END IF
  92.  
  93.         IF bits = 8 AND chan = 1 THEN
  94.             REDIM mono8 AS _UNSIGNED _BYTE
  95.             GET #ch, , mono8
  96.             lef = mono8 / 256
  97.             righ = lef
  98.         END IF
  99.  
  100.         IF RATE > 44100 THEN frekvence = RATE ELSE frekvence = 44100
  101.  
  102.         oss = UBOUND(OutputSound)
  103.         REDIM _PRESERVE OutputSound(oss + (frekvence / RATE)) AS Snd
  104.  
  105.         FOR plll = 1 TO frekvence / RATE
  106.             '            _SNDRAW lef, righ
  107.             OutputSound(oss + plll).Left = lef
  108.             OutputSound(oss + plll).Right = righ
  109.  
  110.         NEXT plll
  111.  
  112.         DO WHILE _SNDRAWLEN > 0: LOOP: REM comment this
  113.     LOOP
  114.     CLOSE ch
  115.  
  116. SUB SAVESOUND16S (arr() AS Snd, file AS STRING)
  117.  
  118.     TYPE head16
  119.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  120.         size AS LONG '              4 bytes  (?E??)
  121.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  122.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  123.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  124.         format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  125.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  126.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  127.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  128.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  129.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  130.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  131.     END TYPE '                     40 bytes  total
  132.     DIM H16 AS head16
  133.     ch = FREEFILE
  134.  
  135.     H16.chunk = "RIFF"
  136.     H16.size = 36 + UBOUND(arr) * 4 'two channels, it create 16 bit, stereo wav file, one sample use 2 bytes to one channel
  137.  
  138.     H16.fomat = "WAVE"
  139.     H16.sub1 = "fmt"
  140.     H16.subchunksize = 16
  141.     H16.format = 1
  142.     H16.channels = 2
  143.     H16.rate = 44100
  144.     H16.ByteRate = 44100 * 2 * 16 / 8
  145.     H16.Block = 4
  146.     H16.Bits = 16
  147.     H16.subchunk2 = "data"
  148.     IF _FILEEXISTS(file$) THEN KILL file$
  149.  
  150.     OPEN file$ FOR BINARY AS #ch
  151.     PUT #ch, , H16
  152.     DIM LeftChannel AS INTEGER, RightChannel AS INTEGER
  153.  
  154.     FOR audiodata = 0 TO UBOUND(arr)
  155.         LeftChannel = arr(audiodata).Left * 44100
  156.         RightChannel = arr(audiodata).Right * 44100
  157.  
  158.         PUT #ch, , LeftChannel
  159.         PUT #ch, , RightChannel
  160.     NEXT
  161.     CLOSE ch
  162.  

* twosounds.ZIP (Filesize: 303.33 KB, Downloads: 235)

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Simple Piano
« Reply #18 on: March 26, 2020, 02:52:42 pm »
So see. My intention to mix sounds in an easy way has become so complicated that it's not possible. But I thought of another way I would try tomorrow or later. I will describe the way to do this:
Pressing the key starts counting the time. The moment the next key is pressed, the name of the file from which it was played and the length of time it was played until the key is pressed are entered in the field. This is done every time you press a key. Leave the sound mixing on SNDPLAY. These time data are then used in the calculation of the number of samples. Example:
The first sound was 11.wav, played for 2 seconds before the sound of 22.wav started, played for a second, and then the sound of 5.wav

 So: The first file is 3 seconds long. Okay, copy the first two seconds as they are, that is, from the file I take after byte 40     44100 (samplerate) * 2 (integer format) * 2 (stereo) * 120 (seconds) bytes from the file. I copy them as they are into the sound field. I'll take the remaining second and do the math average with the first second of the 22.wav file. Since 22.wav played for 1 second, but is longer, so the rest of the samples from the 22.wav file are mixed again by the mathematical average with the samples from the 5.wav file

So this is my suggestion, which I am going to again try do it tomorrow or later because I know it just can be done!

Offline Ashish

  • Forum Resident
  • Posts: 630
  • Never Give Up!
    • View Profile
Re: Simple Piano
« Reply #19 on: March 27, 2020, 07:56:33 am »
Nice @Petr !
I tried your example and it worked. Looking forward to your work on this.
if (Me.success) {Me.improve()} else {Me.tryAgain()}


My Projects - https://github.com/AshishKingdom?tab=repositories
OpenGL tutorials - https://ashishkingdom.github.io/OpenGL-Tutorials

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Simple Piano
« Reply #20 on: March 28, 2020, 06:26:01 pm »
Hi.

I have devoted myself to this subject because it has aroused my interest. I have a working program that - maybe first in QB64 ever? Saves composed music to WAV format. Well, this is the first prototype, I still have to find a reason why there are audio outages and cracking, I seem to have to fine-tune the conversion from RAW to WAV and vice versa. Here it is, before the WAV is saved, there are several intermediate operations, the program informs about everything and is commented.
Also, this method allows you to build your own format (for QB64 program) - just do it by using the _SNDPLAY method when only the time and sound number fields are saved. The tuning will not be so fast, I have to do something else again. Certainly this was a very interesting problem.

Use keys asdfghjkl for piano and then ESC for save as wav and quit.

Code: QB64: [Select]
  1. TYPE Recorder
  2.     File AS INTEGER '                             played sound index (file 1.wav is 1, 2.wav is 2...)
  3.     Time AS SINGLE '                              time in which is it played from start recording (record now after running)
  4.     Lenght AS SINGLE '                            real track lenght - NOT USED
  5.  
  6. REDIM R(150) AS Recorder
  7.  
  8.  
  9. DIM Sounds(1 TO 9) AS LONG '                      Load WAV files to Sounds array for use with _SNDPLAY (for testing, if idea is correct)
  10. FOR l = 1 TO 9
  11.     f$ = LTRIM$(STR$(l)) + ".wav"
  12.     Sounds(l) = _SNDOPEN(f$)
  13.     R(l).Lenght = _SNDLEN(Sounds(l))
  14.     IF _FILEEXISTS(f$) THEN PRINT "File "; f$; " loaded to memory as "; Sounds(l)
  15.  
  16.  
  17. TYPE Snd
  18.     Left AS SINGLE
  19.     Right AS SINGLE
  20. REDIM SHARED OutputSound(-1) AS Snd
  21.  
  22. TYPE OtpSndHelper
  23.     Offset_Start AS LONG
  24.     Offset_End AS LONG
  25. REDIM SHARED OTP(1) AS OtpSndHelper
  26.  
  27.  
  28.  
  29.  
  30.  
  31. PRINT "Piano - SOUND SAVE example. Press keys a, s, d, f, g, h, j, k, l for play.      Press ESC for end and save."
  32.  
  33. k$ = "asdfghjkl" '                                      Keys, which calls piano sounds
  34.  
  35.  
  36.  
  37. StartTime = TIMER '                                     When program start, is automaticaly started Time - recording
  38. DO UNTIL i$ = CHR$(27)
  39.     i$ = LCASE$(INKEY$)
  40.     IF LEN(i$) THEN
  41.         PlayIndex = INSTR(1, k$, i$)
  42.         IF PlayIndex THEN
  43.             _SNDPLAY Sounds(PlayIndex) '                If is some key from k$ pressed, is recorded time and file number to array R
  44.             R(record).File = PlayIndex
  45.             R(record).Time = TIMER - StartTime
  46.             record = record + 1
  47.             IF record > UBOUND(r) THEN REDIM _PRESERVE R(record + 100) AS Recorder
  48.         END IF
  49.     END IF
  50. TrackTime = TIMER - StartTime
  51.  
  52. PRINT "You play this: (TIME record)" '                  This play your music (after ESC) using _SNDPLAY an TIME records from array R (idea test)
  53. FOR Show = 0 TO UBOUND(r) '                             so here is next option: Create own music format and write it for your program in this way...
  54.     IF R(Show).File THEN
  55.         PRINT "File index:"; R(Show).File; "     In time: "; R(Show).Time; " sec."
  56.         RealRecord = RealRecord + 1
  57.     END IF
  58.  
  59.  
  60. t = TIMER
  61. DO UNTIL SNDTest = RealRecord
  62.     IF TIMER - t >= R(SNDTest).Time THEN
  63.         _SNDPLAY Sounds(R(SNDTest).File)
  64.         SNDTest = SNDTest + 1
  65.     END IF
  66.  
  67.  
  68. PRINT "For SAVE this content, must this be REWRITED as binary data, therefore is WAV format used."
  69.  
  70. PRINT "Loading RAW audio..." '                           Load used tracks (Piano WAV files) as binary data to RAM. I use WAVes due are not compressed
  71.  
  72. DIM RAW(1 TO 9) AS LONG
  73.  
  74. FOR l = 1 TO 9
  75.     f$ = LTRIM$(STR$(l)) + ".wav"
  76.     RAW(l) = WAVtoRAW(f$)
  77.  
  78. PRINT "Sounds loaded."
  79. FOR test = 1 TO 9
  80.     PRINT "RAW:"; test; "Start Offset:"; OTP(test).Offset_Start; "End Offset:"; OTP(test).Offset_End
  81.  
  82. 'GOTO notest '                                            Next test - is RAW content from WAV files really saved correctly?
  83. PRINT "Testing RAW content:"
  84. FOR test = 1 TO 9
  85.     PRINT "Test: Playing RAW sample"; test
  86.     RAWPLAY test
  87.     DO UNTIL _SNDRAWLEN = 0: LOOP
  88. notest:
  89. PRINT "RAW test OK" '                                    YES!
  90.  
  91.  
  92. PRINT "Building content..." '
  93.  
  94.  
  95.  
  96.  
  97. REDIM NEWSOUND(TrackTime * 44100) AS Snd '               Array, which contains really WAV (saved file) data is NEWSOUND
  98. 'spocitat celkovou delku pole pro vystupni zvuk
  99.  
  100.  
  101.  
  102. FOR build = 0 TO RealRecord - 1
  103.     lenght& = 44100 * R(build).Time '                   calculate lenght for track in BYTES: Time lenght * sample rate (44100 is used also in WAV header)
  104.     '    PRINT lenght&, UBOUND(newsound)
  105.     IF R(build).File THEN
  106.         RAWCOPY R(build).File, NEWSOUND(), lenght& '    copy piano sounds to correct places (in time) to array (if on this places is sound, is content
  107.     END IF '                                            mixed in SUB RAWCOPY
  108.  
  109. PRINT "RAW WAVEFORM data created in memory. Playing test..."
  110.  
  111. FOR test = 0 TO UBOUND(newsound)
  112.     _SNDRAW NEWSOUND(test).Left, NEWSOUND(test).Right ' Ready WAV content is playing using SNDRAW - last test before is content saved - still SNDRAW
  113. NEXT '                                                  contains stereo bug...
  114.  
  115. PRINT "You listen difference? ITS KNOWN SNDRAW BUG!!!"
  116. PRINT "Saving content as file 'piano_save.wav'"
  117. SAVESOUND16S NEWSOUND(), "piano_save.wav" '             And finally, content is saved.
  118.  
  119.  
  120.  
  121.  
  122. FUNCTION WAVtoRAW (file$) '                              Function load WAV file (this just 16bit, stereo format) and load it to array as RAW.
  123.     TYPE head
  124.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  125.         size AS LONG '              4 bytes  (?E??)
  126.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  127.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  128.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  129.         format AS STRING * 2 '      2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  130.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  131.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  132.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  133.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  134.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  135.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  136.     END TYPE '                     40 bytes  total
  137.     DIM H AS head
  138.     ch = FREEFILE
  139.  
  140.     IF _FILEEXISTS(file$) THEN OPEN file$ FOR BINARY AS #ch ELSE PRINT file$; " not found": SLEEP 2: SYSTEM
  141.     GET #ch, , H
  142.  
  143.     block = H.Block
  144.     RATE = H.rate
  145.     chan = H.channels
  146.     bits = H.Bits
  147.  
  148.     SEEK #ch, 49
  149.  
  150.     OTP_Size = UBOUND(otp)
  151.     OTP(OTP_Size).Offset_Start = UBOUND(outputsound) + 1
  152.  
  153.     DO WHILE NOT EOF(ch)
  154.         IF bits = 16 AND chan = 2 THEN
  155.             REDIM lefi AS INTEGER, righi AS INTEGER
  156.             GET #ch, , lefi
  157.             GET #ch, , righi
  158.             lef = lefi / RATE / 2
  159.             righ = righi / RATE / 2
  160.         END IF
  161.  
  162.         IF RATE > 44100 THEN frekvence = RATE ELSE frekvence = 44100
  163.  
  164.         oss = UBOUND(OutputSound)
  165.         REDIM _PRESERVE OutputSound(oss + (frekvence / RATE)) AS Snd
  166.  
  167.         FOR plll = 1 TO frekvence / RATE
  168.             OutputSound(oss + plll).Left = lef
  169.             OutputSound(oss + plll).Right = righ
  170.         NEXT plll
  171.  
  172.         DO WHILE _SNDRAWLEN > 0: LOOP: REM comment this
  173.     LOOP
  174.  
  175.     OTP(OTP_Size).Offset_End = UBOUND(outputsound)
  176.     REDIM _PRESERVE OTP(OTP_Size + 1) AS OtpSndHelper
  177.     CLOSE ch
  178.     WAVtoRAW = OTP_Size
  179.  
  180. SUB RAWPLAY (handle) '                                                      Play file content from RAW array (array OutputSounds)
  181.     FOR Playi = OTP(handle).Offset_Start TO OTP(handle).Offset_End
  182.         _SNDRAW OutputSound(Playi).Left, OutputSound(Playi).Right
  183.     NEXT
  184.  
  185. SUB RAWCOPY (handle, arr() AS Snd, position AS LONG)
  186.     SoundLenghtInBytes = OTP(handle).Offset_End - OTP(handle).Offset_Start
  187.     REM   IF UBOUND(arr) < UBOUND(arr) + SoundLenghtInBytes THEN REDIM _PRESERVE arr(UBOUND(arr) + SoundLenghtInBytes) AS Snd
  188.     DIM rc AS LONG, OTPS AS LONG
  189.     OTPS = OTP(handle).Offset_Start
  190.     FOR rc = position TO position + SoundLenghtInBytes
  191.         IF arr(rc).Left THEN arr(rc).Left = (arr(rc).Left + OutputSound(OTPS).Left) / 2 ELSE arr(rc).Left = OutputSound(OTPS).Left
  192.         IF arr(rc).Right THEN arr(rc).Right = (arr(rc).Right + OutputSound(OTPS).Right) / 2 ELSE arr(rc).Right = OutputSound(OTPS).Right
  193.  
  194.         '        arr(rc).Left = OutputSound(OTPS).Left
  195.         '        arr(rc).Right = OutputSound(OTPS).Right
  196.         OTPS = OTPS + 1
  197.     NEXT rc
  198.  
  199. SUB SAVESOUND16S (arr() AS Snd, file AS STRING)
  200.  
  201.     TYPE head16
  202.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  203.         size AS LONG '              4 bytes  (file size)
  204.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  205.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  206.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  207.         format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  208.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  209.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  210.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  211.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  212.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  213.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  214.         lenght AS LONG '            4 bytes  Data block size
  215.     END TYPE '                     44 bytes  total
  216.     DIM H16 AS head16
  217.     ch = FREEFILE
  218.  
  219.     H16.chunk = "RIFF"
  220.     H16.size = 44 + UBOUND(arr) * 4 'two channels, it create 16 bit, stereo wav file, one sample use 2 bytes to one channel
  221.  
  222.     H16.fomat = "WAVE"
  223.     H16.sub1 = "fmt "
  224.     H16.subchunksize = 16
  225.     H16.format = 1
  226.     H16.channels = 2
  227.     H16.rate = 44100
  228.     H16.ByteRate = 44100 * 2 * 16 / 8
  229.     H16.Block = 4
  230.     H16.Bits = 16
  231.     H16.subchunk2 = "data"
  232.     H16.lenght = UBOUND(arr) * 4
  233.     IF _FILEEXISTS(file$) THEN KILL file$
  234.  
  235.     OPEN file$ FOR BINARY AS #ch
  236.     PUT #ch, , H16
  237.     DIM LeftChannel AS INTEGER, RightChannel AS INTEGER
  238.  
  239.     FOR audiodata = 0 TO UBOUND(arr)
  240.         LeftChannel = arr(audiodata).Left * 22050
  241.         RightChannel = arr(audiodata).Right * 22050
  242.  
  243.         PUT #ch, , LeftChannel
  244.         PUT #ch, , RightChannel
  245.     NEXT
  246.     CLOSE ch
  247.  

For run you need attachment.

EDIT:
I forgot to write here a change in concept from the previous post above. I did it the easier way. In this program, the track time is counted from the start of the program. It means that silence will be recorded if you don't press anything. The principle is that the sound is copied to the time position in the field. Thus, this program uses 44100 samples per second. So the sound to be played 2 seconds after start will be started in the NEWSOUND field at position 88200. If you look at SUB RAWCOPY, you can see the condition here. If there is already a record at the position where the binary content of the file is copied, the mathematical average of the samples is made. So. If you press a key in time 2 seconds and the tone is three seconds and then press another, or the same key in time 3 seconds with the same sound, the sound in the NEWSOUND field is the mathematical average of both sounds in 2 to 3 seconds. If you press another key in time 2.5 seconds, the resulting sound in 2.5 seconds to 3 seconds is average of the three sounds. I think this concept is clear to understand.
* Piano_waves.zip (Filesize: 1.33 MB, Downloads: 217)
« Last Edit: March 29, 2020, 07:25:23 am by Petr »

Offline Ashish

  • Forum Resident
  • Posts: 630
  • Never Give Up!
    • View Profile
Re: Simple Piano
« Reply #21 on: March 29, 2020, 09:28:29 am »
Hi @Petr
This is really cool! It saved the music. and Yes, I can notice the difference.
if (Me.success) {Me.improve()} else {Me.tryAgain()}


My Projects - https://github.com/AshishKingdom?tab=repositories
OpenGL tutorials - https://ashishkingdom.github.io/OpenGL-Tutorials

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Simple Piano
« Reply #22 on: March 30, 2020, 11:36:05 am »
As promised. Here is the corrected version. The recording is now clear, without cracking or interference. I had to add a few things and tweak some things. I will continue with other experiments regarding audio storage.

Again you need attachment from previous reply.

Code: QB64: [Select]
  1. TYPE Recorder
  2.     File AS INTEGER '                             played sound index (file 1.wav is 1, 2.wav is 2...)
  3.     Time AS SINGLE '                              time in which is it played from start recording (record now after running)
  4.     Lenght AS SINGLE '                            real track lenght - NOT USED
  5.  
  6. REDIM R(150) AS Recorder
  7.  
  8.  
  9. DIM Sounds(1 TO 9) AS LONG '                      Load WAV files to Sounds array for use with _SNDPLAY (for testing, if idea is correct)
  10. FOR l = 1 TO 9
  11.     f$ = LTRIM$(STR$(l)) + ".wav"
  12.     Sounds(l) = _SNDOPEN(f$)
  13.     R(l).Lenght = _SNDLEN(Sounds(l))
  14.     IF _FILEEXISTS(f$) THEN PRINT "File "; f$; " loaded to memory as "; Sounds(l)
  15.  
  16.  
  17. TYPE Snd
  18.     Left AS SINGLE
  19.     Right AS SINGLE
  20. REDIM SHARED OutputSound(-1) AS Snd
  21.  
  22. TYPE OtpSndHelper
  23.     Offset_Start AS LONG
  24.     Offset_End AS LONG
  25. REDIM SHARED OTP(1) AS OtpSndHelper
  26.  
  27.  
  28.  
  29.  
  30.  
  31. PRINT "Piano - SOUND SAVE example. Press keys a, s, d, f, g, h, j, k, l for play.      Press ESC for end and save."
  32.  
  33. k$ = "asdfghjkl" '                                      Keys, which calls piano sounds
  34.  
  35.  
  36.  
  37. StartTime = TIMER '                                     When program start, is automaticaly started Time - recording
  38. DO UNTIL i$ = CHR$(27)
  39.     i$ = LCASE$(INKEY$)
  40.     IF LEN(i$) THEN
  41.         PlayIndex = INSTR(1, k$, i$)
  42.         IF PlayIndex THEN
  43.             _SNDPLAY Sounds(PlayIndex) '                If is some key from k$ pressed, is recorded time and file number to array R
  44.             R(record).File = PlayIndex
  45.             R(record).Time = TIMER - StartTime
  46.             record = record + 1
  47.             IF record > UBOUND(r) THEN REDIM _PRESERVE R(record + 100) AS Recorder
  48.         END IF
  49.     END IF
  50. TrackTime = TIMER - StartTime
  51.  
  52. PRINT "You play this: (TIME record)" '                  This play your music (after ESC) using _SNDPLAY an TIME records from array R (idea test)
  53. FOR Show = 0 TO UBOUND(r) '                             so here is next option: Create own music format and write it for your program in this way...
  54.     IF R(Show).File THEN
  55.         PRINT "File index:"; R(Show).File; "     In time: "; R(Show).Time; " sec."
  56.         RealRecord = RealRecord + 1
  57.     END IF
  58.  
  59.  
  60. t = TIMER
  61. DO UNTIL SNDTest = RealRecord
  62.     IF TIMER - t >= R(SNDTest).Time THEN
  63.         _SNDPLAY Sounds(R(SNDTest).File)
  64.         SNDTest = SNDTest + 1
  65.     END IF
  66.  
  67.  
  68. PRINT "For SAVE this content, must this be REWRITED as binary data, therefore is WAV format used."
  69.  
  70. PRINT "Loading RAW audio..." '                           Load used tracks (Piano WAV files) as binary data to RAM. I use WAVes due are not compressed
  71.  
  72. DIM RAW(1 TO 9) AS LONG
  73.  
  74. FOR l = 1 TO 9
  75.     f$ = LTRIM$(STR$(l)) + ".wav"
  76.     RAW(l) = WAVtoRAW(f$)
  77.  
  78. PRINT "Sounds loaded."
  79. FOR test = 1 TO 9
  80.     PRINT "RAW:"; test; "Start Offset:"; OTP(test).Offset_Start; "End Offset:"; OTP(test).Offset_End
  81.  
  82. GOTO notest '                                            Next test - is RAW content from WAV files really saved correctly?
  83. PRINT "Testing RAW content:"
  84. FOR test = 1 TO 9
  85.     PRINT "Test: Playing RAW sample"; test
  86.     RAWPLAY test
  87.     DO UNTIL _SNDRAWLEN = 0: LOOP
  88. notest:
  89. PRINT "RAW test OK" '                                    YES!
  90.  
  91.  
  92. PRINT "Building content..." '
  93.  
  94.  
  95.  
  96.  
  97. REDIM NEWSOUND(TrackTime * 44100) AS Snd '               Array, which contains really WAV (saved file) data is NEWSOUND
  98. 'spocitat celkovou delku pole pro vystupni zvuk
  99.  
  100.  
  101.  
  102. FOR build = 0 TO RealRecord - 1
  103.     lenght& = 44100 * R(build).Time '                   calculate lenght for track in BYTES: Time lenght * sample rate (44100 is used also in WAV header)
  104.     '    PRINT lenght&, UBOUND(newsound)
  105.     IF R(build).File THEN
  106.         RAWCOPY R(build).File, NEWSOUND(), lenght& '    copy piano sounds to correct places (in time) to array (if on this places is sound, is content
  107.     END IF '                                            mixed in SUB RAWCOPY
  108.  
  109. PRINT "RAW WAVEFORM data created in memory. Playing test..."
  110.  
  111. FOR test = 0 TO UBOUND(newsound)
  112.     _SNDRAW NEWSOUND(test).Left, NEWSOUND(test).Right ' Ready WAV content is playing using SNDRAW - last test before is content saved - still SNDRAW
  113. NEXT '                                                  contains stereo bug...
  114.  
  115. PRINT "You listen difference? ITS KNOWN SNDRAW BUG!!!"
  116. PRINT "Saving content as file 'piano_save.wav'"
  117.  
  118. RealLenght NEWSOUND(), TrackTime
  119. SAVESOUND16S NEWSOUND(), "piano_save.wav" '             And finally, content is saved.
  120.  
  121.  
  122.  
  123. FUNCTION WAVtoRAW (file$) '                              Function load WAV file (this just 16bit, stereo format) and load it to array as RAW.
  124.     TYPE head
  125.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  126.         size AS LONG '              4 bytes  (?E??)
  127.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  128.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  129.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  130.         format AS STRING * 2 '      2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  131.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  132.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  133.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  134.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  135.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  136.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  137.     END TYPE '                     40 bytes  total
  138.     DIM H AS head
  139.     ch = FREEFILE
  140.  
  141.     IF _FILEEXISTS(file$) THEN OPEN file$ FOR BINARY AS #ch ELSE PRINT file$; " not found": SLEEP 2: SYSTEM
  142.     GET #ch, , H
  143.  
  144.     block = H.Block
  145.     RATE = H.rate
  146.     chan = H.channels
  147.     bits = H.Bits
  148.  
  149.     SEEK #ch, Find_data_area(file$)
  150.  
  151.     OTP_Size = UBOUND(otp)
  152.     OTP(OTP_Size).Offset_Start = UBOUND(outputsound) + 1
  153.  
  154.     DO WHILE NOT EOF(ch)
  155.         IF bits = 16 AND chan = 2 THEN
  156.             REDIM lefi AS INTEGER, righi AS INTEGER
  157.             GET #ch, , lefi
  158.             GET #ch, , righi
  159.             lef = lefi / 65535
  160.             righ = righi / 65535
  161.         END IF
  162.  
  163.         IF RATE > 44100 THEN frekvence = RATE ELSE frekvence = 44100
  164.  
  165.         oss = UBOUND(OutputSound)
  166.         REDIM _PRESERVE OutputSound(oss + (frekvence / RATE)) AS Snd
  167.  
  168.         FOR plll = 1 TO frekvence / RATE
  169.             OutputSound(oss + plll).Left = lef
  170.             OutputSound(oss + plll).Right = righ
  171.         NEXT plll
  172.  
  173.         DO WHILE _SNDRAWLEN > 0: LOOP: REM comment this
  174.     LOOP
  175.  
  176.     OTP(OTP_Size).Offset_End = UBOUND(outputsound)
  177.     REDIM _PRESERVE OTP(OTP_Size + 1) AS OtpSndHelper
  178.     CLOSE ch
  179.     WAVtoRAW = OTP_Size
  180.  
  181. SUB RAWPLAY (handle) '                                                      Play file content from RAW array (array OutputSounds)
  182.     FOR Playi = OTP(handle).Offset_Start TO OTP(handle).Offset_End
  183.         _SNDRAW OutputSound(Playi).Left, OutputSound(Playi).Right
  184.     NEXT
  185.  
  186. SUB RAWCOPY (handle, arr() AS Snd, position AS LONG)
  187.     SoundLenghtInBytes = OTP(handle).Offset_End - OTP(handle).Offset_Start
  188.     IF UBOUND(arr) < UBOUND(arr) + SoundLenghtInBytes THEN REDIM _PRESERVE arr(UBOUND(arr) + SoundLenghtInBytes) AS Snd
  189.     DIM rc AS LONG, OTPS AS LONG
  190.     OTPS = OTP(handle).Offset_Start
  191.     FOR rc = position TO position + SoundLenghtInBytes
  192.  
  193.         IF arr(rc).Left THEN OutLeft = (arr(rc).Left + OutputSound(OTPS).Left) ELSE OutLeft = OutputSound(OTPS).Left
  194.         IF arr(rc).Right THEN OutRight = (arr(rc).Right + OutputSound(OTPS).Right) ELSE OutRight = OutputSound(OTPS).Right
  195.  
  196.         IF OutLeft > .9 THEN OutLeft = .9
  197.         IF OutLeft < -.9 THEN OutLeft = -.9
  198.  
  199.         IF OutRight > .9 THEN OutRight = .9
  200.         IF OutRight < -.9 THEN OutRight = -.9
  201.  
  202.         arr(rc).Left = OutLeft
  203.         arr(rc).Right = OutRight
  204.  
  205.         '   arr(rc).Left = OutputSound(OTPS).Left
  206.         '   arr(rc).Right = OutputSound(OTPS).Right
  207.         OTPS = OTPS + 1
  208.     NEXT rc
  209.  
  210. SUB SAVESOUND16S (arr() AS Snd, file AS STRING)
  211.  
  212.     TYPE head16
  213.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  214.         size AS LONG '              4 bytes  (file size)
  215.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  216.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  217.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  218.         format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  219.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  220.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  221.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  222.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  223.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  224.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  225.         lenght AS LONG '            4 bytes  Data block size
  226.     END TYPE '                     44 bytes  total
  227.     DIM H16 AS head16
  228.     ch = FREEFILE
  229.  
  230.     H16.chunk = "RIFF"
  231.     H16.size = 44 + UBOUND(arr) * 4 'two channels, it create 16 bit, stereo wav file, one sample use 2 bytes to one channel
  232.  
  233.     H16.fomat = "WAVE"
  234.     H16.sub1 = "fmt "
  235.     H16.subchunksize = 16
  236.     H16.format = 1
  237.     H16.channels = 2
  238.     H16.rate = 44100
  239.     H16.ByteRate = 44100 * 2 * 16 / 8
  240.     H16.Block = 4
  241.     H16.Bits = 16
  242.     H16.subchunk2 = "data"
  243.     H16.lenght = UBOUND(arr) * 4
  244.     IF _FILEEXISTS(file$) THEN KILL file$
  245.  
  246.     OPEN file$ FOR BINARY AS #ch
  247.     PUT #ch, , H16
  248.     DIM LeftChannel AS INTEGER, RightChannel AS INTEGER
  249.  
  250.     FOR audiodata = 0 TO UBOUND(arr)
  251.         LeftChannel = arr(audiodata).Left * 32768
  252.         RightChannel = arr(audiodata).Right * 32768
  253.  
  254.         PUT #ch, , LeftChannel
  255.         PUT #ch, , RightChannel
  256.     NEXT
  257.     CLOSE ch
  258.  
  259.  
  260. SUB RealLenght (arr() AS Snd, time AS SINGLE)
  261.     Size = time * _SNDRATE
  262.     REDIM _PRESERVE arr(Size) AS Snd
  263.  
  264. FUNCTION Find_data_area (handle$)
  265.     REDIM D AS STRING * 1024
  266.     ff = FREEFILE
  267.     OPEN handle$ FOR BINARY AS #ff
  268.     GET #ff, 1, D$
  269.     CLOSE #ff
  270.     Find_data_area = INSTR(1, D$, "data") + 8
  271.  

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Simple Piano
« Reply #23 on: March 30, 2020, 01:47:59 pm »
Next upgrade:

Now is possible SAVE YOUR MUSIC to 8 bit Stereo WAV format (or 16 bit Stereo WAV). I do some music editor later. I must found some patterns first (patterns as guitar, drumm...)
Program do both formats now.

Code: QB64: [Select]
  1. TYPE Recorder
  2.     File AS INTEGER '                             played sound index (file 1.wav is 1, 2.wav is 2...)
  3.     Time AS SINGLE '                              time in which is it played from start recording (record now after running)
  4.     Lenght AS SINGLE '                            real track lenght - NOT USED
  5.  
  6. REDIM R(150) AS Recorder
  7.  
  8.  
  9. DIM Sounds(1 TO 9) AS LONG '                      Load WAV files to Sounds array for use with _SNDPLAY (for testing, if idea is correct)
  10. FOR l = 1 TO 9
  11.     f$ = LTRIM$(STR$(l)) + ".wav"
  12.     Sounds(l) = _SNDOPEN(f$)
  13.     R(l).Lenght = _SNDLEN(Sounds(l))
  14.     IF _FILEEXISTS(f$) THEN PRINT "File "; f$; " loaded to memory as "; Sounds(l)
  15.  
  16.  
  17. TYPE Snd
  18.     Left AS SINGLE
  19.     Right AS SINGLE
  20. REDIM SHARED OutputSound(-1) AS Snd
  21.  
  22. TYPE OtpSndHelper
  23.     Offset_Start AS LONG
  24.     Offset_End AS LONG
  25. REDIM SHARED OTP(1) AS OtpSndHelper
  26.  
  27.  
  28.  
  29.  
  30.  
  31. PRINT "Piano - SOUND SAVE example. Press keys a, s, d, f, g, h, j, k, l for play.      Press ESC for end and save."
  32.  
  33. k$ = "asdfghjkl" '                                      Keys, which calls piano sounds
  34.  
  35.  
  36.  
  37. StartTime = TIMER '                                     When program start, is automaticaly started Time - recording
  38. DO UNTIL i$ = CHR$(27)
  39.     i$ = LCASE$(INKEY$)
  40.     IF LEN(i$) THEN
  41.         PlayIndex = INSTR(1, k$, i$)
  42.         IF PlayIndex THEN
  43.             _SNDPLAY Sounds(PlayIndex) '                If is some key from k$ pressed, is recorded time and file number to array R
  44.             R(record).File = PlayIndex
  45.             R(record).Time = TIMER - StartTime
  46.             record = record + 1
  47.             IF record > UBOUND(r) THEN REDIM _PRESERVE R(record + 100) AS Recorder
  48.         END IF
  49.     END IF
  50. TrackTime = TIMER - StartTime
  51.  
  52. PRINT "You play this: (TIME record)" '                  This play your music (after ESC) using _SNDPLAY an TIME records from array R (idea test)
  53. FOR Show = 0 TO UBOUND(r) '                             so here is next option: Create own music format and write it for your program in this way...
  54.     IF R(Show).File THEN
  55.         PRINT "File index:"; R(Show).File; "     In time: "; R(Show).Time; " sec."
  56.         RealRecord = RealRecord + 1
  57.     END IF
  58.  
  59.  
  60. t = TIMER
  61. DO UNTIL SNDTest = RealRecord
  62.     IF TIMER - t >= R(SNDTest).Time THEN
  63.         _SNDPLAY Sounds(R(SNDTest).File)
  64.         SNDTest = SNDTest + 1
  65.     END IF
  66.  
  67.  
  68. PRINT "For SAVE this content, must this be REWRITED as binary data, therefore is WAV format used."
  69.  
  70. PRINT "Loading RAW audio..." '                           Load used tracks (Piano WAV files) as binary data to RAM. I use WAVes due are not compressed
  71.  
  72. DIM RAW(1 TO 9) AS LONG
  73.  
  74. FOR l = 1 TO 9
  75.     f$ = LTRIM$(STR$(l)) + ".wav"
  76.     RAW(l) = WAVtoRAW(f$)
  77.  
  78. PRINT "Sounds loaded."
  79. FOR test = 1 TO 9
  80.     PRINT "RAW:"; test; "Start Offset:"; OTP(test).Offset_Start; "End Offset:"; OTP(test).Offset_End
  81.  
  82. GOTO notest '                                            Next test - is RAW content from WAV files really saved correctly?
  83. PRINT "Testing RAW content:"
  84. FOR test = 1 TO 9
  85.     PRINT "Test: Playing RAW sample"; test
  86.     RAWPLAY test
  87.     DO UNTIL _SNDRAWLEN = 0: LOOP
  88. notest:
  89. PRINT "RAW test OK" '                                    YES!
  90.  
  91.  
  92. PRINT "Building content..." '
  93.  
  94.  
  95.  
  96.  
  97. REDIM NEWSOUND(TrackTime * 44100) AS Snd '               Array, which contains really WAV (saved file) data is NEWSOUND
  98. 'spocitat celkovou delku pole pro vystupni zvuk
  99.  
  100.  
  101.  
  102. FOR build = 0 TO RealRecord - 1
  103.     lenght& = 44100 * R(build).Time '                   calculate lenght for track in BYTES: Time lenght * sample rate (44100 is used also in WAV header)
  104.     '    PRINT lenght&, UBOUND(newsound)
  105.     IF R(build).File THEN
  106.         RAWCOPY R(build).File, NEWSOUND(), lenght& '    copy piano sounds to correct places (in time) to array (if on this places is sound, is content
  107.     END IF '                                            mixed in SUB RAWCOPY
  108.  
  109. PRINT "RAW WAVEFORM data created in memory. Playing test..."
  110.  
  111. FOR test = 0 TO UBOUND(newsound)
  112.     _SNDRAW NEWSOUND(test).Left, NEWSOUND(test).Right ' Ready WAV content is playing using SNDRAW - last test before is content saved - still SNDRAW
  113. NEXT '                                                  contains stereo bug...
  114.  
  115. PRINT "You listen difference? ITS KNOWN SNDRAW BUG!!!"
  116. PRINT "Saving content as file 'piano_save.wav'"
  117.  
  118. RealLenght NEWSOUND(), TrackTime
  119. SAVESOUND16S NEWSOUND(), "piano_save.wav" '             And finally, content is saved.
  120. PRINT "And new: Saving also as 8 bit stereo wav - Piano8_save.wav"
  121. SAVESOUND8S NEWSOUND(), "piano8_save.wav"
  122.  
  123.  
  124.  
  125. FUNCTION WAVtoRAW (file$) '                              Function load WAV file (this just 16bit, stereo format) and load it to array as RAW.
  126.     TYPE head
  127.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  128.         size AS LONG '              4 bytes  (?E??)
  129.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  130.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  131.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  132.         format AS STRING * 2 '      2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  133.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  134.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  135.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  136.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  137.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  138.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  139.     END TYPE '                     40 bytes  total
  140.     DIM H AS head
  141.     ch = FREEFILE
  142.  
  143.     IF _FILEEXISTS(file$) THEN OPEN file$ FOR BINARY AS #ch ELSE PRINT file$; " not found": SLEEP 2: SYSTEM
  144.     GET #ch, , H
  145.  
  146.     block = H.Block
  147.     RATE = H.rate
  148.     chan = H.channels
  149.     bits = H.Bits
  150.  
  151.     SEEK #ch, Find_data_area(file$)
  152.  
  153.     OTP_Size = UBOUND(otp)
  154.     OTP(OTP_Size).Offset_Start = UBOUND(outputsound) + 1
  155.  
  156.     DO WHILE NOT EOF(ch)
  157.         IF bits = 16 AND chan = 2 THEN
  158.             REDIM lefi AS INTEGER, righi AS INTEGER
  159.             GET #ch, , lefi
  160.             GET #ch, , righi
  161.             lef = lefi / 65535
  162.             righ = righi / 65535
  163.         END IF
  164.  
  165.         IF RATE > 44100 THEN frekvence = RATE ELSE frekvence = 44100
  166.  
  167.         oss = UBOUND(OutputSound)
  168.         REDIM _PRESERVE OutputSound(oss + (frekvence / RATE)) AS Snd
  169.  
  170.         FOR plll = 1 TO frekvence / RATE
  171.             OutputSound(oss + plll).Left = lef
  172.             OutputSound(oss + plll).Right = righ
  173.         NEXT plll
  174.  
  175.         DO WHILE _SNDRAWLEN > 0: LOOP: REM comment this
  176.     LOOP
  177.  
  178.     OTP(OTP_Size).Offset_End = UBOUND(outputsound)
  179.     REDIM _PRESERVE OTP(OTP_Size + 1) AS OtpSndHelper
  180.     CLOSE ch
  181.     WAVtoRAW = OTP_Size
  182.  
  183. SUB RAWPLAY (handle) '                                                      Play file content from RAW array (array OutputSounds)
  184.     FOR Playi = OTP(handle).Offset_Start TO OTP(handle).Offset_End
  185.         _SNDRAW OutputSound(Playi).Left, OutputSound(Playi).Right
  186.     NEXT
  187.  
  188. SUB RAWCOPY (handle, arr() AS Snd, position AS LONG)
  189.     SoundLenghtInBytes = OTP(handle).Offset_End - OTP(handle).Offset_Start
  190.     IF UBOUND(arr) < UBOUND(arr) + SoundLenghtInBytes THEN REDIM _PRESERVE arr(UBOUND(arr) + SoundLenghtInBytes) AS Snd
  191.     DIM rc AS LONG, OTPS AS LONG
  192.     OTPS = OTP(handle).Offset_Start
  193.     FOR rc = position TO position + SoundLenghtInBytes
  194.  
  195.         IF arr(rc).Left THEN OutLeft = (arr(rc).Left + OutputSound(OTPS).Left) ELSE OutLeft = OutputSound(OTPS).Left
  196.         IF arr(rc).Right THEN OutRight = (arr(rc).Right + OutputSound(OTPS).Right) ELSE OutRight = OutputSound(OTPS).Right
  197.  
  198.         IF OutLeft > .9 THEN OutLeft = .9
  199.         IF OutLeft < -.9 THEN OutLeft = -.9
  200.  
  201.         IF OutRight > .9 THEN OutRight = .9
  202.         IF OutRight < -.9 THEN OutRight = -.9
  203.  
  204.         arr(rc).Left = OutLeft
  205.         arr(rc).Right = OutRight
  206.  
  207.         '   arr(rc).Left = OutputSound(OTPS).Left
  208.         '   arr(rc).Right = OutputSound(OTPS).Right
  209.         OTPS = OTPS + 1
  210.     NEXT rc
  211.  
  212. SUB SAVESOUND16S (arr() AS Snd, file AS STRING)
  213.  
  214.     TYPE head16
  215.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  216.         size AS LONG '              4 bytes  (file size)
  217.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  218.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  219.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  220.         format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  221.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  222.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  223.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  224.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  225.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  226.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  227.         lenght AS LONG '            4 bytes  Data block size
  228.     END TYPE '                     44 bytes  total
  229.     DIM H16 AS head16
  230.     ch = FREEFILE
  231.  
  232.     H16.chunk = "RIFF"
  233.     H16.size = 44 + UBOUND(arr) * 4 'two channels, it create 16 bit, stereo wav file, one sample use 2 bytes to one channel
  234.  
  235.     H16.fomat = "WAVE"
  236.     H16.sub1 = "fmt "
  237.     H16.subchunksize = 16
  238.     H16.format = 1
  239.     H16.channels = 2
  240.     H16.rate = 44100
  241.     H16.ByteRate = 44100 * 2 * 16 / 8
  242.     H16.Block = 4
  243.     H16.Bits = 16
  244.     H16.subchunk2 = "data"
  245.     H16.lenght = UBOUND(arr) * 4
  246.     IF _FILEEXISTS(file$) THEN KILL file$
  247.  
  248.     OPEN file$ FOR BINARY AS #ch
  249.     PUT #ch, , H16
  250.     DIM LeftChannel AS INTEGER, RightChannel AS INTEGER
  251.  
  252.     FOR audiodata = 0 TO UBOUND(arr)
  253.         LeftChannel = arr(audiodata).Left * 32767
  254.         RightChannel = arr(audiodata).Right * 32767
  255.  
  256.         PUT #ch, , LeftChannel
  257.         PUT #ch, , RightChannel
  258.     NEXT
  259.     CLOSE ch
  260.  
  261. SUB SAVESOUND8S (arr() AS Snd, file AS STRING)
  262.  
  263.     TYPE head8
  264.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  265.         size AS LONG '              4 bytes  (file size)
  266.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  267.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  268.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  269.         format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  270.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  271.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  272.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  273.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  274.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  275.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  276.         lenght AS LONG '            4 bytes  Data block size
  277.     END TYPE '                     44 bytes  total
  278.     DIM H8 AS head8
  279.     ch = FREEFILE
  280.  
  281.     H8.chunk = "RIFF"
  282.     H8.size = 44 + UBOUND(arr) * 2 'two channels, it create 16 bit, stereo wav file, one sample use 2 bytes to one channel
  283.  
  284.     H8.fomat = "WAVE"
  285.     H8.sub1 = "fmt "
  286.     H8.subchunksize = 16
  287.     H8.format = 1
  288.     H8.channels = 2
  289.     H8.rate = 44100
  290.     H8.ByteRate = 44100 * 2 * 8 / 8
  291.     H8.Block = 2
  292.     H8.Bits = 8
  293.     H8.subchunk2 = "data"
  294.     H8.lenght = UBOUND(arr) * 2
  295.     IF _FILEEXISTS(file$) THEN KILL file$
  296.  
  297.     OPEN file$ FOR BINARY AS #ch
  298.     PUT #ch, , H8
  299.     DIM LeftChannel8 AS _UNSIGNED _BYTE, RightChannel8 AS _UNSIGNED _BYTE
  300.  
  301.     FOR audiodata = 0 TO UBOUND(arr)
  302.         LeftChannel8 = 128 - arr(audiodata).Left * 128
  303.         RightChannel8 = 128 - arr(audiodata).Right * 128
  304.  
  305.         PUT #ch, , LeftChannel8
  306.         PUT #ch, , RightChannel8
  307.     NEXT
  308.     CLOSE ch
  309.  
  310.  
  311. SUB RealLenght (arr() AS Snd, time AS SINGLE)
  312.     Size = time * _SNDRATE
  313.     REDIM _PRESERVE arr(Size) AS Snd
  314.  
  315. FUNCTION Find_data_area (handle$)
  316.     REDIM D AS STRING * 1024
  317.     ff = FREEFILE
  318.     OPEN handle$ FOR BINARY AS #ff
  319.     GET #ff, 1, D$
  320.     CLOSE #ff
  321.     Find_data_area = INSTR(1, D$, "data") + 8
  322.  

Offline TrialAndTerror

  • Newbie
  • Posts: 11
  • Kick The Can
    • View Profile
    • T&T Ware
Re: Simple Piano
« Reply #24 on: April 01, 2020, 10:06:55 pm »
Love it!

One thing: Why doesn't it accept the Csus chord? (On the computer keyboard "d"+"h"+"j")
This piece of code is useful to me, thanks Terry. :)

T&T

Offline TerryRitchie

  • Seasoned Forum Regular
  • Posts: 495
  • Semper Fidelis
    • View Profile
Re: Simple Piano
« Reply #25 on: April 01, 2020, 10:37:22 pm »
Love it!

One thing: Why doesn't it accept the Csus chord? (On the computer keyboard "d"+"h"+"j")
This piece of code is useful to me, thanks Terry. :)

T&T

I just ran the program on my system and I can press D H and J all at the same time. Not all keyboard controllers are made equal. Some controllers will see more simultaneous keys down than others. I've run across this in the past with the lower cost generic 104 key keyboards that cost around $10.
In order to understand recursion, one must first understand recursion.

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: Simple Piano
« Reply #26 on: April 30, 2020, 09:20:59 pm »
Very nice piano Terry. Good way to use sound files too.

Offline TerryRitchie

  • Seasoned Forum Regular
  • Posts: 495
  • Semper Fidelis
    • View Profile
Re: Simple Piano
« Reply #27 on: May 01, 2020, 12:14:44 am »
Thanks :-)
In order to understand recursion, one must first understand recursion.