QB64.org Forum

Active Forums => Programs => Topic started by: TerryRitchie on March 25, 2020, 02:25:21 pm

Title: Simple Piano
Post by: TerryRitchie on March 25, 2020, 02:25:21 pm
I was going through my QB64 tutorials and forget about this little program I wrote back in 2014. It's a one-octave piano simulator that allows you to switch between octaves. The asset files (images and sounds) are contained in the attached PIANO.zip file. Be sure to place the \PIANO folder in your QB64 folder (the programs looks for the .\PIANO\ folder when loading assets.

Directions for use are at the top of the source code listing.

Note: I wrote this using version 0.954 and because of that the sounds are loaded using options not available in the latest versions of QB64. If you compile this using an SDL version of QB64 the piano keys will stop the sound as soon as you release them. In non SDL versions of QB64 the key sustains even after released.

Code: QB64: [Select]
  1. '*
  2. '* QB64 Simple Piano
  3. '*
  4. '* by Terry Ritchie
  5. '*
  6. '* Demonstrates the use of external sound files to create a realistic piano.
  7. '*
  8. '* ESC         - exit program
  9. '* RIGHT ARROW - increase octave
  10. '* LEFT ARROW  - decrease octave
  11. '* Piano Keys  -  R T  U I O   (black keys)
  12. '*             - D F GH J K L  (white keys)
  13. '*
  14.  
  15. '--------------------------------
  16. '- Variable Declaration Section -
  17. '--------------------------------
  18.  
  19. TYPE IVORY '          key information
  20.     u AS INTEGER '    upper case value
  21.     l AS INTEGER '    lower case value
  22.     Down AS INTEGER ' key position
  23.     x AS INTEGER '    key indicator x coordinate
  24.     y AS INTEGER '    key indicator y coordinate
  25.  
  26. DIM K(12) AS IVORY '  key information array
  27. DIM Tone&(88) '       piano key sounds array
  28. DIM imgPiano& '       piano keyboard image
  29. DIM imgAoctave& '     active octave image
  30. DIM imgIoctave& '     inactive octave image
  31. DIM Octave% '         current octave
  32. DIM Khit& '           keyboard status
  33. DIM Keys% '           key cycle counter
  34.  
  35. '----------------------------
  36. '- Main Program Begins Here -
  37. '----------------------------
  38.  
  39. LOADPIANO '                                                          load piano assets
  40. SCREEN _NEWIMAGE(512, 263, 32) '                                     create default screen
  41. _TITLE "PIANO" '                                                     set window title
  42. _SCREENMOVE _MIDDLE '                                                center window on desktop
  43. _DELAY .25
  44. _PUTIMAGE (0, 0), imgPiano& '                                        show piano image
  45. SHOWOCTAVE '                                                         update octave indicator
  46. DO '                                                                 MAIN LOOP begins
  47.     Khit& = _KEYHIT '                                                get keyboard status
  48.     IF Khit& THEN '                                                  was a key hit?
  49.         IF Khit& = 19200 OR Khit& = 19712 THEN '                     yes, left or right key?
  50.             IF Khit& = 19200 THEN '                                  yes, left key?
  51.                 Octave% = Octave% - 1 '                              yes, decrease octave
  52.                 IF Octave% = -1 THEN Octave% = 0 '                   keep octave in limits
  53.             ELSE '                                                   no, must be right key
  54.                 Octave% = Octave% + 1 '                              increase octave
  55.                 IF Octave% = 5 THEN Octave% = 4 '                    keep octave in limits
  56.             END IF
  57.             SHOWOCTAVE '                                             update octave indicator
  58.         ELSEIF Khit& = 27 THEN '                                     no, escape key?
  59.             QUIT '                                                   yes, quit program
  60.         END IF
  61.     END IF
  62.     FOR Keys% = 1 TO 12 '                                            cycle through keys
  63.         IF _KEYDOWN(K(Keys%).u) OR _KEYDOWN(K(Keys%).l) THEN '       key pressed?
  64.             PRESS Keys% '                                            yes, play note
  65.         ELSE '                                                       no
  66.             RELEASE Keys% '                                          remove key indicator
  67.         END IF
  68.     NEXT Keys%
  69.     _DISPLAY '                                                       update screen changes
  70. LOOP '                                                               MAIN LOOP back
  71.  
  72. '-----------------------------------
  73. '- Function and Subroutine section -
  74. '-----------------------------------
  75.  
  76. '--------------------------------------------------------------------------------------------
  77.  
  78. SUB QUIT ()
  79.  
  80.     '*
  81.     '* Cleans RAM by removing all image and sound assets and then exits to Windows.
  82.     '*
  83.  
  84.     SHARED Tone&() '     need access to piano key sounds array
  85.     SHARED imgPiano& '   need access to piano keyboard image
  86.     SHARED imgAoctave& ' need access to active octave image
  87.     SHARED imgIoctave& ' need access to inactive octave image
  88.  
  89.     DIM Count% '         generic counter
  90.  
  91.     FOR Count% = 1 TO 88 '        cycle through all 88 sound files
  92.         _SNDCLOSE Tone&(Count%) ' remove sound file from RAM
  93.     NEXT Count%
  94.     _FREEIMAGE imgPiano& '        remove piano image from RAM
  95.     _FREEIMAGE imgAoctave& '      remove active octave image from RAM
  96.     _FREEIMAGE imgIoctave& '      remove inactive octave image from RAM
  97.     SYSTEM '                      return to Windows
  98.  
  99.  
  100. '--------------------------------------------------------------------------------------------
  101.  
  102. SUB RELEASE (k%)
  103.  
  104.     '*
  105.     '* Removes key press display and sets key as being released
  106.     '*
  107.  
  108.     SHARED K() AS IVORY ' need access to key information array
  109.  
  110.     IF K(k%).Down THEN '                                                  is key pressed?
  111.         K(k%).Down = 0 '                                                  yes, set it as released
  112.         SELECT CASE k% '                                                  which key is it?
  113.             CASE 1, 3, 5, 6, 8, 10, 12 '                                  white key
  114.                 LINE (K(k%).x, K(k%).y)-(K(k%).x + 27, K(k%).y + 27), _RGB32(255, 255, 255), BF
  115.             CASE ELSE '                                                   black key
  116.                 LINE (K(k%).x, K(k%).y)-(K(k%).x + 27, K(k%).y + 27), _RGB32(32, 32, 32), BF
  117.         END SELECT
  118.     END IF
  119.  
  120.  
  121. '--------------------------------------------------------------------------------------------
  122.  
  123. SUB PRESS (k%)
  124.  
  125.     '*
  126.     '* Applies key press display and sets key as being pressed
  127.     '*
  128.  
  129.     SHARED K() AS IVORY ' need access to key information array
  130.     SHARED Tone&() '      need access to piano key sounds array
  131.     SHARED Octave% '      need access to current octave
  132.  
  133.     IF NOT K(k%).Down THEN '                                               is key released?
  134.         K(k%).Down = -1 '                                                  yes, set it as pressed
  135.         _SNDPLAY Tone&(Octave% * 12 + k%) '                                play tone for key
  136.         SELECT CASE k% '                                                   which key is it?
  137.             CASE 1, 3, 5, 6, 8, 10, 12 '                                   white key
  138.                 LINE (K(k%).x, K(k%).y)-(K(k%).x + 27, K(k%).y + 27), _RGB32(0, 0, 0), BF
  139.             CASE ELSE '                                                    black key
  140.                 LINE (K(k%).x, K(k%).y)-(K(k%).x + 27, K(k%).y + 27), _RGB32(255, 255, 255), BF
  141.         END SELECT
  142.     END IF
  143.  
  144.  
  145. '--------------------------------------------------------------------------------------------
  146.  
  147. SUB SHOWOCTAVE
  148.  
  149.     '*
  150.     '* Updates the small top piano keyboard to show current active octave
  151.     '*
  152.  
  153.     SHARED Octave% '     need access to current octave
  154.     SHARED imgAoctave& ' need access to active octave image
  155.     SHARED imgIoctave& ' need access to inactive octave image
  156.  
  157.     DIM Count% '         generic counter
  158.  
  159.     FOR Count% = 0 TO 4 '                                    cycle through octaves
  160.         IF Count% = Octave% THEN '                           current octave?
  161.             _PUTIMAGE (96 + (Count% * 64), 0), imgAoctave& ' yes, place active octave image
  162.         ELSE '                                               no
  163.             _PUTIMAGE (96 + (Count% * 64), 0), imgIoctave& ' place inactive octave image
  164.         END IF
  165.     NEXT Count%
  166.  
  167.  
  168. '--------------------------------------------------------------------------------------------
  169.  
  170. SUB LOADPIANO ()
  171.  
  172.     '*
  173.     '* Loads the piano sounds and images and initializes variables
  174.     '*
  175.  
  176.     SHARED K() AS IVORY ' need access to key information array
  177.     SHARED Tone&() '      need access to piano key sounds array
  178.     SHARED imgPiano& '    need access to piano keyboard image
  179.     SHARED imgAoctave& '  need access to active octave image
  180.     SHARED imgIoctave& '  need access to inactive octave image
  181.     SHARED Octave% '      need access to current octave
  182.  
  183.     DIM Note% '           counter used to open sounds
  184.     DIM Count% '          counter used to close sounds if error
  185.     DIM Path$ '           path to sound and graphics files
  186.     DIM File$ '           sound file names
  187.  
  188.     Path$ = ".\PIANO\"
  189.     IF _DIREXISTS(Path$) THEN '                                        path exist?
  190.         FOR Note% = 1 TO 88 '                                          cycle through notes
  191.             File$ = Path$ + LTRIM$(STR$(Note%)) + ".ogg" '             construct file name
  192.             IF _FILEEXISTS(File$) THEN '                               sound file exist?
  193.                 Tone&(Note%) = _SNDOPEN(File$, "VOL,SYNC,LEN,PAUSE") ' yes, load sound file
  194.             ELSE '                                                     no, sound file missing
  195.                 PRINT '                                                report error to user
  196.                 PRINT " ERROR: Sound file "; File$; " is missing."
  197.                 IF Note% > 1 THEN '                                    did any sounds load?
  198.                     FOR Count% = Note% TO 1 STEP -1 '                  yes, cycle notes backwards
  199.                         _SNDCLOSE Tone&(Count%) '                      remove sound from RAM
  200.                     NEXT Count%
  201.                     END '                                              end program
  202.                 END IF
  203.             END IF
  204.         NEXT Note%
  205.     ELSE '                                                             no, path missing
  206.         PRINT '                                                        report error to user
  207.         PRINT " ERROR: The \PIANO\ folder could not be found."
  208.         END '                                                          end program
  209.     END IF
  210.     IF _FILEEXISTS(Path$ + "piano.png") THEN '                     image file exist?
  211.         imgPiano& = _LOADIMAGE(Path$ + "piano.png", 32) '          yes, load image file
  212.     ELSE '                                                         no, image file missing
  213.         PRINT '                                                    report error to user
  214.         PRINT " ERROR: piano.png missing."
  215.         END '                                                      end program
  216.     END IF
  217.     IF _FILEEXISTS(Path$ + "active.png") THEN '                    image file exist?
  218.         imgAoctave& = _LOADIMAGE(Path$ + "active.png", 32) '       yes, load image file
  219.     ELSE '                                                         no, image file missing
  220.         PRINT '                                                    report error to user
  221.         PRINT " ERROR: active.png missing."
  222.         _FREEIMAGE imgPiano& '                                     remove image from RAM
  223.         END '                                                      end program
  224.     END IF
  225.     IF _FILEEXISTS(Path$ + "inactive.png") THEN '                  image file exist?
  226.         imgIoctave& = _LOADIMAGE(Path$ + "inactive.png", 32) '     yes, load image file
  227.     ELSE '                                                         no, image file missing
  228.         PRINT '                                                    report error to user
  229.         PRINT " ERROR: inactive.png missing."
  230.         _FREEIMAGE imgPiano& '                                     remove image from RAM
  231.         _FREEIMAGE imgAoctave& '                                   remove image from RAM
  232.         END '                                                      end program
  233.     END IF
  234.  
  235.     K(1).x = 22: K(1).y = 212: K(2).x = 60: K(2).y = 132 '             set indicator coordinates
  236.     K(3).x = 95: K(3).y = 212: K(4).x = 134: K(4).y = 132
  237.     K(5).x = 168: K(5).y = 212: K(6).x = 241: K(6).y = 212
  238.     K(7).x = 278: K(7).y = 132: K(8).x = 314: K(8).y = 212
  239.     K(9).x = 353: K(9).y = 132: K(10).x = 387: K(10).y = 212
  240.     K(11).x = 428: K(11).y = 132: K(12).x = 460: K(12).y = 212
  241.     K(1).l = 100: K(1).u = 68: K(2).l = 114: K(2).u = 82 '             set key case values
  242.     K(3).l = 102: K(3).u = 70: K(4).l = 116: K(4).u = 84
  243.     K(5).l = 103: K(5).u = 71: K(6).l = 104: K(6).u = 72
  244.     K(7).l = 117: K(7).u = 85: K(8).l = 106: K(8).u = 74
  245.     K(9).l = 105: K(9).u = 73: K(10).l = 107: K(10).u = 75
  246.     K(11).l = 111: K(11).u = 79: K(12).l = 108: K(12).u = 76
  247.     Octave% = 2 '                                                      set initial octave
  248.  
  249.  
  250. '--------------------------------------------------------------------------------------------
  251.  
Title: Re: Simple Piano
Post by: Petr on March 25, 2020, 05:19:50 pm
Hi, i like this nice program!
Title: Re: Simple Piano
Post by: TempodiBasic on March 25, 2020, 06:43:50 pm
Hi Terry
I remember this your application about sounds chapter!
Fine!
Title: Re: Simple Piano
Post by: johnno56 on March 25, 2020, 07:32:55 pm
Nicely done! Slight modification to PATH (Linux user), but other than that, works quite well. Thank you!
Title: Re: Simple Piano
Post by: TerryRitchie on March 26, 2020, 12:15:03 am
Thanks everyone :-)

Johnno, what did you need to change? I need to start taking note of the Linux differences. I'm thinking about switching my main programming system over to Linux soon. I'll need to start using metacommands to detect Linux vs Windows systems for the compiled .EXE files.
Title: Re: Simple Piano
Post by: Ashish on March 26, 2020, 02:00:11 am
WOW! I liked this program a lot! Great Job!
@TerryRitchie Could you please add a feature so that I can save the music I create in .wav or .mp3 or any other format?
Title: Re: Simple Piano
Post by: Petr on March 26, 2020, 05:55:44 am
Ashish wrote:

Quote
WOW! I liked this program a lot! Great Job!
@TerryRitchie Could you please add a feature so that I can save the music I create in .wav or .mp3 or any other format?

GREAT IDEA! I'm not with a source code computer now, but TODAY later I add functional source code here that can save WAV files. You will still need to convert the OGG source files to WAV format for the feature you describe and the concept I have in mind. Combining individual keys, when playing 4 notes at the same time, is performed as a mathematical average of the values of the wav files. This interweaving really works like this, I have tried it. I will also try to modify this program in the way you write about!
Title: Re: Simple Piano
Post by: johnno56 on March 26, 2020, 06:32:50 am
Terry,

The only change needed was:

Line 193: Path$ = ".\PIANO\"  to  Path$ = "./PIANO/"

Hope this helps.

J
Title: Re: Simple Piano
Post by: luke on March 26, 2020, 06:45:03 am
I'm not in a position to check right now, but I'm pretty sure QB64 supports paths with "/” slashes on both Windows and Unix. If that is indeed correct they're the safer slash to use everywhere.
Title: Re: Simple Piano
Post by: SMcNeill on March 26, 2020, 06:49:03 am
I'm not in a position to check right now, but I'm pretty sure QB64 supports paths with "/” slashes on both Windows and Unix. If that is indeed correct they're the safer slash to use everywhere.

I thought QB64 changed slashes to fit the file system automatically, so it didn't matter if either / or \ was used? 
Title: Re: Simple Piano
Post by: Qwerkey on March 26, 2020, 07:42:45 am
We Librarians (well one of us at least) like this.  @TerryRitchie Now that we have "Output EXE to Source Folder", if you're going to update this program, why not get rid of all the PATH$ and IF _FILEEXISTS stuff?
Title: Re: Simple Piano
Post by: TerryRitchie on March 26, 2020, 09:42:28 am
WOW! I liked this program a lot! Great Job!
@TerryRitchie Could you please add a feature so that I can save the music I create in .wav or .mp3 or any other format?

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?
Title: Re: Simple Piano
Post by: TerryRitchie on March 26, 2020, 09:52:46 am
We Librarians (well one of us at least) like this.  @TerryRitchie Now that we have "Output EXE to Source Folder", if you're going to update this program, why not get rid of all the PATH$ and IF _FILEEXISTS stuff?

Here is the code modified to remove the need for a separate .\PIANO\ folder for the assets.

Code: QB64: [Select]
  1. '*
  2. '* QB64 Simple Piano
  3. '*
  4. '* by Terry Ritchie
  5. '*
  6. '* Demonstrates the use of external sound files to create a realistic piano.
  7. '*
  8. '* Modified 03/26/20
  9. '* Removed the need for a separate .\PIANO\ folder to hold the graphics and
  10. '* sound assets. All assets are now to be contained in the same folder as
  11. '* PIANO.EXE
  12. '*
  13. '* ESC         - exit program
  14. '* RIGHT ARROW - increase octave
  15. '* LEFT ARROW  - decrease octave
  16. '* Piano Keys  -  R T  U I O   (black keys)
  17. '*             - D F GH J K L  (white keys)
  18. '*
  19.  
  20. '--------------------------------
  21. '- Variable Declaration Section -
  22. '--------------------------------
  23.  
  24. TYPE IVORY '          key information
  25.     u AS INTEGER '    upper case value
  26.     l AS INTEGER '    lower case value
  27.     Down AS INTEGER ' key position
  28.     x AS INTEGER '    key indicator x coordinate
  29.     y AS INTEGER '    key indicator y coordinate
  30.  
  31. DIM K(12) AS IVORY '  key information array
  32. DIM Tone&(88) '       piano key sounds array
  33. DIM imgPiano& '       piano keyboard image
  34. DIM imgAoctave& '     active octave image
  35. DIM imgIoctave& '     inactive octave image
  36. DIM Octave% '         current octave
  37. DIM Khit& '           keyboard status
  38. DIM Keys% '           key cycle counter
  39.  
  40. '----------------------------
  41. '- Main Program Begins Here -
  42. '----------------------------
  43.  
  44. LOADPIANO '                                                          load piano assets
  45. SCREEN _NEWIMAGE(512, 263, 32) '                                     create default screen
  46. _TITLE "PIANO" '                                                     set window title
  47. _SCREENMOVE _MIDDLE '                                                center window on desktop
  48. _DELAY .25
  49. _PUTIMAGE (0, 0), imgPiano& '                                        show piano image
  50. SHOWOCTAVE '                                                         update octave indicator
  51. DO '                                                                 MAIN LOOP begins
  52.     Khit& = _KEYHIT '                                                get keyboard status
  53.     IF Khit& THEN '                                                  was a key hit?
  54.         IF Khit& = 19200 OR Khit& = 19712 THEN '                     yes, left or right key?
  55.             IF Khit& = 19200 THEN '                                  yes, left key?
  56.                 Octave% = Octave% - 1 '                              yes, decrease octave
  57.                 IF Octave% = -1 THEN Octave% = 0 '                   keep octave in limits
  58.             ELSE '                                                   no, must be right key
  59.                 Octave% = Octave% + 1 '                              increase octave
  60.                 IF Octave% = 5 THEN Octave% = 4 '                    keep octave in limits
  61.             END IF
  62.             SHOWOCTAVE '                                             update octave indicator
  63.         ELSEIF Khit& = 27 THEN '                                     no, escape key?
  64.             QUIT '                                                   yes, quit program
  65.         END IF
  66.     END IF
  67.     FOR Keys% = 1 TO 12 '                                            cycle through keys
  68.         IF _KEYDOWN(K(Keys%).u) OR _KEYDOWN(K(Keys%).l) THEN '       key pressed?
  69.             PRESS Keys% '                                            yes, play note
  70.         ELSE '                                                       no
  71.             RELEASE Keys% '                                          remove key indicator
  72.         END IF
  73.     NEXT Keys%
  74.     _DISPLAY '                                                       update screen changes
  75. LOOP '                                                               MAIN LOOP back
  76.  
  77. '-----------------------------------
  78. '- Function and Subroutine section -
  79. '-----------------------------------
  80.  
  81. '--------------------------------------------------------------------------------------------
  82.  
  83. SUB QUIT ()
  84.  
  85.     '*
  86.     '* Cleans RAM by removing all image and sound assets and then exits to Windows.
  87.     '*
  88.  
  89.     SHARED Tone&() '     need access to piano key sounds array
  90.     SHARED imgPiano& '   need access to piano keyboard image
  91.     SHARED imgAoctave& ' need access to active octave image
  92.     SHARED imgIoctave& ' need access to inactive octave image
  93.  
  94.     DIM Count% '         generic counter
  95.  
  96.     FOR Count% = 1 TO 88 '        cycle through all 88 sound files
  97.         _SNDCLOSE Tone&(Count%) ' remove sound file from RAM
  98.     NEXT Count%
  99.     _FREEIMAGE imgPiano& '        remove piano image from RAM
  100.     _FREEIMAGE imgAoctave& '      remove active octave image from RAM
  101.     _FREEIMAGE imgIoctave& '      remove inactive octave image from RAM
  102.     SYSTEM '                      return to Windows
  103.  
  104.  
  105. '--------------------------------------------------------------------------------------------
  106.  
  107. SUB RELEASE (k%)
  108.  
  109.     '*
  110.     '* Removes key press display and sets key as being released
  111.     '*
  112.  
  113.     SHARED K() AS IVORY ' need access to key information array
  114.  
  115.     IF K(k%).Down THEN '                                                  is key pressed?
  116.         K(k%).Down = 0 '                                                  yes, set it as released
  117.         SELECT CASE k% '                                                  which key is it?
  118.             CASE 1, 3, 5, 6, 8, 10, 12 '                                  white key
  119.                 LINE (K(k%).x, K(k%).y)-(K(k%).x + 27, K(k%).y + 27), _RGB32(255, 255, 255), BF
  120.             CASE ELSE '                                                   black key
  121.                 LINE (K(k%).x, K(k%).y)-(K(k%).x + 27, K(k%).y + 27), _RGB32(32, 32, 32), BF
  122.         END SELECT
  123.     END IF
  124.  
  125.  
  126. '--------------------------------------------------------------------------------------------
  127.  
  128. SUB PRESS (k%)
  129.  
  130.     '*
  131.     '* Applies key press display and sets key as being pressed
  132.     '*
  133.  
  134.     SHARED K() AS IVORY ' need access to key information array
  135.     SHARED Tone&() '      need access to piano key sounds array
  136.     SHARED Octave% '      need access to current octave
  137.  
  138.     IF NOT K(k%).Down THEN '                                               is key released?
  139.         K(k%).Down = -1 '                                                  yes, set it as pressed
  140.         _SNDPLAY Tone&(Octave% * 12 + k%) '                                play tone for key
  141.         SELECT CASE k% '                                                   which key is it?
  142.             CASE 1, 3, 5, 6, 8, 10, 12 '                                   white key
  143.                 LINE (K(k%).x, K(k%).y)-(K(k%).x + 27, K(k%).y + 27), _RGB32(0, 0, 0), BF
  144.             CASE ELSE '                                                    black key
  145.                 LINE (K(k%).x, K(k%).y)-(K(k%).x + 27, K(k%).y + 27), _RGB32(255, 255, 255), BF
  146.         END SELECT
  147.     END IF
  148.  
  149.  
  150. '--------------------------------------------------------------------------------------------
  151.  
  152. SUB SHOWOCTAVE
  153.  
  154.     '*
  155.     '* Updates the small top piano keyboard to show current active octave
  156.     '*
  157.  
  158.     SHARED Octave% '     need access to current octave
  159.     SHARED imgAoctave& ' need access to active octave image
  160.     SHARED imgIoctave& ' need access to inactive octave image
  161.  
  162.     DIM Count% '         generic counter
  163.  
  164.     FOR Count% = 0 TO 4 '                                    cycle through octaves
  165.         IF Count% = Octave% THEN '                           current octave?
  166.             _PUTIMAGE (96 + (Count% * 64), 0), imgAoctave& ' yes, place active octave image
  167.         ELSE '                                               no
  168.             _PUTIMAGE (96 + (Count% * 64), 0), imgIoctave& ' place inactive octave image
  169.         END IF
  170.     NEXT Count%
  171.  
  172.  
  173. '--------------------------------------------------------------------------------------------
  174.  
  175. SUB LOADPIANO ()
  176.  
  177.     '*
  178.     '* Loads the piano sounds and images and initializes variables
  179.     '*
  180.  
  181.     SHARED K() AS IVORY ' need access to key information array
  182.     SHARED Tone&() '      need access to piano key sounds array
  183.     SHARED imgPiano& '    need access to piano keyboard image
  184.     SHARED imgAoctave& '  need access to active octave image
  185.     SHARED imgIoctave& '  need access to inactive octave image
  186.     SHARED Octave% '      need access to current octave
  187.  
  188.     DIM Note% '           counter used to open sounds
  189.     DIM Count% '          counter used to close sounds if error
  190.     DIM File$ '           sound file names
  191.  
  192.     FOR Note% = 1 TO 88 '                                          cycle through notes
  193.         File$ = LTRIM$(STR$(Note%)) + ".ogg" '                     construct file name
  194.         IF _FILEEXISTS(File$) THEN '                               sound file exist?
  195.             Tone&(Note%) = _SNDOPEN(File$, "VOL,SYNC,LEN,PAUSE") ' yes, load sound file
  196.         ELSE '                                                     no, sound file missing
  197.             PRINT '                                                report error to user
  198.             PRINT " ERROR: Sound file "; File$; " is missing."
  199.             IF Note% > 1 THEN '                                    did any sounds load?
  200.                 FOR Count% = Note% TO 1 STEP -1 '                  yes, cycle notes backwards
  201.                     _SNDCLOSE Tone&(Count%) '                      remove sound from RAM
  202.                 NEXT Count%
  203.                 END '                                              end program
  204.             END IF
  205.         END IF
  206.     NEXT Note%
  207.     IF _FILEEXISTS("piano.png") THEN '                             image file exist?
  208.         imgPiano& = _LOADIMAGE("piano.png", 32) '                  yes, load image file
  209.     ELSE '                                                         no, image file missing
  210.         PRINT '                                                    report error to user
  211.         PRINT " ERROR: piano.png missing."
  212.         END '                                                      end program
  213.     END IF
  214.     IF _FILEEXISTS("active.png") THEN '                            image file exist?
  215.         imgAoctave& = _LOADIMAGE("active.png", 32) '               yes, load image file
  216.     ELSE '                                                         no, image file missing
  217.         PRINT '                                                    report error to user
  218.         PRINT " ERROR: active.png missing."
  219.         _FREEIMAGE imgPiano& '                                     remove image from RAM
  220.         END '                                                      end program
  221.     END IF
  222.     IF _FILEEXISTS(Path$ + "inactive.png") THEN '                  image file exist?
  223.         imgIoctave& = _LOADIMAGE("inactive.png", 32) '             yes, load image file
  224.     ELSE '                                                         no, image file missing
  225.         PRINT '                                                    report error to user
  226.         PRINT " ERROR: inactive.png missing."
  227.         _FREEIMAGE imgPiano& '                                     remove image from RAM
  228.         _FREEIMAGE imgAoctave& '                                   remove image from RAM
  229.         END '                                                      end program
  230.     END IF
  231.  
  232.     K(1).x = 22: K(1).y = 212: K(2).x = 60: K(2).y = 132 '         set indicator coordinates
  233.     K(3).x = 95: K(3).y = 212: K(4).x = 134: K(4).y = 132
  234.     K(5).x = 168: K(5).y = 212: K(6).x = 241: K(6).y = 212
  235.     K(7).x = 278: K(7).y = 132: K(8).x = 314: K(8).y = 212
  236.     K(9).x = 353: K(9).y = 132: K(10).x = 387: K(10).y = 212
  237.     K(11).x = 428: K(11).y = 132: K(12).x = 460: K(12).y = 212
  238.     K(1).l = 100: K(1).u = 68: K(2).l = 114: K(2).u = 82 '         set key case values
  239.     K(3).l = 102: K(3).u = 70: K(4).l = 116: K(4).u = 84
  240.     K(5).l = 103: K(5).u = 71: K(6).l = 104: K(6).u = 72
  241.     K(7).l = 117: K(7).u = 85: K(8).l = 106: K(8).u = 74
  242.     K(9).l = 105: K(9).u = 73: K(10).l = 107: K(10).u = 75
  243.     K(11).l = 111: K(11).u = 79: K(12).l = 108: K(12).u = 76
  244.     Octave% = 2 '                                                  set initial octave
  245.  
  246.  
  247. '--------------------------------------------------------------------------------------------
  248.  
Title: Re: Simple Piano
Post by: TerryRitchie on March 26, 2020, 09:55:48 am
Terry,

The only change needed was:

Line 193: Path$ = ".\PIANO\"  to  Path$ = "./PIANO/"

Hope this helps.

J

I though an earlier version of QB64 corrected that ... the / or \ could be used and the compiler would correct for the underlying OS?

Oh, I see that Steve thought that too.
Title: Re: Simple Piano
Post by: FellippeHeitor on March 26, 2020, 10:11:11 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.
Title: Re: Simple Piano
Post by: TerryRitchie 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. :-)
Title: Re: Simple Piano
Post by: Petr 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.
Title: Re: Simple Piano
Post by: Petr 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.  

Title: Re: Simple Piano
Post by: Petr 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!
Title: Re: Simple Piano
Post by: Ashish on March 27, 2020, 07:56:33 am
Nice @Petr !
I tried your example and it worked. Looking forward to your work on this.
Title: Re: Simple Piano
Post by: Petr 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.
Title: Re: Simple Piano
Post by: Ashish 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.
Title: Re: Simple Piano
Post by: Petr 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.  
Title: Re: Simple Piano
Post by: Petr 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.  
Title: Re: Simple Piano
Post by: TrialAndTerror 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
Title: Re: Simple Piano
Post by: TerryRitchie 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.
Title: Re: Simple Piano
Post by: SierraKen on April 30, 2020, 09:20:59 pm
Very nice piano Terry. Good way to use sound files too.
Title: Re: Simple Piano
Post by: TerryRitchie on May 01, 2020, 12:14:44 am
Thanks :-)