Skip to content

Instantly share code, notes, and snippets.

@jix
Created July 13, 2020 15:08
Show Gist options
  • Save jix/53464e37ad68ceb34e7e573aa79e9c1c to your computer and use it in GitHub Desktop.
Save jix/53464e37ad68ceb34e7e573aa79e9c1c to your computer and use it in GitHub Desktop.
#include <pulse/simple.h>
#include <pulse/error.h>
#include <array>
#include <stdexcept>
#include <memory>
#include <iostream>
#include <complex>
#include <functional>
#include <GLFW/glfw3.h>
#include <fftw3.h>
/*
* compile using g++ --std=c++11 hellbert.cpp -o hellbert -lpulse -lpulse-simple -lglfw -lGL -lfftw3
*/
class scope_guard {
std::function<void()> function;
public:
scope_guard(std::function<void()> f) : function(std::move(f)) {}
scope_guard(scope_guard const &other) = delete;
~scope_guard() { function(); }
};
using namespace std;
constexpr unsigned buffersize = 1 << 11;
constexpr unsigned skip = 1 << 6;
constexpr unsigned highpass = 2;
constexpr unsigned samplerate = 96000;
static_assert(2 * skip < buffersize, "");
static_assert(2 * highpass < buffersize, "");
typedef float color[4];
int main(int argc, char *argv[])
{
try {
char * dev = argc >= 2 ? argv[1] : nullptr; //NULL;
int error;
pa_simple *s;
pa_sample_spec ss;
ss.format = PA_SAMPLE_FLOAT32NE;
ss.channels = 1;
ss.rate = samplerate;
s = pa_simple_new(NULL,
"Hellbert",
PA_STREAM_RECORD,
dev,
"Hellbert input",
&ss,
NULL,
NULL,
&error);
if (s == nullptr)
throw runtime_error(string("couldn't initialize pulseaudio: ") + pa_strerror(error));
scope_guard free_pulse_audio([&]{pa_simple_free(s);});
if(!glfwInit())
throw runtime_error("couldn't initialize GLFW");
scope_guard deinit_glfw(glfwTerminate);
unique_ptr<float[]> buffer(new float[buffersize]);
unique_ptr<float[]> window(new float[buffersize]);
unique_ptr<complex<double>[]> cbuffer(new complex<double>[buffersize]);
unique_ptr<complex<double>[]> fbuffer(new complex<double>[buffersize]);
unique_ptr<color[]> colors(new color[buffersize]);
for (unsigned i = 0; i < buffersize; i++) {
window[i] = 0.54 - 0.46 * cos(2 * M_PI * i / buffersize);
}
scope_guard cleanup_fftw(fftw_cleanup);
fftw_plan analyze = fftw_plan_dft_1d(buffersize,
reinterpret_cast<fftw_complex*>(cbuffer.get()),
reinterpret_cast<fftw_complex*>(fbuffer.get()),
FFTW_FORWARD, FFTW_DESTROY_INPUT);
fftw_plan synthesize = fftw_plan_dft_1d(buffersize,
reinterpret_cast<fftw_complex*>(fbuffer.get()),
reinterpret_cast<fftw_complex*>(cbuffer.get()),
FFTW_BACKWARD, FFTW_DESTROY_INPUT);
auto win = glfwCreateWindow(512, 512, "Hellbert", nullptr, nullptr);
glfwMakeContextCurrent(win);
if(!win)
throw runtime_error("couldn't open GLFW window");
glClearColor(0,0,0,0);
float alpha = 0.3;
glColor4f(0.5 * alpha, alpha, 0, alpha);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glLineWidth(2);
for (unsigned i = 0; i < buffersize; i++) {
float t = static_cast<float>(i) / buffersize;
float alpha = 0.3;
float a = 0.5+0.5*sin(15 * 2 * M_PI * t);
float b = 0.5+0.5*cos(15 * 2 * M_PI * t);
colors[i][0] = alpha * 0.7 * a;
colors[i][1] = alpha * (b + 0.3 * a);
colors[i][2] = 0;
colors[i][3] = alpha;
}
float scale = 1.0;
while (true) {
scale *= 0.99;
double startTime = glfwGetTime();
if (pa_simple_read(s, buffer.get(), sizeof(float) * buffersize, &error) < 0)
throw runtime_error(string("couldn't read input stream: ") + pa_strerror(error));
double endTime = glfwGetTime();
if (endTime - startTime > 0.5 / samplerate * buffersize) {
int width, height;
glfwGetWindowSize(win, &width, &height);
glViewport(0, 0, width, height);
for (unsigned i = 0; i < buffersize; i++) {
scale = max(scale, abs(buffer[i]));
}
for (unsigned i = 0; i < buffersize; i++) {
cbuffer[i] = buffer[i] / buffersize / scale;
}
double energy = 0.0;
fftw_execute(analyze);
for (unsigned i = 0; i < buffersize; i++) {
energy += abs(fbuffer[i]);
}
for (unsigned i = 0; i < highpass; i++) {
fbuffer[i] = 0;
}
for (unsigned i = buffersize / 2; i < buffersize; i++) {
fbuffer[i] = 0;
}
fftw_execute(synthesize);
for (unsigned i = 0; i < buffersize; i++) {
// cbuffer[i] /= window[i];
}
glClear(GL_COLOR_BUFFER_BIT);
glVertexPointer(2, GL_DOUBLE, 0, cbuffer.get());
glColorPointer(4, GL_FLOAT, 0, colors.get());
glDrawArrays(GL_LINE_STRIP, skip, buffersize - 2 * skip);
glfwSwapBuffers(win);
if (glfwWindowShouldClose(win))
break;
}
}
} catch (std::exception &e) {
cerr << "error: " << e.what() << endl;
exit(1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment