Flutter Notemus
Professional music notation rendering for Flutter with SMuFL-compliant engraving, Bravura glyph support, first-party notation-to-MIDI pipeline, and full MEI v5 conformance.
Project Links
- GitHub repository: https://github.com/alessonqueirozdev-hub/flutter_notemus
- pub.dev package: https://pub.dev/packages/flutter_notemus
- GitHub Pages (site/build): https://alessonqueirozdev-hub.github.io/flutter_notemus/
- Issue tracker: https://github.com/alessonqueirozdev-hub/flutter_notemus/issues
Table of Contents
- Current Status
- MEI v5 Conformance
- Open Pending Work
- Highlights
- Installation
- Required Initialization
- Quick Start
- API Guide
- MusicScore Widget
- Pitch and Notes
- Durations
- Rests
- Measures and Staff
- Clefs
- Key Signatures
- Time Signatures
- Barlines
- Chords
- Ties and Slurs
- Articulations
- Dynamics
- Ornaments
- Tempo Marks
- Grace Notes
- Tuplets
- Beams
- Octave Markings
- Volta Brackets
- Polyphony and Multi-Voice
- Repeats
- Playing Techniques
- Breath and Caesura
- Import from JSON, MusicXML, and MEI
- MIDI Mapping and Export
- Themes and Styling
- Reference JSON Format
- Architecture
- Development Checklist
- License
MEI v5 Conformance
flutter_notemus implements 100% of the MEI v5 (Music Encoding Initiative) specification, covering all repertoires and analytical features defined in the MEI v5 Guidelines.
Coverage by MEI v5 module
| MEI v5 Module | Coverage | Key classes |
|---|---|---|
| CMN — Pitch & Duration | ✅ 100% | Pitch, Duration, DurationType (maxima → 2048th) |
| CMN — Events | ✅ 100% | Note, Rest, Chord, Space, MeasureSpace |
| CMN — Measure & Staff | ✅ 100% | Measure (with @n), Staff (configurable lineCount), xml:id on all elements |
| CMN — Clef / Key / Meter | ✅ 100% | Clef (20 types), KeySignature (with KeyMode), TimeSignature (free + additive) |
| CMN — Articulation | ✅ 100% | ArticulationType (17 types), Articulation |
| CMN — Dynamics | ✅ 100% | Dynamic, DynamicType (44 types, hairpin) |
| CMN — Ornaments | ✅ 100% | Ornament, OrnamentType (60+ types) |
| CMN — Slur / Tie / Beam | ✅ 100% | SlurType, TieType, BeamType, AdvancedSlur |
| CMN — Tuplets | ✅ 100% | Tuplet, TupletBracket, nested tuplets |
| CMN — Polyphony | ✅ 100% | Voice, MultiVoiceMeasure, StemDirection |
| CMN — Score structure | ✅ 100% | Score, StaffGroup, ScoreDefinition (<scoreDef>) |
| CMN — Navigation | ✅ 100% | RepeatMark, VoltaBracket, BarlineType (12 types) |
| Lyrics & Text | ✅ 100% | MusicText, Verse, Syllable, SyllableType (MEI <syl>) |
| Metadata (meiHead) | ✅ 100% | MeiHeader, FileDescription, EncodingDescription, WorkList, ManifestationList, RevisionDescription |
| Harmonic Analysis | ✅ 100% | HarmonicLabel, MelodicInterval, HarmonicInterval, ScaleDegree, ChordTable |
| Figured Bass | ✅ 100% | FiguredBass, FigureElement (MEI <fb>/<f>) |
| Microtonality | ✅ 100% | AccidentalType (sagittal, koma, quarter-tone), Pitch.pitchClass |
| Solmization | ✅ 100% | Pitch.fromSolmization(), Pitch.solmizationName |
| Tablature | ✅ 100% | TabNote, TabGrp, TabDurSym, TabTuning, Note.tabFret/tabString |
| Mensural Notation | ✅ 100% | MensuralNote, Ligature, Mensur, ProportMark, MensuralRest |
| Neume Notation | ✅ 100% | Neume, NeumeComponent, NeumeType, NeumeDivision |
Key MEI v5 features
// xml:id on any element (MEI cross-referencing)
final note = Note(pitch: Pitch(step: 'C', octave: 4), duration: Duration(DurationType.quarter))
..xmlId = 'note-1';
// Explicit measure number (MEI <measure @n>)
final measure = Measure(number: 5);
// Configurable staff lines (MEI staffDef @lines)
final percStaff = Staff(lineCount: 1); // percussion
final tabStaff = Staff(lineCount: 6); // guitar tab
// KeyMode (MEI @mode)
KeySignature(2, mode: KeyMode.dorian)
// Free-time measure (senza misura)
TimeSignature.free()
// Additive meter (3+2+2)/8
TimeSignature.additive(groups: [3, 2, 2], denominator: 8)
// Lyrics with syllabification (MEI <syl>)
Verse(number: 1, syllables: [
Syllable(text: 'A-', type: SyllableType.initial),
Syllable(text: 've', type: SyllableType.terminal),
])
// Figured bass (MEI <fb>/<f>)
FiguredBass(figures: [
FigureElement(numeral: '6'),
FigureElement(numeral: '4', accidental: FigureAccidental.sharp),
])
// Tablature (MEI @tab.fret @tab.string)
Note(
pitch: Pitch(step: 'E', octave: 4),
duration: Duration(DurationType.quarter),
tabString: 1, tabFret: 0, // open first string
)
// Mensural notation
MensuralNote(pitchName: 'G', octave: 3, duration: MensuralDuration.semibreve)
Mensur(tempus: 3, prolatio: 2) // Tempus perfectum, prolatio minor
// Neume (Gregorian chant)
Neume(type: NeumeType.pes, components: [
NeumeComponent(pitchName: 'F', octave: 3, form: NcForm.punctum),
NeumeComponent(pitchName: 'G', octave: 3, form: NcForm.virga),
])
// Full MEI header (meiHead)
Score(
staffGroups: [...],
meiHeader: MeiHeader(
fileDescription: FileDescription(
title: 'Ave Maria',
contributors: [Contributor(name: 'Schubert', role: ResponsibilityRole.composer)],
),
encodingDescription: EncodingDescription(
meiVersion: '5',
applications: ['flutter_notemus'],
),
),
scoreDefinition: ScoreDefinition(
clef: Clef(clefType: ClefType.treble),
keySignature: KeySignature(0),
timeSignature: TimeSignature(numerator: 4, denominator: 4),
),
)
// Pitch class (MEI pclass) and solmization
Pitch(step: 'C', octave: 4).pitchClass // → 0
Pitch(step: 'A', octave: 4).pitchClass // → 9
Pitch.fromSolmization('sol', octave: 4) // → G4
Pitch(step: 'G', octave: 4).solmizationName // → 'sol'
// All MEI dur values including breve, long, maxima, and 256–2048
const Duration(DurationType.breve)
const Duration(DurationType.long)
const Duration(DurationType.maxima)
const Duration(DurationType.twoHundredFiftySixth)
DurationType.fromMeiValue('2048') // → DurationType.twoThousandFortyEighth
For a full conformance audit see docs/MEI_V5_AUDIT.md.
Current Status
- Current package release target:
2.2.1 - Previous pub.dev baseline before the new generation:
0.1.0 - Core notation rendering is production-ready.
- MIDI mapping and
.midexport are available in the package. - Android native audio backend is active; other native targets are configured and tracked as pending.
Open Pending Work
All pending work is tracked as GitHub issues:
- Native audio backend parity (iOS/macOS/Linux/Windows): https://github.com/alessonqueirozdev-hub/flutter_notemus/issues/1
- Real notation engraving for PDF export: https://github.com/alessonqueirozdev-hub/flutter_notemus/issues/2
- SMuFL brace workflow for staff groups: https://github.com/alessonqueirozdev-hub/flutter_notemus/issues/3
- Stem/flag primitive parameterization via engraving defaults: https://github.com/alessonqueirozdev-hub/flutter_notemus/issues/4
- Robust fallback for
repeatBoth: https://github.com/alessonqueirozdev-hub/flutter_notemus/issues/5
Highlights
Core notation
- Notes from whole through 1024th durations
- Rests for all supported durations
- Accidentals (natural, sharp, flat, double sharp, double flat)
- Automatic ledger lines
Clefs
- Treble, bass, alto, tenor, percussion, tablature
- Octave-transposing clef variants (8va, 8vb, 15ma, 15mb)
Rhythm and layout
- Proportional rhythmic spacing
- Auto and manual beaming
- Tuplet support
- Collision-aware layout
- Multi-measure and multi-system rendering
Symbols and expression
- Dynamics and hairpins
- Articulations
- Ornaments
- Tempo marks and text
- Ties and slurs
- Volta brackets, repeat symbols, and structural barlines
- Octave markings
Multi-staff and polyphony
- Multiple voices in a single staff (
MultiVoiceMeasure) - Multi-staff score support (
Score,StaffGroup) - Grand staff scenarios (piano)
- SATB-style aligned staff rendering
Import and interoperability
- JSON parser
- MusicXML parser (
score-partwiseandscore-timewise) - MEI parser
- Unified normalization to the same internal model
MIDI pipeline
- Notation-to-MIDI mapping from
StaffandScore - Repeat and volta expansion for playback timeline
- Tuplet/polyphony/tie-aware event generation
- Metronome track generation
- Standard MIDI file export (
MidiFileWriter)
Installation
Add dependency to your pubspec.yaml:
dependencies:
flutter_notemus: ^2.2.1
Install packages:
flutter pub get
Required Initialization
Load Bravura and SMuFL metadata before rendering any score:
import 'package:flutter/services.dart';
import 'package:flutter_notemus/flutter_notemus.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final loader = FontLoader('Bravura');
loader.addFont(
rootBundle.load('packages/flutter_notemus/assets/smufl/Bravura.otf'),
);
await loader.load();
await SmuflMetadata().load();
runApp(const MyApp());
}
Quick Start
import 'package:flutter/material.dart';
import 'package:flutter_notemus/flutter_notemus.dart';
class SimpleScorePage extends StatelessWidget {
const SimpleScorePage({super.key});
@override
Widget build(BuildContext context) {
final staff = Staff();
final measure = Measure();
measure.add(Clef(clefType: ClefType.treble));
measure.add(TimeSignature(numerator: 4, denominator: 4));
measure.add(Note(
pitch: const Pitch(step: 'C', octave: 5),
duration: const Duration(DurationType.quarter),
));
measure.add(Note(
pitch: const Pitch(step: 'E', octave: 5),
duration: const Duration(DurationType.quarter),
));
measure.add(Note(
pitch: const Pitch(step: 'G', octave: 5),
duration: const Duration(DurationType.quarter),
));
measure.add(Note(
pitch: const Pitch(step: 'C', octave: 6),
duration: const Duration(DurationType.quarter),
));
staff.add(measure);
return Scaffold(
body: SizedBox(
height: 180,
child: MusicScore(staff: staff),
),
);
}
}
API Guide
MusicScore Widget
MusicScore(
staff: staff,
staffSpace: 12.0,
theme: const MusicScoreTheme(),
)
Pitch and Notes
Note(
pitch: const Pitch(step: 'G', octave: 4),
duration: const Duration(DurationType.quarter),
articulations: [ArticulationType.staccato],
tie: TieType.start,
slur: SlurType.start,
beam: BeamType.start,
)
Accidentals are encoded directly in Pitch:
const Pitch(step: 'F', octave: 5, alter: 1.0); // sharp
const Pitch(step: 'B', octave: 4, alter: -1.0); // flat
const Pitch(step: 'C', octave: 5, alter: 2.0); // double sharp
const Pitch(step: 'D', octave: 4, alter: -2.0); // double flat
Durations
const Duration(DurationType.whole);
const Duration(DurationType.half);
const Duration(DurationType.quarter);
const Duration(DurationType.eighth);
const Duration(DurationType.sixteenth);
const Duration(DurationType.thirtySecond);
const Duration(DurationType.sixtyFourth);
const Duration(DurationType.oneHundredTwentyEighth);
Dotted durations:
const Duration(DurationType.quarter, dots: 1);
const Duration(DurationType.half, dots: 2);
Rests
Rest(duration: const Duration(DurationType.whole));
Rest(duration: const Duration(DurationType.half));
Rest(duration: const Duration(DurationType.eighth));
Measures and Staff
final staff = Staff();
final measure = Measure();
measure.add(Clef(clefType: ClefType.treble));
measure.add(TimeSignature(numerator: 3, denominator: 4));
measure.add(Note(
pitch: const Pitch(step: 'C', octave: 5),
duration: const Duration(DurationType.quarter),
));
staff.add(measure);
Clefs
Clef(clefType: ClefType.treble);
Clef(clefType: ClefType.treble8vb);
Clef(clefType: ClefType.bass);
Clef(clefType: ClefType.alto);
Clef(clefType: ClefType.tenor);
Clef(clefType: ClefType.percussion);
Clef(clefType: ClefType.tab6);
Key Signatures
KeySignature(0); // C major / A minor
KeySignature(2); // D major (2 sharps)
KeySignature(-3); // E-flat major (3 flats)
Time Signatures
TimeSignature(numerator: 4, denominator: 4);
TimeSignature(numerator: 3, denominator: 4);
TimeSignature(numerator: 6, denominator: 8);
Barlines
Barline(type: BarlineType.single);
Barline(type: BarlineType.double);
Barline(type: BarlineType.final_);
Barline(type: BarlineType.repeatForward);
Barline(type: BarlineType.repeatBackward);
Barline(type: BarlineType.repeatBoth);
Chords
Chord(
notes: [
Note(pitch: const Pitch(step: 'C', octave: 4), duration: const Duration(DurationType.half)),
Note(pitch: const Pitch(step: 'E', octave: 4), duration: const Duration(DurationType.half)),
Note(pitch: const Pitch(step: 'G', octave: 4), duration: const Duration(DurationType.half)),
],
duration: const Duration(DurationType.half),
);
Ties and Slurs
Note(
pitch: const Pitch(step: 'C', octave: 5),
duration: const Duration(DurationType.half),
tie: TieType.start,
);
Note(
pitch: const Pitch(step: 'D', octave: 5),
duration: const Duration(DurationType.quarter),
slur: SlurType.start,
);
Articulations
Note(
pitch: const Pitch(step: 'G', octave: 4),
duration: const Duration(DurationType.quarter),
articulations: [
ArticulationType.staccato,
ArticulationType.accent,
ArticulationType.tenuto,
ArticulationType.marcato,
],
);
Dynamics
Dynamic(type: DynamicType.piano);
Dynamic(type: DynamicType.mezzoForte);
Dynamic(type: DynamicType.forte);
Dynamic(type: DynamicType.crescendo);
Dynamic(type: DynamicType.diminuendo);
Ornaments
Ornament(type: OrnamentType.trill);
Ornament(type: OrnamentType.mordent);
Ornament(type: OrnamentType.turn);
Ornament(type: OrnamentType.fermata);
Tempo Marks
TempoMark(
bpm: 120,
beatUnit: DurationType.quarter,
text: 'Allegro',
);
Grace Notes
GraceNote(
pitch: const Pitch(step: 'D', octave: 5),
type: GraceNoteType.acciaccatura,
);
Tuplets
Tuplet(
actualNotes: 3,
normalNotes: 2,
elements: [
Note(pitch: const Pitch(step: 'C', octave: 5), duration: const Duration(DurationType.eighth)),
Note(pitch: const Pitch(step: 'D', octave: 5), duration: const Duration(DurationType.eighth)),
Note(pitch: const Pitch(step: 'E', octave: 5), duration: const Duration(DurationType.eighth)),
],
);
Beams
Note(
pitch: const Pitch(step: 'E', octave: 5),
duration: const Duration(DurationType.eighth),
beam: BeamType.start,
);
Octave Markings
OctaveMark(type: OctaveType.ottava);
OctaveMark(type: OctaveType.ottavaBassa);
OctaveMark(type: OctaveType.quindicesima);
OctaveMark(type: OctaveType.quindicesimaBassa);
Volta Brackets
VoltaBracket(number: 1, length: 220);
VoltaBracket(number: 2, length: 180, hasOpenEnd: true);
Polyphony and Multi-Voice
final measure = MultiVoiceMeasure();
final voice1 = Voice.voice1();
voice1.add(Clef(clefType: ClefType.treble));
voice1.add(TimeSignature(numerator: 4, denominator: 4));
voice1.add(Note(
pitch: const Pitch(step: 'E', octave: 5),
duration: const Duration(DurationType.quarter),
));
final voice2 = Voice.voice2();
voice2.add(Note(
pitch: const Pitch(step: 'C', octave: 4),
duration: const Duration(DurationType.half),
));
measure.addVoice(voice1);
measure.addVoice(voice2);
Repeats
Barline(type: BarlineType.repeatForward);
Barline(type: BarlineType.repeatBackward);
Barline(type: BarlineType.repeatBoth);
Playing Techniques
PlayingTechnique(type: TechniqueType.pizzicato);
PlayingTechnique(type: TechniqueType.colLegno);
PlayingTechnique(type: TechniqueType.sulTasto);
PlayingTechnique(type: TechniqueType.sulPonticello);
Breath and Caesura
Breath();
Breath(type: BreathType.caesura);
Breath(type: BreathType.shortBreath);
Import from JSON, MusicXML, and MEI
final jsonStaff = JsonMusicParser.parseStaff(jsonString);
final musicXmlStaff = MusicXMLParser.parseMusicXML(musicXmlString);
final meiStaff = MEIParser.parseMEI(meiString);
final autoDetected = NotationParser.parseStaff(sourceString);
MIDI Mapping and Export
import 'dart:io';
import 'package:flutter_notemus/midi.dart';
Future<void> exportMidi(Staff staff) async {
final sequence = MidiMapper.fromStaff(
staff,
options: const MidiGenerationOptions(
ticksPerQuarter: 960,
defaultBpm: 120,
includeMetronome: true,
),
);
final bytes = MidiFileWriter.write(sequence);
await File('score.mid').writeAsBytes(bytes, flush: true);
}
Native integration APIs:
MidiNativeAudioBackendMethodChannelMidiNativeAudioBackendMidiNativeSequenceBridge
Themes and Styling
MusicScore(
staff: staff,
theme: const MusicScoreTheme(
staffLineColor: Colors.black,
noteheadColor: Colors.black,
stemColor: Colors.black,
clefColor: Colors.black,
barlineColor: Colors.black,
dynamicColor: Colors.black,
tieColor: Colors.black,
slurColor: Colors.black,
textColor: Colors.black,
),
)
Reference JSON Format
{
"measures": [
{
"clef": "treble",
"timeSignature": { "numerator": 4, "denominator": 4 },
"keySignature": 0,
"elements": [
{ "type": "note", "step": "C", "octave": 5, "duration": "quarter" },
{ "type": "note", "step": "E", "octave": 5, "duration": "quarter" },
{ "type": "note", "step": "G", "octave": 5, "duration": "quarter" },
{ "type": "rest", "duration": "quarter" }
]
}
]
}
Architecture
flutter_notemus/
├── lib/
│ ├── flutter_notemus.dart # Public entry point
│ ├── midi.dart # Public MIDI entry point
│ ├── core/ # Music model
│ └── src/
│ ├── midi/ # MIDI mapping/export/native bridge
│ ├── layout/ # Layout and spacing
│ ├── rendering/ # Renderers and positioning
│ ├── smufl/ # Metadata and glyph references
│ ├── parsers/ # JSON, MusicXML, MEI parsers
│ └── theme/ # Theme and style system
├── assets/smufl/ # Bravura + metadata
├── android/ios/macos/linux/windows # Plugin/native targets
└── example/ # Demo application
Rendering flow:
Staff/Score -> LayoutEngine -> PositionedElements -> StaffRenderer -> Canvas
Development Checklist
Typical local validation:
dart analyze
flutter test
flutter pub publish --dry-run
License
- Project code: Apache License 2.0 (
LICENSE) - Third-party assets and references:
THIRD_PARTY_LICENSES.md
Libraries
- core/articulation
- core/barline
- core/beam
- core/breath
- core/chord
- core/clef
- core/cluster
- core/core
- core/duration
- core/dynamic
- core/figured_bass
- core/harmonic_analysis
- core/key_signature
- core/line
- core/measure
- core/mei_header
- core/mensural
- core/musical_element
- core/neume
- core/note
- core/octave
- core/ornament
- core/pitch
- core/repeat
- core/rest
- core/score
- core/score_def
- core/slur
- core/space
- core/staff
- core/staff_group
- core/tablature
- core/technique
- core/tempo
- core/text
- core/time_signature
- core/tuplet
- core/tuplet_bracket
- core/tuplet_number
- core/voice
- core/volta_bracket
- flutter_notemus
- Flutter Notemus — professional music notation rendering for Flutter.
- flutter_notemus_web
- midi