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.
// 8 channel ring
val a = VBAPSetup(2, Seq(0, 45, 90, 135, 180, -135, -90, -45))
val b = Buffer.alloc(s, a.bufferData.size)
b.setn(a.bufferData)
val x = play {
val azi = "azi".kr(0)
val ele = "ele".kr(0)
val spr = "spr".kr(0)
VBAP.ar(8, PinkNoise.ar(0.2), b.id, azi, ele, spr)
}
// test them out
x.set("azi" -> a.directions(1).azi)
x.set("azi" -> a.directions(2).azi)
x.set("azi" -> a.directions(3).azi)
// ...
x.set("azi" -> a.directions(7).azi)
x.set("azi" -> a.directions(0).azi)
// try the spread
x.set("spr" -> 20)
x.set("spr" -> 100) // all speakers
x.free(); b.free();
if (value == 1) Synth.play(ssa.name)
if (value == 2) {
ssa release(2)
println("recu 2")
}
After I created 'transactional' systems based on ScalaCollider, I wanted to remove the plain side-effecting methods from the basic API. The basic API just supports generating the OSC message; when you call a method such as synth.set(...)
or synth.free(...)
, this is facilitated by an additional import de.sciss.synth.Ops._
. So when you look at the README, you have this preamble:
import de.sciss.synth._
import ugen._
import Ops._
The last import makes the side-effects available. The docs are here: https://sciss.github.io/ScalaCollider/latest/api/#de.sciss.synth.Ops$ - so for release
, that is NodeOps
: https://sciss.github.io/ScalaCollider/latest/api/#de.sciss.synth.Ops$$NodeOps
server.run(cfg) { serv =>
var synth = Synth()
ssa.recv(serv) //previous SynthDef
receiver.action = {
case (m@osc.Message("/test", value: Int), s) =>
if (value == 1) synth = Synth.play(ssa.name)
if (value == 2) println("recu 2"); synth.release(2.0) //synth.free()
}
}
compilation is ok, free() works fine, but release() has no action still :/
val synth = play {}
That's the same as in SuperCollider - using play { }
will add an envelope that has a control gate
that is used by release
. release
is just a convention for synth.set("gate" -> -1 - releaseTime)
(or similar). So if you want to add a similar kind of envelope to a SynthDef
you either have to create an EnvGen
with a gate
argument, or you use the pseudo-UGen WrapOut
instead (that's the thing that play {}
uses):
SynthDef.recv("test") {
val sig = WhiteNoise.ar(Seq(0.2, 0.2))
WrapOut(sig) // !
}
val x = Synth.play("test")
x.release(10)
WrapOut
creates controls "out"
(for bus) and "gate"
(for release)
WrapOut
which when expanding adds the envelope. https://github.com/Sciss/ScalaCollider/blob/master/src/main/scala/de/sciss/synth/ugen/HelperElements.scala#L196
exception in GraphDef_Recv: UGen 'VBAP' not installed.
val a = VBAPSetup(2, Seq(Polar(-60,0), Polar(60,0), Polar(110,0), Polar(-110,0), 3.35))
val b = Buffer.alloc(serv, a.bufferData.size)
b.setn(a.bufferData)
SynthDef.write("synthTest.txt", MySynths.load(b), 1)
synthDefs = SynthDef.read("synthTest.txt")
val ssa = synthDefs.find(_.name == "soudscape-1").get
ssa.recv(serv)
MySynths.load(Buffer)
loads a Seq[SynthDef]
to store
write
will always write in the standard binary format of SuperCollider. Therefore, this is not a "text file".val sd = SynthDef("test") { ... }
sd.write(dir = "my-dir")
SynthDef.load(path = "my-dir/test.scsyndef")
// ... synchronise! then
Synth.play("test")
It is stored - the synth definition - but not the contents of buffer
. If you want to store this, you should also never hard-code the buffer id. This is just an integer number associated with a live buffer on the server. If you load a synth def with a hard-coded buffer id (here it will be just the constant that you happened to have received by the allocator at the time you created the synth-def), it will be pure luck that you get to have the same buffer again. And it will not be allocated unless you do so.
So in SuperCollider, for buffers it's always advisable - except for quick live coding - to use a control instead:
SynthDef("test") {
val bufId = "buf".kr
VBAP.ar(4, sig, bufId, ...)
...
}
val b = Buffer.alloc(...)
b.setn(...)
val x = Synth.play("test", args = Seq("buf" -> b.id))
That way the buffer-identifier becomes a parameter of the synth def.
Is there a particular reason you want to store the synth-def, anyway? I have stopped doing this at all, it's quick enough to generate it ad hoc.
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 ?