Skip to content

Commit

Permalink
feat!: 💥 make main entity collections unmodifiable
Browse files Browse the repository at this point in the history
  • Loading branch information
albertms10 committed Mar 7, 2024
1 parent 613a8af commit 25ca153
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 105 deletions.
25 changes: 14 additions & 11 deletions lib/src/harmony/chord.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -21,14 +22,16 @@ import 'chord_pattern.dart';
class Chord<T extends Scalable<T>>
with Chordable<Chord<T>>
implements Transposable<Chord<T>> {
final List<T> _items;

/// The [Scalable] items this [Chord] is built of.
final List<T> items;
List<T> 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].
///
Expand All @@ -45,15 +48,15 @@ class Chord<T extends Scalable<T>>
/// == ChordPattern.majorTriad.add7().add9()
/// ```
ChordPattern get pattern =>
ChordPattern.fromIntervalSteps(items.intervalSteps);
ChordPattern.fromIntervalSteps(_items.intervalSteps);

/// The modifier [T]s from the root note.
///
/// Example:
/// ```dart
/// Note.a.majorTriad.add7().add9().modifiers == const [Note.g, Note.b]
/// ```
List<T> get modifiers => items.skip(3).toList();
List<T> get modifiers => _items.skip(3).toList();

/// Returns a new [Chord] with an [ImperfectQuality.diminished] root triad.
///
Expand Down Expand Up @@ -117,15 +120,15 @@ class Chord<T extends Scalable<T>>
/// ```
@override
Chord<T> 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<T> && ListEquality<T>().equals(items, other.items);
other is Chord<T> && ListEquality<T>().equals(_items, other._items);

@override
int get hashCode => Object.hashAll(items);
int get hashCode => Object.hashAll(_items);
}
32 changes: 17 additions & 15 deletions lib/src/harmony/chord_pattern.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -16,11 +16,13 @@ import 'chord.dart';
/// * [Interval].
@immutable
class ChordPattern with Chordable<ChordPattern> {
final List<Interval> _intervals;

/// The intervals from the root note.
final List<Interval> intervals;
List<Interval> 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]);
Expand Down Expand Up @@ -77,7 +79,7 @@ class ChordPattern with Chordable<ChordPattern> {
/// == const Chord([Note.c, Note.e, Note.g])
/// ```
Chord<T> on<T extends Scalable<T>>(T scalable) => Chord(
intervals.fold(
_intervals.fold(
[scalable],
(chordItems, interval) =>
[...chordItems, scalable.transposeBy(interval)],
Expand All @@ -90,7 +92,7 @@ class ChordPattern with Chordable<ChordPattern> {
/// ```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;
Expand All @@ -111,7 +113,7 @@ class ChordPattern with Chordable<ChordPattern> {
/// ChordPattern.majorTriad.add7().add9().modifiers
/// == const [Interval.m7, Interval.M9]
/// ```
List<Interval> get modifiers => intervals.skip(2).toList();
List<Interval> get modifiers => _intervals.skip(2).toList();

/// Returns a new [ChordPattern] with an [ImperfectQuality.diminished] root
/// triad.
Expand All @@ -123,7 +125,7 @@ class ChordPattern with Chordable<ChordPattern> {
/// ```
@override
ChordPattern get diminished =>
ChordPattern([...diminishedTriad.intervals, ...modifiers]);
ChordPattern([...diminishedTriad._intervals, ...modifiers]);

/// Returns a new [ChordPattern] with an [ImperfectQuality.minor] root
/// triad.
Expand All @@ -135,7 +137,7 @@ class ChordPattern with Chordable<ChordPattern> {
/// ```
@override
ChordPattern get minor =>
ChordPattern([...minorTriad.intervals, ...modifiers]);
ChordPattern([...minorTriad._intervals, ...modifiers]);

/// Returns a new [ChordPattern] with an [ImperfectQuality.major] root
/// triad.
Expand All @@ -147,7 +149,7 @@ class ChordPattern with Chordable<ChordPattern> {
/// ```
@override
ChordPattern get major =>
ChordPattern([...majorTriad.intervals, ...modifiers]);
ChordPattern([...majorTriad._intervals, ...modifiers]);

/// Returns a new [ChordPattern] with an [ImperfectQuality.augmented] root
/// triad.
Expand All @@ -159,13 +161,13 @@ class ChordPattern with Chordable<ChordPattern> {
/// ```
@override
ChordPattern get augmented =>
ChordPattern([...augmentedTriad.intervals, ...modifiers]);
ChordPattern([...augmentedTriad._intervals, ...modifiers]);

/// Returns a new [ChordPattern] adding [interval].
@override
ChordPattern add(Interval interval, {Set<int>? replaceSizes}) {
final sizesToReplace = [interval.size, ...?replaceSizes];
final filteredIntervals = intervals.whereNot(
final filteredIntervals = _intervals.whereNot(
(chordInterval) => sizesToReplace.contains(chordInterval.size),
);

Expand All @@ -189,13 +191,13 @@ class ChordPattern with Chordable<ChordPattern> {
}

@override
String toString() => '$abbreviation (${intervals.join(' ')})';
String toString() => '$abbreviation (${_intervals.join(' ')})';

@override
bool operator ==(Object other) =>
other is ChordPattern &&
const ListEquality<Interval>().equals(intervals, other.intervals);
const ListEquality<Interval>().equals(_intervals, other._intervals);

@override
int get hashCode => Object.hashAll(intervals);
int get hashCode => Object.hashAll(_intervals);
}
19 changes: 11 additions & 8 deletions lib/src/harmony/harmonic_function.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -13,11 +14,13 @@ import '../scale/scale_degree.dart';
/// * [ScaleDegree].
@immutable
class HarmonicFunction {
final List<ScaleDegree> _scaleDegrees;

/// The scale degrees that define this [HarmonicFunction].
final List<ScaleDegree> scaleDegrees;
List<ScaleDegree> 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]);
Expand Down Expand Up @@ -59,7 +62,7 @@ class HarmonicFunction {
String toString({
ScaleDegreeNotation system = ScaleDegreeNotation.standard,
}) =>
scaleDegrees
_scaleDegrees
.map((scaleDegree) => scaleDegree.toString(system: system))
.join('/');

Expand All @@ -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<ScaleDegree>()
.equals(scaleDegrees, other.scaleDegrees);
.equals(_scaleDegrees, other._scaleDegrees);

@override
int get hashCode => Object.hashAll(scaleDegrees);
int get hashCode => Object.hashAll(_scaleDegrees);
}
42 changes: 26 additions & 16 deletions lib/src/key/key_signature.dart
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -18,12 +23,14 @@ import 'mode.dart';
/// * [Key].
@immutable
final class KeySignature implements Comparable<KeySignature> {
final List<Note> _notes;

/// The set of [Note] that define this [KeySignature], which may include
/// cancellation [Accidental.natural]s.
final List<Note> notes;
List<Note> 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([]);
Expand Down Expand Up @@ -62,7 +69,7 @@ final class KeySignature implements Comparable<KeySignature> {
/// 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.
///
Expand All @@ -74,7 +81,7 @@ final class KeySignature implements Comparable<KeySignature> {
/// == 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].
///
Expand All @@ -88,7 +95,7 @@ final class KeySignature implements Comparable<KeySignature> {
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;
Expand Down Expand Up @@ -129,12 +136,15 @@ final class KeySignature implements Comparable<KeySignature> {
/// ```
Map<TonalMode, Key> 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);
Expand All @@ -161,7 +171,7 @@ final class KeySignature implements Comparable<KeySignature> {
}

@override
String toString() => '$distance (${notes.map(
String toString() => '$distance (${_notes.map(
(note) => note.toString(system: _noteNotation),
).join(' ')})';

Expand All @@ -181,28 +191,28 @@ final class KeySignature implements Comparable<KeySignature> {
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<Note>().equals(notes, other.notes);
const ListEquality<Note>().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,
]);
}
Loading

0 comments on commit 25ca153

Please sign in to comment.