Files
oldboy/to_continue.md
2025-10-15 02:53:48 +02:00

29 KiB

Project Mode Implementation - Continuation Guide

Overview

This document provides a complete roadmap for continuing the implementation of dual-mode support in OldBoy: Composition Mode (full document evaluation) and Live Coding Mode (block-based evaluation with persistent Csound instance).


What Has Been Completed

1. Type System & Data Model

Files Modified:

  • /src/lib/project-system/types.ts
  • /src/lib/project-system/project-manager.ts

Changes:

  1. Created ProjectMode type:

    export type ProjectMode = 'composition' | 'livecoding';
    
  2. Added mode field to CsoundProject interface:

    export interface CsoundProject {
      // ... existing fields
      mode: ProjectMode;  // NEW: Execution mode
    }
    
  3. Updated CreateProjectData to accept optional mode:

    export interface CreateProjectData {
      // ... existing fields
      mode?: ProjectMode;  // Defaults to 'composition' if not provided
    }
    
  4. Updated UpdateProjectData to allow mode updates:

    export interface UpdateProjectData {
      // ... existing fields
      mode?: ProjectMode;  // Can change mode after creation
    }
    
  5. Modified ProjectManager.createProject():

    • Line 100: Added mode: data.mode || 'composition' to project initialization
    • Ensures all new projects have a mode (defaults to composition)
  6. Modified ProjectManager.updateProject():

    • Line 165: Added ...(data.mode !== undefined && { mode: data.mode }) to update logic
    • Allows mode to be updated without affecting other fields

Result: All projects now have a mode field that persists in IndexedDB and is included in import/export operations.


2. UI for Mode Selection

Files Modified:

  • /src/lib/components/ui/FileBrowser.svelte
  • /src/lib/stores/projectEditor.svelte.ts
  • /src/App.svelte

FileBrowser Changes:

  1. Imports (Line 7):

    import type { ProjectMode } from '../../project-system/types';
    
  2. Props Interface (Line 13):

    onMetadataUpdate?: (projectId: string, updates: {
      title?: string;
      author?: string;
      mode?: ProjectMode  // NEW
    }) => void;
    
  3. State (Line 30):

    let editMode = $state<ProjectMode>('composition');
    
  4. Effect Hook (Lines 32-38):

    $effect(() => {
      if (selectedProject) {
        editTitle = selectedProject.title;
        editAuthor = selectedProject.author;
        editMode = selectedProject.mode;  // NEW: Sync mode from project
      }
    });
    
  5. Metadata Change Handler (Lines 103-118):

    function handleMetadataChange() {
      if (!selectedProject) return;
    
      const hasChanges =
        editTitle !== selectedProject.title ||
        editAuthor !== selectedProject.author ||
        editMode !== selectedProject.mode;  // NEW: Include mode in change detection
    
      if (hasChanges) {
        onMetadataUpdate?.(selectedProject.id, {
          title: editTitle,
          author: editAuthor,
          mode: editMode  // NEW: Send mode updates
        });
      }
    }
    
  6. UI Element (Lines 183-193):

    <div class="field">
      <label for="file-mode">Mode</label>
      <select
        id="file-mode"
        bind:value={editMode}
        onchange={handleMetadataChange}
      >
        <option value="composition">Composition</option>
        <option value="livecoding">Live Coding</option>
      </select>
    </div>
    
  7. CSS (Lines 373-390):

    .field input,
    .field select {
      padding: 0.5rem;
      background-color: #2a2a2a;
      border: 1px solid #3a3a3a;
      color: rgba(255, 255, 255, 0.87);
      font-size: 0.875rem;
      outline: none;
    }
    
    .field select {
      cursor: pointer;
    }
    

ProjectEditor Changes (Line 108):

async updateMetadata(updates: {
  title?: string;
  author?: string;
  mode?: import('../project-system/types').ProjectMode  // NEW
}): Promise<boolean>

App.svelte Changes (Line 123):

async function handleMetadataUpdate(
  projectId: string,
  updates: {
    title?: string;
    author?: string;
    mode?: import('./lib/project-system/types').ProjectMode  // NEW
  }
)

Result: Users can now select project mode in the Files panel metadata editor. Mode changes are immediately persisted.


3. Block Evaluation Infrastructure

File Created:

  • /src/lib/editor/block-eval.ts

Purpose: Provides utilities for extracting code blocks and visual feedback, adapted from flok's cm-eval package.

Exports:

  1. flash(view, from, to, timeout)

    • Visually highlights evaluated code region
    • Default timeout: 150ms
    • Background color: #FFCA2880 (yellow with transparency)
  2. flashField(style?)

    • CodeMirror StateField for managing flash decorations
    • Returns StateField to be added to editor extensions
    • Handles flash effect lifecycle
  3. getSelection(state): EvalBlock

    • Returns currently selected text with positions
    • Returns { text: '', from: null, to: null } if no selection
  4. getLine(state): EvalBlock

    • Returns the entire line at cursor position
    • Includes line's from/to positions
  5. getBlock(state): EvalBlock

    • Returns paragraph block (text separated by blank lines)
    • Searches up and down from cursor until blank lines found
    • Core functionality for live coding mode
  6. getDocument(state): EvalBlock

    • Returns entire document text
    • Used for composition mode evaluation

EvalBlock Interface:

interface EvalBlock {
  text: string;        // The code to evaluate
  from: number | null; // Start position in document
  to: number | null;   // End position in document
}

Implementation Details:

  • Block Detection Algorithm:

    1. Start at cursor line
    2. If line is blank, return empty block
    3. Search backwards until blank line or document start
    4. Search forwards until blank line or document end
    5. Extract text from start to end positions
  • Flash Effect:

    • Uses CodeMirror StateEffect system
    • Creates decoration with inline style
    • Automatically clears after timeout
    • Non-blocking (doesn't prevent editing)

Result: Complete block evaluation infrastructure ready to integrate with Editor component.


What Needs to Be Done Next

Phase 1: Editor Integration with Block Evaluation

Goal: Connect block-eval utilities to the Editor component and add visual feedback.

File to Modify: /src/lib/components/editor/Editor.svelte

Steps:

  1. Import block-eval utilities (add to imports):

    import {
      flashField,
      flash,
      getSelection,
      getBlock,
      getDocument
    } from '../editor/block-eval';
    
  2. Add flashField to extensions:

    Find where extensions are created (likely in a $derived or similar). It should look something like:

    let extensions = $derived([
      // ... existing extensions
    ]);
    

    Add flashField:

    let extensions = $derived([
      // ... existing extensions
      flashField(),  // NEW: Add flash effect support
    ]);
    
  3. Expose block extraction methods:

    Add these methods to the Editor component (after the component logic, before closing tag):

    export function getSelectedText(): string | null {
      if (!editorView) return null;
      const { text } = getSelection(editorView.state);
      return text || null;
    }
    
    export function getCurrentBlock(): string | null {
      if (!editorView) return null;
      const { text } = getBlock(editorView.state);
      return text || null;
    }
    
    export function getFullDocument(): string {
      if (!editorView) return '';
      const { text } = getDocument(editorView.state);
      return text;
    }
    
    export function evaluateWithFlash(text: string, from: number | null, to: number | null) {
      if (editorView && from !== null && to !== null) {
        flash(editorView, from, to);
      }
    }
    
  4. Modify keyboard shortcut for execute:

    Find the keyboard shortcut setup (search for "Ctrl-Enter" or "Cmd-Enter"). It might look like:

    keymap.of([
      {
        key: "Ctrl-Enter",
        run: () => {
          onExecute?.(value);
          return true;
        }
      }
    ])
    

    Change it to call a new internal method:

    keymap.of([
      {
        key: "Ctrl-Enter",
        mac: "Cmd-Enter",
        run: () => {
          handleExecute();
          return true;
        }
      }
    ])
    
  5. Add internal execute handler:

    function handleExecute() {
      if (!editorView) return;
    
      // Get selection or block
      const selection = getSelection(editorView.state);
      if (selection.text) {
        // Has selection: evaluate it
        flash(editorView, selection.from, selection.to);
        onExecute?.(selection.text, 'selection');
      } else {
        // No selection: evaluate block or document based on mode
        // For now, always get block (mode logic will be in App.svelte)
        const block = getBlock(editorView.state);
        if (block.text) {
          flash(editorView, block.from, block.to);
          onExecute?.(block.text, 'block');
        }
      }
    }
    
  6. Update onExecute prop type:

    Change the prop signature from:

    onExecute?: (code: string) => void;
    

    To:

    onExecute?: (code: string, source: 'selection' | 'block' | 'document') => void;
    

Expected Result:

  • Cmd/Ctrl+Enter will flash the code being evaluated
  • Editor can distinguish between selection, block, and document evaluation
  • Parent components can access block extraction methods

Phase 2: Execution Strategy Implementation

Goal: Create strategy pattern for handling composition vs livecoding execution modes.

File to Create: /src/lib/csound/execution-strategies.ts

Implementation:

import type { CsoundStore } from './store';
import type { ProjectMode } from '../project-system/types';

export interface ExecutionStrategy {
  execute(
    csound: CsoundStore,
    code: string,
    fullContent: string,
    source: 'selection' | 'block' | 'document'
  ): Promise<void>;
}

/**
 * Composition Mode Strategy
 * - Always evaluates full CSD document
 * - Uses ephemeral instances (fresh instance each time)
 * - Standard workflow: compile → start → play → cleanup
 */
export class CompositionStrategy implements ExecutionStrategy {
  async execute(
    csound: CsoundStore,
    code: string,
    fullContent: string,
    source: 'selection' | 'block' | 'document'
  ): Promise<void> {
    // Always evaluate full document in composition mode
    await csound.evaluate(fullContent);
  }
}

/**
 * Live Coding Mode Strategy
 * - Evaluates blocks incrementally
 * - Uses persistent instance (maintains state)
 * - Supports: score events, channel updates, instrument redefinition
 */
export class LiveCodingStrategy implements ExecutionStrategy {
  private isInitialized = false;
  private headerCompiled = false;

  async execute(
    csound: CsoundStore,
    code: string,
    fullContent: string,
    source: 'selection' | 'block' | 'document'
  ): Promise<void> {
    // First time: initialize with full document
    if (!this.isInitialized) {
      await this.initializeFromDocument(csound, fullContent);
      this.isInitialized = true;
      return;
    }

    // Subsequent evaluations: handle blocks
    await this.evaluateBlock(csound, code);
  }

  private async initializeFromDocument(
    csound: CsoundStore,
    fullContent: string
  ): Promise<void> {
    // Parse CSD to extract orchestra and initial score
    const { header, instruments, score } = this.parseCSD(fullContent);

    // Compile header + instruments
    const fullOrchestra = header + '\n' + instruments;
    const compileResult = await csound.compileOrchestra(fullOrchestra);

    if (!compileResult.success) {
      throw new Error(compileResult.errorMessage || 'Compilation failed');
    }

    this.headerCompiled = true;

    // Start performance
    await csound.startPerformance();

    // If score has events, send them
    if (score.trim()) {
      await csound.readScore(score);
    }
  }

  private async evaluateBlock(csound: CsoundStore, code: string): Promise<void> {
    const trimmedCode = code.trim();

    if (!trimmedCode) return;

    // Detect what kind of code this is
    if (this.isScoreEvent(trimmedCode)) {
      // Send score event (e.g., "i 1 0 2 0.5")
      await csound.sendScoreEvent(trimmedCode);
    }
    else if (this.isInstrumentDefinition(trimmedCode)) {
      // Recompile instrument
      await csound.compileOrchestra(trimmedCode);
    }
    else if (this.isChannelSet(trimmedCode)) {
      // Set channel value (e.g., "freq = 440")
      await this.handleChannelSet(csound, trimmedCode);
    }
    else {
      // Default: try to compile as orchestra code
      await csound.compileOrchestra(trimmedCode);
    }
  }

  private parseCSD(content: string): {
    header: string;
    instruments: string;
    score: string
  } {
    // Extract <CsInstruments> section
    const orcMatch = content.match(/<CsInstruments>([\s\S]*?)<\/CsInstruments>/);
    if (!orcMatch) {
      return { header: '', instruments: '', score: '' };
    }

    const orchestra = orcMatch[1].trim();

    // Extract <CsScore> section
    const scoMatch = content.match(/<CsScore>([\s\S]*?)<\/CsScore>/);
    const score = scoMatch ? scoMatch[1].trim() : '';

    // Split orchestra into header (sr, ksmps, nchnls, etc.) and instruments
    const instrMatch = orchestra.match(/([\s\S]*?)(instr\s+\d+[\s\S]*)/);

    if (instrMatch) {
      return {
        header: instrMatch[1].trim(),
        instruments: instrMatch[2].trim(),
        score
      };
    }

    // If no instruments found, treat entire orchestra as header
    return {
      header: orchestra,
      instruments: '',
      score
    };
  }

  private isScoreEvent(code: string): boolean {
    // Check for score event syntax: i, f, e, a followed by space/number
    return /^[ifea]\s+[\d\-]/.test(code);
  }

  private isInstrumentDefinition(code: string): boolean {
    // Check for instrument definition
    return /^\s*instr\s+/.test(code);
  }

  private isChannelSet(code: string): boolean {
    // Simple channel set syntax: varname = value
    // e.g., "freq = 440" or "cutoff = 2000"
    return /^\w+\s*=\s*[\d\.\-]+/.test(code);
  }

  private async handleChannelSet(csound: CsoundStore, code: string): Promise<void> {
    // Parse: varname = value
    const match = code.match(/^(\w+)\s*=\s*([\d\.\-]+)/);
    if (!match) return;

    const [, channelName, valueStr] = match;
    const value = parseFloat(valueStr);

    await csound.setControlChannel(channelName, value);
  }

  /**
   * Reset the strategy state (called when switching documents or resetting)
   */
  reset(): void {
    this.isInitialized = false;
    this.headerCompiled = false;
  }
}

/**
 * Factory function to create appropriate strategy based on mode
 */
export function createExecutionStrategy(mode: ProjectMode): ExecutionStrategy {
  return mode === 'livecoding'
    ? new LiveCodingStrategy()
    : new CompositionStrategy();
}

Expected Result:

  • Strategy pattern cleanly separates composition and livecoding behaviors
  • LiveCodingStrategy maintains state across evaluations
  • Supports score events, instrument redefinition, and channel control

Phase 3: Update App.svelte to Use Strategies

File to Modify: /src/App.svelte

Steps:

  1. Import strategy utilities:

    import { createExecutionStrategy, type ExecutionStrategy } from './lib/csound/execution-strategies';
    
  2. Track current strategy:

    let currentStrategy = $state<ExecutionStrategy | null>(null);
    let currentMode = $state<import('./lib/project-system/types').ProjectMode>('composition');
    
  3. Update strategy when project changes:

    Add effect to watch for project mode changes:

    $effect(() => {
      const mode = projectEditor.currentProject?.mode || 'composition';
    
      // Only recreate strategy if mode changed
      if (mode !== currentMode) {
        currentMode = mode;
        currentStrategy = createExecutionStrategy(mode);
    
        // If switching to livecoding, need to reset csound
        if (mode === 'livecoding') {
          // Reset to ensure clean state
          csound.reset().catch(console.error);
        }
      }
    });
    
  4. Update handleExecute function:

    Replace existing handleExecute (around line 99):

    async function handleExecute(code: string, source: 'selection' | 'block' | 'document') {
      try {
        if (!currentStrategy) {
          currentStrategy = createExecutionStrategy(currentMode);
        }
    
        const fullContent = projectEditor.content;
        await currentStrategy.execute(csound, code, fullContent, source);
      } catch (error) {
        console.error('Execution error:', error);
      }
    }
    
  5. Update EditorWithLogs onExecute prop:

    Find where EditorWithLogs is used (around line 274):

    <EditorWithLogs
      value={projectEditor.content}
      language="javascript"
      onChange={handleEditorChange}
      onExecute={handleExecute}  <!-- Already correct, just verify signature matches -->
      logs={interpreterLogs}
      {editorSettings}
    />
    

Expected Result:

  • App automatically uses correct strategy based on project mode
  • Strategy instance persists across evaluations
  • Mode switches trigger proper cleanup/reset

Phase 4: Update Csound Store for Mode-Aware Instance Management

Goal: Make Csound store use persistent vs ephemeral instances based on project mode.

File to Modify: /src/lib/contexts/app-context.ts

Current Code (likely around line 10-15):

const csound = createCsoundStore();

Change to:

const csound = createCsoundStore('ephemeral');  // Default to ephemeral

Then in App.svelte, add mode-based reset logic:

In the $effect that watches mode changes (from Phase 3, Step 3), enhance it:

$effect(() => {
  const mode = projectEditor.currentProject?.mode || 'composition';

  if (mode !== currentMode) {
    const oldMode = currentMode;
    currentMode = mode;
    currentStrategy = createExecutionStrategy(mode);

    // Handle Csound instance mode switching
    if (mode === 'livecoding' && oldMode === 'composition') {
      // Switching TO livecoding: need persistent instance
      // Reset will be handled by first LiveCodingStrategy execution
      console.log('Switched to live coding mode');
    } else if (mode === 'composition' && oldMode === 'livecoding') {
      // Switching FROM livecoding: stop and cleanup
      csound.stop().catch(console.error);
      console.log('Switched to composition mode');
    }
  }
});

Note: The actual instance mode (ephemeral/persistent) is now implicitly handled by the strategy:

  • CompositionStrategy: calls csound.evaluate() which uses ephemeral mode (current default)
  • LiveCodingStrategy: calls compileOrchestra() + startPerformance() which uses the same instance repeatedly

The key insight is that persistence is achieved by NOT calling evaluate() (which destroys/recreates), but instead using the low-level API (compileOrchestra, startPerformance, sendScoreEvent).

Expected Result:

  • Composition mode: Each evaluation gets fresh instance
  • Live coding mode: Single instance persists across block evaluations
  • Switching modes properly cleans up old instances

Phase 5: Testing & Verification

Test Plan:

Test 1: Composition Mode (Baseline)

  1. Create new project (defaults to composition mode)
  2. Paste this CSD code:
    <CsoundSynthesizer>
    <CsOptions>
    -odac
    </CsOptions>
    <CsInstruments>
    sr = 48000
    ksmps = 32
    nchnls = 2
    0dbfs = 1
    
    instr 1
      aOut poscil 0.2, 440
      outs aOut, aOut
    endin
    </CsInstruments>
    <CsScore>
    i 1 0 2
    </CsScore>
    </CsoundSynthesizer>
    
  3. Press Cmd/Ctrl+Enter
  4. Expected: Entire document flashes yellow, sound plays for 2 seconds
  5. Modify frequency to 880, press Cmd/Ctrl+Enter again
  6. Expected: New instance created, new sound plays

Test 2: Live Coding Mode - Initial Setup

  1. Open FileBrowser, select the project
  2. In metadata, change Mode from "Composition" to "Live Coding"
  3. Press Cmd/Ctrl+Enter on the entire document
  4. Expected:
    • Document flashes
    • Csound initializes (check logs for "Starting performance...")
    • Sound plays if score has events

Test 3: Live Coding Mode - Block Evaluation

  1. Clear the <CsScore> section (or comment out with semicolons)
  2. Ensure document is initialized (press Cmd/Ctrl+Enter on full document)
  3. Add a new line at the bottom of the document:
    i 1 0 2 0.5
    
  4. Place cursor on that line, press Cmd/Ctrl+Enter
  5. Expected:
    • Only that line flashes
    • Sound plays immediately (instrument 1 triggered)
    • Logs show score event sent, NOT full recompilation

Test 4: Live Coding Mode - Channel Control

  1. Modify instrument 1 to use a channel:
    instr 1
      kFreq chnget "freq"
      aOut poscil 0.2, kFreq
      outs aOut, aOut
    endin
    
  2. Reinitialize (Cmd+Enter on full document)
  3. Start a long note:
    i 1 0 60
    
  4. While playing, evaluate this block:
    freq = 440
    
  5. Then evaluate:
    freq = 880
    
  6. Expected:
    • Frequency changes in real-time while note plays
    • No audio interruption

Test 5: Live Coding Mode - Instrument Redefinition

  1. While note is playing from Test 4, modify instrument 1:
    instr 1
      kFreq chnget "freq"
      aOut vco2 0.2, kFreq
      outs aOut, aOut
    endin
    
  2. Select only the instrument definition, press Cmd+Enter
  3. Expected:
    • Instrument recompiles
    • Next triggered note uses new definition
    • Currently playing notes might continue with old definition (Csound behavior)

Test 6: Mode Switching

  1. In live coding mode with performance running
  2. Switch mode to "Composition" in FileBrowser
  3. Expected:
    • Performance stops
    • Logs show "Stopped"
  4. Press Cmd+Enter
  5. Expected:
    • Full document evaluation (composition mode behavior)

Test 7: Persistence Across Sessions

  1. Create project in live coding mode
  2. Close browser tab
  3. Reopen, load project
  4. Expected:
    • Mode is still "Live Coding" in metadata
    • First evaluation initializes correctly

Troubleshooting Guide

Issue: "Cannot find module '@flok-editor/session'"

Cause: The block-eval code references types from flok's session package.

Solution:

  • Our implementation in /src/lib/editor/block-eval.ts is standalone and doesn't need this
  • If TypeScript complains, we don't use the Document type from flok
  • Our editor integration passes callbacks, not Document objects

Issue: Flashing doesn't appear

Cause: flashField not added to editor extensions

Solution:

  1. Verify import { flashField } from '../editor/block-eval' in Editor.svelte
  2. Verify flashField() is in the extensions array
  3. Check browser console for errors

Issue: Block evaluation evaluates wrong text

Cause: Block detection algorithm confused by comment syntax

Solution:

  • Csound uses ; for line comments
  • Blank line detection: line.text.trim().length === 0
  • If commented lines interfere, update getBlock() to treat ;-only lines as blank

Issue: Live coding mode doesn't persist

Cause: Strategy state lost between evaluations

Solution:

  • Verify currentStrategy is stored in $state(), not recreated each time
  • Check that $effect only recreates strategy when mode actually changes
  • Ensure LiveCodingStrategy instance is reused

Issue: Performance doesn't start in live coding mode

Cause: startPerformance() not called or called before compilation

Solution:

  • Check logs for compilation errors
  • Verify compileOrchestra() returns success: true
  • Ensure startPerformance() is awaited

Issue: Mode changes don't take effect

Cause: Project not reloaded after metadata update

Solution:

  • handleMetadataUpdate() should call projectEditor.updateMetadata()
  • This should update projectEditor.currentProject
  • $effect watching currentProject?.mode should trigger
  • Verify effect dependencies are correct

File Structure Reference

New Files Created

/src/lib/editor/block-eval.ts         - Block evaluation utilities (✅ COMPLETE)
/src/lib/csound/execution-strategies.ts - Strategy pattern (❌ TODO)

Modified Files

/src/lib/project-system/types.ts      - Added ProjectMode type (✅ COMPLETE)
/src/lib/project-system/project-manager.ts - Added mode handling (✅ COMPLETE)
/src/lib/components/ui/FileBrowser.svelte - Added mode selector UI (✅ COMPLETE)
/src/lib/stores/projectEditor.svelte.ts - Updated metadata types (✅ COMPLETE)
/src/App.svelte - Updated handlers (✅ PARTIAL - needs strategy integration)
/src/lib/components/editor/Editor.svelte - Needs block eval integration (❌ TODO)
/src/lib/contexts/app-context.ts - Might need mode-aware csound init (❌ TODO)

Key Design Principles to Remember

  1. No Fallbacks: If livecoding fails, it fails. Don't silently fall back to composition mode.

  2. Mode Per Project: Mode is project metadata, not global state. Different tabs could have different modes.

  3. Persistent = Reuse Low-Level API: Persistence isn't a csound setting, it's about calling compileOrchestra() + startPerformance() instead of evaluate().

  4. Block = Paragraph: A block is text separated by blank lines. This is the standard live coding convention.

  5. Flash for Feedback: Always flash evaluated code so user knows what executed.

  6. Strategy Owns State: LiveCodingStrategy tracks initialization state, not the store or app.


Implementation Order

Follow this exact order to minimize issues:

  1. Phase 1: Editor integration (visual feedback works first)
  2. Phase 2: Create execution strategies (logic isolated and testable)
  3. Phase 3: Wire strategies to App.svelte (connect pieces)
  4. Phase 4: Verify instance management (make sure persistence works)
  5. Phase 5: Test thoroughly (catch edge cases)

Success Criteria

Composition Mode

  • [] Full document evaluation on Cmd+Enter
  • [] Flash effect shows what was evaluated
  • [] Fresh instance every time (no state leakage)

Live Coding Mode

  • [] First evaluation initializes from full document
  • [] Subsequent evaluations process blocks incrementally
  • [] Score events trigger sounds immediately
  • [] Channel updates affect running performance
  • [] Instrument redefinition works
  • [] Instance persists across evaluations
  • [] Logs show block evaluations, not full recompilations

Both Modes

  • [] Mode selection UI works
  • [] Mode persists in database
  • [] Mode switching cleans up properly
  • [] Keyboard shortcuts work consistently

Additional Notes

Why Ephemeral Default Makes Sense Now

The original implementation used ephemeral mode by default because Web Audio reconnection after csound.reset() was unreliable. However, in live coding mode:

  • We don't call reset() between evaluations
  • We don't call evaluate() which destroys the instance
  • We use low-level API (compileOrchestra, sendScoreEvent) which reuses the instance

So "persistent mode" is actually achieved by avoiding the methods that destroy/recreate.

Why We Adapted cm-eval Instead of Using It

The flok cm-eval package:

  • Assumes a Document object with an evaluate() method
  • Is tightly coupled to flok's session architecture
  • Uses their specific remote evaluation system

Our needs:

  • Evaluate blocks locally (no remote)
  • Different document format (CSD vs raw code)
  • Integrate with existing Csound store

Solution: Extract the core block detection and flash logic, adapt for our architecture.

Csound Score Event Syntax Quick Reference

For testing live coding mode:

; Start instrument 1 at time 0, duration 2 seconds, amplitude 0.5
i 1 0 2 0.5

; Start instrument 1 now (time 0), indefinite duration (-1)
i 1 0 -1 0.5

; Turn off all instances of instrument 1
i -1 0 0

; Function table: create table 1, size 8192, sine wave
f 1 0 8192 10 1

Next Session Checklist

When you resume implementation:

  • Read this document thoroughly
  • Verify all completed work with pnpm build
  • Start with Phase 1 (Editor integration)
  • Test each phase before moving to next
  • Update this document if you deviate from the plan
  • Document any issues found and solutions

Questions to Resolve

None at this time. The architecture is well-defined and ready for implementation.


Document Version: 1.0 Last Updated: 2025-01-15 Status: Ready for Phase 1 implementation