oof
This commit is contained in:
@ -1,10 +1,15 @@
|
||||
import { Csound } from '@csound/browser';
|
||||
import { Csound as Csound6 } from '@csound/browser';
|
||||
import { Csound as Csound7 } from 'csound7';
|
||||
import type { File } from '../project-system/types';
|
||||
import { compileCSD, type CsoundObj } from './types';
|
||||
|
||||
export interface CsoundEngineOptions {
|
||||
onMessage?: (message: string) => void;
|
||||
onError?: (error: string) => void;
|
||||
onPerformanceEnd?: () => void;
|
||||
onAnalyserNodeCreated?: (node: AnalyserNode) => void;
|
||||
getProjectFiles?: () => Promise<File[]>;
|
||||
useCsound7?: boolean;
|
||||
}
|
||||
|
||||
export interface PerformanceMetrics {
|
||||
@ -19,7 +24,7 @@ export interface CompilationResult {
|
||||
}
|
||||
|
||||
export class CsoundEngine {
|
||||
private csound: Csound | null = null;
|
||||
private csound: CsoundObj | null = null;
|
||||
private initialized = false;
|
||||
private compiled = false;
|
||||
private running = false;
|
||||
@ -27,9 +32,11 @@ export class CsoundEngine {
|
||||
private scopeNode: AnalyserNode | null = null;
|
||||
private audioNode: AudioNode | null = null;
|
||||
private audioContext: AudioContext | null = null;
|
||||
private useCsound7: boolean;
|
||||
|
||||
constructor(options: CsoundEngineOptions = {}) {
|
||||
this.options = options;
|
||||
this.useCsound7 = options.useCsound7 ?? false;
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
@ -42,7 +49,9 @@ export class CsoundEngine {
|
||||
|
||||
private async ensureCsoundInstance(): Promise<void> {
|
||||
if (!this.csound) {
|
||||
this.log('Creating Csound instance...');
|
||||
const version = this.useCsound7 ? '7' : '6';
|
||||
this.log(`Creating Csound ${version} instance...`);
|
||||
const Csound = this.useCsound7 ? Csound7 : Csound6;
|
||||
this.csound = await Csound();
|
||||
this.setupCallbacks();
|
||||
}
|
||||
@ -68,6 +77,7 @@ export class CsoundEngine {
|
||||
}
|
||||
|
||||
await this.csound!.setOption('-m0');
|
||||
await this.csound!.setOption('-d');
|
||||
await this.csound!.setOption('-odac');
|
||||
await this.csound!.setOption('-+msg_color=false');
|
||||
await this.csound!.setOption('--daemon');
|
||||
@ -82,14 +92,43 @@ export class CsoundEngine {
|
||||
throw new Error('Failed to compile base header');
|
||||
}
|
||||
|
||||
await this.csound!.start();
|
||||
|
||||
this.compiled = true;
|
||||
this.running = true;
|
||||
this.setupAnalyser();
|
||||
this.log('Csound restarted and ready');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all project files to Csound's virtual filesystem
|
||||
* This enables #include directives to work properly
|
||||
*/
|
||||
private async syncProjectFilesToFS(): Promise<void> {
|
||||
if (!this.csound) {
|
||||
this.log('Warning: Cannot sync files - no Csound instance');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.getProjectFiles) {
|
||||
this.log('Warning: Cannot sync files - no getProjectFiles callback');
|
||||
return;
|
||||
}
|
||||
|
||||
const files = await this.options.getProjectFiles();
|
||||
this.log(`Syncing ${files.length} files to virtual FS...`);
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = encoder.encode(file.content);
|
||||
await this.csound.fs.writeFile(file.title, content);
|
||||
this.log(` - ${file.title}`);
|
||||
} catch (error) {
|
||||
this.log(`Warning: Failed to write ${file.title}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.log('File sync complete');
|
||||
}
|
||||
|
||||
private setupCallbacks(): void {
|
||||
if (!this.csound) return;
|
||||
|
||||
@ -174,7 +213,98 @@ export class CsoundEngine {
|
||||
await this.csound!.readScore(event);
|
||||
}
|
||||
|
||||
async evaluateCode(code: string): Promise<void> {
|
||||
/**
|
||||
* Run a .csd file (composition mode)
|
||||
* Syncs all files to virtual FS, then uses native CSD compilation
|
||||
*/
|
||||
async playCSD(filename: string, content?: string): Promise<void> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('Csound not initialized. Call init() first.');
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[Engine] playCSD() called with filename:', filename);
|
||||
console.log('[Engine] Content provided:', !!content);
|
||||
if (content) {
|
||||
console.log('[Engine] Content preview:', content.substring(0, 100));
|
||||
console.log('[Engine] Content length:', content.length);
|
||||
}
|
||||
|
||||
if (this.running) {
|
||||
await this.stop();
|
||||
}
|
||||
|
||||
await this.restart();
|
||||
this.scopeNode = null;
|
||||
|
||||
// Sync all files to virtual FS so #includes work
|
||||
await this.syncProjectFilesToFS();
|
||||
|
||||
// If content is provided, write it to the filename
|
||||
if (content) {
|
||||
const encoder = new TextEncoder();
|
||||
const encoded = encoder.encode(content);
|
||||
console.log('[Engine] Encoded content length:', encoded.length);
|
||||
await this.csound!.fs.writeFile(filename, encoded);
|
||||
this.log(`Wrote ${filename} to virtual FS (${encoded.length} bytes)`);
|
||||
console.log('[Engine] Successfully wrote to virtual FS');
|
||||
|
||||
// Verify the file was written correctly
|
||||
try {
|
||||
const readBack = await this.csound!.fs.readFile(filename);
|
||||
const decoder = new TextDecoder();
|
||||
const readContent = decoder.decode(readBack);
|
||||
console.log('[Engine] Read back from FS:', readContent.substring(0, 100));
|
||||
console.log('[Engine] Read back length:', readContent.length);
|
||||
console.log('[Engine] Contains closing tag:', readContent.includes('</CsoundSynthesizer>'));
|
||||
} catch (e) {
|
||||
console.error('[Engine] Failed to read back file:', e);
|
||||
}
|
||||
}
|
||||
|
||||
this.log(`Compiling ${filename}...`);
|
||||
|
||||
// Debug: List files in virtual FS root to verify file exists
|
||||
try {
|
||||
const files = await this.csound!.fs.readdir('/');
|
||||
console.log('[Engine] Files in virtual FS root:', files);
|
||||
} catch (e) {
|
||||
console.error('[Engine] Failed to list FS:', e);
|
||||
}
|
||||
|
||||
// Use native CSD compilation (Csound handles #includes)
|
||||
// Mode 0 = path (read from virtual FS), Mode 1 = text (parse as CSD content)
|
||||
console.log('[Engine] Calling compileCSD with filename:', filename, 'mode: 0 (path)');
|
||||
const result = await compileCSD(this.csound!, filename, 0);
|
||||
console.log('[Engine] compileCSD returned:', result);
|
||||
|
||||
if (result !== 0) {
|
||||
throw new Error(`Failed to compile ${filename}`);
|
||||
}
|
||||
|
||||
this.compiled = true;
|
||||
this.log('CSD compiled successfully');
|
||||
|
||||
// Start performance after successful compilation
|
||||
this.log('Starting performance...');
|
||||
await this.csound!.start();
|
||||
this.running = true;
|
||||
this.setupAnalyser();
|
||||
this.log('Performance started');
|
||||
|
||||
} catch (error) {
|
||||
this.running = false;
|
||||
this.compiled = false;
|
||||
const errorMsg = error instanceof Error ? error.message : 'Evaluation failed';
|
||||
this.error(errorMsg);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a .orc file (orchestra only, no score)
|
||||
*/
|
||||
async playORC(orchestraCode: string): Promise<void> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('Csound not initialized. Call init() first.');
|
||||
}
|
||||
@ -185,25 +315,25 @@ export class CsoundEngine {
|
||||
}
|
||||
|
||||
await this.restart();
|
||||
|
||||
this.scopeNode = null;
|
||||
|
||||
const orcMatch = code.match(/<CsInstruments>([\s\S]*?)<\/CsInstruments>/);
|
||||
const scoMatch = code.match(/<CsScore>([\s\S]*?)<\/CsScore>/);
|
||||
this.log('Compiling orchestra...');
|
||||
|
||||
if (!orcMatch || !scoMatch) {
|
||||
throw new Error('Invalid CSD format. Must contain <CsInstruments> and <CsScore> sections.');
|
||||
const result = await this.csound!.compileOrc(orchestraCode);
|
||||
|
||||
if (result !== 0) {
|
||||
throw new Error('Failed to compile orchestra');
|
||||
}
|
||||
|
||||
const orc = orcMatch[1].trim();
|
||||
const sco = scoMatch[1].trim();
|
||||
|
||||
await this.csound!.compileOrc(orc);
|
||||
this.compiled = true;
|
||||
this.log('Orchestra compiled successfully');
|
||||
|
||||
if (sco) {
|
||||
await this.readScore(sco);
|
||||
}
|
||||
// Start performance after successful compilation
|
||||
this.log('Starting performance...');
|
||||
await this.csound!.start();
|
||||
this.running = true;
|
||||
this.setupAnalyser();
|
||||
this.log('Performance started');
|
||||
|
||||
} catch (error) {
|
||||
this.running = false;
|
||||
@ -214,6 +344,28 @@ export class CsoundEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incremental code evaluation (live coding mode)
|
||||
* Requires Csound to be already running
|
||||
*/
|
||||
async evalCode(code: string): Promise<void> {
|
||||
if (!this.csound) {
|
||||
throw new Error('No Csound instance available');
|
||||
}
|
||||
|
||||
if (!this.running) {
|
||||
throw new Error('Csound not running. Use playCSD() or playORC() first.');
|
||||
}
|
||||
|
||||
this.log('Evaluating code...');
|
||||
const result = await this.csound.evalCode(code);
|
||||
|
||||
if (result !== 0) {
|
||||
this.error('Code evaluation failed');
|
||||
throw new Error('Code evaluation failed');
|
||||
}
|
||||
}
|
||||
|
||||
async setControlChannel(name: string, value: number): Promise<void> {
|
||||
if (!this.csound) {
|
||||
throw new Error('No Csound instance available');
|
||||
|
||||
Reference in New Issue
Block a user