|
| 1 | +title:: Buffer Writing Guide |
| 2 | +summary:: This section illustrates some case uses of writing to buffers |
| 3 | +categories:: UGens>Buffer |
| 4 | +related:: Classes/BufRd, Classes/BufWr, Classes/IBufWr |
| 5 | + |
| 6 | +description:: |
| 7 | + |
| 8 | +This section is a complement to the helpfiles of link::Classes/BufWr:: and link::Classes/IBufWr::, covering specific behaviours of both UGens. |
| 9 | + |
| 10 | +subsection:: Multichannel Inputs to a Single Index |
| 11 | + |
| 12 | +Both link::Classes/BufWr:: and link::Classes/IBufWr:: behave the same way, which is to NOT multichannel expand its input, instead passing the input array as is to the UGen. What that means is that the first input can be an array of up to the same size as the number of channels in the buffer it points to. The index (phase) of the buffer these values will be written to is the same for all. |
| 13 | + |
| 14 | +strong::Example 1: Basic Stereo Buffer Writing:: |
| 15 | +code:: |
| 16 | +// allocate a stereo buffer |
| 17 | +c = Buffer.alloc(s, s.sampleRate,2); |
| 18 | + |
| 19 | +( |
| 20 | +{ |
| 21 | + BufWr.ar( // built-in buffer writer, since we won't skip indices |
| 22 | + Phasor.ar(0, [1, 0.5] , 0, c.numFrames), // basic source, multichannel expanded to 2 different ramps |
| 23 | + c.bufnum, |
| 24 | + Phasor.ar(0, 1 , 0, c.numFrames), |
| 25 | + ); |
| 26 | + BufRd.ar( |
| 27 | + 2, |
| 28 | + c.bufnum, |
| 29 | + Phasor.ar(0, 1 , 0, c.numFrames),// reading at one sample per sample |
| 30 | + 0, |
| 31 | + 1 |
| 32 | + ); |
| 33 | +}.plot(0.1) |
| 34 | +) |
| 35 | +:: |
| 36 | + |
| 37 | +strong::Example 2: Common Error, too large an input array:: |
| 38 | +code:: |
| 39 | +// allocate a stereo buffer |
| 40 | +c = Buffer.alloc(s, s.sampleRate,2); |
| 41 | + |
| 42 | +( |
| 43 | +// this code will try to write a larger array than the number of channels in the buffer |
| 44 | +{ |
| 45 | + BufWr.ar( // built-in buffer writer, since we won't skip indices |
| 46 | + Phasor.ar(0, [1, 0.5, 2] , 0, c.numFrames), // basic source, multichannel expanded to 3 different ramps |
| 47 | + c.bufnum, |
| 48 | + Phasor.ar(0, 1 , 0, c.numFrames), |
| 49 | + ); |
| 50 | +}.plot(0.1) |
| 51 | +) |
| 52 | +// check the Post Window, you get told off! |
| 53 | +:: |
| 54 | + |
| 55 | +strong::Example 3: Common Error, multichannel expansion of the index (phase) input:: |
| 56 | +code:: |
| 57 | +// this code will try to multichannel expand the index(phase) input. |
| 58 | +// allocate a stereo buffer |
| 59 | +c = Buffer.alloc(s, s.sampleRate,2); |
| 60 | + |
| 61 | +( |
| 62 | +{ |
| 63 | + BufWr.ar( // built-in buffer writer, since we won't skip indices |
| 64 | + Phasor.ar(0, [1, 2] , 0, c.numFrames), // basic source, multichannel expanded to 2 different ramps |
| 65 | + c.bufnum, |
| 66 | + Phasor.ar(0, [1, 2] , 0, c.numFrames), // trying two different rates of writing, same speed per input |
| 67 | + ); |
| 68 | + BufRd.ar( |
| 69 | + 2, |
| 70 | + c.bufnum, |
| 71 | + Phasor.ar(0, 1 , 0, c.numFrames),// reading at one sample per sample |
| 72 | + 0, |
| 73 | + 1 |
| 74 | + ); |
| 75 | +}.plot(0.1) |
| 76 | +) // if that was working we would expect 2 parallel lines, the 2nd perforated since we skip at the same speed than the values... |
| 77 | +:: |
| 78 | + |
| 79 | +subsection:: The Main Differences between BufWr and IBufWr |
| 80 | + |
| 81 | +The helpfile of link::Classes/IBufWr:: explains clearly the need of a buffer writing UGen that would not leave unfilled indices should the index(phase) input was not contiguous, and illustrates it with classic musical examples. The addition of the interpolating filling has a few consequences on slight different behaviours between the objects, namely variable CPU taxing, a one-sample lag, and the need of a pausing mechanism. |
| 82 | + |
| 83 | +strong:: Variable CPU taxing:: |
| 84 | + |
| 85 | +link::Classes/IBufWr:: needs to check if there is an index or more that has been skipped, which implies some branching in the code, which takes CPU time. Even at one-sample-per-sample writing speed, it is less efficient than link::Classes/BufWr:: because of that check. |
| 86 | + |
| 87 | +code:: |
| 88 | +// declare a buffer |
| 89 | +b = Buffer.alloc(s, s.sampleRate); |
| 90 | + |
| 91 | +// declares a Synth for each writing UGen with potential writing rates |
| 92 | +( |
| 93 | +SynthDef(\testBufWr,{ |
| 94 | + arg rate = 1; |
| 95 | + var source = SinOsc.ar(200,0,0.0001); |
| 96 | + var write_index = Phasor.ar(0, rate , 0, b.numFrames); |
| 97 | + BufWr.ar(source, b.bufnum, write_index); |
| 98 | + Out.ar(0,BufRd.ar(1,b.bufnum,write_index,0, 1)); |
| 99 | +}).send; |
| 100 | + |
| 101 | +SynthDef(\testIBufWr,{ |
| 102 | + arg rate = 1, interp = 0; |
| 103 | + var source = SinOsc.ar(200,0,0.0001); |
| 104 | + var write_index = Phasor.ar(0, rate , 0, b.numFrames); |
| 105 | + IBufWr.ar(source, b.bufnum, write_index, interp); |
| 106 | + Out.ar(0,BufRd.ar(1,b.bufnum,write_index,0, 1)); |
| 107 | +}).send; |
| 108 | +) |
| 109 | + |
| 110 | +//create 100 instances of BufWr, observe the server CPU |
| 111 | +( |
| 112 | +b.zero; |
| 113 | +g = Group.new; |
| 114 | +100.do({Synth(\testBufWr , target:g)}); |
| 115 | +) |
| 116 | + |
| 117 | +// Free them |
| 118 | +g.free |
| 119 | + |
| 120 | +//create 100 instances of IBufWr with no interpolation, simply sampling-and-holding between skipped indices, observe the server CPU |
| 121 | +( |
| 122 | +b.zero; |
| 123 | +g = Group.new; |
| 124 | +100.do({Synth(\testIBufWr , target:g)}) |
| 125 | +) |
| 126 | + |
| 127 | +// worse by about 40%. Free them. |
| 128 | +g.free |
| 129 | + |
| 130 | + |
| 131 | +//create 100 instances of BufWr writing 10 times faster than realtime, observe the server CPU |
| 132 | +( |
| 133 | +b.zero; |
| 134 | +g = Group.new; |
| 135 | +100.do({Synth(\testBufWr, [\rate, 10], g)}) |
| 136 | +) |
| 137 | +// not much has changed since nothing more is done |
| 138 | +g.free |
| 139 | + |
| 140 | +//create 100 instances of IBufWr writing 10 times faster than realtime, still only S&H, observe the server CPU |
| 141 | +( |
| 142 | +b.zero; |
| 143 | +g = Group.new; |
| 144 | +100.do({Synth(\testIBufWr, [\rate, 10], g)}) |
| 145 | +) |
| 146 | +// now the 10 sample writing per sample start to hit the performance |
| 147 | +g.free |
| 148 | + |
| 149 | +//create 100 instances of IBufWr writing 10 times faster than realtime, this time with linear interpolation, observe the server CPU |
| 150 | +( |
| 151 | +b.zero; |
| 152 | +g = Group.new; |
| 153 | +100.do({Synth(\testIBufWr, [\rate, 10, \interp, 1], g)}) |
| 154 | +) |
| 155 | +// now the interpolation hits even more. |
| 156 | +g.free |
| 157 | +:: |
| 158 | + |
| 159 | +strong:: 1 sample late:: |
| 160 | + |
| 161 | +The way link::Classes/IBufWr:: interpolates is by making the difference between indices. What this implies is that at the end of a vector, the known value of the last sample will not be known until the first value of the next vector. In a code where the writing happens right before the playing, it creates a whole in the output. The discriminating ear will have heard that in the previous examples above. This is in fact written as soon as possible, as one can see in the example below: |
| 162 | + |
| 163 | +code:: |
| 164 | +b = Buffer.alloc(s, 200); |
| 165 | +( |
| 166 | +b.zero; |
| 167 | +{ |
| 168 | + IBufWr.ar( |
| 169 | + Phasor.ar(0, 1 , 0, b.numFrames),// writing values, incremented one sample per sample |
| 170 | + b.bufnum, |
| 171 | + Phasor.ar(0, 1 , 0, b.numFrames),// writes one sample per sample |
| 172 | + 1, |
| 173 | + 0.5 |
| 174 | + ); |
| 175 | + BufRd.ar( |
| 176 | + 1, |
| 177 | + b.bufnum, |
| 178 | + Phasor.ar(0, 1 , 0, b.numFrames), |
| 179 | + 0, |
| 180 | + 1 |
| 181 | + ); |
| 182 | +}.plot(130/44100) |
| 183 | +) |
| 184 | +// the plot shows playback holes at the end of buffer |
| 185 | +// but if the buffer is drawn, there are no wholes! |
| 186 | +b.plot |
| 187 | +:: |
| 188 | + |
| 189 | +strong::negative index:: |
| 190 | + |
| 191 | +A feature of link::Classes/IBufWr:: is that the writing can be paused at any time, by providing negative indices. Once the writing starts again, the first index is not interpolated from the previous positive entry. |
0 commit comments