[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[sc-dev] Proposed new Buffer
Okay,
Attached is my first pass at a revised Buffer class. It required a few changes to Server, so I've attached this as well. (not committed yet!)
Changes to Buffer:
Added a sampleRate var which is also updated when a file is read. The default is the server's sampleRate, which is what a b_query reports on a buffer that has been allocated but not read into.
Changed Buffer-new to Buffer-basicNew in order to make this consistent with the Node classes. If committed this would require changing crucial in the following places:
crucial/Players/SFP/SFP.sc:218: sf = Buffer.new(group.server,32768,this.numChannels);
crucial/Sample/Sample.sc:30: buffer = Buffer.new(server,this.size,numChannels);
crucial/Sample/Sample.sc:182: buffer = Buffer.new(server,this.size,numChannels);
crucial/Sample/Sample.sc:416: buffer = Buffer.new(group.asGroup.server,array.size,1);
...which I'm happy to do myself. This may break some user code.
Buffer-new is now the same as Buffer-alloc. I think -new should be the basic case, i.e. create an object and immediately allocate on the server. *alloc is kept for backwards compatibility, but could be removed.
Changed the defaults for numFrames and numChannels to nil. This means that math won't work on them, which eliminates a source of bugs. The only place these getters are called is in crucial, but if I understand the way BufferProxy works, these are always set explicitly, so I don't think this is likely to be an issue.
*read and read now automatically update numFrames, numChannels, and sampleRate. (see auto updating below)
*read and read now lack a completionMsg arg as they use this for the /b_info message.
Since *cueSoundFile requires you to specify the number of channels, I've left that without the auto query. I think this has to be this way unless /b_allocRead is changed to allow one to leave the file open. As a note, calling this will result in the sampleRate variable being set to the sampleRate of the server, but this is in fact consistent with what a b_query currently reports on a cued buffer regardless of the soundfile's rate. If that changes I could look at changing this.
Changed the name of the bufferSize arg in -cueSoundFile and -cueSoundFileMsg to numFrames. It was confusing as it seemed to imply that it resizes the buffer.
The fun bit: Added a *fromCollection method, which takes advantage of the newly fixed SoundFile to pass large sets of data to a local Server via disk. Method looks like this:
<x-tad-smaller>
*fromCollection { </x-tad-smaller><x-tad-smaller>arg</x-tad-smaller><x-tad-smaller> server, collection;
</x-tad-smaller><x-tad-smaller>var</x-tad-smaller><x-tad-smaller> data, sndfile, path, bufnum, buffer;
server = server ? </x-tad-smaller><x-tad-smaller>Server</x-tad-smaller><x-tad-smaller>.default;
server.isLocal.if({
if(collection.isKindOf(</x-tad-smaller><x-tad-smaller>RawArray</x-tad-smaller><x-tad-smaller>).not,
{data = collection.collectAs({</x-tad-smaller><x-tad-smaller>|item|</x-tad-smaller><x-tad-smaller> item}, </x-tad-smaller><x-tad-smaller>FloatArray</x-tad-smaller><x-tad-smaller>)}, {data = collection;}
);
sndfile = </x-tad-smaller><x-tad-smaller>SoundFile</x-tad-smaller><x-tad-smaller>.new;
sndfile.sampleRate = server.sampleRate;
path = </x-tad-smaller><x-tad-smaller>"sounds/"</x-tad-smaller><x-tad-smaller> ++ data.hash.asString;
if(sndfile.openWrite(path),
{
sndfile.writeData(data);
sndfile.close;
bufnum = server.bufferAllocator.alloc(1);
</x-tad-smaller><x-tad-smaller>// when the info comes we know the file's been read </x-tad-smaller><x-tad-smaller>
</x-tad-smaller><x-tad-smaller>// so we can delete the soundfile</x-tad-smaller><x-tad-smaller>
</x-tad-smaller><x-tad-smaller>OSCresponderNode</x-tad-smaller><x-tad-smaller>(server.addr, </x-tad-smaller><x-tad-smaller>"/b_info"</x-tad-smaller><x-tad-smaller>, { </x-tad-smaller><x-tad-smaller>arg</x-tad-smaller><x-tad-smaller> time, responder, message;
if(message[1] == bufnum, {</x-tad-smaller><x-tad-smaller>File</x-tad-smaller><x-tad-smaller>.delete(path); responder.remove; });
}).add;
^</x-tad-smaller><x-tad-smaller>super</x-tad-smaller><x-tad-smaller>.newCopyArgs(server, bufnum)
.allocRead(path, 0, {</x-tad-smaller><x-tad-smaller>"/b_query"</x-tad-smaller><x-tad-smaller>,buf.bufnum]})
.addToServerArray.waitForBufInfo;
}, {</x-tad-smaller><x-tad-smaller>"Failed to write data"</x-tad-smaller><x-tad-smaller>.warn; ^</x-tad-smaller><x-tad-smaller>nil</x-tad-smaller><x-tad-smaller>}
);
}, {</x-tad-smaller><x-tad-smaller>"cannot do fromCollection with a non-local Server"</x-tad-smaller><x-tad-smaller>.warn; ^</x-tad-smaller><x-tad-smaller>nil</x-tad-smaller><x-tad-smaller>});
}
</x-tad-smaller>
So then you can go:
<x-tad-smaller>// simple Float array to test getn</x-tad-smaller><x-tad-smaller>
(
</x-tad-smaller><x-tad-smaller>var</x-tad-smaller><x-tad-smaller> sig;
sig = </x-tad-smaller><x-tad-smaller>FloatArray</x-tad-smaller><x-tad-smaller>[1.9, 42.3, 2];
b = </x-tad-smaller><x-tad-smaller>Buffer</x-tad-smaller><x-tad-smaller>.fromCollection(s, sig);
)
b.getn(0, 3, {</x-tad-smaller><x-tad-smaller>|msg|</x-tad-smaller><x-tad-smaller> msg.postln});
b.free;
</x-tad-smaller><x-tad-smaller>// Signal</x-tad-smaller><x-tad-smaller>
(
</x-tad-smaller><x-tad-smaller>var</x-tad-smaller><x-tad-smaller> sig;
sig = </x-tad-smaller><x-tad-smaller>Signal</x-tad-smaller><x-tad-smaller>.sineFill(128, 1.0/[1,2,3,4,5,6]);
b = </x-tad-smaller><x-tad-smaller>Buffer</x-tad-smaller><x-tad-smaller>.fromCollection(s, sig);
)
(
</x-tad-smaller><x-tad-smaller>"bufFromColl"</x-tad-smaller><x-tad-smaller>,{ </x-tad-smaller><x-tad-smaller>arg</x-tad-smaller><x-tad-smaller> out=0,bufnum;
</x-tad-smaller><x-tad-smaller>Out</x-tad-smaller><x-tad-smaller>.ar( out,
</x-tad-smaller><x-tad-smaller>BufRateScale</x-tad-smaller><x-tad-smaller>.kr(bufnum), loop: 1)
)
}).play(s,[</x-tad-smaller><x-tad-smaller>\bufnum</x-tad-smaller><x-tad-smaller>, b.bufnum ]);
)
b.free;
</x-tad-smaller><x-tad-smaller>// Noise for testing sending a large array</x-tad-smaller><x-tad-smaller>
(
</x-tad-smaller><x-tad-smaller>var</x-tad-smaller><x-tad-smaller> sig;
sig = </x-tad-smaller><x-tad-smaller>FloatArray</x-tad-smaller><x-tad-smaller>.fill(44100 * 5.0, {1.0.rand2}); </x-tad-smaller><x-tad-smaller>// 5 seconds</x-tad-smaller><x-tad-smaller>
b = </x-tad-smaller><x-tad-smaller>Buffer</x-tad-smaller><x-tad-smaller>.fromCollection(s, sig);
)
(
</x-tad-smaller><x-tad-smaller>"bufFromColl"</x-tad-smaller><x-tad-smaller>,{ </x-tad-smaller><x-tad-smaller>arg</x-tad-smaller><x-tad-smaller> out=0,bufnum;
</x-tad-smaller><x-tad-smaller>Out</x-tad-smaller><x-tad-smaller>.ar( out,
</x-tad-smaller><x-tad-smaller>BufRateScale</x-tad-smaller><x-tad-smaller>.kr(bufnum), loop: 0) * 0.5
)
}).play(s,[</x-tad-smaller><x-tad-smaller>\bufnum</x-tad-smaller><x-tad-smaller>, b.bufnum ]);
)
b.free;
</x-tad-smaller>
There should probably be a primitive to do this with the internal server, but this should solve some people's problems.
______________
Auto updating of instance vars:
This version caches all Buffer objects in an Array, where index = bufnum. When a b_query is sent, a single OSCresponderNode updates the Buffer's vars. It keeps a count of queries and removes itself when the count reaches zero. I figured this was more efficient than storing them in a Set and scanning through, or using multiple OSCresponderNodes. But if there's a better way let me know.
As it is now you can avoid the overhead of the responder by using basicNew and msg methods, but a method could be added to create but not update automatically.
______________
Changes to Server:
These instance vars were added to Server:
<x-tad-smaller> </x-tad-smaller><x-tad-smaller>var</x-tad-smaller><x-tad-smaller> <bufferArray, <waitingForBufInfo = </x-tad-smaller><x-tad-smaller>false</x-tad-smaller><x-tad-smaller>, <waitingBufs = 0;
</x-tad-smaller>
and these methods were changed or added:
<x-tad-smaller> </x-tad-smaller><x-tad-smaller>// auto buffer info updating</x-tad-smaller><x-tad-smaller>
addBuf { </x-tad-smaller><x-tad-smaller>|buffer|</x-tad-smaller><x-tad-smaller>
bufferArray.put(buffer.bufnum, buffer);
}
freeBuf { </x-tad-smaller><x-tad-smaller>|i|</x-tad-smaller><x-tad-smaller>
bufferArray.put(i, </x-tad-smaller><x-tad-smaller>nil</x-tad-smaller><x-tad-smaller>);
}
</x-tad-smaller><x-tad-smaller>// /b_info on the way</x-tad-smaller><x-tad-smaller>
waitForBufInfo {
waitingForBufInfo.not.if({
</x-tad-smaller><x-tad-smaller>OSCresponderNode</x-tad-smaller><x-tad-smaller>(addr, </x-tad-smaller><x-tad-smaller>"/b_info"</x-tad-smaller><x-tad-smaller>, { </x-tad-smaller><x-tad-smaller>arg</x-tad-smaller><x-tad-smaller> time, responder, message;
</x-tad-smaller><x-tad-smaller>var</x-tad-smaller><x-tad-smaller> buffer;
(buffer = bufferArray.at(message.at(1))).notNil.if({
buffer.numFrames = message.at(2);
buffer.numChannels = message.at(3);
buffer.sampleRate = message.at(4);
((waitingBufs = waitingBufs - 1) == 0).if({
responder.remove; waitingForBufInfo = </x-tad-smaller><x-tad-smaller>false</x-tad-smaller><x-tad-smaller>;
});
});
}).add;
waitingForBufInfo = </x-tad-smaller><x-tad-smaller>true</x-tad-smaller><x-tad-smaller>;
});
waitingBufs = waitingBufs + 1;
}
</x-tad-smaller>
<x-tad-smaller> boot { </x-tad-smaller><x-tad-smaller>arg</x-tad-smaller><x-tad-smaller> startAliveThread=</x-tad-smaller><x-tad-smaller>true</x-tad-smaller><x-tad-smaller>;
</x-tad-smaller><x-tad-smaller>var</x-tad-smaller><x-tad-smaller> resp;
if (serverRunning, { </x-tad-smaller><x-tad-smaller>"server already running"</x-tad-smaller><x-tad-smaller>.inform; ^</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller> });
if (serverBooting, { </x-tad-smaller><x-tad-smaller>"server already booting"</x-tad-smaller><x-tad-smaller>.inform; ^</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller> });
serverBooting = </x-tad-smaller><x-tad-smaller>true</x-tad-smaller><x-tad-smaller>;
if(startAliveThread, { </x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>.startAliveThread });
</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>.newAllocators;
</x-tad-smaller><x-tad-smaller>//Buffer info support</x-tad-smaller><x-tad-smaller>
</x-tad-smaller><x-tad-smaller>// subtract two to avoid protected scope and record buffers.</x-tad-smaller><x-tad-smaller>
bufferArray = </x-tad-smaller><x-tad-smaller>Array</x-tad-smaller><x-tad-smaller>.newClear(options.numBuffers - 2);
</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>.doWhenBooted({
if(notified, {
</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>.notify;
</x-tad-smaller><x-tad-smaller>"notification is on"</x-tad-smaller><x-tad-smaller>.inform;
}, {
</x-tad-smaller><x-tad-smaller>"notification is off"</x-tad-smaller><x-tad-smaller>.inform;
});
serverBooting = </x-tad-smaller><x-tad-smaller>false</x-tad-smaller><x-tad-smaller>;
</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>.initTree;
});
if (isLocal.not, {
</x-tad-smaller><x-tad-smaller>"You will have to manually boot remote server."</x-tad-smaller><x-tad-smaller>.inform;
},{
</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>.bootServerApp;
});
}
</x-tad-smaller>
<x-tad-smaller> quit {
addr.sendMsg(</x-tad-smaller><x-tad-smaller>"/quit"</x-tad-smaller><x-tad-smaller>);
if (inProcess, {
</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>.quitInProcess;
</x-tad-smaller><x-tad-smaller>"quit done\n"</x-tad-smaller><x-tad-smaller>.inform;
},{
</x-tad-smaller><x-tad-smaller>"/quit sent\n"</x-tad-smaller><x-tad-smaller>.inform;
});
alive = </x-tad-smaller><x-tad-smaller>false</x-tad-smaller><x-tad-smaller>;
dumpMode = 0;
serverBooting = </x-tad-smaller><x-tad-smaller>false</x-tad-smaller><x-tad-smaller>;
</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>.serverRunning = </x-tad-smaller><x-tad-smaller>false</x-tad-smaller><x-tad-smaller>;
if(scopeWindow.notNil) { scopeWindow.quit };
</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>).freeAll;
</x-tad-smaller><x-tad-smaller>this</x-tad-smaller><x-tad-smaller>.newAllocators;
bufferArray = </x-tad-smaller><x-tad-smaller>nil</x-tad-smaller><x-tad-smaller>;
}
</x-tad-smaller>__________
The only downside of this implementation that I can see is that you lose the completionMsg for *read and read. One can still work around this using msg methods. One solution would be to change b_query to have a completion message itself; then they could be nested. I gather from the way cueSoundFile was before my changes that this is possible (although I didn't try it). I guess this would mean losing the ability to query multiple buffers in a single message, but I don't know if that's a real problem for anyone.
Let me know what you all think, and if you see any problems with this. In testing it's been so far so good, but I'd appreciate it if people could put it through its paces. If there're are no issues I'll post this to the user's list for testing and barring major objection, commit it.
S.
Attachment:
Buffer.sc
Description: Binary data
Attachment:
Server.sc
Description: Binary data