see here for how the menu system does it.

did you first call

audio.tape_record_open()

with a filename?

did you write some code to modify norns.state.tape? it shouldn’t reset otherwise.

1 Like

i know you read this elsewhere, but by design you shouldn’t need to clean up.

midi.cleanup() is run by script.clear() which happens when you reload the script.

can you describe the symptom more precisely? what is the time lag of ghost messages?

Thanks - I should’ve looked for that menu code, this is helpful. I did not call audio.tape_record_open - it wasn’t intuitive to me that first I had to “open” a file before recording to it (I wasn’t sure how much tape_record_start was supposed to do for me under the hood).

The only global I’ve been messing around with is the norns.vu callback, but I didn’t think that would cause any issues. Not sure how my tape state got weird but I’ll just rename my files and let you know if I can reproduce the issue.

you can also edit /home/we/dust/data/system.state to change your tape counter (requires a RESTART after)

Can someone give me an example of using a formatter with a param controlspec?

I have added a control but it’s returning a float value and I’d like that to display as a whole number. Would a formatter help me here?

  cs_bpm = controlspec.new(1,480,'lin',0,120,'')
  params:add{type="control",id="bpm",controlspec=cs_bpm,
    action=function(x) self:bpm_change(x) end}

(changing from a number to a control param) The value I get jumps by 5-6 values when I turn an encoder with the control param. Would using delta be better here? If so, an example of how to use that?

The docs are lacking in these areas.

EDIT - I just looked at github jssues and this one pretty much describes why I’m having this problem. If any of the devs can point me to any work in this params area I’d be keen to help (if I can)

1 Like

yeah, JS objects ~== Lua tables, and prototypes ~== metatables.

there’s no ‘official’ way to implement classes in lua, but this same pattern is used in norns core modules a lot (metro, grid, &c):

foo.lua:

local Foo = {}
Foo.__index = Foo

Foo:new ( props ) 
  local f = props or {}
  setmetatable(f, Foo)
  return f
end

function Foo:method()
   print ('i am foo')
end

return foo

you can also do inheritance:

Bar = Foo:new()

that’s it! now Bar:new() will return a table identical to Foo, but named differently.
so you can “override methods” or add new ones or whatever

function Bar:method() 
  print ('i am bar')
end
6 Likes

honestly a huge fan of lua classes. I love that they’re syntactically the same thing as arrays mostly.

For the sole purpose of learning some basic skills I’m trying to swap the Polysub engine used in the Earthsea script with Passersby.
I’ve already done it with fugu without any problem, just by changing the engine references. Did the same with earthsea, but the script doesn’t start

#16 local passersby = include 'passersby/lib/passersby_engine'
#34 engine.name = 'Passersby'
#62 passersby:params()

seem to be the only three times the engine is recalled and i just swapped the engine name and included the right directory.
Is there something else that needs to be done?

Here’s the earthsea script for reference
https://raw.githubusercontent.com/tehn/ash/master/earthsea.lua

:slight_smile:

Check the error output in Maiden - it looks like passersby_engine doesn’t have a function called params, but if you try passersby:add_params() instead it might work.

Not sure this will help your script not starting, but if you haven’t already you might want to swap the syntax for note generation… something like this:

Polysub:
engine.start(e.id, getHzET(note))

Passersby:
engine.noteOn(e.id, getHzET(note), vel)

Thank you! It was easier than I thought! The noteOn and noteOff params were different from the polysub start and stop! Now it works!

Thanks also to @nattog !!
:slight_smile:

2 Likes

I’ve done the basic Norns tutorials and I feel I am ready to try and tackle some sound processing, as opposed to just playing samples.

Is there an example file somewhere that just adds a single effect to incoming audio, like adds distortion to whatever is coming via Input A.

Thanks a lot :slight_smile:

here’s one that just applies gain:

1 Like

Perfect!
Thanks a lot, I will give it a shot.

Let’s say I didn’t ask here, would I have been able to find that somewhere?

I’m looking for the levels.lua example, if anyone has it I would love to see how I can grab incoming audio signal.

Edit: Found it.

After the basic earthsea-passersby, the next exercise I’m doing is adding the earthsea keyboard and pattern to the whole passersby-tunnel script. Everything works fine except for the pattern record (I can record, but I can’t play the pattern).
Here’s the maiden error output that is displayed after pressing the pattern rec button to play it and which I’m not skilled enough to understand.
I think it has something to do with the recall of this pattern.time.lua file, but I don’t understand what to do
lua: /home/we/norns/lua/lib/pattern_time.lua:78: attempt to call a nil value (field ‘process’)

stack traceback:

/home/we/norns/lua/lib/pattern_time.lua:78: in function 'pattern_time.start'

/home/we/dust/code/ash/passtunnearth.lua:649: in field 'key'

/home/we/norns/lua/core/grid.lua:183: in function </home/we/norns/lua/core/grid.lua:174>

lua: /home/we/norns/lua/lib/pattern_time.lua:78: attempt to call a nil value (field 'process')

stack traceback:

/home/we/norns/lua/lib/pattern_time.lua:78: in function 'pattern_time.start'

/home/we/dust/code/ash/passtunnearth.lua:649: in field 'key'

/home/we/norns/lua/core/grid.lua:183: in function </home/we/norns/lua/core/grid.lua:174>

thank you for your time :smiley:

Is pattern time in scope?

Yes, I think i put it in the global scope,

local MusicUtil = require "musicutil"
local UI = require "ui"
local Graph = require "graph"
local EnvGraph = require "envgraph"
local Passersby = require "passersby/lib/passersby_engine"

local tab = require 'tabutil'
local pattern_time = require 'pattern_time'
local g = grid.connect()
local mode_transpose = 0
local root = { x=5, y=5 }
local trans = { x=5, y=5 }
local lit = {}
local screen_framerate = 15
local screen_refresh_metro

local ripple_repeat_rate = 1 / 0.3 / screen_framerate
local ripple_decay_rate = 1 / 0.5 / screen_framerate
local ripple_growth_rate = 1 / 0.02 / screen_framerate
local screen_notes = {}



local tn = include('tunnels/lib/tunnel')

local SCREEN_FRAMERATE = 15
local screen_refresh_metro
local screen_dirty = true

local midi_in_device
local active_notes = {}

local pages
local tabs
local tab_titles = {{"Wave", "FM"}, {"Env", "Reverb"}, {"LFO", "Targets"}, {"Fate"}, {"Tunnels"}}

local input_indicator_active = false
local wave_table = {}
local wave = {}
local wave_graph
local SUB_SAMPLING = 4
local fm1_dial
local fm2_dial
local env_graph
local env_status = {}
local env_status_metro
local spring_path = {}
local reverb_slider
local lfo_graph
local dice_throw_vel = 0
local dice_throw_progress = 0
local dice_thrown = false
local dice_need_update = false
local dice = {}
local drift_dial

local timbre = 0
local wave_shape = {actual = 0, modu = 0, dirty = true}
local wave_folds = {actual = 0, modu = 0, dirty = true}
local fm1_amount = {actual = 0, modu = 0, dirty = true}
local fm2_amount = {actual = 0, modu = 0, dirty = true}
local attack = {actual = 0, modu = 0, dirty = true}
local peak = {actual = 0, modu = 1, dirty = true}
local decay = {actual = 0, modu = 0, dirty = true}
local reverb_mix = {actual = 0, modu = 0, dirty = true}
local lfo_shape = {dirty = true}
local lfo_freq = {dirty = true}
local lfo_destinations = {dirty = true}
local drift = {actual = 0, dirty = true}

engine.name = "Passersby"

here’s the section that controls grid input and pattern
– GRID input

pat = pattern_time.new()
  pat.process = grid_note_trans
local MAX_NUM_VOICES = 16


function g.key(x, y, z)
  if x == 1 then
    if z == 1 then
      if y == 1 and pat.rec == 0 then
        mode_transpose = 0
        trans.x = 5
        trans.y = 5
        pat:stop()
        engine.noteOffAll()
        pat:clear()
        pat:rec_start()
      elseif y == 1 and pat.rec == 1 then
        pat:rec_stop()
        if pat.count > 0 then
          root.x = pat.event[1].x
          root.y = pat.event[1].y
          trans.x = root.x
          trans.y = root.y
          pat:start()
        end
      elseif y == 2 and pat.play == 0 and pat.count > 0 then
        if pat.rec == 1 then
          pat:rec_stop()
        end
        pat:start()
      elseif y == 2 and pat.play == 1 then
        pat:stop()
        engine.noteOffAll()
        stop_all_screen_notes()
        nvoices = 0
        lit = {}
      elseif y == 8 then
        mode_transpose = 1 - mode_transpose
      end
    end
  else
    if mode_transpose == 0 then
      local e = {}
      e.id = x*8 + y
      e.x = x
      e.y = y
      e.state = z
      pat:watch(e)
      grid_note(e)
    else
      trans.x = x
      trans.y = y
    end
  end
  gridredraw()
end


function grid_note(e)
  local note = ((7-e.y)*5) + e.x
  if e.state > 0 then
    if nvoices < MAX_NUM_VOICES then
      --engine.start(id, getHz(x, y-1))
      --print("grid > "..id.." "..note)
      engine.noteOn(e.id, getHzET(note), vel)
     
      lit[e.id] = {}
      lit[e.id].x = e.x
      lit[e.id].y = e.y
      nvoices = nvoices + 1
    end
  else
    if lit[e.id] ~= nil then
      engine.noteOff(e.id)
     
      lit[e.id] = nil
      nvoices = nvoices - 1
    end
  end
  gridredraw()
end

function grid_note_trans(e)
  local note = ((7-e.y+(root.y-trans.y))*5) + e.x + (trans.x-root.x)
  if e.state > 0 then
    if nvoices < MAX_NUM_VOICES then
      --engine.start(id, getHz(x, y-1))
      --print("grid > "..id.." "..note)
      engine.noteOn(e.id, getHzET(note))
      start_screen_note(note)
      lit[e.id] = {}
      lit[e.id].x = e.x + trans.x - root.x
      lit[e.id].y = e.y + trans.y - root.y
      nvoices = nvoices + 1
    end
  else
    engine.noteOff(e.id)
    stop_screen_note(note)
    lit[e.id] = nil
    nvoices = nvoices - 1
  end
  gridredraw()
end

function gridredraw()
  g:all(0)
  g:led(1,1,2 + pat.rec * 10)
  g:led(1,2,2 + pat.play * 10)
  g:led(1,8,2 + mode_transpose * 10)

  if mode_transpose == 1 then g:led(trans.x, trans.y, 4) end
  for i,e in pairs(lit) do
    g:led(e.x, e.y,15)
  end

  g:refresh()
end

function note_on(note, vel)
  if nvoices < MAX_NUM_VOICES then
    --engine.start(id, getHz(x, y-1))
    engine.start(note, getHzET(note))
    start_screen_note(note)
    nvoices = nvoices + 1
  end
end

function note_off(note, vel)
  engine.stop(note)
  stop_screen_note(note)
  nvoices = nvoices - 1
end


function midi_event(data)
  if #data == 0 then return end
  local msg = midi.to_msg(data)

  -- Note off
  if msg.type == "note_off" then
    note_off(msg.note)

    -- Note on
  elseif msg.type == "note_on" then
    note_on(msg.note, msg.vel / 127)
end
end
function cleanup()
  stop_all_screen_notes()
  pat:stop()
  pat = nil
end

I would focus on this - I’m not sure where grid_note_trans is defined, but your instance of pattern_time thinks it’s nil.

Update: I see grid_note_trans is defined in the earthsea script. You would have to copy that entire function from earthsea, but then that function also references other things that are scoped to earthsea, so you’re going to run into more errors with that and would have to figure out how to copy those things also.

I think I copied the entire grid_note_trans function from earthsea, which is defined later in my script and I think all the variables scoped in the earthsea script should be in my global scope too.
Not sure I did all the right things though, it’s the first time I face so many variables

My guess is that at the time this is evaluated: pat.process = grid_note_trans

grid_note_trans is nil because it is defined further down in your script. Try moving the grid_note_trans function above that line, or moving the code that sets pat and pat.process into the init method