-
-
{title}
+
+
+
+ {title}
+
+ {@render leftActions?.()}
{@render children?.()}
@@ -31,6 +35,12 @@
box-sizing: border-box;
}
+ .left-section {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ }
+
.title-section {
display: flex;
align-items: center;
diff --git a/src/lib/csound/engine.ts b/src/lib/csound/engine.ts
index 97e9083..9e98681 100644
--- a/src/lib/csound/engine.ts
+++ b/src/lib/csound/engine.ts
@@ -3,6 +3,7 @@ import { Csound } from '@csound/browser';
export interface CsoundEngineOptions {
onMessage?: (message: string) => void;
onError?: (error: string) => void;
+ onPerformanceEnd?: () => void;
}
export class CsoundEngine {
@@ -10,6 +11,9 @@ export class CsoundEngine {
private initialized = false;
private running = false;
private options: CsoundEngineOptions;
+ private scopeNode: AnalyserNode | null = null;
+ private audioNode: AudioNode | null = null;
+ private audioContext: AudioContext | null = null;
constructor(options: CsoundEngineOptions = {}) {
this.options = options;
@@ -18,26 +22,12 @@ export class CsoundEngine {
async init(): Promise {
if (this.initialized) return;
- try {
- this.csound = await Csound();
-
- this.csound.on('message', (message: string) => {
- this.options.onMessage?.(message);
- });
-
- await this.csound.setOption('-odac');
-
- this.initialized = true;
- this.log('Csound initialized successfully');
- } catch (error) {
- const errorMsg = error instanceof Error ? error.message : 'Failed to initialize Csound';
- this.error(errorMsg);
- throw error;
- }
+ this.initialized = true;
+ this.log('Csound ready');
}
async evaluateCode(code: string): Promise {
- if (!this.initialized || !this.csound) {
+ if (!this.initialized) {
throw new Error('Csound not initialized. Call init() first.');
}
@@ -46,8 +36,29 @@ export class CsoundEngine {
await this.stop();
}
- this.log('Resetting Csound...');
- await this.csound.reset();
+ this.scopeNode = null;
+
+ this.log('Creating new Csound instance...');
+ this.csound = await Csound();
+
+ this.csound.on('message', (message: string) => {
+ this.options.onMessage?.(message);
+ });
+
+ this.csound.on('onAudioNodeCreated', (node: AudioNode) => {
+ this.audioNode = node;
+ this.audioContext = node.context as AudioContext;
+ this.log('Audio node created and captured');
+ });
+
+ this.csound.on('realtimePerformanceEnded', async () => {
+ try {
+ await this.csound?.cleanup();
+ } catch {}
+ this.running = false;
+ this.log('Performance complete');
+ this.options.onPerformanceEnd?.();
+ });
this.log('Setting audio output...');
await this.csound.setOption('-odac');
@@ -63,22 +74,28 @@ export class CsoundEngine {
const sco = scoMatch[1].trim();
this.log('Compiling orchestra...');
- await this.csound.compileOrc(orc);
+ const compileResult = await this.csound.compileOrc(orc);
+
+ if (compileResult !== 0) {
+ throw new Error('Failed to compile orchestra');
+ }
this.log('Reading score...');
await this.csound.readScore(sco);
- this.log('Starting...');
+ this.log('Starting performance...');
+ this.running = true;
await this.csound.start();
- this.log('Performing...');
- this.running = true;
- await this.csound.perform();
+ this.setupAnalyser();
- this.log('Performance complete');
- this.running = false;
} catch (error) {
this.running = false;
+ if (this.csound) {
+ try {
+ await this.csound.cleanup();
+ } catch {}
+ }
const errorMsg = error instanceof Error ? error.message : 'Evaluation failed';
this.error(errorMsg);
throw error;
@@ -89,8 +106,9 @@ export class CsoundEngine {
if (!this.csound || !this.running) return;
try {
+ this.log('Stopping...');
await this.csound.stop();
- await this.csound.reset();
+ await this.csound.cleanup();
this.running = false;
this.log('Stopped');
} catch (error) {
@@ -107,6 +125,36 @@ export class CsoundEngine {
return this.initialized;
}
+ getAudioContext(): AudioContext | null {
+ return this.audioContext;
+ }
+
+ private setupAnalyser(): void {
+ if (!this.audioNode || !this.audioContext) {
+ this.log('Warning: Audio node not available yet');
+ return;
+ }
+
+ try {
+ this.scopeNode = this.audioContext.createAnalyser();
+ this.scopeNode.fftSize = 2048;
+ this.scopeNode.smoothingTimeConstant = 0.3;
+
+ this.audioNode.disconnect();
+ this.audioNode.connect(this.scopeNode);
+ this.scopeNode.connect(this.audioContext.destination);
+
+ this.log('Analyser node created and connected');
+ } catch (error) {
+ console.error('Failed to setup analyser:', error);
+ this.log('Error setting up analyser: ' + error);
+ }
+ }
+
+ getAnalyserNode(): AnalyserNode | null {
+ return this.scopeNode;
+ }
+
private log(message: string): void {
this.options.onMessage?.(message);
}
diff --git a/src/lib/csound/store.ts b/src/lib/csound/store.ts
index 1437c3c..fdcc659 100644
--- a/src/lib/csound/store.ts
+++ b/src/lib/csound/store.ts
@@ -92,6 +92,20 @@ function createCsoundStore() {
}));
},
+ getAudioContext(): AudioContext | null {
+ if (engine) {
+ return engine.getAudioContext();
+ }
+ return null;
+ },
+
+ getAnalyserNode(): AnalyserNode | null {
+ if (engine) {
+ return engine.getAnalyserNode();
+ }
+ return null;
+ },
+
async destroy() {
if (engine) {
await engine.destroy();
diff --git a/web-ide b/web-ide
new file mode 160000
index 0000000..d74138b
--- /dev/null
+++ b/web-ide
@@ -0,0 +1 @@
+Subproject commit d74138bb72e8f471c1e9fac3ab21cea3ad38438f