Пример #1
0
        /// <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);
        }
Пример #2
0
        /// <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);
        }
Пример #3
0
 /// <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);
     }
 }
Пример #4
0
        /// <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);
        }
Пример #5
0
 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);
     }
 }
Пример #6
0
        /// <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);
        }
Пример #7
0
        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);
            }
        }