monome / crow

Crow speaks and listens and remembers bits of text. A scriptable USB-CV-II machine
GNU General Public License v3.0
164 stars 34 forks source link

fast output (re)sets trip up asl #209

Closed pq closed 4 years ago

pq commented 4 years ago

hacking up a monosynth patch and hit this while playing around w/ note retriggers.

here's a distilled test case that repros for me on norns w/ crow v1.0.0:

-- crow stress test

function init()
end

local function sleep(n)
  os.execute("sleep " .. tonumber(n))
end

function redraw()
  print('stressing...')
  sleep(2)
  crow.send('^^v')
  for i=1,100 do
    print('>>')
    crow.output[2].volts = 0
    crow.output[2].action = 'ar(1,5)'
    crow.output[2].execute()
    sleep(0.01)
  end
  print('done')
end

salient repl output:

...
crow:   [string "local Asl = {}..."]:115: attempt to get length of a nil value (field 'exe')
crow:   no asl active

/cc @trentgill @tehn

trentgill commented 4 years ago

Thanks for the test script. I’ll investigate!

On 8 Oct 2019, at 08:37, Phil Quitslund notifications@github.com wrote:

hacking up a monosynth patch and hit this while playing around w/ note retriggers.

here's a distilled test case that repros for me on norns w/ crow v1.0.0:

-- crow stress test

function init() end

local function sleep(n) os.execute("sleep " .. tonumber(n)) end

function redraw() print('stressing...') sleep(2) crow.send('^^v') for i=1,100 do print('>>') crow.output[2].volts = 0 crow.output[2].action = 'ar(1,5)' crow.output[2].execute() sleep(0.01) end print('done') end salient repl output:

... crow: [string "local Asl = {}..."]:115: attempt to get length of a nil value (field 'exe') crow: no asl active /cc @trentgill @tehn

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

pq commented 4 years ago

likely related: https://llllllll.co/t/crow-help-general-connectivity-device-qs-ecosystem/25866/95

trentgill commented 4 years ago

from user Dewb on lines. seems similar: Sure, happy to help! Here's a distilled example. I'm using crow 1.0.0 but I saw similar effects prior to the update. I'm using the USB cable that came with crow in the leftmost norns USB port.

[details="test script"]

-- crow: example of setting action on clock

local BeatClock = require 'beatclock'
local clk = BeatClock.new()

local function step()
  local a = 4
  local b = 2
  local c = 5
  crow.output[1].volts = a
  crow.output[2].action = "pulse(0.1, 8, 0)"
  crow.output[2].execute()
  crow.output[3].volts = b
  crow.output[4].volts = c
end

function init()

  clk.on_step = step
  clk.on_select_internal = function() clk:start() end
  clk:add_clock_params()

  params:set("bpm", 91)
  params:default()

  crow.reset()

  clk:start()

end

function cleanup ()
  clk:stop()
end

[/details]

This works initially -- but the pulse sounds too fast, almost double-time. And eventually, sometime between a couple triggers and a couple minutes later, it starts failing with this error:

crow receive: [string "local Asl = {}..."]:115: attempt to get length of a nil value (field 'exe')
crow receive: [string "local Asl = {}..."]:115: attempt to get length of a nil value (field 'exe')
crow receive: [string "local Asl = {}..."]:115: attempt to get length of a nil value (field 'exe')
crow receive: [string "local Asl = {}..."]:115: attempt to get length of a nil value (field 'exe')
crow receive: [string "local Asl = {}..."]:115: attempt to get length of a nil value (field 'exe')
crow receive: [string "local Asl = {}..."]:115: attempt to get length of a nil value (field 'exe')

Sometimes, if the script is able to run for a while, periodic instances of this error also appear in the console. Sometimes the error is accompanied by a single missed pulse, but not always.

crow receive: [string "eval"]:1: attempt to index a nil value (field 'vooutput')

If I move the crow.output[2].action line into init() after crow.reset(), then I never see the local Asl errors, and the timing on the pulse is correct. I do occasionally still see the attempt to index a nil value message, but more rarely, sometimes with a dropped pulse also.

(In both cases, I also have about a 10% failure rate with hitting Play in maiden and having the script work, hitting Play again usually fixes it, maybe I need a delay after calling crow.reset()?)

trentgill commented 4 years ago

for exe to be a nil value it likely means .action is not receiving the arguments correctly. i'm guessing this is because of strings getting corrupted as they're sent norns -> crow.

the fact that it seems fine for some seconds to minutes before starting to fail suggests a memory issue. should try calling print(collectgarbage('count')) before the .action to see if failure is correlated to a higher RAM usage reading.

Dewb commented 4 years ago

After some reflection I think the nil value (field 'vooutput') error is a bigger issue than the repeated .action assignment issue, as it results in dropped triggers with no workaround. Does it seem like the two symptoms are related, or should I file a separate issue?

Dewb commented 4 years ago

The plot thickens. I did some more testing, and it looks like moving the crow.output[2].execute() to the end of step(), after the other outputs' .volts are set is successful in preventing the nil value (field 'vooutput') error. But I still got dropped triggers.

So I took a look at output 2 on an oscilloscope to see if the pulses were not being omitted, or if they were and Plaits was missing them. Surprisingly, the output was not a 0-8V pulse train, but a -5/+5 triangle LFO! (I'm guessing that's the default linear ramp LFO action behavior.) Changing the .action set in init() seemed to have no effect -- pulse, ar, whatever, I always got a triangle wave. (I think this explains the double-time triggers observed in my lines post.)

I moved the .action back into step(), and that made the oscilloscope output look as expected. I figured the .action assignment immediately after reset() is getting lost somehow, and indeed adding a metro delay of about 680ms in between calling crow.reset() and crow.output[2].action = ... seems to make the action take effect.

So it looks like there are three interesting things going on here:

trentgill commented 4 years ago

This seems like a race-condition between the slopes library & the usb-input, as it's norns specific. The same script running natively on crow doesn't have the problem.

Probably because either the .volts or .execute is creating a callback from C into Lua, right after the next command has been parsed & executed over USB.

I'm not entirely sure the best way to go about solving this. It seems the only reason to call .volts and .action in series is because you want to force the output to a predefined level before starting an envelope / lfo etc. Is the purpose pretty universally to set it to 0V before starting an envelope? If that's the desired behaviour, we can at least provide an additional argument to ar(attack-time, release-time, reset-to-zero) to make the issue less present until we can solve it.

pq commented 4 years ago

my use case was env re-trigger so something like reset-to-zero would do it!

trentgill commented 4 years ago

Fixed by #238