Alternative to Pmono?

I use Pmono a lot, but I find that it has a number of shortcomings. There’s a simple API problem, where it expects the instrument name as the first argument - this makes it not match Pbind, and additionally makes it VERY hard to use Pmono with any event type where the instrument is determined programmatically (for example, I’ve got lots of buffer-playback SynthDefs that have different versions for different channel counts - I’ll choose these based on an event parameter, but this doesn’t work easily with Pmono).

I also find that Pmono does some very weird and disruptive things from time to time - when using it with chained Pdefs, it can sometimes just kill and restart your monophonic synths because upstream Pdefs have changed. And in general, it’s hard to figure out these problems because the internals of Pmono are a bit arcane and over-complicated.

To work around a few of these problems the other day, I tried to re-implement it from scratch, as a simple Event stream transformation that:

  1. Sends the first event without an explicit end event or gate,
  2. Transforms every event after that into a \set event.

It’s simple, works well with Pbind, and doesn’t event require a lot of code or infrastructure. It could easily be adapted to conditionally provide PmonoArtic behavior as well. I thought I’d post it, in case anyone else wants to test or needs to workaround Pmono problems.

There may be holes or bugs in this approach, but so far it’s solved three or four of the things that were troubling me on this project with no downsides. The code is dirt-simple (and could be simplified more).

Usage:

Pbind.mono(
    \instrument, \default,
    \dur, 1/12,
    \scale, Scale.aeolian,
    \degree, Pwhite(0, 12) + [0, 2],
).play


(Pmonophonic() <> Pbind(
    \instrument, \default,
    \dur, 1/12,
    \scale, Scale.aeolian,
    \degree, Pwhite(0, 12) + [0, 2],
)).play
6 Likes

Agreed. Your solution looks better.

Would you mind to parameterize the event types that are used? I struggled with Pmono for Syn/Plug because the logic is coupled to specific event types. If I could override \off and \set then Pmonophonic would work neatly with Syn/Plug (I think). Perhaps a monoEventTypes key in the event, e.g. Pbind.mono(\monoEventTypes, (off: \synOff, set: \synSet), ...).

hjh

When I can spend a little more time on it, I might reformulate it in a more general way. IMO monophonic event streams are, at their core, something like:

  1. Pass the initial event along normally, (prevent the corresponding “end” / note off event).
  2. Change all future events to be a “set” version of the incoming event, targeting the resulting node from the first event.

At the lowest level, I think there’s more a general component required: a pattern that passes events through, and provides a context to modify the current event based on the played result of the previous event. So, something like Pfunc except it works like Pfunc({ |event, previouslyPlayedEvent| }). This may or may not overlap with a “context injector” Pattern, where a single fixed context object is passed in to each event passing through, which allow sequential events in a stream to pass data / communicate with each other.

Once we have one or both of these components, then monophonic playback can actually be implemented by ANY event type pretty trivially, so long as it has representative “set” and “stop” behaviors. It might be best to actually implement the “set” and “stop” outside of the event type system, e.g. as a separate key for your event type.

Supposing a really naive version: a Pattern that injects the last played event into subsequent events in the stream, some key like context:

Plast(\context) <> Pbind(,..)

Then you have code in your play function that looks like:

if (~context.isNil) {
   ~subtype = \start;
} {
  ~subType = \set;
};

if (~restart) { // restart mono synths min-stream? A more general version of monoartic
  // run this with ~subType = \stop
  // then run this with ~subType = \start
} {
  switch (~subType)
    { \start } {
        // start a new synth
    }
    { \set } {
       ~id = ~context[\playingSynth];
       // set an existing synth
    }
}

~context = ( playingSynth: ~id );

This way, the monophonic-ness is entirely the responsibility of your event type, and the pattern system itself is only just providing a generic way to pass information between sequential events.

In your case, you might be best off then just modifying your \syn play functions - but ideally, this would be pretty easy. In general, I think there’s a lot of benefit to making the event type / play funcs much more composable. For example, I could see you implementing the above a something like:

Event.addEventType(\syn, {
   |server|
   // do initial setup stuff
   ~subType = ~findPlayFunc.(~subType ? \start);
   ~subType.(server);
   // do cleanup stuff
})

(Assuming ~findPlayFunc is something like { |type| ~parentTypes[type] })

Then you can have one top-level event type, and a family of related event types that you can functionally compose together.

Incidentally, if event types were more composable (imagine e.g. you can provide a function in addition to just a key), then the Plast functionality would be SO simple - something similar to:

Prout({
  |inval|
  var contextKey = \context; // probably an argument to the pattern...
  var contextObj = nil;
  var incomingType;

  while {inval.notNil} {
    incomingType = inval[\type];
    inval[\type] = {
        currentEnvironment[contextKey] = contextObj;
        ~playEventType.(incomingType);
        contextObj = currentEnvironment[contextKey];
    };
    inval = inval.yield;
  }
})

There are various ways of doing this depending on the desired behavior, but there are some cool nesting things you can do - this, for example, has the potential to work correctly even if you’re layering up contexts, even if their keys are overlapping?