Skip to content

Commit

Permalink
feat(scale): add class and fromNote method
Browse files Browse the repository at this point in the history
  • Loading branch information
albertms10 committed Apr 24, 2023
1 parent 8dacd91 commit c606119
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"cSpell.ignoreWords": ["music"],
"cSpell.words": ["helmholtz"],
"cSpell.words": ["helmholtz", "octatonic"],
"editor.unicodeHighlight.allowedCharacters": {
"′": true
}
Expand Down
1 change: 1 addition & 0 deletions lib/music_notes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ part 'src/note/enharmonic_note.dart';
part 'src/note/note.dart';
part 'src/note/notes.dart';
part 'src/note/positioned_note.dart';
part 'src/scale/scale.dart';
part 'src/tonality/key_signature.dart';
part 'src/tonality/modes.dart';
part 'src/tonality/tonality.dart';
Expand Down
99 changes: 99 additions & 0 deletions lib/src/scale/scale.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
part of '../../music_notes.dart';

class Scale {
/// The interval steps that define the scale.
final List<Interval> intervalSteps;

/// Creates a new [Scale] from [intervalSteps].
const Scale(this.intervalSteps);

static const major = Scale([
Interval.majorSecond,
Interval.majorSecond,
Interval.minorSecond,
Interval.majorSecond,
Interval.majorSecond,
Interval.majorSecond,
]);
static const naturalMinor = Scale([
Interval.majorSecond,
Interval.minorSecond,
Interval.majorSecond,
Interval.majorSecond,
Interval.minorSecond,
Interval.majorSecond,
]);
static const harmonicMinor = Scale([
Interval.majorSecond,
Interval.minorSecond,
Interval.majorSecond,
Interval.majorSecond,
Interval.minorSecond,
Interval.augmentedSecond,
]);
static const melodicMinor = Scale([
Interval.majorSecond,
Interval.minorSecond,
Interval.majorSecond,
Interval.majorSecond,
Interval.majorSecond,
Interval.majorSecond,
]);

static const chromatic = Scale([
Interval.augmentedUnison,
Interval.minorSecond,
Interval.augmentedUnison,
Interval.minorSecond,
Interval.minorSecond,
Interval.augmentedUnison,
Interval.minorSecond,
Interval.augmentedUnison,
Interval.minorSecond,
Interval.augmentedUnison,
Interval.minorSecond,
]);
static const tones = Scale([
Interval.majorSecond,
Interval.majorSecond,
Interval.majorSecond,
Interval.majorSecond,
Interval.majorSecond,
]);

static const pentatonic = Scale([
Interval.majorSecond,
Interval.majorSecond,
Interval.minorThird,
Interval.majorSecond,
]);
static const octatonic = Scale([
Interval.majorSecond,
Interval.minorSecond,
Interval.majorSecond,
Interval.minorSecond,
Interval.majorSecond,
Interval.minorSecond,
Interval.majorSecond,
]);

/// Returns the scale of notes starting from [note].
///
/// Example:
/// ```dart
/// Scale.major.fromNote(Note.c)
/// == const [Note.c, Note.d, Note.e, Note.f, Note.g, Note.a, Note.b]
///
/// Scale.naturalMinor.fromNote(Note.a)
/// == const [Note.a, Note.b, Note.c, Note.d, Note.e, Note.f, Note.g]
///
/// Scale.melodicMinor.fromNote(Note.c)
/// == const [Note.c, Note.d, Note.eFlat, Note.f, Note.g, Note.a, Note.b]
/// ```
List<Note> fromNote(Note note) =>
intervalSteps.fold([note], (scaleNotes, interval) {
final lastNote = scaleNotes.lastOrNull ?? note;

return [...scaleNotes, lastNote.transposeBy(interval)];
});
}
2 changes: 2 additions & 0 deletions test/src/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'note/enharmonic_note_test.dart' as enharmonic_note_test;
import 'note/note_test.dart' as note_test;
import 'note/notes_test.dart' as notes_test;
import 'note/positioned_note_test.dart' as positioned_note_test;
import 'scale/scale_test.dart' as scale_test;
import 'tonality/key_signature_test.dart' as key_signature_test;
import 'tonality/modes_test.dart' as modes_test;
import 'tonality/tonality_test.dart' as tonality_test;
Expand All @@ -26,6 +27,7 @@ void main() {
note_test.main();
notes_test.main();
positioned_note_test.main();
scale_test.main();
key_signature_test.main();
modes_test.main();
tonality_test.main();
Expand Down
167 changes: 167 additions & 0 deletions test/src/scale/scale_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import 'package:music_notes/music_notes.dart';
import 'package:test/test.dart';

void main() {
group('Scale', () {
group('.fromNote()', () {
test('should return the major scale notes starting from Note', () {
expect(
Scale.major.fromNote(Note.aFlat),
const [
Note.aFlat,
Note.bFlat,
Note.c,
Note.dFlat,
Note.eFlat,
Note.f,
Note.g,
],
);
expect(
Scale.major.fromNote(Note.f),
const [Note.f, Note.g, Note.a, Note.bFlat, Note.c, Note.d, Note.e],
);
expect(
Scale.major.fromNote(Note.c),
const [Note.c, Note.d, Note.e, Note.f, Note.g, Note.a, Note.b],
);
expect(
Scale.major.fromNote(Note.d),
const [
Note.d,
Note.e,
Note.fSharp,
Note.g,
Note.a,
Note.b,
Note.cSharp,
],
);
});

test(
'should return the natural minor scale notes starting from Note',
() {
expect(
Scale.naturalMinor.fromNote(Note.dSharp),
const [
Note.dSharp,
Note(Notes.e, Accidental.sharp),
Note.fSharp,
Note.gSharp,
Note.aSharp,
Note.b,
Note.cSharp,
],
);
expect(
Scale.naturalMinor.fromNote(Note.d),
const [Note.d, Note.e, Note.f, Note.g, Note.a, Note.bFlat, Note.c],
);
},
);

test(
'should return the harmonic minor scale notes starting from Note',
() {
expect(
Scale.harmonicMinor.fromNote(Note.bFlat),
const [
Note.bFlat,
Note.c,
Note.dFlat,
Note.eFlat,
Note.f,
Note.gFlat,
Note.a,
],
);
expect(
Scale.harmonicMinor.fromNote(Note.d),
const [
Note.d,
Note.e,
Note.f,
Note.g,
Note.a,
Note.bFlat,
Note.cSharp,
],
);
},
);

test(
'should return the melodic minor scale notes starting from Note',
() {
expect(
Scale.melodicMinor.fromNote(Note.d),
const [
Note.d,
Note.e,
Note.f,
Note.g,
Note.a,
Note.b,
Note.cSharp,
],
);
expect(
Scale.melodicMinor.fromNote(Note.gSharp),
const [
Note.gSharp,
Note.aSharp,
Note.b,
Note.cSharp,
Note.dSharp,
Note(Notes.e, Accidental.sharp),
Note(Notes.f, Accidental.doubleSharp),
],
);
},
);

test('should return the tones scale notes starting from Note', () {
expect(
Scale.tones.fromNote(Note.c),
const [Note.c, Note.d, Note.e, Note.fSharp, Note.gSharp, Note.aSharp],
);
expect(
Scale.tones.fromNote(Note.dFlat),
const [Note.dFlat, Note.eFlat, Note.f, Note.g, Note.a, Note.b],
);
expect(
Scale.tones.fromNote(Note.cSharp),
const [
Note.cSharp,
Note.dSharp,
Note(Notes.e, Accidental.sharp),
Note(Notes.f, Accidental.doubleSharp),
Note(Notes.g, Accidental.doubleSharp),
Note(Notes.a, Accidental.doubleSharp),
],
);
});

test('should return the chromatic scale notes starting from Note', () {
expect(
Scale.chromatic.fromNote(Note.c),
const [
Note.c,
Note.cSharp,
Note.d,
Note.dSharp,
Note.e,
Note.f,
Note.fSharp,
Note.g,
Note.gSharp,
Note.a,
Note.aSharp,
Note.b,
],
);
});
});
});
}

0 comments on commit c606119

Please sign in to comment.