# A sequencing / live looping utility lib

**URL:** <https://in-thread.sonic-pi.net/t/a-sequencing-live-looping-utility-lib/8794>\
**Category:** Creations & Ideas\
**Created:** [April 23, 2024, 4:25pm UTC](https://in-thread.sonic-pi.net/t/a-sequencing-live-looping-utility-lib/8794 "2024-04-23T16:25:29Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![pebblepoison](https://avatars.discourse-cdn.com/v4/letter/p/d9b06d/32.png) [@pebblepoison](https://in-thread.sonic-pi.net/u/pebblepoison)\
**Post date:** [April 23, 2024, 4:25pm UTC](https://in-thread.sonic-pi.net/t/a-sequencing-live-looping-utility-lib/8794/1 "2024-04-23T16:25:29Z")

</div>

I’m new here and I’m having a lot of fun (thanks @samaaron 🤩 ). I was wondering if any of you would be interested in a little lib / microframework called “seq” I made? It’s just a few functions that sort of automate and clean up things in 2 domains:

- the loops as sequencer tracks (sync, mute / solo track, skip to time, generic ticking clock)
- a meter utility to manipulate beats, time signatures and cue generation

I define my tracks and meters like this:

```ruby
# defining a meter, 7/4 with custom onbeats (the bools map)
seq_meter_info :seven_quarter, (bools 1, 0, 0, 1, 0, 0, 0), period: 0.25

# storing all meters in a easy to access hash
meters = seq_map (seq_meter_info ...), (seq_meter_info ...)

# same pattern, defining a track, it's on state and optional
# active sections (as an array of tick ranges) -> 7*8 or
# eight of my seven_quarters bars
seq_track_info :lead, true, sections: [(7*8..)]

# same pattern, storing them into a track map (for soloing the track)
tracks = seq_map (seq_track_info ...), (seq_track_info ...)

```

Then I can create a meter clock:

```ruby
seq_meter_clock :main, # tick name and prefix for the cues
  meters[:seven_quarter], # the meter determines how the standard cues work
  [:beat, :onbeat, :offbeat] # custom cue selection prefixed here with main_
# available cues: beat, onbeat, offbeat/backbeat,
# downbeat/bars, upbeat, oddbars, evenbars, hyperbeat

```

I put it in the main loop and I add the commented `skip!` action (for easy triggering):

```ruby
#skip! :main, 7*2 <- sets the :main tick at 2 bars from the start
live_loop :main do
  seq_meter_clock :main,
    meters[:seven_quarter],
    [:beat, :onbeat, :offbeat] # ticking and firing cues is taken care of
end

```

I can create a track and tie it to the clock:

```ruby
# first arg is the cues it listens to, second is the track info
seq_track [:main_offbeat], tracks[:lead] do |message|
  play_chord chord(:a3, :m) # just some chord
end

```

The tracks are set in loops too, with `mute!` and `solo!` actions:

```ruby
#solo! tracks, :lead
#mute! tracks[:lead]
live_loop :lead do
  seq_track ...
end

#solo! tracks, :thrill
#mute! tracks[:thrill]
live_loop :thrill do
  seq_track ...
end

```

Would any of you see a use for it? It’s difficult from where I stand to understand if I made something really cool or an unusable gimmick 😋

---

<div class="post-metadata">

**Author:** ![pebblepoison](https://avatars.discourse-cdn.com/v4/letter/p/d9b06d/32.png) [@pebblepoison](https://in-thread.sonic-pi.net/u/pebblepoison)\
**Post date:** [April 23, 2024, 4:55pm UTC](https://in-thread.sonic-pi.net/t/a-sequencing-live-looping-utility-lib/8794/2 "2024-04-23T16:55:15Z")

</div>

A little test track I made using it (30 lines of code, yay). It’s not Mozart.

 Your browser does not support the audio element.

 ![image](https://europe1.discourse-cdn.com/flex017/uploads/sonic_pi/original/2X/1/1355b30729588cc01612b3fa9145361cf5deffd5.png)

---

<div class="post-metadata">

**Author:** ![vino.exe](https://avatars.discourse-cdn.com/v4/letter/v/3da27b/32.png) [@vino.exe](https://in-thread.sonic-pi.net/u/vino.exe)\
**Post date:** [April 26, 2024, 8:34am UTC](https://in-thread.sonic-pi.net/t/a-sequencing-live-looping-utility-lib/8794/3 "2024-04-26T08:34:41Z")

</div>

It would be amazing for me to be able to access this library!

---

<div class="post-metadata">

**Author:** ![pebblepoison](https://avatars.discourse-cdn.com/v4/letter/p/d9b06d/32.png) [@pebblepoison](https://in-thread.sonic-pi.net/u/pebblepoison)\
**Post date:** [April 26, 2024, 9:54am UTC](https://in-thread.sonic-pi.net/t/a-sequencing-live-looping-utility-lib/8794/4 "2024-04-26T09:54:47Z")

</div>

Oh, cool! Well, it still has some training wheels but I’m working out the kinks as I’m polishing my first Sonic Pi composition 🥳 (well, second one, but first one that is not a joke).

I’m more used to traditional song composition than live looping, so mixing the 2 and hacking my way into the concepts has been a real pleasure!

It looks quite a bit different from what it looked like in the example though, because yeah the code is less than a week old 😅 So I’d rather stabilize it a bit before I sharing it, but that’s what my current song is about, so: soon!

Quick overview:

- seq\_map has been taken out as it’s basically a hash
- generated cues also available as tests like `play 65 if is_downbeat? time`
- seq\_track includes the live\_loop
- `apply_fx` to allow fx chaining for a block
- polyrhythms

I’ll be done for now once I get this composition out, I think. It’s swallowing the rest of my life pretty quick 🥲

---

<div class="post-metadata">

**Author:** ![pebblepoison](https://avatars.discourse-cdn.com/v4/letter/p/d9b06d/32.png) [@pebblepoison](https://in-thread.sonic-pi.net/u/pebblepoison)\
**Post date:** [April 26, 2024, 6:51pm UTC](https://in-thread.sonic-pi.net/t/a-sequencing-live-looping-utility-lib/8794/5 "2024-04-26T18:51:50Z")

</div>

Ok, track done, I’ll do a specific post about it I guess even if I feel quite ashamed of the final production 😅 Baby steps!

I’m currently revising my lib because there was a lot to be improved upon. Some of my “features” were just hindrances at a larger scale. The more you know.

---

<div class="post-metadata">

**Author:** ![pebblepoison](https://avatars.discourse-cdn.com/v4/letter/p/d9b06d/32.png) [@pebblepoison](https://in-thread.sonic-pi.net/u/pebblepoison)\
**Post date:** [April 27, 2024, 6:14pm UTC](https://in-thread.sonic-pi.net/t/a-sequencing-live-looping-utility-lib/8794/6 "2024-04-27T18:14:46Z")

</div>

Hey @vino.exe , here it is! It’s a lot more fun now and so I was pumped enough to make a fake ad for it 😂

 ![fake ad](https://europe1.discourse-cdn.com/flex017/uploads/sonic_pi/original/2X/5/5d50c1d6e90c4045bf8506e967002d2535d7a479.png)

> ~~All your sequencing needs~~ (just kidding) under 230 lines of code! Call now +XXX-YYY-ZZZ

> **code**
>
> ```ruby
> # seq, a sequencer lib for sonic pi
> # author: pebblepoison (https://soundcloud.com/briar-p-marten)
> # v2, rework to fix the many many conceptual flaws :p
> 
> # SCORE NOTATION
> 
> def seq_meter pattern, precision: 1
> { pattern:, precision:, bar_count: pattern.length }
> end
> 
> def seconds_to_ticks meter, seconds
> seconds * meter[:precision]
> end
> 
> def ticks_to_seconds meter, ticks
> ticks * 1.0 / meter[:precision]
> end
> 
> def seq_compose meters, sections
> playing = 0
> sections = sections.flatten
> tracks = sections.map { |s| s[:tracks] } .flatten.uniq
> allowed = tracks.map { |name| [name, true] } .to_h
>   
> return { playing:, clocks: sections[0][:clocks], tracks:, allowed:, sections:, meters: }
> end
> 
> def is_playing? composition, track_name
> return false unless composition[:allowed][track_name]
>   
> composition[:sections][composition[:playing]][:tracks].include? track_name
> end
> 
> def seq_section clocks, duration, tracks
> { clocks:, duration:, tracks: }
> end
> 
> def refresh_score composition, ticks
> count = 0
>   
> composition[:playing] = composition[:sections].find_index do |section|
> return if section[:duration] == -1
>     
> count += section[:duration]
>     
> count > ticks
> end
>   
> stop if composition[:playing].nil?
>   
> composition[:clocks] = composition[:sections][composition[:playing]][:clocks]
> end
> 
> # TRACK BUILDING
> 
> def seq_track composition, name, signals
> signals.map do |signal|
> live_loop :"#{name}_track_#{signal}" do
> msg = sync signal
>       
> stop if composition[:playing].nil?
>       
> yield msg if is_playing? composition, name
> end
> end
> end
> 
> def seq_effects *fx_list, controls: {}, &block
> fx = fx_list.shift
>   
> unless fx_list.length.zero?
> with_fx *fx do |ctrl|
> controls = { **controls, "#{fx[0]}_control": ctrl }
> seq_effects *fx_list, controls: controls, &block 
> end
> else
> with_fx *fx do |ctrl|
> block.call ({ **controls, "#{fx[0]}_control": ctrl })
> end
> end
> end
> 
> def seq_sampler *samples
> samples.each_with_index do |s, i|
> sample s[0], *(s.length > 2 ? s[2..] : [])
> sleep s[1] unless s[1] == 0 or samples.length - 1 == i
> end
> end
> 
> # live commands
> 
> def solo! composition, name
> composition[:allowed].keys.each do |key|
> composition[:allowed][key] = key == name
> end
> end
> 
> def mute! composition, name
> composition[:allowed][name] = false
> end
> 
> # PERFORMANCE SETUP
> 
> def seq_clock name, signals: {beat: -> _ {true}}, precision: 1, composition: {}, &block
> live_loop :"#{name}_clock", delay: 1 do |old_msg|
> old_msg = {} if old_msg == 0 or old_msg.nil?
>     
> play_head = play_head! name
> tick_set name, play_head.nil? ? look(name) : play_head
>     
> msg = {
> **old_msg,
> "#{name}_ticks": look(name),
> clocks: composition[:clocks].dup.freeze,
> section: composition[:playing],
> }
>     
> stop if composition[:playing].nil?
>     
> if composition[:clocks].include? name
> refresh_score composition, look(name)
>       
> msg = yield msg
>       
> signals.each do |signal, test|
> cue signal, **msg if test.call look(name)
> end
> end
>     
> tick_set name, look(name) + 1
> sleep ticks_to_seconds ({ precision: }), 1
>     
> msg
> end
> end
> 
> def seq_meter_clock meters, name, signal_names, composition: {}, &block
> seq_clock name,
> signals: (seq_meter_signals name, meters[name], signal_names),
> precision: meters[name][:precision],
> composition: composition do |msg|
> msg = msg == 0 ? {} : msg
> block.call ({ **msg, "#{name}_seconds": (ticks_to_seconds meters[name], look(name)) })
> end
> end
> 
> def seq_meter_signals name, meter, signal_names
> signal_names.map do |signal_name|
> [:"#{name}_#{signal_name}", case signal_name
> when :beat
> -> _ { true }
> when :onbeat
> -> ticks { is_onbeat? meter, ticks }
> when :offbeat, :backbeat
> -> ticks { is_offbeat? meter, ticks }
> when :downbeat, :bar
> -> ticks { is_downbeat? meter, ticks }
> when :upbeat
> -> ticks { is_upbeat? meter, ticks }
> when :oddbar
> -> ticks { is_oddbar? meter, ticks }
> when :evenbar
> -> ticks { is_evenbar? meter, ticks }
> else
> -> _ { false }
> end]
> end.to_h
> end
> 
> def seq_trigger msg, clock_name, ticks
> trigger! msg, clock_name, ticks if ticks == look(clock_name)
> end
> 
> # live commands
> 
> def play! clock_name, ticks
> set :"#{clock_name}_play_head", ticks
> end
> 
> def play_head! clock_name
> key = :"#{clock_name}_play_head"
> play_head = get[key]
>   
> set key, nil unless play_head.nil?
>   
> return play_head
> end
> 
> def trigger! msg, clock_name, ticks
> cue :"#{clock_name}_trigger_#{ticks}", **msg
> end
> 
> # signal tests
> 
> def is_onbeat? meter, ticks
> meter[:pattern][ticks]
> end
> 
> def is_offbeat? meter, ticks
> not meter[:pattern][ticks]
> end
> 
> def is_backbeat? meter, ticks
> is_offbeat? meter, ticks
> end
> 
> def is_downbeat? meter, ticks
> ticks % meter[:bar_count] == 0
> end
> 
> def is_bar? meter, ticks
> is_downbeat? meter, ticks
> end
> 
> def is_upbeat? meter, ticks
> (ticks + 1) % meter[:bar_count] == 0
> end
> 
> def is_oddbar? meter, ticks
> (ticks * 1.0 / meter[:bar_count]) % 2 == 0
> end
> 
> def is_evenbar? meter, ticks
> (ticks * 1.0 / meter[:bar_count]) % 2 == 1
> end
> 
> ```

I’ll explain a bit so you hopefully know what to expect.  
I’ll also include below a demo track I made today as this might be a bit dense 😓

- score notation: `seq_meter`, `seq_compose` and `seq_section` to create scores with specific time signatures

- track building: `seq_track`, `seq_effects` and `seq_sampler` to quickly build and have access to your instruments, tracks and patterns while minimizing the clutter

- performance setup: clocks, metered cue builders and play head positioning to configure you performance or development

> I still need to work on parallel clocks: currently you can theorically set them up but you’d be better off with a live loop. I want to set up 2 possibilities: a “snap” clock that has a meter start at the beginning of every section they’re involved in, and a “free” clock that just runs in parallel and doesn’t care about composition at all. Right now I’ve just got a very mid in-between. But hey, it’s a start 👍

---

<div class="post-metadata">

**Author:** ![pebblepoison](https://avatars.discourse-cdn.com/v4/letter/p/d9b06d/32.png) [@pebblepoison](https://in-thread.sonic-pi.net/u/pebblepoison)\
**Post date:** [April 27, 2024, 6:21pm UTC](https://in-thread.sonic-pi.net/t/a-sequencing-live-looping-utility-lib/8794/7 "2024-04-27T18:21:06Z")

</div>

And here is the demo track.

https://w.soundcloud.com/player/?visual=false&url=https%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F1809285204&show_artwork=true&maxheight=166

> **code**
>
> ```ruby
> run_file "<your path to>/seq2.rb"
> 
> # Millenium
> # A demo for seq2
> 
> # SCORE
> 
> meters = {
> fq: (seq_meter (bools 1, 0, 1, 0), precision: 4),
> fh: (seq_meter (bools 1, 0, 1, 0), precision: 2),
> }
> 
> # duration shortcuts
> fq05 = ticks_to_seconds meters[:fq], 0.5
> fq1 = ticks_to_seconds meters[:fq], 1
> fq2 = ticks_to_seconds meters[:fq], 2
> fq4 = ticks_to_seconds meters[:fq], 4
> fq8 = ticks_to_seconds meters[:fq], 8
> 
> fh05 = ticks_to_seconds meters[:fh], 0.5
> fh1 = ticks_to_seconds meters[:fh], 1
> fh4 = ticks_to_seconds meters[:fh], 4
> 
> millenium_score = seq_compose meters, [
> (seq_section [:fq], 4*8, [:pads, :step_beat]),
> [
> (seq_section [:fq], 8, [:pads, :strong_beat, :wawa]),
> (seq_section [:fq], 8, [:pads, :strong_beat])
> ] * 2,
> (seq_section [:fq], 8, [:woosh, :crowd_call, :wawa]),
> (seq_section [:fq], 8, [:step_beat]),
> [
> (seq_section [:fq], 4*2, [:pads, :strong_beat]),
> (seq_section [:fq], 8, [:pads, :strong_beat, :wawa]),
> ] * 4,
> (seq_section [:fq], 8, [:woosh, :wawa_end]),
> (seq_section [:fh], 4*8, [:pads, :strong_beat]),
> ]
> 
> millenium_live = seq_compose meters, [
> (seq_section :fq, -1, [:pads, :wawa, :wawa_end, :woosh, :strong_beat, :step_beat, :crowd_call]),
> ]
> 
> # composition selection
> millenium = millenium_score
> 
> # TRACKS
> 
> #solo! millenium, :pads
> #mute! millenium, :pads
> seq_track millenium, :pads, [:fq_downbeat, :fh_downbeat] do |msg|
> m4 = (msg[:clocks].include? :fq) ? fq4 : fh4
>   
> seq_effects [:slicer, amp: 1.5] do
> play (chord msg[:root_note] + 48, :major), decay: 0, sustain: m4, release: 0.05
> end
> end
> 
> #solo! millenium, :wawa
> #mute! millenium, :wawa
> seq_track millenium, :wawa, [:fq_oddbar] do |msg|
> seq_effects [:tremolo, phase: fq1, depth: 1] do
> play msg[:root_note] + 72, attack: 0, decay: 0, sustain: fq8, release: 0.05
> end
> end
> 
> #solo! millenium, :wawa_end
> #mute! millenium, :wawa_end
> seq_track millenium, :wawa_end, [:fq_oddbar] do |msg|
> seq_effects [:tremolo, phase: fq1, depth: 1], [:octaver], [:echo], [:reverb], [:pitch_shift, pitch_slide: 1] do |sliders|
> play msg[:root_note] + 72, attack: 0, decay: 0, sustain: fq8, release: 0.05
>     
> 4.times do |i|
> control sliders[:pitch_shift_control], pitch: -i
> sleep fq2
>       
> use_bpm_mul 0.5
> end
> end
> end
> 
> #solo! millenium, :woosh
> #mute! millenium, :woosh
> seq_track millenium, :woosh, [:fq_oddbar] do |_|
> seq_effects [:flanger, feedback_slide: 1, amp: 0.2] do |sliders|
> use_synth :bnoise
>     
> play 48, attack: fq1, decay: 0, sustain: fq4+fq1, release: fq2
> sleep fq4 + fq2
> control sliders[:flanger_control], feedback: 0.5
> end
> end
> 
> #solo! millenium, :strong_beat
> #mute! millenium, :strong_beat
> seq_track millenium, :strong_beat, [:fq_downbeat, :fh_downbeat] do |msg|
> m05 = (msg[:clocks].include? :fq) ? fq05 : fh05
> m1 = (msg[:clocks].include? :fq) ? fq1 : fh1
>   
> seq_sampler [:bd_haus, m1], [:bd_haus, m05, amp: 0.8], [:bd_haus, m05, amp: 0.8],
> [:hat_noiz, 0], [:sn_dolf, m1], [:bd_haus, m05, amp: 0.8], [:bd_haus, m05, amp: 0.8]
> end
> 
> #solo! millenium, :step_beat
> #mute! millenium, :step_beat
> seq_track millenium, :step_beat, [:fq_onbeat] do |msg|
> seq_sampler [:bd_haus, fq1], [:hat_noiz, fq1, amp: 0.8]
> end
> 
> #solo! millenium, :crowd_call
> #mute! millenium, :crowd_call
> seq_track millenium, :crowd_call, [:fq_trigger_66] do |_|
> # Large crowd outdoor screaming and yelling during a meeting in the street of Mexico City by felix.blume -- https://freesound.org/s/214147/ -- License: Creative Commons 0
> sample "<your path to>/crowd call.wav", amp: 0.5
> end
> 
> # PERFORMANCE
> 
> use_bpm 60
> 
> #play! :fq, 80
> seq_meter_clock meters, :fq, [:onbeat, :downbeat, :oddbar], composition: millenium do |msg|
> msg[:root_note] = [0, -2, 3, -4].ring.tick if is_downbeat? meters[:fq], msg[:fq_ticks]
>   
> #trigger! msg, :fq, 66 if msg[:fq_beat] / 4.0 % 16 == 0
> seq_trigger msg, :fq, 66
>   
> msg
> end
> 
> #play! :fh, 40
> seq_meter_clock meters, :fh, [:downbeat], composition: millenium do |msg|
> msg[:root_note] = [-2, -4, 1, -6].ring.tick if is_downbeat? meters[:fh], msg[:fh_ticks]
> msg
> end
> 
> ```

It can be fun as a live loop or a composed track so feel free to tweak and break it 😉

Also, I wouldn’t recommend using the `precision` option for `seq_meter` as a way to change bpm, I did that because I couldn’t think of another use of a 2nd clock on this demo.
