Better architectural distinction between live coding mode and composition mode

This commit is contained in:
2025-10-15 11:04:27 +02:00
parent 46925f5c2e
commit bbdb01200e
11 changed files with 434 additions and 160 deletions

View File

@ -10,11 +10,11 @@
import Spectrogram from './lib/components/audio/Spectrogram.svelte';
import ConfirmDialog from './lib/components/ui/ConfirmDialog.svelte';
import InputDialog from './lib/components/ui/InputDialog.svelte';
import { createCsoundDerivedStores, type LogEntry } from './lib/csound';
import TemplateDialog from './lib/components/ui/TemplateDialog.svelte';
import { createCsoundDerivedStores, type LogEntry, type EvalSource } from './lib/csound';
import { type CsoundProject } from './lib/project-system';
import { DEFAULT_CSOUND_TEMPLATE, LIVECODING_TEMPLATE } from './lib/config/templates';
import { templateRegistry, type CsoundTemplate } from './lib/templates/template-registry';
import { createAppContext, setAppContext } from './lib/contexts/app-context';
import { createExecutionStrategy, type ExecutionStrategy } from './lib/csound/execution-strategies';
import type { ProjectMode } from './lib/project-system/types';
import {
PanelLeftClose,
@ -32,13 +32,11 @@
const appContext = createAppContext();
setAppContext(appContext);
const { csound, projectManager, editorSettings, projectEditor, uiState } = appContext;
const { csound, projectManager, editorSettings, projectEditor, uiState, executionContext } = appContext;
const csoundDerived = createCsoundDerivedStores(csound);
let analyserNode = $state<AnalyserNode | null>(null);
let interpreterLogs = $state<LogEntry[]>([]);
let currentStrategy = $state<ExecutionStrategy | null>(null);
let currentMode = $state<ProjectMode>('composition');
let logsUnsubscribe: (() => void) | undefined;
@ -47,12 +45,16 @@
const result = await projectManager.getAllProjects();
if (result.success && result.data.length === 0) {
await projectManager.createProject({
title: 'Template',
author: 'System',
content: DEFAULT_CSOUND_TEMPLATE,
tags: []
});
const classicTemplate = templateRegistry.getById('classic');
if (classicTemplate) {
await projectManager.createProject({
title: 'Welcome',
author: 'System',
content: classicTemplate.content,
tags: [],
mode: classicTemplate.mode
});
}
}
logsUnsubscribe = csoundDerived.logs.subscribe(logs => {
@ -78,9 +80,10 @@
projectEditor.setContent(value);
}
function handleNewFile() {
function handleNewEmptyFile() {
const emptyTemplate = templateRegistry.getEmpty();
const result = projectEditor.requestSwitch(
() => projectEditor.createNew(DEFAULT_CSOUND_TEMPLATE)
() => projectEditor.createNew(emptyTemplate.content)
);
if (result === 'confirm-unsaved') {
@ -88,9 +91,15 @@
}
}
function handleNewLiveCodingFile() {
function handleNewFromTemplate() {
uiState.showTemplateDialog();
}
function handleTemplateSelect(template: CsoundTemplate) {
uiState.hideTemplateDialog();
const result = projectEditor.requestSwitch(
() => projectEditor.createNew(LIVECODING_TEMPLATE)
() => projectEditor.createNew(template.content)
);
if (result === 'confirm-unsaved') {
@ -110,14 +119,9 @@
}
}
async function handleExecute(code: string, source: 'selection' | 'block' | 'document') {
async function handleExecute(code: string, source: EvalSource) {
try {
if (!currentStrategy) {
currentStrategy = createExecutionStrategy(currentMode);
}
const fullContent = projectEditor.content;
await currentStrategy.execute(csound, code, fullContent, source);
await executionContext.execute(code, source);
} catch (error) {
console.error('Execution error:', error);
}
@ -189,28 +193,16 @@
$effect(() => {
const mode = projectEditor.currentProject?.mode || 'composition';
const projectId = projectEditor.currentProjectId;
if (mode !== currentMode) {
const oldMode = currentMode;
currentMode = mode;
if (mode !== executionContext.mode) {
executionContext.switchMode(mode).catch(console.error);
}
// IMPORTANT: Only create new strategy if mode actually changed
// Reset the old strategy if switching away from livecoding
if (oldMode === 'livecoding' && currentStrategy) {
const liveCodingStrategy = currentStrategy as any;
if (liveCodingStrategy.reset) {
liveCodingStrategy.reset();
}
}
executionContext.setContentProvider(() => projectEditor.content);
currentStrategy = createExecutionStrategy(mode);
if (mode === 'livecoding' && oldMode === 'composition') {
console.log('Switched to live coding mode');
} else if (mode === 'composition' && oldMode === 'livecoding') {
csound.stop().catch(console.error);
console.log('Switched to composition mode');
}
if (projectId) {
executionContext.startSession(projectId).catch(console.error);
}
});
@ -236,8 +228,8 @@
<FileBrowser
{projectManager}
onFileSelect={handleFileSelect}
onNewFile={handleNewFile}
onNewLiveCodingFile={handleNewLiveCodingFile}
onNewEmptyFile={handleNewEmptyFile}
onNewFromTemplate={handleNewFromTemplate}
onMetadataUpdate={handleMetadataUpdate}
selectedProjectId={projectEditor.currentProjectId}
/>
@ -325,7 +317,6 @@
onExecute={handleExecute}
logs={interpreterLogs}
{editorSettings}
mode={currentMode}
/>
</div>
@ -448,6 +439,12 @@
placeholder="Untitled"
onConfirm={handleSaveAs}
/>
<TemplateDialog
bind:visible={uiState.templateDialogVisible}
onSelect={handleTemplateSelect}
onCancel={() => uiState.hideTemplateDialog()}
/>
</div>
<style>