tfw u realize foo:baz() == foo.baz(foo)

1 Like

I think it should actually be print(foo:get_item("baz")(foo)) because the above is definitionally equal and will lead to the same error.

@tyleretters I think the problem is in how you defined bar_mixin.init, since you haven’t used get_item yet, the table won’t be have been made yet, so you don’t run into the error until you use foo:get_item("baz") (this is all wrong actually). There should be a way around needing the function wrapper, I just haven’t found it yet.

1 Like

yeah, my great scott was in response to foo:get_item("baz")(foo)! still super fugly but it gets the job done.

@andrew i am embarrased at how long it took me to understand the : vs . mystery.

related i’m doing something similar that i’d love to do better:

I guess I don’t understand why, but self.items[key] is actually a function (bar_mixin.init?), so changing that line to return self.items[key](self) makes foo:get_item("baz") work how I think you want it to.

good catch @Tyler !

like many lua subtleties it confused / weirded me out at first but I’ve grown pretty fond of it compared to javascript which throws in a this implicitly (which doesn’t lend itself to the workaround case that fixed this error)

also I’m being picky here but Foo isn’t strictly speaking a metatable, more like a class or protoype depending on how you think about it. { __index = Foo } is the metatable, which just stores a metamethod to point back to Foo for null members of the object / subtype (metatables are also solidly in the weird/confusing category, but they are sooo powerful)

yea I was thinking something along those lines - the get/register methods feel a little trivial / unnecessary in this example but it depends on if they play a bigger role outside of this example I suppose

4 Likes

ayeeee, thank you for that. i sorta wish the lua community would just put a stake in the ground and tell us what this type of pattern/object/class/construct/prototype/supertype ought to be called.

yeah this is absolutely a contrived and over simplified example. in the context of arcologies there are dozens of these mixins and adding a new one requires updating business logic in several different places. there are only going to be more mixins as time goes on and i want all the concerns to be encapsulated in one file for maintainability and ease of new developers adding their own. right now i’m working on replacing this.

2 Likes

You can make pretty simple table with functions like

funcs = {
   ['example_func'] = function bla bla return smth end, 
   ['another_func'] = function bla bla end
}

and then just call it like that -

funcs['example_func']()
2 Likes

right, that’s where this is headed. had to figure out how to get the mixins to register themselves to funcs! thank you.

and thank you @Tyler and @andrew. you totally made my day. :3

4 Likes

what are some best practices around clock.sync(1)? i’m about to add midi delays into arcologies and changing that integer to a smaller float is terrifying.

delays are going to be the typical tempo divisions or time (ms)… so how low can i go with the float? if i want to go as fast as 32nd notes and .1 second, i dunno what that would make my least common denominator. or should i spin up a separate clock that isn’t coupled to my global playback just for the delay management?

this might get best visibility in Norns: clock, but i’m running 32nd-syncing clocks in cheat codes without any trouble. clock.sync(1) is always a 1/4 note, so clock.sync(1/8) should get you stable 32nd :slight_smile:

i like having separate clocks for different components, especially because it makes management + debugging easy.


where you’d want a lowest common denominator is if you want different steps to have different note length values – this gets into step sequencer territory, which might not be relevant to your goals, but it’s something that tripped me up at first because I thought that changing the clock.sync() argument would magically let me sequence.

programmatically changing the clock.sync() value is not the same as saying “gimme a quarter note and then an eighth note and then a whole note and then a…” – it’s just saying that “when the next clock tick of x value happens, then do y”.
so if you clock.sync(1/4) and then immediately clock.sync(1/2), it won’t be the same as a 16th note followed by an eighth note, because you’ll have to wait a 16th note until the next eighth note clock value occurs. does that explanation make sense? it’s wibbly and just based on hours of being like “the heck is happening?”, so happy to put better words into it.

instead, I’ve had success using a “runner” that has an awareness of a given step’s duration at the lowest common denominator, driven by a clock at the lowest common denominator. so, if i want to have a 1/4 note step followed by an 1/8th note step followed by a 1/32nd note, i’d do something like this:

function advance(target)
  while true do
    clock.sync(1/8)
    if runner == 1 then
      play_note_or_whatever()
    end
    if runner == step_duration[step] then
      step = step + 1
      runner = 0
    end
    if step > seq_end_point then
      step = seq_start_point
    end
    runner = runner + 1
    end
  end
end

where step_duration is a table of durations (defined by how many ticks it takes to count a step value at the desired resolution). so, a 1/4 note step in a sequencer that goes down to 32nd notes would mean 8 ticks, because 8/32 = 1/4.

hope that helps?

minor correction: only in a 4/4 (or, more generally, n/4) song :slight_smile:

1 Like

ahh yes, this is clicking. the way i have it setup is i have this concept of “registering notes”:

which handles duplicate notes etc. then i subtract from the note’s duration each beat:

sorta like each note has hit points and each tick is an attack against those hit points.

so the notion of a runner is exactly where i think i need to go. thanks for such a detailed explanation!!

2 Likes

here is my solution! Athenaeum


(EDIT can’t create new posts consecutively)

i’m working on a livecoding feature for the REPL. is there a way to call functions without typing the () afterwards? since this is live coding, terseness is highly valued! i don’t want to type ().

example = {}

function example.init()
  example.foo1 = function() example:bar() end
  example.foo2 = function() return example:bar() end
  example.foo3 = load("example:bar()")
  example.foo4 = example.bar
end

function example:bar() 
  print("hi")
end

example.init()

with all the above examples i have to append the parenthesis.

# script run
>> reading PMAP /home/we/dust/data/dev/untitled/untitled.pmap
m.read: /home/we/dust/data/dev/untitled/untitled.pmap not read.
<ok>
Engine.register_commands; count: 0
___ engine commands ___
___ polls ___
amp_in_l
amp_in_r
amp_out_l
amp_out_r
cpu_avg
cpu_peak
pitch_in_l
pitch_in_r
# script init
example.foo1
function: 0x3a08a8
example.foo2
function: 0x415de8
example.foo3
function: 0x415140
example.foo4
function: 0x3c4700
example.foo1()
hi
<ok>
example.foo2()
hi
<ok>
example.foo3()
hi
<ok>
example.foo4()
hi
<ok>

You should be able to do this with a metatable with an __index function — something like

example = {}
example.methods = {
  foo1 = function() ... end
  foo2 = function() ... end
}
setmetatable(example, {
  __index = function(self, index)
    if self.methods[index] ~= nil then
      return self.methods[index]()
    end
  end
})

Accessing the “pseudo-field” example.foo1 would call example.methods.foo1() behind the scenes.

Not omitting the parentheses would then be like calling example.methods.foo1()(): calling the function, then calling its return value. I doubt the __index function has any way of knowing whether the field being indexed is going to be called as a function or not, so I don’t think you can make parens and no-parens behave identically (all the methods could return themselves at the end, but then calling them with parens would still mean calling them twice).

edit: if you don’t need them to return anything else, they could all return a dummy function that did nothing (or __index could return it, that would save you a few repeated lines of code). Then example.foo1() would work like example.methods.foo1(); noop()

5 Likes

ahhh thank you. this is super close now. any idea why the REPL spits out a nil afterwards? i tried messing around with __newindex too but it wasn’t doing anything:

example = {}
example.methods = {
  foo1 = function() print("this is magic!") end,
  foo2 = function() print("this is sparta!") end
}
e = setmetatable(example, {
  __index = function(self, index)
    if self.methods[index] ~= nil then
      return self.methods[index]()
    end
  end,
  __newindex = function(self, k, v)
    -- doesn't do anything?
    print(k, v)
  end
})

in the REPL, e.foo1 outputs:

this is magic!
nil

2 Likes

nil is probably the return value of the call to foo1().

__newindex should run if you call e.foo1 = 'bar' – if it doesn’t, that’s strange…

2 Likes

ahh, brilliant. this does the trick!! foo1 = function() print("this is magic!") return "" end

yes, e.foo1 = 'bar' behaves as expected. i misunderstood how that metamethod works.

THANK YOU!!

3 Likes

Hi. I’ve stumbled my way through a POC of my first norns script here. I’d appreciate being skewered with best practices.

3 Likes

Looks pretty good to me! You’ve managed to do a lot with a pretty terse script.

From a readability place it’s pretty good. The naming could be improved a little (some functions are unclear what they do). Perhaps write a single paragraph comment at the top describing the general structure as I skimmed the code but didn’t grok how it works just that it looks like i could understand it with some fine attention.

Personally when writing lua ternary statements (the and or idiom), I wrap the conditional in parens. It’s not necessary, and most Lua-familiar programmers identify it straight away, but it helps newer scripters to understand what’s going on IMO.

Looks nice though, and seems a fun game / music making crossover.

2 Likes

Thank you! These are great notes for my next pass.