Crossfading when playhead and recordinghead meet

Imagine you have a buffer that is continuously being writting to and read from. In my example i read the buffer in a loop backwards. For visual aid i made a beautiful image in paint.
Imagine a buffer that is circular with a playinghead moving counterclockwise and the recordinghead moving clockwise. If they have the same speed and both start on top, then they would meet twice every cycle, once on top and once on the bottom.
When the heads meet id like to have some kind of crossfade. My attempt is as follows:
Once the heads are at a distance d from eachother, i try to crossfade from the playheads current position to one frame behind the recordinghead. Once the heads are closer then distance d there is a trigger, to trigger this crossfade. I thought that this way you crossfade over the collision and there would be no clicks. However there are still clicks.

	var buffer, sig, trigger, recHead, playHead, startPos, d = 1000;
	//with a modulating frequency of 1 there are no clicks
	//because the buffer contents effectively dont change
	sig = +, mul: 100))*0.1;
	buffer = Buffer.alloc(s, 44100);    
	recHead =, 1, 0, 44100);
	//we pretend PlayBufCF uses this playHead
	playHead =, -1, 44100 - 1, -1);, buffer, recHead, 0);
	//|playHead - recHead| < d || |playHead - recHead| > (44100 - d)
	//second part of if statement is when
	//the playHead and recHead are on opposite sides of the 0 frame
	trigger = ((playHead - recHead).abs < d + ((playHead - recHead).abs > (44100 - d))) > 0;
	//bufwr for reference
	//with a modulating frequency of 1 there are also no clicks with BufRd
	//, buffer, playHead, 0);
	//trying out different lagtimes, but it doesnt change much, buffer, -1, trigger, recHead - 1, loop:1,,1).poll);

I know that in my example the “real” playhead jumps a few frames ahead every trigger, so the trigger gets more and more behind. So i’d need to update the playhead and then update the trigger retroactively with this new playhead, however i have no idea how i should do that.

Has anyone solved this problem before and if so how did you implement a smooth transition when recordinghead and playhead cross?

I made a different approach now. Instead of trying to crossfade, i try to avoid that theres a collision of heads in the first place. I made 3 buffers, everytime a buffer is being written to, the previous one is being read from. This way the writing always happens one buffer ahead of the writing modulo 3. This way i can read in reverse without being afraid that the heads will collide. Sometimes there are still clicks when you switch buffers, so instead of trying to fade between buffers with xfade2 or something, i faded the input of the buffers.

This will only work when |readingrate| == |writingrate|. Maybe i can find a solution to more general cases with different reading rates later.

	var playBackLength = 1.5;
	var buf1, buf2, buf3, in, out, phase, currentBuf, nextBuf, fade = 0.1, trigger =, extraDelay = 0.1;
	//random input
	in =,800) +, 100)) *,0.2),;
	//fade input for the buffers
	in = in *[0,1,1,0],[fade,playBackLength - (fade * 2),fade],'sin'), trigger) * 0.5;
	//make 3 buffers
	buf1 = Buffer.alloc(s, 44100*playBackLength); 
	buf2 = Buffer.alloc(s, 44100*playBackLength); 
	buf3 = Buffer.alloc(s, 44100*playBackLength);
	//nextbuf is the buf being written to and currentbuf is the buf being read
	nextBuf =, 0, Dseq([buf1, buf2, buf3], inf));
	currentBuf =, playBackLength + extraDelay, playBackLength + extraDelay);
        //recordbuf seems to work more reliable than bufwr with a phasor, nextBuf, loop:1);
	//reverse playback
	phase =, -1, 44100*playBackLength - 1, -1);
	//delay until the first buffer filled
	phase =, playBackLength + extraDelay, playBackLength + extraDelay);
	out =, currentBuf, phase);
	//make 3 buffers
	buf1 = Buffer.alloc(s, 44100*playBackLength); 
	buf2 = Buffer.alloc(s, 44100*playBackLength); 
	buf3 = Buffer.alloc(s, 44100*playBackLength);

Btw, you shouldn’t allocate buffers inside of a synthdef - calling play on a function wraps it in a synthdef.

Yeah i know, it was just convenient for testing. This is just a rough sketch for a possible solution.