diff --git a/buzz/recording.py b/buzz/recording.py index db77fc52..a831823f 100644 --- a/buzz/recording.py +++ b/buzz/recording.py @@ -9,6 +9,9 @@ from PyQt6.QtCore import QObject, pyqtSignal class RecordingAmplitudeListener(QObject): stream: Optional[sounddevice.InputStream] = None amplitude_changed = pyqtSignal(float) + average_amplitude_changed = pyqtSignal(float) + + ACCUMULATION_SECONDS = 1 def __init__( self, @@ -17,6 +20,8 @@ class RecordingAmplitudeListener(QObject): ): super().__init__(parent) self.input_device_index = input_device_index + self.buffer = np.ndarray([], dtype=np.float32) + self.accumulation_size = 0 def start_recording(self): try: @@ -27,6 +32,7 @@ class RecordingAmplitudeListener(QObject): callback=self.stream_callback, ) self.stream.start() + self.accumulation_size = int(self.stream.samplerate * self.ACCUMULATION_SECONDS) except Exception as e: self.stop_recording() logging.exception("Failed to start audio stream on device %s: %s", self.input_device_index, e) @@ -38,5 +44,9 @@ class RecordingAmplitudeListener(QObject): def stream_callback(self, in_data: np.ndarray, frame_count, time_info, status): chunk = in_data.ravel() - amplitude = np.sqrt(np.mean(chunk**2)) # root-mean-square - self.amplitude_changed.emit(amplitude) + self.amplitude_changed.emit(float(np.sqrt(np.mean(chunk**2)))) + + self.buffer = np.append(self.buffer, chunk) + if self.buffer.size >= self.accumulation_size: + self.average_amplitude_changed.emit(float(np.sqrt(np.mean(self.buffer**2)))) + self.buffer = np.ndarray([], dtype=np.float32) diff --git a/buzz/transcriber/recording_transcriber.py b/buzz/transcriber/recording_transcriber.py index d547e22b..87048307 100644 --- a/buzz/transcriber/recording_transcriber.py +++ b/buzz/transcriber/recording_transcriber.py @@ -39,6 +39,7 @@ class RecordingTranscriber(QObject): finished = pyqtSignal() error = pyqtSignal(str) amplitude_changed = pyqtSignal(float) + average_amplitude_changed = pyqtSignal(float) is_running = False SAMPLE_RATE = whisper_audio.SAMPLE_RATE @@ -180,6 +181,7 @@ class RecordingTranscriber(QObject): self.mutex.release() amplitude = self.amplitude(samples) + self.average_amplitude_changed.emit(amplitude) logging.debug( "Processing next frame, sample size = %s, queue size = %s, amplitude = %s", @@ -188,7 +190,7 @@ class RecordingTranscriber(QObject): amplitude, ) - if amplitude < 0.025: + if amplitude < 0.0025: time.sleep(0.5) continue @@ -351,7 +353,7 @@ class RecordingTranscriber(QObject): # Try to enqueue the next block. If the queue is already full, drop the block. chunk: np.ndarray = in_data.ravel() - amplitude = float(np.sqrt(np.mean(chunk**2))) + amplitude = self.amplitude(chunk) self.amplitude_changed.emit(amplitude) with self.mutex: @@ -360,7 +362,7 @@ class RecordingTranscriber(QObject): @staticmethod def amplitude(arr: np.ndarray): - return (abs(max(arr)) + abs(min(arr))) / 2 + return float(np.sqrt(np.mean(arr**2))) def _drain_stderr(self): if self.process and self.process.stderr: diff --git a/buzz/widgets/audio_meter_widget.py b/buzz/widgets/audio_meter_widget.py index 0329a25a..a320afe2 100644 --- a/buzz/widgets/audio_meter_widget.py +++ b/buzz/widgets/audio_meter_widget.py @@ -1,7 +1,7 @@ from typing import Optional from PyQt6 import QtGui -from PyQt6.QtCore import Qt +from PyQt6.QtCore import Qt, QRect from PyQt6.QtGui import QColor, QPainter from PyQt6.QtWidgets import QWidget @@ -20,13 +20,16 @@ class AudioMeterWidget(QWidget): def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.setMinimumWidth(10) - self.setFixedHeight(16) + self.setFixedHeight(30) + self.BARS_HEIGHT = 14 # Extra padding to fix layout self.PADDING_TOP = 3 self.current_amplitude = 0.0 + self.average_amplitude = 0.0 + self.MINIMUM_AMPLITUDE = 0.00005 # minimum amplitude to show the first bar self.AMPLITUDE_SCALE_FACTOR = 10 # scale the amplitudes such that 1/AMPLITUDE_SCALE_FACTOR will show all bars @@ -58,18 +61,23 @@ class AudioMeterWidget(QWidget): center_x - ((i + 1) * (self.BAR_MARGIN + self.BAR_WIDTH)), rect.top() + self.PADDING_TOP, self.BAR_WIDTH, - rect.height() - self.PADDING_TOP, + self.BARS_HEIGHT - self.PADDING_TOP, ) # draw to right painter.drawRect( center_x + (self.BAR_MARGIN + (i * (self.BAR_MARGIN + self.BAR_WIDTH))), rect.top() + self.PADDING_TOP, self.BAR_WIDTH, - rect.height() - self.PADDING_TOP, + self.BARS_HEIGHT - self.PADDING_TOP, ) + text_rect = QRect(rect.left(), self.BARS_HEIGHT, rect.width(), rect.height() - self.BARS_HEIGHT) + painter.setPen(self.BAR_ACTIVE_COLOR) + painter.drawText(text_rect, Qt.AlignmentFlag.AlignCenter, f"{self.average_amplitude:.4f}") + def reset_amplitude(self): self.current_amplitude = 0.0 + self.average_amplitude = 0.0 self.repaint() def update_amplitude(self, amplitude: float): @@ -77,3 +85,7 @@ class AudioMeterWidget(QWidget): amplitude, self.current_amplitude * self.SMOOTHING_FACTOR ) self.update() + + def update_average_amplitude(self, amplitude: float): + self.average_amplitude = amplitude + self.update() diff --git a/buzz/widgets/recording_transcriber_widget.py b/buzz/widgets/recording_transcriber_widget.py index a4b82cdb..c179c47e 100644 --- a/buzz/widgets/recording_transcriber_widget.py +++ b/buzz/widgets/recording_transcriber_widget.py @@ -509,6 +509,9 @@ class RecordingTranscriberWidget(QWidget): self.recording_amplitude_listener.amplitude_changed.connect( self.on_recording_amplitude_changed, Qt.ConnectionType.QueuedConnection ) + self.recording_amplitude_listener.average_amplitude_changed.connect( + self.audio_meter_widget.update_average_amplitude, Qt.ConnectionType.QueuedConnection + ) self.recording_amplitude_listener.start_recording() def on_record_button_clicked(self): @@ -519,6 +522,9 @@ class RecordingTranscriberWidget(QWidget): self.recording_amplitude_listener.amplitude_changed.disconnect( self.on_recording_amplitude_changed ) + self.recording_amplitude_listener.average_amplitude_changed.disconnect( + self.audio_meter_widget.update_average_amplitude + ) self.recording_amplitude_listener.stop_recording() self.recording_amplitude_listener = None self.audio_meter_widget.reset_amplitude() @@ -587,6 +593,9 @@ class RecordingTranscriberWidget(QWidget): self.transcriber.amplitude_changed.connect( self.on_recording_amplitude_changed, Qt.ConnectionType.QueuedConnection ) + self.transcriber.average_amplitude_changed.connect( + self.audio_meter_widget.update_average_amplitude, Qt.ConnectionType.QueuedConnection + ) # Stop the separate amplitude listener to avoid two streams on the same device if self.recording_amplitude_listener is not None: