cmake build system: visiblity support for clang
[supercollider.git] / SCClassLibrary / Common / Control / Buffer.sc
blob45b316b4e193de7462c7814b8d336ec02749674b
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 = 0.0, 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                 collstream = CollStream.new;
242                 collstream.collection = collection;
243                 collsize = collection.size;
245                 if ( collsize > ((numFrames - startFrame) * numChannels),
246                                 { "Collection larger than available number of Frames".warn });
248                 this.streamCollection(collstream, collsize, startFrame * numChannels, wait, action);
249         }
251         // called internally
252         streamCollection { arg collstream, collsize, startFrame = 0, wait = -1, action;
253                 var bundsize, pos;
254                 {
255                         // wait = -1 sends each packet when the previous has arrived
256                         // wait = 0 might not be safe in a high traffic situation
257                         // maybe okay with tcp
258                         pos = collstream.pos;
259                         while { pos < collsize } {
260                                 // 1626 max size for setn under udp
261                                 bundsize = min(1626, collsize - pos);
262                                 server.listSendMsg(['/b_setn', bufnum, pos + startFrame, bundsize]
263                                         ++ Array.fill(bundsize, { collstream.next }));
264                                 pos = collstream.pos;
265                                 if(wait >= 0) { wait.wait } { server.sync };
266                         };
268                         action.value(this);
270                 }.forkIfNeeded;
271         }
273         // these next two get the data and put it in a float array which is passed to action
275         loadToFloatArray { arg index = 0, count = -1, action;
276                 var msg, cond, path, file, array;
277                 {
278                         path = PathName.tmp ++ this.hash.asString;
279                         msg = this.write(path, "aiff", "float", count, index);
280                         server.sync;
281                         file = SoundFile.new;
282                         protect {
283                                 file.openRead(path);
284                                 array = FloatArray.newClear(file.numFrames * file.numChannels);
285                                 file.readData(array);
286                         } {
287                                 file.close;
288                                 if(File.delete(path).not) { ("Could not delete data file:" + path).warn };
289                         };
291                         action.value(array, this);
293                 }.forkIfNeeded;
294         }
296         // risky without wait
297         getToFloatArray { arg index = 0, count, wait = 0.01, timeout = 3, action;
298                 var refcount, array, pos, getsize, resp, done = false;
299                 pos = index = index.asInteger;
300                 count = (count ? (numFrames * numChannels)).asInteger;
301                 array = FloatArray.newClear(count);
302                 refcount = (count / 1633).roundUp;
303                 count = count + pos;
304                 //("refcount" + refcount).postln;
305                 resp = OSCFunc({ arg msg;
306                         if(msg[1] == bufnum, {
307                                 //("received" + msg).postln;
308                                 array = array.overWrite(FloatArray.newFrom(msg.copyToEnd(4)), msg[2] - index);
309                                 refcount = refcount - 1;
310                                 //("countDown" + refcount).postln;
311                                 if(refcount <= 0, {done = true; resp.clear; action.value(array, this); });
312                         });
313                 }, '/b_setn', server.addr);
314                 {
315                         while({pos < count}, {
316                                 // 1633 max size for getn under udp
317                                 getsize = min(1633, count - pos);
318                                 //("sending from" + pos).postln;
319                                 server.listSendMsg(this.getnMsg(pos, getsize));
320                                 pos = pos + getsize;
321                                 if(wait >= 0) { wait.wait } { server.sync };
322                         });
324                 }.forkIfNeeded;
325                 // lose the responder if the network choked
326                 SystemClock.sched(timeout,
327                         { done.not.if({ resp.free; "Buffer-streamToFloatArray failed!".warn;
328                                 "Try increasing wait time".postln;});
329                 });
330         }
332         write { arg path, headerFormat = "aiff", sampleFormat = "int24", numFrames = -1,
333                                                 startFrame = 0,leaveOpen = false, completionMessage;
334                 path = path ?? { thisProcess.platform.recordingsDir +/+ "SC_" ++ Date.localtime.stamp ++ "." ++ headerFormat };
335                 server.listSendMsg(
336                         this.writeMsg(path, headerFormat, sampleFormat, numFrames, startFrame,
337                                 leaveOpen, completionMessage)
338                         );
339         }
340         writeMsg { arg path,headerFormat="aiff",sampleFormat="int24",numFrames = -1,
341                                                 startFrame = 0,leaveOpen = false, completionMessage;
342                 // doesn't change my path
343                 ^["/b_write", bufnum, path,
344                                 headerFormat,sampleFormat, numFrames, startFrame,
345                                 leaveOpen.binaryValue, completionMessage.value(this)];
346                 // writeEnabled = true;
347         }
349         free { arg completionMessage;
350                 server.listSendMsg( this.freeMsg(completionMessage) );
351         }
352         freeMsg { arg completionMessage;
353                 this.uncache;
354                 server.bufferAllocator.free(bufnum);
355                 ^["/b_free", bufnum, completionMessage.value(this)];
356         }
357         *freeAll { arg server;
358                 var b;
359                 server = server ? Server.default;
360                 server.bufferAllocator.blocks.do({ arg block;
361                         (block.address .. block.address + block.size - 1).do({ |i|
362                                 b = b.add( ["/b_free", i] );
363                         });
364                         server.bufferAllocator.free(block.address);
365                 });
366                 server.sendBundle(nil, *b);
367                 this.clearServerCaches(server);
368         }
369         zero { arg completionMessage;
370                 server.listSendMsg(this.zeroMsg(completionMessage));
371         }
372         zeroMsg { arg completionMessage;
373                 ^["/b_zero", bufnum ,  completionMessage.value(this) ]
374         }
376         set { arg index,float ... morePairs;
377                 server.listSendMsg(["/b_set",bufnum,index,float] ++ morePairs);
378         }
379         setMsg { arg index,float ... morePairs;
380                 ^["/b_set",bufnum,index,float] ++ morePairs;
381         }
383         setn { arg ... args;
384                 server.sendMsg(*this.setnMsg(*args));
385         }
386         setnMsgArgs{arg ... args;
387                 var nargs;
388                 nargs = List.new;
389                 args.pairsDo{ arg control, moreVals;
390                         if(moreVals.isArray,{
391                                 nargs.addAll([control, moreVals.size]++ moreVals)
392                         },{
393                                 nargs.addAll([control, 1, moreVals]);
394                         });
395                 };
396                 ^nargs;
397         }
398         setnMsg { arg ... args;
399                 ^["/b_setn",bufnum] ++ this.setnMsgArgs(*args);
400         }
401         get { arg index, action;
402                 OSCpathResponder(server.addr,['/b_set',bufnum,index],{ arg time, r, msg;
403                         action.value(msg.at(3)); r.remove }).add;
404                 server.listSendMsg(["/b_get",bufnum,index]);
405         }
406         getMsg { arg index;
407                 ^["/b_get",bufnum,index];
408         }
409         getn { arg index, count, action;
410                 OSCpathResponder(server.addr,['/b_setn',bufnum,index],{arg time, r, msg;
411                         action.value(msg.copyToEnd(4)); r.remove } ).add;
412                 server.listSendMsg(["/b_getn",bufnum,index, count]);
413         }
414         getnMsg { arg index, count;
415                 ^["/b_getn",bufnum,index, count];
416         }
419         fill { arg startAt,numFrames,value ... more;
420                 server.listSendMsg(["/b_fill",bufnum,startAt,numFrames.asInt,value]
421                          ++ more);
422         }
423         fillMsg { arg startAt,numFrames,value ... more;
424                 ^["/b_fill",bufnum,startAt,numFrames.asInt,value]
425                          ++ more;
426         }
428         normalize { arg newmax=1, asWavetable=false;
429                 server.listSendMsg(["/b_gen",bufnum,if(asWavetable, "wnormalize", "normalize"),newmax]);
430         }
431         normalizeMsg { arg newmax=1, asWavetable=false;
432                 ^["/b_gen",bufnum,if(asWavetable, "wnormalize", "normalize"),newmax];
433         }
435         gen { arg genCommand, genArgs, normalize=true,asWavetable=true,clearFirst=true;
436                 server.listSendMsg(["/b_gen",bufnum,genCommand,
437                         normalize.binaryValue
438                         + (asWavetable.binaryValue * 2)
439                         + (clearFirst.binaryValue * 4)]
440                         ++ genArgs)
441         }
442         genMsg { arg genCommand, genArgs, normalize=true,asWavetable=true,clearFirst=true;
443                 ^["/b_gen",bufnum,genCommand,
444                         normalize.binaryValue
445                         + (asWavetable.binaryValue * 2)
446                         + (clearFirst.binaryValue * 4)]
447                         ++ genArgs;
448         }
449         sine1 { arg amps,normalize=true,asWavetable=true,clearFirst=true;
450                 server.listSendMsg(["/b_gen",bufnum,"sine1",
451                         normalize.binaryValue
452                         + (asWavetable.binaryValue * 2)
453                         + (clearFirst.binaryValue * 4)]
454                         ++ amps)
455         }
456         sine2 { arg freqs, amps,normalize=true,asWavetable=true,clearFirst=true;
457                 server.listSendMsg(["/b_gen",bufnum,"sine2",
458                         normalize.binaryValue
459                         + (asWavetable.binaryValue * 2)
460                         + (clearFirst.binaryValue * 4)]
461                         ++ [freqs, amps].lace(freqs.size * 2))
462         }
463         sine3 { arg freqs, amps, phases,normalize=true,asWavetable=true,clearFirst=true;
464                 server.listSendMsg(["/b_gen",bufnum,"sine3",
465                         normalize.binaryValue
466                         + (asWavetable.binaryValue * 2)
467                         + (clearFirst.binaryValue * 4)]
468                         ++ [freqs, amps, phases].lace(freqs.size * 3))
469         }
470         cheby { arg amplitudes,normalize=true,asWavetable=true,clearFirst=true;
471                 server.listSendMsg(["/b_gen",bufnum,"cheby",
472                         normalize.binaryValue
473                         + (asWavetable.binaryValue * 2)
474                         + (clearFirst.binaryValue * 4)]
475                         ++ amplitudes)
476         }
477         sine1Msg { arg amps,normalize=true,asWavetable=true,clearFirst=true;
478                 ^["/b_gen",bufnum,"sine1",
479                         normalize.binaryValue
480                         + (asWavetable.binaryValue * 2)
481                         + (clearFirst.binaryValue * 4)]
482                         ++ amps
483         }
484         sine2Msg { arg freqs, amps,normalize=true,asWavetable=true,clearFirst=true;
485                 ^["/b_gen",bufnum,"sine2",
486                         normalize.binaryValue
487                         + (asWavetable.binaryValue * 2)
488                         + (clearFirst.binaryValue * 4)]
489                         ++ [freqs, amps].lace(freqs.size * 2)
490         }
491         sine3Msg { arg freqs, amps, phases,normalize=true,asWavetable=true,clearFirst=true;
492                 ^["/b_gen",bufnum,"sine3",
493                         normalize.binaryValue
494                         + (asWavetable.binaryValue * 2)
495                         + (clearFirst.binaryValue * 4)]
496                         ++ [freqs, amps, phases].lace(freqs.size * 3)
497         }
498         chebyMsg { arg amplitudes,normalize=true,asWavetable=true,clearFirst=true;
499                 ^["/b_gen",bufnum,"cheby",
500                         normalize.binaryValue
501                         + (asWavetable.binaryValue * 2)
502                         + (clearFirst.binaryValue * 4)]
503                         ++ amplitudes
504         }
506         copy { arg buf, dstStartAt = 0, srcStartAt = 0, numSamples = -1;
507                 if(buf.notNil) {
508                         this.deprecated(thisMethod, this.class.findRespondingMethodFor(\copyData));
509                         this.copyData(buf, dstStartAt, srcStartAt, numSamples);
510                 } {
511                         ^super.copy
512                 }
513         }
515         copyData { arg buf, dstStartAt = 0, srcStartAt = 0, numSamples = -1;
516                 server.listSendMsg(
517                         this.copyMsg(buf, dstStartAt, srcStartAt, numSamples)
518                 )
519         }
520         copyMsg { arg buf, dstStartAt = 0, srcStartAt = 0, numSamples = -1;
521                 ^["/b_gen", buf.bufnum, "copy", dstStartAt, bufnum, srcStartAt, numSamples]
522         }
524         // close a file, write header, after DiskOut usage
525         close { arg completionMessage;
526                 server.listSendMsg( this.closeMsg(completionMessage)  );
527         }
528         closeMsg { arg completionMessage;
529                 ^["/b_close", bufnum, completionMessage.value(this) ];
530         }
532         query {
533                 OSCFunc({ arg msg;
534                         Post << "bufnum      :" << msg[1] << Char.nl
535                                 << "numFrames   : " << msg[2] << Char.nl
536                                 << "numChannels : " << msg[3] << Char.nl
537                                 << "sampleRate  :" << msg[4] << Char.nl << Char.nl;
538                 }, '/b_info', server.addr).oneShot;
539                 server.sendMsg("/b_query",bufnum)
540         }
542         updateInfo { |action|
543                 // add to the array here. That way, update will be accurate even if this buf
544                 // has been freed
545                 this.cache;
546                 doOnInfo = action;
547                 server.sendMsg("/b_query", bufnum);
548         }
550         // cache Buffers for easy info updating
551         cache {
552                 Buffer.initServerCache(server);
553                 serverCaches[server][bufnum] = this;
554         }
555         uncache {
556                 if(serverCaches[server].notNil,{
557                         serverCaches[server].removeAt(bufnum);
558                 });
559                 if(serverCaches[server].size == 1) {
560                         // the 1 item would be the responder
561                         // if there is more than 1 item then the rest are cached buffers
562                         // else we can remove.
563                         // cx: tho i don't see why its important. it will just have to be added
564                         // back when the next buffer is added and the responder is removed when
565                         // the server reboots
566                         Buffer.clearServerCaches(server);
567                 }
568         }
569         *initServerCache { |server|
570                 serverCaches[server] ?? {
571                         serverCaches[server] = IdentityDictionary.new;
572                         serverCaches[server][\responder] = OSCFunc({ |m|
573                                 var     buffer = serverCaches[server][m[1]];
574                                 if(buffer.notNil) {
575                                         buffer.numFrames = m[2];
576                                         buffer.numChannels = m[3];
577                                         buffer.sampleRate = m[4];
578                                         buffer.queryDone;
579                                 };
580                         }, '/b_info', server.addr).fix;
581                         NotificationCenter.register(server,\newAllocators,this,{
582                                 this.clearServerCaches(server);
583                         });
584                 }
585         }
586         *clearServerCaches { |server|
587                 if(serverCaches[server].notNil) {
588                         serverCaches[server][\responder].free;
589                         serverCaches.removeAt(server);
590                 }
591         }
592         *cachedBuffersDo { |server, func|
593                 var     i = 0;
594                 serverCaches[server] !? {
595                         serverCaches[server].keysValuesDo({ |key, value|
596                                 if(key.isNumber) { func.value(value, i); i = i + 1 };
597                         });
598                 }
599         }
600         *cachedBufferAt { |server, bufnum|
601                 ^serverCaches[server].tryPerform(\at, bufnum)
602         }
604         // called from Server when b_info is received
605         queryDone {
606                 doOnInfo.value(this);
607                 doOnInfo = nil;
608         }
610         printOn { arg stream;
611                 stream << this.class.name << "(" <<* [bufnum,numFrames,numChannels,sampleRate,path] <<")";
612         }
614         *loadDialog { arg server,startFrame = 0,numFrames, action, bufnum;
615                 var buffer;
616                 server = server ? Server.default;
617                 bufnum ?? { bufnum = server.bufferAllocator.alloc(1) };
618                 if(bufnum.isNil) {
619                         Error("No more buffer numbers -- free some buffers before allocating more.").throw
620                 };
621                 buffer = super.newCopyArgs(server, bufnum).cache;
622                 File.openDialog("Select a file...",{ arg path;
623                         buffer.doOnInfo_(action)
624                                 .allocRead(path,startFrame,numFrames, {["/b_query",buffer.bufnum]})
625                 });
626                 ^buffer;
627         }
629         play { arg loop = false, mul = 1;
630                 ^{ var player;
631                         player = PlayBuf.ar(numChannels,bufnum,BufRateScale.kr(bufnum),
632                                 loop: loop.binaryValue);
633                         loop.not.if(FreeSelfWhenDone.kr(player));
634                         player * mul;
635                 }.play(server);
636         }
638         duration { ^numFrames / sampleRate }
640         asUGenInput { ^this.bufnum }
641         asControlInput { ^this.bufnum }
643         asBufWithValues {
644                 ^this
645         }
647         *readExample {|server, startFrame = 0, numFrames = -1, action, bufnum|
648                 var file = Platform.exampleSoundFile;
649                 ^this.read(server, file, startFrame, numFrames, action, bufnum)
650         }