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")

SPACE%=HIMEM-END :REM check enough space available. Nice friendly way to exit...
IF SPACE%<100000 STOP

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:
        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
    PROCrescore(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:PROClMusic
    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:PROClMusic
          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
    PROCset_score(0)
    PROCSetupBarStarts(0)
    PROCscore_init
    PROCSetDefaultChannels
    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 PROClMusic
LOCALC%,B%,ret%
INPUT#FILE%,GATE%
GATE%+=MUSIC%
FORC%=0TO7
INPUT#FILE%,FINE%(C%):FINE%(C%)+=MUSIC%(C%): REM Sets up start and end pointers for data
NEXT
B%=MUSIC%:WHILEB%<GATE%:?B%=BGET#FILE%:B%+=1:ENDWHILE
FORC%=0TO7
    B%=MUSIC%(C%)
    WHILEB%<FINE%(C%)
        ?B%=BGET#FILE%
        B%+=1
    ENDWHILE
NEXT
PP%=MUSIC%:P%()=MUSIC%()
ENDPROC

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
LOCALC%,B%
BPUT#FILE%,1
PRINT#FILE%,GATE%-MUSIC%
FORC%=0TO7
PRINT#FILE%,FINE%(C%)-MUSIC%(C%)
NEXT
B%=MUSIC%:WHILEB%<GATE%:BPUT#FILE%,?B%:B%+=1:ENDWHILE
FORC%=0TO7
B%=MUSIC%(C%):WHILEB%<FINE%(C%):BPUT#FILE%,?B%:B%+=1: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%,X%,Y%) : REM plot sprite S%(s%) at X%,Y%
SYS "OS_SpriteOp", SprPlot%, SprBlk%, S%(s%), X%-x%(s%),Y%-y%(s%), 8, factors%, pixtrans%
REM overwrite screen colour, but using sprite mask
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
::
REM   PROCEDURE: start_music
REM
REM DESCRIPTION: Initialise to start of music
:
DEF PROCstart_music
LOCAL n%
BAR%=0 :REM current bar
GP%=MUSIC%:REM Set current gate pointer to start of displayed music
N%()=MUSIC%()
CLEF%()=0:REM Start with base clefs (NOT BASS!)
SIG%(0)=%01100111:REM (Default time sig of 4/4 time)
SIG%(1)=%00000010:REM (Default of C Major) First displayed signatures attribute byte
PX%=0
PROCPutBarInfo(0)
ENDPROC

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

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

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

DEF PROCnote_accidental(C%,A%)
N%(C%)?1=N%(C%)?1AND&F8 ORA%
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%,T%)
?N%(C%)=?N%(C%)AND&FB OR(T%<>0)AND4
ENDPROC

DEF PROCnote_join(C%,J%)
?N%(C%)=?N%(C%)AND&FD OR(J%<>0)AND2
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:N%(C%)?1=0
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 PROCinsert_gate(W%)
LOCALG%
IFGP%<GATE% THEN
FORG%=GATE%-W%TOGP%STEP-4:G%!W%=!G%:NEXT:REM Shift up by W% from insertion
G%+=3:REM Byte before last copied
WHILEG%>=GP%:G%?W%=?G%:G%+=TRUE:ENDWHILE:REM Clear up in odd word
ENDIF
GATE%+=W%:REM Insert gate of size W% into gate queue
EP%+=W%:REM This assumes gates will only be inserted below EP%
?GP%=0:GP%?(W%-1)=0:REM Zeroise inserted gate
ENDPROC

DEF PROCinsert_note(C%)
LOCALN%
IFN%(C%)<FINE%(C%) THEN
FORN%=FINE%(C%)-2TON%(C%)STEP-4:N%!2=!N%:NEXT:REM Shift up from insertion
N%+=3:REM Last copied word
WHILEN%>=N%(C%):N%?2=?N%:N%+=TRUE:ENDWHILE:REM Clear up in odd word
ENDIF
FINE%(C%)+=2:REM Insert a note word into this queue
?GP%=?GP%OR%1<<C%:REM Mark in gate
PROCnote_clear(C%)
ENDPROC
::
DEF PROCdelete_gate(W%)
LOCALG%
GATE%-=W%
IFGP%<GATE% FORG%=GP%TOGATE%STEP4:!G%=G%!W%:NEXT
EP%-=W%:REM This assumes gates will only be deleted below EP%
ENDPROC
::
DEF PROCdelete_note(C%)
LOCALN%
FINE%(C%)-=2
IFN%(C%)<FINE%(C%) FORN%=N%(C%)TOFINE%(C%)STEP4:!N%=N%!2:NEXT
?GP%=?GP%ANDNOT(%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%,bar%
B%=TRUE
C%=TRUE
IF ?GP% THEN
ELSE
  REPEAT
    GP%+=2
  UNTIL ?GP% OR GP%>=GATE%
ENDIF
WHILE B% AND GP%<GATE%
  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%>=GATE%
  ENDIF
ENDWHILE
PROCSetupBarStarts(BAR%)
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%)?-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%)
SWAPN%(s%)?1,N%(d%)?1
ENDPROC

DEF PROCmove_note(N%)
LOCALs%,d%
s%=C%(N%):d%=c%(N%)
PROCinsert_note(d%)
?N%(d%)=?N%(s%)
N%(d%)?1=N%(s%)?1
PROCdelete_note(s%)
ENDPROC

DEF FNbest
LOCALN%,C%
LOCAL short%,free%,rest%,any%,tied%
FOR N%=NC%TO0STEP-1
C%=n%(N%)
IFFNin(C%,c%())<0 THEN
  IFN%(C%)?-2AND4 THEN
    tied%=C%+1
  ELSE
    IFpg%AND%1<<C% THEN
      IF(N%(C%)?-1>>3)=shortest% short%=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)>>3
IF R%=0 THEN
  R%=N%(C%)?1 AND &7
  IF R%=0 THEN R%=16 ELSE R%=R%*2
ENDIF
r%=(N%(c%)?-2 AND &F8)>>3
IF r%=0 THEN
  r%=N%(c%)?-1 AND &7
  IF r%=0 THEN r%=16 ELSE r%=r%*2
ENDIF
=N%(c%)-2>=MUSIC%(c%) AND (g% OR (N%(c%)?-2 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%<MUSIC%+2ORgp%?-1=FALSE
=?gp%ANDgp%>MUSIC%+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%>MUSIC%ANDgp%?-1=FALSE:gp%-=2:ENDWHILE
UNTILgm%AND?gp%ORgp%<MUSIC%+2
=?gp%ANDgp%>MUSIC%+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%<GATE%
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
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%)+=2: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 (L%)
LOCAL H%,S%,s%
H%=L%>>8 AND &FF
IF (L% AND &F8) THEN
  REM Note
  S%=H%>>5 OR L%<<3 AND &8
ELSE
  REM Rest
  S%=rest% OR H%>>5
ENDIF
lx0%=x%(S%):REM Prefix width
lx1%=X%(S%):REM Suffix width
ly0%=y%(S%):REM Decender height
ly1%=Y%(S%):REM Ascender height
REM If a Note, and has accidentals, add width
IF (H% AND &7)>0 AND (L% AND &F8)>0 THEN
  s%=accidental% OR (H% AND &7)
  lx0%+=x%(s%):REM Add accidental width
  IF y%(s%)>ly0% THEN ly0%=y%(s%):REM Lower ?
  IF Y%(s%)>ly1% THEN ly1%=Y%(s%):REM Higher ?
ENDIF
REM If Note or Rest has dots, add width
IF (H% AND &18) THEN
  s%=dot%+(H%>>3 AND &3)
  lx1%=x%(S%)+X%(s%)
  REM Dot adds suffix and may be lower
  IF y%(s%)>ly0% THEN ly0%=y%(s%)
ENDIF
ENDPROC

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%)
    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%)
LOCALPX%
BAR%=0
REM must be subtle about redrawing last note (or other item) of 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; but don't lose 1st-note draw
IF NBars%>2 THEN
WHILE PX%(PXn%(BAR%+2))<A% AND BAR%<=NBars%-1
 BAR%+=1
 ENDWHILE  :REM get to drawstart quickly
 IF BAR%>NBars% ENDPROC
 PROCGetBarInfo(BAR%)
 IF GP%>=GATE% ENDPROC
 WHILEPX%(PX%+3)<A%ANDPX%<EX%
  PROCskip_gate
  ENDWHILE:REM Skip music until A%
ELSE
 PROCstart_music
ENDIF
WHILEPX%(PX%)<=B%ANDGP%<GATE%
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
REM
REM Join=?N%(C%)AND2
:
DEF PROCdraw_notes(G%)
LOCAL C%,x%,y%,s%,l%,ly%,NC0%,NC1%,lasty%,checkstagger%
PX%+=1:REM Move on to next position
x%=X%+PX%(PX%):REM Calculate gate column X (convert from work- to screen-coord)
checkstagger%=FALSE
C%=-1
REPEAT
  REPEAT C%-=TRUE:UNTIL G% AND %1<<C%
  NC0%=?N%(C%) : NC1%=N%(C%)?1 :REM fast local vars
  y%=Y%+Y_STAVE%(S_C%(C%))
  IF NC0% AND &F8 THEN
    REM Draw note
    l%=(NC0%>>3)-16
    ly%=y%
    y%+=Li%*l%
    s%=NC1%>>5 OR NC0%<<3 AND 8
    IF checkstagger% THEN
      REM stagger next note if too close to one below
      REM stagger fails sometimes if channels of 2 adjacent notes
      REM are not in draw order?
      IF ABS(lasty%-y%)<2*Li% PROCsprite(s%,x%+Pgap%,y%):checkstagger%=FALSE ELSE PROCsprite(s%,x%,y%)
    ELSE
      PROCsprite(s%,x%,y%):checkstagger%=TRUE
    ENDIF
    IF ABSl%>5 PROCsprite(ledger%+l%DIV2,x%,ly%):REM Ledger lines
    lasty%=y%
    IF NC1% AND 7 PROCsprite(accidental% OR NC1% AND 7,x%-x%(s%),y%)
  ELSE
    REM Draw rest
    s%=rest% OR NC1%>>5
    l%=(NC1% AND &7)
    IF l%<>0 THEN l%-=4
    y%+=(Li%*2)*l%
    PROCsprite(s%,x%,y%)
  ENDIF
  IF NC1% AND 24 PROCsprite(dot%+(NC1%>>3AND3),x%+x%(s%),y%)
  IF NC0% AND 4 PROCsprite(tie%,x%,y%)
  N%(C%)+=2:REM Pull from note queue
UNTIL(2<<C%)>G%:REM Until no more notes (1 bits)
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%<GATE%
 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%)
PX%+=1
LOCALC%
C%=-1
REPEAT
  REPEAT
    C%+=1
  UNTILG%AND%1<<C%
  N%(C%)+=2
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%)
PX%-=1
LOCALC%:C%=TRUE
REPEATREPEATC%-=TRUE:UNTILG%AND%1<<C%:N%(C%)-=2: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 over a current note
  end%=(GP%=EP%)
  IF end% THEN
    PROCinsert_gate(1)
  ELSE
    C%=FNput_conflict(S%,SCRIBE%(line%),type%)
  ENDIF
  IF end% OR C%=-1 THEN
    C%=FNallocate_channel(S%)
    IF C%>=0 THEN
      PROCinsert_note(C%)
      PROCnote_type(C%,s% AND 7)
      PROCnote_line(C%,SCRIBE%(line%),s%)
      IF type%=note% THEN PROCnote_stem(C%,s% AND 8)
      s%=!N%(C%)
      IF end% THEN PROCset_score(PX%):X%=PX%(PX%+1)         
      PROCupdate_note(S%,s%,X%,Y%)
    ENDIF
  ELSE
    s%=!N%(C%)
    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%)
      IF ?GP% THEN
        PROCupdate_note(S%,s%,X%,Y%)
      ELSE
        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
  PROCinsert_gate(1)
  C%=FNallocate_channel(S%)
  PROCinsert_note(C%)
  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
IF?N%(C%)AND4 THEN
PROCnote_tie(C%,FALSE)
PROCscore_update(X%+24,Y%+12,X%+70,Y%+24)
ELSE
LOCALI%
PROCnote_tie(C%,TRUE)
PROCskip_notes(?GP%):GP%+=1
PROCarrange_stave(S%)
GP%=SCRIBE%(sgp%)
FORI%=0TO7:N%(I%)=SCRIBE%(I%):NEXT
IFN%(C%)+2>=FINE%(C%)OR(?N%(C%)EORN%(C%)?2)AND&F8 PROCnote_tie(C%,FALSE) ELSEPROCscore_update(X%+24,Y%+12,X%+70,Y%+24)
ENDIF
ENDPROC

DEF PROCput_accidental
LOCALA%,a%
a%=N%(C%)?1AND7
A%=s%AND7
IFA%=a% A%=0
PROCnote_accidental(C%,A%)
IFA%*a% PROCscore_update(X%-34,Y%-24,X%+16,Y%+52) ELSEPROCrescore(PX%)
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%)
PROCscore_update(X%+24,Y%-8,X%+50,Y%)
ELSE
PROCrescore(PX%)
ENDIF
ENDPROC

DEF PROCput_clef
LOCALc%
c%=s%AND3
IFGP%=EP%OR?GP%OR(GP%?1AND%111)<>Clef% THEN
PROCinsert_gate(2)
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
PROCinsert_gate(2)
PROCclef(S%,c%)
ENDIF
ENDIF
PROCrescore(PX%)
ENDPROC

DEF PROCput_key
IFGP%=EP%OR?GP%OR(GP%?1AND%11)<>Key% THEN
PROCinsert_gate(2)
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
  PROCinsert_gate(2)
  PROCtime_sig(TIME_SIG%(0),TIME_SIG%(1))
  PROCrescore(PX%)
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))
    PROCrescore(PX%)
  ELSE
    REM Delete the Time Signature
    PROCdelete_gate(2)
    PROCrescore(PX%)
  ENDIF
ENDIF
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%>MUSIC% 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
      PROCinsert_gate(2)
      PROCbar
      NBars%+=1
      PROCrescore(PX%)
    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(S%,N%,X%,Y%)
LOCALlx0%,lx1%,ly0%,ly1%
PROCbound_note(N%):REM Get minimum bounding rectangle of note
IFN%AND4 lx1%=X%(tie%):IFY%(tie%)>ly1% ly1%=Y%(tie%):REM Tie sets suffix and may be higher (Tie bounds only apply to updating)
IFABS(Y%-Y_STAVE%(S%))>Li%*5 THEN
IFx%(dot%)>lx0% lx0%=x%(dot%)
IFX%(dot%)>lx1% lx1%=X%(dot%):REM Extend to ledger extent
ENDIF
ly1%+=Y%:Y%-=ly0%:ly0%=Y_STAVE%(S%):REM Stave position
IFly1%<ly0%-Li%*5 ly1%=ly0%-Li%*5 ELSEIFY%>ly0%+Li%*5 Y%=ly0%+Li%*5:REM If ledger lines will be drawn increase vertical update space
PROCscore_update(X%-lx0%,Y%,X%+lx1%+Pgap%,ly1%):REM Update symbol space
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 previous distance, pointers etc
    PROCsavp
    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 previous distance, pointers etc
    PROCsavp
    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, etc
  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%)
    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%=(N%(C%)?1 AND &7)-4
    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 PROCsavp
n%()=N%()
d%=D%
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
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%<GATE%
IF?PP% PROCplay_notes(?PP%):PP%+=1 ELSEPROCplay_attribute(PP%?1):PP%+=2
First%=FALSE
ENDWHILE
IFPP%>=GATE% 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%:IFT%AND4 TIE%=TIE%ANDNOT(%1<<C%):T%=P%(C%)+1:REPEATT%+=2:D%+=Duration%(Tempo%)?(?T%>>3):UNTILT%>FINE%(C%)OR4ANDNOTT%?TRUE:IFD%>254 D%=254
    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%)+=2: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
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
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,-12,-3,20,4,Tie,-12,-3,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 ""

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%
FINE%()=MUSIC%()
PP%=MUSIC%:P%()=MUSIC%()
NBars%=0 :REM number of bars
BAR%=0:REM Current bar number
PBAR%=0:REM Playing bar counter
SBAR%=0:REM scrolling bar number
PROCstart_music:REM Set pointers to start of music store
EP%=GP%
REM Set BarlineType to single
BarlineType%=0
PROCwimp_seticonval(SharpsPane_h%,13,"Ssbarline")
PROCbar:REM Music always starts off with a bar
GATE%=MUSIC%+2:REM End of minimum score
PX%(0)=0:REM Score starts at left window edge
PXn%(BAR%)=0
PW%(0)=4*Hi%:REM Bar width
PTYPE%(0)=Bar%:REM Score starts with a bar
GP%=MUSIC%:REM Reset gate pointer for set score
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
SPACE%=HIMEM-END :REM find available space to tailor allocation
IFFNassert(SPACE%>1024,"OutMem3") STOP

Max_Gate%=SPACE%/100 :REM max no. of gates (events, chords)
REM Me thinks this is a hatchet job. Let's try and be more accurate...
REM But there again, all space reserved is USED. Inefficiency alert!
REM 400K odd used for a 26K tune. Whew!
Max_Bar%=Max_Gate%/4 :REM maximum number of bars

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
::
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 MUSIC%(7),FINE%(7):REM Pointers delimiting music storage
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.

LOCAL n%
n%=MaxNotesInBar%*4

DIM MIDI_OFF%(1,n%) :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

SPACE%=HIMEM-END
SPACE%-=&6000:REM Leave a few K for the program and Basic stack (was &4000 before printing)

IFFNassert(SPACE%>1024,"OutMem3") STOP
DIM MUSIC% SPACE%+8:REM Allocate main music space (+8: Extra bytes in case of overrun)

FINE%=MUSIC%+SPACE%:REM End of music memory
GATE%=MUSIC%:REM No gate space used as yet

FORC%=0TO7
  MUSIC%(C%)=MUSIC%+(C%+1)*SPACE%/9
NEXT:REM Share out storage (NB No bounds checking ever occurs!)

FINE%()=MUSIC%():REM No music defined yet

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 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
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% PROCexit :PROCCloseWindow(QuitQuery_h%)
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, "", "")

  FILE%=FILE%:IF FILE% CLOSE#FILE% : FILE%=FALSE
  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%

    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%() = MUSIC%()
    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%)

    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% = MUSIC%
    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% < GATE%
        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% < GATE%

                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% < GATE% 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% < GATE% 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% < GATE% 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  PROCprint_sprite(tie%, xs + Pgap%, y%, note_factors%)
                                    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  PROCprint_sprite(tie%, xs, y%, note_factors%)
                                    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  PROCprint_sprite(tie%, xs, y%, note_factors%)
                                    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%) += 2
                        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% >= GATE% 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_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% < (GATE% - MUSIC%)) 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% >= GATE% - MUSIC% 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% > GATE% - MUSIC%)

= 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% > GATE% - MUSIC%)

= 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% > GATE% - MUSIC%)

= 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% > GATE% - MUSIC%)

= 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% > GATE% - MUSIC%)


= 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%