Chat and questions about the Scala based SuperCollider client. Newbies welcome. @ me if you need fast replies, otherwise I might not see new posts in a few days. Also checkout https://gitter.im/Sciss/Mellite for the computer music environment that embeds ScalaCollider.
Phasor
and press Ctrl-D, you should see help file for the Phasor
UGen. This saves you time to navigate to the scaladoc file on http://sciss.github.io/ScalaCollider/latest/api/
you may have noticed, I pushed a minimal update to ScalaCollider 1.19.0; this (and ScalaCollider-UGens) relaxes the license from GPL to LGPL, allowing to link to it as a library without having to worry about applying the GPL to your own projects.
I also wanted to share some thoughts on a research I have started to conduct...
I have started to map out some directions of development for our sonification platform SysSon -- which you may know uses ScalaCollider at the bottom level. One thing that is interesting to explore is the possibility to decompose complex synth graphs with expensive optional "branches" into actually different synths. Here is a simplified model:
// ---- Unit result ----
If (freq > 100) {
Out.ar(0, SinOsc.ar(freq))
}
If (freq > 100) {
Out.ar(0, SinOsc.ar(freq))
} Else {
freq.poll(0, "freq")
}
// ---- GE result ----
val res: GE = If (freq > 100) {
SinOsc.ar(freq)
} Else {
WhiteNoise.ar
}
Out.ar(0, res)
val res: GE = If (freq > 1000) {
SinOsc.ar(freq)
} ElseIf (freq > 100) {
Dust.ar(freq)
} Else {
WhiteNoise.ar
}
Out.ar(0, res)
Currently we can do something like this of course using signal comparators, like
Out.ar(0, SinOsc.ar(freq) * (freq > 100))
or
val freqGt = freq > 100
val res: GE = {
SinOsc.ar(freq) * freqGt
} + {
WhiteNoise.ar * (1 - freqGt)
}
Out.ar(0, res)
But these imply that all UGens compute at all times. So for expensive mutually exclusive branches (as they can occur in SysSon), it might be great to decompose this into sub-trees that end up in different synths:
// ---- tree 1 ----
val freq: GE = ???
// ---- tree 2a
val res = SinOsc.ar(freq)
// ---- tree 2b
val res = Dust.ar(freq)
// ---- tree 2c
val res = WhiteNoise.ar
// ---- tree 3 ----
Out.ar(0, res)
This poses multiple challenges, of course. The most basic one, the syntax with which to declare this If
, ElseIf
, Else
structure.
Then how to automatically partition the graph into connected trees.
We would need to inject bus-controls between shared signals.
Imagine that tree 3 also made use of the freq
parameter, so there
might be actually quite a bit of wiring going on. And then the
correct real-time control. I am currently thinking of pausing the
synths for the inactive branches, this would still allow theIf
conditional expression to run at control rate. An even
smarter UGen graph builder could try to parse the conditional
to see if the graph can be decided already at building time
(scalar condition that can be decided on the client).
So if you have any thoughts on this, I'd like to hear them. The Gitter channel might be the best place for this. At this stage it is not clear whether this branching mechanism will be feasible at all.
.reciprocal
) that are now provided by the Numbers library, and the conversion from String
to ControlProxyFactory
moved into the Ops
object, meaning that you may have to add import Ops._
if you hadn't done already and are using the "control".kr
syntax. The reason for this change is that I need to get hold of this syntax in SoundProcesses where controls are created differently.
ScalaCollider v1.21.0 includes the fix for LocalIn
; this is a breaking change from LocalIn.ar(numChannels: Int)
to LocalIn.ar(init: GE)
. Unfortunately, this may appear source compatible if your code was previously LocalIn.ar(4)
for example. So be sure to scan through your sources if you used LocalIn
. LocalIn.ar
without argument will work as before, and this also shows why using named arguments LocalIn.ar(numChannels = 4)
can be very useful.
A number of other issues on the -UGens and the main project have been closed, too.
The UGenSource
API has been changed to move out the unwrap
and rewrap
methods in the hope that the class files become significantly smaller (they do, but the gain is not very high). If you have written custom graph elements, you may need to import UGenSource._
which now contains these utility methods.
ScalaColliderSwing v1.31.0 corresponds with this release. It includes the -DOT library now, and the syntax for getting GUI methods on a graph function. Before gui { ... } .waveform()
which was odd because you needed to replace play
by gui
. Now: play { ... }
and graph { ... } play()
are equivalent. The gui is now graph { ... } .gui.waveform()
, and the DOT is hidden behind graph { ... } .gui.diagram()
. The latter is a bit hackish as it only works if the dot
Unix command is found. Also the rendering on Swing using Apache Batik is quite horrible, so this will hopefully improve in the next versions. But for now, it's useful enough to get a visual overview of an expanded SynthGraph
(the old viewDef
based on Prefuse is not imported automatically any longer, as it was never really fully worked out and not very useful, although pretty).
The call for the workshop on ScalaCollider & Co on 10 December is out. Details in German: http://iem.kug.ac.at/institut-aktuell/details/article/sc-meeting-iem-graz-10-dezember-2016.html :
SC meeting @ IEM Graz, December 10th, 2016
For the second time a SC meeting is scheduled at the Institute of Electronic Music and
Acoustics at University of Music and Performing Arts Graz, Austria. Idea of the format: a
thematic focus should be set by a lecturer with subsequent resp. included opportunity of
practical programming, testing of examples and discussions with the participants. The
event is open to all interested people, students and colleagues as well as the SC
community in Austria and abroad. In this edition Hanns Holger Rutz will present his
software developments.
ScalaCollider is a dialect of SuperCollider based on Scala, a statically typed, object-
functional language that principally targets the Java Virtual Machine (JVM). ScalaCollider
comes with a mini-IDE and various extensions, and it forms the basis for the computer
music platform SoundProcesses and its graphical environment Mellite, all of which are
cross-platform and open source software. SoundProcesses/Mellite in turn contain further
components, such as an interface for live improvisation, a timeline editor or multi-tracker,
and furthermore the SysSon sonification platform is designed as an extension of Mellite.
The workshop begins with an introduction to ScalaCollider and focuses on the specific
architectural strengths and weaknesses that differentiate it from SuperCollider, making it
an interesting alternative. In the further course of the workshop, we look at the Mellite
application and some typical scenarios.
Schedule:
Saturday, December 10th, 2016, 10:00 h - ca. 14:45 h
Requirements:
Basic knowledge of SuperCollider: SynthDefs
The participants are kindly asked to bring their own laptops and headphones and to
register via e-mail, so they will receive on time information about software installation links
(Linux, Mac, Windows).
Fee: none
Location:
Institute of Electronic Music and Acoustics – CUBE
Inffeldgasse 10 / 3, 8010 Graz, Austria
http://iem.at
There are new updates to ScalaCollider (v1.22.3) and ScalaCollider-Swing (v1.32.2). As the versions indicate, these are binary compatible to the previous versions, but now are also published against Scala 2.12.
I have also uploaded a new binary build for ScalaCollider-Swing; this is relevant to users, as this build is using Scala 2.12, having two implications:
val x = Line.kr(0, 6, 10)
typing x.db<tab>
finds .dbamp
. It is still limited when trying to chain calls, though.sbt ++2.11.8 clean assembly
.x.set("key" -> value)
– here is a quick hack:object Control {
implicit def toGE(c: Control): GE = c.name.kr(c())
}
class Control(p: Player, val name: String, private var value: Double) {
def apply(): Double = value
def update(x: Double): Unit = {
value = x
p.synth.set(name -> x)
}
override def toString = s"$name = $value"
}
trait Player {
private var _synth: Synth = _
def synth: Synth = {
require(_synth != null, "Forgot to call play")
_synth
}
def stop(): Unit = synth.free()
protected def control(name: String, init: Double = 0.0) =
new Control(this, name, init)
protected def play[A](body: => A)
(implicit r: GraphFunction.Result[A]): Unit = {
require(_synth == null, "Called play twice")
val gf = new GraphFunction[A](() => body)
_synth = gf.play()
}
}
class SineSynth extends Player {
val freq = control("freq", 100)
play {
val sin = SinOsc.ar(freq)
sin * 0.25
}
}
val sin = new SineSynth
sin.freq() = 441
sin.freq() = 666
sin.stop()
New versions are out for ScalaCollider (1.23.0) and ScalaCollider-Swing (1.35.0). not much to say about them, they mainly clean up a couple of issues; i added the 3rd party DEIND plugins (e.g. JPverb) -- if you are interested in more 3rd party plugins, please let me know or even better, contribute some XML files to the -UGens project.
another construction site is the patterns project -- https://github.com/Sciss/Patterns -- which came out of my conversation with Ron Kuivila about his use of the SuperCollider patterns library; i'm looking here at possibilities to implement some of them for Scala, eventually ScalaCollider and SoundProcesses.
there are new versions of ScalaCollider (v1.27.0) and -Swing (v1.39.0).
The most apparent (and breaking) change is that I adopted camel-case
style for the GE
operators; I'm still looking at the result and
wondering if this is good or not; I do think it's more consistent with
idiomatic Scala, but the eye has to get used to it. So if you look at
the examples
(https://raw.githubusercontent.com/Sciss/ScalaCollider/master/ExampleCmd.sc),
you'll mostly notice these changes:
madd
--> mulAdd
midicps
--> midiCps
\
--> out
this goes for all similar things (e.g. linLin
now instead oflinlin
). You may prefer the old style because it requires less typing,
but the new style makes everything more regular (and UGen arguments have
always used camel-case).
Missing random unary and binary ops have been added now, such as coin
or rand
, or rangeRand
:
graph {
SinOsc.ar.mulAdd(0.5, 0.5).coin
} .gui.waveform(duration = 0.02)
graph {
SinOsc.ar.rand
} .gui.waveform(duration = 0.02)
graph {
SinOsc.ar.rand
} .gui.waveform(duration = 0.02)
graph {
SinOsc.ar.rangeRand(1.0)
} .gui.waveform(duration = 0.02)
(These don't work yet on numbers, e.g. 0.5.coin
doesn't exist. I will
add that feature in a future version, the reason being that we need to
assume a mutable random number generator on the client side.)
Hi, I was trying out scala collider. I installed the scala collider swing booted the server and tried out the example.sc
. However I am getting few errors. Here is the log
JACK server starting in realtime mode with priority 10
self-connect-mode is "Don't restrict self connect requests"
Cannot lock down 82280346 byte memory area (Cannot allocate memory)
audio_reservation_init
Acquire audio card Audio0
creating alsa driver ... hw:0|hw:0|1024|2|48000|0|0|nomon|swmeter|-|32bit
configuring for 48000Hz, period = 1024 frames (21.3 ms), buffer = 2 periods
ALSA: final selected sample format for capture: 32bit integer little-endian
ALSA: use 2 periods for capture
ALSA: final selected sample format for playback: 32bit integer little-endian
ALSA: use 2 periods for playback
Cannot use real-time scheduling (RR/10)(1: Operation not permitted)
AcquireSelfRealTime error
Cannot lock down 82280346 byte memory area (Cannot allocate memory)
JackDriver: client name is 'SuperCollider'
SC_AudioDriver: sample rate = 48000.000000, driver's block size = 1024
Cannot use real-time scheduling (RR/5)(1: Operation not permitted)
JackClient::AcquireSelfRealTime error
SuperCollider 3 server ready.
<console>:580: error: not found: value viewDef
val f25 = viewDef(df25)
^
<console>:649: error: scrutinee is incompatible with pattern type;
found : Seq[A]
required: de.sciss.synth.ugen.Pitch
val Seq(freq0, hasFreq0) = Pitch.kr(???)
Any pointers ?
df25.gui.diagram()
should work if you have graphviz installed (dot
must be on the PATH
).
Seq[GE]
from Pitch
; instead you need to use out
, or hasFreq
and freq
(see following section "you can do")
sudo apt install qjackctl
), although Jack probably does work in your case despite the RT errors. If you start Jack audio server from QJackCtl, SuperCollider will just use that instead of launching one itself.
FAILURE IN SERVER /n_set Node 1001 not found
FAILURE IN SERVER /n_set Node 1001 not found
FAILURE IN SERVER /n_set Node 1001 not found
FAILURE IN SERVER /n_free Node 1002 not found
FAILURE IN SERVER /n_free Node 1003 not found
FAILURE IN SERVER /n_free Node 1004 not found
FAILURE IN SERVER /n_free Node 1005 not found
FAILURE IN SERVER /n_free Node 1006 not found
FAILURE IN SERVER /n_free Node 1007 not found
FAILURE IN SERVER /n_free Node 1008 not found
FAILURE IN SERVER /n_free Node 1010 not found
FAILURE IN SERVER /n_free Node 1011 not found
FAILURE IN SERVER /n_free Node 1012 not found
FAILURE IN SERVER /n_free Node 1013 not found
FAILURE IN SERVER /n_free Node 1015 not found
FAILURE IN SERVER /n_free Node 1016 not found
FAILURE IN SERVER /n_free Node 1017 not found
FAILURE IN SERVER /n_free Node 1018 not found
FAILURE IN SERVER /n_free Node 1019 not found
FAILURE IN SERVER /n_free Node 1020 not found
File 'sounds/a11wlk01.wav' could not be opened: System error : No such file or directory.
FAILURE IN SERVER /n_set Node 1021 not found
FAILURE IN SERVER /n_set Node 1022 not found
FAILURE IN SERVER /n_free Node 1023 not found
Buffer UGen: no buffer data
FAILURE IN SERVER /n_free Node 1024 not found
java.lang.NoClassDefFoundError: Could not initialize class $line10.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$
at $line10.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$anonfun$2.isDefinedAt(<console>:559)
at $line10.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$anonfun$2.isDefinedAt(<console>:558)
at de.sciss.synth.message.Responder$Impl.handle(Responder.scala:49)
at de.sciss.synth.impl.OnlineServerImpl$OSCReceiverActor$.$anonfun$$bang$4(ServerImpl.scala:253)
at de.sciss.synth.impl.OnlineServerImpl$OSCReceiverActor$.$anonfun$$bang$4$adapted(ServerImpl.scala:252)
at scala.collection.immutable.Set$Set1.foreach(Set.scala:95)
at de.sciss.synth.impl.OnlineServerImpl$OSCReceiverActor$.$anonfun$$bang$2(ServerImpl.scala:252)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at scala.concurrent.impl.ExecutionContextImpl$DefaultThreadFactory$$anon$2$$anon$3.block(ExecutionContextImpl.scala:71)
at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3313)
at scala.concurrent.impl.ExecutionContextImpl$DefaultThreadFactory$$anon$2.blockOn(ExecutionContextImpl.scala:83)
at scala.concurrent.package$.blocking(package.scala:142)
at de.sciss.synth.impl.OnlineServerImpl$OSCReceiverActor$.$anonfun$$bang$1(ServerImpl.scala:241)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:654)
at scala.util.Success.$anonfun$map$1(Try.scala:251)
at scala.util.Success.map(Try.scala:209)
at scala.concurrent.Future.$anonfun$map$1(Future.scala:288)
at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:29)
at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:29)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
*** ERROR: SynthDef mag-above not found
FAILURE IN SERVER /s_new SynthDef not found
FAILURE IN SERVER /n_free Node 1025 not found
FAILURE IN SERVER /n_free Node 1026 not found
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
FAILURE IN SERVER /n_free Node 1027 not found
It's just a small part of it
Node x not found
means you are trying to use x.set
or x.free
but the synth corresponding with x
has not been created or already freed.
'sounds/a11wlk01.wav'
bit is simply that I couldn't hard code the file path into the examples file. Normally, when you install SuperCollider, this is one of the example files installed. For example, on Linux the absolute path would probably be /usr/local/share/SuperCollider/sounds/a11wlk01.wav
Cannot lock down 82280346 byte memory area (Cannot allocate memory)
JackDriver: client name is 'SuperCollider'
SC_AudioDriver: sample rate = 44100.000000, driver's block size = 1024
Cannot use real-time scheduling (RR/5)(1: Operation not permitted)
JackClient::AcquireSelfRealTime error
SuperCollider 3 server ready.
x0: de.sciss.synth.Synth = Synth(<localhost>,1000) : <temp_55>
df1: de.sciss.synth.SynthDef = SynthDef(AnalogBubbles)
x1: de.sciss.synth.Synth = Synth(<localhost>,1001) : <AnalogBubbles>
FAILURE IN SERVER /n_set Node 1001 not found
FAILURE IN SERVER /n_set Node 1001 not found
FAILURE IN SERVER /n_set Node 1001 not found
So the nodes on the sound server have ids, the count beginning at 1000. The first synth x0 you created, is 1000; the second is x1 with 1001; the error log shows that "n_set" does not find node x1 or 1001. This has again todo with the execution order; I assume you executed this en-bloc:
val df1 = SynthDef("AnalogBubbles") {
val f1 = "freq1".kr(0.4)
val f2 = "freq2".kr(8.0)
val d = "detune".kr(0.90375)
val f = LFSaw.ar(f1).mulAdd(24, LFSaw.ar(Seq(f2, f2 * d)).mulAdd(3, 80)).midiCps // glissando function
val x = CombN.ar(SinOsc.ar(f) * 0.04, 0.2, 0.2, 4) // echoing sine wave
Out.ar(0, x)
}
val x1 = df1.play()
x1.set("freq1" -> 0.1)
x1.set("freq2" -> 222.2)
x1.set("detune" -> 0.44)
However, there are some procedures in SuperCollider which are asynchronous, so they do not complete instantaneously. One of them is SynthDef#play
. The server tries to run x1.set
before the synth x1
is actually initalised. Sorry, this is not obvious from the document, only if you were familiar with SuperCollider. So you need to execute that in two steps:
val df1 = SynthDef("AnalogBubbles") {
val f1 = "freq1".kr(0.4)
val f2 = "freq2".kr(8.0)
val d = "detune".kr(0.90375)
val f = LFSaw.ar(f1).mulAdd(24, LFSaw.ar(Seq(f2, f2 * d)).mulAdd(3, 80)).midiCps // glissando function
val x = CombN.ar(SinOsc.ar(f) * 0.04, 0.2, 0.2, 4) // echoing sine wave
Out.ar(0, x)
}
val x1 = df1.play()
And then, separately:
x1.set("freq1" -> 0.1)
x1.set("freq2" -> 222.2)
x1.set("detune" -> 0.44)
Are you hearing the sounds?
Tried that. Not hearing anything. Also tried
val x0 = play {
val f = LFSaw.kr(0.4).mulAdd(24, LFSaw.kr(Seq(8, 7.23)).mulAdd(3, 80)).midiCps // glissando function
CombN.ar(SinOsc.ar(f)*0.04, 0.2, 0.2, 4) // echoing sine wave
}
Not working. However super collider client works like a charm without QJackCtl.
var summation: GE = SinOsc.ar(220.0)
(2 to 16 ).zipWithIndex.foreach{
case (ot, idx) => {
summation = summation + (SinOsc.ar(220.0 * ot) * weights(idx))
}
}
@Bmottomus Yes sure; so let's say your complete example is
play {
val f0 = 220.0
var summation: GE = SinOsc.ar(f0)
val weights = Seq.tabulate(15)(i => 1.0 / (i + 1))
(2 to 16 ).zipWithIndex.foreach{
case (ot, idx) => {
summation = summation + (SinOsc.ar(f0 * ot) * weights(idx))
}
}
summation * 0.2
}
That is, weights are 1.0, 0.5, 0.333, .... You could write that more concisely as
play {
val f0 = 220.0 // fudamental
val n = 16 // number of partials
val sum = Mix.tabulate(n) { i =>
val j = i + 1
SinOsc.ar(f0 * j) * 1.0 / j
}
sum * 0.2
}
Or use multi-channel expansion followed by a Mix
:
play {
val f0 = 220.0 // fudamental
val n = 16 // number of partials
val f = (1 to n).map(_ * f0)
val w = (1 to n).map(_.reciprocal)
val sum = Mix(SinOsc.ar(f) * w)
sum * 0.2
}