From 202f0b21fbb9b7edfd8b3d09d202eb0134a9f0ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Ma=C3=B1osa?= Date: Thu, 6 Apr 2023 01:18:23 +0200 Subject: [PATCH] refactor(accidental): rewrite new `Accidental` class (#8) * refactor(accidental): rewrite new `Accidental` class * test(accidental): add test cases for `Accidental` * docs(tonality): fix typo * docs(accidental): reformat Markdown list --- lib/music_notes.dart | 2 +- lib/src/classes/enharmonic_note.dart | 10 +-- lib/src/classes/key_signature.dart | 8 +- lib/src/classes/note.dart | 26 +++--- lib/src/classes/relative_tonalities.dart | 8 +- lib/src/classes/tonality.dart | 14 +-- lib/src/enums/accidentals.dart | 106 ----------------------- lib/src/note/accidental.dart | 49 +++++++++++ test/interval_test.dart | 32 +++---- test/music_test.dart | 20 ++--- test/note/accidental_test.dart | 46 ++++++++++ 11 files changed, 155 insertions(+), 166 deletions(-) delete mode 100644 lib/src/enums/accidentals.dart create mode 100644 lib/src/note/accidental.dart create mode 100644 test/note/accidental_test.dart diff --git a/lib/music_notes.dart b/lib/music_notes.dart index 1e6c0ae4..a23e281b 100644 --- a/lib/music_notes.dart +++ b/lib/music_notes.dart @@ -16,11 +16,11 @@ part 'src/classes/key_signature.dart'; part 'src/classes/note.dart'; part 'src/classes/relative_tonalities.dart'; part 'src/classes/tonality.dart'; -part 'src/enums/accidentals.dart'; part 'src/enums/intervals.dart'; part 'src/enums/modes.dart'; part 'src/enums/notes.dart'; part 'src/enums/qualities.dart'; part 'src/interfaces/music_item.dart'; part 'src/interfaces/transposable.dart'; +part 'src/note/accidental.dart'; part 'src/utils/music.dart'; diff --git a/lib/src/classes/enharmonic_note.dart b/lib/src/classes/enharmonic_note.dart index 21efe8f5..b56d8f8e 100644 --- a/lib/src/classes/enharmonic_note.dart +++ b/lib/src/classes/enharmonic_note.dart @@ -18,12 +18,12 @@ class EnharmonicNote extends Enharmonic { return SplayTreeSet.from({ Note( noteBelow, - AccidentalsValues.fromValue(note.value - noteBelow.value), + Accidental(note.value - noteBelow.value), ), Note(note), Note( noteAbove, - AccidentalsValues.fromValue(note.value - noteAbove.value), + Accidental(note.value - noteAbove.value), ), }); } @@ -31,11 +31,11 @@ class EnharmonicNote extends Enharmonic { return SplayTreeSet.from({ Note( NotesValues.fromValue(semitones - 1)!, - Accidentals.sharp, + Accidental.sharp, ), Note( NotesValues.fromValue(semitones + 1)!, - Accidentals.flat, + Accidental.flat, ), }); } @@ -50,7 +50,7 @@ class EnharmonicNote extends Enharmonic { /// EnharmonicNote.note(5, Accidentals.flat) /// == const Note(Notes.fa, Accidentals.flat) /// ``` - static Note note(int semitones, [Accidentals? preferredAccidental]) { + static Note note(int semitones, [Accidental? preferredAccidental]) { final enharmonicNotes = EnharmonicNote.fromSemitones(semitones).items; return enharmonicNotes.firstWhere( diff --git a/lib/src/classes/key_signature.dart b/lib/src/classes/key_signature.dart index 8fbc5f14..8b0448f7 100644 --- a/lib/src/classes/key_signature.dart +++ b/lib/src/classes/key_signature.dart @@ -3,7 +3,7 @@ part of '../../music_notes.dart'; @immutable class KeySignature { final int number; - final Accidentals? accidental; + final Accidental? accidental; const KeySignature(this.number, [this.accidental]) : assert(number >= 0, 'Provide a positive number'), @@ -18,8 +18,8 @@ class KeySignature { distance == 0 ? null : distance > 0 - ? Accidentals.sharp - : Accidentals.flat, + ? Accidental.sharp + : Accidental.flat, ); /// Returns [RelativeTonalities] with the two tonalities that are defined @@ -50,7 +50,7 @@ class KeySignature { final n = i == iterations ? nModExcludeZero(number, notesValues) : notesValues; - list.add('$n × ${accidental!.increment(i - 1)!.name}'); + list.add('$n × ${Accidental(accidental!.value + i - 1).symbol}'); } return list.join(', '); diff --git a/lib/src/classes/note.dart b/lib/src/classes/note.dart index 31335c76..8c32cfc2 100644 --- a/lib/src/classes/note.dart +++ b/lib/src/classes/note.dart @@ -3,13 +3,13 @@ part of '../../music_notes.dart'; @immutable class Note implements MusicItem, Comparable { final Notes note; - final Accidentals? accidental; + final Accidental? accidental; const Note(this.note, [this.accidental]); factory Note.fromSemitones( int semitones, [ - Accidentals? preferredAccidental, + Accidental? preferredAccidental, ]) => EnharmonicNote.note(semitones, preferredAccidental); @@ -27,7 +27,7 @@ class Note implements MusicItem, Comparable { factory Note.fromTonalityAccidentals( int accidentals, Modes mode, [ - Accidentals? accidental, + Accidental? accidental, ]) { final note = Note.fromRawAccidentals(accidentals, accidental); @@ -51,18 +51,18 @@ class Note implements MusicItem, Comparable { /// Note.fromRawAccidentals(0) /// == const Note(Notes.la) /// ``` - factory Note.fromRawAccidentals(int accidentals, [Accidentals? accidental]) => + factory Note.fromRawAccidentals(int accidentals, [Accidental? accidental]) => Note.fromSemitones( Interval( Intervals.fifth, Qualities.perfect, - descending: accidental == Accidentals.flat, + descending: accidental == Accidental.flat, ).semitones * accidentals + 1, - (accidental == Accidentals.flat && accidentals > 8) || - (accidental == Accidentals.sharp && accidentals > 10) - ? accidental!.incremented + (accidental == Accidental.flat && accidentals > 8) || + (accidental == Accidental.sharp && accidentals > 10) + ? Accidental(accidental!.value + 1) : accidental, ); @@ -97,7 +97,7 @@ class Note implements MusicItem, Comparable { final distance = _runSemitonesDistance( note, interval.semitones, - Accidentals.sharp, + Accidental.sharp, ); return distance < chromaticDivisions @@ -105,7 +105,7 @@ class Note implements MusicItem, Comparable { : _runSemitonesDistance( note, interval.inverted.semitones, - Accidentals.flat, + Accidental.flat, ) * -1; } @@ -117,7 +117,7 @@ class Note implements MusicItem, Comparable { int _runSemitonesDistance( Note note, int semitones, - Accidentals preferredAccidental, + Accidental preferredAccidental, ) { var distance = 0; var currentPitch = this.semitones; @@ -173,12 +173,12 @@ class Note implements MusicItem, Comparable { /// ) == const Note(Notes.mi, Accidentals.flat) /// ``` /// ``` - Note transposeBy(int semitones, [Accidentals? preferredAccidental]) => + Note transposeBy(int semitones, [Accidental? preferredAccidental]) => Note.fromSemitones(this.semitones + semitones, preferredAccidental); @override String toString() => - note.name + (accidental != null ? ' ${accidental!.name}' : ''); + note.name + (accidental != null ? ' ${accidental!.symbol}' : ''); @override bool operator ==(Object other) => diff --git a/lib/src/classes/relative_tonalities.dart b/lib/src/classes/relative_tonalities.dart index 62283f8a..4c76dc0f 100644 --- a/lib/src/classes/relative_tonalities.dart +++ b/lib/src/classes/relative_tonalities.dart @@ -21,10 +21,10 @@ class RelativeTonalities implements Comparable { static int _itemsAccidentals(Set tonalities) => tonalities.first.accidentals; - /// Returns an [Accidentals] enum item of [tonalities]. + /// Returns the [Accidental] of [tonalities]. /// /// It is mainly used by [accidental] getter. - static Accidentals _itemsAccidental(Set tonalities) => + static Accidental _itemsAccidental(Set tonalities) => tonalities.first.accidental; /// Returns the number of accidentals of this [RelativeTonalities]. @@ -38,7 +38,7 @@ class RelativeTonalities implements Comparable { /// ``` int get accidentals => _itemsAccidentals(tonalities); - /// Returns an [Accidentals] enum item of this [RelativeTonalities]. + /// Returns the [Accidental] of this [RelativeTonalities]. /// /// Example: /// ```dart @@ -47,7 +47,7 @@ class RelativeTonalities implements Comparable { /// const Tonality(Note(Notes.ut), Modes.minor), /// }).accidental == Accidentals.flat /// ``` - Accidentals get accidental => _itemsAccidental(tonalities); + Accidental get accidental => _itemsAccidental(tonalities); @override String toString() => '$tonalities'; diff --git a/lib/src/classes/tonality.dart b/lib/src/classes/tonality.dart index 642dc1f6..53193c23 100644 --- a/lib/src/classes/tonality.dart +++ b/lib/src/classes/tonality.dart @@ -10,7 +10,7 @@ class Tonality implements Comparable { factory Tonality.fromAccidentals( int accidentals, Modes mode, [ - Accidentals? accidental, + Accidental? accidental, ]) => Tonality( Note.fromTonalityAccidentals(accidentals, mode, accidental), @@ -29,23 +29,23 @@ class Tonality implements Comparable { note, ).abs(); - /// Returns an [Accidentals] enum item of this [Tonality]’s key signature. + /// Returns the [Accidental] of this [Tonality]’s key signature. /// /// Examples: /// ```dart /// const Tonality(Note(Notes.mi), Modes.major).accidental - /// == Accidentals.sharp + /// == Accidental.sharp /// /// const Tonality(Note(Notes.fa), Modes.minor).accidental - /// == Accidentals.flat + /// == Accidental.flat /// ``` - Accidentals get accidental => exactFifthsDistance( + Accidental get accidental => exactFifthsDistance( Tonality.fromAccidentals(0, mode).note, note, ) > 0 - ? Accidentals.sharp - : Accidentals.flat; + ? Accidental.sharp + : Accidental.flat; /// Returns the [Modes.major] or [Modes.minor] relative [Tonality] /// of this [Tonality]. diff --git a/lib/src/enums/accidentals.dart b/lib/src/enums/accidentals.dart deleted file mode 100644 index 774018b1..00000000 --- a/lib/src/enums/accidentals.dart +++ /dev/null @@ -1,106 +0,0 @@ -part of '../../music_notes.dart'; - -enum Accidentals { - tripleSharp, - doubleSharp, - sharp, - natural, - flat, - doubleFlat, - tripleFlat, -} - -extension AccidentalsValues on Accidentals { - static const accidentalsValues = { - Accidentals.tripleSharp: 3, - Accidentals.doubleSharp: 2, - Accidentals.sharp: 1, - Accidentals.natural: 0, - Accidentals.flat: -1, - Accidentals.doubleFlat: -2, - Accidentals.tripleFlat: -3, - }; - - static const accidentalsSymbols = { - Accidentals.tripleSharp: '♯𝄪', - Accidentals.doubleSharp: '𝄪', - Accidentals.sharp: '♯', - Accidentals.natural: '♮', - Accidentals.flat: '♭', - Accidentals.doubleFlat: '𝄫', - Accidentals.tripleFlat: '♭𝄫', - }; - - /// Returns an [Accidentals] enum item that matches [value] - /// in [accidentalsValues], otherwise returns `null`. - /// - /// Examples: - /// ```dart - /// AccidentalsValues.fromValue(1) == Accidentals.sharp - /// AccidentalsValues.fromValue(-2) == Accidentals.doubleFlat - /// AccidentalsValues.fromValue(3) == Accidentals.tripleSharp - /// ``` - static Accidentals? fromValue(int value) => - accidentalsValues.keys.firstWhereOrNull( - (accidental) => - chromaticMod(value + 3) - 3 == accidentalsValues[accidental], - ); - - /// Returns the value of this [Accidentals] enum item in [accidentalsValues]. - /// - /// Examples: - /// ```dart - /// Accidentals.flat.value == -1 - /// Accidentals.doubleSharp.value == 2 - /// ``` - int get value => accidentalsValues[this]!; - - /// Returns the symbol of this [Accidentals] enum item. - /// - /// Examples: - /// ```dart - /// Accidentals.flat.symbol == '♭' - /// Accidentals.doubleSharp.symbol == '𝄪' - /// ``` - String get symbol => accidentalsSymbols[this]!; - - /// Returns the incremented [Accidentals] enum item of this. - /// - /// Examples: - /// ```dart - /// Accidentals.doubleFlat.incremented == Accidentals.tripleFlat - /// Accidentals.sharp.incremented == Accidentals.doubleSharp - /// ``` - Accidentals get incremented => increment(1)!; - - /// Returns the decremented [Accidentals] enum item of this. - /// - /// Examples: - /// ```dart - /// Accidentals.doubleFlat.decremented == Accidentals.flat - /// Accidentals.sharp.decremented == Accidentals.natural - /// ``` - Accidentals get decremented => decrement(1)!; - - /// Returns the incremented [Accidentals] enum item of this by [n]. - /// - /// Examples: - /// ```dart - /// Accidentals.flat.increment(2) == Accidentals.tripleFlat - /// Accidentals.sharp.increment(1) == Accidentals.doubleSharp - /// ``` - Accidentals? increment(int n) => - fromValue((value.abs() + n) * (value > 0 ? 1 : -1)); - - /// Returns the decremented [Accidentals] enum item of this by [n]. - /// - /// It is an alias for `increment(-n)`. - /// - /// Examples: - /// ```dart - /// Accidentals.flat.decrement(2) == Accidentals.sharp - /// Accidentals.sharp.decrement(1) == Accidentals.natural - /// Accidentals.doubleFlat.decrement(4) == Accidentals.doubleSharp - /// ``` - Accidentals? decrement(int n) => increment(-n); -} diff --git a/lib/src/note/accidental.dart b/lib/src/note/accidental.dart new file mode 100644 index 00000000..787b2a43 --- /dev/null +++ b/lib/src/note/accidental.dart @@ -0,0 +1,49 @@ +part of '../../music_notes.dart'; + +/// An accidental. +class Accidental { + /// The value representing this [Accidental]: + /// + /// - `> 0` for sharps. + /// - `== 0` for natural. + /// - `< 0` for flats. + final int value; + + /// Creates a new [Accidental] from a [value]. + const Accidental(this.value); + + static const Accidental tripleSharp = Accidental(3); + static const Accidental doubleSharp = Accidental(2); + static const Accidental sharp = Accidental(1); + static const Accidental natural = Accidental(0); + static const Accidental flat = Accidental(-1); + static const Accidental doubleFlat = Accidental(-2); + static const Accidental tripleFlat = Accidental(-3); + + static const String doubleSharpSymbol = '𝄪'; + static const String sharpSymbol = '♯'; + static const String naturalSymbol = '♮'; + static const String flatSymbol = '♭'; + static const String doubleFlatSymbol = '𝄫'; + + /// Returns the symbol of this [Accidental]. + /// + /// Examples: + /// ```dart + /// assert(Accidental.flat.symbol == '♭') + /// assert(Accidental.doubleSharp.symbol == '𝄪') + /// ``` + String get symbol { + if (value == 0) return naturalSymbol; + + return (value.isOdd ? (value.isNegative ? flatSymbol : sharpSymbol) : '') + + (value.isNegative ? doubleFlatSymbol : doubleSharpSymbol) * + (value.abs() ~/ 2); + } + + @override + bool operator ==(Object other) => other is Accidental && value == other.value; + + @override + int get hashCode => value.hashCode; +} diff --git a/test/interval_test.dart b/test/interval_test.dart index cf0c4364..ee80f7ef 100644 --- a/test/interval_test.dart +++ b/test/interval_test.dart @@ -25,7 +25,7 @@ void main() { ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.ut, Accidentals.sharp)), + .exactInterval(const Note(Notes.ut, Accidental.sharp)), equals(const Interval(baseInterval, Qualities.augmented)), ); }); @@ -34,12 +34,12 @@ void main() { const baseInterval = Intervals.second; expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.re, Accidentals.doubleFlat)), + .exactInterval(const Note(Notes.re, Accidental.doubleFlat)), equals(const Interval(baseInterval, Qualities.diminished)), ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.re, Accidentals.flat)), + .exactInterval(const Note(Notes.re, Accidental.flat)), equals(const Interval(baseInterval, Qualities.minor)), ); expect( @@ -48,7 +48,7 @@ void main() { ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.re, Accidentals.sharp)), + .exactInterval(const Note(Notes.re, Accidental.sharp)), equals(const Interval(baseInterval, Qualities.augmented)), ); }); @@ -57,12 +57,12 @@ void main() { const baseInterval = Intervals.third; expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.mi, Accidentals.doubleFlat)), + .exactInterval(const Note(Notes.mi, Accidental.doubleFlat)), equals(const Interval(baseInterval, Qualities.diminished)), ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.mi, Accidentals.flat)), + .exactInterval(const Note(Notes.mi, Accidental.flat)), equals(const Interval(baseInterval, Qualities.minor)), ); expect( @@ -71,7 +71,7 @@ void main() { ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.mi, Accidentals.sharp)), + .exactInterval(const Note(Notes.mi, Accidental.sharp)), equals(const Interval(baseInterval, Qualities.augmented)), ); }); @@ -80,7 +80,7 @@ void main() { const baseInterval = Intervals.fourth; expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.fa, Accidentals.flat)), + .exactInterval(const Note(Notes.fa, Accidental.flat)), equals(const Interval(baseInterval, Qualities.diminished)), ); expect( @@ -89,7 +89,7 @@ void main() { ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.fa, Accidentals.sharp)), + .exactInterval(const Note(Notes.fa, Accidental.sharp)), equals(const Interval(baseInterval, Qualities.augmented)), ); }); @@ -98,7 +98,7 @@ void main() { const baseInterval = Intervals.fifth; expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.sol, Accidentals.flat)), + .exactInterval(const Note(Notes.sol, Accidental.flat)), equals(const Interval(baseInterval, Qualities.diminished)), ); expect( @@ -107,7 +107,7 @@ void main() { ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.sol, Accidentals.sharp)), + .exactInterval(const Note(Notes.sol, Accidental.sharp)), equals(const Interval(baseInterval, Qualities.augmented)), ); }); @@ -116,12 +116,12 @@ void main() { const baseInterval = Intervals.sixth; expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.la, Accidentals.doubleFlat)), + .exactInterval(const Note(Notes.la, Accidental.doubleFlat)), equals(const Interval(baseInterval, Qualities.diminished)), ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.la, Accidentals.flat)), + .exactInterval(const Note(Notes.la, Accidental.flat)), equals(const Interval(baseInterval, Qualities.minor)), ); expect( @@ -130,7 +130,7 @@ void main() { ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.la, Accidentals.sharp)), + .exactInterval(const Note(Notes.la, Accidental.sharp)), equals(const Interval(baseInterval, Qualities.augmented)), ); }); @@ -139,12 +139,12 @@ void main() { const baseInterval = Intervals.seventh; expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.si, Accidentals.doubleFlat)), + .exactInterval(const Note(Notes.si, Accidental.doubleFlat)), equals(const Interval(baseInterval, Qualities.diminished)), ); expect( const Note(Notes.ut) - .exactInterval(const Note(Notes.si, Accidentals.flat)), + .exactInterval(const Note(Notes.si, Accidental.flat)), equals(const Interval(baseInterval, Qualities.minor)), ); expect( diff --git a/test/music_test.dart b/test/music_test.dart index 088c4028..653ecead 100644 --- a/test/music_test.dart +++ b/test/music_test.dart @@ -10,15 +10,15 @@ void main() { const Note(Notes.ut), }), EnharmonicNote({ - const Note(Notes.ut, Accidentals.sharp), - const Note(Notes.re, Accidentals.flat), + const Note(Notes.ut, Accidental.sharp), + const Note(Notes.re, Accidental.flat), }), EnharmonicNote({ const Note(Notes.re), }), EnharmonicNote({ - const Note(Notes.re, Accidentals.sharp), - const Note(Notes.mi, Accidentals.flat), + const Note(Notes.re, Accidental.sharp), + const Note(Notes.mi, Accidental.flat), }), EnharmonicNote({ const Note(Notes.mi), @@ -27,22 +27,22 @@ void main() { const Note(Notes.fa), }), EnharmonicNote({ - const Note(Notes.fa, Accidentals.sharp), - const Note(Notes.sol, Accidentals.flat), + const Note(Notes.fa, Accidental.sharp), + const Note(Notes.sol, Accidental.flat), }), EnharmonicNote({ const Note(Notes.sol), }), EnharmonicNote({ - const Note(Notes.sol, Accidentals.sharp), - const Note(Notes.la, Accidentals.flat), + const Note(Notes.sol, Accidental.sharp), + const Note(Notes.la, Accidental.flat), }), EnharmonicNote({ const Note(Notes.la), }), EnharmonicNote({ - const Note(Notes.la, Accidentals.sharp), - const Note(Notes.si, Accidentals.flat), + const Note(Notes.la, Accidental.sharp), + const Note(Notes.si, Accidental.flat), }), EnharmonicNote({ const Note(Notes.si), diff --git a/test/note/accidental_test.dart b/test/note/accidental_test.dart new file mode 100644 index 00000000..40d63405 --- /dev/null +++ b/test/note/accidental_test.dart @@ -0,0 +1,46 @@ +// ignore_for_file: use_named_constants + +import 'package:music_notes/music_notes.dart'; +import 'package:test/test.dart'; + +void main() { + group('Accidental', () { + group('constructor', () { + test('should create a new Accidental from static constants', () { + expect(Accidental.tripleSharp, const Accidental(3)); + expect(Accidental.doubleSharp, const Accidental(2)); + expect(Accidental.sharp, const Accidental(1)); + expect(Accidental.natural, const Accidental(0)); + expect(Accidental.flat, const Accidental(-1)); + expect(Accidental.doubleFlat, const Accidental(-2)); + expect(Accidental.tripleFlat, const Accidental(-3)); + }); + }); + + group('.symbol', () { + test('should return the symbol string of this Accidental', () { + expect(const Accidental(5).symbol, '♯𝄪𝄪'); + expect(const Accidental(4).symbol, '𝄪𝄪'); + expect(Accidental.tripleSharp.symbol, '♯𝄪'); + expect(Accidental.doubleSharp.symbol, '𝄪'); + expect(Accidental.sharp.symbol, '♯'); + expect(Accidental.natural.symbol, '♮'); + expect(Accidental.flat.symbol, '♭'); + expect(Accidental.doubleFlat.symbol, '𝄫'); + expect(Accidental.tripleFlat.symbol, '♭𝄫'); + expect(const Accidental(-4).symbol, '𝄫𝄫'); + expect(const Accidental(-5).symbol, '♭𝄫𝄫'); + }); + }); + + group('.hashCode', () { + test('should ignore equal Accidental instances in a Set', () { + final collection = { + Accidental.natural, + Accidental.flat, + }..add(Accidental.natural); + expect(collection.length, 2); + }); + }); + }); +}