Skip to content

Language for a Reusable Piece

On this page you turn a drum part into code you can reuse. You name values, write a function, keep a phrase in an object, and move a helper into another file.

Finish Live Coding Workflow. Open your project’s src/main.non. Evaluate RESET; on its own, then remove it from the file. You should have an empty, silent session. Select and evaluate each block below in order.

use "std/instruments" { Sampler, Kit };
let level = -9;
let rhythm = [bd _ sd _, hh*8];
let drums = AudioTrack("drums");
drums.set_instrument(Sampler(Kit("CR-78")));
drums.volume << level;
drums << rhythm;
PLAY;

let gives a value a name. Here the values are a number, a pattern, and a track. A semicolon finishes a statement; // starts a comment. << sends a value to a track or parameter. It differs from =, which changes a variable:

level = -12;
print(level);
drums.volume << level;

The Output panel prints -12 and the drums get quieter. Assigning level alone would not turn the fader: you send the new value with the last line.

A function names a set of steps. Define one at the top level of the file, outside other blocks:

fn drum_part(name, pattern, level = -9) {
let track = AudioTrack(name);
track.set_instrument(Sampler(Kit("CR-78")));
track.volume << level;
track << pattern;
return track;
}
let hats = drum_part("hats", [hh*8], -18);
drums << [bd _ sd _];

You now hear the hats on their own quieter track. name, pattern and level are parameters: values passed into the function. The local track name exists only inside it. return gives the track back to the caller, who keeps it in hats. The default level = -9 lets you omit the last argument.

Try drum_part("hats", [hh*4], -15); on its own. It updates the same named track.

A tuple groups values; a destructuring let unpacks them into names:

fn parts() {
return ([bd _ sd _], [hh*8]);
}
let (beat, ticking) = parts();
drums << beat;
hats << ticking;

The beat returns to its earlier shape. You will use the same (drums, verb) return and let (drums, verb) unpacking in Your First Mix.

A class groups stored values (fields) and functions that use them (methods). Write this definition below your functions:

class Phrase {
let pattern;
let level;
fn new(pattern, level = -9) {
this.pattern = pattern;
this.level = level;
}
fn play_on(track) {
track.volume << this.level;
track << this.pattern;
}
}
let fill = Phrase([sd*8], -12);
fill.play_on(drums);

Phrase(...) creates an instance and calls new. this is that instance. You hear a snare roll, with the hats still playing. Change a field, then call the method again:

fill.pattern = [bd _ sd _];
fill.play_on(drums);

The kick and snare return. Editing the field stores a new pattern; calling play_on sends it.

Create src/drum_parts.non. Copy only the drum_part function definition into it, with its import, and save:

use "std/instruments" { Sampler, Kit };
fn drum_part(name, pattern, level = -9) {
let track = AudioTrack(name);
track.set_instrument(Sampler(Kit("CR-78")));
track.volume << level;
track << pattern;
return track;
}

A .non file imported with use is a module. Its top-level functions are available to the importing file. Replace src/main.non with this smaller piece, save, then evaluate it:

use "./drum_parts" as drum_parts;
let drums = drum_parts.drum_part("drums", [bd _ sd _]);
let hats = drum_parts.drum_part("hats", [hh*8], -18);
PLAY;

./drum_parts is relative to src/main.non; the .non extension is optional. as drum_parts names the module, and drum_parts.drum_part calls its function. The two existing tracks keep playing with the new settings.

Change the default level in drum_parts.non to -12, save it, and reevaluate main.non. The drums get quieter; the hats keep their explicit -18. You have one helper to edit for all parts that use it. Finish by evaluating STOP; on its own.