saavy_music 0.4.0 copy "saavy_music: ^0.4.0" to clipboard
saavy_music: ^0.4.0 copied to clipboard

An opinionated, compositional, and immutable music theory library for Dart, designed for developers who think about harmony in terms of shapes and relationships.

saavy_music #

pub package License: MIT

saavy_music is a dart library for representing and working with music theory concepts in code: notes, intervals, chords, scales, and key signatures. the library is immutable, compositional, and dependency-light, with utilities for midi numbers and frequencies.

new in 0.2.0: key-aware enharmonic spelling with octave-accurate KeySignature.resolveDegree.

overview #

  • immutable value types
  • chord construction via ChordRecipe (triad quality + optional extensions)
  • key-aware enharmonic spelling with octave preservation
  • midi number and frequency helpers
  • core dart only (no flutter dependency)

install #

add this to your package's pubspec.yaml:

dependencies:
  saavy_music: ^0.2.0

usage #

notes and intervals #

import 'package:saavy_music/saavy_music.dart';

void main() {
  final c4 = Note.c(4);
  final fSharp5 = Note.f(5, accidental: Accidental.sharp);

  final note = Note.fromComponents(
    noteName: NoteName.c,
    octave: 4,
    accidental: Accidental.natural,
  );

  print(c4.frequency); // 261.6256
  print(c4.name);      // "c4"
  print(c4.midiNumber); // 60

  final perfectFifth = Interval.perfectFifth();
  print(perfectFifth.semitones); // 7
  print(perfectFifth.name);      // "Perfect Fifth"
}

chord recipes #

chords are constructed from a ChordRecipe that combines a triad quality with optional extensions.

final c4 = Note.c(4);

final cmaj7 = Chord.fromRecipe(c4, ChordRecipes.majorSeventh);
final dm = Chord.fromRecipe(Note.d(4), ChordRecipes.minorTriad);
final g7 = Chord.fromRecipe(Note.g(4), ChordRecipes.dominantSeventh);

print(cmaj7.notes); // [c4, e4, g4, b4]
print(cmaj7.name);  // "c4maj7"

// convenience constructors
final cMajor = Chord.major(c4);
final dMinor = Chord.minor(Note.d(4));
final g7Chord = Chord.dominantSeventh(Note.g(4));

custom chord recipes #

const myCustomChord = ChordRecipe(
  id: 'custom_add9',
  displayLabel: 'add9',
  fullName: 'Custom Add Nine',
  triadQuality: TriadQuality.major,
  extensions: {ChordExtension.majorNinth},
);

final customChord = Chord.fromRecipe(Note.c(4), myCustomChord);
print(customChord.notes); // [c4, e4, g4, d5]

key signatures and scale degrees #

final keyOfEbMajor = KeySignature(
  tonic: Note.e(4, accidental: Accidental.flat),
);

final tonic = keyOfEbMajor.resolveDegree(Degrees.i);
final dominant = keyOfEbMajor.resolveDegree(Degrees.v);
final subdominant = keyOfEbMajor.resolveDegree(Degrees.iv);

final I = Chord.major(tonic);
final IV = Chord.major(subdominant);
final V7 = Chord.dominantSeventh(dominant);

print('${I.name} - ${IV.name} - ${V7.name}');

key-aware enharmonic spelling #

KeySignature.resolveDegree returns KeyAwareNotes that are spelled according to the selected key and mode, including double sharps/flats when appropriate. octaves are preserved unless an octaveOffset is applied.

final fSharpMajor = KeySignature(tonic: Note.f(4, accidental: Accidental.sharp));

fSharpMajor.resolveDegree(Degrees.vii).name; // e#4
fSharpMajor.resolveDegree(Degrees.vii.sharp()).name; // e##4

final cbMinor = KeySignature(
  tonic: Note.c(4, accidental: Accidental.flat),
  mode: ScaleMode.aeolian,
);

cbMinor.resolveDegree(Degrees.iii).name; // ebb4
cbMinor.resolveDegree(Degrees.vi).name;  // abb4

chord inversions #

final cMajor = Chord.major(Note.c(4));

print(cMajor.notes);           // [c4, e4, g4]
print(cMajor.invert(1).notes); // [e4, g4, c5]
print(cMajor.invert(2).notes); // [g4, c5, e5]

print(cMajor.invert(1).name); // "c4maj/e4"

additional chord types #

final cmaj9 = Chord.fromRecipe(Note.c(4), ChordRecipes.majorNinth);
final dm11 = Chord.fromRecipe(Note.d(4), ChordRecipes.minorEleventh);

final g7b9 = Chord.fromRecipe(Note.g(4), ChordRecipes.dominantFlatNinth);
final g7sharp9 = Chord.fromRecipe(Note.g(4), ChordRecipes.dominantSharpNinth);

print('Triads: ${ChordRecipes.triads.length}');
print('Sevenths: ${ChordRecipes.sevenths.length}');
print('Extensions: ${ChordRecipes.extensions.length}');

example: building simple progressions #

final keys = [
  KeySignature(tonic: Note.c(4)),
  KeySignature(tonic: Note.g(4)),
  KeySignature(tonic: Note.f(4)),
];

final progressions = [
  [Degrees.i, Degrees.vi, Degrees.iv, Degrees.v],
  [Degrees.ii, Degrees.v, Degrees.i],
];

for (final key in keys) {
  for (final progression in progressions) {
    final chords = progression.map((degree) {
      final root = key.resolveDegree(degree);
      return Chord.major(root);
    }).toList();

    print('${key.label}: ${chords.map((c) => c.name).join(' - ')}');
  }
}

api reference #

  • Note: musical note with midi number and velocity
  • Interval: distance between two pitches
  • Chord: collection of notes
  • ChordRecipe: template for building chords
  • KeySignature: musical key with tonic and mode
  • ScaleDegree: roman numeral analysis utilities

contributing #

contributions are welcome via pull request. for major changes, open an issue first to discuss the approach.

license #

mit, see LICENSE.

1
likes
150
points
123
downloads

Publisher

unverified uploader

Weekly Downloads

An opinionated, compositional, and immutable music theory library for Dart, designed for developers who think about harmony in terms of shapes and relationships.

Repository (GitHub)
View/report issues
Contributing

Topics

#music-theory #ear-training #music #composition #harmony

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

collection, meta

More

Packages that depend on saavy_music