Writing Scores with SuperCollider

Hello Everybody!

I wanted to ask the community, if there has someone a practice in writing scores in / with supercollider. With score i mean common music notation scores in form of lilypond files oder musicXML.

Great question, the approach really depends on the artistic context you’re working in.

There’s actually a small but lively corner of composers using sclang for contemporary instrumental work, often seasoning their pieces with a bit of generative magic. This topic has floated around the community before, for instance here:

If you’re not married to real-time notation on the fly, the pragmatic route is just routing your event streams into a DAW such as Reaper, export the clips as MIDI files, and hand it off to a notation tool like Sibelius to clean things up into something performers can read without raising an eyebrow. If, however, you do want to watch your midi events to crystallize into staff notation on the fly, then a solid option is pairing Max/MSP with the bach library, which is built for score-based output.

If you’re curious, here’s one of my more recent generative instrumental pieces driven largely by sclang’s pattern library doing the heavy lifting behind the scenes.

1 Like

It is a bit unfortunate that the second edition of The SuperCollider Book, published fairly recently, discusses the use of FOSC, even though FOSC now seems no longer to work (see also: Possible missing dependency: `gs` required for `FoscNote(...).show` Ā· Issue #8 Ā· n-armstrong/fosc Ā· GitHub). That feels particularly unfortunate for new readers trying to follow the examples.

At the moment, it seems that neither FOSC nor FOMUS is really usable. From what I can tell, SCStaves and Notator may be the two remaining possibilities, and if MusicXML is needed, Notator may be the only practical option.

If anyone has managed to get FOSC or FOMUS working, or has suggestions for a current notation workflow in SuperCollider, I would be very interested to hear them.

More generally, I often find that discussions of notation in SuperCollider end up pointing towards LilyPond syntax. That is understandable, but I am increasingly interested in the possibility of a more SuperCollider‑native approach: using things like SequenceableCollection, IdentityDictionary, Set, or Patterns as the musical representation, and generating MusicXML directly from that.

Personally, I would prefer a SuperCollider‑native approach that does not rely on LilyPond, Guido, or ABC syntax. If I were to revisit my own Notator, that is probably the direction I would take and, if successful, I would consider releasing it properly as a Quark.

The difficulty is that there is no single obvious model for such an approach, so it would really need discussion with other users. Still, I would be very interested to know whether others think this would be a worthwhile direction.

It is a lot of work to do this. And it’s more work in SuperCollider, as it doesn’t have libraries to do a lot of the grunt stuff for you.

If you can do score generation in something else then I’d recommend it (i.e. Python, Common Lisp and Haskell).

Actually, i tried FOSC now and its working with my lilypond (-> 2.20.0), i don’t get an error and the example is created as pdf.
Maybe try brew install ghostscript on MacOS.

Thank you for your report!

brew install ghostscript on macOS does not resolve the problem in my case.

I retested using lilypond-2.25.81 on macOS 15.7.5 with an M1 Max (ARM, not Intel).
The score is rendered and displayed, but FOSC still prints the following error:

ERROR: Changing working directory to: `/Users/prko/Library/Application Support/SuperCollider/fosc-output'ERROR: 
Processing `/Users/prko/Library/Application Support/SuperCollider/fosc-output/0016.ly'
Parsing...
Interpreting music...
Preprocessing graphical objects...
Finding the ideal number of pages...
Fitting music on 1 page...
Drawing systems...
Converting to `0016.pdf'...
ERROR: Success: compilation successfully completed
-> a FoscNote

So LilyPond reports success, but FOSC still emits an error message.

PDF is not generated and FOSC does not complete the process.

On Windows 11, I could not get it working at all (tested with lilypond-2.25.81 and lilypond-2.24.4).

I think the documentation should clarify:

  • which LilyPond versions are compatible, and
  • how to install/configure LilyPond per OS (macOS / Windows / Linux).

Real errors generally stop the process, but here, no failure is reported, and the process continues to its end, with an output file.

So the conclusion is not that the library is unusable, but rather that there’s some incorrect messaging in it (status messages being tagged as errors, e.g. ā€œERROR: Success:ā€ :wink: ) – which can be found and fixed.

hjh

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.

I’m surprised Opusmodus isn’t talked about more, it’s an amazing tool ( not free but well worth the cost IMO ). It’s a Lisp coding environment for algorithmic composition that has its own language for working with notes/harmony and common music notation. You code something up and it creates the notation/score which you can either export as music xml or import directly into a notation app like Dorico/Musescore/Sib. I’ve only used it with orchestral sample libraries, but it integrates with SC using cl-collider.

For me, I Iove SC for synthesis/sound design but IMO it’s woefully inadequate when trying to create/represent music of any complexity with conventional notes/harmony/notation.

If you’re using a Mac then I recommend installing Homebrew and getting Lilypond from that. Homebrew simplifies a lot of things on the mac.

I don’t think it’s very well known. I stumbled across after I’d already been using Common Lisp for a while, and by that point I’d already invested time in other approaches. But it does look good.

There’s also Common Music for Common Lisp, which someone resurrected - and which itself uses an old (but it still works fine) Common Lisp port of Fomus, which can generate Lilypond and MusicXML pretty well. I have my own library for Common Lisp which is pulling out the good bits of Common Music (cool concepts, seriously shitty code), but isn’t really read for prime time atm.

And for completeness there is this:

I’ve only used it with orchestral sample libraries, but it integrates with SC using cl-collider.

If you’re comfortable with Common Lisp, then CL-Collider is highly recommended. It can do everything that SCLang can do, and is a very nice environment for building synthdefs. It’s a developer did a really good job. And in Emacs with Slime/Sly, it’s a wonderful environment for livecoding.

1 Like

Thanks, but installing LilyPond via Homebrew on a silicon Mac currently raises a problem described here:

The best way to install LilyPond per OS and per architecture is described here:

I have opened a PR that:

  • adds Mac (ARM64) support to Fosc;
  • adds Windows support to Fosc; and
  • suppresses unnecessary messages that are presented as errors.

With these changes, the current compatibility status of Fosc with SC 3.15.0 is as follows:

  • Windows 11

    • Works on ARM machines with LilyPond 2.24.4.
    • Works on Intel machines with LilyPond 2.24.4 and 2.25.81.
  • macOS 15.7.5

    • Works on ARM machines with LilyPond 2.24.4, 2.25.81 (x86_64), and 2.25.81 (ARM64).
    • Intel machines: untested, as I do not have access to an Intel Mac capable of running a macOS version later than 10.14.6.
  • Ubuntu 22.04.5 LTS

    • Works on Intel machines with LilyPond 2.24.4 and 2.25.81.
    • Does not currently work on ARM machines, as there is no working version of LilyPond available for that platform.

For reference, Fosc and LilyPond work with SuperCollider 3.13.0 on macOS 10.14.6 without this PR. This has been tested with LilyPond 2.24.4 and 2.25.81 (x86_64). N.B. The LilyPond path must not contain whitespace.

Pretty sure that’s an FOSC problem, as Lillypond works fine for me.
FOSC probably just doesn’t have the right paths for OSX (the locations changed for ARM), of there’s an issue with your environment variables.

Thanks for the suggestion.

I tested the following paths:

Fosc.lilypondPath = "/opt/homebrew/Cellar/lilypond/2.24.4/bin/lilypond"
Fosc.lilypondPath = "/opt/homebrew/bin/lilypond"

Since I tested both the direct Cellar path and the Homebrew symlink under /opt/homebrew/bin, I do not think this is simply a matter of Fosc using the wrong path.

Evaluating the following code with both paths produces the same error when LilyPond is installed via Homebrew, but not when using the official LilyPond download:

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

Error message:

warning: g_spawn_sync failed (-1): gs: Failed to execute child process ā€œgsā€ (No such file or directory)
warning: `(gs -q -dNODISPLAY -dNOSAFER -dNOPAUSE -dBATCH -dAutoRotatePages=/None -dPrinted=false /var/folders/2_/hdf9s2tx6kg7_yqv0bwqtlcm0000gn/T//lilypond-tmp-7128040)' failed (-1)
/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

I also compared the two lilypond binaries:

  • Homebrew-installed LilyPond

    • version: 2.24.4
    • path: /opt/homebrew/Cellar/lilypond/2.24.4/bin
    • size: 6,326,800 bytes
    • created: Friday, February 6, 2026 at 21:33
    • modified: Friday, February 6, 2026 at 21:33
  • Official LilyPond download

    • version: 2.24.4
    • path: /Users/prko/Dropbox/prko/_macApps@Dropbox/LilyPond/lilypond-2.24.4_x86_64/lilypond-2.24.4/bin
    • size: 13,164,632 bytes
    • created: Saturday, July 20, 2024 at 20:47
    • modified: Saturday, July 20, 2024 at 20:47

So although the version number is the same, the binaries do not appear to be identical.

Based on this, my current hypothesis is that the Homebrew LilyPond build on ARM64 macOS does not behave in the same way as the official LilyPond binary, and that this is at least part of what I am seeing here. For that reason, I do not think it is clear that this is an Fosc issue alone.

Could you share your setup details, including:

  • whether your machine is Intel or Apple Silicon;
  • your macOS version;
  • your LilyPond version and how it was installed; and
  • your SuperCollider version?

Without that background, it is difficult to compare results or determine whether the issue lies in Fosc, the LilyPond build, or the local environment.

You’re comparing an ARM binary to an intel binary (the official one - x86_64 is Intel).

I’m using Lilypond on a Mac (Arm) which was installed using homebrew. It is the latest version. It compiles Lilypond files with no problem.

If there is a problem with your Lilypond install, then the way to test that is to try and compile a simple Lilypond file. If it works, then the problem is not your Lilypond.

I’m guessing the problem is either that it can’t find Lilypond, or more likely it can’t find some of the library files that Lilypond needs. That’s a FOSC problem. On ARM these files are in a different location to intel, and that’s probably the issue.

You were right.

I found the problem and fixed it:

Thank you very much, and I’m sorry it took me so long to understand.

1 Like