Exercises: music theory in code
These exercises practise this part on the real code: notes and chords, numerals, the editing tools, the hearing of chords and keys, sets, rows and micromodes, voice leading and the import of recordings. Each one says what is already there, so you extend it rather than build it again. Some are things to play in the app and listen to; some are pieces of Rust with a clear end; the ones called “Think about it” are questions, with no code to write. Many are music theory first: work out the answer on paper, then let the code check it.
Try the Rust ones on a branch of your own. Run cargo test after each
change, and cargo fmt --all --check and
cargo clippy --workspace --all-targets -- -D warnings before you call
one finished: CI runs all three.
Notes, scales and chords
1. A chord that is still missing (lineup-core, easy)
ChordQuality::ALL holds thirty qualities, from the triads to
Dominant13 and Major13 (see Chords: building, naming and
inversions). Read ChordQuality::intervals,
symbol, name and numeral_suffix, then add one that is absent,
such as the minor-major ninth, MinorMajor9 (0, 3, 7, 11, 14), to
ChordQuality::ALL too, whose length, part of its type, goes up to
31. The compiler lists every match to update. SPELLINGS, in
harmony/symbol.rs, needs a line for the new quality, and
every_quality_is_written_and_read_back fails until its symbol reads
back as the new quality. no_two_qualities_have_the_same_notes tells
you if its pitch set collides with an existing one.
As pitch classes, why do big chords so often look like other chords on another root? Write out the notes of C13 and find a seventh chord hiding in them.
Practises: exhaustive match, arrays whose length is part of the
type, letting tests guard your data.
2. Cadd2 and the power chord (lineup-core, medium)
Pop charts sometimes write Cadd2: the same notes as Cadd9, with the
D played low, next to the C. Add “add2” to the spellings of Add9 in
SPELLINGS, and a test that reads it. What is lost by reading it as
Add9?
C5 is a power chord: just C and G, no third, the guitar’s
favourite. It is not a ChordQuality. Before adding it, list what else
would have to know about it: ChordQuality::family, which every chord
must answer as major, minor or diminished; is_minor; the harmony
keys; and find_chord, which would then name every bare fifth. Is a
chord without a third major or minor? Decide, and write the decision
into a test.
Practises: following a type through the program before changing it.
3. The line cliché (lineup-core, small)
The line cliché keeps one chord while a single voice moves down by
semitones: Am | Am/G# | Am/G | Am/F#. Read it with
library::chart::parse_chart_in, play it with chart::accompany and
Bass::Held, and check the bass notes in a test. Is G♯ in A minor? In
which minor? And what is Am/F♯, as a chord of its own, in the names of
Chords?
Practises: reading chart text, slash chords, a test on music you can sing.
4. A drop-2 voicing (lineup-core, medium)
tools::inversion(pitches, times) and tools::invert(notes, up) turn
chords over (see Editing tools as plain
functions). Jazz pianists and
arrangers use another voicing too, drop 2: take a chord in close
position and lower its second-highest note by an octave. C E G B, from
C4 up, becomes G3 C4 E4 B4.
Add tools::drop_two(notes) beside invert, with a test like
inverting_chords_up_and_down, and return None when a note would
fall below Note::MIN. Then put it in the piano roll’s Tools menu.
Does the chord lane still name the chord the same way, or does the new
bass change its name? Why?
Practises: Vec manipulation (remove, insert, sort), tests
that check your own output.
5. Snap to scale, with a direction (lineup-core and lineup-app, medium)
tools::nearest_in_key and tools::snap_to_scale move notes outside
the key to the nearest note of it, for the piano roll’s Snap to
scale. nearest_in_key tries one semitone below, then above, then
two, so the lower note wins a tie, and in C major C♯ goes to C.
Add nearest_in_key_towards(note, scale, up), which breaks a tie in
the direction the melody is moving: a C♯ on its way up to D should
become D. Write tests for notes exactly halfway between two notes of
the key, both ways. snapping_moves_only_notes_outside_the_key pins
the old rule; would the new function change its answers?
Practises: searching with iterators (find_map), keeping the logic
out of the window.
6. Minor numerals, two ways (the app and lineup-core, easy)
Edit > Settings… > Harmony > Roman numerals in minor keys writes the numerals of a minor key in two ways (see Minor keys: bIII or III).
- In A harmonic minor, what does each way write for C augmented (C E G♯)? And for E minor? Work it out, then check with the harmony keys.
- Dorian is a minor mode too, but
Scale::is_minor_keyis false for it, so its numerals always count from the major scale. D Dorian is D E F G A B C. Which of its numerals would change if they counted from its own scale, and why might that confuse more than help? MinorNumerals::withkeeps the old setting in a guard,let _restore = Restore(MinorNumerals::current());, whosedropputs it back. Rename_restoretorestoreand runcargo clippy. Then change it to a bare_and runcargo test -p lineup-core. What happens each time, and why does_behave so differently from_restore?
Practises: reading numerals, guards that clean up in Drop.
7. Think about it: naming chords
find_chordtries the bass note as the root first. Can you find a set of notes where this gives a surprising name? Is there a better rule?- When nothing fits,
find_chordadds the missing fifth and tries again, but only accepts chords of four notes or more. Why would accepting a triad that way be wrong? Think of C and E alone.
Numerals and what chords do
8. The Neapolitan comes next (lineup-core, medium)
After a chord, the harmony keys outline the chords that often follow
it, from the tables in harmony/progression.rs (see What comes next:
knowledge as data).
The chord sheet already names the Neapolitan, ♭II, a major chord
on the lowered second degree (D♭ in C), but next_chords has no slot
for it, so after a D♭ chord nothing lights up.
A Neapolitan usually goes to V, or to I⁶₄ and then V. Add a Slot
with root 1 and ChordFamily::Major, with a move to V and a reason, to
MAJOR and to MINOR. Write the tests first: after D♭ in C major, G;
after D♭ in C minor, G too. Is D♭ a note of C minor’s scale, or is the
Neapolitan outside that key as well? A Suggestion is only made for a
chord whose root is a note of the scale: find the field of
Suggestion that needs it, and explain why the Neapolitan itself can
never be suggested.
Practises: extending a data table, tests as a specification.
9. Functions by ear (the app, easy)
Type these on the chord sheet and listen.
C Fm C, thenC F C. The only note that changes is A to A♭. What does it do to the mood? Find a song you know that does this.- In G major, which chord is V7/V? Which of its notes is not in G major, and where does that note want to go? Type it and check the function the sheet gives it.
- Write a modulation from C major to F major through a pivot chord. Which chords do the two keys share? Which chord shows that F major has arrived? (Which note does F major have that C major does not?)
Practises: hearing borrowed chords, secondary dominants and pivots.
10. Spelling decides (lineup-core, small)
is_neapolitanchecks the chord’s spelling withkey.tonic_name().letter.steps_up_to(symbol.root.letter) == 1. Explain why C♯ major in C fails that test while D♭ major passes, though both are played on the same keys of the piano. Find the test that says so.- The chord sheet does not name the augmented sixth chords
(Italian, French and German), which lead to the dominant too. Look
up their notes in C minor. Which of them sounds exactly like a
dominant seventh on A♭, and what in its spelling tells the two
apart? What would a
Role::AugmentedSixthhave to check?
Practises: why music spells notes, letters as well as semitones.
11. A row of borrowed chords (lineup-app, medium)
Songs in major keys often borrow chords from the minor key on the same tonic: iv (F minor in C), ♭VI (A♭), ♭VII (B♭) and ♭III (E♭). This is modal interchange, behind many of pop’s bittersweet moments, and the chord sheet already names such chords as borrowed. The harmony keys, though, play only the key’s own chords.
Add a second, smaller row of keys under the harmony keys with these
four chords, built from numerals with library::numeral::parse and
RomanChord::in_key. Give them their own colour, and a tooltip that
says which minor-key chord each one is.
Practises: reusing the library’s numerals, egui layout.
12. Think about it: tables or rules
next_chords keeps its knowledge in tables of root, family and moves.
Most of the table could be computed from a few rules instead: roots
falling a fifth, steps up to the dominant. Which entries would the rules
miss? Is a table that says plainly what it means better than rules that
are shorter?
Editing tools
13. Humanize velocities (lineup-core and lineup-app, easy)
Notes all played exactly as hard sound mechanical. The demos already
vary their velocities (library/interpret.rs), but the piano roll has
no tool for your own notes. Add
tools::humanize(notes, amount, seed) -> Vec<NoteEvent>, which moves
each velocity up or down by at most amount, kept within 1 to 127, and
a “Humanize velocity” item in the piano roll’s Tools menu.
Ferrous Lineup has no random number crate, and does not need one:
ToneRow::random and the soloist use small generators of their own,
and library/interpret.rs uses the “SplitMix64” mixer, a few lines of
wrapping_mul and shifts. Why does the function take a seed instead
of making one up? How would you test it, and what should the same seed
give twice in a row? Where would the piano roll get a different seed
each time?
Practises: pure functions with an explicit seed, clamp.
14. Retrograde a phrase (lineup-core and lineup-app, medium)
Playing a phrase backwards is a classic way to make a variation. Rows
have their retrograde (see Twelve-tone rows and their
rules), but the piano roll has no tool for
a phrase. Add
tools::retrograde(notes: &[NoteEvent], span: usize) -> Vec<NoteEvent>:
within span steps, each note keeps its pitch and length, and its
place is mirrored.
First decide what “mirrored” means for a note of length 2 in a span of
16: does its start mirror, its end, or both? Which choice keeps the
phrase inside the span and sounds most like the original played
backwards? What happens to nudge and trim? Add a “Retrograde” item
to the Tools menu, with a test on a four-note phrase you can sing.
Practises: deciding a musical rule before coding it, NoteEvent
fields.
15. Note-length tools (lineup-core and lineup-app, medium)
The piano roll’s Length sets how long new notes are, but no tool changes notes already written. Add two:
tools::set_note_length(notes, steps)makes every notestepslong, keeping its start and pitch;tools::legato(notes, pattern_length)makes each note last until the next note starts, so a line joins up.
NoteEvent keeps its length in whole steps, plus trim, how many
ticks before the end of its last step it stops. A note made longer
must clear a trim that no longer means anything, and tidy_notes
keeps a pattern’s notes in order and inside it. Check what happens when
two notes of a channel would then overlap. Why is set_note_length
not simply note.length = steps?
Practises: note lengths and ticks, keeping a pattern’s rules.
16. A strum tool, or a channel strum (all three crates, harder)
A guitarist strikes a chord one string after another. The band’s
guitar does it already: strum in band/guitar.rs starts each string
a few ticks after the one before. Your own notes cannot do it yet. Work
out two designs before writing any code.
- In the notes. A Strum tool gives each note of a chord a growing
nudge, from the lowest note up, a few ticks apart. Nothing else inNoteEventchanges: the file savesnudgealready, and Quantize puts the notes back on the grid. A nudge is at most 23 ticks, one step less one. - In the instrument. Each channel gets a “strum” setting in
milliseconds. When several notes of a channel start in the same
frame, the audio thread starts them that far apart, lowest first.
Nothing in the pattern changes, but
Channel, theInsertChannelcommand, the app’sproject_commandsand the file format all need the new field.
Which one lets you strum one chord and not the next? Which one changes fewer files? Real studios have both.
Practises: designing across crates.
17. Think about it: tools
- The selection holds notes by value, which works because a channel
never holds two notes in the same place:
same_placecompares pitch, start andnudge. Imagine two notes in exactly the same place. What would go wrong with the selection and withreplace_notes? Would a permanent id for every note fix it, and what would ids cost in the file? tools::transposerefuses to move a chord if one note would leave the MIDI range. Another design would move the notes that can move and leave the rest. When would that be better? How could the piano roll tell you why nothing happened?
Hearing harmony and finding the key
18. A walking bass, by hand (pencil and paper, easy)
Hearing harmony
works out how C and C7 score over a walking bass, with the passing
B♭ weighing 0.15 a step, and says that at 0.35 C would still win, by
0.941 to 0.925. Work those two numbers out yourself: the B♭ then weighs
1.4 and the profile 29.4. Then change PASSING_BASS in
harmony/hearing.rs and write the case as a test. At what weight does
C7 win?
Practises: following a scoring rule by hand before trusting it.
19. When to cut a bar (pencil and paper, easy)
When the chord sheet is filled from the notes, a cut costs 0.08 halfway
through the bar (CUT_HALF_BAR) and 0.12 on any other beat
(CUT_BEAT) (see Cutting a bar into
chords). A bar of
4/4 plays C, then G7. Each of them, heard alone, scores 1.02 per beat;
the whole bar, heard as one stretch, 1.0 per beat.
Does the sheet cut the bar when G7 comes on beat 4, for one beat? And
when it comes on beat 3, for two? Look at the if total > here.0 in
the loop: what happens on a tie? How high must the score of each chord
alone be for the cut to happen in each case?
Practises: dynamic programming on a small case.
20. The Augurs chord (the app and pencil, easy)
Play E, G♯ and B with the left hand and E♭, G, B♭ and D♭ with the
right: the chord that opens the Augurs of Spring in The Rite of
Spring. Now play the A♭ harmonic minor scale, A♭ B♭ C♭ D♭ E♭ F♭ G,
and check that every note of the chord is in it. Why does
in_one_key, in harmony/hearing.rs, leave harmonic minor out?
Then a question about the cache of write_heard_chords: its key holds
the notes of the bars around a bar too, not only the bar’s own. Why?
Practises: enharmonic spelling, what a cache key must include.
21. Measure the library’s ear (lineup-core, medium)
The library has a simpler ear of its own, library::listen::hear_chords,
for the band’s patterns, and every_style_is_heard_as_its_chart checks
it on every style as written. Write a report of your own, an ignored
test: loop over the styles, play each chart with every Comping and a
few Bass lines through chart::accompany, hear it, and count how
often the chords come back as written. Print the combinations that
fail most. Then try to raise the number by changing one constant at the
top of listen.rs, keeping the existing test passing. Run it with
cargo test --release: the same test runs many times faster.
Practises: measuring before changing, nested loops over enums.
22. Where the Habanera changes key (the app, easy)
- Open the Habanera demo (Bizet’s, from Carmen), turn on 🔍 Find the key, and click bars 9, 10 and 11 on the ruler. Where exactly does D minor turn to D major? Is the D major refrain just “the same tune in another key”? Compare the first notes of both.
- Hold A, C and E on the typing keyboard for a few bars, then add G♯, the raised seventh. Does the listener become surer of A minor? Why does one note matter so much?
Practises: hearing a modulation, what makes a key a key.
23. The price of a key change (lineup-core, easy)
In harmony/key_finding.rs, set CHANGE_COST to 0.8 and run the
report:
cargo test --release -p lineup-core --test key_finding report -- --ignored --nocapture
How many changes are found now, and how many of them are not in the scores? Try 3.0. What does that tell you about the penalty?
Practises: tuning by measurement.
Sets, rows and micromodes
24. A tour of the Row window (the app, easy)
- Open File > Demos > Twelve-tone > Triad chorale and press Space. Watch the section names in the playlist: the same music, then backwards, upside down, and both.
- Open the piano roll (F7) and click Row…. Pick Famous and example rows > Schoenberg’s Suite. What does the window say about its groups of three, and which form is it combinatorial with?
- Click the label I11 at the top of the matrix, set Together to 6: hexachords, and click Write I11 into current pattern: two hexachords as chords. Press Ctrl+Z to take it back.
- Tick the three Serialism boxes, set Octaves to Wide, and write P4 again: Webern-like points of sound.
- Click Sets…, type
octatonic, and click Use as the key. The harmony keys now play chords from the octatonic scale.
25. Distinct forms (lineup-core, small)
A row has 48 forms, but they are not always 48 different rows. Write a
test that collects them into a HashSet<ToneRow> and counts them.
Check that Webern’s Symphony row has 24 and Schoenberg’s Suite 48.
Can any row have fewer than 24? (Could a row be one of its own I
forms? Write out what that means for each note, and you will find that
at most two notes could fit.)
Practises: HashSet, a proof by counting.
26. Derived rows (lineup-core, medium)
Webern’s Concerto, Op. 24, uses a row whose four groups of three all
belong to the same class, 3-3 [014] (see
Micromodes).
Such a row is called derived. Add ToneRow::is_derived(size), true
when every segment of that size has the same set class, and a
random_derived that builds one by backtracking, as
random_all_interval does.
Practises: backtracking, segments and set classes.
27. Forte’s way (lineup-core, small)
For six classes, Forte’s prime form differs from Rahn’s, which Ferrous
Lineup uses (see Pitch-class sets). Write
forte_prime(), which breaks ties from the start of the set instead of
from the end, and show it in the Sets window’s details when it differs.
Why can the trick of “the smallest number” not do this one directly?
(Think about which bits a comparison of numbers looks at first.)
Practises: bits as sets, reading a definition carefully.
28. Multiplying by 5 (lineup-core, medium)
Multiplying every pitch class by 5 (mod 12) turns the chromatic scale
into the circle of fourths: 0 5 10 3 8 1… This M5 operation is
another way to transform a row or a set, used by some composers after
Webern. Add PitchSet::multiply and ToneRow::multiply, and find
which set classes M5 turns into a different class. (The Z-related
pairs are worth a look.)
Practises: modular arithmetic, testing over every set class.
29. A series of its own (lineup-core and lineup-app, medium)
With Durations ticked, the row writes its own numbers as the
durations. Let the user type a second row, used only for the
durations, and show both series in the window. RowWriting derives
Copy: why does it still compile once it holds a second ToneRow, and
why would it not if the second series were a Vec<u8>?
Practises: Copy and what it asks of every field.
30. The twelve-tone rules by ear (the app, easy)
- Choose twelve-tone row as the key, pick Schoenberg’s Suite in Row… with E as the key’s note, and write P4 (E F G C♯ F♯ E♭ A♭ D B C A B♭) as a melody. Tick Voice leading. Then swap two notes, then leave one out. How many notes does the check mark each time, and where does the dashed outline go?
- Write the first three notes of P4 as a chord, then the rest as a melody. Then write notes 1, 2 and 4 as the chord. What changes?
- Write P4 in one track and P4 an octave lower in another, in the same rhythm. What does the check say, and why does it say it only once?
- Write a chord of C, E and G in a row key. Then G, B, D and F. Which rule do they break, and why did Berg break it on purpose?
31. A repeated note (lineup-core, small)
In Walk::take, in harmony/voice_leading/serial.rs, a note that only
repeats the note just played is never out of place. Find the lines that
make it so. Then write a test where a chord shares a note with the
chord before it, and check that nothing is marked.
Practises: reading bit masks, a test for an exception to a rule.
32. A tour of the micromodes (the app, easy)
- Open the piano roll (F7) on the starting song’s Chords pattern, press Ctrl+A, and click Micromodes…. Read the colours of C major: eight Mayor 2, six triads, one diminished.
- Click Menor 3, then ▶ Hear it. Try Inverted (I) and Move it up, and watch the triangle turn and flip on the clock.
- Click 3-3 in Sets →. The Sets window opens on 3-3. Pick 7-35 and click one of the micromodes under Micromodes inside to come back.
- Open File > Demos > Micromodes > Triads, two notes kept and press Space. Listen for the one note that moves in each chord.
- Click Webern’s trichords in the Library’s Twelve-tone rows group. With 3 notes chosen under the key, the harmony keys play its four groups of three, each labelled 3-3. Now switch the key menu from twelve-tone row to pitch-class set: the key becomes the row’s first group of three, and the harmony keys play its forms.
- Back on the starting song, while it plays, click Fourths groove in the Library: the song goes on in sus chords.
33. The micromodes of a melody (lineup-core, small)
Write melody_micromodes(notes: &[PitchClass]) -> Vec<Option<&'static Micromode>>, which names the micromode of every three notes in a row:
notes 1 to 3, 2 to 4, 3 to 5 and so on (windows(3) does the
walking). Why is the answer None for some windows? Test it on
Webern’s Concerto row: its four groups are Menor 3, but what are the
windows that straddle two groups?
Practises: windows, Option in a list.
34. Every colour at once (lineup-core, small)
Loop over SetClass::all() and find the smallest sets whose micromode
content has no zero: sets that hold all twelve micromodes. How many
notes do they need, and which are they? (For the size: the augmented
triad and Menor 1 must both fit.)
Practises: searching a table, micromodes_in.
35. Schat’s tonalities (lineup-core, medium)
Webern’s Concerto row splits the twelve notes into four forms of Menor 3. Write a backtracking search that finds every way to split the twelve notes into four forms of one micromode, and count them for each of the twelve. One micromode cannot do it at all: which, and why? (Think about which notes the forms of each micromode can take.)
Practises: backtracking, counting carefully.
36. A walk that remembers more (lineup-core, medium)
The walk of a micromode style that keeps two notes picks the freshest new note first, and only then the form used least recently (see The walk: choosing with a tuple). Make a walk that is sure to use every form it can reach before any comes back, and test that Menor 3 visits all 24 of its forms in 24 steps. Is that always possible? For which micromodes can a “keep two notes” walk visit every form exactly once? (This is a question about paths in a graph.)
Practises: graphs, backtracking.
37. Hear the family (lineup-app, small)
Add a ▶ next to each family’s name in the Micromodes window that
plays its micromodes one after another, from middle C.
MicroAction::Hear plays notes together; what would you add to play
them in turn? Look at how the piano roll’s preview of a chord is
stopped.
Practises: actions as data, timing in the window.
Voice leading
38. Parallel octaves (the app, easy)
Write I–IV–V–I in C major in four parts, the bass C3 F3 G3 C3 and the soprano E5 F5 D5 E5. Tick Voice leading. Then move the soprano’s D5 to G5 and read what the check says. Why are octaves between the soprano and the bass avoided, while thirds and sixths between them are welcome?
39. The leading tone and the seventh (the app, easy)
- Write G7 to C with the seventh, F, in the alto falling to E. Then write G7 to Am, the deceptive cadence. Where must the leading tone go now? And the seventh?
- The check leaves the leading tone alone in inner parts. Write V7 to I with the tenor going from B down to G. What does the C chord gain, and what would it lack if the tenor rose to C instead?
- Write C, F, G7, C in four parts with the bass C3 F2 G2 C3, every inner part moving by step or holding. Read the line of moves at each change: are the outer parts ever in parallel or similar motion?
40. A unison to an octave (lineup-core, small)
In kind_of_move, a unison going to an octave falls through to the
last two arms of the match. Work out which kind it gets when both
parts rise, then write a test in voice_leading.rs that checks it,
with the satb helper the other tests use.
Practises: reading a match arm by arm.
41. A step of three semitones (lineup-core, small)
Move::size calls one or two semitones a step. An augmented second,
A♭ to B, is three semitones, and its name says “second”. Which size
does the code give it, and which would a harmony teacher give it?
Change size to count letters, as motion does, and write a test.
Which other moves change size?
Practises: letters and semitones, again.
Importing a recording
42. A sus4 template (lineup-listen, medium)
chords.rs knows six qualities. Add ChordQuality::Sus4 to
QUALITIES and see what it does on a recording with a sus chord in it:
render a demo with one to WAV (the write_wav test in
tests/key_changes.rs does that). More candidates means more ways to
be wrong. Does SEVENTH_COST need a cousin for rare chords?
Practises: measuring a change on known data.
43. Six-eight (lineup-listen, medium)
meter::guess knows 3 and 4 beats to the bar. Add 6/8: six beats in
two groups of three, where the chords change on beats 1 and 4. How do
you tell 6/8 from 3/4 at twice the tempo, when both put changes every
three beats? (Perhaps you cannot, from chords alone: what else could
tell them apart?)
Practises: the limits of a method.
44. A recording without a copy (lineup-audio, lineup-core and lineup-listen, harder)
The import saves a copy of the recording with a lead-in of silence (see
Rough edges), only because a sampler note
plays its sound from the first sample. The sampler can already start a
voice partway (start_at, used by resume, see Starting
partway). Give
SampleSource::File a start offset in seconds, negative for silence
before the sound, and let the sampler’s note_on start there. Then
delete write_wav, and set the offset from the lead-in instead. What
must resume add to elapsed? And SampleSource is saved in project
files: which format number does the change need, and what does an older
file read as?
Practises: changing a type that is saved in files, the audio thread.
45. A clear key, or the whole recording’s (lineup-listen, easy)
keys::main_key takes the key of the longest stretch, even when that
stretch’s key is not clear (KeySpan::is_clear, see Key
changes). Try two other rules: the key of the
longest clear stretch, and bpm-key’s key of the whole recording
(whole in analyze) whenever the longest stretch is not clear. Run
the report in tests/key_changes.rs for each. Which rule gets the most
songs right, and are the songs it gets wrong the same?
Practises: letting a measurement decide.
46. Think about it: listening
- Chords are named per beat, and a beat’s chroma is the average of its frames. What happens to a chord that changes on the “and” of a beat, as in a lot of jazz and Latin music?
- Why is the meter guessed from the chords and not from the loudness of the beats, the way a listener feels the downbeat?
- The import runs on its own thread and is let go, not stopped, when another project opens. When would it be worth stopping it, and how would you tell a thread to stop? (See how an export is stopped.)