Hello everybody!
As I already said in this post
http://en.sfml-dev.org/forums/index.php?topic=7642.0 I wanted to make a simple Beat Detection using SFML. I used the algorithm I found here:
http://www.flipcode.com/misc/BeatDetectionAlgorithms.pdf (to be precise I implemented the third version of the first algorithm, because I want to use it for techno and electronic music later anyway).
In the thread above you can see that it didn't quiet work with sf::Music, because I couldn't acces the Samples in time, so I tried using sf::SoundRecorder, because I wanted to use a direct input later anyway. I could get it to work and using a custom SoundStream I could even get it play the samples afterwards. It seems to work fairly good if I put a mic in front of a speaker. It doesn't work too too well when i plug a cable from an ipod into the mic input directly.
So now here is the thing. I am new to the whole concept of threads and mutexs and such, but I would like to use this code in a later project. I know there are a lot of very talented C++ programmers here, so would anybody mind to look at my code and tell me what they think about it and what to improve. I'm not quiet sure if i use the mutexes correctly or if the way I pass the buffers around is the most efficient one or if there is some other way of optimizing my code... Please have a look and tell me what you think.
Also does anyone have an idea why plugging in a cable into the mic input directly makes the code constantly detect a beat?
Thanks in advance,
Foaly
Here is my code:
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <iostream>
#include <vector>
sf::Mutex GlobalMutex;
class CustomRecorder : public sf::SoundRecorder
{
std::vector<sf::Int16> m_Samples;
std::size_t m_ChunckSize;
virtual bool onStart() // optional
{
// Initialize whatever has to be done before the capture starts
m_ChunckSize = 1024;
// Return true to start playing
return true;
}
virtual bool onProcessSamples(const sf::Int16* samples, std::size_t sampleCount)
{
// std::cout << "Recording: " << sampleCount << "Samples" << std::endl;
sf::Lock Lock(GlobalMutex);
std::copy(samples, samples + sampleCount, std::back_inserter(m_Samples));
// Return true to continue playing
return true;
}
virtual void onStop() // optional
{
// Clean up whatever has to be done after the capture ends
}
public:
// Fill the given buffer with 1024 samples (if possible)
bool FillBuffer(std::vector<sf::Int16>& VectorToFill)
{
if(m_Samples.size() >= m_ChunckSize)
{
sf::Lock Lock(GlobalMutex);
VectorToFill.insert(VectorToFill.begin(), m_Samples.begin(), m_Samples.begin() + m_ChunckSize + 1);
m_Samples.erase(m_Samples.begin(), m_Samples.begin() + m_ChunckSize + 1);
return true;
}
return false;
}
};
class CustomPlayer : public sf::SoundStream
{
private:
sf::Mutex m_mutex;
std::vector<sf::Int16> m_Samples;
bool b_FirstTime;
virtual bool onGetData(sf::SoundStream::Chunk& Data)
{
sf::Lock Lock(m_mutex);
// Check if there is enough data to stream
if (m_Samples.size() < 1024)
{
// Returning false means that we want to stop playing the stream
return false;
}
if(b_FirstTime)
b_FirstTime = false;
else
m_Samples.erase(m_Samples.begin(), m_Samples.begin() + 1024 + 1);
// Fill the stream chunk with a pointer to the audio data and the number of samples to stream
Data.samples = &m_Samples[0];
Data.sampleCount = 1024;
return true;
}
virtual void onSeek(sf::Time timeOffset)
{
// Change the current position in the stream source
}
public:
CustomPlayer()
{
b_FirstTime = true;
initialize(1, 44100);
}
void FillBuffer(std::vector<sf::Int16>& SourceVector)
{
sf::Lock Lock(m_mutex);
m_Samples.insert(m_Samples.end(), SourceVector.begin(), SourceVector.end() + 1);
};
};
int main()
{
// Check that the device can capture audio
if (CustomRecorder::isAvailable() == false)
{
std::cout << "Sorry, audio capture is not supported by your system" << std::endl;
return EXIT_SUCCESS;
}
srand(time(NULL));
sf::RenderWindow window(sf::VideoMode(500, 500), "SFML works!");
sf::RectangleShape Block;
Block.setSize(sf::Vector2f(20, 200));
Block.setOrigin(10, 100);
Block.setPosition(250, 250);
Block.setFillColor(sf::Color::Green);
Block.setScale(1, 2);
sf::Clock Clock;
float FrameTime;
CustomRecorder Recorder;
CustomPlayer Player;
std::vector<sf::Int16> Buffer;
// Create the energy history buffer and set it to zero
std::vector<float> EnergyHistoryBuffer;
EnergyHistoryBuffer.resize(43, 0.f);
Recorder.start();
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
// Close Event
if (event.type == sf::Event::Closed)
window.close();
// Escape key pressed
if ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape))
window.close();
}
FrameTime = Clock.getElapsedTime().asSeconds();
// std::cout << "Framerate: " << 1.f / FrameTime << std::endl;
Clock.restart();
if(Recorder.FillBuffer(Buffer))
{
GlobalMutex.lock();
// Compute instant energy
float InstantEnergie = 0;
for (std::vector<sf::Int16>::iterator itor = Buffer.begin(); itor < Buffer.end(); itor++)
{
InstantEnergie += ((*itor) / 32767.f) * ((*itor) / 32767.f);
}
// Compute average local energy
float AverageLocalEnergySum = 0;
for (std::vector<float>::iterator itor = EnergyHistoryBuffer.begin(); itor < EnergyHistoryBuffer.end(); itor++)
{
AverageLocalEnergySum += (*itor);
}
float AverageLocalEnergy = (1/43.f) * AverageLocalEnergySum;
// Calculate the Variance of the last samples
float VarianceSum = 0;
for (std::vector<float>::iterator itor = EnergyHistoryBuffer.begin(); itor < EnergyHistoryBuffer.end(); itor++)
{
VarianceSum += ((*itor) - AverageLocalEnergy) * ((*itor) - AverageLocalEnergy);
}
float Variance = (1/43.f) * VarianceSum;
// Calculate the Sesibility Constant
float SensibilityConstant = (-0.0025714 * Variance) + 1.5142857;
// Insert the Instant energy at the front of the history buffer and remove the last entry
EnergyHistoryBuffer.insert(EnergyHistoryBuffer.begin(), InstantEnergie);
EnergyHistoryBuffer.pop_back();
// std::cout << "Instant Energy: " << InstantEnergie << " Local Energy: " << SensibilityConstant * AverageLocalEnergy << std::endl;
// see if we have a beat
if(InstantEnergie > SensibilityConstant * AverageLocalEnergy)
{
// std::cout << "We have a BEAT!" << std::endl;
Block.setScale(1, 2);//(rand()) / RAND_MAX * (0.4) + 1.8 );
}
Player.FillBuffer(Buffer);
Player.play();
Buffer.clear();
GlobalMutex.unlock();
}
// Block.rotate(50 * FrameTime);
if(Block.getScale().y > 0.8)
Block.setScale(1, Block.getScale().y - 0.9 * FrameTime);
window.clear();
window.draw(Block);
window.display();
}
Recorder.stop();
return 0;
}
edit: maybe a little suggestion for the forum: what about a "spoiler" function or something like that? So that you only see the code when you click on it