Skip to content

Commit

Permalink
refactor(note): ♻️ rewrite notation systems to allow extending behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
albertms10 committed Dec 7, 2023
1 parent 61a38db commit 9d9e579
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 132 deletions.
31 changes: 1 addition & 30 deletions lib/src/note/base_note.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,36 +147,7 @@ enum BaseNote implements Comparable<BaseNote> {

@override
String toString({NoteNotation system = NoteNotation.english}) =>
switch (system) {
NoteNotation.english => name.toUpperCase(),
NoteNotation.german => switch (this) {
BaseNote.c => 'C',
BaseNote.d => 'D',
BaseNote.e => 'E',
BaseNote.f => 'F',
BaseNote.g => 'G',
BaseNote.a => 'A',
BaseNote.b => 'H',
},
NoteNotation.italian => switch (this) {
BaseNote.c => 'Do',
BaseNote.d => 'Re',
BaseNote.e => 'Mi',
BaseNote.f => 'Fa',
BaseNote.g => 'Sol',
BaseNote.a => 'La',
BaseNote.b => 'Si',
},
NoteNotation.french => switch (this) {
BaseNote.c => 'Ut',
BaseNote.d => 'Ré',
BaseNote.e => 'Mi',
BaseNote.f => 'Fa',
BaseNote.g => 'Sol',
BaseNote.a => 'La',
BaseNote.b => 'Si',
},
};
system.baseNoteNotation(this);

@override
int compareTo(BaseNote other) => semitones.compareTo(other.semitones);
Expand Down
154 changes: 128 additions & 26 deletions lib/src/note/note.dart
Original file line number Diff line number Diff line change
Expand Up @@ -377,32 +377,13 @@ final class Note implements Comparable<Note>, Scalable<Note> {
/// ```dart
/// Note.c.toPitchClass() == PitchClass.c
/// Note.e.sharp.toPitchClass() == PitchClass.f
/// Note.c.flat.flat.toPitchClass() == PitchClass.a.sharp
/// Note.c.flat.flat.toPitchClass() == PitchClass.aSharp
/// ```
PitchClass toPitchClass() => PitchClass(semitones);

@override
String toString({NoteNotation system = NoteNotation.english}) =>
switch (system) {
NoteNotation.german => switch (this) {
Note(baseNote: BaseNote.b, accidental: Accidental.flat) => 'B',
// Flattened notes.
final note when note.accidental.semitones < 0 => switch (
note.baseNote) {
BaseNote.a ||
BaseNote.e =>
'${note.baseNote.toString(system: system)}s'
'${'es' * (note.accidental.semitones.abs() - 1)}',
final baseNote => '${baseNote.toString(system: system)}'
'${'es' * note.accidental.semitones.abs()}',
},
// Sharpened and natural notes.
final note => '${baseNote.toString(system: system)}'
'${'is' * note.accidental.semitones}',
},
final system => baseNote.toString(system: system) +
(accidental != Accidental.natural ? accidental.symbol : ''),
};
system.noteNotation(this);

@override
bool operator ==(Object other) =>
Expand All @@ -421,16 +402,137 @@ final class Note implements Comparable<Note>, Scalable<Note> {
}

/// Note notations.
enum NoteNotation {
sealed class NoteNotation {
/// Creates a new [NoteNotation].
const NoteNotation();

/// The English alphabetic notation system.
english,
static const english = EnglishNoteNotation();

/// The German alphabetic notation system.
german,
static const german = GermanNoteNotation();

/// The Italian solmization notation system.
italian,
static const italian = ItalianNoteNotation();

/// The French solmization notation system.
french,
static const french = FrenchNoteNotation();

/// [Note] notation.
String noteNotation(Note note);

/// [BaseNote] notation.
String baseNoteNotation(BaseNote baseNote);

/// [TonalMode] notation.
String tonalModeNotation(TonalMode tonalMode);
}

/// The English alphabetic notation system.
class EnglishNoteNotation extends NoteNotation {
/// Creates a new [EnglishNoteNotation].
const EnglishNoteNotation();

@override
String noteNotation(Note note) =>
note.baseNote.toString(system: this) +
(note.accidental != Accidental.natural ? note.accidental.symbol : '');

@override
String baseNoteNotation(BaseNote baseNote) => baseNote.name.toUpperCase();

@override
String tonalModeNotation(TonalMode tonalMode) => tonalMode.name;
}

/// The German alphabetic notation system.
class GermanNoteNotation extends NoteNotation {
/// Creates a new [GermanNoteNotation].
const GermanNoteNotation();

@override
String noteNotation(Note note) => switch (note) {
Note(baseNote: BaseNote.b, accidental: Accidental.flat) => 'B',
// Flattened notes.
final note when note.accidental.semitones < 0 => switch (
note.baseNote) {
BaseNote.a ||
BaseNote.e =>
'${note.baseNote.toString(system: this)}s'
'${'es' * (note.accidental.semitones.abs() - 1)}',
final baseNote => '${baseNote.toString(system: this)}'
'${'es' * note.accidental.semitones.abs()}',
},
// Sharpened and natural notes.
final note => '${note.baseNote.toString(system: this)}'
'${'is' * note.accidental.semitones}',
};

@override
String baseNoteNotation(BaseNote baseNote) => switch (baseNote) {
BaseNote.b => 'H',
final baseNote => baseNote.name.toUpperCase(),
};

@override
String tonalModeNotation(TonalMode tonalMode) => switch (tonalMode) {
TonalMode.major => 'Dur',
TonalMode.minor => 'Moll',
};
}

/// The Italian alphabetic notation system.
class ItalianNoteNotation extends NoteNotation {
/// Creates a new [ItalianNoteNotation].
const ItalianNoteNotation();

@override
String noteNotation(Note note) =>
note.baseNote.toString(system: this) +
(note.accidental != Accidental.natural ? note.accidental.symbol : '');

@override
String baseNoteNotation(BaseNote baseNote) => switch (baseNote) {
BaseNote.c => 'Do',
BaseNote.d => 'Re',
BaseNote.e => 'Mi',
BaseNote.f => 'Fa',
BaseNote.g => 'Sol',
BaseNote.a => 'La',
BaseNote.b => 'Si',
};

@override
String tonalModeNotation(TonalMode tonalMode) => switch (tonalMode) {
TonalMode.major => 'maggiore',
TonalMode.minor => 'minore',
};
}

/// The French alphabetic notation system.
class FrenchNoteNotation extends NoteNotation {
/// Creates a new [FrenchNoteNotation].
const FrenchNoteNotation();

@override
String noteNotation(Note note) =>
note.baseNote.toString(system: this) +
(note.accidental != Accidental.natural ? note.accidental.symbol : '');

@override
String baseNoteNotation(BaseNote baseNote) => switch (baseNote) {
BaseNote.c => 'Ut',
BaseNote.d => 'Ré',
BaseNote.e => 'Mi',
BaseNote.f => 'Fa',
BaseNote.g => 'Sol',
BaseNote.a => 'La',
BaseNote.b => 'Si',
};

@override
String tonalModeNotation(TonalMode tonalMode) => switch (tonalMode) {
TonalMode.major => 'majeur',
TonalMode.minor => 'mineur',
};
}
14 changes: 7 additions & 7 deletions lib/src/note/pitch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -386,22 +386,22 @@ final class Pitch implements Comparable<Pitch>, Scalable<Pitch> {
'${_subPrime * (octave - 2).abs()}';
}

/// Returns the string representation of this [Pitch] based on [notation].
/// Returns the string representation of this [Pitch] based on [system].
///
/// Example:
/// ```dart
/// Note.c.inOctave(4).toString() == 'C4'
/// Note.a.inOctave(3).toString() == 'A3'
/// Note.b.flat.inOctave(1).toString() == 'B♭1'
///
/// Note.c.inOctave(4).toString(notation: PitchNotation.helmholtz) == 'c′'
/// Note.a.inOctave(3).toString(notation: PitchNotation.helmholtz) == 'a'
/// Note.b.flat.inOctave(1).toString(notation: PitchNotation.helmholtz)
/// Note.c.inOctave(4).toString(system: PitchNotation.helmholtz) == 'c′'
/// Note.a.inOctave(3).toString(system: PitchNotation.helmholtz) == 'a'
/// Note.b.flat.inOctave(1).toString(system: PitchNotation.helmholtz)
/// == 'B♭͵'
/// ```
@override
String toString({PitchNotation notation = PitchNotation.scientific}) =>
switch (notation) {
String toString({PitchNotation system = PitchNotation.scientific}) =>
switch (system) {
PitchNotation.scientific => _scientificNotation,
PitchNotation.helmholtz => _helmholtzNotation,
};
Expand All @@ -420,7 +420,7 @@ final class Pitch implements Comparable<Pitch>, Scalable<Pitch> {
]);
}

/// Pitch notations.
/// Pitch notation systems.
enum PitchNotation {
/// See [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation).
scientific,
Expand Down
65 changes: 42 additions & 23 deletions lib/src/note/pitch_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -207,36 +207,25 @@ final class PitchClass implements Scalable<PitchClass>, Comparable<PitchClass> {
/// See [Pitch-class multiplication modulo 12](https://en.wikipedia.org/wiki/Multiplication_(music)#Pitch-class_multiplication_modulo_12).
PitchClass operator *(int factor) => PitchClass(chroma * factor);

String get _enharmonicSpellingsNotation => '{${spellings().join('|')}}';

String get _integerNotation => switch (chroma) {
10 => 't',
11 => 'e',
final chroma => '$chroma',
};

/// Returns the string representation of this [PitchClass] based on
/// [notation].
/// [system].
///
/// Example:
/// ```dart
/// PitchClass.c.toString() == '{C}'
/// PitchClass.g.toString() == '{G}'
/// PitchClass.dSharp.toString() == '{D♯|E♭}'
///
/// PitchClass.c.toString(notation: PitchClassNotation.integer) == '0'
/// PitchClass.f.toString(notation: PitchClassNotation.integer) == '5'
/// PitchClass.aSharp.toString(notation: PitchClassNotation.integer) == 't'
/// PitchClass.b.toString(notation: PitchClassNotation.integer) == 'e'
/// PitchClass.c.toString(system: PitchClassNotation.integer) == '0'
/// PitchClass.f.toString(system: PitchClassNotation.integer) == '5'
/// PitchClass.aSharp.toString(system: PitchClassNotation.integer) == 't'
/// PitchClass.b.toString(system: PitchClassNotation.integer) == 'e'
/// ```
@override
String toString({
PitchClassNotation notation = PitchClassNotation.enharmonicSpellings,
PitchClassNotation system = PitchClassNotation.enharmonicSpellings,
}) =>
switch (notation) {
PitchClassNotation.enharmonicSpellings => _enharmonicSpellingsNotation,
PitchClassNotation.integer => _integerNotation,
};
system.pitchClassNotation(this);

@override
bool operator ==(Object other) =>
Expand All @@ -250,10 +239,40 @@ final class PitchClass implements Scalable<PitchClass>, Comparable<PitchClass> {
}

/// Pitch-class notations.
enum PitchClassNotation {
/// See [Tonal counterparts](https://en.wikipedia.org/wiki/Pitch_class#Other_ways_to_label_pitch_classes).
enharmonicSpellings,
abstract class PitchClassNotation {
/// Creates a new [PitchClassNotation].
const PitchClassNotation();

/// The enharmonic spellings [PitchClassNotation].
static const enharmonicSpellings = PitchClassEnharmonicSpellingsNotation();

/// The integer [PitchClassNotation].
static const integer = PitchClassIntegerNotation();

/// [PitchClass] notation.
String pitchClassNotation(PitchClass pitchClass);
}

/// See [Integer notation](https://en.wikipedia.org/wiki/Pitch_class#Integer_notation).
integer;
/// See [Tonal counterparts](https://en.wikipedia.org/wiki/Pitch_class#Other_ways_to_label_pitch_classes).
class PitchClassEnharmonicSpellingsNotation extends PitchClassNotation {
/// Creates a new [PitchClassEnharmonicSpellingsNotation].
const PitchClassEnharmonicSpellingsNotation();

@override
String pitchClassNotation(PitchClass pitchClass) =>
'{${pitchClass.spellings().join('|')}}';
}

/// See [Integer notation](https://en.wikipedia.org/wiki/Pitch_class#Integer_notation).
class PitchClassIntegerNotation extends PitchClassNotation {
/// Creates a new [PitchClassIntegerNotation].
const PitchClassIntegerNotation();

@override
String pitchClassNotation(PitchClass pitchClass) =>
switch (pitchClass.chroma) {
10 => 't',
11 => 'e',
final chroma => '$chroma',
};
}
16 changes: 1 addition & 15 deletions lib/src/tonality/mode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,7 @@ enum TonalMode implements Mode {

@override
String toString({NoteNotation system = NoteNotation.english}) =>
switch (system) {
NoteNotation.english => name,
NoteNotation.german => switch (this) {
TonalMode.major => 'Dur',
TonalMode.minor => 'Moll',
},
NoteNotation.italian => switch (this) {
TonalMode.major => 'maggiore',
TonalMode.minor => 'minore',
},
NoteNotation.french => switch (this) {
TonalMode.major => 'majeur',
TonalMode.minor => 'mineur',
},
};
system.tonalModeNotation(this);

@override
int compareTo(Mode other) => Mode.compare(this, other);
Expand Down
Loading

0 comments on commit 9d9e579

Please sign in to comment.