yoanbernabeu / music-gpt-bundle
Symfony bundle for integrating Music GPT API into your applications
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:symfony-bundle
pkg:composer/yoanbernabeu/music-gpt-bundle
Requires
- php: >=8.4
- symfony/config: ^7.0|^8.0
- symfony/dependency-injection: ^7.0|^8.0
- symfony/http-client: ^7.0|^8.0
- symfony/http-kernel: ^7.0|^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- symfony/phpunit-bridge: ^7.0|^8.0
- symfony/var-dumper: ^7.0|^8.0
README
A Symfony bundle for integrating Music GPT API into your applications.
🎵 Powered by MusicGPT.com - Generate AI music and covers
🔌 API Access: musicgpt.com/api
Features
- 🎵 Music AI - Generate custom music from text prompts
- 🎤 Cover Songs - Transform audio with AI voice models
- 🗣️ Text To Speech - Convert text to realistic speech with voice models
- 🎼 Audio Extraction - Extract stems (vocals, drums, bass, etc.) from audio
- 🎙️ Voice Management - Search and list AI voices
- 🔍 Conversion Tracking - Check status and retrieve results
- 🚀 Type-Safe - Full PHP 8.4 type safety with DTOs and Enums
- 📦 Easy to Use - Simple, intuitive API
Installation
composer require yoanbernabeu/music-gpt-bundle
If you're using Symfony Flex, the bundle will be automatically registered. Otherwise, add it to your config/bundles.php:
return [ // ... YoanBernabeu\MusicGptBundle\MusicGptBundle::class => ['all' => true], ];
Configuration
Create config/packages/music_gpt.yaml:
music_gpt: api: api_key: '%env(MUSIC_GPT_API_KEY)%'
Add your API key to .env:
MUSIC_GPT_API_KEY=your_api_key_here
Get your API key at musicgpt.com/api.
Usage
Generate Music with AI
use YoanBernabeu\MusicGptBundle\Contract\MusicAIServiceInterface; use YoanBernabeu\MusicGptBundle\DTO\MusicAI\MusicAIRequest; class MusicController { public function __construct( private readonly MusicAIServiceInterface $musicAI ) {} public function generate(): void { // Simple prompt $request = new MusicAIRequest( prompt: 'A cheerful song about coding in PHP' ); $response = $this->musicAI->generate($request); echo "Task ID: {$response->getTaskId()}\n"; echo "ETA: {$response->getEta()} seconds\n"; } public function generateAdvanced(): void { // With custom style and vocals $request = new MusicAIRequest( musicStyle: 'Lo-fi Hip Hop', lyrics: 'Coding all night long...', voiceId: 'Drake', makeInstrumental: false ); $response = $this->musicAI->generate($request); // The API generates 2 versions [$version1, $version2] = $response->getConversionIds(); } }
Available Parameters:
prompt- Natural language descriptionmusicStyle- Genre (Rock, Pop, Jazz, Lo-fi, etc.)lyrics- Custom lyricsvoiceId- Voice model for vocalsmakeInstrumental- Generate without vocals (default: false)vocalOnly- Generate vocals only (default: false)webhookUrl- Callback URL for async notifications
Create AI Voice Covers
use YoanBernabeu\MusicGptBundle\Contract\CoverServiceInterface; use YoanBernabeu\MusicGptBundle\DTO\Cover\CoverRequest; class CoverController { public function __construct( private readonly CoverServiceInterface $cover ) {} public function create(): void { // From URL $request = new CoverRequest( audioUrl: 'https://example.com/song.mp3', voiceId: 'Drake' ); $response = $this->cover->createCover($request); echo "Task ID: {$response->getTaskId()}\n"; echo "Conversion ID: {$response->getConversionId()}\n"; } public function createWithPitch(): void { // From local file with pitch adjustment $request = new CoverRequest( audioFile: '/path/to/audio.wav', voiceId: 'Taylor Swift', pitch: -2 // -12 to +12 semitones ); $response = $this->cover->createCover($request); } }
Note: Provide either audioUrl OR audioFile, not both.
Generate Text To Speech
use YoanBernabeu\MusicGptBundle\Contract\TextToSpeechServiceInterface; use YoanBernabeu\MusicGptBundle\DTO\TextToSpeech\TextToSpeechRequest; class TextToSpeechController { public function __construct( private readonly TextToSpeechServiceInterface $textToSpeech ) {} public function create(): void { // Simple text to speech with voice ID $request = new TextToSpeechRequest( text: 'Hello world, this is a test of the text to speech feature.', gender: 'male', voiceId: 'Drake' ); $response = $this->textToSpeech->createTextToSpeech($request); echo "Task ID: {$response->getTaskId()}\n"; echo "ETA: {$response->getEta()} seconds\n"; } public function createWithSampleAudio(): void { // Using a sample audio URL for voice cloning $request = new TextToSpeechRequest( text: 'The character Sherlock Holmes first appeared in print in 1887.', gender: 'female', sampleAudioUrl: 'https://example.com/voice-sample.mp3' ); $response = $this->textToSpeech->createTextToSpeech($request); } public function createWithWebhook(): void { // With webhook for async notification $request = new TextToSpeechRequest( text: 'When I think of superheroes I think of super humans.', gender: 'male', voiceId: 'Drake', webhookUrl: 'https://example.com/webhook' ); $response = $this->textToSpeech->createTextToSpeech($request); } }
Available Parameters:
text(required) - Text content to convert to speechgender(required) - Voice gender: "male" or "female"voiceId(optional) - Voice model ID (e.g., "Drake", "Adele")sampleAudioUrl(optional) - URL of voice sample for cloningwebhookUrl(optional) - Callback URL for async notifications
Note: Priority is given to sampleAudioUrl, then voiceId, then gender.
Extract Audio Stems
use YoanBernabeu\MusicGptBundle\Contract\ExtractionServiceInterface; use YoanBernabeu\MusicGptBundle\DTO\Extraction\ExtractionRequest; class ExtractionController { public function __construct( private readonly ExtractionServiceInterface $extraction ) {} public function extractVocals(): void { // Extract vocals from a song $request = new ExtractionRequest( audioUrl: 'https://example.com/song.mp3', stems: ['vocals', 'instrumental'] ); $response = $this->extraction->extractStems($request); echo "Task ID: {$response->getTaskId()}\n"; echo "ETA: {$response->getEta()} seconds\n"; } public function extractMultipleStems(): void { // Extract multiple stems from a song $request = new ExtractionRequest( audioUrl: 'https://example.com/song.mp3', stems: ['vocals', 'drums', 'bass', 'guitar', 'piano'] ); $response = $this->extraction->extractStems($request); } public function extractWithPreprocessing(): void { // Extract with audio cleanup $request = new ExtractionRequest( audioFile: '/path/to/audio.wav', stems: ['vocals'], preprocessingOptions: ['Denoise', 'Dereverb'] ); $response = $this->extraction->extractStems($request); } public function cleanupAudio(): void { // Only apply preprocessing without extraction $request = new ExtractionRequest( audioUrl: 'https://example.com/noisy-audio.mp3', preprocessingOptions: ['Denoise', 'Deecho', 'Dereverb'] ); $response = $this->extraction->extractStems($request); } }
Available Stems:
- Basic:
vocals,instrumental - Vocal types:
male_vocal,female_vocal,lead_vocal,back_vocal - Instruments:
bass,drums,guitar,piano,keys,strings,winds - Guitar types:
rhythm_guitar,solo_guitar,acoustic_guitar,electric_guitar - Drum components:
kick_drum,snare_drum,toms,hi_hat,ride,crash
Preprocessing Options:
Denoise- Remove background noiseDeecho- Remove echo effectsDereverb- Remove reverb effects
Note: Provide either audioUrl OR audioFile, not both.
Search and List Voices
use YoanBernabeu\MusicGptBundle\Contract\VoiceServiceInterface; class VoiceController { public function __construct( private readonly VoiceServiceInterface $voice ) {} public function searchVoices(): void { // Search for voices by name $response = $this->voice->searchVoices('Drake'); echo "Found {$response->getTotal()} voices\n"; foreach ($response->getVoices() as $voice) { echo "ID: {$voice->getVoiceId()} - Name: {$voice->getVoiceName()}\n"; } } public function searchWithPagination(): void { // Search with pagination $response = $this->voice->searchVoices( query: 'Taylor', limit: 50, page: 0 ); echo "Page {$response->getPage()} of " . ceil($response->getTotal() / $response->getLimit()) . "\n"; } public function listAllVoices(): void { // Get all available voices $response = $this->voice->getAllVoices(limit: 100, page: 0); echo "Total voices available: {$response->getTotal()}\n"; echo "Showing: " . count($response->getVoices()) . " voices\n"; } public function browseVoices(): void { // Browse through all voices with pagination $page = 0; $limit = 20; do { $response = $this->voice->getAllVoices($limit, $page); foreach ($response->getVoices() as $voice) { echo "{$voice->getVoiceName()}\n"; } $page++; } while (($page * $limit) < $response->getTotal()); } }
Available Methods:
searchVoices(string $query, int $limit = 20, int $page = 0)- Search voices by namegetAllVoices(int $limit = 20, int $page = 0)- Get all available voices
Response Data:
getVoices()- Array of VoiceInfo objectsgetTotal()- Total number of voices availablegetLimit()- Results per pagegetPage()- Current page number
Track Conversion Status
use YoanBernabeu\MusicGptBundle\Contract\ConversionServiceInterface; use YoanBernabeu\MusicGptBundle\Enum\ConversionType; class ConversionController { public function __construct( private readonly ConversionServiceInterface $conversion ) {} public function checkStatus(): void { // Get details by Task ID $details = $this->conversion->getByTaskId( taskId: 'task_123456', conversionType: ConversionType::MUSIC_AI ); if ($details->isCompleted()) { echo "✅ Completed!\n"; echo "Audio 1: {$details->getAudioUrl1()}\n"; echo "Audio 2: {$details->getAudioUrl2()}\n"; } elseif ($details->isProcessing()) { echo "⏳ Processing: {$details->getStatus()}\n"; } elseif ($details->isFailed()) { echo "❌ Failed: {$details->getStatusMessage()}\n"; } } public function getByConversionId(): void { // Get details by Conversion ID $details = $this->conversion->getByConversionId( conversionId: 'conv_789012', conversionType: ConversionType::COVER ); if ($details->isCompleted()) { echo "Audio: {$details->getAudioUrl()}\n"; echo "Video: {$details->getVideoUrl()}\n"; echo "Cover: {$details->getImageUrl()}\n"; } } public function checkTextToSpeech(): void { // Check Text To Speech conversion $details = $this->conversion->getByTaskId( taskId: 'task_tts_123', conversionType: ConversionType::TEXT_TO_SPEECH ); if ($details->isCompleted()) { echo "✅ Speech generated!\n"; echo "Audio (MP3): {$details->getAudioUrl()}\n"; echo "Audio (WAV): {$details->getAudioUrlWav()}\n"; } } public function checkExtraction(): void { // Check Extraction conversion $details = $this->conversion->getByTaskId( taskId: 'task_extract_456', conversionType: ConversionType::EXTRACTION ); if ($details->isCompleted()) { echo "✅ Stems extracted!\n"; echo "Vocals: {$details->getVocalsUrl()}\n"; echo "Instrumental: {$details->getInstrumentalUrl()}\n"; } } }
Complete Workflow Example
use YoanBernabeu\MusicGptBundle\Contract\MusicAIServiceInterface; use YoanBernabeu\MusicGptBundle\Contract\ConversionServiceInterface; use YoanBernabeu\MusicGptBundle\DTO\MusicAI\MusicAIRequest; use YoanBernabeu\MusicGptBundle\Enum\ConversionType; class WorkflowController { public function __construct( private readonly MusicAIServiceInterface $musicAI, private readonly ConversionServiceInterface $conversion ) {} public function generateAndWait(): void { // 1. Generate music $request = new MusicAIRequest(prompt: 'A relaxing piano melody'); $response = $this->musicAI->generate($request); $taskId = $response->getTaskId(); // 2. Poll for completion $maxAttempts = 30; for ($i = 0; $i < $maxAttempts; $i++) { sleep(10); $details = $this->conversion->getByTaskId( $taskId, ConversionType::MUSIC_AI ); if ($details->isCompleted()) { echo "✅ Done!\n"; echo "Download: {$details->getAudioUrl1()}\n"; break; } if ($details->isFailed()) { echo "❌ Failed: {$details->getStatusMessage()}\n"; break; } echo "⏳ Processing... (attempt {$i}/{$maxAttempts})\n"; } } }
Error Handling
use YoanBernabeu\MusicGptBundle\Exception\AuthenticationException; use YoanBernabeu\MusicGptBundle\Exception\PaymentRequiredException; use YoanBernabeu\MusicGptBundle\Exception\RateLimitException; use YoanBernabeu\MusicGptBundle\Exception\ValidationException; try { $response = $this->musicAI->generate($request); } catch (AuthenticationException $e) { // Invalid API key (401/403) echo "Authentication error: {$e->getMessage()}"; } catch (PaymentRequiredException $e) { // Insufficient credits (402) echo "Payment required: {$e->getMessage()}"; } catch (RateLimitException $e) { // Too many requests (429) echo "Rate limited. Retry after: {$e->getRetryAfter()} seconds"; } catch (ValidationException $e) { // Invalid parameters (400/422) echo "Validation errors: " . print_r($e->getErrors(), true); }
Rate Limits
Different plans have different limits. When rate limited (429), use $exception->getRetryAfter() to know when to retry.
| Plan | Audio Generations | Get by ID |
|---|---|---|
| Free | 1 parallel | 20/min |
| PLUS | 5 parallel | 200/min |
| PRO | 10 parallel | 500/min |
See official rate limits documentation.
API Documentation
Main Documentation
Conversion Endpoints
Helper Endpoints
Development
Run tests:
composer test
Check code style:
composer cs-check composer cs-fix
Static analysis:
composer phpstan
Run all checks:
composer cs-check && composer phpstan && composer test
License
This bundle is open-sourced software licensed under the MIT license.
Author
Yoan Bernabeu
- Website: yoandev.com
- GitHub: @yoanbernabeu
Support
For questions or issues, please open an issue on GitHub.