Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/app/exercise/Exercise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,14 @@ export namespace Exercise {
endSeconds: number,
}

export type Question<GAnswer extends string = string> = NotesQuestion<GAnswer> | YouTubeQuestion<GAnswer>;
export interface AudioQuestion<GAnswer extends string = string> extends BaseQuestion<GAnswer, {
rightAnswer: GAnswer,
pathToAudio: string,
}> {
type: 'audio',
}

export type Question<GAnswer extends string = string> = NotesQuestion<GAnswer> | YouTubeQuestion<GAnswer> | AudioQuestion<GAnswer>;

export type Answer<GAnswer extends string = string> = GAnswer;

Expand Down
13 changes: 13 additions & 0 deletions src/app/exercise/exercise.page/state/exercise-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -409,6 +413,15 @@ export class ExerciseStateService implements OnDestroy {
await this._youtubePlayer.onStop();
}

private async _playAudioQuestion(question: Exercise.AudioQuestion): Promise<void> {
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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<InstrumentName> {
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<InstrumentName> {
return [
'Flute',
'Clarinet',
];
}

getQuestion(): Exercise.Question<InstrumentName> {
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`,
}]
};
}

}
Original file line number Diff line number Diff line change
@@ -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<InstrumentName, string[]> = {
Clarinet: getNumberedVariations(2),
Flute: getNumberedVariations(2),
}
16 changes: 16 additions & 0 deletions src/app/services/audio-player.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
33 changes: 33 additions & 0 deletions src/app/services/audio-player.service.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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;
}
}
Binary file added src/assets/audio/Clarinet_run_C3.mp3
Binary file not shown.
Binary file added src/assets/audio/Clarinet_run_C4.mp3
Binary file not shown.
Binary file added src/assets/audio/Clarinet_run_D2.mp3
Binary file not shown.
Binary file added src/assets/audio/Clarinet_stacatto_C3.mp3
Binary file not shown.
Binary file added src/assets/audio/Clarinet_stacatto_C4.mp3
Binary file not shown.
Binary file added src/assets/audio/Clarinet_stacatto_D2.mp3
Binary file not shown.
Binary file added src/assets/audio/Clarinet_sustain_C3.mp3
Binary file not shown.
Binary file added src/assets/audio/Clarinet_sustain_C4.mp3
Binary file not shown.
Binary file added src/assets/audio/Clarinet_sustain_C5.mp3
Binary file not shown.
Binary file added src/assets/audio/Flute_run_C3.mp3
Binary file not shown.
Binary file added src/assets/audio/Flute_run_C4.mp3
Binary file not shown.
Binary file added src/assets/audio/Flute_run_C5.mp3
Binary file not shown.
Binary file added src/assets/audio/Flute_sustain_C3.mp3
Binary file not shown.
Binary file added src/assets/audio/Flute_sustain_C4.mp3
Binary file not shown.
Binary file added src/assets/audio/Flute_sustain_C5.mp3
Binary file not shown.
Binary file added src/assets/audio/Flute_sustain_C6.mp3
Binary file not shown.
Binary file added src/assets/audio/flute_Stacatto_C3.mp3
Binary file not shown.
Binary file added src/assets/audio/flute_Stacatto_C4.mp3
Binary file not shown.
Binary file added src/assets/audio/flute_Stacatto_C5.mp3
Binary file not shown.