Skip to content

Instantly share code, notes, and snippets.

@CallumHoward
Last active December 15, 2017 05:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CallumHoward/763c429cf2f0ed04859581e3662e035a to your computer and use it in GitHub Desktop.
Save CallumHoward/763c429cf2f0ed04859581e3662e035a to your computer and use it in GitHub Desktop.
Cinder Input Analyzer Sample
/*
* This sample illustrates how to get audio data from an input device, such as a microphone,
* with audio::InputDeviceNode. It then visualizes the input in the frequency domain. The frequency
* spectrum analysis is accomplished with an audio::MonitorSpectralNode.
*
* The plot is similar to a typical spectrogram, where the x-axis represents the linear
* frequency bins (0 - samplerate / 2) and the y-axis is the magnitude of the frequency
* bin in normalized decibels (0 - 100).
*
* author: Richard Eakin (2014)
*/
#include "cinder/app/App.h"
#include "cinder/gl/gl.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/TextureFont.h"
#include "cinder/Utilities.h"
#include "cinder/audio/audio.h"
#include "../../common/AudioDrawUtils.h"
using namespace ci;
using namespace ci::app;
using namespace std;
class InputAnalyzer : public App {
public:
void setup() override;
void mouseDown( MouseEvent event ) override;
void keyDown(KeyEvent event ) override;
void update() override;
void draw() override;
void drawSpectralCentroid();
void drawLabels();
void printBinInfo( int mouseX );
audio::InputDeviceNodeRef mInputDeviceNode;
audio::MonitorSpectralNodeRef mMonitorSpectralNode;
vector<float> mMagSpectrum;
SpectrumPlot mSpectrumPlot;
gl::TextureFontRef mTextureFont;
bool isRecording = false;
audio::BufferRecorderNodeRef bufferRecord;
audio::SampleRecorderNodeRef sampleRecord;
};
void InputAnalyzer::setup()
{
auto ctx = audio::Context::master();
cout << audio::Device::printDevicesToString();
// The InputDeviceNode is platform-specific, so you create it using a special method on the Context:
mInputDeviceNode = ctx->createInputDeviceNode();
// mInputDeviceNode = ctx->createInputDeviceNode(audio::Device::getDevices()[0]);
bufferRecord = ctx->makeNode( new audio::BufferRecorderNode() );
bufferRecord->setNumSeconds(30 * 60);
// By providing an FFT size double that of the window size, we 'zero-pad' the analysis data, which gives
// an increase in resolution of the resulting spectrum data.
auto monitorFormat = audio::MonitorSpectralNode::Format().fftSize( 2048 ).windowSize( 1024 );
mMonitorSpectralNode = ctx->makeNode( new audio::MonitorSpectralNode( monitorFormat ) );
mInputDeviceNode >> mMonitorSpectralNode;
mInputDeviceNode >> bufferRecord;
// InputDeviceNode (and all InputNode subclasses) need to be enabled()'s to process audio. So does the Context:
mInputDeviceNode->enable();
bufferRecord->enable();
ctx->enable();
getWindow()->setTitle( mInputDeviceNode->getDevice()->getName() );
}
void InputAnalyzer::mouseDown( MouseEvent event )
{
if( mSpectrumPlot.getBounds().contains( event.getPos() ) )
printBinInfo( event.getX() );
}
void InputAnalyzer::keyDown( KeyEvent event )
{
if( event.getChar() == ' ') {
isRecording = !isRecording;
if (isRecording) {
cout<<"recording..."<<endl;
bufferRecord->start();
}
else {
bufferRecord->stop();
string filepath = "/Applications/_cinder/Cinder.git/samples/_audio/InputAnalyzerRecorder/assets/" + toString( (int) std::time(0) ) + ".wav";
cout<<"should write to: "<<filepath<<endl;
fs::path tmp(filepath);
cout<<tmp.generic_string()<<endl;
bufferRecord->writeToFile( tmp );
}
}
}
void InputAnalyzer::update()
{
mSpectrumPlot.setBounds( Rectf( 40, 40, (float)getWindowWidth() - 40, (float)getWindowHeight() - 40 ) );
// We copy the magnitude spectrum out from the Node on the main thread, once per update:
mMagSpectrum = mMonitorSpectralNode->getMagSpectrum();
}
void InputAnalyzer::draw()
{
gl::clear();
gl::enableAlphaBlending();
mSpectrumPlot.draw( mMagSpectrum );
drawSpectralCentroid();
drawLabels();
}
void InputAnalyzer::drawSpectralCentroid()
{
// The spectral centroid is largely correlated with 'brightness' of a sound. It is the center of mass of all frequency values.
// See the note on audio::MonitorSpectralNode::getSpectralCentroid() - it may be analyzing a more recent magnitude spectrum
// than what we're drawing in the SpectrumPlot. It is not a problem for this simple sample, but if you need a more precise
// value, use audio::dsp::spectralCentroid() directly.
float spectralCentroid = mMonitorSpectralNode->getSpectralCentroid();
float nyquist = (float)audio::master()->getSampleRate() / 2.0f;
Rectf bounds = mSpectrumPlot.getBounds();
float freqNormalized = spectralCentroid / nyquist;
float barCenter = bounds.x1 + freqNormalized * bounds.getWidth();
Rectf verticalBar = { barCenter - 2, bounds.y1, barCenter + 2, bounds.y2 };
gl::ScopedColor colorScope( 0.85f, 0.45f, 0, 0.4f ); // transparent orange
gl::drawSolidRect( verticalBar );
}
void InputAnalyzer::drawLabels()
{
if( ! mTextureFont )
mTextureFont = gl::TextureFont::create( Font( Font::getDefault().getName(), 16 ) );
gl::color( 0, 0.9f, 0.9f );
// draw x-axis label
string freqLabel = "Frequency (hertz)";
mTextureFont->drawString( freqLabel, vec2( getWindowCenter().x - mTextureFont->measureString( freqLabel ).x / 2, (float)getWindowHeight() - 20 ) );
// draw y-axis label
string dbLabel = "Magnitude (decibels, linear)";
gl::pushModelView();
gl::translate( 30, getWindowCenter().y + mTextureFont->measureString( dbLabel ).x / 2 );
gl::rotate( -M_PI / 2 );
mTextureFont->drawString( dbLabel, vec2( 0 ) );
gl::popModelView();
}
void InputAnalyzer::printBinInfo( int mouseX )
{
size_t numBins = mMonitorSpectralNode->getFftSize() / 2;
size_t bin = std::min( numBins - 1, size_t( ( numBins * ( mouseX - mSpectrumPlot.getBounds().x1 ) ) / mSpectrumPlot.getBounds().getWidth() ) );
float binFreqWidth = mMonitorSpectralNode->getFreqForBin( 1 ) - mMonitorSpectralNode->getFreqForBin( 0 );
float freq = mMonitorSpectralNode->getFreqForBin( bin );
float mag = audio::linearToDecibel( mMagSpectrum[bin] );
console() << "bin: " << bin << ", freqency (hertz): " << freq << " - " << freq + binFreqWidth << ", magnitude (decibels): " << mag << endl;
}
CINDER_APP( InputAnalyzer, RendererGl( RendererGl::Options().msaa( 8 ) ) )
@CallumHoward
Copy link
Author

Thread 1 "audio-InputAnal" received signal SIGSEGV, Segmentation fault.
0x00382880 in cinder::audio::TargetFile::write (this=0xfb22c700, buffer=0x917e6c, numFrames=46592) at /home/odroid/Documents/Cinder.git/src/cinder/audio/Target.cpp:79
79              performWrite( buffer, numFrames, 0 );
(gdb) bt
#0  0x00382880 in cinder::audio::TargetFile::write (this=0xfb22c700, buffer=0x917e6c, numFrames=46592) at /home/odroid/Documents/Cinder.git/src/cinder/audio/Target.cpp:79
#1  0x00380584 in cinder::audio::BufferRecorderNode::writeToFile (this=0x917fc8, filePath=..., sampleType=cinder::audio::SampleType::INT_16)
    at /home/odroid/Documents/Cinder.git/src/cinder/audio/SampleRecorderNode.cpp:171
#2  0x0033804e in InputAnalyzer::keyDown (this=0x7b1b90, event=...) at /home/odroid/Documents/Cinder.git/samples/_audio/InputAnalyzer/src/InputAnalyzerApp.cpp:101
#3  0x0036e4ce in cinder::app::Window::emitKeyDown (this=0x90e950, event=0xbeffe3a0) at /home/odroid/Documents/Cinder.git/src/cinder/app/Window.cpp:415
#4  0x0046e24e in cinder::app::GlfwCallbacks::onKeyboard (glfwWindow=0x7c8320, key=32, scancode=65, action=1, mods=0) at /home/odroid/Documents/Cinder.git/src/cinder/app/linux/AppImplLinuxGlfw.cpp:203
#5  0x00459398 in _glfwInputKey (window=0x7c8320, key=32, scancode=65, action=1, mods=0) at /home/odroid/Documents/Cinder.git/src/glfw/src/input.c:64
#6  0x0046157a in processEvent (event=0xbeffe564) at /home/odroid/Documents/Cinder.git/src/glfw/src/x11_window.c:1002
#7  0x0046316a in _glfwPlatformPollEvents () at /home/odroid/Documents/Cinder.git/src/glfw/src/x11_window.c:2060
#8  0x0045ceae in glfwPollEvents () at /home/odroid/Documents/Cinder.git/src/glfw/src/window.c:869
#9  0x0046cdc0 in cinder::app::AppImplLinux::run (this=0x7b1e58) at /home/odroid/Documents/Cinder.git/src/cinder/app/linux/AppImplLinuxGlfw.cpp:406
#10 0x00465378 in cinder::app::AppLinux::launch (this=0x7b1b90) at /home/odroid/Documents/Cinder.git/src/cinder/app/linux/AppLinux.cpp:48
#11 0x00361f08 in cinder::app::AppBase::executeLaunch (this=0x7b1b90) at /home/odroid/Documents/Cinder.git/src/cinder/app/AppBase.cpp:194
#12 0x0033a8ba in cinder::app::AppLinux::main<InputAnalyzer>(std::shared_ptr<cinder::app::Renderer> const&, char const*, int, char* const*, std::function<void (cinder::app::AppBase::Settings*)> const&) (
    defaultRenderer=std::shared_ptr (count 3, weak 0) 0x7aefb8, title=0x5fcca8 "InputAnalyzer", argc=1, argv=0xbeffe994, settingsFn=...)
    at /home/odroid/Documents/Cinder.git/include/cinder/app/linux/AppLinux.h:85
#13 0x0033890c in main (argc=1, argv=0xbeffe994) at /home/odroid/Documents/Cinder.git/samples/_audio/InputAnalyzer/src/InputAnalyzerApp.cpp:177
(gdb) info locals
__PRETTY_FUNCTION__ = "void cinder::audio::TargetFile::write(const Buffer*, size_t)"
(gdb) frame 1
#1  0x00380584 in cinder::audio::BufferRecorderNode::writeToFile (this=0x917fc8, filePath=..., sampleType=cinder::audio::SampleType::INT_16)
    at /home/odroid/Documents/Cinder.git/src/cinder/audio/SampleRecorderNode.cpp:171
171             target->write( copiedBuffer.get(), currentWritePos );
(gdb) info locals
currentWritePos = 46592
copiedBuffer = std::shared_ptr (count 2, weak 0) 0x917e6c
target = std::shared_ptr (count 1, weak 0) 0xfb22c700
(gdb) frame 2
#2  0x0033804e in InputAnalyzer::keyDown (this=0x7b1b90, event=...) at /home/odroid/Documents/Cinder.git/samples/_audio/InputAnalyzer/src/InputAnalyzerApp.cpp:101
101                 bufferRecord->writeToFile( tmp );
(gdb) info locals
filepath = "~/1513316409.wav"
tmp = {static preferred_separator = 47 '/', m_pathname = "~/1513316409.wav"}
(gdb)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment