Skip to content

Commit

Permalink
refactor(note): ♻️ rewrite notation systems to allow extending behavi…
Browse files Browse the repository at this point in the history
…or (#316)

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

* docs(note): 📖 consistently rewrite methods documentation

* refactor(pitch): ♻️ rewrite `PitchNotationSystem` to allow extending behavior

* docs(pitch): 📖 add missing documentation
  • Loading branch information
albertms10 committed Dec 13, 2023
1 parent 0a4f676 commit a59b8ce
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 162 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
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

0 comments on commit a59b8ce

Please sign in to comment.