sc_learning_diary / phenosynth / controllers.sc

PSController {
  /*pass all server instructions through this guy to allow the instructions
  to be delivered in the right order and the boring bus/server allocation
  details to be abstracted away, and to track resources needing freeing.*/
  
  //Instance vars are all public to aid debugging, but, honestly, don't
  //touch them. Why would you touch them?
  var <outBus;
  var <numChannels;
  var <q; //i.e. a Queue.
  var <server;
  var <all;
  var <playGroup;
  *new {|server, bus, numChannels=2, q|
    ^super.newCopyArgs(bus, numChannels, q).init(server);
  }
  init {|serverOrGroup|
    all = IdentityDictionary.new;
    serverOrGroup.isKindOf(Group).if(
      {
        server = serverOrGroup.server;
        q ?? {q = PSServerQueue.new(server);};
        playGroup = serverOrGroup;
      }, {
        server = serverOrGroup;
        q ?? {q = PSServerQueue.new(server);};
        q.push({playGroup = Group.head(server);});
      }
    );
    outBus ?? {q.push({outBus = Bus.audio(server, numChannels)});};
  }
  playIndividual {|phenome|
    //this doesn't actually play - it sets up a callback to play
    q.push({
      var indDict;
      indDict = this.getIndividualDict(phenome);
      this.loadIndividualDict(
        indDict
      );
      this.actuallyPlay(indDict);
    });
  }
  loadIndividualDict{|indDict|
    all.put(indDict.phenome.identityHash, indDict);
  }
  getIndividualDict {|phenome|
    //this doesn't need to be called in the server queue;
    //but in general, one could so need.
    ^(\phenome: phenome,
      \playBus: outBus
    );
  }
  actuallyPlay {|indDict|
    q.push({
      indDict.phenome.play(out:indDict.playBus, group:playGroup);
    });
  }
  freeIndividual {|phenome|
    var freed = all.at(phenome.identityHash);
    all.removeAt(phenome.identityHash);
    ^freed;
  }
}

PSListenSynthController : PSController {
  /* Handle a number of simultaneous synths being digitally listened to
  */
  var <fitnessPollInterval;
  var <listenGroup;
  var <worker;
  classvar <listenSynth = \ps_listen_eight_hundred;
  *new {|server, bus, numChannels=2, q, fitnessPollInterval=1|
    ^super.newCopyArgs(bus, numChannels, q).init(
      server, fitnessPollInterval);
  }
  init {|serverOrGroup, thisFitnessPollInterval|
    var clock;
    super.init(serverOrGroup);
    fitnessPollInterval = thisFitnessPollInterval;
    q.push({listenGroup = Group.after(playGroup);});
    clock = TempoClock.new(fitnessPollInterval.reciprocal, 1);
    worker = Routine.new({loop {this.updateFitnesses; 1.wait;}}).play(clock);
  }
  getIndividualDict {|phenome|
    ^(\phenome: phenome,
      \playBus: Bus.audio(server, numChannels),
      \listenBus: Bus.control(server, 1)
    )
  }
  actuallyPlay {|indDict|
    q.push({
      //play the synth to which we wish to listen
      indDict.phenome.play(out:indDict.playBus, group:playGroup);
      //analyse its output by listening to its bus
      Synth(this.class.listenSynth,
        this.getListenSynthArgs(indDict),
        listenGroup);
      //re-route some output to the master input
      Synth(\jack, [\in, indDict.playBus, \out, outBus], listenGroup);
    });
  }
  getListenSynthArgs{|indDict|
    ^[\in, indDict.playBus, \active, 1];
  }
  freeIndividual {|phenome|
    var freed = super.freeIndividual(phenome);
    ^freed;
  }
  updateFitnesses {
    all.keysValuesDo({|key, indDict|
      ['tick', indDict, key].postln;
      //server cmd, but doesn't need to be queued coz it's read-only.
      indDict.listenBus.get({|val|
        ["got val", val, "for phenome id", key].postln;
        indDict.phenome.fitness = val;
      });
    });
  }
}

PSServerQueue {
  /*a queue to service instructions, waiting on sync from a particular server
  
  I know this looks overblown, but it sure does stop the wacky, unpredictable
  explosions I was having before.
  */
  var <server;
  var <fifo;
  var <worker;
  var doneFlag;//internal condition for list servicing
//  var <emptyFlag;//external signal that the list is empty - not implemented
  *new {|server|
    ^super.newCopyArgs(server ? Server.default).init;
  }
  init {
    fifo = LinkedList.new;
    doneFlag = Condition.new(false);
    worker = Routine({
      var job, result;
      loop {
        job = fifo.pop;
        job.isNil.if({
          doneFlag.hang;
        }, {
          result = job.value;
          server.sync;
        })
      }
    }).play;
  }
  push {|job|
    fifo.addFirst(job);
    doneFlag.unhang;
  }
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.