In this example we will show how to group together a number of notes into a pattern.
The first few lines are the same as the scheduling notes example: create an LV2 sampler, set its volume, and connect its stereo outputs to the system outputs.
local sampler = Lv2.Plugin("http://www.openavproductions.com/fabla", "fabla808") sampler.control("volume", 0.6) sampler => Audio.StereoOutput("main", true)
Also as seen in the scheduling notes example we will create our notes:
local kickNote = Midi.Note(36, 127, 0.25) local snareNote = Midi.Note(40, 127, 0.25)
Now instead of scheduling the notes directly on the sampler, we will create a pattern object and add the notes to the pattern.
First create a new, empty, MIDI pattern to hold a drum beat:
local simpleBeat = Midi.Pattern()
Now add the kick + snare to the pattern to define a simple beat with the snare on the 2nd and 4th beat and the kick on the 1st beat and the 2nd + 3rd offbeat:
simpleBeat.add(kickNote, 1.0) simpleBeat.add(snareNote, 1.25) simpleBeat.add(kickNote, 1 + 3/8.0) // 4th eight note position = 2nd beat offbeat simpleBeat.add(kickNote, 1 + 5/8.0) // 6th eight note position = 3rd beat offbeat simpleBeat.add(snareNote, 1.75)
We can access the note events in a pattern by using array-like indexing:
simpleBeat[0] // the first event in the pattern
We can also iterate over all the events using the foreach loop:
foreach(event in simpleBeat) { println(event.note.pitch + " @ " + event.measure) }
Now we want to create another version of this same pattern that has additional elements. We do this by copying the existing pattern with the clone keyword:
local fullBeat = clone simpleBeat
Now at this point the new pattern is a direct copy of the pattern from which it was created, we can go ahead and add addional notes to the new pattern.
Adding each note line by line sometimes makes sense but things can easily become cluttered, one way to save space is to add our notes within a for loop, for example adding a note on every eighth note:
for(local i = 0; i < 8; i++) { fullBeat.add(Midi.Note(47, 127, 0.125), 1 + i/8.0) }
Notice we have also saved a line of code by creating the note in place rather than predefining a variable as we did with "kickNote" and "snareNote", but it makes it less clear what the note represents (in this case, maracas).
So using loops and creating note inline can save us lines of code but it still doesn't make for very readable scripts.
One thing we can do to fix that is to use a shorthand notation for entering the notes. For example, one classic shorthand musical notation is ABC notation, ABC support means we can write music in ABC and then use a Midi.ABCReader to convert it into a Midi.Pattern.
Another shorthand notation for representing notes is drum tablature. The Midi.DrumTabReader converts a string containing drum tab into a MIDI pattern that we can use as any other pattern.
The first two characters of each line are a symbol representing the drum to be played or alternatively the MIDI pitch value for this line.
Following that are the measures which are divided by the number of characters in each, each character represents a duration of that division. A dash ("-") character represents a rest and other characters represent notes of that duration.
Here is a drum fill notated in drum tab and then converted into a MIDI pattern, notice the use of the @" notation to create a multi-line string:
local fillPattern = Midi.DrumTabReader().read(@" 40|o-o-o-o-oooooooo| 36|o-------o---o---| ")
In this tab we have one measure subdivided into sixteenth notes. The pattern will play 12 sixteenth notes of MIDI note 40 according to the pattern on the top line and 3 sixteenth notes of MIDI note 36 according to the bottom line.
Now we have made three patterns: a simple beat, a copy of the simple beat with added eighth notes, and a drum fill. But the patterns are just containers for the notes, we still have not scheduled anything to play, so the final step is to schedule the patterns to play on the sampler plugin.
We'll create a simple arrangement that starts with the simple beat and progresses to the full beat after 8 bars, with the fill every 8 bars, using the sampler's schedule method to schedule the patterns:
for(local measure = 1; measure <= 16; measure++) { if(measure % 8 == 0) { sampler.schedule(fillPattern, measure) } else if(measure <= 8) { sampler.schedule(simpleBeat, measure) } else { sampler.schedule(fullBeat, measure) } } Time.def.start()
We can schedule a pattern to play at any point by including a position and division within the measure, in the same way as we schedule MIDI notes. However these parameters are optional and if we are scheduling the pattern at the beginning of the measure then we can omit them as we have done above.
local sampler = Lv2.Plugin("http://www.openavproductions.com/fabla", "fabla808") sampler.control("volume", 0.6) sampler => Audio.StereoOutput("main", true) local kickNote = Midi.Note(36, 127, 0.25) local snareNote = Midi.Note(40, 127, 0.25) local simpleBeat = Midi.Pattern() simpleBeat.add(kickNote, 1.0) simpleBeat.add(snareNote, 1.25) simpleBeat.add(kickNote, 1 + 3/8.0) // 4th eight note position = 2nd beat offbeat simpleBeat.add(kickNote, 1 + 5/8.0) // 6th eight note position = 3rd beat offbeat simpleBeat.add(snareNote, 1.75) simpleBeat[0] // the first event in the pattern foreach(event in simpleBeat) { println(event.note.pitch + " @ " + event.measure) } local fullBeat = clone simpleBeat for(local i = 0; i < 8; i++) { fullBeat.add(Midi.Note(47, 127, 0.125), 1 + i/8.0) } local fillPattern = Midi.DrumTabReader().read(@" 40|o-o-o-o-oooooooo| 36|o-------o---o---| ") for(local measure = 1; measure <= 16; measure++) { if(measure % 8 == 0) { sampler.schedule(fillPattern, measure) } else if(measure <= 8) { sampler.schedule(simpleBeat, measure) } else { sampler.schedule(fullBeat, measure) } } Time.def.start()