Author Topic: Attempting PLAY & MIDI-like songs using _SNDRAW  (Read 4315 times)

0 Members and 1 Guest are viewing this topic.

Offline Dav

  • Forum Resident
  • Posts: 792
    • View Profile
Attempting PLAY & MIDI-like songs using _SNDRAW
« on: January 11, 2021, 11:02:23 pm »
I'm trying to come up with a way to play several musical notes at the same time, unlike the PLAY which only plays one at a time. So I combined the the SPLAY thing I made for the holiday thread (which used sound files), with the _SNDRAW command to make/play the notes, and it wil play MIDI like songs in the background.

This is just beginning, only 15 notes working, but it seems to have some possibilities.  I'll keep working on it.  Any suggestions welcomed... 

- Dav

Code: QB64: [Select]
  1. '==============
  2. 'SNDRAWPLAY.BAS v1.00
  3. '==============
  4. 'Attempting a PLAY-like song format using _SNDRAW
  5. 'Plays notes and allows more than one note to sound at the same time.
  6. 'Can play notes in the background.
  7. 'Coded By Dav, JAN/2021
  8.  
  9. 'For now, there only 15 notes playable, about 2 octaves.
  10. ' e1 f1 g1 a1 b1 | c2 d2 e2 f2 g2 a2 b2 | c3 d3 e3
  11. 'You can play a chord of notes by grouping inside ()
  12. ' (c2 e2 g2)
  13.  
  14. 'Assign current note/rest length values like this...
  15. 'WN = Whole note, HN = Half note, DQ = Dotted quarter note
  16. 'QN = Quarter note, EN = Eighth note, SN = Sixteenth note
  17.  
  18. 'Rests - nothing played, but time continues
  19. 'RN = Rest note.  Uses current note length value set.
  20. 'For example, to rest a quarter note, do this:
  21. 'QN RN
  22.  
  23. 'Assign Tempos like this (always must be in 4 characters):
  24. 'T120  ... or T060   ...  or  T100
  25.  
  26. 'Assign current meter (for whole length value to work)
  27. 'M3  (thats for 3/4)....  M4   (Thats for 4/4)
  28.  
  29. '========================================================
  30.  
  31.  
  32. '=== Playing all notes...
  33.  
  34. 'Set tempo 120, meter 4/4, set sixteen note value
  35. SPLAY "t120 m4 sn e1f1g1a1b1c2d2e2f2g2a2b2c3d3e3"
  36.  
  37.     COLOR RND * 16
  38.     PRINT "Testing all notes...e1 to e3..."
  39.  
  40.  
  41. '=== Playing some chords...
  42.  
  43. 'Set tempo 120, meter 4/4, set sixteen note value, play all notes
  44. 'Note: You don't have to include spaces, but I did here...
  45. SPLAY "qn (c2 e2 g2) (f2a2c3) (g2b2d3) (f2a2c3) (c2 e2 g2)"
  46.  
  47.     COLOR RND * 16
  48.     PRINT "Playing chords..."
  49.  
  50.  
  51. '=== Playing a song.... part of Silent Night.,,,
  52.  
  53. Song$ = ""
  54. Song$ = Song$ + "t100m3dq(c2e2g2)en(c2f2a2)qn(c2e2g2)wn(g1c2e2)"
  55. Song$ = Song$ + "dq(c2e2g2)en(c2f2a2)qn(c2e2g2)wn(g1c2e2)"
  56. Song$ = Song$ + "hn(d3b2g2)qnd3(g2b2)c3(d3g2f2)"
  57. Song$ = Song$ + "dq(c2e2g2)en(c2f2a2)qn(c2e2g2)wn(g1c2e2)"
  58. SPLAY Song$
  59.  
  60.     COLOR RND * 16
  61.     PRINT "Silent Night ";
  62.     IF INKEY$ <> "" THEN EXIT DO
  63.  
  64.  
  65.  
  66.  
  67.  
  68. SUB SPLAY (Music$)
  69.  
  70.     rate = _SNDRATE
  71.  
  72.     'Set Defaults, just in case empty
  73.     IF Tempo = 0 THEN Tempo = 60
  74.     IF Meter = 0 THEN Meter = 3
  75.     IF NoteValue = 0 THEN NoteValue = 1
  76.  
  77.     Music$ = UCASE$(Music$)
  78.     cur = 1
  79.  
  80.     DO
  81.  
  82.         'skip any spaces
  83.         IF MID$(Music$, cur, 1) = " " THEN cur = cur + 1
  84.  
  85.         'Check for tempo
  86.         IF MID$(Music$, cur, 1) = "T" THEN
  87.             cur = cur + 1
  88.             Tempo = VAL(MID$(Music$, cur, 3)): cur = cur + 3
  89.         END IF
  90.  
  91.         'Check for Meter
  92.         IF MID$(Music$, cur, 1) = "M" THEN
  93.             cur = cur + 1
  94.             Meter = VAL(MID$(Music$, cur, 1)): cur = cur + 1
  95.         END IF
  96.  
  97.         'Get notevalue
  98.         SELECT CASE MID$(Music$, cur, 2)
  99.             CASE IS = "DQ": cur = cur + 2: NoteValue = 1.5
  100.             CASE IS = "EN": cur = cur + 2: NoteValue = .5
  101.             CASE IS = "QN": cur = cur + 2: NoteValue = 1
  102.             CASE IS = "HN": cur = cur + 2: NoteValue = 2
  103.             CASE IS = "WN": cur = cur + 2
  104.                 IF Meter = 3 THEN NoteValue = 3 ELSE NoteValue = 4
  105.             CASE IS = "SN": cur = cur + 2: NoteValue = .25
  106.         END SELECT
  107.  
  108.         'If regular note/rest found (not a group)
  109.         SELECT CASE MID$(Music$, cur, 2)
  110.  
  111.             CASE IS = "E1": note = 329.63: cur = cur + 2: GOSUB LoadNote
  112.             CASE IS = "F1": note = 349.23: cur = cur + 2: GOSUB LoadNote
  113.             CASE IS = "G1": note = 392.00: cur = cur + 2: GOSUB LoadNote
  114.             CASE IS = "A1": note = 440.00: cur = cur + 2: GOSUB LoadNote
  115.             CASE IS = "B1": note = 493.88: cur = cur + 2: GOSUB LoadNote
  116.             CASE IS = "C2": note = 523.25: cur = cur + 2: GOSUB LoadNote
  117.             CASE IS = "D2": note = 587.33: cur = cur + 2: GOSUB LoadNote
  118.  
  119.             CASE IS = "E2": note = 659.25: cur = cur + 2: GOSUB LoadNote
  120.             CASE IS = "F2": note = 698.46: cur = cur + 2: GOSUB LoadNote
  121.             CASE IS = "G2": note = 783.99: cur = cur + 2: GOSUB LoadNote
  122.             CASE IS = "A2": note = 880.00: cur = cur + 2: GOSUB LoadNote
  123.             CASE IS = "B2": note = 987.77: cur = cur + 2: GOSUB LoadNote
  124.             CASE IS = "C3": note = 1046.50: cur = cur + 2: GOSUB LoadNote
  125.             CASE IS = "D3": note = 1174.66: cur = cur + 2: GOSUB LoadNote
  126.             CASE IS = "E3": note = 1318.51: cur = cur + 2: GOSUB LoadNote
  127.             CASE IS = "RN": note = 0: cur = cur + 2: GOSUB LoadNote
  128.         END SELECT
  129.  
  130.         'if group of notes found
  131.         IF MID$(Music$, cur, 1) = "(" THEN
  132.             cur = cur + 1
  133.             'Grab up until ')' found
  134.             Group$ = ""
  135.             DO
  136.                 a$ = MID$(Music$, cur, 1): cur = cur + 1
  137.                 IF a$ = ")" THEN EXIT DO
  138.                 IF a$ <> " " THEN Group$ = Group$ + a$
  139.             LOOP
  140.  
  141.             NumOfNotes = LEN(Group$) / 2
  142.             Length = (60 * NoteValue / Tempo)
  143.             FOR L = 0 TO Length * rate STEP NumOfNotes
  144.  
  145.                 FOR N = 1 TO LEN(Group$) STEP 2
  146.                     'fade = EXP(-L / rate * 8)
  147.                     note$ = MID$(Group$, N, 2)
  148.                     IF note$ = "E1" THEN _SNDRAW SIN((L / rate) * 329.63 * ATN(1) * 8) '* fade
  149.                     IF note$ = "F1" THEN _SNDRAW SIN((L / rate) * 349.23 * ATN(1) * 8) '* fade
  150.                     IF note$ = "G1" THEN _SNDRAW SIN((L / rate) * 392.00 * ATN(1) * 8) '* fade
  151.                     IF note$ = "A1" THEN _SNDRAW SIN((L / rate) * 440.00 * ATN(1) * 8) '* fade
  152.                     IF note$ = "B1" THEN _SNDRAW SIN((L / rate) * 493.88 * ATN(1) * 8) '* fade
  153.                     IF note$ = "C2" THEN _SNDRAW SIN((L / rate) * 523.25 * ATN(1) * 8) '* fade
  154.                     IF note$ = "D2" THEN _SNDRAW SIN((L / rate) * 587.33 * ATN(1) * 8) '* fade
  155.                     IF note$ = "E2" THEN _SNDRAW SIN((L / rate) * 659.25 * ATN(1) * 8) '* fade
  156.                     IF note$ = "F2" THEN _SNDRAW SIN((L / rate) * 698.46 * ATN(1) * 8) '* fade
  157.                     IF note$ = "G2" THEN _SNDRAW SIN((L / rate) * 783.99 * ATN(1) * 8) '* fade
  158.                     IF note$ = "A2" THEN _SNDRAW SIN((L / rate) * 880.00 * ATN(1) * 8) '* fade
  159.                     IF note$ = "B2" THEN _SNDRAW SIN((L / rate) * 987.77 * ATN(1) * 8) '* fade
  160.                     IF note$ = "C3" THEN _SNDRAW SIN((L / rate) * 1046.50 * ATN(1) * 8) '* fade
  161.                     IF note$ = "D3" THEN _SNDRAW SIN((L / rate) * 1174.66 * ATN(1) * 8) '* fade
  162.                     IF note$ = "E3" THEN _SNDRAW SIN((L / rate) * 1318.51 * ATN(1) * 8) '* fade
  163.                 NEXT
  164.  
  165.             NEXT
  166.  
  167.         END IF
  168.  
  169.         IF cur >= LEN(Music$) THEN EXIT DO
  170.  
  171.         'IF INKEY$ <> "" THEN EXIT SUB
  172.  
  173.     LOOP
  174.  
  175.     EXIT SUB
  176.  
  177.     '=========================================================
  178.     LoadNote:
  179.     '========
  180.     Length = (60 * NoteValue / Tempo)
  181.     FOR L = 0 TO Length * rate
  182.         'fade = EXP(-l / rate * 8)
  183.         IF note = 0 THEN
  184.             _SNDRAW 0
  185.         ELSE
  186.             _SNDRAW SIN((L / rate) * note * ATN(1) * 8) '* fade
  187.         END IF
  188.     NEXT
  189.     RETURN
  190.  
  191.  
  192.  
« Last Edit: January 11, 2021, 11:24:40 pm by Dav »

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #1 on: January 11, 2021, 11:50:07 pm »
Wow that is pretty nice sounding. Sounds like an organ. :)

Offline Dav

  • Forum Resident
  • Posts: 792
    • View Profile
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #2 on: January 12, 2021, 07:59:29 am »
I guess it does.  Your mantle clock using _SNDRAW got me on the idea to this, @SierraKen

Here's a better test code.  Playing some background music while doing more intense graphics.  Looks like it plays OK while doing other tasks, and _LIMIT 30 doesn't seem to mess with it, but there is a noticeable delay when the music has to be loaded again.  I guess there's no way around that. 

- Dav

Code: QB64: [Select]
  1. 'SNDRAWPLAY test by Dav
  2.  
  3. SCREEN _NEWIMAGE(600, 600, 32): h2 = _HEIGHT / 2
  4.  
  5. s$ = "" 'a little tune to use
  6. s$ = s$ + "t120 en (c2f1a1)(f2c2a1)(a2f2c2)(c3f2a2)dq(b2g2d2)"
  7. s$ = s$ + "qn (c2f1a1) en (f2c2a1)(a2f2c2)(c3f2a2) qn (d3b2g2)(b2g2)"
  8. s$ = s$ + "en (c2f1a1)(f2c2a1)(a2f2c2)(c3f2a2)qn(b2g2d2)en(g2)wn(a2f2c2)dqrn"
  9.  
  10.  
  11.     'Start music if no sound playing
  12.     IF _SNDRAWLEN = 0 THEN SPLAY s$
  13.  
  14.  
  15.     'something to display while music playing
  16.     t = TIMER
  17.     FOR x = 0 TO _WIDTH
  18.         FOR y = 0 TO _HEIGHT
  19.             r = SIN(1.1 * t) * h2 - y + h2
  20.             LINE (x, y)-(x, y), _RGB(r, r - y, -r), BF
  21.         NEXT: t = t + .01
  22.     NEXT
  23.  
  24.     _LIMIT 30: _DISPLAY
  25.  
  26.  
  27.  
  28.  
  29.  
  30.  
  31. SUB SPLAY (Music$)
  32.  
  33.     rate = _SNDRATE
  34.  
  35.     'Set Defaults, just in case empty
  36.     IF Tempo = 0 THEN Tempo = 60
  37.     IF Meter = 0 THEN Meter = 3
  38.     IF NoteValue = 0 THEN NoteValue = 1
  39.  
  40.     Music$ = UCASE$(Music$)
  41.     cur = 1
  42.  
  43.     DO
  44.  
  45.         'skip any spaces
  46.         IF MID$(Music$, cur, 1) = " " THEN cur = cur + 1
  47.  
  48.         'Check for tempo
  49.         IF MID$(Music$, cur, 1) = "T" THEN
  50.             cur = cur + 1
  51.             Tempo = VAL(MID$(Music$, cur, 3)): cur = cur + 3
  52.         END IF
  53.  
  54.         'Check for Meter
  55.         IF MID$(Music$, cur, 1) = "M" THEN
  56.             cur = cur + 1
  57.             Meter = VAL(MID$(Music$, cur, 1)): cur = cur + 1
  58.         END IF
  59.  
  60.         'Get notevalue
  61.         SELECT CASE MID$(Music$, cur, 2)
  62.             CASE IS = "DQ": cur = cur + 2: NoteValue = 1.5
  63.             CASE IS = "EN": cur = cur + 2: NoteValue = .5
  64.             CASE IS = "QN": cur = cur + 2: NoteValue = 1
  65.             CASE IS = "HN": cur = cur + 2: NoteValue = 2
  66.             CASE IS = "WN": cur = cur + 2
  67.                 IF Meter = 3 THEN NoteValue = 3 ELSE NoteValue = 4
  68.             CASE IS = "SN": cur = cur + 2: NoteValue = .25
  69.         END SELECT
  70.  
  71.         'If regular note/rest found (not a group)
  72.         SELECT CASE MID$(Music$, cur, 2)
  73.  
  74.             CASE IS = "E1": note = 329.63: cur = cur + 2: GOSUB LoadNote
  75.             CASE IS = "F1": note = 349.23: cur = cur + 2: GOSUB LoadNote
  76.             CASE IS = "G1": note = 392.00: cur = cur + 2: GOSUB LoadNote
  77.             CASE IS = "A1": note = 440.00: cur = cur + 2: GOSUB LoadNote
  78.             CASE IS = "B1": note = 493.88: cur = cur + 2: GOSUB LoadNote
  79.             CASE IS = "C2": note = 523.25: cur = cur + 2: GOSUB LoadNote
  80.             CASE IS = "D2": note = 587.33: cur = cur + 2: GOSUB LoadNote
  81.  
  82.             CASE IS = "E2": note = 659.25: cur = cur + 2: GOSUB LoadNote
  83.             CASE IS = "F2": note = 698.46: cur = cur + 2: GOSUB LoadNote
  84.             CASE IS = "G2": note = 783.99: cur = cur + 2: GOSUB LoadNote
  85.             CASE IS = "A2": note = 880.00: cur = cur + 2: GOSUB LoadNote
  86.             CASE IS = "B2": note = 987.77: cur = cur + 2: GOSUB LoadNote
  87.             CASE IS = "C3": note = 1046.50: cur = cur + 2: GOSUB LoadNote
  88.             CASE IS = "D3": note = 1174.66: cur = cur + 2: GOSUB LoadNote
  89.             CASE IS = "E3": note = 1318.51: cur = cur + 2: GOSUB LoadNote
  90.             CASE IS = "RN": note = 0: cur = cur + 2: GOSUB LoadNote
  91.         END SELECT
  92.  
  93.         'if group of notes found
  94.         IF MID$(Music$, cur, 1) = "(" THEN
  95.             cur = cur + 1
  96.             'Grab up until ')' found
  97.             Group$ = ""
  98.             DO
  99.                 a$ = MID$(Music$, cur, 1): cur = cur + 1
  100.                 IF a$ = ")" THEN EXIT DO
  101.                 IF a$ <> " " THEN Group$ = Group$ + a$
  102.             LOOP
  103.  
  104.             NumOfNotes = LEN(Group$) / 2
  105.             Length = (60 * NoteValue / Tempo)
  106.             FOR L = 0 TO Length * rate STEP NumOfNotes
  107.  
  108.                 FOR N = 1 TO LEN(Group$) STEP 2
  109.                     'fade = EXP(-L / rate * 8)
  110.                     note$ = MID$(Group$, N, 2)
  111.                     IF note$ = "E1" THEN _SNDRAW SIN((L / rate) * 329.63 * ATN(1) * 8) '* fade
  112.                     IF note$ = "F1" THEN _SNDRAW SIN((L / rate) * 349.23 * ATN(1) * 8) '* fade
  113.                     IF note$ = "G1" THEN _SNDRAW SIN((L / rate) * 392.00 * ATN(1) * 8) '* fade
  114.                     IF note$ = "A1" THEN _SNDRAW SIN((L / rate) * 440.00 * ATN(1) * 8) '* fade
  115.                     IF note$ = "B1" THEN _SNDRAW SIN((L / rate) * 493.88 * ATN(1) * 8) '* fade
  116.                     IF note$ = "C2" THEN _SNDRAW SIN((L / rate) * 523.25 * ATN(1) * 8) '* fade
  117.                     IF note$ = "D2" THEN _SNDRAW SIN((L / rate) * 587.33 * ATN(1) * 8) '* fade
  118.                     IF note$ = "E2" THEN _SNDRAW SIN((L / rate) * 659.25 * ATN(1) * 8) '* fade
  119.                     IF note$ = "F2" THEN _SNDRAW SIN((L / rate) * 698.46 * ATN(1) * 8) '* fade
  120.                     IF note$ = "G2" THEN _SNDRAW SIN((L / rate) * 783.99 * ATN(1) * 8) '* fade
  121.                     IF note$ = "A2" THEN _SNDRAW SIN((L / rate) * 880.00 * ATN(1) * 8) '* fade
  122.                     IF note$ = "B2" THEN _SNDRAW SIN((L / rate) * 987.77 * ATN(1) * 8) '* fade
  123.                     IF note$ = "C3" THEN _SNDRAW SIN((L / rate) * 1046.50 * ATN(1) * 8) '* fade
  124.                     IF note$ = "D3" THEN _SNDRAW SIN((L / rate) * 1174.66 * ATN(1) * 8) '* fade
  125.                     IF note$ = "E3" THEN _SNDRAW SIN((L / rate) * 1318.51 * ATN(1) * 8) '* fade
  126.                 NEXT
  127.  
  128.             NEXT
  129.  
  130.         END IF
  131.  
  132.         IF cur >= LEN(Music$) THEN EXIT DO
  133.  
  134.         'IF INKEY$ <> "" THEN EXIT SUB
  135.  
  136.     LOOP
  137.  
  138.     EXIT SUB
  139.  
  140.     '=========================================================
  141.     LoadNote:
  142.     '========
  143.     Length = (60 * NoteValue / Tempo)
  144.     FOR L = 0 TO Length * rate
  145.         'fade = EXP(-l / rate * 8)
  146.         IF note = 0 THEN
  147.             _SNDRAW 0
  148.         ELSE
  149.             _SNDRAW SIN((L / rate) * note * ATN(1) * 8) '* fade
  150.         END IF
  151.     NEXT
  152.     RETURN
  153.  
  154.  
  155.  

FellippeHeitor

  • Guest
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #3 on: January 12, 2021, 09:24:35 am »
Great job, Dav. I assume the format is the same as the version with audio files you shared before?

Now about:
Quote
I'm trying to come up with a way to play several musical notes at the same time, unlike the PLAY which only plays one at a time.

Just a reminder that PLAY has been upgraded by Galleon with QB64 to play multiple notes, using a comma as an indicator of simultaneous play:

Code: QB64: [Select]
  1. PLAY "O2 L8 C,E,G L1 C,E,G"

Offline Dav

  • Forum Resident
  • Posts: 792
    • View Profile
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #4 on: January 12, 2021, 09:32:20 am »
Wow! How did I miss that!  Thanks, @FellippeHeitor

Oh well...back to the puzzle games!

Edit:  now I'm wondering how hard it would be to convert a simple midi file of a keyboard instrument song to PLAY statements. Maybe will explore that...

- Dav
« Last Edit: January 12, 2021, 09:40:55 am by Dav »

Offline Dav

  • Forum Resident
  • Posts: 792
    • View Profile
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #5 on: January 12, 2021, 10:23:01 am »
@FellippeHeitor:  Is there a way to stop PLAY from playing notes once it's been started? 

- Dav

FellippeHeitor

  • Guest
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #6 on: January 12, 2021, 01:20:27 pm »
There's no stopping _SNDRAW output, except when the buffer is empty again (_SNDRAWLEN = 0). What PLAY does internally is actually generate sound waves, exactly like you are doing with your program above.

Offline Dav

  • Forum Resident
  • Posts: 792
    • View Profile
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #7 on: January 12, 2021, 03:12:38 pm »
OK. Obrigado.

I think i'll ditch this sndraw stuff and go back to using sound files/packs, bring something more unique to the QB64 music table.  Ive got a playing/recording system going with more octaves, am putting together a keyboard virtual instrument thing, like the tank drum program I made earlier. 

- Dav

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #8 on: January 12, 2021, 03:48:00 pm »
The way to mute _SNDRAW immediately is to multiply the unplayed audio data by zero. In addition to the audio program - you can now easily lay out your ogg files via _MEMSOUND and add various sound effects, including mixing audio into each other. Just add the samples. But then it's a good idea to check if the samples are in the range -32767 to 32768 (they are INTEGER type) so that the sound is not overheated. I'm literally crawling very slow through the programs now, because (traditionally) I can't catch up. The only reason I started avoiding to _SNDRAW, is poor stereo output.

Offline Dav

  • Forum Resident
  • Posts: 792
    • View Profile
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #9 on: January 12, 2021, 04:00:32 pm »
Thank you, @Petr.  I have not experimented with _MEMSOUND very much.  My piano app uses ogg sounds.  Will try to get it in forum posting shape soon.  I will highly value your advice with using _MEMSOUND.

- Dav

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: Attempting PLAY & MIDI-like songs using _SNDRAW
« Reply #10 on: January 14, 2021, 03:17:52 pm »
Awesome Felippe! I never knew you could do multiple notes at once either.
Another good thing about PLAY is that you can play the sounds as other code is going too by adding MB in the beginning. SOUND command doesn't do that.
This is from the Wiki page:
MF - Play music in the foreground (each note must be completed before another can start).
MB - Play music in the background while program code execution continues (QB64 has no note buffer limits).