diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index a7e32b8..69f0162 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,15 +3,11 @@ import { Routes, RouterModule } from '@angular/router'; import { ChordsComponent } from './chords/chords.component'; import { ScalesComponent } from './scales/scales.component'; -import { ProgressionsComponent } from './progressions/progressions.component'; -import { PiecesComponent } from './pieces/pieces.component'; const routes: Routes = [ { path: '', redirectTo: '/chords', pathMatch: 'full' }, { path: 'chords', component: ChordsComponent }, { path: 'scales', component: ScalesComponent }, - { path: 'progressions', component: ProgressionsComponent }, - { path: 'pieces', component: PiecesComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 11a0ba9..d5e7f43 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -8,24 +8,10 @@ import { PianoComponent } from './piano/piano.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ChordsComponent } from './chords/chords.component'; import { ScalesComponent } from './scales/scales.component'; -import { ProgressionsComponent } from './progressions/progressions.component'; -import { PiecesComponent } from './pieces/pieces.component'; @NgModule({ - declarations: [ - AppComponent, - PianoComponent, - ChordsComponent, - ScalesComponent, - ProgressionsComponent, - PiecesComponent, - ], - imports: [ - BrowserModule, - AppRoutingModule, - BrowserAnimationsModule, - FormsModule, - ], + declarations: [AppComponent, PianoComponent, ChordsComponent, ScalesComponent], + imports: [BrowserModule, AppRoutingModule, BrowserAnimationsModule, FormsModule], providers: [], bootstrap: [AppComponent], }) diff --git a/src/app/chords/chords.component.html b/src/app/chords/chords.component.html index 0279731..885abbd 100644 --- a/src/app/chords/chords.component.html +++ b/src/app/chords/chords.component.html @@ -4,11 +4,11 @@

No chords enabled!

+ (change)="chordQuestionGenerator.generateQuestions()"> Sharps + (change)="chordQuestionGenerator.generateQuestions()"> Naturals

@@ -16,6 +16,6 @@

Enabled chord types

+ (change)="chordQuestionGenerator.generateQuestions()"> {{ chordNames[item.key] }}
diff --git a/src/app/chords/chords.component.ts b/src/app/chords/chords.component.ts index bb7d589..f14f480 100644 --- a/src/app/chords/chords.component.ts +++ b/src/app/chords/chords.component.ts @@ -3,7 +3,7 @@ import { ChangeDetectorRef } from '@angular/core'; import { PianoService } from '../piano/piano.service'; import { Subscription } from 'rxjs'; import { ChordQuestionGenerator } from './chords'; -import { chordNames } from '../data'; +import { chordNames } from '../utils/data'; @Component({ selector: 'app-chords', @@ -11,29 +11,26 @@ import { chordNames } from '../data'; styleUrls: ['./chords.component.scss'], }) export class ChordsComponent implements OnInit, OnDestroy { - public chordQuestionGenerator = new ChordQuestionGenerator(); + public chordQuestionGenerator: ChordQuestionGenerator; public chordNames = chordNames; - private chordSubscription: Subscription; + private chordQuestionSubscription: Subscription; constructor( - private ref: ChangeDetectorRef, + private ref: ChangeDetectorRef, // private pianoService: PianoService ) { - this.chordSubscription = this.pianoService.chordsSource.subscribe( - chords => { - if (this.chordQuestionGenerator.checkAnswer(chords)) { - this.chordQuestionGenerator.nextQuestion(); - } - this.ref.detectChanges(); - } - ); + this.chordQuestionGenerator = new ChordQuestionGenerator(pianoService); + this.chordQuestionSubscription = this.chordQuestionGenerator.pageUpdateSource.subscribe(() => { + this.ref.detectChanges(); + }); } ngOnInit() {} ngOnDestroy() { // prevent memory leak when component destroyed - this.chordSubscription.unsubscribe(); + this.chordQuestionGenerator.destroy(); + this.chordQuestionSubscription.unsubscribe(); } } diff --git a/src/app/chords/chords.ts b/src/app/chords/chords.ts index 73d2314..d67a1d6 100644 --- a/src/app/chords/chords.ts +++ b/src/app/chords/chords.ts @@ -1,58 +1,59 @@ -import { numToString } from '../data'; -import { chordpatterns } from '../data'; +import { numToString } from '../utils/data'; +import { chordpatterns } from '../utils/data'; +import { QuestionGenerator } from '../models/questionGenerator'; +import { PianoService } from '../piano/piano.service'; +import { Subject, Subscription } from 'rxjs'; + +export class ChordQuestionGenerator implements QuestionGenerator { + public enabledQuestions = []; + public question = ''; + public pageUpdateSource = new Subject(); + + private chordSubscription: Subscription; -export class ChordQuestionGenerator { public enableSharps = false; public enableNaturals = true; public enableChords = { - '': false, + '': true, 7: false, maj7: false, m: false, m7: false, mmaj7: false, dim: false, - dim7: true, + dim7: false, }; - public enabledChordNames = []; - public question = ''; + constructor(pianoService: PianoService) { + this.generateQuestions(); + this.chordSubscription = pianoService.chordsSource.subscribe(chords => { + if (this.checkAnswer(chords)) { + this.nextQuestion(); + } + }); + } - public generateChordNames() { - this.enabledChordNames = []; + public generateQuestions() { + this.enabledQuestions = []; for (let root = 21; root < 33; root++) { const note = numToString(root, false); - if ( - (note.length === 1 || this.enableSharps) && - (note.length === 2 || this.enableNaturals) - ) { - for (const pat of Object.keys(chordpatterns).filter( - patt => this.enableChords[patt] - )) { - this.enabledChordNames.push(note + ' ' + pat); + if ((note.length === 1 || this.enableSharps) && (note.length === 2 || this.enableNaturals)) { + for (const pat of Object.keys(chordpatterns).filter(patt => this.enableChords[patt])) { + this.enabledQuestions.push(note + ' ' + pat); } } } - console.log(this.enabledChordNames); this.nextQuestion(); } - - constructor() { - this.generateChordNames(); - } - public nextQuestion() { - this.question = - this.enabledChordNames[ - Math.floor(Math.random() * this.enabledChordNames.length) - ]; - return this.question; + this.question = this.enabledQuestions[Math.floor(Math.random() * this.enabledQuestions.length)]; + this.pageUpdateSource.next(); + } + public destroy() { + this.chordSubscription.unsubscribe(); } - public checkAnswer(chords: string[]) { - if (chords.includes(this.question)) { - return true; - } - return false; + private checkAnswer(chords: string[]) { + return chords.includes(this.question); } } diff --git a/src/app/models/questionGenerator.ts b/src/app/models/questionGenerator.ts new file mode 100644 index 0000000..fab470e --- /dev/null +++ b/src/app/models/questionGenerator.ts @@ -0,0 +1,10 @@ +import { Subject } from 'rxjs'; + +export interface QuestionGenerator { + question: string; + enabledQuestions: string[]; + pageUpdateSource: Subject; + generateQuestions(): void; + nextQuestion(): void; + destroy(): void; +} diff --git a/src/app/piano/piano.component.html b/src/app/piano/piano.component.html index f0db665..d7f2f9e 100644 --- a/src/app/piano/piano.component.html +++ b/src/app/piano/piano.component.html @@ -14,9 +14,6 @@ diff --git a/src/app/piano/piano.service.ts b/src/app/piano/piano.service.ts index ff0e85e..73e5fde 100644 --- a/src/app/piano/piano.service.ts +++ b/src/app/piano/piano.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; -import { octaves, noteNames, generateChords, enharmonicNotes } from '../data'; +import { enharmonicNotes, generateChords, noteNames, octaves } from '../utils/data'; +import { arraysEqual } from '../utils/utils'; @Injectable({ providedIn: 'root', @@ -10,24 +11,24 @@ export class PianoService { private activeNotes = []; private chords = generateChords(); - notesSource = new Subject(); + notesSource = new Subject(); noteSource = new Subject(); chordsSource = new Subject(); constructor() { - for (let i of octaves) { - for (let j of noteNames) { + for (const i of octaves) { + for (const j of noteNames) { this.notes[j + i] = false; } } } private detectChord() { // List of notes current pressed without octave numbers - var notes = this.activeNotes.map(note => note.slice(0, -1)).sort(); - var uniqueNotes = Array.from(new Set(notes)); + const notes = this.activeNotes.map(note => note.slice(0, -1)).sort(); + const uniqueNotes = Array.from(new Set(notes)); // Find chord - let chords: string[] = []; - for (let chordName in this.chords) { + const chords: string[] = []; + for (const chordName in this.chords) { if (arraysEqual(uniqueNotes, this.chords[chordName])) { chords.push(chordName); const splitChord = chordName.split(' '); @@ -51,19 +52,3 @@ export class PianoService { this.chordsSource.next(this.detectChord()); } } - -function arraysEqual(a, b) { - if (a === b) return true; - if (a == null || b == null) return false; - if (a.length != b.length) return false; - - // If you don't care about the order of the elements inside - // the array, you should sort both arrays here. - // Please note that calling sort on an array will modify that array. - // you might want to clone your array first. - - for (var i = 0; i < a.length; ++i) { - if (a[i] !== b[i]) return false; - } - return true; -} diff --git a/src/app/pieces/pieces.component.html b/src/app/pieces/pieces.component.html deleted file mode 100644 index dd1aa3b..0000000 --- a/src/app/pieces/pieces.component.html +++ /dev/null @@ -1,3 +0,0 @@ -

- Play {{ pieceQuestionGenerator.question }} chord -

diff --git a/src/app/pieces/pieces.component.scss b/src/app/pieces/pieces.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/pieces/pieces.component.spec.ts b/src/app/pieces/pieces.component.spec.ts deleted file mode 100644 index 22d26ac..0000000 --- a/src/app/pieces/pieces.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { PiecesComponent } from './pieces.component'; - -describe('PiecesComponent', () => { - let component: PiecesComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ PiecesComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(PiecesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/pieces/pieces.component.ts b/src/app/pieces/pieces.component.ts deleted file mode 100644 index 4db901a..0000000 --- a/src/app/pieces/pieces.component.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; -import { Subscription } from 'rxjs'; -import { PianoService } from '../piano/piano.service'; -import { PieceQuestionGenerator } from './pieces'; - -@Component({ - selector: 'app-pieces', - templateUrl: './pieces.component.html', - styleUrls: ['./pieces.component.scss'], -}) -export class PiecesComponent implements OnInit, OnDestroy { - public pieceQuestionGenerator = new PieceQuestionGenerator(); - - private chordSubscription: Subscription; - - constructor( - private ref: ChangeDetectorRef, - private pianoService: PianoService - ) { - this.chordSubscription = this.pianoService.chordsSource.subscribe( - chords => { - if (this.pieceQuestionGenerator.checkAnswer(chords)) { - this.pieceQuestionGenerator.nextQuestion(); - } - this.ref.detectChanges(); - } - ); - } - - ngOnInit() {} - - ngOnDestroy() { - // prevent memory leak when component destroyed - this.chordSubscription.unsubscribe(); - } -} diff --git a/src/app/pieces/pieces.ts b/src/app/pieces/pieces.ts deleted file mode 100644 index e7b659e..0000000 --- a/src/app/pieces/pieces.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { numToString } from '../data'; -import { chordpatterns } from '../data'; - -const FQBK_handbook_1 = [ - 'Bb 7', - 'Eb 7', - 'Bb 7', - 'F m7', - 'Bb 7', - // - 'Eb 7', - 'Eb 7', - 'Bb 7', - 'D dim', - 'G 7', - // - 'C m7', - 'F 7', - 'D m7', - 'G 7', - 'C m7', - 'F 7', -]; - -const allTheThingsYouAre = [ - 'F m7', - 'Bb m7', - 'Eb 7', - 'Ab maj7', - // - 'Db maj7', - 'G 7', - 'C maj7', - 'C maj7', - // - 'C m7', - 'F m7', - 'Bb 7', - 'Eb maj7', - // -]; - -export class PieceQuestionGenerator { - public chords = allTheThingsYouAre; - private currentQuestionIndex = 0; - public question = ''; - - constructor() { - this.nextQuestion(); - } - - public nextQuestion() { - this.question = this.chords[this.currentQuestionIndex]; - this.currentQuestionIndex += 1; - if (this.currentQuestionIndex >= this.chords.length) { - this.currentQuestionIndex = 0; - } - return this.question; - } - - public checkAnswer(chords: string[]) { - return chords.includes(this.question); - } -} diff --git a/src/app/progressions/progressions.component.html b/src/app/progressions/progressions.component.html deleted file mode 100644 index 4286029..0000000 --- a/src/app/progressions/progressions.component.html +++ /dev/null @@ -1,2 +0,0 @@ -

Play {{ question }} scale

-

{{ progressionQuestionGenerator.progression }}

diff --git a/src/app/progressions/progressions.component.scss b/src/app/progressions/progressions.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/progressions/progressions.component.spec.ts b/src/app/progressions/progressions.component.spec.ts deleted file mode 100644 index 79018c0..0000000 --- a/src/app/progressions/progressions.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { ProgressionsComponent } from './progressions.component'; - -describe('ProgressionsComponent', () => { - let component: ProgressionsComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ProgressionsComponent], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ProgressionsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/progressions/progressions.component.ts b/src/app/progressions/progressions.component.ts deleted file mode 100644 index 886759d..0000000 --- a/src/app/progressions/progressions.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ProgressionQuestionGenerator } from './progressions'; -import { ChangeDetectorRef } from '@angular/core'; -import { PianoService } from '../piano/piano.service'; -import { Subscription } from 'rxjs'; - -@Component({ - selector: 'app-progressions', - templateUrl: './progressions.component.html', - styleUrls: ['./progressions.component.scss'], -}) -export class ProgressionsComponent implements OnInit { - public progressionQuestionGenerator = new ProgressionQuestionGenerator(); - - noteSubscription: Subscription; - public question = ''; - public progress = ''; - - constructor( - private ref: ChangeDetectorRef, - private pianoService: PianoService - ) { - this.noteSubscription = this.pianoService.noteSource.subscribe(note => { - if (this.progressionQuestionGenerator.nextNote(note)) { - this.question = this.progressionQuestionGenerator.nextQuestion(); - } - this.progress = this.progressionQuestionGenerator.getProgressString(); - this.ref.detectChanges(); - }); - this.question = this.progressionQuestionGenerator.nextQuestion(); - this.progress = this.progressionQuestionGenerator.getProgressString(); - } - - ngOnInit() {} - - ngOnDestroy() { - // prevent memory leak when component destroyed - this.noteSubscription.unsubscribe(); - } -} diff --git a/src/app/progressions/progressions.ts b/src/app/progressions/progressions.ts deleted file mode 100644 index a91719b..0000000 --- a/src/app/progressions/progressions.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { - numToString, - scalepatterns, - generateScales, - noteNames, - chordpatterns, - generateChords, -} from '../data'; - -function chordIntersection(c1, c2) { - return c1.filter(value => -1 !== c2.indexOf(value)); -} - -export class ProgressionQuestionGenerator { - private enableSharps = false; - private enableNaturals = true; - private enableScaleModes = { Ionian: true }; - private enableChords = { - '': true, - 7: true, - maj7: false, - m: true, - m7: true, - mmaj7: false, - dim: false, - dim7: false, - }; - - private enabledScaleNames = []; - private scale: string = ''; - public progression = []; - private scales = generateScales(); - private chords = generateChords(); - - private sequence = []; - public question = ''; - - public generateScaleNames() { - this.enabledScaleNames = []; - for (let root = 21; root < 33; root++) { - let note = numToString(root, false); - if ( - (note.length == 1 || this.enableSharps) && - (note.length == 2 || this.enableNaturals) - ) { - for (let pat of Object.keys(scalepatterns).filter( - pat => this.enableScaleModes[pat] - )) { - this.enabledScaleNames.push(note + ' ' + pat); - } - } - } - this.generateChords(); - } - - public generateChords() { - this.scale = - this.enabledScaleNames[ - Math.floor(Math.random() * this.enabledScaleNames.length) - ]; - let enabledChordNames = []; - for (let note of this.scales[this.scale].slice(0, -1)) { - for (let pat of Object.keys(chordpatterns).filter( - pat => this.enableChords[pat] - )) { - enabledChordNames.push(note + ' ' + pat); - } - } - console.log(enabledChordNames); - console.log('AA', this.chords); - let rootChords = enabledChordNames.filter( - chord => chord.split(' ')[0] == this.scale.split(' ')[0] - ); - this.progression.push( - rootChords[Math.floor(Math.random() * rootChords.length)] - ); - let notLooping = true; - while (notLooping) { - while (this.progression.length < 4) { - let potenantialNextChords = []; - for (let cn of enabledChordNames) { - let c1 = this.chords[this.progression[this.progression.length - 1]]; - let c2 = this.chords[cn]; - let intersection = chordIntersection(c1, c2); - console.log(c1, c2, intersection); - if ( - this.progression.filter( - chord => chord.split(' ')[0] == cn.split(' ')[0] - ).length == 0 - ) { - for (let i = 0; i < intersection.length; i++) { - potenantialNextChords.push(cn); - } - } - } - console.log('AAA', this.progression, potenantialNextChords); - this.progression.push( - potenantialNextChords[ - Math.floor(Math.random() * potenantialNextChords.length) - ] - ); - } - console.log( - 'aaa', - this.chords[this.progression[this.progression.length - 1]], - this.chords[this.progression[0]] - ); - if ( - chordIntersection( - this.chords[this.progression[this.progression.length - 1]], - this.chords[this.progression[0]] - ).length > 1 - ) { - notLooping = false; - } else { - this.progression = [this.progression[0]]; - } - } - - console.log(this.progression); - // this.nextQuestion(); - } - - constructor() { - this.generateScaleNames(); - } - - public nextQuestion() { - this.question = - this.enabledScaleNames[ - Math.floor(Math.random() * this.enabledScaleNames.length) - ]; - return this.question; - } - - public nextNote(note: string) { - this.sequence.push(note.slice(0, -1)); - let i; - for (i = 0; i < this.sequence.length; i++) { - if (this.scales[this.question][i] != this.sequence[i]) { - this.sequence = []; - } - } - if (i == this.scales[this.question].length) { - this.sequence = []; - return true; - } - console.log(this.sequence); - } - - public getProgressString() { - let outstr = ''; - for (let i = 0; i < this.scales[this.question].length; i++) { - if (i < this.sequence.length) { - outstr += this.sequence[i] + ' '; - } else { - outstr += '● '; - } - } - return outstr.slice(0, -1); - } -} diff --git a/src/app/questions/question.ts b/src/app/questions/question.ts deleted file mode 100644 index 640963a..0000000 --- a/src/app/questions/question.ts +++ /dev/null @@ -1,6 +0,0 @@ -class Question { - note: string; - constructor(note: string) { - this.note = note; - } -} diff --git a/src/app/scales/scales.component.html b/src/app/scales/scales.component.html index 017025e..b5e3f25 100644 --- a/src/app/scales/scales.component.html +++ b/src/app/scales/scales.component.html @@ -1,5 +1,24 @@ -

Play {{ question }} scale

-

{{ progress }}

+

Play {{ scaleQuestionGenerator.question }} scale

+

No scales enabled!

+

{{ scaleQuestionGenerator.progress }}

+ +Sharps + +Naturals +
+
+

Enabled scale types

+
+ + {{ scaleNames[item.key] }} +
+