Musical Pattern Generation with the McLaren Synth Kit

  • by

The McLaren Synth Kit (at https://github.com/mclarenlabs/libs-mclaren-alpha) is an Objective-C toolkit for building projects that simplify working with MIDI and sound synthesis on Linux. We open-sourced it last year and have been refining it with new features and capabilities.

Our latest features are the Metronome and a Pattern generation little-language. The Metronome hooks into to the ALSA MIDI system to provide a MIDI-clock timebase. It counts out beats and measures. The Pattern facility further abstracts the Metronome to longer sequences of events that repeat and can nest.

What is a Pattern?

A Pattern is a repeating sequence of instructions, some of which specify the passage of time, others execute code. Patterns are built up by calling methods on a Pattern object.

In the snippet below, assume that there is Synthesizer object named synth that has a method playNote:. The following defines a pattern that executes four times and then exits.

Pattern *pat = [[Pattern alloc] initWithName:'pat1'];

[pat sync:@"beat"];
[pat thunk:^{
  [synth playNote:64];
  }];

[pat sync:@"beat"];
[pat thunk:^{
  [synth playNote:60];
  }];

[pat sync:@"beat"];
[pat thunk:^{
  [synth playNote:60];
  }];

[pat sync:@"beat"];
[pat thunk:^{
  [synth playNote:60];
  }];

[pat repeat:4];

The Pattern methods are a little-language for specifying how to synchronize with the Metronome. This pattern says synchronize with a beat (beat 0) and then play note 64. Then synchronize with the next beat (beat 1) and play note 60. Because there are fourr sync: calls, the pattern synchronizes with each beat in a 4/4 measure. The repeat: call is an annotation on the pattern that specifies its repeat factor.

A Pattern can also specify the passage of time in ways other than synchronizing with events. A Pattern can specify the passage of time in “ticks” or “realtime”.

Pattern *pat2 = [[Pattern alloc] initWithName:'pat2'];

[pat2 sync:@"beat"];
[pat2 thunk:^{ doSomething(); }];

[pat2 ticks:31];
[pat2 thunk:^{ doSomething(); }];

[pat2 seconds:1.3];
[pat2 thunk:^{ doSomethingElse(); }];

When run, the Pattern above synchronizes with the next Metronome beat and performs doSomething(). It then sleeps for 31 ticks (there are 120 ticks per quarter note) and performs doSomething() again. It then sleeps for 1.3 seconds and then performs doSomethingElse().

This pattern is not annotated with a repeat: repetition factor, so it executes once and exits.

Synchronization Events

At the present time there are only a small number of named synchronization events.

  • beat – the Metronome beat callback
  • downbeat – Metronome beat callback with beat==0
  • clock – the MIDI beat clock, 24 clocks per quarter

Sub-patterns

Patterns can call other patterns, which execute as subroutines. The Pattern pat: method specifies a sub-pattern. A sub-pattern blocks its parent until it is done.

Consider the following pattern. It plays four sixteenth notes, synchronized with the next beat.

Pattern *pat3 = [[Pattern alloc] initWithName:@"pat3"];
[pat3 sync:@"beat"];
[pat3 thunk:^{ [synth playNote:70; ]}];
[pat ticks:30];
[pat3 thunk:^{ [synth playNote:70; ]}];
[pat ticks:30];
[pat3 thunk:^{ [synth playNote:70; ]}];
[pat ticks:30];
[pat3 thunk:^{ [synth playNote:70; ]}];

This pattern could be incorporated into a parent pattern. The pattern below plays a note on beat 0 and beat 1, on beat 2 it plays four sixteenths, and then on beat 3 it plays a note. The pattern then repeats 4 times.

Pattern *pat4 = [[Pattern alloc] initWithName:@"pat4"];

[pat4 sync:@"beat"];
[pat4 thunk:^{ [synth playNote:64; ]}];

[pat4 sync:@"beat"];
[pat4 thunk:^{ [synth playNote:60; ]}];

[pat4 pat:pat3]; // play the sixteenth notes

[pat4 sync:@"beat"];
[pat4 thunk:^{ [synth playNote:60; ]}];

[pat4 repeat:4];

Patterns in StepTalk

StepTalk is a Smalltalk-dialect interpreter that can call Objective-C classes and methods. Using Smalltalk syntax with Patterns is slightly prettier because of the “cascade” operator (operator semicolon “;” in Smalltalk).

With StepTalk, the first example of this chapter looks like the following.

pat := Pattern alloc initWithName:'pat1'.

pat
  sync: #beat;
  block: [ synth playNote:64. ];

  sync: #beat;
  block: [ synth playNote:60. ];

  sync: #beat;
  block: [ synth playNote:60. ];

  sync: #beat;
  block: [ synth playNote:60. ];

  repeat: 4.

Note: we haven’t quite finished producing a working demo with the McLaren Synth Kit integrated with StepTalk, but when we do, this is what patterns will look like.

Running Patterns

So far, we have shown how to create patterns, but we haven’t shown how to start them or stop them. For that we need to create a Scheduler and attach it to a Metronome. Once attached, the Scheduler TAKES OVER all of the callbacks of the Metronome and is controlled by the start/stop/kontinue methods of the Metronome.

Assume that we have previously created a Metronome named _metro. Then the following creates a Scheduler.

@property Scheduler *sched;

- (void) makeScheduler {

  _sched = [[Scheduler alloc] init];
  [_sched registerMetronome:_metro];
}

Patterns can be added to the Scheduler to be launched when the Scheduler starts. The following shows how two patterns are added to a Scheduler to start when the Metronome starts.

[_sched addLaunch:pat];
[_sched addLaunch:pat4];

The tempo of the playing and the starting and stopping are controlled through the underlying Metronome.

[_metro setTempo:90 error:&err];
[_metro start];

What can execute in blocks?

As with the Metronome callbacks, the Scheduler “thunk” invocations are also performed on the MIDI dispatch queue by default. As a programmer, you should take care to not perform operations in the thunk that could block execution or interfere with other threads.

Calls to NSLog are generally alright during development.

Conclusion

The Pattern mechanism abstracts the calllbacks of the Metronome and provides a little-language for specifying rhythmic phrases. The pattern mechanism only specifies the passage of time in varying ways and leaves the execution of what to do at an instant up to an open-ended block invocation. This leaves open the option to launch context sounds, control model parameters, or do other things.

Because Patterns are tied to the MIDI clock of the ALSA Seq, Patterns provide tick-accurate timing in a user-friendly way.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.