InB♭InPython docs
Welcome to the concert hall
Londoner to taxi driver: "How do you get to the Royal Albert Hall?"
London cabbie: "Practice, guvnor, practice!"
Think of InB♭InPython as a virtual concert hall with two creative spaces.
The rehearsal room is the light one you arrived from: a code editor containing a musical score as Python code, and a Play button.
The stage is the dark one: press Play and twenty musicians - each a short video, everyone playing in the key of B♭ - wait in the wings for your code to call on them. The top of the stage contains the Stop button, which returns you to the code, and log messages that tell you what's going on.
The code is the score, and you are the conductor.
This documentation tells the story of the score, from one solo musician to a full interactive production. Along the way, small examples run live right here in the page (press the Run buttons - they are real Python, in your browser, courtesy of importpython.org, built upon Browser Friendly Python and MicroPython). Each idea ends with a link that opens the real rehearsal room with an illustrative example score, ready to read, modify and perform.
Musical terms are explained as we meet them and no musical training is assumed, only curiosity and a willingness to share playful creative coding.
Your first musician
One good thing about music
When it hits you (You feel no pain)
A performance is a special Python function - one
declared with async def, which simply means "this code
knowingly unfolds through time". Inside it, await means
"wait here until this is done" (you're simply awaiting something to finish).
That's the only programming theory you really need to know.
If your code doesn't work, it's most likely to do with a missing or misused
async or await.
To get a feel what await does before any music
is involved, run this - and notice the awaits (where we call
asyncio.sleep() for a couple of seconds) are real
pauses in the execution:
import asyncio print("a beat...") await asyncio.sleep(2) print("another beat...") await asyncio.sleep(2) print("rests are part of the music.")
Here is the smallest score that makes a sound:
from inbflat import enter, start async def performance(): """One musician, one entrance.""" await enter("mallets") start(performance())
enter() brings a musician on stage - their
video fades up in a randomly chosen seat and begins to
play - and start() is the downbeat that
sets the whole performance going. When the musician is
finished, they fade away to a ghost image of themselves:
a visible memory of their contribution.
Hear one musician enter - it opens in the rehearsal room with this score in the editor, ready for Play.
Meet the band
It was twenty years ago today
When Sgt. Pepper taught the band to play
Twenty musicians wait in the wings, and their names - all listed in the following code example - are often opaque.
What is an "omnichord"?
Who is behind "balloons ambience"?
What on earth is a "plastic toy sax" doing there?
Since Python is the language of the score, let's create a small script so you can meet our musicians - every one recorded in the key of B♭, which is why any combination of them harmonises. This is also a good opportunity to show how Browser Friendly Python (BFP) - the technology underneath this project - harmonises with the browser environment.
First, let's list all the musicians in band.py:
MUSICIANS = [ "electric guitar", "mallets", "kaoss pad", "acoustic violin", "plastic toy sax", "electric bass", "harmon trumpet", "clarinet", "vocals", "banjo with ebow", "the poem", "ebow", "acoustic guitar", "balloons ambience", "omnichord and qchord", "korg ds-10", "synth cello", "emx-1", "acoustic piano", "rhodes keyboard", ]
We need to create an HTML interface and add a sprinkling of style to our interactive roll-call of musicians:
<style> body { background: #faf7f1; font: 1rem Georgia, serif; display: grid; gap: 0.6rem; justify-items: center; } select, button { font: inherit; padding: 0.35rem 0.9rem; } button { color: #faf7f1; background: #23201b; border: none; border-radius: 0.2em; cursor: pointer; } button:hover { background: #3d382f; } video { width: 22rem; max-width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: #0f1013; } </style> <p> <select id="who"></select> <button id="meet">Play</button> </p> <video id="stage" playsinline></video>
Finally, let's write some Python code to help us audition our musicians.
from bfp import web, when from band import MUSICIANS from importpython import EMBEDDER_URL # The embedder is this site's docs page, so the videos # live beside its docs/ directory. VIDEOS = EMBEDDER_URL.split("/docs")[0] + "/videos/" dropdown = web.page["who"] for name in MUSICIANS: dropdown.append(web.option(name)) @when("click", "#meet") def meet(event): """Show and play the chosen musician.""" where = VIDEOS + dropdown.value.replace(" ", "-") stage = web.page["stage"]._dom_element stage.poster = where + ".jpg" stage.src = where + ".mp4" stage.play()
Click "Meet the band" then select a musician from the dropdown menu and press play to meet them.
Rests
My God! What has sound got to do with music?
In music, a rest is written silence - as
deliberate as any note. On the stage, the code
await rest(6) is six seconds of quiet patience and
await rest(2, 10) randomly chooses
somewhere between two and ten seconds of silence. Rests are where the
listening happens, and they are what turn a list of
entrances into a deliberate piece of sound sculpture:
from inbflat import enter, rest, start async def performance(): """Three entrances, with room to listen between.""" await enter("rhodes keyboard") await rest(6) await enter("clarinet") await rest(2, 10) await enter("vocals") start(performance())
Hear the rests at work - it opens in the rehearsal room with this score in the editor, ready for Play.
Rolling the dice
I really didn't have a plan, I just went in and started playing.
Terry Riley's "In C" (1964) and Darren Solomon's
"In Bb 2.0" (2009) - the two ancestors of this piece -
are aleatoric music: composed chance (from the
Latin alea for dice). The composer decides what is possible;
the dice decide what happens at the moment of performance. In Python the
dice are the random module, and
random.seed() is the beautiful trick that
makes chance repeatable: the same seed deals the same
random shuffle, every time.
Here's a Python list containing the musicians:
MUSICIANS = [ "electric guitar", "mallets", "kaoss pad", "acoustic violin", "plastic toy sax", "electric bass", "harmon trumpet", "clarinet", "vocals", "banjo with ebow", "the poem", "ebow", "acoustic guitar", "balloons ambience", "omnichord and qchord", "korg ds-10", "synth cello", "emx-1", "acoustic piano", "rhodes keyboard", ]
And the deal - run it twice; it never changes because we set a seed (1935):
import random from band import MUSICIANS def shuffle(items): """Fisher-Yates shuffling.""" for i in range(len(items) - 1, 0, -1): j = random.randrange(i + 1) items[i], items[j] = items[j], items[i] random.seed(1935) names = list(MUSICIANS) shuffle(names) print("tonight's first five, for seed 1935:") for name in names[:5]: print(" " + name)
That is InB♭InPython's own shuffle function, so this example rolls
the random dice exactly
as the stage does. On the stage, all of this is one
line: arrange() shuffles the musicians and
returns your orchestra. Change the seed and press Play
again: a different group of musicians will appear. Keep
the seed the same and press Play again: the same group
will appear in exactly the same order. If you don't specify
a seed, a new random order will be generated each time.
import random from inbflat import arrange, enter, rest, start random.seed(42) # Change this number to change the seed. orchestra = arrange(musicians=6) async def performance(): """The dice decide who plays, and in what order.""" for musician in orchestra: await enter(musician) await rest(4, 9) start(performance())
Let the dice arrange an orchestra - it opens in the rehearsal room with this score in the editor, ready for Play.
The dice are fair, which also makes them heartless:
the musician you love may simply not be chosen.
arrange(featuring=...) names one
performer - or a list of them - each guaranteed a
place in the orchestra; the random module still chooses
everyone else, and also chooses where in the order
your featured musicians appear.
import random from inbflat import arrange, enter, rest, start random.seed(2009) # The year of "In Bb 2.0". orchestra = arrange(musicians=6, featuring="plastic toy sax") async def performance(): """Six are dealt; the toy sax is promised a seat.""" for musician in orchestra: await enter(musician) await rest(6) start(performance())
Promise a musician their place - it opens in the rehearsal room with this score in the editor, ready for Play.
Polyphony
The idea is distributed in space. It isn't only in one part; one part can't express the idea any longer, only the union of parts can completely express the idea.
Polyphony is a musical term we use to mean "how many voices sound at once". Twenty all at once is a glorious cacophony; three to five is a subtle texture; one soloist is a uniquely personal encounter through sound.
arrange(musicians=8, polyphony=2)
caps the stage at two performers at once: everyone else waits in the wings.
Each rest(), after the stage is full, holds on
until a musician exits, then counts its silence before the
next musician enters -
so the pause always sits between an exit and an entry.
import random from inbflat import arrange, enter, rest, start random.seed(43) orchestra = arrange(musicians=8, polyphony=2) async def performance(): """Two voices at once; the rest wait in the wings.""" for musician in orchestra: await enter(musician) await rest(2, 5) start(performance())
Hear two voices take turns - it opens in the rehearsal room with this score in the editor, ready for Play.
The cap set when arranging is not a law of nature.
Calling polyphony() mid-performance
changes it on the spot: thin the texture towards a
solo, or open it wide. Nobody is evicted when the cap
drops - the stage simply admits no one new until
enough voices have finished:
import random from inbflat import arrange, enter, polyphony, rest, start random.seed(1964) # The year of "In C". orchestra = arrange(musicians=8, polyphony=4) async def performance(): """An intro of four voices, a solo, an outro of three.""" for musician in orchestra[:4]: await enter(musician) await rest(3) await rest(8) polyphony(1) # Entries now wait for an empty stage. await enter(orchestra[4]) # The solo, once truly alone. await rest(10) polyphony(3) # The outro opens the texture again. for musician in orchestra[5:]: await enter(musician) await rest(3, 8) start(performance())
Thin the texture mid-piece - it opens in the rehearsal room with this score in the editor, ready for Play.
Playing together
Make your interactions with people transformational, not just transactional.
Chance is only one way to compose. You can also through-compose: write every entrance yourself, no random musical dice at all.
For that you will want simultaneity -
ensemble() performs several things as one
and returns when the last has finished. And the musicians
comes pre-sorted into sections in the inbflat module, as orchestras do:
strings, winds,
keyboards, electronics,
percussion and voices are
ordinary Python lists you can loop over - or ignore,
and compose your own by mood.
from inbflat import enter, ensemble, rest, start async def performance(): """The winds arrive as one, then a string joins them.""" await ensemble( enter("harmon trumpet"), enter("clarinet"), enter("plastic toy sax"), ) await rest(10) await enter("acoustic violin") start(performance())
Hear the winds arrive as one - it opens in the rehearsal room with this score in the editor, ready for Play.
Dynamics
A loud voice is not always angry; a soft voice not always to be dismissed; and a well-placed silence can be the indisputable last word.
Dynamics are music's louds and softs. A
crescendo grows; a diminuendo quietens;
and al niente - "to nothing" - is the musical term
for a fade that ends in silence. When a musician's part
says tacet, they simply stop playing and sit
quietly; on this stage tacet() fades them
al niente (sound and video together), and frees their
voice for whoever waits in the wings. They can always
enter() again later.
The crescendo() and diminuendo() functions work in the same
way: they take a musician's name, a target volume (between
0.0 [silence] and 1.0 [loudest]), and a duration in seconds
over which the change occurs.
The tacet() function takes a musician's name and an optional
duration (niente) in seconds, over which to fade to silence.
If the niente argument is omitted, the fade occurs over 4 seconds,
if it is set to 0, the musician suddenly stops.
It's also possible to set the musician's initial dynamic level when they enter the stage,
by passing a dynamic argument to enter(). The value should be
between 0.0 (silence) and 1.0 (loudest).
from inbflat import (crescendo, diminuendo, enter, rest, start, tacet) async def performance(): """Louder, softer, and away to nothing.""" await enter("mallets", dynamic=0.3) await rest(10) await crescendo("mallets", to=1.0, over=4) await rest(6) await diminuendo("mallets", to=0.3, over=4) await rest(6) await tacet("mallets", niente=8) start(performance())
Hear the mallets rise, sink and vanish - it opens in the rehearsal room with this score in the editor, ready for Play.
The lighting desk
I felt extremely uncomfortable as the focal point, in the spotlight. I really like the behind the scenes role, because all my freedom is there.
A followspot is the moving beam that picks a
soloist out of the dark; a wash is even light
across the whole stage, where nobody is special.
followspot("electric guitar") brightens one
musician and dims the rest; calling it on someone else
moves the light; wash() restores equality.
Purely visual - pair it with a crescendo when you want
ears and eyes agreeing about who matters.
from inbflat import (enter, ensemble, followspot, rest, start, wash) async def performance(): """Three arrive; the light finds one; then an even wash.""" await ensemble( enter("electric guitar"), enter("electric bass"), enter("kaoss pad"), ) await rest(8) followspot("electric guitar") await rest(8) wash() start(performance())
See the followspot move - it opens in the rehearsal room with this score in the editor, ready for Play.
Words on the stage
Although they are
Only breath, words
which I command
are immortal.
One of the twenty performers speaks rather than plays: "Information", a is beautifully thoughtful poem written and read by Daniel Donahoo, whose lines rise across the stage as the poem is recited.
You can also make use of this capability for your own words.
caption() floats one line of your own;
proclaim() gives words the big, centred,
still treatment of the poem's close; both take an
optional hold argument in seconds (with defaults of
6 and 12 respectively). The
recite() function takes a whole timed sequence of
(seconds, text) pairs - your poem, should
you have one.
from inbflat import caption, enter, proclaim, rest, start async def performance(): """A musician, a caption, and a proclamation.""" await enter("acoustic piano") await rest(6) await caption("words can join the music") await rest(8) await proclaim("and some arrive with weight") start(performance())
See words join the music - it opens in the rehearsal room with this score in the editor, ready for Play.
The quickest way to make this piece entirely your own
is to write some words. recite() takes a
whole poem as (seconds, text) pairs -
the seconds counted from the moment of the call - and
sets its lines rising across the stage while the
music plays on. Here is a small orchestra beneath a
poem about why any of us write code at all, its final
two lines given proclaim()'s big still
treatment:
import random from inbflat import arrange, enter, proclaim, recite, rest, start random.seed(8080) orchestra = arrange(musicians=3) async def performance(): """A small texture beneath your own words.""" for musician in orchestra: await enter(musician) await rest(3) recite([ (0, "Code runs our world."), (5, "Yet code isn't written for computers."), (11, "We write code because we are human beings"), (14, "imbued with ideas, imagination and feelings."), (19, "Code expresses our thoughts made digital:"), (22, "a medium for our values, cultures and ways of life."), (28, "If you knew code, what would you create?"), (35, "For those who venture into the world of code"), (38, "will be the ones creating our futures."), ]) await rest(44) await proclaim("What will your contribution be..?") await proclaim("The only way to find out, is to try.") start(performance())
Recite a poem of your own - it opens in the rehearsal room with this score in the editor, ready for Play.
The audience's instrument
I know that the most joy in my life has come to me from my violin.
"In C" and "In Bb" are both examples of community music-making.
This site encourages community music-making by sharing code.
But the keys() function enlarges that community
to the audience: a row of soft round keys along the
foot of the stage, each sounding a note of the B♭
major pentatonic scale - notes chosen so that
nothing you press can be wrong. A timbre is a
sound's character - the reason a trumpet and a violin
playing the same note sound different. The keys come with four
pre-defined timbres: "bell",
"pluck", "breath" and
"drone".
from inbflat import caption, enter, keys, rest, start async def performance(): """A drone settles, then the keys are yours.""" await enter("ebow") await rest(6) keys("pluck") # Or "bell", "breath" and "drone" await caption("press a key - whatever you play belongs") start(performance())
Settle a drone and play along - it opens in the rehearsal room with this score in the editor, ready for Play.
The keys respond to the computer keyboard too: the digits
1 to 9 strike the first nine keys, 0 the tenth, and
each key lights up as it sounds - the log says so
when the row appears: keys ready, click to play
or type 0-9. And where a mouse click can only
press one key at a time, a keyboard can press several
digits at once: the audience can play chords or strum!
The selected notes are a musical decision too, and flats and sharps are both valid. Here the audience gets the B♭ blues scale - the pentatonic's streetwise cousin, whose flattened fifth makes everything sound knowing - so whatever they press sounds funky:
from inbflat import caption, enter, keys, rest, start async def performance(): """A blues palette for the audience, spelled in sharps.""" await enter("electric bass") await rest(6) keys("pluck", notes=["Bb3", "C#4", "D#4", "E4", "F4", "G#4"]) await caption("a B flat blues - press something bent") start(performance())
Play along with a B♭ blues - it opens in the rehearsal room with this score in the editor, ready for Play.
The key's timbre is nothing more than a function that makes a sound.
Here is a bell built from scratch -
an oscillator and an envelope - with a button to ring
it. Play with the frequency, the wave and the ramps: this is
how new instruments and effects are born, and a function like
ring, taking the audio context and a
frequency, is exactly what
keys(timbre=...) accepts. MDN explains
the raw materials beautifully: the
oscillator
that makes the tone, and a full
tutorial
on shaping sounds with envelopes.
First of all, we need a nicely styled button as HTML, so we can ring the bell:
<style> body { background: #faf7f1; display: grid; place-items: center; min-height: 5rem; } button { font: 1.05rem Georgia, serif; padding: 0.5rem 1.8rem; color: #faf7f1; background: #23201b; border: none; border-radius: 0.2em; cursor: pointer; } button:hover { background: #3d382f; } </style> <button id="ring">Ring the bell</button>
And here's the Python code to make the bell ring:
from bfp import web, window audio = None def ring(event): """One soft bell, built from first principles.""" global audio if audio is None: audio = window.AudioContext.new() now = audio.currentTime tone = audio.createOscillator() tone.type = "sine" tone.frequency.value = 233.08 # B flat. level = audio.createGain() level.gain.setValueAtTime(0, now) level.gain.linearRampToValueAtTime(0.2, now + 0.02) level.gain.exponentialRampToValueAtTime(0.001, now + 2) tone.connect(level) level.connect(audio.destination) tone.start(now) tone.stop(now + 2.1) web.page["ring"].on_click = ring
Seats and props
I call architecture frozen music.
The stage containing the band has twenty seats in a grid, five across and
four deep, numbered 0 to 19 reading left to right, top
to bottom. (Musicians count from one and programmers
from zero; the management is aware of the dispute.) The
default random dice normally choose seating, but enter(name,
seat=...) places a musician exactly where you want them, and
prop() seats any HTML element you can build
with bfp.web - a sign, a picture, a button
of your own devising. Props take seats but not voices:
and the polyphony ignores such furniture.
from bfp import web from inbflat import enter, prop, rest, start async def performance(): """A front row arranged by hand, and one prop.""" await enter("mallets", seat=0) await rest(3) await enter("acoustic guitar", seat=2) await rest(3) await enter("acoustic violin", seat=4) await rest(3) prop(web.p("(reserved)"), seat=3) start(performance())
Arrange a front row by hand - it opens in the rehearsal room with this score in the editor, ready for Play.
Make your music
Music was my refuge. I could crawl into the space between the notes and curl my back to loneliness.
Every link above works because of a small honest trick: the Share button in the rehearsal room puts your whole score into a URL. Send it anywhere; whoever opens it finds your score in their editor. There is no server, no account, no saving: a performance simply is its link.
Don't just sit there, do something!
- Change a seed.
- Compose a section of your own.
- Fade the whole band al niente and proclaim something.
- Write a poem and
recite()it over the drones. - Build an instrument nobody has heard.
- Surprise and delight us by sharing your creativity.
The twenty musicians of "In Bb 2.0" have been waiting many years in the wings for a conductor exactly like you ~ and whatever you do, it will be in B♭, so it cannot be wrong. :-)
Peace,