Using sclang in a makefile

I want to generate scsyndef files using a makefile. I use sclang and reference a file as follows, but sclang doesn’t exit when there’s an error. Is there some way around this?

// Define the first SynthDef and add it to the library
SynthDef("test1", { |out=0, freq=440, amp=0.5, pan=0|
    var sig = SinOsc.ar(freq) * amp;
    Out.ar(out, Pan2.ar(sig, pan));
    asdf // Works fine without this line, but with it never exits.
}).writeDefFile("bin");

0.exit();

Ok, after trying this, it seems to do what you want.

fork {
    // Define the first SynthDef and add it to the library
    SynthDef("test1", { |out=0, freq=440, amp=0.5, pan=0|
        var sig = SinOsc.ar(freq) * amp;
        Out.ar(out, Pan2.ar(sig, pan));

    }).writeDefFile("bin");

    3.wait; 
    thisProcess.shutdown;0.exit;

}

SC’s error handlers are try and protect. I’d use protect here because the second function is always called.

protect {
    ... stuff that could error...
} {
    ... stuff that needs to happen no matter what...
}

So the first block would be your SynthDef-writer, and the second block could be like this:

protect {
    ... stuff that could error...
} { |error|
    exit(
        if(error.isNil) { 0 } { -1 }
    )
}

hjh

I think I’m not being clear. I mean if there’s a syntax error. I couldn’t get either example to work if there’s a syntax error, in my example just typing in junk.

@smoge my original code worked without adding the syntax error. Why would I need the fork?

@jamshark70 the protect is for runtime errors I think?

Unless I’m misunderstanding, I didn’t see a solution when there is a syntax error?

-Paul

I think there might be a risk of you exiting the process before evaluating and writing all your SynthDefs. The server is not running, but still I think there is a similar problem there, or I’m mistaken? I’d just give some time for the script before killing the language process.

EDIT: I’m assuming your script has only this job.

I read again your original post. Is there a reason for doing this way? I would just load the SynthDef file from your other file. Much simpler…

edit: Ah… are you generating code from code (meta-programming, as lisp macros?), and trying to validate the result? I see now.

Correct. If there’s a syntax error in the file, none of it will run and you’re hosed at that point.

I guess you could guard against syntax errors by starting sclang with a wrapper script that is carefully proofread, and this script would try to compile a script from another file. The outer script could detect syntax errors in the inner script if compileFile returned nil.

hjh

@smoge the background for this idea is explained in How can you tell when sclang has finished loading file - Questions - scsynth. I think that since I can use d_load and wait for that command to complete, I’ll have a way for the client to start fresh each time.

Additionally, I like the idea of building the synths as “modules” which I believe could load more quickly since they are already compiled. The rest of my project is built with makefiles, so just trying to duplicate that ability with sclang.

I’m going to try and understand compileFile next.

1 Like

It reads the file and, if it compiles successfully, gives you a Function. Then you can .value the function. If compilation failed, it returns nil (IIRC). This way you can tell the difference between syntax error and execution returned nil.

hjh

OK, I think this solution works. The only question is if I could send a parameter rather than hardcode he “test.csd” file. If not, I suppose I could build a .scd file each time.

compile.scd:
r = this.compileFile("test.scd");

if(r.value == nil, { 1.exit; }, { r.value.writeDefFile("bin"); 0.exit; });


test.scd:
    SynthDef("test1", { |out=0, freq=440, amp=0.5, pan=0|
        var sig = SinOsc.ar(freq) * amp;
        Out.ar(out, Pan2.ar(sig, pan));
        //adfd;
    });

1 Like

To my surprise, you can just declare args at the top of the scd file, even without braces. I really didn’t think that would work, but I tried it and it does, e.g.

test.scd:

arg xyz;

... Do stuff...

|| syntax isn’t allowed in this case, but the arg keyword does define parameters that you can then pass in the .value call.

hjh

Maybe I’m missing something @jamshark70 , but I want to pass the string “test.scd” to the compile.scd script when I call it with sclang. I’m exploring storing the name in a file, with little luck. Though it is cool you could pass args to writeDefFile.

Oh that… I misunderstood.

See Main’s argv method, which gives access to the commandline arguments.

hjh

That’s the ticket. Thank you!

thisProcess.argv.postln;

if(thisProcess.argv.size == 0, { "No file provided.".postln; 1.exit; },
{
    r = this.compileFile(thisProcess.argv[0]);

    if(r.value == nil, { 1.exit; }, { r.value.writeDefFile("bin"); 0.exit; });
});

One more update. I appears that sometimes the build can fail in such a way that it never exits. In my particular case I wanted to the output file to be named differently, as it always chooses the name of the last SynthDef in the .scd file. I initially tried to create a synthdef at the end:

SynthDef('basics');

But now I know it needs to be

SynthDef('basics', {});

This particular error cauesd my previous script to get stuck. So now I’m using the try code block mentioned before. Maybe not the last revision, but the best so far.

"Building ".post;
thisProcess.argv.postln;

if(thisProcess.argv.size == 0, { "No file provided.".postln; 1.exit; },
{
    try {
        r = this.compileFile(thisProcess.argv[0]);

        if(r.value == nil, 
            { 1.exit; }, 
            { r.value.writeDefFile("bin"); 0.exit; });

    } {
        1.exit;
    }
});

Also, here’s the makefile I’m using. compile.scd is in a different directory so it doesn’t build itself each time, which causes and endless loop :slight_smile:

# Makefile for compiling SuperCollider synth definition files

# Set the source and target directories
SRC_DIR := .
BUILD_DIR := bin
BUILD_TOOL := buildtools/compile.scd

# # Get all .scd files in the source directory
SCD_FILES := $(wildcard $(SRC_DIR)/*.scd)
SCSYNDEF_FILES := $(wildcard $(BUILD_DIR)/*.scsyndef)
 
# all: $(SCSYNDEF_FILES)

all: $(SCD_FILES:$(SRC_DIR)/%.scd=$(BUILD_DIR)/%.scsyndef)

$(BUILD_DIR)/%.scsyndef: $(SRC_DIR)/%.scd
	@echo "Building $@ from $<"
	@mkdir -p $(BUILD_DIR)
	@sclang $(BUILD_TOOL) $<

# Clean target: remove all generated .scsyndef files
clean:
	@echo "Cleaning up"
	@rm $(BUILD_DIR)/*.scsyndef

# PHONY targets: targets that are not real files
.PHONY: all clean