Orchestral Texture v1

I like to share one of my first experiment in using samples from SSO (https://github.com/peastman/sso).

Here’s the code I’ve used: http://sccode.org/1-5bz .

Here a recording of the piece: https://soundcloud.com/nicolaariutti/orchestral-texture-1 .

2 Likes

I find it very interesting that you found an approach that doesn’t involve an external sampler process to load the sonatina soundfont.

1 Like

Thank you @shiihs.
Happy that it is interesting for you.
Let me know if you have advices to improve the code or in case you are going to use it in your own project :slight_smile:

Well… I’d remove some of the code duplication, e.g. something like
(note: a bug/limitation appears to exist in the syntax highlighter on the forum…)

// Orchestral Sound texture v1
(
s.options.numBuffers = 1024 * 32;
//increase the memory available to the server
s.options.memSize = 8192 * 128;
s.waitForBoot({
    //LOAD SAMPLES //////////////////////////////////////////////////////////////////
    // create data structure to load samples
    var patterns = ();
    var loadfunc;

    if (~samples.notNil) {
        ~samples.do({
            |key|
            if (~samples[key].notNil) {
                ~samples[key][\buffers].do({
                    | buffer |
                    if (buffer.notNil) {
                        buffer.free;
                    };
                });
            };
        });
    };
    ~samples = ();

    // add function to load samples
    loadfunc = {
        // loaded samples will be stored under samples[key]
        | key, relpath, namefilter, destination, basepath="~/development/supercollider/sso/sso/Sonatina Symphonic Orchestra/Samples" |
        var path = PathName(basepath +/+ relpath);
        var tmp_buffers = path.entries.removeAllSuchThat({|item| item.fileName.contains(namefilter); });
        var tmp_array = tmp_buffers.collect({
            | item, index |
            var d = ();
            var string = item.fileNameWithoutExtension;
            var output = string.findRegexp("[abcdefgABCDEFG]#?[0123456789]");
            var noteNameAll = output[0][1];
            var octNumber = noteNameAll.rotate(1)[0].asString.asInteger;
            var noteName = noteNameAll[0].asString;
            var isSharp = noteNameAll.contains("#"); // boolean
            var midiNumber = (octNumber +1) * 12;
            switch( noteName,
                "c", { midiNumber = midiNumber+0; },
                "d", { midiNumber = midiNumber+2; },
                "e", { midiNumber = midiNumber+4; },
                "f", { midiNumber = midiNumber+5; },
                "g", { midiNumber = midiNumber+7; },
                "a", { midiNumber = midiNumber+9; },
                "b", { midiNumber = midiNumber+11; },
            );
            if (isSharp) {midiNumber = midiNumber + 1;};
            [noteNameAll, noteName, isSharp, octNumber, midiNumber];//.debug("[fullname, note, sharp, octave, midinum]");
            d[\midi] = midiNumber.asInteger;
            d[\note] = noteNameAll;
            d[\buffer] = Buffer.readChannel(s, item.fullPath.debug("reading file"), channels:1);
            d;
        });
        key.debug("loading");
        tmp_array.sortBy(\midi);
        destination[key] = ();
        destination[key][\lowest_note] = tmp_array[0][\midi];
        destination[key][\buffers] = tmp_array.collect({|item| item[\buffer]; });
        destination[key][\midinotes] = tmp_array.collect({|item| item[\midi]; });
        destination;
    };

    // Load all samples
    ~samples = loadfunc.(\violins_1st_sus, "1st Violins", "1st-violins-sus-", ~samples);
    ~samples = loadfunc.(\violins_2nd_sus, "2nd Violins", "2nd-violins-sus-", ~samples);
    ~samples = loadfunc.(\celli_sus, "Celli", "celli-sus-", ~samples);
    ~samples = loadfunc.(\basses_sus, "Basses", "basses-sus-", ~samples);
    ~samples = loadfunc.(\oboes_sus, "Oboes", "oboes-sus-", ~samples);
    ~samples = loadfunc.(\bassoons_sus, "Bassoons", "bassoons-sus-", ~samples);
    ~samples = loadfunc.(\clarinets_sus, "Clarinets", "clarinets-sus-", ~samples);

    s.sync;

    SynthDef(\play_orchestra, {
        |out=0,amp=0.5, gate=1, buf, rate=1.0,pan=0.0, atk=0.01, dcy=0.2, sus=0.7, rel=0.1|
        var sig, env;
        env = EnvGen.ar(Env.adsr(atk, dcy, sus, rel), gate, doneAction:2);
        sig = Mix.ar(PlayBuf.ar(2, buf, BufRateScale.ir(buf)*rate, 1, doneAction:0));
        sig = sig * amp * env;
        Out.ar(out, Pan2.ar(sig, pan));
    }).add;

    // a synth to play buffers in a granular way
    // NOTE: you will need buffers to be loaded as MONO !!!
    SynthDef(\grain_buffer, {
        |out=0, gate=1, amp=0.9, buf, atk=5, rel=5, rate=1, pan=0.0|
        var sig, env;

        var density = LFNoise0.kr(25).range(1, 5);
        var trigger = Impulse.kr( density );
        var pos = 0.5 + TRand.kr(trigger, -0.35, 0.35);
        var length = 1 + TRand.kr(trigger, 0.25, 0.35);

        env = EnvGen.kr(Env.asr(atk, 1, rel, -4), gate, doneAction:2);

        sig = GrainBuf.ar(2,
            trigger,
            length,
            buf,
            rate,
            pos,
            2,
            pan: pan);

        sig = sig * amp * env;
        Out.ar(out, sig);
    }).add;

    SynthDef(\verb, {
        |out=0, mix=0.6, room=0.8, damp=0.6|
        var in;
        in = In.ar(out, 2);
        in = FreeVerb.ar(in, mix, room, damp);
        Out.ar(out, Pan2.ar(in));
    }).add;

    s.sync;

    patterns[\violins_1st_sus] = Pbind(
        \instrument, \grain_buffer,
        \scale, Scale.major,
        \root, Pfunc({~root}),
        \octave, Pwrand([5,6,7],[1,0.5, 0.125].normalizeSum, inf),
        \degree, Pxrand([1, 4, 5], inf)-1,
        \index, Pfunc { |e| ((e.use{ ~midinote.()} - ~samples[\violins_1st_sus][\lowest_note] - 1)/3).floor },
        \buf, Pindex(~samples[\violins_1st_sus][\buffers], Pkey(\index)),
        \rate, (Pfunc{ |e| e.use {~midinote.()}} - Pindex(~samples[\violins_1st_sus][\midinotes], Pkey(\index))).midiratio,
        \amp, Pwhite(0.05, 0.1, inf),
        \dur, Pwhite(4,8, inf),
        \type, Pwrand([\note, \rest], [1,0.25].normalizeSum, inf),
        \atk, Pkey(\dur) * 0.6,
        \rel, Pkey(\atk),
        \out, 0,
        \pan, Pwhite(-0.8, 0.2,inf)
    );

    patterns[\violins_2nd_sus] = Pbind(
        \instrument, \grain_buffer,
        \scale, Scale.major,
        \root, Pfunc({~root}),
        \octave, Pwhite(5,6,inf).round(1),
        \degree, Pxrand([1, 4, 5], inf)-1,
        \index, Pfunc { |e| ((e.use{ ~midinote.()} - ~samples[\violins_2nd_sus][\lowest_note]-1)/3).floor },
        \buf, Pindex(~samples[\violins_2nd_sus][\buffers], Pkey(\index)),
        \rate, (Pfunc{ |e| e.use {~midinote.()}} - Pindex(~samples[\violins_1st_sus][\midinotes], Pkey(\index))).midiratio,
        \amp, Pwhite(0.05, 0.1, inf),
        \dur, Pwhite(4,8, inf),
        \type, Pwrand([\note, \rest], [1,0.25].normalizeSum, inf),
        \atk, Pkey(\dur) * 0.6,
        \rel, Pkey(\atk),
        \out, 0,
        \pan, Pwhite(-0.2, 0.8,inf)
    );

    patterns[\celli_sus] = Pbind(
        \instrument, \grain_buffer,
        \scale, Scale.major,
        \root, Pfunc({~root}),
        \octave, Pwrand([3, 4], [0.25, 1].normalizeSum, inf),
        \degree, Pseq([1], inf)-1,
        \index, Pfunc { |e| ((e.use{ ~midinote.()} - ~samples[\celli_sus][\lowest_note]-1)/3).floor },
        \buf, Pindex(~samples[\celli_sus][\buffers], Pkey(\index)),
        \rate, (Pfunc{ |e| e.use {~midinote.()}} - Pindex(~samples[\celli_sus][\midinotes], Pkey(\index))).midiratio,
        \amp, Pwhite(0.1, 0.2, inf),
        \dur, Pwhite(2,7, inf),
        \type, Pwrand([\note, \rest], [1,0.125].normalizeSum, inf),
        \atk, Pkey(\dur) * 0.6,
        \rel, Pkey(\atk),
        \out, 0,
        \pan, Pwhite(-0.3, 0.3,inf)
    );

    patterns[\basses_sus] = Pbind(
        \instrument, \grain_buffer,
        \scale, Scale.major,
        \root, Pfunc({~root}),
        \octave, 3,
        \degree, Pseq([1], inf)-1,
        \index, Pfunc { |e| ((e.use{ ~midinote.()} - ~samples[\basses_sus][\lowest_note]-1)/3).floor },
        \buf, Pindex(~samples[\basses_sus][\buffers], Pkey(\index)),
        \rate, (Pfunc{ |e| e.use {~midinote.()}} - Pindex(~samples[\basses_sus][\midinotes], Pkey(\index))).midiratio,
        \amp, Pwhite(0.1, 0.2, inf),
        \dur, Pwhite(4,8, inf),
        \type, Pwrand([\note, \rest], [1,0.125].normalizeSum, inf),
        \atk, Pkey(\dur) * 0.6,
        \rel, Pkey(\atk),
        \out, 0,
        \pan, 0.0
    );

    patterns[\oboes_sus] = Pbind(
        \instrument, \grain_buffer,
        \scale, Scale.major,
        \root, Pfunc({~root}),
        \octave, Pwhite(5,6,inf).round(1),
        \degree, Pwrand([1,5],[0.3, 0.6], inf)-1,
        \index, Pfunc { |e| ((e.use{ ~midinote.()} - ~samples[\oboes_sus][\lowest_note]-1)/3).floor },
        \buf, Pindex(~samples[\oboes_sus][\buffers], Pkey(\index)),
        \rate, (Pfunc{ |e| e.use {~midinote.()}} - Pindex(~samples[\oboes_sus][\midinotes], Pkey(\index))).midiratio,
        \amp, Pwhite(0.1, 0.2, inf),
        \dur, Pwhite(4,8, inf),
        \type, Pwrand([\note, \rest], [1,0.25].normalizeSum, inf),
        \atk, Pkey(\dur) * 0.6,
        \rel, Pkey(\atk),
        \out, 0,
        \pan, Pwhite(-0.2, 0.8,inf)
    );

    patterns[\clarinets_sus] = Pbind(
        \instrument, \grain_buffer,
        \scale, Scale.major,
        \root, Pfunc({~root}),
        \octave, Pwhite(5,6,inf).round(1),
        \degree, Pwrand([1,5],[0.3, 0.6], inf)-1,
        \index, Pfunc { |e| ((e.use{ ~midinote.()} - ~samples[\clarinets_sus][\lowest_note]-1)/3).floor },
        \buf, Pindex(~samples[\clarinets_sus][\buffers], Pkey(\index)),
        \rate, (Pfunc{ |e| e.use {~midinote.()}} - Pindex(~samples[\clarinets_sus][\midinotes], Pkey(\index))).midiratio,
        \amp, Pwhite(0.2, 0.3, inf),
        \dur, Pwhite(4,8, inf),
        \type, Pwrand([\note, \rest], [1,0.25].normalizeSum, inf),
        \atk, Pkey(\dur) * 0.6,
        \rel, Pkey(\atk),
        \out, 0,
        \pan, Pwhite(-0.8, 0.2,inf)
    );

    patterns[\bassoons_sus] = Pbind(
        \instrument, \grain_buffer,
        \scale, Scale.major,
        \root, Pfunc({~root}),
        \octave, Pwhite(4,5,inf).round(1),
        \degree, Pwrand([1,5],[0.3, 0.6], inf)-1,
        \index, Pfunc { |e| ((e.use{ ~midinote.()} - ~samples[\bassoons_sus][\lowest_note]-1)/3).floor },
        \buf, Pindex(~samples[\bassoons_sus][\buffers], Pkey(\index)),
        \rate, (Pfunc{ |e| e.use {~midinote.()}} - Pindex(~samples[\bassoons_sus][\midinotes], Pkey(\index))).midiratio,
        \amp, Pwhite(0.1, 0.2, inf),
        \dur, Pwhite(4,8, inf),
        \type, Pwrand([\note, \rest], [1,0.25].normalizeSum, inf),
        \atk, Pkey(\dur) * 0.6,
        \rel, Pkey(\atk),
        \out, 0,
        \pan, Pwhite(-0.4, 0.4,inf)
    );

    ~root = 0;

    ~ensemble = Pfx(Ppar([
        patterns[\violins_1st_sus],
        patterns[\violins_2nd_sus],
        patterns[\oboes_sus],
        patterns[\clarinets_sus],
        patterns[\bassoons_sus],
        patterns[\celli_sus],
        patterns[\basses_sus]
    ],inf), \verb).asEventStreamPlayer;

    ~ensemble.play;
});
);

~root = 0; // also try to use 5, 7 and 9
~ensemble.play;

(
~ensemble.stop;
~samples.do({
    |key|
    ~samples[key][\buffers].do({
        | buffer |
        buffer.free;
    });
});
)

Since the patterns you use all look very similar, it’s possible to simplify those as well (variations on a base pattern) but I didn’t do it here, because in general I expect different patterns to be more independent.

Just made a Lua2SC release for reading sounfonts because of this thread!!

1 Like

What a wonderful idea! How much of the .sfz spec does it implement?

This is the list of processed opcodes by the moment
{
[ampeg_attack]=0
[ampeg_decay]=0
[ampeg_delay]=0
[ampeg_hold]=0
[ampeg_release]=0.1
[ampeg_start]=0
[ampeg_sustain]=100
[ampeg_vel2attack]=0
[ampeg_vel2decay]=0
[ampeg_vel2delay]=0
[ampeg_vel2hold]=0
[ampeg_vel2release]=0
[ampeg_vel2sustain]=0
[default_path]=0
[group]=0
[hikey]=0
[hirand]=0
[hivel]=0
[key]=0
[lokey]=0
[loop_end]=0
[loop_start]=0
[lorand]=0
[lovel]=0
[offset]=0
[pan]=0
[pitch_keycenter]=0
[pitch_random]=0
[sample]=0
[seq_length]=0
[seq_position]=0
[transpose]=0
[tune]=0
[volume]=0
[xfin_hikey]=0
[xfin_hivel]=0
[xfin_lokey]=0
[xfin_lovel]=0
[xfout_hikey]=0
[xfout_hivel]=0
[xfout_lokey]=0
[xfout_lovel]=0}

I would appreciate anyone giving it a try!!

Unfortunately I don’t speak LUA at all at the moment so I’m not sure how high the learning curve would be, but I’d like to take a stab at creating an sclang based .sfz reader at some point (maybe a good summer holiday project, if outside temperatures don’t kill my energy levels). I think it’d be a very welcome addition to supercollider, especially for people who (like me) sometimes just want to toy with algorithmic composition without having to delve into sound design first.

Dont be afraid about Lua. Its learning curve is really not step.
In case you need information it is here:

https://www.lua.org/manual/5.1/
https://www.lua.org/pil/contents.html

And also: You can execute the examples in Lua2SC without knowing Lua at all!!

1 Like

also a note for me:
take a look at this SC tutorial where it shows an alternative way of making multi-sample instruments!

this might be a good starting point if you don’t want to work from scratch:

i abandoned it because i almost never use samples in my music anymore, but of course you’re free to fork and continue development on it.