Control Patterns with MIDI


#1

I’d like to use my new Korg nanoKONTROL 2 for a SuperCollider project, but couldn’t figure out how to change only one key of my Pdef without setting up a new instance. Do you have any advice?

You can find my code on GitHub: https://github.com/kenokenobingo/studies .


#2

Here’s a simple example of how to get MIDI into event patterns:

(
    {
        // initialize midi:
        MIDIClient.initialized.isNil.or(MIDIClient.initialized.not).if { 
            MIDIClient.init; 
            MIDIIn.connectAll 
        };

        // now we need an environment variable...
        ~foo = 64;
        
        // ...a MIDIFunc that assigns the value of an incoming CC to that variable...
        ~bar = ~bar ?? MIDIFunc.cc({|val| ~foo = val }, 1);

        // ...and a way of reading the current value of that variable from a Pbind...
        ~baz = Pbind(
            \midinote, Pfunc({ ~foo })
        ).asEventStreamPlayer;

        s.bootSync;
        ~baz.play;
    }.fork
)


// this simulates an incoming cc message. the arguments are source, channel, cc#, value
MIDIIn.doControlAction(1, 1, 1, 36.rrand(72))

(
    // cleanup
    ~baz.stop;
    ~bar.free
)

If you read the documentation regarding any part of this example that you don’t understand, it should be enough to get you started.

This question comes up very regularly and at its core it’s about how patterns can access information outside their scope. There are lots of different ways to handle this in practice; using environment variables in Pfunc is probably the simplest, but be aware that this approach may not scale well as your code becomes more complex.


#3

Thanks, @catniptwinz! I’ve alread found this approach in the documentation, but I was hesitating, since global variables seemed to be bad style to me. I’ll try it out! :blush:


#4

There are no real global variables in SuperCollider. These are environmental variables and they are local to the environment, if you don’t define one it’s the currentEnvironment.
If you feel uncomfortable with free floating variables you can act in a certain envir (e.g. with push or use), then things are encapsulated.
Similar to Pfunc + envir vars you can use PLx patterns of miSCellaneous_lib quark.
If you want to exchange certain key streams of a Pbind have a look at Pbindef.


#5

Another option is to use an event to encapsulate the necessary state and compose it into the event stream so it can be accessed with Pkey or Pfunc:

(
    // event containing state
    ~foo = (bar: 64);

    // state is composed into event stream and can be accessed from pbind
    ~baz = (Pbind(\midinote, Pkey(\bar)) <> ~foo).asEventStreamPlayer;
    s.waitForBoot({ ~baz.play })
)

// assigning new values to event keys has the expected effect on pbind
~foo.bar = 36.rrand(72)

// cleanup
~baz.stop

If this still feels too much like leaky scope to you, the next step might be to write a class that encapsulates the event and the pattern.