package provide dialog_midi 0.1 package require pdtcl_compat namespace eval ::dialog_midi:: { namespace export pdtk_midi_dialog namespace export pdtk_alsa_midi_dialog variable referenceconfig "" } # reference configuration string (to detect whether the config has changed) set ::dialog_midi::referenceconfig "" # the length of the 'currently used' lists is currently hardcoded to 9 # (to match Pd's "midi-dialog") set ::dialog_midi::max_devices 9 # selected devices # a value of '0' indicates no-device; a value of '1' is the first valid MIDI device,... #::dialog_midi::indev1 -- ::dialog_midi::indev${max_devices} #::dialog_midi::outdev1 -- ::dialog_midi::outdev${max_devices} ## midi devices (available and used) # ::midi_*devlist: a list of available input devices (type:string) # ::midi_*devices: currently used input devices (type:index) # ::midi_*devcechannels: currently used channels per input device (type:int) # the length of the 'currently used' lists is $max_devices # '-1' indicates no-device; '0' is the first valid MIDI device... # (WARNING: this is different from ::dialog_midi::iodevN!!!) set ::midi_indevlist {} set ::midi_indevices {-1 -1 -1 -1 -1 -1 -1 -1 -1} set ::midi_outdevlist {} set ::midi_outdevices {-1 -1 -1 -1 -1 -1 -1 -1 -1} ####################### midi dialog ################## proc ::dialog_midi::config2string { } { set result {} foreach dir {in out} { upvar ::midi_${dir}devlist devlist upvar ::dialog_midi::ports${dir} ports for {set i 1} {$i <= $::dialog_midi::max_devices} {incr i} { upvar ::dialog_midi::${dir}dev${i} dev if { $dev < 0 || $dev > [llength $devlist] } { set dev 0 } lappend result $dev } set ports [expr $ports + 0] } lappend result $::dialog_midi::portsin $::dialog_midi::portsout return $result } proc ::dialog_midi::apply {mytoplevel {force ""}} { set config [config2string] if { $force ne "" || $config ne $::dialog_midi::referenceconfig} { pdsend "pd midi-dialog ${config}" } set ::dialog_midi::referenceconfig $config } proc ::dialog_midi::cancel {mytoplevel} { destroy $mytoplevel } proc ::dialog_midi::ok {mytoplevel} { ::dialog_midi::apply $mytoplevel 1 ::dialog_midi::cancel $mytoplevel } proc ::dialog_midi::fill_frame_device {frame direction port} { ## create a single device-pulldown # - : where to create the pull-down # - : 'in' or 'out' # - : port upvar ::dialog_midi::${direction}dev${port} selected upvar ::midi_${direction}devlist devlist set x $selected set device [lindex $devlist $x] set nodevice_name [format "(%s)" [_ "no device" ]] # the 1st device is typically 'none', which really means "no device" if { $x == 0 && ( $device eq "" || $device eq "none" ) } { set device ${nodevice_name} } # invalid device selected if { "$x" < 0 || $x >= [llength $devlist ]} { set device ${nodevice_name} } # create the pull-down button (using the currently selected device name as text) label $frame.l1 -text "${port}:" menubutton $frame.x1 -indicatoron 1 -menu $frame.x1.menu \ -relief raised -highlightthickness 1 -anchor c \ -direction flush \ -text ${device} menu $frame.x1.menu # create the pull-down options set idx 0 foreach dev $devlist { if { $idx == 0 && ( $dev eq "" || $dev eq "none") } { set dev ${nodevice_name} } $frame.x1.menu add radiobutton \ -label "${dev}" \ -value ${idx} -variable ::dialog_midi::${direction}dev${port} \ -command [list $frame.x1 configure -text ${dev}] incr idx } pack $frame.l1 -side left pack $frame.x1 -side left -fill x -expand 1 } proc ::dialog_midi::fill_frame_devices {frame direction maxports} { ## create a list of device pull-downs (one for each port) # side effects: ## ro: devlist_midi_${direction}devlist upvar ::midi_${direction}devlist devlist # NB: don't create entry for 'none' device set numdevs [expr [llength $devlist] - 1] if { $maxports > $numdevs } { set maxports $numdevs } for {set p 1} {$p <= $maxports} {incr p} { set w ${frame}.${p}f frame $w pack $w -side top -fill x fill_frame_device $w ${direction} ${p} } if {$maxports < 1} { label $frame.label -text [_ "no input devices" ] pack $frame.label -side top -fill x } } proc ::dialog_midi::make_frame_iodevices {frame maxdevs longform} { # device-selection dialog (OSS, PortMidi) # side effects: none if {[winfo exists $frame]} { destroy $frame } frame $frame pack $frame -side top -fill x -anchor n -expand 1 set showdevs $maxdevs if {$longform == 0} { set showdevs 1 } # input devices labelframe $frame.inputs -text [_ "Input Devices"] -padx 5 -pady 5 -borderwidth 1 pack $frame.inputs -side top -fill x -pady 5 ::dialog_midi::fill_frame_devices $frame.inputs in ${showdevs} # output devices labelframe $frame.outputs -text [_ "Output Devices"] -padx 5 -pady 5 -borderwidth 1 pack $frame.outputs -side top -fill x -pady 5 ::dialog_midi::fill_frame_devices $frame.outputs out ${showdevs} # If not the "long form" but if "multi" is 2, make a button to # restart with longform set. if {$longform == 0 && $maxdevs > 1} { frame $frame.longbutton pack $frame.longbutton -side right -fill x button $frame.longbutton.b -text [_ "Use Multiple Devices"] \ -command "::dialog_midi::make_frame_iodevices $frame $maxdevs 1" pack $frame.longbutton.b -expand 1 -ipadx 10 -pady 5 } } proc ::dialog_midi::make_frame_ports {frame inportsvar outportsvar} { # ALSA-like MIDI dialog # - just declare how many input/output ports are used # - (no need for a device-list to pick from) if {[winfo exists $frame]} { destroy $frame } frame $frame pack $frame -side top -fill x -anchor n -expand 1 label $frame.l1 -text [_ "In Ports:"] entry $frame.x1 -textvariable $inportsvar -width 4 pack $frame.l1 $frame.x1 -side left label $frame.l2 -text [_ "Out Ports:"] entry $frame.x2 -textvariable $outportsvar -width 4 pack $frame.l2 $frame.x2 -side left } proc ::dialog_midi::fill_frame {frame {include_backends 1}} { set longform 0 init_devicevars if { $include_backends != 0 && [llength $::midi_apilist] > 1} { labelframe $frame.backend -text [_ "MIDI system"] -padx 5 -pady 5 -borderwidth 1 pack $frame.backend -side top -fill x -pady 5 menubutton $frame.backend.api -indicatoron 1 -menu $frame.backend.api.menu \ -relief raised -highlightthickness 1 -anchor c \ -direction flush \ -text [_ "MIDI system" ] menu $frame.backend.api.menu -tearoff 0 foreach api $::midi_apilist { foreach {api_name api_id} $api { $frame.backend.api.menu add radiobutton \ -label "${api_name}" \ -value ${api_id} -variable ::pd_whichmidiapi \ -command {pdsend "pd midi-setapi $::pd_whichmidiapi"} if { ${api_id} == ${::pd_whichmidiapi} } { $frame.backend.api configure -text "${api_name}" } } } pack $frame.backend.api -side left -fill x -expand 1 } if { $::dialog_midi::portsin > 1 || $::dialog_midi::portsout > 1 } { set longform 1 } switch -- $::pd_whichmidiapi { 1 {::dialog_midi::make_frame_ports $frame.contentf ::dialog_midi::portsin ::dialog_midi::portsout } default {::dialog_midi::make_frame_iodevices $frame.contentf $::dialog_midi::max_devices $longform} } } proc ::dialog_midi::init_devicevars {} { # initializes # - the number of ports (::dialog_midi::portsin, ::dialog_midi::portsout) # - the selected device for each port (::dialog_midi::{in,out}dev[0-9]) foreach direction {in out} { # input vars ## list of selected devices for each port in the current direction upvar ::midi_${direction}devices devices # output vars ## the number of used ports upvar ::dialog_midi::ports${direction} ports # 'i' is the 1-based port-index for {set i 1} {$i <= $::dialog_midi::max_devices} {incr i} { # output vars ## the device selected for this port upvar ::dialog_midi::${direction}dev${i} dev set d "" if { [llength $devices] > 0 } { set d [lindex $devices ${i}-1] } if { "${d}" eq "" } { set dev 0 } else { set dev $d } } # how many ports are actually used? # with ALSA, this is the value to be displayed set ports 0 set i 0 foreach x $devices { if { $x > 0 } { set ports [incr i] } } } set ::dialog_midi::referenceconfig [config2string] } proc ::dialog_midi::set_configuration {API inputdevices indevs outputdevices outdevs} { set ::pd_whichmidiapi $API set ::midi_indevlist ${inputdevices} # use integer indices (and filter out invalid(=negative) values) set ::midi_indevices [lmap _ $indevs {set _ [expr int($_) ]; if {$_ < 0} continue; set _}] set ::midi_outdevlist ${outputdevices} set ::midi_outdevices [lmap _ $outdevs {set _ [expr int($_) ]; if {$_ < 0} continue; set _}] } # start a dialog window to select midi devices. "longform" asks us to make # controls for opening several devices; if not, we get an extra button to # turn longform on and restart the dialog. # iodev==0 indicates that it is disabled proc ::dialog_midi::pdtk_midi_dialog {id \ indev1 indev2 indev3 indev4 indev5 indev6 indev7 indev8 indev9 \ outdev1 outdev2 outdev3 outdev4 outdev5 outdev6 outdev7 outdev8 outdev9 \ {longform ignored}} { ::dialog_midi::set_configuration ${::pd_whichmidiapi} \ ${::midi_indevlist} \ [list $indev1 $indev2 $indev3 $indev4 $indev5 $indev6 $indev7 $indev8 $indev9] \ ${::midi_outdevlist} \ [list $outdev1 $outdev2 $outdev3 $outdev4 $outdev5 $outdev6 $outdev7 $outdev8 $outdev9] ::dialog_midi::create $id } proc ::dialog_midi::create {id} { # check if there's already an open gui-preference # where we can splat the new midi preferences into if {[winfo exists ${::dialog_preferences::midi_frame}]} { ::preferencewindow::removechildren ${::dialog_preferences::midi_frame} ::dialog_midi::fill_frame ${::dialog_preferences::midi_frame} return {} } # destroy leftover dialogs destroy $id # create a dialog window toplevel $id -class DialogWindow wm withdraw $id wm title $id [_ "MIDI Settings"] wm group $id . wm resizable $id 0 0 wm transient $id wm minsize $id 240 260 $id configure -menu $::dialog_menubar $id configure -padx 10 -pady 5 ::pd_bindings::dialog_bindings $id "midi" frame $id.midi pack $id.midi -side top -anchor n -fill x -expand 1 ::dialog_midi::fill_frame $id.midi 0 # save all settings button button $id.saveall -text [_ "Save All Settings"] \ -command "::dialog_midi::apply $id 1; pdsend \"pd save-preferences\"" pack $id.saveall -side top -expand 1 -ipadx 10 -pady 5 # buttons frame $id.buttonframe pack $id.buttonframe -side top -after $id.saveall -pady 2m button $id.buttonframe.cancel -text [_ "Cancel"] \ -command "::dialog_midi::cancel $id" pack $id.buttonframe.cancel -side left -expand 1 -fill x -padx 15 -ipadx 10 if {$::windowingsystem ne "aqua"} { button $id.buttonframe.apply -text [_ "Apply"] \ -command "::dialog_midi::apply $id 1" pack $id.buttonframe.apply -side left -expand 1 -fill x -padx 15 -ipadx 10 } button $id.buttonframe.ok -text [_ "OK"] \ -command "::dialog_midi::ok $id" -default active pack $id.buttonframe.ok -side left -expand 1 -fill x -padx 15 -ipadx 10 # set focus focus $id.buttonframe.ok # for focus handling on OSX if {$::windowingsystem eq "aqua"} { # remove cancel button from focus list since it's not activated on Return $id.buttonframe.cancel config -takefocus 0 # show active focus on multiple device button if {[winfo exists $id.longbutton.b]} { bind $id.longbutton.b "$id.longbutton.b invoke" bind $id.longbutton.b "::dialog_midi::unbind_return $id; $id.longbutton.b config -default active" bind $id.longbutton.b "::dialog_midi::rebind_return $id; $id.longbutton.b config -default normal" } # show active focus on save settings button bind $id.saveall "$id.saveall invoke" bind $id.saveall "::dialog_midi::unbind_return $id; $id.saveall config -default active" bind $id.saveall "::dialog_midi::rebind_return $id; $id.saveall config -default normal" # show active focus on the ok button as it *is* activated on Return $id.buttonframe.ok config -default normal bind $id.buttonframe.ok "$id.buttonframe.ok config -default active" bind $id.buttonframe.ok "$id.buttonframe.ok config -default normal" # since we show the active focus, disable the highlight outline if {[winfo exists $id.longbutton.b]} { $id.longbutton.b config -highlightthickness 0 } $id.saveall config -highlightthickness 0 $id.buttonframe.ok config -highlightthickness 0 $id.buttonframe.cancel config -highlightthickness 0 } # set min size based on widget sizing & pos over pdwindow wm minsize $id [winfo reqwidth $id] [winfo reqheight $id] position_over_window $id .pdwindow raise $id } proc ::dialog_midi::pdtk_alsa_midi_dialog {id \ indev1 indev2 indev3 indev4 \ outdev1 outdev2 outdev3 outdev4 \ longform alsa} { if {$alsa} { set ::pd_whichmidiapi 1 } ::dialog_midi::pdtk_midi_dialog $id \ $indev1 $indev2 $indev3 $indev4 0 0 0 0 0 \ $outdev1 $outdev2 $outdev3 $outdev4 0 0 0 0 0 \ $longform if {[winfo exists $id]} { wm title $id [_ "ALSA MIDI Settings"] } } # for focus handling on OSX proc ::dialog_midi::rebind_return {mytoplevel} { bind $mytoplevel "::dialog_midi::ok $mytoplevel" focus $mytoplevel.buttonframe.ok return 0 } # for focus handling on OSX proc ::dialog_midi::unbind_return {mytoplevel} { bind $mytoplevel break return 1 }