From d58b8002b42494feeba12641fc0c8b85b00ff8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Ma=C3=B1osa?= Date: Thu, 7 Mar 2024 21:47:41 +0100 Subject: [PATCH] =?UTF-8?q?feat!:=20=F0=9F=92=A5=20make=20main=20entity=20?= =?UTF-8?q?collections=20unmodifiable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/harmony/chord.dart | 25 ++++++----- lib/src/harmony/chord_pattern.dart | 32 ++++++------- lib/src/harmony/harmonic_function.dart | 19 ++++---- lib/src/key/key_signature.dart | 42 ++++++++++------- lib/src/scale/scale.dart | 47 +++++++++++--------- lib/src/scale/scale_pattern.dart | 41 +++++++++-------- lib/src/tuning/equal_temperament.dart | 25 ++++++----- test/src/harmony/chord_pattern_test.dart | 11 +++++ test/src/harmony/chord_test.dart | 11 +++++ test/src/harmony/harmonic_function_test.dart | 11 +++++ test/src/key/key_signature_test.dart | 19 +++++++- test/src/note/base_note_test.dart | 2 +- test/src/scale/scale_pattern_test.dart | 15 +++++++ test/src/scale/scale_test.dart | 15 +++++++ test/src/tuning/equal_temperament_test.dart | 19 ++++++-- 15 files changed, 228 insertions(+), 106 deletions(-) diff --git a/lib/src/harmony/chord.dart b/lib/src/harmony/chord.dart index 328ce4dd..c8e17d3a 100644 --- a/lib/src/harmony/chord.dart +++ b/lib/src/harmony/chord.dart @@ -1,4 +1,5 @@ -import 'package:collection/collection.dart' show ListEquality; +import 'package:collection/collection.dart' + show ListEquality, UnmodifiableListView; import 'package:meta/meta.dart' show immutable; import '../chordable.dart'; @@ -21,14 +22,16 @@ import 'chord_pattern.dart'; class Chord> with Chordable> implements Transposable> { + final List _items; + /// The [Scalable] items this [Chord] is built of. - final List items; + List get items => UnmodifiableListView(_items); - /// Creates a new [Chord] from [items]. - const Chord(this.items); + /// Creates a new [Chord] from [_items]. + const Chord(this._items); /// The root [Scalable] of this [Chord]. - T get root => items.first; + T get root => _items.first; /// The [ChordPattern] for this [Chord]. /// @@ -45,7 +48,7 @@ class Chord> /// == ChordPattern.majorTriad.add7().add9() /// ``` ChordPattern get pattern => - ChordPattern.fromIntervalSteps(items.intervalSteps); + ChordPattern.fromIntervalSteps(_items.intervalSteps); /// The modifier [T]s from the root note. /// @@ -53,7 +56,7 @@ class Chord> /// ```dart /// Note.a.majorTriad.add7().add9().modifiers == const [Note.g, Note.b] /// ``` - List get modifiers => items.skip(3).toList(); + List get modifiers => _items.skip(3).toList(); /// Returns a new [Chord] with an [ImperfectQuality.diminished] root triad. /// @@ -117,15 +120,15 @@ class Chord> /// ``` @override Chord transposeBy(Interval interval) => - Chord(items.transposeBy(interval).toList()); + Chord(_items.transposeBy(interval).toList()); @override - String toString() => '$root ${pattern.abbreviation} (${items.join(' ')})'; + String toString() => '$root ${pattern.abbreviation} (${_items.join(' ')})'; @override bool operator ==(Object other) => - other is Chord && ListEquality().equals(items, other.items); + other is Chord && ListEquality().equals(_items, other._items); @override - int get hashCode => Object.hashAll(items); + int get hashCode => Object.hashAll(_items); } diff --git a/lib/src/harmony/chord_pattern.dart b/lib/src/harmony/chord_pattern.dart index c0bf71cc..5fe70bb2 100644 --- a/lib/src/harmony/chord_pattern.dart +++ b/lib/src/harmony/chord_pattern.dart @@ -1,5 +1,5 @@ import 'package:collection/collection.dart' - show IterableExtension, ListEquality; + show IterableExtension, ListEquality, UnmodifiableListView; import 'package:meta/meta.dart' show immutable; import '../chordable.dart'; @@ -16,11 +16,13 @@ import 'chord.dart'; /// * [Interval]. @immutable class ChordPattern with Chordable { + final List _intervals; + /// The intervals from the root note. - final List intervals; + List get intervals => UnmodifiableListView(_intervals); - /// Creates a new [ChordPattern] from [intervals]. - const ChordPattern(this.intervals); + /// Creates a new [ChordPattern] from [_intervals]. + const ChordPattern(this._intervals); /// A diminished triad [ChordPattern]. static const diminishedTriad = ChordPattern([Interval.m3, Interval.d5]); @@ -77,7 +79,7 @@ class ChordPattern with Chordable { /// == const Chord([Note.c, Note.e, Note.g]) /// ``` Chord on>(T scalable) => Chord( - intervals.fold( + _intervals.fold( [scalable], (chordItems, interval) => [...chordItems, scalable.transposeBy(interval)], @@ -90,7 +92,7 @@ class ChordPattern with Chordable { /// ```dart /// ChordPattern.majorTriad.add7().add9().rootTriad == ChordPattern.majorTriad /// ``` - ChordPattern get rootTriad => ChordPattern(intervals.sublist(0, 2)); + ChordPattern get rootTriad => ChordPattern(_intervals.sublist(0, 2)); /// Whether this [ChordPattern] is [ImperfectQuality.diminished]. bool get isDiminished => rootTriad == diminishedTriad; @@ -111,7 +113,7 @@ class ChordPattern with Chordable { /// ChordPattern.majorTriad.add7().add9().modifiers /// == const [Interval.m7, Interval.M9] /// ``` - List get modifiers => intervals.skip(2).toList(); + List get modifiers => _intervals.skip(2).toList(); /// Returns a new [ChordPattern] with an [ImperfectQuality.diminished] root /// triad. @@ -123,7 +125,7 @@ class ChordPattern with Chordable { /// ``` @override ChordPattern get diminished => - ChordPattern([...diminishedTriad.intervals, ...modifiers]); + ChordPattern([...diminishedTriad._intervals, ...modifiers]); /// Returns a new [ChordPattern] with an [ImperfectQuality.minor] root /// triad. @@ -135,7 +137,7 @@ class ChordPattern with Chordable { /// ``` @override ChordPattern get minor => - ChordPattern([...minorTriad.intervals, ...modifiers]); + ChordPattern([...minorTriad._intervals, ...modifiers]); /// Returns a new [ChordPattern] with an [ImperfectQuality.major] root /// triad. @@ -147,7 +149,7 @@ class ChordPattern with Chordable { /// ``` @override ChordPattern get major => - ChordPattern([...majorTriad.intervals, ...modifiers]); + ChordPattern([...majorTriad._intervals, ...modifiers]); /// Returns a new [ChordPattern] with an [ImperfectQuality.augmented] root /// triad. @@ -159,13 +161,13 @@ class ChordPattern with Chordable { /// ``` @override ChordPattern get augmented => - ChordPattern([...augmentedTriad.intervals, ...modifiers]); + ChordPattern([...augmentedTriad._intervals, ...modifiers]); /// Returns a new [ChordPattern] adding [interval]. @override ChordPattern add(Interval interval, {Set? replaceSizes}) { final sizesToReplace = [interval.size, ...?replaceSizes]; - final filteredIntervals = intervals.whereNot( + final filteredIntervals = _intervals.whereNot( (chordInterval) => sizesToReplace.contains(chordInterval.size), ); @@ -189,13 +191,13 @@ class ChordPattern with Chordable { } @override - String toString() => '$abbreviation (${intervals.join(' ')})'; + String toString() => '$abbreviation (${_intervals.join(' ')})'; @override bool operator ==(Object other) => other is ChordPattern && - const ListEquality().equals(intervals, other.intervals); + const ListEquality().equals(_intervals, other._intervals); @override - int get hashCode => Object.hashAll(intervals); + int get hashCode => Object.hashAll(_intervals); } diff --git a/lib/src/harmony/harmonic_function.dart b/lib/src/harmony/harmonic_function.dart index 039fcb8b..455e515d 100644 --- a/lib/src/harmony/harmonic_function.dart +++ b/lib/src/harmony/harmonic_function.dart @@ -1,4 +1,5 @@ -import 'package:collection/collection.dart' show ListEquality; +import 'package:collection/collection.dart' + show ListEquality, UnmodifiableListView; import 'package:meta/meta.dart' show immutable; import '../interval/quality.dart'; @@ -13,11 +14,13 @@ import '../scale/scale_degree.dart'; /// * [ScaleDegree]. @immutable class HarmonicFunction { + final List _scaleDegrees; + /// The scale degrees that define this [HarmonicFunction]. - final List scaleDegrees; + List get scaleDegrees => UnmodifiableListView(_scaleDegrees); - /// Creates a new [HarmonicFunction] from [scaleDegrees]. - const HarmonicFunction(this.scaleDegrees); + /// Creates a new [HarmonicFunction] from [_scaleDegrees]. + const HarmonicFunction(this._scaleDegrees); /// A I (tonic) degree [HarmonicFunction]. static const i = HarmonicFunction([ScaleDegree.i]); @@ -59,7 +62,7 @@ class HarmonicFunction { String toString({ ScaleDegreeNotation system = ScaleDegreeNotation.standard, }) => - scaleDegrees + _scaleDegrees .map((scaleDegree) => scaleDegree.toString(system: system)) .join('/'); @@ -76,14 +79,14 @@ class HarmonicFunction { /// ]) /// ``` HarmonicFunction operator /(HarmonicFunction other) => - HarmonicFunction([...scaleDegrees, ...other.scaleDegrees]); + HarmonicFunction([..._scaleDegrees, ...other._scaleDegrees]); @override bool operator ==(Object other) => other is HarmonicFunction && const ListEquality() - .equals(scaleDegrees, other.scaleDegrees); + .equals(_scaleDegrees, other._scaleDegrees); @override - int get hashCode => Object.hashAll(scaleDegrees); + int get hashCode => Object.hashAll(_scaleDegrees); } diff --git a/lib/src/key/key_signature.dart b/lib/src/key/key_signature.dart index 666a2db3..0f1c8529 100644 --- a/lib/src/key/key_signature.dart +++ b/lib/src/key/key_signature.dart @@ -1,5 +1,10 @@ import 'package:collection/collection.dart' - show IterableEquality, IterableExtension, ListEquality; + show + IterableEquality, + IterableExtension, + ListEquality, + UnmodifiableListView, + UnmodifiableMapView; import 'package:meta/meta.dart' show immutable; import 'package:music_notes/utils.dart'; @@ -18,12 +23,14 @@ import 'mode.dart'; /// * [Key]. @immutable final class KeySignature implements Comparable { + final List _notes; + /// The set of [Note] that define this [KeySignature], which may include /// cancellation [Accidental.natural]s. - final List notes; + List get notes => UnmodifiableListView(_notes); - /// Creates a new [KeySignature] from [notes]. - const KeySignature(this.notes); + /// Creates a new [KeySignature] from [_notes]. + const KeySignature(this._notes); /// An empty [KeySignature]. static const empty = KeySignature([]); @@ -62,7 +69,7 @@ final class KeySignature implements Comparable { /// KeySignature.empty.accidental == Accidental.natural /// ``` Accidental get accidental => - clean.notes.firstOrNull?.accidental ?? Accidental.natural; + clean._notes.firstOrNull?.accidental ?? Accidental.natural; /// Returns a new [KeySignature] without cancellation [Accidental.natural]s. /// @@ -74,7 +81,7 @@ final class KeySignature implements Comparable { /// == KeySignature([Note.f.sharp, Note.c.sharp, Note.g.sharp]) /// ``` KeySignature get clean => - KeySignature(notes.where((note) => !note.accidental.isNatural).toList()); + KeySignature(_notes.where((note) => !note.accidental.isNatural).toList()); /// The fifths distance of this [KeySignature]. /// @@ -88,7 +95,7 @@ final class KeySignature implements Comparable { int? get distance { if (accidental.isNatural) return 0; - final cleanNotes = clean.notes; + final cleanNotes = clean._notes; final apparentDistance = cleanNotes.length * accidental.semitones.sign; final apparentFirstNote = accidental.isFlat ? _firstCanonicalFlatNote : _firstCanonicalSharpNote; @@ -129,12 +136,15 @@ final class KeySignature implements Comparable { /// ``` Map get keys { final distance = this.distance; - if (distance == null) return {}; + if (distance == null) return const {}; final rootNote = Interval.P5.circleFrom(Note.c, distance: distance).last; final major = rootNote.major; - return {TonalMode.major: major, TonalMode.minor: major.relative}; + return UnmodifiableMapView({ + TonalMode.major: major, + TonalMode.minor: major.relative, + }); } static const _noteNotation = EnglishNoteNotation(showNatural: true); @@ -161,7 +171,7 @@ final class KeySignature implements Comparable { } @override - String toString() => '$distance (${notes.map( + String toString() => '$distance (${_notes.map( (note) => note.toString(system: _noteNotation), ).join(' ')})'; @@ -181,28 +191,28 @@ final class KeySignature implements Comparable { if (this == empty) return other; final cancelledNotes = accidental == other.accidental - ? clean.notes.whereNot(other.notes.contains) - : clean.notes; + ? clean._notes.whereNot(other._notes.contains) + : clean._notes; return KeySignature([ ...cancelledNotes.map((note) => note.natural).toSet(), - ...other.notes, + ...other._notes, ]); } @override bool operator ==(Object other) => other is KeySignature && - const ListEquality().equals(notes, other.notes); + const ListEquality().equals(_notes, other._notes); @override - int get hashCode => Object.hash(notes, accidental); + int get hashCode => Object.hash(_notes, accidental); @override int compareTo(KeySignature other) => compareMultiple([ () => accidental.compareTo(other.accidental), () => - notes.length.compareTo(other.notes.length) * + _notes.length.compareTo(other._notes.length) * accidental.semitones.nonZeroSign, ]); } diff --git a/lib/src/scale/scale.dart b/lib/src/scale/scale.dart index e95d3217..f3633eb5 100644 --- a/lib/src/scale/scale.dart +++ b/lib/src/scale/scale.dart @@ -1,4 +1,5 @@ -import 'package:collection/collection.dart' show IterableEquality, ListEquality; +import 'package:collection/collection.dart' + show IterableEquality, ListEquality, UnmodifiableListView; import 'package:meta/meta.dart' show immutable; import '../harmony/chord.dart'; @@ -22,14 +23,22 @@ import 'scale_pattern.dart'; /// * [ScaleDegree]. @immutable class Scale> implements Transposable> { + final List _degrees; + /// The [Scalable] degrees that define this [Scale]. - final List degrees; + List get degrees => UnmodifiableListView(_degrees); - /// The descending [Scalable] degrees that define this [Scale] (if different). + /// The descending [Scalable] degrees that define this [Scale]. + /// If null, the result is the same as calling `_degrees.reversed`. final List? _descendingDegrees; - /// Creates a new [Scale] instance from [degrees]. - const Scale(this.degrees, [this._descendingDegrees]); + /// The descending [Scalable] degrees that define this [Scale]. + List get descendingDegrees => + UnmodifiableListView(_descendingDegrees ?? _degrees.reversed); + + /// Creates a new [Scale] instance from [_degrees] and optional + /// [_descendingDegrees]. + const Scale(this._degrees, [this._descendingDegrees]); /// The length of this [Scale]. /// @@ -40,11 +49,7 @@ class Scale> implements Transposable> { /// ScalePattern.octatonic.on(Note.d.flat).length == 8 /// ScalePattern.chromatic.on(Note.c).length == 12 /// ``` - int get length => degrees.length - 1; - - /// The descending [Scalable] degrees that define this [Scale]. - List get descendingDegrees => - _descendingDegrees ?? degrees.reversed.toList(); + int get length => _degrees.length - 1; /// The [ScalePattern] of this [Scale]. /// @@ -54,7 +59,7 @@ class Scale> implements Transposable> { /// Note.c]) == ScalePattern.major /// ``` ScalePattern get pattern => ScalePattern( - degrees.intervalSteps.toList(), + _degrees.intervalSteps.toList(), _descendingDegrees?.descendingIntervalSteps.toList(), ); @@ -67,7 +72,7 @@ class Scale> implements Transposable> { /// Note.a]) /// ``` Scale get reversed => - Scale(descendingDegrees, _descendingDegrees != null ? degrees : null); + Scale(descendingDegrees, _descendingDegrees != null ? _degrees : null); /// The [Chord] for each [ScaleDegree] of this [Scale]. /// @@ -83,8 +88,9 @@ class Scale> implements Transposable> { /// Note.g.sharp.diminishedTriad, /// ] /// ``` - List> get degreeChords => - [for (var i = 1; i < degrees.length; i++) degreeChord(ScaleDegree(i))]; + List> get degreeChords => [ + for (var i = 1; i < _degrees.length; i++) degreeChord(ScaleDegree(i)), + ]; /// The [T] for the [scaleDegree] of this [Scale]. /// @@ -95,7 +101,7 @@ class Scale> implements Transposable> { /// Note.a.flat.major.scale.degree(ScaleDegree.vi) == Note.f /// ``` T degree(ScaleDegree scaleDegree) { - final scalable = degrees[scaleDegree.ordinal - 1]; + final scalable = _degrees[scaleDegree.ordinal - 1]; if (scaleDegree.semitonesDelta == 0) return scalable; return scalable.transposeBy( @@ -150,7 +156,7 @@ class Scale> implements Transposable> { /// ``` bool isEnharmonicWith(Scale other) => const IterableEquality() - .equals(degrees.toClass(), other.degrees.toClass()) && + .equals(_degrees.toClass(), other._degrees.toClass()) && const IterableEquality().equals( (_descendingDegrees ?? const []).toClass(), (other._descendingDegrees ?? const []).toClass(), @@ -165,24 +171,25 @@ class Scale> implements Transposable> { /// ``` @override Scale transposeBy(Interval interval) => Scale( - degrees.transposeBy(interval).toList(), + _degrees.transposeBy(interval).toList(), _descendingDegrees?.transposeBy(interval).toList(), ); @override - String toString() => '${degrees.first} ${pattern.name} (${degrees.join(' ')}' + String toString() => + '${_degrees.first} ${pattern.name} (${_degrees.join(' ')}' '${_descendingDegrees != null ? ', ' '${_descendingDegrees.join(' ')}' : ''})'; @override bool operator ==(Object other) => other is Scale && - ListEquality().equals(degrees, other.degrees) && + ListEquality().equals(_degrees, other._degrees) && ListEquality().equals(_descendingDegrees, other._descendingDegrees); @override int get hashCode => Object.hash( - Object.hashAll(degrees), + Object.hashAll(_degrees), _descendingDegrees != null ? Object.hashAll(_descendingDegrees) : null, ); } diff --git a/lib/src/scale/scale_pattern.dart b/lib/src/scale/scale_pattern.dart index acbeda61..d623354e 100644 --- a/lib/src/scale/scale_pattern.dart +++ b/lib/src/scale/scale_pattern.dart @@ -1,4 +1,5 @@ -import 'package:collection/collection.dart' show IterableEquality; +import 'package:collection/collection.dart' + show IterableEquality, UnmodifiableListView; import 'package:meta/meta.dart' show immutable; import '../harmony/chord_pattern.dart'; @@ -17,16 +18,22 @@ import 'scale_degree.dart'; /// * [Scale]. @immutable final class ScalePattern { + final List _intervalSteps; + /// The interval steps that define this [ScalePattern]. - final List intervalSteps; + List get intervalSteps => UnmodifiableListView(_intervalSteps); - /// The descending interval steps that define this [ScalePattern] (if - /// different). + /// The descending interval steps that define this [ScalePattern]. + /// If null, the result is the same as calling `_intervalSteps.reversed`. final List? _descendingIntervalSteps; - /// Creates a new [ScalePattern] from [intervalSteps] and optional + /// The descending interval steps that define this [ScalePattern]. + List get descendingIntervalSteps => + UnmodifiableListView(_descendingIntervalSteps ?? _intervalSteps.reversed); + + /// Creates a new [ScalePattern] from [_intervalSteps] and optional /// [_descendingIntervalSteps]. - const ScalePattern(this.intervalSteps, [this._descendingIntervalSteps]); + const ScalePattern(this._intervalSteps, [this._descendingIntervalSteps]); /// ![C Ionian scale](https://upload.wikimedia.org/score/p/2/p2fun2296uif26uyy61yxjli7ocfq9d/p2fun229.png). static const ionian = ScalePattern([ @@ -253,10 +260,6 @@ final class ScalePattern { /// ``` int get length => degreePatterns.length; - /// The descending interval steps that define this [ScalePattern]. - List get descendingIntervalSteps => - _descendingIntervalSteps ?? intervalSteps.reversed.toList(); - /// The scale of notes starting from [scalable]. /// /// Example: @@ -274,7 +277,7 @@ final class ScalePattern { /// Note.c]) /// ``` Scale on>(T scalable) => Scale( - intervalSteps.fold( + _intervalSteps.fold( [scalable], (scale, interval) => [...scale, scale.last.transposeBy(interval)], ), @@ -300,7 +303,7 @@ final class ScalePattern { /// ``` ScalePattern get mirrored => ScalePattern( descendingIntervalSteps, - _descendingIntervalSteps != null ? intervalSteps : null, + _descendingIntervalSteps != null ? _intervalSteps : null, ); /// The [ChordPattern] for each scale degree in this [ScalePattern]. @@ -318,7 +321,7 @@ final class ScalePattern { /// ] /// ``` List get degreePatterns => [ - for (var i = 1; i <= intervalSteps.length; i++) + for (var i = 1; i <= _intervalSteps.length; i++) degreePattern(ScaleDegree(i)), ]; @@ -337,7 +340,7 @@ final class ScalePattern { return ChordPattern.fromQuality(scaleDegree.quality!); } - // Deduce the diatonic `ChordPattern` from this `Scale.intervalSteps`. + // Deduce the diatonic `ChordPattern` from this `intervalSteps`. return ChordPattern.fromIntervalSteps([ _addNextStepTo(scaleDegree.ordinal), _addNextStepTo(scaleDegree.ordinal + 2), @@ -345,7 +348,7 @@ final class ScalePattern { } Interval _stepFrom(int ordinal) => - intervalSteps[(ordinal - 1) % intervalSteps.length]; + _intervalSteps[(ordinal - 1) % _intervalSteps.length]; Interval _addNextStepTo(int ordinal) => _stepFrom(ordinal) + _stepFrom(ordinal + 1); @@ -360,7 +363,7 @@ final class ScalePattern { /// ``` bool isEnharmonicWith(ScalePattern other) => const IterableEquality() - .equals(intervalSteps.toClass(), other.intervalSteps.toClass()) && + .equals(_intervalSteps.toClass(), other._intervalSteps.toClass()) && const IterableEquality().equals( (_descendingIntervalSteps ?? const []).toClass(), (other._descendingIntervalSteps ?? const []).toClass(), @@ -398,20 +401,20 @@ final class ScalePattern { ? ', ${_descendingIntervalSteps.join(' ')}' : ''; - return '$name (${intervalSteps.join(' ')}$descendingSteps)'; + return '$name (${_intervalSteps.join(' ')}$descendingSteps)'; } @override bool operator ==(Object other) => other is ScalePattern && const IterableEquality() - .equals(intervalSteps, other.intervalSteps) && + .equals(_intervalSteps, other._intervalSteps) && const IterableEquality() .equals(_descendingIntervalSteps, other._descendingIntervalSteps); @override int get hashCode => Object.hash( - Object.hashAll(intervalSteps), + Object.hashAll(_intervalSteps), _descendingIntervalSteps != null ? Object.hashAll(_descendingIntervalSteps) : null, diff --git a/lib/src/tuning/equal_temperament.dart b/lib/src/tuning/equal_temperament.dart index 100018aa..8a186682 100644 --- a/lib/src/tuning/equal_temperament.dart +++ b/lib/src/tuning/equal_temperament.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; -import 'package:collection/collection.dart' show ListEquality; +import 'package:collection/collection.dart' + show ListEquality, UnmodifiableListView; import 'package:meta/meta.dart' show immutable; import 'package:music_notes/utils.dart'; @@ -20,29 +21,31 @@ import 'tuning_system.dart'; /// * [TuningSystem]. @immutable class EqualTemperament extends TuningSystem { + final List _steps; + /// The equal divisions between each [BaseNote] and the next one. - final List steps; + List get steps => UnmodifiableListView(_steps); - /// Creates a new [EqualTemperament] from [referencePitch] and [steps]. - const EqualTemperament({ - required this.steps, + /// Creates a new [EqualTemperament] from [_steps] and [referencePitch]. + const EqualTemperament( + this._steps, { super.referencePitch = _defaultReferencePitch, }); /// See [12 equal temperament](https://en.wikipedia.org/wiki/12_equal_temperament). const EqualTemperament.edo12({super.referencePitch = _defaultReferencePitch}) - : steps = const [2, 2, 1, 2, 2, 2, 1]; + : _steps = const [2, 2, 1, 2, 2, 2, 1]; /// See [19 equal temperament](https://en.wikipedia.org/wiki/19_equal_temperament). const EqualTemperament.edo19({super.referencePitch = _defaultReferencePitch}) - : steps = const [3, 3, 2, 3, 3, 3, 2]; + : _steps = const [3, 3, 2, 3, 3, 3, 2]; static const _defaultReferencePitch = Pitch(Note.a, octave: 4); /// The equal divisions of the octave of this [EqualTemperament]. /// /// See [EDO](https://en.xen.wiki/w/EDO). - int get edo => steps.reduce((value, element) => value + element); + int get edo => _steps.reduce((value, element) => value + element); /// The cents for each division step in this [EqualTemperament]. /// @@ -83,14 +86,14 @@ class EqualTemperament extends TuningSystem { Cent get generator => cents.closestTo(referenceGeneratorCents); @override - String toString() => 'EDO $edo (${steps.join(' ')})'; + String toString() => 'EDO $edo (${_steps.join(' ')})'; @override bool operator ==(Object other) => other is EqualTemperament && - const ListEquality().equals(steps, other.steps) && + const ListEquality().equals(_steps, other._steps) && referencePitch == other.referencePitch; @override - int get hashCode => Object.hash(Object.hashAll(steps), referencePitch); + int get hashCode => Object.hash(Object.hashAll(_steps), referencePitch); } diff --git a/test/src/harmony/chord_pattern_test.dart b/test/src/harmony/chord_pattern_test.dart index 152c3875..4d65784e 100644 --- a/test/src/harmony/chord_pattern_test.dart +++ b/test/src/harmony/chord_pattern_test.dart @@ -1,8 +1,19 @@ +import 'dart:collection' show UnmodifiableListView; + import 'package:music_notes/music_notes.dart'; import 'package:test/test.dart'; void main() { group('ChordPattern', () { + group('.intervals', () { + test('returns an unmodifiable collection', () { + expect( + ChordPattern.majorTriad.intervals, + isA>(), + ); + }); + }); + group('.fromIntervalSteps()', () { test('creates a new ChordPattern from interval steps', () { expect( diff --git a/test/src/harmony/chord_test.dart b/test/src/harmony/chord_test.dart index 246aa1b9..033ccc8b 100644 --- a/test/src/harmony/chord_test.dart +++ b/test/src/harmony/chord_test.dart @@ -1,8 +1,19 @@ +import 'dart:collection' show UnmodifiableListView; + import 'package:music_notes/music_notes.dart'; import 'package:test/test.dart'; void main() { group('Chord', () { + group('.items', () { + test('returns an unmodifiable collection', () { + expect( + ChordPattern.majorTriad.on(Note.c).items, + isA>(), + ); + }); + }); + group('.root', () { test('returns the root of this Chord', () { expect(ChordPattern.majorTriad.on(Note.f).root, Note.f); diff --git a/test/src/harmony/harmonic_function_test.dart b/test/src/harmony/harmonic_function_test.dart index 686c0bcc..613747fb 100644 --- a/test/src/harmony/harmonic_function_test.dart +++ b/test/src/harmony/harmonic_function_test.dart @@ -1,8 +1,19 @@ +import 'dart:collection' show UnmodifiableListView; + import 'package:music_notes/music_notes.dart'; import 'package:test/test.dart'; void main() { group('HarmonicFunction', () { + group('.scaleDegrees', () { + test('returns an unmodifiable collection', () { + expect( + HarmonicFunction.i.scaleDegrees, + isA>(), + ); + }); + }); + group('operator /()', () { test('returns the HarmonicFunction relating this to other', () { expect( diff --git a/test/src/key/key_signature_test.dart b/test/src/key/key_signature_test.dart index cf60af1e..bfb47982 100644 --- a/test/src/key/key_signature_test.dart +++ b/test/src/key/key_signature_test.dart @@ -1,10 +1,20 @@ -import 'dart:collection' show SplayTreeSet; +import 'dart:collection' + show SplayTreeSet, UnmodifiableListView, UnmodifiableMapView; import 'package:music_notes/music_notes.dart'; import 'package:test/test.dart'; void main() { group('KeySignature', () { + group('.notes', () { + test('returns an unmodifiable collection', () { + expect( + KeySignature([Note.f.sharp]).notes, + isA>(), + ); + }); + }); + group('.fromDistance()', () { test('creates a new KeySignature from the given distance', () { expect( @@ -122,6 +132,13 @@ void main() { }); group('.keys', () { + test('returns an unmodifiable collection', () { + expect( + KeySignature([Note.f.sharp]).keys, + isA>(), + ); + }); + test('returns the TonalMode to Keys Map for this KeySignature', () { expect( KeySignature.fromDistance(-10).keys, diff --git a/test/src/note/base_note_test.dart b/test/src/note/base_note_test.dart index cc1bb7af..8acb5865 100644 --- a/test/src/note/base_note_test.dart +++ b/test/src/note/base_note_test.dart @@ -1,4 +1,4 @@ -import 'dart:collection'; +import 'dart:collection' show SplayTreeSet; import 'package:music_notes/music_notes.dart'; import 'package:test/test.dart'; diff --git a/test/src/scale/scale_pattern_test.dart b/test/src/scale/scale_pattern_test.dart index 50fc20f8..3a0b1273 100644 --- a/test/src/scale/scale_pattern_test.dart +++ b/test/src/scale/scale_pattern_test.dart @@ -1,8 +1,23 @@ +import 'dart:collection' show UnmodifiableListView; + import 'package:music_notes/music_notes.dart'; import 'package:test/test.dart'; void main() { group('ScalePattern', () { + group('.intervalSteps', () { + test('returns an unmodifiable collection', () { + expect( + ScalePattern.aeolian.intervalSteps, + isA>(), + ); + expect( + ScalePattern.chromatic.descendingIntervalSteps, + isA>(), + ); + }); + }); + group('.fromChordPattern()', () { test('creates a new ScalePattern from the given ChordPattern', () { expect( diff --git a/test/src/scale/scale_test.dart b/test/src/scale/scale_test.dart index 766cd2cc..b606f175 100644 --- a/test/src/scale/scale_test.dart +++ b/test/src/scale/scale_test.dart @@ -1,8 +1,23 @@ +import 'dart:collection' show UnmodifiableListView; + import 'package:music_notes/music_notes.dart'; import 'package:test/test.dart'; void main() { group('Scale', () { + group('.degrees', () { + test('returns an unmodifiable collection', () { + expect( + ScalePattern.aeolian.on(Note.c).degrees, + isA>(), + ); + expect( + ScalePattern.melodicMinor.on(Note.c).descendingDegrees, + isA>(), + ); + }); + }); + group('.length', () { test('returns the length of this Scale', () { expect(ScalePattern.minorPentatonic.on(Note.f).length, 5); diff --git a/test/src/tuning/equal_temperament_test.dart b/test/src/tuning/equal_temperament_test.dart index 9b3c5c77..b039f04a 100644 --- a/test/src/tuning/equal_temperament_test.dart +++ b/test/src/tuning/equal_temperament_test.dart @@ -1,8 +1,19 @@ +import 'dart:collection' show UnmodifiableListView; + import 'package:music_notes/music_notes.dart'; import 'package:test/test.dart'; void main() { group('EqualTemperament', () { + group('.steps', () { + test('returns an unmodifiable collection', () { + expect( + const EqualTemperament.edo12().steps, + isA>(), + ); + }); + }); + group('.edo', () { test('returns the EDO for this EqualTemperament', () { expect(const EqualTemperament.edo12().edo, 12); @@ -91,9 +102,9 @@ void main() { test('returns the same hashCode for equal EqualTemperaments', () { expect( // ignore: prefer_const_constructors, prefer_const_literals_to_create_immutables - EqualTemperament(steps: [1, 2]).hashCode, + EqualTemperament([1, 2]).hashCode, // ignore: prefer_const_constructors, prefer_const_literals_to_create_immutables - EqualTemperament(steps: [1, 2]).hashCode, + EqualTemperament([1, 2]).hashCode, ); }); @@ -103,8 +114,8 @@ void main() { isNot(const EqualTemperament.edo19().hashCode), ); expect( - const EqualTemperament(steps: [1, 2]).hashCode, - isNot(const EqualTemperament(steps: [2, 1]).hashCode), + const EqualTemperament([1, 2]).hashCode, + isNot(const EqualTemperament([2, 1]).hashCode), ); }); });