UNPKG

harmony-cli

Version:

AI-powered music generation CLI tool using Google's Lyria RealTime for coding background music

240 lines (200 loc) • 8.01 kB
#!/usr/bin/env node import inquirer from 'inquirer'; import chalk from 'chalk'; import ora from 'ora'; import figlet from 'figlet'; import { pastel } from 'gradient-string'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { LyriaClient } from './lyria-client.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Create a temporary directory for audio files const tempDir = path.join(__dirname, 'temp'); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir); } // Welcome screen function showWelcome() { console.clear(); const welcomeText = figlet.textSync('HARMONY CLI', { font: 'Standard', horizontalLayout: 'fitted', verticalLayout: 'controlled smushing', }); console.log(pastel.multiline(welcomeText)); // console.log(welcomeText) console.log(chalk.cyan('\nšŸŽµ Generate AI-powered music while you code! šŸŽµ\n')); } // Get user inputs async function getUserInputs() { const questions = [ { type: 'password', name: 'apiKey', message: 'Enter your Gemini API key:', mask: '*', validate: (input) => { if (input.trim() === '') { return 'API key is required!'; } return true; } }, { type: 'input', name: 'musicTheme', message: 'What kind of music would you like? (e.g., "chill lo-fi hip hop", "energetic techno", "peaceful piano"):', validate: (input) => { if (input.trim() === '') { return 'Music theme is required!'; } return true; } }, { type: 'list', name: 'bpm', message: 'Select the tempo (BPM):', choices: [ { name: 'Slow (70 BPM) - Relaxed coding', value: 70 }, { name: 'Medium (100 BPM) - Balanced focus', value: 100 }, { name: 'Fast (130 BPM) - Energetic coding', value: 130 }, { name: 'Very Fast (160 BPM) - High energy', value: 160 } ] }, { type: 'list', name: 'mood', message: 'Select the mood:', choices: [ 'Chill', 'Upbeat', 'Ambient', 'Energetic', 'Peaceful', 'Dreamy', 'Focused' ] } ]; return await inquirer.prompt(questions); } // Music generation class class MusicGenerator { constructor(apiKey) { this.apiKey = apiKey; this.lyriaClient = new LyriaClient(apiKey); this.isPlaying = false; } async connect() { return await this.lyriaClient.connect(); } async generateMusic(theme, options = {}) { const spinner = ora('Setting up music generation...').start(); try { const { bpm = 100, mood = 'Chill' } = options; spinner.text = 'Configuring music parameters...'; // Set up prompts for Lyria const prompts = [ { text: theme, weight: 2.0 }, { text: mood, weight: 1.5 }, { text: 'Instrumental', weight: 1.0 }, { text: 'Coding Background Music', weight: 1.0 } ]; await this.lyriaClient.setPrompts(prompts); // Configure music generation parameters const config = { bpm: bpm, temperature: 1.1, density: 0.7, brightness: mood.toLowerCase() === 'energetic' ? 0.8 : 0.6, guidance: 4.0 }; await this.lyriaClient.setConfiguration(config); spinner.succeed(`Music configured: ${theme} (${mood}, ${bpm} BPM)`); return { success: true, theme, bpm, mood, duration: 'continuous streaming' }; } catch (error) { spinner.fail('Music generation setup failed'); console.error(chalk.red('Setup error:'), error.message); return { success: false, error: error.message }; } } async startContinuousPlayback(segmentLength = 60) { console.log(chalk.green(`\nšŸŽµ Preparing continuous AI music generation! (${segmentLength}s segments) šŸŽµ`)); // Start continuous music loop this.isPlaying = true; // Set up Ctrl+C handler const gracefulShutdown = () => { console.log(chalk.yellow('\n\nStopping music playbook...')); this.lyriaClient.stop(); this.lyriaClient.disconnect(); this.isPlaying = false; process.exit(0); }; process.on('SIGINT', gracefulShutdown); process.on('SIGTERM', gracefulShutdown); // Start the continuous loop try { await this.lyriaClient.startContinuousLoop(segmentLength); } catch (error) { console.error(chalk.red('āŒ Music loop error:'), error.message); } console.log(chalk.green('\nšŸŽµ Thanks for using Harmony CLI! Happy coding! šŸŽµ')); } displayControls() { console.log(chalk.cyan('šŸŽ›ļø Harmony CLI - Real AI Music:')); console.log(chalk.white(' • āœ… REAL audio generated by Lyria RealTime')); console.log(chalk.white(' • šŸŽ¼ Custom music based on your prompts')); console.log(chalk.white(' • šŸŽÆ High-quality 48kHz stereo audio')); console.log(chalk.white(' • ā¹ļø Press Ctrl+C to stop\n')); console.log(chalk.magenta('šŸŽØ Powered by Google\'s Lyria RealTime')); console.log(chalk.gray(' Real AI-generated instrumental music\n')); } } // Main CLI function async function main() { try { showWelcome(); const inputs = await getUserInputs(); console.log(chalk.blue('\nšŸ“ Your music preferences:')); console.log(chalk.white(` Theme: ${inputs.musicTheme}`)); console.log(chalk.white(` Tempo: ${inputs.bpm} BPM`)); console.log(chalk.white(` Mood: ${inputs.mood}`)); console.log(chalk.white(` Duration: 1 minute segments (looped)\n`)); const musicGen = new MusicGenerator(inputs.apiKey); const connected = await musicGen.connect(); if (!connected) { console.log(chalk.red('āŒ Could not establish connection. Please check your API key.')); process.exit(1); } const result = await musicGen.generateMusic(inputs.musicTheme, { bpm: inputs.bpm, mood: inputs.mood }); if (!result.success) { console.log(chalk.red(`āŒ Music generation failed: ${result.error}`)); process.exit(1); } console.log(chalk.green('āœ… Music ready for continuous playback!\n')); // Start continuous music playback with 60-second segments await musicGen.startContinuousPlayback(60); } catch (error) { console.error(chalk.red('\nāŒ An error occurred:'), error.message); process.exit(1); } } // Handle uncaught errors process.on('unhandledRejection', (error) => { console.error(chalk.red('\nāŒ Unhandled error:'), error.message); process.exit(1); }); // Start the application main();