Author Topic: Latency issue with a Reverse Stream  (Read 1134 times)

aybe

  • Posts: 145
Latency issue with a Reverse Stream
« on: 24 Sep '10 - 16:07 »
Hello,

1st Question :
I have been doing some trials on creating a stream that behaves like a turntable, so which tempo can be changed, but direction as well.

I am currently having an issue when reversing the stream, depending the "dec_block" parameter, when I change its direction, it's not straight.
After changing the direction, I can still hear (for a short time) the stream playing in the previous direction, then I hear it playing the new direction normally. It looks like it does play the last buffer in the previous direction before playing the new direction.

2nd question :
What is from your point of view, and BASS internals, the best way to create such a stream with the API ?

So far, here are the methods I can create a stream behaving like that :

- Original decode stream -> Tempo stream -> Reverse stream
- Original decode stream -> Reverse stream -> Tempo stream
- Original decode stream -> Reverse stream -> BassAsio:BASS_ASIO_ChannelSetRate

With method 1,
Getting stream position (from reverse one) is slow and seems related to "dec_block" setting; (you get a new position every dec_block)
I did use the same value as in your documentation (2 seconds), a lower setting makes it faster but not comparable to getting a position from a simple stream you directly play.

Method 2,
This method will be useful when I will want to upgrade to use any protocol (DirectSound, Asio, Wasapi)

Method 3,
So far this method applies only for Asio though it works fine, I guess the resampling is done right on the unmanaged part of BASS.
The method 2, 3 works much better but I am still having the issue in question 1.

On the sample I set dec_block to 30 because you can hear it more obviously at this setting.

(Update : The issue occurs only when going from forward to reverse)

Any ideas ?

Many thanks.

Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Un4seen.Bass;
using Un4seen.Bass.AddOn.Fx;
using Un4seen.BassAsio;

namespace pitchSetting
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private WindowInteropHelper _helper;
        private ASIOPROC _procAsio;
        private SYNCPROC _procSync;
        private int _pStream;
        private int _pReverse;

        public MainWindow()
        {
            InitializeComponent();
            Closing += WindowClosing;
            _procSync = SyncProc;
            _procAsio = AsioProc;
        }

        private void WindowLoaded(object sender, RoutedEventArgs e)
        {
            _helper = new WindowInteropHelper(this);
            if (!Bass.BASS_Init(0, 44100, BASSInit.BASS_DEVICE_DEFAULT, _helper.Handle) || !BassAsio.BASS_ASIO_Init(0))
                return;

            // Create the original decoding stream and the reverse stream
            string file = @"D:\Music\Singles\House\Tribal\Dub\Francois K. - Edge Of Time (Tee's Freeze Mix).mp3";
            _pStream = Bass.BASS_StreamCreateFile(file, 0, 0, BASSFlag.BASS_STREAM_PRESCAN | BASSFlag.BASS_STREAM_DECODE);
            _pReverse = BassFx.BASS_FX_ReverseCreate(_pStream, 30f, BASSFlag.BASS_STREAM_DECODE);
            bool b = Bass.BASS_ChannelSetAttribute(_pReverse, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, 1);

            // Set a sync for end so we pause it, and we will unpause when changing direction (just like a real CDJ), otherwise it won't do it
            IntPtr user = new IntPtr(_pReverse);
            int sync = Bass.BASS_ChannelSetSync(_pReverse, BASSSync.BASS_SYNC_MIXTIME | BASSSync.BASS_SYNC_END, 0, _procSync, user);

            // Start Asio
            bool bassAsioChannelEnable = BassAsio.BASS_ASIO_ChannelEnable(false, 0, _procAsio, user);
            bool bassAsioChannelJoin = BassAsio.BASS_ASIO_ChannelJoin(false, 1, 0);
            bool bassAsioStart = BassAsio.BASS_ASIO_Start(0);
        }

        void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Closing -= WindowClosing;
            bool bassAsioStop = BassAsio.BASS_ASIO_Stop();
            bool bassAsioFree = BassAsio.BASS_ASIO_Free();
        }

        private void ButtonDirectionClick(object sender, RoutedEventArgs e)
        {
            if (sender == null) return;
            Button button = sender as Button;
            if (button == null) return;
            
            // Reverse direction
            float currentDirection = 0;
            bool getAttribute = Bass.BASS_ChannelGetAttribute(_pReverse, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, ref currentDirection);
            
            float newDirection = currentDirection >= 0 ? -1 : 1;
            bool setAttribute = Bass.BASS_ChannelSetAttribute(_pReverse, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, newDirection);
            
            bool reset = BassAsio.BASS_ASIO_ChannelReset(false, 0, BASSASIOReset.BASS_ASIO_RESET_PAUSE);

            button.Content = newDirection;
        }

        private void SliderTempoValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            bool setRate = BassAsio.BASS_ASIO_ChannelSetRate(false, 0, e.NewValue);
        }

        private void SyncProc(int handle, int channel, int data, IntPtr user)
        {
            if (channel == _pReverse)
            {
                // We pause it which we'll allow us to unpause it later
                bool bassAsioChannelPause = BassAsio.BASS_ASIO_ChannelPause(false, 0);
            }
        }

        private int AsioProc(bool input, int channel, IntPtr buffer, int length, IntPtr user)
        {
            return Bass.BASS_ChannelGetData(user.ToInt32(), buffer, length);
        }
    }
}

« Last Edit: 24 Sep '10 - 18:07 by aybe »

Ian @ un4seen

  • Administrator
  • Posts: 20427
Re: Latency issue with a Reverse Stream
« Reply #1 on: 27 Sep '10 - 15:54 »
When using tempo and reverse together, the reverse processing should come before the tempo processing, ie. source -> reverse -> tempo; the other way round will introduce glitches in the sound due to the tempo processing being reset for each reversed block of data. But that does mean that direction changes will be delayed by the tempo/ouput buffer. To avoid that, you can clear the output buffer by seeking on the tempo stream, something like this...

Code: [Select]
BASS_ChannelPause(tempo_stream); // pause output
QWORD pos=BASS_ChannelGetPosition(tempo_stream, BASS_POS_BYTE); // get current position
BASS_ChannelSetAttribute(reverse_stream, BASS_ATTRIB_REVERSE_DIR, direction); // set direction
BASS_ChannelSetPosition(tempo_stream, pos, BASS_POS_BYTE); // re-set output position (to clear output buffer)
BASS_ChannelPlay(tempo_stream, 0); // resume output

aybe

  • Posts: 145
Re: Latency issue with a Reverse Stream
« Reply #2 on: 27 Sep '10 - 18:09 »
Thanks, it's perfect !  :D