Are the environment variables the tilda ones? This is my first sc script.

1 Like

yes
http://doc.sccode.org/Classes/Environment.html

i quickly edited your engine. didn’t test except to ensure it compiles. hopefully it will help you get started. notice:

  • i’m careful about freeing resources
  • i use arrays of buffers and synths
  • i added a command to load samples and a command to create a synth
  • i removed folder variable; it’s probably better to manage filesystem on the lua side. (easier to incorporate UI feedback around filenames.)
  • you had the synthdef hardcoded to go to the main SC output bus. this would actually be OK right now in norns 2.0, but wouldn’t have been in 1.0 (would have bypassed level controls, meters, fx) and could break in future. i made synth output bus an argument to the def.
  • some other general mistakes: you were attaching audio input busses to nonexistent args in synthdef; you were using amp as a synthdef argument though it is a variable.
Engine_Sampler : CroneEngine {
	var <synth;
	var <samples;

	// count of slots
	classvar <numSlots = 128;
	// array of buffers
	var <buffers;
	// array of synths
	var <synths;

	*new { arg context, doneCallback;
		^super.new(context, doneCallback);
	}

	alloc{
		SynthDef(\sampler, {
			arg out, buf, vel=64, gate=0, rate=1;
			var sig, env, amp;
			env = EnvGen.kr(Env.asr(), gate, doneAction:2);
			sig = PlayBuf.ar(2, buf, rate*BufRateScale.ir( buf ));
			amp = LinExp.kr(vel, 1, 127, 0.01, 1);
			sig = sig * env * amp;
			Out.ar(out, sig);
		}).add;

		context.server.sync;

                // empty arrays for bufs and synths to be created by commands
		buffers = Array.newClear(numSlots);
		synths = Array.newClear(numSlots);

		// load a sample into a given buffer slot
		this.addCommand("load_sample", "is", { arg msg;
			var slot = msg[1];
			var path = msg[2];
			var newbuf;
			var oldbuf;
			if(slot < numSlots && slot >= 0, {
				newbuf = Buffer.read(context.server, path);
				// we should free the existing buf if there is one
				if(buffers[slot].notNil, {
					oldbuf = buffers[slot];
					buffers[slot] = newbuf;
					oldbuf.free;
				}, {
					buffers[slot] = newbuf;
				});
			});
		});

		// make new synth in the given slot, at given velocity and rate
		// release any synth currently in given slot
		this.addCommand("note_on", "iif", { arg msg;
			var slot = msg[1];
			var vel = msg[2];
			var rate = msg[3];
			if(slot < numSlots && slot >= 0, {
				if(synths[slot].isNil, {
					// will get freed by our doneAction
					synths[slot].set(\gate, 0);
				});

				synths[slot] = Synth.new(\sampler, [
                                        \out, context.out_b,
					\buf, buffers[slot],
                                        \gate, 1,
					\vel, vel,
					\rate, rate
				], context.xg);

			});
		});
	}


	free {
		buffers.do({ arg buf; if(buf.notNil, {buf.free;}); });
		synths.do({ arg syn; if(syn.notNil, {syn.free;}); });
	}
}

BTW: we’re considering options for launching .scds from norns menu in the future instead of forcing people to understand classes. (and yes: there are a lot of pro’s and con’s; i’m personally not gonna rehash them again here.) this would be in a future major revision.

5 Likes

This works great, thanks! And easy to understand too.

1 Like

One thing I get in SC after some time is this Jack: xrun detected - resyncing clock And then it just keeps coming every 1-2 seconds after it happens once. Forgive my ignorance on all of this.

Are you on norns 1.0 or 2.0? If 1.0 then turn off the reverb and they will likely go away. This should happen less frequently on norns 2.0.

I’m on 1.0. Tried turing off all fx and am still getting it. Is it perhaps because at times I might be triggering a sample in a synth slot that hasn’t been freed, maybe even multiple times triggering the same sample?

Yes if your synths aren’t getting freed then they would build up and likely cause those xruns. Can you post your LUA script as well that you’re using?

Sure:

-- KEY 3 trigger random note 
-- with random velocity
--

engine.name = "Sampler"
local names = {}
local chord0 = {9, 40, 57, 16, 0, 45, 36, 24, 40, 33, 48} -- Abmin - ab1 eb4 ab5 eb2 b0 ab4 b3 b2 eb4 ab3 b4
local chord1 = {10, 40, 57, 17, 2, 46, 38, 26, 41, 34, 40} -- A_#11 - a1 eb4 ab5 e2 db1 a4 db4 db3 e4 a3 eb4
local chord2 = {2, 53, 57, 14, 17, -1, 21, 26, 38, 41, -1} -- Dbmin - db1 e5 ab5 db2 e2 - ab2 db3 db4 e4 -
local chord3 = {0, 53, 57, 19, 28, 46, 34, 38, 40, 29, 52} -- B0 E5 G#5 F#2 D#3 A4 A3 C#4 D#4 E3 D#5
-- B0 F#2 D#3 E3 A3 C#4 D#4 A4 D#5 E5 G#5 (in order of chord above, not sure what chord it is)


local chords = {chord0, chord1, chord2, chord3}
local screen_text = "Ready..."
local which_chord = 0
local prevnum = 0;

path = "/home/we/dust/audio/samples"

function redraw()
  screen.clear()
  screen.move(0,10)
  screen.text(screen_text)
  screen.update()
end


function init()
  -- load all samples
  dirname = path
  f = io.popen('ls ' .. dirname)
  i = 0;
  for name in f:lines() do 
    print("loading: "..name.." "..i) 
    engine.load_sample(i,path..name)
    names[i] = name
    i=i+1
  end
  redraw()
end

function key(n,z)
  if n == 3 then
    if z == 1 then
      -- choose a note from a chord
      choose = math.random(1,11)
      num = chords[1+which_chord][choose]
      if num >= 0 then
        engine.note_on(num, math.random(50,100), 1.0)
        screen_text = "Chord "..which_chord.." Note "..names[num]
        prevnum = num
      else
        engine.note_on(prevnum, math.random(50,100), 1.0)
        screen_text = "no note here"
      end
      redraw()
    end
  end
    
   if n == 2 then
      if z == 1 then
        which_chord = (which_chord+1)%4 -- since 4 chords right now
	      screen_text ="Switch Chord to: "..which_chord
	      redraw()
      end
   end
end

function enc(n,d)
  if n==1 then mix:delta("output",d) end
end

Essentially, I’m building up to a generative chord thing playing many different samples one after another. Still adding in chords now before generative part. Currently triggering different notes with key 3 and changing chords with key 2.

Add note_off command and use as appropriate… even assuming the engine works perfectly and frees current synth slots in note_on (verify this,) 128 simultaneous sampler voices is a lot.

In Norns 1.0, consider a ParGroup containing sampler voices to spread the cpu load, and definitely keep reverb/compression off

In 2.0, sc only gets one core, but this buys you other features and more stability

Would it be possible (i.e. easy) to add a ā€œloopā€ option to Tape playback?

I just now discovered that Tape play back independently of whatever script is playing (at least in the 2.0 beta).

A ā€œreverseā€ function would be a huge bonus as well, but I should not try for too many feature requests at once :grin:

I thought this might have been requested previously, but I can’t seem to find a reference to it (and tape and loop are a bit generic for search terms here)

1 Like

Yes it was asked in ideas thread. Loop is pretty easy. Varying rates is more work. You can always load tape files into softcut buffers

2 Likes

Just seems that since it’s already there, adding loop option would be a handy addition.

1 Like

This is a rambly nostalgia post.

I began a new Norns project to build some kind of filter bank but I’m not sure what kind or how yet. I started digging through the SuperCollider IDE help files. This reminded me how interesting it is to browse these documents. I ran into DFM1 (from sc3-plugins) which

is a digitally modeled analog filter. It provides low-pass and high-pass filtering. The filter can be overdriven and will self-oscillate at high resonances.

The examples in the help file sounded pretty good and the CPU usage was modest. At the bottom of the help file there was the following text

For questions related to the audio code in DFM1 please contact Tony Hardie-Bick <tony entitysynth.net>

I checked out that website and read this

Entity is a true stereo no compromise polyphonic synthesizer for the Soundart Chameleon.
It includes filters based on real analogue circuitry.

Huh. Weird. I wonder what the Soundart Chameleon is? The links on the DFM1 website referencing this synth are long gone. In their place is some kind of SEO hacking scam website.

Turns out Wikipedia has an entry on this discontinued product.

The Soundart Chameleon was a hardware synthesizer module, designed by the Spanish company Soundart. The name Chameleon comes from the fact that the machine was able to change its ā€œskinsā€, which are different sound engines.

Norns is so cool. I love how it fits into this weird niche computer music history. I also find it amazing that Linux audio software and small embedded hardware have come so far since the age of these specialized hardware DSP devices. The SuperCollider distribution is such of hub of so many esoteric computer music projects.

8 Likes

Interesting think I learnt a trick about ā€˜analogue modelling’ filters looking around the dfm1 ugen source code… I remember reading something similar in the blurb for zynaddsubfx.

The noise has two purposes: Disruption and dithering. In an analogue circuit,
* a large number of noise sources is accumulated, resulting in rare high amplitude
* events that can push the filter further into or out of distortion, knocking the
* filter’s resonant phase-locked loop out of or into phase with a signal harmonic.
* Noise is applied so the magnitude of its low frequency components increases
* inversely with respect to filter cutoff frequency.

1 Like

Monsters!

messing about with supercollider wavetables!

need to get my head around aliasing and wavetables - presumably make band limited versions of my tables I guess (or just shove a low pass filter on them :wink: - might be ok for the idea I have)

2 Likes

if anyone is interested I put my wavetable stuff into GitHub

If you look in latest tracks you can hear what I was up to with it (not sharing the code for that track - it’s too messy - I had wondered about making it into a Norns instrument but it’s basically that track - not sure how useful parameterising it would be)

3 Likes

The aliasing is a bummer for sure, this seems like it could have some crazy potential.

I still get a lot of aliasing

yeah that will happen with naive WT playback for sure

(apologies if the following is familiar)

the typical way to make an antialiased WT oscillator is by multisampling. that is, in a nutshell:

  • anywhere you would use a single WT buffer, make an array of buffers (one per octave is usually enough)
  • the ā€œbottom-mostā€ WT buffer will include the ā€œfullā€ spectrum, whatever that is.
  • successive WTs should be bandlimited to progressively lower ratios of the fundamental. (if there’s one per octave, each should have harmonic content that caps at 1/2 the previous, &c.)
  • during playback, choose the appropriate WT based on current frequency. this can be crossfaded. let’s say your ā€œoriginalā€ wavetable’s highest harmonic is H (some integer, probably; H=1 would be a sinewave.) and lets say the current playback frequency is f and sampling rate is fs. your ā€œoriginalā€ wavetable will alias when f exceeds fs/(2*H), so that is where you want to start bringing in additional bandlimited WTs.

nigel redmon’s blog has a good series describing the technique in full (using c++) - it is long but comprehensive:
http://www.earlevel.com/main/2012/05/04/a-wavetable-oscillator—part-1/

visual illustration of BL’d sawtooth tables, from that series:

(you can do multisampling of arbitrary waveforms on the fly, usually by FFT->brickwall->IFFT. i remember there was some waldorf hardware synth a few years back that did this with live-sampled waveforms, neat trick. but then i remember my mom doing something like it back in the 80’s with a TG77 and sysex! ha.)

it’s a bit of a bummer that there’s no built-in functionality for doing this stuff in SC. but it is a fairly straightforward application of VOsc, which gives you efficient crossfading between WTs. seems likely that someone has already cooked up an extension somewhere; if not, it would be a great exercise.


different approach: it was a long time ago, but at one point i did a bunch of experiments with linear combinations of bandlimited ugens in SC. (VarSaw, Pulse, &c.) this actually is quite flexible and leads to a lot of interesting timbres that remain bandlimited if you are reasonably careful - basically, subtracting BL’d saws/pulses from each other at various phases and duty cycles

10 Likes

thank you for this! really helpful - will be heading down this rabbit hole later :slight_smile:

1 Like