\ midi utilities menu & gui \ \ midi_menu \ generic, mac specific, menu bar items for testing and configuring midi io. \ Note that a good deal of the code is dependent on mac's gui and will not \ be easy to port. \ \ note: this component requires the resource file "midi menu res". \ \ \ description of words: \ \ midi.menu.init ( -- , initialize system ) \ midi.menu.term ( -- , terminate & restore menubar ) \ \ call the above words before and after use, respectively. \ \ midi.menu.default ( -- , sets variables to default ) \ \ called internally by MIDI.MENU.INIT. The word restores internal variables \ to their default state, but does not affect the state of the menu. \ \ draw.midi.menu ( -- , add the midi menu to the menubar ) \ undraw.midi.menu ( -- , removes the midi menu from the menubar ) \ \ these words add and remove, respectively, the midi menu to the menubar. \ \ enable.midi.menu ( item# -- , enable one midi menu item ) \ disable.midi.menu ( item# -- , disable one midi menu item ) \ \ midi.menu.on ( -- , enables the entire midi menu ) \ midi.menu.off ( -- , disables the entire midi menu ) \ \ ENABLE.MIDI.MENU and DISABLE.MIDI.MENU, respectively, turns on and off the \ given item of the midi menu. MIDI.MENU.OFF diables the entire menu, while \ MIDI.MENU.ON turns the menu back to its previous state (i.e. any individual \ items that have been disabled, will remain disabled). \ \ midi.menu.play ( -- , only enable items useful when MIDI is active ) \ midi.menu.idle ( -- , have all items available for use ) \ \ These are for quick and easy changing of the midi menu from a state in \ which all items are active to one in which only those useful in performance \ are active (panic & alloff). \ \ \ code: Han-earl Park \ copyright 2004 buster & friends' C-ALTO Labs \ (Southampton, May 2000 - \ \ MOD: HeP 05/23/00 Started project. \ MOD: HeP 07/12/00 Add the "which-part @ 15 AND" line...!?! \ MOD: HeP 07/15/00 Add words to enable/disable menu and menu items. \ MOD: HeP 07/24/00 Preliminary test of the "modeless" dialog box. \ MOD: HeP 07/26/00 Doesn't seem to be an easy way of keeping the dialog box \ at front due to the way the H4th event tracker works! \ Add menu for changing buffer size (i.e. time-advance). \ MOD: HeP 08/07/00 MIDI.MENU.ON and MIDI.MENU.OFF respectively enables and \ disables midi menu instead of each and every menu item. \ MOD: HeP 08/26/00 Change resource id numbers. Note that hmsl restricts menu \ id numbers to between 128 and 143. \ MOD: HeP 08/26/00 Change the dialog boxs' procID from 5 to 4 to prevent \ possible problems w/ earlier system software. \ MOD: HeP 09/03/00 Add MENUS.ON and MENUS.OFF which enable and disable all \ of hmsl's (and user defined) menus. \ MOD: HeP 09/08/00 Test out the midi dialog. Works, although in terms of Mac \ human interface guidelines, a bit funky :-/ \ MENUS.ON and MENUS.OFF enables/disables the entire apple \ menu, not just the "about..." item. \ MIDI.DIALOG.START and MIDI.DIALOG.STOP are called from \ MDLOG.OPEN and MDLOG.CLOSE. \ Implement DRAW.MIDI.MENU and UNDRAW.MIDI.MENU and tested. \ Add "parent" words to words that alter the appearance of \ the menubar. The words are the same except the "parent" \ words do not call DrawMenuBar(). \ MOD: HeP 09/11/00 Setup default values for buffer, port and driver menus. \ BUFFER.MENU.FUNC checks current menu item and unchecks \ previously selected item. \ MOD: HeP 09/12/00 Stop the driver selector from retriggering (eg. when the \ custom driver is called twice). \ MOD: HeP 09/14/00 Buffer Size dialog box specifies time in milliseconds. \ Buffer Size menu and dialog box implemented and tested. \ Change MDLOG. prefix to MTDLOG. and BDLOG. to BTDLOG. \ Trash MIDI.DIALOG.START and MIDI.DIALOG.STOP. \ MOD: HeP 09/15/00 Provisional (?) implementation of MIDIM.AVAILABLE? to \ check whether apple midi manager is installed. \ Moved commonly used dialog support code to myt:dialog. \ MOD: HeP 09/19/00 Fix bug involving code in myt:dialog closing dialogs. \ Reintroduce the user specified dialog start/stop words. \ MOD: HeP 09/22/00 Fix bug in MBDLOG.HANDLE.OK that left a zero on the stack. \ MOD: HeP 10/23/00 Fix another bug in MBDLOG.HANDLE.OK that left a number \ on the stack! \ MOD: HeP 11/20/00 midi test uses duration based on current RTC.RATE@. \ MOD: HeP 11/22/00 Change command key for Panic menu item from ascii ! to P. \ MOD: HeP 03/22/01 Use DLOG.CLICK.BUTTON when EOL in dialog windows. \ MOD: HeP 03/23/01 Rewrite MBDLOG.GET.NUMBER based on DLOG.GET.INT. \ Fix: Selecting the other_sec_item# menu item used to \ update the buffer size value, even when dialog window was \ already open. \ MOD: HeP 05-06-04 allot 256 bytes for buffer-item-text. \ MOD: HeP 05-24-04 Fix (MIDI.MENU.ON) and (MIDI.MENU.OFF) which used to call \ DrawMenuBar(). \ MIDI.MENU.PLAY and MIDI.MENU.IDLE not longer have a \ redundant call to DrawMenuBar(). include? task-dialog myt:dialog anew task-midi_menu .NEED ticks>msec : TICKS>MSEC ( ticks -- msec , convert ticks to milliseconds ) 1000 * rtc.rate@ / ; : MSEC>TICKS ( msec -- ticks , convert milliseconds to ticks ) rtc.rate@ * 1000 / ; .THEN \ resource id# 140 constant test_dlog_id 141 constant buffer_dlog_id 140 constant midi_menu_id 141 constant buffer_menu_id 142 constant port_menu_id 143 constant driver_menu_id \ menu handles variable midi-menu-handle variable buffer-menu-handle variable port-menu-handle variable driver-menu-handle \ menu item# 1 constant midi_test_item# 2 constant midi_thru_item# 4 constant midi_panic_item# 5 constant midi_alloff_item# 7 constant midi_buffer_item# 8 constant midi_port_item# 9 constant midi_driver_item# 1 constant 0.0_sec_item# 2 constant 0.5_sec_item# 3 constant 1.0_sec_item# 4 constant 1.5_sec_item# 5 constant 2.0_sec_item# 7 constant other_sec_item# 1 constant modem_item# 2 constant printer_item# 1 constant apple_midi_item# 2 constant custom_midi_item# \ menu state variable mmenu-buffer variable mmenu-port variable mmenu-driver 1.0_sec_item# value mmenu_default_buffer modem_item# value mmenu_default_port custom_midi_item# value mmenu_default_driver \ draw & undraw midi menu : (DRAW.MIDI.MENU) ( -- ) midi-menu-handle @ 0 InsertMenu() \ menuHandle beforeID -- buffer-menu-handle @ -1 InsertMenu() port-menu-handle @ -1 InsertMenu() driver-menu-handle @ -1 InsertMenu() ; : (UNDRAW.MIDI.MENU) ( -- ) midi-menu-handle @ IF midi_menu_id DeleteMenu() buffer_menu_id DeleteMenu() port_menu_id DeleteMenu() driver_menu_id DeleteMenu() THEN ; : DRAW.MIDI.MENU ( -- ) (draw.midi.menu) DrawMenuBar() ; : UNDRAW.MIDI.MENU ( -- ) (undraw.midi.menu) DrawMenuBar() ; \ disable & enable menu : (ENABLE.MIDI.MENU) ( item# -- ) midi-menu-handle @ swap EnableItem() ; : (DISABLE.MIDI.MENU) ( item# -- ) midi-menu-handle @ swap DisableItem() ; : ENABLE.MIDI.MENU ( item# -- , enable one midi menu item ) (enable.midi.menu) DrawMenuBar() ; : DISABLE.MIDI.MENU ( item# -- , disable item ) (disable.midi.menu) DrawMenuBar() ; : (MIDI.MENU.ON) ( -- ) 0 (enable.midi.menu) \ *** used to call enable.midi.menu *** ; : (MIDI.MENU.OFF) ( -- ) 0 (disable.midi.menu) \ *** used to call disable.midi.menu *** ; : MIDI.MENU.ON ( -- , enable midi menu ) (midi.menu.on) DrawMenuBar() ; : MIDI.MENU.OFF ( -- , disable midi menu ) (midi.menu.off) DrawMenuBar() ; \ disable & enable all hmsl's menus : (MENUS.ON) ( -- ) 144 128 DO i GetMHandle() \ -- handle | 0 ?dup IF 0 EnableItem() THEN LOOP ; : (MENUS.OFF) ( -- ) 144 128 DO i GetMHandle() \ -- handle | 0 ?dup IF 0 DisableItem() THEN LOOP ; : MENUS.ON ( -- , enable all of hmsl's menus ) (menus.on) DrawMenuBar() ; : MENUS.OFF ( -- , diable all of hmsl's menus ) (menus.off) DrawMenuBar() ; \ midi dialogs variable midi-dlog defer MTDLOG.START ( -- , user specified procedure ) defer MTDLOG.STOP ( -- ) 'c noop is mtdlog.start 'c noop is mtdlog.stop : (MTDLOG.START) ( -- ) \ menus.off ; : (MTDLOG.STOP) ( -- ) \ menus.on ; : MTDLOG.OPEN ( -- , open non-modal dialog box ) midi-dlog test_dlog_id dlog.open MTDLOG.START ; : MTDLOG.CLOSE ( -- , close midi dialog box ) midi-dlog dlog.close MTDLOG.STOP ; : MTDLOG.SET.TEXT ( $ -- , set static text of the dialog box ) midi-dlog @ 2 dlog.item.handle@ swap SetIText() \ itemHandle string -- ; : MIDI.DIALOG ( $ -- , set text and open dialog box ) mtdlog.open mtdlog.set.text ; \ dialog event handling \ \ We use an custom event loop instead of ?TERMINAL to prevent another window \ (eg. the HMSL or H4th window) from becoming active. We should ignore the \ any mouse clicks that activate other windows, but allows other apps to \ continue running in the background. -1 constant every_event \ mac event filter : MTDLOG.CLOSE.EVENT? ( -- flag , has user closed dialog box ) last-event ..@ er_what keyDwnEvt = IF last-event ..@ er_message $ FF and EOL= \ -- flag dup IF midi-dlog 1 dlog.click.button \ dlogVar item# -- THEN ELSE last-event midi-dlog item-hit DialogSelect() \ -- flag THEN ; : MTDLOG.HANDLE.EVENT ( -- , handle non-dialog events ) \ handle other events here? last-event ..@ er_what updatEvt = IF last-event ..@ er_message BeginUpdate() last-event ..@ er_message EndUpdate() THEN ; : (MTDLOG.EVENT) ( -- flag , event tracker for semi-modeless operation ) last-event IsDialogEvent() IF mtdlog.close.event? \ -- flag dup IF mtdlog.close \ close dialog box THEN ELSE mtdlog.handle.event false THEN ; : MTDLOG.EVENT ( -- flag , non-MultiFinder dialog event tracker ) SystemTask() \ every_event last-event GetNextEvent() \ eventmask event -- 0|1 IF (MTDLOG.EVENT) \ -- flag ELSE false THEN ; : MTDLOG.MF.EVENT ( -- flag , MultiFinder dialog event tracker ) every_event last-event 0 null WaitNextEvent() \ eventmask event sleep mouseRgn -- 0|1 IF (MTDLOG.EVENT) \ -- flag ELSE false THEN ; \ midi test dialog : (MIDI.TEST.DIALOG) { last_time -- curr_time , run test } time@ last_time - rtc.rate@ 4/ > IF time@ -> last_time \ 17 1 DO i midi.channel! midi.lastoff 61 choose 36 + 80 midi.noteon LOOP THEN \ last_time ; : MIDI.TEST.DIALOG ( -- ) " Sending random(ish) MIDI note on messages on all 16 channels." midi.dialog \ { | last_time -- } time@ 20 + -> last_time \ IfMulti \ -- flag , MultiFinder available? IF BEGIN last_time (midi.test.dialog) -> last_time mtdlog.mf.event \ -- flag , has dialog box been closed? UNTIL ELSE BEGIN last_time (midi.test.dialog) -> last_time mtdlog.event \ -- flag , has dialog box been closed? UNTIL THEN \ 17 1 DO i midi.channel! midi.lastoff LOOP ; \ midi thru dialog : (MIDI.THRU.DIALOG) ( -- ) 8 0 DO midi.recv IF midi.xmit midi.flush THEN LOOP ; : MIDI.THRU.DIALOG ( -- ) " Echoing MIDI messages recieved on input port to output port." midi.dialog \ midi.clear \ IfMulti \ -- flag , MultiFinder available? IF BEGIN (midi.thru.dialog) mtdlog.mf.event \ -- flag , has dialog box been closed? UNTIL ELSE BEGIN (midi.thru.dialog) mtdlog.event \ -- flag , has dialog box been closed? UNTIL THEN ; \ midi menu functions : MIDI.MENU.FUNC ( -- , midi menu handler ) which-part @ 15 AND CASE midi_test_item# OF midi.test.dialog ENDOF midi_thru_item# OF midi.thru.dialog ENDOF \ ------------------------ midi_panic_item# OF midi.clear midi.panic ENDOF midi_alloff_item# OF midi.clear midi.killall ENDOF ENDCASE ; \ midi buffer dialog variable buffer-dlog windowTracker buffer-tracker create buffer-item-text 256 allot \ handle dialog text : MBDLOG.SET.TEXT ( addr count -- , set user text of the dialog box ) buffer-item-text off buffer-item-text $append \ setup buffer-item-text \ buffer-dlog @ 4 dlog.item.handle@ buffer-item-text SetIText() \ itemHandle $ -- \ buffer-dlog @ 4 0 buffer-item-text c@ SeLIText() \ the-dialog itemno strtsel endsel -- ; : MBDLOG.GET.TEXT ( -- addr count , get user text of the dialog box ) buffer-dlog @ 4 dlog.item.handle@ buffer-item-text GetIText() \ itemHandle varText -- \ buffer-item-text count ; : MBDLOG.SET.NUMBER ( n -- ) n>text mbdlog.set.text ; : MBDLOG.GET.NUMBER ( -- n flag , true if valid number ) mbdlog.get.text text>string \ number? \ -- d true | false IF drop \ just want 32 bit precision \ dpl @ \ decimal point location dup 0> IF 0 DO 10 / LOOP ELSE drop THEN \ TRUE \ -- n true ELSE 0 FALSE \ -- 0 false THEN ; \ open/close dialog : MBDLOG.OPEN ( -- , open modeless MIDI buffer dialog box ) buffer-dlog @ 0= IF buffer-dlog buffer_dlog_id dlog.open buffer-dlog @ buffer-tracker link.window<->tracker \ time-advance @ ticks>msec mbdlog.set.number ELSE buffer-dlog buffer_dlog_id dlog.open THEN ; : MBDLOG.CLOSE ( -- , close midi dialog box ) buffer-dlog dlog.close ; \ dialog event handling : MBDLOG.HANDLE.OK ( -- ) mbdlog.get.number IF msec>ticks dup 0 rtc.rate@ 4* within? IF dup time-advance ! \ CASE 0 OF 0.0_sec_item# ENDOF rtc.rate@ 2/ OF 0.5_sec_item# ENDOF rtc.rate@ OF 1.0_sec_item# ENDOF rtc.rate@ dup 2/ + OF 1.5_sec_item# ENDOF rtc.rate@ 2* OF 2.0_sec_item# ENDOF \ other_sec_item# swap ENDCASE \ buffer-menu-handle @ mmenu-buffer @ FALSE checkitem() buffer-menu-handle @ over TRUE checkitem() mmenu-buffer ! \ mbdlog.close ELSE drop " Buffer size must be between 0 and 4000 milliseconds." dialog.a THEN ELSE drop " Invalid number!" dialog.a THEN ; : MBDLOG.HANDLE.CANCEL ( -- ) mbdlog.close ; : MBDLOG.HANDLE.DEFAULT ( -- ) mmenu_default_buffer CASE 0.0_sec_item# OF " 0" count mbdlog.set.text ENDOF 0.5_sec_item# OF " 500" count mbdlog.set.text ENDOF 1.0_sec_item# OF " 1000" count mbdlog.set.text ENDOF 1.5_sec_item# OF " 1500" count mbdlog.set.text ENDOF 2.0_sec_item# OF " 2000" count mbdlog.set.text ENDOF ENDCASE ; : MBDLOG.HANDLE.HIT ( item# -- ) CASE 1 OF mbdlog.handle.ok ENDOF 2 OF mbdlog.handle.cancel ENDOF 3 OF mbdlog.handle.default ENDOF ENDCASE ; : (MBDLOG.HANDLE.EVENT) ( -- processed? ) last-event IsDialogEvent() IF last-event ..@ er_what keyDwnEvt = IF last-event ..@ er_message $ FF and EOL= IF buffer-dlog 1 dlog.click.button \ dlogVar item# -- mbdlog.handle.ok true ELSE false THEN ELSE false THEN 0= IF last-event buffer-dlog item-hit DialogSelect() IF item-hit w@ mbdlog.handle.hit THEN THEN true ELSE false THEN ; : MBDLOG.HANDLE.EVENT ( -- ) (mbdlog.handle.event) drop ; : MBDLOG.WINDOW.CONTENT ( -- ) FrontWindow() which-window @ - IF which-window @ SelectWindow() THEN ; : MBDLOG.WINDOW.DRAG ( -- ) which-window @ last-event ..@ er_where DragRect DragWindow() ; : MBDLOG.HANDLE.DOWN ( -- ) (mbdlog.handle.event) 0= IF which-part @ CASE InContent OF mbdlog.window.content ENDOF InDrag OF mbdlog.window.drag ENDOF ENDCASE THEN ; : MIDI.BUFFER.DIALOG ( -- ) mbdlog.open ; \ buffer size submenu function : BUFFER.MENU.FUNC ( -- ) which-part @ 15 AND \ dup other_sec_item# = IF drop midi.buffer.dialog ELSE dup CASE 0.0_sec_item# OF 0 time-advance ! ENDOF 0.5_sec_item# OF rtc.rate@ 2/ time-advance ! ENDOF 1.0_sec_item# OF rtc.rate@ time-advance ! ENDOF 1.5_sec_item# OF rtc.rate@ dup 2/ + time-advance ! ENDOF 2.0_sec_item# OF rtc.rate@ 2* time-advance ! ENDOF ENDCASE \ buffer-menu-handle @ mmenu-buffer @ FALSE checkitem() buffer-menu-handle @ over TRUE checkitem() mmenu-buffer ! \ buffer-dlog @ IF time-advance @ ticks>msec mbdlog.set.number THEN THEN ; \ serial port submenu function : PORT.MENU.FUNC ( -- ) which-part @ 15 AND \ dup CASE modem_item# OF modem_port midi-port ! ENDOF printer_item# OF printer_port midi-port ! ENDOF ENDCASE \ port-menu-handle @ mmenu-port @ FALSE checkitem() port-menu-handle @ over TRUE checkitem() mmenu-port ! ; \ driver submenu functions : MIDI.DRIVER.DIALOG ( -- flag ) " Are you sure you want to switch MIDI drivers?" dialog.a/b ; : MIDI.MANAGER.DIALOG ( -- ) " The Apple MIDI Manger is not present in your system. Install the driver, and restart your computer." dialog.a ; \ check for midi manager \ \ note: I couldn't find any documentation regarding the apple midi manager \ or SndDispVersion(). I'm relying on the code used in hmsl to infer the \ working of the system. The joys of using legacy software :-) : MIDIM.AVAILABLE? ( -- flag , check if Apple MIDI Manager is installed ) MIDIToolNum SndDispVersion() ; : MIDIM.PORTS.INIT+ ( -- flag , initialize ports for MIDI ) midim-if-on @ 0= IF midim.available? IF ." Apple MIDI Manager Initialized!" cr midim.signin midim.add.ports midim-if-on on midim.connect.ports \ TRUE ELSE MIDI.MANAGER.DIALOG \ FALSE THEN ELSE TRUE THEN ; : DRIVER.MENU.APPLE ( -- ) mmenu-driver @ custom_midi_item# = IF midi.driver.dialog IF midi-port @ \ save current port setting use.midi.manager \ MIDIM.PORTS.INIT+ IF midi.init \ ??? is this the correct .INIT word ??? \ driver-menu-handle @ apple_midi_item# TRUE checkitem() driver-menu-handle @ custom_midi_item# FALSE checkitem() \ apple_midi_item# mmenu-driver ! ELSE use.custom.midi \ driver-menu-handle @ apple_midi_item# DisableItem() THEN \ midi-port ! \ restore port setting THEN THEN ; : DRIVER.MENU.CUSTOM ( -- ) mmenu-driver @ apple_midi_item# = IF midi.driver.dialog IF midi-port @ \ save current port setting \ use.custom.midi midi.init \ ??? is this the correct .INIT word ??? \ driver-menu-handle @ apple_midi_item# FALSE checkitem() driver-menu-handle @ custom_midi_item# TRUE checkitem() \ custom_midi_item# mmenu-driver ! \ midi-port ! \ restore port setting THEN THEN ; : DRIVER.MENU.FUNC ( -- , driver selector ) which-part @ 15 AND CASE apple_midi_item# OF driver.menu.apple ENDOF custom_midi_item# OF driver.menu.custom ENDOF ENDCASE ; \ simple menu state control \ \ note: for consistency with other words here, we define "parent" words : (MIDI.MENU.PLAY) ( -- ) midi_test_item# (disable.midi.menu) midi_thru_item# (disable.midi.menu) midi_panic_item# (enable.midi.menu) \ enable panic item midi_alloff_item# (enable.midi.menu) \ enable alloff items midi_buffer_item# (disable.midi.menu) midi_port_item# (disable.midi.menu) midi_driver_item# (disable.midi.menu) \ mtdlog.close mbdlog.close ; : (MIDI.MENU.IDLE) ( -- ) midi_test_item# (enable.midi.menu) midi_thru_item# (enable.midi.menu) midi_panic_item# (enable.midi.menu) midi_alloff_item# (enable.midi.menu) midi_buffer_item# (enable.midi.menu) midi_port_item# (enable.midi.menu) midi_driver_item# (enable.midi.menu) ; : MIDI.MENU.PLAY ( -- , only enable items useful when MIDI is active ) (midi.menu.play) ; : MIDI.MENU.IDLE ( -- , have all items available for use ) (midi.menu.idle) ; \ setup & clearup : MIDI.MENU.DEFAULT ( -- ) mmenu_default_buffer mmenu-buffer ! mmenu_default_port mmenu-port ! mmenu_default_driver mmenu-driver ! ; : MIDI.MENU.INIT ( -- ) midi.menu.default \ 0 midi-dlog ! 'c (mtdlog.start) is MTDLOG.START 'c (mtdlog.stop) is MTDLOG.STOP \ 0 buffer-dlog ! 'c mbdlog.handle.event buffer-tracker ..! wt_evhandler 'c mbdlog.handle.down buffer-tracker ..! wt_MDHandler \ midi_menu_id GetMenu() midi-menu-handle ! buffer_menu_id GetMenu() buffer-menu-handle ! port_menu_id GetMenu() port-menu-handle ! driver_menu_id GetMenu() driver-menu-handle ! \ 'c midi.menu.func midi_menu_id ev.menu.func! 'c buffer.menu.func buffer_menu_id ev.menu.func! 'c port.menu.func port_menu_id ev.menu.func! 'c driver.menu.func driver_menu_id ev.menu.func! \ midim.available? NOT IF driver-menu-handle @ apple_midi_item# DisableItem() custom_midi_item# -> mmenu_default_driver THEN ; : MIDI.MENU.TERM ( -- ) mtdlog.close \ mbdlog.close \ undraw.midi.menu \ midi-menu-handle @ IF midi-menu-handle @ DisposeMenu() buffer-menu-handle @ DisposeMenu() port-menu-handle @ DisposeMenu() driver-menu-handle @ DisposeMenu() \ 0 midi-menu-handle ! 0 buffer-menu-handle ! 0 port-menu-handle ! 0 driver-menu-handle ! THEN ; if.forgotten midi.menu.term cr ." Remember to include the 'midi menu res' resource file." cr cr