• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

albertms10 / music_notes / 15889365663

25 Jun 2025 11:32PM UTC coverage: 97.841% (-2.2%) from 100.0%
15889365663

Pull #608

github

web-flow
Merge 542d1fa7c into 5122c56a3
Pull Request #608: refactor: ♻️ rename formatters to `*Notation`

152 of 181 new or added lines in 16 files covered. (83.98%)

3 existing lines in 1 file now uncovered.

1450 of 1482 relevant lines covered (97.84%)

2.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.96
/lib/src/note/base_note.dart
1
import 'package:collection/collection.dart' show IterableExtension;
2
import 'package:music_notes/utils.dart';
3

4
import '../interval/size.dart';
5
import '../music.dart';
6
import '../notation_system.dart';
7
import 'note.dart';
8

9
/// The base note names of the diatonic scale.
10
///
11
/// ---
12
/// See also:
13
/// * [Note].
14
enum BaseNote implements Comparable<BaseNote> {
15
  /// Note C.
16
  c(0),
17

18
  /// Note D.
19
  d(2),
20

21
  /// Note E.
22
  e(4),
23

24
  /// Note F.
25
  f(5),
26

27
  /// Note G.
28
  g(7),
29

30
  /// Note A.
31
  a(9),
32

33
  /// Note B.
34
  b(11);
35

36
  /// The number of semitones that identify this [BaseNote].
37
  final int semitones;
38

39
  /// Creates a new [BaseNote] from [semitones].
40
  const BaseNote(this.semitones);
41

42
  /// Returns a [BaseNote] that matches with [semitones] as in [BaseNote],
43
  /// otherwise returns `null`.
44
  ///
45
  /// Example:
46
  /// ```dart
47
  /// BaseNote.fromSemitones(2) == BaseNote.d
48
  /// BaseNote.fromSemitones(7) == BaseNote.g
49
  /// BaseNote.fromSemitones(10) == null
50
  /// ```
51
  static BaseNote? fromSemitones(int semitones) => values.firstWhereOrNull(
2✔
52
    (note) => semitones % chromaticDivisions == note.semitones,
4✔
53
  );
54

55
  /// Returns a [BaseNote] that matches with [ordinal].
56
  ///
57
  /// Example:
58
  /// ```dart
59
  /// BaseNote.fromOrdinal(3) == BaseNote.e
60
  /// BaseNote.fromOrdinal(7) == BaseNote.b
61
  /// BaseNote.fromOrdinal(10) == BaseNote.e
62
  /// ```
63
  factory BaseNote.fromOrdinal(int ordinal) =>
1✔
64
      values[ordinal.nonZeroMod(values.length) - 1];
4✔
65

66
  /// Parse [source] as a [BaseNote] and return its value.
67
  ///
68
  /// If the [source] string does not contain a valid [BaseNote], a
69
  /// [FormatException] is thrown.
70
  ///
71
  /// Example:
72
  /// ```dart
73
  /// BaseNote.parse('B') == BaseNote.b
74
  /// BaseNote.parse('a') == BaseNote.a
75
  /// BaseNote.parse('z') // throws a FormatException
76
  /// ```
77
  factory BaseNote.parse(String source) {
1✔
78
    try {
79
      return values.byName(source.toLowerCase());
2✔
80
    }
81
    // TODO(albertms10): find a better way to catch an invalid BaseNote.
82
    // ignore: avoid_catching_errors
83
    on ArgumentError catch (e, stackTrace) {
1✔
84
      Error.throwWithStackTrace(
1✔
85
        FormatException('Invalid BaseNote', source, 0),
1✔
86
        stackTrace,
87
      );
88
    }
89
  }
90

91
  /// The ordinal number of this [BaseNote].
92
  ///
93
  /// Example:
94
  /// ```dart
95
  /// BaseNote.c.ordinal == 1
96
  /// BaseNote.f.ordinal == 4
97
  /// BaseNote.b.ordinal == 7
98
  /// ```
99
  int get ordinal => values.indexOf(this) + 1;
3✔
100

101
  /// The [Size] that conforms between this [BaseNote] and [other].
102
  ///
103
  /// Example:
104
  /// ```dart
105
  /// BaseNote.d.intervalSize(BaseNote.f) == Size.third
106
  /// BaseNote.a.intervalSize(BaseNote.e) == Size.fifth
107
  /// BaseNote.d.intervalSize(BaseNote.c) == Size.seventh
108
  /// BaseNote.c.intervalSize(BaseNote.a) == Size.sixth
109
  /// ```
110
  Size intervalSize(BaseNote other) => Size(
2✔
111
    other.ordinal - ordinal + (ordinal > other.ordinal ? values.length : 0) + 1,
9✔
112
  );
113

114
  /// The difference in semitones between this [BaseNote] and [other].
115
  ///
116
  /// Example:
117
  /// ```dart
118
  /// BaseNote.c.difference(BaseNote.c) == 0
119
  /// BaseNote.c.difference(BaseNote.e) == 4
120
  /// BaseNote.f.difference(BaseNote.e) == -1
121
  /// BaseNote.a.difference(BaseNote.e) == -5
122
  /// ```
123
  int difference(BaseNote other) => Note(this).difference(Note(other));
4✔
124

125
  /// The positive difference in semitones between this [BaseNote] and [other].
126
  ///
127
  /// When [difference] would return a negative value, this method returns the
128
  /// difference with [other] being in the next octave.
129
  ///
130
  /// Example:
131
  /// ```dart
132
  /// BaseNote.c.positiveDifference(BaseNote.c) == 0
133
  /// BaseNote.c.positiveDifference(BaseNote.e) == 4
134
  /// BaseNote.f.positiveDifference(BaseNote.e) == 11
135
  /// BaseNote.a.positiveDifference(BaseNote.e) == 7
136
  /// ```
137
  int positiveDifference(BaseNote other) {
1✔
138
    final diff = difference(other);
1✔
139

140
    return diff.isNegative ? diff + chromaticDivisions : diff;
2✔
141
  }
142

143
  /// Transposes this [BaseNote] by interval [size].
144
  ///
145
  /// Example:
146
  /// ```dart
147
  /// BaseNote.g.transposeBySize(Size.unison) == BaseNote.g
148
  /// BaseNote.g.transposeBySize(Size.fifth) == BaseNote.d
149
  /// BaseNote.a.transposeBySize(-Size.third) == BaseNote.f
150
  /// ```
151
  BaseNote transposeBySize(Size size) =>
1✔
152
      BaseNote.fromOrdinal(ordinal + size.incrementBy(-1));
5✔
153

154
  /// The next ordinal [BaseNote].
155
  ///
156
  /// Example:
157
  /// ```dart
158
  /// BaseNote.c.next == BaseNote.d
159
  /// BaseNote.f.next == BaseNote.a
160
  /// BaseNote.b.next == BaseNote.c
161
  /// ```
162
  BaseNote get next => transposeBySize(Size.second);
2✔
163

164
  /// The previous ordinal [BaseNote].
165
  ///
166
  /// Example:
167
  /// ```dart
168
  /// BaseNote.e.previous == BaseNote.d
169
  /// BaseNote.g.previous == BaseNote.f
170
  /// BaseNote.c.previous == BaseNote.b
171
  /// ```
172
  BaseNote get previous => transposeBySize(-Size.second);
3✔
173

174
  /// The string representation of this [BaseNote] based on [formatter].
175
  @override
1✔
176
  String toString({
177
    Formatter<BaseNote> formatter = const EnglishBaseNoteNotation(),
178
  }) => formatter.format(this);
1✔
179

180
  @override
1✔
181
  int compareTo(BaseNote other) => semitones.compareTo(other.semitones);
3✔
182
}
183

184
/// The English notation system for [BaseNote
185
final class EnglishBaseNoteNotation extends NotationSystem<BaseNote> {
186
  /// Creates a new [EnglishBaseNoteNotation].
187
  const EnglishBaseNoteNotation();
2✔
188

189
  @override
1✔
190
  String format(BaseNote baseNote) => baseNote.name.toUpperCase();
2✔
191

NEW
192
  @override
×
193
  BaseNote parse(String source) {
NEW
194
    throw UnimplementedError();
×
195
  }
196
}
197

198
/// The German notation system for [BaseNote
199
final class GermanBaseNoteNotation extends NotationSystem<BaseNote> {
200
  /// Creates a new [GermanBaseNoteNotation].
201
  const GermanBaseNoteNotation();
1✔
202

203
  @override
1✔
204
  String format(BaseNote baseNote) => switch (baseNote) {
205
    BaseNote.b => 'H',
1✔
206
    BaseNote(:final name) => name.toUpperCase(),
2✔
207
  };
208

NEW
209
  @override
×
210
  BaseNote parse(String source) {
NEW
211
    throw UnimplementedError();
×
212
  }
213
}
214

215
/// The Romance notation system for [BaseNote
216
final class RomanceBaseNoteNotation extends NotationSystem<BaseNote> {
217
  /// Creates a new [RomanceBaseNoteNotation].
218
  const RomanceBaseNoteNotation();
1✔
219

220
  @override
1✔
221
  String format(BaseNote baseNote) => switch (baseNote) {
222
    BaseNote.c => 'Do',
1✔
223
    BaseNote.d => 'Re',
1✔
224
    BaseNote.e => 'Mi',
1✔
225
    BaseNote.f => 'Fa',
1✔
226
    BaseNote.g => 'Sol',
1✔
227
    BaseNote.a => 'La',
1✔
228
    BaseNote.b => 'Si',
1✔
229
  };
230

NEW
231
  @override
×
232
  BaseNote parse(String source) {
NEW
233
    throw UnimplementedError();
×
234
  }
235
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc