Skip to main content

Comprehensive Python package for MIDI music anomalies detection and features analysis

Project description

midiano

Comprehensive Python package for MIDI music anomalies detection and features analysis

midiano

Installation

!pip install midiano

Use as follows

Quick Start

from midiano import MIDIFeatureExtractor, MIDIProblemDetector

# Extract all features from a MIDI file
extractor = MIDIFeatureExtractor('song.mid')
features = extractor.extract_all()

# Detect concrete problems in the MIDI file
detector = MIDIProblemDetector(features)
problems = detector.detect()

# Print detected problems
for p in problems:
    print(f"[{p['severity'].upper()}] {p['category']}: {p['issue']}{p['detail']}")

Classes

MIDIFeatureExtractor

Extracts a comprehensive set of 200+ features from a MIDI file across 12 categories, designed for downstream anomaly detection and quality assessment.

from midiano import MIDIFeatureExtractor

# Basic usage
extractor = MIDIFeatureExtractor('song.mid')

# Filter to a specific MIDI channel (e.g., channel 0 only)
extractor = MIDIFeatureExtractor('song.mid', filter_channel=0)

# Extract all features as a flat dictionary
features = extractor.extract_all()

Constructor Parameters:

Parameter Type Default Description
midi_filepath str required Path to the MIDI file
filter_channel int or None None If set, only extract notes from this MIDI channel

Key Properties:

Property Type Description
filename str Basename of the MIDI file
parse_error str or None Error message if parsing failed
notes list All note events sorted by (start, duration, pitch)
tempos list Tempo change events
time_sigs list Time signature events
key_sigs list Key signature events
patches dict Patch changes per track
control_changes list Control change events
pitch_bends list Pitch wheel events
ticks_per_quarter int Ticks per quarter note from the file header

Feature Categories

extract_all() returns a flat dictionary with features prefixed by category:

1. Structural / Metadata (struct_) File size, track count, note count, duration, tempo change count, time signature changes, empty track detection, note density, file efficiency.

features = MIDIFeatureExtractor('song.mid').extract_all()

features['struct_total_notes']           # Total number of notes
features['struct_track_count']           # Number of tracks
features['struct_duration_secs']         # Duration in seconds
features['struct_notes_per_second']      # Note density
features['struct_empty_track_ratio']     # Ratio of empty tracks
features['struct_nonstandard_tpq']       # 1 if ticks/quarter is unusual
features['struct_bytes_per_note']        # File size efficiency

2. Problem Detection (prob_) Zero-duration notes, velocity-zero notes, out-of-range pitches, negative durations, overlapping same-pitch notes, stuck notes, extreme bursts, tempo anomalies, excessive time-sig changes.

features['prob_zero_duration_ratio']         # Ratio of zero-duration notes
features['prob_velocity_zero_ratio']          # Ratio of velocity-zero notes
features['prob_out_of_range_pitch_count']     # Pitches outside 0–127
features['prob_negative_duration_count']      # Notes with negative duration
features['prob_overlapping_same_pitch_ratio'] # Overlapping same-pitch/channel
features['prob_stuck_note_ratio']             # Notes held >16 beats
features['prob_extreme_burst_count']          # Onsets with >20 simultaneous notes
features['prob_single_velocity']              # 1 if all notes share one velocity
features['prob_all_max_velocity']             # 1 if all notes at velocity 127

3. Instrument (instruments_) Unique instrument count, channel count, drum vs. melodic ratio, polytimbral flag, channel entropy.

features['instruments_unique_count']      # Distinct (channel, patch) pairs
features['instruments_channel_count']     # Active MIDI channels
features['instruments_drum_note_ratio']   # Ratio of channel-10 notes
features['instruments_is_polytimbral']    # 1 if multiple instruments
features['instruments_channel_entropy']   # Shannon entropy of channel distribution

4. Tempo & Pace (tempo_, pace_) BPM statistics (mean, std, min, max, range, median, IQR, skew, kurtosis, etc.), tempo change dynamics, notes per beat/bar.

features['tempo_bpm_mean']           # Average BPM
features['tempo_bpm_range']          # BPM max - min
features['tempo_bpm_change_max']     # Largest single tempo change
features['pace_notes_per_beat']      # Note density per beat
features['pace_notes_per_bar']       # Note density per bar

5. Grid & Rhythm (grid_) Quantization offset, start/duration alignment ratios, quarter/eighth/sixteenth note alignment, swing ratio, syncopation ratio, rhythmic entropy.

features['grid_start_alignment_ratio']   # How well notes snap to grid
features['grid_on_quarter_ratio']        # Quarter-note alignment
features['grid_on_eighth_ratio']         # Eighth-note alignment
features['grid_swing_ratio']             # Swing feel detection
features['grid_syncopation_ratio']       # Off-beat note ratio
features['grid_rhythmic_entropy']        # Rhythmic complexity

6. Harmonic & Key (harmonic_) Key estimation via Krumhansl-Schmuckler, key clarity/strength, pitch-class entropy, consonance/dissonance ratios, interval classification.

features['harmonic_key_tonic']           # Estimated key (0=C, 1=C#, …)
features['harmonic_key_is_minor']        # 1 if minor, 0 if major
features['harmonic_key_clarity']         # Correlation with key profile
features['harmonic_key_strength']        # Gap between top-2 key candidates
features['harmonic_pitch_class_entropy'] # High = atonal
features['harmonic_consonance_ratio']    # Consonant interval ratio
features['harmonic_dissonance_ratio']    # Dissonant interval ratio

7. Note (delta_times_, durations_, pitches_, velocities_, pitch_intervals_) Full statistical summaries (mean, std, min, max, range, median, Q25, Q75, IQR, skew, kurtosis, MAD, CV, entropy) for inter-onset intervals, durations, pitches, velocities, and melodic pitch intervals. Also includes pitch-class distribution, direction changes, step/leap ratios, and velocity dynamics.

features['durations_mean']               # Average note duration
features['pitches_range']                # Pitch span
features['pitch_intervals_mean']         # Average melodic interval
features['pitches_step_ratio']           # Ratio of steps (≤2 semitones)
features['pitches_leap_ratio']           # Ratio of leaps (>4 semitones)
features['pitches_ascending_ratio']      # Upward melodic motion ratio
features['delta_times_zero_ratio']       # Simultaneous onset ratio
features['delta_times_nonzero_mean']     # Avg non-zero inter-onset interval
features['pitch_class_0_ratio']          # C note ratio (0–11 for C–B)

8. Chord (chords_) Chord count, chord ratio, chord size statistics, uniqueness ratio, triad quality analysis (major, minor, diminished, augmented, sus2, sus4) — with correct inversion handling.

features['chords_count']            # Number of simultaneous note groups (≥2)
features['chords_ratio']            # Chord onsets / total onsets
features['chords_size_mean']        # Average notes per chord
features['chords_major_ratio']      # Major triad ratio
features['chords_minor_ratio']      # Minor triad ratio
features['chords_uniqueness_ratio'] # Harmonic variety

9. Polyphony (polyphony_) Max/mean polyphony (time-weighted), monophonic ratio, overlap ratio, multi-channel flag.

features['polyphony_max']                # Maximum simultaneous voices
features['polyphony_mean']               # Time-weighted average polyphony
features['polyphony_monophonic_ratio']   # Ratio of time with ≤1 voice
features['polyphony_overlap_ratio']      # Notes overlapping with others
features['polyphony_multi_channel']      # 1 if multiple channels used

10. Phrase (phrases_) Phrase segmentation via gap detection, phrase count, note count statistics, phrase length statistics.

features['phrases_count']            # Number of detected phrases
features['phrases_avg_note_count']   # Average notes per phrase
features['phrases_length_mean']      # Average phrase length in ticks

11. Dynamics (dynamics_) Velocity range, unique velocity count, dynamic range in dB, velocity entropy, consecutive same-velocity ratio, short-note ratio.

features['dynamics_velocity_range']       # Max velocity - min velocity
features['dynamics_range_db']             # Dynamic range in decibels
features['dynamics_entropy']              # Velocity distribution entropy
features['dynamics_same_consecutive_ratio']  # Flat dynamics indicator

12. Quality / Completeness (quality_) Presence of tempo/time-sig/key-sig metadata, default-value detection, sparsity/density flags, pitch range flags.

features['quality_has_tempo']           # 1 if tempo events exist
features['quality_has_time_sig']        # 1 if time signature exists
features['quality_default_tempo']       # 1 if tempo ≈ 120 BPM default
features['quality_default_time_sig']    # 1 if time sig is 4/4 default
features['quality_very_sparse']         # 1 if <0.5 notes/second
features['quality_very_dense']          # 1 if >100 notes/second
features['quality_very_narrow_range']   # 1 if pitch range <1 octave

MIDIProblemDetector

Rule-based detector that identifies concrete MIDI problems — corruption, malformation, encoding errors, and quality issues — from extracted features.

from midiano import MIDIFeatureExtractor, MIDIProblemDetector

features = MIDIFeatureExtractor('song.mid').extract_all()
detector = MIDIProblemDetector(features)
problems = detector.detect()

Constructor Parameters:

Parameter Type Description
features dict Feature dictionary from MIDIFeatureExtractor.extract_all()

detect() Returns:

A list of problem dictionaries, each containing:

Key Type Description
category str 'corruption', 'structure', or 'quality'
issue str Short problem identifier
severity str 'critical', 'warning', or 'info'
detail str Human-readable description with values

Detected Problems:

Category Issue Severity Thresholds
corruption parse_error critical — file failed to parse
corruption no_notes critical — no note events found
corruption zero_duration_notes critical (>50%), warning (>10%), info (>1%)
corruption velocity_zero_notes critical (>30%), warning (>5%)
corruption out_of_range_pitches critical — any pitch outside 0–127
corruption negative_durations critical — any negative duration
corruption overlapping_same_pitch warning (>30%), info (>10%)
corruption stuck_notes warning — notes held >16 beats
corruption extreme_burst warning (>50), info (>20 simultaneous onsets)
structure nonstandard_tpq info — unusual ticks per quarter
structure excessive_tempo_changes warning (>10/s), info (>2/s)
structure extreme_tempo warning — BPM <20 or >300
structure large_tempo_range warning — >200 BPM variation
structure excessive_time_sig_changes warning — >10 time-sig changes
structure many_empty_tracks warning (>70%), info (>40%)
quality no_dynamics warning — single velocity value
quality all_max_velocity warning — all notes at velocity 127
quality atonal warning — key clarity <0.3
quality weak_tonality info — key clarity <0.5
# Filter by severity
critical = [p for p in problems if p['severity'] == 'critical']
warnings = [p for p in problems if p['severity'] == 'warning']

# Filter by category
corruption = [p for p in problems if p['category'] == 'corruption']

# Pretty-print all problems
for p in problems:
    icon = {'critical': '🔴', 'warning': '🟡', 'info': '🔵'}[p['severity']]
    print(f"{icon} [{p['severity'].upper()}] {p['issue']}: {p['detail']}")

Batch Processing

from midiano import MIDIFeatureExtractor, MIDIProblemDetector
import os, json

midi_dir = 'path/to/midi/files'
results = []

for fname in tqdm.tqdm(os.listdir(midi_dir)):
    if not fname.endswith(('.mid', '.midi')):
        continue
    path = os.path.join(midi_dir, fname)
    
    extractor = MIDIFeatureExtractor(path)
    features = extractor.extract_all()
    
    if 'error' not in features:
        detector = MIDIProblemDetector(features)
        problems = detector.detect()
        features['problem_count'] = len(problems)
        features['critical_count'] = sum(1 for p in problems if p['severity'] == 'critical')
    
    results.append(features)

# Save features as JSON
with open('features.json', 'w') as f:
    json.dump(results, f, indent=2, default=str)

Anomaly Detection with Isolation Forest

import pandas as pd
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
from midiano import MIDIFeatureExtractor

# Collect features from multiple MIDI files
feature_dicts = []
for path in midi_file_paths:
    f = MIDIFeatureExtractor(path).extract_all()
    if 'error' not in f:
        feature_dicts.append(f)

# Convert to DataFrame and clean
df = pd.DataFrame(feature_dicts).set_index('filename')
df = df.dropna(axis=1, how='any')          # drop columns with NaN
df = df.select_dtypes(include='number')     # keep only numeric columns

# Scale and detect anomalies
scaler = StandardScaler()
X = scaler.fit_transform(df)

iso = IsolationForest(contamination=0.1, random_state=42)
df['anomaly'] = iso.fit_predict(X)  # -1 = anomaly, 1 = normal

# Show flagged files
anomalies = df[df['anomaly'] == -1]
print(f"Flagged {len(anomalies)} anomalous MIDI files:")
print(anomalies.index.tolist())

Attribution


Project Los Angeles

Tegridy Code 2026

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

midiano-26.5.16.tar.gz (3.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

midiano-26.5.16-py3-none-any.whl (3.0 MB view details)

Uploaded Python 3

File details

Details for the file midiano-26.5.16.tar.gz.

File metadata

  • Download URL: midiano-26.5.16.tar.gz
  • Upload date:
  • Size: 3.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for midiano-26.5.16.tar.gz
Algorithm Hash digest
SHA256 0c5e8572dbe7fb52b764c9f97daa405916efa3d7d7fa2abdfb5aba75a7c8bfea
MD5 53e31edaeb5b63f40ab2e2c430f7e003
BLAKE2b-256 f95c81229f43e2032bcba0469432062d94582f520a2a367520425f844a325837

See more details on using hashes here.

File details

Details for the file midiano-26.5.16-py3-none-any.whl.

File metadata

  • Download URL: midiano-26.5.16-py3-none-any.whl
  • Upload date:
  • Size: 3.0 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for midiano-26.5.16-py3-none-any.whl
Algorithm Hash digest
SHA256 26de3d08665acdbff5bae09655ae420859829cacef9f7007f148737619ab09ef
MD5 1c817878cf8eea97a568ccde8866ed7d
BLAKE2b-256 53f0a9e0e3226b59a2797e38e48317ca3bc32226bfea06dad13ec3d6734d602a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page