class library: DUGen - the server now handles audio-rate inputs correctly
[supercollider.git] / SCClassLibrary / Common / Control / Buffer.sc
blobca482fbd4da750abc6fb9be8ea2412daee225c25
1 Buffer {
2         classvar        serverCaches;
4         // don't use the setter methods for the vars below
5         // they're private and have no effect on the server
6         var <server, <bufnum, <>numFrames, <>numChannels, <>sampleRate;
7         var <>path, >doOnInfo;
9         *initClass { serverCaches = IdentityDictionary.new }
11         // doesn't send
12         *new { arg server, numFrames, numChannels, bufnum;
13                 server = server ? Server.default;
14                 bufnum ?? { bufnum = server.bufferAllocator.alloc(1) };
15                 if(bufnum.isNil) {
16                         Error("No more buffer numbers -- free some buffers before allocating more.").throw
17                 };
18                 ^super.newCopyArgs(server,
19                                                 bufnum,
20                                                 numFrames,
21                                                 numChannels).sampleRate_(server.sampleRate).cache;
22         }
24         *alloc { arg server, numFrames, numChannels = 1, completionMessage, bufnum;
25                 server = server ? Server.default;
26                 bufnum ?? { bufnum = server.bufferAllocator.alloc(1) };
27                 if(bufnum.isNil) {
28                         Error("No more buffer numbers -- free some buffers before allocating more.").throw
29                 };
30                 ^super.newCopyArgs(server,
31                                                 bufnum,
32                                                 numFrames,
33                                                 numChannels)
34                                         .alloc(completionMessage).sampleRate_(server.sampleRate).cache;
35         }
37         *allocConsecutive { |numBufs = 1, server, numFrames, numChannels = 1, completionMessage,
38                         bufnum|
39                 var     bufBase, newBuf;
40                 bufBase = bufnum ?? { server.bufferAllocator.alloc(numBufs) };
41                 if(bufBase.isNil) {
42                         Error("No block of % consecutive buffer numbers is available.".format(numBufs)).throw
43                 };
44                 ^Array.fill(numBufs, { |i|
45                         newBuf = Buffer.new(server, numFrames, numChannels, i + bufBase);
46                         server.sendMsg(\b_alloc, i + bufBase, numFrames, numChannels,
47                                 completionMessage.value(newBuf, i));
48                         newBuf.cache
49                 });
50         }
52         alloc { arg completionMessage;
53                 server.listSendMsg( this.allocMsg(completionMessage) )
54         }
55         allocRead { arg argpath,startFrame = 0,numFrames = -1, completionMessage;
56                 path = argpath;
57                 server.listSendMsg(this.allocReadMsg( argpath,startFrame,numFrames, completionMessage));
58         }
59         allocReadChannel { arg argpath,startFrame,numFrames = 0, channels = -1, completionMessage;
60                 path = argpath;
61                 server.listSendMsg(this.allocReadChannelMsg( argpath,startFrame,numFrames, channels,
62                         completionMessage));
63         }
64         allocMsg { arg completionMessage;
65                 this.cache;
66                 ^["/b_alloc", bufnum, numFrames.asInt, numChannels, completionMessage.value(this)]
67         }
68         allocReadMsg { arg argpath,startFrame = 0,numFrames = -1, completionMessage;
69                 this.cache;
70                 path = argpath;
71                 ^["/b_allocRead",bufnum, path,startFrame,(numFrames ? -1).asInt, completionMessage.value(this)]
72         }
73         allocReadChannelMsg { arg argpath,startFrame = 0,numFrames = -1, channels, completionMessage;
74                 this.cache;
75                 path = argpath;
76                 ^["/b_allocReadChannel",bufnum, path,startFrame, (numFrames ? -1).asInt] ++ channels ++                         [completionMessage.value(this)]
77         }
79         // read whole file into memory for PlayBuf etc.
80         // adds a query as a completion message
81         *read { arg server,path,startFrame = 0,numFrames = -1, action, bufnum;
82                 server = server ? Server.default;
83                 bufnum ?? { bufnum = server.bufferAllocator.alloc(1) };
84                 if(bufnum.isNil) {
85                         Error("No more buffer numbers -- free some buffers before allocating more.").throw
86                 };
87                 ^super.newCopyArgs(server, bufnum)
88                                         .doOnInfo_(action).cache
89                                         .allocRead(path,startFrame,numFrames,{|buf|["/b_query",buf.bufnum]});
90         }
91         read { arg argpath, fileStartFrame = 0, numFrames = -1,
92                                         bufStartFrame = 0, leaveOpen = false, action;
93                 this.cache;
94                 doOnInfo = action;
95                 server.listSendMsg(
96                         this.readMsg(argpath,fileStartFrame,numFrames,bufStartFrame,
97                                                 leaveOpen,{|buf|["/b_query",buf.bufnum]} )
98                 );
99         }
100         *readChannel { arg server,path,startFrame = 0,numFrames = -1, channels, action, bufnum;
101                 server = server ? Server.default;
102                 bufnum ?? { bufnum = server.bufferAllocator.alloc(1) };
103                 if(bufnum.isNil) {
104                         Error("No more buffer numbers -- free some buffers before allocating more.").throw
105                 };
106                 ^super.newCopyArgs(server, bufnum)
107                                         .doOnInfo_(action).cache
108                                         .allocReadChannel(path,startFrame,numFrames,channels,
109                                                 {|buf|["/b_query",buf.bufnum]});
110         }
111         readChannel { arg argpath, fileStartFrame = 0, numFrames = -1,
112                                         bufStartFrame = 0, leaveOpen = false, channels, action;
113                 this.cache;
114                 doOnInfo = action;
115                 server.listSendMsg(
116                         this.readChannelMsg(argpath,fileStartFrame,numFrames,bufStartFrame,
117                                                 leaveOpen,channels,{|buf|["/b_query",buf.bufnum]} )
118                 );
119         }
120         *readNoUpdate { arg server,path,startFrame = 0,numFrames = -1, bufnum, completionMessage;
121                 server = server ? Server.default;
122                 bufnum ?? { bufnum = server.bufferAllocator.alloc(1) };
123                 if(bufnum.isNil) {
124                         Error("No more buffer numbers -- free some buffers before allocating more.").throw
125                 };
126                 ^super.newCopyArgs(server, bufnum)
127                                         .allocRead(path,startFrame,numFrames, completionMessage);
128         }
129         readNoUpdate { arg argpath, fileStartFrame = 0, numFrames = -1,
130                                         bufStartFrame = 0, leaveOpen = false, completionMessage;
131                 server.listSendMsg(
132                         this.readMsg(argpath,fileStartFrame,numFrames,bufStartFrame,
133                                 leaveOpen, completionMessage)
134                 );
135         }
136         readMsg { arg argpath, fileStartFrame = 0, numFrames = -1,
137                                         bufStartFrame = 0, leaveOpen = false, completionMessage;
138                 path = argpath;
139                 ^["/b_read", bufnum, path, fileStartFrame, (numFrames ? -1).asInt,
140                         bufStartFrame, leaveOpen.binaryValue, completionMessage.value(this)]
141                 // doesn't set my numChannels etc.
142         }
144         readChannelMsg { arg argpath, fileStartFrame = 0, numFrames = -1,
145                                         bufStartFrame = 0, leaveOpen = false, channels, completionMessage;
146                 path = argpath;
147                 ^["/b_readChannel", bufnum, path, fileStartFrame, (numFrames ? -1).asInt,
148                         bufStartFrame, leaveOpen.binaryValue] ++ channels ++ [completionMessage.value(this)]
149                 // doesn't set my numChannels etc.
150         }
152         // preload a buffer for use with DiskIn
153         *cueSoundFile { arg server,path,startFrame = 0,numChannels= 2,
154                          bufferSize=32768,completionMessage;
155                 ^this.alloc(server,bufferSize,numChannels,{ arg buffer;
156                                                 buffer.readMsg(path,startFrame,bufferSize,0,true,completionMessage)
157                                         }).cache;
158         }
160         cueSoundFile { arg path,startFrame,completionMessage;
161                 this.path_(path);
162                 server.listSendMsg(
163                         this.cueSoundFileMsg(path,startFrame,completionMessage)
164                 )
165         }
166         cueSoundFileMsg { arg path,startFrame = 0,completionMessage;
167                 ^["/b_read", bufnum,path,startFrame,numFrames.asInt,0,1,completionMessage.value(this) ]
168         }
170         // transfer a collection of numbers to a buffer through a file
171         *loadCollection { arg server, collection, numChannels = 1, action;
172                 var data, sndfile, path, bufnum, buffer;
173                 server = server ? Server.default;
174                 bufnum = server.bufferAllocator.alloc(1);
175                 if(bufnum.isNil) {
176                         Error("No more buffer numbers -- free some buffers before allocating more.").throw
177                 };
178                 server.isLocal.if({
179                         if(collection.isKindOf(RawArray).not) { collection = collection.as(FloatArray) };
180                         sndfile = SoundFile.new;
181                         sndfile.sampleRate = server.sampleRate;
182                         sndfile.numChannels = numChannels;
183                         path = PathName.tmp ++ sndfile.hash.asString;
184                         if(sndfile.openWrite(path),
185                                 {
186                                         sndfile.writeData(collection);
187                                         sndfile.close;
188                                         ^super.newCopyArgs(server, bufnum)
189                                                 .cache.doOnInfo_({ |buf|
190                                                         if(File.delete(path), { buf.path = nil},
191                                                                 {("Could not delete data file:" + path).warn;});
192                                                         action.value(buf);
193                                                 }).allocRead(path, 0, -1,{|buf|["/b_query",buf.bufnum]});
195                                 }, {"Failed to write data".warn; ^nil}
196                         );
197                 }, {"cannot use loadCollection with a non-local Server".warn; ^nil});
198         }
200         loadCollection { arg collection, startFrame = 0, action;
201                 var data, sndfile, path;
202                 server.isLocal.if({
203                         if(collection.isKindOf(RawArray).not,
204                                 {data = collection.collectAs({|item| item}, FloatArray)}, {data = collection;}
205                         );
206                         if ( collection.size > ((numFrames - startFrame) * numChannels),
207                                 { "Collection larger than available number of Frames".warn });
208                         sndfile = SoundFile.new;
209                         sndfile.sampleRate = server.sampleRate;
210                         sndfile.numChannels = numChannels;
211                         path = PathName.tmp ++ sndfile.hash.asString;
212                         if(sndfile.openWrite(path),
213                                 {
214                                         sndfile.writeData(data);
215                                         sndfile.close;
216                                         this.read(path, bufStartFrame: startFrame, action: { |buf|
217                                                 if(File.delete(path), { buf.path = nil },
218                                                         {("Could not delete data file:" + path).warn;});
219                                                 action.value(buf);
220                                         });
222                                 }, {"Failed to write data".warn;}
223                         );
224                 }, {"cannot do fromCollection with a non-local Server".warn;});
225         }
227         // send a Collection to a buffer one UDP sized packet at a time
228         *sendCollection { arg server, collection, numChannels = 1, wait = -1, action;
229                 var buffer = this.new(server, ceil(collection.size / numChannels), numChannels);
230                 forkIfNeeded {
231                         buffer.alloc;
232                         server.sync;
233                         buffer.sendCollection(collection, 0, wait, action);
234                 }
235                 ^buffer;
236         }
238         sendCollection { arg collection, startFrame = 0, wait = -1, action;
239                 var collstream, collsize, bundsize;
241                 if(collection.isSequenceableCollection
242                 and: { startFrame.isNumber and: { wait.isNumber } }) {
243                         collstream = CollStream.new;
244                         collstream.collection = collection;
245                         collsize = collection.size;
247                         if ( collsize > ((numFrames - startFrame) * numChannels),
248                                 { "Collection larger than available number of Frames".warn });
250                         this.streamCollection(collstream, collsize, startFrame * numChannels, wait, action);
251                 } {
252                         MethodError("Invalid arguments to Buffer:sendCollection", this).throw;
253                 };
254         }
256         // called internally
257         streamCollection { arg collstream, collsize, startFrame = 0, wait = -1, action;
258                 var bundsize, pos;
259                 {
260                         // wait = -1 allows an OSC roundtrip between packets
261                         // wait = 0 might not be safe in a high traffic situation
262                         // maybe okay with tcp
263                         pos = collstream.pos;
264                         while { pos < collsize } {
265                                 // 1626 max size for setn under udp
266                                 bundsize = min(1626, collsize - pos);
267                                 server.listSendMsg(['/b_setn', bufnum, pos + startFrame, bundsize]
268                                         ++ Array.fill(bundsize, { collstream.next }));
269                                 pos = collstream.pos;
270                                 if(wait >= 0) { wait.wait } { server.sync };
271                         };
273                         action.value(this);
275                 }.forkIfNeeded;
276         }
278         // these next two get the data and put it in a float array which is passed to action
280         loadToFloatArray { arg index = 0, count = -1, action;
281                 var msg, cond, path, file, array;
282                 {
283                         path = PathName.tmp ++ this.hash.asString;
284                         msg = this.write(path, "aiff", "float", count, index);
285                         server.sync;
286                         file = SoundFile.new;
287                         protect {
288                                 file.openRead(path);
289                                 array = FloatArray.newClear(file.numFrames * file.numChannels);
290                                 file.readData(array);
291                         } {
292                                 file.close;
293                                 if(File.delete(path).not) { ("Could not delete data file:" + path).warn };
294                         };
296                         action.value(array, this);
298                 }.forkIfNeeded;
299         }
301         // risky without wait
302         getToFloatArray { arg index = 0, count, wait = 0.01, timeout = 3, action;
303                 var refcount, array, pos, getsize, resp, done = false;
304                 pos = index = index.asInteger;
305                 count = (count ? (numFrames * numChannels)).asInteger;
306                 array = FloatArray.newClear(count);
307                 refcount = (count / 1633).roundUp;
308                 count = count + pos;
309                 //("refcount" + refcount).postln;
310                 resp = OSCFunc({ arg msg;
311                         if(msg[1] == bufnum, {
312                                 //("received" + msg).postln;
313                                 array = array.overWrite(FloatArray.newFrom(msg.copyToEnd(4)), msg[2] - index);
314                                 refcount = refcount - 1;
315                                 //("countDown" + refcount).postln;
316                                 if(refcount <= 0, {done = true; resp.clear; action.value(array, this); });
317                         });
318                 }, '/b_setn', server.addr);
319                 {
320                         while({pos < count}, {
321                                 // 1633 max size for getn under udp
322                                 getsize = min(1633, count - pos);
323                                 //("sending from" + pos).postln;
324                                 server.listSendMsg(this.getnMsg(pos, getsize));
325                                 pos = pos + getsize;
326                                 if(wait >= 0) { wait.wait } { server.sync };
327                         });
329                 }.forkIfNeeded;
330                 // lose the responder if the network choked
331                 SystemClock.sched(timeout,
332                         { done.not.if({ resp.free; "Buffer-streamToFloatArray failed!".warn;
333                                 "Try increasing wait time".postln;});
334                 });
335         }
337         write { arg path, headerFormat = "aiff", sampleFormat = "int24", numFrames = -1,
338                                                 startFrame = 0,leaveOpen = false, completionMessage;
339                 path = path ?? { thisProcess.platform.recordingsDir +/+ "SC_" ++ Date.localtime.stamp ++ "." ++ headerFormat };
340                 server.listSendMsg(
341                         this.writeMsg(path, headerFormat, sampleFormat, numFrames, startFrame,
342                                 leaveOpen, completionMessage)
343                         );
344         }
345         writeMsg { arg path,headerFormat="aiff",sampleFormat="int24",numFrames = -1,
346                                                 startFrame = 0,leaveOpen = false, completionMessage;
347                 // doesn't change my path
348                 ^["/b_write", bufnum, path,
349                                 headerFormat,sampleFormat, numFrames, startFrame,
350                                 leaveOpen.binaryValue, completionMessage.value(this)];
351                 // writeEnabled = true;
352         }
354         free { arg completionMessage;
355                 server.listSendMsg( this.freeMsg(completionMessage) );
356         }
357         freeMsg { arg completionMessage;
358                 this.uncache;
359                 server.bufferAllocator.free(bufnum);
360                 ^["/b_free", bufnum, completionMessage.value(this)];
361         }
362         *freeAll { arg server;
363                 var b;
364                 server = server ? Server.default;
365                 server.bufferAllocator.blocks.do({ arg block;
366                         (block.address .. block.address + block.size - 1).do({ |i|
367                                 b = b.add( ["/b_free", i] );
368                         });
369                         server.bufferAllocator.free(block.address);
370                 });
371                 server.sendBundle(nil, *b);
372                 this.clearServerCaches(server);
373         }
374         zero { arg completionMessage;
375                 server.listSendMsg(this.zeroMsg(completionMessage));
376         }
377         zeroMsg { arg completionMessage;
378                 ^["/b_zero", bufnum ,  completionMessage.value(this) ]
379         }
381         set { arg index,float ... morePairs;
382                 server.listSendMsg(["/b_set",bufnum,index,float] ++ morePairs);
383         }
384         setMsg { arg index,float ... morePairs;
385                 ^["/b_set",bufnum,index,float] ++ morePairs;
386         }
388         setn { arg ... args;
389                 server.sendMsg(*this.setnMsg(*args));
390         }
391         setnMsgArgs{arg ... args;
392                 var nargs;
393                 nargs = List.new;
394                 args.pairsDo{ arg control, moreVals;
395                         if(moreVals.isArray,{
396                                 nargs.addAll([control, moreVals.size]++ moreVals)
397                         },{
398                                 nargs.addAll([control, 1, moreVals]);
399                         });
400                 };
401                 ^nargs;
402         }
403         setnMsg { arg ... args;
404                 ^["/b_setn",bufnum] ++ this.setnMsgArgs(*args);
405         }
406         get { arg index, action;
407                 OSCpathResponder(server.addr,['/b_set',bufnum,index],{ arg time, r, msg;
408                         action.value(msg.at(3)); r.remove }).add;
409                 server.listSendMsg(["/b_get",bufnum,index]);
410         }
411         getMsg { arg index;
412                 ^["/b_get",bufnum,index];
413         }
414         getn { arg index, count, action;
415                 OSCpathResponder(server.addr,['/b_setn',bufnum,index],{arg time, r, msg;
416                         action.value(msg.copyToEnd(4)); r.remove } ).add;
417                 server.listSendMsg(["/b_getn",bufnum,index, count]);
418         }
419         getnMsg { arg index, count;
420                 ^["/b_getn",bufnum,index, count];
421         }
424         fill { arg startAt,numFrames,value ... more;
425                 server.listSendMsg(["/b_fill",bufnum,startAt,numFrames.asInt,value]
426                          ++ more);
427         }
428         fillMsg { arg startAt,numFrames,value ... more;
429                 ^["/b_fill",bufnum,startAt,numFrames.asInt,value]
430                          ++ more;
431         }
433         normalize { arg newmax=1, asWavetable=false;
434                 server.listSendMsg(["/b_gen",bufnum,if(asWavetable, "wnormalize", "normalize"),newmax]);
435         }
436         normalizeMsg { arg newmax=1, asWavetable=false;
437                 ^["/b_gen",bufnum,if(asWavetable, "wnormalize", "normalize"),newmax];
438         }
440         gen { arg genCommand, genArgs, normalize=true,asWavetable=true,clearFirst=true;
441                 server.listSendMsg(["/b_gen",bufnum,genCommand,
442                         normalize.binaryValue
443                         + (asWavetable.binaryValue * 2)
444                         + (clearFirst.binaryValue * 4)]
445                         ++ genArgs)
446         }
447         genMsg { arg genCommand, genArgs, normalize=true,asWavetable=true,clearFirst=true;
448                 ^["/b_gen",bufnum,genCommand,
449                         normalize.binaryValue
450                         + (asWavetable.binaryValue * 2)
451                         + (clearFirst.binaryValue * 4)]
452                         ++ genArgs;
453         }
454         sine1 { arg amps,normalize=true,asWavetable=true,clearFirst=true;
455                 server.listSendMsg(["/b_gen",bufnum,"sine1",
456                         normalize.binaryValue
457                         + (asWavetable.binaryValue * 2)
458                         + (clearFirst.binaryValue * 4)]
459                         ++ amps)
460         }
461         sine2 { arg freqs, amps,normalize=true,asWavetable=true,clearFirst=true;
462                 server.listSendMsg(["/b_gen",bufnum,"sine2",
463                         normalize.binaryValue
464                         + (asWavetable.binaryValue * 2)
465                         + (clearFirst.binaryValue * 4)]
466                         ++ [freqs, amps].lace(freqs.size * 2))
467         }
468         sine3 { arg freqs, amps, phases,normalize=true,asWavetable=true,clearFirst=true;
469                 server.listSendMsg(["/b_gen",bufnum,"sine3",
470                         normalize.binaryValue
471                         + (asWavetable.binaryValue * 2)
472                         + (clearFirst.binaryValue * 4)]
473                         ++ [freqs, amps, phases].lace(freqs.size * 3))
474         }
475         cheby { arg amplitudes,normalize=true,asWavetable=true,clearFirst=true;
476                 server.listSendMsg(["/b_gen",bufnum,"cheby",
477                         normalize.binaryValue
478                         + (asWavetable.binaryValue * 2)
479                         + (clearFirst.binaryValue * 4)]
480                         ++ amplitudes)
481         }
482         sine1Msg { arg amps,normalize=true,asWavetable=true,clearFirst=true;
483                 ^["/b_gen",bufnum,"sine1",
484                         normalize.binaryValue
485                         + (asWavetable.binaryValue * 2)
486                         + (clearFirst.binaryValue * 4)]
487                         ++ amps
488         }
489         sine2Msg { arg freqs, amps,normalize=true,asWavetable=true,clearFirst=true;
490                 ^["/b_gen",bufnum,"sine2",
491                         normalize.binaryValue
492                         + (asWavetable.binaryValue * 2)
493                         + (clearFirst.binaryValue * 4)]
494                         ++ [freqs, amps].lace(freqs.size * 2)
495         }
496         sine3Msg { arg freqs, amps, phases,normalize=true,asWavetable=true,clearFirst=true;
497                 ^["/b_gen",bufnum,"sine3",
498                         normalize.binaryValue
499                         + (asWavetable.binaryValue * 2)
500                         + (clearFirst.binaryValue * 4)]
501                         ++ [freqs, amps, phases].lace(freqs.size * 3)
502         }
503         chebyMsg { arg amplitudes,normalize=true,asWavetable=true,clearFirst=true;
504                 ^["/b_gen",bufnum,"cheby",
505                         normalize.binaryValue
506                         + (asWavetable.binaryValue * 2)
507                         + (clearFirst.binaryValue * 4)]
508                         ++ amplitudes
509         }
511         copyData { arg buf, dstStartAt = 0, srcStartAt = 0, numSamples = -1;
512                 server.listSendMsg(
513                         this.copyMsg(buf, dstStartAt, srcStartAt, numSamples)
514                 )
515         }
516         copyMsg { arg buf, dstStartAt = 0, srcStartAt = 0, numSamples = -1;
517                 ^["/b_gen", buf.bufnum, "copy", dstStartAt, bufnum, srcStartAt, numSamples]
518         }
520         // close a file, write header, after DiskOut usage
521         close { arg completionMessage;
522                 server.listSendMsg( this.closeMsg(completionMessage)  );
523         }
524         closeMsg { arg completionMessage;
525                 ^["/b_close", bufnum, completionMessage.value(this) ];
526         }
528         query {
529                 OSCFunc({ arg msg;
530                         Post << "bufnum      :" << msg[1] << Char.nl
531                                 << "numFrames   : " << msg[2] << Char.nl
532                                 << "numChannels : " << msg[3] << Char.nl
533                                 << "sampleRate  :" << msg[4] << Char.nl << Char.nl;
534                 }, '/b_info', server.addr).oneShot;
535                 server.sendMsg("/b_query",bufnum)
536         }
538         updateInfo { |action|
539                 // add to the array here. That way, update will be accurate even if this buf
540                 // has been freed
541                 this.cache;
542                 doOnInfo = action;
543                 server.sendMsg("/b_query", bufnum);
544         }
546         // cache Buffers for easy info updating
547         cache {
548                 Buffer.initServerCache(server);
549                 serverCaches[server][bufnum] = this;
550         }
551         uncache {
552                 if(serverCaches[server].notNil,{
553                         serverCaches[server].removeAt(bufnum);
554                 });
555                 if(serverCaches[server].size == 1) {
556                         // the 1 item would be the responder
557                         // if there is more than 1 item then the rest are cached buffers
558                         // else we can remove.
559                         // cx: tho i don't see why its important. it will just have to be added
560                         // back when the next buffer is added and the responder is removed when
561                         // the server reboots
562                         Buffer.clearServerCaches(server);
563                 }
564         }
565         *initServerCache { |server|
566                 serverCaches[server] ?? {
567                         serverCaches[server] = IdentityDictionary.new;
568                         serverCaches[server][\responder] = OSCFunc({ |m|
569                                 var     buffer = serverCaches[server][m[1]];
570                                 if(buffer.notNil) {
571                                         buffer.numFrames = m[2];
572                                         buffer.numChannels = m[3];
573                                         buffer.sampleRate = m[4];
574                                         buffer.queryDone;
575                                 };
576                         }, '/b_info', server.addr).fix;
577                         NotificationCenter.register(server,\newAllocators,this,{
578                                 this.clearServerCaches(server);
579                         });
580                 }
581         }
582         *clearServerCaches { |server|
583                 if(serverCaches[server].notNil) {
584                         serverCaches[server][\responder].free;
585                         serverCaches.removeAt(server);
586                 }
587         }
588         *cachedBuffersDo { |server, func|
589                 var     i = 0;
590                 serverCaches[server] !? {
591                         serverCaches[server].keysValuesDo({ |key, value|
592                                 if(key.isNumber) { func.value(value, i); i = i + 1 };
593                         });
594                 }
595         }
596         *cachedBufferAt { |server, bufnum|
597                 ^serverCaches[server].tryPerform(\at, bufnum)
598         }
600         // called from Server when b_info is received
601         queryDone {
602                 doOnInfo.value(this);
603                 doOnInfo = nil;
604         }
606         printOn { arg stream;
607                 stream << this.class.name << "(" <<* [bufnum,numFrames,numChannels,sampleRate,path] <<")";
608         }
610         *loadDialog { arg server,startFrame = 0,numFrames, action, bufnum;
611                 var buffer;
612                 server = server ? Server.default;
613                 bufnum ?? { bufnum = server.bufferAllocator.alloc(1) };
614                 if(bufnum.isNil) {
615                         Error("No more buffer numbers -- free some buffers before allocating more.").throw
616                 };
617                 buffer = super.newCopyArgs(server, bufnum).cache;
618                 File.openDialog("Select a file...",{ arg path;
619                         buffer.doOnInfo_(action)
620                                 .allocRead(path,startFrame,numFrames, {["/b_query",buffer.bufnum]})
621                 });
622                 ^buffer;
623         }
625         play { arg loop = false, mul = 1;
626                 ^{ var player;
627                         player = PlayBuf.ar(numChannels,bufnum,BufRateScale.kr(bufnum),
628                                 loop: loop.binaryValue);
629                         loop.not.if(FreeSelfWhenDone.kr(player));
630                         player * mul;
631                 }.play(server);
632         }
634         duration { ^numFrames / sampleRate }
636         asUGenInput { ^this.bufnum }
637         asControlInput { ^this.bufnum }
639         asBufWithValues {
640                 ^this
641         }