live controller input

26 views
Skip to first unread message

Curtis Ullerich

unread,
Jul 7, 2022, 9:20:19 PM7/7/22
to beadsp...@googlegroups.com
Hi Beads fans,

I've been using Beads this week for the first time and really enjoying it. I'd like to make a UGen that produces live control data from a joystick (the GameTrak). I implemented one but it had some audible artifacts, so I wrote a very simple patch using MouseResponder to see how it behaved. I just tied the mouse x position to the pitch of a sine wave, and I can hear discrete pitch steps when I move the mouse left and right, which is unexpected/undesirable. This is with the default buffer of 512. A larger buffer (1024, 2048) eliminates the discontinuities, but now with noticeable lag. Smaller buffers result in more discontinuities. I realize that to an extent, this is just part of the game, but I'm wondering what I can do to improve it.

For example, I see in the implementation that MouseResponder interpolates linearly between positions read once per buffer. Maybe more frequent reads and a small buffer would do the trick? I tried something in a custom UGen using an internal Clock to fill bufOut once per sample tick, but it results in audible circular buffer overlaps, presumably because messageReceived and calculateBuffer are not mutually synchronized.

Here is my simple demo patch (in Kotlin) using Beads 3.2:
import net.beadsproject.beads.core.AudioContext
import net.beadsproject.beads.ugens.Plug
import net.beadsproject.beads.ugens.MouseResponder
import net.beadsproject.beads.ugens.Gain
import net.beadsproject.beads.data.Buffer
import net.beadsproject.beads.ugens.Function
import net.beadsproject.beads.ugens.WavePlayer

fun main() {
val ac = AudioContext(1024)
val mouseX = Plug(ac, MouseResponder(ac), 0)
val freq = object : Function(mouseX) {
override fun calculate() = scale(x[0], 0f, 1f, 200f, 600f)
}
val wp = WavePlayer(ac, freq, Buffer.SINE)
val g = Gain(ac, 1, 0.5f)
g.addInput(wp)
ac.out.addInput(g)
ac.start()
}

inline fun scale(n: Float, x1: Float, x2: Float, y1: Float, y2: Float) = (((n-x1)/(x2-x1)) * (y2-y1)) + y1
and here is my stab at using messageReceived.
import net.beadsproject.beads.core.AudioContext
import net.beadsproject.beads.core.Bead
import net.beadsproject.beads.core.UGen
import net.beadsproject.beads.data.Buffer
import net.beadsproject.beads.ugens.*
import net.beadsproject.beads.ugens.Function
import java.awt.MouseInfo
import java.awt.Toolkit

class MouseTick @JvmOverloads constructor(private val ac: AudioContext = getDefaultContext()) :
UGen(ac, 2) {
private val clock = Clock(ac, ac.samplesToMs(1.0).toFloat())
private val width = Toolkit.getDefaultToolkit().screenSize.width.toFloat()
private val height = Toolkit.getDefaultToolkit().screenSize.height.toFloat()

init {
ac.out.addDependent(clock)
clock.addMessageListener(this)
}

// Works, but not like you want it to, because this isn't synchronized with calculateBuffer
override fun messageReceived(message: Bead) {
val i = (clock.count % bufferSize).toInt()
val point = MouseInfo.getPointerInfo().location
bufOut[0][i] = point.x.toFloat() / width
bufOut[1][i] = point.y.toFloat() / height
}

override fun calculateBuffer() = Unit
}

fun main() {
val ac = AudioContext(2048)
val mouse = MouseTick(ac)
val mouseX = Plug(ac, mouse, 0)
val freq = object : Function(mouseX) { override fun calculate() = scale(x[0], 0f, 1f, 200f, 600f) }
val wp = WavePlayer(ac, freq, Buffer.SINE)
val g = Gain(ac, 1, 0.5f)
g.addInput(wp)
ac.out.addInput(g)
ac.start()
}
inline fun scale(n: Float, x1: Float, x2: Float, y1: Float, y2: Float) = (((n-x1)/(x2-x1)) * (y2-y1)) + y1
Thanks for any suggestions!
Curtis

Oliver Bown

unread,
Jul 7, 2022, 9:24:13 PM7/7/22
to beadsp...@googlegroups.com
Hi Curtis,

I haven’t looked at your code carefully but you should check out the Glide object. The Glide object creates a simpler linear transition. You can use it to control audio parameters. e.g.

wavePlayer.setFrequency(myGlide);

Then your input data plugs into the glide:

myGlide.setValue(valX);

Very excited to see you using Beads in Kotlin. 

Ollie

--
You received this message because you are subscribed to the Google Groups "beadsproject" group.
To unsubscribe from this group and stop receiving emails from it, send an email to beadsproject...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/beadsproject/CADqA8NfUa6-JTnSEOV-1eA%3DEtmf7zWpmeSTPx-psup1_YWgCRQ%40mail.gmail.com.

Curtis Ullerich

unread,
Jul 8, 2022, 2:37:35 AM7/8/22
to beadsp...@googlegroups.com
Thanks for the prompt suggestion Ollie; I gave it a shot. I tied the glide.setValue() updates to the clock (on a slow tick). messageReceived is getting called as expected, but I'm hearing what sounds like FM from the WavePlayer, yet the reported frequency of the glide and WavePlayer never change in successive calls. Not sure why it's stuck. Maybe I took your suggestion in an unexpected direction. I was originally thinking of implementing something like this with coroutines because that's analogous to how I would do this in ChucK, but encapsulating the controller in a UGen would be way nicer.
import net.beadsproject.beads.core.*
import net.beadsproject.beads.data.Buffer
import net.beadsproject.beads.ugens.*
import java.awt.MouseInfo
import java.awt.Toolkit

fun main() {
val ac = AudioContext()
val width = Toolkit.getDefaultToolkit().screenSize.width.toFloat()
val clock = Clock(ac, 1000f).apply { ticksPerBeat = 1 }
val wp = WavePlayer(ac, 1f, Buffer.SINE)
val glide = Glide()
val mouseTrigger = object : Bead() {
override fun messageReceived(msg: Bead) {
val point = MouseInfo.getPointerInfo().location
val x = point.x / width
val f = scale(x, 0f, 1f, 200f, 600f)
// Result: x tracks the mouse, but glide/wp get a starting value on the second message and never change
println("x $f, glide ${glide.currentValue}, wp ${wp.frequency}")
glide.value = f
}
}
clock.addMessageListener(mouseTrigger)
wp.setFrequency(glide)
val g = Gain(ac, 1, 0.5f).apply { addInput(wp) }
ac.out.addDependent(clock)

ac.out.addInput(g)
ac.start()
}
inline fun scale(n: Float, x1: Float, x2: Float, y1: Float, y2: Float) = (((n-x1)/(x2-x1)) * (y2-y1)) + y1

Curtis Ullerich

unread,
Jul 11, 2022, 1:49:08 PM7/11/22
to beadsp...@googlegroups.com
I think a more general question might be instructive. Are there common patterns for doing sub-frame timing synchronization in Beads (more granular than each call of calculateBuffer)?

I see some features like subtick in Clock. I've tried various things using Clock and also System.nanoTime(), and had poor results.

For context about this situation: The GameTrak produces input data at 125 Hz and I am maintaining a deque of recent events in another thread, tagging them with the event time, and trying to place the events in bufOut in the correct position.

Oliver Bown

unread,
Jul 24, 2022, 5:33:55 AM7/24/22
to beadsp...@googlegroups.com
Sorry, was on holiday.

That’s about it.
I can’t see anything wrong with your code. I mean, I’d normally be grabbing the mouse position in a different thread, since glide is tolerant of things running at different rates. Glide does work with straight line segments so there may be discontinuities. I’ve never noticed artefacts from this but conceivably there are, and we’d need to make a glide substitute that uses easing.

Best,

Ollie

Reply all
Reply to author
Forward
0 new messages