void ProcessCommon(AudioReader reader, ref float[] impulse) { int targetLen = QMath.Base2Ceil((int)reader.Length) << 1; Complex[] commonSpectrum = new Complex[targetLen]; FFTCache cache = new FFTCache(targetLen); for (int ch = 0; ch < reader.ChannelCount; ++ch) { float[] channel = new float[targetLen]; WaveformUtils.ExtractChannel(impulse, channel, ch, reader.ChannelCount); Complex[] spectrum = Measurements.FFT(channel, cache); for (int band = 0; band < spectrum.Length; ++band) { commonSpectrum[band] += spectrum[band]; } } float mul = 1f / reader.ChannelCount; for (int band = 0; band < commonSpectrum.Length; ++band) { commonSpectrum[band] = (commonSpectrum[band] * mul).Invert(); } Array.Resize(ref impulse, impulse.Length << 1); for (int ch = 0; ch < reader.ChannelCount; ++ch) { Convolver filter = GetFilter(commonSpectrum, 1, reader.SampleRate); filter.Process(impulse, ch, reader.ChannelCount); } }
static Dictionary <double, float[]> GetGainDifferences(List <HRTFSetEntry> entries, List <double> hValues) { Dictionary <double, float[]> result = new Dictionary <double, float[]>(); // Distance; array by hValues for (int entry = 0, count = entries.Count; entry < count; ++entry) { HRTFSetEntry setEntry = entries[entry]; float maxGain = float.MinValue, minGain = float.MaxValue; for (int channel = 0; channel < setEntry.Data.Length; ++channel) { float peak = QMath.GainToDb(WaveformUtils.GetRMS(setEntry.Data[channel])); if (maxGain < peak) { maxGain = peak; } if (minGain > peak) { minGain = peak; } } if (!result.ContainsKey(setEntry.Distance)) { result[setEntry.Distance] = new float[hValues.Count]; } result[setEntry.Distance][hValues.IndexOf(setEntry.Azimuth)] = maxGain - minGain; } return(result); }
void ProcessPerChannel(AudioReader reader, ref float[] impulse) { int targetLen = QMath.Base2Ceil((int)reader.Length) << 1; Convolver[] filters = new Convolver[reader.ChannelCount]; FFTCache cache = new FFTCache(targetLen); for (int ch = 0; ch < reader.ChannelCount; ++ch) { float[] channel = new float[targetLen]; WaveformUtils.ExtractChannel(impulse, channel, ch, reader.ChannelCount); Complex[] spectrum = Measurements.FFT(channel, cache); for (int band = 0; band < spectrum.Length; ++band) { spectrum[band] = spectrum[band].Invert(); } filters[ch] = GetFilter(spectrum, WaveformUtils.GetRMS(channel), reader.SampleRate); } Array.Resize(ref impulse, impulse.Length << 1); for (int ch = 0; ch < reader.ChannelCount; ++ch) { filters[ch].Process(impulse, ch, reader.ChannelCount); } }
/// <summary> /// Read all stream data from this block, without separating frames. /// </summary> public byte[] GetData() { reader.Position = firstFrame; int length = QMath.Sum(frameSizes); return(reader.ReadBytes(length)); }
private QVector2IntDir getExitForRect(QRectInt rect) { QMazeOutputDirection dir; int ix = QMath.getRandom(0, rect.width); int iy; if (ix == 0 || ix == rect.width - 1) { iy = QMath.getRandom(0, rect.height); dir = (ix == 0 ? QMazeOutputDirection.W : QMazeOutputDirection.E); } else { if (QMath.getRandom() > 0.5) { iy = 0; dir = QMazeOutputDirection.N; } else { iy = rect.height - 1; dir = QMazeOutputDirection.S; } } return(new QVector2IntDir(ix, iy, dir)); }
/// <summary> /// Convert a float array to complex a size that's ready for FFT. /// </summary> public static Complex[] ParseForFFT(this float[] source) { Complex[] result = new Complex[QMath.Base2Ceil(source.Length)]; for (int i = 0; i < source.Length; ++i) { result[i].Real = source[i]; } return(result); }
/// <summary> /// Moves a graph's average value to 0. /// </summary> public static void Normalize(float[] graph) { float avg = QMath.Average(graph); for (int i = 0; i < graph.Length; ++i) { graph[i] -= avg; } }
private void generateNextLevel() { baseMazeEngine.destroyImmediateMazeGeometry(); List <QVector2IntDir> exitPositionList = baseMazeEngine.getExitPositionList(); if (exitPositionList.Count > 1) { exitPositionList.RemoveAt(0); baseMazeEngine.setExitPositionList(exitPositionList); } List <QVector2Int> obstaclePositionList = new List <QVector2Int>(); if (prevMazeEngine == null) { prevRect = new QRectInt(QMath.getRandom(1, baseMazeEngine.getMazeWidth() - CHILD_MAZE_SIZE - 2), QMath.getRandom(1, baseMazeEngine.getMazeHeight() - CHILD_MAZE_SIZE - 2), CHILD_MAZE_SIZE, CHILD_MAZE_SIZE); obstaclePositionList.AddRange(rectToList(prevRect)); prevMazeEngine = createChildMaze(prevRect, childMazeEngine_1); prevMazeEngine.generateMaze(); player.setPosition(prevMazeEngine.transform.TransformPoint(prevMazeEngine.getFinishPositionList()[0].toVector3())); } else { prevMazeEngine.destroyImmediateMazeGeometry(); prevRect = nextRect; prevMazeEngine = nextMazeEngine; obstaclePositionList.AddRange(rectToList(prevRect)); } nextRect = new QRectInt(QMath.getRandom(1, baseMazeEngine.getMazeWidth() - CHILD_MAZE_SIZE - 2), QMath.getRandom(1, baseMazeEngine.getMazeHeight() - CHILD_MAZE_SIZE - 2), CHILD_MAZE_SIZE, CHILD_MAZE_SIZE); while (isRectNear(prevRect, nextRect)) { nextRect.x = QMath.getRandom(1, baseMazeEngine.getMazeWidth() - CHILD_MAZE_SIZE - 2); nextRect.y = QMath.getRandom(1, baseMazeEngine.getMazeHeight() - CHILD_MAZE_SIZE - 2); } obstaclePositionList.AddRange(rectToList(nextRect)); baseMazeEngine.setObstaclePositionList(obstaclePositionList); nextMazeEngine = createChildMaze(nextRect, prevMazeEngine == childMazeEngine_1 ? childMazeEngine_2 : childMazeEngine_1); nextMazeEngine.generateMaze(); List <QVector2IntDir> nextMazeEngineFinishPositionList = nextMazeEngine.getFinishPositionList(); finishTransform.parent = nextMazeEngine.getMazeData()[nextMazeEngineFinishPositionList[0].x][nextMazeEngineFinishPositionList[0].y].geometry.transform; finishTransform.localPosition = new Vector3(); player.setGoal(nextMazeEngine.transform.TransformPoint(nextMazeEngineFinishPositionList[0].toVector3()), goalReachedHandler); baseMazeEngine.generateMaze(); currentLevel++; levelText.text = "LEVEL: " + currentLevel; }
public void TruncateTest() { Decimal i = 16.151m; int midPointRouting = 2; Decimal expected = 16.15m; Decimal actual; actual = QMath.Truncate(i, midPointRouting); Assert.AreEqual(expected, actual); }
public void CeillingTest_d() { Decimal i = 16.156m; int midPointRouting = 2; Decimal expected = 16.16m; Decimal actual; actual = QMath.Ceilling(i, midPointRouting); Assert.AreEqual(expected, actual); }
//TODO Switch to latest versions of .NET and implement a BigInt Version. /// <summary> /// Rounds the given value to the specified number of significant figures/digits. /// </summary> /// <param name="val"> The value to be rounded </param> /// <param name="digits"> the number of significant figures </param> /// <returns> the rounded number </returns> public static double RoundToSignificantDigits(double val, int digits) { if (val == 0) { return(0); } double scale = QMath.Pow(10, QMath.Floor(QMath.Log10(QMath.Abs(val))) + 1); return(scale * QMath.Round(val / scale, digits)); }
#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand /// <summary> /// Reads the next VINT from a stream, does not cut the leading 1. /// </summary> public static long ReadTag(Stream reader) { int first = reader.ReadByte(); int extraBytes = QMath.LeadingZerosInByte(first); long value = first; for (int i = 0; i < extraBytes; ++i) { value = (value << 8) | reader.ReadByte(); } return(value); }
/// <summary> /// Apply variable smoothing (in octaves) on a graph drawn with /// <see cref="ConvertToGraph(float[], double, double, int, int)"/>. /// </summary> public static float[] SmoothGraph(float[] samples, float startFreq, float endFreq, float startOctave, float endOctave) { float[] startGraph = SmoothGraph(samples, startFreq, endFreq, startOctave), endGraph = SmoothGraph(samples, startFreq, endFreq, endOctave), output = new float[samples.Length]; float positioner = 1f / samples.Length; for (int i = 0; i < samples.Length; ++i) { output[i] = QMath.Lerp(startGraph[i], endGraph[i], i * positioner); } return(output); }
/// <summary> /// Cache the samples if the source should be rendered. This wouldn't be thread safe. /// </summary> /// <returns>The collection should be performed, as all requirements are met</returns> protected internal virtual bool Precollect() { if (delay > 0) { delay -= listener.UpdateRate; return(false); } if (listener.sourceDistances.Contains(distance)) { if (listener.AudioQuality != QualityModes.Low) { if (DopplerLevel == 0) { calculatedPitch = Pitch; } else { float dopplerTarget = Pitch + lastDoppler * // c / (c - dv), dv = ds / dt (SpeedOfSound / (SpeedOfSound - (lastDistance - distance) / listener.pulseDelta) - 1); lastDoppler = Math.Clamp(QMath.Lerp(lastDoppler, dopplerTarget, 10 * listener.UpdateRate / (float)listener.SampleRate), 0, DopplerLevel); calculatedPitch = Math.Clamp(lastDoppler, .5f, 3f); } } else { calculatedPitch = 1; // Disable any pitch change on low quality } resampleMult = listener.SampleRate != Clip.SampleRate ? (float)Clip.SampleRate / listener.SampleRate : 1; baseUpdateRate = (int)(listener.UpdateRate * calculatedPitch); PitchedUpdateRate = (int)(baseUpdateRate * resampleMult); if (samples.Length != PitchedUpdateRate) { samples = new float[PitchedUpdateRate]; } if (Clip.Channels == 2 && leftSamples.Length != PitchedUpdateRate) { leftSamples = new float[PitchedUpdateRate]; rightSamples = new float[PitchedUpdateRate]; } Rendered = GetSamples(); if (rendered.Length != Listener.Channels.Length * listener.UpdateRate) { rendered = new float[Listener.Channels.Length * listener.UpdateRate]; } return(true); } Rendered = null; return(false); }
/// <summary> /// Constructs an optimized convolution. /// </summary> public FastConvolver(float[] impulse, int delay = 0) { int fftSize = 2 << QMath.Log2Ceil(impulse.Length); // Zero padding for the falloff to have space cache = new FFTCache(fftSize); filter = new Complex[fftSize]; for (int sample = 0; sample < impulse.Length; ++sample) { filter[sample].Real = impulse[sample]; } filter.InPlaceFFT(cache); present = new Complex[fftSize]; future = new float[fftSize + delay]; this.delay = delay; }
public int UpdateMe(Node node, Person me) { me.angleQ = QMath.RadLimitQ(me.angleQ + node.angleDeltaQ); me.xQ += QMath.CosQ(me.angleQ, me.speedRev); me.zQ += QMath.SinQ(me.angleQ, me.speedRev); me.steps--; if (me.steps == 0) { return((int)node.NodeFeedback.targetReached); } else { return((int)node.NodeFeedback.notFinished); } }
/// <summary> /// Gets the number of minimum bits required to represent this integer. Of course, all integers take the same space but a lower integer can be represented by /// less bits. /// </summary> /// <param name="number"> the integer </param> /// <returns> the bits required to represent said integer. if negative, will return 32 as negative values require the flag. input the absolute value /// if you wish the minimum bits if the number was not negative to be returned. </returns> public static int GetIntMinimumBits(int number) { if (number < 0) { return(32); } for (int i = 31; i >= 0; i--) { if (number % QMath.Pow(2, i) != number) { return(i + 1); //TODO Left here. } } return(1); }
void ProcessImpulse(object sender, RoutedEventArgs e) { if (browser.ShowDialog().Value) { AudioReader reader = AudioReader.Open(browser.FileName); float[] impulse = reader.Read(); float gain = 1; if (keepGain.IsChecked.Value) { gain = WaveformUtils.GetPeak(impulse); } if (commonEQ.IsChecked.Value) { ProcessCommon(reader, ref impulse); } else { ProcessPerChannel(reader, ref impulse); } if (keepGain.IsChecked.Value) { WaveformUtils.Gain(impulse, gain / WaveformUtils.GetPeak(impulse)); } BitDepth bits = reader.Bits; if (forceFloat.IsChecked.Value) { bits = BitDepth.Float32; } int targetLen = QMath.Base2Ceil((int)reader.Length); if (separateExport.IsChecked.Value) { ReferenceChannel[] channels = ChannelPrototype.GetStandardMatrix(reader.ChannelCount); for (int ch = 0; ch < reader.ChannelCount; ++ch) { string exportName = Path.GetFileName(browser.FileName); int idx = exportName.LastIndexOf('.'); string channelName = ChannelPrototype.Mapping[(int)channels[ch]].Name; exporter.FileName = $"{exportName[..idx]} - {channelName}{exportName[idx..]}";
private void generateNextPart() { QMazeEngine mazeEngine = (QMazeEngine)GameObject.Instantiate(mazeEnginePrefab); mazeEngine.getMazePiecePack().getPiece(QMazePieceType.Intersection).use = false; mazeEngine.transform.position = new Vector3(currentPartId * mazeEngine.getMazeWidth() * mazeEngine.getMazePieceWidth(), 0, 0); parts.Enqueue(mazeEngine); if (currentPartId == 0) { List <QVector2IntDir> startPositionList = new List <QVector2IntDir>(); startPositionList.Add(new QVector2IntDir(0, 0, QMazeOutputDirection.NotSpecified)); mazeEngine.setStartPositionList(startPositionList); lastExitY = QMath.getRandom(0, mazeEngine.getMazeHeight() - 1); List <QVector2IntDir> exitPositionList = new List <QVector2IntDir>(); exitPositionList.Add(new QVector2IntDir(mazeEngine.getMazeWidth() - 1, lastExitY, QMazeOutputDirection.E)); mazeEngine.setExitPositionList(exitPositionList); } else { List <QVector2IntDir> exitPositionList = new List <QVector2IntDir>(); exitPositionList.Add(new QVector2IntDir(0, lastExitY, QMazeOutputDirection.W)); lastExitY = QMath.getRandom(0, mazeEngine.getMazeHeight() - 1); exitPositionList.Add(new QVector2IntDir(mazeEngine.getMazeWidth() - 1, lastExitY, QMazeOutputDirection.E)); mazeEngine.setExitPositionList(exitPositionList); } GameObject block = (GameObject)GameObject.Instantiate(blockPrefab); block.transform.parent = mazeEngine.gameObject.transform; block.transform.position = new Vector3(((currentPartId + 1) * mazeEngine.getMazeWidth() - 0.5f) * mazeEngine.getMazePieceWidth(), 0, -lastExitY * mazeEngine.getMazePieceHeight()); block.GetComponent <QBlock>().triggerHandlerEvent += blockHandler; mazeEngine.generateMazeAsync(this, 0.016f); levelText.text = "LEVEL: " + currentPartId; currentPartId++; }
/// <summary> /// Constructs an optimized convolution. /// </summary> public DualConvolver(float[] impulse1, float[] impulse2, int delay1 = 0, int delay2 = 0) { int impulseLength = Math.Max(impulse1.Length, impulse2.Length); int fftSize = 2 << QMath.Log2Ceil(impulseLength); // Zero padding for the falloff to have space cache = new FFTCache(fftSize); filter = new Complex[fftSize]; for (int sample = 0; sample < impulse1.Length; ++sample) { filter[sample].Real = impulse1[sample]; } for (int sample = 0; sample < impulse2.Length; ++sample) { filter[sample].Imaginary = impulse2[sample]; } filter.InPlaceFFT(cache); present = new Complex[fftSize]; future = new Complex[fftSize + Math.Max(delay1, delay2)]; this.delay1 = delay1; this.delay2 = delay2; }
/// <summary> /// Gets the gain at a given frequency. /// </summary> public double this[double frequency] { get { int bandCount = bands.Count; if (bandCount == 0) { return(0); } int nextBand = 0, prevBand = 0; while (nextBand != bandCount && bands[nextBand].Frequency < frequency) { prevBand = nextBand; ++nextBand; } if (nextBand != bandCount && nextBand != 0) { return(QMath.Lerp(bands[prevBand].Gain, bands[nextBand].Gain, QMath.LerpInverse(bands[prevBand].Frequency, bands[nextBand].Frequency, frequency))); } return(bands[prevBand].Gain); } }
/// <summary> /// Resamples a single channel with medium quality (linear interpolation). /// </summary> /// <param name="samples">Samples of the source channel</param> /// <param name="to">New sample count</param> /// <returns>Returns a resampled version of the given array</returns> public static float[] Lerp(float[] samples, int to) { if (samples.Length == to) { return(samples); } float[] output = new float[to]; float ratio = samples.Length / (float)to; int lerpUntil = (int)((samples.Length - 1) / ratio); // Halving point where i * ratio would be over the array for (int i = 0; i < lerpUntil; ++i) { int sample = (int)(i * ratio); output[i] = QMath.Lerp(samples[sample], samples[sample + 1], i * ratio % 1); } for (int i = lerpUntil; i < to; ++i) { output[i] = samples[(int)(i * ratio)]; } return(output); }
void Update() { float[][] samples = Source.cavernSource.Rendered; if (samples != null) { float peakSize = float.NegativeInfinity; for (int channel = 0; channel < samples.Length; ++channel) { float channelSize = 20 * (float)Math.Log10(WaveformUtils.GetPeak(samples[channel])); if (channelSize < -600) { channelSize = -600; } if (peakSize < channelSize) { peakSize = channelSize; } } float size = Mathf.Clamp(peakSize / -DynamicRange + 1, 0, 1); scale = QMath.Lerp(scale, (MaxSize - MinSize) * size + MinSize, 1 - Smoothing); transform.localScale = new Vector3(scale, scale, scale); } }
void ObjectBasicInfo(BitExtractor extractor, bool readAllBlocks) { int blocks = readAllBlocks ? 3 : extractor.Read(2); // Gain if ((blocks & 2) != 0) { int gainHelper = extractor.Read(2); gain = gainHelper switch { 0 => 1, 1 => 0, 2 => (gainHelper = extractor.Read(6)) < 15 ? QMath.DbToGain(15 - gainHelper) : QMath.DbToGain(14 - gainHelper), _ => - 1, }; } // Priority - unneccessary, everything's rendered if ((blocks & 1) != 0 && !extractor.ReadBit()) { extractor.Skip(5); } }
/// <summary> /// Reads the next VINT from a stream, cuts the leading 1, reads the correct value. /// </summary> public static long ReadValue(Stream reader) { long value = ReadTag(reader); return(value - (1L << QMath.BitsAfterMSB(value))); }
/// <summary> /// For E-AC-3, data for multiple blocks is included in an audio frame header. /// </summary> void AudioFrame() { expstre = header.Blocks != 6 || extractor.ReadBit(); ahte = header.Blocks == 6 && extractor.ReadBit(); snroffststr = extractor.Read(2); transproce = extractor.ReadBit(); blkswe = extractor.ReadBit(); dithflage = extractor.ReadBit(); bamode = extractor.ReadBit(); frmfgaincode = extractor.ReadBit(); dbaflde = extractor.ReadBit(); skipflde = extractor.ReadBit(); spxattene = extractor.ReadBit(); if (header.ChannelMode > 1) // Not mono { cplstre[0] = true; cplinu[0] = extractor.ReadBit(); for (int block = 1; block < cplstre.Length; ++block) { if (cplstre[block] = extractor.ReadBit()) { cplinu[block] = extractor.ReadBit(); } else { cplinu[block] = cplinu[block - 1]; } } } else { for (int block = 1; block < cplstre.Length; ++block) { cplinu[block] = false; } } // Exponent strategy data init if (expstre) { for (int block = 0; block < cplexpstr.Length; ++block) { if (cplinu[block]) { cplexpstr[block] = (ExpStrat)extractor.Read(2); } for (int channel = 0; channel < channels.Length; ++channel) { chexpstr[block][channel] = (ExpStrat)extractor.Read(2); } } } else { int ncplblks = 0; for (int block = 0; block < cplinu.Length; ++block) { if (cplinu[block]) { ++ncplblks; } } if (header.ChannelMode > 1 && ncplblks > 0) { frmcplexpstr = extractor.Read(5); } for (int channel = 0; channel < channels.Length; ++channel) { frmchexpstr[channel] = extractor.Read(5); } for (int block = 0; block < cplexpstr.Length; ++block) { cplexpstr[block] = frmcplexpstr_tbl[frmcplexpstr][block]; for (int channel = 0; channel < channels.Length; ++channel) { chexpstr[block][channel] = frmcplexpstr_tbl[frmchexpstr[channel]][block]; } } } if (header.LFE) { for (int block = 0; block < lfeexpstr.Length; ++block) { lfeexpstr[block] = extractor.ReadBit(); } } // Converter exponent strategy data if (header.StreamType == StreamTypes.Independent && (convexpstre = header.Blocks == 6 || extractor.ReadBit())) { for (int channel = 0; channel < channels.Length; ++channel) { convexpstr[channel] = extractor.Read(5); } } // AHT data if (ahte) { throw new UnsupportedFeatureException("AHT"); } // Audio frame SNR offset data if (snroffststr == 0) { frmcsnroffst = extractor.Read(6); frmfsnroffst = extractor.Read(4); } // Transient pre-noise processing data if (transproce) { for (int channel = 0; channel < channels.Length; ++channel) { if (chintransproc[channel] = extractor.ReadBit()) { transprocloc[channel] = extractor.Read(10); transproclen[channel] = extractor.Read(8); } } } // Spectral extension attenuation data if (spxattene) { for (int ch = 0; ch < channels.Length; ++ch) { if (chinspxatten[ch] = extractor.ReadBit()) { spxattencod[ch] = extractor.Read(5); } } } if (blkstrtinfoe = header.Blocks != 1 && extractor.ReadBit()) { int nblkstrtbits = (header.Blocks - 1) * (4 + QMath.Log2Ceil(header.WordsPerSyncframe)); blkstrtinfo = extractor.Read(nblkstrtbits); } // Syntax state init for (int channel = 0; channel < channels.Length; ++channel) { firstspxcos[channel] = true; firstcplcos[channel] = true; } firstcplleak = true; }
/// <summary> /// Generate the left/right ear filters. /// </summary> /// <param name="right">The object is to the right of the <see cref="Listener"/>'s forward vector</param> /// <param name="samples">Single-channel downmixed samples to process</param> public void Generate(bool right, float[] samples) { float dirMul = -90; if (right) { dirMul = 90; } Vector3 sourceForward = new Vector3(0, dirMul, 0).RotateInverse(source.listener.Rotation).PlaceInSphere(), dir = source.Position - source.listener.Position; float distance = dir.Length(), rawAngle = (float)Math.Acos(Vector3.Dot(sourceForward, dir) / distance), angle = rawAngle * VectorExtensions.Rad2Deg; distance /= distanceFactor; // Find bounding angles with discrete impulses int smallerAngle = 0; while (smallerAngle < angles.Length && angles[smallerAngle] < angle) { ++smallerAngle; } if (smallerAngle != 0) { --smallerAngle; } int largerAngle = smallerAngle + 1; if (largerAngle == angles.Length) { largerAngle = angles.Length - 1; } float angleRatio = Math.Min(QMath.LerpInverse(angles[smallerAngle], angles[largerAngle], angle), 1); // Find bounding distances with discrete impulses int smallerDistance = 0; while (smallerDistance < distances.Length && distances[smallerDistance] < distance) { ++smallerDistance; } if (smallerDistance != 0) { --smallerDistance; } int largerDistance = smallerDistance + 1; if (largerDistance == distances.Length) { largerDistance = distances.Length - 1; } float distanceRatio = Math.Clamp(QMath.LerpInverse(distances[smallerDistance], distances[largerDistance], distance), 0, 1); // Find impulse candidates and their weight float[][] candidates = new float[4][] { impulses[smallerAngle][smallerDistance], impulses[smallerAngle][largerDistance], impulses[largerAngle][smallerDistance], impulses[largerAngle][largerDistance] }; float[] gains = new float[4] { (float)Math.Sqrt((1 - angleRatio) * (1 - distanceRatio)), (float)Math.Sqrt((1 - angleRatio) * distanceRatio), (float)Math.Sqrt(angleRatio * (1 - distanceRatio)), (float)Math.Sqrt(angleRatio * distanceRatio) }; // Apply the ear canal's response Array.Clear(filter.Impulse, 0, filterSize); for (int candidate = 0; candidate < candidates.Length; ++candidate) { WaveformUtils.Mix(candidates[candidate], filter.Impulse, gains[candidate]); } filter.Process(samples); // Apply gains float angleDiff = (float)(Math.Sin(rawAngle) * .097f); float ratioDiff = (distance + angleDiff) * (VirtualizerFilter.referenceDistance - angleDiff) / ((distance - angleDiff) * (VirtualizerFilter.referenceDistance + angleDiff)); ratioDiff *= ratioDiff; if (right) { if (ratioDiff < 1) { RightGain = ratioDiff; } else { LeftGain = 1 / ratioDiff; } } else { if (ratioDiff < 1) { LeftGain = ratioDiff; } else { RightGain = 1 / ratioDiff; } } }
void Update() { if (!CavernSource) { OnDisable(); return; } for (int row = 0; row < Rows; ++row) { for (int column = 0; column < Columns; ++column) { SeatMovements[row][column].Height = 200; } } int lastRow = Rows - 1, lastColumn = Columns - 1; if (CavernSource[0] != null) // Front left { SeatMovements[0][0].Height = CavernSource[0].Height; } if (CavernSource[1] != null) // Front right { SeatMovements[0][lastColumn].Height = CavernSource[1].Height; } if (CavernSource[2] != null && CavernSource[2].Height != Cavernizer.unsetHeight) // Center { SeatMovements[0][lastColumn / 2].Height = CavernSource[2].Height; } if (CavernSource[6] != null) // Side left { SeatMovements[lastRow][0].Height = CavernSource[6].Height; } if (CavernSource[7] != null) // Side right { SeatMovements[lastRow][lastColumn].Height = CavernSource[7].Height; } // Addition is okay, and should be used, as the rears are near the sides in the back corners. if (CavernSource[4] != null) // Rear left { SeatMovements[lastRow][0].Height += CavernSource[4].Height; } if (CavernSource[5] != null) // Rear right { SeatMovements[lastRow][lastColumn].Height += CavernSource[5].Height; } SpatializedChannel rearCenter = CavernSource.GetChannel(ReferenceChannel.RearCenter); if (rearCenter != null) // Rear center { SeatMovements[lastRow][lastColumn / 2].Height = rearCenter.Height; } // Use the front channels for moving all seats if nothing else is available for the rear sides if (SeatMovements[lastRow][0].Height == 200) { SeatMovements[lastRow][0].Height = SeatMovements[0][0].Height; } if (SeatMovements[lastRow][lastColumn].Height == 200) { SeatMovements[lastRow][lastColumn].Height = SeatMovements[0][lastColumn].Height; } // Seat position interpolation for (int row = 0; row < Rows; ++row) { int prev = 0; for (int column = 0; column < Columns; ++column) { if (SeatMovements[row][column].Height != 200) { float lerpDiv = column - prev; for (int oldColumn = prev; oldColumn < column; ++oldColumn) { SeatMovements[row][oldColumn].Height = QMath.Lerp(SeatMovements[row][prev].Height, SeatMovements[row][column].Height, (oldColumn - prev) / lerpDiv); } prev = column; } } if (prev != lastColumn) { float lerpDiv = lastColumn - prev; for (int oldColumn = prev; oldColumn < lastColumn; ++oldColumn) { SeatMovements[row][oldColumn].Height = QMath.Lerp(SeatMovements[row][prev].Height, SeatMovements[row][lastColumn].Height, (oldColumn - prev) / lerpDiv); } } } for (int column = 0; column < Columns; ++column) { int prev = 0; for (int row = 0; row < Rows; ++row) { if (SeatMovements[row][column].Height != 200) { float lerpDiv = row - prev; for (int oldRow = prev; oldRow < row; ++oldRow) { SeatMovements[oldRow][column].Height = QMath.Lerp(SeatMovements[prev][column].Height, SeatMovements[row][column].Height, (oldRow - prev) / lerpDiv); } prev = row; } } if (prev != lastRow) { float lerpDiv = lastRow - prev; for (int oldRow = prev; oldRow < lastRow; ++oldRow) { SeatMovements[oldRow][column].Height = QMath.Lerp(SeatMovements[prev][column].Height, SeatMovements[lastRow][column].Height, (oldRow - prev) / lerpDiv); } } } // Seat rotation interpolation for (int row = 0; row < Rows; ++row) { SeatMovements[row][0].Rotation.z = Mathf.Clamp((SeatMovements[row][1].Height - SeatMovements[row][0].Height) * RotationConstant * 2, -MaxRotationSide, MaxRotationSide); for (int column = 1; column < lastColumn; ++column) { SeatMovements[row][column].Rotation.z = Mathf.Clamp((SeatMovements[row][column + 1].Height - SeatMovements[row][column - 1].Height) * RotationConstant, -MaxRotationSide, MaxRotationSide); } SeatMovements[row][lastColumn].Rotation.z = Mathf.Clamp((SeatMovements[row][lastColumn].Height - SeatMovements[row][lastColumn - 1].Height) * RotationConstant * 2, -MaxRotationSide, MaxRotationSide); } for (int column = 0; column < Columns; ++column) { SeatMovements[0][column].Rotation.x = Mathf.Clamp((SeatMovements[1][column].Height - SeatMovements[0][column].Height) * RotationConstant * 2, -20, 20); for (int row = 1; row < lastRow; ++row) { SeatMovements[row][column].Rotation.x = Mathf.Clamp((SeatMovements[row + 1][column].Height - SeatMovements[row - 1][column].Height) * RotationConstant, -MaxRotationFace, MaxRotationFace); } SeatMovements[lastRow][column].Rotation.x = Mathf.Clamp((SeatMovements[lastRow][column].Height - SeatMovements[lastRow - 1][column].Height) * RotationConstant * 2, -MaxRotationFace, MaxRotationFace); } }
/// <summary> /// Process the source and returns a mix to be added to the output. /// </summary> protected internal virtual float[] Collect() { // Preparations, clean environment int channels = Listener.Channels.Length, updateRate = listener.UpdateRate; Array.Clear(rendered, 0, rendered.Length); // Render audio if not muted if (!Mute) { int clipChannels = Clip.Channels; // 3D renderer preprocessing if (SpatialBlend != 0) { if (listener.AudioQuality >= QualityModes.High && clipChannels != 1) // Mono downmix above medium quality { Array.Clear(samples, 0, PitchedUpdateRate); for (int channel = 0; channel < clipChannels; ++channel) { WaveformUtils.Mix(Rendered[channel], samples); } WaveformUtils.Gain(samples, 1f / clipChannels); } else // First channel only otherwise { Array.Copy(Rendered[0], samples, PitchedUpdateRate); } } // 1D renderer if (SpatialBlend != 1) { float volume1D = Volume * (1f - SpatialBlend); // 1:1 mix for non-stereo sources if (clipChannels != 2) { samples = Resample.Adaptive(samples, updateRate, listener.AudioQuality); WriteOutput(samples, rendered, volume1D, channels); } // Full side mix for stereo sources else { Array.Copy(Rendered[0], leftSamples, PitchedUpdateRate); Array.Copy(Rendered[1], rightSamples, PitchedUpdateRate); leftSamples = Resample.Adaptive(leftSamples, updateRate, listener.AudioQuality); rightSamples = Resample.Adaptive(rightSamples, updateRate, listener.AudioQuality); Stereo1DMix(volume1D); } } // 3D mix, if the source is in range if (SpatialBlend != 0 && distance < listener.Range) { Vector3 direction = (Position - listener.Position).RotateInverse(listener.Rotation); float rolloffDistance = GetRolloff(); samples = Resample.Adaptive(samples, updateRate, listener.AudioQuality); baseUpdateRate = samples.Length; // Apply filter if set if (SpatialFilter != null) { SpatialFilter.Process(samples); } // Distance simulation for HRTF // TODO: gain correction for this in both engines if (DistanceSimulation && Listener.HeadphoneVirtualizer) { if (distancer == null) { distancer = new Distancer(this); } distancer.Generate(direction.X > 0, samples); } // ------------------------------------------------------------------ // Balance-based engine for symmetrical layouts // ------------------------------------------------------------------ if (Listener.IsSymmetric) { float volume3D = Volume * rolloffDistance * SpatialBlend; if (!LFE) { // Find a bounding box int bottomFrontLeft = -1, bottomFrontRight = -1, bottomRearLeft = -1, bottomRearRight = -1, topFrontLeft = -1, topFrontRight = -1, topRearLeft = -1, topRearRight = -1; // Closest layers on Y and Z axes float closestTop = 78, closestBottom = -73, closestTF = 75, closestTR = -73, closestBF = 75, closestBR = -69; // Find closest horizontal layers if (Listener.HeadphoneVirtualizer) { direction = direction.WarpToCube() / Listener.EnvironmentSize; } else { direction /= Listener.EnvironmentSize; } for (int channel = 0; channel < channels; ++channel) { if (!Listener.Channels[channel].LFE) { float channelY = Listener.Channels[channel].CubicalPos.Y; if (channelY < direction.Y) { if (channelY > closestBottom) { closestBottom = channelY; } } else if (channelY < closestTop) { closestTop = channelY; } } } for (int channel = 0; channel < channels; ++channel) { if (!Listener.Channels[channel].LFE) { Vector3 channelPos = Listener.Channels[channel].CubicalPos; if (channelPos.Y == closestBottom) // Bottom layer { AssignHorizontalLayer(channel, ref bottomFrontLeft, ref bottomFrontRight, ref bottomRearLeft, ref bottomRearRight, ref closestBF, ref closestBR, direction, channelPos); } if (channelPos.Y == closestTop) // Top layer { AssignHorizontalLayer(channel, ref topFrontLeft, ref topFrontRight, ref topRearLeft, ref topRearRight, ref closestTF, ref closestTR, direction, channelPos); } } } // Fix incomplete top layer FixIncompleteLayer(ref topFrontLeft, ref topFrontRight, ref topRearLeft, ref topRearRight); // When the bottom layer is completely empty (= the source is below all channels), copy the top layer if (bottomFrontLeft == -1 && bottomFrontRight == -1 && bottomRearLeft == -1 && bottomRearRight == -1) { bottomFrontLeft = topFrontLeft; bottomFrontRight = topFrontRight; bottomRearLeft = topRearLeft; bottomRearRight = topRearRight; } // Fix incomplete bottom layer else { FixIncompleteLayer(ref bottomFrontLeft, ref bottomFrontRight, ref bottomRearLeft, ref bottomRearRight); } // When the top layer is completely empty (= the source is above all channels), copy the bottom layer if (topFrontLeft == -1 || topFrontRight == -1 || topRearLeft == -1 || topRearRight == -1) { topFrontLeft = bottomFrontLeft; topFrontRight = bottomFrontRight; topRearLeft = bottomRearLeft; topRearRight = bottomRearRight; } // Spatial mix gain precalculation Vector2 layerVol = new Vector2(.5f); // (bottom; top) if (topFrontLeft != bottomFrontLeft) // Height ratio calculation { float bottomY = Listener.Channels[bottomFrontLeft].CubicalPos.Y; layerVol.Y = (direction.Y - bottomY) / (Listener.Channels[topFrontLeft].CubicalPos.Y - bottomY); layerVol.X = 1f - layerVol.Y; } // Length ratios (bottom; top) Vector2 frontVol = new Vector2(LengthRatio(bottomRearLeft, bottomFrontLeft, direction.Z), LengthRatio(topRearLeft, topFrontLeft, direction.Z)); // Width ratios float BFRVol = WidthRatio(bottomFrontLeft, bottomFrontRight, direction.X), BRRVol = WidthRatio(bottomRearLeft, bottomRearRight, direction.X), TFRVol = WidthRatio(topFrontLeft, topFrontRight, direction.X), TRRVol = WidthRatio(topRearLeft, topRearRight, direction.X), innerVolume3D = volume3D; if (Size != 0) { frontVol = QMath.Lerp(frontVol, new Vector2(.5f), Size); BFRVol = QMath.Lerp(BFRVol, .5f, Size); BRRVol = QMath.Lerp(BRRVol, .5f, Size); TFRVol = QMath.Lerp(TFRVol, .5f, Size); TRRVol = QMath.Lerp(TRRVol, .5f, Size); innerVolume3D *= 1f - Size; float extraChannelVolume = volume3D * Size / channels; for (int channel = 0; channel < channels; ++channel) { WriteOutput(samples, rendered, extraChannelVolume, channel, channels); } } // Spatial mix gain finalization Vector2 rearVol = new Vector2(1) - frontVol; layerVol *= innerVolume3D; frontVol *= layerVol; rearVol *= layerVol; WriteOutput(samples, rendered, frontVol.X * (1f - BFRVol), bottomFrontLeft, channels); WriteOutput(samples, rendered, frontVol.X * BFRVol, bottomFrontRight, channels); WriteOutput(samples, rendered, rearVol.X * (1f - BRRVol), bottomRearLeft, channels); WriteOutput(samples, rendered, rearVol.X * BRRVol, bottomRearRight, channels); WriteOutput(samples, rendered, frontVol.Y * (1f - TFRVol), topFrontLeft, channels); WriteOutput(samples, rendered, frontVol.Y * TFRVol, topFrontRight, channels); WriteOutput(samples, rendered, rearVol.Y * (1f - TRRVol), topRearLeft, channels); WriteOutput(samples, rendered, rearVol.Y * TRRVol, topRearRight, channels); } // LFE mix if (!listener.LFESeparation || LFE) { for (int channel = 0; channel < channels; ++channel) { if (Listener.Channels[channel].LFE) { WriteOutput(samples, rendered, volume3D, channel, channels); } } } } // ------------------------------------------------------------------ // Directional/distance-based engine for asymmetrical layouts // ------------------------------------------------------------------ else { // Angle match calculations float[] angleMatches; if (listener.AudioQuality >= QualityModes.High) { angleMatches = CalculateAngleMatches(channels, direction); } else { angleMatches = LinearizeAngleMatches(channels, direction); } // Object size extension if (Size != 0) { float maxAngleMatch = angleMatches[0]; for (int channel = 1; channel < channels; ++channel) { if (maxAngleMatch < angleMatches[channel]) { maxAngleMatch = angleMatches[channel]; } } for (int channel = 0; channel < channels; ++channel) { angleMatches[channel] = QMath.Lerp(angleMatches[channel], maxAngleMatch, Size); } } // Only use the closest 3 speakers on non-Perfect qualities or in Theatre mode if (listener.AudioQuality != QualityModes.Perfect || Listener.EnvironmentType == Environments.Theatre) { float top0 = 0, top1 = 0, top2 = 0; for (int channel = 0; channel < channels; ++channel) { if (!Listener.Channels[channel].LFE) { float match = angleMatches[channel]; if (top0 < match) { top2 = top1; top1 = top0; top0 = match; } else if (top1 < match) { top2 = top1; top1 = match; } else if (top2 < match) { top2 = match; } } } for (int channel = 0; channel < channels; ++channel) { if (!Listener.Channels[channel].LFE && angleMatches[channel] != top0 && angleMatches[channel] != top1 && angleMatches[channel] != top2) { angleMatches[channel] = 0; } } } float totalAngleMatch = 0; for (int channel = 0; channel < channels; ++channel) { totalAngleMatch += angleMatches[channel] * angleMatches[channel]; } totalAngleMatch = (float)Math.Sqrt(totalAngleMatch); // Place in sphere, write data to output channels float volume3D = Volume * rolloffDistance * SpatialBlend / totalAngleMatch; for (int channel = 0; channel < channels; ++channel) { if (Listener.Channels[channel].LFE) { if (!listener.LFESeparation || LFE) { WriteOutput(samples, rendered, volume3D * totalAngleMatch, channel, channels); } } else if (!LFE && angleMatches[channel] != 0) { WriteOutput(samples, rendered, volume3D * angleMatches[channel], channel, channels); } } } } } // Timing TimeSamples += PitchedUpdateRate; if (TimeSamples >= Clip.Samples) { if (Loop) { TimeSamples %= Clip.Samples; } else { TimeSamples = 0; IsPlaying = false; } } return(rendered); }
/// <summary> /// Reads the next signed VINT from a stream, cuts the leading 1, reads the correct value. /// </summary> public static long ReadSignedValue(Stream reader) { long value = ReadTag(reader); return(value - (3L << (QMath.BitsAfterMSB(value) - 1)) + 1); }