DIYing a DSP Processor engine. (Solution) - Page 3 - AVS Forum | Home Theater Discussions And Reviews
Forum Jump: 
 42Likes
Reply
 
Thread Tools
post #61 of 272 Old 11-05-2016, 09:38 AM
AVS Forum Special Member
 
LastButNotLeast's Avatar
 
Join Date: Feb 2007
Location: 08077
Posts: 8,296
Mentioned: 35 Post(s)
Tagged: 1 Thread(s)
Quoted: 1777 Post(s)
Liked: 1396
Work with high-def audio movies, I hope?
Michael

Did you really need to quote that entire post in your reply?
Downloadable FREE demo discs: Demonstration Blu-Ray Discs (Independently Authored)
Welcome to AVS - Get out while you still can!
Don't guess, measure: Getting Started With REW: A Step-by-Step Guide
LastButNotLeast is offline  
Sponsored Links
Advertisement
 
post #62 of 272 Old 11-05-2016, 09:57 AM
AVS Forum Special Member
 
3ll3d00d's Avatar
 
Join Date: Sep 2007
Location: London, UK
Posts: 2,891
Mentioned: 100 Post(s)
Tagged: 0 Thread(s)
Quoted: 1650 Post(s)
Liked: 621
Quote:
Originally Posted by dscoker View Post
So can anyone tell me what this diy custom dsp application is going to get you over and above what is already possible with JRiver? I have always been under the impression that JRiver has the most extensive dsp possibilities of any product on the market. I cant imagine that you would need anything more extensive. What can this diy dsp application do that JRiver can not?
Jriver has a very rich feature set in its dsp engine, well beyond running a few biquad filters. However it is not well suited (imo anyway) to use as a pure real time audio processor for two main reasons, one is it tends to need big buffers to avoid glitches (not a problem when jriver does playback because it delays the video) and this seems particularly true of its asio line in method. This is basically a deal breaker.

The other is it is not really designed to run headless and you need to use windows to use asio line in at all.

If you packaged the jriver DSP engine alone so you could run it as a service/daemon and then tuned it up to run faster then that would be a nice piece of software ideally suited to this job.
dwaleke likes this.
3ll3d00d is online now  
post #63 of 272 Old 11-05-2016, 12:25 PM
AVS Forum Special Member
 
Augerhandle's Avatar
 
Join Date: Jan 2009
Location: About 25" away from my computer screen
Posts: 4,485
Mentioned: 17 Post(s)
Tagged: 0 Thread(s)
Quoted: 857 Post(s)
Liked: 775
Quote:
Originally Posted by dscoker View Post
So can anyone tell me what this diy custom dsp application is going to get you over and above what is already possible with JRiver? I have always been under the impression that JRiver has the most extensive dsp possibilities of any product on the market. I cant imagine that you would need anything more extensive. What can this diy dsp application do that JRiver can not?

From the first sentence of the Original Post:


Quote:
Originally Posted by BassThatHz View Post
So, after using JRiver for a while I was getting pretty fed up with it's jittery real-time DSP in ASIO mode (from pre-testing my Motu project initiative).

So...

"The wise understand by themselves; fools follow the reports of others"-Tibetan Proverb
_____________________ http://www.scientificamerican.com/article/auger-handle/ ____________________________
Augerhandle is offline  
 
post #64 of 272 Old 11-05-2016, 01:38 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Quote:
Originally Posted by 3ll3d00d View Post
Jriver has a very rich feature set in its dsp engine, well beyond running a few biquad filters. However it is not well suited (imo anyway) to use as a pure real time audio processor for two main reasons, one is it tends to need big buffers to avoid glitches (not a problem when jriver does playback because it delays the video) and this seems particularly true of its asio line in method. This is basically a deal breaker.

The other is it is not really designed to run headless and you need to use windows to use asio line in at all.

If you packaged the jriver DSP engine alone so you could run it as a service/daemon and then tuned it up to run faster then that would be a nice piece of software ideally suited to this job.
^^^this, and... its inability to process more than 32 channels.

I'm hoping that mine will only be limited to how many channels NAudio or ASIO can hook onto, which "seems to be" at least 256 channels in and out...

Mine will run as fast as C# can run, basically I read the ASIO Input, do the DSP, and send it to the ASIO output. (I don't think I'll even support re-sampling, not for version 1 at least...)

Mine supports the-same and/or different ASIO drivers. JRiver has a hard time with that I've noticed!
Mine doesn't require multi-client drivers either, so long as the cards are ran in exclusive-mode.
(as it SHOULD be!)

Other programs are doing a lot more than that under the hood... (Like zoning, playlists, 3D particle fountains, VST API's, and a bunch of other junk that "slows things down".)

Plus it requires an installation, where as mine runs natively on Windows 8 or higher (or Win 7/XP with .Net 4.0 installed) with a <1MB file, and with minimal ram and CPU usage (as minimal as .Net allows at least...)

and once I get to version 1.0 I'll provide the source code, making it Open-Source.
That way you can modify it however you see fit...

Last edited by BassThatHz; 11-05-2016 at 01:44 PM.
BassThatHz is offline  
post #65 of 272 Old 11-05-2016, 01:55 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I haven't done any work on this project for a few days now, I've been getting home from work, stuffing a meal in my face, and going directly to bed, only to wake up and do it again.

I literally just woke up.

The release notes for Version 0.3 will be this:
A brand-new ground up DSP engine that I code, as <0.3 was still based on the original NAudio sample apps.
This is because they are "getting in my way", preventing me from doing things like multi-threading, or even a clean way to attach filters across channels for that matter...
0.3 will be multi-threaded. 1 thread per output channel.
If you buy an Intel i9 with 256 cores in it, it will run 256 threads *if you process 256 channels worth of audio...
So it should scale "fairly well" into the foreseeable future.
BassThatHz is offline  
post #66 of 272 Old 11-05-2016, 02:19 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I'm starting with nothing...

Just Namespaces and Object references.

I always implement IDisposable so that the GC knows how to get rid of my object.
It also allows you to use Using's if you want.
In any case, if people don't call close(), GC will do it for them, it's like idiot-proofing.

Internally I use 32-bit floats for the DSP.
There is no advantage to using 64bit, it just uses more CPU power for no reason, 32Bit is already beyond audibility. You ain't gonna hear 1db of noise when your playing at 192db! An SNR of -192db is fairly good me thinks!

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BassThatHz_ASIO_DSP_Processor.DSP
{
    using NAudio.Wave;
    using NAudio.Wave.Asio;
    using NAudio.Dsp;

    public class DSP_Engine : IDisposable
    {
        private AudioFileReader FileReader;
        private AsioOut ASIO_Out;
        private AsioOut ASIO_In;
        private float[] ReadBuffer;
        private float[] WriteBuffer;

        public DSP_Engine()
        {
            ///todo: code this
        }

        public void Dispose()
        {
            CleanUp();
        }

        private void CleanUp()
        {
            if (ASIO_Out != null)
            {
                ASIO_Out.Dispose();
            }

            if (ASIO_In != null)
            {
                ASIO_In.Dispose();
            }

            if (FileReader != null)
            {
                FileReader.Dispose();
            }
        }
    }
}

Last edited by BassThatHz; 11-05-2016 at 03:12 PM.
BassThatHz is offline  
post #67 of 272 Old 11-05-2016, 03:15 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I've added regions to the GUI. Just helps keep the code organized.
It was getting a bit crazy in there... there are a lot of controls doing various things, all at the same time!

(You really don't want to see the fully-expanded version!!! )

Eventually the DSP controls will be in a custom control instead of living on the main-form like it does now.
Attached Thumbnails
Click image for larger version

Name:	654.png
Views:	263
Size:	24.2 KB
ID:	1755737  
BassThatHz is offline  
post #68 of 272 Old 11-05-2016, 03:38 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I think this is all the code that is needed to apply a BiQuad from a read to a write buffer.

The class is non-static because it is designed to be re-used each time a buffer request is issued.
No sense re-allocating initialized-variables more than once!

The for-loop, I've done something unconventional there. To increase speed of course! (I believe that is still VALID syntax).
I probably could have just used a do-loop, but whatever...

"ref" is C# way of saying * / & in C++, or pointer.

As you can see, I'm putting the read-buffer directly into the write-buffer via the NAudio DSP transform function.
If ASIO rejects that linkage, then I'll type-cast it to a new float like this: (float)inputBiQuad
Which will allocate a new memory address for each float in the read buffer.

Right now it is a single Filter, it will eventually be an array of Filters, and a 2nd inner for-loop to process them.

Code:
namespace BassThatHz_ASIO_DSP_Processor.DSP
{
    using NAudio.Dsp;
    public class BiQuadProcessor
    {
        private int ReadCounter = 0;
        private int ChannelOffsetCounter = 0;
        public void RunBiQuad(ref float[] readBuffer, ref float[] writeBuffer, int readBufferSize, int readBuffer_ChannelOffset, BiQuadFilter inputBiQuad)
        {
            for (; ReadCounter < readBufferSize; ReadCounter++)
            {
                ChannelOffsetCounter += readBuffer_ChannelOffset;
                writeBuffer[ChannelOffsetCounter] = inputBiQuad.Transform(readBuffer[ChannelOffsetCounter]);
            }
            ReadCounter = 0;
            ChannelOffsetCounter = 0;
        }
    }
}
FYI: In C# int is 32bit (at least for x86). So each buffer can be up to 2GB in size, and 2 billion filters can be attached before an out-of-index error occurs!
The default buffer size is usually just 0.5kb to 16kb so it is total overkill. (No kill like overkill! )

Last edited by BassThatHz; 11-05-2016 at 03:46 PM.
BassThatHz is offline  
post #69 of 272 Old 11-05-2016, 04:25 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Here is the new multi-threaded output channel processor:
Pretty simple, not a whole lot going on this this class. By design

I could have used a parameterized thread starter, but I'm aiming for speed here and thus I don't want to put trust in "more Microsoft code" than is absolutely "necessary". (Because if you want something done right you gotta D.I.Y )

If you don't want multi-threading you just call RunningThread() directly, instead of using Start() Join()
(that said, you should probably just be calling BiQuadProcessor directly in that case! )

Code:
namespace BassThatHz_ASIO_DSP_Processor.DSP
{
    using NAudio.Dsp;
    using System.Threading;
    public class Threaded_OutputChannelProcessor : IDisposable
    {
        private BiQuadProcessor processor;
        private Thread thread;
        private float[] ReadBuffer;
        private float[] WriteBuffer;
        private int ReadBufferSize;
        private int ReadBuffer_ChannelOffset;
        private BiQuadFilter[] BiQuads;
        private int InVolume;
        private int OutVolume;

        public Threaded_OutputChannelProcessor(ref BiQuadFilter[] inputBiQuads, ref float[] readBuffer, ref float[] writeBuffer, int readBufferSize, int readBuffer_ChannelOffset, int inVolume, int outVolume)
        {
            Initialize(ref inputBiQuads, ref readBuffer, ref writeBuffer, readBufferSize, readBuffer_ChannelOffset, inVolume, outVolume);
        }

        private void Initialize(ref BiQuadFilter[] inputBiQuads, ref float[] readBuffer, ref float[] writeBuffer, int readBufferSize, int readBuffer_ChannelOffset, int inVolume, int outVolume)
        {
            BiQuads = inputBiQuads;
            ReadBuffer = readBuffer;
            WriteBuffer = writeBuffer;
            ReadBufferSize = readBufferSize;
            ReadBuffer_ChannelOffset = readBuffer_ChannelOffset;
            InVolume = inVolume;
            OutVolume = outVolume;

            processor = new BiQuadProcessor();
            thread = new Thread(RunningThread);
        }

        public void Start()
        {
            thread.Start();
        }

        public void RunningThread()
        {
            processor.RunBiQuad(ref ReadBuffer, ref WriteBuffer, ReadBufferSize, ReadBuffer_ChannelOffset, BiQuads, InVolume, OutVolume);
        }

        public float[] Join()
        {
            thread.Join();
            return WriteBuffer;
        }

        public void Dispose()
        {
            if (processor != null)
            {
                processor.Dispose();   
            }
            if (thread != null)
            {
                thread.Abort();
                thread = null;
            }
        }
    }
}
Code:
namespace BassThatHz_ASIO_DSP_Processor.DSP
{
    using NAudio.Dsp;
    public class BiQuadProcessor : IDisposable
    {
        private int ReadCounter = 0;
        private int ChannelOffsetCounter = 0;
        public void RunBiQuad(ref float[] readBuffer, ref float[] writeBuffer, int readBufferSize, int readBuffer_ChannelOffset, BiQuadFilter[] inputBiQuads, int InputMasterVolume, int OutputMasterVolume)
        {
            //We are zero-based array, but 0 += 0 is still 0!
            if (readBuffer_ChannelOffset == 0)
                ChannelOffsetCounter = 1;

            for (; ReadCounter < readBufferSize; ReadCounter++)
            {
                ChannelOffsetCounter += readBuffer_ChannelOffset;
                foreach (var BiQuad in inputBiQuads)
                {
                    writeBuffer[ChannelOffsetCounter] *= BiQuad.Transform(readBuffer[ChannelOffsetCounter] * InputMasterVolume);
                }
                writeBuffer[ChannelOffsetCounter] *= OutputMasterVolume;
            }
            ReadCounter = 0;
            ChannelOffsetCounter = 0;
        }

        public void Dispose()
        {
        }
    }
}
My philosophy is that: If code looks really complicated, then it is probably coded wrong. As-in, it should have been made into several classes and/or separate functions. Single responsibility people!

I have also seen people go the opposite direction too, make things so damn OOP'ed and interface-indirection-soup that nobody could understand what objects were "actually" being instantiated, which is bad if it's a quick one-off app that will NEVER see future expansion. It is not an API or framework people! It doesn't need flexible/reusable extension capability!
Besides, you can always branch it and refactor it if you can't inherit-extend or wrap the objects into something more powerful.

Don't write code in ways that will allow itself to break.
Don't handle exceptions that can't or shouldn't be handled.
Don't optimize until it needs optimization.
Don't simplify until it needs simplification.
Don't refactor until it needs refactoring.
Don't break existing class and interface, interfaces.
Don't fix something that isn't yet broken! (unless you know it will, and then see # 1 )

Noobs can really create some god awful code. Especially code that steps on its own feet.

[rant off]

Last edited by BassThatHz; 11-05-2016 at 07:17 PM.
BassThatHz is offline  
post #70 of 272 Old 11-05-2016, 05:38 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I think I have the public functions of the DSP_Engine mostly defined.
The parameters will change, and so will the code inside them, but I think that should do it. (Unless I'm forgetting something.)

Code:
namespace BassThatHz_ASIO_DSP_Processor.DSP
{
    using NAudio.Wave;
    using NAudio.Wave.Asio;
    using NAudio.Dsp;

    public class DSP_Engine : IDisposable
    {
        #region Private variables
        private AudioFileReader FileReader;
        private AsioOut ASIO_Out;
        private AsioOut ASIO_In;
        private float[] ReadBuffer;
        private float[] WriteBuffer;
        private BiQuadFilter[] BiQuads;
        private Threaded_OutputChannelProcessor[] OutputChannelProcessors;

        private int InChannelCount;
        private float InVolume;
        private int InSampleRate;
        private int InBitDepth;
        private int OutChannelCount;
        private float OutVolume;
        private int OutSampleRate;
        private int OutBitDepth;   
        #endregion

        public DSP_Engine() { }

        #region Volume Control
        public void ChangeInputMasterVolume(float newValue) { }
        public void ChangeOutputMasterVolume(float newValue) { }
        #endregion

        #region ASIOShowControlPanel
        public void ASIOShowControlPanel(string ASIODeviceName) { }
        #endregion

        #region Get ASIO Device Info
        public string[] GetDriverNames() { return new string[]; }
        public int GetInputChannelCount(string ASIODeviceName) { return -1; }
        public int GetOutputChannelCount(string ASIODeviceName) { return -1; }
        public float GetPlaybackLatency(string ASIODeviceName) { return 0F; }
        public int GetSampleRate(string ASIODeviceName) { return -1; }
        public int GetBitDepth(string ASIODeviceName) { return -1; }
        #endregion

        #region FileStream
        public void FileStream_Play() { }
        public void FileStream_Pause() { }
        public void FileStream_Stop() { }
        #endregion

        #region ProcessIOInRealTime
        public void ProcessIOInRealTime() { }
        #endregion

        #region ChangeDSPOptions
        public void ChangeEQFilters() { }
        #endregion

        #region Debugging/GetBuffers
        public float[] GetReadBuffer() { return ReadBuffer; }
        public float[] GetWriteBuffer() { return WriteBuffer; }
        #endregion

        ~#region Dispose
    }
}
Ok, now it is time to actually code it!

Last edited by BassThatHz; 11-05-2016 at 06:45 PM.
BassThatHz is offline  
post #71 of 272 Old 11-05-2016, 06:36 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
The Show ASIO Control panel and Routing the master volume values to the engine is done.
Pretty simple. As it should be!

Engine code:
Code:
       #region Volume Control
        public void ChangeInputMasterVolume(float newValue) 
        { 
            InVolume = newValue;
            UpdateDSPParameters();
        }

        public void ChangeOutputMasterVolume(float newValue) 
        {
            OutVolume = newValue;
            UpdateDSPParameters();
        }
        #endregion

        #region ASIOShowControlPanel
        public void ASIOShowControlPanel(string asioDeviceName) 
        {
            using (var tempASIO = new AsioOut(asioDeviceName))
            {
                tempASIO.ShowControlPanel();
            }
        }
        #endregion
GUI code:
Code:
        #region Volume Control
        private void volInputMaster_VolumeChanged(object sender, EventArgs e)
        {
            dsp.ChangeInputMasterVolume(volInputMaster.Volume);
        }

        private void volOutputMaster_VolumeChanged(object sender, EventArgs e)
        {
            dsp.ChangeOutputMasterVolume(volOutputMaster.Volume);
        }
        #endregion

       #region Show ASIO Control Panels
        private void btnASIOInputControlPanel_Click(object sender, EventArgs e)
        {
           ASIOShowControlPanel(cboASIOInputDevice.Text);
        }

        private void btnASIOOutputControlPanel_Click(object sender, EventArgs e)
        {
            ASIOShowControlPanel(cboASIOOutputDevice.Text);
        }

        private void ASIOShowControlPanel(string asioDeviceName)
        {
            try
            {
                dsp.ASIOShowControlPanel(asioDeviceName);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion

Similar for the ASIO device enumeration, ask NAudio for it:
Code:
        public string[] GetDriverNames()
        {
            return AsioOut.GetDriverNames();
        }
Code:
 private void InitAndDisplayASIODeviceNames()
        {
            foreach (var device in dsp.GetDriverNames())
            {
                this.cboASIOInputDevice.Items.Add(device);
                this.cboASIOOutputDevice.Items.Add(device);
            }

            if (this.cboASIOInputDevice.Items.Count > 0)
            {
                this.cboASIOInputDevice.SelectedIndex = 0;
            }

            if (this.cboASIOOutputDevice.Items.Count > 0)
            {
                this.cboASIOOutputDevice.SelectedIndex = 0;
            }
       }

Last edited by BassThatHz; 11-05-2016 at 06:51 PM.
BassThatHz is offline  
post #72 of 272 Old 11-05-2016, 07:03 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Everytime the ASIO Device is changed by the user, update various values.
(I need to finish this*, but this will do for now!)

*Sample Rate, Bit Depth, Min/Max Latency

GUI Code:
Code:
#region ASIO Device Change
        private void cboASIOInputDevice_SelectedIndexChanged(object sender, EventArgs e)
        {
            try
            {
                DisplayInputBufferSize();
                gboInputSends.Text = "Input sends: (Channel Count: " + dsp.GetInputChannelCount(cboASIOInputDevice.Text) + " @ 44.1kHz)";
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void cboASIOOutputDevice_SelectedIndexChanged(object sender, EventArgs e)
        {
            try
            {
                DisplayOutputBufferSize();
                gboOutputReceives.Text = "Output Receives: (Channel Count: " + dsp.GetOutputChannelCount(cboASIOOutputDevice.Text) + " @ 44.1kHz)";
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void DisplayOutputBufferSize()
        {
            var Latency = dsp.GetPlaybackLatency(cboASIOOutputDevice.Text);
            cboOutputBufferSize.Items.Clear();
            cboOutputBufferSize.Items.Add("Hardware Min (x ms)");
            cboOutputBufferSize.Items.Add("Hardware Recommended (x ms)".Replace("x", Latency.ToString()));
            cboOutputBufferSize.Items.Add("Hardware Max (x ms)");
            cboOutputBufferSize.SelectedItem = cboOutputBufferSize.Items[1];
        }

        private void DisplayInputBufferSize()
        {
            var Latency = dsp.GetPlaybackLatency(cboASIOInputDevice.Text);
            cboInputBufferSize.Items.Clear();
            cboInputBufferSize.Items.Add("Hardware Min (x ms)");
            cboInputBufferSize.Items.Add("Hardware Recommended (x ms)".Replace("x", Latency.ToString()));
            cboInputBufferSize.Items.Add("Hardware Max (x ms)");
            cboInputBufferSize.SelectedItem = cboInputBufferSize.Items[1];
        }
        #endregion
Engine Code:
Again ask NAudio for the values.

Grabs the active device's values, or the first device in the enumeration if null.
Code:
 public int GetPlaybackLatency(string asioDeviceName)
        {
            using (var tempASIO = new AsioOut(asioDeviceName))
            {
                return tempASIO.PlaybackLatency;
            }
        }

        public int GetInputChannelCount(string asioDeviceName)
        {
            int ReturnValue = 0;
            if (ASIO_In != null)
            {
                ReturnValue = ASIO_In.DriverInputChannelCount;
            }
            else
            {
                using (var tempASIO = new AsioOut(asioDeviceName))
                {
                    ReturnValue = tempASIO.DriverInputChannelCount;
                }
            }

            return ReturnValue;
        }

        public int GetOutputChannelCount(string asioDeviceName)
        {
            int ReturnValue = 0;
            if (ASIO_Out != null)
            {
                ReturnValue = ASIO_Out.DriverOutputChannelCount;
            }
            else
            {
                using (var tempASIO = new AsioOut(asioDeviceName))
                {
                    ReturnValue = tempASIO.DriverOutputChannelCount;
                }
            }

            return ReturnValue;
        }
BassThatHz is offline  
post #73 of 272 Old 11-05-2016, 08:13 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Ok, this is where things start to get more complicated. To play the File, you have to create an NAudio File Reader,
then initialize the NAudio ASIO output device.

You also have to initialize the buffer callback-handler, and in my case the multi-threaded DSP channel processor as well! Both of which I have left out of the snippet below for simplicity sake.

Not sure if this code works... so now I'm off to try it / debug it!!!

I'll talk about the callback handler later. I don't want to overwhelm you in a single huge post, but this is the "simplified" version:
Code:
 public void FileStream_Play(string filePath, string output_ASIODeviceName, ref BiQuadFilter[] biQuads, float inVolume, float outVolume) 
        {
            OutVolume = outVolume;
            InVolume = inVolume;
            BiQuads = biQuads;
            FileReader = new AudioFileReader(filePath);
            if (FileReader != null)
            {
                FileReader.Position = 0;

                var WaveFormat = ((ISampleProvider)FileReader).WaveFormat;
                ReadBuffer = new float[WaveFormat.BlockAlign];
                WriteBuffer = new float[WaveFormat.BlockAlign];

                var ChannelCountIndex = WaveFormat.Channels - 1;
                OutputChannelProcessors = new Threaded_OutputChannelProcessor[ChannelCountIndex];
                for (var i = 0; i < ChannelCountIndex; i++)
                {
                    OutputChannelProcessors[i] = new Threaded_OutputChannelProcessor(ref BiQuads, ref ReadBuffer, ref WriteBuffer, WaveFormat.BlockAlign, i - 1, InVolume, OutVolume); 
                }

                ConfigASIO(String.Empty, output_ASIODeviceName);

                BufferFiller = new BufferCallbackHandler(FileReader);
                ASIO_Out.Init(BufferFiller);
                ASIO_Out.Play();
            }
        }

        private void ConfigASIO(string input_ASIODeviceName, string output_ASIODeviceName)
        {
            if (ASIO_Out != null && ASIO_Out.DriverName != output_ASIODeviceName)
            {
                ASIO_Out.PlaybackStopped -= AsioOut_PlaybackStopped;
                ASIO_Out.Dispose();
                ASIO_Out = null;
            }

            if (ASIO_In != null && ASIO_In.DriverName != input_ASIODeviceName)
            {
                ASIO_In.AudioAvailable -= AsioIn_AudioAvailable;
                ASIO_In.Dispose();
                ASIO_In = null;
            }

            // create device if necessary
            if (ASIO_Out == null && output_ASIODeviceName != String.Empty)
            {
                ASIO_Out = new AsioOut(output_ASIODeviceName);
                ASIO_Out.PlaybackStopped += AsioOut_PlaybackStopped;
                ASIO_Out.ChannelOffset = 0;
            }

            if (ASIO_In == null && input_ASIODeviceName != String.Empty)
            {
                ASIO_In = new AsioOut(input_ASIODeviceName);
                ASIO_In.AudioAvailable += AsioIn_AudioAvailable;
                ASIO_In.ChannelOffset = 0;
            }
        }
Right now I have the channel offsets hardcoded to 0. I will eventually allow you to customize that, in fact it will be seamless, as the channel-mapper in the GUI is exactly doing that! It's a 1-to-1 relationship ALWAYS! ... and therefore it should be set to 0 in the code, always.
In fact I think the default value is 0, but I set it anyways just to make sure.

Last edited by BassThatHz; 11-08-2016 at 12:41 AM.
BassThatHz is offline  
post #74 of 272 Old 11-05-2016, 09:00 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Here is the GUI code for that:
Code:
private void buttonPlay_Click(object sender, EventArgs e)
        {
            try
            {
                SetFilters();
                TogglePlayButtons();
               
                dsp.FileStream_Play(FilePath, cboASIOOutputDevice.Text, ref BiQuads, volInputMaster.Volume, volOutputMaster.Volume);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void SetFilters()
        {
            BiQuads = new BiQuadFilter[]
                {
                    BiQuadFilter.HighPassFilter(44100F, 2000F, 1F)
                };
        }
Right now I have the GUI setting the BiQuads, but I might change that (we'll see...)

The above code works, I'm just working on the callback handler.
Which might require some minor changes.

Last edited by BassThatHz; 11-05-2016 at 09:05 PM.
BassThatHz is offline  
post #75 of 272 Old 11-06-2016, 09:37 AM
Senior Member
 
Join Date: Nov 2014
Posts: 210
Mentioned: 5 Post(s)
Tagged: 0 Thread(s)
Quoted: 171 Post(s)
Liked: 85
That's some incredible work you are doing. I'd be very interested to see a compressor (especially a multi-band compressor) feature implemented. Are there existing code available for something like that?
bcodemz is offline  
post #76 of 272 Old 11-06-2016, 11:00 AM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
There is a bug that I'm trying to fix.
The "ref" variables aren't working as I had planned.
.Net removes the pointer and replaces it with a by-value copy upon function exit. Which really messes with my write buffers, especially in multi-threaded mode.

I'm thinking the most effective way is to use a callback, and have it pass it by-ref, and only invoke it within the context of the BiQuad processor. That way it should behave as expected.

I might also have to create a unit-test to confirm that I am filling the write buffer correctly as there seems to be something no quite right there, which might get resolved automatically once the above is corrected (not sure yet...)
BassThatHz is offline  
post #77 of 272 Old 11-06-2016, 11:09 AM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Quote:
Originally Posted by bcodemz View Post
That's some incredible work you are doing. I'd be very interested to see a compressor (especially a multi-band compressor) feature implemented. Are there existing code available for something like that?
That's basically DEQ, right?
You select the frequency, the q, the compression ratio, the threshold, and the attack/release times.

NAudio has a compressor, but it's full bandwidth though...
BassThatHz is offline  
post #78 of 272 Old 11-06-2016, 11:20 AM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I have reactivated the first 3 filters in the first bus in the GUI.
You just have to hit Apply Filter to change the values, which can be done safely while it is running.

Right now everything is still limited to 44.1khz
Code:
 
        private void SetFilters()
        {
            BiQuads = new BiQuadFilter[]
                {
                    BiQuadFilter.HighPassFilter(44100F,  float.Parse(txtHPF.Text), float.Parse(txtHPFQ.Text)),
                    BiQuadFilter.LowPassFilter(44100F,  float.Parse(txtLPF.Text), float.Parse(txtLPFQ.Text)),
                    BiQuadFilter.PeakingEQ(44100F,  float.Parse(txtF1.Text), float.Parse(txtQ1.Text), float.Parse(txtG1.Text)),
                };
         }
I have also added a new feature:

The ability to control at what CPU load the app enters multi-core mode.
The left value is the cpu usage of core 0 (the default core the app uses), and the right value is the total cpu usage (i.e. across all available cores).
Whichever is the lesser of the two defines the threshold, when the usage hits that level it then engages multi-core mode automatically.

In previous versions it was using total-cpu only, and you couldn't control the values.
Attached Thumbnails
Click image for larger version

Name:	5435.jpg
Views:	218
Size:	9.1 KB
ID:	1756833  
BassThatHz is offline  
post #79 of 272 Old 11-06-2016, 11:32 AM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
There is a bug somewhere in this code. I just don't know "what" yet...
Code:
for (var i = 0; i < ChannelCount; i++)
{
    OutputChannelProcessors[i] = new Threaded_OutputChannelProcessor(ref BiQuads, ref ReadBuffer, ref WriteBuffer, SampleSize / ChannelCount, i + 1, ChannelCount, InVolume, OutVolume);
}
Code:
 private int ReadCounter = 0;
        private int ChannelOffsetCounter = 0;
        public void RunBiQuad(ref float[] readBuffer, ref float[] writeBuffer, int readBufferSize, int readBuffer_ChannelOffset, int ReadBuffer_TotalNumberOfChannels, BiQuadFilter[] inputBiQuads, float InputMasterVolume, float OutputMasterVolume)
        {
            ChannelOffsetCounter = readBuffer_ChannelOffset;
            for (; ReadCounter < readBufferSize; ReadCounter++)
            {
                float BuffItem = readBuffer[ChannelOffsetCounter - 1] * InputMasterVolume;
                foreach(var BiQuad in inputBiQuads)
                {
                    writeBuffer[ChannelOffsetCounter - 1] = BiQuad.Transform(BuffItem);
                    BuffItem = writeBuffer[ChannelOffsetCounter - 1];
                }
                writeBuffer[ChannelOffsetCounter - 1] *= OutputMasterVolume;
                ChannelOffsetCounter += ReadBuffer_TotalNumberOfChannels;
            }
            ReadCounter = 0;
            ChannelOffsetCounter = 0;
        }

Last edited by BassThatHz; 11-06-2016 at 11:48 AM.
BassThatHz is offline  
post #80 of 272 Old 11-06-2016, 11:34 AM
AVS Forum Special Member
 
PretzelFisch's Avatar
 
Join Date: Feb 2011
Posts: 1,106
Mentioned: 6 Post(s)
Tagged: 0 Thread(s)
Quoted: 405 Post(s)
Liked: 264
Quote:
Originally Posted by BassThatHz View Post
There is a bug that I'm trying to fix.
The "ref" variables aren't working as I had planned.
.Net removes the pointer and replaces it with a by-value copy upon function exit. Which really messes with my write buffers, especially in multi-threaded mode.

I'm thinking the most effective way is to use a callback, and have it pass it by-ref, and only invoke it within the context of the BiQuad processor. That way it should behave as expected.

I might also have to create a unit-test to confirm that I am filling the write buffer correctly as there seems to be something no quite right there, which might get resolved automatically once the above is corrected (not sure yet...)
It's possible it's not a copy, and that you need to pin the memory if this code is dealing with unmanaged code. Otherwise the framework will move memory and referances.
PretzelFisch is offline  
post #81 of 272 Old 11-06-2016, 11:38 AM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Quote:
Originally Posted by PretzelFisch View Post
It's possible it's not a copy, and that you need to pin the memory if this code is dealing with unmanaged code. Otherwise the framework will move memory and referances.
It's definitely managed code, and it is definitely making a copy.
BassThatHz is offline  
post #82 of 272 Old 11-06-2016, 11:59 AM
AVS Forum Special Member
 
3ll3d00d's Avatar
 
Join Date: Sep 2007
Location: London, UK
Posts: 2,891
Mentioned: 100 Post(s)
Tagged: 0 Thread(s)
Quoted: 1650 Post(s)
Liked: 621
aren't c# parameters "pass object reference by value" by default? and primitive arrays are implemented as objects?

in which case why are you using ref anyway? I haven't read your code in any detail but it reads a bit like confusion between the array as a object (which you want to pass as a reference and not change) and the primitives that are assigned to slots within that array (which you do want to change)
3ll3d00d is online now  
post #83 of 272 Old 11-06-2016, 01:36 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I've added another new feature: DSP Latency

Handy to see how fast it is actually running.

This includes File read time (not sure if it also includes the actual ASIO components, they might be excluded... really not sure!)

It is still fast with 3 filters.

I haven't even really optimized for performance yet!



I've seen values as low as <1ms (.Net can't compute values below 1ms by default so it is reported as 0ms.)
Attached Thumbnails
Click image for larger version

Name:	765757.jpg
Views:	245
Size:	51.6 KB
ID:	1757121  
BassThatHz is offline  
post #84 of 272 Old 11-06-2016, 05:23 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Alright... I'm calling version 0.3 done.



This is using the new and improved DSP engine (faster running, and more flexible/cleaner code).

Real-time IO works (with and without DSP), but you still have to select a file first still (I haven't figured that part out yet.)
File Player also works (with and without DSP).

I'd recommend you close and reopen the app instead of trying to switch between the two modes with the buttons. Some times the ASIO stuff "complains".

Still limited to 2-ch, 44.1khz and still not multi-threaded internally. Threads not reporting on the GUI yet either.

With the new engine I'm still limited to 1 ASIO device for now (I just haven't had time to code it yet.)

DSP works (sort of) I still have the DSP glitch, as a result... I've added a DSP Bypass button (which also makes it faster, obviously!)

Compiled exe:
https://drive.google.com/open?id=0Bw...jFIZnJxVElMR28

Source Code:
https://drive.google.com/open?id=0Bw...lhQN3dnV1QzcTQ

Now that you have the source code... maybe you can help me fix the DSP issue?
I simply don't have time to work on it until next weekend, most likely...

There are still features I haven't implemented, like bus's, routing,
variable amounts of filters from the GUI (the engine already supports this one.)

I haven't cleaned up the code, it is still under active development, haven't added add code comments or unit-tests either...

I just wanted to get it in your hands (if you care... LOL! )
Attached Thumbnails
Click image for larger version

Name:	4325.jpg
Views:	237
Size:	272.3 KB
ID:	1757433  
BassThatHz is offline  
post #85 of 272 Old 11-06-2016, 05:27 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I removed the ref parameters and replaced it with a callback and structure. Makes for cleaner code (those parameters were giving me a headache... LOL)

Definitely cleaner code this way, and less code too...
Code:
 public class DSPInfo
    {
        public float[] ReadBuffer;
        public float[] WriteBuffer;
        public int ReadBufferSize;
        public BiQuadFilter[] BiQuads;
        public float InVolume;
        public float OutVolume;
        public int ReadBuffer_TotalNumberOfChannels;
        public int SampleRate;

        public int InChannelCount;
        public int InSampleRate;
        public int InBitDepth;
        public int OutChannelCount;
        public int OutSampleRate;
        public int OutBitDepth;  
    }
Code:
namespace BassThatHz_ASIO_DSP_Processor.DSP
{
    using NAudio.Dsp;
    using System.Threading;
    public class Threaded_OutputChannelProcessor : IDisposable
    {
        private Thread thread;

        private BiQuadProcessor processor;
        private BiQuadProcessor.GetDSPInfo GetInfoCallback;
        private int ReadBuffer_ChannelOffset;

        public Threaded_OutputChannelProcessor(int readBuffer_ChannelOffset)
        {
            ReadBuffer_ChannelOffset = readBuffer_ChannelOffset;
            processor = new BiQuadProcessor();
        }

        public void Start(BiQuadProcessor.GetDSPInfo getInfoCallback)
        {
            if (getInfoCallback == null) throw new ArgumentException("getInfoCallback cannot be null");

            GetInfoCallback = getInfoCallback;
            thread = new Thread(RunningThread);
            thread.Start();
        }

        public void RunningThread()
        {
            processor.RunBiQuad(GetInfoCallback, ReadBuffer_ChannelOffset);
        }

        public void RunningThread(BiQuadProcessor.GetDSPInfo getInfoCallback)
        {
            if (getInfoCallback == null) throw new ArgumentException("getInfoCallback cannot be null");
            processor.RunBiQuad(getInfoCallback, ReadBuffer_ChannelOffset);
        }


        public void Join()
        {
            thread.Join();
        }

        public void Dispose()
        {
            if (processor != null)
            {
                processor.Dispose();   
            }
            if (thread != null)
            {
                thread.Abort();
                thread = null;
            }
        }
    }
}
Code:
namespace BassThatHz_ASIO_DSP_Processor.DSP
{
    using NAudio.Dsp;
    public class BiQuadProcessor : IDisposable
    {
        public delegate void GetDSPInfo(ref DSPInfo dspInfo);

        private int ReadCounter = 0;
        private int ChannelOffsetCounter = 0;
        public void RunBiQuad(GetDSPInfo getDSPInfo, int ReadBuffer_ChannelOffset)
        {
            DSPInfo dspInfo = new DSPInfo();
            getDSPInfo(ref dspInfo);
            ChannelOffsetCounter = ReadBuffer_ChannelOffset;
            for (; ReadCounter < dspInfo.ReadBufferSize; ReadCounter++)
            {
                float BuffItem = dspInfo.ReadBuffer[ChannelOffsetCounter - 1] * dspInfo.InVolume;
                foreach (var BiQuad in dspInfo.BiQuads)
                {
                    dspInfo.WriteBuffer[ChannelOffsetCounter - 1] = BiQuad.Transform(BuffItem);
                    BuffItem = dspInfo.WriteBuffer[ChannelOffsetCounter - 1];
                }
                dspInfo.WriteBuffer[ChannelOffsetCounter - 1] *= dspInfo.OutVolume;
                ChannelOffsetCounter += dspInfo.ReadBuffer_TotalNumberOfChannels;
            }
            ReadCounter = 0;
            ChannelOffsetCounter = 0;
        }

        public void Dispose()
        {
        }
    }
}
BassThatHz is offline  
post #86 of 272 Old 11-06-2016, 05:38 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Now for the complicated part. Remember how I said I'd talk about it later, well... now is the time!

This is the main controller class.

This class is the one that "actually" provides the "running ASIO driver" with audio data, it handles the read/write buffers, reading of the audio file, this also controls the multi-threading, the DSP engagement, it communicates changes to the actively running DSP threads (filters, volume levels etc).

I spend most of my time in this class, and the BiQuadProcessor class. Lots of coding and debugging.

The time-critical function is the one named "public int Read(float[] buffer, int offset, int count)"
that's ground zero.

The level below that is the NAudio API, and below that level is the ASIO driver and the actual soundcard hardware itself, both running in kernel-mode.

Code:
namespace BassThatHz_ASIO_DSP_Processor.DSP
{
    using NAudio.Wave;
    using NAudio.Wave.Asio;
    using NAudio.Wave.SampleProviders;
    using NAudio.Dsp;

    public class BufferCallbackHandler : ISampleProvider, IWaveProvider, IDisposable
    {
        private ISampleProvider SourceProvider;
        private SampleToWaveProvider Converter;
        private Threaded_OutputChannelProcessor[] OutputChannelProcessors;
        private DSPInfo DSP_Info = new DSPInfo();
        private TimeSpan DSPLatency = new TimeSpan();

        private bool HasASIOInput = false;
        private bool IsRealTimeIOMode = false;
        private bool BypassDSP = false;

        private WaveFormat Wave_Format;

        private object LockObject = new object();

        public BufferCallbackHandler(ISampleProvider sourceProvider, ref DSPInfo inputDSPInfo, bool isRealTimeIOMode)
        {
            IsRealTimeIOMode = isRealTimeIOMode;
            SourceProvider = sourceProvider;
            ChangeDSPSettings(ref inputDSPInfo);
            SetChannelsAndSampleRate();
            Converter = new SampleToWaveProvider(this);
        }

        public void ChangeDSPSettings(ref DSPInfo inputDSPInfo)
        {
            lock (LockObject)
            {
                DSP_Info.BiQuads = inputDSPInfo.BiQuads;
                DSP_Info.InVolume = inputDSPInfo.InVolume;
                DSP_Info.OutVolume = inputDSPInfo.OutVolume;
            }
        }

        public TimeSpan GetDSPLatency()
        {
            return DSPLatency;
        }

        public void TurnOnDSP()
        {
            BypassDSP = false;
        }

        public void TurnOffDSP()
        {
            BypassDSP = true;
        }

        public bool IsDSPBypassed()
        {
            return BypassDSP;
        }

        private void SetChannelsAndSampleRate()
        {
            lock (LockObject)
            {
                DSP_Info.ReadBuffer_TotalNumberOfChannels = SourceProvider.WaveFormat.Channels;
                DSP_Info.SampleRate = SourceProvider.WaveFormat.SampleRate;
                Wave_Format = SourceProvider.WaveFormat;
            }
        }

        public void AsioIn_AudioAvailable(object sender, AsioAudioAvailableEventArgs e)
        {
            lock (LockObject)
            {
                HasASIOInput = true;
               
                float[] RealTimeReadBuffer = e.GetAsInterleavedSamples();
                int count = RealTimeReadBuffer.Count();
                if (DSP_Info.ReadBuffer == null)
                {
                    DSP_Info.ReadBuffer = new float[count];
                }

                ///todo: Should use pointer copy
                for (int i = 0; i < count; i++)
                {
                    DSP_Info.ReadBuffer[i] = (float)RealTimeReadBuffer[i];
                }
            }
        }

        private void Config_DSPProcessors(int SampleSize)
        {
            //Init the float Buffers IF necessary
            //if (ReadBuffer == null || (ReadBuffer != null && ReadBuffer.Count() != ReadBufferSize))
            //{
            //    ReadBuffer = new float[ReadBufferSize];
            //}
            //if (WriteBuffer == null || (WriteBuffer != null && WriteBuffer.Count() != ReadBufferSize))
            //{
            //    WriteBuffer = new float[ReadBufferSize];
            //}

            var ChannelCount = DSP_Info.ReadBuffer_TotalNumberOfChannels;

            if (OutputChannelProcessors == null)
            {
                OutputChannelProcessors = new Threaded_OutputChannelProcessor[ChannelCount];
                for (var i = 0; i < ChannelCount; i++)
                {
                    OutputChannelProcessors[i] = new Threaded_OutputChannelProcessor(i + 1);
                }

                lock (LockObject)
                {
                    DSP_Info.ReadBufferSize = SampleSize / ChannelCount;         
                }
            }
        }

        private void Run_DSPProcessors()
        {
            OutputChannelProcessors[0].RunningThread(GetDSPInfo);
            OutputChannelProcessors[1].RunningThread(GetDSPInfo);

            //foreach (var processor in OutputChannelProcessors)
            //{
            //    processor.Start(GetDSPInfo);
            //}

            //foreach (var processor in OutputChannelProcessors)
            //{
            //    processor.Join();
            //}
        }
        
        private void GetDSPInfo(ref DSPInfo dspInfo)
        {
            lock (LockObject)
            {
                dspInfo.BiQuads = DSP_Info.BiQuads;
                dspInfo.InVolume = DSP_Info.InVolume;
                dspInfo.OutVolume = DSP_Info.OutVolume;
                dspInfo.ReadBuffer = DSP_Info.ReadBuffer;
                dspInfo.WriteBuffer = DSP_Info.WriteBuffer;
                dspInfo.ReadBuffer_TotalNumberOfChannels = DSP_Info.ReadBuffer_TotalNumberOfChannels;
                dspInfo.ReadBufferSize = DSP_Info.ReadBufferSize;
            }
        }

        public int Read(byte[] buffer, int offset, int count)
        {
            return Converter.Read(buffer, offset, count);
        }

        public int Read(float[] buffer, int offset, int count)
        {
            long StartTime = DateTime.Now.Ticks;

            var BufferCount = buffer.Count();
            if (DSP_Info.ReadBuffer == null || (DSP_Info.ReadBuffer != null && DSP_Info.ReadBuffer.Count() != BufferCount))
            {
                DSP_Info.ReadBuffer = new float[BufferCount];
            }

            DSP_Info.WriteBuffer = buffer;
            if (!IsRealTimeIOMode)
            {
                SourceProvider.Read(DSP_Info.ReadBuffer, offset, count);
            }

            if (BypassDSP && DSP_Info.ReadBuffer != null)
            {
                for (int i = 0; i < count; i++)
                {
                    buffer[i] = DSP_Info.ReadBuffer[i];
                }
            }
            else if(HasASIOInput || !BypassDSP)
            {
                Config_DSPProcessors(count);
                Run_DSPProcessors();
            }

            DSPLatency = new TimeSpan(DateTime.Now.Ticks - StartTime);
            return count;
        }

        public WaveFormat WaveFormat
        {
            get
            {
                return Wave_Format;
            }
        }

        public void Dispose()
        {
            if (OutputChannelProcessors != null)
            {
                foreach (var processor in OutputChannelProcessors)
                {
                    processor.Dispose();
               }
                OutputChannelProcessors = null;
            }
            
        }
    }
}
BassThatHz is offline  
post #87 of 272 Old 11-06-2016, 07:33 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I've tracked down the glitch.

It is the NAudio BiQuad class, Transform function.

This works:
Code:
dspInfo.WriteBuffer[ChannelOffsetCounter - 1] = BuffItem;
But this doesn't:
Code:
dspInfo.WriteBuffer[ChannelOffsetCounter - 1] = BiQuad.Transform(BuffItem);
Why though?

The only thing I can think of is that it can't be ran in interleaved mode.
Something seems off here, doesn't seem logical to me.

I require that mode for easy multi-threading. grrr!
BassThatHz is offline  
post #88 of 272 Old 11-06-2016, 07:47 PM
Senior Member
 
Join Date: Nov 2014
Posts: 210
Mentioned: 5 Post(s)
Tagged: 0 Thread(s)
Quoted: 171 Post(s)
Liked: 85
Quote:
Originally Posted by BassThatHz View Post
That's basically DEQ, right?
You select the frequency, the q, the compression ratio, the threshold, and the attack/release times.

NAudio has a compressor, but it's full bandwidth though...
I've never heard of DEQ. It can't be dynamic EQ right? But yes it is where you choose compression ratio, threshold, and attack/release times.

One can make a multi-band compressor by splitting the audio band using low/high pass filters, like a crossover, apply a compressor to each band, then sum up all the bands into one. If a full band compressor code is there, turning it into a multi-band compressor should be relatively easy.
bcodemz is offline  
post #89 of 272 Old 11-06-2016, 08:17 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
I fixed the bug.

The BiQuad filters have to be an array-of-arrays, as each channel array element needs it's own array of BiQuads.
I was sharing them across the channels, but they have a temporary "persistent state", dedicated to whatever stream they are transforming. (i.e. they cannot be shared or reused.)

My bad...

I will overwrite version 0.3 on my google drive with these fixes (once I get the DSP Updater working with this new format, that is...)

Code:
private BiQuadFilter[][] BiQuadChannelFilters;
Code:
               BiQuadChannelFilters = new BiQuadFilter[ChannelCount][];
                for (var i = 0; i < ChannelCount; i++)
                {
                    OutputChannelProcessors[i] = new Threaded_OutputChannelProcessor(i + 1);
                    BiQuadChannelFilters[i] = new BiQuadFilter[DSP_Info.BiQuads.Count()];
                    for (var j = 0;  j < DSP_Info.BiQuads.Count(); j++)
                    {
                      BiQuadChannelFilters[i][j] = DSP_Info.BiQuads[j];
                    }
                }
Code:
 private void GetDSPInfo(ref DSPInfo dspInfo, int ReadBuffer_ChannelOffset)
{
     dspInfo.BiQuads = BiQuadChannelFilters[ReadBuffer_ChannelOffset-1];
}
BassThatHz is offline  
post #90 of 272 Old 11-06-2016, 08:53 PM - Thread Starter
AVS Forum Special Member
 
BassThatHz's Avatar
 
Join Date: Apr 2008
Location: Northern Okan range (NW Cascades region)
Posts: 7,873
Mentioned: 99 Post(s)
Tagged: 0 Thread(s)
Quoted: 2317 Post(s)
Liked: 2065
Quote:
Originally Posted by bcodemz View Post
I've never heard of DEQ. It can't be dynamic EQ right? But yes it is where you choose compression ratio, threshold, and attack/release times.

One can make a multi-band compressor by splitting the audio band using low/high pass filters, like a crossover, apply a compressor to each band, then sum up all the bands into one. If a full band compressor code is there, turning it into a multi-band compressor should be relatively easy.
Yeah dynamic EQ.

The only difference is that it uses a notch instead of a bandpass.

If I ever enable bus to bus routing, you can define whatever combo of DSP effects that you desire.
BassThatHz is offline  
Sponsored Links
Advertisement
 
Reply DIY Speakers and Subs

Thread Tools
Show Printable Version Show Printable Version
Email this Page Email this Page


Forum Jump: 

Posting Rules  
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are Off
Pingbacks are Off
Refbacks are Off