Skip to content

Commit

Permalink
Merge 53dc8ff into 4e67c11
Browse files Browse the repository at this point in the history
  • Loading branch information
albertms10 committed Jun 2, 2023
2 parents 4e67c11 + 53dc8ff commit 496e5c8
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ migrate_working_dir/
.pub-cache/
.pub/
/build/
/bin/

# Symbolication related
app.*.symbols
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"cSpell.words": ["latin"]
"cSpell.ignoreWords": ["music"],
"cSpell.words": ["cryptogrammatic", "latin"]
}
191 changes: 191 additions & 0 deletions lib/model/cryptogram_scheme.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import 'dart:collection' show SplayTreeMap;

import 'package:music_notes/music_notes.dart';

class CryptogramScheme {
final String name;
final Map<Pattern, Note> patterns;

/// Under this scheme the vowel sounds in the text are matched to the vowel
/// sounds of the solmization syllables of Guido of Arezzo (where 'ut' is the
/// root, which we now call 'do', [Note.c]).
///
/// See [Musical cryptogram](https://en.wikipedia.org/wiki/Musical_cryptogram#Syllables_to_solmization_names).
CryptogramScheme.solmization()
: name = 'Solmization',
patterns = {
RegExp('do|ut', caseSensitive: false): Note.c,
RegExp('re', caseSensitive: false): Note.d,
RegExp('mi', caseSensitive: false): Note.e,
RegExp('fa', caseSensitive: false): Note.f,
RegExp('sol?', caseSensitive: false): Note.g,
RegExp('la', caseSensitive: false): Note.a,
RegExp('(t|s)i', caseSensitive: false): Note.b,
RegExp('a', caseSensitive: false): Note.f,
RegExp('e', caseSensitive: false): Note.d,
RegExp('i', caseSensitive: false): Note.e,
RegExp('u', caseSensitive: false): Note.c,
};

/// Arisen late in the 19th century, it was more akin to normal encipherment.
/// The most popular version involved writing out the letters H–N, O–U and
/// V–Z in lines under the original diatonic notes A–G, as follows:
///
/// | A | B | C | D | E | F | G |
/// |:-:|:-:|:-:|:-:|:-:|:-:|:-:|
/// | H | I | J | K | L | M | N |
/// | O | P | Q | R | S | T | U |
/// | V | W | X | Y | Z | | |
///
/// See [Musical cryptogram](https://en.wikipedia.org/wiki/Musical_cryptogram#French).
CryptogramScheme.french()
: name = 'French',
patterns = {
RegExp('A', caseSensitive: false): Note.a,
RegExp('B', caseSensitive: false): Note.b,
RegExp('C', caseSensitive: false): Note.c,
RegExp('D', caseSensitive: false): Note.d,
RegExp('E', caseSensitive: false): Note.e,
RegExp('F', caseSensitive: false): Note.f,
RegExp('G', caseSensitive: false): Note.g,
RegExp('H', caseSensitive: false): Note.a,
RegExp('I', caseSensitive: false): Note.b,
RegExp('J', caseSensitive: false): Note.c,
RegExp('K', caseSensitive: false): Note.d,
RegExp('L', caseSensitive: false): Note.e,
RegExp('M', caseSensitive: false): Note.f,
RegExp('N', caseSensitive: false): Note.g,
RegExp('O', caseSensitive: false): Note.a,
RegExp('P', caseSensitive: false): Note.b,
RegExp('Q', caseSensitive: false): Note.c,
RegExp('R', caseSensitive: false): Note.d,
RegExp('S', caseSensitive: false): Note.e,
RegExp('T', caseSensitive: false): Note.f,
RegExp('U', caseSensitive: false): Note.g,
RegExp('V', caseSensitive: false): Note.a,
RegExp('W', caseSensitive: false): Note.b,
RegExp('X', caseSensitive: false): Note.c,
RegExp('Y', caseSensitive: false): Note.d,
RegExp('Z', caseSensitive: false): Note.e,
};

/// Derived on the [CryptogramScheme.french] but leaving H = [Note.b] and
/// starting the second line with 'I'.
///
/// See [Musical cryptogram](https://en.wikipedia.org/wiki/Musical_cryptogram#Summary_of_signature_motifs)
/// (Alain).
CryptogramScheme.frenchVariant()
: name = 'French variant',
patterns = {
RegExp('A', caseSensitive: false): Note.a,
RegExp('B', caseSensitive: false): Note.b,
RegExp('C', caseSensitive: false): Note.c,
RegExp('D', caseSensitive: false): Note.d,
RegExp('E', caseSensitive: false): Note.e,
RegExp('F', caseSensitive: false): Note.f,
RegExp('G', caseSensitive: false): Note.g,
RegExp('H', caseSensitive: false): Note.b,
RegExp('I', caseSensitive: false): Note.a,
RegExp('J', caseSensitive: false): Note.b,
RegExp('K', caseSensitive: false): Note.c,
RegExp('L', caseSensitive: false): Note.d,
RegExp('M', caseSensitive: false): Note.e,
RegExp('N', caseSensitive: false): Note.f,
RegExp('O', caseSensitive: false): Note.g,
RegExp('P', caseSensitive: false): Note.a,
RegExp('Q', caseSensitive: false): Note.b,
RegExp('R', caseSensitive: false): Note.c,
RegExp('S', caseSensitive: false): Note.d,
RegExp('T', caseSensitive: false): Note.e,
RegExp('U', caseSensitive: false): Note.f,
RegExp('V', caseSensitive: false): Note.g,
RegExp('W', caseSensitive: false): Note.a,
RegExp('X', caseSensitive: false): Note.b,
RegExp('Y', caseSensitive: false): Note.c,
RegExp('Z', caseSensitive: false): Note.d,
};

/// Because the development of note names took place within the framework of
/// [Mode]s, in the German-speaking world [Note.b.flat] was named 'B' and
/// [Note.b] (natural) was named 'H'.
///
/// Similarly, other note names were derived by sound, e.g., [Note.e.flat],
/// 'Es' in German, could represent 'S' and [Note.a.flat] the digraph 'As'.
///
/// See [Musical cryptogram](https://en.wikipedia.org/wiki/Musical_cryptogram#German).
CryptogramScheme.german()
: name = 'German',
patterns = {
RegExp('As', caseSensitive: false): Note.a.flat,
RegExp('Ais', caseSensitive: false): Note.a.sharp,
RegExp('A', caseSensitive: false): Note.a,
RegExp('B', caseSensitive: false): Note.b.flat,
RegExp('Ces', caseSensitive: false): Note.c.flat,
RegExp('Cis', caseSensitive: false): Note.c.sharp,
RegExp('C', caseSensitive: false): Note.c,
RegExp('Des', caseSensitive: false): Note.d.flat,
RegExp('Dis', caseSensitive: false): Note.d.sharp,
RegExp('D', caseSensitive: false): Note.d,
RegExp('Es', caseSensitive: false): Note.e.flat,
RegExp('Eis', caseSensitive: false): Note.e.sharp,
RegExp('Fes', caseSensitive: false): Note.f.flat,
RegExp('Fis', caseSensitive: false): Note.f.sharp,
RegExp('F', caseSensitive: false): Note.f,
RegExp('Ges', caseSensitive: false): Note.g.flat,
RegExp('Gis', caseSensitive: false): Note.g.sharp,
RegExp('G', caseSensitive: false): Note.g,
RegExp('E', caseSensitive: false): Note.e,
RegExp('H', caseSensitive: false): Note.b,
RegExp('I', caseSensitive: false): Note.b,
RegExp('J', caseSensitive: false): Note.c,
RegExp('K', caseSensitive: false): Note.d,
RegExp('L', caseSensitive: false): Note.e,
RegExp('M', caseSensitive: false): Note.f,
RegExp('N', caseSensitive: false): Note.g,
RegExp('O', caseSensitive: false): Note.a,
RegExp('P', caseSensitive: false): Note.b,
RegExp('Q', caseSensitive: false): Note.c,
RegExp('R', caseSensitive: false): Note.d,
RegExp('S', caseSensitive: false): Note.e.flat,
RegExp('T', caseSensitive: false): Note.f,
RegExp('U', caseSensitive: false): Note.g,
RegExp('V', caseSensitive: false): Note.a,
RegExp('W', caseSensitive: false): Note.b,
RegExp('X', caseSensitive: false): Note.c,
RegExp('Y', caseSensitive: false): Note.d,
RegExp('Z', caseSensitive: false): Note.e,
};
}

extension CryptogramStringExtension on String {
/// Returns the cryptogrammatic sequence of [Note]s, a sequence which can be
/// taken to refer to an extra-musical text by some 'logical' relationship,
/// usually between note names and letters.
///
/// Example:
/// ```dart
/// 'Bach'.toCryptogram(CryptogramScheme.german())
/// == [Note.b.flat, Note.a, Note.c, Note.b]
///
/// 'Alain'.toCryptogram(CryptogramScheme.frenchVariant())
/// == [Note.a, Note.d, Note.a, Note.a, Note.f]
/// ```
List<Note> toCryptogram(CryptogramScheme scheme) {
final seenMatches = <Match>[];
final notes = SplayTreeMap<int, Note>();

for (final pattern in scheme.patterns.keys) {
for (final match in pattern.allMatches(this)) {
final isMatchSeen = seenMatches.any(
(element) => match.start >= element.start && match.end <= element.end,
);
if (!isMatchSeen) {
seenMatches.add(match);
notes[match.start] = scheme.patterns[pattern]!;
}
}
}

return notes.values.toList();
}
}
2 changes: 1 addition & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ packages:
source: hosted
version: "2.1.1"
characters:
dependency: "direct main"
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
Expand Down
1 change: 0 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ environment:
sdk: ">=3.0.0 <4.0.0"

dependencies:
characters: ^1.3.0
diacritic: ^0.1.3
flutter:
sdk: flutter
Expand Down
80 changes: 80 additions & 0 deletions test/model/cryptogram_string_extension_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:music_notes/music_notes.dart';
import 'package:note_names/model/cryptogram_scheme.dart';

void main() {
group('CryptogramStringExtension', () {
group('.toCryptogram()', () {
test('should return the musical cryptogram of this String', () {
final herculesDuxFerrarie = 'Hercules Dux Ferrarie'
.toCryptogram(CryptogramScheme.solmization());
expect(
herculesDuxFerrarie,
[Note.d, Note.c, Note.d, Note.c, Note.d, Note.f, Note.e, Note.d],
);

final arnoldSchonberg =
'A. SCHBEG'.toCryptogram(CryptogramScheme.german());
expect(
arnoldSchonberg,
[Note.a, Note.e.flat, Note.c, Note.b, Note.b.flat, Note.e, Note.g],
);

final belaBartok = 'BE BA'.toCryptogram(CryptogramScheme.french());
expect(belaBartok, [Note.b, Note.e, Note.b, Note.a]);

final carlPhilipEmmanuelBach =
'CFE BACH'.toCryptogram(CryptogramScheme.german());
expect(
carlPhilipEmmanuelBach,
[Note.c, Note.f, Note.e, Note.b.flat, Note.a, Note.c, Note.b],
);

final dimitriSchostakowitsch =
'D. SCH'.toCryptogram(CryptogramScheme.german());
expect(dimitriSchostakowitsch, [Note.d, Note.e.flat, Note.c, Note.b]);

final edvardGried = 'EBG'.toCryptogram(CryptogramScheme.french());
expect(edvardGried, [Note.e, Note.b, Note.g]);

final franzSchubert = 'F. SCH'.toCryptogram(CryptogramScheme.german());
expect(franzSchubert, [Note.f, Note.e.flat, Note.c, Note.b]);

final gustavHolst = 'GSAH'.toCryptogram(CryptogramScheme.german());
expect(gustavHolst, [Note.g, Note.e.flat, Note.a, Note.b]);

final jehanAlain =
'ALAIN'.toCryptogram(CryptogramScheme.frenchVariant());
expect(jehanAlain, [Note.a, Note.d, Note.a, Note.a, Note.f]);

final johannesBrahms = 'BAHS'.toCryptogram(CryptogramScheme.german());
expect(johannesBrahms, [Note.b.flat, Note.a, Note.b, Note.e.flat]);

final johannSebastianBach =
'BACH'.toCryptogram(CryptogramScheme.german());
expect(johannSebastianBach, [Note.b.flat, Note.a, Note.c, Note.b]);

final johnCage = 'CAGE'.toCryptogram(CryptogramScheme.german());
expect(johnCage, [Note.c, Note.a, Note.g, Note.e]);

final josephHaydn = 'HAYDN'.toCryptogram(CryptogramScheme.german());
expect(josephHaydn, [Note.b, Note.a, Note.d, Note.d, Note.g]);

final metaAbegg = 'ABEGG'.toCryptogram(CryptogramScheme.german());
expect(metaAbegg, [Note.a, Note.b.flat, Note.e, Note.g, Note.g]);

final nielsGade = 'GADE'.toCryptogram(CryptogramScheme.german());
expect(nielsGade, [Note.g, Note.a, Note.d, Note.e]);

final paulSacher = 'SACHER'.toCryptogram(CryptogramScheme.german());
expect(
paulSacher,
[Note.e.flat, Note.a, Note.c, Note.b, Note.e, Note.d],
);

final robertSchumann = 'SCHA'.toCryptogram(CryptogramScheme.german());
expect(robertSchumann, [Note.e.flat, Note.c, Note.b, Note.a]);
});
});
});
}
3 changes: 3 additions & 0 deletions test/model/main.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import 'alphabet_test.dart' as alphabet_test;
import 'cryptogram_string_extension_test.dart'
as cryptogram_string_extension_test;
import 'name_test.dart' as name_test;

void main() {
alphabet_test.main();
cryptogram_string_extension_test.main();
name_test.main();
}

0 comments on commit 496e5c8

Please sign in to comment.