diff --git a/docs/events/action.md b/docs/events/action.md index f59e9ab..55d375f 100644 --- a/docs/events/action.md +++ b/docs/events/action.md @@ -1,3 +1,44 @@ # Action events -Action events trigger Python functions. \ No newline at end of file +Action events trigger Python functions. Set the `action` property to a function or lambda, and it will be executed at the time of the event: + +```python +timeline.schedule({ + "action": lambda: print("Hello world") +}) +``` + +Observe that, when you run the above, it will print `Hello world` indefinitely, once per beat. Why is this? + +Just as for [notes](note.md) and other event types, the `duration` parameter of the event template defaults to an infinitely-repeating pattern generated by `PConstant`. To limit the number of repeats that an action performs, use the `count` argument: + +```python +timeline.schedule({ + "action": lambda: print(round(timeline.current_time)) +}, count=4) +``` + +## Action arguments + +For more complex functions, custom named keyword arguments can be passed to the function using the `args` property + +This executes an action every 4 beats to change the global key of the piece, using the [Globals](/patterns/#globals) variables: + +```python +def set_key(k): + iso.Globals.set("key", iso.Key(key)) + +timeline.schedule({ + "action": set_key, + "args": { + "k": iso.PWhite(8) + }, + "duration": 4 +}) +timeline.schedule({ + "degree": 0, + "key": iso.PGlobals("key"), + "octave": 4 +}) +``` + diff --git a/docs/events/index.md b/docs/events/index.md index 3fda4a5..d46d85f 100644 --- a/docs/events/index.md +++ b/docs/events/index.md @@ -6,8 +6,10 @@ Events are scheduled by passing a dict to `Timeline.schedule()`, which inspects - Event dicts with a `control` or `program_change` key are assumed to be `control` events - Event dicts with an `action` key is assumed to be an `action` event +The default values for unspecified parameters in an Event dict are infinite patterns generated by `PConstant`. This means that, unless a finite parameter is explicitly passed, events will continue to be generated forever. + ## Event types - [Note events](note.md) trigger discrete MIDI notes, with a duration and amplitude - [Control events](control.md) include MIDI control change, program change and pitchwheel messages, and can apply quasi-continuous control curves -- [Action events](control.md) call arbitrary Python functions +- [Action events](action.md) call arbitrary Python functions diff --git a/docs/events/note.md b/docs/events/note.md index b1c219f..51039a1 100644 --- a/docs/events/note.md +++ b/docs/events/note.md @@ -141,5 +141,6 @@ Many Pattern classes can operate explicitly on rests, or introduce rests. For example: - `PCollapse` takes an input and steps past any rests to remove gaps -- `PSkipIf` replaces notes with rests randomly given a probability +- `PSkipIf` replaces notes with rests given a conditional +- `PSkip` replaces notes with rests randomly given a probability - `PPad` pads a sequence with rests until it reaches a specified length \ No newline at end of file diff --git a/docs/patterns/index.md b/docs/patterns/index.md index f5a3fba..670bde1 100644 --- a/docs/patterns/index.md +++ b/docs/patterns/index.md @@ -25,7 +25,7 @@ StopIteration Note that this means that patterns can't seek backwards in time. Their only concern is generating the next event. -By assigning patterns to properties of [events](events/index.md), you can specify sequences of values to control any aspect of the control output: pitch, velocity, duration, etc. +By assigning patterns to properties of [events](/events/), you can specify sequences of values to control any aspect of the control output: pitch, velocity, duration, etc. Patterns can be finite, such as the example above, or infinite, in which case they will keep generating new values forever. @@ -35,6 +35,12 @@ Patterns can also typically generate different Python types. Some Pattern classe - `PWhite(0, 10)` generates a stream of ints between `[0 .. 9]` - `PWhite(0.0, 10.0)` generates a stream of floats between `[0.0 .. 10.0]` - `PChoice([ Key("C", "major"), Key("A", "minor") ])` picks one of the specified [Key](../events/note.md)s at random + +## Pattern resolution + +When a pattern returns a pattern, the embedded pattern will also be resolved recursively. For example: + + - `PChoice([ PSequence([0, 2, 3]), PSequence([7, 5, 2 ]) ])` each step, picks one of the embedded patterns and returns its next value ## Pattern operators diff --git a/docs/timelines/index.md b/docs/timelines/index.md index c3fed9b..8b548db 100644 --- a/docs/timelines/index.md +++ b/docs/timelines/index.md @@ -62,6 +62,14 @@ Scheduling can be quantized or delayed by passing args to the `schedule()` metho - `quantize=N`: quantize to the next `N` beats before beginning playback. For example, `quantize=1` will quantize to the next beat. `quantize=0.25` will quantize to a quarter-beat. - `delay=N`: delay by `N` beats before beginning playback. If `quantize` and `delay` are both specified, quantization is applied, and the event is scheduled `delay` beats after the quantization time. +To limit the number of iterations of an event, pass the `count` property: + +``` +timeline.schedule({ + "note": iso.PSeries(0, 1) + 60 +}, count=4) +``` + ## Clock resolution and accuracy isobar's internal clock by default has a resolution of 480 ticks per beat (PPQN), which equates to a timing precision of 1ms at 120bpm.