Skip to content

Commit

Permalink
music.md updates on sequencer
Browse files Browse the repository at this point in the history
  • Loading branch information
bwhitman committed Nov 8, 2024
1 parent a83f677 commit 67c97da
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 19 deletions.
18 changes: 18 additions & 0 deletions docs/music.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ You can also easily change the BPM of the sequencer -- this will impact everythi
tulip.seq_bpm(120)
```

(Make sure to read below about the higher-accuracy sequencer API, `amy.send(sequence)`. The Tulip `seq_X` commands are simple and easy to use, but if you're making a music app that requires rock-solid timing, you'll want to use the AMY sequencer directly.)

## Making new Synths

We're using `midi.config.get_synth(channel=1)` to "borrow" the synth booted with Tulip. But if you're going to want to share your ideas with others, you should make your own `Synth` that doesn't conflict with anything already running on Tulip. That's easy, you can just run:
Expand Down Expand Up @@ -477,6 +479,22 @@ s.note_on(55, 1)
Try saving these setup commands (including the `store_patch`, which gets cleared on power / boot) to a python file, like `woodpiano.py`, and `execfile("woodpiano.py")` on reboot to set it up for you!


## Direct AMY sequencer

Tulip can use the AMY sequencer directly. The `tulip.seq_X` commands are written in Python, and may end up being delayed some small amount of milliseconds if Python is busy doing other things (like drawing a screen.) For this reason, we recommend using the AMY sequencer directly for music, and using the Tulip sequencer for graphical updates. The AMY sequencer runs in a separate "thread" on Tulip and cannot be interrupted. It will maintain rock-solid timing using the audio clock on your device.

A great longer example of how to do this is in our [`drums` app](https://github.com/shorepine/tulipcc/blob/main/tulip/shared/py/drums.py). You can see that the drum pattern itself is updated in AMY any time a parameter is changed, and that we use `tulip.seq_X` callbacks only to update the "time LED" ticker across the top.

You can schedule events to happen in a sequence in AMY using `amy.send(sequence=` commands. For the drum machine example, you set the `period` of the sequence and then update events using AMY commands at the right `tick` offset to that length. For example, a drum machine that has 16 steps, each an eighth note, would have a `period` of 24 * 16 = 384. (24 is half of the sequencer's PPQ. If you wanted 16 quarter notes, you would use 48 * 16. Sixteenth notes would be 12 * 16.) And then, each event you expect to play in that pattern is sequenced with an "offset" `tick` into that pattern. The first event in the pattern is at `tick` 0, and the 9th event would be at `tick` 24 * 9 = 216.

```python
amy.send(reset=amy.RESET_SEQUENCER) # clears the sequence

amy.send(osc=0, vel=1, wave=amy.PCM, patch=0, sequence="0,384,1") # first slot of a 16 1/8th note drum machine
amy.send(osc=1, vel=1, wave=amy.PCM, patch=1, sequence="216,384,2") # ninth slot of a 16 1/8th note drum machine
```

The three parameters in `sequence` are `tick`, `period` and then `tag`. `tag` is used to keep track of which events are scheduled, so you can overwrite their parameters or delete them later.



Expand Down
19 changes: 0 additions & 19 deletions tulip/shared/py/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ def current_lv_group():

def hide(i):
g = tulip.current_uiscreen().group
#g = lv.screen_active().get_child(0)
to_hide = g.get_child(i)
try:
to_hide.add_flag(1) # hide
Expand All @@ -69,7 +68,6 @@ def hide(i):


def unhide(i):
#g = lv.screen_active().get_child(0)
g = tulip.current_uiscreen().group
to_unhide = g.get_child(i)
try:
Expand All @@ -84,7 +82,6 @@ class UIScreen():
# Start drawing at this position, a little to the right of the edge and 100px down
default_offset_x = 10
default_offset_y = 100
#load_delay = 200 # milliseconds between section loads

def __init__(self, name, keep_tfb = False, bg_color=default_bg_color, offset_x=default_offset_x, offset_y=default_offset_y,
activate_callback=None, quit_callback=None, deactivate_callback=None, handle_keyboard=False):
Expand Down Expand Up @@ -166,9 +163,6 @@ def alttab_callback(self, e):
if(len(running_apps)>1):
self.active = False

#for i in range(self.group.get_child_count()):
# hide(i)

if(self.deactivate_callback is not None):
self.deactivate_callback(self)

Expand Down Expand Up @@ -206,7 +200,6 @@ def add(self, obj, first_align=lv.ALIGN.TOP_LEFT, direction=lv.ALIGN.OUT_RIGHT_M

if(type(obj) != list): obj = [obj]
for o in obj:
#o.update_callbacks(self.change_callback)
o.group.set_parent(self.group)
o.group.set_style_bg_color(pal_to_lv(self.bg_color), lv.PART.MAIN)
o.group.set_height(lv.SIZE_CONTENT)
Expand All @@ -220,7 +213,6 @@ def add(self, obj, first_align=lv.ALIGN.TOP_LEFT, direction=lv.ALIGN.OUT_RIGHT_M
o.group.align_to(self.group,first_align,self.offset_x,self.offset_y)
o.group.set_width(o.group.get_width()+pad_x)
o.group.set_height(o.group.get_height()+pad_y)
#o.group.add_flag(1) # Hide by default
if(x is not None and y is not None): o.group.set_pos(x,y)
self.last_obj_added = o.group

Expand All @@ -234,14 +226,6 @@ def present(self):

lv.screen_load(self.screen)


# We stagger the loading of LVGL elements in presenting a screen.
# Tulip can draw the screen faster, but the bandwidth it uses on SPIRAM to draw to the screen BG kills audio if done too fast.
#wait_time = UIScreen.load_delay
#for i in range(self.group.get_child_count()):
# tulip.defer(unhide, i, UIScreen.load_delay + i*UIScreen.load_delay)
# wait_time = wait_time + UIScreen.load_delay

if(self.handle_keyboard):
get_keypad_indev().set_group(self.kb_group)

Expand All @@ -259,7 +243,6 @@ def present(self):

if(self.activate_callback is not None):
self.activate_callback(self)
#tulip.defer(self.activate_callback, self, wait_time)

tulip.ui_quit_callback(self.screen_quit_callback)
tulip.ui_switch_callback(self.alttab_callback)
Expand All @@ -279,7 +262,6 @@ class UIElement():
temp_screen = lv.obj()

def __init__(self, debug=False):
#self.change_callback = None
self.group = lv.obj(UIElement.temp_screen)
# Hot tip - set this to 1 if you're debugging why elements are not aligning like you think they should
bw = 0
Expand All @@ -289,7 +271,6 @@ def __init__(self, debug=False):

def update_callbacks(self, cb):
pass
#self.change_callback = cb

# Remove the elements you created (including the group)
def remove_items(self):
Expand Down

0 comments on commit 67c97da

Please sign in to comment.