Unable to get playdate.sound.controlsignal to work

,

Platform: Mac (Simulator)

I've been exploring the audio APIs the last few days and for the life of me I can't seem to get control signals to do anything. I've created a control signal and added a bunch of random events to it. I also added it to an otherwise functioning track in a sequence, and also tried setting it as a parameter mod for various things: a delay line's mix amount on the main channel, parameters for various waveforms in synths playing on that track, etc. But no matter what I do there's no automation. All connected parameter mods act as though they're just receiving zeroes.

Here's some minimal sample code illustrating the problem

local snd = playdate.sound
math.randomseed(playdate.getSecondsSinceEpoch())

local control = snd.controlsignal.new()
for i = 1,256 do
  control:addEvent(i, math.random())
end

local lfo = snd.lfo.new()
lfo:setRate(0.2)
lfo:setDepth(1)

local synth = snd.synth.new(snd.kWavePODigital)
synth:setVolume(0.2)
synth:setAttack(0.01)
synth:setDecay(0.1)
synth:setSustain(0.1)
synth:setRelease(0)
-- This doesn't work (lfos seem fine, though)
synth:setParameterMod(1, control)

local instrument = snd.instrument.new()
instrument:addVoice(synth)

local track = snd.track.new()

track:setInstrument(instrument)
for i = 1, 16 do
  track:addNote(i, math.random(60, 71), 4)
end

-- Note: This crashes if you try to add a control without any events!
track:addControlSignal(control)

local sequence = snd.sequence.new()
sequence:setTempo(4)
sequence:setLoops(1, 16)
sequence:addTrack(track)

local delay = snd.delayline.new(0.275)
delay:setFeedback(0.7)
delay:setMix(0.0)
snd.addEffect(delay)
-- This doesn't work either
delay:setMixMod(control)

sequence:play()

Is this a known problem/bug, or does anyone have an example of some code that shows control signals working as expected? Or maybe a simulator issue? I don't yet have a real device to test on.

Thanks!

1 Like

Hey, also on a Mac running the sim. Did you ever sort this out or find a solution? I just got to where you were when you posted this. I'm able to do a lot with midi, and I'm able to print out the info for control signals within the midi. (their type, their data, etc) and even do 'if then' logic on what type of control signal it is.

all that, and when I set, say my_channel:setPanMod( my_control_signal )
i get no audible results. I can deal without, but gosh the music could start to get really interesting if i could get parameter automation to work.

idk if typing them in manually would work, but that's so inefficient compared to automating parameters in a DAW that it's honestly not even worth investigating rn.

sorry for the rant. lmk if you found a solution!

ps: im probably using the SDK incorrectly, more than likely lol

example console output that lets me know im at least getting signal controllers from my midi file
controllers

Sorry I missed this first time around, @frozenbears, and thanks @DJ_irl for resurrecting it! I dug into the code and found a logic glitch that keeps the control signal's timer from advancing unless you put an event at step 0. For track:addNote() I'm subtracting 1 from the step # to change from Lua's standard 1-based counting to C-style 0-based, I forgot to do that on controlsignal, so those will be off by one. :confused: I'll fix that in the next bugfix release.

Anyway, it's an easy fix for the controlsignals not working, just add a value for step = 0. Let me know if that doesn't fix it for y'all!

1 Like

new theory, see problem description below. and sorry for the essay

my values in my events are [0, 127] it appears. the channel pan effect only wants values [-1, 1]

feeding it values way over 1 may be causing an issue. im imaging writing a function that turns the numbers [0-127] into their relative position between [-1, 1] but im assuming this is what interpolate is supposed to do?

i wrote something that loops through the events of the control signal, and i can print out individual values for 'step,' 'value,' and 'interpolate.' But when I try to reset their values using the = operator I don't see any changes reflected in the events.

my theory is that if i could change, or interpolate, the event values to between -1 and 1 that might fix my issue.


hey thank you for the quick and actionable reply. really means a lot when stuck on a project. switching between 1 and 0 indexed languages is... let's call it fun! sorry to bother you with more but here's my results.

im getting some changes to audio now but they are bizarre. I took video, VOLUME JUMP WARNING.

with the addEvent line added to this code, I do hear a change. that's good. it's registering it exists. but the change is kinda crazy. rather than affecting the channel's pan, I get a huge jump in volume and distortion on what otherwise is a soft sine wave.

let me know if im doing anything you'd do differently.

more misc info

  • im on mac m1, simulator only for now
  • im automating the control signals inside of ableton
  • i am exporting midi files from ableton, then merging them via this method
-- from SDK example, slight modifications
function newsynth()
	local s = sfx.synth.new(sfx.kWaveSine)
	s:setVolume(0.3)
	s:setAttack(.4303)
	s:setDecay(0.37)
	s:setSustain(0.4)
	s:setRelease(.257)
	return s
end

function newinst(n)
	local inst = sfx.instrument.new()
	for i=1,n do
		inst:addVoice(newsynth())
	end
	return inst
end

-- my code
function mergeMidiTest()

  local midi = 'sounds/output.mid'
  s = sfx.sequence.new(midi)

  ch00 = sfx.channel.new()
  ch00:setVolume(.35)

local track1 = s:getTrackAtIndex(1)
  track1_control_signals = track1:getControlSignals()
  for i=1,#track1_control_signals do
    track1_control_signals[i]:addEvent(0,0, false)
    print("controller type: "..track1_control_signals[i]:getControllerType())
    if track1_control_signals[i]:getControllerType() == 10 then -- Pan
      local newMod =  track1_control_signals[i]
      ch00:setPanMod(newMod)
    end
  end
  
  local newInst1 = newinst(1)
  track1:setInstrument(newInst1)
  ch00:addSource(newInst1)

  s:setTempo(666)
  s:setLoops(1, s:getLength()+300, 0)
  printTable(track1_control_signals)
  print(track1_control_signals[1])
  s:play()
end

mergeMidiTest()

I'm not sure what the bug is but I can reproduce it so we're off to a good start. Gotta say, I do like an audio bug that makes an interesting sound instead of a horrible screeching eardrum blowout. :slight_smile:

panmodbuf.wav.zip (2.2 MB)

I'll let you know what I find.

1 Like

thanks for your effort on this. i really appreciate it.

and yeah, it sounds like it’s sending multiple instances of the audio through the panning effect and it’s stacking in odd ways, and possibly in an “off by one” way. that could account for the jump in volume and the phasing effect. potentially caused by the high numbers (sending a value of 10+ to an effect that only wants -1 to 1)

Found the bug--or one of them, anyway.. "Signal" objects, like LFOs and envelopes and controlsignals, are updated once per frame (256 samples) but can mark a specific time within the frame to change values, to allow sample-accurate timing at step changes. The bug is I'm not clearing that time offset when it doesn't need to flag a middle-of-frame update, so it's causing the signal value to jump around in odd ways. It's weird, I made a test case when I wrote this code, and it worked fine. I don't know what's changed.. Maybe the bug was there but didn't present in such an obvious way.

A lot of this synth engine code is in a similar state--I wanted a feature, so I wrote a demo and then added code to make it work, but it hasn't really been stress tested. Thank you for trying it out and reporting the bugs you find!

For future reference here's code demonstrating the bug, based on @frozenbear's demo:

snd = playdate.sound

instrument = snd.instrument.new()
synth = snd.synth.new(snd.kWaveSine)
instrument:addVoice(synth)

control = snd.controlsignal.new()
for i = 1,16 do control:addEvent(i-1, 2*math.random()-1, true) end

chan = snd.channel.new()
chan:setPanMod(control)
chan:addSource(instrument)

track = snd.track.new()
track:addControlSignal(control)
track:setInstrument(instrument)

for i = 1, 16 do track:addNote(i, math.random(60, 71), 4) end

local sequence = snd.sequence.new()
sequence:setTempo(4)
sequence:setLoops(1, 16)
sequence:addTrack(track)
sequence:play()

function playdate.update() end
1 Like

thanks for looking into this.

A lot of this synth engine code is in a similar state

no worries.

pd.sound is a work of art. i dont imagine most handhelds have ringmod, bit crusher, or independent multi-track parameter automation. i plan to make a library of instruments and effects. everything from string emulation, to woodwinds, to drums, to less traditional sounds. i plan to push it so i'll let you know what i find haha. with some late nights, and help from you, soon i'll have a game to show y'all with sick music

1 Like