Mp3 player & Oscilloscope ?!


#1

Hi guys,

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.

Here you have the codes from dRowAudio :

https://github.com/drowaudio/drowaudio/blob/master/dRowAudio/gui/dRowAudio_AudioOscilloscope.h

https://github.com/drowaudio/drowaudio/blob/master/dRowAudio/gui/dRowAudio_AudioOscilloscope.cpp

If someone knows how to do this I'd appreciate the help a lot.

Thanks.


#2

Looking at the code in there:

/** 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!


#3

Thanks Timur,

I will try it right now.


#4

I tried something but didn't work.

Let me explain more to you so you can understand my situation.

I have implemented this mp3 player using these classes :

AudioDeviceManager,
AudioFormatManager,
ScopedPointer<AudioFormatReaderSource>,
AudioTransportSource,
AudioSourcePlayer.

The mp3 player works just fine.

Now, how am I supposed to call AudioOscilloscope::processBlock() ?

I tried like this : deviceManager.addAudioCallback(audioOscilloscope.processBlock(?????)); 

Maybe I'm doing it wrong because I want this oscilloscope to react to the music that is being played and maybe this oscilloscope isn't for my case.

Thank's for the help.

Hope you can tell me what's the problem with this. 
 


#5

Hi there,

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 {}

#6

Thanks, i'll try it. ;)


#7

I followed your steps one by one.

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.

I really appreciate your help. ;)

Thanks.


#8

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...


#9

Here you have the code :

MainComponent.h :


#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

MainComponent.cpp :


#include "MainComponent.h"
//==============================================================================
MainContentComponent::MainContentComponent()
   
{
    addAndMakeVisible(musicList);
    addAndMakeVisible(controlBar);
    addAndMakeVisible(menu);
    addAndMakeVisible(audioOscilloscope);

    musicList.deviceManager.addAudioCallback(this);
    
    audioOscilloscope.setTraceColour(Colours::blue);
    
    
    setSize (400, 300);
}
MainContentComponent::~MainContentComponent()
{
    musicList.deviceManager.removeAudioCallback(this);
}
void MainContentComponent::paint (Graphics& g)
{
    g.fillAll(Colours::black);
    g.setGradientFill(ColourGradient(Colours::black.brighter(0.18f), getWidth() * 0.5f, getHeight() * 0.5f, Colours::black.brighter(0.05f), 0.0f, 0.0f, true));
    g.fillAll();
}
void MainContentComponent::resized()
{
    Rectangle<int> r(getLocalBounds());
    if ((r.getWidth() <= 400) && (r.getHeight() <= 300))
    {
        musicList.setBounds(75, 0, 250, getHeight() - 70);
        controlBar.setBounds(0, getHeight() - 70, getWidth(), 70);
        menu.setVisible(false);
        
        audioOscilloscope.setVisible(false);
        
    }
    else if ((r.getWidth() > 400) && (r.getHeight() > 300))
    {
        menu.setVisible(true);
        
        audioOscilloscope.setVisible(true);
        
        musicList.setBounds(0, 30, 250, getHeight() - 100);
        controlBar.setBounds(0, getHeight() - 70, getWidth(), 70);
        menu.setBounds(0, 0, getWidth(), 30);
        
        audioOscilloscope.setBounds(250, 30, getWidth() - 250, getHeight() - 100);
        
        
    }
}
void MainContentComponent::audioDeviceIOCallback(const float **inputChannelData, int numInputChannels,
    float **outputChannelData, int numOutputChannels, int numSamples)
{
    audioOscilloscope.processBlock(*outputChannelData, numSamples);
}
void MainContentComponent::audioDeviceAboutToStart() 
{
    
}
void MainContentComponent::audioDeviceStopped()
{
    
}

I haven't used AudioIODeviceCallback before so I may be totally wrong with the audioDeviceIOCallback method . :P

Thanks. 


#10

Oh yes it's just the wrong function signature for one of the overrides. Didn't write this correctly in my first post above. It's

   void audioDeviceAboutToStart (AudioIODevice* device)

not

   void audioDeviceAboutToStart()

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.


#11

I already found that problem with void audioDeviceAboutToStart (AudioIODevice* device).

I didn't add override because i got this error ' override ' specifier illegal on function definiton...

Anyway I'm not so familiar with virtual functions, abstract classes, etc.

Thanks for the recommendation though. ( I'm a beginner ;) )

I did  something with this oscilloscope and when I played a song in my mp3 player a wavefom appeared and a lot of noise too.

This wasn't what i wanted.

Yesterday I watched your cppcon video on youtube. While you were doing the presentation you showed an oscilloscope with that sine synth.

That kind of oscilloscope is what I'm looking for and not some kind of audio waveform.

Is there any open source code for that oscilloscope to use in juce ?

And if there isn't any, can you explain me how can i create one by myself ?

Thanks.


#12

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.


#13

Thanks a lot Timur. yes

I will try it directly for my player. ;)


#14

So, after writing your code exactly how it is, I get an error when I try to compile it.

"Incomplete type is not allowed". 

The problem is in this line : 

std::array<float, 1024> buffer;

What might be the problem with this ?


#15

#include <array>


#16

What I did now was writing the code from the begining based in your tutorial on youtube at cppcon.

The problem is that when i try to compile it I get this error at the initializer list of the MainContentComponent class :

I don't know why did this happen now.

Hope you understand the problem and tell me.

It happens on both the projects, on your tutorial and my player too.

 


#17

So, this initializer wasn't necessary at all.

I succeeded with your tutorial oscilloscope.

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 ?


 

 


#18

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.

At this point I'll let you figure out the rest...


#19

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.

 


#20

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.

This is why in my last post I thought I'd recommend to check out the tutorials and the Juce Demo...