1 /**
2 Safe, flexible, audio buffer RAII structure.
3 
4 Copyright: Copyright Guillaume Piolat 2021.
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 */
7 module dplug.audio.audiobuffer;
8 
9 import core.bitop: bsf;
10 import core.stdc.string;
11 import dplug.core.math;
12 import inteli.emmintrin;
13 
14 // A word of warning:
15 // Do not try to use "inout" in this file.
16 // this is the path to misery.
17 
18 nothrow:
19 @nogc:
20 @safe:
21 
22 /// Allocate a new `AudioBuffer` with given `frames` and `channels`.
23 /// Its data is _not_ initialized.
24 AudioBuffer!T audioBufferAlloc(T)(int channels, int frames, int alignment = 1)
25 {
26     AudioBuffer!T buf;
27     buf.resize(channels, frames, alignment);
28     return buf;
29 }
30 
31 /// Allocate a new `AudioBuffer` with given `frames` and `channels`.
32 /// Its data is initialized to zeroes.
33 AudioBuffer!T audioBufferAllocZeroed(T)(int channels, int frames, int alignment = 1)
34 {
35     AudioBuffer!T buf;
36     buf.resize(channels, frames, alignment);
37     buf.fillWithZeroes();
38     return buf;
39 }
40 
41 /// Create a `AudioBuffer` by reusing existing data. Hence no format conversion happens.
42 AudioBuffer!T audioBufferFromData(T)(int channels, int frames, T** inData) @system
43 {
44     AudioBuffer!T buf;
45     buf.initWithData(channels, frames, inData);
46     return buf;
47 }
48 ///ditto
49 const(AudioBuffer!T) audioBufferFromData(T)(int channels, int frames, const(T*)* inData) @system
50 {    
51     int frameStart = 0;
52     ubyte alignment = 1;
53     ubyte flags = 0;
54     return const(AudioBuffer!T)(channels, frames, inData, frameStart, alignment, flags);
55 }
56 
57 /*
58 ///ditto
59 const(AudioBuffer!T) audioBufferCreateSubBuffer(T)(ref const(AudioBuffer!T) parent, int frameStart, int frameEnd) @trusted
60 {
61     assert(frameStart >= 0);
62     assert(frameStart <= frameEnd);
63     assert(frameEnd <= parent.frames());
64     ubyte alignment = 1;
65     int channels = parent.channels();
66     int frames = frameEnd - frameStart;
67 
68     const(T*)* data = parent.getChannelsPointers();
69     bool parentHasZeroFlag = parent.hasZeroFlag();
70 
71     return const(AudioBuffer!T)(channels, frames, data, frameStart,
72                                 alignment, parentHasZeroFlag ? AudioBuffer!T.Flags.isZero : 0);
73 }
74 */
75 
76 /// Duplicate an `AudioBuffer` with an own allocation, make it mutable.
77 AudioBuffer!T audioBufferDup(T)(ref const(AudioBuffer!T) buf, int alignment = 1)
78 {
79     AudioBuffer!T b;
80     b.resize(buf.channels(), buf.frames(), alignment);
81     b.copyFrom(buf);
82     return b;
83 }
84 
85 
86 /// An `AudioBuffer` is a multi-channel buffer, with defined length, to act as storage 
87 /// of audio samples of type `T`.
88 /// It is passed around by DSP algorithms.
89 /// Data is store deinterleaved.
90 struct AudioBuffer(T)
91 {
92 public:
93 nothrow:
94 @nogc:
95 @safe:
96 
97     /// Change the size (`channels` and `frames` of the underlying store.
98     /// Data is left uninitialized.
99     /// Typically you would reuse an `AudioBuffer` if you want to reuse the allocation.
100     /// When the same size is requested, the same allocation is reused (unless alignment is changed).
101     void resize(int channels, int frames, int alignment = 1)
102     {
103         resizeDiscard(channels, frames, alignment);
104 
105         // Debug: fill with NaNs, this will make non-initialization problem very explicit.
106         debug
107         {
108             fillWithValue(T.nan);
109         }
110     }
111 
112     /// Dispose the previous content, if any.
113     /// Allocate a new `AudioBuffer` with given `frames` and `channels`. This step can reuse an existing owned allocation.
114     void initWithData(int channels, int frames, T** inData) @system
115     {
116         assert(channels <= maxPossibleChannels);
117 
118         // Release own memory if any.
119         cleanUpData();
120 
121         _channels = channels;
122         _frames = frames; 
123         _alignment = 1;
124         _flags = Flags.hasOtherMutableReference;
125 
126         for (int n = 0; n < channels; ++n)
127         {
128             _channelPointers[n] = inData[n];
129         }
130     }
131 
132     ~this()
133     {
134         cleanUp();
135     }
136 
137     /// Return number of channels in this buffer.
138     int channels() const
139     {
140         return _channels;
141     }
142 
143     /// Return number of frames in this buffer.
144     int frames() const
145     {
146         return _frames;
147     }
148 
149     /// Return alignment of the sample storage.
150     int alignment() const
151     {
152         return _alignment;
153     }
154 
155 
156     @disable this(this);
157 
158     /// Recompute the status of the zero flag.
159     /// Otherwise, asking for a mutable pointer into the zero data will clear this flag.
160     /// This is useful if you've written in a buffer and want to optimize downstream.
161     void recomputeZeroFlag()
162     {
163         if (computeIsBufferSilent())
164             setZeroFlag();
165         else
166         {
167             // Normally the zero flag is already unset.
168             // is is a logical error if the zero flag is set when it can be non-zero
169             assert(!hasZeroFlag);
170         }
171     }
172 
173     /// Returns: true if the buffer is all zeroes.
174     bool isSilent() const
175     {
176         if (hasZeroFlag())
177         {
178             if (isIsolated())
179                 return true;
180             else
181                 return computeIsBufferSilent();
182         }
183         else
184             return computeIsBufferSilent();
185     }
186 
187     /// Returns: `true` is the data is only pointed to by this `AudioBuffer`.
188     bool isIsolated() const
189     {
190         return (_flags & Flags.hasOtherMutableReference)== 0;
191     }
192 
193     // This break the isolated flag manually, in case you want to be able 
194     // to use the zero flag regardless, at your own risk.
195     void assumeIsolated()
196     {
197         clearHasOtherMutableReferenceFlag();
198     }
199 
200     /// Returns: `true` is the buffer own its pointed audio data.
201     bool hasOwnership() const
202     {
203         return (_flags & Flags.hasOwnership) != 0;
204     }
205 
206     // <data-access>
207 
208     /// Get pointer of a given channel.
209     T* getChannelPointer(int channel)
210     {
211         clearZeroFlag();
212         return _channelPointers[channel];
213     }
214 
215     /// Get const pointer of a given channel.
216     const(T)* getChannelPointer(int channel) const
217     {
218         return _channelPointers[channel];
219     }
220 
221     /// Get immutable pointer of a given channel.
222     immutable(T)* getChannelPointer(int channel) immutable
223     {
224         return _channelPointers[channel];
225     }
226 
227     /// Get channel pointers.
228     T** getChannelsPointers() return @trusted
229     {
230         clearZeroFlag();
231         return _channelPointers.ptr;
232     }
233 
234     /// Get const channel pointers.
235     const(T*)* getChannelsPointers() return @trusted const
236     {
237         return _channelPointers.ptr;
238     }
239 
240     /// Get immutable channel pointers.
241     immutable(T*)* getChannelsPointers() return @trusted immutable
242     {
243         return _channelPointers.ptr;
244     }
245 
246     /// Get slice of a given channel.
247     T[] getChannel(int channel) @trusted
248     {
249         clearZeroFlag();
250         return _channelPointers[channel][0.._frames];
251     }
252     ///ditto
253     const(T)[] getChannel(int channel) const @trusted
254     {
255         return _channelPointers[channel][0.._frames];
256     }
257     ///ditto
258     immutable(T)[] getChannel(int channel) immutable @trusted
259     {
260         return _channelPointers[channel][0.._frames];
261     }
262     ///ditto
263     inout(T)[] opIndex(int channel) inout @trusted
264     {
265         return _channelPointers[channel][0.._frames];
266     }
267 
268     // </data-access>
269 
270     // <opIndex>
271 
272     /// Create an AudioBuffer that is a ref to the same data.
273     AudioBuffer opIndex()
274     {
275         return sliceFrames(0, _frames);
276     }
277     ///ditto
278     const(AudioBuffer) opIndex() const
279     {
280         return sliceFrames(0, _frames);
281     }
282 
283     /// Index a single sample.
284     ref inout(T) opIndex(int channel, int frame) inout @trusted
285     { 
286         return _channelPointers[channel][frame];
287     }
288 
289     /// Slice with a sub-range of channels.
290     AudioBuffer opIndex(int[2] chan)
291     {
292         return sliceChannels(chan[0], chan[1]);
293     } 
294     ///ditto
295     const(AudioBuffer) opIndex(int[2] chan) const
296     {
297         return sliceChannels(chan[0], chan[1]);
298     }
299 
300     /// Slice across channels and temporally.
301     /// Take a sub-range of channels, and a sub-range of frames.
302     AudioBuffer opIndex(int[2] chan, int[2] framesBounds)
303     {
304         return sliceChannels(chan[0], chan[1]).sliceFrames(framesBounds[0], framesBounds[1]);
305     } 
306     ///ditto
307     const(AudioBuffer) opIndex(int[2] chan, int[2] framesBounds) const
308     {
309         return sliceChannels(chan[0], chan[1]).sliceFrames(framesBounds[0], framesBounds[1]);
310     }
311 
312     // </op-index>
313 
314     // <slice>
315 
316     int opDollar(size_t dim : 0)() pure const 
317     { 
318         return _channels; 
319     }
320 
321     int opDollar(size_t dim : 1)() pure const
322     { 
323         return _frames; 
324     }
325 
326     /// Select only a slice of channels from `AudioBuffer`.
327     /// Params:
328     ///    frameStart offset in the buffer. Must be >= 0 and <= `frameEnd`.
329     ///    frameEnd offset in the buffer. Cannot be larger than the parent size.
330     AudioBuffer sliceChannels(int channelStart, int channelEnd) @trusted
331     {
332         assert(channelStart >= 0);
333         assert(channelStart <= channelEnd);
334         assert(channelEnd <= this.channels());
335         int channelSub = channelEnd - channelStart;
336         T** data = this.getChannelsPointers();
337 
338         ubyte flags = Flags.hasOtherMutableReference;
339         if (this.hasZeroFlag())
340             flags |= Flags.isZero;
341 
342         // Because this is a mutable reference, both the parent and the result
343         // get the "has another mutable ref" flag.
344         this.setHasOtherMutableReferenceFlag();
345 
346         return AudioBuffer!T(channelSub, 
347                              this.frames(), 
348                              data + channelStart,
349                              0, 
350                              _alignment,
351                              flags);
352     }
353     //ditto
354     const(AudioBuffer) sliceChannels(int channelStart, int channelEnd) const @trusted
355     {
356         assert(channelStart >= 0);
357         assert(channelStart <= channelEnd);
358         assert(channelEnd <= this.channels());
359         int channelSub = channelEnd - channelStart;
360         const(T*)* data = this.getChannelsPointers();
361 
362         ubyte flags = 0;
363         if (this.hasZeroFlag())
364             flags |= Flags.isZero;
365 
366         return AudioBuffer!T(channelSub, 
367                              this.frames(), 
368                              data + channelStart,
369                              0, 
370                              _alignment,
371                              flags);
372     }
373     ///ditto
374     int[2] opSlice(size_t dim)(int start, int end) const
375     {
376         return [start, end];
377     }
378 
379     /// Create a `AudioBuffer` derivated from another buffer.
380     /// Params:
381     ///    frameStart offset in the buffer. Must be >= 0 and <= `frameEnd`.
382     ///    frameEnd offset in the buffer. Cannot be larger than the parent size.
383     AudioBuffer sliceFrames(int frameStart, int frameEnd) @trusted
384     {
385         assert(frameStart >= 0);
386         assert(frameStart <= frameEnd);
387         assert(frameEnd <= this.frames());
388         ubyte alignment = childAlignment(_alignment, T.sizeof, frameStart);
389         int channels = this.channels();
390         int framesSub = frameEnd - frameStart;
391 
392         T** data = this.getChannelsPointers();
393 
394         ubyte flags = Flags.hasOtherMutableReference;
395         if (this.hasZeroFlag())
396             flags |= Flags.isZero;
397 
398         // Because this is a mutable reference, both the parent and the result
399         // get the "has another mutable ref" flag.
400         this.setHasOtherMutableReferenceFlag();
401 
402         return AudioBuffer!T(channels, 
403                              framesSub, 
404                              data,
405                              frameStart, 
406                              alignment,
407                              flags);
408     }
409     ///ditto
410     const(AudioBuffer) sliceFrames(int frameStart, int frameEnd) const @trusted
411     {
412         assert(frameStart >= 0);
413         assert(frameStart <= frameEnd);
414         assert(frameEnd <= this.frames());
415         ubyte alignment = childAlignment(_alignment, T.sizeof, frameStart);
416         int channels = this.channels();
417         int framesSub = frameEnd - frameStart;
418 
419         const(T*)* data = this.getChannelsPointers();
420 
421         ubyte flags = 0;
422         if (this.hasZeroFlag())
423             flags |= Flags.isZero;
424 
425         return AudioBuffer!T(channels, 
426                              framesSub, 
427                              data,
428                              frameStart, 
429                              alignment,
430                              flags);
431     }
432 
433     // </slice>
434 
435     // <copy>
436 
437     /// Copy samples from `source` to `dest`.
438     /// Number of `frames` and `channels` must match.
439     void copyFrom(ref const(AudioBuffer) source) @trusted
440     {
441         assert(_frames == source.frames());
442         assert(_channels == source.channels());
443 
444         size_t bytesForOneChannel = T.sizeof * _frames;
445         for (int chan = 0; chan < _channels; ++chan)
446         {
447             memmove(_channelPointers[chan], source._channelPointers[chan], bytesForOneChannel);
448         }
449         if (source.hasZeroFlag)
450             setZeroFlag();
451     }
452 
453     // </copy>
454 
455 
456     // <filling the buffer>
457 
458     /// Fill the buffer with zeroes.
459     void fillWithZeroes() @trusted
460     {
461         size_t bytesForOneChannel = T.sizeof * _frames;
462         for (int chan = 0; chan < _channels; ++chan)
463         {
464             memset(_channelPointers[chan], 0, bytesForOneChannel);
465         }
466         setZeroFlag();
467     }
468 
469     /// Fill the buffer with a single value.
470     /// Warning: the buffer must be in `fp32` format.
471     void fillWithValue(T value) @trusted
472     {
473         if (value == 0)
474             return fillWithZeroes(); // Note: this turns -0.0 into +0.0
475 
476         for (int chan = 0; chan < _channels; ++chan)
477         {
478             T* p = getChannelPointer(chan);
479             p[0.._frames] = value;
480         }
481         assert(!hasZeroFlag);
482     }
483 
484     // </filling the buffer>
485 
486     // <buffer splitting>
487 
488     /// Return an input range that returns several subbuffers that covers the 
489     /// parent buffer, each with length not larger than `maxFrames`.
490     auto chunkBy(int maxFrames)
491     {
492         static struct AudioBufferRange
493         {
494             AudioBuffer buf;
495             int offset = 0;
496             int maxFrames;
497             int totalFrames;
498 
499             AudioBuffer front()
500             {
501                 int end = offset + maxFrames;
502                 if (end > totalFrames)
503                     end = totalFrames;
504                 AudioBuffer res = buf.sliceFrames(offset, end);
505                 return res;
506             }
507 
508             void popFront()
509             {
510                 offset += maxFrames;
511             }
512 
513             bool empty()
514             {
515                 return offset >= totalFrames;
516             }
517         }
518         return AudioBufferRange( sliceFrames(0, frames()), 0, maxFrames, frames() );
519     }
520     ///ditto
521     auto chunkBy(int maxFrames) const
522     {
523         static struct AudioBufferRange
524         {
525             const(AudioBuffer) buf;
526             int offset;
527             int maxFrames;
528             int totalFrames;
529 
530             const(AudioBuffer) front()
531             {
532                 int end = offset + maxFrames;
533                 if (end > totalFrames)
534                     end = totalFrames;
535                 const(AudioBuffer) res = buf.sliceFrames(offset, end);
536                 return res;
537             }
538 
539             void popFront()
540             {
541                 offset += maxFrames;
542             }
543 
544             bool empty()
545             {
546                 return offset >= totalFrames;
547             }
548         }
549         return AudioBufferRange( sliceFrames(0, frames()), 0, maxFrames, frames() );
550     }
551 
552     // </buffer splitting>
553 
554 private:
555 
556     /// Internal flags
557     enum Flags : ubyte
558     {
559         /// Zero flag.
560         isZero = 1,
561 
562         /// Owner flag.
563         hasOwnership = 2, 
564 
565         /// Growable flag.
566         //isGrowable = 4
567 
568         /// Data is pointed to, mutably, by other things than this `AudioBuffer`.
569         hasOtherMutableReference = 8,
570     }
571 
572     // TODO: lift that limitation
573     enum maxPossibleChannels = 8;
574 
575     // Pointers to beginning of every channel.
576     T*[maxPossibleChannels] _channelPointers;
577     
578     // Pointer to start of data.
579     // If memory is _owned_, then it is allocated with `_mm_realloc_discard`/`_mm_free`
580     void* _data     = null;
581 
582     // Number of frames in the buffer.
583     int _frames;
584 
585     // Number of channels in the buffer.
586     int _channels;
587 
588     // Various flags.
589     ubyte _flags;
590 
591     // Current allocation alignment.
592     ubyte _alignment = 0; // current alignment, 0 means "unassigned" 
593 
594     // Test if the zero flag is set.
595     // Returns: `true` is the buffer has the `zeroFlag` set.
596     // Warning: that when this flag isn't set, the buffer could still contains only zeroes.
597     //          If you want to test for zeroes, use `isSilent` instead.
598     bool hasZeroFlag() const
599     {
600         return (_flags & Flags.isZero) != 0;
601     }    
602 
603     void clearZeroFlag()
604     {
605         _flags &= ~cast(int)Flags.isZero;
606     }
607 
608     void setZeroFlag()
609     {
610         _flags |= Flags.isZero;
611     }
612 
613     void setHasOtherMutableReferenceFlag()
614     {
615         _flags |= Flags.hasOtherMutableReference;
616     }
617 
618     void clearHasOtherMutableReferenceFlag()
619     {
620         _flags &= ~cast(int)Flags.hasOtherMutableReference;
621     }
622 
623     // Private constructor, the only way to create const/immutable object.
624     this(int channels, 
625          int frames, 
626          const(T*)* inData, 
627          int offsetFrames, // point further in the input
628          ubyte alignment, 
629          ubyte flags) @system 
630     {
631         assert(offsetFrames >= 0);
632         assert(channels <= maxPossibleChannels);
633         _channels = channels;
634         _frames = frames; 
635         _alignment = alignment;
636         _flags = flags;
637 
638         for (int n = 0; n < channels; ++n)
639         {
640             _channelPointers[n] = cast(T*)(inData[n]) + offsetFrames; // const_cast here
641         }
642     }
643 
644     void resizeDiscard(int channels, int frames, int alignment) @trusted
645     {
646         assert(channels >= 0 && frames >= 0);
647         assert(alignment >= 1 && alignment <= 128);
648         assert(isPowerOfTwo(alignment));
649         assert(channels <= maxPossibleChannels); // TODO allocate to support arbitrary channel count.
650 
651         if (_alignment != 0 && _alignment != alignment)
652         {
653             // Can't keep the allocation if the alignment changes.
654             cleanUpData();
655         }
656 
657         _channels = channels;
658         _frames = frames; 
659         _alignment = cast(ubyte)alignment;
660 
661         size_t bytesForOneChannel = T.sizeof * frames;
662         bytesForOneChannel = nextMultipleOf(bytesForOneChannel, alignment);
663         
664         size_t bytesTotal = bytesForOneChannel * channels;
665         if (bytesTotal == 0) 
666             bytesTotal = 1; // so that zero length or zero-channel buffers still kinda work.
667 
668         _data = _mm_realloc_discard(_data, bytesTotal, alignment);
669         _flags = Flags.hasOwnership;
670 
671         for (int n = 0; n < _channels; ++n)
672         {
673             ubyte* p = (cast(ubyte*)_data) + bytesForOneChannel * n;
674             _channelPointers[n] = cast(T*)p;
675         }
676     }
677 
678     void cleanUp()
679     {
680         cleanUpData();
681     }
682 
683     void cleanUpData()
684     {
685         if (hasOwnership())
686         {
687             if (_data !is null)
688             {
689                 _mm_free(_data);
690                 _data = null;
691             }
692         }
693 
694         // Note: doesn't loose the ownership flag, because morally this AudioBuffer is still 
695         // the kind of AudioBuffer that owns its data, it just has no data right now.
696     }
697 
698     // Returns: true if the whole buffer is filled with 0 (or -0 for floating-point) 
699     // Do not expose this API as it isn't clear how fast it is.
700     bool computeIsBufferSilent() const nothrow @nogc @trusted
701     {
702         for (int channel = 0; channel < _channels; ++channel)
703         {
704             const(T)* samples = getChannelPointer(channel);
705             for (int n = 0; n < _frames; ++n)
706             {
707                 if (samples[n] != 0)
708                     return false;
709             }
710         }
711         return true;
712     }
713 }
714 
715 private:
716 
717 // Compute largest possible byte alignment for a sub-buffer.
718 ubyte childAlignment(ubyte parentAlignment, size_t itemSize, int frameStart) pure
719 {
720     assert(parentAlignment >= 1 && parentAlignment <= 128);
721 
722     // For reference, this is the alignment for T == float:
723     //
724     // float(4 bytes)|  0   |  1  |  2   |  3  |   4 |
725     // ----------------------------------------------|
726     // parent 1      |  1   |  1  |  1   |  1  |   1 |
727     // parent 2      |  2   |  2  |  2   |  2  |   2 |
728     // parent 4      |  4   |  4  |  4   |  4  |   4 |
729     // parent 8      |  8   |  4  |  8   |  4  |   8 |
730     // parent 16     |  16  |  4  |  8   |  4  |  16 |
731     // parent 32     |  32  |  4  |  8   |  4  |  16 |
732 
733     size_t offset = frameStart * itemSize;
734     // how many zero bits there are in LSB?
735     if (offset == 0)
736         return parentAlignment;
737     int zeroBits = bsf(offset);
738     if (zeroBits > 7) 
739         zeroBits = 7; // do not exceed 128
740     int a = (1 << zeroBits);
741     if (a > parentAlignment)
742         a = parentAlignment;
743     return cast(ubyte)a;
744 }
745 unittest
746 {
747     assert( childAlignment(1, 8, 8) == 1 );        // do not exceed aprent align
748     assert( childAlignment(16, 4, 2) == 8 );
749     assert( childAlignment(16, 8, 0) == 16 );
750     assert( childAlignment(16, 8, 1024) == 16 );
751     assert( childAlignment(16, 4, 1) == 4 );
752     assert( childAlignment(128, 4, 1024) == 128 ); // do not exceed 128
753 }
754 
755 // How zero flag works:
756 @trusted unittest 
757 {
758     AudioBuffer!double a = audioBufferAlloc!double(3, 1024, 16);
759     assert(!a.hasZeroFlag());
760     assert(!a.isSilent);
761     a.fillWithValue(0.0);
762 
763     // Newly created AudioBuffer with own memory is zeroed out.
764     assert(a.hasZeroFlag());
765     assert(a.isSilent);
766 
767     // Getting a mutable pointer make the zero flag disappear.
768     a.getChannelPointer(2)[1023] = 0.0;
769     assert(!a.hasZeroFlag());
770     assert(a.isSilent());
771 
772     // To set the zero flag, either recompute it (slow)
773     // or fill the buffer with zeroes.
774     a.recomputeZeroFlag();
775     assert(a.hasZeroFlag());
776     assert(a.isSilent());
777 }
778 
779 unittest 
780 {
781     // Buffer must reuse an existing allocation if the size/alignment is the same.
782     // Even in this case, the content is NOT preserved.
783     AudioBuffer!double b;
784     b.resize(1, 1024, 16);
785     double* chan0 = b.getChannelPointer(0);
786 
787     b.resize(1, 1024, 16);
788     assert(chan0 == b.getChannelPointer(0));
789 
790     // If the alignment changes though, can't reuse allocation.
791     b.resize(1, 1024, 128);
792     assert(b.channels() == 1);
793     assert(b.frames() == 1024);
794     b.resize(2, 1023, 128);
795 
796     b.fillWithValue(4.0);
797     double[] p = b.getChannel(1);
798     assert(p[1022] == 4.0);
799     assert(!b.hasZeroFlag());
800 }
801 
802 @trusted unittest
803 {
804     // const borrow preserve zero flag
805     int numChans = 8;
806     const(AudioBuffer!float) c = audioBufferAllocZeroed!float(numChans, 123);
807     assert(c.hasZeroFlag());
808     const(float*)* buffers = c.getChannelsPointers();
809     for (int chan = 0; chan < 8; ++chan)
810     {
811         assert(buffers[chan] == c.getChannelPointer(chan));
812     }
813     const(AudioBuffer!float) d = c.sliceFrames(10, c.frames());
814     assert(d.frames() == 123 - 10);
815     assert(d.hasZeroFlag());
816 
817     const(AudioBuffer!float) e = d[0..$, 0..24];
818     assert(e.channels() == d.channels());
819     assert(e.frames() == 24);
820 }
821 
822 @trusted unittest
823 {
824     // Mutable borrow doesn't preserve zero flag, and set hasOtherMutableReference flag
825     int numChans = 2;
826     AudioBuffer!double c = audioBufferAlloc!double(numChans, 14);
827     c.fillWithZeroes();
828     assert(c.hasZeroFlag());
829     assert(c.isIsolated());
830     
831     AudioBuffer!double d = c[0..$, 10 .. 14];
832     assert(!c.hasZeroFlag());
833     assert(!d.hasZeroFlag());
834     assert(!c.isIsolated);
835     assert(!d.isIsolated);
836 
837     // Fill right channel with 2.5f 
838     d.getChannel(1)[] = 2.5;
839     assert(c[1, 9] == 0.0);
840     c[1, 8] = -1.0;
841     assert(c[1, 8] == -1.0);    
842     
843     assert(c[1][10] == 2.5);
844 
845     // Mutable dup
846     AudioBuffer!double e = audioBufferDup(d);
847     assert(e.isIsolated);
848 }
849 
850 @trusted unittest
851 {   
852     // Create mutable buffer from mutable data.
853     {
854         float[128][2] data;
855         float*[2] pdata;
856         pdata[0] = data[0].ptr;
857         pdata[1] = data[1].ptr;
858         AudioBuffer!float b;
859         b.initWithData(2, 128, pdata.ptr);
860         assert(!b.isIsolated);
861 
862         // This break the isolated flag manually, in case you want to be able 
863         // to use the zero flag regardless, at your own risk.
864         b.assumeIsolated(); 
865         assert(b.isIsolated);
866     }
867 
868     // Create const buffer from const data.
869     {
870         float[128][2] data;
871         const(float*)[2] pdata = [ data[0].ptr, data[1].ptr];
872         const(AudioBuffer!float) b = audioBufferFromData(2, 128, pdata.ptr);
873         assert(b.isIsolated);
874     }
875 }
876 
877 unittest
878 {
879     // Chunked foreach
880     {
881         AudioBuffer!double whole = audioBufferAlloc!double(2, 323 + 1024, 16);
882         foreach(b; whole.chunkBy(1024))
883         {
884             assert(b.frames() <= 1024);
885             b.fillWithZeroes();
886 
887             AudioBuffer!double c = b[0..$];
888             assert(c.channels == whole.channels);
889             assert(c.alignment == whole.alignment); // inherited alignment correctly
890             assert(c.frames == b.frames);
891         }
892         assert(whole.computeIsBufferSilent());
893     }
894 
895     // Chunked const foreach
896     {
897         const(AudioBuffer!double) whole = audioBufferAllocZeroed!double(3, 2000);
898         foreach(b; whole.chunkBy(1024)) // split by frames
899         {
900             assert(b.isSilent);
901 
902             // Split by channels
903             const(AudioBuffer!double) left = b[0..1];
904             const(AudioBuffer!double) right = b.sliceChannels(1, 2);
905             assert(left.isSilent);
906             assert(b.frames == left.frames);
907             assert(left.channels == 1);
908             assert(right.isSilent);
909         }
910     }
911 }