Main Objective
Filtering enables the DJ to control different frequency components of their songs as it plays. This allows them to mix audio between different songs for seamless transitions and more comfortable listening experiences. The frequency EQ is undoubtedly the most important feature of any DJ controller. The filters are integrated with our real-time audio playback code so the DJ can control the EQ in real time as their song plays. We implemented ours using Butterworth high pass and low pass filters, as well as Biquad parametric EQ filters. We calculated the filter coefficients using their respective formulas, and then convolved all of them together so we only need to call the filter applying function once to save processing time.
This code illustrates our filter coefficient calulations. We used different parametric eq filters for positive and negative gain for more balanced mixing. Negative gain will apply its effect through a more selective band of frequencies so the user can remove them more specifically through the mix. This is especially useful when beatmatching songs because overlapping frequency ranges between songs sounds worse so it's important to be able to easily and specifically remove them from the mix.
To enable real time filtering we have to split the audio up into thousands of tiny chunks, apply our filters to the audio, and then seamlessly play the audio chunks back in real time. To do this we utilized a number of different Python libraries such as Sound Device's audio streams with a custom audio callback function. We also utilized multithreading so we can continuously call the audio playback while simultaneously applying the effects and reading in data from our custom UI.
To try our live filtering demo, download the livefiltering.py file from our Github repository, and run the command
python .\livefiltering.py song.wav
with your song in place of song.wav. Make sure you have Scipy, Tkinter, Numpy, Wave, and Sounddevice installed.
Challenges
To achieve synchronous effect application, audio playback, and UI performance, multithreading had to be utilized. This was a huge hurdle for us because none of us were familiar with how to overcome this kind of problem before. It made solving some issues extremely difficult to near impossible. For example implementing a pause button, while on the surface sounds like a relatively straight forward task becomes highly challenging when thread synchronization issues are considered. Furthermore, achieving seamless audio playback to begin with was also very difficult. The audio callback function needs to be continuously called at the right times to ensure no gaps in playback, and while that's happening audio chunks need to be continuously fed into the effects function. Additionally, the effect processing needs to happen fast enough so that the buffer is never emptied. This need prompted me to convolve all the filter coefficients together so the filtering function only needed to be called once instead of several times. This change works because the filters are all LTI systems so cascading them into one doesn't change the output. We were able to overcome these challenges however and produce a working real-time adjustable filter for use in a real DJ controller. A video demonstration of our controller can be found in the next page.