Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(note): ♻️ rewrite notation systems to allow extending behavior #316

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
162 changes: 132 additions & 30 deletions lib/src/note/note.dart
Original file line number Diff line number Diff line change
Expand Up @@ -376,31 +376,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.isFlat => 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 @@ -418,17 +400,137 @@ final class Note implements Comparable<Note>, Scalable<Note> {
]);
}

/// Note notations.
enum NoteNotation {
/// The English alphabetic notation system.
english,
/// The abstraction for [Note] notation systems.
abstract class NoteNotation {
/// Creates a new [NoteNotation].
const NoteNotation();

/// The English alphabetic [NoteNotation] system.
static const english = EnglishNoteNotation();

/// The German alphabetic [NoteNotation] system.
static const german = GermanNoteNotation();

/// The Italian solmization [NoteNotation] system.
static const italian = ItalianNoteNotation();

/// The French solmization [NoteNotation] system.
static const french = FrenchNoteNotation();

/// Returns the string notation for [note].
String noteNotation(Note note);

/// Returns the string notation for [baseNote].
String baseNoteNotation(BaseNote baseNote);

/// Returns the string notation for [tonalMode].
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.isFlat => 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();

/// The German alphabetic notation system.
german,
@override
String noteNotation(Note note) =>
note.baseNote.toString(system: this) +
(note.accidental != Accidental.natural ? note.accidental.symbol : '');

/// The Italian solmization notation system.
italian,
@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',
};

/// The French solmization notation system.
french,
@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',
};
}
85 changes: 53 additions & 32 deletions lib/src/note/pitch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -368,42 +368,22 @@ final class Pitch implements Comparable<Pitch>, Scalable<Pitch> {
}) =>
referenceFrequency * tuningSystem.ratio(this).value;

String get _scientificNotation => '${note.baseNote}'
'${note.accidental != Accidental.natural ? note.accidental.symbol : ''}'
'$octave';

String get _helmholtzNotation {
final accidentalSymbol =
note.accidental != Accidental.natural ? note.accidental.symbol : '';

if (octave >= 3) {
return '${note.baseNote.name}$accidentalSymbol'
'${_superPrime * (octave - 3)}';
}

return '${note.baseNote.name.toUpperCase()}$accidentalSymbol'
'${_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) {
PitchNotation.scientific => _scientificNotation,
PitchNotation.helmholtz => _helmholtzNotation,
};
String toString({PitchNotation system = PitchNotation.scientific}) =>
system.pitchNotation(this);

@override
bool operator ==(Object other) =>
Expand All @@ -419,11 +399,52 @@ final class Pitch implements Comparable<Pitch>, Scalable<Pitch> {
]);
}

/// Pitch notations.
enum PitchNotation {
/// See [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation).
scientific,
/// Pitch notation systems.
abstract class PitchNotation {
/// Creates a new [PitchNotation].
const PitchNotation();

/// The scientific [PitchNotation] system.
static const scientific = ScientificPitchNotation();

/// The Helmholtz [PitchNotation] system.
static const helmholtz = HelmholtzPitchNotation();

/// Returns the string representation for [pitch].
String pitchNotation(Pitch pitch);
}

/// See [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation).
class ScientificPitchNotation extends PitchNotation {
/// Creates a new [ScientificPitchNotation].
const ScientificPitchNotation();

@override
String pitchNotation(Pitch pitch) {
final accidental = pitch.note.accidental != Accidental.natural
? pitch.note.accidental.symbol
: '';
return '${pitch.note.baseNote}$accidental${pitch.octave}';
}
}

/// See [Helmholtz’s pitch notation](https://en.wikipedia.org/wiki/Helmholtz_pitch_notation).
class HelmholtzPitchNotation extends PitchNotation {
/// Creates a new [HelmholtzPitchNotation].
const HelmholtzPitchNotation();

/// See [Helmholtz’s pitch notation](https://en.wikipedia.org/wiki/Helmholtz_pitch_notation).
helmholtz;
@override
String pitchNotation(Pitch pitch) {
final accidentalSymbol = pitch.note.accidental != Accidental.natural
? pitch.note.accidental.symbol
: '';

if (pitch.octave >= 3) {
return '${pitch.note.baseNote.name}$accidentalSymbol'
'${Pitch._superPrime * (pitch.octave - 3)}';
}

return '${pitch.note.baseNote.name.toUpperCase()}$accidentalSymbol'
'${Pitch._subPrime * (pitch.octave - 2).abs()}';
}
}
Loading