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 03 of 10

Decisions and loops

You have already used two words that end with a colon: l 1 16: and if c:. They are modifiers: they decide whether, how often, or with which chance the rest of the line runs. This study goes through all of them and builds a drum fill: a snare roll that thickens towards the end of the bar.

A modifier, then a line or a block

A modifier ends with :. What it governs is either the rest of the same line, or, when nothing follows the colon, the indented lines below it:

Listing 3-1 · The same loop, in one line and as a block
def two_forms
  menu "LEARN" >> "TWO FORMS"
  l 1 4: tr.add i
  l 9 12:
    tr.add i
    pl.set i amp.vol 100

A block ends at the first line that is less indented. All the lines of one block must start at the same column; the width is yours to choose, but only spaces count (a tab is an error). Modifiers also chain on one line, read from left to right: l 1 16: if er 5 16 i: tr.add i is a loop, then a condition, then the action.

if, elif, else

if c: runs what it governs when c is true. elif c: (else if) and else: follow it, on the next lines and at the same indentation; only the first branch whose condition holds runs.

Listing 3-2 · Three cases for each step
def kick_snare_hat
  menu "LEARN" >> "K S H"
  tr.clr
  l 1 16:
    tr.add i
    if eq mod i 8 1: pl.set i amp.vol 127
    elif eq mod i 8 5: pl.set i amp.vol 110
    else: pl.set i amp.vol 50
Output · what the simulator prints for $ otype.py run kick_snare_hat -v
kick_snare_hat  seed 1 (--seed)  pattern A01 len 16 (default)  track 1
T1  1   5   9   13
    XXXXXXXXXXXXXXXX
    step 1: amp.vol 127   step 2: amp.vol 50   step 3: amp.vol 50   step 4: amp.vol 50
    step 5: amp.vol 110   step 6: amp.vol 50   step 7: amp.vol 50   step 8: amp.vol 50
    step 9: amp.vol 127   step 10: amp.vol 50   step 11: amp.vol 50   step 12: amp.vol 50
    step 13: amp.vol 110   step 14: amp.vol 50   step 15: amp.vol 50   step 16: amp.vol 50
      X trig  ! one-shot  o trigless trig  - trigless lock  l locks only  . none
part:  tempo 120
cost: 332/336   random draws: rand 0  toss 0  prob 0  drunk 0  file 0

Loops: l and each

l a b: counts i from a to b, both included, downwards if a is greater. Its bounds are fixed before the run: numbers, a const, a param, or an argument of your function, but never a variable. That is what lets OType promise that every run ends.

Inside a loop in a loop, i is the inner counter and j the outer one:

Listing 3-3 · Two counters: j the beat, i the step in the beat
def diagonal
  menu "LEARN" >> "DIAGONAL"
  tr.clr
  l 1 4:                                # j: beats 1..4
    l 1 4:                              # i: steps 1..4 of the beat
      if eq i j: tr.add add mul sub j 1 4 i
Output · what the simulator prints for $ otype.py run diagonal
diagonal  seed 1 (--seed)  pattern A01 len 16 (default)  track 1
T1  1   5   9   13
    X....X....X....X
      X trig  ! one-shot  o trigless trig  - trigless lock  l locks only  . none
part:  tempo 120
cost: 172/256   random draws: rand 0  toss 0  prob 0  drunk 0  file 0

The trig moves one step later in each beat: step 1 of the first beat, step 2 of the second, and so on. add mul sub j 1 4 i turns the beat and the step in the beat into a step of the pattern: (j − 1) × 4 + i. Everything after # is a comment.

each: repeats for each step of the current step set, with i as the step. It shines once you narrow the steps to, say, those that hold a trig; that is the subject of tracks and steps.

Leaving early: break and stop

break leaves the loop it is in; stop ends the whole function there.

Listing 3-4 · Stop placing trigs past step 6
def first_six
  menu "LEARN" >> "FIRST SIX"
  tr.clr
  l 1 16:
    if gt i 6: break
    tr.add i
Output · what the simulator prints for $ otype.py run first_six
first_six  seed 1 (--seed)  pattern A01 len 16 (default)  track 1
T1  1   5   9   13
    XXXXXX..........
      X trig  ! one-shot  o trigless trig  - trigless lock  l locks only  . none
part:  tempo 120
cost: 159/208   random draws: rand 0  toss 0  prob 0  drunk 0  file 0

Chance and counting: prob, every, skip

prob p: runs what it governs with a chance of p percent. Now the fill: the further into the bar, the likelier a snare.

Listing 3-5 · A fill that thickens
def snare_fill
  menu "LEARN" >> "FILL"
  tr.clr
  l 1 16: prob mul i 6: tr.add i
Output · what the simulator prints for $ otype.py run snare_fill --seed 3
snare_fill  seed 3 (--seed)  pattern A01 len 16 (default)  track 1
T1  1   5   9   13
    ...X.XXX.XXX.XXX
      X trig  ! one-shot  o trigless trig  - trigless lock  l locks only  . none
part:  tempo 120
cost: 184/208   random draws: rand 0  toss 0  prob 16  drunk 0  file 0

Each run draws new numbers, so each press gives a different fill. The simulator shows one of them (its --seed fixes which; the randomness study explains seeds).

every n: runs once every n times the line is reached, and skip n: every time except that one. Their count is kept from one press to the next, so every 4: tr.add 16 adds a trig on step 16 on every fourth press only.

What you learned

…:
a modifier governs the rest of its line, or the indented block below
if · elif · else
choose between branches
l a b: · each:
count with i (and j outside) · visit the steps
break · stop
leave the loop · leave the function
prob p:
with p % chance
every n: · skip n:
once every n times · all but that time

Try it

  • Rewrite first_six without break, with the loop bounds alone.
  • Make the fill thin out instead: a chance of sub 100 mul i 6.
  • Add an every 2: line that clears the track on every other press.