aboutsummaryrefslogtreecommitdiffstats
path: root/classes/Bowelyzer.sc
blob: e1fba76d8e8e749735e780b9ad3f0abf6bcbecb7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
Bowelyzer{

  var <gui, <server, <analyzer, <config, <hub;

  *new{
    arg configFile;
    ^super.new.init(configFile);
  }

  init{
    //initialize with configuration, if available (else use default)
    arg configFile;
    config = BowelyzerConfig.new(configFile);
    server = Server.new(
      \bowelyzer,
      BowelyzerOSCHub.getNetAddr(
        config.config.at(\synthServerAddress),
        config.config.at(\synthServerPort)
      )
    );
    Server.default = server;
    server.waitForBoot({
      hub = BowelyzerOSCHub.new(config.config);
      analyzer = BowelyzerAnalyzer.new(config.config);
      gui = BowelyzerGUI.new(config);
      this.addGUIListeners;
      this.addServerListeners;
   },
   5,
   {"scsynth failed to start!".postln});
  }

  // add OSCdefs listening for GUI events
  addGUIListeners{
    // listen for control changes
    OSCdef.newMatching(
      key: \controls,
      func: {|msg, time, addr, recvPort|
        postln("Received: "++msg);
        // if the control exists, change it
        if(msg[1].notNil && msg[2].notNil && msg[3].notNil,{
          if(config.config.at(\controls).includesKey(msg[1]),{
            if(config.config.at(\controls).at(msg[1]).includesKey(msg[2]),{
              config.config.at(\controls).at(msg[1]).put(msg[2], config.getControlValue(msg[2], msg[3]));
            });
          });
        });
      },
      path: "/controls",
      srcID: NetAddr.new("127.0.0.1", NetAddr.langPort)
    );

    // listen for input changes (rename and input channel)
    OSCdef.newMatching(
      key: \inputs,
      func: {|msg, time, addr, recvPort|
        var name = msg[1],
        type = msg[2],
        update = msg[3];
        postln("Received: "++msg);
        if(name.notNil && type.notNil && update.notNil,{
          if(config.config.at(\inputs).includesKey(name) && config.config.at(\controls).includesKey(name),{
            switch(type,
              \name,{
                // if the name is not empty and not used, change to it
                if((update != "") && config.config.at(\inputs).includesKey(update).not, {
                  //move the controls
                  config.config.at(\controls).put(update.asSymbol, config.config.at(\controls).at(name));
                  config.config.at(\controls).removeAt(name.asSymbol);
                  // rename the input
                  config.config.at(\inputs).put(update.asSymbol, config.config.at(\inputs).at(name));
                  config.config.at(\inputs).removeAt(name);
                  // rename the channelView
                  gui.setChannelName(name, update);
                  // free the old OSC indicator fadeout Task
                  gui.freeOSCIndicatorFadeOutTask(name);
                  // add a new OSC indicator fadeout Task using the new name
                  gui.addOSCIndicatorFadeOutTask(update);
                  // stop the listener for LevelIndicator by name and start a new one based upon the updated name
                  OSCdef(("levels_"++name).asSymbol).free;
                  this.setupLevelListener(update, hub.synthServer);
                  //TODO: refactor into function
                  // free the synth on the server and start a new one according to the config
                  analyzer.freeAnalysisSynth(name.asSymbol);
                  analyzer.synths.removeAt(name);
                  //TODO: refactor into function
                  analyzer.addSynthWithName(update);
                  // start the Synth (possibly paused)
                  Routine{
                    1.wait;
                    analyzer.startSynthWithNameAndControls(update.asSymbol, config.config.at(\controls).at(update), config.config.at(\inputs).at(update));
                  }.play;
                  //TODO: refactor into function
                  // rename the hub listener for the Synth
                  hub.freeSynthListener(name);
                  hub.addSynthListener(update);
                  hub.startSynthListener(update);
                },{
                  ("New name not valid: "++update).error;
                  gui.setChannelText(name, name);
                });
              },
              \input,{
                // change the input in configuration
                config.config.at(\inputs).put(name, update.asInteger);
                // change the input in the Synth
                analyzer.setSynthControl(name.asSymbol, \inputChannel, update.asInteger);
              }
            );
          });
        });
      },
      path: "/inputs",
      srcID: NetAddr.new("127.0.0.1", NetAddr.langPort)
    );

    // listen for toggling messages to "mute" channel
    OSCdef.newMatching(
      key: \toggle,
      func: {|msg, time, addr, recvPort|
        var name = msg[1],
        toggle = msg[2];
        postln("Received: "++msg);
        if(name.notNil && toggle.notNil && toggle.isInteger,{
          if(config.config.at(\inputs).includesKey(name),{
            switch(toggle,
              0,{
                config.config.at(\controls).at(name).put(\active, true);
                analyzer.startAnalysisSynth(name);
              },
              1,{
                config.config.at(\controls).at(name).put(\active, false);
                analyzer.stopAnalysisSynth(name);
              }
            );
          });
        });
      },
      path: "/toggle",
      srcID: NetAddr.new("127.0.0.1", NetAddr.langPort)
    );

    // listen for address messages to change OSC addresses
    OSCdef.newMatching(
      key: \address,
      func: {|msg, time, addr, recvPort|
        var type = msg[1],
        address = msg[2];
        postln("Received: "++msg);
        if(type.notNil && config.config.includesKey(type),{
          config.config.put(type.asSymbol, address.asString);
          hub.setupNetAddressesFromConfig(config.config);
        });
      },
      path: "/address",
      srcID: hub.local
    );

    // listen for port messages to change OSC ports
    OSCdef.newMatching(
      key: \port,
      func: {|msg, time, addr, recvPort|
        var type = msg[1],
        port = msg[2];
        postln("Received: "++msg);
        if(type.notNil && config.config.includesKey(type),{
          config.config.put(type.asSymbol, port.asInteger);
          hub.setupNetAddressesFromConfig(config.config);
        });
      },
      path: "/port",
      srcID: hub.local
    );

    // listen for message to save configuration to current file
    OSCdef.newMatching(
      key: \save,
      func: {|msg, time, addr, recvPort|
        var path = msg[1];
        postln("Received: "++msg);
        if(config.configFilePath.isNil || config.configFilePath == "",{
          gui.saveAsAction(nil);
        },{
          config.writeConfigurationFile;
        });
      },
      path: "/save",
      srcID: hub.local
    );

    // listen for message to save configuration to current file
    OSCdef.newMatching(
      key: \saveas,
      func: {|msg, time, addr, recvPort|
        var name = msg[0],
        fileName = msg[1];
        postln("Received: "++msg);
        config.writeConfigurationFile(fileName);
      },
      path: "/saveas",
      srcID: hub.local
    );

    // listen for messages to load configuration from file
    OSCdef.newMatching(
      key: \load,
      func: {|msg, time, addr, recvPort|
        var name = msg[0],
        fileName = msg[1];
        postln("Received: "++msg);
        // free all channels
        config.config.at(\inputs).order.do({ |name|
          this.freeChannel(name.asSymbol);
        });
        // read the configuration file
        if(config.readConfigurationFile(fileName),{
          hub.setupNetAddressesFromConfig(config.config);
          hub.setupSynthListenersFromConfig(config.config);
          analyzer.setupSynthsFromConfig(config.config);
          config.config.at(\inputs).keysDo({ |name|
            gui.setupChannelView(name, config.config);
            // setup a LevelListener for the new Synth
            this.setupLevelListener(name, hub.synthServer);
            // setup a OSC listener for the Synth by name
            hub.addSynthListener(name);
          });
          // reload the addresses and ports Views
          gui.removeAddressesAndPorts;
          gui.setupAddressesAndPorts(config.config);
        },{
          ("Configuration could not be read: "++fileName).error;
        });
      },
      path: "/load",
      srcID: hub.local
    );

    // listen for messages to free (close) channels
    OSCdef.newMatching(
      key: \free,
      func: {|msg, time, addr, recvPort|
        var type = msg[0],
        name = msg[1];
        postln("Received: "++msg);
        this.freeChannel(name);
      },
      path: "/free",
      srcID: NetAddr.new("127.0.0.1", NetAddr.langPort)
    );

    // listen for messages to add channels
    OSCdef.newMatching(
      key: \add,
      func: {|msg, time, addr, recvPort|
        var type = msg[0],
        name;
        postln("Received: "++msg);
        this.addChannel;
      },
      path: "/add",
      srcID: NetAddr.new("127.0.0.1", NetAddr.langPort)
    );
  }

  // add OSCdefs listening for messages coming from scsynth, to update the GUI
  addServerListeners{
    this.setupIndicatorListener;
    // add and start a listener for each channel's LevelIndicator
    config.config.at(\inputs).pairsDo({|key,value|
      this.setupLevelListener(key, hub.synthServer);
    });
  }

  // setup a listener for a level indicator by name and source (of synth server)
  setupIndicatorListener{
    // listen for indicator messages
    OSCdef.newMatching(
      key: \indicate,
      func: {|msg, time, addr, recvPort|
        var name = msg[1];
        if(config.config.at(\inputs).includesKey(name),{
          gui.pingOSCIndicator(name);
        });
      },
      path: "/indicate",
      srcID: NetAddr.new("127.0.0.1", NetAddr.langPort)
    );
  }

  // setup a listener for a level indicator by name and source (of synth server)
  setupLevelListener{
    arg name, server;
    postln("Setting up LevelListener for: "++name);
    OSCdef.newMatching(
      key: ("levels_"++name).asSymbol,
      func: {|msg, time, addr, recvPort|
        var name = msg[0].asString.replace("/levels/", "").asSymbol,
        value = msg[3].ampdb.linlin(-40, 0, 0, 1),
        peak = msg[4].ampdb.linlin(-40, 0, 0, 1);
        gui.setLevel(name, value, peak);
      },
      path: "/levels/"++name,
      srcID: server
    );
  }

  // add a new channel by name
  addChannel{
    arg extName;
    // add a new input to the config
    var name;
    if(extName.isNil,{
      name = config.addInput;
    },{
      name = config.addInput(extName);
    });
    // initialize a new channelView with the name and controls
    gui.setupChannelView(name, config.config);
    // add and start a new Synth by the given name
    Routine{
      analyzer.addSynthWithName(name);
      1.wait;
      analyzer.startSynthWithNameAndControls(name, config.config.at(\controls).at(name), config.config.at(\inputs).at(name));
    }.play;
    // setup a LevelListener for the new Synth
    this.setupLevelListener(name, hub.synthServer);
    // setup a OSC listener for the Synth by name
    hub.addSynthListener(name);
  }

  // free (remove) a channel
  freeChannel{
    arg name;
    ("Removing channel: "++name).postln;
    // free Synth
    analyzer.freeAnalysisSynth(name);
    // remove OSCdefs for the channel
    OSCdef.all.pairsDo({|key, value|
      if(key == name.asSymbol,{
        ("Freeing OSCdef: "++name).postln;
        value.free;
      });
      if(key == ("levels_"++name).asSymbol,{
        ("Freeing OSCdef: levels_"++name).postln;
        value.free;
      });
    });
    // remove configurations for the channel
    config.config.at(\inputs).removeAt(name);
    config.config.at(\controls).removeAt(name);
    // remove fadeout task for OSC indicator
    gui.freeOSCIndicatorFadeOutTask(name);
    // remove GUI elements
    gui.removeChannelView(name, config.config.at(\inputs).size);
  }

}