| name | midi-sequencing |
| description | MIDI — protocole, fichiers Standard MIDI (.mid), séquenceurs logiciels, piano roll, génération algorithmique, mapping contrôleurs, MIDI 2.0, integration hardware/software. |
| tags | ["audio","midi","sequencing","music","synthesis","daw","standards","controllers"] |
| platforms | ["linux","macos","windows"] |
| related_skills | ["music-generation","webaudio-api","audio-processing"] |
MIDI & Sequencing — Protocole, Fichiers et Production Musicale
Guide complet du MIDI (Musical Instrument Digital Interface) : protocole, fichiers SMF, séquenceurs, génération algorithmique, MIDI 2.0, intégration matérielle/logicielle.
1. Protocole MIDI 1.0
1.1 Messages MIDI
Message MIDI = Status Byte + Data Bytes (1-2)
Status byte : 0x80-0xFF
- High nibble = type de message
- Low nibble = canal MIDI (0-15)
Data bytes : 0x00-0x7F
| Message | Status (hex) | Data 1 | Data 2 | Description |
|---|
| Note Off | 0x80+canal | Note (0-127) | Velocity (0-127) | Relâchement de note |
| Note On | 0x90+canal | Note (0-127) | Velocity (0-127) | Début de note |
| Poly Key Pressure | 0xA0+canal | Note | Pressure | Aftertouch polyphonique |
| Control Change | 0xB0+canal | CC# (0-119) | Value (0-127) | Changement de contrôle |
| Program Change | 0xC0+canal | Program# (0-127) | - | Changement d'instrument |
| Channel Pressure | 0xD0+canal | Pressure | - | Aftertouch monophonique |
| Pitch Bend | 0xE0+canal | LSByte (0-127) | MSByte (0-127) | Pitch bend (14-bit) |
1.2 Contrôleurs MIDI (CC) — Les plus importants
CC 0 = Bank Select (MSB)
CC 1 = Modulation Wheel
CC 2 = Breath Controller
CC 4 = Foot Controller
CC 5 = Portamento Time
CC 7 = Volume (Main)
CC 8 = Balance
CC 10 = Pan (stéréo)
CC 11 = Expression
CC 12 = Effect Control 1
CC 13 = Effect Control 2
CC 64 = Sustain Pedal (Damper)
CC 65 = Portamento On/Off
CC 66 = Sostenuto
CC 67 = Soft Pedal
CC 71 = Resonance (Filter Q)
CC 72 = Release Time
CC 73 = Attack Time
CC 74 = Brightness (Filter Cutoff)
CC 75 = Decay Time
CC 91 = Reverb Send Level
CC 92 = Tremolo Level
CC 93 = Chorus Send Level
CC 94 = Celeste/Detune
CC 95 = Phaser Level
CC 120 = All Sound Off
CC 121 = Reset All Controllers
CC 123 = All Notes Off
CC 99 = NRPN MSB
CC 98 = NRPN LSB
CC 6 = Data Entry MSB
CC 38 = Data Entry LSB
CC 101 = RPN MSB
CC 100 = RPN LSB
1.3 MIDI Notes — Mapping
def midi_to_freq(note):
"""Convertit un numéro MIDI (0-127) en fréquence (Hz)."""
return 440.0 * (2.0 ** ((note - 69) / 12.0))
def freq_to_midi(freq):
"""Convertit une fréquence (Hz) en numéro MIDI."""
return 69 + 12 * np.log2(freq / 440.0)
NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
def midi_to_name(note):
"""Convertit MIDI note (0-127) en nom (ex: C4, F#5)."""
octave = (note // 12) - 1
name = NOTE_NAMES[note % 12]
return f"{name}{octave}"
2. Standard MIDI File (SMF) Format
2.1 Structure du fichier
Fichier .mid = Header Chunk + Track Chunks
Header Chunk:
"MThd" (4 bytes)
length = 6 (4 bytes, big-endian)
format (2 bytes) : 0=single track, 1=multi-track synchro, 2=multi-track asynchrone
n_tracks (2 bytes)
division (2 bytes) : ticks per quarter note (tick/pulse)
Track Chunk:
"MTrk" (4 bytes)
length (4 bytes)
events (variable length) :
- Delta-time (Variable-Length Quantity)
- Event (MIDI event, Sysex, Meta)
2.2 Meta Events
META_EVENTS = {
0x00: "Sequence Number",
0x01: "Text",
0x02: "Copyright",
0x03: "Track Name",
0x04: "Instrument Name",
0x05: "Lyric",
0x06: "Marker",
0x07: "Cue Point",
0x20: "MIDI Channel Prefix",
0x2F: "End of Track",
0x51: "Set Tempo (microsecondes/crotchet)",
0x54: "SMPTE Offset",
0x58: "Time Signature (num, denom_pow2, clocks_per_tick, 32nd_per_quarter)",
0x59: "Key Signature (sharps/flats, minor/major)",
0x7F: "Sequencer Specific"
}
def decode_vlq(data, offset):
"""Décode un nombre en VLQ MIDI."""
value = 0
for i in range(4):
byte = data[offset + i]
value = (value << 7) | (byte & 0x7F)
if not (byte & 0x80):
return value, offset + i + 1
return , offset +
():
bytes_list = []
:
bytes_list.insert(, value & )
value >>=
value == :
i ((bytes_list) - ):
bytes_list[i] |=
(bytes_list)
2.3 Lecture et écriture avec Python
import mido
from mido import MidiFile, MidiTrack, Message, MetaMessage
mid = MidiFile('song.mid')
print(f"Format: {mid.type}, Tracks: {len(mid.tracks)}, Ticks/beat: {mid.ticks_per_beat}")
for i, track in enumerate(mid.tracks):
print(f"\nTrack {i}: {track.name}")
for msg in track:
print(f" {msg}")
def create_midi_file(output_path, bpm=120):
"""Crée un fichier MIDI simple."""
mid = MidiFile()
mid.ticks_per_beat = 480
track = MidiTrack()
mid.tracks.append(track)
tempo_us = mido.bpm2tempo(bpm)
track.append(MetaMessage('set_tempo', tempo=tempo_us))
track.append(MetaMessage('time_signature', numerator=4, denominator=4))
track.append(MetaMessage('track_name', name='Piano'))
notes = [60, 62, 64, 65, 67, , , ]
i, note (notes):
track.append(Message(
,
note=note,
velocity=,
time= i ==
))
track.append(Message(
,
note=note,
velocity=,
time=
))
track.append(MetaMessage())
mid.save(output_path)
mid
():
ticks * tempo_us / (ticks_per_beat * )
3. Production avec Python-MIDI
3.1 Génération algorithmique
import mido
import random
class MidiGenerator:
"""Générateur de musique MIDI algorithmique."""
SCALES = {
'major': [0, 2, 4, 5, 7, 9, 11],
'minor': [0, 2, 3, 5, 7, 8, 10],
'pentatonic_major': [0, 2, 4, 7, 9],
'pentatonic_minor': [0, 3, 5, 7, 10],
'blues': [0, 3, 5, 6, 7, 10],
'chromatic': list(range(12)),
'dorian': [0, 2, 3, 5, 7, 9, 10],
'phrygian': [0, 1, 3, 5, 7, , ],
: [, , , , , , ],
: [, , , , , , ],
: [, , , , , , ],
}
():
.bpm = bpm
.tpb = ticks_per_beat
():
scale_notes = .SCALES[scale]
melody = []
last_note = root
_ (length):
note = random.choice(scale_notes) + root
(note - last_note) > :
note = last_note + random.choice([-, ]) random.random() > note
last_note = note
duration = note_length
roll = random.random()
roll < :
duration = note_length //
roll > :
duration = note_length *
velocity = random.randint(, )
melody.append((note, duration, velocity))
melody
():
scale_notes = .SCALES[scale]
chords = [
[, , ],
[, , ],
[, , , ],
[, , , ],
[, ],
[, , , , ],
]
progression = []
_ (n_chords):
root_offset = random.choice(scale_notes)
chord_intervals = random.choice(chords)
chord = [(root + root_offset + interval) interval chord_intervals]
progression.append(chord)
progression
():
mid = MidiFile()
mid.ticks_per_beat = .tpb
track = MidiTrack()
mid.tracks.append(track)
track.append(MetaMessage(, tempo=mido.bpm2tempo(.bpm)))
track.append(MetaMessage(, numerator=, denominator=))
track.append(MetaMessage(, name=))
note, duration, velocity melody:
track.append(Message(, note=note, velocity=velocity, time=))
track.append(Message(, note=note, velocity=, time=duration))
track.append(MetaMessage())
mid.save(output_path)
mid
gen = MidiGenerator(bpm=)
melody = gen.generate_melody(root=, scale=, length=)
gen.to_midi(melody, )
3.2 Arpégiateur
class Arpeggiator:
"""Génère des arpèges à partir d'accords."""
PATTERNS = {
'up': lambda notes, octaves: [n + o * 12 for o in range(octaves) for n in notes],
'down': lambda notes, octaves: [n + o * 12 for o in reversed(range(octaves)) for n in reversed(notes)],
'updown': lambda notes, octaves: [n + o * 12 for o in range(octaves) for n in notes] +
[n + o * 12 for o in reversed(range(octaves - 1)) for n in reversed(notes)],
'random': lambda notes, octaves: [random.choice(notes) + random.choice([0, 12]) for _ in range(len(notes) * octaves)],
'chord': lambda notes, octaves: notes * octaves,
}
def __init__(self, chord, pattern=, octaves=, note_value=, velocity=):
.notes = .PATTERNS[pattern](chord, octaves)
.note_value = note_value
.velocity = velocity
():
mid = MidiFile()
mid.ticks_per_beat =
track = MidiTrack()
mid.tracks.append(track)
track.append(MetaMessage(, tempo=mido.bpm2tempo()))
track.append(MetaMessage(, name=))
total_notes = (beats * ) // .note_value
i (total_notes):
note = .notes[i % (.notes)]
track.append(Message(, note=note, velocity=.velocity, time=))
track.append(Message(, note=note, velocity=, time=.note_value))
track.append(MetaMessage())
mid
arp = Arpeggiator(chord=[, , ], pattern=, octaves=)
mid = arp.generate(beats=)
mid.save()
4. MIDI 2.0 — Principales évolutions
| Fonctionnalité | MIDI 1.0 | MIDI 2.0 |
|---|
| Résolution | 7-bit (0-127) | 32-bit (flottant ou entier) |
| Précision note | 128 vélocités | 65536 vélocités |
| Contrôle | 128 CC × 128 valeurs | Per-note control |
| Articulation | Program Change | Per-note articulation |
| Communication | Unidirectionnelle | Bidirectionnelle (UMP) |
| Découverte | Aucune | Property Exchange (profile, config) |
| Transport | DIN 5 broches | USB, Ethernet, réseau |
5. Contrôleurs MIDI et Mapping
5.1 Mapping hardware (Ableton Push, Launchpad, etc.)
import rtmidi
from rtmidi.midiutil import open_midiinput, open_midioutput
class MidiControllerMapper:
"""Mapping et filtrage de contrôleur MIDI."""
def __init__(self):
self.mappings = {
(0xB0, 1): ('modulation', lambda v: v / 127),
(0xB0, 7): ('volume', lambda v: v / 127 * 100),
(0xB0, 10): ('pan', lambda v: (v - 64) / 64),
(0xB0, 74): ('filter_cutoff', lambda v: 20 + v * 30),
(0xB0, 71): ('filter_resonance', lambda v: v / 127),
(0xB0, 64): ('sustain', lambda v: v >= 64),
(0xB0, 43): ('volume_track_1', lambda v: v / * - + ),
(, ): (, v: v >= ),
(, ): (, v: v >= ),
}
():
status = message[] (message) >
status :
key =
(message) >= :
key = (status, message[])
key .mappings:
name, transform = .mappings[key]
value = transform(message[] (message) > )
name, value
5.2 Virtual MIDI ports (OSC / DAW)
midi_out = rtmidi.MidiOut()
ports = midi_out.get_ports()
print(f"Ports MIDI: {ports}")
note_on = [0x90, 60, 100]
midi_out.send_message(note_on)
6. Intégration FluidSynth (Lecture audio)
import subprocess
import mido
def play_midi_via_fluidsynth(midi_path, soundfont_path, output_wav=None):
"""Joue ou exporte un fichier MIDI avec FluidSynth."""
if output_wav:
subprocess.run([
'fluidsynth',
'-F', output_wav,
'-g', '0.8',
'-r', '44100',
soundfont_path,
midi_path
])
else:
subprocess.Popen([
'fluidsynth',
'-a', 'alsa',
'-g', '0.8',
'-r', '44100',
soundfont_path,
midi_path
])
7. Analyse de fichiers MIDI
7.1 Statistiques et caractéristiques
def analyze_midi(midi_path):
"""Analyse un fichier MIDI pour en extraire des métriques."""
mid = mido.MidiFile(midi_path)
total_notes = 0
note_range = (127, 0)
unique_notes = set()
velocities = []
durations = []
pitch_bends = []
cc_changes = {}
for track in mid.tracks:
current_time = 0
last_notes = {}
for msg in track:
current_time += msg.time
if msg.type == 'note_on' and msg.velocity > 0:
total_notes += 1
note_range = (min(note_range[0], msg.note), max(note_range[1], msg.note))
unique_notes.add(msg.note)
velocities.append(msg.velocity)
last_notes[msg.note] = current_time
elif msg.type == 'note_off' or (msg.type == 'note_on' and msg.velocity == 0):
if msg.note in last_notes:
duration = current_time - last_notes[msg.note]
durations.append(duration)
del last_notes[msg.note]
elif msg.type == 'pitchwheel':
pitch_bends.append(msg.pitch)
elif msg.type == 'control_change':
if msg.control cc_changes:
cc_changes[msg.control] = []
cc_changes[msg.control].append(msg.value)
result = {
: mid.,
: (mid.tracks),
: mid.ticks_per_beat,
: total_notes,
: ,
: (unique_notes),
: (velocities) / (velocities) velocities ,
: (durations) / (durations) durations ,
: (pitch_bends),
: {k: (v) k, v cc_changes.items()}
}
result
7.2 Outils CLI
midicsv song.mid song.csv
csvmidi song.csv song_modified.mid
python3 -m mido song.mid --verbose
python3 -c "
import mido
m = mido.MidiFile('song.mid')
print(f'Format: {m.type}, Tracks: {len(m.tracks)}, TPB: {m.ticks_per_beat}')
for t in m.tracks:
print(f'Track: {t.name} ({len(t)} events)')
"
8. Pitfalls et solutions
| Problème | Cause | Solution |
|---|
| Notes qui restent bloquées | Note Off manquant | Envoyer All Notes Off (CC 123) |
| Timing imprécis | Mauvaise gestion delta-time | Utiliser ticks_per_beat cohérent |
| Instruments incorrects | Bank Select non défini | Utiliser CC 0 + CC 32 + Program Change |
| MIDI saturé | Trop d'événements | Filtrer CC redondants, réduire résolution |
| Latence audio | Buffer trop grand | 256 ou 128 samples |
| Notes hors gamme | Génération non contrainte | Vérifier note_range 0-127 |
9. Ressources matérielles
| Appareil | Type | Usage |
|---|
| KORG nanoKONTROL2 | Control surface | Mixage, effets |
| Novation Launchpad | Grid controller | Performance, clips |
| Akai APC40 | Ableton controller | DAW control |
| Arturia KeyLab | Keyboard controller | Notes + contrôles |
| Behringer BCF2000 | Motor faders | Mixage automatisé |
| Roland A-49 | Compact keyboard | Portable |
| Keith McMillen QuNexus | Pressure sensitive | Expression |
Références