Cook Book

This page contains concise code snippets for common tasks using Symusic. If you're looking for more detailed explanations, please refer to the Tutorials and API Reference sections.

File Operations

Loading a MIDI file

from symusic import Score

# Load with default tick time unit
score = Score("path/to/file.mid")

# Load with quarter note time unit
score = Score("path/to/file.mid", ttype="quarter")

# Load with second time unit
score = Score("path/to/file.mid", ttype="second")

Saving a MIDI file

# Save to MIDI format
score.dump_midi("output.mid")

# Get MIDI bytes without saving to file
midi_bytes = score.dumps_midi()

Loading and saving ABC notation

# Load ABC notation
score = Score("path/to/file.abc", fmt="abc")

# Save to ABC notation
score.dump_abc("output.abc")

Time Unit Conversion

# Convert from ticks to quarter notes
score_quarter = score.to("quarter")

# Convert from any unit to seconds
score_second = score.to("second")

# Convert back to ticks with minimum duration
score_tick = score_second.to("tick", min_dur=1)

Basic Manipulations

Transposition

# Transpose up by 2 semitones (non-inplace)
transposed_score = score.shift_pitch(2)

# Transpose down by 3 semitones (inplace)
score.shift_pitch_inplace(-3)

Time Shifting

# Shift all events forward by 480 ticks (non-inplace)
shifted_score = score.shift_time(480)

# Shift all events backward by 1 quarter note (inplace)
score_quarter.shift_time_inplace(-1.0)

Velocity Adjustment

# Increase velocity by 10 (non-inplace)
louder_score = score.shift_velocity(10)

# Decrease velocity by 20 (inplace)
score.shift_velocity_inplace(-20)

Clipping

# Extract a section from the score (non-inplace)
excerpt = score.clip(start=960, end=1920)

# Extract a section and also clip notes that extend beyond the end time
excerpt = score.clip(start=960, end=1920, clip_end=True)

Track Operations

Creating a new track

# Create an empty score
score = Score(tpq=480)

# Add a piano track
piano_track = score.tracks.append_track(name="Piano", program=0, is_drum=False)

# Add a drum track
drum_track = score.tracks.append_track(name="Drums", program=0, is_drum=True)

Adding notes to a track

from symusic import Note

# Add individual notes
piano_track.notes.append(Note(time=0, duration=480, pitch=60, velocity=64))
piano_track.notes.append(Note(time=480, duration=480, pitch=64, velocity=64))
piano_track.notes.append(Note(time=960, duration=480, pitch=67, velocity=64))

# Add multiple notes using NumPy arrays
import numpy as np
times = np.array([0, 480, 960])
durations = np.array([480, 480, 480])
pitches = np.array([48, 55, 52])
velocities = np.array([64, 64, 64])
bass_notes = Note.from_numpy(times, durations, pitches, velocities, "tick")
for note in bass_notes:
    piano_track.notes.append(note)

Working with specific tracks

# Get the first track
first_track = score.tracks[0]

# Iterate through all tracks
for track in score.tracks:
    print(f"Track: {track.name}, Notes: {track.note_num()}")

# Extract a specific instrument
for track in score.tracks:
    if track.program == 0 and not track.is_drum:  # Piano
        piano_track = track
    elif track.is_drum:  # Drums
        drum_track = track

Global Events

Adding tempo changes

from symusic import Tempo

# Set initial tempo (120 BPM)
score.tempos.append(Tempo(time=0, qpm=120.0))

# Add a tempo change
score.tempos.append(Tempo(time=1920, qpm=100.0))

Adding time signatures

from symusic import TimeSignature

# Set 4/4 time signature
score.time_signatures.append(TimeSignature(time=0, numerator=4, denominator=4))

# Change to 3/4 at measure 5 (assuming 4/4 with 1920 ticks per measure)
score.time_signatures.append(TimeSignature(time=1920*4, numerator=3, denominator=4))

Adding key signatures

from symusic import KeySignature

# Set C major key signature (no sharps/flats)
score.key_signatures.append(KeySignature(time=0, key=0, tonality=0))

# Change to G major (1 sharp) at measure 9
score.key_signatures.append(KeySignature(time=1920*8, key=1, tonality=0))

Piano Roll Conversion

# Resample to reduce size before creating piano roll
score_resampled = score.resample(tpq=6, min_dur=1)

# Create piano roll for the entire score
pianoroll = score_resampled.pianoroll(
    modes=["onset", "frame"],  # Only include onset and frame information
    pitch_range=[21, 109],     # Piano range
    encode_velocity=True       # Use velocity values instead of binary
)

# Create piano roll for a single track
track_pianoroll = score_resampled.tracks[0].pianoroll(
    modes=["frame"],           # Only include frame information
    pitch_range=[0, 128],      # Full MIDI range
    encode_velocity=False      # Binary encoding (0 or 1)
)

Audio Synthesis

from symusic import Score, Synthesizer, BuiltInSF3, dump_wav

# Load a score
score = Score("input.mid")

# Create a synthesizer using a built-in SoundFont
synthesizer = Synthesizer(
    sf_path=BuiltInSF3.MuseScoreGeneral().path(download=True),
    sample_rate=44100,
    quality=4
)

# Render the score to audio
audio_data = synthesizer.render(score, stereo=True)

# Save the audio to a WAV file
dump_wav("output.wav", audio_data)

Multiprocessing

import multiprocessing as mp
import pickle
from symusic import Score

def process_file(file_path):
    # Load MIDI file
    score = Score(file_path)

    # Process the score (example: transpose up by 2 semitones)
    processed_score = score.shift_pitch(2)

    # Return or save the result
    return processed_score

if __name__ == "__main__":
    # List of MIDI files to process
    file_paths = ["file1.mid", "file2.mid", "file3.mid"]

    # Process files in parallel
    with mp.Pool(processes=4) as pool:
        results = pool.map(process_file, file_paths)

    # Save the results
    for i, processed_score in enumerate(results):
        processed_score.dump_midi(f"processed_{i}.mid")

Analysis

# Get basic score information
print(f"Tracks: {score.track_num()}")
print(f"Notes: {score.note_num()}")
print(f"Duration: {score.end() - score.start()}")

# Analyze pitch distribution
pitches = []
for track in score.tracks:
    for note in track.notes:
        pitches.append(note.pitch)

import numpy as np
pitch_histogram = np.histogram(pitches, bins=128, range=(0, 127))

# Find average velocity
velocities = []
for track in score.tracks:
    for note in track.notes:
        velocities.append(note.velocity)

avg_velocity = np.mean(velocities)
print(f"Average velocity: {avg_velocity:.1f}")

Working with Specific Events

Control Changes

from symusic import ControlChange

# Add volume control (controller 7)
track.controls.append(ControlChange(time=0, number=7, value=100))

# Add expression control (controller 11)
track.controls.append(ControlChange(time=0, number=11, value=127))

# Add sustain pedal events (controller 64)
track.controls.append(ControlChange(time=0, number=64, value=127))
track.controls.append(ControlChange(time=960, number=64, value=0))

Pitch Bends

from symusic import PitchBend

# Add pitch bend events
track.pitch_bends.append(PitchBend(time=0, value=0))       # Center
track.pitch_bends.append(PitchBend(time=240, value=4096))  # Bend up
track.pitch_bends.append(PitchBend(time=480, value=0))     # Center again

Pedal Events

from symusic import Pedal

# Add sustain pedal with duration
track.pedals.append(Pedal(time=0, duration=960))

Lyrics

from symusic import TextMeta

# Add lyrics to a vocal track
track.lyrics.append(TextMeta(time=0, text="Hel-"))
track.lyrics.append(TextMeta(time=240, text="lo"))
track.lyrics.append(TextMeta(time=480, text="World!"))