Track: You Can't Get My

I never share my music on here so whatever, here’s a little track I made in SC.

Hope Bandcamp embed works on this thing.

16 Likes

Thanks for a chill listening break. really like the drums/digita-percussion

Love this! The glitchy drums are outstanding.

Yeah, Drums are amazing, and dreamy athmosphere or composition remind me some naive idm tunes of late 90s. Thanks for sharing!

1 Like

How do you code drums like that in SC?

I used something I call a “micro-tracker,” where each character corresponds to a different drum hit. For example, I could type k.k.s.hghgkks.hg where k = kick, s = snare, h = hat, g = glitch sound. Sequencing was done manually.

All drum hits are synthesized with standard methods. Glitches were made mostly using FM. Leading up to significant downbeats, random effects like pitch shifting and comb filtering are enabled to add some variety to the sound design.

I’ve thought about using characters or symbols as a way to sequence stuff like this but I gave up. I need to get into SC again.

@nathan Fantasic !!!

I love this a LOT

I don’t expect a full disclosure, but could you point me in the direction of how to accomplish something like that “micro-tracker”?

Either way, great job!

Thanks for listening! It’s a string parser where each character is mapped to a SynthDef using a dictionary. Here’s some example code, SynthDefs not included:

var pattern = "
k.k.s.hghgkks.hg
k.k.s.hghgs.hghg
k.k.s.hghgkks.hg
s.s.hgs.hghgs.hg
";
var map = IdentityDictionary[
    $k -> \kick,
    $s -> \snare,
    $h -> \hat,
    $g -> \glitch
];

Routine {
    var bpm, beat, tatum;
    bpm = 190;
    beat = 60 / bpm;
    tatum = beat / 4;
    pattern.do { |character|
        var synthdef;
        if(map[character].notNil) {
            synthdef = map[character];
            s.makeBundle(s.latency, { Synth(synthdef); });
            tatum.wait;
        } {
            if(character == $.) {
                tatum.wait;
            }
        }
    };
} .play;

Since it’s only a dozen lines, I usually rewrite a micro-tracker from scratch for each new piece, so the syntax and semantics can differ. Some variations include using the digits 0-9 for probabilities or velocities of drum hits, entering melodies with hexadecimal, etc.

5 Likes

Nice song and cool method. How exact do you implement the variations, for example mapping the digits 0-9 to amp? Do you simply create a second string and map the amp to it? Or do you do this in a single string/mapping object?

Have you been working with something similar or do you know a good way for creating either (with your presented ‘micro-tracker’ method):

  1. multiple drum hits at ones (snare + kick) or
  2. adding more variations without coding all notes one by one?
    For the latter I’ve been recently experiencing with Place which gives quick good results. But with a single string its maybe a bit harder to implement.

Also another quick one, why don’t you use patterns?
I’ve tried to implement your method with some minimalstic created drum synths and using ur method of strings but reading them with a Pbind instead:

(
SynthDef(\kick,{
	|amp=0.2|
	var sig, e;
	e = EnvGen.kr(Env.perc(0.01,0.15,1,-4),doneAction:2)**1.2;
	sig = (1*SinOsc.ar(e.linlin(0,1,55,110)))+PinkNoise.ar(1);
	sig = RLPF.ar(sig,e.linlin(0,1,220,1100),5).tanh;
	Out.ar(0,sig * e * amp!2);
}).add;
SynthDef(\snare,{|amp=0.1|
	var sig, e;
	e = EnvGen.kr(Env.new([1,0.15,0],[0.01,0.1],[\sine,-3]),doneAction:2);
	sig = WhiteNoise.ar() + (SinOsc.ar(e.linlin(0,1,110,440)) * e * 2);
	sig = BPeakEQ.ar(sig,e.linlin(0,1,880,1760),10,15).tanh;
	sig = RHPF.ar(sig,220,0.05).lag(0.00015);
	Out.ar(0,sig* amp * e!2);
}).add;
SynthDef(\hat,{|amp=0.1|
	var sig, e;
	e = EnvGen.kr(Env.perc(0.005,0.04),doneAction:2);
	sig = WhiteNoise.ar(1.5).tanh;
	sig = HPF.ar(sig,3520).lag(0.0001);
	Out.ar(0,0.5*sig* amp * e!2);
}).add;
)

(
var ptrn, map;
ptrn = "
k.. h.k 
k.. h.s
ks. .ks
h.h sss
";
//remove spaces and line breaks
ptrn = ptrn.replace(" ","").replace("\n","");
map = IdentityDictionary[
    $k -> \kick,
    $s -> \snare,
	$h -> \hat,
	$. -> \rest
];
p.stop;
p = Pbind(
	\instrument, Pn(Pseries(0,1,ptrn.size)),
	\instrument, Pfunc{|key| map[ptrn[key[\instrument]]]},
	\dur,0.12,
	\amp,0.1*Pn(Pseries(1,-0.1,3)).trace,
).play;
)

cheers.

Just for fun, here’s an alternate (and slightly shorter) way to do the same thing, using Psym1 to lookup a sequence of characters (interpreted as symbols) directly in a pattern:

(
var ptrn, map;
ptrn = "
k.. h.k 
k.. h.s
ks. .ks
h.h sss
";
//remove spaces and line breaks
ptrn = ptrn.replace(" ","").replace("\n","");
map = (k: \kick, s: \snare, h: \hat, '.': \rest);
p.stop;
p = Pbind(
	\instrument, Pn(Psym1(Pseq(ptrn), map)),
	\dur,0.12,
	\amp,0.1*Pn(Pseries(1,-0.1,3)).trace,
).play;
)
2 Likes

Separate strings for each drum type. For example 9.....9. for kick, ..9...9. for snare, etc. Alternatively you can use two characters for each time unit, the first character indicating the drum hit type and the second character for velocity. Whatever works best for the piece.

I stopped using them in favor of Routines years ago. It’s much easier to memorize a single class and a small handful of control structures vs. hundreds of poorly documented P* classes. (Not to mention the numerous design flaws in said classes.)

1 Like

I’ve wondered a lot about the difference / advantages of using Routines (or Tdefs?) instead of Patterns for a while now. One thing that I wasn’t yet sure how to implement was a transition over time in the same way a Pseg would offer eg Pseg([1, 10], 30, \sine) - is this possible with any of the control structures you use?

For gradual automations, I tend to use server-side Env mapped to control busses for that so you get smooth movement while notes are playing, but if you want client-side automation in a Routine you can use functions of thisThread.seconds or thisThread.beats I think. That’s what Penv and Pseg do under the hood.

1 Like

Great method, it inspire me a lot. And i have question that how can i use two characters for each time unit.

Hi and welcome! The string looks something like this: k9 h3 h3 k5 s9 h3 h5 h3. You can parse it by looping through each character, setting a flag if you encounter a valid drum hit, and if that flag is enabled you parse the next character as velocity/probability/effects/etc. I can’t share code at the moment but hopefully that should get you started.

IMO for parsing, CollStream is everyone’s best friend – because then the parser can be a literal state machine, without needing flags in the loop. E.g., this way, it can handle multiple modifier characters seamlessly.

(
~parseOneHit = { |stream, ch|
	var hit = ch;
	var modifiers = "";
	
	while {
		ch = stream.next;
		ch.notNil and: { ch.isSpace.not }
	} {
		modifiers = modifiers ++ ch;
	};
	
	[hit, modifiers, ch]
};

~parseString = { |string|
	var stream = CollStream(string);
	var ch;
	var items;
	var hit, modifiers;
	
	ch = stream.next;
	while {
		ch.notNil
	} {
		case
		{ ch.isAlpha } {
			#hit, modifiers, ch = ~parseOneHit.(stream, ch);
			items = items.add([hit, modifiers]);
		}
		{ ch.isSpace } {
			ch = stream.next
		}
		// ... other cases
		{ Error("Unrecognized hit '%'".format(ch)).throw };
	};
	
	items
};
)

~parseString.("k9 h3 h3 k5 s9 h3877 h5 h3");
-> [ [ k, 9 ], [ h, 3 ], [ h, 3 ], [ k, 5 ], [ s, 9 ], [ h, 3877 ], [ h, 5 ], [ h, 3 ] ]

hjh

1 Like