OTYPElearn1.1scripting for the octatrackoctatrack

The studies
  1. Learn OType
  2. 01Quickstart
  3. 02Numbers and expressions
  4. 03Decisions and loops
  5. 04Variables and memory
  6. 05Tracks and steps
  7. 06Functions
  8. 07Asking the player
  9. 08Chance you can repeat
  10. 09Live scripts
  11. 10When something is wrong
  12. 11Appendix: scope at a glance

study 06 of 10

Functions

Every script so far was one function with a menu entry. A function can also be a word of your own, used by the others: write a Euclidean rhythm once, then call it on any track with any number of hits. This study builds a small library of such words.

Bricks

A function without menu does not appear in the menu. We call it a brick: the other functions use it. Its arguments follow its name, after a |:

Listing 6-1 · A brick, and a function that uses it twice
def two_euclids
  menu "LEARN" >> "2 EUCLIDS"
  euclid_on | 1 5 0
  euclid_on | 2 3 4

def euclid_on | track hits rot
  trk track:
    tr.clr
    l 1 16: if er.r hits 16 rot i: tr.add i
Output · what the simulator prints for $ otype.py run two_euclids -v
two_euclids  seed 1 (--seed)  pattern A01 len 16 (default)  track 1
T1  1   5   9   13
    X...X..X..X..X..
T2  1   5   9   13
    ....X.....X....X
      X trig  ! one-shot  o trigless trig  - trigless lock  l locks only  . none
part:  tempo 120
cost: 354/450   random draws: rand 0  toss 0  prob 0  drunk 0  file 0

A call is the function's name, a |, then its values. The bar is required whenever your function takes arguments, and never written after one of the Octatrack's operations: at a glance, you see what is yours. Your names use letters, digits and _, never a dot; dotted names belong to the machine.

Inside the brick, track, hits and rot are read like variables but cannot be changed. A function takes up to four arguments. They are usually numbers, but can also be a text, a list or a trig condition: OType works out which from the way the brick uses them, and checks every call.

Giving a value back: ret

ret value ends the function and gives the value back to the caller, so the call can stand inside an expression like any operation:

Listing 6-2 · A function that computes a step
def backbeat
  menu "LEARN" >> "BACKBEAT"
  tr.clr
  tr.add beat_step | 2
  tr.add beat_step | 4

def beat_step | beat
  ret sub mul beat 4 3       # beat 1..4 gives step 1, 5, 9 or 13
Output · what the simulator prints for $ otype.py run backbeat
backbeat  seed 1 (--seed)  pattern A01 len 16 (default)  track 1
T1  1   5   9   13
    ....X.......X...
      X trig  ! one-shot  o trigless trig  - trigless lock  l locks only  . none
part:  tempo 120
cost: 140/140   random draws: rand 0  toss 0  prob 0  drunk 0  file 0

A function that ends without ret is worth 0. Called as a statement, its value is simply dropped.

What a function sees

Each call has its own a b c d, all at 0. A function you call cannot touch yours:

Listing 6-3 · Two a's that never meet
def keep_a
  menu "LEARN" >> "KEEP A"
  a 5
  set_a
  msg "A IS STILL" a

def set_a
  a 9
Output · what the simulator prints for $ otype.py run keep_a
keep_a  seed 1 (--seed)  pattern A01 len 16 (default)  track 1
(no track holds anything)
      X trig  ! one-shot  o trigless trig  - trigless lock  l locks only  . none
part:  tempo 120
msg:   "A IS STILL 5"
end box: "A IS STILL 5" (the last msg, instead of DONE)
cost: 4/4   random draws: rand 0  toss 0  prob 0  drunk 0  file 0

The other things a function can see are shared: the global x y z t, every function, and every const. As the previous study showed, the called function also starts with a copy of the caller's tracks and steps. The appendix sums it all up in one table.

Constants

const name value, at the start of a line, names a value that never changes: a number, a text, a list or a trig condition. Constants and functions are global: every file in /SCRIPTS/ sees them, so a file can be a library that the others use.

Listing 6-4 · Names instead of numbers
const kick 1
const snare 2

def named_tracks
  menu "LEARN" >> "NAMED"
  euclid_on | kick 4 0
  euclid_on | snare 2 4

def euclid_on | track hits rot
  trk track:
    tr.clr
    l 1 16: if er.r hits 16 rot i: tr.add i
Output · what the simulator prints for $ otype.py run named_tracks -v
named_tracks  seed 1 (--seed)  pattern A01 len 16 (default)  track 1
T1  1   5   9   13
    X...X...X...X...
T2  1   5   9   13
    ....X.......X...
      X trig  ! one-shot  o trigless trig  - trigless lock  l locks only  . none
part:  tempo 120
cost: 346/450   random draws: rand 0  toss 0  prob 0  drunk 0  file 0

A name can only be used once across all the files: two functions, or a function and a constant, with the same name are both refused.

What functions cannot do

  • Call themselves, directly or through others. ping calling pong calling ping is refused with loop ping>pong>ping.
  • Nest deeper than eight calls.

Both rules, with the fixed loop bounds of decisions and loops, make sure a run always ends, and the checker can tell you in advance how much work it does at most.

What you learned

def name | a b
a function with arguments
brick
a function without menu, used by the others
name | 1 2
calling your own function
ret v
end and give a value back
const name value
a named value, global to all files
no recursion, eight calls deep
so every run ends

Try it

  • Give euclid_on a fourth argument: the number of steps of the rhythm.
  • Write a brick clamp_vol | v that returns v kept between 40 and 127.
  • Move euclid_on to its own file, LIB.OTY, and check both files together.