I am trying to build an oscilloscope that responds for the current song that is playing in the mp3 player that i have built.
It's too difficult for me to build an oscilloscope from the scratch because I'm a beginner but I found that dRowAudio has already built one before.
The problem is that I don't know if this is possible. I wrote the codes of dRowAudio oscilloscope in my mp3 player but didn't know how to connect the oscilloscope with the player.
/** Processes a number of samples displaying them on the scope.
The resolution will depend on how zoomed in you are @see setHorizontalZoom.
*/
void processBlock (const float* inputChannelData,
int numSamples);
So perhaps try to call AudioOscilloscope::processBlock() from your app's audio callback, and pass in the pointer to the audio channel you want to display!
I tried like this : deviceManager.addAudioCallback(audioOscilloscope.processBlock(?????));
That won't work because the oscilloscope is not inheriting from AudioIODeviceCallback. Also, keep in mind that the audiocallback itself will have multiple audio channels, but that Oscilloscope class seems to be able to handle only one.
There are several possibilities to do this, one would be for example:
Take whatever class owns your deviceManager, and make it inherit from AudioIODeviceCallback.
In the constructor of that class, call deviceManager.addAudioCallback (this);
In the destructor, call deviceManager.removeAudioCallback (this);
Implement the audioDeviceIOCallback method of that class. In there, choose which channel you want to display on the oscilloscope, and then explicitly call audioOscilloscope.processBlock() and pass a pointer to that channel.
Add this as well to the class declaration, otherwise it won't compile: void audioDeviceAboutToStart () override {} void audioDeviceStopped() override {}
Let me explain a few things just to be sure that I am doing things right.
The AudioDeviceManager class is owned by MusicList class and MusicList class is owned by MainContentComponent class.
Also the AudioOscilloscope class is owned by MainContentComponent class.
The reason why the MainContentComponent is owning both of these classes is because I want the class where I have the player controls to access the objects of MusicList class in order to play or stop music, etc.
What I did was :
1- I made MainContentComponent inherit from AudioIODeviceCallback() .
2 -In the constructor of MainContentComponent i called deviceManager.addAudioCallback (this); and then in the destructor i called deviceManager.removeAudioCallback (this); .
3 - I declared these two functions :
void audioDeviceAboutToStart () override {}
void audioDeviceStopped() override {} // in the MainContentComponent class.
4 - I tried to implement this method audioDeviceIOCallback and I can guarantee you that I was implementing it wrong becuase when I tried to compile and error appeared saying " Error C2259 'MainContentComponent': cannot instantiate abstract class ".
Maybe i didn't understand how to implement audioDeviceIOCallback method. So, if you can help me a little bit.
Can you post your code? Specifically your attempt to implement audioDeviceIOCallback. Hard to say what you did wrong otherwise, because what you are doing sounds right to me...
#ifndef MAINCOMPONENT_H_INCLUDED
#define MAINCOMPONENT_H_INCLUDED
#include "../JuceLibraryCode/JuceHeader.h"
#include "MusicList.h"
#include "ControlBar.h"
#include "Menu.h"
#include "AudioOscilloscope.h"
//==============================================================================
/*
This component lives inside our window, and this is where you should put all
your controls and content.
*/
class MainContentComponent : public Component
public AudioIODeviceCallback
{
public:
//==============================================================================
MainContentComponent();
~MainContentComponent();
void paint (Graphics&);
void resized();
void audioDeviceIOCallback(const float **inputChannelData, int numInputChannels,
float **outputChannelData, int numOutputChannels, int numSamples);
void audioDeviceAboutToStart();
void audioDeviceStopped();
private:
MusicList musicList;
ControlBar controlBar;
Menu menu;
AudioOscilloscope audioOscilloscope;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
#endif // MAINCOMPONENT_H_INCLUDED
It's a very good idea to always add the override keyword in such cases. You get better compiler errors this way because the compiler sees that you are trying to override a virtual function from a base class (in this case, AudioIODeviceCallback).
Just checking: you are familiar with inheritance, pure virtual functions, abstract classes, etc. right? Otherwise I'd recommend to read up on these in a C++ book before you continue.
Here is some quite hacky code for an Oscilloscope that I wrote a while ago, maybe you can use it as a starting point:
/*
==============================================================================
This file is part of the Projucer live coding demo.
Copyright (c) 2015 - ROLI Ltd.
Use this code at your own risk & have some fun with C++ live coding!
This code is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
==============================================================================
*/
#ifndef OSCILLOSCOPE_H_INCLUDED
#define OSCILLOSCOPE_H_INCLUDED
class Oscilloscope : public Component, private Timer
{
public:
//==========================================================================
Oscilloscope()
: writePos (0)
{
startTimer (40);
}
//==========================================================================
void pushBuffer (const float* data, int numSamples)
{
for (int i = 0; i < numSamples; ++i)
buffer[++writePos % buffer.size()] = data[i];
}
//==========================================================================
void paint (Graphics& g) override
{
g.fillAll (Colour (0xff000000));
Rectangle<int> r = getLocalBounds();
Path path;
path.startNewSubPath (0, 0.5 * r.getHeight());
const float bufferYscale = 3.0;
int paintPos = 2;
while (paintPos < buffer.size())
{
if (isZeroCrossing (paintPos))
break;
++paintPos;
}
const int posOffset = paintPos;
while (paintPos < buffer.size())
{
Point<float> p ((paintPos - posOffset) * r.getWidth() / paintSize,
0.5 * ((bufferYscale * buffer[paintPos]) + 1) * r.getHeight());
path.lineTo (p);
++paintPos;
}
g.setColour (Colour (0xff00ff00));
g.strokePath (path, PathStrokeType (2.0f));
}
private:
//==========================================================================
void timerCallback() override
{
repaint();
}
bool isZeroCrossing (int i) const noexcept
{
jassert (i > 0);
return buffer[i] > buffer[i - 1] && buffer[i] > 0 && buffer[i - 1] < 0;
}
//==========================================================================
std::array<float, 1024> buffer;
std::size_t writePos;
const int bufferSize = 16384;
const int paintSize = 256;
};
#endif // OSCILLOSCOPE_H_INCLUDED
I didn't put it into JUCE yet as it has a few problems: the waveform keeps wiggling. Didn't have time to tidy that up yet. Maybe you can find out why that is :-) Have fun.
P.S. pushBuffer the method that is analogous to processBlock of the other one.
Now, when i open my player and try to play a song the song doesn't start. Maybe this happens because in the MusicList class where I have declared the AudioDeviceManager the AudioDeviceManager has another addAudioCallback which is the AudioSourcePlayer. And here in the MainContentComponent class I have audioDeviceManager.addAudioCallback(this), thats why the song cant start.
I tried a different thing inside the audioDeviceIOCallback() method which is totally wrong even that it worked a bit.
I wrote this : " musicList.sourcePlayer.audioDeviceIOCallback(inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); " and it caused a lot of noises and speed change when I played a song.
What should I write in order to have the song played without this problem ?
Are you sure you have only *one* AudioDeviceManager for the whole app? Or do you now have one in the main component and another one in the MusicList? (which would be wrong)
Strongly recommended to let the MainComponent manage the deviceManager.
There's plenty of example code: here's a tutorial on how to build an audio file player, and the JuceDemo has a audio file player as well.
Yes, I have only one AudioDeviceManager in the whole app. I have declared the AudioDeviceManager in the MusicList class because I have implemented there a function that plays a listed song in the listbox if it is double clicked. And in the MainContentComponent class I have called the AudioDeviceManager from MusicList class like this : musicList.audioDeviceManager.addAudioCallback(this); .
Actually my player its a bit more complicated because it has the forward and backword buttons and I might add shuffle and repeat button after fixing this problem with the oscilloscope.
The problem is that I don't know what to write inside this class :
void MainComponent::audioDeviceIOCallback(const float **/*inputChannelData*/,
int /*numInputChannels*/,
float **outputChannelData,
int /*numOutputChannels*/,
int numSamples)
{
oscilloscope.pushBuffer(outputChannelData[0], numSamples);
}
the reason why I don't know is because i have never used it before + I'm a beginner and haven't had the time to practice all the Juce classes before.