Skip to content

Commit 9080651

Browse files
author
Salma Alam-Naylor
committed
Identify theoretical modes and provide a link to the enharmonic
1 parent 9a1b9ac commit 9080651

19 files changed

+406
-123
lines changed

apps/fretonator-web/src/app/common/fretonator/fretonator.component.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@
5555

5656
<div class="infoContainer">
5757
<app-scale-map [mode]="mode"
58-
[scale]="scale"></app-scale-map>
58+
[scale]="scale"
59+
[isTheoretical]="scale | isTheoreticalScale"
60+
[modeDisplayString]="modeDisplayString"
61+
[note]="note"
62+
[noteExtenderString]="noteExtenderString"></app-scale-map>
5963

6064
<app-interval-map [intervalMap]="intervalMap"></app-interval-map>
6165

apps/fretonator-web/src/app/common/fretonator/fretonator.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,9 @@ export class FretonatorComponent {
1212
@Input() intervalMap: [];
1313
@Input() chordMap: ChordMap;
1414
@Input() mode: Mode;
15+
@Input() modeDisplayString: string;
16+
@Input() note: string;
17+
@Input() noteExtenderString: string;
18+
1519
frets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
1620
}

apps/fretonator-web/src/app/common/fretonator/fretonator.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import { GetFretFromFretMapPipe } from './get-fret-from-fret-map.pipe';
55
import { ChordMapModule } from './chord-map/chord-map.module';
66
import { IntervalMapModule } from './interval-map/interval-map.module';
77
import { ScaleMapModule } from './scale-map/scale-map.module';
8+
import { IsTheoreticalScalePipe } from './is-theoretical-scale.pipe';
89

910
@NgModule({
1011
declarations: [
1112
FretonatorComponent,
1213
GetFretFromFretMapPipe,
14+
IsTheoreticalScalePipe,
1315
],
1416
imports: [
1517
CommonModule,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { IsTheoreticalScalePipe } from './is-theoretical-scale.pipe';
2+
3+
describe('IsTheoreticalScalePipe', () => {
4+
let pipe: IsTheoreticalScalePipe;
5+
6+
beforeEach(() => {
7+
pipe = new IsTheoreticalScalePipe();
8+
});
9+
it('create an instance', () => {
10+
expect(pipe).toBeTruthy();
11+
});
12+
13+
it('returns true for a scale with a double flat in', () => {
14+
expect(pipe.transform(['C', 'D𝄫'])).toBe(true);
15+
});
16+
17+
it('returns true for a scale with a double sharp in', () => {
18+
expect(pipe.transform(['Cx', 'D'])).toBe(true);
19+
});
20+
21+
it('returns false for scales without double flat or double sharp', () => {
22+
expect(pipe.transform(['C', 'D'])).toBe(false);
23+
});
24+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
import { Scale } from '../../util/types';
3+
4+
@Pipe({
5+
name: 'isTheoreticalScale'
6+
})
7+
export class IsTheoreticalScalePipe implements PipeTransform {
8+
9+
transform(scale: Scale): boolean {
10+
for (const note of scale) {
11+
if(note.indexOf('x') !== -1 || note.indexOf('𝄫') !== -1) {
12+
return true;
13+
}
14+
}
15+
return false;
16+
}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { GetEnharmonicEquivalentPipe } from './get-enharmonic-equivalent.pipe';
2+
3+
describe('GetHarmonicEquivalentPipe', () => {
4+
it('create an instance', () => {
5+
const pipe = new GetEnharmonicEquivalentPipe();
6+
expect(pipe).toBeTruthy();
7+
});
8+
9+
it('returns A♭ for G#', () => {
10+
const pipe = new GetEnharmonicEquivalentPipe();
11+
expect(pipe.transform('G#')).toBe('A♭');
12+
})
13+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
import { Enharmonics } from '../../../util/constants';
3+
4+
@Pipe({
5+
name: 'getEnharmonicEquivalent'
6+
})
7+
export class GetEnharmonicEquivalentPipe implements PipeTransform {
8+
9+
transform(note: string): string {
10+
for (const group of Enharmonics) {
11+
if (group.indexOf(note) === 0) {
12+
return group[1]
13+
}
14+
15+
if (group.indexOf(note) === 1) {
16+
return group[0]
17+
}
18+
}
19+
}
20+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { GetEnharmonicRouterLinkPipe } from './get-enharmonic-router-link.pipe';
2+
3+
describe('GetEnharmonicRouterLinkPipe', () => {
4+
let pipe;
5+
beforeEach(() => {
6+
pipe = new GetEnharmonicRouterLinkPipe();
7+
});
8+
it('create an instance', () => {
9+
expect(pipe).toBeTruthy();
10+
});
11+
12+
it('returns correctly for g sharp ionian', () => {
13+
expect(pipe.transform('g', 'sharp', 'ionian')).toStrictEqual(['/', 'a', 'flat', 'ionian']);
14+
});
15+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
import { RouterLink } from '@angular/router';
3+
import { Enharmonics } from '../../../util/constants';
4+
5+
@Pipe({
6+
name: 'getEnharmonicRouterLink'
7+
})
8+
export class GetEnharmonicRouterLinkPipe implements PipeTransform {
9+
10+
transform(note: string, noteExtenderString: string, mode: string): string[] {
11+
let newNote;
12+
13+
for(const group of Enharmonics) {
14+
if (group[0].includes(note.toUpperCase())) {
15+
newNote = group[1].charAt(0).toLowerCase();
16+
}
17+
18+
if (group[1].includes(note.toUpperCase())) {
19+
newNote = group[0].charAt(0).toLowerCase();
20+
}
21+
}
22+
23+
const newNoteExtender = noteExtenderString === 'sharp' ? 'flat' : 'sharp';
24+
25+
return ['/', newNote, newNoteExtender, mode];
26+
}
27+
28+
}
Lines changed: 94 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,99 @@
1-
<div class="scaleMap__row">
2-
<div class="scaleMap__degrees" *ngIf="mode | displayScaleDegrees">
3-
<h4 class="scaleMap__title">Degrees</h4>
4-
<div class="degreesDisplay">
5-
<span class="degreesDisplay__degree degreesDisplay__degree--tonic">1</span>
6-
<span class="degreesDisplay__degree degreesDisplay__degree--mediant"
7-
>3</span
8-
>
9-
<span class="degreesDisplay__degree degreesDisplay__degree--dominant"
10-
>5</span>
11-
</div>
12-
<button class="scaleDegrees__toggle"
13-
aria-label="Scale Degrees Info Toggle"
14-
type="button"
15-
(click)="toggleScaleMapInfo()">{{scaleDegreesToggleText}}
16-
</button>
1+
<div class="scaleMap__row">
2+
<div class="scaleDisplay">
3+
<h4 class="scaleDisplay__title"
4+
[class.infoHighlight]="isTheoretical"><span *ngIf="isTheoretical">Theoretical &nbsp;</span>Scale</h4>
5+
<div class="scaleDisplay__notes">
6+
<ng-container *ngFor="let scaleNote of scale; let i = index">
7+
<h5 class="scaleDisplay__note">
8+
{{ scaleNote }}
9+
<span *ngIf="i < scale.length - 1"> - </span>
10+
</h5>
11+
</ng-container>
1712
</div>
13+
<button *ngIf="isTheoretical"
14+
class="button__infoToggle"
15+
aria-label="Theoretical Scales Info Toggle"
16+
type="button"
17+
(click)="toggleTheoreticalScaleInfo()">{{theoreticalScalesToggleText}}
18+
</button>
19+
</div>
1820

19-
<div class="scaleDisplay">
20-
<h4 class="scaleDisplay__title">Scale</h4>
21-
<div class="scaleDisplay__notes">
22-
<ng-container *ngFor="let scaleNote of scale; let i = index">
23-
<h5 class="scaleDisplay__note">
24-
{{ scaleNote }}
25-
<span *ngIf="i < scale.length - 1"> - </span>
26-
</h5>
27-
</ng-container>
28-
</div>
21+
<div class="scaleMap__degrees" *ngIf="mode | displayScaleDegrees">
22+
<h4 class="scaleMap__title">Degrees</h4>
23+
<div class="degreesDisplay">
24+
<span class="degreesDisplay__degree degreesDisplay__degree--tonic">1</span>
25+
<span class="degreesDisplay__degree degreesDisplay__degree--mediant"
26+
>3</span
27+
>
28+
<span class="degreesDisplay__degree degreesDisplay__degree--dominant"
29+
>5</span>
2930
</div>
31+
<button class="button__infoToggle"
32+
aria-label="Scale Degrees Info Toggle"
33+
type="button"
34+
(click)="toggleScaleMapInfo()">{{scaleDegreesToggleText}}
35+
</button>
3036
</div>
37+
</div>
3138

32-
<div class="scaleMapInfo"
33-
[attr.aria-expanded]="showScaleMapInfo"
34-
*ngIf="showScaleMapInfo">
35-
<h2 class="scaleMapInfo__title">About Scale Degrees
36-
<button class="scaleMapInfo__close"
37-
aria-label="Scale Degrees Info Close"
38-
type="button"
39-
(click)="toggleScaleMapInfo()">
40-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
41-
<path fill="#474350" fill-rule="evenodd"
42-
d="M14 1.41L12.59 0 7 5.59 1.41 0 0 1.41 5.59 7 0 12.59 1.41 14 7 8.41 12.59 14 14 12.59 8.41 7z"></path>
43-
</svg>
44-
</button>
45-
</h2>
46-
<p class="scaleMapInfo__copy">
47-
Each of the 7 notes of a mode or scale is called a <em>scale degree</em>, and has a specific name.
48-
</p>
49-
<ol class="scaleMapInfo__list">
50-
<li class="scaleMapInfo__listItem">Tonic</li>
51-
<li class="scaleMapInfo__listItem">Supertonic</li>
52-
<li class="scaleMapInfo__listItem">Mediant</li>
53-
<li class="scaleMapInfo__listItem">Subdominant</li>
54-
<li class="scaleMapInfo__listItem">Dominant</li>
55-
<li class="scaleMapInfo__listItem">Submediant</li>
56-
<li class="scaleMapInfo__listItem">Leading note</li>
57-
</ol>
58-
<p class="scaleMapInfo__copy">
59-
Notes 1, 3 and 5 of a scale make up the <em>tonic triad</em>, which is simply a chord of three notes built on
60-
the first note of the scale. This is the the most important chord to learn when jamming as it is the <em>home</em>
61-
chord - meaning it's usually the chord that the jam track starts on and returns to often.
62-
</p>
63-
<p class="scaleMapInfo__copy">
64-
Scale degrees 1, 3, and 5 are highlighted on the fretboard to give you a helpful point of reference when learning
65-
the mode.
66-
</p>
67-
</div>
39+
<div class="infoBlock"
40+
[attr.aria-expanded]="showTheoreticalScalesInfo"
41+
*ngIf="showTheoreticalScalesInfo">
42+
<h2 class="infoBlock__title">About Theoretical Scales
43+
<button class="infoBlock__close"
44+
aria-label="Theoretical Scales Info Close"
45+
type="button"
46+
(click)="toggleTheoreticalScaleInfo()">
47+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
48+
<path fill="#474350" fill-rule="evenodd"
49+
d="M14 1.41L12.59 0 7 5.59 1.41 0 0 1.41 5.59 7 0 12.59 1.41 14 7 8.41 12.59 14 14 12.59 8.41 7z"></path>
50+
</svg>
51+
</button>
52+
</h2>
53+
<p class="infoBlock__copy">A theoretical scale or <em>impossible key</em> is a key whose key signature has at least
54+
one double flat (𝄫) or double sharp (x).</p>
55+
<p class="infoBlock__copy">{{modeDisplayString}} is a theoretical scale and looks unnecessarily complicated. We can make things simpler!</p>
56+
<p class="infoBlock__copy">Switch to the enharmonic equivalent note of {{scale[0] | getEnharmonicEquivalent }} to make
57+
the notes easier to read. (It'll sound exactly the same!)</p>
58+
<a class="actionButton"
59+
(click)="toggleTheoreticalScaleInfo()"
60+
[routerLink]="note | getEnharmonicRouterLink: noteExtenderString: mode"
61+
>Switch to {{scale[0] | getEnharmonicEquivalent }}</a>
62+
</div>
63+
64+
<div class="infoBlock"
65+
[attr.aria-expanded]="showScaleMapInfo"
66+
*ngIf="showScaleMapInfo">
67+
<h2 class="infoBlock__title">About Scale Degrees
68+
<button class="infoBlock__close"
69+
aria-label="Scale Degrees Info Close"
70+
type="button"
71+
(click)="toggleScaleMapInfo()">
72+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
73+
<path fill="#474350" fill-rule="evenodd"
74+
d="M14 1.41L12.59 0 7 5.59 1.41 0 0 1.41 5.59 7 0 12.59 1.41 14 7 8.41 12.59 14 14 12.59 8.41 7z"></path>
75+
</svg>
76+
</button>
77+
</h2>
78+
<p class="infoBlock__copy">
79+
Each of the 7 notes of a mode or scale is called a <em>scale degree</em>, and has a specific name.
80+
</p>
81+
<ol class="infoBlock__list">
82+
<li class="infoBlock__listItem">Tonic</li>
83+
<li class="infoBlock__listItem">Supertonic</li>
84+
<li class="infoBlock__listItem">Mediant</li>
85+
<li class="infoBlock__listItem">Subdominant</li>
86+
<li class="infoBlock__listItem">Dominant</li>
87+
<li class="infoBlock__listItem">Submediant</li>
88+
<li class="infoBlock__listItem">Leading note</li>
89+
</ol>
90+
<p class="infoBlock__copy">
91+
Notes 1, 3 and 5 of a scale make up the <em>tonic triad</em>, which is simply a chord of three notes built on
92+
the first note of the scale. This is the the most important chord to learn when jamming as it is the <em>home</em>
93+
chord - meaning it's usually the chord that the jam track starts on and returns to often.
94+
</p>
95+
<p class="infoBlock__copy">
96+
Scale degrees 1, 3, and 5 are highlighted on the fretboard to give you a helpful point of reference when learning
97+
the mode.
98+
</p>
99+
</div>

0 commit comments

Comments
 (0)