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 }