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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: 馃挜 make main entity collections unmodifiable #429

Merged
merged 1 commit into from
Mar 7, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,
]);
}