diff --git a/package.json b/package.json index 90a0e4a9c8dfdfc1c6daca6d79f6f94f346c4755..ae1f874123ff17fa014e93ef936b34787bebeb3b 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "rxjs": "~7.8.0", "tailwindcss": "^3.4.17", "tslib": "^2.3.0", + "wavesurfer.js": "^7.11.1", "zone.js": "~0.14.3" }, "devDependencies": { diff --git a/src/app/pronunciation/pronunciation.component.css b/src/app/pronunciation/pronunciation.component.css index 4e8dc6be7a4d8278c2c7338f9f44c5043c5664d2..e4fc3f5cec14adf0f91b6bd17abb67a24af67c90 100644 --- a/src/app/pronunciation/pronunciation.component.css +++ b/src/app/pronunciation/pronunciation.component.css @@ -8,8 +8,71 @@ flex-direction: column; } +/* Keep the dropdown at the start */ +.dropdown-row { + display: flex; + align-items: center; + +} + +/* Accessible helper — hide visually but keep available to screen readers */ +.visually-hidden { + position: absolute !important; + height: 1px; + width: 1px; + overflow: hidden; + clip: rect(1px, 1px, 1px, 1px); + white-space: nowrap; + border: 0; + padding: 0; + margin: -1px; +} + +/* Styled select that fits the app button styles */ +.select-dropdown { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + display: inline-block; + min-width: 180px; + height: 36px; + padding: 6px 36px 6px 12px; /* extra right padding for custom arrow */ + font-size: 14px; + line-height: 1; + color: #111827; + background-color: #ffffff; + border: 1px solid #d1d5db; + border-radius: 8px; + background-image: linear-gradient(45deg, transparent 50%, #6b7280 50%), linear-gradient(135deg, #6b7280 50%, transparent 50%), linear-gradient(to right, #fff, #fff); + background-position: calc(100% - 18px) calc(1em + 2px), calc(100% - 13px) calc(1em + 2px), 100% 0; + background-size: 6px 6px, 6px 6px, 2.5em 2.5em; + background-repeat: no-repeat; + cursor: pointer; +} + + /* Hover / focus states for accessibility */ + .select-dropdown:hover { + border-color: #9ca3af; + } + + .select-dropdown:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(59,130,246,0.14); + border-color: #3b82f6; + } + + /* Smaller variant if needed */ + .select-dropdown.small { + min-width: 140px; + height: 32px; + font-size: 13px; + border-radius: 6px; + } + .header { - text-align: center; + /*text-align: center;*/ + display:flex; + gap:13vw; } .title { @@ -53,8 +116,8 @@ .controls-section { display: flex; flex-direction: column; - justify-content: center; - gap: 2vw; + justify-content: space-evenly; + gap: 0.5vw; width: 30vw; } diff --git a/src/app/pronunciation/pronunciation.component.html b/src/app/pronunciation/pronunciation.component.html index c46a6867bc6b3939d3b7ab7dd41e75c4f4a72b6e..6aba4ebb9252421b7e0f38a08715a204d81a557b 100644 --- a/src/app/pronunciation/pronunciation.component.html +++ b/src/app/pronunciation/pronunciation.component.html @@ -1,7 +1,16 @@
- +

Pronunciation Trainer

@@ -44,7 +53,7 @@ [class.active]="!isOriginal" (click)="isOriginal = false; updateSelection()"> Original Voice -
+
+ +
+
+
Teacher
+
+
+ +
+
Student
+
+
+
+ + - - -
-
-

{{ result.suggestion }}

-
+ +
+

Feedback & Suggestions

+
+
+

{{ result?.suggestion }}

- +
@@ -188,6 +211,6 @@ - -
+
+ diff --git a/src/app/pronunciation/pronunciation.component.ts b/src/app/pronunciation/pronunciation.component.ts index 2e1ac9d8f63f7ae545d5f10773a115240ba4d016..5be5f744f1c9b8f562fb2c5043ebc8620a6f4b2c 100644 --- a/src/app/pronunciation/pronunciation.component.ts +++ b/src/app/pronunciation/pronunciation.component.ts @@ -1,8 +1,8 @@ -// (trimmed waveform/canvas logic; kept recording/playback, API and pagination) import { Component, OnDestroy, ChangeDetectorRef, Inject, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { ApiService } from './pronunciation.service'; // adjust path if needed +import WaveSurfer from 'wavesurfer.js'; @Component({ selector: 'app-pronunciation', @@ -92,6 +92,11 @@ export class PronunciationComponent implements OnDestroy, OnInit { private recordingStopPromise?: Promise; private recordingStopResolver?: (() => void) | null = null; + // WaveSurfer state + pronMode: string = 'phonetics'; // current dropdown mode + private teacherWaveSurfer: any; + private studentWaveSurfer: any; + constructor(private http: HttpClient, private cdr: ChangeDetectorRef, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any, @@ -103,14 +108,69 @@ export class PronunciationComponent implements OnDestroy, OnInit { this.showQuestion(0); } - // Called when the toggle changes - updateSelection() { + // handle dropdown mode change + public onModeChange(mode: string | null | undefined) { + const m = (mode ?? 'phonetics').toString().trim().toLowerCase(); + console.log('[Pronunciation] onModeChange ->', { raw: mode, normalized: m }); + this.pronMode = m; + if (m === 'waveform') { + this.initWaveSurfers(); + const cached = this.teacherAudioCache.get(this.word); + if (cached && this.teacherWaveSurfer) { + try { this.teacherWaveSurfer.load(cached); } catch { /* ignore */ } + } + if (this.recordedBlobUrl && this.studentWaveSurfer) { + try { this.studentWaveSurfer.load(this.recordedBlobUrl); } catch { /* ignore */ } + } + } else { + this.destroyWaveSurfers(); + } + } + + // Added: updateSelection used by template toggle + public updateSelection(): void { // stop any current teacher playback and clear cache so selection change takes immediate effect this.stopTeacherPlayback(); this.teacherAudioCache.clear(); this.cdr.detectChanges(); } + private initWaveSurfers(): void { + // avoid re-creating + if (!this.teacherWaveSurfer) { + try { + this.teacherWaveSurfer = WaveSurfer.create({ + container: '#teacherWaveform', + waveColor: '#d0e9ff', + progressColor: '#007acc', + cursorColor: '#333', + height: 80 + + }); + } catch (err) { console.warn('could not create teacher WaveSurfer', err); } + } + + if (!this.studentWaveSurfer) { + try { + this.studentWaveSurfer = WaveSurfer.create({ + container: '#studentWaveform', + waveColor: '#ffdcd0', + progressColor: '#ff6b3d', + cursorColor: '#333', + height: 80 + + }); + } catch (err) { console.warn('could not create student WaveSurfer', err); } + } + } + + private destroyWaveSurfers(): void { + try { + if (this.teacherWaveSurfer) { try { this.teacherWaveSurfer.destroy(); } catch { } this.teacherWaveSurfer = undefined; } + if (this.studentWaveSurfer) { try { this.studentWaveSurfer.destroy(); } catch { } this.studentWaveSurfer = undefined; } + } catch { /* ignore */ } + } + // Helper: build static asset path for a word (normalize to lowercase, underscores) private getStaticTeacherAudioPath(word: string): string { const clean = (word || '') @@ -118,7 +178,7 @@ export class PronunciationComponent implements OnDestroy, OnInit { .trim() .replace(/\s+/g, '_') // spaces -> underscores .replace(/[^a-z0-9_]/g, ''); // remove other chars - return `assets/audio/original/${clean}.m4a`; + return `assets/audio/${clean}.mp3`; } // Play teacher audio: use local static file when original voice selected, otherwise fall back to existing flow. @@ -200,10 +260,53 @@ export class PronunciationComponent implements OnDestroy, OnInit { }); } - // Simplified playback: no waveform/canvas support in finalized HTML + // Simplified playback: now supports WaveSurfer mode public playAudioWithWaveform(src: string, type: 'teacher' | 'recorded'): void { if (!src) return; + // If the dropdown is in waveform mode, use WaveSurfer instances + if (this.pronMode === 'waveform') { + // ensure wavesurfer instances exist + this.initWaveSurfers(); + + if (type === 'teacher') { + try { + if (this.teacherWaveSurfer) { + // load will accept blob/object URL or remote URL + this.teacherWaveSurfer.load(src); + // autoplay when ready + this.teacherWaveSurfer.once('ready', () => { + try { this.teacherWaveSurfer.play(); } catch { /* ignore */ } + }); + } else { + // fallback to simple audio element + this.fallbackAudioPlay(src, 'teacher'); + } + } catch (e) { + console.warn('WaveSurfer teacher play failed, fallback', e); + this.fallbackAudioPlay(src, 'teacher'); + } + } else { + // recorded + try { + if (this.studentWaveSurfer) { + this.studentWaveSurfer.load(src); + this.studentWaveSurfer.once('ready', () => { + try { this.studentWaveSurfer.play(); } catch { /* ignore */ } + }); + } else { + this.fallbackAudioPlay(src, 'recorded'); + } + } catch (e) { + console.warn('WaveSurfer student play failed, fallback', e); + this.fallbackAudioPlay(src, 'recorded'); + } + } + + return; + } + + // Non-waveform fallback (existing behavior) if (type === 'teacher') { this.stopTeacherPlayback(); this.teacherAudio = new Audio(); @@ -238,11 +341,32 @@ export class PronunciationComponent implements OnDestroy, OnInit { } } + // Simple fallback audio creation used when wavesurfer errors + private fallbackAudioPlay(src: string, type: 'teacher' | 'recorded') { + if (type === 'teacher') { + this.stopTeacherPlayback(); + const a = new Audio(src); + try { if (!src.startsWith('blob:')) a.crossOrigin = 'anonymous'; } catch { } + a.play().catch(() => { /* ignore */ }); + this.teacherAudio = a; + } else { + this.stopRecordedPlayback(); + const a = new Audio(src); + try { if (!src.startsWith('blob:')) a.crossOrigin = 'anonymous'; } catch { } + a.play().catch(() => { /* ignore */ }); + this.recordedAudio = a; + } + } + private stopTeacherPlayback(): void { if (this.teacherAudio) { try { this.teacherAudio.pause(); this.teacherAudio.onended = null; } catch { } this.teacherAudio = undefined; } + // also pause wavesurfer if present + if (this.teacherWaveSurfer) { + try { this.teacherWaveSurfer.pause(); } catch { } + } } private stopRecordedPlayback(): void { @@ -250,6 +374,9 @@ export class PronunciationComponent implements OnDestroy, OnInit { try { this.recordedAudio.pause(); this.recordedAudio.onended = null; } catch { } this.recordedAudio = undefined; } + if (this.studentWaveSurfer) { + try { this.studentWaveSurfer.pause(); } catch { } + } } // Loading state for check pronunciation request @@ -269,15 +396,12 @@ export class PronunciationComponent implements OnDestroy, OnInit { } } - // If there is an object URL for the finalized recording, prefer fetching it. let audioBlob: Blob | null = null; if (this.recordedBlobs && this.recordedBlobs.length > 0) { - // merge recorded chunk blobs const inferredType = this.recordedBlobs[0]?.type || 'audio/webm'; audioBlob = new Blob(this.recordedBlobs, { type: inferredType }); } else if (this.audioBlobUrl) { - // fetch blob from the object URL created in onstop try { const resp = await fetch(this.audioBlobUrl); audioBlob = await resp.blob(); @@ -313,17 +437,129 @@ export class PronunciationComponent implements OnDestroy, OnInit { form.append('audio', audioBlob, filename); form.append('word', this.word); + // ensure mode normalized and appended + const modeToSend = (this.pronMode ?? 'phonetics').toString().trim().toLowerCase(); + form.append('mode', modeToSend); + + // optional: include reference file + if (this.selectedFile) { + form.append('reference', this.selectedFile); + } + + // Debug: log mode and FormData entries (inspect network panel to confirm) + console.log('[Pronunciation] sending check_pronunciation', { word: this.word, mode: modeToSend }); + try { + // enumerate FormData entries for verification + for (const [k, v] of (form as any).entries()) { + // for files, log name and size + if (v instanceof File) { + console.log(' FormData entry:', k, 'File name=', v.name, 'size=', v.size); + } else { + console.log(' FormData entry:', k, v); + } + } + } catch (e) { + console.warn('Could not enumerate FormData entries for logging', e); + } + try { const res = await this.http.post(`${this.backendURL}/pron/check_pronunciation`, form).toPromise(); - // Compute phonemic score (backend provides phoneme_similarity in range 0..1) + // Waveform mode response handling + if (res?.mode === 'waveform' || typeof res?.waveform_similarity !== 'undefined') { + const sim = Number(res.waveform_similarity ?? 0); + const matched = Boolean(res.waveform_match); + + // Put similarity on your speakometer / needle + this.result = { + score: Math.round(sim), + suggestion: matched ? 'Audio matches teacher' : 'Audio does not match teacher', + feedbackAudioUrl: undefined, + phonemeScore: sim, + acousticScore: undefined + }; + + // Show simple structured feedback + this.suggestions = [{ + id: 1, + title: 'Waveform Match', + message: `Similarity: ${sim}% — Matched: ${matched ? 'Yes' : 'No'}` + }]; + + // If backend returned teacher audio path, load it into WaveSurfer; otherwise request teacher audio and load + if (this.pronMode === 'waveform') { + // load student into wavesurfer + if (this.recordedBlobUrl && this.studentWaveSurfer) { + try { this.studentWaveSurfer.load(this.recordedBlobUrl); } catch { /* ignore */ } + } + + // prefer backend-provided teacher url if present + if (res.teacher_audio_url) { + const teacherUrl = res.teacher_audio_url.startsWith('http') ? res.teacher_audio_url : `${this.backendURL}/${res.teacher_audio_url}`; + this.teacherAudioCache.set(this.word, teacherUrl); + if (this.teacherWaveSurfer) { + try { this.teacherWaveSurfer.load(teacherUrl); } catch { /* ignore */ } + } + } else { + // otherwise try cached or request generation + const cached = this.teacherAudioCache.get(this.word); + if (cached && this.teacherWaveSurfer) { + try { this.teacherWaveSurfer.load(cached); } catch { /* ignore */ } + } else { + // call API to get teacher URL (generate_teacher_audio returns url) + this.api.generateTeacherAudio(this.word, this.selectedFile).subscribe({ + next: ({ audioUrl }) => { + this.teacherAudioCache.set(this.word, audioUrl); + if (this.teacherWaveSurfer) { + try { this.teacherWaveSurfer.load(audioUrl); } catch { /* ignore */ } + } + }, + error: () => { /* ignore */ } + }); + } + } + } + + this.suggestionPage = 0; + this.isChecking = false; + this.cdr.detectChanges(); + return; + } + + // If frontend is in "select" mode do NOT bind score or suggestion — only populate suggestions returned by backend. + const isSelectMode = modeToSend === 'select' || this.pronMode === 'select'; + if (isSelectMode) { + if (Array.isArray(res?.suggestion)) { + this.suggestions = res.suggestion.map((s: any, idx: number) => { + const raw = (typeof s === 'string') ? s : (s.message ?? JSON.stringify(s)); + const msg = this.sanitizeFeedbackText(raw); + const explicitTitle = (s && s.title) ? this.sanitizeFeedbackText(String(s.title)) : ''; + const title = explicitTitle || this.deriveTitleFromMessage(msg, idx); + return { + id: s?.id ?? (idx + 1), + title, + type: s?.type ?? '', + message: msg + }; + }); + } else if (typeof res?.suggestion === 'string' && res.suggestion.trim()) { + const msg = this.sanitizeFeedbackText(res.suggestion); + this.suggestions = [{ id: 1, title: this.deriveTitleFromMessage(msg, 0), type: '', message: msg }]; + } else { + this.suggestions = []; + } + // Do not set this.result.score or this.result.suggestion for select mode + this.suggestionPage = 0; + this.isChecking = false; + this.cdr.detectChanges(); + return; + } + + // --- existing phoneme/ASR handling (unchanged) --- const phonemeSimilarity = Number(res?.phoneme_similarity ?? res?.phonemeSimilarity ?? 0); const phonemePct = Math.round(Math.max(0, Math.min(1, phonemeSimilarity)) * 100); - - // Keep acoustic score if you want to display/use later const acousticScore = Number(res?.acoustic_score ?? res?.score ?? res?.score_acoustic ?? 0); - // Set the speakometer to show phonemic score scaled to 0..100 this.result = { score: phonemePct, suggestion: (typeof res?.suggestion === 'string') ? res.suggestion : '', @@ -332,7 +568,7 @@ export class PronunciationComponent implements OnDestroy, OnInit { acousticScore: isNaN(acousticScore) ? undefined : acousticScore }; - // Build structured suggestions with sanitized messages and derived titles + // existing suggestion building... if (Array.isArray(res?.suggestion)) { this.suggestions = res.suggestion.map((s: any, idx: number) => { const raw = (typeof s === 'string') ? s : (s.message ?? JSON.stringify(s)); @@ -352,8 +588,6 @@ export class PronunciationComponent implements OnDestroy, OnInit { } else { this.suggestions = []; } - - // Reset suggestion pagination this.suggestionPage = 0; } catch (err) { @@ -406,6 +640,7 @@ export class PronunciationComponent implements OnDestroy, OnInit { ngOnDestroy(): void { this.stopTeacherPlayback(); this.stopRecordedPlayback(); + this.destroyWaveSurfers(); if (this.micStream) { try { this.micStream.getTracks().forEach(t => t.stop()); } catch { } this.micStream = undefined; @@ -427,6 +662,11 @@ export class PronunciationComponent implements OnDestroy, OnInit { requestTeacherAudio() { const cached = this.teacherAudioCache.get(this.word); if (cached) { + // if in waveform mode, load into wavesurfer instead of creating new audio + if (this.pronMode === 'waveform' && this.teacherWaveSurfer) { + try { this.teacherWaveSurfer.load(cached); this.teacherWaveSurfer.once('ready', () => this.teacherWaveSurfer.play()); } catch { /* ignore */ } + return; + } const a = new Audio(cached); a.play().catch(err => console.warn('play failed', err)); return; @@ -435,8 +675,12 @@ export class PronunciationComponent implements OnDestroy, OnInit { this.api.generateTeacherAudio(this.word, this.selectedFile).subscribe({ next: ({ audioUrl }) => { this.teacherAudioCache.set(this.word, audioUrl); - const a = new Audio(audioUrl); - a.play().catch(err => console.warn('play failed', err)); + if (this.pronMode === 'waveform' && this.teacherWaveSurfer) { + try { this.teacherWaveSurfer.load(audioUrl); this.teacherWaveSurfer.once('ready', () => this.teacherWaveSurfer.play()); } catch { /* ignore */ } + } else { + const a = new Audio(audioUrl); + a.play().catch(err => console.warn('play failed', err)); + } }, error: err => { console.error('generateTeacherAudio failed', err); @@ -486,6 +730,11 @@ export class PronunciationComponent implements OnDestroy, OnInit { this.audioBlobUrl = URL.createObjectURL(audioBlob); this.recordedBlobUrl = this.audioBlobUrl; + // If in waveform mode, load the student waveform for immediate playback/visualization + if (this.pronMode === 'waveform' && this.studentWaveSurfer) { + try { this.studentWaveSurfer.load(this.recordedBlobUrl); } catch { /* ignore */ } + } + try { if (this.micStream) this.micStream.getTracks().forEach((t: any) => t.stop()); } catch { } this.micStream = undefined; @@ -609,6 +858,14 @@ export class PronunciationComponent implements OnDestroy, OnInit { this.phonetics = q.phonetics ?? ''; this.imgsrc = q.imgsrc ?? this.imgsrc; + // If in waveform mode and teacher audio cached, load it for the new word + if (this.pronMode === 'waveform' && this.teacherWaveSurfer) { + const cached = this.teacherAudioCache.get(this.word); + if (cached) { + try { this.teacherWaveSurfer.load(cached); } catch { /* ignore */ } + } + } + this.cdr.detectChanges(); } } diff --git a/src/app/pronunciation/pronunciation.service.ts b/src/app/pronunciation/pronunciation.service.ts index 2660318f242de017dbe367b5aba7db35873bc99f..f6020cd0ed9a165fc81933bbf02cec136910b8eb 100644 --- a/src/app/pronunciation/pronunciation.service.ts +++ b/src/app/pronunciation/pronunciation.service.ts @@ -18,34 +18,25 @@ export class ApiService { constructor(private http: HttpClient) { } + generateTeacherAudio(word: string, reference?: File): Observable<{ audioUrl: string }> { const url = `${this.pronBase}/generate_teacher_audio`; + // Always send FormData so Flask can read request.form (and accept optional reference file). + const form = new FormData(); + form.append('word', word); if (reference) { - const form = new FormData(); - form.append('word', word); form.append('reference', reference, reference.name); - - // Do NOT set Content-Type header for FormData; browser sets the boundary. - return this.http.post(url, form).pipe( - map(res => ({ audioUrl: `${this.pronBase}/${res.audio_url}` })), - tap(r => console.log('[API] generateTeacherAudio (with ref) ->', r)), - catchError(err => { - console.error('[API] generateTeacherAudio error', err); - return throwError(() => err); - }) - ); - } else { - const headers = { 'Content-Type': 'application/json' }; - return this.http.post(url, { word }, { headers }).pipe( - map(res => ({ audioUrl: `${this.pronBase}/${res.audio_url}` })), - tap(r => console.log('[API] generateTeacherAudio ->', r)), - catchError(err => { - console.error('[API] generateTeacherAudio error', err); - return throwError(() => err); - }) - ); } + + return this.http.post(url, form).pipe( + map(res => ({ audioUrl: `${this.pronBase}/${res.audio_url}` })), + tap(r => console.log('[API] generateTeacherAudio ->', r)), + catchError(err => { + console.error('[API] generateTeacherAudio error', err); + return throwError(() => err); + }) + ); } // New: request teacher audio as raw bytes (no server-side persistent file) @@ -53,30 +44,19 @@ export class ApiService { generateTeacherAudioStream(word: string, reference?: File): Observable { const streamUrl = `${this.pronBase}/generate_teacher_audio_stream`; - if (reference) { - const form = new FormData(); - form.append('word', word); - form.append('reference', reference, reference.name); - // post form and expect blob response - return this.http.post(streamUrl, form, { responseType: 'blob' }).pipe( - tap(() => console.log('[API] generateTeacherAudioStream (with ref)')), - map((b: any) => b as Blob), - catchError(err => { - console.error('[API] generateTeacherAudioStream error', err); - return throwError(() => err); - }) - ); - } else { - const headers = { 'Content-Type': 'application/json' }; - return this.http.post(streamUrl, { word }, { headers, responseType: 'blob' as 'json' }).pipe( - tap(() => console.log('[API] generateTeacherAudioStream')), - map((b: any) => b as Blob), - catchError(err => { - console.error('[API] generateTeacherAudioStream error', err); - return throwError(() => err); - }) - ); - } + const form = new FormData(); + form.append('word', word); + if (reference) form.append('reference', reference, reference.name); + + // Request blob response. Using 'blob' as 'json' keeps TS happy with HttpClient overloads. + return this.http.post(streamUrl, form, { responseType: 'blob' as 'json' }).pipe( + tap(() => console.log('[API] generateTeacherAudioStream')), + map((b: any) => b as Blob), + catchError(err => { + console.error('[API] generateTeacherAudioStream error', err); + return throwError(() => err); + }) + ); } // Helper: generate teacher audio then download it as a Blob @@ -84,19 +64,11 @@ export class ApiService { generateTeacherAudioBlob(word: string, reference?: File): Observable<{ audioUrl: string; blob: Blob }> { const postUrl = `${this.pronBase}/generate_teacher_audio`; - const post$ = reference - ? (() => { - const form = new FormData(); - form.append('word', word); - form.append('reference', reference, reference.name); - return this.http.post(postUrl, form); - })() - : (() => { - const headers = { 'Content-Type': 'application/json' }; - return this.http.post(postUrl, { word }, { headers }); - })(); + const form = new FormData(); + form.append('word', word); + if (reference) form.append('reference', reference, reference.name); - return post$.pipe( + return this.http.post(postUrl, form).pipe( switchMap((res: any) => { // backend returns e.g. "audio/teacher-xxxx.wav" — build full URL under /pron const audioUrl = `${this.pronBase}/${res.audio_url}`; diff --git a/src/assets/audio/apple.mp3 b/src/assets/audio/apple.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..45f4de88a7d0322609c341b1005c3d4b6d40fe89 --- /dev/null +++ b/src/assets/audio/apple.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36b263be26c27afa3d24960fc53ce9d6b5363714dda95e22b230ce8eb8c607c9 +size 12408 diff --git a/src/assets/audio/ball.mp3 b/src/assets/audio/ball.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..26fdca27583deb2a3256358383aec3cbcf41af10 --- /dev/null +++ b/src/assets/audio/ball.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e04526715ad4a8ec31f85eed6db1c377bf669e00a9bd2d551d19daec5460418f +size 10392 diff --git a/src/assets/audio/cat.mp3 b/src/assets/audio/cat.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..cce3f4f4c9f43595fba58dc5f80c5f93fdaa9e14 --- /dev/null +++ b/src/assets/audio/cat.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f28d8a06857382b8e7cd0b51712696ecb17d9fcdfd2a6928afb7b199b081e23e +size 12912 diff --git a/src/assets/audio/dog.mp3 b/src/assets/audio/dog.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8972e2e76c564eaf21cc5c3130d43367bf09dc25 --- /dev/null +++ b/src/assets/audio/dog.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:988790d50de59b4422a425aa3ee3bc0b38e5e03ec14354d525a57cd313f6a23a +size 11184 diff --git a/src/assets/audio/egg.mp3 b/src/assets/audio/egg.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e51e423e4a836604867d212a3f794561c07e53fb --- /dev/null +++ b/src/assets/audio/egg.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f50aecc9e38956f68572510ee93c00223f4a3f684125b4580635f13963ebaa8f +size 9936 diff --git a/src/assets/audio/fish.mp3 b/src/assets/audio/fish.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..434b8db0b51c52112c57293efdbf188d65c6b487 --- /dev/null +++ b/src/assets/audio/fish.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bb0887b2bc028a2d454e6bd9bf4f3c80a5dc6bccfd9ea1417b3693f3424da24 +size 9456 diff --git a/src/assets/audio/grapes.mp3 b/src/assets/audio/grapes.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..29a73b432c2f70c16316992bc1b4b9c584bc81e3 --- /dev/null +++ b/src/assets/audio/grapes.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa8a75e85c457f948c0b810c16665667c99c095c71224c267f222b5dceabdb2f +size 13008 diff --git a/src/assets/audio/hat.mp3 b/src/assets/audio/hat.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6fbab27690bed5e014a227b893dc2841d0cfbd72 --- /dev/null +++ b/src/assets/audio/hat.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2a6e1d6d94fdbfdf3615e352715c652d72bc104fa3ff5ce7eb20b987b1a5468 +size 10800 diff --git a/src/assets/audio/ice_cream.mp3 b/src/assets/audio/ice_cream.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a7c52aef9acf54fe15f1cf8e76132ed7ad99443c --- /dev/null +++ b/src/assets/audio/ice_cream.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33cf0de8e2225a9eddfc0083c9a50e1f3ad5cf78a1b937d04694a250ed62ac83 +size 14784 diff --git a/src/assets/audio/jar.mp3 b/src/assets/audio/jar.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1b1e291dfec1072f8bc527630f608f0725741075 --- /dev/null +++ b/src/assets/audio/jar.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cbb5f8dcc21cc4c78a3e2c61a6bb1a8f16eb2ed6e656a2b5b5c606a577d68cf +size 12432 diff --git a/src/assets/audio/kite.mp3 b/src/assets/audio/kite.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..394fff4882ad8349e9dd48a3a76ab6b1aaee0c0c --- /dev/null +++ b/src/assets/audio/kite.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a1f086fbe0b46c4281d49cfdc4497259322f37c35aebee88e7ecba8bc507d47 +size 12216 diff --git a/src/assets/audio/lion.mp3 b/src/assets/audio/lion.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..bdba5c6b5651e9516617e3d77131d590d9e2ac01 --- /dev/null +++ b/src/assets/audio/lion.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97e8dfeb933de825ddccb7ab23616f0544fb1bf13dfbe7174d79dcd5769c3493 +size 14112 diff --git a/src/assets/audio/moon.mp3 b/src/assets/audio/moon.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5b9ed229e8d72988fbe2948bb7d9a46da7d1c6b9 --- /dev/null +++ b/src/assets/audio/moon.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08b408ab5d8d19c22a4ff80da95e954553473ace618347e67a65525a1296743d +size 11016 diff --git a/src/assets/audio/nest.mp3 b/src/assets/audio/nest.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f3e0d0eec3c9661d93f9c2249097c593fd76022f --- /dev/null +++ b/src/assets/audio/nest.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a6d84024ed543fc47619bce66d258cc9d89ed86d0960c6b02b3ff984947d2cf +size 11376 diff --git a/src/assets/audio/orange.mp3 b/src/assets/audio/orange.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f9cb6d233dee82e2aedc0282b4c048f6ba3d0cdb --- /dev/null +++ b/src/assets/audio/orange.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28e970be8938cdae9ba382519ad8df8f59006474ae0dccc3a1026537b6916f6a +size 15408 diff --git a/src/assets/audio/original/ball.m4a b/src/assets/audio/original/ball.m4a deleted file mode 100644 index 155350c014d0375f810092418e6c370ee149be2d..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/ball.m4a and /dev/null differ diff --git a/src/assets/audio/original/cat.m4a b/src/assets/audio/original/cat.m4a deleted file mode 100644 index 7ad9eb2b1f3acee8a7984fa2321e46ee39f8ac8d..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/cat.m4a and /dev/null differ diff --git a/src/assets/audio/original/dog.m4a b/src/assets/audio/original/dog.m4a deleted file mode 100644 index 67e0e7f225e47a255546e732d2b4e924d08608c1..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/dog.m4a and /dev/null differ diff --git a/src/assets/audio/original/egg.m4a b/src/assets/audio/original/egg.m4a deleted file mode 100644 index 7a777c236ff432109772361b2bae18521088146b..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/egg.m4a and /dev/null differ diff --git a/src/assets/audio/original/fish.m4a b/src/assets/audio/original/fish.m4a deleted file mode 100644 index 7290e8bbdaed579b22582826951dd819151aeaab..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/fish.m4a and /dev/null differ diff --git a/src/assets/audio/original/grapes.m4a b/src/assets/audio/original/grapes.m4a deleted file mode 100644 index 7700ed8125b7b9844fff3bc9c05fa86156863d7f..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/grapes.m4a and /dev/null differ diff --git a/src/assets/audio/original/hat.m4a b/src/assets/audio/original/hat.m4a deleted file mode 100644 index e5418ea12810d20c6b1d44e3d5ba0838f766108b..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/hat.m4a and /dev/null differ diff --git a/src/assets/audio/original/ice_cream.m4a b/src/assets/audio/original/ice_cream.m4a deleted file mode 100644 index 4ea147039c65cfeeac867e5eb6032e75433b7a7e..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/ice_cream.m4a and /dev/null differ diff --git a/src/assets/audio/original/jar.m4a b/src/assets/audio/original/jar.m4a deleted file mode 100644 index dc13730c02399957d048d0fe1116f2d987d8c437..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/jar.m4a and /dev/null differ diff --git a/src/assets/audio/original/kite.m4a b/src/assets/audio/original/kite.m4a deleted file mode 100644 index b23ae2e52085328ef49e62466e4b790bfabab653..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/kite.m4a and /dev/null differ diff --git a/src/assets/audio/original/lion.m4a b/src/assets/audio/original/lion.m4a deleted file mode 100644 index 8a2dfb9681a68078f0b922b840e3dcd7fb5992fc..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/lion.m4a and /dev/null differ diff --git a/src/assets/audio/original/moon.m4a b/src/assets/audio/original/moon.m4a deleted file mode 100644 index d0df29711e45c4f152cc6cc84b272c259bd11bd2..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/moon.m4a and /dev/null differ diff --git a/src/assets/audio/original/nest.m4a b/src/assets/audio/original/nest.m4a deleted file mode 100644 index 4cfe0a67a030625f25fd7f17dfda92a64f26dff5..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/nest.m4a and /dev/null differ diff --git a/src/assets/audio/original/orange.m4a b/src/assets/audio/original/orange.m4a deleted file mode 100644 index e92e47f027f436d38215b18844296c0586e3fdbd..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/orange.m4a and /dev/null differ diff --git a/src/assets/audio/original/pig.m4a b/src/assets/audio/original/pig.m4a deleted file mode 100644 index 73fd04e3b816c6df6694b342215515e5de479219..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/pig.m4a and /dev/null differ diff --git a/src/assets/audio/original/queen.m4a b/src/assets/audio/original/queen.m4a deleted file mode 100644 index 5809fd2a6850d261bc56607d2258d81ac2a02c72..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/queen.m4a and /dev/null differ diff --git a/src/assets/audio/original/rabbit.m4a b/src/assets/audio/original/rabbit.m4a deleted file mode 100644 index 8124a276e7e48f8a924431c213ef2098c96faf16..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/rabbit.m4a and /dev/null differ diff --git a/src/assets/audio/original/sun.m4a b/src/assets/audio/original/sun.m4a deleted file mode 100644 index 4843dfb2a162e3a138639a0a3310b7e679fd63f9..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/sun.m4a and /dev/null differ diff --git a/src/assets/audio/original/tree.m4a b/src/assets/audio/original/tree.m4a deleted file mode 100644 index 3f3228eb28c92626168e07ec34ee2f592a94e07e..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/tree.m4a and /dev/null differ diff --git a/src/assets/audio/original/van.m4a b/src/assets/audio/original/van.m4a deleted file mode 100644 index 4405a790671f52ba889602e10925d3f3850a1278..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/van.m4a and /dev/null differ diff --git a/src/assets/audio/original/watch.m4a b/src/assets/audio/original/watch.m4a deleted file mode 100644 index 6eb48d0d48f86e1c1a3f0c292d3173ad328b2f42..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/watch.m4a and /dev/null differ diff --git a/src/assets/audio/original/xylophone.m4a b/src/assets/audio/original/xylophone.m4a deleted file mode 100644 index 3f4a4656c849a80637ad864b6df23cb44ef1a31a..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/xylophone.m4a and /dev/null differ diff --git a/src/assets/audio/original/yarn.m4a b/src/assets/audio/original/yarn.m4a deleted file mode 100644 index 7f7ab351415eb5ceaa1177a98bbb28a696c422cb..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/yarn.m4a and /dev/null differ diff --git a/src/assets/audio/original/zebra.m4a b/src/assets/audio/original/zebra.m4a deleted file mode 100644 index c8134ef6e2ecfb0f926c4f6435b90849f4743193..0000000000000000000000000000000000000000 Binary files a/src/assets/audio/original/zebra.m4a and /dev/null differ diff --git a/src/assets/audio/pig.mp3 b/src/assets/audio/pig.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..fa446d1a2666e159f60f9cae9a95950b98b321c2 --- /dev/null +++ b/src/assets/audio/pig.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b73eb1b22b1e2615a6336c813a974ddc71e6e9e047453a92b5ab7c1714318007 +size 9504 diff --git a/src/assets/audio/queen.mp3 b/src/assets/audio/queen.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..dbdea523b86fdb986bf7df00d385b27aac52be0e --- /dev/null +++ b/src/assets/audio/queen.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f822d6e09e355dd10e6ec9a86567da8c8865a4e9207711dc6c6f91fe73a0fae4 +size 11568 diff --git a/src/assets/audio/rabbit.mp3 b/src/assets/audio/rabbit.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..21ab33c7e1703f027a91ae68fd912a2dc6113e76 --- /dev/null +++ b/src/assets/audio/rabbit.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fce73c31d372c8e310bc5d3e47bb973b203410a3b599bb6322ecdf2fd096672 +size 10776 diff --git a/src/assets/audio/sun.mp3 b/src/assets/audio/sun.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2ba4393869090be4df53d76d5b50efd512ab8307 --- /dev/null +++ b/src/assets/audio/sun.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4e405e94eb7a7a9ce89b7b69fca109ccceac1b41c01fc165d882a8a9f1e7a8e +size 11904 diff --git a/src/assets/audio/tree.mp3 b/src/assets/audio/tree.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..653a769b0cfea077ed8a90cd36c9b97c96a4abe2 --- /dev/null +++ b/src/assets/audio/tree.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9159be7faa03940ac783d589827efbf9666d3daf15e67a5953b7af8db2ac9703 +size 13008 diff --git a/src/assets/audio/umbrella.mp3 b/src/assets/audio/umbrella.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e40e144c52608102dfebf4fb8e511535a77a771c --- /dev/null +++ b/src/assets/audio/umbrella.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec6c59c06372515022b65731f2f22804b469d342e8d477d1b7c13a030ec33650 +size 14472 diff --git a/src/assets/audio/van.mp3 b/src/assets/audio/van.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..24918820ba67b8b4ffd8ea8af098c9761bf143eb --- /dev/null +++ b/src/assets/audio/van.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c480b9dccbe19e3476b6eb2bd146de00d497c9a791f15ce9115536eb740f915 +size 10920 diff --git a/src/assets/audio/watch.mp3 b/src/assets/audio/watch.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0ce2e8868365e4638cfd3eafc87868dfcaba4177 --- /dev/null +++ b/src/assets/audio/watch.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ce263f5a14dc765359bd7190d1c1c08e865cc0d8a7c731246a5c012753a4ed4 +size 14256 diff --git a/src/assets/audio/xylophone.mp3 b/src/assets/audio/xylophone.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..31ccf2d08a1dea161bd87958b7e58323505064ca --- /dev/null +++ b/src/assets/audio/xylophone.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:153cccd194813d091e9982995deb4531c3a8c1fc231f18426c6582a6b38dac93 +size 17904 diff --git a/src/assets/audio/yarn.mp3 b/src/assets/audio/yarn.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1dcdc128f095460402e9a72203401f735e6d4aba --- /dev/null +++ b/src/assets/audio/yarn.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c42885de2f656500a16c9b72ee84558470df5a05237e3d5304b27075c7a2bcbf +size 13392 diff --git a/src/assets/audio/zebra.mp3 b/src/assets/audio/zebra.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2c4f3ba964341aaaa70a49e386488488035063d5 --- /dev/null +++ b/src/assets/audio/zebra.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eecafd487e7f1c96e5a86e1f41430ff7d32e96cd26aecbfd4454b47969cf3292 +size 15096