‹ Serial MatchESP Mesh Network with OSC ›

Buffer xFader

2020-03-29 13:42 supercollider

This SuperCollider function automates the process of making seamless loops as shown in this animation...

i.e. creating continuous loops from buffers following this recipe...

  1. select a few seconds from the end.
  2. fade out the selection.
  3. cut the selection.
  4. paste the selection at time 0.
  5. select a few seconds from the beginning (usually same length as in #1).
  6. fade in the selection.
  7. mix the two tracks.
(
~xfader= {|inBuffer, duration= 2, curve= -2, action|
  var frames= duration*inBuffer.sampleRate;
  if(frames>inBuffer.numFrames, {
    "xfader: crossfade duration longer than half buffer - clipped.".warn;
  });
  frames= frames.min(inBuffer.numFrames.div(2)).asInteger;
  Buffer.alloc(inBuffer.server, inBuffer.numFrames-frames, inBuffer.numChannels, {|outBuffer|
    inBuffer.loadToFloatArray(action:{|arr|
      var interleavedFrames= frames*inBuffer.numChannels;
      var startArr= arr.copyRange(0, interleavedFrames-1);
      var endArr= arr.copyRange(arr.size-interleavedFrames, arr.size-1);
      var result= arr.copyRange(0, arr.size-1-interleavedFrames);
      interleavedFrames.do{|i|
        var fadeIn= i.lincurve(0, interleavedFrames-1, 0, 1, curve);
        var fadeOut= i.lincurve(0, interleavedFrames-1, 1, 0, 0-curve);
        result[i]= (startArr[i]*fadeIn)+(endArr[i]*fadeOut);
      };
      outBuffer.loadCollection(result, 0, action);
    });
  });
};
)

s.boot;

//edit and load a sound file (or use an already existing buffer)
b.free;  b= Buffer.read(s, "~/Desktop/testnoise.wav".standardizePath);

//evaluate the function to create the seamless cross fade
c= ~xfader.value(b);

//try looping it - should loop smoothly and without discontinuities
d= {PlayBuf.ar(c.numChannels, c, loop:1)}.play;
d.release;

//compare with the input file - this will probably have a hickup
d= {PlayBuf.ar(b.numChannels, b, loop:1)}.play;
d.release;

b.free;
c.free;


//--save to disk example with shorter cross fade and done action function.
b.free;  b= Buffer.read(s, "~/Desktop/testnoise.wav".standardizePath);
c= ~xfader.value(b, 0.5, -3, action:{|buf| ("done with buffer"+b).postln});
c.write("~/Desktop/testnoise-looped.wav".standardizePath);
c.free;
b.free;

The SuperCollider code works with any Buffer containing sound files, live sampled or generated sounds. The duration argument set the cross fade length in seconds and with the curve argument one can set curvature.

Note that duration can not be longer than half the duration of the input buffer and a curve of 0.0 or greater will mean that the amplitude will dip in the middle of the crossfade. So it is recommended to bend the curve a bit to get more of an equal power crossfade. -2.0 to -4.0 seem like sensible values. The function will allocate and return a new Buffer instance and not touch the buffer passed in as an argument.

An action function is optional. The function is asynchronous. Also, note that the resulting file will be shorter than the original because of the crossfade.

Footnote:
I remember learning this trick from Peter Lundén ~20 years ago. He showed it to me on an SGI machine running IRIX, possibly using the Snd sound editor.

‹ Serial MatchESP Mesh Network with OSC ›