import json from pathlib import Path import tempfile import unittest from unittest.mock import patch import app import numpy as np import soundfile as sf from app import prepare_audio, run, example class AppTests(unittest.TestCase): def test_audio_validation_and_prefix(self): with tempfile.TemporaryDirectory() as tmp: src=Path(tmp)/'source.wav'; out=Path(tmp)/'out.wav' sf.write(src,np.sin(np.arange(16000)*.1),16000) self.assertAlmostEqual(prepare_audio(src,out,.5),.5) sf.write(src,np.zeros(16000),16000) with self.assertRaises(Exception): prepare_audio(src,out,1) def test_cpu_audio_pipeline(self): events=list(run('Audio → MIDI','examples/en/source.wav','','English','',.5,200)) self.assertEqual(len(events),2) self.assertIn('Complete',events[-1][0]) for path in (events[-1][1], events[-1][3]): self.assertTrue(Path(path).exists()) self.assertGreater(len(json.loads(events[-1][2])['notes']), 0) report=json.loads((Path(events[-1][3]).parent/'report.json').read_text()) self.assertEqual(report['optimization']['device'],'cpu') self.assertGreater(report['notes'],0) def test_zerogpu_speech_dispatch_keeps_fitting_on_cpu(self): wave = .05 * np.sin(2*np.pi*220*np.arange(12000)/24000) with patch.object(app, 'DEVICE', 'zerogpu'), patch.object( app, 'generate_zero_speech', return_value=(wave, 24000), create=True) as generate: events = list(run('Text → speech → MIDI', None, 'Hello.', 'English', app.VOICE, .5, 200)) generate.assert_called_once_with('Hello.', 'English', app.VOICE) output = Path(events[-1][3]).parent self.assertTrue((output/'speech.wav').exists()) self.assertFalse((output/'tts.log').exists()) report = json.loads((output/'report.json').read_text()) self.assertEqual(report['optimization']['device'], 'cpu') def test_instrumental_pipeline_at_30_second_limit(self): with tempfile.TemporaryDirectory() as tmp: source = Path(tmp)/'long.wav' rate = 8000 wave = .05*np.sin(2*np.pi*220*np.arange(31*rate)/rate) sf.write(source, wave, rate) events = list(run('Audio → MIDI', str(source), '', 'English', '', 30, 200, 'Instrumental (sparse)')) final = events[-1] self.assertIn('Instrumental (sparse)', final[0]) self.assertEqual(Path(final[3]).name, 'instrumental.mid') self.assertAlmostEqual(sf.info(final[1]).duration, 30) payload = json.loads(final[2]) self.assertAlmostEqual(payload['duration'], 30.5) self.assertTrue(payload['notes']) self.assertTrue(any(n['duration'] > .02 for n in payload['notes'])) self.assertTrue(all(n['start']+n['duration'] <= 30.001 for n in payload['notes'])) report = json.loads((Path(final[3]).parent/'report.json').read_text()) self.assertEqual(report['device'], 'cpu') self.assertEqual(len(report['chunks']), 3) def test_reject_duration_and_style(self): for duration, style in [(30.5, 'Speech (dense)'), (30, 'unknown')]: with self.assertRaises(Exception): list(run('Audio → MIDI', 'examples/en/source.wav', '', 'English', '', duration, 200, style)) def test_cached_examples(self): for language in ['en','zh']: output=example(language) for path in (output[1], output[3]): self.assertTrue(Path(path).exists()) self.assertTrue(json.loads(output[2])['audio'].startswith('data:audio/wav;base64,')) if __name__=='__main__': unittest.main()