/// <summary>
        ///     Removes and audio stream from a mixer.
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        /// <param name="mixerChannel">The mixer channel.</param>
        public static void RemoveFromMixer(AudioStream audioStream, int mixerChannel)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded())
                throw new Exception("Audio file null or not audio not loaded");

            if (mixerChannel == int.MinValue) throw new Exception("Mixer channel not initialized");

            //lock (Lock)
            {
                DebugHelper.WriteLine($"RemoveFromMixer {audioStream.Description} {audioStream.Channel}...");
                BassMix.BASS_Mixer_ChannelPause(audioStream.Channel);
                Bass.BASS_ChannelLock(mixerChannel, true);

                foreach (var channel in audioStream.Channels)
                {
                    BassMix.BASS_Mixer_ChannelRemove(channel);
                }

                Bass.BASS_ChannelLock(mixerChannel, false);
                DebugHelper.WriteLine("done");
            }
        }
        public static void LoadAudio(AudioStream audioStream)
        {
            // abort if audio data already loaded
            if (audioStream.IsAudioLoaded()) return;

            AudioDataHelper.LoadAudioData(audioStream);

            var channel = Bass.BASS_StreamCreateFile(audioStream.AudioData.DataPointer,
                0,
                audioStream.AudioData.Data.Length,
                BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_STREAM_PRESCAN);

            audioStream.AddChannel(channel);

            if (audioStream.Channel == 0)
                throw new Exception("Cannot load " + audioStream.Filename + ". Error code: " + Bass.BASS_ErrorGetCode());

            DebugHelper.WriteLine("Creating reverse FX stream " + audioStream.Description + "...");
            audioStream.AddChannel(BassFx.BASS_FX_ReverseCreate(audioStream.Channel, 1, BASSFlag.BASS_STREAM_DECODE));

            if (audioStream.Channel == 0)
                throw new Exception("Cannot load " + audioStream.Filename + ". Error code: " + Bass.BASS_ErrorGetCode());

            Bass.BASS_ChannelSetAttribute(audioStream.Channel, BASSAttribute.BASS_ATTRIB_REVERSE_DIR,
                (float)BASSFXReverse.BASS_FX_RVS_FORWARD);


            DebugHelper.WriteLine("Creating tempo FX stream " + audioStream.Description + "...");

            audioStream.AddChannel(BassFx.BASS_FX_TempoCreate(audioStream.Channel,
                BASSFlag.BASS_FX_FREESOURCE | BASSFlag.BASS_STREAM_DECODE));

            if (audioStream.Channel == 0)
                throw new Exception("Cannot load " + audioStream.Filename + ". Error code: " + Bass.BASS_ErrorGetCode());

            DebugHelper.WriteLine("Calculating track length " + audioStream.Description + "...");

            audioStream.Length = Bass.BASS_ChannelGetLength(audioStream.Channel);
            audioStream.DefaultSampleRate = GetSampleRate(audioStream.Channel);

            SetReplayGain(audioStream);
            SetPosition(audioStream, 0);


        }
        /// <summary>
        ///     Sets the length of an audio stream.
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        public static void SetLength(AudioStream audioStream)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded())
                throw new Exception("Audio file null or not audio not loaded");

            audioStream.Length = Bass.BASS_ChannelGetLength(audioStream.Channel);
        }
        /// <summary>
        ///     Adds an audio stream to a mixer
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        /// <param name="mixerChannel">The mixer channel.</param>
        public static void AddToMixer(AudioStream audioStream, int mixerChannel)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded())
                throw new Exception("Audio file null or not audio not loaded");

            if (mixerChannel == int.MinValue) throw new Exception("Mixer channel not initialized");

            DebugHelper.WriteLine($"AddToMixer {audioStream.Description} {mixerChannel} {audioStream.Channel}...");
            AddChannelToMixer(audioStream.Channel, mixerChannel);

            DebugHelper.WriteLine("done");
        }
        /// <summary>
        ///     Does an audio stream power down effect asynchronously
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        private static void PowerDownAsync(AudioStream audioStream)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded()) return;

            var freq = audioStream.DefaultSampleRate;
            var interval = (int)(BpmHelper.GetDefaultLoopLength(audioStream.Bpm) * 1000) / 128;

            // set the volume slide
            Bass.BASS_ChannelSlideAttribute(audioStream.Channel, BASSAttribute.BASS_ATTRIB_VOL, 0F, interval * 8);

            var percentValue = 0.70;
            while (freq > 100)
            {
                percentValue = percentValue / 1.2;
                interval = (int)(interval * 0.9D);
                freq = (int)(audioStream.DefaultSampleRate * percentValue);
                if (freq <= 100 || audioStream.Channel == int.MinValue) continue;
                Bass.BASS_ChannelSlideAttribute(audioStream.Channel, BASSAttribute.BASS_ATTRIB_FREQ, freq, interval);
                Thread.Sleep(interval);
            }
            Pause(audioStream);
            if (!audioStream.IsAudioLoaded()) return;
            Bass.BASS_ChannelSetAttribute(audioStream.Channel, BASSAttribute.BASS_ATTRIB_FREQ,
                audioStream.DefaultSampleRate);
            SetVolume(audioStream, 100M);
        }
        /// <summary>
        /// Sets the replay gain for a channel.
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        private static void SetReplayGain(AudioStream audioStream)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded())
                throw new Exception("Audio file null or not audio not loaded");

            if (!audioStream.IsGainChannelInitialized() && audioStream.Gain == 0) return;

            var volume = DecibelToPercent(audioStream.Gain);
            DebugHelper.WriteLine("SetReplayGain for " + audioStream + " to " + volume);

            if (!audioStream.IsGainChannelInitialized())
            {
                audioStream.GainChannel = Bass.BASS_ChannelSetFX(audioStream.Channel, BASSFXType.BASS_FX_BFX_VOLUME, int.MaxValue);
            }

            var volumeParameters = new BASS_BFX_VOLUME(volume, BASSFXChan.BASS_BFX_CHANALL);
            Bass.BASS_FXSetParameters(audioStream.GainChannel, volumeParameters);
        }
        /// <summary>
        ///     Does an audio stream power down effect asynchronously
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        private static void SmoothPauseAsync(AudioStream audioStream)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded()) return;

            var volume = ((float)GetVolume(audioStream)) / 100F;
            SetVolumeSlide(audioStream, volume, 0F, 0.15D);
            Thread.Sleep(150);

            if (!audioStream.IsAudioLoaded()) return;
            BassMix.BASS_Mixer_ChannelPause(audioStream.Channel);

            SetVolume(audioStream, volume);
        }
        /// <summary>
        ///     Sets the duration and start/end volumes for an audio stream volume slide.
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        /// <param name="startVolume">The start volume.</param>
        /// <param name="endVolume">The end volume.</param>
        /// <param name="sampleDuration">Sample length duration.</param>
        public static void SetVolumeSlide(AudioStream audioStream, float startVolume, float endVolume,
            long sampleDuration)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded()) return;

            var seconds = audioStream.SamplesToSeconds(sampleDuration);
            SetVolumeSlide(audioStream, startVolume, endVolume, seconds);
        }
 /// <summary>
 ///     Gets the current audioStream position.
 /// </summary>
 /// <param name="audioStream">The audio stream.</param>
 /// <returns>The current audio stream position</returns>
 public static long GetPosition(AudioStream audioStream)
 {
     if (audioStream == null) return 0;
     return !audioStream.IsAudioLoaded() ? 0 : Bass.BASS_ChannelGetPosition(audioStream.Channel);
 }
 /// <summary>
 ///     Pause an audio stream
 /// </summary>
 /// <param name="audioStream">The audio stream.</param>
 public static void Pause(AudioStream audioStream)
 {
     if (audioStream == null || !audioStream.IsAudioLoaded()) return;
     BassMix.BASS_Mixer_ChannelPause(audioStream.Channel);
 }
        /// <summary>
        ///     Sets the audio stream position
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        /// <param name="samplePosition">The audio stream position.</param>
        public static void SetPosition(AudioStream audioStream, long samplePosition)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded()) return;
            if (samplePosition < 0 || samplePosition > audioStream.Length) return;

            var secondPosition = TimeFormatHelper.GetFormattedSeconds(audioStream.SamplesToSeconds(samplePosition));
            DebugHelper.WriteLine($"SetPosition {audioStream.Description} {secondPosition} {samplePosition}");
            Bass.BASS_ChannelSetPosition(audioStream.Channel, samplePosition);
            DebugHelper.WriteLine($"SetPosition END {audioStream.Description} {secondPosition} {samplePosition}");
        }
        /// <summary>
        ///     Resets the audio stream pitch.
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        public static void ResetPitch(AudioStream audioStream)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded()) return;

            Bass.BASS_ChannelSetAttribute(audioStream.Channel, BASSAttribute.BASS_ATTRIB_TEMPO_PITCH, 0F);
        }
        /// <summary>
        ///     Sets the audio stream pitch, based on a percent pitch value (0 - 200, 100 being 'normal' pitch)
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        /// <param name="pitch">The pitch.</param>
        public static void SetPitch(AudioStream audioStream, double pitch)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded()) return;

            DebugHelper.WriteLine("SetPitch");

            float sampleRate = audioStream.DefaultSampleRate;
            Bass.BASS_ChannelSetAttribute(audioStream.Channel, BASSAttribute.BASS_ATTRIB_TEMPO_FREQ, sampleRate);
        }
        /// <summary>
        ///     Unloads the audio of an audio stream
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        public static void UnloadAudio(AudioStream audioStream)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded())
                throw new Exception("Audio file null or not audio not loaded");
            DebugHelper.WriteLine($"UnloadAudio {audioStream.Description}...");

            foreach (var channel in audioStream.Channels)
            {
                Bass.BASS_StreamFree(channel);
            }
            audioStream.Channels.Clear();

            AudioDataHelper.UnloadAudioData(audioStream);

            DebugHelper.WriteLine("done");
        }
        /// <summary>
        ///     Plays an audio stream
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        public static void Play(AudioStream audioStream)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded()) return;

            //lock (Lock)
            //{
                DebugHelper.WriteLine("Play Audio Stream (" + audioStream.Description + ")");
                BassMix.BASS_Mixer_ChannelPlay(audioStream.Channel);
           // }
        }
 /// <summary>
 ///     Gets the audio stream volume.
 /// </summary>
 /// <param name="audioStream">The audio stream.</param>
 /// <returns>The tracks volume</returns>
 public static decimal GetVolume(AudioStream audioStream)
 {
     if (audioStream == null || !audioStream.IsAudioLoaded()) return 0;
     return GetVolume(audioStream.Channel);
 }
        /// <summary>
        ///     Sets the duration and start/end volumes for an audio stream volume slide.
        /// </summary>
        /// <param name="audioStream">The audio stream.</param>
        /// <param name="startVolume">The start volume as a percentage (0 - 1).</param>
        /// <param name="endVolume">The end volume as a percentage (0 - 1).</param>
        /// <param name="seconds">The seconds.</param>
        public static void SetVolumeSlide(AudioStream audioStream, float startVolume, float endVolume, double seconds)
        {
            if (audioStream == null || !audioStream.IsAudioLoaded()) return;

            // set start volume
            SetVolume(audioStream, startVolume);

            var miliseconds = (int)(seconds * 1000);

            // set the volume slide
            Bass.BASS_ChannelSlideAttribute(audioStream.Channel, BASSAttribute.BASS_ATTRIB_VOL, endVolume, miliseconds);
        }