├── docs
├── CNAME
├── generate.py
├── page.html
├── github-markdown.css
├── content.md
└── index.html
├── braid.egg-info
├── top_level.txt
├── dependency_links.txt
├── requires.txt
├── SOURCES.txt
└── PKG-INFO
├── MANIFEST.in
├── package.sh
├── .gitattributes
├── braid
├── logger.py
├── __init__.py
├── custom.py
├── synths.yaml
├── core.py
├── midi.py
├── signal.py
├── notation.py
├── pattern.py
├── tween.py
└── thread.py
├── .gitignore
├── README.md
├── setup.py
├── examples
├── 20181009.py
├── 20181008.py
├── 20181014.py
├── 20181012.py
└── 20180804.py
├── CHANGELOG.md
└── LICENSE.txt
/docs/CNAME:
--------------------------------------------------------------------------------
1 | braid.live
--------------------------------------------------------------------------------
/braid.egg-info/top_level.txt:
--------------------------------------------------------------------------------
1 | braid
2 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include braid/synths.yaml
2 |
--------------------------------------------------------------------------------
/braid.egg-info/dependency_links.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/package.sh:
--------------------------------------------------------------------------------
1 | python3 setup.py sdist
2 | rm -r dist
3 |
--------------------------------------------------------------------------------
/braid.egg-info/requires.txt:
--------------------------------------------------------------------------------
1 | PyYAML>=3.11
2 | python-rtmidi>=1.0.0
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-vendored=true
2 | *.html linguist-vendored=true
--------------------------------------------------------------------------------
/braid/logger.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import logging
4 |
5 | logger = logging.getLogger("braid")
6 |
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 | .DS_Store
3 | __pycache__
4 | TODO.md
5 | shimring
6 | build
7 | dist
8 | examples/*
9 | recordings
10 | dev
11 | refs
12 | NOTES.md
13 | *backup.zip
14 | .idea
15 | *.code-workspace
16 |
17 | *.py
18 | !setup.py
19 | !*/*.py
20 |
--------------------------------------------------------------------------------
/braid/__init__.py:
--------------------------------------------------------------------------------
1 | import inspect
2 |
3 | def num_args(f):
4 | """Returns the number of arguments received by the given function"""
5 | return len(inspect.getfullargspec(f).args)
6 |
7 | from .midi import midi_out
8 |
9 | from .thread import *
10 | from .signal import *
11 | from .core import *
12 | from . import custom
13 |
14 | def log_midi(value):
15 | midi.log_midi = True if value else False
16 |
17 | if LIVECODING:
18 | play()
19 |
--------------------------------------------------------------------------------
/docs/generate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os, markdown2
4 |
5 | with open(os.path.join(os.path.dirname(__file__), "page.html")) as f:
6 | page = f.read().strip()
7 | with open(os.path.join(os.path.dirname(__file__), "content.md")) as f:
8 | content = markdown2.markdown(f.read().strip(), extras=['fenced-code-blocks', 'codehilite']).replace(">>>", ">>>").replace("{{","").replace("}}","").replace("...", "...")
9 | with open(os.path.join(os.path.dirname(__file__), "index.html"), 'w') as f:
10 | f.write(page.replace("CONTENT", content))
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Braid
2 | =====
3 |
4 | Braid is a single-import module for Python 3 that comprises a sequencer and musical notation system for monophonic MIDI synths. Its emphasis is on interpolation, polyrhythms, phasing, and entrainment.
5 |
6 | Please see the [documentation](https://braid.live/).
7 |
8 |
9 | ### Copyright/License
10 |
11 | Copyright (c) 2013-2021 Brian House
12 |
13 | This program is free software licensed under the GNU General Public License, and you are welcome to redistribute it under certain conditions. It comes without any warranty whatsoever. See the LICENSE file for details, or see
Braid is a single-import module for Python 3 that comprises a sequencer and musical notation system for monophonic MIDI synths. Its emphasis is on interpolation, polyrhythms, phasing, and entrainment.
122 | 123 |Braid can be downloaded from its GitHub repository.
124 | 125 |It was developed by Brian House.
126 | 127 |Thread.chordThread.pattern, part 1Thread.pattern, part 2Thread.pattern, part 3Thread.velocity and Thread.graceThread.phaseThread.rate Idiosyncracy
165 | Braid is a domain-specific language for a very specific set of concerns, namely my interest in gradually evolving rhythmic relationships and music with a relationship to data.
Limited scope
167 | Braid is MIDI-based, it's monophonic, and it's only a sequencer, not a synthesizer. It's intended to be used with things like the Meeblip and the Korg Volca series, but you can wire it into a DAW, too.
Integrates with Python
169 | I find specialized development environments frustrating, as they limit what's possible to their own sandbox. Braid is just a python module, and as such can be used within other python projects. This is the source of much of its usefullness and power (particularly when it comes to working with data).
Livecoding?
171 | Maybe. Normally you'd include braid in a script and execute it, but you can also run it in the python interpreter, and that opens up some additional possibilities for exploration and improvisation. Perhaps this could be expanded on with something like jupyter into a full-fledged livecoding environment.
This framework is called Braid, and the fundamental objects are called threads—a thread corresponds to a hardware or software monosynth, and refers to the temporal operations of Braid through which threads can come in and out of sync. This is not a thread in the programming sense (in that respect Braid is single-threaded).
177 | 178 |You'll need to have Python 3 installed—if you're using macOS, I recommend doing so with Homebrew. Tested with python 3.7.
181 | 182 |To install (or update) Braid via the terminal:
183 | pip3 install git+git://github.com/brianhouse/braid --user --upgrade
Braid is, fundamentally, an extension to Python 3. This documentation won't cover python usage and syntax—for that, look here. Most of the power of Braid comes from the fact that it can be interleaved with other python code. Such possibilities are left to the practitioner, or at least out of this documentation.
190 | 191 |Additionally, this documentation assumes a general knowledge of MIDI.
192 | 193 |If you don't know what you're doing with python and the terminal but you've gotten this far, follow the instructions for saving a Braid "Hello World" script below. Then in the Finder, right-click that python file, and open it using the latest version of IDLE, which should appear as one of your choices. You can then use IDLE's built-in text editor to write, save, and run ("Run->Run Module") Braid scripts.
194 | 195 |Any MIDI software or hardware device you have running should more or less work with Braid to make sounds. If you are on macOS, to simplify things download and run this simple MIDI bridge app which will let you use General MIDI for the purposes of this documentation (make sure no other MIDI devices are running before launching the app, and launch it before starting Braid).
198 | 199 |To begin working with Braid, create a new file in your favorite text editor (such as Sublime Text). Create a thread—the fundamental object of Braid—and start it:
200 | 201 |from braid import *
202 |
203 | t = Thread(1) # create a thread with MIDI channel
204 | t.pattern = C, C, C, C # add a pattern
205 | t.start() # start it
206 |
207 | play() # don't forget this
208 |
209 |
210 | Save it as hello_world.py, and run it with python3 hello_world.py 0 0. The (optional) arguments designate the MIDI out and in interfaces to use.
$ python3 hello_world.py
213 | Python 3.7.4 (default, Jul 9 2019, 18:13:23)
214 | [Clang 10.0.1 (clang-1001.0.46.4)] on darwin
215 | Type "help", "copyright", "credits" or "license" for more information.
216 | MIDI outputs available: ['to general_MIDI_bridge 1', 'to general_MIDI_bridge 2']
217 | MIDI OUT: to general_MIDI_bridge 1
218 | MIDI IN: from general_MIDI_bridge 1
219 | Loaded synths
220 | Braid started
221 | Playing
222 |
223 |
224 | That's it! You should be hearing the steady pulse of progress.
225 | 226 |You can start and stop individual threads, with a_thread.start() and a_thread.stop(), which essentially behave like a mute button.
Braid also has some universal playback controls. When Braid launches, it is automatically in play mode. Use pause() to mute everything, and play() to get it going again. If you use stop(), it will stop all threads, so you'll need to start them up again individually. clear() just stops the threads, but Braid itself is still going and if you start a thread it will sound right away.
Advanced note: If you're doing a lot of livecoding, it's easy to create a new thread with the same name as an old one, and this can lead to orphan threads that you hear but can't reference. Use stop() or clear() to silence these.
Try it now:
235 | 236 |clear()
237 |
238 |
239 | chordStart a thread
242 | 243 |t = Thread(1) # create a thread on channel 1
244 | t.start()
245 |
246 |
247 | MIDI pitch value can be specified by MIDI number or with note-name aliases between C0 and B8. C is an alias for C4, likewise for the rest of the middle octave
248 | 249 |t.pattern = C, C, C, C
250 |
251 |
252 | is the same as
253 | 254 |t.pattern = 60, 60, 60, 60
255 |
256 |
257 | 0s simply sustain (no MIDI sent)
258 | 259 |t.pattern = C, 0, C, C
260 |
261 |
262 | Rests (explicit MIDI note-offs) are specified with a Z
263 | 264 |t.pattern = C, Z, C, Z
265 |
266 |
267 | By default, there is no specified chord. But if there is one, notes can be specified by scale degree
268 | 269 |t.chord = C4, MAJ
270 | t.pattern = 1, 3, 5, 7
271 |
272 |
273 | Negative numbers designate the octave below
274 | 275 |t.pattern = -5, -7, 1, 5
276 |
277 |
278 | A chord consists of a root note and a scale. For example, C, MAJ is a major scale built off of C4. That means 1, 2, 3, 4, 5 is the equivalent of C4, D4, E4, F4, G4. But behind the scenes, it's specified like this: Scale([0, 2, 4, 5, 7, 9, 11]). Here's the list of built-in scales.
Custom scales can be generated with the following syntax, where numbers are chromatic steps from the root
281 | 282 |whole_tone_scale = Scale([0, 2, 4, 6, 8, 10])
283 |
284 |
285 | R specifies a random note in the scale
286 | 287 |t.chord = C4, whole_tone_scale
288 | t.pattern = 1, R, R, -6
289 |
290 |
291 | Grace notes are specified by using floats
292 | 293 |t.pattern = 1, 1., 1., 1.
294 |
295 |
296 | Use the g function to create a grace note on note specified with a symbol
297 | 298 |t.chord = None
299 | t.pattern = C, g(C), g(C), g(C)
300 |
301 |
302 | Thread.pattern, part 1Start a thread with a pattern
305 | 306 |t = Thread(1)
307 | t.chord = C, DOR
308 | t.pattern = 1, 1, 1, 1
309 | t.start()
310 |
311 |
312 | Once started, a thread repeats its pattern. Each repetition is called a cycle. Each cycle is subdivided evenly by the steps in the pattern.
313 | 314 |t.pattern = 1, 0, 1, 0 # 4/4
315 | t.pattern = 1, 0, 1 # 3/4
316 | t.pattern = 1, 1, 0, 1, 1, 0, 1 # 7/8
317 |
318 |
319 | Each step of a pattern can be a note, but it can also be a subdivision
320 | 321 |t.pattern = 1, [1, 1], 1, 1
322 | t.pattern = 1, [1, 1], 1, [1, 1, 1]
323 |
324 |
325 | ...or a subdivision of subdivisions, ad finitum
326 | 327 |t.pattern = 1, [2, [1., 1.]], [3, [2, 1], 1.], [5, [4., 3.]]
328 |
329 |
330 | So brackets indicate subdivisions. Parens, however, indicate a choice.
331 | 332 |t.pattern = 1, (2, 3, 4), 1, 1
333 |
334 |
335 | Brackets and parens can be combined to create intricate markov chains
336 | 337 |tempo(132) # set the universal tempo
338 | d = Thread(10) # channel 10 is MIDI for drums
339 |
340 | d.pattern = [([K, H], [K, K]), (K, O)], (H, [H, K]), (S, [S, (O, K), 0, g(S)]), [[H, H], ([H, H], O, [g(S), g(S), g(S), g(S)])] # K, S, H, O are built-in aliases for 36, 38, 42, 46
341 | d.start()
342 |
343 |
344 | Patterns are python lists, so they can be manipulated as such
345 | 346 |d.pattern = [K, [O, H]] * 4
347 | d.pattern[2] = S
348 | d.pattern[6] = S
349 | d.pattern[6] = [(S, [S, K])]
350 |
351 | d.pattern.reverse()
352 |
353 |
354 | Thread.pattern, part 2There are additional functions for working with rhythms. For example, euclidean rhythms can be generated with the euc function
357 | 358 |tempo(132)
359 | d = Thread(10)
360 | d.start()
361 |
362 | steps = 7
363 | pulses = 3
364 | note = K
365 | d.pattern = euc(steps, pulses, note) # [K, 0, K, 0, K, 0, 0]
366 |
367 |
368 | Adding a pattern to an existing pattern fills any 0s with the new pattern
369 | 370 |d.pattern.add(euc(7, 5, H)) # [K, H, K, H, K, 0, H]
371 |
372 |
373 | XOR'ing a pattern to an existing pattern adds it, but turns any collisions into 0s
374 | 375 |d.pattern.xor([1, 1, 0, 0, 0, 0, 0]) # [0, 0, K, H, K, 0, H]
376 |
377 |
378 | These can be done even if the patterns are different lengths, to create crossrhythms
379 | 380 |d.pattern = [K, K] * 2
381 | d.pattern.add([H, H, H, H, H])
382 |
383 |
384 | Patterns can also be blended
385 | 386 |d.pattern = blend([K, K, K, K], [S, S, S, S]) # this is probabilistic and will be different every time!
387 |
388 |
389 | same as
390 | 391 |d.pattern = K, K, K, K
392 | d.pattern.blend([S, S, S, S])
393 |
394 |
395 | blend can take a balance argument, where 0 is fully pattern A, and 1 is fully pattern B.
396 | 397 |d.pattern = blend([K, K, K, K], [S, S, S, S], 0.2) # more kicks, less snare
398 |
399 |
400 | Thread.pattern, part 3Additionally, any given step in a pattern may also be a function. This function should return a note value.
403 | 404 |t = Thread(1)
405 | t.chord = D, PRG
406 |
407 | def x():
408 | return choice([1, 3, 5, 7])
409 |
410 | t.pattern = [x] * 8
411 |
412 | t.start()
413 |
414 |
415 | This is particularly useful for manipulating synth parameters at each step (see below). In this case, creating a wrapped function allows the actual note value to be passed as a parameter.
416 | 417 |t = VolcaKick(1)
418 |
419 | def k(n):
420 | def f(t):
421 | t.pulse_colour = 127
422 | t.pulse_level = 127
423 | t.tone = 40
424 | t.amp_attack = 0
425 | t.amp_decay = 80
426 | t.resonator_pitch = 0
427 | return n
428 | return f
429 |
430 | def s(n):
431 | def f(t):
432 | t.pulse_colour = 127
433 | t.pulse_level = 127
434 | t.tone = 60
435 | t.amp_attack = 0
436 | t.amp_decay = 20
437 | t.resonator_pitch = 34
438 | return n
439 | return f
440 |
441 | t.pattern = k(1), k(3), s(20), k(1) # custom properties for k and s notes
442 | t.start()
443 |
444 |
445 | Thread.velocity and Thread.graceAll threads come with some properties built-in. We've seen chord already.
t = Thread(10)
450 | t.chord = C, MAJ
451 | t.pattern = 1, 1., 1, 1.
452 | t.start()
453 |
454 |
455 | There is also, of course, velocity
t.velocity = 0.5
458 |
459 |
460 | and grace is a percentage of velocity, to control the depth of the grace notes
t.velocity = 1.0
463 | t.grace = .45
464 |
465 |
466 | Thread.phaseConsider the following:
469 | 470 |t1 = Thread(10)
471 | t1.chord = 76, CHR # root note is "Hi Wood Block"
472 |
473 | t2 = Thread(10)
474 | t2.chord = 77, CHR # root note is "Lo Wood Block"
475 |
476 | t1.pattern = [1, 1, 1, 0], [1, 1, 0, 1], [0, 1, 1, 0] # thanks Steve
477 | t2.pattern = t1.pattern
478 |
479 | t1.start()
480 | t2.start(t1) # t1 as argument
481 |
482 |
483 | Note that in this example, t2 takes t1 as an argument. This ensures that t2 will start in sync with t1. Otherwise, t1 and t2 will start at arbitrary times, which may not be desirable.
However, each thread also has a phase property that allows us to control the relative phase of threads deliberately. Phase goes from 0-1 and indicates how much of the cycle the pattern is offset.
t2.phase = 1/12 # adjust phase by one subdivision
488 | t2.phase = 3/12
489 | t2.phase = 7/12
490 |
491 |
492 | Thread.rateAs we've already used it, the tempo() function sets the universal BPM (or at least the equivalent BPM if cycles were in 4/4 time). Braid silently keeps track of cycles at this tempo. By default, the cycles of each thread match this reference. We just saw how phase can offset the patterns of a thread—it does this in relation to the reference cycle.
Likewise, individual threads can also cycle at their own rate. The rate property of each thread is a multiplier of the reference cycles—0.5 is twice as slow, 2 is twice as fast.
t1 = Thread(1)
499 | t1.pattern = C, C, C, C
500 | t1.start()
501 |
502 | t2 = Thread(2)
503 | t2.pattern = G, G, G, G
504 | t2.start(t1) # keep in phase
505 |
506 | t2.rate = 1/2 # half-speed!
507 |
508 |
509 | Notice that depending on when you hit return, changing the rate can make threads go out of sync (similar to how starting threads at different times puts them out of phase). The way to get around this is to make sure it changes on a cycle edge. For this, use a trigger:
510 | 511 |t2.stop()
512 | t2.start(t1)
513 | def x(): t2.rate = 0.5 # one-line python function
514 | ... # hit return twice
515 | t2.trigger(x) # executes x at the beginning of the next cycle
516 |
517 |
518 | If you're working with scripts, using triggers like this isn't necessary, as things will execute simultaneously.
519 | 520 |Now for the fun part. Any property on a thread can be tweened—that is, interpolated between two values over time (yes, the term is borrowed from Flash).
523 | 524 |This is done simply by assigning a tween() function to the property instead of a value. tween() has two required arguments: the target value, and the number of cycles to get there. (A transition function can also be specified, more on that below.) Braid will then automatically tween from the current value to the target value, starting with the next cycle.
p1 = Thread(1)
527 | p2 = Thread(2)
528 |
529 | pp = [E4, Gb4, B4, Db5], [D5, Gb4, E4, Db5], [B4, Gb4, D5, Db5]
530 | p1.pattern = p2.pattern = pp
531 |
532 | p1.start(); p2.start(p1) # two commands, one line
533 |
534 | p2.phase = tween(1/12, 4.0) # take four cycles to move one subdivision
535 |
536 |
537 | All properties on a thread can be tweened. Device specific MIDI parameters move stepwise between ints within the range 0-127 (see below). rate, phase, velocity, grace change continuously over float values. chord will probabilistically waver between the current value and the target value. pattern will perform a blend between the current and target patterns on each cycle, with the balance shifting from one to the other.
t = Thread(10)
540 | t.start()
541 | t.pattern = K, K, S, [0, 0, 0, K]
542 | t.pattern = tween([[K, K], [S, 0, K, 0], [0, K], [S, K, 0, K]], 8)
543 |
544 | # or:
545 |
546 | t.pattern = euc(8, 5, 43)
547 | t.pattern = tween(euc(8, 6, 50), 8)
548 | t.pattern = tween(euc(8, 5, 43), 8)
549 |
550 |
551 | When a tween completes, it can trigger a function, using the on_end parameter. The following example tweens the phase of Thread t over 8 cycles, and then stops the thread (Note the lack of parentheses around t.stop—we want the thread to execute the function at the end of the tween, not during this declaration!).
t.phase = tween(0.5, 8, on_end=t.stop)
554 |
555 |
556 | Tweens can take an additional property, called a signal. This is any function that takes a float value from 0 to 1 and return another value from 0 to 1—a nonlinear transition function when you don't want to go from A to B in a straight line. (Yes, Flash again).
559 | 560 |Built-in signals: linear (default), ease_in, ease_out, ease_in_out, ease_out_in
t = Thread(1)
563 | t.chord = D, DOR
564 | t.pattern = [1, 3, 5, 7] * 4
565 | t.start()
566 |
567 | t.chord = tween((E, MAJ), 8, ease_in_out())
568 | t.chord = tween((E, MAJ), 8, ease_out_in())
569 |
570 |
571 | Since signals are just functions, you can write your own in Python. ease_out, for example, is just
def ease_out(pos):
574 | pos = clamp(pos)
575 | return (pos - 1)**3 + 1
576 |
577 |
578 | To view a graphic representation of the function, plot it.
579 | 580 |plot(ease_out)
581 |
582 |
583 | You can also convert any timeseries data into a signal function using timeseries(). You might use this to tween velocity over an entire composition, for example, or for data sonification.
data = 0, 0, 1, 0.8, 1, 0.2, 0, 0.4, 0.8, 1 # arbitrary timseries
586 | f = timeseries(data)
587 | plot(f)
588 |
589 | t = Thread(1)
590 | t.chord = D, SUSb9
591 | t.pattern = [1, 2, 3, 4] * 4
592 | t.start()
593 |
594 | t.velocity = 0.0 # sets the lower bound of the range to 0.0
595 | t.velocity = tween(1.0, 24, f) # sets the uppper bound of the range to 1.0, and applies the signal shape over 24 cycles
596 |
597 |
598 | Likewise, you can specify a function with breakpoints using breakpoints(). Each breakpoint is specified with an x,y coordinate system—it doesn't matter what the range and domain are, as it will be normalized. Additionally, a signal shape can be specified for each breakpoint transition, which allows complex curves and transitions.
f = breakpoints(
601 | [0, 0],
602 | [2, 0],
603 | [8, 1, ease_in_out()],
604 | [13, 0, ease_in_out()],
605 | [20, 3, ease_in_out()],
606 | [24, 0, ease_out()],
607 | [27, 1, ease_in_out()],
608 | [28.5, 0, ease_out()],
609 | [37.5, 4, ease_in_out()],
610 | [48, 0, ease_out()]
611 | )
612 | f = breakpoints(data)
613 | plot(f)
614 |
615 |
616 | Braid does something special when you assign a tween to Thread.rate. Ordinarily, if two threads started in sync and one thread tweened its rate, they would inevitably end up out of sync. However, Braid automatically adjusts its tweening function such that threads will remain aligned as best as possible.
t1 = Thread(1)
621 | t1.chord = D, SUSb9
622 | t1.pattern = 1, 1, 1, 1
623 | t1.start()
624 |
625 | t2 = Thread(2)
626 | t2.chord = D, SUSb9
627 | t2.pattern = 4, 4, 4, 4
628 | t2.start(t1)
629 |
630 | t2.rate = tween(0.5, 4)
631 |
632 |
633 | As simple as that is, that's probably the most interesting feature of Braid to me, and what give it its name.
634 | 635 |If you don't want this functionality, pass sync=False to the thread constructor, and the thread won't try to reconcile itself.
t = Thread(1, sync=False)
638 |
639 |
640 | You can sequence in Braid using triggers. A trigger consists of a function, the number of complete cycles to wait before executing it, and whether or not (and how many times) to repeat. Triggers can be added to individual threads (Thread.trigger()), which then reference the thread's cycle, or they can use the universal trigger() function, which reference the universal (silent) cycles (as we've seen with Thread.rate and Thread.phase, these can be different).
Triggers execute at the edge between cycles.
645 | 646 |t = Thread(1)
649 | t.chord = D, SUSb9
650 | t.pattern = 1, 1, 1, 1
651 | t.start()
652 |
653 | def x(): t.pattern = 4, 4, 4, 4 # one-line python function
654 | ...
655 | t.trigger(x) # triggers x at the end of the current cycle
656 | t.trigger(x, 1) # triggers x at the end of the first complete cycle
657 | t.trigger(x, 4) # triggers x at the end of the fourth complete cycle
658 |
659 |
660 | You might want to reuse the same triggered function with different threads. This is facilitated by including an argument in the function definition which will be passed the thread that triggered it.
661 | 662 |t1 = Thread(1)
663 | t1.chord = D, SUSb9
664 | t1.pattern = 1, 1, 1, 1
665 | t1.start()
666 |
667 | t2 = Thread(2)
668 | t2.chord = D, SUSb9
669 | t2.pattern = 4, 4, 4, 4
670 | t2.start(t1)
671 |
672 | def x(t): t.pattern = R, R, R, R # generic 't' argument
673 | ...
674 | t1.trigger(x, 2)
675 | t2.trigger(x, 2) # same function
676 |
677 |
678 | Using a third argument, triggers can be repeated infinitely or for a set number of times.
679 | 680 |t.trigger(x, 4, 2) # trigger x after 4 cycles and after 8 cycles
681 | t.trigger(y, 6, True) # trigger x every 6 cycles
682 |
683 |
684 | Also:
685 | 686 |t.trigger(y, 0, True) # nope
687 |
688 | t.trigger(y) # you probably wanted to do this
689 | t.trigger(y, 1, True)
690 |
691 |
692 | To cancel all triggers on a thread, pass False.
t.trigger(False)
695 |
696 |
697 | To cancel any triggers on a thread that are repeating infinitely, pass repeat=False without other arguments.
t.trigger(repeat=False)
700 |
701 |
702 | For universal triggers, no thread argument can be supplied to the trigger function. And universal triggers operate via the underlying cycle at the global tempo. Otherwise, they are the same as thread triggers, and are particularly useful for sets of changes, as defined in larger functions, or universal functions.
705 | 706 |t1 = Thread(1)
707 | t1.chord = D, SUSb9
708 | t1.pattern = 1, 1, 1, 1
709 | t1.start()
710 |
711 | t2 = Thread(2)
712 | t2.chord = D, SUSb9
713 | t2.pattern = 4, 4, 4, 4
714 | t2.start(t1)
715 |
716 | def x():
717 | ... tempo(tempo() * 1.3) # calling tempo without arguments returns the current value
718 | ... t1.chord = D2, SUSb9
719 | ... t1.phase = 1/8
720 | ... t1.pattern = t2.pattern = R, R, R, R
721 | trigger(x, 2)
722 |
723 |
724 | Braid is designed to work with hardware monosynths. Thus far, the only actual MIDI output we've been sending are MIDI notes. But CC values from devices are mapped directly to thread properties.
727 | 728 |Devices are represented in Braid as extensions to the thread object. To create a custom thread, use the make() function. make() is passed a dictionary with property names mapped to MIDI CC channels.
Voltron = make({'attack': 54, 'decay': 53, 'filter_cutoff': 52, 'pulse_width': 51})
731 |
732 |
733 | Now, Voltron can be used like any thread, but it will also have the specified CC values that can be set, tweened, etc.
734 | 735 |t = Voltron(1)
736 | t.pattern = [1, 2, 3, 5] * 4
737 | t.filter_cutoff = 0
738 | t.filter_cutoff = tween(127, 8)
739 | t.start()
740 |
741 |
742 | Since you'll probably be using the same MIDI devices all the time, and it is tedious to specify this each time you run Braid (especially with large numbers of controls), Braid also automatically loads custom thread types from the synths.yaml file in the root directory.
Note: A second dictionary can be passed to make() as an additional parameter with property names mapped to default MIDI values.
See custom.py in the examples.
In some cases, you may want to use a reference property that does not directly affect a thread itself or send any MIDI data—a thread-specific variable that can be tweened as though it were a property, in order to guide other processes.
753 | 754 |t = Thread(1)
755 | t.add('ref')
756 | t.ref = 0
757 | t.ref = tween(1.0, 8)
758 |
759 |
760 | Threadcyclesteptriggerlog_midi(True|False) Choose whether to see MIDI output (default: False)midi_out.interface = int Change MIDI interface for output (zero-indexed)midi_in.interface = int Change MIDI interface for input (zero-indexed)midi_out.scan() Scan MIDI interfacesThread(int channel) Create a Thread on the specified MIDI channelScale([ints]) Create a Scale with a list of ints corresponding to half-steps from root (0)play()pause()stop()clear()tempo()g()clamp()plot()trigger()random()choice()make()timeseries()breakpoints()K, S, H, OCHR Chromatic, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
805 | MAJ Major, 0, 2, 4, 5, 7, 9, 11
806 | DOM Dominant, 0, 2, 4, 5, 7, 9, 10
807 | MIN Harmonic minor, 0, 2, 3, 5, 7, 8, 11
808 | MMI Major-Minor, 0, 2, 4, 5, 6, 9, 11
809 | PEN Pentatonic, 0, 2, 5, 7, 10
810 | SUSb9 Suspended flat 9, 0, 1, 3, 5, 7, 8, 10
811 | ION Ionian, 0, 2, 4, 5, 7, 9, 11
812 | DOR Dorian, 0, 2, 3, 5, 7, 9, 10
813 | PRG Phrygian, 0, 1, 3, 5, 7, 8, 10
814 | MYX Myxolydian, 0, 2, 4, 5, 7, 9, 10
815 | AOL Aolian, 0, 2, 3, 5, 7, 8, 10
816 | LOC Locrian, 0, 1, 3, 5, 6, 8, 10
817 | BLU Blues, 0, 3, 5, 6, 7, 10
818 | SDR Gamelan Slendro, 0, 2, 5, 7, 9
819 | PLG Gamelan Pelog, 0, 1, 3, 6, 7, 8, 10
820 | JAM jamz, 0, 2, 3, 5, 6, 7, 10, 11
821 | DRM stepwise drums, 0, 2, 7, 14, 6, 10, 3, 39, 31, 13
linear (default)
826 | ease_in
827 | ease_out
828 | ease_in_out
829 | ease_out_in