REM >!Maestro.!RunImage

REM Copyright 2012 Castle Technology Ltd
REM
REM Licensed under the Apache License, Version 2.0 (the "License");
REM you may not use this file except in compliance with the License.
REM You may obtain a copy of the License at
REM
REM     http://www.apache.org/licenses/LICENSE-2.0
REM
REM Unless required by applicable law or agreed to in writing, software
REM distributed under the License is distributed on an "AS IS" BASIS,
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
REM See the License for the specific language governing permissions and
REM limitations under the License.
REM

REM (c) Acorn Computers 1988

Task_h%=0

REM TRACE TO "serial:"

DIM message_buffer% 256, err_block% 204
Messages_h% = FNopen_messagefile("<Maestro$Dir>.Messages")
maestro$ = FNmessage_lookup(Messages_h%, "Maestro")

ON ERROR PROCerror:END

INITIALISED%=FNinitialise
OSCLI ("Set Maestro$Running Yes")
ON ERROR PROCerror

REM get any filename passed as an argument
SYS "OS_GetEnv" TO EnvStr$
IF INSTR(EnvStr$," -quit ") THEN
  I%=INSTR(EnvStr$,"""")
  I%=INSTR(EnvStr$,"""",I%+1)
  REPEATI%+=1:UNTILMID$(EnvStr$,I%,1)<>" "
  f$=MID$(EnvStr$,I%)
  IF f$ <> "" THEN
    IF FNvalid_number_of_gates (f$) THEN
      PROCload_music(f$)
    ELSE
      ret_code% = FNCheckOK (FNmessage_lookupN(Messages_h%, "TooBig", f$, "", "", ""), 1)
    ENDIF
  ENDIF
ENDIF

REPEAT
  IF PLAYING% THEN PROCCheckQ
  SYS "Wimp_Poll",(NOT(PLAYING% OR SCORING% OR AwaitingAck% OR SCROLLING%) AND 1),Block% TO reason%
  REM Recheck since poll might have stayed away for a while
  IF PLAYING% THEN PROCCheckQ 
  CASE reason% OF
    WHEN 0:REM Null Event
      IF AwaitingAck% THEN
        IF FNCheckOKTag("BadData",3) THEN
          AwaitingAck%=FALSE
          CHANGED%=wasCHANGED%
        ENDIF
      ENDIF
      IF SCORING% THEN PROCsymbol_pointer
      IF SCROLLING% THEN PROCCheckScroll
    WHEN 1:REM Redraw Window
      PROCredraw_window_request
    WHEN 2:REM Open Window      
      PROCOpenWindow(Block%)
    WHEN 3:REM Close Window
      PROCCloseWindow(Block%!0)
    WHEN 4:REM Pointer Leaving Window
      IF Block%!0=ScoreWind_h% THEN
        PROCrelease
        wasSCORING%=SCORING%
        SCORING%=FALSE
      ENDIF
    WHEN 5:REM Pointer Entering Window
      IF Block%!0=ScoreWind_h% THEN
        SCORING%=(wasSCORING% AND NOT stopSCORING%)=TRUE
      ENDIF
    WHEN 6:REM Mouse Click
      PROCmouse_button_click(Block%!12,Block%!16,Block%!8,Block%!0,Block%!4)
    WHEN 7:REM User Drag Box
      PROCUserDragBox
    WHEN 8:REM Key Pressed
      PROCKeyPressed(Block%!0,Block%!24)
    WHEN 9:REM Menu Selection
      PROCMenuSelect(Block%!0,Block%!4,Block%!8)
    WHEN 10:REM Scroll Request
      PROCScrollReq(Block%!32,Block%!36)
    WHEN 17,18:REM User Message
      PROCreceive
  ENDCASE
UNTIL FALSE
END



DEF PROCCheckQ
  B1%=B2%: B2%=BEAT
  IFB2%<B1% PROCplay_bar
ENDPROC

DEF PROCreceive
LOCAL task%,ref%
LOCAL f$,wx%,wy%
task%=Block%!4
ref%=Block%!8
REM Ignore messages from this task
IF task%=Task_h% THEN ENDPROC
REM Process Messages
CASE Block%!16 OF
  WHEN 0:REM Quit
    PROCterminate
  WHEN 1:REM DataSave
    IF NOT SAVING% THEN
      doing_scrap_load%=TRUE
      Block%!0=64
      Block%!12=ref%  
      Block%!16=2                   :REM DataSaveAck
      Block%!36=-1                  :REM Unsafe data
      Block%!40=&AF1                :REM Filetype
      $(Block%+44)="<Wimp$Scrap>"   :REM Path
      ?(Block%+44+13)=0
      SYS "Wimp_SendMessage",17,Block%,task%
    ENDIF
  WHEN 8:REM PreQuit
    IF CHANGED% THEN
      IF (Block%!20 AND %1)=0 THEN
        shutdown%=TRUE              :REM Flag for restarting quit
      ELSE
        shutdown%=FALSE
      ENDIF
      REM Acknowledge PreQuit message
      Block%!0=20
      Block%!12=ref%
      Block%!16=0
      quit_sender%=task%
      SYS "Wimp_SendMessage",19,Block%,task%
      PROCOpenDiscardCancelBox
    ELSE
      PROCterminate
    ENDIF
  WHEN 2:REM DataSaveAck
    IF SAVING% THEN
      wasCHANGED%=CHANGED%
      PROCsave_music(FNGetStr(Block%+44))
      SAVING%=FALSE
      REM Send DataLoad message
      Block%!0=256
      Block%!12=my_ref%
      Block%!16=3                   :REM DataLoad
      Block%!36=-1                  :REM Unsafe data
      SYS "Wimp_SendMessage",17,Block%,task%
      AwaitingAck%=TRUE
      SYS "Wimp_CreateMenu",,-1
    ENDIF
  WHEN 3:REM DataLoad
    REM Check filetype
    f$=FNGetStr(Block%+44)
    IF FNvalid_file_type(f$) THEN   
      PROCDataLoadAck(f$,ref%,task%)   
    ELSE
      ret_code%=FNCheckOK(FNmessage_lookupN(Messages_h%,"NotMusic",f$,"","",""),1)
    ENDIF
  WHEN 4:REM DataLoadAck
    AwaitingAck%=FALSE
  WHEN 5:REM DataOpen
    f$=FNGetStr(Block%+44)
    IF Block%!40=MusicFileType% THEN PROCDataLoadAck(f$,ref%,task%)
  WHEN 10:REM SaveDesktop
    desktop_save_handle%=Block%!20
    SYS "OS_ReadVarVal","Maestro$Dir",Block%,256,0,0 TO ,,len%
    Block%?len%=13
    BPUT #desktop_save_handle%,"Run "+$Block%
  WHEN 11:REM DeviceClaim
    IF Block%!20=7 THEN
      f$=FNmessage_lookup(Messages_h%,"SSIU")
      Block%!0=(32+LEN(f$)) AND NOT(3)
      Block%!12=0
      Block%!16=12                  :REM DeviceInUse
      $(Block%+28)=f$+CHR$(0)       :REM Text
      SYS "Wimp_SendMessage",17,Block%,Block%!4
    ENDIF
  WHEN &502:REM HelpRequest
    PROCHelp(Block%!32,Block%!36,ref%,task%)
  WHEN &400C0:REM Menu Warning
    wx%=Block%!24
    wy%=Block%!28 
    CASE Block%!20 OF
      WHEN Bar_h%:       
        PROCgoto_init
        SYS "Wimp_CreateSubMenu",,Bar_h%,wx%,wy%
      WHEN Print_h%:
        PROCprint_init
        SYS "Wimp_CreateSubMenu",,Print_h%,wx%,wy%
    ENDCASE
  WHEN &400C1:REM ModeChange
    PROCgetmodeinfo(FALSE)
ENDCASE
ENDPROC

DEF PROCDataLoadAck (f$,ref%,task%)
IF FNvalid_number_of_gates(f$) THEN
  IF CHANGED% THEN
    load_pending%=TRUE
    pending_filename$=f$
    PROCOpenDiscardCancelBox
  ELSE
    PROCload_music(f$)
  ENDIF
  REM Acknowledge message
  Block%!0=256
  Block%!12=ref%
  Block%!16=4    :REM DataLoadAck
  SYS "Wimp_SendMessage",17,Block%,task% 
  IF doing_scrap_load% THEN doing_scrap_load%=FALSE
ELSE
  ret_code%=FNCheckOK(FNmessage_lookupN(Messages_h%,"TooBig",f$,"","",""),1)
ENDIF
ENDPROC

DEF FNvalid_number_of_gates (filename$)

  LOCAL fhandle%,M$,loop%,ret%,gates%, bomb%
  ret%=TRUE
  M$=""
  bomb%=FALSE
REM  SYS "OS_Find", 79, filename$ TO fhandle%
  fhandle% = OPENIN (filename$)
  FOR loop% = 1 TO 8
    IF NOT EOF#fhandle% THEN M$ += CHR$(BGET#fhandle%) ELSE CLOSE#fhandle%:=TRUE : REM This is an invalid file, load_music picks it up
  NEXT loop%
  IF M$ = "Maestro"+CHR$(10) THEN
    IF BGET#fhandle% = 2 THEN
      REPEAT
        CASE BGET#fhandle% OF
          WHEN 1
            INPUT#fhandle%, gates%
            IF gates% > Max_Gate% THEN
              ret% = FALSE
            ENDIF
            bomb%=TRUE : REM Stop here, we've got what we want

          WHEN 2
            FOR loop%=1 TO 3
              INPUT#fhandle%,gates%
            NEXT loop%

          WHEN 3
            FOR loop%=1 TO 17
              INPUT#fhandle%,gates%
            NEXT loop%

          WHEN 4, 5
            FOR loop%=1 TO 9
              INPUT#fhandle%,gates%
            NEXT loop%

          WHEN 6
            FOR loop%=1 TO 2
              INPUT#fhandle%,gates%
            NEXT loop%

          WHEN 7
            INPUT#fhandle%, M$

          WHEN 8
            FOR loop%=1 TO 8
              INPUT#fhandle%,M$
            NEXT loop%

          WHEN 9
            FOR loop%=1 TO 8
              INPUT#fhandle%,gates%
            NEXT loop%

          OTHERWISE : REM !!
        ENDCASE
      UNTIL bomb% OR EOF#fhandle%
    ENDIF
  ENDIF
  CLOSE#fhandle%
=ret%



DEF FNvalid_file_type (filename$)

  LOCAL laddr%, type%, ret_code%

  IF filename$ <> "" THEN
    SYS"OS_File",5,filename$ TO type%,,laddr%
  ENDIF

  CASE type% OF
    WHEN 1
      IF ((laddr%>>20)AND&FFF)=&FFF THEN
        IF (laddr%>>8 AND &FFF) = MusicFileType% THEN
          =TRUE
        ELSE
          =FALSE
        ENDIF
      ENDIF
    WHEN 2
      =FALSE
  ENDCASE

=FALSE

DEF PROCHelp (window%,icon%,ref%,task%)
LOCAL text$
CASE window% OF
  WHEN -2:
    text$=FNmessage_lookup(Messages_h%,"IconHelp")
  WHEN ScoreWind_h%:
    IF SCORING% AND SCRIBE%(drawn%) THEN
      text$=FNmessage_lookup(Messages_h%,"ScoreHelp0")
    ELSE
      text$=FNmessage_lookup(Messages_h%,"ScoreHelp1")
    ENDIF
  WHEN NotesPane_h%:
    IF icon%>0 AND icon%<17 THEN
      text$=FNmessage_lookup(Messages_h%,"NH"+STR$icon%)
      text$=FNmessage_lookupN(Messages_h%,"SelectObject",text$,"","","")
    ENDIF
  WHEN SharpsPane_h%:
    CASE icon% OF
      WHEN 2,3,4,5,6,7,8,9,10,11,12,17,19,23:
        text$=FNmessage_lookup(Messages_h%,"AH"+STR$icon%)
        text$=FNmessage_lookupN(Messages_h%,"SelectObject",text$,"","","")
      WHEN 14,16,18,20:
        text$=FNmessage_lookup(Messages_h%,"AH"+STR$icon%)
      WHEN 13:REM Barlines
        text$=FNmessage_lookup(Messages_h%,"B"+STR$(BarlineType%))
        text$=FNmessage_lookupN(Messages_h%,"SelectObject",text$,"","","")
      WHEN 15:REM Clefs
        text$=FNmessage_lookup(Messages_h%,"C"+STR$(ClefType%))
        text$=FNmessage_lookupN(Messages_h%,"SelectObject",text$,"","","")
    ENDCASE 
  WHEN InstrWind_h%:
    CASE icon% OF
      WHEN 20,21,22,23,24,25,26,27:REM Instruments popup
        text$=FNmessage_lookup(Messages_h%,"IH0")
      WHEN 36,37,38,39,40,41,42,43:REM Volumes (down)
        text$=FNmessage_lookup(Messages_h%,"IH1")
      WHEN 44,45,46,47,48,49,50,51:REM Stereos
        text$=FNmessage_lookup(Messages_h%,"IH2")
      WHEN 62,63,64:text$=FNmessage_lookup(Messages_h%,"IH"+STR$icon%)
      OTHERWISE:text$=FNmessage_lookup(Messages_h%,"IH")
    ENDCASE
  WHEN StaveWind_h%:
    CASE icon% OF
      WHEN 2,3,4,5:text$=FNmessage_lookup(Messages_h%,"SH1")
      WHEN 6,7,8:text$=FNmessage_lookup(Messages_h%,"SH"+STR$icon%)
      OTHERWISE:text$=FNmessage_lookup(Messages_h%,"SH")
    ENDCASE
  WHEN Save_h%:
    CASE icon% OF
      WHEN 0,1,2:text$=FNmessage_lookup(Messages_h%,"SVH"+STR$icon%)
      OTHERWISE:text$=FNmessage_lookup(Messages_h%,"SVH")
    ENDCASE 
  WHEN QuitQuery_h%:
    CASE icon% OF
      WHEN 0,2:text$=FNmessage_lookup(Messages_h%,"QH"+STR$icon%)
      OTHERWISE:text$=FNmessage_lookup(Messages_h%,"QH")
    ENDCASE
  WHEN ClearQuery_h%:
    CASE icon% OF
      WHEN 0,2,3:text$=FNmessage_lookup(Messages_h%,"CH"+STR$icon%)
      OTHERWISE:text$=FNmessage_lookup(Messages_h%,"CH")
     ENDCASE
  WHEN Print_h%:
    CASE icon% OF
      WHEN 1,2:text$=FNmessage_lookup(Messages_h%,"PH"+STR$icon%)
      OTHERWISE:text$=FNmessage_lookup(Messages_h%,"PH")
    ENDCASE
  WHEN TimeSig_h%:
    CASE icon% OF
      WHEN 2,3,4,5,6:text$=FNmessage_lookup(Messages_h%,"TS"+STR$icon%)
      OTHERWISE:text$=FNmessage_lookup(Messages_h%,"TS")
    ENDCASE 
  OTHERWISE:
    SYS "Wimp_GetMenuState",1,Block%,window%,icon%
    CASE CurrentMenu% OF
      WHEN MainMenu%:
        IF Block%!0>-1 THEN
          text$=STR$(Block%!0)
          IF Block%!4>-1 THEN
            text$=text$+"."+STR$(Block%!4)
            IF Block%!8>-1 THEN text$=text$+".0"
          ENDIF
        ENDIF
        IF text$<>"" THEN text$=FNmessage_lookup(Messages_h%,"MH"+text$)
      WHEN IconMenu%:
        IF Block%!0>-1 THEN
          text$=FNmessage_lookup(Messages_h%,"ICH"+STR$(Block%!0))
        ENDIF
      OTHERWISE:text$=""
    ENDCASE
ENDCASE
REM If HelpText found, send HelpReply message
IF text$<>"" THEN
  Block%!0=(((20+LEN(text$)+1) DIV 4)*4)+4
  Block%!12=ref%
  Block%!16=&503
  $(Block%+20)=text$+CHR$(0)
  SYS "Wimp_SendMessage",17,Block%,task%
ENDIF
ENDPROC

DEF PROCredraw_window_request
LOCAL R%
SYS "Wimp_RedrawWindow",,Window%+handle% TO R%
WHILE R%
  IF Window%!handle% = ScoreWind_h% PROCdraw_staves:IF PLAYING% PROCCheckQ
REM try to avoid interruptions in the music
  SYS "Wimp_GetRectangle",,Window%+handle% TO R%
  ENDWHILE
ENDPROC

DEF PROCmouse_button_click (window%,icon%,button%,mx%,my%)
IF (window%<>QuitQuery_h%) AND shutdown% THEN shutdown%=FALSE
IF button%=2 THEN
  PROCStopScoring
  IF window%=-2 THEN
    SYS "Wimp_CreateMenu",,IconMenu%,mx%-64,96+44*3
    CurrentMenu%=IconMenu%
  ELSE
    IF window%=ScoreWind_h% THEN
      SYS "Wimp_CreateMenu",,MainMenu%,mx%-64,my%
      CurrentMenu%=MainMenu% 
    ENDIF
  ENDIF
ELSE
  CASE window% OF
    WHEN -2:REM Iconbar click
      IF FirstOpen% THEN
        PROCscore_init
      ELSE
        Block%!0=ScoreWind_h%
        SYS "Wimp_GetWindowState",,Block%
        Block%!28=-1
        PROCOpenWindow(Block%)
        ScoreClosed%=FALSE
        IF PLAYING% THEN PROCCheckScroll
      ENDIF      
    WHEN QuitQuery_h%:
      REM This window is used in a couple of circumstances:
      REM 1. when we want to quit
      REM 2. when we're about to overwrite a bit of music with a new piece
      IF icon%=2 THEN
        IF load_pending% THEN
          PROCload_music(pending_filename$)
        ELSE
          IF shutdown% THEN
            SYS "Wimp_GetCaretPosition",,Block%
            REM Restart PreQuit, send CTRL-SHIFT-F12
            Block%!24=&1FC                 
            SYS "Wimp_SendMessage",8,Block%,quit_sender%
          ENDIF
          PROCterminate
        ENDIF
        SYS "Wimp_CreateMenu",,-1
      ELSE
        REM We've decided not to quit
        IF shutdown% THEN shutdown%=FALSE
        REM Cancel any impending load 
        IF load_pending% THEN load_pending%=FALSE 
        SYS "Wimp_CreateMenu",,-1
      ENDIF
    WHEN ClearQuery_h%:
      CASE icon% OF
        WHEN 2:REM Discard
          PROCClearAllMusic
          Block%!0=ScoreWind_h%
          SYS "Wimp_GetWindowState",,Block%
          Block%!20=0
          LHBAR%=0
          SYS "Wimp_OpenWindow",,Block%
          SYS "Wimp_CreateMenu",,-1
        WHEN 0:REM Save
          SYS "Wimp_CreateMenu",,Save_h%,mx%,my%
        WHEN 3:REM Cancel
          SYS "Wimp_CreateMenu",,-1
      ENDCASE
    WHEN Print_h%:
      PROCprint_click(icon%,button%)
    WHEN NotesPane_h%,SharpsPane_h%,ScoreWind_h%:
      PROCscore_click(window%,icon%,button%)
    WHEN InstrWind_h%:
      PROCinstruments_click(icon%,mx%,button%)
    WHEN StaveWind_h%:
      PROCstaves_click(icon%,button%)
    WHEN Save_h%:
      PROCStopScoring
      IF icon%=0 THEN
        PROCsave_music(FNwimp_geticontext(Save_h%,1))
        PROCCloseWindow(window%)
        SYS "Wimp_CreateMenu",,-1
      ENDIF
      IF icon%=2 THEN
        LOCAL x%,y%                          : REM dragging icon
        Window%!handle%=window%
        SYS "Wimp_GetWindowState",,Window%+handle%
        ysize%=Window%!y1%-Window%!y0%
        x%=Window%!x0%
        y%=Window%!y0%
        !Window%=window%
        Window%!4=icon%
        SYS "Wimp_GetIconState",,Window% : REM returns icon box in right place for drag box
        Window%!8  += x%
        Window%!12 += y% + ysize%
        Window%!16 += x%
        Window%!20 += y% + ysize%
    REM get size idan appropriate part of block: parent box=screen boundary
        Window%!24 = 0
        Window%!28 = 0
        Window%!32 = S_Width%
        Window%!36 = S_Height%
        !Window%=0
        Window%!4=5: REM fixed size drag box
        SAVING%=TRUE
        SYS "OS_Byte", 161, &1C TO ,,icon%
        IF icon% AND 2 THEN
           SYS "XDragASprite_Start", &C5, 1, "file_af1", Window% + 8 
        ELSE
           SYS "Wimp_DragBox",,Window%
        ENDIF
      ENDIF
    WHEN TimeSig_h%:
      PROCtimesig_click(icon%,button%)
    WHEN Clef_h%:
      CASE icon% OF
        WHEN 0:REM Treble clef
          PROCwimp_seticonval(SharpsPane_h%,15,"Sstreble")
          ClefType%=icon%  
        WHEN 1:REM Bass clef
          PROCwimp_seticonval(SharpsPane_h%,15,"Ssbass")
          ClefType%=icon% 
      ENDCASE
      IF FNwimp_geticonstate(SharpsPane_h%,15,2) THEN PROCattach_clef
      IF button%=4 THEN PROCCloseWindow(Clef_h%)      
    WHEN Barline_h%:
      CASE icon% OF
        WHEN 0:REM Single barline
          PROCwimp_seticonval(SharpsPane_h%,13,"Ssbarline")
          BarlineType%=icon%
        WHEN 1:REM Double barline
          PROCwimp_seticonval(SharpsPane_h%,13,"Ssdbar")
          BarlineType%=icon% 
      ENDCASE
      IF FNwimp_geticonstate(SharpsPane_h%,13,2) THEN PROCattach_barline
      IF button%=4 THEN PROCCloseWindow(Barline_h%)      
  ENDCASE
ENDIF
ENDPROC  

REM // Instruments //

DEF PROCinstruments_init (x%,y%)
LOCAL i%,c%,text$
REM Create Voices menu
PROCCheckInstalledVoices
FOR i%=0 TO NVoices%-1
  PROCwimp_setmenutext(VoiceMenu%,i%,LEFT$(Voice$(i%+1),31))  
NEXT
VoiceMenu%!(28+(24*(NVoices%-1)))=128
REM Set volume/stereo fields
FOR c%=0 TO 7
  text$=FNmessage_lookup(Messages_h%,"V"+STR$(Volumes%(c%)))
  PROCwimp_seticontext(InstrWind_h%,c%+28,LEFT$(text$,3))
  PROCinstruments_slider(c%+44,Stereo_Position%(c%))
NEXT
PROCinstruments_fill(OutputType%)
PROCwimp_opendialogue(InstrWind_h%,ScoreWind_h%,1)
SYS "Wimp_SetCaretPosition",InstrWind_h%,-1
ENDPROC

DEF PROCinstruments_fill (type%)
LOCAL text$,c%
REM Set voice/MIDI channel fields
text$="OT"+STR$(type%)
PROCwimp_seticontext(InstrWind_h%,61,FNmessage_lookup(Messages_h%,text$))
FOR c%=0 TO 7
  CASE type% OF
    WHEN 0:REM Internal sound system
      text$=LEFT$(Voice$(Instrument%(c%,1)),20)
    WHEN 1:REM MIDI device
      text$=FNmessage_lookupN(Messages_h%,"MIDIvoice",STR$(MIDIChannel%(c%)),"","","")
  ENDCASE
  PROCwimp_seticontext(InstrWind_h%,c%+12,text$)
NEXT
REM Set Output Menu options
PROCwimp_setmenustate(OutputMenu%,1,22,NOT MIDIpresent%)
PROCwimp_radiotick(OutputMenu%,type%)
ENDPROC

DEF PROCinstruments_click (icon%,mx%,button%)
LOCAL c%,v%,text$,inc%,wx%,ix%
PROCStopScoring
PROCChangedScore
REM Voices
IF icon%>19 AND icon%<28 THEN
  REM Set tick in VoiceMenu/ChannelMenu
  PopUpIcon%=icon%-8
  CASE FNwimp_getmenustate(OutputMenu%,0,0) OF
    WHEN TRUE:REM Internal sound system
      text$=FNwimp_geticontext(InstrWind_h%,icon%-8)
      FOR i%=0 TO NVoices%-1
        IF FNwimp_getmenutext(VoiceMenu%,i%)=text$ THEN
          PROCwimp_radiotick(VoiceMenu%,i%)
        ENDIF
      NEXT
      CurrentMenu%=FNwimp_popup(InstrWind_h%,VoiceMenu%,icon%)
    WHEN FALSE:REM MIDI
      text$=FNwimp_geticontext(InstrWind_h%,icon%-8)
      text$=RIGHT$(text$,2)
      IF LEFT$(text$,1)=" " THEN text$=RIGHT$(text$,1)
      PROCwimp_radiotick(ChannelMenu%,VAL(text$)-1)
      CurrentMenu%=FNwimp_popup(InstrWind_h%,ChannelMenu%,icon%)
  ENDCASE  
ENDIF
REM Volumes
IF icon%>35 AND icon%<44 THEN
  REM Set tick in VolumeMenu
  v%=FNinstruments_getvol(FNwimp_geticontext(InstrWind_h%,icon%-8))
  PROCwimp_radiotick(VolumeMenu%,v%)
  PopUpIcon%=icon%-8   
  CurrentMenu%=FNwimp_popup(InstrWind_h%,VolumeMenu%,icon%)
ENDIF
REM Stereo positions
IF icon%>43 AND icon%<52 THEN
  Block%!0=InstrWind_h%
  SYS "Wimp_GetWindowState",,Block%
  wx%=Block%!4
  Block%!4=44
  SYS "Wimp_GetIconState",,Block%
  ix%=Block%!8
  PROCinstruments_slider(icon%,(mx%-wx%-ix%) DIV 24)
ENDIF
REM Output PopUp
IF icon%=62 THEN
  CurrentMenu%=FNwimp_popup(InstrWind_h%,OutputMenu%,icon%)
ENDIF
REM Cancel button
IF icon%=63 AND button%=4 THEN PROCwimp_closedialogue(InstrWind_h%,ScoreWind_h%)
REM Set button
IF icon%=64 THEN
  FOR c%=0 TO 7
    REM Voices/MIDI channels
    text$=FNwimp_geticontext(InstrWind_h%,c%+12)
    CASE FNwimp_getmenustate(OutputMenu%,0,0) OF
      WHEN TRUE:REM Internal sound system
        FOR i%=1 TO NVoices%
          IF text$=Voice$(i%) THEN v%=i%
        NEXT
        PROCAttachVoice(c%,v%)
        OutputType%=0
      WHEN FALSE:REM MIDI device
        text$=RIGHT$(text$,2)
        IF LEFT$(text$,1)=" " THEN text$=RIGHT$(text$,1)
        MIDIChannel%(c%)=VAL(text$)
        OutputType%=1
    ENDCASE
    REM Set Volumes
    text$=FNwimp_geticontext(InstrWind_h%,c%+28)
    Volumes%(c%)=FNinstruments_getvol(text$)
    REM Set Stereo positions
    Block%!4=c%+44
    SYS "Wimp_GetIconState",,Block%
    ix%=Block%!8
    Block%!4=c%+52
    SYS "Wimp_GetIconState",,Block%
    mx%=Block%!8+12
    Stereo_Position%(c%)=(mx%-ix%) DIV 24
    SYS "Sound_Stereo",c%+1,Stereo%(Stereo_Position%(c%))
  NEXT 
  IF button%=4 THEN PROCwimp_closedialogue(InstrWind_h%,ScoreWind_h%)
ENDIF
ENDPROC

DEF FNinstruments_keypress (key%)
LOCAL used%
used%=TRUE
CASE key% OF
  WHEN &1B:REM Escape
    PROCinstruments_click(63,0,4)  
  WHEN &0D:REM Enter
    PROCinstruments_click(64,0,4) 
  WHEN &1A2:REM Ctrl-F2
    PROCinstruments_click(63,0,4)
  OTHERWISE:used%=FALSE
ENDCASE
=used%

DEF FNinstruments_getvol (s$)
LOCAL i%,v%
FOR i%=0 TO 7
  IF s$=FNmessage_lookup(Messages_h%,"V"+STR$(i%)) THEN v%=i%
NEXT
=v%

DEF PROCinstruments_slider (icon%,pos%)
REM We want to work on pre-RISC OS 3.5, so can't use Wimp_ResizeIcon
REM to implement an 8-point slider. Delete and re-create icons instead
LOCAL i%,x0%,x1%,y0%,y1%
Block%!0=InstrWind_h%
Block%!4=icon%
SYS "Wimp_GetIconState",,Block%
x0%=Block%!8  
y0%=Block%!12
x1%=Block%!16
y1%=Block%!20
Block%!4=icon%+8
SYS "Wimp_GetIconState",,Block%
SYS "Wimp_DeleteIcon",,Block%
FOR i%=8 TO 40 STEP 4
  Block%!i%=Block%!(i%+4)
NEXT
Block%!4=x0%+4+(pos%*22)
Block%!12=x0%+4+(pos%*22)+24
SYS "Wimp_CreateIcon",icon%+8,Block%
SYS "Wimp_ForceRedraw",Block%!0,x0%,y0%,x1%,y1%
ENDPROC

REM // Staves //

DEF PROCstaves_init (x%,y%)
LOCAL i%
FOR i%=2 TO 5
  PROCwimp_seticonstate(StaveWind_h%,i%,21,FALSE)
NEXT
PROCwimp_seticonstate(StaveWind_h%,2+STAVE%,21,TRUE)
IF PERC% THEN
  PROCwimp_seticonstate(StaveWind_h%,6,21,TRUE)
ELSE
  PROCwimp_seticonstate(StaveWind_h%,6,21,FALSE)
ENDIF
PROCwimp_opendialogue(StaveWind_h%,ScoreWind_h%,1)
SYS "Wimp_SetCaretPosition",StaveWind_h%,-1
ENDPROC

DEF PROCstaves_click (icon%,button%)
CASE icon% OF
  WHEN 7:REM Cancel
    IF button%=4 THEN PROCwimp_closedialogue(StaveWind_h%,ScoreWind_h%)
  WHEN 8:REM Set
    FOR i%=2 TO 5
      IF FNwimp_geticonstate(StaveWind_h%,i%,21) THEN
        STAVE%=i%-2
      ENDIF
    NEXT
    IF FNwimp_geticonstate(StaveWind_h%,6,21) THEN
      PERC%=1
    ELSE
      PERC%=0
    ENDIF
    SYS "Hourglass_On"
    PROCposition_staves
    PROCSetExtent(S_Width%)
    PROCscore_init
    PROCSetDefaultChannels
    PROCstart_music
    PROCset_score(0)
    PROCSetupBarStarts(0)
    SYS "Hourglass_Off"   
    PROCChangedScore
    IF button%=4 THEN PROCwimp_closedialogue(StaveWind_h%,ScoreWind_h%)
ENDCASE
ENDPROC

DEF FNstaves_keypress (key%)
LOCAL used%
used%=TRUE
CASE key% OF
  WHEN &1B:REM Escape
    PROCstaves_click(7,4)  
  WHEN &0D:REM Enter
    PROCstaves_click(8,4) 
  WHEN &1A2:REM Ctrl-F2
    PROCstaves_click(7,4) 
  OTHERWISE:used%=FALSE
ENDCASE
=used%

REM // TimeSig //

DEF PROCtimesig_init (icon%)
LOCAL n%,b%
n%=TIME_SIG%(0)+1
b%=2^(TIME_SIG%(1)-1)
PROCwimp_seticontext(TimeSig_h%,0,STR$(n%))
PROCwimp_seticontext(TimeSig_h%,1,STR$(b%))
CurrentMenu%=FNwimp_popup(SharpsPane_h%,TimeSig_h%,icon%)
ENDPROC

DEF PROCtimesig_click (icon%,button%)
REM Time signature held in TIME_SIG%() as (n+1) / 2^(d-1)
REM To edit, we have to convert to the following range:
REM n = 2-16; b = 2,4,8,16
LOCAL n%,b%,v%
n%=VAL(FNwimp_geticontext(TimeSig_h%,0))-1
b%=SQR(VAL(FNwimp_geticontext(TimeSig_h%,1))+1)+1 
CASE icon% OF
  WHEN 2:REM Decrement n
    IF button%=4 AND n%>1 THEN
      v%=1+(n%+13) MOD 15
      PROCwimp_seticontext(TimeSig_h%,0,STR$(v%+1))
    ENDIF
    IF button%=1 AND n%<15 THEN
      v%=1+(n%+15) MOD 15
      PROCwimp_seticontext(TimeSig_h%,0,STR$(v%+1))
    ENDIF
  WHEN 3:REM Increment n
    IF button%=4 AND n%<15 THEN
      v%=1+(n%+15) MOD 15
      PROCwimp_seticontext(TimeSig_h%,0,STR$(v%+1))
    ENDIF
    IF button%=1 AND n%>1 THEN
      v%=1+(n%+13) MOD 15
      PROCwimp_seticontext(TimeSig_h%,0,STR$(v%+1))
    ENDIF   
  WHEN 4:REM Decrement b
    IF button%=4 AND b%>2 THEN
      v%=(b%+1) MOD 4+2
      PROCwimp_seticontext(TimeSig_h%,1,STR$(2^(v%-1)))
    ENDIF
    IF button%=1 AND b%<5 THEN
      v%=(b%-1) MOD 4+2
      PROCwimp_seticontext(TimeSig_h%,1,STR$(2^(v%-1)))
    ENDIF
  WHEN 5:REM Increment b
    IF button%=4 AND b%<5 THEN
      v%=(b%-1) MOD 4+2
      PROCwimp_seticontext(TimeSig_h%,1,STR$(2^(v%-1)))
    ENDIF
    IF button%=1 AND b%>2 THEN
      v%=(b%+1) MOD 4+2
      PROCwimp_seticontext(TimeSig_h%,1,STR$(2^(v%-1)))
    ENDIF   
  WHEN 6:REM Select a new time sig
    PROCSetTimeSig(n%,b%)
    IF button%=4 THEN PROCCloseWindow(TimeSig_h%)      
ENDCASE
ENDPROC

DEF PROCattach_clef
CASE ClefType% OF
  WHEN 0:REM Treble
    PROCattach(clef%,%100)
  WHEN 1:REM Bass
    PROCattach(clef%+3,%100)
ENDCASE   
ENDPROC  

DEF PROCattach_barline
CASE BarlineType% OF
  WHEN 0:REM Single
    PROCattach(bar%,%10010000)
  WHEN 1:REM Double
    PROCattach(bar%+1,%10010000)
ENDCASE   
ENDPROC 

REM // Tool icons //

DEF PROCtoolicon_click (window%,icon%)
PROCrelease
wasSCORING%=TRUE
stopSCORING%=FALSE
PROCtoolicon_clear
PROCwimp_seticonstate(window%,icon%,2,TRUE)
PROCwimp_seticonstate(window%,icon%,5,TRUE)
SelW%=window%
SelI%=icon%
ENDPROC

DEF PROCtoolicon_clear
IF SelI%>-1 THEN
  PROCwimp_seticonstate(SelW%,SelI%,2,FALSE)
  PROCwimp_seticonstate(SelW%,SelI%,5,FALSE)
ENDIF
ENDPROC

REM // Voice handling //

DEF PROCCheckInstalledVoices
    LOCAL NewVoice$, NewNVoices%, changedVoices%

    changedVoices%=FALSE

    SYS "Sound_InstallVoice" TO I$,NewNVoices%:NewNVoices% -= 1

    IF FNassert(NVoices%>0,"NoVoices") STOP

    IF NewNVoices% <> NVoices% THEN
        changedVoices% = TRUE
        NVoices% = NewNVoices%
    ENDIF

    IF NVoices%>MAX_VOICES% THEN NVoices%=MAX_VOICES%

    FOR R% = 1 TO NVoices%
        SYS "Sound_InstallVoice",2,R% TO ,,,NewVoice$
        IF NewVoice$<>Voice$(R%) THEN
            changedVoices%=TRUE
            Voice$(R%)=NewVoice$
        ENDIF
    NEXT : REM find if instruments have changed

    IF changedVoices% THEN
        FOR R% = 0 TO 7
            SYS "Sound_AttachVoice",R% + 1,0 TO L%,S%
            IF S% < 1 OR S% > NVoices% THEN
                S%=1:REM Make sure a voice is attached to all channels
            ENDIF
            SYS "Sound_AttachVoice",L%,S%
            Instrument%(R%,0) = S_C%(R%)+1 : REM Instrument stave
            Instrument%(R%,1) = S%:REM Instrument voice
        NEXT : REM Get details of each channel
        PROCSetDefaultChannels
    ENDIF
ENDPROC

DEF PROCAttachVoice (c%,slot%)
LOCAL v%
PROCCheckInstalledVoices
REM Find channel's previous voice
SYS "Sound_AttachVoice",c%+1 TO ,v%
REM Attach new voice
IF v%=0 THEN v%=1
IF slot%=0 THEN slot%=v%
SYS "Sound_AttachVoice",c%+1,slot%
Instrument%(c%,1)=slot%
ENDPROC

DEF PROCUserDragBox
LOCAL filename$
filename$=FNwimp_geticontext(Save_h%,1)
IF SAVING% THEN
  SYS "Wimp_GetPointerInfo",,Block%
  Block%!32=Block%!4
  Block%!28=Block%!0
  Block%!24=Block%!16
  Block%!20=Block%!12 :REM this is the destination window handle
  Block%!16=1         :REM DataSave
  Block%!12=0
  Block%!36=-1        :REM don't know file size / unsafe?
  Block%!40=MusicFileType%
  $(Block%+44)=FNGetLeafName(filename$)+CHR$(0)
  !Block%=64
  SYS "Wimp_SendMessage",17,Block%,Block%!20,Block%!24
  my_ref%=Block%!8
  SYS "DragASprite_Stop"
ENDIF
ENDPROC

DEF PROCMenuSelect (sel1%,sel2%,sel3%)
LOCAL n%,SoundEnable%,mx%,my%,button%,text$,allowadjust%
SYS "Wimp_GetPointerInfo",,Block%
mx%=Block%!0
my%=Block%!4
button%=Block%!8
allowadjust%=TRUE
CASE CurrentMenu% OF
  WHEN MainMenu%:
    CASE sel1% OF
      WHEN 0:REM File menu
        CASE sel2% OF
          WHEN 1:REM Save file
            SYS "Wimp_CreateMenu",,Save_h%,mx%,my%
            allowadjust%=FALSE 
          WHEN 2:REM Print file
            PROCprint_init
            SYS "Wimp_CreateMenu",,Print_h%,mx%,my%
            allowadjust%=FALSE          
        ENDCASE   
      WHEN 1:REM Edit menu
        CASE sel2% OF
          WHEN 0:REM Go to bar
            PROCgoto_init
            SYS "Wimp_CreateMenu",,Bar_h%,mx%,my%
            allowadjust%=FALSE
          WHEN 1:REM Clear file
            IF CHANGED% THEN
              PROCOpenSaveDiscardCancelBox
            ELSE
              PROCClearAllMusic
              Block%!0=ScoreWind_h%
              SYS "Wimp_GetWindowState",,Block%
              Block%!20=0
              LHBAR%=0
              SYS "Wimp_OpenWindow",,Block%
            ENDIF
            allowadjust%=FALSE
        ENDCASE 
      WHEN 2:REM Score menu
        CASE sel2% OF
          WHEN 0:REM REM Staves
            PROCstaves_init(mx%,my%)
            allowadjust%=FALSE 
          WHEN 1:REM Instruments
            PROCinstruments_init(mx%,my%)
            allowadjust%=FALSE 
          WHEN 2:REM Volume menu
            IF sel3%>=0 THEN
              PROCSetVolume(sel3%)
              PROCChangedScore
            ENDIF
          WHEN 3:REM Tempo menu
            IF sel3%>=0 THEN
              PROCSetTempo(sel3%)
              PROCChangedScore
            ENDIF
        ENDCASE
      WHEN 3:REM Play
        SYS "Sound_Enable",0 TO SoundEnable%:REM dont pretend to be able to play if the sound system is disabled
        IF SoundEnable%=2 THEN
          SCORING%=FALSE
          PLAYING%=NOT PLAYING%
          SCROLLING%=PLAYING%
          IF PLAYING% THEN PROCplay_start ELSE PROCplay_stop
        ELSE
          n%=FNCheckOKTag("NoSound",1)
        ENDIF
    ENDCASE
  WHEN IconMenu%:
    CASE sel1% OF
      WHEN 1:REM Help
        OSCLI("Filer_Run <Maestro$Dir>.!Help")
      WHEN 2:REM Quit
        IF CHANGED% THEN PROCOpenDiscardCancelBox ELSE PROCterminate
    ENDCASE
  WHEN KeyMenu%:
    REM Key signature
    PROCSetKeySig(sel1%,sel2%)
  WHEN VoiceMenu%:
    text$=Voice$(sel1%+1)     
    PROCwimp_seticontext(InstrWind_h%,PopUpIcon%,text$)
    PROCwimp_radiotick(VoiceMenu%,sel1%)
  WHEN ChannelMenu%:
    text$=FNmessage_lookupN(Messages_h%,"MIDIvoice",STR$(sel1%+1),"","","")
    PROCwimp_seticontext(InstrWind_h%,PopUpIcon%,text$)
    PROCwimp_radiotick(ChannelMenu%,sel1%)
  WHEN VolumeMenu%:
    REM This is when open over the Instruments dialogue as a pop-up
    text$=FNmessage_lookup(Messages_h%,"V"+STR$(sel1%))
    PROCwimp_seticontext(InstrWind_h%,PopUpIcon%,text$)
    PROCwimp_radiotick(VolumeMenu%,sel1%)
  WHEN OutputMenu%:
    IF FNwimp_getmenustate(OutputMenu%,sel1%,0)=FALSE THEN
      PROCinstruments_fill(sel1%)
    ENDIF   
ENDCASE
IF allowadjust% AND button%=1 THEN SYS "Wimp_CreateMenu",,CurrentMenu%
ENDPROC

DEF PROCKeyPressed (window%,key%)
LOCAL used%
used%=TRUE
CASE window% OF
  WHEN Save_h%:
    CASE key% OF
      WHEN &0D:REM Enter
        PROCsave_music(FNwimp_geticontext(Save_h%,1))
        PROCCloseWindow(window%)
        SYS "Wimp_CreateMenu",,-1
      OTHERWISE:used%=FALSE
    ENDCASE
  WHEN Bar_h%:
    used%=FNgoto_keypress(key%)
  WHEN StaveWind_h%:
    used%=FNstaves_keypress(key%)
  WHEN ScoreWind_h%:
    used%=FNscore_keypress(key%)
  WHEN InstrWind_h%:
    used%=FNinstruments_keypress(key%)
  OTHERWISE:used%=FALSE
ENDCASE
IF used%=FALSE THEN SYS "Wimp_ProcessKey",key%   
ENDPROC

DEF PROCScrollReq(x_scroll%, y_scroll%)
IF Window%!handle%=ScoreWind_h% THEN
  IF PLAYING%AND(x_scroll%<>0) ENDPROC
  CASE ABS(x_scroll%) OF
    WHEN 1:Window%!scx%+=x_scroll%*4*Pgap%
    WHEN 2:Window%!scx%+=(x_scroll%/2)*(Window%!x1%-Window%!x0%)
    ENDCASE
  CASE ABS(y_scroll%) OF
    WHEN 1:Window%!scy%+=y_scroll%*C_Height%
    WHEN 2:Window%!scy%+=(y_scroll%/2)*(Window%!y1%-Window%!y0%)
    ENDCASE
  SYS "Wimp_OpenWindow",,Window%+handle%
  ScoreClosed%=FALSE
  LHBAR%=FNFindBar(Window%!scx%) :REM the bar number at the left of the window
ENDIF
ENDPROC

DEF PROCOpenWindow (b%)
LOCAL x0%,x1%,y0%,y1%,behind%,ox%
IF b%!0=ScoreWind_h% THEN
  IF PLAYING% THEN b%!20=OldX%
  SYS "Wimp_OpenWindow",,b%
  SYS "Wimp_GetWindowState",,b%
  x0%=b%!4
  y0%=b%!8
  x1%=b%!12
  y1%=b%!16
  ox%=b%!20
  behind%=b%!28 
  REM Open Sharps Pane
  Block%!0=SharpsPane_h%
  Block%!4=x0%
  Block%!8=y0%
  Block%!12=x1%
  Block%!16=y0%+PaneHeight%
  Block%!20=0
  Block%!24=0
  Block%!28=behind%
  SYS "Wimp_OpenWindow",,Block%
  REM Open Notes Pane
  Block%!0=NotesPane_h%
  Block%!4=x0%
  Block%!8=y1%-PaneHeight%
  Block%!12=x1%
  Block%!16=y1%  
  Block%!20=0
  Block%!24=0
  Block%!28=SharpsPane_h%
  SYS "Wimp_OpenWindow",,Block% 
  IF PLAYING% THEN PROCCheckScroll
  PROCrelease
  LHBAR%=FNFindBar(ox%)
ELSE
  SYS "Wimp_OpenWindow",,b%
ENDIF
ENDPROC

DEF PROCUpdateTitle (title$)
LOCAL top%
Block%!0=ScoreWind_h%
SYS "Wimp_GetWindowInfo",,Block% OR 1
$(Block%!76)=title$+CHR$(0)
SYS "Wimp_GetWindowState",,Block%
top%=Block%!16
SYS "Wimp_GetWindowOutline",,Block%
SYS "Wimp_ForceRedraw",-1,Block%!4,top%,Block%!12,Block%!16
ENDPROC

DEF PROCCloseWindow (handle%)
Block%!0=handle%
SYS "Wimp_CloseWindow",,Block%
IF handle%=ScoreWind_h% THEN
  Block%!0=NotesPane_h%
  SYS "Wimp_CloseWindow",,Block%
  Block%!0=SharpsPane_h%
  SYS "Wimp_CloseWindow",,Block%
  ScoreClosed%=TRUE
ENDIF
ENDPROC

DEF PROCsymbol_pointer
SYS "Wimp_GetPointerInfo",,Block%
IF Block%!12=ScoreWind_h% THEN PROCscribe(Block%!0,Block%!4)
ENDPROC

DEF PROCStopScoring
stopSCORING%=TRUE
PROCrelease
PROCtoolicon_clear
ENDPROC

DEF PROCChangedScore
IF NOT CHANGED% THEN
  CHANGED%=TRUE
  PROCwimp_seticontext(FileInfo_h%,2,FNmessage_lookup(Messages_h%,"Yes"))
  PROCUpdateTitle(FileName$+" *")
ENDIF
ENDPROC

DEF PROCSetExtent (width%)
Score_Width%=width%
Block%!0=0
Block%!4=-Score_Height%-PaneHeight%
Block%!8=Score_Width%
Block%!12=PaneHeight%
SYS "Wimp_SetExtent",ScoreWind_h%,Block%
ENDPROC

DEF FNGetLeafName(name$)
LOCAL ch$,n%
IF ( (INSTR(name$,".")=0) AND (INSTR(name$,":")=0) ) THEN=name$
n%=LEN(name$)
REM scan string to find leaf name of file
REPEAT
  ch$= MID$(name$, n%, 1)
  n%-=1
  UNTIL (n%<=0 OR ch$="." OR ch$=":")
IF n%>0 THEN
    =RIGHT$(name$, LEN(name$)-n%-1)
ELSE
    = name$
ENDIF
= ""

DEF FNGetStr(s%) : REM get string
LOCAL n$
WHILE?s%:n$+=CHR$?s%:s%+=1:ENDWHILE
=n$

DEF PROCSetDefaultChannels
LOCAL s%,text$
REM Only 1 percussion channel permitted
REM Channel assignment changes if staves change
FOR s%=0 TO 7
  S_C%(s%)=Stave_Channels%(STAVE%,s%)
NEXT
REM Steal channel for percussion lines
IF PERC% THEN S_C%(7)=STAVE%+1
REM Instrument allocation
FOR s%=0 TO 7
  Instrument%(s%,0)=S_C%(s%)+1
  REM Set up stave names in instrument window
  text$="S"+STR$(Instrument%(s%,0)+(STAVE%-3)*((Instrument%(s%,0)-1)>STAVE%))
  PROCwimp_seticontext(InstrWind_h%,s%+4,FNmessage_lookup(Messages_h%,text$)) 
  PROCAttachVoice(s%,0)
NEXT
REM Steal 1 channel for percussion
IF PERC% THEN
  text$=FNmessage_lookup(Messages_h%,"S5")
  PROCwimp_seticontext(InstrWind_h%,11,text$)
ENDIF
ENDPROC

DEF FNGetFileInfo (file$)
LOCAL type%,laddr%,eaddr%,len%,attr%
IF file$<>"" AND file$<>FNmessage_lookup(Messages_h%,"Untitled") THEN
  SYS "OS_File",5,file$ TO type%,,laddr%,eaddr%,len%,attr%
ENDIF
IF file$="" OR file$=FNmessage_lookup(Messages_h%,"Untitled") OR ((type%=1) AND (attr% AND 1) AND (len%>8))=0 THEN
  PROCwimp_seticontext(FileInfo_h%,1,FNmessage_lookup(Messages_h%,"Untitled"))
  PROCset_type_string(MusicFileType%)
  PROCwimp_seticontext(FileInfo_h%,4,"")
  ?ftime%=3
  SYS "OS_Word",14,ftime%
  SYS "Territory_ConvertStandardDateAndTime",-1,ftime%,Block%,255
  PROCwimp_seticontext(FileInfo_h%,5,FNGetStr(Block%))
  =FALSE
ELSE
  PROCwimp_seticontext(FileInfo_h%,1,file$)
  PROCwimp_seticontext(FileInfo_h%,4,STR$(len%))
  IF ((laddr%>>20) AND &FFF)=&FFF THEN
    IF (laddr%>>8 AND &FFF)=MusicFileType% THEN
      PROCset_type_string(MusicFileType%)
    ELSE
      PROCset_type_string(laddr%>>8 AND &FFF)
    ENDIF
    ftime%?4=laddr% AND &FF
    ftime%?3=eaddr%>>24 AND &FF
    ftime%?2=eaddr%>>16 AND &FF
    ftime%?1=eaddr%>>8 AND &FF
    ftime%?0=eaddr% AND &FF
    SYS "OS_ConvertStandardDateAndTime",ftime%,Block%,255
    PROCwimp_seticontext(FileInfo_h%,5,FNGetStr(Block%))
  ENDIF
ENDIF
=TRUE

DEF PROCset_type_string (type%)
LOCAL s$,r2%,r3%,space$
s$=""
SYS "OS_FSControl",18,,type% TO ,,r2%,r3%
s$=CHR$(r2% AND &FF)+CHR$((r2%>>8) AND &FF)
s$+=CHR$((r2%>>16) AND &FF)+CHR$((r2%>>24) AND &FF)
s$+=CHR$(r3% AND &FF)+CHR$((r3%>>8) AND &FF)
s$+=CHR$((r3%>>16) AND &FF)+CHR$((r3%>>24) AND &FF)
space$=STRING$(8-LEN(s$)," ")
PROCwimp_seticontext(FileInfo_h%,3,s$+space$+"("+STR$~type%+")")
ENDPROC  

DEF PROCload_music(FF$)
LOCAL F%,M$,ZZ%
LOCAL ERROR
T%=0
SCORING%=FALSE
IF PLAYING% PROCplay_stop
SCROLLING%=FALSE
IF LENFF$=0 THEN VDU7 : T%=FNCheckOKTag("BadName",1) : ENDPROC
SYS "Hourglass_On"
FILE%=OPENINFF$
REM SYS "OS_Find", 79, FF$ TO FILE%

ON ERROR LOCAL VDU7:OSCLI("FX 229,1"):PROCClearAllMusic:SYS "Hourglass_Off":T%=FNCheckOK(FNmessage_lookupN(Messages_h%, "IntErr", REPORT$, STR$(ERL), "", ""),1):ENDPROC

M$=""
FORR%=1TO7
  IF NOT EOF#FILE% THEN M$+=CHR$BGET#FILE% ELSE SYS "Hourglass_Off":T%=FNCheckOK(FNmessage_lookup(Messages_h%,"BadFile"),1): ENDPROC
NEXT
OSCLI("FX 229,0") :REM enable escape key
B%=BGET#FILE%
IFM$="Maestro" THEN
  IF NOT FNGetFileInfo(FF$) THEN VDU7 :OSCLI("FX 229,1") : SYS "Hourglass_Off" :T%=FNCheckOKTag("BadFile",1) : ENDPROC
  T%=TRUE
  NBars%=0
  CASE BGET#FILE% OF
    WHEN 0:T%=FALSE
    WHEN 1
      PROClTempo:PROClInstruments:PROClStaves
      T%=FNlMusic
      IF T%=FALSE THEN
        SYS "Hourglass_Off"
        ZZ%=FNCheckOKTag("OutMem2",1)
        PROCClearAllMusic
        LHBAR%=0
        CLOSE#FILE%
        ENDPROC
      ENDIF
    OTHERWISE REM File id version 2 and above
      A%=FALSE
      REPEAT
        REM Used to be an ON BGET#FILE% statement,
        REM changed to allow exiting if music is too big.
        CASE BGET#FILE% OF
          WHEN 1:T%=FNlMusic
            IF T%=FALSE THEN
              SYS "Hourglass_Off"
              ZZ%=FNCheckOKTag("OutMem2",1)
              PROCClearAllMusic
              LHBAR%=0
              CLOSE#FILE%
              ENDPROC
            ENDIF
          WHEN 2:PROClStaves
          WHEN 3:PROClInstruments
          WHEN 4:PROClVolumes
          WHEN 5:PROClStereos
          WHEN 6:PROClTempo            
          OTHERWISE:A%=TRUE
        ENDCASE
      UNTIL EOF#FILE% OR A%
    ENDCASE
  CLOSE#FILE%

  FILE%=FALSE
  OSCLI("FX 229,1")
  PROCwimp_seticontext(FileInfo_h%,1,FNmessage_lookup(Messages_h%,FF$))
  PROCwimp_seticontext(FileInfo_h%,2,FNmessage_lookup(Messages_h%,"No"))
  IF LEFT$(FF$,12)="<Wimp$Scrap>" THEN
    FF$=FNmessage_lookup(Messages_h%,"Untitled") 
    PROCwimp_seticontext(Save_h%,1,FNmessage_lookup(Messages_h%,"MusicFile"))
  ELSE
    PROCwimp_seticontext(Save_h%,1,FF$)
  ENDIF
  T%=FNGetFileInfo(FF$)
  PROCUpdateTitle(FF$)
  FileName$=FF$
  SYS "Hourglass_Off"
  SYS "Hourglass_On"
  IF T% THEN
    PROCposition_staves
    PROCSetExtent(S_Width%)
    PROCstart_music
    PROCSetDefaultChannels
    PROCset_score(0)
    PROCSetupBarStarts(0)
    PROCscore_init
    PROCscore_update(0,-Score_Height%,Score_Width%,0)
    PROCStopScoring
    CHANGED%=FALSE
  ENDIF
ELSE
  OSCLI("FX 229,1")
  T%=FALSE
ENDIF
IF NOT T% THEN ZZ%=FNCheckOKTag("InvMusic",1) : CLOSE#FILE%
load_pending% = FALSE
SYS "Hourglass_Off"
ENDPROC

DEF FNlMusic
LOCAL c%,ptr%,size%,bsize%,n%
REM Get sizes of queues and resize heap blocks if needed
INPUT#FILE%,GateSize%
bsize%=FNheap_getblocksize(Heap%,GateBlock%)
IF GateSize%>bsize% THEN
  IF NOT FNheap_resizeblock(Heap%,GateBlock%,GateSize%-bsize%) THEN
    REM Couldn't resize block: terminate load
    n%=FNCheckOKTag("OutMem2",1)
    =FALSE
  ENDIF
ENDIF
FOR c%=0 TO 7
  INPUT#FILE%,size%
  NoteSize%(c%)=size%*2
  bsize%=FNheap_getblocksize(Heap%,NoteBlock%(c%))
  IF NoteSize%(c%)>bsize% THEN
    IF NOT FNheap_resizeblock(Heap%,NoteBlock%(c%),NoteSize%(c%)-bsize%) THEN
      REM Couldn't resize block: terminate load
      n%=FNCheckOKTag("OutMem2",1)
      =FALSE
    ENDIF
  ENDIF
NEXT
REM Fill Gate queue
ptr%=GateBlock%
WHILE ptr%<GateBlock%+GateSize%
  ?ptr%=BGET#FILE%
  ptr%+=1
ENDWHILE
REM Fill Note queue
FOR c%=0 TO 7
  ptr%=NoteBlock%(c%)
  WHILE ptr%<NoteBlock%(c%)+NoteSize%(c%)
    ptr%?0=BGET#FILE%
    ptr%?1=BGET#FILE%
    ptr%?2=0
    ptr%?3=0
    ptr%+=NSIZE%
  ENDWHILE
NEXT
REM Set pointers to Gate and Note queues
PP%=GateBlock%
P%()=NoteBlock%()
=TRUE

DEF PROClStaves
STAVE%=BGET#FILE%
PERC%=BGET#FILE%
ENDPROC

DEF PROClInstruments
LOCAL c%,chan%,v%
FOR c%=0 TO 7
  REM Get channel
  chan%=BGET#FILE%
  REM Write voice numbers allocated to each channel
  v%=BGET#FILE% MOD (NVoices%+1)
  IF v%=0 THEN v%=1
  SYS "Sound_AttachVoice",chan%+1,v%
  PROCAttachVoice(chan%,0)
NEXT
ENDPROC

DEF PROClVolumes
LOCAL c%
FOR c%=0 TO 7
  Volumes%(c%)=BGET#FILE%
  IF Volumes%(c%)>7 THEN Volumes%(c%)=7
  IF Volumes%(c%)<0 THEN Volumes%(c%)=0
NEXT
ENDPROC

DEF PROClStereos
LOCAL c%
FOR c%=0 TO 7
  Stereo_Position%(c%)=BGET#FILE%
  SYS "Sound_Stereo",c%+1,Stereo%(Stereo_Position%(c%))
NEXT
ENDPROC

DEF PROClTempo
LOCAL t%
t%=BGET#FILE%
PROCSetTempo(t%)
ENDPROC

DEF PROCsave_init (x%,y%)
LOCAL f$
f$=FNwimp_geticontext(Save_h%,1)
IF (INSTR(f$,".")=0 AND INSTR(f$,":")=0) THEN
  SYS "Wimp_CreateMenu",,Save_h%,x%,y%
ELSE
  PROCsave_music(f$)
ENDIF            
ENDPROC

DEF PROCsave_music (FF$)
LOCAL n%,tmp%,laddr%,eaddr%
LOCAL ERROR
T%=0
REM simple check for pathname rather than local name
IF (INSTR(FF$,".")=0 AND INSTR(FF$,":")=0 AND INSTR(FF$,"<")=0) THEN
  n%=FNCheckOKTag("ToSave",1)
  ENDPROC
ENDIF
SYS "Hourglass_On"
ON ERROR LOCAL OSCLI("FX 229,1"):SYS "Hourglass_Off":T%=FNCheckOK(REPORT$,1):ENDPROC
OSCLI("FX 229,0"):REM enable escape key
SYS "OS_File",5,FF$ TO ,,laddr%,eaddr%
IF CHANGED% OR (((laddr%>>20) AND &FFF)<>&FFF) THEN
  REM file changed or wasn't timestamped
  REM get current time
  Block%?0=3:SYS "OS_Word",&0E,Block%
  laddr%=Block%?4
  eaddr%=Block%!0
  ENDIF
REM force music file type, and preserve timestamp
laddr%=(laddr% AND &FF) OR (&FFF<<20) OR (MusicFileType%<<8)
REM I don't know what the length will be, so use zero
SYS "OS_File", &07, FF$, laddr%, eaddr%, 0, 0
REM OPENUP, error if directory or not found
SYS "OS_Find", &CC, FF$ TO FILE%
REM timestamp is automatically updated on 1st byte write
BPUT#FILE%,"Maestro"
BPUT#FILE%,2
PROCsMusic
PROCsStaves
PROCsInstruments
PROCsVolumes
PROCsStereos
PROCsTempo
CLOSE#FILE%:FILE%=FALSE
IF (NOT CHANGED%) AND (((laddr%>>20) AND &FFF)=&FFF) THEN
  REM file not changed and was timestamped
  REM preserve original timestamp
  SYS "OS_File",2,FF$,laddr% :REM re-stamp with old stamp
  SYS "OS_File",3,FF$,,eaddr% :REM nb eaddr% in r3
ENDIF
OSCLI("FX 229,1")
REM Twiddle the name we keep so that it doesn't ever get set to <Wimp$Scrap>
REM Also, if a scrap transfer is done, the document should still be marked as
REM having been altered - a scrap transfer isn't safe.
IF LEFT$(FF$, 12) = "<Wimp$Scrap>" THEN
  F$ = LEFT$(FileName$) : REM Preserve old title
  IF CHANGED% THEN
    PROCUpdateTitle(LEFT$(F$) + "*") : REM Will already have space on end
  ENDIF
ELSE
  CHANGED%=FALSE
  PROCwimp_seticontext(FileInfo_h%,2,FNmessage_lookup(Messages_h%,"No"))
  F%=FNGetFileInfo(FF$)
  PROCUpdateTitle(FF$)
  PROCwimp_seticontext(Save_h%,1,FF$+CHR$(0))
  FileName$=FF$
ENDIF
SYS "Hourglass_Off"
ENDPROC

DEF PROCsMusic
LOCAL c%,ptr%,size%
BPUT#FILE%,1
REM Write sizes of Gate and Note queues
PRINT#FILE%,GateSize%
FOR c%=0 TO 7
  size%=NoteSize%(c%) DIV 2
  PRINT#FILE%,size%
NEXT
REM Write Gate data
ptr%=GateBlock%
WHILE ptr%<GateBlock%+GateSize%
  BPUT#FILE%,?ptr%
  ptr%+=1
ENDWHILE
REM Write Note data
FOR c%=0 TO 7
  ptr%=NoteBlock%(c%)
  WHILE ptr%<NoteBlock%(c%)+NoteSize%(c%)
    BPUT#FILE%,ptr%?0
    BPUT#FILE%,ptr%?1
    ptr%+=NSIZE%
  ENDWHILE
NEXT
ENDPROC

DEF PROCsStaves
BPUT#FILE%,2
BPUT#FILE%,STAVE%
BPUT#FILE%,PERC%
ENDPROC

DEF PROCsInstruments
LOCALC%
BPUT#FILE%,3
FORC%=0TO7
BPUT#FILE%,C%
BPUT#FILE%,Instrument%(C%,1)
NEXT
ENDPROC

DEF PROCsVolumes
LOCALC%
BPUT#FILE%,4
FORC%=0TO7
BPUT#FILE%,Volumes%(C%)
NEXT
ENDPROC

DEF PROCsStereos
LOCALC%
BPUT#FILE%,5
FORC%=0TO7
BPUT#FILE%,Stereo_Position%(C%)
NEXT
ENDPROC

DEF PROCsTempo
BPUT#FILE%,6
BPUT#FILE%,Tempo%
ENDPROC

DEF PROCsprite(s%,sx%,sy%)
REM Plot sprite S%(s%) at sx%,sy%
sx%=sx%-x%(s%)
sy%=sy%-y%(s%)
SYS "OS_SpriteOp",SprPlot%,SprBlk%,S%(s%),sx%,sy%,8,factors%,pixtrans%
ENDPROC

DEF PROCfloat (s%,sx%,sy%)
LOCAL more%,width%,height%,n%,b%
REM Get size of attribute for redraw rectangle
CASE s% OF
  WHEN key%:REM Key signature
    IF KEY_SIG%(1) THEN
      n%=X%(accidental%+2+KEY_SIG%(0))+x%(accidental%+2+KEY_SIG%(0))
      width%=KEY_SIG%(1)*n%
    ELSE
      width%=X%(key%)
    ENDIF
    height%=72
  WHEN time%:REM Time signature
    n%=TIME_SIG%(0)+1
    b%=2^(TIME_SIG%(1)-1)
    width%=X%(time%+n%)
    IF width%<X%(time%+b%) THEN width%=X%(time%+b%)
    height%=Stave_Height%
  OTHERWISE:REM Any other attribute
    width%=X%(s%)
    height%=Y%(s%)
ENDCASE
Block%!0=ScoreWind_h%
Block%!12=sx%+width%+2
sx%-=x%(s%)
Block%!4=sx%
Block%!16=sy%+height%
sy%-=y%(s%)
Block%!8=sy%
SYS "Wimp_UpdateWindow",,Block% TO more%
sx%+=Block%!4-Block%!20
sy%+=Block%!16-Block%!24
WHILE more%
  CASE s% OF
    WHEN key%:PROCfloat_key_sig(sx%,sy%+y%(s%))
    WHEN time%:PROCfloat_time_sig(sx%,sy%,width%)
    OTHERWISE:
      SYS "OS_SpriteOp",SprPlot%,SprBlk%,S%(s%),sx%,sy%,11,factors%,fpixtrans%
  ENDCASE
  SYS "Wimp_GetRectangle",,Block% TO more%
ENDWHILE
ENDPROC

DEF PROCfloat_key_sig (kx%,ky%)
LOCAL i%,a%,c%,w%,m%
IF KEY_SIG%(1) THEN
  c%=SCRIBE%(sclef%)
  a%=KEY_SIG%(0)
  m%=accidental%+2+a%
  w%=x%(m%)+X%(m%)
  ky%-=y%(m%)
  FOR i%=0 TO KEY_SIG%(1)-1
    SYS "OS_SpriteOp",SprPlot%,SprBlk%,S%(m%),kx%,ky%+Li%*Key_Y%(c%,a%,i%),11,factors%,fpixtrans%
    kx%+=w%
  NEXT
ELSE
  SYS "OS_SpriteOp",SprPlot%,SprBlk%,S%(key%),kx%,ky%-y%(key%),11,factors%,fpixtrans%
ENDIF
ENDPROC

DEF PROCfloat_time_sig (tx%,ty%,width%)
LOCAL sx%,w%,n%,b%
n%=TIME_SIG%(0)+1
w%=X%(time%+n%)
IF w%<width% THEN sx%=tx%+12 ELSE sx%=tx%
SYS "OS_SpriteOp",SprPlot%,SprBlk%,S%(time%+n%),sx%,ty%+36,11,factors%,fpixtrans%
b%=2^(TIME_SIG%(1)-1)
w%=X%(time%+b%)
IF w%<width% THEN sx%=tx%+12 ELSE sx%=tx%
SYS "OS_SpriteOp",SprPlot%,SprBlk%,S%(time%+b%),sx%,ty%+4,11,factors%,fpixtrans%
ENDPROC

REM *********************************************************************** REM
REM                                                                         REM
REM         M U S I C   T R A N S C R I P T I O N   R O U T I N E S         REM
REM                                                                         REM
REM *********************************************************************** REM

DEF PROCstart_music
REM Initialise to start of music data
GP%=GateBlock%
N%()=NoteBlock%()
REM Set default clef, time sig (4/4) and key sig (Cmaj)
CLEF%()=0
SIG%(0)=%01100111
SIG%(1)=%00000010
REM Go to score start and cache bar info
BAR%=0
PX%=0
PROCPutBarInfo(0)
ENDPROC

REM   PROCEDURE: note_type(Channel C%, Type T%)

DEF PROCnote_type(C%,T%)
N%(C%)?1=N%(C%)?1 AND &1F OR T%<<5
ENDPROC

DEF PROCnote_dots(C%,D%)
N%(C%)?1=N%(C%)?1 AND &E7 OR D%<<3
ENDPROC

DEF PROCnote_accidental(C%,A%)
N%(C%)?1=N%(C%)?1 AND &F8 OR A%
ENDPROC

DEF PROCnote_line (c%,l%,type%)
REM Sets the y pos for Notes/Rests
REM Note range is +/-15, Rest range is +/-3
REM For Notes, these are converted to 1-31
REM For Rests, they're converted to and 1-3 below centre line,
REM 5-7 above centre line, and 0 on centre line (to preserve
REM compatibility with older versions where Rests were always centred).
IF type%>=rest% THEN
  IF l%=0 THEN l%=-4
  N%(c%)?1=(N%(c%)?1 AND &F8) OR l%+4
ELSE
  ?N%(c%)=(?N%(c%) AND &7) OR (l%+16)<<3
ENDIF
ENDPROC

DEF PROCnote_tie (c%,type%)
REM Add/remove a tie from a note
CASE type% OF
  WHEN 0:REM Remove tie
    ?N%(c%)=?N%(c%) AND NOT (2<<1)
  WHEN 1:REM Add an over-note tie
    ?N%(c%)=?N%(c%) AND NOT (1<<1)
    ?N%(c%)=?N%(c%) OR (1<<2)
  WHEN 2:REM Add an under-note tie
    ?N%(c%)=?N%(c%) OR (3<<1)
ENDCASE
ENDPROC

DEF PROCnote_stem(C%,D%)
?N%(C%)=?N%(C%)AND&FE OR(D%<>0)AND%1
ENDPROC

DEF PROCnote_clear (c%)
N%(c%)!0=0
ENDPROC

DEF PROCnote_chord
REM Sets the chord data for all Notes in a Gate
LOCAL n%,m%,type%,orient%,line%,t%,o%,l%,chord%,top%,bottom%
REM Clear any existing chord data
FOR n%=0 TO 7
  IF (GP%?0 AND 1<<n%) THEN N%(n%)?2=N%(n%)?2 AND NOT &7
NEXT
FOR n%=0 TO 7
  REM Step through all Notes in Gate, ignoring Rests
  IF (GP%?0 AND 1<<n%) THEN
    IF (N%(n%)?0 AND &F8) THEN
      REM Get Note attributes
      type%=N%(n%)?1>>5
      orient%=(N%(n%)?0 AND &1)
      line%=N%(n%)?0>>3
      REM Step through rest of Notes in Gate
      chord%=FALSE
      top%=line%
      bottom%=line%
      FOR m%=0 TO 7
        IF (GP%?0 AND 1<<m%) AND m%<>n% THEN
          IF (N%(m%)?0 AND &F8) THEN
            REM Is new note same Stave, note-type and stem dir?
            t%=N%(m%)?1>>5
            IF t%>1 THEN o%=(N%(m%)?0 AND &1) ELSE o%=orient%
            IF S_C%(m%)=S_C%(n%) AND t%=type% AND o%=orient% THEN
              REM Mark first Note as being in a chord
              chord%=TRUE
              N%(n%)?2=(N%(n%)?2 OR &1)
              REM Check top/bottom chord positions
              l%=N%(m%)?0>>3
              IF l%>top% THEN top%=l%
              IF l%<bottom% THEN bottom%=l%
              IF line%>l% THEN N%(m%)?2=N%(m%)?2 AND NOT (1<<1)
              IF line%<l% THEN N%(m%)?2=N%(m%)?2 AND NOT (1<<2)
            ENDIF
          ENDIF  
        ENDIF
      NEXT
      REM Set chord data
      IF chord%=TRUE THEN
        N%(n%)?2=N%(n%)?2 OR &1
        IF line%=top% THEN N%(n%)?2=N%(n%)?2 OR &2
        IF line%=bottom% THEN N%(n%)?2=N%(n%)?2 OR &4
      ELSE
        N%(n%)?2=N%(n%)?2 AND NOT &7
      ENDIF
    ENDIF
  ENDIF
NEXT
ENDPROC

DEF PROCnote_stagger
REM Sets the stagger data for overlapping Notes in a Gate
LOCAL n%,m%,line%,stagger%,l%,s%,sc1%,sc2%
REM Clear any existing stagger data
FOR n%=0 TO 7
  IF (GP%?0 AND 1<<n%) THEN N%(n%)?2=N%(n%)?2 AND NOT &8
NEXT
REM Step through all Notes in Gate, ignoring Rests
FOR n%=0 TO 7
  IF (GP%?0 AND 1<<n%) THEN
    IF (N%(n%)?0 AND &F8) THEN
      line%=N%(n%)?0>>3
      stagger%=N%(n%)?2 AND &8
      REM Step through rest of Notes
      FOR m%=0 TO 7
        IF (GP%?0 AND 1<<m%) THEN
          IF (N%(m%)?0 AND &F8) AND S_C%(m%)=S_C%(n%) AND m%<>n% THEN
            l%=N%(m%)?0>>3
            s%=N%(m%)?2 AND &8
            REM If Notes are adjacent, and both are non-staggered,
            REM calculate which one to keep in place
            REM Ends of chords have highest priority,
            REM followed by line height
            IF ABS(line%-l%)=1 AND stagger%=0 AND s%=0 THEN
              sc1%=0:sc2%=0
              IF (N%(n%)?2 AND &6) THEN sc1%+=2
              IF (N%(m%)?2 AND &6) THEN sc2%+=2
              IF line%>l% THEN sc1%+=1
              IF l%>line% THEN sc2%+=1
              IF sc1%>sc2% THEN
                N%(m%)?2=N%(m%)?2 OR &8:s%=1
              ELSE
                N%(n%)?2=N%(n%)?2 OR &8:stagger%=1
              ENDIF
            ENDIF
          ENDIF
        ENDIF
      NEXT
    ENDIF
  ENDIF
NEXT
ENDPROC

DEF PROCtime_sig(N%,B%)
?GP%=0:GP%?1=Time%ORN%<<1ORB%<<5
ENDPROC

DEF PROCkey_sig (A%,N%)
GP%?0=0
GP%?1=Key%ORA%<<2ORN%<<3
ENDPROC

DEF PROCclef (S%,C%)
GP%?0=0
GP%?1=Clef% OR C%<<3 OR S%<<6
ENDPROC

DEF PROCbar
GP%?0=0
GP%?1=Bar% OR BarlineType%<<6
ENDPROC

DEF FNinsert_gate (gsize%)
REM Inserts a new Gate. Returns FALSE if error extending heap block
LOCAL ptr%,bsize%,goff%,n%
REM Check if heap block needs extending
bsize%=FNheap_getblocksize(Heap%,GateBlock%)
IF GateSize%>bsize%-4 THEN
  goff%=GP%-GateBlock%
  IF NOT FNheap_resizeblock(Heap%,GateBlock%,512) THEN
    REM Error: can't resize block
    n%=FNCheckOKTag("OutMem1",1)
    =FALSE
  ENDIF
  GP%=GateBlock%+goff%
  EP%=GateBlock%+GateSize%
  PROCSetupBarStarts(0)  
ENDIF
IF GP%<EP% THEN
 REM Shift data up by gsize% from insertion point
  FOR ptr%=GateBlock%+GateSize% TO GP% STEP -4
    ptr%!gsize%=!ptr%
  NEXT
  REM Clear up odd word
  ptr%+=3
  WHILE ptr%>=GP%
    ptr%?gsize%=?ptr%
    ptr%-=1
  ENDWHILE
ENDIF
REM Insert a Gate block into the queue and clear it
GateSize%+=gsize%
EP%+=gsize%
?GP%=0
GP%?(gsize%-1)=0
=TRUE

DEF FNinsert_note (c%)
REM Inserts a new Note. Returns FALSE if error extending heap block
LOCAL ptr%,end%,bsize%,noff%,n%
REM Check if heap block needs extending
bsize%=FNheap_getblocksize(Heap%,NoteBlock%(c%))
IF NoteSize%(c%)>bsize%-NSIZE% THEN
  noff%=N%(c%)-NoteBlock%(c%)
  IF NOT FNheap_resizeblock(Heap%,NoteBlock%(c%),256) THEN
    REM Error: can't resize block
    n%=FNCheckOKTag("OutMem1",1)
    =FALSE
  ENDIF
  N%(c%)=NoteBlock%(c%)+noff%
  PROCsavp
  PROCSetupBarStarts(0)
  PROCrstp
  SCRIBE%(c%)=N%(c%)
ENDIF
end%=NoteBlock%(c%)+NoteSize%(c%)
REM Shift data up by NSIZE% from insertion point
IF N%(c%)<end% THEN
  FOR ptr%=end%-NSIZE% TO N%(c%) STEP -4
    ptr%!NSIZE%=ptr%!0
  NEXT
ENDIF
REM Insert a Note block into the queue and clear it
NoteSize%(c%)+=NSIZE%
GP%?0=GP%?0 OR (1<<c%)
PROCnote_clear(c%)
=TRUE

DEF PROCdelete_gate (gsize%)
LOCAL ptr%,end%
GateSize%-=gsize%
end%=GateBlock%+GateSize%
REM Shift data down by gsize%
IF GP%<end% THEN
  FOR ptr%=GP% TO end% STEP 4
    !ptr%=ptr%!gsize%
  NEXT
ENDIF
EP%-=gsize%
ENDPROC

DEF PROCdelete_note (c%)
LOCAL ptr%,end%
NoteSize%(c%)-=NSIZE%
end%=NoteBlock%(c%)+NoteSize%(c%)
REM Shift data down by NSIZE%
IF N%(c%)<end% THEN
  FOR ptr%=N%(c%) TO end% STEP 4
    ptr%!0=ptr%!NSIZE%
  NEXT
ENDIF
GP%?0=GP%?0 AND NOT (1<<c%)
ENDPROC

DEF FNallocate_channel (s%)
LOCAL channel%,c%
channel%=-1
c%=7
REPEAT
  WHILE c%>=0 AND (?GP% AND 1<<c%)
    c%-=1
  ENDWHILE
  IF c%>=0 THEN
    IF S_C%(c%)=s% THEN channel%=c%
  ENDIF
  c%-=1
UNTIL c%<0
=channel%

DEF PROCarrange_stave (S%)
LOCAL C%,B%,end%
end%=GateBlock%+GateSize%
B%=TRUE
C%=TRUE
IF ?GP% THEN
ELSE
  REPEAT
    GP%+=2
  UNTIL ?GP% OR GP%>=end%
ENDIF
WHILE B% AND GP%<end%
  C%=C% OR FNsort_gate
  PROCskip_notes(?GP%)
  GP%+=1
  IF ?GP% THEN
  ELSE
    B%=C%
    C%=FALSE
    REPEAT
      GP%+=2
    UNTIL ?GP% OR GP%>=end%
  ENDIF
ENDWHILE
PROCSetupBarStarts(0)
ENDPROC

DEF FNsort_gate
LOCALC%,NC%,NN%,G%,g%,pg%,d%,shortest%,Gchanged%
shortest%=255
G%=?GP%
g%=FNprevious_gate(GP%)
pg%=FNpreceding_gate(GP%):REM note-gate immediately before
NC%=-1
NN%=-1
FORC%=0TO7
IFS_C%(C%)=S% THEN
NC%+=1:n%(NC%)=C%:IFG%AND%1<<C% NN%+=1:C%(NN%)=C%
IFpg%AND%1<<C% d%=N%(C%)?(-NSIZE%+1)>>3EOR%11100:IFd%<shortest% shortest%=d%
ENDIF
NEXT
shortest%=shortest%EOR%11100
IFg%ANDNC%>0ANDNN%>=0 PROCsort
=Gchanged%

DEF PROCsort
LOCAL N%,M%
c%()=-1
FOR M%=0 TO NC%
  FOR N%=0 TO NN%
    IF FNsame_pitch(n%(M%),C%(N%)) THEN
      c%(N%)=n%(M%)
    ENDIF
  NEXT
NEXT
FOR N%=0 TO NN%
  IF c%(N%)<0 THEN c%(N%)=FNbest
NEXT
FOR N%=0 TO NN%
  IF C%(N%)=c%(N%) THEN
  ELSE
    Gchanged%=TRUE
    M%=FNin(c%(N%),C%())
    IF M%>N% THEN
      PROCswap_notes(N%,M%)
    ELSE
      PROCmove_note(N%)
    ENDIF
  ENDIF
NEXT
ENDPROC

DEF PROCswap_notes(N%,M%)
LOCALs%,d%
s%=C%(N%):d%=c%(N%)
SWAPC%(N%),C%(M%)
SWAP!N%(s%),!N%(d%)
ENDPROC

DEF PROCmove_note(N%)
LOCALs%,d%
s%=C%(N%):d%=c%(N%)
IF NOT FNinsert_note(d%) THEN ENDPROC
!N%(d%)=!N%(s%)
PROCdelete_note(s%)
PROCSetupBarStarts(0)
ENDPROC

DEF FNbest
LOCALN%,C%
LOCAL short%,free%,rest%,any%,tied%
FOR N%=NC%TO0STEP-1
C%=n%(N%)
IFFNin(C%,c%())<0 THEN
  IF N%(C%)?-NSIZE% AND 4 THEN
    tied%=C%+1
  ELSE
    IFpg%AND%1<<C% THEN
      IF (N%(C%)?(-NSIZE%+1)>>3)=shortest% short%=C%+1
      IF N%(C%)?-NSIZE% AND &F8 ELSE rest%=C%+1
    ELSE
      free%=C%+1
    ENDIF
    any%=C%+1
  ENDIF
ENDIF
NEXT
IFshort% C%=short% ELSEIFfree% C%=free% ELSEIFrest% C%=rest% ELSEIFany% C%=any% ELSEC%=tied%
=C%-1

DEF FNin(U%,U%())
LOCALI%:I%=NN%
WHILEI%ANDU%<>U%(I%):I%-=1:ENDWHILE
=I%+(U%<>U%(I%))

DEF FNsame_pitch(c%,C%)
LOCAL R%,r%
R%=?N%(C%) AND &F8
r%=N%(c%)?-NSIZE% AND &F8
=N%(c%)-NSIZE%>=NoteBlock%(c%) AND (g% OR (N%(c%)?-NSIZE% AND 4)=4) AND %1<<c% AND (R% EOR r%)=FALSE AND (FALSE<>R% EOR r%=FALSE)

DEF FNpreceding_gate(gp%)
LOCALC%,gm%
FORC%=0TO7:IFS_C%(C%)=S% gm%=gm%OR%1<<C%
NEXT
REPEAT
gp%-=1
UNTILgm%AND?gp%ORgp%<GateBlock%+2ORgp%?-1=FALSE
=?gp%ANDgp%>GateBlock%+1ANDgp%?-1<>FALSE

DEF FNprevious_gate(gp%)
LOCALC%,gm%
FORC%=0TO7:IFS_C%(C%)=S% gm%=gm%OR%1<<C%
NEXT
REPEAT
gp%-=1
WHILEgp%>GateBlock%ANDgp%?-1=FALSE:gp%-=2:ENDWHILE
UNTILgm%AND?gp%ORgp%<GateBlock%+2
=?gp%ANDgp%>GateBlock%+1

REM *********************************************************************** REM
REM                                                                         REM
REM           M U S I C   T Y P E S E T T I N G   R O U T I N E S           REM
REM                                                                         REM
REM *********************************************************************** REM
::
REM   PROCEDURE: set_score(Starting position PX%)
REM
REM DESCRIPTION: Typeset music score from current bar to end of music/screen
REM              No drawing is done as only the positions are calculated.
REM
REM     EFFECTS: Stores gate X positions & types in PX%() & PTYPE%()
:
DEF PROCset_score(PX%)
WHILE GP%<GateBlock%+GateSize%
IF?GP% PROCset_notes(?GP%):GP%+=1 ELSEPROCset_attribute(GP%?1):GP%+=2
ENDWHILE:REM Until edge of screen or end of music
EP%=GP%:REM Last GP%, pointer to first undrawn gate
EX%=PX%:REM Last PX%, index to last position
PX%(PX%+1)=PX%(PX%)+PW%(PX%)+Pgap%:REM Appendation position after last symbol
IF PX%(PX%+1)>S_Width% PROCSetExtent(PX%(PX%+1)+100*Hi%) ELSE PROCSetExtent(S_Width%)
PXn%(NBars%)=PX%
PTYPE%(PX%+1)=Note%:REM Note type by default
ENDPROC
::
REM NOTE: The key signature is the only attribute that must be kept track of in order to ensure correct setting.
REM       This is because a naturalising signature consists of the same number of naturals as accidentals in the previous key signature
:
DEF PROCset_attribute (a%)
LOCAL type%,subtype%,n%,b%,w%
IF a% AND PTYPE%(PX%)+TRUE ELSE IF a% AND PTYPE%(PX%) ENDPROC
type%=%1:IF a% AND %1 ELSE REPEAT type%=type%<<1:UNTIL a% AND type%
PX%(PX%+1)=PX%(PX%)+PW%(PX%)+Pgap%
PX%+=1
PTYPE%(PX%)=type%
CASE type% OF
  WHEN Time%:REM Time signature
    n%=(a%>>1 AND 15)+1
    b%=%1<<(a%>>5)-1
    w%=X%(time%+n%)
    IF X%(time%+b%)>w% THEN w%=X%(time%+b%)
    PW%(PX%)=x%(time%+2)+w%
  WHEN Key%:REM Key signature
    IF a% AND 56 THEN
      REM New Key Signature has accidentals
      subtype%=accidental%+(a%>>2 AND %1)+2
      SIG%(1)=a%
    ELSE
      REM New Key Signature has no accidentals
      subtype%=accidental%+1
      SWAP a%,SIG%(1)
      IF a% AND 56 THEN
        REM Width: naturalising Key Signature
      ELSE
        REM Width: empty Key Signature
        a%=8
        subtype%+=1
      ENDIF
    ENDIF
    PW%(PX%)=(a%>>3 AND 7)*(x%(subtype%)+X%(subtype%))
  WHEN Clef%:REM Clef
    PW%(PX%)=x%(clef%+3)+X%(clef%+3)
  WHEN Bar%:REM Barline
    subtype%=a%>>6
    PW%(PX%)=x%(bar%+subtype%)+X%(bar%+subtype%)
ENDCASE
ENDPROC

DEF PROCset_notes(G%)
LOCALlx0%,lx1%,ly0%,ly1%
LOCALC%,P%,R%,s%:REM Channel, Note prefix & remainder widths, Sprite
PROCnote_chord
PROCnote_stagger
C%=-1
REPEAT
REPEATC%-=TRUE:UNTILG%AND%1<<C%
PROCbound_note(N%(C%))
IFlx0%>P% P%=lx0%:REM Maximum prefix
IFlx1%>R% R%=lx1%:REM Maximum remainder
N%(C%)+=NSIZE%:REM Pull from note queue
UNTIL(2<<C%)>G%:REM Until no more notes (1 bits)
PX%(PX%+1)=PX%(PX%)+PW%(PX%)+Pgap%+P%:REM Next position is previous position + width+gap+prefix of next note
PX%+=1:REM next note position index
PW%(PX%)=R%:REM Width of new note
PTYPE%(PX%)=Note%:REM Note type
ENDPROC

DEF PROCbound_note (n%)
REM Return bounding box for Note
LOCAL n0%,n1%,n2%,s1%,s2%
n0%=n%?0
n1%=n%?1
n2%=n%?2
REM Get Note/Rest sprite
IF (n0% AND &F8) THEN
  s1%=n1%>>5 OR n0%<<3 AND &8
ELSE
  s1%=rest% OR n1%>>5
ENDIF
lx0%=x%(s1%):REM Prefix width
lx1%=X%(s1%):REM Suffix width
ly0%=y%(s1%):REM Decender height
ly1%=Y%(s1%):REM Ascender height
IF (n0% AND &F8) THEN
  REM Adjust for note staggers
  IF (n2% AND &8) THEN
    REM Note is on its own
    IF (n2% AND &1)=0 THEN lx1%+=X%(3)-4
    REM Note is in a chord
    IF (n2% AND &1) THEN
      IF (n0% AND &1) THEN lx0%+=X%(3)-4 ELSE lx1%+=X%(3)-4
    ENDIF
  ENDIF
  REM Adjust for Accidentals
  IF (n1% AND &7) THEN
    s2%=accidental% OR (n1% AND &7)
    lx0%+=x%(s2%)
    IF y%(s2%)>ly0% THEN ly0%=y%(s2%)
    IF Y%(s2%)>ly1% THEN ly1%=Y%(s2%)
  ENDIF
ENDIF
REM Adjust for Dots
IF (n1% AND &18) THEN
  s2%=dot%+(n1%>>3 AND &3)
  lx1%=x%(s1%)+X%(s2%)
  IF y%(s2%)>ly0% THEN ly0%=y%(s2%)
ENDIF
ENDPROC

DEF FNbound_tie (c%,pos%)
REM Scans ahead to check that a valid second note exists
REM to tie to. If the scan passes more than one barline, or
REM hits the end of the score, return 0. Otherwise, return
REM the x position of the gate that contains the note.
LOCAL gp%,px%,bar%,sline%,xpos%,bline%
REM Get first note's staveline
sline%=(?N%(c%) AND &F8)>>3
REM Store current score and bar pointers
PROCsavp
bar%=BAR%
REM Scan for a note on the same channel and staveline
WHILE GP%<GateBlock%+GateSize%+1 AND bline%<2
  PROCskip_gate
  pos%+=1
  REM Check for barline
  IF GP%?0=0 AND GP%?1=&20 THEN bline%+=1
  REM Check for note
  IF (?GP% AND (1<<c%)) AND bline%<2 THEN
    IF ((?N%(c%) AND &F8)>>3)=sline% THEN
      xpos%=pos%
      GP%=GateBlock%+GateSize%+1
    ENDIF
  ENDIF
ENDWHILE
REM Restore score and bar pointers
PROCrstp
BAR%=bar%
=xpos%

DEF PROCposition_staves
LOCAL y%,s%
Score_Height%=(PERC%+1+3*(STAVE%+1)+1)*Stave_Height%
y%=-Score_Height%-Stave_Height% DIV 2
IF PERC% THEN
  FOR s%=PERC% TO 1 STEP -1
    y%+=Stave_Height%
    Y_STAVE%(STAVE%+s%)=y%
  NEXT
ENDIF
FOR s%=STAVE% TO 0 STEP -1
  y%+=3*Stave_Height%
  Y_STAVE%(s%)=y%
NEXT
ENDPROC

REM // Score //

DEF PROCscore_init
LOCAL wy%
REM Ensure visible height matches height of workarea
Block%!0=ScoreWind_h%
SYS "Wimp_GetWindowInfo",,(Block% OR 1)
wy%=ABS(Block%!48)+PaneHeight%
Block%!8=Block%!16-wy%
PROCOpenWindow(Block%)
LHBAR%=0
ScoreClosed%=FALSE
FirstOpen%=FALSE
SYS "Wimp_SetCaretPosition",ScoreWind_h%,-1
ENDPROC

DEF PROCscore_click (window%,icon%,button%)
CASE window% OF
  WHEN NotesPane_h%:
    IF icon%>0 THEN
      PROCtoolicon_click(NotesPane_h%,icon%)
      IF icon%<9 THEN PROCattach(note%+icon%-1,%111000)
      IF icon%>8 THEN PROCattach(rest%+icon%-9,%111000)
    ENDIF
  WHEN SharpsPane_h%:
    IF icon%>1 THEN
      CASE icon% OF
        WHEN 14,16,18,20:
        OTHERWISE:
          IF icon%=21 OR icon%=22 THEN icon%=19
          PROCtoolicon_click(SharpsPane_h%,icon%)
      ENDCASE      
      IF icon%<9 THEN PROCattach(accidental%+icon%-1,%1100000)
      IF icon%>8 AND icon%<12 THEN PROCattach(dot%+icon%-8,%1101000)
      IF icon%=12 THEN PROCattach(tie%,%1101000)
      IF icon%=13 THEN PROCattach_barline
      IF icon%=14 THEN CurrentMenu%=FNwimp_popup(SharpsPane_h%,Barline_h%,icon%)
      IF icon%=15 THEN PROCattach_clef
      IF icon%=16 THEN CurrentMenu%=FNwimp_popup(SharpsPane_h%,Clef_h%,icon%)
      IF icon%=17 THEN PROCattach(key%,%10)    :REM key signature
      IF icon%=18 THEN CurrentMenu%=FNwimp_popup(SharpsPane_h%,KeyMenu%,icon%)
      IF icon%=19 THEN PROCattach(time%,%1)    :REM time signature
      IF icon%=20 THEN PROCtimesig_init(icon%)
      IF icon%=23 THEN PROCattach(tie%+1,%1101000)
    ENDIF
  WHEN ScoreWind_h%:
    IF button%=4 THEN
      IF SCORING% AND SCRIBE%(drawn%) THEN PROCput_down
    ENDIF
    SYS "Wimp_SetCaretPosition",ScoreWind_h%,-1
ENDCASE
ENDPROC

DEF FNscore_keypress (key%)
LOCAL used%,x%,y%,mx%,my%
used%=TRUE
SYS "Wimp_GetPointerInfo",,Block%
mx%=Block%!0
my%=Block%!4
CASE key% OF
  WHEN &181:REM F1 (Help)
    OSCLI("Filer_Run <Maestro$Dir>.!Help")
  WHEN &183:REM F3 (Save)
    Block%!0=Save_h%
    SYS "Wimp_GetWindowState",,Block%
    x%=mx%-(Block%!12-Block%!4) DIV 2
    y%=my%+(Block%!16-Block%!8) DIV 2
    SYS "Wimp_CreateMenu",,Save_h%,x%,y%
  WHEN &185:REM F5 (Goto)
    Block%!0=Bar_h%
    SYS "Wimp_GetWindowState",,Block%
    x%=mx%-(Block%!12-Block%!4) DIV 2
    y%=my%+(Block%!16-Block%!8) DIV 2
    PROCgoto_init
    SYS "Wimp_CreateMenu",,Bar_h%,x%,y%
  WHEN &013:REM ^S (Staves)
    Block%!0=StaveWind_h%
    SYS "Wimp_GetWindowState",,Block%
    x%=mx%-(Block%!12-Block%!4) DIV 2
    y%=my%+(Block%!16-Block%!8) DIV 2
    PROCstaves_init(x%,y%) 
  WHEN &009:REM ^I (Instruments)
    Block%!0=InstrWind_h%
    SYS "Wimp_GetWindowState",,Block%
    x%=mx%-(Block%!12-Block%!4) DIV 2
    y%=my%+(Block%!16-Block%!8) DIV 2
    PROCinstruments_init(x%,y%)
  WHEN &180:REM Print
    Block%!0=Print_h%
    SYS "Wimp_GetWindowState",,Block%
    x%=mx%-(Block%!12-Block%!4) DIV 2
    y%=my%+(Block%!16-Block%!8) DIV 2
    PROCprint_init
    SYS "Wimp_CreateMenu",,Print_h%,x%,y%
  WHEN &1A2:REM Ctrl-F2
    PROCCloseWindow(ScoreWind_h%)
  OTHERWISE:used%=FALSE
ENDCASE
=used%

DEF PROCscore_update (x0%,y0%,x1%,y1%)
LOCAL more%
Block%!0=ScoreWind_h%
Block%!4=x0%
Block%!8=y0%
Block%!12=x1%
Block%!16=y1%
SYS "Wimp_UpdateWindow",,Block% TO more%
WHILE more%
  CLG
  PROCdraw_staves
  SYS "Wimp_GetRectangle",,Block% TO more%
ENDWHILE
ENDPROC

REM // Goto bar //

DEF PROCgoto_init
LOCAL bar%
Block%!0=ScoreWind_h%
SYS "Wimp_GetWindowState",,Block%
bar%=FNFindBar(Block%!20)
IF bar%<1 THEN bar%=1
PROCwimp_seticontext(Bar_h%,0,STR$(bar%))
PROCwimp_seticontext(Bar_h%,2,STR$(NBars%-1))
ENDPROC

DEF FNgoto_keypress (key%)
LOCAL used%
used%=TRUE
CASE key% OF
  WHEN &0D:REM Enter
    BAR%=VAL(FNwimp_geticontext(Bar_h%,0))
    IF BAR%>NBars% THEN
      BAR%=NBars%
    ELSE
      IF BAR%<0 THEN BAR%=0
    ENDIF
    Block%!0=ScoreWind_h%
    SYS "Wimp_GetWindowState",,Block%
    Block%!20=PX%(PXn%(BAR%))+4
    REM Set scroll to requested bar number
    SYS "Wimp_OpenWindow",,Block%
    LHBAR%=FNFindBar(Block%!20)
    PROCCloseWindow(Bar_h%)
    SYS "Wimp_CreateMenu",,-1
  WHEN &1A2:REM Ctrl-F2
    PROCCloseWindow(Bar_h%)
  OTHERWISE:used%=FALSE
ENDCASE
=used%

REM // Draw //

REM   PROCEDURE: draw_staves
REM
REM DESCRIPTION: Draw current stave structure
REM              1 stave for 1 voice
REM              2 staves for keyboard
REM              3 staves for 1 voice and keyboard
REM              4 staves for 4 voice chorus
:
DEF PROCdraw_staves
LOCAL Y%,T%,B%,S%,L%:REM Y%,T%,B%=Position & Y bounds of each stave, S%=Stave index, L%=Line position
LOCAL x%,y%,lx1%:REM Virtual coordinates of bottom left & top right of score window
LOCAL c%,t%,b%:REM Left edge of clip window and Y bounds
y%=Block%!16-Block%!24
x%=Block%!4-Block%!20
lx1%=x%+Score_Width%
IF lx1%>Block%!36 THEN lx1%=Block%!36
c%=Block%!28
IF c%<x%+Hi% THEN c%=x%+Hi%
b%=Block%!32
t%=Block%!40
B%=Score_Width%
T%=Block%!36-x%
IF T%<B% THEN B%=T%:REM Right bound
T%=Block%!28-x%
IF T%<0 THEN T%=0:REM Left bound
PROCdraw_score(x%,y%,T%,B%):REM Draw symbols on stave
IFPERC% THEN
FOR S%=STAVE%+1TOSTAVE%+PERC%
Y%=y%+Y_STAVE%(S%)
IFb%<=Y%ANDt%>=Y% LINEc%,Y%,lx1%,Y%
NEXT
ENDIF
FOR S%=0 TO STAVE%
Y%=y%+Y_STAVE%(S%):T%=Y%+Stave_Height%DIV2:B%=T%-Stave_Height%
IFb%<=T%ANDt%>=B% THEN
LINEc%,Y%,lx1%,Y%:REM Plot centre bar line
FORL%=Li%*2TOL%*2STEPL%
  LINEc%,Y%+L%,lx1%,Y%+L%:REM Plot upper line
  LINEc%,Y%-L%,lx1%,Y%-L%:REM Plot lower line
NEXT
ENDIF
NEXT
ENDPROC

REM   PROCEDURE: draw_score(Based X%,Y%, PX bounds A%,B%)
REM
REM DESCRIPTION: Write current section of music score that appears between A%
REM              and B%

DEF PROCdraw_score(X%,Y%,A%,B%)
LOCAL PX%
REM Must be subtle about redrawing last note (or other item) of
REM score to prevent picking up rubbish data after end
IF A%>PX%(PXn%(NBars%)) THEN
  IF A%>PX%(PXn%(NBars%))+2*Pgap% ENDPROC ELSE A%=PX%(PXn%(NBars%))
ENDIF
REM Ensure bar numbers are always completely updated,
REM but don't lose first note draw
BAR%=0
IF NBars%>2 THEN
  REM Move to the first bar to draw from
  WHILE PX%(PXn%(BAR%+2))<A% AND BAR%<=NBars%-1
    BAR%+=1
  ENDWHILE
  IF BAR%>NBars% THEN ENDPROC
  REM Move back two bars to ensure any note ties are picked up,
  REM then get bar pointers and update clipping window
  IF BAR%>2 THEN BAR%-=2
  PROCGetBarInfo(BAR%)
  IF PX%<A% THEN A%=PX%
  IF GP%>=GateBlock%+GateSize% THEN ENDPROC
  REM Skip music until A%
  WHILE PX%(PX%+3)<A% AND PX%<EX%
    PROCskip_gate
  ENDWHILE
ELSE
  PROCstart_music
ENDIF
WHILEPX%(PX%)<=B%ANDGP%<GateBlock%+GateSize%
IF?GP% PROCdraw_notes(?GP%):GP%+=1 ELSEPROCdraw_attribute(GP%?1):GP%+=2:IF PLAYING% PROCCheckQ
ENDWHILE
ENDPROC

REM   PROCEDURE: draw_notes(Gate G%)
REM
REM DESCRIPTION: Get notes from the indicated channel queues and draw the
REM              particular types of note in the correct places on the stave.
REM              One may be a rest, may have accidentals preceding it, may have
REM              dots following it, may be tied
REM
REM ASSUMPTIONS: Gate is non-zero

DEF PROCdraw_notes (g%)
LOCAL c%,n0%,n1%,n2%
LOCAL x%,y%,s%,line%,type%,offset%,barb%
REM Move to next position
PX%+=1
x%=X%+PX%(PX%)
REM Cycle through Notes in a Gate
c%=-1
REPEAT
  REPEAT c%+=1:UNTIL (g% AND 1<<c%)
  n0%=N%(c%)?0
  n1%=N%(c%)?1
  n2%=N%(c%)?2
  y%=Y%+Y_STAVE%(S_C%(c%))
  IF (n0% AND &F8) THEN
    REM Set up Note data
    line%=(n0%>>3)-16
    type%=n1%>>5
    s%=type% OR n0%<<3 AND &8
    offset%=0
    barb%=0
    IF (n2% AND &1) THEN
      REM Note is in a chord
      REM Check stagger position
      IF (n2% AND &8) THEN
        IF (n0% AND &1)=0 THEN offset%=X%(3)-4
        IF (n0% AND &1) THEN offset%=-X%(3)+4
      ENDIF
      REM If a stemmed note:
      REM - only draw barbs if at the 'barb-end' of a chord
      REM - use a reverse sprite if barbed note is staggered
      IF type%>1 THEN
        barb%=(n0% AND &1)+1
        IF (n2% AND 1<<barb%) THEN
          PROCdraw_cstem(g%,x%,y%+(line%*Li%),c%,type%,(n0% AND &1))
          IF (n2% AND &8) AND offset%<0 THEN s%=SPR_REVERSE%+(type%-2)
        ELSE
          IF type%=2 THEN s%=SPR_MHEAD% ELSE s%=SPR_CHEAD%
        ENDIF
      ENDIF
    ELSE
      REM Note is on its own: check stagger position
      IF (n2% AND &8) THEN offset%=X%(s%)-4
    ENDIF
    REM Draw note
    PROCsprite(s%,x%+offset%,y%+(line%*Li%))
    REM Draw ledger lines
    IF ABS(line%)>5 THEN PROCsprite(ledger%+(line% DIV 2),x%+offset%,y%)
    REM Draw accidentals
    IF (n1% AND &7) THEN
      PROCsprite(accidental% OR (n1% AND &7),x%+offset%-x%(s%),y%+(line%*Li%))
    ENDIF
  ELSE
    REM Draw rest
    line%=FNrest_getline(n1%)
    s%=(rest% OR n1%>>5)
    y%+=(Li%*2)*line%
    PROCsprite(s%,x%,y%)
  ENDIF
  REM Draw dots
  IF (n1% AND &18) THEN PROCsprite(dot%+(n1%>>3 AND &3),x%+x%(s%),y%+(line%*Li%))
  REM Draw tie
  IF (n0% AND &4) THEN PROCdraw_tie(tie%+((n0% AND &2)>>1),x%,y%+(line%*Li%),c%)
UNTIL (2<<c%)>g%
REM Increment pointers for all drawn Notes in Gate
c%=-1
REPEAT
  REPEAT c%+=1:UNTIL (g% AND 1<<c%)
  N%(c%)+=NSIZE%
UNTIL (2<<c%)>g%
ENDPROC

DEF PROCdraw_cstem (g%,x%,y%,c%,type%,orient%)
REM Draw the stem section of a chord
LOCAL nc%,norient%,ntype%,nl%,ny%,b%
REM We have the 'barb-end' note; find the 'note-end' note
IF orient%=0 THEN b%=2 ELSE b%=1
FOR nc%=0 TO 7
  IF (g% AND 1<<nc%) THEN
    IF (N%(nc%)?2 AND 1<<b%) THEN
      REM Now check same stave, type and orientation
      norient%=(N%(nc%)?0 AND &1)
      ntype%=N%(nc%)?1>>5
      IF norient%=orient% AND S_C%(nc%)=S_C%(c%) AND type%=ntype% THEN
        REM Draw stem between Notes
        nl%=(N%(nc%)?0>>3)-16
        ny%=Y%+Y_STAVE%(S_C%(nc%))+(Li%*nl%)
        IF b%=2 THEN MOVE x%+X%(3)-2,y%:DRAW x%+X%(3)-2,ny%
        IF b%=1 THEN MOVE x%,ny%:DRAW x%,y%
      ENDIF
    ENDIF
  ENDIF
NEXT
ENDPROC

DEF PROCdraw_tie (s%,sx%,sy%,c%)
REM Plot a scaled tie sprite
LOCAL span%,swidth%
LOCAL oldx0%,oldx1%,scale,scale%
sx%=sx%-x%(s%)
sy%=sy%-y%(s%)
swidth%=x%(s%)+X%(s%)
span%=X%+PX%(FNbound_tie(c%,PX%))-sx%+Pgap%
oldx0%=factors%!0
oldx1%=factors%!8
scale=span%/swidth%
IF scale<0.6 THEN scale=0.6
scale%=scale*10
factors%!0=oldx0%*scale%
factors%!8=oldx1%*10
SYS "OS_SpriteOp",SprPlot%,SprBlk%,S%(s%),sx%,sy%,8,factors%,pixtrans%
factors%!0=oldx0%
factors%!8=oldx1%
ENDPROC

DEF PROCdraw_attribute(A%)
LOCALx%,N%
N%=TRUE:REPEATN%-=TRUE:UNTILA%AND%1<<N%
IFPTYPE%(PX%)EOR%1<<N% PX%+=1
x%=X%+PX%(PX%)
ONN%+1 PROCdraw_time_sig(A%,x%),PROCdraw_key_sig(A%),PROCdraw_clef(A%),PROCdraw_slur(A%),PROCdraw_octave(A%),PROCdraw_barline(A%)
ENDPROC

DEF PROCPutBarInfo(bar%)
REM setup the various pointers to arrays at the start of this bar
FOR n%=0 TO 7
 BPn%(n%,bar%)=N%(n%)
 NEXT
PXn%(bar%)=PX%
GPn%(bar%)=GP%
FOR n%=0 TO Max_Stave%
 CLEFn%(n%,bar%)=CLEF%(n%)
 NEXT
SIGn%(0,bar%)=SIG%(0)
SIGn%(1,bar%)=SIG%(1)
ENDPROC

DEF PROCGetBarInfo(bar%)
REM get the values of the data at the start of this bar
FOR n%=0 TO 7
 N%(n%)=BPn%(n%,bar%)
 NEXT
PX%=PXn%(bar%)
GP%=GPn%(bar%)
FOR n%=0 TO Max_Stave%
 CLEF%(n%)=CLEFn%(n%,bar%)
 NEXT
SIG%(0)=SIGn%(0,bar%)
SIG%(1)=SIGn%(1,bar%)
ENDPROC

DEF PROCSetupBarStarts(bar%)
REM find pointers to various bits at the starts of all bars through
REM the music. Start at bar%
REM this is just to speed up redraw, avoiding all the note-skipping
LOCAL last%
BAR%=bar%
IF bar%>0 PROCGetBarInfo(bar%) ELSE PROCstart_music
last%=BAR%
PROCPutBarInfo(BAR%)
WHILE GP%<GateBlock%+GateSize%
 IF?GP% THEN
  PROCskip_notes(?GP%):GP%+=1
 ELSE
  PROCskip_attribute(GP%?1):GP%+=2
  IF BAR%<>last% last%=BAR% : PROCPutBarInfo(BAR%)
 ENDIF
 ENDWHILE
BAR%+=1
PROCPutBarInfo(BAR%)
NBars%=BAR%
ENDPROC

DEF PROCskip_gate
IF?GP% PROCskip_notes(?GP%):GP%+=1 ELSEPROCskip_attribute(GP%?1):GP%+=2
ENDPROC

DEF PROCskip_notes (g%)
LOCAL c%
PX%+=1
c%=-1
REPEAT
  REPEAT
    c%+=1
  UNTIL (g% AND 1<<c%)
  N%(c%)+=NSIZE%
UNTIL (2<<c%)>g%
ENDPROC

DEF PROCskip_attribute(A%)
LOCALT%
T%=%1:IFA%AND%1 ELSEREPEATT%=T%<<%1:UNTILA%ANDT%
CASET%OF
WHENTime%,Key%:SIG%(T%-1)=A%
WHENClef%:CLEF%(A%>>6)=A%>>3AND3
WHENBar%:BAR%+=1
ENDCASE
IFT%EORPTYPE%(PX%) PX%+=1
ENDPROC

DEF PROCback_gate
IFGP%?-2 GP%-=1:PROCback_notes(?GP%) ELSEGP%-=2:PROCback_attribute(GP%?1)
ENDPROC

DEF PROCback_notes (g%)
LOCAL c%
PX%-=1
c%=-1
REPEAT
  REPEAT
    c%+=1
  UNTIL (g% AND 1<<c%)
  N%(c%)-=NSIZE%
UNTIL (2<<c%)>g%
ENDPROC

DEF PROCback_attribute(A%)
IFA%=Bar% BAR%-=1
IFA%ANDPTYPE%(PX%+1)+TRUE ELSEIFA%ANDPTYPE%(PX%+1) ENDPROC
PX%-=1
ENDPROC

DEF PROCdraw_time_sig (a%,x%)
LOCAL i%,n%,b%,nx%,bx%
SIG%(0)=a%
n%=(a%>>1 AND 15)+1
b%=%1<<(a%>>5)-1
IF X%(time%+n%)+x%(time%+n%)<PW%(PX%) THEN nx%=x%+12 ELSE nx%=x%
nx%+=x%(time%+n%)
IF X%(time%+b%)+x%(time%+b%)<PW%(PX%) THEN bx%=x%+12 ELSE bx%=x%
bx%+=x%(time%+b%)
FOR i%=0 TO STAVE%+PERC%
  PROCsprite(time%+n%,nx%,Y%+Y_STAVE%(i%))
  PROCsprite(time%+b%,bx%,Y%+Y_STAVE%(i%)-32)
NEXT
ENDPROC  

DEF PROCdraw_key_sig(A%)
LOCALS%,C%,N%,a%,W%
IFA%AND56 SIG%(1)=A% ELSESWAPA%,SIG%(1):a%=accidental%+1:REM Change to C Major uses naturals in old key sig positions
N%=(A%>>3AND7)-1
IFN%>=0 THEN
A%=A%>>2AND%1:REM 0-Sharp or 1-flat signature
IFa% ELSEa%=accidental%+2+A%
W%=x%(a%)+X%(a%)
x%+=x%(a%)
FOR C%=0 TO N%
FOR S%=0TOSTAVE%
PROCsprite(a%,x%,Y%+Y_STAVE%(S%)+Li%*Key_Y%(CLEF%(S%),A%,C%))
NEXT
x%+=W%
NEXT
ENDIF
ENDPROC

DEF PROCdraw_clef(A%)
LOCALS%
S%=A%>>6
CLEF%(S%)=A%>>3AND3
IFS%<=STAVE% PROCsprite(clef%+CLEF%(S%),x%,Y%+Y_STAVE%(S%))
ENDPROC

DEF PROCdraw_slur(A%)
ENDPROC

DEF PROCdraw_octave(A%)
ENDPROC

DEF PROCdraw_barline (a%)
LOCAL s%,type%,by%,bx%
REM Draw barline sprite
type%=a%>>6
BAR%+=1
FOR s%=0 TO STAVE%+PERC%
  PROCsprite(bar%+type%,x%,Y%+Y_STAVE%(s%))
NEXT
REM Print bar number
IF BAR% MOD 5=0 THEN
  MOVE x%,Y%+Y_STAVE%(0)+Stave_Height%+20*Vi%
  PRINT STR$(BAR%)
ENDIF
REM Connect keyboard staves
bx%=x%-x%(bar%+type%)
IF (STAVE%+1) AND 2 THEN
  MOVE bx%,Y%+Y_STAVE%(STAVE%)+Stave_Height% DIV 2
  by%=Y_STAVE%(STAVE%-1)-Y_STAVE%(STAVE%)-Stave_Height%
  DRAW BY 0,by%
  IF type%=1 THEN
    RECTANGLE FILL bx%+6,Y%+Y_STAVE%(STAVE%)+Stave_Height% DIV 2,5,by% 
  ENDIF
ENDIF
ENDPROC

REM // Put //

DEF PROCput_down
LOCALC%,PX%,X%,Y%,S%,s%
SYS "Hourglass_On"
GP%=SCRIBE%(sgp%)
FORC%=0TO7:N%(C%)=SCRIBE%(C%):NEXT
PX%=SCRIBE%(posx%)
X%=SCRIBE%(sx%)
Y%=SCRIBE%(sy%)
s%=SCRIBE%(sprite%)
S%=SCRIBE%(stave%)
C%=SCRIBE%(sc%)
PROCrelease:REM undraw sprite being dragged
IF s%<accidental% THEN PROCput_note
IF s%>=accidental% AND s%<clef% THEN PROCput_accidental
IF s%>=clef% AND s%<=clef%+3 THEN PROCput_clef
IF s%>dot% AND s%<=dot%+3 THEN PROCput_dot
IF s%>=bar% AND s%<=bar%+3 THEN PROCput_bar
IF s%>=tie% AND s%<=tie%+1 THEN PROCput_tie
IF s%=time% THEN PROCput_time
IF s%=key% THEN PROCput_key
GP%=SCRIBE%(sgp%)
FORC%=0TO7:N%(C%)=SCRIBE%(C%):NEXT
PROCarrange_stave(SCRIBE%(stave%))
IF PX%(EX%)+200*Hi%>S_Width% PROCSetExtent(PX%(EX%)+200*Hi%)
PROCChangedScore
SYS "Hourglass_Off"
ENDPROC

DEF PROCput_note
LOCAL end%,type%
IF s%<rest% THEN type%=note% ELSE type%=rest%
IF X%=PX%(PX%+1) THEN
  REM Insert at end of score or within an existing Gate
  end%=(GP%=EP%)
  IF end% THEN
    IF NOT FNinsert_gate(1) THEN ENDPROC
  ELSE
    C%=FNput_conflict(S%,SCRIBE%(line%),type%)
  ENDIF
  IF end% OR C%=-1 THEN
    C%=FNallocate_channel(S%)
    IF C%>=0 THEN
      IF NOT FNinsert_note(C%) THEN ENDPROC
      PROCnote_type(C%,s% AND 7)
      PROCnote_line(C%,SCRIBE%(line%),s%)
      IF type%=note% THEN PROCnote_stem(C%,s% AND 8)
      PROCupdate_note
    ENDIF
  ELSE
    REM Only notes delete notes/rests delete rests
    IF ((?N%(C%) AND &F8) AND type%=note%) OR ((?N%(C%) AND &F8)=0 AND type%=rest%) THEN
      PROCdelete_note(C%)
      REM Check if previous Note has a tie; if so, remove it
      IF N%(C%)>NoteBlock%(C%) THEN
        N%(C%)-=NSIZE%
        IF (?N%(C%) AND &4) THEN PROCnote_tie(C%,0)
        N%(C%)+=NSIZE%
      ENDIF
      IF ?GP% THEN
        REM Delete within a Gate
        PROCupdate_note
      ELSE
        REM Note is last in Gate; delete whole Gate
        PROCdelete_gate(1)
        IF GP%?-2=0 THEN
          WHILE ?GP%=0 AND GP%<EP%
            PROCdelete_gate(2)
          ENDWHILE
        ENDIF
        PROCrescore(PX%)
      ENDIF
    ENDIF
  ENDIF
ELSE
  REM Insert between notes
  IF X%>PX%(PX%+1) THEN PROCskip_gate
  IF NOT FNinsert_gate(1) THEN ENDPROC
  C%=FNallocate_channel(S%)
  IF NOT FNinsert_note(C%) THEN ENDPROC
  PROCnote_type(C%,s% AND 7)
  PROCnote_line(C%,SCRIBE%(line%),s%)
  IF type%=note% THEN PROCnote_stem(C%,s% AND 8)
  PROCrescore(PX%)
ENDIF
ENDPROC

DEF FNput_conflict (s%,l%,type%)
REM Check if the placed note/rest is over an existing note/rest
REM Returns channel number (0-7) if conflict found, otherwise -1
LOCAL c%,pline%,conflict%
REM Convert the line value of the placed note/rest to 1-32
IF type%=rest% THEN l%=l%*2
l%+=16
REM Check against converted line values of already placed notes/rests
c%=7
REPEAT
  IF (?GP% AND 1<<c%) AND S_C%(c%)=s% THEN
   IF (?N%(c%) AND &F8) THEN
      REM Already-placed object is a note
      pline%=(?N%(c%)>>3)
    ELSE
      REM Already-placed object is a rest
      pline%=(N%(c%)?1 AND &7)
      IF pline%=0 THEN pline%=4
      pline%=(pline%-4)*2+16
    ENDIF
  ENDIF
  conflict%=(pline%=l%)
  c%-=1
UNTIL conflict% OR c%<0
=c%-(conflict%<>0)

DEF PROCput_tie
LOCAL c%,tx%
IF (?N%(C%) AND &4) THEN
  REM Remove a tie from the note and redraw
  PROCnote_tie(C%,0)
  tx%=FNbound_tie(C%,SCRIBE%(posx%)+1)
  PROCscore_update(X%,Y%-26,X%+PX%(tx%),Y%+28)
ELSE
  REM Check that first note isn't a rest
  IF (?N%(C%) AND &F8)=0 THEN ENDPROC
  REM Place a tie on the first note
  PROCnote_tie(C%,SCRIBE%(sprite%)-(tie%-1))
  REM Arrange channel distribution on the stave so that the next note
  REM appearing on the same stave line (if present) is also the next
  REM note in the Note queue for that channel
  PROCskip_notes(?GP%):GP%+=1
  PROCarrange_stave(S%)
  REM Check that there's a second note on the same staveline to tie to.
  REM If there is, mark the area to redraw; if not, remove the tie.
  GP%=SCRIBE%(sgp%)
  FOR c%=0 TO 7
    N%(c%)=SCRIBE%(c%)
  NEXT
  tx%=FNbound_tie(C%,SCRIBE%(posx%)+1)
  IF tx% THEN
    PROCscore_update(X%,Y%-26,X%+PX%(tx%)+70,Y%+28)
  ELSE
    PROCnote_tie(C%,0)
  ENDIF
ENDIF
ENDPROC

DEF PROCput_accidental
LOCALA%,a%
a%=N%(C%)?1AND7
A%=s%AND7
IFA%=a% A%=0
PROCnote_accidental(C%,A%)
PROCupdate_note
ENDPROC

DEF PROCput_dot
LOCALD%,d%
d%=N%(C%)?1>>3AND3
D%=s%AND3
IFD%=d% D%=0
PROCnote_dots(C%,D%)
IFD%*d% THEN
s%=N%(C%)?1>>5OR?N%(C%)<<3AND8:IF?N%(C%)AND&F8 ELSEs%=s%ORrest%
X%+=x%(s%)
ENDIF
PROCupdate_note
ENDPROC

DEF PROCput_clef
LOCALc%
c%=s%AND3
IF GP%=EP% OR ?GP% OR (GP%?1 AND %111)<>Clef% THEN
  IF FNinsert_gate(2) THEN PROCclef(S%,c%)
ELSE
WHILE ?GP%=0AND(GP%?1AND%111)=Clef%AND(GP%?1>>6)<>S%ANDGP%<EP%
PROCskip_gate
ENDWHILE
IFGP%<EP%AND?GP%=0AND(GP%?1AND%111)=Clef%AND(GP%?1>>6)=S% THEN
IF(GP%?1>>3AND3)<>c% PROCclef(S%,c%) ELSEPROCdelete_gate(2)
ELSE
  IF FNinsert_gate(2) THEN PROCclef(S%,c%)
ENDIF
ENDIF
PROCrescore(PX%)
ENDPROC

DEF PROCput_key
IF GP%=EP% OR ?GP% OR (GP%?1 AND %11)<>Key% THEN
  IF FNinsert_gate(2) THEN PROCkey_sig(KEY_SIG%(0),KEY_SIG%(1))
ELSE
IFKEY_SIG%(1)>0AND(GP%?1>>2AND%1)<>KEY_SIG%(0)OR(GP%?1>>3)<>KEY_SIG%(1) PROCkey_sig(KEY_SIG%(0),KEY_SIG%(1)) ELSEPROCdelete_gate(2)
ENDIF
PROCrescore(PX%)
ENDPROC

DEF PROCput_time
IF GP%=EP% OR ?GP% OR (GP%?1AND%1)<>Time% THEN
  REM Insert a new Time Signature
  IF FNinsert_gate(2) THEN PROCtime_sig(TIME_SIG%(0),TIME_SIG%(1))
ELSE
  IF (GP%?1>>1AND15)<>TIME_SIG%(0) OR (GP%?1>>5)<>TIME_SIG%(1) THEN
    REM Overwrite new values on an existing Time Signature
    PROCtime_sig(TIME_SIG%(0),TIME_SIG%(1))
  ELSE
    REM Delete the Time Signature
    PROCdelete_gate(2)
  ENDIF
ENDIF
PROCrescore(PX%)
ENDPROC

DEF PROCput_bar
IF (?GP% OR (GP%?1 AND %111111)<>Bar%) AND X%>PX%(PX%+1) AND GP%<EP% THEN
  PROCskip_gate
ELSE
  IF GP%?-2=0 AND (GP%?-1 AND %111111)=Bar% THEN PROCback_gate
ENDIF
IF GP%>GateBlock% THEN
  IF GP%<EP% AND ?GP%=0 AND (GP%?1 AND %111111)=Bar% THEN
    PROCdelete_gate(2)
    NBars%-=1
    WHILE ?GP%=0 AND GP%<EP%
      PROCdelete_gate(2)
    ENDWHILE
    PROCrescore(PX%)
  ELSE
    IF GP%?-2 THEN
      IF FNinsert_gate(2) THEN
        PROCbar
        NBars%+=1
        PROCrescore(PX%)
      ENDIF
    ENDIF
  ENDIF
ENDIF
ENDPROC

DEF PROCrescore(px%)
IFpx% ELSEPROCstart_music:REM Bar position assumed set, but reset if px%=0
PROCset_score(px%)
IF px%=0 PROCSetupBarStarts(0) ELSE PROCSetupBarStarts(FNFindBar(px%))
PROCscore_update(PX%(px%),-Score_Height%,Score_Width%,0)
ENDPROC

DEF PROCupdate_note
REM Re-typeset score when note width changes
LOCAL oldx%,newx%,shift%,px%
px%=PX%
REM Update current note width
oldx%=PX%(PX%+1)+PW%(PX%+1)+Pgap%
PROCset_notes(GP%?0)
REM Update following symbol x positions
newx%=PX%(PX%)+PW%(PX%)+Pgap%
shift%=newx%-oldx%
IF shift% THEN
  GP%+=1
  WHILE GP%<GateBlock%+GateSize%
    PX%+=1
    PX%(PX%)+=shift%
    IF ?GP% THEN GP%+=1 ELSE GP%+=2
  ENDWHILE
  REM Update final GP%/PX% markers
  EP%=GP%
  EX%=PX%
  REM Set attributes for next symbol
  PX%(PX%+1)=PX%(PX%)+PW%(PX%)+Pgap%
  PXn%(NBars%)=PX%
  PTYPE%(PX%+1)=Note%
ENDIF
REM Cache bar starts and update Score window
IF px%=0 THEN
  PROCSetupBarStarts(0)
ELSE
  PROCSetupBarStarts(FNFindBar(px%))
ENDIF
PROCscore_update(PX%(px%),-Score_Height%,Score_Width%,0)
ENDPROC

DEF PROCattach(s%,V%)
SCRIBE%(sx%)=TRUE:REM New position (Maintain Y for orientation maintainance)
SCRIBE%(drawn%)=FALSE
IFs%<rest%ANDSCRIBE%(sprite%)<rest% s%=s%AND7ORSCRIBE%(sprite%)AND8:REM Maintain note orientation
SCRIBE%(sprite%)=s%
SCRIBE%(valid%)=V%
SCORING%=TRUE
ENDPROC

DEF PROCrelease
IF SCORING% AND SCRIBE%(drawn%) THEN
  REM Undraw sprite
  PROCfloat(SCRIBE%(sprite%),SCRIBE%(sx%),SCRIBE%(sy%))
  IF SCRIBE%(sprite%)<rest% THEN
    IF ABSSCRIBE%(line%)>5 THEN
      REM Undraw ledger lines
      PROCfloat(ledger%+SCRIBE%(line%)DIV2,SCRIBE%(sx%),Y_STAVE%(SCRIBE%(stave%)))
    ENDIF
  ENDIF
  SCRIBE%(sx%)=TRUE:REM Continue drawing of symbol
  SCRIBE%(drawn%)=FALSE
ENDIF
ENDPROC

REM   PROCEDURE: scribe(At X%,Y%)
REM
REM DESCRIPTION: Draw current music symbol at position over score window

DEF PROCscribe(X%,Y%)
LOCAL S%,L%,C%,A%
Block%!0=ScoreWind_h%
SYS "Wimp_GetWindowState",,Block%
X%-=Block%!4-Block%!20
Y%-=Block%!16-Block%!24
PROCproximate(SCRIBE%(valid%))
IF X%<>SCRIBE%(sx%) OR Y%<>SCRIBE%(sy%) THEN
  A%=TRUE
  IF (SCRIBE%(valid%) AND &70)=&60 THEN
    A%=FALSE
    IF C%>=0 THEN A%=SCRIBE%(valid%) AND &8 OR S%<=STAVE% AND ?N%(C%) AND &F8
  ENDIF
  IF A% THEN
    IF SCRIBE%(drawn%) THEN
      PROCfloat(SCRIBE%(sprite%),SCRIBE%(sx%),SCRIBE%(sy%))
      IF SCRIBE%(sprite%)<rest% THEN
        IF ABS(SCRIBE%(line%))>5 THEN
          PROCfloat(ledger%+SCRIBE%(line%) DIV 2,SCRIBE%(sx%),Y_STAVE%(SCRIBE%(stave%)))
        ENDIF
      ENDIF
    ELSE
      SCRIBE%(drawn%)=TRUE
    ENDIF
    IF SCRIBE%(sprite%)<rest% THEN
      IF ABS(L%)>5 THEN PROCfloat(ledger%+L% DIV 2,X%,Y_STAVE%(S%))
      IF Y%<>SCRIBE%(sy%) THEN
        SCRIBE%(sprite%)=SCRIBE%(sprite%) AND 7 OR 8 AND Y%>SCRIBE%(sy%)
      ENDIF
    ENDIF
    SCRIBE%(sx%)=X%
    SCRIBE%(sy%)=Y%
    SCRIBE%(stave%)=S%
    IF S%<=Max_Stave% THEN
      SCRIBE%(sclef%)=CLEF%(S%)
    ELSE
      SCRIBE%(sclef%)=CLEF%(Max_Stave%)
    ENDIF  
    SCRIBE%(line%)=L%
    SCRIBE%(posx%)=PX%
    SCRIBE%(sgp%)=GP%
    SCRIBE%(sc%)=C%
    FOR C%=0 TO 7
      SCRIBE%(C%)=N%(C%)
    NEXT
    PROCfloat(SCRIBE%(sprite%),X%,Y%)
  ENDIF
ENDIF
ENDPROC

REM PROCEDURE:   proximate(Positions valid mask V%)
REM
REM DESCRIPTION: Will set X%,Y% to be the nearest valid position to the point
REM              X%,Y% supplied (all in stave coordinates) and return the
REM              associated position index PX%.
REM
REM              A mask is supplied to indicate the valid positions:
REM              V% bit  0 -> Valid Time signature positions
REM                      1 -> Valid Key signature positions
REM                      2 -> Valid Clef positions
REM                      3 -> At note/rest positions
REM                      4 -> In between notes
REM                      5 -> To nearest stave line (else nearest stave)
REM                      6 -> To nearest symbol present
REM                      7 -> On bar line if near
REM
REM SETS:        S%        Stave
REM              L%        Line(-15..15)
REM              C%        Channel of note
REM              GP%,N%()  Gate and Note pointers
REM              PX%       PX%() index
REM              X%,Y%     Position

DEF PROCproximate(V%):REM (V%, var X%,Y%) = (S%,L%,PX%)
LOCAL d%,D%:REM Distance previous&current
LOCAL px%,x%:REM Previous PX%, Between note X position
LOCAL gp%:REM Previous gate pointer, key sig
LOCAL T%:REM Type mask
IF NBars%>4 THEN BAR%=LHBAR% ELSE BAR%=0
REM Start from start of page
PROCGetBarInfo(BAR%)
D%=4*S_Width%:REM A suitably excessive distance
IF (V% AND &7) THEN
  REM Set positions for Time Signatures, Key Signatures and Clefs
  REM Pointer points to centre of object  
  X%-=X%(clef%)>>1
  REM Valid Clef/Key/Time: Work out types to ignore after bars 
  T%=-V%<<1 AND &6
  REM Repeat until distance increases or no more positions
  REPEAT
    REM Save score pointers and distance
    PROCsavp:d%=D%
    REM Move to next bar PX%
    REPEAT
      PROCskip_gate
    UNTIL PTYPE%(PX%) AND Bar% OR GP%=EP%
    IF PTYPE%(PX%) AND Bar% THEN
      REM Skipping any T% types
      WHILE PTYPE%(PX%+1) AND T% AND GP%<EP%
        REM Skip all clefs
        REPEAT
          PROCskip_gate
        UNTIL GP%=EP% OR GP%?1 AND %111 EOR %100
      ENDWHILE
      REM Calculate distance to this position
      D%=ABS(X%-PX%(PX%+1))
    ELSE
      REM No more bars
      D%=2*S_Width%
    ENDIF
  UNTIL D%>d% OR GP%=EP%
  REM Previous position may be closer; if so, reset
  IF d%<D% THEN PROCrstp
  REM Set as position
  X%=PX%(PX%+1)
ELSE
  REM Set x positions for Notes/Rests
  REM Pointer points to centre of note
  X%-=X%(2)>>1
  REM Repeat until distance increases or no more notes
  REPEAT
    REM Save score pointers and distance
    PROCsavp:d%=D%
    REM Move to next symbol
    PROCskip_gate
    REM Skip any attribute types
    WHILE PTYPE%(PX%+1) AND GP%<EP%
      PROCskip_gate
    ENDWHILE
    REM Calculate distance to this note position
    D%=ABS(X%-PX%(PX%+1))
  UNTIL D%>d%ORGP%=EP%
  REM Previous position may be closer (or the last valid note 
  REM position for an accidental/dot to appear); if so, reset 
  IF d%<D% OR GP%=EP% AND (V% AND &40) THEN PROCrstp
  REM If mask setting: To Nearest Stave Line
  IF (V% AND &10) AND GP%<EP% THEN
    REM Valid between notes (if before last position)
    IF X%<PX%(PX%+1) OR X%>PX%(EX%) THEN
      REM Before current: between previous (also if after last note)
      IF PTYPE%(PX%) THEN
        x%=PX%(PX%)+PW%(PX%)-X%(2)
      ELSE
        x%=PX%(PX%)
      ENDIF
      x%=x%+PX%(PX%+1)>>1
      IF (V% AND &80) THEN x%+=8:IF PTYPE%(PX%)=Bar% THEN x%=PX%(PX%)
    ELSE
      REM After current: between next
      x%=PX%(PX%+1)+PX%(PX%+2)>>1
      IF (V% AND &80) THEN x%+=8:IF PTYPE%(PX%+2)=Bar% THEN x%=PX%(PX%+2)
    ENDIF
    REM If mask setting: At Notes, find closest position
    IF (V% AND &8) AND ABS(PX%(PX%+1)-X%)<ABS(x%-X%) THEN
      X%=PX%(PX%+1)
    ELSE
      X%=x%
    ENDIF
  ELSE
    REM Only At Notes: find closest position
    X%=PX%(PX%+1)
  ENDIF
ENDIF
C%=-1         :REM Initially no channel
L%=0          :REM Initially centre line
res%=1        :REM Resolution setting for Rest placement
IF (V% AND &40) AND ?GP%>0 THEN
  REM Set y position of Accidentals, Dots and Ties
  REM Find nearest symbol at gate
  LOCAL G%,c%:REM Gate mask copy, Previous channel counter
  c%=C%
  d%=2*S_Height%:REM A suitably excessive distance
  G%=?GP%
  REPEAT
    REPEAT
      C%+=1
    UNTIL G% AND %1<<C%
    D%=Y%-Y_STAVE%(S_C%(C%))
    IF (?N%(C%) AND &F8) THEN
      REM Note
      D%=ABS(D%-Li%*((?N%(C%)>>3)-16))
    ELSE
      REM Rest
      D%=ABS(D%-Li%*FNrest_getline(N%(C%)?1))
    ENDIF
    IF D%<d% THEN d%=D%:c%=C%
  UNTIL (2<<C%)>G%
  C%=c%:REM Nearest note
  S%=S_C%(C%)
  IF (?N%(C%) AND &F8) THEN
    REM Note
    L%=(?N%(C%)>>3)-16
  ELSE
    REM Rest
    L%=FNrest_getline(N%(C%)?1)
    res%=2
  ENDIF
ELSE
  REM Set y positions of other objects
  REM First find the correct stave
  LOCAL MS%:REM Highest stave number
  MS%=STAVE%
  REM If object is Clef or Key Signature,
  REM do not include percussion stave
  IF (V% AND &6) THEN
  ELSE
    MS%+=PERC%
  ENDIF
  S%=-1:REM Before first stave
  D%=2*S_Height%:REM A suitably excessive distance
  REM Repeat until distance increases or no more staves
  REPEAT
    d%=D%:REM Copy previous distance
    S%+=1:REM Next stave
    D%=ABS(Y%-Y_STAVE%(S%)):REM Distance between point and current stave
  UNTIL D%>d% OR S%=MS%
  S%+=d%<D%:REM Previous stave was closest unless last&current stave is nearer
  REM Now set y position within stave, if a Note/Rest
  REM Ledger lines take priority over percussion lines
  IF (V% AND &20) THEN
    IF S%=STAVE%+1 THEN S%+=Y%>=Y_STAVE%(STAVE%)-Li%*16
    IF S%<=STAVE% THEN
      IF SCRIBE%(sprite%)>=rest% THEN
        REM Rest
        REM Y% is L% steps away from centre stave, step=Li%*2
        L%=(Y%-Y_STAVE%(S%))/(Li%*2)+8.75
        L%-=8
        res%=2
        REM No further than 3 lines away
        IF ABS(L%)>3 THEN L%=3*SGN(L%)
      ELSE
        REM Note
        REM Y% is L% steps away from centre stave, step=Li%
        L%=(Y%-Y_STAVE%(S%))/Li%+16.75
        L%-=16
        REM No further than 15 lines away
        IF ABS(L%)>15 THEN L%=15*SGN(L%)
      ENDIF
    ENDIF
  ENDIF
ENDIF
REM Set Y% to centre line of stave, +/- offset, * resolution
Y%=Y_STAVE%(S%)+L%*(Li%*res%)
ENDPROC

DEF FNrest_getline (n%)
LOCAL line%
line%=(n% AND &7)
IF line%>0 THEN line%-=4
=line%

DEF PROCsavp
n%()=N%()
px%=PX%
gp%=GP%
clef%()=CLEF%()
sig%()=SIG%()
ENDPROC

DEF PROCrstp
N%()=n%()
PX%=px%
GP%=gp%
CLEF%()=clef%()
SIG%()=sig%()
ENDPROC

DEF PROCSetTempo (t%)
Tempo%=t%
SYS "Sound_QTempo",Tempo%(t%)*128*4096 DIV 6000
PROCwimp_radiotick(TempoMenu%,t%)
ENDPROC

DEF PROCSetVolume (v%)
SYS "Sound_Volume",Volume%(v%)
PROCwimp_radiotick(VolumeMenu%,v%)
ENDPROC

DEF PROCSetKeySig (m%,k%)
LOCAL n%
KEY_SIG%(0)=(k%>=7)+1
KEY_SIG%(1)=ABS(k%-7)    
IF KEY_SIG%(1) THEN
  n%=accidental%+2+KEY_SIG%(0)
  X%(key%)=(x%(n%)+X%(n%))*KEY_SIG%(1)
ELSE
  X%(key%)=x%(accidental%+2)+X%(accidental%+2)
ENDIF
PROCwimp_radiotick(KeyMenu%,m%)
CASE m% OF
  WHEN 0:REM Major menu      
    PROCwimp_radiotick(MajorMenu%,k%)
    PROCwimp_radiotick(MinorMenu%,-1)
  WHEN 1:REM Minor menu
    PROCwimp_radiotick(MajorMenu%,-1)
    PROCwimp_radiotick(MinorMenu%,k%)
ENDCASE
IF FNwimp_geticonstate(SharpsPane_h%,17,2) THEN PROCattach(key%,%10)
ENDPROC  

DEF PROCSetTimeSig (n%,b%)
TIME_SIG%(0)=n%
TIME_SIG%(1)=b%
PROCwimp_seticonval(SharpsPane_h%,21,"S"+STR$(TIME_SIG%(0)+1))
PROCwimp_seticonval(SharpsPane_h%,22,"S"+STR$(2^(TIME_SIG%(1)-1)))
IF FNwimp_geticonstate(SharpsPane_h%,19,2) THEN PROCattach(time%,%1)
ENDPROC

REM given x position, return number of bar
DEF FNFindBar(xpos%)
LOCAL bar%
bar%=0
WHILE PX%(PXn%(bar%))<xpos% AND bar%<=NBars%:bar%+=1:ENDWHILE
IF bar%>0 bar%-=1
=bar%

REM // Play //

DEF PROCplay_start
LOCALC%,n%
SYS "Sound_Configure",8
REM set play start bar PBAR% to start of currently displayed window (scx%)
Window%!handle%=ScoreWind_h%
SYS "Wimp_GetWindowState",,Window%+handle%
PBAR%=FNFindBar(Window%!scx%)
PP%=GPn%(PBAR%)
FOR n%=0 TO 7
 P%(n%)=BPn%(n%,PBAR%)
 NEXT
FORC%=0TO3
PCLEF%(C%)=Clef%(CLEFn%(C%,PBAR%))
NEXT
PROCplay_key_sig(SIGn%(1,PBAR%))
PLAYING%=TRUE
SCROLLING%=TRUE
Beats%=((SIGn%(0,PBAR%)>>1AND&F)+1)*Length%(SIGn%(0,PBAR%)>>3AND%11100)
Q%()=Beats%
TIE%=&FF
B2%=&10000
SYS "Sound_QBeat",Beats%
SYS "Sound_QSchedule",Beats%,Sch% OR Sound_QTempo%,Tempo%(Tempo%)*128*4096DIV6000
C%=Beats%/50*&1000:IFC%>&7FFF C%=&7FFF
SYS "Sound_QTempo",C%
MIDI_OFF%(0,0) = -1
MIDI_OFF%(1,0) = -1
PROCwimp_setmenustate(MainMenu%,3,0,TRUE)
PROCwimp_setmenustate(EditMenu%,0,22,TRUE)
ENDPROC

REM flush sound queue and ensure note_offs are sent to midi channels
DEFPROCplay_stop
LOCAL note%, n%
PLAYING%=FALSE
SYS "Sound_QInit"
REM Handle MIDI
IF OutputType%=1 THEN
 FOR n%=0 TO 1
  note%=0
  WHILE (MIDI_OFF%(n%,note%) > 0)
   SYS MIDI_TxCommand%, MIDI_OFF%(n%,note%)
   note%+=1
   ENDWHILE
  NEXT
 REM reset end markers
 MIDI_OFF%(0,0) = -1
 MIDI_OFF%(1,0) = -1
ENDIF
PROCwimp_setmenustate(MainMenu%,3,0,FALSE)
PROCwimp_setmenustate(EditMenu%,0,22,FALSE)
ENDPROC

REM This should give a reasonable approximation to a smooth scroll
DEF PROCCheckScroll
LOCAL PosX%, WindowWidth%, BarPos%, LastScroll%,ThisScroll%
IF ScoreClosed% THEN
 IF NOT PLAYING% SCROLLING%=FALSE
 ENDPROC
 ENDIF
IF PBAR%<=2 ENDPROC
IF PLAYING% PROCCheckQ
IF PLAYING% SBAR%=PBAR%-2 ELSE B1%=B2%:B2%=BEAT:IFB2%<B1%:SBAR%+=1
IF SBAR%>=NBars% OR SBAR%>PBAR%+2 SCROLLING%=FALSE:PROCplay_stop:ENDPROC
Block%!0=ScoreWind_h%
SYS "Wimp_GetWindowState",,Block%
WindowWidth%=Block%!12-Block%!4
LastScroll%=Block%!20
PosX%=PX%(PXn%(SBAR%))
BarPos%=(PX%(PXn%(SBAR%+1))-PosX%)*BEAT DIV Beats%
IF BarPos%*2<Pgap% ENDPROC :REM if small amount into bar can scroll to wrong bar (sync with play)
PosX%+=BarPos%
ThisScroll% = PosX%-WindowWidth%DIV2
IF ThisScroll%<=0 ENDPROC
IF ThisScroll%<>LastScroll% THEN
 REM auto-scroll
 REM divide scroll into small bits to make it appear smooth
 Block%!20=(LastScroll%+ThisScroll%)/2
 SYS "Wimp_OpenWindow",,Block%
 Block%!20=ThisScroll%
 OldX%=ThisScroll%
 SYS "Wimp_OpenWindow",,Block%
 LHBAR%=FNFindBar(ThisScroll%)-1 :REM the bar number at the left of the window
 IF PLAYING%=FALSE THEN
   REM stop scrolling at end of music
   IF Block%!20<=LastScroll% THEN SCROLLING%=FALSE   
 ENDIF
ENDIF
ENDPROC

DEF PROCplay_bar
LOCALC%,L%,I%,D%,S%,Q%,T%,B%,A%:REM Vars for play_bar & play_notes
LOCAL f,end%
end%=GateBlock%+GateSize%
MIDI_Notenum%=0
Q%()=Beats%:REM Reset stave queue counters for next bar
B%=PBAR%
Accidental%()=0:REM No incidental accidentals initially
First%=TRUE
WHILEB%=PBAR%ANDPP%<end%
IF?PP% PROCplay_notes(?PP%):PP%+=1 ELSEPROCplay_attribute(PP%?1):PP%+=2
First%=FALSE
ENDWHILE
IFPP%>=end% THEN
  PLAYING%=FALSE:SBAR%+=1
  PROCwimp_setmenustate(MainMenu%,3,0,FALSE)
  PROCwimp_setmenustate(EditMenu%,0,22,FALSE)
ENDIF
ENDPROC

REM Notes for each gate start playing at Q%; the latest Q%() of the staves on
REM which notes are to play. This is because all notes in a gate play at the
REM same time and cannot start before the shortest of the notes preceding them
REM have finished.
REM Q% is the time at which the notes in the gate are played
REM Q%(S%) is the time at the end of the shortest note played on stave S%.
REM QI%(S%) is the current shortest note beats interval on stave S%

DEF PROCplay_notes(G%)
LOCAL note%, packed%, Qoff%,xtraVol,tempo
tempo = 5 * 128 * Tempo%(Tempo%) / 6000
Q%=FALSE:C%=TRUE
REPEATREPEATC%-=TRUE:UNTILG%AND%1<<C%
IFQ%(S_C%(C%))>Q% Q%=Q%(S_C%(C%))
UNTIL(2<<C%)>G%
QI%()=&10000:C%=TRUE
REPEAT
  IF First% xtraVol=1.01 ELSE xtraVol=1.0 :REM a little dynamics. stress 1st beat in bar very slightly
  REPEATC%-=TRUE:UNTILG%AND%1<<C%
  IF INT(xtraVol*Volume%(Volumes%(C%)))>&7F xtraVol=1.0  :REM ensure no overflow
  T%=?P%(C%):D%=P%(C%)?1:I%=D%>>3:S%=S_C%(C%):L%=T%>>3:A%=0
  IFL%ANDS%<=STAVE% THEN
    IFD%AND7 Accidental%(S%,L%)=D%AND7
    A%=Accidental%(S%,L%):L%+=PCLEF%(S%):IFA% ELSEA%=Key%(L%MOD7):REM If no accidental then revert to key signature
  ENDIF
  IFTIE%AND%1<<C% THEN
    D%=Duration%(Tempo%)?I%
    IF T% AND 4 THEN
      TIE%=TIE% AND NOT (%1<<C%)
      T%=P%(C%)+1
      REPEAT T%+=2
        D%+=Duration%(Tempo%)?(?T%>>3)
      UNTIL T%>(NoteBlock%(C%)+NoteSize%(C%)) OR 4 AND NOT T%?TRUE
      IF D%>254 THEN D%=254
    ENDIF
    IFL% THEN
      REM Internal sound system
      IF OutputType%=0 THEN
        SOUNDC%+1,INT(xtraVol*Volume%(Volumes%(C%)))OR&100,Line(L%)+Aoff(A%),D%,Q%
      ENDIF
      REM MIDI
      IF OutputType%=1 THEN
        note% = 12 + INT(((Line(L%)+Aoff(A%))/&1000) * 12)
        packed% = (MIDIChannel%(C%)-1) OR (note%<<8) OR INT(xtraVol*(Volume%(Volumes%(C%)))<<16)
        MIDI_OFF%(PBAR%MOD2,MIDI_Notenum%) = M_NoteOff% OR packed%
        MIDI_Notenum% += 1
        MIDI_OFF%(PBAR%MOD2,MIDI_Notenum%) = -1 :REM end marker
        REM D% = no. of 5 centisecs
        Qoff% = Q% + D% * tempo
        IF Qoff% > Q% + 15 Qoff% -= 10  :REM ensure note is off after start and before next note start
        SYS "Sound_QSchedule",Q%,    Sch% OR MIDI_TxCommand%, M_NoteOn%  OR packed%
        SYS "Sound_QSchedule",Qoff%, Sch% OR MIDI_TxCommand%, M_NoteOff% OR packed%
      ENDIF
    ENDIF
  ELSE
    IF4ANDNOTT% TIE%=TIE%OR%1<<C%
  ENDIF
  P%(C%)+=NSIZE%:IFLength%(I%)<QI%(S%) QI%(S%)=Length%(I%):Q%(S%)=Q%+QI%(S%)
UNTIL(2<<C%)>G%
ENDPROC

DEF PROCplay_attribute(A%)
C%=TRUE:REPEATC%-=TRUE:UNTILA%AND%1<<C%
ONC%+1 PROCplay_time_sig(A%),PROCplay_key_sig(A%),PROCplay_clef(A%),PROCplay_slur(A%),PROCplay_octave(A%),PROCplay_barline(A%)
ENDPROC

DEF PROCplay_time_sig(A%)
A%=((A%>>1AND&F)+1)*Length%(A%>>3AND%11100)
SYS "Sound_QSchedule",Beats%,Sch% OR Sound_QBeat%,A%
Beats%=A%
ENDPROC

DEF PROCplay_key_sig(A%)
LOCALN%:A%=A%>>2
FORN%=0TO6:Key%(N%)=Key_Sig%(A%,N%):NEXT
ENDPROC

DEF PROCplay_clef(A%)
PCLEF%(A%>>6)=Clef%(A%>>3AND3)
ENDPROC

DEF PROCplay_slur(A%)
ENDPROC

DEF PROCplay_octave(A%)
ENDPROC

DEF PROCplay_barline(A%)
PBAR%+=1
ENDPROC

DEF FNinitialise
LOCAL SoundEnable%
PROCEnumerateSWIs
PROCinitialise_miscellany
PROCinitialise_screen
PROCinitialise_sprites
PROCinitialise_music
PROCinitialise_wimp
PROCinitialise_menu
SoundEnable%=FNinitialise_sound
IF SoundEnable%=1 IF NOT FNCheckOKTag("NoSoundQuit", 3) PROCterminate
PROCinitialise_memory
REM Set up the defaults for an empty score: Cmaj, 4/4, FF, Moderato
PROCClearAllMusic
PROCSetKeySig(0,7)
PROCSetTimeSig(3,3)
PROCSetVolume(6)
PROCSetTempo(8)
REM Set Toolbar defaults
ClefType%=0     :REM Treble clef
BarlineType%=0  :REM Single barline
REM Set up channels
PROCSetDefaultChannels
=TRUE

DEF PROCEnumerateSWIs
Sch%=&0F000000 : REM SWI number mask for QSchedule SWI
Sound_QTempo%=&401C5
Sound_QBeat%=&401C6
LOCAL M%
LOCAL ERROR
ON ERROR LOCAL MIDIpresent%=FALSE : ENDPROC
SYS "OS_SWINumberFromString",0,"MIDI_SoundEnable" TO M%
M_NoteOff% = &80
M_NoteOn%  = &90
MIDI_SoundEnable%        = M%
MIDI_SetTxChannel%       = M% + 2
MIDI_SetTxActiveSensing% = M% + 3
MIDI_TxCommand%          = M% + 10
MIDI_TxLocalControl%     = M% + 15
MIDI_TxOmniModeOff%      = M% + 17
MIDI_TxOmniModeOn%       = M% + 18
MIDI_TxMonoModeOn%       = M% + 19
MIDI_TxPolyModeOn%       = M% + 20
MIDI_TxProgramChange%    = M% + 21
MIDI_TxSystemReset%      = M% + 30
MIDIpresent%=TRUE : REM Disable untested MIDI bits
ENDPROC

DEF PROCinitialise_miscellany
DIM Block% 255
TIME=0
AwaitingAck%=FALSE
SAVING%=FALSE
load_pending% = FALSE
pending_filename$ = ""
ret_code% = -1
doing_scrap_load%=FALSE : REM Used to indicate that we're loading from another application
INITIALISED%=FALSE
shutdown% = FALSE
quit_sender% = 0
my_ref% = -1 : REM unset
CHANGED%=FALSE
FILE%=FALSE
REM File attributes
FileName$=FNmessage_lookup(Messages_h%,"Untitled")
MusicFileType%=&AF1
DIM ftime% 5
ENDPROC

DEF PROCinitialise_screen
DIM modeblockin  40
DIM modeblockout 40
PROCgetmodeinfo(TRUE)
ENDPROC

DEF PROCgetmodeinfo(new%)

  LOCAL S_Rows%, S_Columns%

  modeblockin!0=0 : REM ModeFlags
  modeblockin!4=1 : REM ScrRCol
  modeblockin!8=2 : REM ScrBCol
  modeblockin!12=3 : REM NColour
  modeblockin!16=4 : REM XEigFactor
  modeblockin!20=5 : REM YEigFactor
  modeblockin!24=11 : REM XWindLimit
  modeblockin!28=12 : REM YWindLimit
  modeblockin!32=-1 : REM terminate list

  SYS "OS_ReadVduVariables",modeblockin, modeblockout
  REM no point in reading sizes since they are fixed by the wimp
  Hi%=1<<(modeblockout!16)
  Vi%=1<<(modeblockout!20)
  S_Width%=Hi% * ((modeblockout!24)+1)
  S_Height%=Vi% * ((modeblockout!28)+1)
  Hi%=2: Vi%=4
  C_Width%=8*Hi% : C_Height%=8*Vi%

  IF new%=FALSE THEN
    SYS "Wimp_ReadPixTrans",&200,SprBlk%,S%(0),,,,factors%,pixtrans%
    j%=7
    FOR i%=0 TO 15
      fpixtrans%?i%=pixtrans%?j%
      IF i%>6 THEN j%-=1
    NEXT
  ENDIF  
  
ENDPROC

DEF PROCinitialise_sprites
LOCAL path$,size%,file%,yeig%
path$="<Maestro$Dir>.Sprites"
SYS "OS_ReadModeVariable",-1,5 TO ,,yeig%
IF yeig%=1 THEN path$=path$+"22"
IF yeig%=0 THEN path$=path$+"11"
file%=OPENIN path$
size%=EXT#file%+16
CLOSE#file%

DIM SprBlk% size%
SprBlk%!0=size%
SprBlk%!8=16
SYS "OS_SpriteOp",&109,SprBlk%
SYS "OS_SpriteOp",&10A,SprBlk%,path$

SprPlot% = &234  : REM sprite plot code
LOCAL I%,x%,y%,W%,H%
RESTORE
Num%=-1
READS$
WHILES$<>""
READx%,y%,W%,H%,S$
Num%+=1
ENDWHILE
IFFNassert(Num%>=0,"SpriteLoad") STOP
DIM S$(Num%),x%(Num%),y%(Num%),X%(Num%),Y%(Num%), S%(Num%) : REM S%() are sprite pointers
RESTORE
FORI%=0TONum%
READ S$(I%),x%,y%,W%,H%
SYS "OS_SpriteOp", 256+24, SprBlk%, S$(I%) TO ,,S%(I%) : REM get pointer to sprite
x%(I%)=x%*Hi%
y%(I%)=y%*Vi%
X%(I%)=(W%-x%)*Hi%
Y%(I%)=(H%-y%)*Vi%
NEXT
note%=0
rest%=16
accidental%=24
clef%=32
dot%=40
ledger%=43
tie%=44
bar%=51
time%=55
key%=56
SPR_CHEAD%=72
SPR_MHEAD%=73
SPR_REVERSE%=74
SCORING%=FALSE:REM Flag indicating a sprite is to be drawn under the pointer
wasSCORING%=FALSE
stopSCORING%=TRUE
DIM SCRIBE%(18):REM Details of symbol under pointer
REM 0-7 Channel pointers into N%()
sx%=8:REM Current sprite X position
sy%=9:REM Current sprite Y position
drawn%=10:REM Flag indicating sprite presence
sprite%=11:REM Sprite number
valid%=12:REM Valid stave positions
stave%=13:REM Current stave symbol is on
sclef%=14:REM Current clef applying to symbol (used for key sigs)
line%=15:REM Current stave line symbol is on
posx%=16:REM X music position
sgp%=17:REM Symbol gate pointer
sc%=18:REM Channel number if symbol specified close to note
REM Set up sprite colours for fixed and floating objects
DIM factors% 15
DIM pixtrans% 15
DIM fpixtrans% 15
PROCgetmodeinfo(FALSE)
ENDPROC
REM name, x, y, w, h
DATA B,7,3,24,6,SB,0,2,13,5,Mu,0,2,12,17,Cu,0,2,12,17
DATA Qu,0,2,19,17,SQu,0,2,20,17,DSQu,0,2,20,17,SDSQu,0,2,20,17
DATA B,7,3,24,6,SB,0,2,13,5,Md,0,14,12,17,Cd,0,14,12,17
DATA Qd,0,14,12,17,SQd,0,14,12,17,DSQd,0,14,12,17,SDSQd,0,14,12,17
DATA Rest,-1,0,8,4,Rest1,2,-2,16,6,Rest2,2,0,16,2,Rest4,-2,5,9,13
DATA Rest8,-1,4,9,9,Rest16,0,6,12,13,Rest32,1,7,15,16,Rest64,2,9,18,19
DATA Natural,8,6,7,12,Natural,8,6,7,12,Sharp,10,6,9,13,Flat,8,3,8,11
DATA Sharp2,9,2,10,5,Flat2,14,3,16,11,NSharp,17,6,17,12,NFlat,15,6,16,14
DATA Treble,0,14,23,31,Alto,0,8,23,17,Alto,0,4,23,17,Bass,0,5,24,14
DATA ldg5,2,28,16,17,ldg4,2,24,16,13,ldg3,2,20,16,9,ldg2,2,16,16,5,ldg1,2,12,16,1
DATA Dot1,-14,1,4,2,Dot2,-14,1,9,2,Dot3,-14,1,14,2,Tie,-8,-3,20,4,Tie2,-8,7,20,4
DATA ldg1,2,-12,16,1,ldg2,2,-12,16,5,ldg3,2,-12,16,9,ldg4,2,-12,16,13,ldg5,2,-12,16,17
DATA Bar,-1,8,4,16,DBar,-1,8,6,16,Bar,-2,8,1,16,Bar,-2,8,1,16
DATA Time,1,9,14,16,Key,0,8,9,17
DATA 2,3,0,14,8,3,3,0,14,8,4,3,0,14,8,5,3,0,14,8,6,3,0,14,8
DATA 7,3,0,14,8,8,3,0,14,8,9,3,0,14,8,10,3,0,26,8,11,3,0,26,8
DATA 12,3,0,26,8,13,3,0,26,8,14,3,0,26,8,15,3,0,26,8,16,3,0,26,8
DATA C,0,2,12,4,M,0,2,12,4
DATA Mdb,0,14,12,17,Cdb,0,14,12,17,Qdb,0,14,20,17
DATA SQdb,0,14,21,17,DSQdb,0,14,21,17,SDSQdb,0,14,2,17
DATA ""

DEF PROCinitialise_wimp
PROCenumerate_wimp_offsets
Block%!0=2
Block%!4=3
Block%!8=4
Block%!12=5
Block%!16=10
Block%!20=&502
Block%!24=&400C1
Block%!28=11
Block%!32=12
Block%!36=8
Block%!40=1
Block%!44=&400C0
Block%!48=0
SYS "Wimp_Initialise",300,&4B534154,maestro$,Block% TO wimpversion%,Task_h%
message_buffer%!0 = (32 + LEN(maestro$)) AND NOT(3)
message_buffer%!12 = 0
message_buffer%!16 = 11
message_buffer%!20 = 7
message_buffer%!24 = 0
$(message_buffer%+28)=maestro$+CHR$(0)
SYS "Wimp_SendMessage",17,message_buffer%,0
REPEAT
  SYS "Wimp_Poll",&1972,message_buffer% TO R%
  IF R% = 17 OR R% = 18 OR R% = 19 THEN
    IF message_buffer%!16 = 12 AND message_buffer%!20 = 7 THEN
      R% = FNCheckOK($(message_buffer% + 28), 1)
      PROCterminate
    ENDIF
  ENDIF
UNTIL R% = 0
DIM SpriteName% 14
$SpriteName%=FNmessage_lookup (Messages_h%, "SpriteName")
REM Create icon bar icon
Block%!0=-1
Block%!4=0
Block%!8=0
Block%!12=64
Block%!16=68
Block%!20=(&311A OR (0<<24) OR (7<<28))
Block%!24=SpriteName%
Block%!28=1
Block%!32=12
SYS "Wimp_CreateIcon",,Block% TO Maestro_h%
PROCload_templates
REM Initialise various window settings
OldX%=0
FirstOpen%=TRUE
PopUpIcon%=0
ScoreClosed%=TRUE
PROCwimp_seticontext(ProgInfo_h%,3,FNmessage_lookup(Messages_h%,"_Version"))
Block%!0=NotesPane_h%
SYS "Wimp_GetWindowState",,Block%
PaneHeight%=Block%!16-Block%!8
SelW%=-1
SelI%=-1
ENDPROC

DEF PROCenumerate_wimp_offsets
Window%=Block%+4
handle%=-4
x0%=0:y0%=4:x1%=8:y1%=12
scx%=16:scy%=20
ENDPROC

DEF PROCload_templates
wblock%=FNwimp_opentemplates("<Maestro$Dir>.Templates")
  ProgInfo_h%=FNwimp_loadtemplate("ProgInfo",wblock%,FALSE)
  QuitQuery_h%=FNwimp_loadtemplate("query",wblock%,FALSE)
  FileInfo_h%=FNwimp_loadtemplate("FileInfo",wblock%,FALSE)
  TimeSig_h%=FNwimp_loadtemplate("TimeSigW",wblock%,FALSE) 
  Bar_h%=FNwimp_loadtemplate("BarW",wblock%,FALSE)
  ClearQuery_h%=FNwimp_loadtemplate("close",wblock%,FALSE)
  ScoreWind_h%=FNwimp_loadtemplate("ScoreWind",wblock%,SprBlk%)
  Print_h%=FNwimp_loadtemplate("print_db",wblock%,FALSE)
  Save_h%=FNwimp_loadtemplate("xfer_send",wblock%,FALSE)
  InstrWind_h%=FNwimp_loadtemplate("InstrWind",wblock%,SprBlk%)
  SharpsPane_h%=FNwimp_loadtemplate("SharpsPane",wblock%,SprBlk%)
  NotesPane_h%=FNwimp_loadtemplate("NotesPane",wblock%,SprBlk%)
  Clef_h%=FNwimp_loadtemplate("ClefW",wblock%,SprBlk%)
  Barline_h%=FNwimp_loadtemplate("BarLineW",wblock%,SprBlk%)
  StaveWind_h%=FNwimp_loadtemplate("StaveWind",wblock%,FALSE)
SYS "Wimp_CloseTemplate"
ENDPROC  

DEF PROCinitialise_menu
LOCAL i%,f%
VolumeMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Volume"),8)
FOR i%=0 TO 7
  IF i%<7 THEN f%=0 ELSE f%=128
  PROCwimp_additem(VolumeMenu%,i%,f%,-1,FNmessage_lookup(Messages_h%,"V"+STR$(i%)))
NEXT
TempoMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Tempo"),15)
FOR i%=0 TO 14
  IF i%<14 THEN f%=0 ELSE f%=128
  PROCwimp_additem(TempoMenu%,i%,f%,-1,FNmessage_lookup(Messages_h%,"T"+STR$(i%)))
NEXT
FileMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"File"),3)
PROCwimp_additem(FileMenu%,0,0,FileInfo_h%,FNmessage_lookup(Messages_h%,"FInfo"))
PROCwimp_additem(FileMenu%,1,0,Save_h%,FNmessage_lookup(Messages_h%,"Save"))
PROCwimp_additem(FileMenu%,2,136,Print_h%,FNmessage_lookup(Messages_h%,"Print"))
EditMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Edit"),2)
PROCwimp_additem(EditMenu%,0,8,Bar_h%,FNmessage_lookup(Messages_h%,"Goto"))
PROCwimp_additem(EditMenu%,1,128,-1,FNmessage_lookup(Messages_h%,"Clear"))
ScoreMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Score"),4)
PROCwimp_additem(ScoreMenu%,0,0,-1,FNmessage_lookup(Messages_h%,"Staves"))
PROCwimp_additem(ScoreMenu%,1,0,-1,FNmessage_lookup(Messages_h%,"Instruments"))
PROCwimp_additem(ScoreMenu%,2,0,VolumeMenu%,FNmessage_lookup(Messages_h%,"Volume"))
PROCwimp_additem(ScoreMenu%,3,128,TempoMenu%,FNmessage_lookup(Messages_h%,"Tempo"))
MainMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Maestro"),4)
PROCwimp_additem(MainMenu%,0,0,FileMenu%,FNmessage_lookup(Messages_h%,"File"))
PROCwimp_additem(MainMenu%,1,0,EditMenu%,FNmessage_lookup(Messages_h%,"Edit"))
PROCwimp_additem(MainMenu%,2,2,ScoreMenu%,FNmessage_lookup(Messages_h%,"Score"))
PROCwimp_additem(MainMenu%,3,128,-1,FNmessage_lookup(Messages_h%,"Play"))
IconMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Maestro"),3)
PROCwimp_additem(IconMenu%,0,0,ProgInfo_h%,FNmessage_lookup(Messages_h%,"Info"))
PROCwimp_additem(IconMenu%,1,0,-1,FNmessage_lookup(Messages_h%,"Help"))
PROCwimp_additem(IconMenu%,2,128,-1,FNmessage_lookup(Messages_h%,"Quit"))
ChannelMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Channel"),16)
FOR i%=0 TO 15
  IF i%<15 THEN f%=0 ELSE f%=128
  PROCwimp_additem(ChannelMenu%,i%,f%,-1,STR$(i%+1))
NEXT
VoiceMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Voices"),MAX_VOICES%-2)
FOR i%=0 TO MAX_VOICES%-3
  IF i%<MAX_VOICES%-3 THEN f%=0 ELSE f%=128
  PROCwimp_additem(VoiceMenu%,i%,f%,-1,STRING$(32,"X"))
NEXT
OutputMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Output"),2)
PROCwimp_additem(OutputMenu%,0,0,-1,FNmessage_lookup(Messages_h%,"OT0"))
PROCwimp_additem(OutputMenu%,1,128,-1,FNmessage_lookup(Messages_h%,"OT1"))
MajorMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Major"),15)
FOR i%=0 TO 14
  IF i%<14 THEN f%=0 ELSE f%=128
  IF i%=6 OR i%=7 THEN f%=2
  PROCwimp_additem(MajorMenu%,i%,f%,-1,FNmessage_lookup(Messages_h%,"MJ"+STR$(i%)))
NEXT
MinorMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"Minor"),15)
FOR i%=0 TO 14
  IF i%<14 THEN f%=0 ELSE f%=128
  IF i%=6 OR i%=7 THEN f%=2
  PROCwimp_additem(MinorMenu%,i%,f%,-1,FNmessage_lookup(Messages_h%,"MN"+STR$(i%)))
NEXT
KeyMenu%=FNwimp_makemenu(FNmessage_lookup(Messages_h%,"KeySig"),2)
PROCwimp_additem(KeyMenu%,0,0,MajorMenu%,FNmessage_lookup(Messages_h%,"Major"))
PROCwimp_additem(KeyMenu%,1,128,MinorMenu%,FNmessage_lookup(Messages_h%,"Minor"))
ENDPROC

DEF FNinitialise_sound
LOCAL SoundEnable%
SYS "Sound_Configure",8 TO OldConfigure
SYS "Sound_Enable",0 TO SoundEnable%
REM disconnect midi interpreter
IF MIDIpresent% THEN SYS MIDI_SoundEnable%,0
=SoundEnable%

DEF PROCClearAllMusic
LOCAL f%
REM Clear Note queues and reset pointers
NoteSize%()=0
PP%=GateBlock%
P%()=NoteBlock%()
NBars%=0
BAR%=0
PBAR%=0
SBAR%=0
PROCstart_music
EP%=GP%
REM Add a Barline attribute to the empty score
BarlineType%=0
PROCwimp_seticonval(SharpsPane_h%,13,"Ssbarline")
PROCbar
GateSize%=2
PX%(0)=0
PXn%(BAR%)=0
PW%(0)=4*Hi%
PTYPE%(0)=Bar%
REM Reset Gate pointer
GP%=GateBlock%
GPn%(BAR%)=GP%
PROCrescore(0)
PROCSetExtent(S_Width%)
PROCrelease
wasSCORING%=FALSE
stopSCORING%=TRUE
SCORING%=FALSE
CHANGED%=FALSE
REM Reset file information
FileName$=FNmessage_lookup(Messages_h%,"Untitled")
PROCUpdateTitle(FileName$)
PROCwimp_seticontext(FileInfo_h%,2,FNmessage_lookup(Messages_h%,"No"))
PROCwimp_seticontext(Save_h%,1,FNmessage_lookup(Messages_h%,"MusicFile")+CHR$(0))
f%=FNGetFileInfo("")
ENDPROC

REM   PROCEDURE: initialise_music
REM
REM  STRUCTURES: Queue of Gate;
REM              Queue of Music;
REM
REM       TYPES:  Gate= byte0>0 -> Gate_Mask
REM                     byte0=0 -> Music_Attribute
REM
REM          Gate_Mask= byte; bitn=1 -> gate 1 note/rest from music queue n (n=0-7)
REM
REM    Music_Attribute= word; bit0_7=0
REM                             bit8=1 -> Time signature bit12_9 No. beats-1
REM                                                      bit15_13 Beat type
REM                             bit9=1 -> Key signature bit10 0-#, 1-b
REM                                                     bit13_11 0-7
REM                            bit10=1 -> Clef bit12_11 Treble,Alto,Tenor,Bass
REM                                            bit15_14 Stave 1-4
REM                            bit11=1 -> Slur switch bit12 on/off
REM                                                   bit15_14 Stave 1-4
REM                            bit12=1 -> Octave shift bit13 0-up,1-down
REM                                                    bit15_14 Stave 1-4
REM                            bit13=1 -> bar
REM                         (bit13_0=0 -> Reserved)
REM
REM              Music= word; bit7_3>0 -> Note
REM                           bit7_3=0 -> Rest
REM
REM               Note= word; bit0=     Stem orientation: 0-up,1-down
REM                           bit1=1 -> Join barbs to next note
REM                           bit2=1 -> Tie with next note
REM                         bit7_3>0    Note stave line position 1 to 31 (16=centre line)
REM                        bit10_8>0 -> Accidental N,#,b,X,bb,N#,Nb
REM                       bit12_11=     Number of dots 0 to 3
REM                       bit15_13=     Type: Breve to Semi-demi-semiquaver
REM
REM               Rest= word; bit0=0    NB If a rest coincides with a note, its
REM                           bit1=0       position is determined by the
REM                           bit2=0       following note on the same channel.
REM                         bit7_3=0
REM                        bit10_8=0
REM                       bit12_11=     Number of dots 0 to 3
REM                       bit15_13=     Type: Breve rest to Semi-demi-semiquaver rest
REM
REM      Position_type= 0 -> Note
REM                     1 -> Time Signature
REM                     2 -> Key Signature
REM                     3 -> Clef
REM                     4 -> Slur
REM                     5 -> Octave shift
REM                     6 -> Bar
:
DEF PROCinitialise_music
LOCALN%,C%:REM Note,channel
PROCinitialise_options
Note%=0:Time%=1:Key%=2:Clef%=4:Slur%=8:Octave%=16:Bar%=32:REM Stave attribute enumerators
DIM Ninc%(6),Line(42),Aoff(7),Clef%(3),Key%(6),Key_Sig%(15,6),Length%(31),Duration%(NTempos%),Accidental%(3,31)
FORN%=0TO6:Ninc%(N%)=ASCMID$("024579;",N%+1)AND15:NEXT:REM Note increment
LOCALST:ST=&1000/12:REM Semitone increment
FORN%=0TO42
Line(N%)=(1+N%DIV7<<12)+Ninc%(N%MOD7)*ST+.49
NEXT:REM Notes corresponding to stave lines (C octave 1 TO C octave 7)
Aoff(2)=ST:Aoff(3)=-ST:Aoff(4)=ST*2:Aoff(5)=-ST*2:Aoff(6)=ST:Aoff(7)=-ST:REM Accidental offsets
Clef%(0)=11:Clef%(1)=5:Clef%(2)=3:Clef%(3)=-1:REM Line offsets for each clef
FORC%=2TO15
FORN%=0TO(C%>>1)-1
Key_Sig%(C%,(7+Key_Y%(1,C%AND%1,N%))MOD7)=C%MOD2+2
NEXT
NEXT:REM Set up note offsets for each key signature
FORC%=0TO31
Length%(C%)=(%1<<7-(C%>>2))*(%1111000>>(C%AND3)AND%1111):REM Length of each possible note/rest (dotted) in tempo beats
NEXT
LOCALD%:REM Duration
FORN%=0TONTempos%
DIM C% 32:Duration%(N%)=C%:REM Reserve space for each tempo
FORC%=0TO31
D%=75/Tempo%(N%)*Length%(C%)/8+.5:REM Durations of note+dot combinations in 20ths of a second for each tempo (Max 22.5 Seconds)
IFD%>254 D%=254:REM Limit to maximum possible duration (12.7 Seconds)
Duration%(N%)?C%=D%
NEXT
NEXT
TIE%=&FF:REM Tie state of channels - Each bit corresponds to a channel, 0 if tied

PLAYING%=FALSE:REM Flag indicating play in process
SCROLLING%=FALSE:REM Flag indicating auto-scrolling while playing

DIM Q%(Max_Stave%+2):REM Time positions of next notes on each stave
DIM QI%(Max_Stave%+2):REM Time increments of Q%() for each stave

B1%=0:B2%=0:REM Alternate beat counters used to detect zero wrap

REM Set maximum Gates and Bars
Max_Gate%=1600
Max_Bar%=Max_Gate% DIV 4

DIM GPn%(Max_Bar%) :REM GP% at the start of each bar
DIM PX%(Max_Gate%),PW%(Max_Gate%),PTYPE%(Max_Gate%):REM Notation screen positions, widths & types
REM PX% is PX%() index to screen positions & types - Always refers to the note/attribute just passed
DIM PXn%(Max_Bar%) :REM PX% at start of each bar
DIM BPn%(7,Max_Bar%):REM Indices to (8) note queues at the start of each bar
DIM N%(7),n%(7):REM Pointers to current notes (n%()=copy, cf PROCproximate)
DIM C%(7),c%(7):REM Indexes of gate channels used in sorting
DIM CLEF%(Max_Stave%),clef%(Max_Stave%):REM Current clef on each stave (& clef copy)
DIM CLEFn%(Max_Stave%,Max_Bar%) :REM CLEF% at start of each bar
DIM SIG%(1),sig%(1):REM Base bar key & time sigs, current and copy
DIM SIGn%(1,Max_Bar%) :REM SIG% at the start of each bar
DIM P%(7),PCLEF%(3):REM Playing note pointers, clef

MaxNotesInBar%=128
REM while one half of draw q is being filled, the other half is being used for scrolling.
DIM MIDI_OFF%(1,MaxNotesInBar%*4) :REM an array of MIDI noteoffs if playing is stopped in the middle of a bar
MIDI_OFF%(0,0)=-1
MIDI_OFF%(1,0)=-1
MIDI_Notenum%=0

Pgap%=X%(2)DIV2:REM Symbol spacing (half note blob width)
ENDPROC

DEF PROCinitialise_options
REM Tempo
NTempos%=14
DIM Tempo%(NTempos%)
Tempo%(0)=40
Tempo%(1)=50
Tempo%(2)=60
Tempo%(3)=65
Tempo%(4)=70
Tempo%(5)=80
Tempo%(6)=90
Tempo%(7)=100
Tempo%(8)=115
Tempo%(9)=130
Tempo%(10)=145
Tempo%(11)=160
Tempo%(12)=175
Tempo%(13)=190
Tempo%(14)=210

REM Time signatures
DIM TIME_SIG%(1)   :REM Current time signature numerator(0) and denominator(1)

REM Key signatures
DIM KEY_SIG%(1)    :REM Current key signature (0: 0/1 flats/sharps; 1: 0-7)
DIM Key_Y%(3,1,6)  :REM Key signature 0-sharp/1-flat stave line positions
LOCAL C%,A%,P%     :REM Indices
FOR C%=0 TO 3      :REM For each clef
  FOR A%=0 TO 1    :REM For each accidental type
    FOR P%=0 TO 6  :REM For each accidental position
      REM Position offsets of key signature accidentals from centre stave line
      Key_Y%(C%,1-A%,P%)=3*(P%AND%1)-P%DIV2+(P%-3)*A%+(A%ANDC%<>2AND(P%AND5)=0)*7-1-(C%-1>>1)-2*(C%=2)
    NEXT
  NEXT
NEXT

REM Volumes
NVolumes%=7
DIM Volume%(NVolumes%),Volumes%(7)
FOR R%=0 TO NVolumes%
  REM don't permit full volume so that dynamics are possible
  Volume%(R%)=(R%+1)*120/(NVolumes%+1)-1
NEXT
SYS "Sound_Volume" TO R%
RestoreVolume=R%
REM don't permit full volume. This is reserved for dynamics
Volume%=R%*(NVolumes%+1)/120-.5
IF Volume%<0 THEN Volume%=0
SYS "Sound_Volume",Volume%(Volume%)
FOR R%=0 TO 7
  Volumes%(R%)=6
NEXT

REM Staves
Max_Stave%=3
Li%=2*Vi%
Stave_Height%=Li%*8
DIM Y_STAVE%(Max_Stave%+2)
STAVE%=0   :REM means 1 stave
PERC%=0    :REM means no percussion
Score_Width%=S_Width%
PROCposition_staves
LOCALS%
DIM Stave_Channels%(Max_Stave%+2,7):REM Primary stave allocation of channels 0-7 for each stave structure
FOR S%=0 TO Max_Stave%
FOR R%=0 TO 7
Stave_Channels%(S%,R%)=(S%+1)*R%DIV8:REM Close formula to desired data
NEXT
NEXT
Stave_Channels%(2,1)=1
Stave_Channels%(2,2)=1
Stave_Channels%(2,5)=2:REM Correct the exceptions to formula
DIM S_C%(7):REM Current stave allocation
FORR%=0TO7
S_C%(R%)=Stave_Channels%(STAVE%,R%):REM Initialise channel allocation (Also reset each time stave structure is changed)
NEXT

REM Voices
LOCALI$,L%,M%:REM Instrument name, Number of instruments, Instrument name length, Max
MAX_VOICES%=32+1           :REM 1-32 Voice slots + MIDI
DIM Voice$(MAX_VOICES%)    :REM Names of available voices
DIM RestoreVoice%(7)       :REM Table of original voice/channel allocation
DIM Instrument%(7,1)       :REM Instrument information: 0=Stave,1=Voice
REM Get number of voices available
SYS "Sound_InstallVoice" TO I$,NVoices%
NVoices%-=1
IF FNassert(NVoices%>0,"NoVoices") STOP
REM Get names of available voices
FOR R%=1 TO NVoices%
  SYS "Sound_InstallVoice",2,R% TO ,,,Voice$(R%)
NEXT
REM Get voice allocation for each channel
FOR R%=0 TO 7
  SYS "Sound_AttachVoice",R%+1,0 TO L%,S%
  RestoreVoice%(R%)=S%
  REM Make sure a voice is attached to all channels
  IF S%<1 OR S%>NVoices% THEN S%=1
  SYS "Sound_AttachVoice",L%,S%
  REM Create instrument information (stave and voice)
  Instrument%(R%,0)=S_C%(R%)+1
  Instrument%(R%,1)=S%
NEXT

REM Stereo positions
NStereos%=6
DIM Stereo%(NStereos%),Stereo_Position%(7)
FOR R%=0 TO NStereos%
  Stereo%(R%)=(2*R%/NStereos%-1)*127
NEXT
FOR R%=0 TO 7
  Stereo_Position%(R%)=NStereos% DIV 2
  SYS "Sound_Stereo",R%+1,Stereo%(Stereo_Position%(R%))
NEXT

REM Audio output
REM 0=Internal sound system
REM 1=MIDI device
OutputType%=0

REM MIDI options
NMIDIChannels%=16
DIM MIDIChannel%(7)
FOR c%=0 TO 7
  MIDIChannel%(c%)=c%+1
NEXT
ENDPROC

DEF PROCinitialise_memory
LOCAL c%,n%
NSIZE%=4                                     :REM Size of Note block in memory
REM Reserve initial memory for music: 9 x 1K blocks + headers
Heap%=FNheap_init(9252)
IF Heap%=0 THEN
  n%=FNCheckOKTag("OutMem3",1)
  PROCterminate
ENDIF
REM Set up Gate data
GateBlock%=FNheap_newblock(Heap%,1024)       :REM Pointer to start of Gate data
IF GateBlock%=0 THEN
  n%=FNCheckOKTag("OutMem3",1)
  PROCterminate
ENDIF
GateSize%=0                                  :REM Size of Gate data
REM Set up Note data (eight channels)
DIM NoteBlock%(7)
DIM NoteSize%(7)
FOR c%=0 TO 7
  NoteBlock%(c%)=FNheap_newblock(Heap%,1024) :REM Pointer to start of Note data
  IF NoteBlock%(c%)=0 THEN
    n%=FNCheckOKTag("OutMem3",1)
    PROCterminate
  ENDIF
  NoteSize%(c%)=0                            :REM Size of Note data
NEXT
ENDPROC

DEF PROCrestore
REMOSCLI("audio off")
FORR%=0TO7    :REM restore channel/voice allocation
  SYS "Sound_AttachVoice",R%+1,RestoreVoice%(R%)
NEXT
SYS "Sound_Volume", RestoreVolume :REM restore volume
SYS "Sound_Configure",OldConfigure
ENDPROC

DEF PROCexit
PROCCloseWindow(ScoreWind_h%)
PROCrestore
ENDPROC

DEF PROCterminate
LOCAL i%
IF INITIALISED% IF PLAYING% PROCplay_stop
*UnSet Maestro$Running
PROCclose_messagefile(Messages_h%)
IF Task_h%>0 THEN
 ON ERROR SYS "Wimp_CloseDown", Task_h%, "TASK" : END
ELSE
 ON ERROR END
ENDIF
IF INITIALISED% THEN
  PROCheap_freeblock(Heap%,GateBlock%)
  FOR i%=0 TO 7
    PROCheap_freeblock(Heap%,NoteBlock%(i%))
  NEXT
  PROCexit
  PROCCloseWindow(QuitQuery_h%)
ENDIF
ON ERROR END
IF Task_h%>0 THEN SYS "Wimp_CloseDown", Task_h%, "TASK"
END
ENDPROC

DEF FNassert(E%,A$)
LOCAL e%
A$=FNmessage_lookup(Messages_h%, A$)
IFE% THEN=FALSE ELSE e%=FNCheckOK(FNmessage_lookupN(Messages_h%, "Fatal", A$, "", "", ""),1)
PROCterminate
=TRUE


DEF PROCerror

  LOCAL e%
  LOCAL ERROR
  ON ERROR LOCAL PROCterminate: STOP
  SYS "Hourglass_Smash"
  E$=FNmessage_lookupN(Messages_h%, "IntErr", REPORT$, STR$ERL, "", "")

  IF INITIALISED% THEN
    E$+=" "+FNmessage_lookup (Messages_h%, "ExProg")
    IF FNCheckOK(E$,3) PROCterminate
  ELSE
    e%=FNCheckOK(E$,1) : PROCterminate
  ENDIF

ENDPROC


DEF PROCOpenDiscardCancelBox

  REM Dbox saying "Blah edited", Discard, Cancel

  LOCAL c%,d%
  LOCAL mc_dx%,mc_dy%,mc_sw%,mc_sh%,scrx%,scry%

  Window%!handle%=QuitQuery_h%
  SYS "Wimp_GetWindowState", ,Window%+handle%

  SYS"OS_ReadModeVariable",-1,4 TO ,,mc_dx%:mc_dx%=1<<mc_dx%
  SYS"OS_ReadModeVariable",-1,5 TO ,,mc_dy%:mc_dy%=1<<mc_dy%
  SYS"OS_ReadModeVariable",-1,11 TO ,,mc_sw%:mc_sw%+=1
  SYS"OS_ReadModeVariable",-1,12 TO ,,mc_sh%:mc_sh%+=1
  scrx%=mc_sw%*mc_dx%
  scry%=mc_sh%*mc_dy%
  c%=(scrx%-(Window%!8 - Window%!0 )) DIV 2
  d%=(scry%-(Window%!4 - Window%!12)) DIV 2
  SYS "Wimp_CreateMenu",,QuitQuery_h%,c%,d%

ENDPROC


DEF PROCOpenSaveDiscardCancelBox

  REM Dbox saying "Blah not saved", Save, Discard, Cancel

  LOCAL c%,d%
  LOCAL mc_dx%,mc_dy%,mc_sw%,mc_sh%,scrx%,scry%

  Window%!handle%=ClearQuery_h%
  SYS "Wimp_GetWindowState", ,Window%+handle%

  SYS"OS_ReadModeVariable",-1,4 TO ,,mc_dx%:mc_dx%=1<<mc_dx%
  SYS"OS_ReadModeVariable",-1,5 TO ,,mc_dy%:mc_dy%=1<<mc_dy%
  SYS"OS_ReadModeVariable",-1,11 TO ,,mc_sw%:mc_sw%+=1
  SYS"OS_ReadModeVariable",-1,12 TO ,,mc_sh%:mc_sh%+=1
  scrx%=mc_sw%*mc_dx%
  scry%=mc_sh%*mc_dy%
  c%=(scrx%-(Window%!8 - Window%!0 )) DIV 2
  d%=(scry%-(Window%!4 - Window%!12)) DIV 2
  SYS "Wimp_CreateMenu",,ClearQuery_h%,c%,d%

ENDPROC


DEF FNCheckOK(E$, boxes%)
LOCAL E%
FILE%=FILE%:IF FILE% CLOSE#FILE% : FILE%=FALSE
!err_block%=0
$(err_block%+4)=LEFT$(E$,200)+CHR$0
SYS "Wimp_ReportError", err_block%, boxes%, maestro$ TO ,E%
=E%=1 :  REM return TRUE if OK pressed

DEF FNCheckOKTag(E$, boxes%)
=FNCheckOK(FNmessage_lookup(Messages_h%, E$), boxes%)

DEF FNopen_messagefile(name$)
LOCAL type%,len%
SYS "OS_File",17,name$ TO type%,,,,len%
IF type%<>1 SYS "OS_File",19,name$,type%
DIM message_data% (len% + 19) AND &FFFFFFFC
SYS "MessageTrans_OpenFile", message_data%, name$, message_data%+16
=message_data%

DEF PROCclose_messagefile(mh%)
SYS "MessageTrans_CloseFile", mh%
ENDPROC

DEF FNmessage_lookup(mh%, tag$)
LOCAL s$
SYS "MessageTrans_Lookup", mh%, tag$, 0, 0, 0, 0, 0, 0 TO ,,s$
=s$

DEF FNmessage_lookupN(mh%, tag$, ss0$, ss1$, ss2$, ss3$)
LOCAL s$
SYS "MessageTrans_Lookup", mh%, tag$, message_buffer%, 256, ss0$, ss1$, ss2$, ss3$ TO ,,s$
=s$

REM // Print //

DEF PROCprint_init
LOCAL pname$
LOCAL ERROR
ON ERROR LOCAL:RESTORE ERROR:PROCwimp_seticontext(Print_h%,1,FNmessage_lookup(Messages_h%,"NoPrinter")):PROCwimp_seticonstate(Print_h%,2,22,TRUE):ENDPROC 
SYS "PDriver_Info" TO ,,,,pname$
PROCwimp_seticontext(Print_h%,1,pname$)
PROCwimp_seticonstate(Print_h%,2,22,FALSE)
ENDPROC

DEF PROCprint_click (icon%,button%)
CASE icon% OF
  WHEN 2:REM Print
    IF FNwimp_geticontext(Print_h%,1)<>FNmessage_lookup(Messages_h%,"NoPrinter") THEN
      PROCprint
    ELSE
      icon%=FNCheckOKTag("NoPrinter",1)
    ENDIF
    IF button%=1 THEN SYS "Wimp_CreateMenu",,-1
ENDCASE
ENDPROC

DEF PROCprint
    LOCAL Hi%, Vi%
    LOCAL f%, e%, h%
    LOCAL page_x%, page_y%, margin%
    LOCAL score_height%, staves_per_page%
    LOCAL p%, gp%
    LOCAL y1%, y2%, y3%
    LOCAL x1%, x2%
    LOCAL s%
    LOCAL l%, i%
    LOCAL page%, last_page%
    LOCAL title$
    LOCAL r0%
    LOCAL lx0%, lx1%, ly0%, ly1% : REM returned by bound_note
    LOCAL C%, P%, R%             : REM Channel, Max prefix, Max width
    LOCAL G%, A%, a%, W%                 : REM Note, Attribute
    LOCAL N%()                   : REM Note indices for each channel
    LOCAL n_start%(), n%()       : REM Local copy of N%
    LOCAL SIG%(), sig%()
    LOCAL clef%()
    LOCAL nbars%
    LOCAL NC0%, NC1%
    LOCAL x%, y%, lasty%, checkstagger%
    LOCAL N%
    LOCAL note_factors%, factors%
    LOCAL bars_plotted%
    LOCAL on_page%, last_pos%, index%, last_x_offset%
    LOCAL current_gate%, n_bars%, multiplier, w1, xs
    LOCAL events%, events_start%
    LOCAL bar_at_front%, bar_at_front_start%
    LOCAL x_res%, y_res%, ptr%, features%
    LOCAL end%

    DIM note_factors% 16, factors% 16
    DIM N%(7)
    DIM n_start%(7), n%(7)
    DIM SIG%(1), sig%(1)
    DIM clef%(Max_Stave%)
    DIM PrintRectangle%16, PrintTransform%16, PrintPos%8

    SYS "PDriver_Info" TO ,x_res%,y_res%, features%
    IF x_res% < 120 OR y_res% < 144 THEN
        REM Keep the above resolutions in step with the error in the messages file
        IF NOT FNCheckOK(FNmessage_lookupN(Messages_h%,"LoRes",FNwimp_geticontext(Print_h%,1),STR$(x_res%),STR$(y_res%),""),3) THEN ENDPROC
    ENDIF

    Hi% = 2
    Vi% = 4

    x_mul_scale% = 3
    y_mul_scale% = 3
    x_div_scale% = 4
    y_div_scale% = 4

    SYS "Hourglass_On"

    SYS "Wimp_ReadPixTrans",&200,SprBlk%,S%(0),,,,factors%,pixtrans%
    note_factors%!0  = factors%!0 * x_mul_scale%
    note_factors%!4  = factors%!4 * y_mul_scale%
    note_factors%!8  = (factors%!8) * x_div_scale%
    note_factors%!12 = (factors%!12) * y_div_scale%

    clef%() = CLEF%()
    clef%() = 0
    N%() = NoteBlock%()
    SIG%(0)=%01100111
    SIG%(1)=%00000010
    title$=FNwimp_geticontext(Save_h%,1)
    l% = LEN(title$)
    s% = 1
    FOR i% = 1 TO l% - 1
        c$ = MID$(title$, i%, 1)
        IF (c$ = ":" OR c$ = ".") THEN s% = i% + 1
    NEXT i%
    title$ = MID$(title$, s%)
    end% = GateBlock% + GateSize%

    LOCAL ERROR
    ON ERROR LOCAL: RESTORE ERROR:e%=FNCheckOK (FNmessage_lookupN(Messages_h%, "IntErr", REPORT$, STR$(ERL), "", ""), 1):ENDPROC

    f% = OPENOUT("printer:")
    SYS "PDriver_SelectJob", f%, title$

    SYS "PDriver_CurrentJob" TO h%

REM    IF (h% = 0) THEN
REM        e% = FNCheckOK (FNmessage_lookup (Messages_h%, "JobFailed"), 3)
REM        SYS "Hourglass_Off"
REM        ENDPROC
REM    ENDIF

    IF (features% AND (1 << 29)) <> 0 THEN
        SYS "PDriver_DeclareFont", 0, 0, 0 : REM We don't want to use any
    ENDIF

    ON ERROR LOCAL: RESTORE ERROR: SYS "PDriver_AbortJob", f%: CLOSE#f%:e%=FNCheckOK (FNmessage_lookupN(Messages_h%, "IntErr", REPORT$, STR$(ERL), "", ""), 1):ENDPROC
    SYS "PDriver_PageSize" TO ,page_x%,page_y%, l_edge%, b_edge%, r_edge%, t_edge%

    page_x% = page_x% / 400: page_y% = page_y% / 400
    REM  l_edge% = 0: r_edge% = page_x%
    l_edge% = l_edge% / 400: r_edge% = r_edge% / 400
    b_edge% = b_edge% / 400: t_edge% = t_edge% / 400
    r_edge% -= (Hi% * 12)

    SYS "ColourTrans_SetGCOL", &00000000,,,0,0

    score_height%=(PERC%+1+3*(STAVE%+1))*Stave_Height%
    margin% = Stave_Height%
    staves_per_page% = (page_y% - 2 * margin%) / score_height%

    gp% = GateBlock%
    page% = 0
    last_page% = 0
    p% = gp%
    index% = 0
    last_x_offset% = 0
    x2% = 0
    last_pos% = 0
    current_bar% = 0
    current_gate% = 0
    n%() = N%()
    events% = 0
    bar_at_front% = TRUE

    bars_plotted% = 0

    WHILE gp% < end%
        last_page% = page%
        page% += 1
        PrintRectangle%!0 = 0
        PrintRectangle%!4 = 0
        PrintRectangle%!8 = page_x%
        PrintRectangle%!12 = page_y%
        PrintTransform%!0 = &10000
        PrintTransform%!4 = &00000
        PrintTransform%!8 = &00000
        PrintTransform%!12 = &10000
        PrintPos%!0 = 0
        PrintPos%!4 = 0
        SYS "PDriver_GiveRectangle", 0, PrintRectangle%, PrintTransform%, PrintPos%, &ffffffff
        SYS "PDriver_DrawPage", 1, PrintRectangle%, page%, STR$(page%) TO r0%

        p% = gp%
        index_start% = index%
        last_x_offset_start% = last_x_offset%
        x2_start% = x2%
        last_pos_start% = last_pos%
        current_gate_start% = current_gate%
        current_bar_start% = current_bar%
        n_start%() = n%()
        events_start% = events%
        bar_at_front_start% = bar_at_front%
        sig%() = SIG%()

        WHILE r0%

            IF last_page% = page% THEN
                index% = index_start%
                last_x_offset% = last_x_offset_start%
                x2% = x2_start%
                last_pos% = last_pos_start%
                current_gate% = current_gate_start%
                current_bar% = current_bar_start%
                gp% = p%
                n%() = n_start%()
                events% = events_start%
                bar_at_front% = bar_at_front_start%
                SIG%() = sig%()
            ENDIF

            y1% = page_y% - margin% + Stave_Height%
            y3% = page_y% - margin%
            IF PERC% THEN
                y3% -= Stave_Height%
            ENDIF

            stave_n% = 0
            WHILE stave_n% < staves_per_page% AND gp% < GateBlock% + GateSize%

                y2% = (y3% - score_height% + Stave_Height% / 2)
                IF PERC% THEN
                    y2% -= ((stave_n%+1) * Stave_Height%)
                ENDIF
                FOR s% = 0 TO STAVE%+PERC%
                    IF (PERC%) AND (s% = 0) THEN
                        LINE l_edge%, y2%, r_edge%, y2%
                    ELSE
                        LINE l_edge%, y2% - Li% * 4, r_edge%, y2% - Li% * 4
                        LINE l_edge%, y2% - Li% * 2, r_edge%, y2% - Li% * 2
                        LINE l_edge%, y2%, r_edge%, y2%
                        LINE l_edge%, y2% + Li% * 2, r_edge%, y2% + Li% * 2
                        LINE l_edge%, y2% + Li% * 4, r_edge%, y2% + Li% * 4
                    ENDIF
                    y2% = y2% + 3 * Stave_Height%
                NEXT s%
                y3% = y3% - score_height%
                y2% = y1% - Stave_Height%
                IF NOT PERC% THEN
                    y2% -= Stave_Height%
                ELSE
                    y3% = y3% : REM- (2 * Stave_Height%)
                ENDIF

                IF bar_at_front% AND GP% < GateBlock% + GateSize% THEN
                    PROCprint_barline(l_edge%, y2%, 0, FALSE)
                ENDIF

                x1% = l_edge% : REM + (Hi% * 13):
                on_page% = TRUE
                n_bars% = 0
                x% = 0
                xs = l_edge%
                multiplier = 1
                bars_plotted% = 0

                IF gp% < end% THEN
                    PROCfind_last_bar_on_stave (last_pos%, index%, events%, last_x_offset%, n_bars%, r_edge%, l_edge%)
                    multiplier = (r_edge% - 35) / (last_pos%)
                ELSE
                    PROCprint_barline(r_edge%, y2%, gp%>>6, TRUE)
                ENDIF

                WHILE gp% < end% AND on_page%
                    IF gp%?0 THEN
                        G% = gp%?0
                        x% = FNnote_posn(current_bar%, current_gate%, x2%) : REM Extract pre-calulated positions
                        IF x% > last_pos% AND n_bars% = 0 THEN on_page% = FALSE
                        xs = x% * multiplier
                        checkstagger% = FALSE
                        C% = -1
                        P% = 0
                        R% = 0
                        REPEAT
                            REPEAT
                                C% += 1
                            UNTIL G% AND %1 << C%
                            PROCbound_note(n%(C%))
                            IF lx0% > P% THEN P% = lx0%
                            IF lx1% > R% THEN R% = lx1%
                            NC0% = n%(C%)?0: NC1% = n%(C%)?1
                            y% = y2% + Y_STAVE%(S_C%(C%))
                            IF (NC0% AND &F8) THEN
                                l% = (NC0% >> 3) - 16
                                IF ABS(l%) > 5 PROCprint_sprite(ledger% + l% DIV 2, xs, y%, factors%)
                                y% += Li%*l%
                                s% = NC1% >> 5 OR NC0% << 3 AND 8
                                IF checkstagger% THEN
                                    IF ABS(lasty% - y%) < 2 * Li% THEN
                                        PROCprint_sprite(s%,xs+Pgap%, y%, note_factors%)
                                        checkstagger% = FALSE
                                        IF NC1% AND 24 PROCprint_sprite(dot%+(NC1%>>3 AND 3), xs + x%(s%) + Pgap%, y%, note_factors%)
                                        IF (NC0% AND &4) THEN PROCprint_tie(xs,y%)
                                    ELSE
                                        PROCprint_sprite(s%, xs, y%, note_factors%)
                                        IF NC1% AND 24 PROCprint_sprite(dot%+(NC1%>>3 AND 3), xs + x%(s%), y%, note_factors%)
                                        IF (NC0% AND &4) THEN PROCprint_tie(xs,y%)
                                    ENDIF
                                ELSE
                                    PROCprint_sprite(s%, xs, y%, note_factors%)
                                    IF NC1% AND 24 PROCprint_sprite(dot%+(NC1%>>3 AND 3), xs + x%(s%), y%, note_factors%)
                                    IF (NC0% AND &4) THEN PROCprint_tie(xs,y%)
                                    checkstagger%=TRUE
                                ENDIF
                                lasty% = y%
                                IF NC1% AND 7 PROCprint_sprite(accidental% OR NC1% AND 7, xs - x%(s%), y%, note_factors%)
                            ELSE
                                s% = rest% OR NC1% >> 5
                                l% = (NC1% AND &7)
                                IF l% = 0 THEN l% = 4
                                y% += Li% * (l% - 4) * 2
                                PROCprint_sprite(s%, xs, y%, factors%)
                                IF NC1% AND 24 PROCprint_sprite(dot%+(NC1%>>3 AND 3), xs + x%(s%), y%, note_factors%)
                            ENDIF
                            n%(C%)+=NSIZE%
                        UNTIL (2 << C%) > G%
                        gp% += 1
                        current_gate% += 1
                        IF x% >= last_pos% THEN
                            on_page% = FALSE
                            bar_at_front% = FALSE
                        ELSE
                            bar_at_front% = TRUE
                        ENDIF
                    ELSE
                        A% = gp%?1
                        N% = -1: REPEAT N% += 1: UNTIL A% AND %1 << N%
                        CASE N% OF
                            WHEN 0
                                REM Time Sig
                                sig%(0) = A%
                                B$ = STR$((A% >> 1 AND 15) + 1)
                                D$ = STR$(%1 << (A% >> 5) - 1)
                                x% = FNtime_posn(current_bar%, x2%)
                                xs = x% * multiplier
                                w1 = xs
                                IF LEN B$ < 2 THEN w1 += 5 * Hi%
                                IF LEN D$ < 2 THEN xs += 5 * Hi%
                                FOR S% = 0 TO STAVE%+PERC%
                                    PROCprint_sprite(time%+VAL(B$),w1,y2% + Y_STAVE%(S%), note_factors%)
                                    PROCprint_sprite(time%+VAL(D$),w1,y2% + Y_STAVE%(S%)-32, note_factors%)
                                NEXT S%
                            WHEN 1
                                REM Key Sig
                                x% = FNkey_posn(current_bar%, x2%)
                                xs = x% * multiplier
                                IF A% AND 56 sig%(1)=A% ELSE SWAP A%,sig%(1):a%=accidental%+1
                                N%=(A%>>3AND7)-1
                                IF N% >= 0 THEN
                                    A%=A%>>2AND%1
                                    IF a% ELSE a%=accidental%+2+A%
                                    W%=x%(a%)+X%(a%)
                                    x%+=x%(a%)
                                    xs = x% * multiplier
                                    FOR C%=0 TO N%
                                        FOR S%=0TOSTAVE%
                                            PROCprint_sprite(a%,xs,y2%+Y_STAVE%(S%)+Li%*Key_Y%(clef%(S%),A%,C%), note_factors%)
                                        NEXT
                                    x%+=W%
                                    xs = x% * multiplier
                                    NEXT
                                ENDIF

                            WHEN 2
                                REM Clef
                                sig%(0) = A%  : REM Hack
                                S% = A% >> 6
                                x% = FNclef_posn(current_bar%, x2%)
                                xs = x% * multiplier
                                clef%(S%) = (A% >> 3) AND 3
                                IF S% <= STAVE% PROCprint_sprite(clef% + (A% >> 3 AND 3), xs, y2% + Y_STAVE%(S%), factors%)
                            WHEN 3
                                REM Slur

                            WHEN 4
                                REM Octave Shift

                            WHEN 5
                                REM Barline
                                x% = FNbar_posn(current_bar%, x2%)
                                IF x% >= last_pos% THEN
                                    PROCprint_barline(r_edge%, y2%, A%>>6, TRUE)
                                    on_page% = FALSE
                                    IF (current_bar% + 1) MOD 5 = 0 THEN
                                        MOVE r_edge%-2-((1+LEN(STR$(current_bar%+1))-1)*16),y2% + (Y_STAVE%(0) / 2): REM ***
                                        PRINT STR$(current_bar%+1)
                                    ENDIF
                                ELSE
                                    IF x% = l_edge% THEN
                                        xs = x% - 7: REM &&&
                                    ELSE
                                        xs = x% * multiplier
                                    ENDIF
                                    
                                    PROCprint_barline(xs+2, y2%, A%>>6, FALSE)
                                   
                                    IF (current_bar% + 1) MOD 5 = 0 THEN
                                        MOVE xs-1-((LEN(STR$(current_bar%+1)))*16),y2% + (Y_STAVE%(0) / 2): REM ***
                                        PRINT STR$(current_bar%+1)
                                    ENDIF
                                ENDIF

                                current_bar% += 1
                                bars_plotted% += 1
                        ENDCASE
                        gp% += 2
                    ENDIF               : REM gp%?0
                ENDWHILE                : REM on_page%
                IF gp% >= GateBlock% + GateSize% THEN
                    PROCprint_barline(r_edge%, y2%, 0, FALSE)
                ENDIF
                x2% += (last_pos% - l_edge%)
                y1% = y1% - score_height%
                IF PERC% THEN
                    y1% = y1% - margin%
                ENDIF
                stave_n% += 1
            ENDWHILE
            last_page% = page%
            SYS "PDriver_GetRectangle", ,PrintRectangle% TO r0%
        ENDWHILE
    ENDWHILE
    RESTORE ERROR
    SYS "PDriver_EndJob", f%
    CLOSE#f%
    SYS "Hourglass_Off"
ENDPROC

DEF PROCprint_barline (bx%,by%,type%,right%)
LOCAL s%,y%,h%
REM Adjust xpos if along right-hand edge
IF right% THEN bx%=bx%-(X%(bar%+type%)+x%(bar%+type%))+2
FOR s%=0 TO STAVE%+PERC%
  REM Get ypos and height
  y%=Y_STAVE%(s%)-(Stave_Height% DIV 2)  
  IF ((STAVE%+1) AND 2) AND s%=STAVE% THEN
    h%=Stave_Height%*3
  ELSE
    h%=Stave_Height%
  ENDIF
  REM Draw barline 
  CASE type% OF
    WHEN 0:REM Single barline
      MOVE bx%,by%+y%
      DRAW bx%,by%+y%+h%
    WHEN 1:REM Double barline
      MOVE bx%,by%+y%
      DRAW bx%,by%+y%+h%
      RECTANGLE FILL bx%+6,by%+y%,5,h%  
  ENDCASE
NEXT
ENDPROC

DEF PROCprint_tie (sx%,sy%)
LOCAL s%,g%,span%,oldx%,end%
end%=GateBlock%+GateSize%
IF (NC0% AND &2) THEN s%=tie%+1 ELSE s%=tie%
sx%=sx%-(x%(s%))
sy%=sy%-y%(s%)
g%=gp%+1
span%=1
WHILE g%<=end%
  IF ?g% THEN
    IF ?g% AND (1<<C%) THEN
      g%=end%+1
    ELSE
      span%+=1
      g%+=1
    ENDIF
  ELSE
    g%+=2
  ENDIF
ENDWHILE
oldx%=factors%!0
factors%!0=span%
SYS "OS_SpriteOp",SprPlot%,SprBlk%,S%(s%),sx%,sy%,8,factors%,pixtrans%
factors%!0=oldx%
ENDPROC

DEF PROCprint_sprite(s%, X%, Y%, scale_factors%) : REM plot sprite S%(s%) at X%,Y%

  LOCAL spr_y, scaled_y, y_add, x_add

  spr_y = ((S%(s%)!20) + 1) * (2 / 4)    : REM actual height
  scaled_y = spr_y * (2 / 4) : REM scaled height

  x_add = 0

REM Below are various hack factors to deal with the fact that the sprites are scaled down.

  CASE S$(s%) OF
    WHEN "Md", "Cd","Qd", "SQd", "DSQd", "SDSQd"
       x_add = 6
    WHEN "Mu", "Cu", "Qu", "SQu", "DSQu", "SDSQu", "M", "SB", "Natural", "Sharp", "Flat", "Sharp2", "Flat2", "NSharp", "NFlat", "Tie", "Dot1", "Dot2", "Dot3"
      y_add = 2 : x_add = 6
    WHEN "B"
      y_add = 2.5 : x_add = 6
    WHEN "Bar"
      y_add = -0.5
    OTHERWISE
      y_add = 0 : x_add = 6
  ENDCASE

  SYS "OS_SpriteOp", SprPlot%, SprBlk%, S%(s%), X%-(x%(s%)) + x_add, Y% - y%(s%) + y_add, 8, factors%, pixtrans%
ENDPROC



DEF PROCfind_last_bar_on_stave (RETURN last_pos%, RETURN index%, RETURN events%, RETURN last_x_offset%, RETURN n_bars%, r_edge%, l_edge%)

    LOCAL loop%, x%, last_x%

    x% = l_edge% + PX%(index%) - x2%

    WHILE ((index% < GateSize%) AND x% < (r_edge% - 35))

        CASE PTYPE%(index%) OF
            WHEN 0
                IF n_bars% = 0 THEN
                    last_pos% = x%
                ENDIF
                events% += 1
            WHEN 32
                IF x% <> l_edge% THEN
                    last_pos% = x%
                    n_bars% += 1
                ENDIF
                events% += 2
            OTHERWISE
                IF n_bars% = 0 THEN
                    last_pos% = x%
                ENDIF
                events% += 2
        ENDCASE

        index% += 1
        x% = l_edge% + PX%(index%) - x2%

    ENDWHILE

    IF index% >= GateSize% THEN
         last_pos% = r_edge% - 35
    ENDIF
ENDPROC



DEF FNnote_posn(bar%, gate%, x2%)

    LOCAL loop%, enc%, x%, events%

    enc% = 0
    loop% = 0
    events% = 0

    REPEAT
        IF PTYPE%(loop%) = 0 THEN
            enc% += 1
            x% = l_edge% + PX%(loop%) - x2%
        ENDIF
        loop% += 1
    UNTIL (enc% > gate% OR loop% > GateSize%)

= x%

DEF FNclef_posn(bar%, x2%)

    LOCAL loop%, enc%, x%, events%

    enc% = 0
    loop% = 0
    events% = 0

    REPEAT
        IF PTYPE%(loop%) = 4 THEN
            enc% += 1
            x% = l_edge% + PX%(loop%) - x2%
        ENDIF
        loop% += 1
    UNTIL (enc% >= bar% OR loop% > GateSize%)

= x%


DEF FNkey_posn(bar%, x2%)

    LOCAL loop%, enc%, x%, events%

    enc% = 0
    loop% = 0
    events% = 0

    REPEAT
        IF PTYPE%(loop%) = 2 THEN
            enc% += 1
            x% = l_edge% + PX%(loop%) - x2%
        ENDIF
        loop% += 1
    UNTIL (enc% >= bar% OR loop% > GateSize%)

= x%


DEF FNbar_posn(bar%, x2%)

    LOCAL loop%, enc%, x%, events%

    enc% = 0
    loop% = 0
    events% = 0

    REPEAT
        IF PTYPE%(loop%) = 32 THEN
            enc% += 1
            x% = l_edge% + PX%(loop%) - x2%
        ENDIF
        loop% += 1
    UNTIL (enc% > bar% OR loop% > GateSize%)

= x%


DEF FNtime_posn(bar%, x2%)

    LOCAL loop%, enc%, x%, events%

    enc% = 0
    loop% = 0
    events% = 0

    REPEAT
        IF PTYPE%(loop%) = 1 THEN
            enc% += 1
            x% = l_edge% + PX%(loop%) - x2%
        ENDIF
        loop% += 1
    UNTIL (enc% >= bar% OR loop% > GateSize%)


= x%

REM  DEF PROCdebug (a$)
REM
REM      LOCAL a%, i%, l%
REM
REM      i% = LENa$
REM      a% = TRACE
REM      IF i% = 0 THEN ENDPROC
REM
REM      FOR l% = 1 TO i%
REM          BPUT#a%, ASC(MID$(a$, l%, 1))
REM      NEXT
REM      BPUT#a%, 10
REM      BPUT#a%, 13
REM
REM
REM  ENDPROC
REM

REM // Wimp //

DEF FNwimp_opentemplates (path$)
REM Opens a Template file and returns buffer for largest definition
LOCAL size%,largest%,next%,w%,s$
SYS "Wimp_OpenTemplate",,path$
largest%=0
s$="*"+STRING$(11,CHR$(0))
SYS "Wimp_LoadTemplate",,0,,,,s$,0 TO ,size%,,,,,next%
WHILE next%
  IF size%>largest% THEN largest%=size%
  s$="*"+STRING$(11,CHR$(0))
  SYS "Wimp_LoadTemplate",,0,,,,s$,next% TO ,size%,,,,,next%
ENDWHILE
DIM w% largest%+16
=w%

DEF FNwimp_loadtemplate (name$,buffer%,sprites%)
REM Loads a window definition from a Template file
LOCAL indarea%,indsize%,s$,window%
s$=name$+CHR$(13)+STRING$(12-LEN(name$)+1,CHR$(0))
SYS "Wimp_LoadTemplate",,0,,,,s$ TO ,,indsize%
DIM indarea% indsize%
SYS "Wimp_LoadTemplate",,buffer%,indarea%,indarea%+indsize%,-1,s$
IF sprites% THEN buffer%!64=sprites%
SYS "Wimp_CreateWindow",,buffer% TO window%
=window%

DEF FNwimp_geticontext (window%,icon%)
REM Gets indirected text from an icon
Block%!0=window%
Block%!4=icon%
SYS "Wimp_GetIconState",,Block%
=$(Block%!28)

DEF PROCwimp_seticontext (window%,icon%,text$)
REM Sets the indirected text in an icon
Block%!0=window%
Block%!4=icon%
SYS "Wimp_GetIconState",,Block%
$(Block%!28)=text$
Block%!8=0
Block%!12=0
SYS "Wimp_SetIconState",,Block%
ENDPROC

DEF FNwimp_geticonstate (window%,icon%,bit%)
REM Returns TRUE if bit is set, FALSE if not
Block%!0=window%
Block%!4=icon%
SYS "Wimp_GetIconState",,Block%
IF (Block%!24 AND (1<<bit%)) THEN =TRUE ELSE =FALSE

DEF PROCwimp_seticonstate (window%,icon%,bit%,action%)
REM Sets an icon state bit
Block%!0=window%
Block%!4=icon%
SYS "Wimp_GetIconState",,Block%
IF action% THEN Block%!8=1<<bit%:Block%!12=1<<bit%
IF NOT action% THEN Block%!8=0:Block%!12=1<<bit%
SYS "Wimp_SetIconState",,Block%
ENDPROC

DEF PROCwimp_seticonval (window%,icon%,text$)
REM Sets the validation string in an indirected icon
Block%!0=window%
Block%!4=icon%
SYS "Wimp_GetIconState",,Block%
$(Block%!32)=text$
Block%!8=0
Block%!12=0
SYS "Wimp_SetIconState",,Block%
ENDPROC

DEF FNwimp_makemenu (title$,items%)
REM Create a menu
LOCAL buffer%,tbuffer%
DIM buffer% 28+(24*items%)
IF LEN(title$)>12 THEN
  DIM tbuffer% LEN(title$)+1
  buffer%!0=tbuffer%
  buffer%!4=0
  buffer%!8=LEN(title$)+1
  $(tbuffer%)=title$
ELSE
  $(buffer%)=title$
ENDIF
buffer%!12=&00070207
buffer%!16=80
buffer%!20=44
buffer%!24=0
=buffer%

DEF PROCwimp_additem (menu%,item%,flags%,link%,text$)
REM Add a menu item
LOCAL buffer%
menu%!(28+(item%*24))=flags%
menu%!(28+(item%*24)+4)=link%
IF LEN(text$)>12 THEN
  DIM buffer% LEN(text$)+1
  menu%!(28+(item%*24)+8)=&07000121
  menu%!(28+(item%*24)+12)=buffer%
  menu%!(28+(item%*24)+16)=0
  menu%!(28+(item%*24)+20)=LEN(text$)+1
  $(buffer%)=text$
ELSE
  menu%!(28+(item%*24)+8)=&07000021
  $(menu%+28+(item%*24)+12)=text$
ENDIF
ENDPROC

DEF FNwimp_getmenutext (menu%,item%)
REM Gets indirected text from a menu item
LOCAL menuptr%,indarea%,text$
menuptr%=menu%+28
menuptr%+=item%*24
indarea%=menuptr%!12
text$=$(indarea%)
=text$

DEF PROCwimp_setmenutext (menu%,item%,text$)
REM Sets the indirected text in an menu item
LOCAL menuptr%,indarea%
menuptr%=menu%+28
menuptr%+=item%*24
indarea%=menuptr%!12
$(indarea%)=text$
ENDPROC

DEF FNwimp_getmenustate (menu%,item%,bit%)
REM Gets a menu item's flag state
LOCAL menuptr%
menuptr%=menu%+28+(item%*24)
IF ?menuptr% AND (1<<bit%) THEN =TRUE ELSE =FALSE

DEF PROCwimp_setmenustate (menu%,item%,bit%,state%)
REM Sets a menu item's flags
LOCAL menuptr%
menuptr%=menu%+28+(item%*24)
CASE bit% OF
  WHEN 0,1,2,3,4,7,8:
    IF state% THEN menuptr%!0=menuptr%!0 OR (1<<bit%)
    IF NOT state% THEN menuptr%!0=menuptr%!0 AND NOT (1<<bit%)
  WHEN 22:
    IF state% THEN menuptr%!8=menuptr%!8 OR (1<<bit%)
    IF NOT state% THEN menuptr%!8=menuptr%!8 AND NOT (1<<bit%)
ENDCASE
ENDPROC

DEF PROCwimp_radiotick (menu%,item%)
REM Ticks one menu item and unticks the rest
LOCAL menuptr%,i%
menuptr%=menu%+4
REPEAT
  menuptr%+=24
  IF i%=item% THEN
    menuptr%!0=menuptr%!0 OR (1<<0)
  ELSE
    menuptr%!0=menuptr%!0 AND NOT (1<<0)
  ENDIF
  i%+=1
UNTIL menuptr%!0 AND &80
ENDPROC

DEF PROCwimp_opendialogue (dialogue%,window%,type%)
REM Opens a dialogue box centred over a window/over iconbar
REM type%=0 transient; type%=1 persistent
LOCAL x%,y%,dwidth%,dheight%,width%,height%
Block%!0=dialogue%
SYS "Wimp_GetWindowState",,Block%
dwidth%=Block%!12-Block%!4
dheight%=Block%!16-Block%!8
IF window%=-2 THEN
  Block%!0=window%
  Block%!4=IconHandle%
  SYS "Wimp_GetIconState",,Block%
  x%=Block%!8-64    :REM Same x-offset as default menu
  y%=dheight%+136   :REM Iconbar height + 40
ELSE
  Block%!0=window%
  SYS "Wimp_GetWindowState",,Block%
  x%=Block%!4
  y%=Block%!16
  width%=Block%!12-x%
  height%=y%-Block%!8  
  x%=x%+(width% DIV 2)-(dwidth% DIV 2)
  y%=y%-(height% DIV 2)+(dheight% DIV 2)
ENDIF
CASE type% OF
  WHEN 0:REM Open transient dialogue box
    SYS "Wimp_CreateMenu",,dialogue%,x%,y%
  WHEN 1:REM Open persistent dialogue box
    Block%!0=dialogue%
    Block%!4=x%
    Block%!8=y%-dheight%
    Block%!12=x%+dwidth%
    Block%!16=y%
    Block%!20=0
    Block%!24=0
    Block%!28=-1
    SYS "Wimp_OpenWindow",,Block%
ENDCASE
ENDPROC

DEF PROCwimp_closedialogue (dialogue%,window)
REM Closes a persistent dialogue box and returns focus to window
Block%!0=dialogue%
SYS "Wimp_CloseWindow",,Block%
Block%!0=window%
SYS "Wimp_GetWindowState",,Block%
IF Block%!32 AND (1<<16) THEN
  SYS "Wimp_SetCaretPosition",window%,-1
ENDIF 
ENDPROC

DEF FNwimp_popup (window%,menu%,icon%)
REM Opens a popup menu and returns menu handle
LOCAL x%,y%
Block%!0=window%
SYS "Wimp_GetWindowState",,Block%
x%=Block%!4
y%=Block%!16
Block%!4=icon%
SYS "Wimp_GetIconState",,Block%
x%=x%+Block%!16
y%=y%+Block%!20
SYS "Wimp_CreateMenu",,menu%,x%,y%
=menu%

REM // Heap //

DEF FNheap_init (size%)
REM Sets up the Heap
REM Returns the Heap pointer if successful, 0 if error
LOCAL heap%,heapsize%
heapsize%=FNheap_getmemory(0,size%)
IF heapsize%>0 THEN
  heap%=HIMEM
  SYS "OS_Heap",0,heap%,,heapsize%
ENDIF
=heap%

DEF FNheap_newblock (heap%,size%)
REM Creates a new Block within the Heap
REM Returns Block pointer if successful, 0 if error
LOCAL free%,block%,heapsize%
IF size%=0 THEN =0
IF heap%=0 THEN =0
heapsize%=1
SYS "OS_Heap",1,heap% TO ,,free%
IF free%<size% THEN heapsize%=FNheap_getmemory(heap%,size%-free%)
IF heapsize%>0 THEN SYS "OS_Heap",2,heap%,,size% TO ,,block%
=block%

DEF FNheap_resizeblock (heap%,RETURN block%,change%)
REM Resizes a Block
REM Updates the Block pointer
REM Returns 0 if Block could not be extended, otherwise TRUE
LOCAL flags%,page%,heapsize%,success%
success%=TRUE
SYS "OS_ReadMemMapInfo" TO page%
REPEAT
  SYS "XOS_Heap",4,heap%,block%,change% TO ,,block%;flags%
  IF (flags% AND &1) THEN
    heapsize%=FNheap_getmemory(heap%,page%)
    IF heapsize%=0 THEN success%=FALSE
  ENDIF
UNTIL (flags% AND &1)=0 OR success%=FALSE
IF success%=TRUE THEN
  IF change%<0 THEN PROCheap_losememory(heap%)
ENDIF
=success%

DEF PROCheap_freeblock (heap%,block%)
REM Deletes a Block and frees memory
SYS "OS_Heap",3,heap%,block%
PROCheap_losememory(heap%)
ENDPROC

DEF FNheap_getblocksize (heap%,block%)
REM Returns a Block's size
LOCAL size%
SYS "OS_Heap",6,heap%,block% TO ,,,size%
REM Reduce value by size of block header
size%-=4
=size%

DEF FNheap_getmemory (heap%,memory%)
REM Claims memory and extends WimpSlot
REM If heap%=0 then create a new heap
REM Otherwise, take/release memory from the specified heap
REM Returns the size of Heap if successful, 0 if error
LOCAL old%,new%,heapsize%
SYS "Wimp_SlotSize",-1,-1 TO old%
SYS "Wimp_SlotSize",old%+memory%,-1 TO new%
IF new%<old%+memory% THEN
  SYS "Wimp_SlotSize",old%,-1
ELSE
  IF heap% THEN SYS "OS_Heap",5,heap%,,new%-old%
  heapsize%=new%+&8000-HIMEM
ENDIF
=heapsize%

DEF PROCheap_losememory (heap%)
REM Returns memory and shrinks WimpSlot
LOCAL change%,flags%,old%,page%
SYS "OS_ReadMemMapInfo" TO page%
SYS "Wimp_SlotSize",-1,-1 TO old%
REPEAT
  SYS "XOS_Heap",5,heap%,,-page% TO ,,,change%;flags%
  IF (flags% AND &1) THEN
    SYS "OS_Heap",5,heap%,,change%
  ELSE
    SYS "Wimp_SlotSize",old%-page%,-1
  ENDIF
UNTIL (flags% AND &1)
ENDPROC