import SCLang , { SCLangError } from "@supercollider/lang" ; import Server , { msg , MsgType } from "@supercollider/server" ; import { CallOrder , Command , Dryad } from "dryadic" ; import fs from "fs" ; import path from "path" ; const { defFree , defLoad , defRecv } = msg ; const fsp = fs . promises ; const StateKeys = { SYNTH_DEFS : "SYNTH_DEFS" , } ; /**
 * `synthDef` is returned from compilation by sclang and
 * is set in the context for children Dryads to access.
 */ export interface CompiledSynthDef { name : string ; bytes : Buffer ; synthDesc : SynthDesc ; } export interface LoadedSynthDef { name : string ; } export type SynthDef = CompiledSynthDef | LoadedSynthDef ; export type SynthDesc = object ; interface Properties { source ? : string ; compileFrom ? : string ; watch : boolean ; saveToDir ? : string ; loadFrom ? : string ; } interface Context { sclang ? : SCLang ; synthDef ? : CompiledSynthDef ; scserver ? : Server ; _watcher ? : any ; } /**
 * Compile a SynthDef from sclang source code
 * or load a precompiled .scsyndef
 *
 * If compilation is required then it will insert SCLang as a parent if necessary.
 *
 * Note that the synthDefName is not known until after the source code is compiled.
 */ export default class SCSynthDef extends Dryad < Properties > { static fromSource ( source ) { return new SCSynthDef ( { source , watch : false , } ) ; } static fromFile ( path ) { return new SCSynthDef ( { compileFrom : path , watch : true , } ) ; } defaultProperties ( ) { return { watch : false } ; } /**
     * If there is no SCLang in the parent context,
     * then this will wrap itself in an SCLang (language interpreter).
     */ requireParent ( ) { if ( this . properties . source || this . properties . compileFrom ) { return "SCLang" ; } } prepareForAdd ( ) { return { updateContext : ( context , properties ) => ( { synthDef : this . _prepareForAdd ( context , properties ) , } ) , callOrder : CallOrder . SELF_THEN_CHILDREN , } ; } private async _prepareForAdd ( context , properties ) { if ( properties . source ) { const result = await this . compileSource ( context , properties . source ) ; await this . _sendSynthDef ( context , properties , result ) ; return result ; } if ( properties . compileFrom ) { const result = await this . compileFrom ( context , properties . compileFrom ) ; await this . _sendSynthDef ( context , properties , result ) ; return result ; } const lf = properties . loadFrom ; if ( lf ) { const result = { name : path . basename ( lf , path . extname ( lf ) ) , } ; if ( context . scserver ) { await context . scserver . callAndResponse ( defLoad ( path . resolve ( lf ) ) ) ; } return result ; } throw new Error ( "Nothing specified for SCSynthDef: source|compileFrom|loadFrom Properties:" + JSON . stringify ( properties ) ) ; } private async _sendSynthDef ( context , properties , result ) { if ( ! context . scserver ) { throw new Error ( "Missing scserver in context" ) ; } this . putSynthDef ( context , result . name , result . synthDesc ) ; context . synthDef = result ; const buffer = Buffer . from ( result . bytes ) ; const promises = [ context . scserver . callAndResponse ( defRecv ( buffer ) ) ] ; if ( properties . saveToDir ) { promises . push ( this . _writeSynthDef ( result . name , buffer , result . synthDesc , properties . saveToDir ) ) ; } await Promise . all ( promises ) ; return result ; } private async _writeSynthDef ( name , buffer , synthDesc , saveToDir ) { const dir = path . resolve ( saveToDir ) ; const pathname = path . join ( dir , name + ".scsyndef" ) ; await fsp . writeFile ( pathname , buffer ) ; const descpath = path . join ( dir , name + ".json" ) ; await fsp . writeFile ( descpath , JSON . stringify ( synthDesc , null , 2 ) ) ; } /**
     * Returns a Promise for a SynthDef result object: name, bytes, synthDesc
     */ async compileSource ( context , sourceCode , pathName ? ) { const autoBraced = /^ *arg|\|/ . test ( sourceCode ) ? `{ ${ sourceCode } }` : sourceCode ; const wrappedCode = `{
      var def = { ${ autoBraced } }.value.asSynthDef;
      (
        name: def.name,
        synthDesc: def.asSynthDesc.asJSON(),
        bytes: def.asBytes()
      )
    }.value;` ; if ( ! context . sclang ) { throw new Error ( `Missing sclang in context: ${ JSON . stringify ( context ) }` ) ; } return context . sclang . interpret ( wrappedCode , undefined , false , false , true ) . then ( ( result ) => { return result as CompiledSynthDef ; } , ( error ) => { error . annotate ( `Failed to compile SynthDef  ${ error . message } ${ pathName || "" }` , { properties : this . properties , sourceCode , } ) ; return Promise . reject ( error ) ; } ) ; } /**
     * Returns a Promise for a SynthDef result object: name, bytes, synthDesc
     */ private async compileFrom ( context , sourcePath ) { const source = ( await fsp . readFile ( path . resolve ( sourcePath ) ) ) . toString ( "ascii" ) ; return this . compileSource ( context , source , sourcePath ) ; } add ( ) { return { run : ( context , properties ) => { if ( properties . compileFrom && properties . watch ) { context . _watcher = fs . watch ( path . resolve ( properties . compileFrom ) , ( ) => { if ( properties . compileFrom ) { this . compileFrom ( context , properties . compileFrom ) . then ( ( result ) => { this . _sendSynthDef ( context , properties , result ) . catch ( error => console . error ( error ) ) ; } ) ; } } ) ; } } , } ; } remove ( ) { return { scserver : { msg : ( context ) : void | MsgType => { if ( context . synthDef ) { return defFree ( context . synthDef . name ) ; } } , } , run : ( context ) => { if ( context . _watcher ) { context . _watcher . close ( ) ; delete context . _watcher ; } } , } ; } private putSynthDef ( context , synthDefName , synthDesc ) { context . scserver && context . scserver . state . mutate ( StateKeys . SYNTH_DEFS , state => { return state . set ( synthDefName , synthDesc ) ; } ) ; } /**
     * Return the value of this object, which is the synthDef: {name, bytes, synthDesc}
     * for use in /s_new.
     */ value ( context ) { if ( ! context . synthDef ) { throw new Error ( "No synthDef in context for SCSynthDef" ) ; } return context . synthDef ; } }