Command-line tools for converting polyphonic MIDI files to TidalCycles code expressions.
(Extreme) example of a >4 minute piano improvisation converted to TidalCycles code with pitch, note velocity, and legato data preserved. Here is the TidalCycles code generated from the recorded MIDI:
And using this code, TidalCycles can generate MIDI. If you don't add any transforming functions, the original MIDI data should be reconstructed. Here is the reconstructed MIDI data produced by the above autogenerated TidalCycles code (zoomed in to show velocities and note length):
- Numpy (Tested on numpy version 1.12.1 and 1.20.3)
- For Python 3, install python-midi (newer fork for Python 3) (Tested with python 3.7.10)
- For Python 2, install the original python-midi (Tested on Python 2.7.13.)
Use this tool to take a MIDI file generated by a DAW or instrument and convert it to a TidalCycles expression.
python midi_to_tidalcycles.py [OPTIONS] [MIDIFILE...]
Polyphonic MIDI phrases will be converted to a TidalCycles stack. Optional arguments allow for adding # amp
patterns (similar to midi velocity which is between 1 and 127 but ranging between 0.0 and 1.0 in TidalCycles syntax), adding # legato
patterns, simplifying the generated expression, and controlling rhythmic quantization. Multiple separate MIDI files can be translated to TidalCycles in one command.
For an intro to sending MIDI signals from TidalCycles to your synthesizers and drum machines, check out Kindohm's youtube tutorial.
python midi_to_tidalcycles.py -al ../test_examples/insen_quarter-eighth-notes_duophonic_125bpm.mid
prints
../test_examples/insen_quarter-eighth-notes_duophonic_125bpm.mid
inferred polyphony is 2
slow (8.0/4) $ stack [
n "f5 ~ ~ ~ cs5 ~ ~ ~ c5 ~ ~ ~ gs4 ~ c5 ~ cs5 ~ c5 ~ gs4 ~ g4 ~ ~ ~ ~ ~ ~ ~ ~ ~"
# amp "0.79 0.0 0.0 0.0 0.79 0.0 0.0 0.0 0.79 0.0 0.0 0.0 0.79 0.0 0.79 0.0 0.79 0.0 0.79 0.0 0.79 0.0 0.79 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0"
# legato "4.0 0.0 0.0 0.0 4.0 0.0 0.0 0.0 4.0 0.0 0.0 0.0 2.0 0.0 2.0 0.0 2.0 0.0 2.0 0.0 2.0 0.0 2.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0",
n "c6 ~ ~ ~ gs5 ~ ~ ~ g5 ~ ~ ~ f5 ~ g5 ~ gs5 ~ g5 ~ f5 ~ ds5 ~ ~ ~ ~ ~ ~ ~ ~ ~"
# amp "0.79 0.0 0.0 0.0 0.79 0.0 0.0 0.0 0.79 0.0 0.0 0.0 0.79 0.0 0.79 0.0 0.79 0.0 0.79 0.0 0.79 0.0 0.79 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0"
# legato "4.0 0.0 0.0 0.0 4.0 0.0 0.0 0.0 4.0 0.0 0.0 0.0 2.0 0.0 2.0 0.0 2.0 0.0 2.0 0.0 2.0 0.0 2.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0"
]
to standard out. This "stack" expression is intended to be copied and pasted into TidalCycles. Prepend something like d1 $ slow 8 $
and append # s "midi" # midichan 0
to this stack to start sending your MIDI signal from TidalCycles.
-q, --resolution specify number of quanta per quarter note
-l, --legato print legato pattern
-a, --amp print amplitude pattern
-c, --consolidate simplify repeating values using mini-notation
-e, --events print MIDI event information
-d, --debug print MIDI event information, voice numbers, and quanta numbers for debugging
-s, --shape print MIDI shape (number of quanta and polyphonic voices)
-H, --hide hide inferred polyphony and midi file info (useful for automatic copying of tidalcycles code)
python midi_to_tidalcycles.py ../test_examples/simple_legato_monophonic.mid
inferred polyphony is 1
slow (1.5/4) $ n "c5 d5 ds5 ~ f5 d5"
python midi_to_tidalcycles.py -a ../test_examples/simple_legato_duophonic.mid
../test_examples/simple_legato_duophonic.mid
inferred polyphony is 2
slow (1.5/4) $ stack [
n "c5 d5 ds6 ds5 f5 d5"
# amp "0.79 0.79 0.39 0.79 0.79 0.79",
n "c6 d6 ~ ~ f6 d6"
# amp "0.39 0.39 0.0 0.0 0.39 0.39"
]
Adding amplitude, legato, and changing the resolution (notes per quarternote, or degree of quantization)
python midi_to_tidalcycles.py -al -q 16 ../test_examples/simple_legato_duophonic.mid
../test_examples/simple_legato_duophonic.mid
inferred polyphony is 2
slow (1.5/4) $ stack [
n "c5 ~ ~ ~ d5 ~ ~ ~ ds6 ~ ~ ~ ds5 ~ ~ ~ f5 ~ ~ ~ d5 ~ ~ ~"
# amp "0.79 0.0 0.0 0.0 0.79 0.0 0.0 0.0 0.39 0.0 0.0 0.0 0.79 0.0 0.0 0.0 0.79 0.0 0.0 0.0 0.79 0.0 0.0 0.0"
# legato "4.0 0.0 0.0 0.0 8.0 0.0 0.0 0.0 8.0 0.0 0.0 0.0 4.0 0.0 0.0 0.0 4.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0",
n "c6 ~ ~ ~ d6 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ f6 ~ ~ ~ d6 ~ ~ ~"
# amp "0.39 0.0 0.0 0.0 0.39 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.39 0.0 0.0 0.0 0.39 0.0 0.0 0.0"
# legato "4.0 4.0 0.0 0.0 4.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 4.0 0.0 0.0 0.0 4.0 0.0 0.0 0.0"
]
Simplifying the generated TidalCycles code by consolidating repeating neighboring values (recommmended).
The '-c' or '--consolidate' flag simplifies the resulting TidalCycles expression by combining adjacent repeating values using the '!' mini-notation. The following shows how the above example is simplified with consolidation.
python midi_to_tidalcycles.py -alc -q 16 ../test_examples/simple_legato_duophonic.mid
../test_examples/simple_legato_duophonic.mid
inferred polyphony is 2
slow (1.5/4) $ stack [
n "c5 ~!3 d5 ~!3 ds6 ~!3 ds5 ~!3 f5 ~!3 d5 ~!3"
# amp "0.79 0.0!3 0.79 0.0!3 0.39 0.0!3 0.79 0.0!3 0.79 0.0!3 0.79 0.0!3"
# legato "4.0 0.0!3 8.0 0.0!3 8.0 0.0!3 4.0 0.0!3 4.0 0.0!7",
n "c6 ~!3 d6 ~!11 f6 ~!3 d6 ~!3"
# amp "0.39 0.0!3 0.39 0.0!11 0.39 0.0!3 0.39 0.0!3"
# legato "4.0!2 0.0!2 4.0 0.0!11 4.0 0.0!3 4.0 0.0!3"
]
You can see how the neighboring rests are grouped together now. Consolidation really shines with more complex examples!
python midi_to_tidalcycles.py -al ../test_examples/jazz-chords_played-live_quadraphonic_125bpm.mid ../test_examples/simple_legato_monophonic.mid
inferred polyphony is 4
slow (8.0/4) $ stack [
n "e4 ~ ~ ~ gs4 ~ ~ ~ a4 ~ ~ ~ gs4 ~ ~ ~ g4 ~ ~ ~ gs4 ~ ~ ~ a4 ~ ~ ~ b4 ~ ~ ~"
# amp "0.47 0.0 0.0 0.0 0.43 0.0 0.0 0.0 0.47 0.0 0.0 0.0 0.42 0.0 0.0 0.0 0.12 0.0 0.0 0.0 0.5 0.0 0.0 0.0 0.2 0.0 0.0 0.0 0.51 0.0 0.0 0.0"
# legato "2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 3.0 0.0 0.0 0.0",
n "c4 ~ ~ ~ f4 ~ ~ ~ e4 ~ ~ ~ b4 ~ ~ ~ a4 ~ ~ ~ d5 ~ ~ ~ g5 ~ ~ ~ c5 ~ ~ ~"
# amp "0.43 0.0 0.0 0.0 0.39 0.0 0.0 0.0 0.49 0.0 0.0 0.0 0.46 0.0 0.0 0.0 0.46 0.0 0.0 0.0 0.54 0.0 0.0 0.0 0.46 0.0 0.0 0.0 0.57 0.0 0.0 0.0"
# legato "2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 3.0 0.0 0.0 0.0",
n "g4 ~ ~ ~ d4 ~ ~ ~ g4 ~ ~ ~ d5 ~ ~ ~ e5 ~ ~ ~ f5 ~ ~ ~ c5 ~ ~ ~ e5 ~ ~ ~"
# amp "0.5 0.0 0.0 0.0 0.46 0.0 0.0 0.0 0.48 0.0 0.0 0.0 0.48 0.0 0.0 0.0 0.31 0.0 0.0 0.0 0.55 0.0 0.0 0.0 0.37 0.0 0.0 0.0 0.61 0.0 0.0 0.0"
# legato "2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 3.0 0.0 0.0 0.0",
n "a4 ~ ~ ~ b4 ~ ~ ~ c5 ~ ~ ~ f4 ~ ~ ~ c5 ~ ~ ~ b4 ~ ~ ~ e5 ~ ~ ~ ~ g5 ~ ~"
# amp "0.47 0.0 0.0 0.0 0.39 0.0 0.0 0.0 0.43 0.0 0.0 0.0 0.48 0.0 0.0 0.0 0.47 0.0 0.0 0.0 0.52 0.0 0.0 0.0 0.49 0.0 0.0 0.0 0.0 0.42 0.0 0.0"
# legato "2.0 0.0 0.0 0.0 3.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 0.0 2.0 0.0 0.0"
]
../test_examples/simple_legato_monophonic.mid
inferred polyphony is 1
slow (1.5/4) $ stack [
n "c5 d5 ds5 ~ f5 d5"
# amp "0.79 0.79 0.79 0.0 0.79 0.79"
# legato "1.0 1.0 2.0 0.0 1.0 1.0"
]
This functionality allows you to extract a 'library' of chords/voicings from a MIDI passage. The output format is tailored for use in the select
TidalCycles function (http://tidalcycles.org/docs/reference/conditions/#select).
This command extracts only the chords and ignores the rhythm, sustain, and velocity data of the MIDI file.
Duplicate chords found in the MIDI file are discarded if the -u
flag is given.
The optional command-line string variable provided after the MIDI file specifies the name of the produced library.
This example:
python src/extract_chords.py test_examples/jazz-chords_played-live_quadraphonic_125bpm.mid my_jazz_chord_library
produces the following copyable TidalCycles code:
-- 8 chords
let my_jazz_chord_library p = select p [n "[-8, -12, -5, -3]", n "[-4, -7, -10, -1]", n "[-3, -8, -5, 0]", n "[-4, -1, 2, -7]", n "[-5, -3, 4, 0]", n "[-4, 2, 5, -1]", n "[-3, 7, 0, 4]", n "[-1, 0, 4, 7]"]
where p
, the input to select
, is a float between 0 and 1 (or a pattern of floats!).
This functionality extracts only the notes (and their velocities) in the order in which they are played in the MIDI file. The output format is two monophonic patterns: one for notes and one for amps (MIDI velocity between 0 and 1).
Why output a pattern of notes and a pattern of amplitudes? Patterns are a flexible starting point for composition.
They are a convenient input to the
variants of the nTake
and ampTake
functions I wrote (below). In addition to accepting patterns instead of a list, these variant functions also allow you to control the total number of notes or amplitudes to take. This can be used to limit the amount of "cross-rhythm chaos" that can happen when using a struct
function with N notes and nTake
with M != N notes, for example.
let patternToList pat = map value $ sortOn whole $ queryArc pat (Arc 0 1)
nT name amt p = nTake name (take amt (cycle (patternToList p)))
aT name amt p = ampTake name (take amt (cycle (patternToList p)))
This example command
python src/extract_melody.py test_examples/simple_legato_monophonic.mid
autogenerates the following copyable TidalCycles code:
nT "notez" 5 "0 2 3 5 2"
# aT "ampz" 5 "0.79 0.79 0.79 0.79 0.79"