@@ -3,29 +3,53 @@ use caw_midi::{MidiController01, MidiMessagesT};
33use caw_midi_udp:: { MidiLiveUdp , MidiLiveUdpChannel } ;
44use lazy_static:: lazy_static;
55use std:: {
6+ collections:: HashMap ,
67 net:: SocketAddr ,
78 process:: { Child , Command } ,
9+ sync:: Mutex ,
810} ;
911
1012// For now this library only supports midi channel 0
1113const CHANNEL : u8 = 0 ;
1214
1315lazy_static ! {
16+ // A stream of MIDI events received by the UDP/IP server. It owns the UDP/IP server.
1417 static ref SIG : Sig <SigShared <MidiLiveUdpChannel >> =
1518 sig_shared( MidiLiveUdp :: new_unspecified( ) . unwrap( ) . channel( CHANNEL ) . 0 ) ;
19+
20+ // Used to prevent multiple windows from opening at the same time with the same name. Note that
21+ // when using with evcxr this seems to get cleared when a cell is recomputed, but this is still
22+ // valuable in the context of stereo signals, where a function is evaluated once for the left
23+ // channel and once for the right channel. In such a case, this prevents each knob openned by
24+ // that function from being openned twice.
25+ static ref KNOBS_BY_TITLE : Mutex <HashMap <String , Sig <SigShared <Knob >>>> =
26+ Mutex :: new( HashMap :: new( ) ) ;
27+
28+ /// MIDI controller indices are allocated dynamically and this global tracks the value of the
29+ /// next controller index to allocate.
30+ static ref NEXT_CONTROLLER_INDEX : Mutex <u8 > = Mutex :: new( 0 ) ;
1631}
1732
33+ /// Returns the IP address of the server that will receive MIDI events.
1834fn sig_server_local_socket_address ( ) -> SocketAddr {
1935 SIG . 0 . with_inner ( |midi_live_udp_channel| {
2036 midi_live_udp_channel. server . local_socket_address ( ) . unwrap ( )
2137 } )
2238}
2339
40+ /// Allocates and returns unique MIDI controller value.
41+ fn alloc_controller ( ) -> u8 {
42+ let mut next_controller_index = NEXT_CONTROLLER_INDEX . lock ( ) . unwrap ( ) ;
43+ let controller_index = * next_controller_index;
44+ * next_controller_index += 1 ;
45+ controller_index
46+ }
47+
2448const PROGRAM_NAME : & str = "caw_midi_udp_widgets_app" ;
2549
2650pub struct Knob {
2751 controller : u8 ,
28- title : Option < String > ,
52+ title : String ,
2953 initial_value_01 : f32 ,
3054 sensitivity : f32 ,
3155 process : Option < Child > ,
@@ -34,35 +58,44 @@ pub struct Knob {
3458
3559impl Knob {
3660 pub fn new (
37- controller : u8 ,
38- title : Option < String > ,
61+ title : String ,
3962 initial_value_01 : f32 ,
4063 sensitivity : f32 ,
41- ) -> Sig < Self > {
42- let sig = SIG
43- . clone ( )
44- . controllers ( )
45- . get_with_initial_value_01 ( controller, initial_value_01) ;
46- let mut s = Self {
47- controller,
48- title,
49- initial_value_01,
50- sensitivity,
51- process : None ,
52- sig,
53- } ;
54- let child = s
55- . command ( )
56- . unwrap ( )
57- . spawn ( )
58- . expect ( "Failed to launch process" ) ;
59- s. process = Some ( child) ;
60- Sig ( s)
64+ ) -> Sig < SigShared < Self > > {
65+ let mut knobs_by_title = KNOBS_BY_TITLE . lock ( ) . unwrap ( ) ;
66+ if let Some ( knob) = knobs_by_title. get ( & title) {
67+ knob. clone ( )
68+ } else {
69+ let controller = alloc_controller ( ) ;
70+ let sig = SIG
71+ . clone ( )
72+ . controllers ( )
73+ . get_with_initial_value_01 ( controller, initial_value_01) ;
74+ let mut s = Self {
75+ controller,
76+ title : title. clone ( ) ,
77+ initial_value_01,
78+ sensitivity,
79+ process : None ,
80+ sig,
81+ } ;
82+ let child = match s. command ( ) . spawn ( ) {
83+ Ok ( child) => child,
84+ Err ( e) => panic ! (
85+ "{} (make sure `{}` is in your PATH" ,
86+ e, PROGRAM_NAME
87+ ) ,
88+ } ;
89+ s. process = Some ( child) ;
90+ let s = sig_shared ( s) ;
91+ knobs_by_title. insert ( title, s. clone ( ) ) ;
92+ s
93+ }
6194 }
6295
63- fn command ( & self ) -> anyhow :: Result < Command > {
96+ fn command ( & self ) -> Command {
6497 let mut command = Command :: new ( PROGRAM_NAME ) ;
65- let mut args = vec ! [
98+ let args = vec ! [
6699 "knob" . to_string( ) ,
67100 "--server" . to_string( ) ,
68101 sig_server_local_socket_address( ) . to_string( ) ,
@@ -74,12 +107,11 @@ impl Knob {
74107 format!( "{}" , self . initial_value_01) ,
75108 "--sensitivity" . to_string( ) ,
76109 format!( "{}" , self . sensitivity) ,
110+ "--title" . to_string( ) ,
111+ self . title. clone( ) ,
77112 ] ;
78- if let Some ( title) = self . title . as_ref ( ) {
79- args. extend ( [ "--title" . to_string ( ) , title. clone ( ) ] ) ;
80- }
81113 command. args ( args) ;
82- Ok ( command)
114+ command
83115 }
84116}
85117
@@ -94,30 +126,25 @@ impl SigT for Knob {
94126mod builder {
95127 use super :: Knob ;
96128 use caw_builder_proc_macros:: builder;
97- use caw_core:: Sig ;
129+ use caw_core:: { Sig , SigShared } ;
98130
99131 builder ! {
100- #[ constructor = "knob " ]
132+ #[ constructor = "knob_ " ]
101133 #[ constructor_doc = "A visual knob in a new window" ]
102134 #[ generic_setter_type_name = "X" ]
103135 #[ build_fn = "Knob::new" ]
104- #[ build_ty = "Sig<Knob>" ]
136+ #[ build_ty = "Sig<SigShared< Knob> >" ]
105137 pub struct Props {
106- #[ default = 0 ]
107- controller: u8 ,
108- #[ default = None ]
109- title_opt: Option <String >,
138+ title: String ,
110139 #[ default = 0.5 ]
111140 initial_value_01: f32 ,
112141 #[ default = 0.2 ]
113142 sensitivity: f32 ,
114143 }
115144 }
116145
117- impl Props {
118- pub fn title ( self , title : impl Into < String > ) -> Self {
119- self . title_opt ( Some ( title. into ( ) ) )
120- }
146+ pub fn knob ( title : impl Into < String > ) -> Props {
147+ knob_ ( title. into ( ) )
121148 }
122149}
123150
0 commit comments