Organizing Sound

Putting Sound in Context

To organize sounds, we need to put things into context. Everything in Mégra runs inside a "context" where different sequences (or sequence generators) are synchronized. That's why it's a "Sync-conteXt", or "sx" for short. "Synchronization" here refers to to the generators being started at the same time. If they run at different speeds (more on that later), they won't stay synchronized.

A sync context needs a name and a flag that tells it whether it should be running or not.

Sound in Mégra is predominantly organized by putting sound events in a sequence with the help of sequence generators.

Thus, inside the context are sequence generators. A sequence generator generates a sequence of sound (or control) events, hence the name.

;; This is a comment ... everything behind ;; will be ignored.

;; This is a sync context. It catches the sound events generated by the sequence generators.
;; Names in Mégra start with a ' , so the name here is just 'context. This is inherited from 
;; Lisp family of programming languages.
;; The flag (boolean) is either #t (true) or #f (false) and determines whether the context 
;; active or not.

(sx 'context #t ;; <-- sx <name> <flag>
  (loop 'a-loop-generator (bd) (~) (hats) (~) (sn) (~) (hats) (~))) ;; <-- this generates a sequence of event

You can put multiple sequence generators in the same context, so that they all run in sync: Per default, new generators (that were not present in the context before) are syncronized on a non-silent event.

(sx 'context #t ;; <-- set this to #f to mute this context !
  (loop 'percussion-generator (~) (~) (~) (risset 'a5) (~) (~) (risset 'c6) (~))
  (loop 'bassline-generator (saw 'a1) (~) (saw 'ds2) (saw 'e2) (~) (~) (saw 'c3) (~))
  (loop 'beat-generator (bd) (~) (hats) (~) (sn) (~) (hats) (~))) 

You can also synchronize multiple contexts:

(sx 'context-a #t ;; <-- execute this first
  (loop 'percussion-generator (~) (~) (~) risset:'a5 (~) (~) risset:'c6 (~))
  (loop 'beat-generator bd (~) hats (~) sn (~) hats (~)))

(sx 'context-b #t :sync 'context-a ;; <-- execute this when you deem fit 
  (loop 'bassline-generator (saw 'a1) (~) (saw 'ds2) (saw 'e2) (~) (~) (saw 'c3) (~)))

(clear) ;; clear stops everything

There's more cool stuff you can do with the sync context alone, such as soloing and blocking:

(sx 'context #t :solo 'beat-generator ;; <-- You can solo single generators ...
  (loop 'percussion-generator (~) (~) (~) (risset 'a5) (~) (~) (risset 'c6) (~))
  (loop 'bassline-generator (saw 'a1) (~) (saw 'ds2) (saw 'e2) (~) (~) (saw 'c3) (~))
  (loop 'beat-generator (bd) (~) (hats) (~) (sn) (~) (hats) (~)))

(sx 'context #t :block 'beat-generator ;; <-- ... or block them!
  (loop 'percussion-generator (~) (~) (~) (risset 'a5) (~) (~) (risset 'c6) (~))
  (loop 'bassline-generator (saw 'a1) (~) (saw 'ds2) (saw 'e2) (~) (~) (saw 'c3) (~))
  (loop 'beat-generator (bd) (~) (hats) (~) (sn) (~) (hats) (~)))

(sx 'context #t :block 'sn ;; <-- You can also block or solo a special event types.
  (loop 'percussion-generator (~) (~) (~) (risset 'a5) (~) (~) (risset 'c6) (~))
  (loop 'bassline-generator (saw 'a1) (~) (saw 'ds2) (saw 'e2) (~) (~) (saw 'c3) (~))
  (loop 'beat-generator (bd) (~) (hats) (~) (sn) (~) (hats) (~)))

(sx 'context #t :solo 'sn 'risset ;; <-- You can also block or solo multiple tags.
  (loop 'percussion-generator (~) (~) (~) (risset 'a5) (~) (~) (risset 'c6) (~))
  (loop 'bassline-generator (saw 'a1) (~) (saw 'ds2) (saw 'e2) (~) (~) saw:'c3 (~))
  (loop 'beat-generator (bd) (~) (hats) (~) (sn) (~) (hats) (~)))

(clear)

TECH BACKGROUND:

You can also think of the sync context as a sink for sound events. Every sound event that is emitted by the generator has a bunch of tags, which contain the name of the generator, the event type, and, in case of sample events, the search tags (more about that later). The block and solo keywords activate filters that act on these tags.

Creating Structures - Some Sequence Generators Types

We've already seen one type of generator, the loop generator. There are several more. Each sequence generator is fundamentally a Markov chain that behaves in a certain way, depending on how it is created. A sequence generator can behave non-deterministically, but doesn't necessarily have to.

TIP: You can visualize the Markov chains, as they can easily be represented as a graph. For that end, you need graphviz (https://graphviz.org/) installed! (more later as we go)

Let's look at some generators now. For a complete list, see the 'Sequence Generator' section in the 'Function Reference' section below. As already mentioned, each generator needs an identifier. This is so that we can adress them for visualization, and keep their state over the various executions.

The Nucleus Generator

.. or 'nuc', for short ...

(sx 'ctx #t ;; <-- a continous beep
  (nuc 'core :dur 400 ;; <-- this keyword argument controls the time interval. 
  ;; It can be used with all sequence generators!
    (sine 440)
    ;;(sine 885) ;; <-- you can pass one or many events, uncomment to try
    ))

(clear)

Just a Sine Wave

This is the most simple generator, it just repeats the events it is given over and over at a steady time interval. Each generator has a bunch of keyword arguments, as you can see above.

The Loop Generator

We've already seen this above in the introduction of contexts. The loop generator creates, you've guessed it, loop, even though there's more to it, as we will see.

;; Here's a basic loop:
(sx 'trololo #t ;; <-- there's no need to name every context 'context' ... you don't use 'password' for all your passwords, do you ?
  (loop 'bells (risset 'a4) (~) (risset 'a6) (~) (risset 'a4) (~) (risset 'c5) (risset 'e5)))

By default, the onsets of the sound events are evenly spaced. If you want to modify the time between onsets, you can specify the time in milliseconds (the defualt time is 200ms):

(sx 'trololo # ;; much faster and uneven
    (loop 'bells (risset 'a4) 100 (~) 100 (risset 'a6) (~) 100 (risset 'a4) (~) 300 (risset 'c5) 100 (risset 'e5)))

For a full description, see the entry in the function reference !

The Inference Generator

The loop generator is good to create more or less repetetive sequences from abstract descriptions, But what if you want something more controlled ? If you want to create generators from a set of rules, the 'infer' generator will take them and infer a generator from them. This is also a good opportunity to explain what the markov chains (of probabilistic finite automata, PFA for short) are doing.

If you're into generative music, you probably know already what a Markov Chain is, as it is a fairly common structure in that domain. If not, here's a simple explanation. Even if you know them already, I'd recommend reading the following part to get to know the specific "flavour" of Markov Chains employed by Mégra.

Imagine you want to create a simple boom-bap style beat, with just a bassdrum, a snare and some hi-hats. Only half the time the hi-hat should be in between the bassdrum and the snare.

In slightly more precise terms, we could describe the beat with the following rules. The natural language description is a bit tedious, but bear with me here. More concise descriptions will follow!

  1. Start with a bassdrum.
  2. There's a 50% chance that a snare will follow the bassdrum, after 400 milliseconds.
  3. There's a 50% chance that a hi-hat will follow the bassdrum, after 200 milliseconds.
  4. After every snare will follow a bassdrum, after 400 milliseconds.
  5. After every hi-hat will follow a snare, after 200 milliseconds.
(sx 'boom #t 
  (infer 'bap ;; <- this creates the generator (infers it from rules)
    :events 'b (bd) 's (sn) 'h (hats) ;; <- here's the event mapping ... pretty prosaic ...
    :rules 
    (rule 'b 's 50 400) ;; here's the rules ... format: (rule 'source 'target probability duration)
    (rule 'b 'h 50 200) 
    (rule 's 'b 100 400) 
    (rule 'h 's 100 200)))

;; Visualize it:

(export-dot "beat" :live 'boom 'bap)
;; neato -Tsvg beat_bap_boom.dot -o beat_bap_boom.svg

(clear)

;; You can also introduce repetitions:
(sx 'boom #t 
  (infer 'bap ;; <- this creates the generator (infers it from rules)
    :events 'b (bd) 's (sn) 'h (hats) ;; <- here's the event mapping ... pretty prosaic ...
    :rules 
    (rule 'b 's 50 400)
    (rule 'b 'h 50 200) 
    (rule 's 'b 100 400) 
    (rule 'h 's 10 200)
    (rule 'h 'h 90 50) ;; repeat the hihat with a 90% chance, and really quickly
    (rule 'hhhh 'b 100 200) ;; maximum repetition number: 4
    ))

Visualize again:

(export-dot "beat" :live 'boom 'bap)
$ neato -Tsvg beat_bap_boom.dot -o beat_bap_boom.svg

TECH BACKGROUND: Here you can see the "variable-order" part of variable-order markov chains. You can specify the required memory length when necessary, i.e. to define the number of repetitions.

A bit like irregular trap, no ?

The Learning Generator

Markov chains cannot only be inferred from rules, but also learned from a sample. Here's where a bit of old-fashioned machine learning comes in.

So if instead of an exact sequence you need "something like" the sample, you can learn the pattern from the sample. Durations are fixed for this method.

(sx 'boom #t 
  (learn 'bap ;; <- this creates the generator (learns from a sample)
    :events 'b (bd) 's (sn) 'h (hats) 'r (risset 'c5)
    :sample "bsbs~~~bsbsb~~hbhb~rbrb~~" ;; <-- hack in some sample string .. '~' stands for silence
    ))

(clear)