Jump to content
  • Sky
  • Blueberry
  • Slate
  • Blackcurrant
  • Watermelon
  • Strawberry
  • Orange
  • Banana
  • Apple
  • Emerald
  • Chocolate
  • Charcoal
  • 0
ManIkWeet

Playing multiple notes instantly, also memory leak?

Question

Hi,

 

I am new to OC, coming from CC and I want to make my music player in OC...

The issue I had with CC was that it was not possible to play multiple notes at the same time, but this was fixed with the information here:

http://www.computercraft.info/forums2/index.php?/topic/18995-openperipherals-play-2-or-more-noteblocks-at-the-same-time/

 

Now the 2 solutions available for CC don't seem to be available in CC so I tried around a bit with events... Didn't work as intended and even got a crashing computer out of it...

 

Here's my code:

local component = require("component")

local counter = 1
local noteblocks = {}
for address, componentType in component.list("music") do
  print(tostring(address) .. "," .. componentType)
  noteblocks[counter] = component.proxy(address)
  counter = counter + 1;
end


for _,nb in pairs(noteblocks) do 
  nb.trigger(10)
end


local computer = require("computer")
local event = require("event")

function trigger()
  component.musicblock.trigger(15)
end

event.listen("note", trigger)

for i=1, 10 do
  computer.pushSignal("note")
end
event.pull()
print("finish")

To replicate my issue, just place an adapter anywhere with one or more noteblocks connected to it.

If you run the program you'll hear a clear delay between each note, music will need multiple notes at once...

Preferably I want multiple notes to play instantly out of one single noteblock, but if that's impossible then I guess multiple will have to do...

 

As for the memory leak:

Running this part of the code

local computer = require("computer")
local event = require("event")

function trigger()
  component.musicblock.trigger(15)
end

event.listen("note", trigger)

for i=1, 10 do
  computer.pushSignal("note")
end
event.pull()

Multiple times it will:

1. keep playing more and more notes at the end.

2. eventually crash, saying it ran out of memory.

Link to post
Share on other sites

11 answers to this question

Recommended Posts

  • 0

it's odd that you say your issue was "fixed" as that's not what the CC parallel api does...  

 

you must realise that lua isn't multitasking in a true sense, it's cooperative multi tasking, in that it can only do the "other task" when the first task isn't doing anything... i.e. has yielded or ended...   as lyqyd said on the thread you cite, the "solution" with parallel should actually work *less* well than just calling the two note playing lines one after the other...  if you look into what the parallel API does, it's just a wrapper for coroutine.create / start and status checks... it's not doing anything magic [ or actually parallel, apart from keeping the coroutines managed ] 

 

to replicate - with less lua overhead than CC's Parallel api, just do: [ i'm writing this in a way it can be just poked into an interactive lua session ]

t = function() while true do component.musicblock.trigger( math.random(1,24) ) coroutine.yield() end end
c1 = coroutine.create( t )
c2 = coroutine.create( t )
play = function() while true do  if keyboard.isKeyDown(keyboard.keys.p) then coroutine.resume(c1)  coroutine.resume(c2)  end  event.pull() end
play()

[ press "p" to play a note, ctrl-C to break, if that wasn't apparent ] 

sometimes there'll be a delay, sometimes not - but that's just in the luck of the ticks :) i get a chord 50% of the time i press "p" on my server. 

but that's with 1 block... and i doubt that 1 block is "reliable" or helping the lua be quick, as i'm sure the vanilla block itself isn't as willing to play 2 notes at once as we'd like :)

 

so... i did - after adding another note block to the adapter 

[ again, just in a lua prompt here - and using a bit more melodic patterns :) ]

mbs = {}
for k,v in pairs(components.list()) do if v == "musicblock" then table.insert(mbs,component.proxy(k)) end end
t1 = function() while true do local note = 1 mbs[1].trigger( note ) note = ( note + 4 ) % 24 + 1 coroutine.yield() end end
t2 = function() while true do local note = 4 mbs[2].trigger( note ) note = ( note + 4 ) % 24 + 1 coroutine.yield() end end
c1 = coroutine.create( t1 )
c2 = coroutine.create( t2 )
play = function() while true do  if keyboard.isKeyDown(keyboard.keys.p) then coroutine.resume(c1)  coroutine.resume(c2)  end  event.pull() end
play()

and that, for me, plays simultaneously _most_ of the time... so i think 2 noteblocks helps

 

and holding down p forever, never crashes - it gets a bit behind, naturally... but it never crashes...  

 

not sure if that helps you, but it may help you look at the problem another way... 

 

that is to say, wrap the function which is triggering the note in a coroutine, and call it that way, i'm being lazy in my example and not resuming with parameters to the function... but note that coroutines only resume from the start when they have not yet started... subsequent resumes come in at the point the yield yielded... 
my example is while true do end infinite looping, just to make it fun :) 
 

sorry no more help, i'd have to look more into the computer event stacking internals to figure out more... 


 

Link to post
Share on other sites
  • 0

The solution that you propose doesn't fire the 2 noteblocks at once, there still is a slight delay between them...

 

Sadly OpenPeripherals (the mod to get Noteblocks to CC) doesn't work in 1.7.10 yet, so I have to downgrade to this horrible 1.6.4 that noone likes to demonstrate... will do that in a little while when I got more time...

 

If you fire the 2 noteblocks with a redstone signal, you hear a single sound, I want to replace the redstone with the computer...

 

Here's your code in a more readable and fixed matter:

local component = require("component")
local keyboard = require("keyboard")
local computer = require("computer")
local event = require("event")

local mbs = {}
for k,v in pairs(component.list()) do 
  if v == "musicblock" then 
    table.insert(mbs,component.proxy(k)) 
  end 
end
t1 = function() 
  local note = 1
  while true do 
    --local note = 1 
    mbs[1].trigger( note ) 
    note = ( note + 1 ) % 24 + 1 
    coroutine.yield() 
  end 
end
t2 = function() 
  local note = 4
  while true do 
    --local note = 4 
    mbs[2].trigger( note ) 
    note = ( note + 1 ) % 24 + 1 
    coroutine.yield() 
  end 
end
c1 = coroutine.create( t1 )
c2 = coroutine.create( t2 )
play = function() 
  while true do  
    if keyboard.isKeyDown(keyboard.keys.p) then 
      --for i=1, 10 do
        coroutine.resume(c1)  
        coroutine.resume(c2)  
      --end
    end  
    event.pull() 
  end
end

play()
Link to post
Share on other sites
  • 0

i thought mine was perfectly readable :)

- but my intent with the formatting was so it was copy-pastable into the lua prompt as a quick test, hence how it require the "requires"

 

anyway... that aside.

 

if you use the IronNote block from 1.7.10 Computronics - compatible with CC 1.64pr4 and OC , it works - simultaneously all the time :)

 

just tried it.

 

but that's only if i don't use event pushing...

 

if i use event pushing, then i get the chord of played twice with a lag

 

i.e.

[ on OC ] play = component.iron_noteblock.playNote 
[ on CC ] play = peripheral.wrap("iron_noteblock_0").playNote  -- assuming the block is on a modem, and the first one

chord = function() play(5) play(9) end

chord()  -- this plays simultaneously, ALL the time, on CC and OC in 1.7.10 - yeay

HOWEVER :( if i do this with event triggers - OC only of course, it didn't work.

 

i.e. as soon as i do

event.listen("note", chord)

computer.push("note")

it now plays the chord twice, with a 1 second lag, and not always at the same time... 

something funny is happening in the event chain there for sure, which i thought was a bug :(

 

OR so I thought. but this was because i'd [ in my lua session ] defined that listener more than once, whilst i was hacking about and testing... 

 

i think what's happening with your example code - when it runs out of space after running it multiple times, is that each time you call event.trigger( name, function) 

it pushes another event listener onto the stack, best to call event.ignore before the listen... 

 

the issue you have with the computer.pushSignal delay, is that when a signal is pushed, as per the wiki for that function, the events are processed in FIFO order, not simultaneously... 

 

but in general... use computronics iron note block... it seems to easily cope with 2+ notes at once... 

 

i.e. i can do

lua> play = component.iron_noteblock.playNote

lua> play(5) play(9) play(12)    and it consistently, plays the major chord with no delays or anything :))

Link to post
Share on other sites
  • 0

p.s. this has really made me want to do more music in MC - thanks for the inspiration!

I have made a small video displaying the issue I have: 

 

I have used the following programs:

ComputerCraft:

sleep(2)
local note1 = peripheral.wrap("left")
local note2 = peripheral.wrap("right")

function play1()
  note1.triggerNote()
end

function play2()
  note2.triggerNote()
end

c1 = coroutine.create(play1)
c2 = coroutine.create(play2)

coroutine.resume(c1)
coroutine.resume(c2)
 

OpenComputers:

local component = require("component")
local keyboard = require("keyboard")
local computer = require("computer")
local event = require("event")

local mbs = {}
for k,v in pairs(component.list()) do 
  if v == "musicblock" then 
    table.insert(mbs,component.proxy(k)) 
  end 
end
t1 = function() 
  local note = 1
  while true do 
    --local note = 1 
    mbs[1].trigger( note ) 
    note = ( note + 1 ) % 24 + 1 
    coroutine.yield() 
  end 
end
t2 = function() 
  local note = 1
  while true do 
    --local note = 4 
    mbs[2].trigger( note ) 
    note = ( note + 1 ) % 24 + 1 
    coroutine.yield() 
  end 
end
c1 = coroutine.create( t1 )
c2 = coroutine.create( t2 )
play = function() 
  os.sleep(2)
  --while true do  
    --if keyboard.isKeyDown(keyboard.keys.p) then 
      --for i=1, 10 do
        coroutine.resume(c1)  
        coroutine.resume(c2)  
      --end
    --end  
    --event.pull() 
  --end
end

play()

So clearly there's a difference between how ComputerCraft handles noteblocks and how OpenComputers handles noteblocks...

Still looking for the solution for OpenComputers :)

 

Also, you're welcome for the inspiration :D

Link to post
Share on other sites
  • 0

you couldn't have been using those programs for that video tho :) 

[ as my example requires you to press "p" to make a note play ]

 

but that's by-the-by... i'm guessing you used a simpler thing, it's obvious that there is a lag there, perhaps it's to do with how the adapter works.

 

 

did you try my suggestion of using Computronics' iron note block? as i say - that seemed to work perfectly for me [ in 1.7.10 ] 

 

i.e. i'm using 

play = component.iron_noteblock.playNote

majorTriad = function(root) play(root) play(root+4) play(root+7) end
minorTriad = function(root) play(root) play(root+3) play(root+7) end

majorTriad(1)
os.sleep(0.25) 
minorTriad(3)

.. which does it perfectly :) 

Link to post
Share on other sites
  • 0

you couldn't have been using those programs for that video tho :)

[ as my example requires you to press "p" to make a note play ]

 

but that's by-the-by... i'm guessing you used a simpler thing, it's obvious that there is a lag there, perhaps it's to do with how the adapter works.

 

 

did you try my suggestion of using Computronics' iron note block? as i say - that seemed to work perfectly for me [ in 1.7.10 ] 

 

i.e. i'm using 

play = component.iron_noteblock.playNote

majorTriad = function(root) play(root) play(root+4) play(root+7) end
minorTriad = function(root) play(root) play(root+3) play(root+7) end

majorTriad(1)
os.sleep(0.25) 
minorTriad(3)

.. which does it perfectly :)

But vanilla noteblocks are so much cooler ;)

Thanks on the heads up of the memory leak btw.

Link to post
Share on other sites
  • 0

But vanilla noteblocks are so much cooler ;)

 

hehe.... yeah, i do think it's a shame the iron note blocks don't do the musical particles... 

 

aye, but with the iron noteblock, one can do playNote(instrument, note) and therefore not have to worry about what the block is standing on...

 

- also, for some reason, you get an extra instrument... instrument 0 is more "harpy" to 5's "piano" 

 

 

i made a start on writing a polyphonic Matrisequencer last night...  i'm using multiple note blocks for "surround sound" polyphony, and assigning each note in a chord to a different note block... this way you can stand in the middle of them and spin round and get a Leslie speaker effect... 

 

below isn't how the sequence pattern will work, it was more my prototyping just to check what i needed,

i'm going to change the way the "sequence" table works away from needing a duration, and instead make it phrase/bar based, with the tempo markers/time signature changes within the table so that the table can be independently transferable - mainly so i can have an "output computer" and a "programming computer" - so that the time dedicated to updating the touch screen on the programming computer doesn't effect the timing of the musical pattern player....

--[[ seq.lua ]]--

local component = require("component")

local loadDevices = function()
  local devs = { }
  for k,v in pairs(component.list()) do
    if v == "iron_noteblock" then
      table.insert(devs, component.proxy(k))
    end
  end
  return devs
end

local devices = loadDevices()

local tempo = 240

local lengths = {
    br = 2,
    sb = 1,
    mi = 0.5,
    cr = 0.25,
    qu = 0.125,
    sq = 0.0625,
  }
  
local duration = function(tempo, name)
  return 1 / (tempo/60) * lengths[name]
end

local instruments = {
  harp = 0,
  bd = 1,
  snare = 2,
  rim = 3,
  bass = 4,
  piano = 5.
}

local keys = {
                                                          Fs1 = 0,  G1 = 1, Gs1 = 2,  A1 = 3,  As1 = 4,  B1 = 5,
  C2 = 6,  Cs2 = 7, D2 = 8,  Ds2 = 9,  E2 = 10,  F2 = 11, Fs2 = 12, G2 =13, Gs2 = 14, A2 = 15, As2 = 16, B2 = 17,
  C3 = 18, Cs3 = 19,D3 = 20, Ds3 = 21, E3 = 22,  F3 = 23, Fs3 = 24
  }

local sequence = {
    { inst="piano",   notes={"C2", "E2", "G2"}, length="sb" },
    { inst="harp",   notes={"D2", "A2" }, length="sb" },           
    { inst="piano",   notes={"E2", "G1"}, length="cr" },          
    { inst="rest",    length="cr" },          
    { inst="harp",   notes={"G1", "B1", "D1"}, length="sb" },  
    { inst="piano",   notes={"D2", "F2", "A2"}, length="cr" },      
    { inst="rest",    length="cr" },    
  }
  

local play = function( seq )
  
  for _,event in pairs( seq ) do
    
    dur = duration( tempo, event.length )
    -- print( event.inst, event.length, dur)
    if event.inst == "rest" then
      os.sleep(dur)
    else
      for i, note in ipairs(event.notes) do
        -- print(note)
        devices[i].playNote( instruments[event.inst] ,keys[note])
      end
      os.sleep(dur)      
    end
    
  end
  
  
end

while true do play(sequence) end

  
Link to post
Share on other sites
  • 0

I made a step sequencer as my first project for OC, I ran into this problem too.

My solution as yet unmade was to have multiple computers each with a copy of the 'score'waiting for a midi tick from a server.

My code is still in the showcase if you'd like to take a look.

Multiple computers would be a solution, but it is not exactly a very eco-friendly (aka survival) solution, sadly I think I have to give up on this project...

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use and Privacy Policy.