Help with error message

Below is an error message and stack trace resulting from this:

(
Pn( (dur:Pn(1)) )
<> Pbind()
).play

It seems like it should work since this works:

(
Pn( (dur:1) )
<> Pbind()
).play

I can’t really understand the error message, though, to figure out what is going on.

-> an EventStreamPlayer
ERROR: Message 'schedBundleArrayOnClock' not understood.
RECEIVER:
Instance of Pbinop {    (0x11cf8d6a8, gc=9C, fmt=00, flg=00, set=02)
  instance variables [4]
    operator : Symbol '+'
    a : instance of Pbinop (0x11ac70fe8, size=4, set=2)
    b : Integer 0
    adverb : nil
}
ARGS:
Instance of TempoClock {    (0x1148a7fa8, gc=A0, fmt=00, flg=00, set=03)
  instance variables [7]
    queue : instance of Array (0x11487d1c0, size=1, set=11)
    ptr : RawPointer 0x7fd28a57bac0
    beatsPerBar : Float 4.000000   00000000 40100000
    barsPerBeat : Float 0.250000   00000000 3FD00000
    baseBarBeat : Float 0.000000   00000000 00000000
    baseBar : Float 0.000000   00000000 00000000
    permanent : true
}
Instance of Array {    (0x11a8c4e28, gc=9C, fmt=01, flg=00, set=02)
  indexed slots [1]
      0 : instance of Array (0x11acdd818, size=4, set=2)
}
   Float 0.000000   00000000 00000000
Instance of Server {    (0x1148b6908, gc=A0, fmt=00, flg=00, set=05)
  instance variables [30]
    name : Symbol 'localhost'
    addr : instance of NetAddr (0x1148a83c8, size=4, set=2)
    clientID : Integer 0
    isLocal : true
    inProcess : false
    sendQuit : true
    remoteControlled : false
    maxNumClients : Integer 1
    options : instance of ServerOptions (0x1148acbb8, size=38, set=6)
    latency : Float 0.200000   9999999A 3FC99999
    dumpMode : Integer 0
    nodeAllocator : instance of NodeIDAllocator (0x11b4fcca8, size=7, set=3)
    controlBusAllocator : instance of ContiguousBlockAllocator (0x1147b7f08, size=6, set=3)
    audioBusAllocator : instance of ContiguousBlockAllocator (0x11473e2a8, size=6, set=3)
    bufferAllocator : instance of ContiguousBlockAllocator (0x1146e84a8, size=6, set=3)
    scopeBufferAllocator : instance of StackNumberAllocator (0x11b3389f8, size=4, set=2)
    tree : nil
    defaultGroup : instance of Group (0x11b47c158, size=5, set=3)
    defaultGroups : instance of Array (0x11b3a8748, size=1, set=2)
    syncThread : nil
    syncTasks : nil
    window : nil
    scopeWindow : nil
    emacsbuf : nil
    volume : instance of Volume (0x1148b2278, size=15, set=4)
    recorder : instance of Recorder (0x1148b1e78, size=14, set=4)
    statusWatcher : instance of ServerStatusWatcher (0x1148b6b38, size=20, set=5)
    pid : Integer 64513
    serverInterface : instance of ServerShmInterface (0x11ac22da8, size=2, set=2)
    pidReleaseCondition : instance of Condition (0x1148ab878, size=2, set=2)
}
   nil

PROTECTED CALL STACK:
	Meta_MethodError:new	0x116366380
		arg this = DoesNotUnderstandError
		arg what = nil
		arg receiver = a Pbinop
	Meta_DoesNotUnderstandError:new	0x116368340
		arg this = DoesNotUnderstandError
		arg receiver = a Pbinop
		arg selector = schedBundleArrayOnClock
		arg args = [ a TempoClock, [ [ 15, 1283, gate, 0 ] ], 0.0, localhost, nil ]
	Object:doesNotUnderstand	0x11b0cd640
		arg this = a Pbinop
		arg selector = schedBundleArrayOnClock
		arg args = nil
	a FunctionDef	0x119919bc0
		sourceCode = "#{
					var tempo, server, eventTypes, parentType;

					parentType = ~parentTypes[~type];
					parentType !? { currentEnvironment.parent = parentType };

					server = ~server = ~server ? Server.default;

					~finish.value(currentEnvironment);

					tempo = ~tempo;
					tempo !? { thisThread.clock.tempo = tempo };


					if(currentEnvironment.isRest.not) {
						eventTypes = ~eventTypes;
						(eventTypes[~type] ?? { eventTypes[\\note] }).value(server)
					};

					~callback.value(current...etc..."
		var tempo = nil
		var server = localhost
		var eventTypes = ( 'fadeBus': a Function, 'freeAllocWrite': a Function, 'tree': a Function, 'vst_set': a Function, 
  'instr': a Function, 'on': a Function, 'load': a Function, 'freeBuffer': a Function, 'group': a Function, 
  'freeAllocRead': a Function, 'allocWrite': a Function, 'cue': a Function, 'grain': a Function, 'Synth': a Function, 
  'vst_midi': a Function, 'freeAllocWriteID': a Function, 'rest': a Function, 'alloc': a Function, 'sine2': a Function, 
  'sine1': a Function, 'midi': a Function, 'set': a Function, 's...etc...
		var parentType = nil
	a FunctionDef	0x1198cfbc0
		sourceCode = "<an open Function>"
	Function:prTry	0x116e8e4c0
		arg this = a Function
		var result = nil
		var thread = a Routine
		var next = nil
		var wasInProtectedFunc = false
	
CALL STACK:
	DoesNotUnderstandError:reportError
		arg this = <instance of DoesNotUnderstandError>
	Nil:handleError
		arg this = nil
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Routine>
		arg error = <instance of DoesNotUnderstandError>
	Object:throw
		arg this = <instance of DoesNotUnderstandError>
	Function:protect
		arg this = <instance of Function>
		arg handler = <instance of Function>
		var result = <instance of DoesNotUnderstandError>
	Environment:use
		arg this = <instance of Event>
		arg function = <instance of Function>
		var result = nil
		var saveEnvir = <instance of Environment>
	Event:play
		arg this = <instance of Event>
	Event:playAndDelta
		arg this = <instance of Event>
		arg cleanup = <instance of EventStreamCleanup>
		arg mute = false
	EventStreamPlayer:prNext
		arg this = <instance of EventStreamPlayer>
		arg inTime = 233.63841868303
		var nextTime = nil
		var outEvent = <instance of Event>
	< FunctionDef in Method EventStreamPlayer:init >
		arg inTime = 233.63841868303
	Routine:prStart
		arg this = <instance of Routine>
		arg inval = 233.63841868303
^^ The preceding error dump is for ERROR: Message 'schedBundleArrayOnClock' not understood.
RECEIVER: a Pbinop

well (dur: Pn(1)) is an event - you can check by evaluating (dur: Pn(1)).class

the default is a \note type - I figure the problem is SC cant figure out how to schedule the note off because it doesn’t know how to add Pn(1) to the start time -

that’s what the PbinOp ‘+’ is about I expect…

now Pbind( dur: Pn(1)) will work fine because it expects a pattern…

There’s your answer: Pbind can evaluate patterns given to it. Events don’t. Events accept information from pattern evaluation but the pattern must already be evaluated by something else.

Nope, faulty reasoning here. Pbind and Event are not interchangeable. (Edit: Actually, the places in SC where a pattern can be used in place of a number are rather few.)

I should add that this is a normal way to learn a programming language: to extrapolate from “this works here” to “maybe it works there.” But sometimes it works here but not there (and then you’ve learned a valuable distinction).

hjh

Here’s a better illustration of what I’m encountering.

This works:

(
var val = Pseq([6], inf).asStream;
(Pn( (\octave: val) ) <> Pbind()).play
)

But this doesn’t work

(
var val = Pseq([0.5], inf).asStream;
(Pn( (\dur: val) ) <> Pbind()).play
)

I got to this by discovering that this construct

(
~evt = ();
( Pn(~evt) <> Pbind()  ).play
)

allows me to add and change keys on the fly - similar to Pbindef

This works fine except with \dur. I can’t understand why from the stack trace other than \dur is handled differently than other keys. So I was curious as to what actually was going on.

What do you expect will happen if you’re playing a pattern, and an event coming out of a pattern has a dur that is not a simple number (integer or float)?

What I’m getting at is this: dur calculates delta, which tells the EventStreamPlayer how long to wait until the next event. This is straightforward if the delta is a number.

If it’s a stream as in your example – remember that this is an event, not a pattern. A pattern can pull values from a stream; events are not designed to do this.

The thing about octave is kind of subtle actually. It’s doing math on the stream, producing a big structure of Binary/UnaryOpStreams, which is then assigned to midinote – then freq does ~midinote.value which evaluates the whole ball of operations at once. It works only because there’s a conversion after the calculation accessing octave – the later conversion resolves it to a number.

Try your code structure with detunedFreq – it will break. The takeaway is that the code structure you’re suggesting works in certain specific cases, but it’s not designed to work in all cases. So it isn’t safe to use everywhere.

I’m curious why you went with this option instead of Pbind(... overrides...) <> Pbind (... source...)? Using a Pbind will accept subpatterns where Pn(anEvent) doesn’t.

hjh

PS it’s kind of the same way that:

// this is ok
p = Pbind(
    \value, Pwhite(0.0, 1.0, inf)
).play;

// this is not ok
p = Pbind(
    \value, [Pwhite(0.0, 1.0, inf), Pwhite(1.0, 2.0, inf)]
).play;

You might think “in a lot of places, I can use an array of numbers in place of a number, so I should be able to use an array of patterns in place of a single pattern” but in fact you need Ptuple for this.

Since practical answers are already given by @semiquaver and @jamshark70, and the title of this topic is about the error message, I thought it could be useful to share the way I traversed this stack trace.

I think there is a bit of obfuscation in this stack trace, which could be due to the fact that many functions called inside Event’s internal logic are defined literally within event prototypes, and not as class methods. I’m not a Pattern expert, but I felt I learned something from this experience.

Traversing the stack trace:
At the bottom we find a Routine, calling a function defined in EventStreamPlayer:init.
Inside that function we go to EventStreamPlayer:prNext, which gets an event from the embedded stream, and calls Event:playAndDelta and then Event:play on it.
So the error comes from Event:play. Let’s find out which event we are talking about.

from EventStreamPlayer:prNext
var outEvent = stream.next(event.copy);
from EventStreamPlayer:init
^super.new(stream).event_(event ?? { Event.default }).init;

The EventStreamPlayer is initialized by Pattern:play, which calls Pattern:asEventStreamPlayer on our Pchain.
We can find out the Event like this:

x = (Pn( (dur:Pn(1)) ) <> Pbind()).asEventStreamPlayer
e = x.originalStream.next(Event.default)

Or, by descending further into Pattern:asStream and this Pchain:embedInStream:

e = Pn( (dur:Pn(1)) ).asStream.next(Pbind().asStream.next(Event.default))

The event is (dur:Pn(1)), and (dur:Pn(1)).play gets us the same error we started with.

Descending into Event:play
Event:play calls Environment:use
here our event[\play] is called. (dur:Pn(1))[\play] is nil, so this function must come from the parent event, which is also nil, and thus gets set by Event:play to a defaultParentEvent.
(all following line numbers are from Event.sc)
line 1060: defaultParentEvent = parentEvents.default
line 975: definition of parentEvents: “default” is a composition of various events
line 428: playerEvent is the one part of parentEvents.default which defines a \play function (along with a default type \note)

this \play function is the one shown in our PROTECTED CALL STACK. Here I feel things are getting a bit obfuscated in the stack trace. There’s no sign of a schedBundleArrayOnClock call in this \play function.
What I did was to copy the \play function and assign it directly to our event. This way I could comment lines out until I figured out which line caused the error to appear.

e[\play] = { ... } // function copied from Event.sc:432
e.play; // now calls our 'debuggable' function :)

And the culprit is:
line 448: (eventTypes[~type] ?? { eventTypes[\note] }).value(server)

here eventTypes[\note] gets called. Again, a function that is not a class method, but it is defined literaly in a prototype. Maybe this is why it doesn’t appear in the stack trace?

Find the function in Event.sc, line 498. We could repeat the same copy and comment strategy, but there is a ~schedBundleArray worth examining.
Find its definition at line 317. Here we go, schedBundleArrayOnClock it’s called on its second argument, which from eventTypes[\note] we see it can be either offset (which = ~timeOffset) or sustain+offset. Let’s inspect these values on our event:

e = Pn( (dur:Pn(1)) ).asStream.next(Pbind().asStream.next(Event.default))
e[\timingOffset] // -> 0
e[\sustain] // -> a Function
e.use{e.sustain} // -> a Pbinop

// Definition of sustain:
// line 241 : 	sustain: #{ ~dur * ~legato * ~stretch }```

e.dur // -> a Pn
e.legato // -> 0.8
e.stretch // -> 1

Here is how an event with \dur as a Pattern causes errors when played.
So it would also if instead of dur it was \legato, \stretch or \timingOffset.
These values are used to schedule a bundle and have to be numbers (ctrl+I or ctrl+D reveals that schedBundleArray is defined only for SequenceableCollection and SimpleNumber).

Pbind
The next step would be to investigate how Pbind works instead. We can see clearly from its source that the difference lies in Pbind:embedInStream. Here, a Pbind extracts values from Patterns supplied as patternpairs, so that each emitted event is free from patterns

x = Pbind(\dur,Pn(1)).asStream
x.next(Event.default) // (dur:1)

I hope this can be useful

2 Likes

This is the help I was looking for!! Thank you so much!

I’m trying to create a unified interface where if I play a synth via patterns or midi I can use the same interface for setting the synth args. Under the covers, with an event as my internal data structure I can set Synth args by supplying evt.asPairs() and (provided it works) I can set pattern properties with the construct posted above. My goal is if I have an external sequencer playing a synth or a Pattern playing a synth I can tweak the synth properties using the same methods and also be able to intermix setting properties with Patterns regardless of what is driving the “noteOn/Off” events.

Up until “discovering” this hack I had two methods for setting properties - one for when in a Pattern context (which relied on Pbindef) and one for when in a midi context.

I did come up with workaround for the \dur issue I ran into. So, I do have my unified interface idea more or less working pending more testing

That’s a cool idea. I’d suggest, though, that Pbindef or a PbindProxy may be better for storage – you can tweak parameters here, and when you need an event, you can have a stream based on this object and ask the stream for the event.

That is, it’s easier to get an event from a pattern’s stream than it is to make an event act like a pattern’s stream. So by making an event the central storage location, you might be making it harder on yourself.

The famous Design Patterns book would suggest decoupling storage from usage: instead of trying to embed the event directly, even if it has patterns inside it, use something else for storage and then, for usage, have a translator produce the right kind of linkage. (It’s a very common object oriented design mistake to have one object doing everything – and it took me years to even begin to understand this, it’s very hard.)

Cool that you found a workaround – in the end, it’s whatever works.

SC does something called “tail call optimization,” which removes stack frames when the last method call in a function/method is also the return value. I’m not clear why the note function is being dropped from the stack but I’m pretty sure ~schedBundleArray will optimize this.

e.use{e.sustain} // -> a Pbinop

This analysis is correct, yes.

hjh

How do you get the event from a Pbindef?

~a = Pbindef(\a, \foo, 1).asStream;
~a.value;

~a.value always returns nil

You have to pass in an empty event (or prototype event):

~a.next(());  // () is a synonym for Event.new

hjh

It’s interesting…you get the same kind of error with this:

~p = Pbindef(\p, \foo, 1);
~p.play;
~p.set(\dur, Pseq([1], inf));

Seems I can’t really use Pbindef as a data structure which will work in both contexts. It certainly works in the pattern context. However, in order to extract the event data to use as Synth args I have to call asStream - which snapshots the current state such that subsequent changes to the keys are not reflected until asStream is called again. But in the midi context calling asStream on each Synth invocation won’t cycle through the patterns. My solution is to maintain two parallel data structures - a Pbindef for pattern context and an event for midi context. This detail is hidden behind the interface but still kinda yucky.

Thanks for all your insight.

To complete the picture this is how what I’m working on looks like…

/////////////////////////////////////////
// itof
(
// this the object with the unified interface
~itof = S(\itof, \subtractr)
// set up an fx chain - Ndef under the covers
.filter(100, {arg in;
    var sig = in;
	sig = AllpassC.ar(sig, 1, [3/16, 5/8], 3) * \allpass.kr(0.7) + sig;
	sig = JPverb.ar(sig, 10, 0, 3) * \rev.kr(0.3) + sig;
	sig;
})
)

~itof.set(\dur, [1, 2].pseq * 0.125);
~itof.set(\harmonic, [1, 2, 3].pseq);
~itof.set(\amp, 0.1);
~itof.set(\degree, [ [0, 7].pseq, [ 4, 2, 0, -1].pseq ].place);
~itof.set(\detunehz, 1)
~itof.set(\octave, [4, 5] - 1);
// map to an lfo
~itof.set(\sawsync, { LFTri.kr(1/4).range(1.0, 3.0) })
~itof.set(\atk, 0.05, \rel, [0.1, 2].pseq, \suslevel, 0.7);
~itof.set(\fatk, 0.01, \fatkcurve, 4);
~itof.set(\cutoff, 500, \fvel, 10, \fsuslevel, 0.5, \ffocus, 1, \fwhich, 0);
~itof.set(\pulse, 1, \tri, 0, \sine, 0, \saw, 0.5);
// map to an lfo
~itof.set(\pulsewidth, { SinOsc.kr(5).range(0.4, 0.6) });
// set properties on fx chain
~itof.node.set(\allpass, 0.7, \rev, 0.3);

// play the pattern
~itof.pdef.play;
// stop the pattern
~itof.pdef.stop;

(
// with midi or osc wire-up
// i can play a synth and tweak the properties via the same interface
// properties with patterns can work as well
~itof.on(note, vel.linlin(0, 1, 0.1, 0.5));
~itof.off(note);
)

If it allows arrays, then it would have to allow [1, 2]. It’s impossible for one thread to multichannel expand its wait time – not just “we didn’t implement it,” it’s literally impossible.

Duration really does have to be a number.

Oh my, I didn’t expect that :astonished:

Well, earlier I said:

That’s the architecturally correct solution. I’m not at the computer now but perhaps I could post an example later.

Very common in OOP – if an object by itself doesn’t do everything you want, wrap it in another object that adds features.

hjh

Hm, try PbindProxy instead…?

p = PbindProxy.new;
q = p.asStream;

q.next(());
-> (  )

p.set(\dur, 1);

q.next(());
-> ( 'dur': 1 )  // hm, that's definitely an update

p.set(\dur, 2).set(\freq, Pexprand(200, 800, inf));

q.next(());
-> ( 'freq': 241.84727996333, 'dur': 2 )

hjh

I’ll take a second look at that - I thought Pbindef was effectively a PbindProxy

\dur isn’t being set to an array but to a pattern

I’d have thought so too, but it isn’t…

Pbindef.superclasses
-> [ class Pdef, class EventPatternProxy, class TaskProxy, class PatternProxy, class Pattern, class AbstractFunction, class Object ]

PbindProxy.superclasses
-> [ class Pattern, class AbstractFunction, class Object ]

The inheritance chain is quite different. I think it tries to maintain compatibility but it seems there are some differences.

If there is an impression that Pbindef is just a different kind of PbindProxy, it may be false advertising.

Oh, OK, you’re right. I misread the example.

Hmm…

Pbindef.findRespondingMethodFor(\set)
-> PatternProxy:set

PbindProxy.findRespondingMethodFor(\set)
-> PbindProxy:set

So that would be one of those incompatibilities.

Pbindef inherits its concept of set from Pdef, where (per documentation) it “Sets arguments in the default event” instead of adding as a child pattern. So you can’t rely on patterns with Pbindef:set, but you can with PbindProxy.

Definitely confusing.

hjh