Writing Scores with SuperCollider

OK. FOSC is usable when certain conditions are met. Based on my tests (In summary, based on my tests, FOSC functions reliably on Intel‑based macOS and Linux systems. On ARM64 hardware, it works only under macOS. On Windows, it fails to operate on both Intel and ARM64 architectures.):


With SC3.13.0 and SC3.14.1 on macOS 15.7.5 (ARM64), I could not get FOSC to work with either LilyPond version due to the following error:

SC3.13.0 and SC3.14.1 on macOS 15.7.5, error when using FOSC
-> Fosc
ERROR: Message 'alterations' not understood.
RECEIVER:
   nil
ARGS:
KEYWORD ARGUMENTS:
CALL STACK:
	DoesNotUnderstandError:reportError
		arg this = <instance of DoesNotUnderstandError>
	Nil:handleError
		arg this = nil
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of DoesNotUnderstandError>
	Object:throw
		arg this = <instance of DoesNotUnderstandError>
	Object:doesNotUnderstand
		arg this = nil
		arg selector = 'alterations'
		arg args = [*0]
		arg kwargs = [*0]
	Meta_FoscPitchManager:pitchClassNumberToPitchClassName
		arg this = <instance of Meta_FoscPitchManager>
		arg number = 60
		arg accidental = nil
		var integer = 0
		var frac = 0
		var alterations = nil
		var index = nil
		var accidentalName = nil
		var diatonicPitchClassName = nil
		var result = nil
	Meta_FoscPitchManager:midinoteToPitchName
		arg this = <instance of Meta_FoscPitchManager>
		arg midinote = 60
		arg accidental = nil
		var pitchClassName = nil
		var octaveNumber = nil
		var octaveName = nil
		var result = nil
	FoscPitch:init
		arg this = <instance of FoscPitch>
		arg argName = 60
	FoscNoteHead:writtenPitch_
		arg this = <instance of FoscNoteHead>
		arg pitch = 60
	FoscNoteHead:init
		arg this = <instance of FoscNoteHead>
		arg writtenPitch = 60
		arg argClient = <instance of FoscNote>
		arg argIsCautionary = false
		arg argIsForced = false
		arg argIsParenthesized = false
		arg argTweaks = nil
		var noteHead = nil
		var key = nil
		var val = nil
	FoscNote:initFoscNote
		arg this = <instance of FoscNote>
		arg writtenPitch = 60
		arg isCautionary = false
		arg isForced = false
		arg isParenthesized = false
	< closed FunctionDef >  (no arguments or variables)
	Interpreter:interpretPrintCmdLine
		arg this = <instance of Interpreter>
		var res = nil
		var func = <instance of Function>
		var code = "(
a = FoscNote(60, 1/4);
a.s..."
		var doc = nil
		var ideClass = <instance of Meta_ScIDE>
	Process:interpretPrintCmdLine
		arg this = <instance of Main>
^^ ERROR: Message 'alterations' not understood.
RECEIVER: nil

On Linux (ARM64) and Windows (Intel, ARM64), I could not find a way to resolve the problems yet.

Ubuntu 24.04.3, ARM64
Fosc.lilypondPath = "/home/parallels/Downloads/lilypond-2.25.81-linux-x86_64/lilypond-2.25.81/bin/lilypond"

returns:

-> Fosc

However,

Fosc.lilypondVersion;

returns:

ERROR: /bin/sh: 1: /home/parallels/Downloads/lilypond-2.25.81-linux-x86_64/lilypond-2.25.81/bin/lilypond: Exec format error
ERROR: Message 'at' not understood.
RECEIVER:
   nil
ARGS:
   Integer 0
KEYWORD ARGUMENTS:
CALL STACK:
	DoesNotUnderstandError:reportError
		arg this = <instance of DoesNotUnderstandError>
	Nil:handleError
		arg this = nil
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of DoesNotUnderstandError>
	Object:throw
		arg this = <instance of DoesNotUnderstandError>
	Object:doesNotUnderstand
		arg this = nil
		arg selector = 'at'
		arg args = [*1]
		arg kwargs = [*0]
	Meta_Fosc:lilypondVersion
		arg this = <instance of Meta_Fosc>
		var str = ""
	Interpreter:interpretPrintCmdLine
		arg this = <instance of Interpreter>
		var res = nil
		var func = <instance of Function>
		var code = "Fosc.lilypondVersion;"
		var doc = nil
		var ideClass = <instance of Meta_ScIDE>
	Process:interpretPrintCmdLine
		arg this = <instance of Main>

^^ ERROR: Message 'at' not understood.
RECEIVER: nil

Messages with a similar name understood by the receiver:
	as
	rate
	halt

Many other objects respond to the message 'at' (found 36 superclasses).

“Exec format error” indicates the binary is not compatible with ARM64. You cannot run the x86_64 LilyPond binary on ARM64.

Windows 11 (ARM64 & Intel)

LilyPond itself runs and returns the version correctly, but FOSC fails when trying to open the generated PDF:

Fosc.lilypondPath = "C:/Users/prko/Downloads/lilypond-2.25.81/bin/lilypond.exe"

returns:

-> Fosc
Fosc.lilypondVersion;

returns:

-> 2.25.81

However,

(
a = FoscNote(60, 1/4);
a.show
)

returns:

ERROR: Changing working directory to: `'C:/Users/prko/AppData/Local/SuperCollider/fosc-output'
fatal error: unable to change directory to: `'C:/Users/prko/AppData/Local/SuperCollider/fosc-output'
ERROR: FoscIOManager:openFile: path does not exist: C:\Users\prko\AppData\Local\SuperCollider/fosc-output/0005.pdf.
CALL STACK:
	Object:reportError
		arg this = "FoscIOManager:openFile: path..."
	Nil:handleError
		arg this = nil
		arg error = "FoscIOManager:openFile: path..."
	Thread:handleError
		arg this = <instance of Thread>
		arg error = "FoscIOManager:openFile: path..."
	Object:throw
		arg this = "FoscIOManager:openFile: path..."
	Fosc:show
		arg this = <instance of FoscNote>
		arg paperSize = nil
		arg staffSize = nil
		arg includes = nil
		var illustrateEnvir = <instance of Event>
		var path = "C:\Users\prko\AppData\Local\..."
	Interpreter:interpretPrintCmdLine
		arg this = <instance of Interpreter>
		var res = nil
		var func = <instance of Function>
		var code = "(
a = FoscNote(60, 1/4);
a.s..."
		var doc = nil
		var ideClass = <instance of Meta_ScIDE>
	Process:interpretPrintCmdLine
		arg this = <instance of Main>

The folder does exist:

"C:/Users/prko/AppData/Local/SuperCollider/fosc-output".openOS

So this seems to be a path-handling issue in FOSC on Windows.


Where the error messages come from

The relevant code is here:

A simple implementation

Changing line 147 as follows suppresses the error messages on macOS 15.7.5, Ubuntu 22.04.5, and Ubuntu 24.04.3:

 exitCode = systemCmd(
     command +
     Platform.case(
        \osx,     { "2>/dev/null" }, 
        \linux,   { "2>/dev/null" }, 
        \windows, { "2>NUL" } 
     )
 );

However, this also hides important LilyPond warnings and errors, such as:

ERROR: warning: g_spawn_sync failed (-1): gs: Failed to execute child process “gs” (No such file or directory)
ERROR: warning: `(gs -q -dNODISPLAY -dNOSAFER -dNOPAUSE -dBATCH -dAutoRotatePages=/None -dPrinted=false /var/folders/2_/hdf9s2tx6kg7_yqv0bwqtlcm0000gn/T//lilypond-tmp-372811)' failed (-1)

ERROR: /opt/homebrew/Cellar/lilypond/2.24.4/share/lilypond/2.24.4/ly/init.ly:65:2: error: Guile signaled an error for the expression beginning here
#
 (let ((book-handler (if (defined? 'default-toplevel-book-handler)
ERROR: Throw to key `ly-file-failed' with args `()'.
ERROR: FoscIOManager:openFile: path does not exist: /Users/prko/Library/Application Support/SuperCollider/fosc-output/0028.pdf.
CALL STACK:
	Object:reportError
		arg this = "FoscIOManager:openFile: path..."
	Nil:handleError
		arg this = nil
		arg error = "FoscIOManager:openFile: path..."
	Thread:handleError
		arg this = <instance of Thread>
		arg error = "FoscIOManager:openFile: path..."
	Object:throw
		arg this = "FoscIOManager:openFile: path..."
	Fosc:show
		arg this = <instance of FoscNote>
		arg paperSize = nil
		arg staffSize = nil
		arg includes = nil
		var illustrateEnvir = <instance of Event>
		var path = "/Users/prko/Library/Applicat..."
	Interpreter:interpretPrintCmdLine
		arg this = <instance of Interpreter>
		var res = nil
		var func = <instance of Function>
		var code = "(
a = FoscNote(60, 1/4);
a.s..."
		var doc = nil
		var ideClass = <instance of Meta_ScIDE>
	Process:interpretPrintCmdLine
		arg this = <instance of Main>

So suppressing stderr entirely is not ideal.

A more appropriate implementation

Filtering only the “noise” while keeping real errors visible seems better:

*runLilypond { |path, flags, outputPath, executablePath, clean=false|
		var lilypondBase, filterText, command, exitCode, success;

		executablePath = executablePath ?? { Fosc.lilypondPath };
		lilypondBase = path.splitext[0];
		outputPath = outputPath ? lilypondBase;
		flags = ((flags ? "") ++ " %").format("-dno-point-and-click -o");

		command = "% % % %".format(executablePath, flags, outputPath.shellQuote, path.shellQuote);

		filterText = " 2>&1 | grep -vE " ++
				"'^(Processing|Parsing\\.\\.\\.|Interpreting music\\.\\.\\.|Preprocessing graphical objects\\.\\.\\.|Finding the ideal number of pages\\.\\.\\.|Fitting music on [0-9]+ page[s]?\\.\\.\\.|Drawing systems\\.\\.\\.|Converting to .+)$' | " ++
				"grep -vE '^$'";

		exitCode = systemCmd(
		command +
		Platform.case(
			\osx, { filterText },
			\linux, { filterText },
			\windows, { " 2>NUL" }
		)
	);

		success = (exitCode == 0);
		if (success && clean) { File.delete(path) };

		^success;
	}

I don’t use LilyPond regularly, but whenever I test it, it often breaks due to architecture mismatches (PowerBook → MacBook, Intel → ARM). This has been the main reason I haven’t been able to adopt LilyPond more fully.