October 31, 2025, 11:52:57 AM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


playmidi$ question

Started by soft4fun, January 24, 2009, 03:08:22 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

soft4fun

Hello,
Why is it possible to specify the midi channel in the playmidi$ string, since there is no manner to play multiple channels in same time ?
We cannot program a melody line and an accompaniment line (chords).

Ionic Wind Support Team

Ionic Wind Support Team

soft4fun

Thank you. I know this page.
But how can i play multiple midi channels simoultaneously ?
The following code plays the notes on channel 1 after the notes on channel 0.
I hope you get my point.

PLAYMIDI$ "T90 N0 O5 C2 D2 E2 N1 O5 E2 F2 G2"

Ionic Wind Support Team

Yes, which is why I referred you to the documentation. 

PLAYMIDI$ "T120 N0 I25 O5 *C2EG;D4EFG*A2O6CE; *C2EG;D4EFG*A2O7CE;"

Everything between * and ; are played at the same time index and can be on different channels.    "*N1C2N0EG;"

Ionic Wind Support Team

soft4fun

Please give me an exemple of code that enables playing a melody on a channel and chords on an other channel. The melody has a different rhythm than the chords.

Ionic Wind Support Team

Quite demanding aren't we.

I gave you an example, 3 notes on 2 different channels "*N1C2N0EG;".  Rhythm has no meaning to midi, the time constant does, just like in sheet music.  If your base line is playing half notes and your melody line is playing quarter notes at 4/4 time then your base line has a different rhythm.  Insert rests where needed.

playmidi$ is meant for simple midi music for your programs, it isn't a midi studio or cakewalk. 

Here is a very old program, by Jolly Roger, the start of a midi composer for playmidi$.  Perhaps you can learn something from it.  Please note however, that I didn't write it so you'll need to ask the original author any questions.


'Midi$ composer
'Creates EBASIC subroutines which play midi music when called
'Jolly Roger Sept 2004
AUTODEFINE "OFF"
DEF win:WINDOW
DEF text,notename$[12],instrumentname$[128]:STRING
DEF run:INT
DEF selectednote,notepitch[100],noteduration[100],noteinstrument[100],numberofnotes:INT
DEF notenumber,notevolume[100],noteisarest[100],noteisachord[100],n:INT
DEF playallbutton,deletebutton,insertbutton,uparrow,downarrow,leftarrow,rightarrow:INT
DEF instrumentcombobox,insertpausebutton,staffscrollbar,numberofnoteatleft:INT
DEF laststaffscrollbarposn,volumescrollbar,textbox1,textbox2,textbox3,textbox4,textbox5:INT
DEF playvisiblenotesbutton,stopmusicbutton,textbox6,temposcrollbar,chordradiobutton:INT
DEF noteradiobutton,endnote,selectednotevisible:INT
DEF left,top,width,height,usablewidth,usableheight:INT
DEF linespacing,maxnotewidth,lowertempo,uppertempo:INT
DEF xmult,ymult:FLOAT
DEF midi$pointer:POINTER
DEF midi$[10000]:ISTRING
CONST blue=0xA00000

numberofnotes=1:selectednote=0
notepitch[0]=24:noteduration[0]=12:noteinstrument[0]=0:noteisarest[0]=0:noteisachord[0]=0

midi$pointer=0
notevolume[0]=64
lowertempo=80:uppertempo=120
playallbutton=3:deletebutton=2:insertbutton=1:instrumentcombobox=4:insertpausebutton=5
staffscrollbar=6:volumescrollbar=7:textbox1=8:textbox2=9:textbox3=10:textbox4=11:textbox5=12
playvisiblenotesbutton=13:stopmusicbutton=14:textbox6=15
chordradiobutton=17:noteradiobutton=18
uparrow=0x26:downarrow=0x28:leftarrow=0x25:rightarrow=0x27
numberofnoteatleft=0:laststaffscrollbarposn=0
notename$="C","C#","D","D#","E","F","F#","G","G#","A","A#","B"

OPENWINDOW win,0, 0, 400, 300,@MAXBOX|@MAXIMIZED, 0, "MIDI$ Composer", &main
GETSIZE win,left,top,width,height
GETSCREENSIZE width,n
CLOSEWINDOW win
WAITUNTIL win=0

OPENWINDOW win,0, 0,width,height,@MINBOX|@NOAUTODRAW, 0, "MIDI$ Composer", &main
'Create file menu
BEGINMENU win
MENUTITLE "&File"
MENUITEM "Load music",0, 1
MENUITEM "Save music", 0, 2
MENUITEM "Create midi$ subroutine", 0, 3
ENDMENU
'Find usable window area
GETCLIENTSIZE win,left,top,usablewidth,usableheight
xmult=usablewidth/634.0:ymult=usableheight/411.0
linespacing=10*ymult
'Ensure linespacing is an even number
IF linespacing%2<>0 THEN linespacing=linespacing-1
maxnotewidth=FLOOR(usablewidth/16)
'Ensure maxnotewidth is an even number
IF maxnotewidth%2<>0 THEN maxnotewidth=maxnotewidth-1
       
GOSUB fillinstrumentarray
GOSUB createcontrols
GOSUB drawstaff
GOSUB drawnotes
GOSUB enableordisablecontrols

run = 1
WAITUNTIL run = 0
CLOSEWINDOW win
END



SUB main
SELECT @CLASS
  CASE @IDCLOSEWINDOW
    run = 0
  CASE @IDPAINT
    GOSUB drawnotesymbol
    GOSUB drawstaff
    GOSUB drawnotes
  CASE @IDMENUPICK
     'Click on an item in the file menu
     SELECT @MENUNUM
       CASE 1
         GOSUB loadmusic
       CASE 2
         GOSUB savemusic
       CASE 3
         GOSUB savemidi$assubroutine
     ENDSELECT
  CASE @IDHSCROLL
     GOSUB scrollbarshandler
  CASE @IDLBUTTONDN
    'Left clicked
    IF @MOUSEY<16*linespacing
       'Click on staff
       IF FLOOR(@mousex/maxnotewidth)+numberofnoteatleft<numberofnotes
          selectednote=FLOOR(@mousex/maxnotewidth)+numberofnoteatleft
          IF noteisarest[selectednote]=0
            'Note selected.Set note parameter controls
            SETSELECTED win,instrumentcombobox,noteinstrument[selectednote]
            SETSCROLLPOS win,volumescrollbar,notevolume[selectednote]
            SETSTATE win,chordradiobutton,noteisachord[selectednote]
            SETSTATE win,noteradiobutton,1-noteisachord[selectednote]
          ENDIF
          GOSUB drawnotes
          GOSUB enableordisablecontrols
       ENDIF
    ENDIF
  CASE @IDKEYDOWN
     SELECT @CODE
       CASE uparrow
         IF notepitch[selectednote]<47 & selectednotevisible
           GOSUB eraseselectednote
           notepitch[selectednote]=notepitch[selectednote]+1
           GOSUB drawnotes
         ENDIF
       CASE downarrow
         IF notepitch[selectednote]>0 & selectednotevisible
           GOSUB eraseselectednote
           notepitch[selectednote]=notepitch[selectednote]-1
           GOSUB drawnotes
         ENDIF
       CASE leftarrow
         IF noteduration[selectednote]<72 & selectednotevisible
           IF noteduration[selectednote]%3=0
             noteduration[selectednote]=(noteduration[selectednote]*4)/3
           ELSE
             noteduration[selectednote]=(noteduration[selectednote]*3)/2
           ENDIF
           GOSUB eraseselectednote
           GOSUB drawnotes
         ENDIF
       CASE rightarrow
         IF noteduration[selectednote]>2 & selectednotevisible
           IF noteduration[selectednote]%3<>0
             noteduration[selectednote]=(noteduration[selectednote]*3)/4
           ELSE
             noteduration[selectednote]=(noteduration[selectednote]*2)/3
           ENDIF
           GOSUB eraseselectednote
           GOSUB drawnotes
         ENDIF
     ENDSELECT
  CASE @IDCONTROL
    IF @CONTROLID=instrumentcombobox
        IF @NOTIFYCODE=@CBNSELCHANGE
           'User has selected new instrument
           noteinstrument[selectednote]=GETSELECTED (win,instrumentcombobox)
           'Return focus to window else won't detect keypresses
           SETFOCUS win
        ENDIF
    ENDIF
    IF @CONTROLID=playallbutton
       GOSUB createmidi$(0,numberofnotes-1)
       midi$pointer=PLAYMIDI$(midi$,TRUE)
       'Enable stop music button
       ENABLECONTROL win,stopmusicbutton,1
       'Return focus to window else won't detect keypresses
       SETFOCUS win
    ENDIF
    IF @CONTROLID=stopmusicbutton
      IF midi$pointer<>0
         STOPMIDI$ midi$pointer
      ENDIF
      'Return focus to window else won't detect keypresses
      SETFOCUS win
    ENDIF
    IF @CONTROLID=playvisiblenotesbutton
       endnote=numberofnoteatleft+15
       IF endnote>numberofnotes-1 THEN endnote=numberofnotes-1
       GOSUB createmidi$(selectednote,endnote)
       midi$pointer=PLAYMIDI$(midi$,TRUE)
       'Enable stop music button
       ENABLECONTROL win,stopmusicbutton,1
       'Return focus to window else won't detect keypresses
       SETFOCUS win
    ENDIF
    IF @CONTROLID=insertbutton | @CONTROLID=insertpausebutton
       GOSUB insertnoteorrest
       'Return focus to window else won't detect keypresses
       SETFOCUS win
    ENDIF
    IF @CONTROLID=deletebutton
       GOSUB deletenoteorrest
       'Return focus to window else won't detect keypresses
       SETFOCUS win
    ENDIF
    IF @CONTROLID=noteradiobutton |@CONTROLID=chordradiobutton
       'Set noteisachord[] to 1 if chord radio button selected,0 if it isn't
       noteisachord[selectednote]=GETSTATE(win,chordradiobutton)
       'Return focus to window else won't detect keypresses
       SETFOCUS win
    ENDIF
  ENDSELECT
RETURN
ENDSUB



SUB drawstaff
  'Draw white rectangles with black borders to erase notes
  RECT win,-1,-1,usablewidth+2,3*linespacing+2,0,0xFFFFFF
  RECT win,-1,3*linespacing,usablewidth+2,linespacing+1,0,0xFFFFFF
  RECT win,-1,4*linespacing,usablewidth+2,linespacing+1,0,0xFFFFFF
  RECT win,-1,5*linespacing,usablewidth+2,linespacing+1,0,0xFFFFFF
  RECT win,-1,6*linespacing,usablewidth+2,linespacing+1,0,0xFFFFFF
  RECT win,-1,7*linespacing,usablewidth+2,2*linespacing+1,0,0xFFFFFF

  RECT win,-1,9*linespacing,usablewidth+2,linespacing+1,0,0xFFFFFF
  RECT win,-1,10*linespacing,usablewidth+2,linespacing+1,0,0xFFFFFF
  RECT win,-1,11*linespacing,usablewidth+2,linespacing+1,0,0xFFFFFF
  RECT win,-1,12*linespacing,usablewidth+2,linespacing+1,0,0xFFFFFF
  RECT win,-1,13*linespacing,usablewidth+2,3*linespacing+1,0,0xFFFFFF
RETURN
ENDSUB



SUB createcontrols
  DEF instrument,radiobuttonheight:INT
  DEF text$:STRING
  'Create scroll bars
  CONTROL win,@SCROLLBAR,"",0,16*linespacing,usablewidth,2*linespacing,@CTSCROLLHORIZ,staffscrollbar
  SETSCROLLRANGE win,staffscrollbar,0,0
  CONTROL win,@SCROLLBAR,"",450*xmult,23*linespacing,180*xmult,2*linespacing,@CTSCROLLHORIZ,volumescrollbar
  SETSCROLLRANGE win,volumescrollbar,16,127
  SETSCROLLPOS win,volumescrollbar,64
  CONTROL win,@SCROLLBAR,"",100*xmult,38*linespacing,150*xmult,2*linespacing,@CTSCROLLHORIZ,temposcrollbar
  SETSCROLLRANGE win,temposcrollbar,16,100
  SETSCROLLPOS win,temposcrollbar,40
  'Create buttons
  CONTROL win,@BUTTON,"Add note after selected",60*xmult,19*linespacing,22*linespacing,20*ymult,0,insertbutton
  CONTROL win,@BUTTON,"Add rest after selected",60*xmult,22*linespacing,22*linespacing,20*ymult,0,insertpausebutton
  CONTROL win,@BUTTON,"Delete selected",60*xmult,25*linespacing,220*xmult,2*linespacing,0,deletebutton
  CONTROL win,@BUTTON,"Play all notes",40*xmult,29*linespacing,260*xmult,2*linespacing,0,playallbutton
  CONTROL win,@BUTTON,"Play visible notes from selected one",40*xmult,32*linespacing,260*xmult,2*linespacing,0,playvisiblenotesbutton
  CONTROL win,@BUTTON,"Stop music",40*xmult,35*linespacing,260*xmult,2*linespacing,0,stopmusicbutton
  'Create and fill drop down list with midi instrument names
  CONTROL win,@COMBOBOX,"",450*xmult,20.5*linespacing,180*xmult,20*linespacing,@CTCOMBODROPLIST|@VSCROLL,instrumentcombobox
  FOR instrument=0 TO 127
    ADDSTRING win,instrumentcombobox,instrumentname$[instrument]
  NEXT instrument
  SETSELECTED win,instrumentcombobox,noteinstrument[0]
  'Create static text boxes
  CONTROL win,@STATIC,"Name",370*xmult,31*linespacing,50*xmult,2*linespacing,0,textbox1
  SETCONTROLCOLOR win,textbox1,0,0xFFFFFF
  CONTROL win,@STATIC,"Selected note parameters",420*xmult,18*linespacing,250*xmult,2*linespacing,0,textbox2
  SETCONTROLCOLOR win,textbox2,0,0xFFFFFF
  CONTROL win,@STATIC,"Instrument",370*xmult,20.5*linespacing,80*xmult,2*linespacing,0,textbox3
  SETCONTROLCOLOR win,textbox3,0,0xFFFFFF
  CONTROL win,@STATIC,"Volume",370*xmult,23*linespacing,70*xmult,2*linespacing,0,textbox4
  SETCONTROLCOLOR win,textbox4,0,0xFFFFFF
  CONTROL win,@STATIC,"Overall tempo",5*xmult,38.2*linespacing,95*xmult,2*linespacing,0,textbox6
  SETCONTROLCOLOR win,textbox6,0,0xFFFFFF
  text$="Use up/down arrow keys to change pitch of selected note."+CHR$(13)+CHR$(10)
  text$=text$+"Use left/right arrow keys to change length of selected note."+CHR$(13)+CHR$(10)+"Click on a note to select it."
  text$=text$+"Unselected single notes are black while unselected chords are dark blue."
  CONTROL win,@EDIT,text$,340*xmult,33.5*linespacing,290*xmult,7.5*linespacing,@VSCROLL|@CTEDITMULTI,textbox5
  SETCONTROLCOLOR win,textbox5,0,0xFFFFFF
  'Create chord/note radio buttons
  radiobuttonheight=2*linespacing
  CONTROL win,@RADIOBUTTON,"Single note",370*xmult,26*linespacing,100*xmult,radiobuttonheight,@TABSTOP|@GROUP,noteradiobutton
  CONTROL win,@RADIOBUTTON,"Chord",370*xmult,26*linespacing+radiobuttonheight,100*xmult,2*linespacing,@TABSTOP,chordradiobutton
  SETSTATE win,noteradiobutton,1
RETURN
ENDSUB



SUB drawnotes
   DEF colour,notetop,noteleft,lastnote,notewidth:INT
   DEF currentnotename$:STRING
   lastnote=numberofnoteatleft+15
   IF lastnote>numberofnotes-1 THEN lastnote=numberofnotes-1
   selectednotevisible=0
   FOR notenumber=numberofnoteatleft TO lastnote
      'Set drawing colour to red if selected,black if single note and blue if chord
      IF notenumber=selectednote
         colour=255:selectednotevisible=1
      ELSE
         IF noteisachord[notenumber]
           colour=blue
         ELSE
           colour=0
         ENDIF
      ENDIF
      'Calculate y posn for top of note
      currentnotename$=notename$[notepitch[notenumber]%12]
      notetop=14.5*linespacing-3.5*linespacing*(FLOOR(notepitch[notenumber]/12))-.5*linespacing*(ASC(currentnotename$)-67)+1
      IF currentnotename$="A" | currentnotename$="A#" | currentnotename$="B"
        notetop=notetop-3.5*linespacing
      ENDIF
      notewidth=notewidthinpixels()
      noteleft=(notenumber-numberofnoteatleft)*maxnotewidth+maxnotewidth/2.0-notewidth/2
      IF noteisarest[notenumber]
         'Draw a rest
         RECT win,noteleft,notetop,notewidth,linespacing-1,colour,colour
      ELSE
         'Draw a note
         ELLIPSE win,noteleft,notetop,notewidth,linespacing-1,colour,colour
         IF LEN(currentnotename$)>1
            'Note is a sharp.Draw #
            MOVE win,.6*maxnotewidth+(notenumber-numberofnoteatleft)*maxnotewidth,notetop-15*ymult
            FRONTPEN win,colour:DRAWMODE win,@TRANSPARENT
            PRINT win,"#"
            FRONTPEN win,0:DRAWMODE win,@OPAQUE
         ENDIF
      ENDIF
   NEXT notenumber
   'Print selected note name
   IF noteisarest[selectednote]
     'Rest selected
     MOVE win,530*xmult,31*linespacing:PRINT win,"Rest"
   ELSE
     'Note selected
     currentnotename$=notename$[notepitch[selectednote]%12]
     MOVE win,530*xmult,31*linespacing:PRINT win,currentnotename$,"      "
     GOSUB drawnotesymbol
   ENDIF
RETURN
ENDSUB



SUB createmidi$(firstnote:INT,lastnote:INT)
  DEF secondnotepitch,thirdnotepitch,lastoctave,lastduration,lastinstrument,lastvolume:INT
  DEF lasttempo,duration,tempo:INT
  lastoctave=-1:lastduration=-1:lastinstrument=-1:lastvolume=-1:lasttempo=-1
  midi$="N0 "
  FOR notenumber=firstnote TO lastnote
     IF noteduration[notenumber]%3=0
        'Not a dotted note
        tempo=uppertempo:duration=(noteduration[notenumber]*2)/3
     ELSE
        'Dotted note
        tempo=lowertempo:duration=noteduration[notenumber]
     ENDIF
     IF noteisarest[notenumber]
       'Rest
         midi$=midi$+" R"+LTRIM$(STR$(duration))+" "
       IF 4+FLOOR(notepitch[notenumber]/12)<>lastoctave
         midi$=midi$+"O"+LTRIM$(STR$(4+(FLOOR(notepitch[notenumber]/12))))+" "
         lastoctave=4+FLOOR(notepitch[notenumber]/12)
       ENDIF
     ELSE
       'Note
       IF 4+FLOOR(notepitch[notenumber]/12)<>lastoctave
          'Octave for this note is different to that for last one       
          midi$=midi$+"O"+LTRIM$(STR$(4+(FLOOR(notepitch[notenumber]/12))))+" "
          lastoctave=4+FLOOR(notepitch[notenumber]/12)
       ENDIF
       IF notevolume[notenumber]<>lastvolume
          'Volume for this note is different to that for last one
         midi$=midi$+"V"+LTRIM$(STR$(notevolume[notenumber]))+" "
          lastvolume=notevolume[notenumber]
       ENDIF
       IF noteinstrument[notenumber]<>lastinstrument
          'Instrument for this note is different to that for last one
         midi$=midi$+"I"+LTRIM$(STR$(noteinstrument[notenumber]))+" "
          lastinstrument=noteinstrument[notenumber]
       ENDIF
       'If note is a chord add * to denote start of chord
       IF noteisachord[notenumber] THEN midi$=midi$+"*"
       IF tempo<>lasttempo
          midi$=midi$+" T"+LTRIM$(STR$(tempo))+" "
          lasttempo=tempo
       ENDIF
       'Add name of note to midi$
       midi$=midi$+notename$[notepitch[notenumber]%12]
       IF noteduration[notenumber]<>lastduration
         'Duration for this note is different to that for last one
         midi$=midi$+LTRIM$(STR$(duration))+" "
          lastduration=noteduration[notenumber]
       ENDIF
       IF noteisachord[notenumber]
          'Create two more notes to make chord
          secondnotepitch=notepitch[notenumber]+4
          IF 4+FLOOR(secondnotepitch/12)<>lastoctave
             midi$=midi$+"O"+LTRIM$(STR$(4+FLOOR(secondnotepitch/12)))
             lastoctave=4+FLOOR(secondnotepitch/12)
          ENDIF
          midi$=midi$+notename$[secondnotepitch%12]
          thirdnotepitch=notepitch[notenumber]+7
          IF 4+FLOOR(thirdnotepitch/12)<>lastoctave
             midi$=midi$+"O"+LTRIM$(STR$(4+FLOOR(thirdnotepitch/12)))
             lastoctave=4+FLOOR(thirdnotepitch/12)
          ENDIF
          midi$=midi$+notename$[thirdnotepitch%12]+";"
       ENDIF
     ENDIF
  NEXT notenumber
RETURN
ENDSUB



SUB savemidi$assubroutine
  'Saves the midi$ as a subroutine
  DEF midi$codeline,numberofmidi$codelines:INT
  DEF n:FLOAT
  DEF subroutinefilename,textline$:STRING
  DEF subroutinefile:FILE
  subroutinefilename=FILEREQUEST("Choose name for midi$ subroutine",win,0,"midi$ files (*.midi$.txt)|*.midi$.txt||",".midi$.txt",0,GETSTARTPATH)
  IF subroutinefilename<>""
    GOSUB createmidi$(0,numberofnotes-1)
    OPENFILE (subroutinefile,subroutinefilename,"W")
    WRITE subroutinefile,"SUB createandplaymidi$(),POINTER"
    WRITE subroutinefile,"  DEF midi$["+LTRIM$(STR$(LEN(midi$)+1))+"]:ISTRING"
    WRITE subroutinefile,"  DEF pointertomidi$:POINTER"
    n=LEN(midi$):n=n/64
    numberofmidi$codelines=CEIL(n)
    FOR midi$codeline=0 TO numberofmidi$codelines-1
      textline$="  midi$="
      IF midi$codeline<>0 THEN textline$=textline$+"midi$+"
      textline$=textline$+CHR$(34)+RTRIM$(LTRIM$(MID$(midi$,midi$codeline*64+1,64)))+CHR$(34)
      WRITE subroutinefile,RTRIM$(textline$)
    NEXT midi$codeline
    WRITE subroutinefile,"  pointertomidi$=PLAYMIDI$(midi$,TRUE)"
    WRITE subroutinefile,"RETURN pointertomidi$"
    WRITE subroutinefile,"ENDSUB"
    CLOSEFILE subroutinefile
  ENDIF
RETURN
ENDSUB



SUB savemusic
  DEF savefilename,textline$:STRING
  DEF savefile:FILE
  savefilename=FILEREQUEST("Choose name for music to save",win,0,"music files (*.music.txt)|*.music.txt||",".music.txt",0,GETSTARTPATH)
  IF savefilename<>""
     OPENFILE (savefile,savefilename,"W")
     textline$=LTRIM$(STR$(numberofnotes))+" note"
     IF numberofnotes>1 THEN textline$=textline$+"s"
     WRITE savefile,textline$
     WRITE savefile,LTRIM$(STR$(uppertempo))+" tempo"
     FOR notenumber=0 TO numberofnotes-1
        textline$=LTRIM$(STR$(notepitch[notenumber]))+","
        textline$=textline$+LTRIM$(STR$(noteduration[notenumber]))+","
        textline$=textline$+LTRIM$(STR$(notevolume[notenumber]))+","
        textline$=textline$+LTRIM$(STR$(noteinstrument[notenumber]))+","
        textline$=textline$+LTRIM$(STR$(noteisarest[notenumber]))+","
        textline$=textline$+LTRIM$(STR$(noteisachord[notenumber]))
        WRITE savefile,textline$
     NEXT notenumber
     CLOSEFILE savefile
  ENDIF
RETURN
ENDSUB



SUB loadmusic
DEF loadfilename,textline$:STRING
  DEF loadfile:FILE
  loadfilename=FILEREQUEST("Choose name for music to load",win,1,"music files (*.music.txt)|*.music.txt||","",0,GETSTARTPATH)
  IF loadfilename<>""
     OPENFILE (loadfile,loadfilename,"R")
     READ loadfile,textline$
     IF VAL(textline$)=0 | INSTR(textline$,"note")=0
       MESSAGEBOX win,"Not a valid music file","Oops"
     ELSE
       numberofnotes=VAL(textline$)
       READ loadfile,textline$
       uppertempo=VAL(textline$)
       lowertempo=(uppertempo*2)/3
       SETSCROLLPOS win,temposcrollbar,lowertempo/2
       FOR notenumber=0 TO numberofnotes-1
         READ loadfile,textline$
         notepitch[notenumber]=VAL(textline$)
         textline$=MID$(textline$,INSTR(textline$,",")+1)
         noteduration[notenumber]=VAL(textline$)
         textline$=MID$(textline$,INSTR(textline$,",")+1)
         notevolume[notenumber]=VAL(textline$)
         textline$=MID$(textline$,INSTR(textline$,",")+1)
         noteinstrument[notenumber]=VAL(textline$)
         textline$=MID$(textline$,INSTR(textline$,",")+1)
         noteisarest[notenumber]=VAL(textline$)
         textline$=MID$(textline$,INSTR(textline$,",")+1)
         noteisachord[notenumber]=VAL(textline$)
       NEXT notenumber
       selectednote=0:numberofnoteatleft=0
       IF noteisarest[0]=0
         SETSELECTED win,instrumentcombobox,noteinstrument[0]
         SETSCROLLPOS win,volumescrollbar,notevolume[0]
       ENDIF
       laststaffscrollbarposn=0
       SETSCROLLRANGE win,staffscrollbar,0,numberofnotes-1
       SETSCROLLPOS win,staffscrollbar,0
       SETSTATE win,chordradiobutton,noteisachord[0]
       SETSTATE win,noteradiobutton,1-noteisachord[0]
       'Redraw stuff
       GOSUB drawstaff
       GOSUB drawnotes
       GOSUB enableordisablecontrols
     ENDIF
     CLOSEFILE loadfile
  ENDIF
RETURN
ENDSUB



SUB scrollbarshandler
  IF @CONTROLID=staffscrollbar
    select @CODE
     case @SBTHUMBPOS:case @SBTHUMBTRACK
      setscrollpos win,staffscrollbar,getthumbpos(win,staffscrollbar)
     case @SBLINELEFT
      setscrollpos win,staffscrollbar,(getscrollpos(win,staffscrollbar)-1)
     case @SBLINERIGHT
      setscrollpos win,staffscrollbar,(getscrollpos(win,staffscrollbar)+1)
     case @SBPAGELEFT
      setscrollpos win,staffscrollbar,(getscrollpos(win,staffscrollbar)-10)
     case @SBPAGERIGHT
      setscrollpos win,staffscrollbar,(getscrollpos(win,staffscrollbar)+10)           
   endselect
    numberofnoteatleft=getscrollpos(win,staffscrollbar)
    IF numberofnoteatleft<>laststaffscrollbarposn
       'Scroll bar has moved.Redraw staff & notes
       GOSUB drawstaff
       GOSUB drawnotes
       GOSUB enableordisablecontrols
       laststaffscrollbarposn=numberofnoteatleft
    ENDIF
  ENDIF
  IF @CONTROLID=volumescrollbar
   select @CODE
      case @SBTHUMBPOS:case @SBTHUMBTRACK
         setscrollpos win,volumescrollbar,getthumbpos(win,volumescrollbar)
      case @SBLINELEFT
         setscrollpos win,volumescrollbar,(getscrollpos(win,volumescrollbar)-1)
      case @SBLINERIGHT
         setscrollpos win,volumescrollbar,(getscrollpos(win,volumescrollbar)+1)
      case @SBPAGELEFT
         setscrollpos win,volumescrollbar,(getscrollpos(win,volumescrollbar)-10)
      case @SBPAGERIGHT
         setscrollpos win,volumescrollbar,(getscrollpos(win,volumescrollbar)+10)           
   endselect
    notevolume[selectednote]=getscrollpos(win,volumescrollbar)
  ENDIF
  IF @CONTROLID=temposcrollbar
   select @CODE
      case @SBTHUMBPOS:case @SBTHUMBTRACK
         setscrollpos win,temposcrollbar,getthumbpos(win,temposcrollbar)
      case @SBLINELEFT
         setscrollpos win,temposcrollbar,(getscrollpos(win,temposcrollbar)-1)
      case @SBLINERIGHT
         setscrollpos win,temposcrollbar,(getscrollpos(win,temposcrollbar)+1)
      case @SBPAGELEFT
         setscrollpos win,temposcrollbar,(getscrollpos(win,temposcrollbar)-10)
      case @SBPAGERIGHT
         setscrollpos win,temposcrollbar,(getscrollpos(win,temposcrollbar)+10)           
   endselect
    lowertempo=2*getscrollpos(win,temposcrollbar)
    uppertempo=3*getscrollpos(win,temposcrollbar)
  ENDIF
RETURN
ENDSUB



SUB insertnoteorrest
   ENABLECONTROL win,deletebutton,1
   IF selectednote<>numberofnotes-1
      'Selected note not the last one
     'Copy note data to following note for notes after selected one
     FOR notenumber=numberofnotes-1 TO selectednote+1 STEP -1
       notevolume[notenumber+1]=notevolume[notenumber]
       notepitch[notenumber+1]=notepitch[notenumber]
       noteinstrument[notenumber+1]=noteinstrument[notenumber]
       noteduration[notenumber+1]=noteduration[notenumber]
       noteisarest[notenumber+1]=noteisarest[notenumber]
       noteisachord[notenumber+1]=noteisachord[notenumber]
     NEXT notenumber
   ENDIF
   'Create data for new note/rest
   IF @CONTROLID=insertbutton
     'Add a note
     noteisarest[selectednote+1]=0
     'Set radio buttons
     SETSTATE win,chordradiobutton,noteisachord[selectednote]
     SETSTATE win,noteradiobutton,1-noteisachord[selectednote]
   ELSE
     'Add a rest
     noteisarest[selectednote+1]=1
   ENDIF
   notevolume[selectednote+1]=notevolume[selectednote]
   notepitch[selectednote+1]=notepitch[selectednote]
   noteduration[selectednote+1]=noteduration[selectednote]
   noteinstrument[selectednote+1]=noteinstrument[selectednote]
   noteisachord[selectednote+1]=noteisachord[selectednote]
   numberofnotes=numberofnotes+1
   'Alter scrollbar range
   SETSCROLLRANGE win,staffscrollbar,0,numberofnotes-1
   selectednote=selectednote+1
   'Move scrollbar if new note off right of window
   IF getscrollpos(win,staffscrollbar)+15<selectednote
      SETSCROLLPOS(win,staffscrollbar,selectednote-15)
      numberofnoteatleft=selectednote-15
   ENDIF
   GOSUB drawstaff
   GOSUB drawnotes
   GOSUB enableordisablecontrols
RETURN
ENDSUB



SUB enableordisablecontrols
  IF selectednotevisible
    ENABLECONTROL win,insertbutton,1
    ENABLECONTROL win,insertpausebutton,1
    ENABLECONTROL win,playvisiblenotesbutton,1
  ELSE
    ENABLECONTROL win,insertbutton,0
    ENABLECONTROL win,insertpausebutton,0
    ENABLECONTROL win,playvisiblenotesbutton,0
  ENDIF
  IF selectednotevisible & numberofnotes>1
    ENABLECONTROL win,deletebutton,1
  ELSE
    ENABLECONTROL win,deletebutton,0
  ENDIF
  IF selectednotevisible & noteisarest[selectednote]=0
     'Enable note parameter controls
     ENABLECONTROL win,instrumentcombobox,1
     ENABLECONTROL win,volumescrollbar,1
     ENABLECONTROL win,noteradiobutton,1
     ENABLECONTROL win,chordradiobutton,1
  ELSE
     'Disable note parameter controls
     ENABLECONTROL win,instrumentcombobox,0
     ENABLECONTROL win,volumescrollbar,0
     ENABLECONTROL win,noteradiobutton,0
     ENABLECONTROL win,chordradiobutton,0
  ENDIF
  IF numberofnotes<17 & numberofnoteatleft=0
     ENABLECONTROL win,staffscrollbar,0
  ELSE
     ENABLECONTROL win,staffscrollbar,1
  ENDIF
RETURN
ENDSUB



SUB deletenoteorrest
     IF selectednote<>numberofnotes
        'Selected note is not the last note
        'Copy note data to previous note for notes after deleted one
        FOR notenumber=selectednote TO numberofnotes-2
           notepitch[notenumber]=notepitch[notenumber+1]
           noteduration[notenumber]=noteduration[notenumber+1]
           noteinstrument[notenumber]=noteinstrument[notenumber+1]
           notevolume[notenumber]=notevolume[notenumber+1]
           noteisarest[notenumber]=noteisarest[notenumber+1]
           noteisachord[notenumber]=noteisachord[notenumber+1]
        NEXT notenumber
     ENDIF
     numberofnotes=numberofnotes-1
     'Alter scrollbar range
     SETSCROLLRANGE win,staffscrollbar,0,numberofnotes-1
     selectednote=selectednote-1
     IF selectednote=-1 THEN selectednote=0
     'Check if deleted last note at left of window
     IF numberofnoteatleft>numberofnotes-1
        numberofnoteatleft=numberofnotes-1
     ENDIF
     'Set radio buttons
     SETSTATE win,chordradiobutton,noteisachord[selectednote]
     SETSTATE win,noteradiobutton,1-noteisachord[selectednote]
     GOSUB drawstaff
     GOSUB drawnotes
     GOSUB enableordisablecontrols   
RETURN
ENDSUB



SUB eraseselectednote
  DEF currentnotename$:STRING
  DEF notetop,left:INT
  'Calculate y posn for top of note
  currentnotename$=notename$[notepitch[selectednote]%12]
  notetop=14.5*linespacing-3.5*linespacing*(FLOOR(notepitch[selectednote]/12))-.5*linespacing*(ASC(currentnotename$)-67)+1
  IF currentnotename$="A" | currentnotename$="A#" | currentnotename$="B"
    notetop=notetop-3.5*linespacing
  ENDIF
  left=maxnotewidth*(selectednote-numberofnoteatleft)
  'Erase note/rest
  RECT win,left,notetop,maxnotewidth,linespacing,0xFFFFFF,0xFFFFFF
  IF noteisarest[selectednote]=0
     'Note is not a rest.Check if it is a sharp
     IF LEN(currentnotename$)>1
        'Note is a sharp.Erase #
        MOVE win,.6*maxnotewidth+(selectednote-numberofnoteatleft)*maxnotewidth,notetop-15*ymult
        FRONTPEN win,0xFFFFFF:DRAWMODE win,@TRANSPARENT
        PRINT win,"#"
        FRONTPEN win,0:DRAWMODE win,@OPAQUE
     ENDIF
  ENDIF
  'Redraw staff lines
  LINE win,left,3*linespacing,left+maxnotewidth,3*linespacing
  LINE win,left,4*linespacing,left+maxnotewidth,4*linespacing
  LINE win,left,5*linespacing,left+maxnotewidth,5*linespacing
  LINE win,left,6*linespacing,left+maxnotewidth,6*linespacing
  LINE win,left,7*linespacing,left+maxnotewidth,7*linespacing

  LINE win,left,9*linespacing,left+maxnotewidth,9*linespacing
  LINE win,left,10*linespacing,left+maxnotewidth,10*linespacing
  LINE win,left,11*linespacing,left+maxnotewidth,11*linespacing
  LINE win,left,12*linespacing,left+maxnotewidth,12*linespacing
  LINE win,left,13*linespacing,left+maxnotewidth,13*linespacing
RETURN
ENDSUB



SUB drawnotesymbol
  DEF noteiconx,noteicony,noteiconradius,duration,numberoflines,linenumber:INT
  noteiconx=500*xmult:noteicony=32*linespacing
  noteiconradius=4*xmult:duration=noteduration[selectednote]
  RECT win,noteiconx-5*xmult,noteicony-20*ymult,30*xmult,30*ymult,0xFFFFFF,0xFFFFFF
  IF duration<8
    CIRCLE win,noteiconx,noteicony,noteiconradius,0,0xFFFFFF
  ELSE
    CIRCLE win,noteiconx,noteicony,noteiconradius,0,0
  ENDIF
  IF duration>=4
     LINE win,noteiconx+noteiconradius-1,noteicony,noteiconx+noteiconradius-1,noteicony-20*ymult
  ENDIF
  IF duration%3<>0
     CIRCLE win,noteiconx+noteiconradius+2*xmult,noteicony+noteiconradius,xmult,0,0
  ENDIF
  IF duration>=16
     numberoflines=1
     IF duration>=32 THEN numberoflines=2
     IF duration>=64 THEN numberoflines=3
     FOR linenumber=0 TO numberoflines-1
        LINE win,noteiconx+noteiconradius,noteicony-(20-3*linenumber)*ymult,noteiconx+noteiconradius+5*xmult,noteicony-(17-3*linenumber)*ymult
     NEXT linenumber
  ENDIF
RETURN
ENDSUB



SUB notewidthinpixels(),INT
  DEF widthofnote:INT
  SELECT noteduration[notenumber]
    CASE 2:widthofnote=maxnotewidth
    CASE 3:widthofnote=maxnotewidth*31/40
    CASE 4:widthofnote=maxnotewidth*25/40
    CASE 6:widthofnote=maxnotewidth*20/40
    CASE 8:widthofnote=maxnotewidth*16/40
    CASE 12:widthofnote=maxnotewidth*13/40
    CASE 16:widthofnote=maxnotewidth*11/40
    CASE 24:widthofnote=maxnotewidth*9/40
    CASE 32:widthofnote=maxnotewidth*7/40
    CASE 48:widthofnote=maxnotewidth*5/40
    CASE 64:widthofnote=maxnotewidth*4/40
    CASE 96:widthofnote=maxnotewidth*3/40
  ENDSELECT
RETURN widthofnote
ENDSUB



SUB fillinstrumentarray
instrumentname$="Acoustic grand","Bright acoustic","Electric grand","Honky tonk","Electric piano 1","Electric piano 2"
instrumentname$[6]="Harpsichord","Clav","Celesta","Glockenspiel","Music box","Vibraphone"
instrumentname$[12]="Marimba","Xylophone","Tubular bells","Dulcima","Drawbar organ","Percussive organ"
instrumentname$[18]="Rock organ","Church organ","Reed organ","Accordion","Harmonica","Tango accordion"
instrumentname$[24]="Acoustic guitar(nylon)","Acoustic guitar(steel)","Electric guitar(jazz)","Electric guitar(clean)","Electric guitar(muted)"
instrumentname$[29]="Overdriven guitar","Distortion guitar","Guitar harmonics","Acoustic bass","Electric bass(finger)","Electric bass(pick)"
instrumentname$[35]="Fretless bass","Slap bass 1","Slap bass 2","Synth bass 1","Synth bass 2","Violin"
instrumentname$[41]="Viola","Cello","Contrabass","Tremelo strings","Pizzicato strings","Orchestral strings"
instrumentname$[47]="Timpani","String ensemble 1","String ensemble 2","Synth strings 1","Synth strings 2","Choir aahs"
instrumentname$[53]="Voice oohs","Synth voice","Orchestra hit","Trumpet","Trombone","Tuba"
instrumentname$[59]="Muted trumpet","French horn","Brass section","Synth brass 1","Synth brass 2","Soprano sax"
instrumentname$[65]="Alto sax","Tenor sax","Baritone sax","Oboe","English horn","Bassoon"
instrumentname$[71]="Clarinet","Piccolo","Flute","Recorder","Pan flute","Blown bottle"
instrumentname$[77]="Skakuhachi","Whistle","Occarina","Lead 1(square)","Lead 2(sawtooth)","Lead 3(calliope)"
instrumentname$[83]="Lead 4(chiff)","Lead 5(charang)","Lead 6(voice)","Lead 7(fifths)","Lead 8(bass&lead)","Pad 1(new age)"
instrumentname$[89]="Pad 2(warm)","Pad 3(polysynth)","Pad 4(choir)","Pad 5(bowed)","Pad 6(metallic)","Pad 7(halo)"
instrumentname$[95]="Pad 8(sweep)","FX 1(rain)","FX 2(soundtrack)","FX 3(crystal)","FX 4(atmosphere)","FX 5(brightness)"
instrumentname$[101]="FX 6(goblins)","FX 7(echoes)","FX 8(sci-fi)","Sitar","Banjo","Shamisen"
instrumentname$[107]="Koto","Kalimba","Bagpipe","Fiddle","Shanai","Tinkle bell"
instrumentname$[113]="Agogo","Steel drums","Woodblock","Taiko drum","Melodic tom","Synth drum"
instrumentname$[119]="Reverse cymbal","Guitar fret noise","Breath noise","Seashore","Bird tweet","Telephone ring"
instrumentname$[125]="Helicopter","Applause","Gun shot"
RETURN
ENDSUB


/*
The code below shows the use of the subroutines created.

Code:
AUTODEFINE "OFF"
DEF win:WINDOW
DEF run:INT
DEF music:POINTER

OPENWINDOW win,0, 0, 640, 480,@MINBOX, 0, "MIDI$ subroutine player", &main
CONTROL win,@BUTTON,"Play",100,200,100,20,0,1
CONTROL win,@BUTTON,"Stop",300,200,100,20,0,2

run = 1: WAITUNTIL run = 0: CLOSEWINDOW win: END



SUB main
SELECT @CLASS
  CASE @IDCLOSEWINDOW
    run = 0
  CASE @IDCONTROL
    IF @CONTROLID=1
       music=createandplaymidi$()
    ELSE
       STOPMIDI$ music
    ENDIF
ENDSELECT
RETURN
ENDSUB



SUB createandplaymidi$(),POINTER
  DEF midi$[97]:ISTRING
  DEF pointertomidi$:POINTER
  midi$="N0 O5 V64 I0 * T154 A2 O6C#E;O5 A4 BO6 *C2 EG;C4 C#*D2 F#A;D4 D#"
  midi$=midi$+"V127 *E2 G#B;E4 DCO5 B*A2 O6C#E;"
  pointertomidi$=PLAYMIDI$(midi$,TRUE)
RETURN pointertomidi$
ENDSUB

*/







Ionic Wind Support Team

soft4fun

Thank you !
I think it is easy for Paul Turley to add to playmidi$ the possibility to program several independent musical lines.
This is done in a java module called JFugue : each channel starts simultaneously.
Java is too complicated for me. The EBasic's PLAYMIDI$ commands are very useful and the language is perfect !
This add is valuable for me !

soft4fun

I omitted to specify that i am ready to pay for this improvement, which is very important for me.

yujinwunz

ebasic is so native in so many excellent ways, but im dissapointed :'( in music support. :'( .WAV is  bloated, and midi is difficult to use to full sound card capability. :'(. correct me if im rong.

oh well.

maybe, i can use several .COM threads to emulate different channels? :)