Skip to content

Instantly share code, notes, and snippets.

@willwade
Created July 2, 2025 08:19
Show Gist options
  • Save willwade/9559eaaf3186902166ae8bf5ac62ffd6 to your computer and use it in GitHub Desktop.
Save willwade/9559eaaf3186902166ae8bf5ac62ffd6 to your computer and use it in GitHub Desktop.
A way of testing issue 6
#!/usr/bin/env node
/**
* Issue 6 Reproduction Demo
*
* This demo reproduces the exact testing scenarios described in issue6.md
* to verify that the fixes work correctly across different engines and formats.
*
* Tests performed:
* 1. client.speak() tests with WAV format (SSML, SMD, Plaintext)
* 2. client.speak() tests with MP3 format (SSML, SMD, Plaintext)
* 3. sound.play() tests with WAV format (SSML, SMD, Plaintext)
* 4. sound.play() tests with MP3 format (SSML, SMD, Plaintext)
*
* Engines tested: Amazon Polly, ElevenLabs, Microsoft Azure, WitAI, SAPI
*
* Usage:
* node examples/issue6-reproduction-demo.js [engine]
*
* Examples:
* node examples/issue6-reproduction-demo.js # Test all engines
* node examples/issue6-reproduction-demo.js polly # Test Amazon Polly only
* node examples/issue6-reproduction-demo.js azure # Test Microsoft Azure only
*/
import { getEngineConfigs, getEngineConfig } from './shared/engine-configs.js';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import fs from 'node:fs';
// Load environment variables from .env file
const require = createRequire(import.meta.url);
try {
require('./load-env.cjs');
} catch (error) {
console.warn('Warning: Could not load environment variables from .env file:', error.message);
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const OUTPUT_DIR = path.join(__dirname, "test-output");
// Test texts for different scenarios
const TEST_TEXTS = {
plaintext: "This is a test of the text to speech engine. Testing one two three.",
ssml: "<speak>This is a test of the <emphasis>text to speech</emphasis> engine with <break time='500ms'/> SSML markup.</speak>",
smd: "<speak>This is a test of the <prosody rate='slow'>speech markdown</prosody> engine with <break time='1s'/> SMD syntax.</speak>" // Using SSML as SMD equivalent
};
// Engines to test (matching issue6.md)
const TARGET_ENGINES = ['polly', 'elevenlabs', 'azure', 'witai', 'sapi'];
/**
* Ensure output directory exists
*/
function ensureOutputDir() {
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
}
/**
* Parse command line arguments
*/
function parseArgs() {
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
return { showHelp: true };
}
const engine = args.length > 0 ? args[0].toLowerCase() : null;
return { engine, showHelp: false };
}
/**
* Show help information
*/
function showHelp() {
console.log('Issue 6 Reproduction Demo');
console.log('');
console.log('Usage: node examples/issue6-reproduction-demo.js [engine]');
console.log('');
console.log('Available engines:');
TARGET_ENGINES.forEach(engine => {
console.log(` ${engine}`);
});
console.log('');
console.log('Examples:');
console.log(' node examples/issue6-reproduction-demo.js # Test all engines');
console.log(' node examples/issue6-reproduction-demo.js polly # Test Amazon Polly only');
console.log(' node examples/issue6-reproduction-demo.js azure # Test Microsoft Azure only');
}
/**
* Test client.speak() method with different formats and text types
*/
async function testClientSpeak(engineName, client, format) {
console.log(`\n--- Testing client.speak() with ${format.toUpperCase()} format ---`);
const results = {
ssml: 'unknown',
smd: 'unknown',
plaintext: 'unknown'
};
// Test SSML
try {
console.log(`Testing SSML with ${format}...`);
await client.speak(TEST_TEXTS.ssml);
results.ssml = 'working';
console.log('✓ SSML test completed successfully');
} catch (error) {
results.ssml = `error: ${error.message}`;
console.log(`✗ SSML test failed: ${error.message}`);
}
// Test SMD (using SSML variant)
try {
console.log(`Testing SMD with ${format}...`);
await client.speak(TEST_TEXTS.smd);
results.smd = 'working';
console.log('✓ SMD test completed successfully');
} catch (error) {
results.smd = `error: ${error.message}`;
console.log(`✗ SMD test failed: ${error.message}`);
}
// Test plaintext
try {
console.log(`Testing plaintext with ${format}...`);
await client.speak(TEST_TEXTS.plaintext);
results.plaintext = 'working';
console.log('✓ Plaintext test completed successfully');
} catch (error) {
results.plaintext = `error: ${error.message}`;
console.log(`✗ Plaintext test failed: ${error.message}`);
}
return results;
}
/**
* Test sound.play() method by first synthesizing to file, then playing
*/
async function testSoundPlay(engineName, client, format) {
console.log(`\n--- Testing sound.play() with ${format.toUpperCase()} format ---`);
const results = {
ssml: 'unknown',
smd: 'unknown',
plaintext: 'unknown'
};
ensureOutputDir();
// Test SSML
try {
console.log(`Testing SSML playback with ${format}...`);
const filename = path.join(OUTPUT_DIR, `${engineName}-ssml-playback.${format}`);
await client.synthToFile(TEST_TEXTS.ssml, filename, format);
// Now play the file using the new audio input feature
await client.speak({ filename: filename });
results.ssml = 'working';
console.log('✓ SSML playback test completed successfully');
} catch (error) {
results.ssml = `error: ${error.message}`;
console.log(`✗ SSML playback test failed: ${error.message}`);
}
// Test SMD
try {
console.log(`Testing SMD playback with ${format}...`);
const filename = path.join(OUTPUT_DIR, `${engineName}-smd-playback.${format}`);
await client.synthToFile(TEST_TEXTS.smd, filename, format);
// Now play the file using the new audio input feature
await client.speak({ filename: filename });
results.smd = 'working';
console.log('✓ SMD playback test completed successfully');
} catch (error) {
results.smd = `error: ${error.message}`;
console.log(`✗ SMD playback test failed: ${error.message}`);
}
// Test plaintext
try {
console.log(`Testing plaintext playback with ${format}...`);
const filename = path.join(OUTPUT_DIR, `${engineName}-plaintext-playback.${format}`);
await client.synthToFile(TEST_TEXTS.plaintext, filename, format);
// Now play the file using the new audio input feature
await client.speak({ filename: filename });
results.plaintext = 'working';
console.log('✓ Plaintext playback test completed successfully');
} catch (error) {
results.plaintext = `error: ${error.message}`;
console.log(`✗ Plaintext playback test failed: ${error.message}`);
}
return results;
}
/**
* Test a single engine with all scenarios
*/
async function testEngine(engineName, engineConfig) {
console.log(`\n${'='.repeat(60)}`);
console.log(`Testing Engine: ${engineName.toUpperCase()}`);
console.log(`${'='.repeat(60)}`);
let client = null;
const results = {
'client.speak() WAV': { ssml: 'skipped', smd: 'skipped', plaintext: 'skipped' },
'client.speak() MP3': { ssml: 'skipped', smd: 'skipped', plaintext: 'skipped' },
'sound.play() WAV': { ssml: 'skipped', smd: 'skipped', plaintext: 'skipped' },
'sound.play() MP3': { ssml: 'skipped', smd: 'skipped', plaintext: 'skipped' }
};
try {
// Initialize client
console.log(`Initializing ${engineName}...`);
client = engineConfig.factory();
// Check credentials
console.log(`Checking credentials for ${engineName}...`);
const credentialsValid = await client.checkCredentials();
if (!credentialsValid) {
console.log(`❌ Invalid credentials for ${engineName}, skipping tests`);
return results;
}
console.log(`✅ Credentials valid for ${engineName}`);
// Get and set voice
const voices = await client.getVoices();
if (voices.length === 0) {
console.log(`❌ No voices available for ${engineName}, skipping tests`);
return results;
}
// Select appropriate voice for the engine
let voice = voices[0];
if (engineName === 'polly') {
// For Polly, prefer standard voices that support SSML
const standardVoices = ['Geraint', 'Raveena', 'Aditi', 'Ivy', 'Joanna', 'Kendra', 'Matthew', 'Justin'];
for (const standardVoice of standardVoices) {
const foundVoice = voices.find(v => v.id === standardVoice);
if (foundVoice) {
voice = foundVoice;
break;
}
}
}
console.log(`Using voice: ${voice.name} (${voice.id})`);
await client.setVoice(voice.id);
// Test 1: client.speak() with WAV
results['client.speak() WAV'] = await testClientSpeak(engineName, client, 'wav');
// Test 2: client.speak() with MP3
results['client.speak() MP3'] = await testClientSpeak(engineName, client, 'mp3');
// Test 3: sound.play() with WAV
results['sound.play() WAV'] = await testSoundPlay(engineName, client, 'wav');
// Test 4: sound.play() with MP3
results['sound.play() MP3'] = await testSoundPlay(engineName, client, 'mp3');
console.log(`\n✅ All tests completed for ${engineName}`);
} catch (error) {
console.error(`❌ Error testing ${engineName}:`, error.message);
}
return results;
}
/**
* Print results table
*/
function printResultsTable(allResults) {
console.log(`\n${'='.repeat(80)}`);
console.log('ISSUE 6 REPRODUCTION RESULTS');
console.log(`${'='.repeat(80)}`);
const testTypes = ['client.speak() WAV', 'client.speak() MP3', 'sound.play() WAV', 'sound.play() MP3'];
const textTypes = ['ssml', 'smd', 'plaintext'];
for (const testType of testTypes) {
console.log(`\n## ${testType} tests\n`);
console.log('| engine | SSML test | SMD test | Plaintext test |');
console.log('|:----------------|:----------------------------------------------|:----------------------------------------------|:---------------------|');
for (const engineName of TARGET_ENGINES) {
if (allResults[engineName]) {
const results = allResults[engineName][testType];
const ssmlResult = results.ssml === 'working' ? 'working' : (results.ssml.includes('error') ? 'error' : results.ssml);
const smdResult = results.smd === 'working' ? 'working' : (results.smd.includes('error') ? 'error' : results.smd);
const plaintextResult = results.plaintext === 'working' ? 'working' : (results.plaintext.includes('error') ? 'error' : results.plaintext);
console.log(`| ${engineName.padEnd(15)} | ${ssmlResult.padEnd(45)} | ${smdResult.padEnd(45)} | ${plaintextResult.padEnd(20)} |`);
}
}
}
}
/**
* Main function
*/
async function main() {
const { engine, showHelp } = parseArgs();
if (showHelp) {
showHelp();
return;
}
console.log('Issue 6 Reproduction Demo');
console.log('Testing TTS engines with different formats and playback methods');
console.log(`Target engine: ${engine || 'all'}`);
const engineConfigs = await getEngineConfigs();
const allResults = {};
// Determine which engines to test
const enginesToTest = engine
? (TARGET_ENGINES.includes(engine) ? [engine] : [])
: TARGET_ENGINES;
if (enginesToTest.length === 0) {
console.error(`Unknown engine: ${engine}`);
console.log('Available engines:', TARGET_ENGINES.join(', '));
process.exit(1);
}
// Test each engine
for (const engineName of enginesToTest) {
const engineConfig = getEngineConfig(engineName, engineConfigs);
if (engineConfig) {
allResults[engineName] = await testEngine(engineName, engineConfig);
} else {
console.error(`Engine configuration not found: ${engineName}`);
}
}
// Print results table
printResultsTable(allResults);
console.log(`\n${'='.repeat(80)}`);
console.log('Demo completed! Check the results above to verify issue 6 fixes.');
console.log(`${'='.repeat(80)}`);
}
// Run the demo
main().catch(console.error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment