MORPHEUS - SuperCollider 2 - Available anywhere?

Hi! Recently I came across this article:

MORPHEUS >> emergent music (contents may vary) - John Eacott

Which describe a project of generative music made for SuperCollider 2 and distributed as a CD-ROM (brief moment of early 2000 terminology nostalgia :cd:).

Is this available anywhere?

All the links provided by the article are broken…

This sounds kinda sick :slight_smile:

Often thesis / dissertation submissions are kept at university libraries - you might get ahold of someone at the University of Westminster library to check on whether they keep student publications like this accessible.

Maybe @redFrik knows where there might be a copy on the code, seems like he was involved in the project? Google hasn’t given up much, even a hint of the music.

1 Like

I might have the CD-ROM somewhere but would have to dig it out. One of my two pieces from it included below (but SC2 code; I think it got it running in emulation a year or two back. The other piece, iDAB, requires various sound files).

//pythcirc = pythagorean circulations
//copyright Nick Collins, finished 27/8/2001 all rights reserved 

//lower CPU drain version
//runs at around average CPU use 25-35%, peaks up to 60%, initial peak over 100% 
//many hundred UGens at once, but most are k rate  
//measured on a G4 400MHz tibook with 384 MB RAM  

//elliptical reading of scaled pythagorean scales with rhythmic pulsing
//and synthesised percussion
//no samples were used in this production

//requires Reverb and WavetableManipulation classes

(
var basefreq,startfreq;
var pyth,pyth1,pyth2;
var numranges,ranges;
var createpitchcirc,createfiltcirc,createindexarray;
var fadeprop;
var wavetables;
var percarray,drumpattern;

fadeprop= 0.5.rand+0.5;

//7 note scale using natural intervals from harmonic series
pyth= [1,9/8,5/4,4/3,3/2,5/3,15/8];

//octave completion scale versions
pyth1= pyth ++ [2];

pyth2= pyth ++ (2*pyth) ++ [4];

//a3
basefreq= 110;

//starting frequencies are dependent on pyth scale starting at base
startfreq= pyth*pyth2;

numranges=3;	//for piece generations could make 4.rand

wavetables= Array.fill(numranges,{b=WavetableManipulation.bpsettowavetable(WavetableManipulation.randombps(3+(3.rand)),512,1);});


//creates scale readers for freq input in pitch and filt circulators
createindexarray=
{
arg dur,rangeindex;

var scalemult,bf;
var scaletemp,excess,scale,table;
var phase,indexarray;
var voices;
var initcirc,endcirc; //defaults 10 and 0.1

//choose length of spawned event, num voices, scale mult, circulation speed

voices= 1+ (5.rand);
initcirc= 1+(100.rand);
endcirc= 0.001+(1.rand);	//0.1 should be more probable- need continuous distribution peaking around 0.1?

//choose base freq based on range
bf= basefreq*(2**(rangeindex))*(pyth.choose);

//scalemults depend on range
scaletemp= 0.9*(rangeindex+1)/numranges;
scalemult= 0.1+(scaletemp.rand); 

//scale construction
excess= pyth2.log2;
excess= excess*scalemult;
scaletemp= 2**excess; 

//delete 7 of the 15
7.do(
{
scaletemp=scaletemp.scramble;
scaletemp.pop;
});

//intervals 
scaletemp= SortedList.newFrom(scaletemp);

//build over 2 octaves and add in mirror image
scale= bf*(((1.0/scaletemp).reverse) ++ scaletemp);

table=FloatArray.newFrom(scale); 


//indexing procedure
//phase at rhythmic multiples? to force synchrony on some points?
//can randomise phase for other effects
if(0.7.coin,
{
phase=Array.fill(voices, {arg i; (i/voices)*2*pi}); 
},
{
phase=Array.fill(voices, {2*pi.rand}); 
}
);

indexarray=Array.fill(phase.size,{
arg i;
//stopping different voices at different times 
var leng;
leng= (dur/voices)*(i+1);	//dur


//different index methods- allow Oscs with odd wavetables! Discontinuous 

Index.kr(	
			table,  
			SinOsc.kr(XLine.kr(initcirc,endcirc,leng),phase.at(i),(0.7+(0.3*(SinOsc.kr(XLine.kr(initcirc,endcirc,leng*0.6),phase.at(i),1.0).abs)))*(table.size-1)/2,(table.size-1)/2).round(1.0)
		)

});

indexarray
};

createpitchcirc=
{
arg dur,rangeindex;
var indexarray,env;

indexarray=createindexarray.value(dur,rangeindex);

env=Env.new([1.0,1.0,0.0],[dur*fadeprop,dur*(1.0-fadeprop)]);

EnvGen.ar(env,
((0.2+(0.8*((numranges-rangeindex)/numranges)))/indexarray.size)*
Mix.arFill(indexarray.size,
{
arg i;
var maxpan,minpan;
//pan positions in an annulus around 0

maxpan= (rangeindex+1)/numranges;
minpan= (rangeindex)/numranges;

Pan2.ar(

	//Use an Osc here- vary the wavetable based on rangeindex?
	//can just be SinOsc	
	Osc.ar(
		wavetables.at(rangeindex),
		indexarray.at(i), 
		0,
		0.1
	)
	,(rrand(maxpan,minpan)* ((-1)**(2.rand)))
	)
	}
	)
)

};

createfiltcirc=
{
arg dur,rangeindex;

var env,indexarray;

indexarray=createindexarray.value(dur,rangeindex);

env=Env.new([1.0,1.0,0.0],[dur*fadeprop,dur*(1.0-fadeprop)]);

EnvGen.ar(env,
((0.2+(0.8*((numranges-rangeindex)/numranges)))/indexarray.size)*
Mix.arFill(indexarray.size,
{
arg i;
var maxpan,minpan;
//pan positions in an annulus around 0

maxpan= (rangeindex+1)/numranges;
minpan= (rangeindex)/numranges;

Pan2.ar(

	//filtered noise
	BPF.ar(
		WhiteNoise.ar, 
		indexarray.at(i),
		0.001,
		15
	)
	,(rrand(maxpan,minpan)* ((-1)**(2.rand)))
	)
	}
	)
)


};


//////////
//percussion sounds array
//array of ugenFuncs

percarray=
[
{	//bong kick
Klank.ar(`[[70,73,77,80,83,85], [-3.dbamp,-6.dbamp,-12.dbamp,-12.dbamp,-12.dbamp,-20.dbamp], [1, 0.5,0.4,0.3, 0.25, 0.125]],EnvGen.ar(Env.perc(0.0,0.1,1,-16),WhiteNoise.ar(0.5)))
},
{
var tightness;

tightness=0.01*(5.rand+1);

EnvGen.ar(Env.perc(0.0,tightness,8,-8),BrownNoise.ar)
}
,
{
EnvGen.ar(Env.perc(0.0,0.1,1,-8),PinkNoise.ar(FSinOsc.ar(20)))
}
,
{
EnvGen.ar(Env.perc(0.0,0.1,1,8),Integrator.ar(Impulse.ar(20),0.999,HPF.ar(WhiteNoise.ar,2000)))
}
,
{
BPF.ar(Decay.ar(Pulse.ar(1,0.01,WhiteNoise.ar),0.01,0.03),1000,XLine.kr(0.9,0.1,0.03))
}
,
{
BPF.ar(Pulse.ar(1,0.01,Normalizer.ar(LFNoise0.ar(5),1.0,0.01)),XLine.kr(100,5000,0.01),XLine.kr(0.9,0.1,0.01))
}
,
{
BPF.ar(Pulse.ar(1,0.01,LinCong.ar(0,7,17,486)),XLine.kr(1000,100,0.01),XLine.kr(0.9,0.1,0.01))
}
,
{
BPF.ar(Pulse.ar(1,0.01,LinCong.ar(5,38,17,48600)),100,XLine.kr(0.9,0.1,0.01))
}
,
{
BPF.ar(Pulse.ar(1,0.01,LinCong.ar(51,26,170,93400,0.2)),XLine.ar(10000,50,0.01),0.8)
}
,
{
BPF.ar(Integrator.ar(Pulse.ar(1,0.1,LinCong.ar(510,601,17,9300,0.2)),0.1),XLine.kr(1000,50,0.01),0.8)
}
,
{
EnvGen.ar
(
Env.perc(0.0,0.2,0.4,-1),			//new([1,0],[0.2]),
Resonz.ar(LinCong.ar(4,6,13,800030,Line.kr(1.0,Line.kr(0.5,0.0,0.02),0.1)),6000,XLine.kr(0.8,0.1,0.1))
)
}
,
{
EnvGen.ar
(
Env.perc(0.0,0.2,1.0,-4),			//new([1,0],[0.2]),
Resonz.ar(LinCong.ar(4,6,13,800030,Line.kr(1.0,Line.kr(0.5,0.0,0.02),0.1)),XLine.kr(300,10,0.2),XLine.kr(0.8,0.1,0.1))
)
}
];



drumpattern=
[
[
[Pseq([0.5,1.5,0.75,1.25],inf),Pseq([0.0,Pseq([1.0],3)],inf)],
[Pseq([0.5,1.5,0.75,1.25],inf),Pseq([1.0,Prand([1.0,0.0],2)],inf)],
[Pseq([0.5,Prand([0.5,1.5],1),0.75,1.25],inf),Pseq([0.0,Pseq([1.0],3)],inf)],
[Pseq([0.25,0.25,1.5,0.75,1.25],inf),Pseq([Pseq([1.0],4),0.0],inf)]
],
[
[Pseq([1.0,2.0,0.75,0.25],inf),Pseq([0.0,Pseq([1.0],3)],inf)],
[Pseq([1.0,2.0,Prand([Pseq([0.25,0.5,0.25],1),Pseq([0.5,0.25,0.25],1)],1)],inf),Pseq([0.0,Pseq([1.0],4)],inf)],
[Pseq([1.0,2.0,0.75,0.5],inf),Pseq([0.0,Pseq([1.0],3)],inf)],
[Pseq([1.0,2.0,0.75,0.25,0.25],inf),Pseq([0.0,Pseq([1.0],3)],inf)]
],
[
[Pseq([0.5,Pseq([1.0],3),0.5,Pseq([0.25],12)],inf),Pseq([0.0,Pseq([1.0],3),1.0,Pseries(0,1/12,12)],inf)],
[Pseq([0.5,Pseq([1.0],3),0.5,Pseq([0.25],12)],inf),Pseq([0.0,Pseq([1.0],3),1.0,Pseries(0,1/12,8)],inf)],
[Pseq([0.5,Pseq([1.0],3),0.5,Pseq([0.25],12)],inf),Pseq([0.0,Pseq([1.0],3),1.0,Pseries(0,1/12,10)],inf)],
[Pseq([0.5,Pseq([1.0],4),0.5,Pseq([0.25],12)],inf),Pseq([0.0,Pseq([1.0],3),1.0,Pseries(0,1/12,12)],inf)],
[Pseq([0.5,Pseq([1.0],5),0.5,Pseq([0.25],12)],inf),Pseq([0.0,Pseq([1.0],3),1.0,Pseries(0,1/12,12)],inf)]
],
[
[Pseq([1.0,0.25,0.75,0.25,0.75],inf),Pseq([1.0,1.0,1.0,0.0],inf)],
[Pseq([0.5,Pseq(0.25,0.25,8)],inf),Pseq([1.0,0.0,Prand([1.0,0.0],1),1.0],inf)],
[Prand([Pseq([0.5,0.25,0.25],1),Pseq([0.5,0.25,0.25],1)],inf),Pseq([1.0,0.0,Prand([1.0,0.0],1),1.0],inf)]
]
];


/////////

Synth.play({ 
var circulation;

//circulartempo
SetTempo.kr(thisSynth,SinOsc.kr(0.01,0,0.01,2));

circulation= Mix.arFill(numranges,
{
arg i; 

Spawn.ar(
{
arg sp;

var dur,silence;

//1 to 60 beats
dur= 1+(59.rand);

sp.nextTime= dur;

silence=if(0.3.coin,0,1);

if(0.3.coin,
{silence*(createfiltcirc.value(dur,i))},
{silence*(createpitchcirc.value(dur,i))})

},2,30,nil	
)

}
);

z= 
Spawn.ar(
{
arg sp;
var dur,dursec,env;
var percindex,drumpattindex;

//80 to 200 beats
dur= 4*rrand(20,50);

sp.nextTime=dur;

//always take low estimate of dur in seconds
dursec= (dur-16)/2.01;

env= Env.new([1,1,0],[dursec,0.01]);

//choose without replacement would be better yet
percindex=Array.fill(4,{percarray.choose});

//so always reset take copies
drumpattindex=Array.fill(4,{arg i; drumpattern.at(i).choose});

EnvGen.ar(
env,

Mix.arFill(
4,
{
arg i;

var ampmul;

ampmul= 0.15+(0.1.rand);
Pan2.ar(
Pbind(
\dur,drumpattindex.at(i).at(0),
\amp,drumpattindex.at(i).at(1),
\ampmult,ampmul, //0.25,
\ugenFunc,{arg amp,ampmult; 
amp*ampmult*EnvGen.ar
(
Env.new([1,1,0],[0.4,0.1]),
percindex.at(i).value
)
}
).asSpawn(channels:1)
,
0.2.rand2
)
}
)
)

},
2,
1,
nil
);


Limiter.ar(
1.0*				//HERE IS MASTER VOLUME!
(z+
//circulation pulsing
(SinOsc.ar(GetTempo.kr,0,0.4*circulation))+
Decay.ar(Impulse.ar(2*GetTempo.kr),0.4,circulation)
//panning reverb
+Pan2.ar((0.3*Reverb.grlargeroom(circulation)),SinOsc.kr((GetTempo.kr)/16,0,1.0))
)
,0.99,0.01)
});



)

3 Likes

Thanks!!!

I thought that porting the code would be easier, but this seems to be a difficult one as it depends on external classes…

The two classes it depends on I wrote, so code linked for those too below. I would think porting is most hindered by SC2’s combined synthesis/scheduling UGen paradigm (Spawn etc), but not impossible in practice, I just never had the spare time. Classes and pythcirc code in the linked zip in case of any interest. The Reverb is adapted from a chapter in the CSound Book. Best N

https://composerprogrammer.com/code/PythCirc.zip

2 Likes