Exemple #1
0
    public override Frame Process(Frame frame, FrameSource frameSource)
    {
        if (_pb == null)
        {
            Init();
        }

        UpdateOptions();

        return(_pb.Process(frame));
    }
Exemple #2
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);
        }