diff --git a/src/app/exercise/Exercise.ts b/src/app/exercise/Exercise.ts index 4575b0ef..1ca45ffa 100644 --- a/src/app/exercise/Exercise.ts +++ b/src/app/exercise/Exercise.ts @@ -61,7 +61,14 @@ export namespace Exercise { endSeconds: number, } - export type Question = NotesQuestion | YouTubeQuestion; + export interface AudioQuestion extends BaseQuestion { + type: 'audio', + } + + export type Question = NotesQuestion | YouTubeQuestion | AudioQuestion; export type Answer = GAnswer; diff --git a/src/app/exercise/exercise.page/state/exercise-state.service.ts b/src/app/exercise/exercise.page/state/exercise-state.service.ts index 9e195c3b..e7133cae 100644 --- a/src/app/exercise/exercise.page/state/exercise-state.service.ts +++ b/src/app/exercise/exercise.page/state/exercise-state.service.ts @@ -30,6 +30,7 @@ import { AdaptiveExerciseService } from './adaptive-exercise.service'; import { BehaviorSubject } from 'rxjs'; import AnswerList = Exercise.AnswerList; import Answer = Exercise.Answer; +import { AudioPlayerService } from '../../../services/audio-player.service'; import getAnswerListIterator = Exercise.getAnswerListIterator; const DEFAULT_EXERCISE_SETTINGS: GlobalExerciseSettings = { @@ -72,6 +73,7 @@ export class ExerciseStateService implements OnDestroy { private readonly _exerciseService: ExerciseService, private readonly _notesPlayer: PlayerService, private readonly _youtubePlayer: YouTubePlayerService, + private readonly _audioPlayer: AudioPlayerService, private readonly _exerciseSettingsData: ExerciseSettingsDataService, private readonly _adaptiveExerciseService: AdaptiveExerciseService, private readonly router: Router, @@ -240,6 +242,8 @@ export class ExerciseStateService implements OnDestroy { await this._notesPlayer.playMultipleParts(cadence); } await this._playYouTubeQuestion(this._currentQuestion); + } else if (this._currentQuestion.type === 'audio') { + await this._playAudioQuestion(this._currentQuestion); } else { const partsToPlay: PartToPlay[] = this._getCurrentQuestionPartsToPlay(); if (cadence && (this._globalSettings.playCadence || this._wasKeyChanged)) { @@ -409,6 +413,15 @@ export class ExerciseStateService implements OnDestroy { await this._youtubePlayer.onStop(); } + private async _playAudioQuestion(question: Exercise.AudioQuestion): Promise { + if (this._destroyed) { + return; + } + for (let segment of question.segments) { + await this._audioPlayer.play(segment.pathToAudio); + } + } + private _getCurrentQuestionPartsToPlay(): PartToPlay[] { const partsToPlay: PartToPlay[] = this._currentQuestion.segments.map((segment, i: number): PartToPlay => ({ partOrTime: toSteadyPart(segment.partToPlay), diff --git a/src/app/exercise/exercises/InstrumentExercise/InstrumentExercise.ts b/src/app/exercise/exercises/InstrumentExercise/InstrumentExercise.ts new file mode 100644 index 00000000..687d957f --- /dev/null +++ b/src/app/exercise/exercises/InstrumentExercise/InstrumentExercise.ts @@ -0,0 +1,33 @@ +import { BaseExercise } from '../utility/base-exercises/BaseExercise'; +import { Exercise } from '../../Exercise'; +import { randomFromList } from '../../../shared/ts-utility'; +import { instrumentToVariationList } from './instrumentToAudioFilesMap'; + +export type InstrumentName = 'Flute' | 'Clarinet'; + +export class InstrumentExercise extends BaseExercise { + readonly id: string = 'InstrumentExercise'; + readonly name: string = 'Instrument Identification'; + readonly summary: string = 'Learn to identify the sounds of different instruments'; + readonly explanation: Exercise.ExerciseExplanationContent = ''; // todo + + getAnswerList(): Exercise.AnswerList { + return [ + 'Flute', + 'Clarinet', + ]; + } + + getQuestion(): Exercise.Question { + const instrumentName: InstrumentName = randomFromList(['Flute', 'Clarinet']); // todo: this should be taken out the settings for included answers + const variation: string = randomFromList(instrumentToVariationList[instrumentName]); + return { + type: 'audio', + segments: [{ + rightAnswer: instrumentName, + pathToAudio: `assets/audio/OpenEar_${instrumentName}_${variation}.mp3`, + }] + }; + } + +} diff --git a/src/app/exercise/exercises/InstrumentExercise/instrumentToAudioFilesMap.ts b/src/app/exercise/exercises/InstrumentExercise/instrumentToAudioFilesMap.ts new file mode 100644 index 00000000..3b2676da --- /dev/null +++ b/src/app/exercise/exercises/InstrumentExercise/instrumentToAudioFilesMap.ts @@ -0,0 +1,14 @@ +import { InstrumentName } from './InstrumentExercise'; + +function getNumberedVariations(numberOfVariations: number): string[] { + const array: string[] = []; + for (let i = 0; i < numberOfVariations; i++) { + array.push((i + 1).toString()); + } + return array; +} + +export const instrumentToVariationList: Record = { + Clarinet: getNumberedVariations(2), + Flute: getNumberedVariations(2), +} diff --git a/src/app/services/audio-player.service.spec.ts b/src/app/services/audio-player.service.spec.ts new file mode 100644 index 00000000..748ebcaf --- /dev/null +++ b/src/app/services/audio-player.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AudioPlayerService } from './audio-player.service'; + +describe('AudioPlayerService', () => { + let service: AudioPlayerService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AudioPlayerService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/audio-player.service.ts b/src/app/services/audio-player.service.ts new file mode 100644 index 00000000..b787fadc --- /dev/null +++ b/src/app/services/audio-player.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class AudioPlayerService { + private _currentPath: string | null = null; + private _currentlyPlaying: HTMLAudioElement | null = null; + + async play(path: string): Promise { + if (this._currentlyPlaying) { + this.stop(); + } + + if (this._currentPath !== path) { + this._currentPath = path; + this._currentlyPlaying = new Audio(path); + } + if (!this._currentlyPlaying) { + throw new Error(`Expected _currentPlaying to be defined. Got ${this._currentlyPlaying}`); + } + await this._currentlyPlaying.play(); + } + + stop() { + if (!this._currentlyPlaying) { + return; + } + + this._currentlyPlaying.pause(); + this._currentlyPlaying.currentTime = 0; + } +} diff --git a/src/assets/audio/Clarinet_run_C3.mp3 b/src/assets/audio/Clarinet_run_C3.mp3 new file mode 100644 index 00000000..03e1ec29 Binary files /dev/null and b/src/assets/audio/Clarinet_run_C3.mp3 differ diff --git a/src/assets/audio/Clarinet_run_C4.mp3 b/src/assets/audio/Clarinet_run_C4.mp3 new file mode 100644 index 00000000..45e89af9 Binary files /dev/null and b/src/assets/audio/Clarinet_run_C4.mp3 differ diff --git a/src/assets/audio/Clarinet_run_D2.mp3 b/src/assets/audio/Clarinet_run_D2.mp3 new file mode 100644 index 00000000..38f7cc94 Binary files /dev/null and b/src/assets/audio/Clarinet_run_D2.mp3 differ diff --git a/src/assets/audio/Clarinet_stacatto_C3.mp3 b/src/assets/audio/Clarinet_stacatto_C3.mp3 new file mode 100644 index 00000000..10b838bc Binary files /dev/null and b/src/assets/audio/Clarinet_stacatto_C3.mp3 differ diff --git a/src/assets/audio/Clarinet_stacatto_C4.mp3 b/src/assets/audio/Clarinet_stacatto_C4.mp3 new file mode 100644 index 00000000..26ca98ba Binary files /dev/null and b/src/assets/audio/Clarinet_stacatto_C4.mp3 differ diff --git a/src/assets/audio/Clarinet_stacatto_D2.mp3 b/src/assets/audio/Clarinet_stacatto_D2.mp3 new file mode 100644 index 00000000..5a90d445 Binary files /dev/null and b/src/assets/audio/Clarinet_stacatto_D2.mp3 differ diff --git a/src/assets/audio/Clarinet_sustain_C3.mp3 b/src/assets/audio/Clarinet_sustain_C3.mp3 new file mode 100644 index 00000000..48087b46 Binary files /dev/null and b/src/assets/audio/Clarinet_sustain_C3.mp3 differ diff --git a/src/assets/audio/Clarinet_sustain_C4.mp3 b/src/assets/audio/Clarinet_sustain_C4.mp3 new file mode 100644 index 00000000..f70258f5 Binary files /dev/null and b/src/assets/audio/Clarinet_sustain_C4.mp3 differ diff --git a/src/assets/audio/Clarinet_sustain_C5.mp3 b/src/assets/audio/Clarinet_sustain_C5.mp3 new file mode 100644 index 00000000..fe8d957c Binary files /dev/null and b/src/assets/audio/Clarinet_sustain_C5.mp3 differ diff --git a/src/assets/audio/Flute_run_C3.mp3 b/src/assets/audio/Flute_run_C3.mp3 new file mode 100644 index 00000000..33b24b3d Binary files /dev/null and b/src/assets/audio/Flute_run_C3.mp3 differ diff --git a/src/assets/audio/Flute_run_C4.mp3 b/src/assets/audio/Flute_run_C4.mp3 new file mode 100644 index 00000000..c62bdf0d Binary files /dev/null and b/src/assets/audio/Flute_run_C4.mp3 differ diff --git a/src/assets/audio/Flute_run_C5.mp3 b/src/assets/audio/Flute_run_C5.mp3 new file mode 100644 index 00000000..fd01aba4 Binary files /dev/null and b/src/assets/audio/Flute_run_C5.mp3 differ diff --git a/src/assets/audio/Flute_sustain_C3.mp3 b/src/assets/audio/Flute_sustain_C3.mp3 new file mode 100644 index 00000000..d442ada7 Binary files /dev/null and b/src/assets/audio/Flute_sustain_C3.mp3 differ diff --git a/src/assets/audio/Flute_sustain_C4.mp3 b/src/assets/audio/Flute_sustain_C4.mp3 new file mode 100644 index 00000000..8b45373b Binary files /dev/null and b/src/assets/audio/Flute_sustain_C4.mp3 differ diff --git a/src/assets/audio/Flute_sustain_C5.mp3 b/src/assets/audio/Flute_sustain_C5.mp3 new file mode 100644 index 00000000..9a688eae Binary files /dev/null and b/src/assets/audio/Flute_sustain_C5.mp3 differ diff --git a/src/assets/audio/Flute_sustain_C6.mp3 b/src/assets/audio/Flute_sustain_C6.mp3 new file mode 100644 index 00000000..22a1c2c7 Binary files /dev/null and b/src/assets/audio/Flute_sustain_C6.mp3 differ diff --git a/src/assets/audio/flute_Stacatto_C3.mp3 b/src/assets/audio/flute_Stacatto_C3.mp3 new file mode 100644 index 00000000..0bc064b1 Binary files /dev/null and b/src/assets/audio/flute_Stacatto_C3.mp3 differ diff --git a/src/assets/audio/flute_Stacatto_C4.mp3 b/src/assets/audio/flute_Stacatto_C4.mp3 new file mode 100644 index 00000000..f9f26e74 Binary files /dev/null and b/src/assets/audio/flute_Stacatto_C4.mp3 differ diff --git a/src/assets/audio/flute_Stacatto_C5.mp3 b/src/assets/audio/flute_Stacatto_C5.mp3 new file mode 100644 index 00000000..567ca17b Binary files /dev/null and b/src/assets/audio/flute_Stacatto_C5.mp3 differ