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
JavaScript
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();