Spaces:
Sleeping
Sleeping
File size: 7,283 Bytes
5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b 5e84ffc d53fa1b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
"""Web application for interval practice."""
import time
from typing import Any, List, Tuple
import numpy as np
from improvisation_lab.application.base_app import BasePracticeApp
from improvisation_lab.config import Config
from improvisation_lab.domain.music_theory import Intervals
from improvisation_lab.infrastructure.audio import WebAudioProcessor
from improvisation_lab.presentation.interval_practice import (
IntervalViewTextManager, WebIntervalPracticeView)
from improvisation_lab.service import IntervalPracticeService
class WebIntervalPracticeApp(BasePracticeApp):
"""Web application class for interval practice."""
def __init__(self, service: IntervalPracticeService, config: Config):
"""Initialize the application using web UI.
Args:
service: IntervalPracticeService instance.
config: Config instance.
"""
super().__init__(service, config)
self.audio_processor = WebAudioProcessor(
sample_rate=config.audio.sample_rate,
callback=self._process_audio_callback,
buffer_duration=config.audio.buffer_duration,
)
self.text_manager = IntervalViewTextManager()
self.ui = WebIntervalPracticeView(
on_generate_melody=self.start,
on_end_practice=self.stop,
on_audio_input=self.handle_audio,
config=config,
)
self.base_note = "-"
self.results_table: List[List[Any]] = []
self.progress_timer: float = 0.0
self.is_auto_advance = False
self.note_duration = 1.5
def _process_audio_callback(self, audio_data: np.ndarray):
"""Process incoming audio data and update the application state.
Args:
audio_data: Audio data to process.
"""
if not self.is_running or not self.phrases:
return
current_note = self.phrases[self.current_phrase_idx][
self.current_note_idx
].value
result = self.service.process_audio(audio_data, current_note)
# Update status display
self.text_manager.update_pitch_result(result, self.is_auto_advance)
# Progress to next note if current note is complete
if self.is_auto_advance:
current_time = time.time()
if current_time - self.progress_timer >= self.note_duration:
self._advance_to_next_note()
self.progress_timer = current_time
elif result.remaining_time <= 0:
self._advance_to_next_note()
self.text_manager.update_phrase_text(self.current_phrase_idx, self.phrases)
def _advance_to_next_note(self):
"""Advance to the next note or phrase."""
if self.phrases is None:
return
self.update_results_table()
self.current_note_idx += 1
if self.current_note_idx >= len(self.phrases[self.current_phrase_idx]):
self.current_note_idx = 0
self.current_phrase_idx += 1
if self.current_phrase_idx >= len(self.phrases):
self.current_phrase_idx = 0
self.base_note = self.phrases[self.current_phrase_idx][
self.current_note_idx
].value
def handle_audio(self, audio: Tuple[int, np.ndarray]) -> Tuple[str, str, str, List]:
"""Handle audio input from Gradio interface.
Args:
audio: Audio data to process.
Returns:
Tuple[str, str, str, List]:
The current base note including the next base note,
target note, result text, and results table.
"""
if not self.is_running:
return "-", "Not running", "Start the session first", []
self.audio_processor.process_audio(audio)
return (
self.base_note,
self.text_manager.phrase_text,
self.text_manager.result_text,
self.results_table,
)
def start(
self,
interval: str,
direction: str,
number_problems: int,
is_auto_advance: bool,
note_duration: float,
) -> Tuple[str, str, str, List]:
"""Start a new practice session.
Args:
interval: Interval to move to and back.
direction: Direction to move to and back.
number_problems: Number of problems to generate.
is_auto_advance: Whether to automatically advance to the next note.
note_duration: Duration of each note in seconds.
Returns:
Tuple[str, str, str, List]:
The current base note including the next base note,
target note, result text, and results table.
"""
semitone_interval = Intervals.INTERVALS_MAP.get(interval, 0)
if direction == "Down":
semitone_interval = -semitone_interval
self.phrases = self.service.generate_melody(
num_notes=number_problems, interval=semitone_interval
)
self.current_phrase_idx = 0
self.current_note_idx = 0
self.is_running = True
present_note = self.phrases[0][0].value
self.base_note = present_note
if not self.audio_processor.is_recording:
self.text_manager.initialize_text()
self.audio_processor.start_recording()
self.text_manager.update_phrase_text(self.current_phrase_idx, self.phrases)
self.results_table = []
self.is_auto_advance = is_auto_advance
self.note_duration = note_duration
self.progress_timer = time.time()
return (
self.base_note,
self.text_manager.phrase_text,
self.text_manager.result_text,
self.results_table,
)
def stop(self) -> Tuple[str, str, str]:
"""Stop the current practice session.
Returns:
tuple[str, str, str]:
The current base note including the next base note,
target note, and result text.
"""
self.is_running = False
self.base_note = "-"
if self.audio_processor.is_recording:
self.audio_processor.stop_recording()
self.text_manager.terminate_text()
return (
self.base_note,
self.text_manager.phrase_text,
self.text_manager.result_text,
)
def launch(self, **kwargs):
"""Launch the application."""
self.ui.launch(**kwargs)
def update_results_table(self):
"""Update the results table with the latest result."""
if not self.is_auto_advance:
return
target_note = self.phrases[self.current_phrase_idx][self.current_note_idx].value
if self.base_note == target_note:
return
detected_note = self.text_manager.result_text.split("|")[1].strip()
detected_note = detected_note.replace("Your note: ", "").replace(" ", "")
# Result determination
result = "⭕️" if detected_note == target_note else "X"
new_result = [
self.current_phrase_idx + 1,
self.base_note,
target_note,
detected_note,
result,
]
self.results_table.append(new_result)
|