// Copyright 2007-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#include "AudioOutput.h"

#include "AudioInput.h"
#include "AudioOutputSample.h"
#include "AudioOutputSpeech.h"
#include "Channel.h"
#include "ChannelListenerManager.h"
#include "Log.h"
#include "PluginManager.h"
#include "ServerHandler.h"
#include "Timer.h"
#include "User.h"
#include "Utils.h"
#include "VoiceRecorder.h"
#include "Global.h"

#include <cassert>
#include <cmath>

// Remember that we cannot use static member classes that are not pointers, as the constructor
// for AudioOutputRegistrar() might be called before they are initialized, as the constructor
// is called from global initialization.
// Hence, we allocate upon first call.

QMap< QString, AudioOutputRegistrar * > *AudioOutputRegistrar::qmNew;
QString AudioOutputRegistrar::current = QString();

AudioOutputRegistrar::AudioOutputRegistrar(const QString &n, int p) : name(n), priority(p) {
	if (!qmNew)
		qmNew = new QMap< QString, AudioOutputRegistrar * >();
	qmNew->insert(name, this);
}

AudioOutputRegistrar::~AudioOutputRegistrar() {
	qmNew->remove(name);
}

AudioOutputPtr AudioOutputRegistrar::newFromChoice(QString choice) {
	if (!qmNew)
		return AudioOutputPtr();

	if (!choice.isEmpty() && qmNew->contains(choice)) {
		Global::get().s.qsAudioOutput = choice;
		current                       = choice;
		return AudioOutputPtr(qmNew->value(choice)->create());
	}
	choice = Global::get().s.qsAudioOutput;
	if (qmNew->contains(choice)) {
		current = choice;
		return AudioOutputPtr(qmNew->value(choice)->create());
	}

	AudioOutputRegistrar *r = nullptr;
	foreach (AudioOutputRegistrar *aor, *qmNew)
		if (!r || (aor->priority > r->priority))
			r = aor;
	if (r) {
		current = r->name;
		return AudioOutputPtr(r->create());
	}
	return AudioOutputPtr();
}

bool AudioOutputRegistrar::canMuteOthers() const {
	return false;
}

bool AudioOutputRegistrar::usesOutputDelay() const {
	return true;
}

bool AudioOutputRegistrar::canExclusive() const {
	return false;
}

AudioOutput::AudioOutput() {
	QObject::connect(this, &AudioOutput::bufferInvalidated, this, &AudioOutput::handleInvalidatedBuffer);
	QObject::connect(this, &AudioOutput::bufferPositionChanged, this, &AudioOutput::handlePositionedBuffer);
}

AudioOutput::~AudioOutput() {
	bRunning = false;
	wait();
	wipe();

	delete[] fSpeakers;
	delete[] fSpeakerVolume;
	delete[] bSpeakerPositional;
	delete[] fStereoPanningFactor;
}

// Here's the theory.
// We support sound "bloom"ing. That is, if sound comes directly from the left, if it is sufficiently
// close, we'll hear it full intensity from the left side, and "bloom" intensity from the right side.

float AudioOutput::calcGain(float dotproduct, float distance) {
	// dotproduct is in the range [-1, 1], thus we renormalize it to the range [0, 1]
	float dotfactor = (dotproduct + 1.0f) / 2.0f;

	// Volume on the ear opposite to the sound should never reach 0 in the real world.
	// Therefore, we define the minimum volume as 1/4th of the theoretical maximum (ignoring
	// the sound direction but taking distance into account) for _any_ ear.
	const float offset = (1.0f - dotfactor) * 0.25f;
	dotfactor += offset;

	float att;

	if (distance < 0.01f) {
		// Listener is "inside" source -> no attenuation
		// Without this extra check, we would have a dotfactor of 0.5
		// despite being numerically inside the source leading to a loss
		// of volume.
		att = 1.0f;
	} else if (Global::get().s.fAudioMaxDistVolume > 0.99f) {
		// User selected no distance attenuation
		att = std::min(1.0f, dotfactor + Global::get().s.fAudioBloom);
	} else if (distance < Global::get().s.fAudioMinDistance) {
		// Fade in blooming as soon as the sound source enters fAudioMinDistance and increase it to its full
		// capability when the audio source is at the same position as the local player
		float bloomfac = Global::get().s.fAudioBloom * (1.0f - distance / Global::get().s.fAudioMinDistance);

		att = std::min(1.0f, bloomfac + dotfactor);
	} else {
		float datt;

		if (distance >= Global::get().s.fAudioMaxDistance) {
			datt = Global::get().s.fAudioMaxDistVolume;
		} else {
			float mvol = Global::get().s.fAudioMaxDistVolume;
			if (mvol < 0.005f) {
				mvol = 0.005f;
			}

			float drel = (distance - Global::get().s.fAudioMinDistance)
						 / (Global::get().s.fAudioMaxDistance - Global::get().s.fAudioMinDistance);
			datt = powf(10.0f, log10f(mvol) * drel);
		}

		att = datt * dotfactor;
	}
	return att;
}

void AudioOutput::wipe() {
	// We need to remove all buffers from the qmOutputs map.
	// However, the removeBuffer calls a signal-slot mechanism
	// asynchronously. Doing that while iterating over the map
	// will cause a concurrent modification

	QList< AudioOutputBuffer * > list;

	{
		QReadLocker locker(&qrwlOutputs);
		for (AudioOutputBuffer *buffer : qmOutputs) {
			list.append(buffer);
		}
	}

	for (AudioOutputBuffer *buffer : list) {
		removeBuffer(buffer);
	}
}

const float *AudioOutput::getSpeakerPos(unsigned int &speakers) {
	if ((iChannels > 0) && fSpeakers) {
		speakers = iChannels;
		return fSpeakers;
	}
	return nullptr;
}

void AudioOutput::addFrameToBuffer(ClientUser *sender, const Mumble::Protocol::AudioData &audioData) {
	if (iChannels == 0) {
		return;
	}

	qrwlOutputs.lockForRead();
	// qmOutputs is a map of users and their AudioOutputSpeech objects, which will be created when audio from that user
	// is received. It also contains AudioOutputSample objects with various other non-speech sounds.
	// This map will be iterated in mix(). After the speech or sample audio is finished, the AudioOutputBuffer object
	// will be removed from this map and deleted.
	AudioOutputSpeech *speech = qobject_cast< AudioOutputSpeech * >(qmOutputs.value(sender));

	if (!speech || (speech->m_codec != audioData.usedCodec)) {
		qrwlOutputs.unlock();

		if (speech) {
			removeBuffer(static_cast< AudioOutputBuffer * >(speech));
		}

		while ((iMixerFreq == 0) && isAlive()) {
			QThread::yieldCurrentThread();
		}

		if (!iMixerFreq) {
			return;
		}

		qrwlOutputs.lockForWrite();

		speech = new AudioOutputSpeech(sender, iMixerFreq, audioData.usedCodec, iBufferSize);
		qmOutputs.replace(sender, speech);
	}

	speech->addFrameToBuffer(audioData);

	qrwlOutputs.unlock();
}

void AudioOutput::handleInvalidatedBuffer(AudioOutputBuffer *buffer) {
	QWriteLocker locker(&qrwlOutputs);
	for (auto iter = qmOutputs.begin(); iter != qmOutputs.end(); ++iter) {
		if (iter.value() == buffer) {
			qmOutputs.erase(iter);
			delete buffer;
			break;
		}
	}
}

void AudioOutput::handlePositionedBuffer(AudioOutputBuffer *buffer, float x, float y, float z) {
	QWriteLocker locker(&qrwlOutputs);
	for (auto iter = qmOutputs.begin(); iter != qmOutputs.end(); ++iter) {
		if (iter.value() == buffer) {
			buffer->fPos[0] = x;
			buffer->fPos[1] = y;
			buffer->fPos[2] = z;
			break;
		}
	}
}

void AudioOutput::setBufferPosition(const AudioOutputToken &token, float x, float y, float z) {
	if (!token) {
		return;
	}

	emit bufferPositionChanged(token.m_buffer, x, y, z);
}

void AudioOutput::removeBuffer(AudioOutputBuffer *buffer) {
	if (!buffer) {
		return;
	}

	emit bufferInvalidated(buffer);
}

void AudioOutput::removeUser(const ClientUser *user) {
	removeBuffer(qmOutputs.value(user));
}

void AudioOutput::removeToken(AudioOutputToken &token) {
	removeBuffer(token.m_buffer);
	token = {};
}

AudioOutputToken AudioOutput::playSample(const QString &filename, float volume, bool loop) {
	SoundFile *handle = AudioOutputSample::loadSndfile(filename);
	if (!handle)
		return AudioOutputToken();

	Timer t;
	const quint64 oneSecond = 1000000;

	while (!t.isElapsed(oneSecond) && (iMixerFreq == 0) && isAlive()) {
		QThread::yieldCurrentThread();
	}

	// If we've waited for more than one second, we declare timeout.
	if (t.isElapsed(oneSecond)) {
		qWarning("AudioOutput: playSample() timed out after 1 second: device not ready");
		return AudioOutputToken();
	}

	if (!iMixerFreq)
		return AudioOutputToken();

	QWriteLocker locker(&qrwlOutputs);
	AudioOutputSample *sample = new AudioOutputSample(handle, volume, loop, iMixerFreq, iBufferSize);
	qmOutputs.insert(nullptr, sample);

	return AudioOutputToken(sample);
}

void AudioOutput::initializeMixer(const unsigned int *chanmasks, bool forceheadphone) {
	delete[] fSpeakers;
	delete[] bSpeakerPositional;
	delete[] fSpeakerVolume;
	delete[] fStereoPanningFactor;

	fSpeakers            = new float[iChannels * 3];
	bSpeakerPositional   = new bool[iChannels];
	fSpeakerVolume       = new float[iChannels];
	fStereoPanningFactor = new float[iChannels * 2];

	memset(fSpeakers, 0, sizeof(float) * iChannels * 3);
	memset(bSpeakerPositional, 0, sizeof(bool) * iChannels);

	for (unsigned int i = 0; i < iChannels; ++i)
		fSpeakerVolume[i] = 1.0f;

	if (iChannels > 1) {
		for (unsigned int i = 0; i < iChannels; i++) {
			float *s              = &fSpeakers[3 * i];
			bSpeakerPositional[i] = true;

			switch (chanmasks[i]) {
				case SPEAKER_FRONT_LEFT:
					s[0] = -0.5f;
					s[2] = 1.0f;
					break;
				case SPEAKER_FRONT_RIGHT:
					s[0] = 0.5f;
					s[2] = 1.0f;
					break;
				case SPEAKER_FRONT_CENTER:
					s[2] = 1.0f;
					break;
				case SPEAKER_LOW_FREQUENCY:
					break;
				case SPEAKER_BACK_LEFT:
					s[0] = -0.5f;
					s[2] = -1.0f;
					break;
				case SPEAKER_BACK_RIGHT:
					s[0] = 0.5f;
					s[2] = -1.0f;
					break;
				case SPEAKER_FRONT_LEFT_OF_CENTER:
					s[0] = -0.25;
					s[2] = 1.0f;
					break;
				case SPEAKER_FRONT_RIGHT_OF_CENTER:
					s[0] = 0.25;
					s[2] = 1.0f;
					break;
				case SPEAKER_BACK_CENTER:
					s[2] = -1.0f;
					break;
				case SPEAKER_SIDE_LEFT:
					s[0] = -1.0f;
					break;
				case SPEAKER_SIDE_RIGHT:
					s[0] = 1.0f;
					break;
				case SPEAKER_TOP_CENTER:
					s[1] = 1.0f;
					s[2] = 1.0f;
					break;
				case SPEAKER_TOP_FRONT_LEFT:
					s[0] = -0.5f;
					s[1] = 1.0f;
					s[2] = 1.0f;
					break;
				case SPEAKER_TOP_FRONT_CENTER:
					s[1] = 1.0f;
					s[2] = 1.0f;
					break;
				case SPEAKER_TOP_FRONT_RIGHT:
					s[0] = 0.5f;
					s[1] = 1.0f;
					s[2] = 1.0f;
					break;
				case SPEAKER_TOP_BACK_LEFT:
					s[0] = -0.5f;
					s[1] = 1.0f;
					s[2] = -1.0f;
					break;
				case SPEAKER_TOP_BACK_CENTER:
					s[1] = 1.0f;
					s[2] = -1.0f;
					break;
				case SPEAKER_TOP_BACK_RIGHT:
					s[0] = 0.5f;
					s[1] = 1.0f;
					s[2] = -1.0f;
					break;
				default:
					bSpeakerPositional[i] = false;
					fSpeakerVolume[i]     = 0.0f;
					qWarning("AudioOutput: Unknown speaker %d: %08x", i, chanmasks[i]);
					break;
			}
			if (Global::get().s.bPositionalHeadphone || forceheadphone) {
				s[1] = 0.0f;
				s[2] = 0.0f;
				if (s[0] == 0.0f)
					fSpeakerVolume[i] = 0.0f;
			}
		}
		for (unsigned int i = 0; i < iChannels; i++) {
			float d = sqrtf(fSpeakers[3 * i + 0] * fSpeakers[3 * i + 0] + fSpeakers[3 * i + 1] * fSpeakers[3 * i + 1]
							+ fSpeakers[3 * i + 2] * fSpeakers[3 * i + 2]);
			if (d > 0.0f) {
				fSpeakers[3 * i + 0] /= d;
				fSpeakers[3 * i + 1] /= d;
				fSpeakers[3 * i + 2] /= d;
			}
			float *spf = &fStereoPanningFactor[2 * i];
			spf[0]     = (1.0f - fSpeakers[i * 3 + 0]) / 2.0f;
			spf[1]     = (1.0f + fSpeakers[i * 3 + 0]) / 2.0f;
		}
	} else if (iChannels == 1) {
		fStereoPanningFactor[0] = 0.5;
		fStereoPanningFactor[1] = 0.5;
	}
	iSampleSize =
		static_cast< unsigned int >(iChannels * ((eSampleFormat == SampleFloat) ? sizeof(float) : sizeof(short)));
	qWarning("AudioOutput: Initialized %d channel %d hz mixer", iChannels, iMixerFreq);

	if (Global::get().s.bPositionalAudio && iChannels == 1) {
		Log::logOrDefer(Log::Warning, tr("Positional audio cannot work with mono output devices!"));
	}
}

bool AudioOutput::mix(void *outbuff, unsigned int frameCount) {
#ifdef USE_MANUAL_PLUGIN
	positions.clear();
#endif

	// A list of buffers that have audio to contribute
	QList< AudioOutputBuffer * > qlMix;
	// A list of buffers that no longer have any audio to play and can thus be deleted
	QList< AudioOutputBuffer * > qlDel;

	if (Global::get().s.fVolume < 0.01f) {
		return false;
	}

	const float adjustFactor = std::pow(10.f, -18.f / 20);
	const float mul          = Global::get().s.fVolume;
	const unsigned int nchan = iChannels;
	ServerHandlerPtr sh      = Global::get().sh;
	VoiceRecorderPtr recorder;
	if (sh) {
		recorder = Global::get().sh->recorder;
	}

	qrwlOutputs.lockForRead();

	bool prioritySpeakerActive = false;

	// Get the users that are currently talking (and are thus serving as an audio source)
	QMultiHash< const ClientUser *, AudioOutputBuffer * >::const_iterator it = qmOutputs.constBegin();
	while (it != qmOutputs.constEnd()) {
		const ClientUser *user    = it.key();
		AudioOutputBuffer *buffer = it.value();

		if (!buffer->prepareSampleBuffer(frameCount)) {
			qlDel.append(buffer);
		} else if (!user) {
			// Audio samples
			qlMix.append(buffer);
		} else if (!user->bLocalMute) {
			qlMix.append(buffer);

			if (user->bPrioritySpeaker) {
				prioritySpeakerActive = true;
			}
		}
		++it;
	}

	if (Global::get().prioritySpeakerActiveOverride) {
		prioritySpeakerActive = true;
	}

	// If the audio backend uses a float-array we can sample and mix the audio sources directly into the output.
	// Otherwise we'll have to use an intermediate buffer which we will convert to an array of shorts later
	static std::vector< float > fOutput;
	fOutput.resize(iChannels * frameCount);
	float *output = (eSampleFormat == SampleFloat) ? reinterpret_cast< float * >(outbuff) : fOutput.data();
	memset(output, 0, sizeof(float) * frameCount * iChannels);

	if (!qlMix.isEmpty()) {
		// There are audio sources available -> mix those sources together and feed them into the audio backend
		static std::vector< float > speaker;
		speaker.resize(iChannels * 3);
		static std::vector< float > svol;
		svol.resize(iChannels);

		bool validListener = false;

		// Initialize recorder if recording is enabled
		boost::shared_array< float > recbuff;
		if (recorder) {
			recbuff = boost::shared_array< float >(new float[frameCount]);
			memset(recbuff.get(), 0, sizeof(float) * frameCount);
			recorder->prepareBufferAdds();
		}

		for (unsigned int i = 0; i < iChannels; ++i)
			svol[i] = mul * fSpeakerVolume[i];

		if (Global::get().s.bPositionalAudio && (iChannels > 1) && Global::get().pluginManager->fetchPositionalData()) {
			// Calculate the positional audio effects if it is enabled

			Vector3D cameraDir = Global::get().pluginManager->getPositionalData().getCameraDir();

			Vector3D cameraAxis = Global::get().pluginManager->getPositionalData().getCameraAxis();

			// Direction vector is dominant; if it's zero we presume all is zero.

			if (!cameraDir.isZero()) {
				cameraDir.normalize();

				if (!cameraAxis.isZero()) {
					cameraAxis.normalize();
				} else {
					cameraAxis = { 0.0f, 1.0f, 0.0f };
				}

				const float dotproduct = cameraDir.dotProduct(cameraAxis);
				const float error      = std::abs(dotproduct);
				if (error > 0.5f) {
					// Not perpendicular by a large margin. Assume Y up and rotate 90 degrees.

					float azimuth = 0.0f;
					if (cameraDir.x != 0.0f || cameraDir.z != 0.0f) {
						azimuth = atan2f(cameraDir.z, cameraDir.x);
					}

					float inclination = acosf(cameraDir.y) - static_cast< float >(M_PI) / 2.0f;

					cameraAxis.x = sinf(inclination) * cosf(azimuth);
					cameraAxis.y = cosf(inclination);
					cameraAxis.z = sinf(inclination) * sinf(azimuth);
				} else if (error > 0.01f) {
					// Not perpendicular by a small margin. Find the nearest perpendicular vector.
					cameraAxis = cameraAxis - cameraDir * dotproduct;

					// normalize axis again (the orthogonalized vector us guaranteed to be non-zero
					// as the error (dotproduct) was only 0.5 (and not 1 in which case above operation
					// would create the zero-vector).
					cameraAxis.normalize();
				}
			} else {
				cameraDir = { 0.0f, 0.0f, 1.0f };

				cameraAxis = { 0.0f, 1.0f, 0.0f };
			}

			// Calculate right vector as front X top
			Vector3D right = cameraAxis.crossProduct(cameraDir);

			/*
						qWarning("Front: %f %f %f", front[0], front[1], front[2]);
						qWarning("Top: %f %f %f", top[0], top[1], top[2]);
						qWarning("Right: %f %f %f", right[0], right[1], right[2]);
			*/
			// Rotate speakers to match orientation
			for (unsigned int i = 0; i < iChannels; ++i) {
				speaker[3 * i + 0] = fSpeakers[3 * i + 0] * right.x + fSpeakers[3 * i + 1] * cameraAxis.x
									 + fSpeakers[3 * i + 2] * cameraDir.x;
				speaker[3 * i + 1] = fSpeakers[3 * i + 0] * right.y + fSpeakers[3 * i + 1] * cameraAxis.y
									 + fSpeakers[3 * i + 2] * cameraDir.y;
				speaker[3 * i + 2] = fSpeakers[3 * i + 0] * right.z + fSpeakers[3 * i + 1] * cameraAxis.z
									 + fSpeakers[3 * i + 2] * cameraDir.z;
			}
			validListener = true;
		}

		for (AudioOutputBuffer *buffer : qlMix) {
			// Iterate through all audio sources and mix them together into the output (or the intermediate array)
			float *RESTRICT pfBuffer = buffer->pfBuffer;
			float volumeAdjustment   = 1;

			// Check if the audio source is a user speaking or a sample playback and apply potential volume
			// adjustments
			AudioOutputSpeech *speech = qobject_cast< AudioOutputSpeech * >(buffer);
			AudioOutputSample *sample = qobject_cast< AudioOutputSample * >(buffer);
			const ClientUser *user    = nullptr;
			if (speech) {
				user = speech->p;

				volumeAdjustment *= user->getLocalVolumeAdjustments();

				if (sh && sh->m_version >= Mumble::Protocol::PROTOBUF_INTRODUCTION_VERSION) {
					// The new protocol supports sending volume adjustments which is used to figure out the correct
					// volume adjustment for listeners on the server. Thus, we only have to apply that here.
					volumeAdjustment *= speech->m_suggestedVolumeAdjustment;
				} else if (user->cChannel
						   && Global::get().channelListenerManager->isListening(Global::get().uiSession,
																				user->cChannel->iId)
						   && (speech->m_audioContext == Mumble::Protocol::AudioContext::LISTEN)) {
					// We are receiving this audio packet only because we are listening to the channel
					// the speaking user is in. Thus we receive the audio via our "listener proxy".
					// Thus we'll apply the volume adjustment for our listener proxy as well
					volumeAdjustment *= Global::get()
											.channelListenerManager
											->getListenerVolumeAdjustment(Global::get().uiSession, user->cChannel->iId)
											.factor;
				}

				if (prioritySpeakerActive) {
					if (user->tsState != Settings::Whispering && !user->bPrioritySpeaker) {
						volumeAdjustment *= adjustFactor;
					}
				}
			} else if (sample) {
				volumeAdjustment *= sample->getVolume();
			}

			// As the events may cause the output PCM to change, the connection has to be direct in any case
			const int channels = (speech && speech->bStereo) ? 2 : 1;
			// If user != nullptr, then the current audio is considered speech
			assert(channels >= 0);
			emit audioSourceFetched(pfBuffer, frameCount, static_cast< unsigned int >(channels), SAMPLE_RATE,
									static_cast< bool >(user), user);

			// If recording is enabled add the current audio source to the recording buffer
			if (recorder) {
				if (speech) {
					if (speech->bStereo) {
						// Mix down stereo to mono. TODO: stereo record support
						// frame: for a stereo stream, the [LR] pair inside ...[LR]LRLRLR.... is a frame
						for (std::size_t i = 0; i < frameCount; ++i) {
							recbuff.get()[i] +=
								(pfBuffer[2 * i] / 2.0f + pfBuffer[2 * i + 1] / 2.0f) * volumeAdjustment;
						}
					} else {
						for (std::size_t i = 0; i < frameCount; ++i) {
							recbuff.get()[i] += pfBuffer[i] * volumeAdjustment;
						}
					}

					if (!recorder->isInMixDownMode()) {
						recorder->addBuffer(speech->p, recbuff, static_cast< int >(frameCount));
						recbuff = boost::shared_array< float >(new float[frameCount]);
						memset(recbuff.get(), 0, sizeof(float) * frameCount);
					}

					// Don't add the local audio to the real output
					if (qobject_cast< RecordUser * >(speech->p)) {
						continue;
					}
				}
			}

			if (validListener
				&& ((buffer->fPos[0] != 0.0f) || (buffer->fPos[1] != 0.0f) || (buffer->fPos[2] != 0.0f))) {
				// Add position to position map
#ifdef USE_MANUAL_PLUGIN
				if (user) {
					// The coordinates in the plane are actually given by x and z instead of x and y (y is up)
					positions.insert(user->uiSession, { buffer->fPos[0], buffer->fPos[2] });
				}
#endif

				// If positional audio is enabled, calculate the respective audio effect here
				Position3D outputPos = { buffer->fPos[0], buffer->fPos[1], buffer->fPos[2] };
				Position3D ownPos    = Global::get().pluginManager->getPositionalData().getCameraPos();

				Vector3D connectionVec = outputPos - ownPos;
				float len              = connectionVec.norm();

				if (len > 0.0f) {
					// Don't use normalize-func in order to save the re-computation of the vector's length
					connectionVec.x /= len;
					connectionVec.y /= len;
					connectionVec.z /= len;
				}
				/*
								qWarning("Voice pos: %f %f %f", aop->fPos[0], aop->fPos[1], aop->fPos[2]);
								qWarning("Voice dir: %f %f %f", connectionVec.x, connectionVec.y, connectionVec.z);
				*/
				if (!buffer->pfVolume) {
					buffer->pfVolume = new float[nchan];
					for (unsigned int s = 0; s < nchan; ++s)
						buffer->pfVolume[s] = -1.0;
				}

				if (!buffer->piOffset) {
					buffer->piOffset = std::make_unique< unsigned int[] >(nchan);
					for (unsigned int s = 0; s < nchan; ++s) {
						buffer->piOffset[s] = 0;
					}
				}

				const bool isAudible =
					(Global::get().s.fAudioMaxDistVolume > 0) || (len < Global::get().s.fAudioMaxDistance);

				for (unsigned int s = 0; s < nchan; ++s) {
					const float dot = bSpeakerPositional[s]
										  ? connectionVec.x * speaker[s * 3 + 0] + connectionVec.y * speaker[s * 3 + 1]
												+ connectionVec.z * speaker[s * 3 + 2]
										  : 1.0f;
					float channelVol;
					if (isAudible) {
						// In the current contex, we know that sound reaches at least one ear.
						channelVol = svol[s] * calcGain(dot, len) * volumeAdjustment;
					} else {
						// The user has set the minimum positional volume to 0 and this sound source
						// is exceeding the positional volume range. This means that the sound is completely
						// inaudible at the current position. We therefore set the volume the to 0,
						// making sure the user really can not hear any audio from that source.
						channelVol = 0;
					}

					float *RESTRICT o   = output + s;
					const float old     = (buffer->pfVolume[s] >= 0.0f) ? buffer->pfVolume[s] : channelVol;
					const float inc     = (channelVol - old) / static_cast< float >(frameCount);
					buffer->pfVolume[s] = channelVol;

					// Calculates the ITD offset of the audio data this frame.
					// Interaural Time Delay (ITD) is a small time delay between your ears
					// depending on the sound source position on the horizontal plane and the
					// distance between your ears.
					//
					// Offset for ITD is not applied directly, but rather the offset is interpolated
					// linearly across the entire chunk, between the offset of the last chunk and the
					// newly calculated offset for this chunk. This prevents clicking / buzzing when the
					// audio source or camera is moving, because abruptly changing offsets (and thus
					// abruptly changing the playback position) will create a clicking noise.
					// Normalize dot to range [0,1] instead [-1,1]
					const int offset      = static_cast< int >(INTERAURAL_DELAY * (1.0f + dot) / 2.0f);
					const int oldOffset   = static_cast< int >(buffer->piOffset[s]);
					const float incOffset = static_cast< float >(offset - oldOffset) / static_cast< float >(frameCount);
					buffer->piOffset[s]   = static_cast< unsigned int >(offset);
					/*
										qWarning("%d: Pos %f %f %f : Dot %f Len %f ChannelVol %f", s, speaker[s*3+0],
					   speaker[s*3+1], speaker[s*3+2], dot, len, channelVol);
					*/
					if ((old >= 0.00000001f) || (channelVol >= 0.00000001f)) {
						for (unsigned int i = 0; i < frameCount; ++i) {
							unsigned int currentOffset = static_cast< unsigned int >(
								static_cast< float >(oldOffset) + incOffset * static_cast< float >(i));
							if (speech && speech->bStereo) {
								// Mix stereo user's stream into mono
								// frame: for a stereo stream, the [LR] pair inside ...[LR]LRLRLR.... is a frame
								o[i * nchan] += (pfBuffer[2 * i + currentOffset] / 2.0f
												 + pfBuffer[2 * i + currentOffset + 1] / 2.0f)
												* (old + inc * static_cast< float >(i));
							} else {
								o[i * nchan] += pfBuffer[i + currentOffset] * (old + inc * static_cast< float >(i));
							}
						}
					}
				}
			} else {
				// Mix the current audio source into the output by adding it to the elements of the output buffer after
				// having applied a volume adjustment
				for (unsigned int s = 0; s < nchan; ++s) {
					const float channelVol = svol[s] * volumeAdjustment;
					float *RESTRICT o      = output + s;
					if (buffer->bStereo) {
						// Linear-panning stereo stream according to the projection of fSpeaker vector on left-right
						// direction.
						// frame: for a stereo stream, the [LR] pair inside ...[LR]LRLRLR.... is a frame
						for (unsigned int i = 0; i < frameCount; ++i)
							o[i * nchan] += (pfBuffer[2 * i] * fStereoPanningFactor[2 * s + 0]
											 + pfBuffer[2 * i + 1] * fStereoPanningFactor[2 * s + 1])
											* channelVol;
					} else {
						for (unsigned int i = 0; i < frameCount; ++i)
							o[i * nchan] += pfBuffer[i] * channelVol;
					}
				}
			}
		}

		if (recorder && recorder->isInMixDownMode()) {
			recorder->addBuffer(nullptr, recbuff, static_cast< int >(frameCount));
		}
	}

	bool pluginModifiedAudio = false;
	emit audioOutputAboutToPlay(output, frameCount, nchan, SAMPLE_RATE, &pluginModifiedAudio);

	if (pluginModifiedAudio || (!qlMix.isEmpty())) {
		// Clip the output audio
		if (eSampleFormat == SampleFloat)
			for (unsigned int i = 0; i < frameCount * iChannels; i++)
				output[i] = qBound(-1.0f, output[i], 1.0f);
		else
			// Also convert the intermediate float array into an array of shorts before writing it to the outbuff
			for (unsigned int i = 0; i < frameCount * iChannels; i++)
				reinterpret_cast< short * >(outbuff)[i] =
					static_cast< short >(qBound(-32768.f, (output[i] * 32768.f), 32767.f));
	}

	qrwlOutputs.unlock();

	// Delete all AudioOutputBuffer that no longer provide any new audio
	for (AudioOutputBuffer *buffer : qlDel) {
		removeBuffer(buffer);
	}

#ifdef USE_MANUAL_PLUGIN
	Manual::setSpeakerPositions(positions);
#endif

	// Return whether data has been written to the outbuff
	return (pluginModifiedAudio || (!qlMix.isEmpty()));
}

bool AudioOutput::isAlive() const {
	return isRunning();
}

unsigned int AudioOutput::getMixerFreq() const {
	return iMixerFreq;
}

void AudioOutput::setBufferSize(unsigned int bufferSize) {
	iBufferSize = bufferSize;
}
