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 .

4 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!!

2 Likes

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.

1 Like

@moscardo great work! Are you thinking about how to create glissandos or simulate strange things like multiphonics ? Have you also tried using a sound bank based on extended techniques ? Do you know if there is any literature explaining the details about how commercial samplers works?

1 Like

Thank you @fmiramar for your interest,

great work! Are you thinking about how to create glissandos or simulate strange things like multiphonics ?

I was thinking about that even if I’m not planning to continue working on that till I’ll need it for a next new project. We will see…
For the moment, an empirical approach should be to sweep the rate parameter using a Pseg or something like that.
For sure I’ll keep this thread updated with all new improvements I’m gonna make :slight_smile:
Your help, suggestions and support is also super appreciated!

I think that multiphonics is quite a complicated think to implement. As you have seen, the approach I’m using is strictly bind to the use of samples and while glissandos is something which can be solved from a “compositional” point of view (i think it can be addressed with some Pbind manipulation), multiphonics instead sounds to me as something should be faced from a synthesis perspective.

Have you also tried using a sound bank based on extended techniques ?

I confess: no. What kind of banks are those that use the extended technique? How this technique works? Could you give me some examples?
It would be great to test the system with different samples also because I’m looking for different and sometimes better sounds to use in my compositions. Do you know if are there other free/open/libre sound banks I could use?

Do you know if there is any literature explaining the details about how commercial samplers works?

I think some documentation might exists, don’t know a specific source but I think you can find something doing some research. One of the places I would look for would also be a software patent database.

1 Like

Just tried it very nice.Thanks for posting that. :slight_smile:

I learned quite a bit from that iterative way to load buffers.

This bank and example is quite useful. I managed to load the xilophone in stereo and made some basic phasing rhythms a-la Steve Reich.

1 Like

The most famous that I know is this one, but there are smaller ones as well (IRCAM, Spitfire):
https://www.contimbre.com/en

I really would like to know what happens under the hood in this thing, but I could not find a proper literature…

1 Like