Rename AudioFlinger to AudioManager framework

This commit is contained in:
Thomas Farstrike
2026-01-26 13:13:54 +01:00
parent a32a020e57
commit 1f9eee3a9d
13 changed files with 76 additions and 301 deletions
@@ -2,7 +2,7 @@ import machine
import os
import time
from mpos import Activity, Intent, sdcard, get_event_name, AudioFlinger
from mpos import Activity, Intent, sdcard, get_event_name, AudioManager
class MusicPlayer(Activity):
@@ -63,17 +63,17 @@ class FullscreenPlayer(Activity):
self._filename = self.getIntent().extras.get("filename")
qr_screen = lv.obj()
self._slider_label=lv.label(qr_screen)
self._slider_label.set_text(f"Volume: {AudioFlinger.get_volume()}%")
self._slider_label.set_text(f"Volume: {AudioManager.get_volume()}%")
self._slider_label.align(lv.ALIGN.TOP_MID,0,lv.pct(4))
self._slider=lv.slider(qr_screen)
self._slider.set_range(0,16)
self._slider.set_value(int(AudioFlinger.get_volume()/6.25), False)
self._slider.set_value(int(AudioManager.get_volume()/6.25), False)
self._slider.set_width(lv.pct(90))
self._slider.align_to(self._slider_label,lv.ALIGN.OUT_BOTTOM_MID,0,10)
def volume_slider_changed(e):
volume_int = self._slider.get_value()*6.25
self._slider_label.set_text(f"Volume: {volume_int}%")
AudioFlinger.set_volume(volume_int)
AudioManager.set_volume(volume_int)
self._slider.add_event_cb(volume_slider_changed,lv.EVENT.VALUE_CHANGED,None)
self._filename_label = lv.label(qr_screen)
self._filename_label.align(lv.ALIGN.CENTER,0,0)
@@ -100,12 +100,12 @@ class FullscreenPlayer(Activity):
print("Not playing any file...")
else:
print(f"Playing file {self._filename}")
AudioFlinger.stop()
AudioManager.stop()
time.sleep(0.1)
success = AudioFlinger.play_wav(
success = AudioManager.play_wav(
self._filename,
stream_type=AudioFlinger.STREAM_MUSIC,
stream_type=AudioManager.STREAM_MUSIC,
on_complete=self.player_finished
)
@@ -125,7 +125,7 @@ class FullscreenPlayer(Activity):
obj.set_style_border_width(0, lv.PART.MAIN)
def stop_button_clicked(self, event):
AudioFlinger.stop()
AudioManager.stop()
self.finish()
def player_finished(self, result=None):
@@ -2,7 +2,7 @@
import os
import time
from mpos import Activity, ui, AudioFlinger
from mpos import Activity, ui, AudioManager
def _makedirs(path):
@@ -136,7 +136,7 @@ class SoundRecorder(Activity):
def _update_status(self):
"""Update status label based on microphone availability."""
if AudioFlinger.has_microphone():
if AudioManager.has_microphone():
self._status_label.set_text("Microphone ready")
self._status_label.set_style_text_color(lv.color_hex(0x00AA00), lv.PART.MAIN)
self._record_button.remove_flag(lv.obj.FLAG.HIDDEN)
@@ -243,9 +243,9 @@ class SoundRecorder(Activity):
def _start_recording(self):
"""Start recording audio."""
print("SoundRecorder: _start_recording called")
print(f"SoundRecorder: has_microphone() = {AudioFlinger.has_microphone()}")
print(f"SoundRecorder: has_microphone() = {AudioManager.has_microphone()}")
if not AudioFlinger.has_microphone():
if not AudioManager.has_microphone():
print("SoundRecorder: No microphone available - aborting")
return
@@ -263,12 +263,12 @@ class SoundRecorder(Activity):
return
# Start recording
print(f"SoundRecorder: Calling AudioFlinger.record_wav()")
print(f"SoundRecorder: Calling AudioManager.record_wav()")
print(f" file_path: {file_path}")
print(f" duration_ms: {self._current_max_duration_ms}")
print(f" sample_rate: {self.SAMPLE_RATE}")
success = AudioFlinger.record_wav(
success = AudioManager.record_wav(
file_path=file_path,
duration_ms=self._current_max_duration_ms,
on_complete=self._on_recording_complete,
@@ -302,7 +302,7 @@ class SoundRecorder(Activity):
def _stop_recording(self):
"""Stop recording audio."""
AudioFlinger.stop()
AudioManager.stop()
self._is_recording = False
# Show "Saving..." status immediately (file finalization takes time on SD card)
@@ -364,13 +364,13 @@ class SoundRecorder(Activity):
"""Handle play button click."""
if self._last_recording and not self._is_recording:
# Stop any current playback
AudioFlinger.stop()
AudioManager.stop()
time.sleep_ms(100)
# Play the recording
success = AudioFlinger.play_wav(
success = AudioManager.play_wav(
self._last_recording,
stream_type=AudioFlinger.STREAM_MUSIC,
stream_type=AudioManager.STREAM_MUSIC,
on_complete=self._on_playback_complete,
volume=100
)
+2 -2
View File
@@ -8,7 +8,7 @@ from .content.app_manager import AppManager
from .config import SharedPreferences
from .net.connectivity_manager import ConnectivityManager
from .net.wifi_service import WifiService
from .audio.audioflinger import AudioFlinger
from .audio.audiomanager import AudioManager
from .net.download_manager import DownloadManager
from .task_manager import TaskManager
from .camera_manager import CameraManager
@@ -66,7 +66,7 @@ __all__ = [
"App",
"Activity",
"SharedPreferences",
"ConnectivityManager", "DownloadManager", "WifiService", "AudioFlinger", "Intent",
"ConnectivityManager", "DownloadManager", "WifiService", "AudioManager", "Intent",
"ActivityNavigator", "AppManager", "TaskManager", "CameraManager", "BatteryManager",
# Device and build info
"DeviceInfo", "BuildInfo",
@@ -1,5 +1,5 @@
# AudioFlinger - Centralized Audio Management Service for MicroPythonOS
# AudioManager - Centralized Audio Management Service for MicroPythonOS
# Android-inspired audio routing with priority-based audio focus
# Simple routing: play_wav() -> I2S, play_rtttl() -> buzzer, record_wav() -> I2S mic
from .audioflinger import AudioFlinger
from .audiomanager import AudioManager
@@ -1,4 +1,4 @@
# AudioFlinger - Core Audio Management Service
# AudioManager - Core Audio Management Service
# Centralized audio routing with priority-based audio focus (Android-inspired)
# Supports I2S (digital audio) and PWM buzzer (tones/ringtones)
#
@@ -9,20 +9,20 @@ import _thread
from ..task_manager import TaskManager
class AudioFlinger:
class AudioManager:
"""
Centralized audio management service with priority-based audio focus.
Implements singleton pattern for single audio service instance.
Usage:
from mpos import AudioFlinger
from mpos import AudioManager
# Direct class method calls (no .get() needed)
AudioFlinger.init(i2s_pins=pins, buzzer_instance=buzzer)
AudioFlinger.play_wav("music.wav", stream_type=AudioFlinger.STREAM_MUSIC)
AudioFlinger.set_volume(80)
volume = AudioFlinger.get_volume()
AudioFlinger.stop()
AudioManager.init(i2s_pins=pins, buzzer_instance=buzzer)
AudioManager.play_wav("music.wav", stream_type=AudioManager.STREAM_MUSIC)
AudioManager.set_volume(80)
volume = AudioManager.get_volume()
AudioManager.stop()
"""
# Stream type constants (priority order: higher number = higher priority)
@@ -34,15 +34,15 @@ class AudioFlinger:
def __init__(self, i2s_pins=None, buzzer_instance=None):
"""
Initialize AudioFlinger instance with optional hardware configuration.
Initialize AudioManager instance with optional hardware configuration.
Args:
i2s_pins: Dict with 'sck', 'ws', 'sd' pin numbers (for I2S/WAV playback)
buzzer_instance: PWM instance for buzzer (for RTTTL playback)
"""
if AudioFlinger._instance:
if AudioManager._instance:
return
AudioFlinger._instance = self
AudioManager._instance = self
self._i2s_pins = i2s_pins # I2S pin configuration dict (created per-stream)
self._buzzer_instance = buzzer_instance # PWM buzzer instance
@@ -58,9 +58,9 @@ class AudioFlinger:
capabilities.append("Buzzer (RTTTL)")
if capabilities:
print(f"AudioFlinger initialized: {', '.join(capabilities)}")
print(f"AudioManager initialized: {', '.join(capabilities)}")
else:
print("AudioFlinger initialized: No audio hardware")
print("AudioManager initialized: No audio hardware")
@classmethod
def get(cls):
@@ -100,11 +100,11 @@ class AudioFlinger:
# Check priority
if stream_type <= self._current_stream.stream_type:
print(f"AudioFlinger: Stream rejected (priority {stream_type} <= current {self._current_stream.stream_type})")
print(f"AudioManager: Stream rejected (priority {stream_type} <= current {self._current_stream.stream_type})")
return False
# Higher priority stream - interrupt current
print(f"AudioFlinger: Interrupting stream (priority {stream_type} > current {self._current_stream.stream_type})")
print(f"AudioManager: Interrupting stream (priority {stream_type} > current {self._current_stream.stream_type})")
self._current_stream.stop()
return True
@@ -122,7 +122,7 @@ class AudioFlinger:
# Run synchronous playback in this thread
stream.play()
except Exception as e:
print(f"AudioFlinger: Playback error: {e}")
print(f"AudioManager: Playback error: {e}")
finally:
# Clear current stream
if self._current_stream == stream:
@@ -145,7 +145,7 @@ class AudioFlinger:
stream_type = self.STREAM_MUSIC
if not self._i2s_pins:
print("AudioFlinger: play_wav() failed - I2S not configured")
print("AudioManager: play_wav() failed - I2S not configured")
return False
# Check audio focus
@@ -169,7 +169,7 @@ class AudioFlinger:
return True
except Exception as e:
print(f"AudioFlinger: play_wav() failed: {e}")
print(f"AudioManager: play_wav() failed: {e}")
return False
def play_rtttl(self, rtttl_string, stream_type=None, volume=None, on_complete=None):
@@ -189,7 +189,7 @@ class AudioFlinger:
stream_type = self.STREAM_NOTIFICATION
if not self._buzzer_instance:
print("AudioFlinger: play_rtttl() failed - buzzer not configured")
print("AudioManager: play_rtttl() failed - buzzer not configured")
return False
# Check audio focus
@@ -213,7 +213,7 @@ class AudioFlinger:
return True
except Exception as e:
print(f"AudioFlinger: play_rtttl() failed: {e}")
print(f"AudioManager: play_rtttl() failed: {e}")
return False
def _recording_thread(self, stream):
@@ -230,7 +230,7 @@ class AudioFlinger:
# Run synchronous recording in this thread
stream.record()
except Exception as e:
print(f"AudioFlinger: Recording error: {e}")
print(f"AudioManager: Recording error: {e}")
finally:
# Clear current recording
if self._current_recording == stream:
@@ -249,7 +249,7 @@ class AudioFlinger:
Returns:
bool: True if recording started, False if rejected or unavailable
"""
print(f"AudioFlinger.record_wav() called")
print(f"AudioManager.record_wav() called")
print(f" file_path: {file_path}")
print(f" duration_ms: {duration_ms}")
print(f" sample_rate: {sample_rate}")
@@ -257,25 +257,25 @@ class AudioFlinger:
print(f" has_microphone(): {self.has_microphone()}")
if not self.has_microphone():
print("AudioFlinger: record_wav() failed - microphone not configured")
print("AudioManager: record_wav() failed - microphone not configured")
return False
# Cannot record while playing (I2S can only be TX or RX, not both)
if self.is_playing():
print("AudioFlinger: Cannot record while playing")
print("AudioManager: Cannot record while playing")
return False
# Cannot start new recording while already recording
if self.is_recording():
print("AudioFlinger: Already recording")
print("AudioManager: Already recording")
return False
# Create stream and start recording in separate thread
try:
print("AudioFlinger: Importing RecordStream...")
print("AudioManager: Importing RecordStream...")
from mpos.audio.stream_record import RecordStream
print("AudioFlinger: Creating RecordStream instance...")
print("AudioManager: Creating RecordStream instance...")
stream = RecordStream(
file_path=file_path,
duration_ms=duration_ms,
@@ -284,15 +284,15 @@ class AudioFlinger:
on_complete=on_complete
)
print("AudioFlinger: Starting recording thread...")
print("AudioManager: Starting recording thread...")
_thread.stack_size(TaskManager.good_stack_size())
_thread.start_new_thread(self._recording_thread, (stream,))
print("AudioFlinger: Recording thread started successfully")
print("AudioManager: Recording thread started successfully")
return True
except Exception as e:
import sys
print(f"AudioFlinger: record_wav() failed: {e}")
print(f"AudioManager: record_wav() failed: {e}")
sys.print_exception(e)
return False
@@ -302,16 +302,16 @@ class AudioFlinger:
if self._current_stream:
self._current_stream.stop()
print("AudioFlinger: Playback stopped")
print("AudioManager: Playback stopped")
stopped = True
if self._current_recording:
self._current_recording.stop()
print("AudioFlinger: Recording stopped")
print("AudioManager: Recording stopped")
stopped = True
if not stopped:
print("AudioFlinger: No playback or recording to stop")
print("AudioManager: No playback or recording to stop")
def pause(self):
"""
@@ -320,9 +320,9 @@ class AudioFlinger:
"""
if self._current_stream and hasattr(self._current_stream, 'pause'):
self._current_stream.pause()
print("AudioFlinger: Playback paused")
print("AudioManager: Playback paused")
else:
print("AudioFlinger: Pause not supported or no playback active")
print("AudioManager: Pause not supported or no playback active")
def resume(self):
"""
@@ -331,9 +331,9 @@ class AudioFlinger:
"""
if self._current_stream and hasattr(self._current_stream, 'resume'):
self._current_stream.resume()
print("AudioFlinger: Playback resumed")
print("AudioManager: Playback resumed")
else:
print("AudioFlinger: Resume not supported or no playback active")
print("AudioManager: Resume not supported or no playback active")
def set_volume(self, volume):
"""
@@ -396,7 +396,7 @@ _methods_to_delegate = [
]
for method_name in _methods_to_delegate:
_original_methods[method_name] = getattr(AudioFlinger, method_name)
_original_methods[method_name] = getattr(AudioManager, method_name)
# Helper to create delegating class methods
def _make_class_method(method_name):
@@ -410,6 +410,6 @@ def _make_class_method(method_name):
return class_method
# Attach class methods to AudioFlinger
# Attach class methods to AudioManager
for method_name in _methods_to_delegate:
setattr(AudioFlinger, method_name, _make_class_method(method_name))
setattr(AudioManager, method_name, _make_class_method(method_name))
@@ -1,4 +1,4 @@
# RecordStream - WAV File Recording Stream for AudioFlinger
# RecordStream - WAV File Recording Stream for AudioManager
# Records 16-bit mono PCM audio from I2S microphone to WAV file
# Uses synchronous recording in a separate thread for non-blocking operation
# On desktop (no I2S hardware), generates a 440Hz sine wave for testing
@@ -1,4 +1,4 @@
# RTTTLStream - RTTTL Ringtone Playback Stream for AudioFlinger
# RTTTLStream - RTTTL Ringtone Playback Stream for AudioManager
# Ring Tone Text Transfer Language parser and player
# Uses synchronous playback in a separate thread for non-blocking operation
@@ -1,4 +1,4 @@
# WAVStream - WAV File Playback Stream for AudioFlinger
# WAVStream - WAV File Playback Stream for AudioManager
# Supports 8/16/24/32-bit PCM, mono+stereo, auto-upsampling, volume control
# Uses synchronous playback in a separate thread for non-blocking operation
@@ -296,7 +296,7 @@ mpos.sdcard.init(spi_bus, cs_pin=14)
# === AUDIO HARDWARE ===
from machine import PWM, Pin
from mpos import AudioFlinger
from mpos import AudioManager
# Initialize buzzer (GPIO 46)
buzzer = PWM(Pin(46), freq=550, duty=0)
@@ -315,8 +315,8 @@ i2s_pins = {
'sd_in': 15, # DIN - Serial Data IN (microphone)
}
# Initialize AudioFlinger with I2S and buzzer
AudioFlinger(i2s_pins=i2s_pins, buzzer_instance=buzzer)
# Initialize AudioManager with I2S and buzzer
AudioManager(i2s_pins=i2s_pins, buzzer_instance=buzzer)
# === LED HARDWARE ===
import mpos.lights as LightsManager
@@ -349,9 +349,9 @@ def startup_wow_effect():
#startup_jingle = "ShortBeeps:d=32,o=5,b=320:c6,c7"
# Start the jingle
AudioFlinger.play_rtttl(
AudioManager.play_rtttl(
startup_jingle,
stream_type=AudioFlinger.STREAM_NOTIFICATION,
stream_type=AudioManager.STREAM_NOTIFICATION,
volume=60
)
@@ -200,7 +200,7 @@ mpos.sdcard.init(spi_bus, cs_pin=14)
# === AUDIO HARDWARE ===
from machine import PWM, Pin
from mpos import AudioFlinger
from mpos import AudioManager
# Initialize buzzer: now sits on PC14/CC1 of the CH32X035GxUx so needs custom code
#buzzer = PWM(Pin(46), freq=550, duty=0)
@@ -219,8 +219,8 @@ i2s_pins = {
'sd_in': 15, # DIN - Serial Data IN (microphone)
}
# Initialize AudioFlinger with I2S (buzzer TODO)
AudioFlinger(i2s_pins=i2s_pins)
# Initialize AudioManager with I2S (buzzer TODO)
AudioManager(i2s_pins=i2s_pins)
# === LED HARDWARE ===
import mpos.lights as LightsManager
+2 -2
View File
@@ -98,7 +98,7 @@ def adc_to_voltage(adc_value):
BatteryManager.init_adc(999, adc_to_voltage)
# === AUDIO HARDWARE ===
from mpos import AudioFlinger
from mpos import AudioManager
# Desktop builds have no real audio hardware, but we simulate microphone
# recording with a 440Hz sine wave for testing WAV file generation
@@ -110,7 +110,7 @@ i2s_pins = {
'sck_in': 0, # Simulated - not used on desktop
'sd_in': 0, # Simulated - enables microphone simulation
}
AudioFlinger(i2s_pins=i2s_pins)
AudioManager(i2s_pins=i2s_pins)
# === LED HARDWARE ===
# Note: Desktop builds have no LED hardware
@@ -1,5 +1,5 @@
# Fri3d Camp 2024 Badge Hardware Drivers
# These are simple wrappers that can be used by services like AudioFlinger
# These are simple wrappers that can be used by services like AudioManager
from .buzzer import BuzzerConfig
from .leds import LEDConfig
-225
View File
@@ -1,225 +0,0 @@
# Unit tests for AudioFlinger service
import unittest
import sys
# Import centralized mocks
from mpos.testing import (
MockMachine,
MockPWM,
MockPin,
MockThread,
inject_mocks,
)
# Inject mocks before importing AudioFlinger
inject_mocks({
'machine': MockMachine(),
'_thread': MockThread,
})
# Now import the module to test
from mpos.audio.audioflinger import AudioFlinger
class TestAudioFlinger(unittest.TestCase):
"""Test cases for AudioFlinger service."""
def setUp(self):
"""Initialize AudioFlinger before each test."""
self.buzzer = MockPWM(MockPin(46))
self.i2s_pins = {'sck': 2, 'ws': 47, 'sd': 16}
# Reset singleton instance for each test
AudioFlinger._instance = None
AudioFlinger(
i2s_pins=self.i2s_pins,
buzzer_instance=self.buzzer
)
# Reset volume to default after creating instance
AudioFlinger.set_volume(70)
def tearDown(self):
"""Clean up after each test."""
AudioFlinger.stop()
def test_initialization(self):
"""Test that AudioFlinger initializes correctly."""
af = AudioFlinger.get()
self.assertEqual(af._i2s_pins, self.i2s_pins)
self.assertEqual(af._buzzer_instance, self.buzzer)
def test_has_i2s(self):
"""Test has_i2s() returns correct value."""
# With I2S configured
AudioFlinger._instance = None
AudioFlinger(i2s_pins=self.i2s_pins, buzzer_instance=None)
self.assertTrue(AudioFlinger.has_i2s())
# Without I2S configured
AudioFlinger._instance = None
AudioFlinger(i2s_pins=None, buzzer_instance=self.buzzer)
self.assertFalse(AudioFlinger.has_i2s())
def test_has_buzzer(self):
"""Test has_buzzer() returns correct value."""
# With buzzer configured
AudioFlinger._instance = None
AudioFlinger(i2s_pins=None, buzzer_instance=self.buzzer)
self.assertTrue(AudioFlinger.has_buzzer())
# Without buzzer configured
AudioFlinger._instance = None
AudioFlinger(i2s_pins=self.i2s_pins, buzzer_instance=None)
self.assertFalse(AudioFlinger.has_buzzer())
def test_stream_types(self):
"""Test stream type constants and priority order."""
self.assertEqual(AudioFlinger.STREAM_MUSIC, 0)
self.assertEqual(AudioFlinger.STREAM_NOTIFICATION, 1)
self.assertEqual(AudioFlinger.STREAM_ALARM, 2)
# Higher number = higher priority
self.assertTrue(AudioFlinger.STREAM_MUSIC < AudioFlinger.STREAM_NOTIFICATION)
self.assertTrue(AudioFlinger.STREAM_NOTIFICATION < AudioFlinger.STREAM_ALARM)
def test_volume_control(self):
"""Test volume get/set operations."""
# Set volume
AudioFlinger.set_volume(50)
self.assertEqual(AudioFlinger.get_volume(), 50)
# Test clamping to 0-100 range
AudioFlinger.set_volume(150)
self.assertEqual(AudioFlinger.get_volume(), 100)
AudioFlinger.set_volume(-10)
self.assertEqual(AudioFlinger.get_volume(), 0)
def test_no_hardware_rejects_playback(self):
"""Test that no hardware rejects all playback requests."""
# Re-initialize with no hardware
AudioFlinger._instance = None
AudioFlinger(i2s_pins=None, buzzer_instance=None)
# WAV should be rejected (no I2S)
result = AudioFlinger.play_wav("test.wav")
self.assertFalse(result)
# RTTTL should be rejected (no buzzer)
result = AudioFlinger.play_rtttl("Test:d=4,o=5,b=120:c")
self.assertFalse(result)
def test_i2s_only_rejects_rtttl(self):
"""Test that I2S-only config rejects buzzer playback."""
# Re-initialize with I2S only
AudioFlinger._instance = None
AudioFlinger(i2s_pins=self.i2s_pins, buzzer_instance=None)
# RTTTL should be rejected (no buzzer)
result = AudioFlinger.play_rtttl("Test:d=4,o=5,b=120:c")
self.assertFalse(result)
def test_buzzer_only_rejects_wav(self):
"""Test that buzzer-only config rejects I2S playback."""
# Re-initialize with buzzer only
AudioFlinger._instance = None
AudioFlinger(i2s_pins=None, buzzer_instance=self.buzzer)
# WAV should be rejected (no I2S)
result = AudioFlinger.play_wav("test.wav")
self.assertFalse(result)
def test_is_playing_initially_false(self):
"""Test that is_playing() returns False initially."""
# Reset to ensure clean state
AudioFlinger._instance = None
AudioFlinger(i2s_pins=self.i2s_pins, buzzer_instance=self.buzzer)
self.assertFalse(AudioFlinger.is_playing())
def test_stop_with_no_playback(self):
"""Test that stop() can be called when nothing is playing."""
# Should not raise exception
AudioFlinger.stop()
self.assertFalse(AudioFlinger.is_playing())
def test_audio_focus_check_no_current_stream(self):
"""Test audio focus allows playback when no stream is active."""
af = AudioFlinger.get()
result = af._check_audio_focus(AudioFlinger.STREAM_MUSIC)
self.assertTrue(result)
def test_volume_default_value(self):
"""Test that default volume is reasonable."""
# After init, volume should be at default (70)
AudioFlinger(i2s_pins=None, buzzer_instance=None)
self.assertEqual(AudioFlinger.get_volume(), 70)
class TestAudioFlingerRecording(unittest.TestCase):
"""Test cases for AudioFlinger recording functionality."""
def setUp(self):
"""Initialize AudioFlinger with microphone before each test."""
self.buzzer = MockPWM(MockPin(46))
# I2S pins with microphone input
self.i2s_pins_with_mic = {'sck': 2, 'ws': 47, 'sd': 16, 'sd_in': 15}
# I2S pins without microphone input
self.i2s_pins_no_mic = {'sck': 2, 'ws': 47, 'sd': 16}
# Reset singleton instance for each test
AudioFlinger._instance = None
AudioFlinger(
i2s_pins=self.i2s_pins_with_mic,
buzzer_instance=self.buzzer
)
# Reset volume to default after creating instance
AudioFlinger.set_volume(70)
def tearDown(self):
"""Clean up after each test."""
AudioFlinger.stop()
def test_has_microphone_with_sd_in(self):
"""Test has_microphone() returns True when sd_in pin is configured."""
AudioFlinger._instance = None
AudioFlinger(i2s_pins=self.i2s_pins_with_mic, buzzer_instance=None)
self.assertTrue(AudioFlinger.has_microphone())
def test_has_microphone_without_sd_in(self):
"""Test has_microphone() returns False when sd_in pin is not configured."""
AudioFlinger._instance = None
AudioFlinger(i2s_pins=self.i2s_pins_no_mic, buzzer_instance=None)
self.assertFalse(AudioFlinger.has_microphone())
def test_has_microphone_no_i2s(self):
"""Test has_microphone() returns False when no I2S is configured."""
AudioFlinger._instance = None
AudioFlinger(i2s_pins=None, buzzer_instance=self.buzzer)
self.assertFalse(AudioFlinger.has_microphone())
def test_is_recording_initially_false(self):
"""Test that is_recording() returns False initially."""
self.assertFalse(AudioFlinger.is_recording())
def test_record_wav_no_microphone(self):
"""Test that record_wav() fails when no microphone is configured."""
AudioFlinger._instance = None
AudioFlinger(i2s_pins=self.i2s_pins_no_mic, buzzer_instance=None)
result = AudioFlinger.record_wav("test.wav")
self.assertFalse(result, "record_wav() fails when no microphone is configured")
def test_record_wav_no_i2s(self):
AudioFlinger._instance = None
AudioFlinger(i2s_pins=None, buzzer_instance=self.buzzer)
result = AudioFlinger.record_wav("test.wav")
self.assertFalse(result, "record_wav() should fail when no I2S is configured")
def test_stop_with_no_recording(self):
"""Test that stop() can be called when nothing is recording."""
# Should not raise exception
AudioFlinger.stop()
self.assertFalse(AudioFlinger.is_recording())