Looking at the Processor syntax here:
I don’t see how a Processor can be initialized by a parameter (like for instance processor.frequency). I have an init(samplingFreq) function that I would need to call at Processor creation time (like in the Graph definition I guess).

How can that be done?


I’d have thought you’d normally just call your initialise function at the start of your run() function, before the main loop starts?


OK that makes sense.


BTW we’ll publish some example code either today or early next week, which should help to illustrate some of this…


Thanks… SOUL source code backend in Faust compiler in progress here :wink:.

When a runtime to test SOUL code will be available ?


First thing you’ll be able to play with will be a browser playground for it - we’re working on it, hopefully out within a month or two!


OK, then it should even be doable to call SOUL runtime in our Faust Web editor right ? ( Then users could easily test a Faust => SOUL converter.


Yep, that should be possible!


In doc : External data arrays:

float<2> getNextFrame (external float<2>[] sample)
    return (sampleIndex);
    if (++sampleIndex >= sample.size)
        sampleIndex = 0;

Is that code correct ?


Either incorrect or they have some peculiar way to deal with returns from functions… :thinking: In any case, it’s really just a C, C++ etc convention return immediately breaks the control flow. Maybe they decided to allow returning a result early and still execute to the end of the function.

edit : Looks like the code example is broken. The documentation states :

 The  `return`  statement works just the way you'd expect it to.


Oversampling/undersampling built right into the language! Very nice. Experts are likely going to want to implement their own resampling algorithms, though. Are there plans to allow that?


Yeah, sorry - that example code is totally garbled! Probably should have been something like:

float<2> getNextFrame (external float<2>[] sample)
    let result = (sampleIndex);
    if (++sampleIndex >= sample.size)
        sampleIndex = 0;

    return result;

There’s no fancy control flow going on, just poor proof-reading!


The point about it being built into the language isn’t just because that makes it easy to use - it also means that it can take advantage of DSP hardware which can accelerate those operations, which wouldn’t be possible if you wrote code yourself to do the job.

You can of course write a processor that takes a latched lower sample-rate input and emits a higher rate output by performing any kind of algorithm you want, which you can then stick into your signal chain to do the interpolation.

But I doubt if we’d ever add a special feature that would let you replace the built-in resamplers with custom code, simply because the built-in stuff is baked deeply into the different code generators and not something that could easily be changed.


In our Faust => SOUL model, I guess event handlers will have to be used to update the control values (coming from the UI, OSC, MIDI…) and that basically change the DSP processor internal state.

How is the code running in DSP block run { loop {...} } going to be interleaved with code running in event handlers ? (Event streams section). Are they running in separated threads? Are they any race protection measures to take ?


I see you use a preincrement operator, have you considered to drop these? They come with quite many undefined behaviour issues, so when designing a new language, I am not sure these should be included.


Yeah, that’s how we’ve been doing MIDI and parameter changes so far in our demo code - it actually works well and compiles down to the sort of code you’d want. But we plan to add new types of sparse-streams in the future which will be auto-interpolated and designed for parameter-change type data.

We’ve made sure the language is race-free, as processors can only communicate with each other and the outside world via their streams. Exactly how they get run by the hardware will obviously depend on the backend that’s running it - in our tests so far we’ve been running the whole thing as a single thread (co-operatively multitasked), but on multi-core processors we’ll be able to add some smarts to split the load into an optimal number of threads without the code itself needing to be changed.

Personally I like pre/post increment - it’s incredibly commonly used, and if we dropped it, I think it’d annoy a lot of people, including myself! We did obviously think carefully about it because it’s such a notorious pain in C++, but when we looked at how to implement it in SOUL, there’s actually no need for it to be undefined behaviour - the language spec will define the order of evaluation, and IIRC we throw an error if you do something dumb like referencing and pre-incrementing the same variable within a statement.


We did think that rejecting multiple pre-increments to the same variable within an expression was probably the right thing to do, not because it would be undefined, but it would certainly be confusing, and probably dumb code.

I believe Jules is just adding this to be backlog so that’s my monday morning sorted.


What’s an example of that?

int bar = --foo + foo * foo--; 


Yes, that’s the sort of thing that is rarely useful, so there’s a case for disallowing it. It’s the sort of thing that in a code review could be picked up as being hard to understand, so probably not the best if you are aiming to produce maintainable code.

The performance aspects that normally drive the whole pre/post increment debate isn’t relevant when we only have built-in types, so I think we’re left discussing what will produce the most readable and maintainable code.


Wishing every success to this project!

I’m a bit confused by this - let isn’t a thing in C# (is it?), and in javascript, the difference between let and var is one of scoping rather than constantness IIUC, so having let mean constant seems confusing for javascripters. Why not ditch let and just allow statements of the type const [name] = [initial value];?

A couple of other questions -
Is there any concept of exceptions?

What will the typical IDE / debugging experience be like?