diff --git a/src/bitvis.cpp b/src/bitvis.cpp new file mode 100644 index 0000000..9d4b1ce --- /dev/null +++ b/src/bitvis.cpp @@ -0,0 +1,254 @@ +/* + * bitvis + * Copyright (C) Bob 2012 + * + * bitvis is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bitvis 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "util/inclstdint.h" +#include "bitvis.h" +#include "util/log.h" +#include "util/misc.h" +#include "util/timeutils.h" + +#include +#include +#include +#include +#include + +using namespace std; + +#define CONNECTINTERVAL 10000000 + +CBitVis::CBitVis(int argc, char *argv[]) +{ + g_printdebuglevel = true; + m_stop = false; + m_buf = NULL; + m_bufsize = 0; + m_fftbuf = NULL; + m_samplecounter = 0; + m_nrffts = 0; +} + +CBitVis::~CBitVis() +{ +} + +void CBitVis::Setup() +{ + //init the logfile + SetLogFile("bitvis.log"); + + SetupSignals(); + + jack_set_error_function(JackError); + jack_set_info_function(JackInfo); +} + +void CBitVis::SetupSignals() +{ + m_signalfd = -1; + + sigset_t sigset; + if (sigemptyset(&sigset) == -1) + { + LogError("sigemptyset: %s", GetErrno().c_str()); + return; + } + + if (sigaddset(&sigset, SIGTERM) == -1) + { + LogError("adding SIGTERM: %s", GetErrno().c_str()); + return; + } + + if (sigaddset(&sigset, SIGINT) == -1) + { + LogError("adding SIGINT: %s", GetErrno().c_str()); + return; + } + + //create a file descriptor that will catch SIGTERM and SIGINT + m_signalfd = signalfd(-1, &sigset, SFD_NONBLOCK); + if (m_signalfd == -1) + { + LogError("signalfd: %s", GetErrno().c_str()); + } + else + { + //block SIGTERM and SIGINT + if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) + LogError("sigpocmask: %s", GetErrno().c_str()); + } + + if (sigemptyset(&sigset) == -1) + { + LogError("sigemptyset: %s", GetErrno().c_str()); + return; + } + + if (sigaddset(&sigset, SIGPIPE) == -1) + { + LogError("adding SIGPIPE: %s", GetErrno().c_str()); + return; + } + + //libjack throws SIGPIPE a lot, block it + if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) + LogError("sigpocmask: %s", GetErrno().c_str()); +} + +void CBitVis::Process() +{ + int64_t lastconnect = GetTimeUs() - CONNECTINTERVAL; + + while (!m_stop) + { + if (m_jackclient.ExitStatus()) + { + LogError("Jack client exited with code %i reason: \"%s\"", + (int)m_jackclient.ExitStatus(), m_jackclient.ExitReason().c_str()); + m_jackclient.Disconnect(); + } + + if (!m_jackclient.IsConnected() && GetTimeUs() - lastconnect > CONNECTINTERVAL) + { + m_jackclient.Connect(); + lastconnect = GetTimeUs(); + } + + uint8_t msg; + while ((msg = m_jackclient.GetMessage()) != MsgNone) + LogDebug("got message %s from jack client", MsgToString(msg)); + + if (m_jackclient.IsConnected()) + { + ProcessAudio(); + } + else + { + sleep(1); + } + + ProcessSignalfd(); + } + + m_jackclient.Disconnect(); +} + +void CBitVis::ProcessSignalfd() +{ + signalfd_siginfo siginfo; + int returnv = read(m_signalfd, &siginfo, sizeof(siginfo)); + if (returnv == -1 && errno != EAGAIN) + { + LogError("reading signals fd: %s", GetErrno().c_str()); + if (errno != EINTR) + { + close(m_signalfd); + m_signalfd = -1; + } + } + else if (returnv > 0) + { + if (siginfo.ssi_signo == SIGTERM || siginfo.ssi_signo == SIGINT) + { + Log("caught %s, exiting", siginfo.ssi_signo == SIGTERM ? "SIGTERM" : "SIGINT"); + m_stop = true; + } + else + { + LogDebug("caught signal %i", siginfo.ssi_signo); + } + } +} + +void CBitVis::ProcessAudio() +{ + const int bins = 1024; + const int lines = 46; + int samplerate; + int samples; + if ((samples = m_jackclient.GetAudio(m_buf, m_bufsize, samplerate)) > 0) + { + m_fft.Allocate(bins * 2); + if (!m_fftbuf) + { + m_fftbuf = new float[bins]; + memset(m_fftbuf, 0, bins * sizeof(float)); + } + + int additions = 0; + for (int i = 1; i < lines; i++) + additions += i; + + const int maxbin = Round32(15000.0f / samplerate * bins * 2.0f); + + float increase = (float)(maxbin - lines) / additions; + + for (int i = 0; i < samples; i++) + { + m_fft.AddSample(m_buf[i]); + + m_samplecounter++; + if (m_samplecounter >= samplerate / 30) + { + m_samplecounter = 0; + + m_fft.ApplyWindow(); + fftwf_execute(m_fft.m_plan); + + string out; + float start = 0.0f; + float add = 1.0f; + for (int j = 0; j < lines; j++) + { + float next = start + add; + + int bin = Round32(start); + int nrbins = Round32(next - start); + float outval = 0.0f; + for (int k = bin; k < bin + nrbins; k++) + outval += cabsf(m_fft.m_outbuf[k]) / m_fft.m_bufsize; + + out += string(Clamp(Round32(outval * 100.0f), 1, 100), '|'); + out += '\n'; + + start = next; + add += increase; + } + printf("start\n%send\n", out.c_str()); + fflush(stdout); + } + } + } +} + +void CBitVis::Cleanup() +{ + m_jackclient.Disconnect(); +} + +void CBitVis::JackError(const char* jackerror) +{ + LogDebug("%s", jackerror); +} + +void CBitVis::JackInfo(const char* jackinfo) +{ + LogDebug("%s", jackinfo); +} + diff --git a/src/bitvis.h b/src/bitvis.h new file mode 100644 index 0000000..8eb2969 --- /dev/null +++ b/src/bitvis.h @@ -0,0 +1,53 @@ +/* + * bitvis + * Copyright (C) Bob 2012 + * + * bitvis is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bitvis 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef BITVIS_H +#define BITVIS_H + +#include "jackclient.h" +#include "fft.h" + +class CBitVis +{ + public: + CBitVis(int argc, char *argv[]); + ~CBitVis(); + + void Setup(); + void Process(); + void Cleanup(); + + private: + bool m_stop; + CJackClient m_jackclient; + int m_signalfd; + Cfft m_fft; + float* m_buf; + int m_bufsize; + float* m_fftbuf; + int m_samplecounter; + int m_nrffts; + + void SetupSignals(); + void ProcessSignalfd(); + void ProcessAudio(); + static void JackError(const char* jackerror); + static void JackInfo(const char* jackinfo); +}; + +#endif //BITVIS_H diff --git a/src/clientmessage.h b/src/clientmessage.h new file mode 100644 index 0000000..6177c22 --- /dev/null +++ b/src/clientmessage.h @@ -0,0 +1,49 @@ +/* + * bobdsp + * Copyright (C) Bob 2012 + * + * bobdsp is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bobdsp 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef CLIENTMESSAGE_H +#define CLIENTMESSAGE_H + +#include "util/inclstdint.h" + +enum ClientMessage +{ + MsgNone, + MsgExited, +}; + +inline const char* MsgToString(ClientMessage msg) +{ + static const char* msgstrings[] = + { + "MsgNone", + "MsgExited", + }; + + if (msg >= 0 && (size_t)msg < (sizeof(msgstrings) / sizeof(msgstrings[0]))) + return msgstrings[msg]; + else + return "ERROR: INVALID MESSAGE"; +} + +inline const char* MsgToString(uint8_t msg) +{ + return MsgToString((ClientMessage)msg); +} + +#endif //CLIENTMESSAGE_H diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..53e0bd6 --- /dev/null +++ b/src/config.h @@ -0,0 +1 @@ +#include "../build/config.h" diff --git a/src/fft.cpp b/src/fft.cpp new file mode 100644 index 0000000..38cc9b9 --- /dev/null +++ b/src/fft.cpp @@ -0,0 +1,100 @@ +/* + * bitvis + * Copyright (C) Bob 2012 + * + * bitvis is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bitvis 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include //TODO: REMOVE +#include + +#include "fft.h" +#include "util/timeutils.h" +#include "util/log.h" + +Cfft::Cfft() +{ + m_inbuf = NULL; + m_fftin = NULL; + m_outbuf = NULL; + m_window = NULL; + m_bufsize = 0; + m_plan = NULL; +} + +Cfft::~Cfft() +{ + Free(); +} + +void Cfft::Allocate(unsigned int size) +{ + if (size != m_bufsize) + { + Free(); + + m_bufsize = size; + m_inbuf = new float[m_bufsize]; + m_fftin = (float*)fftw_malloc(m_bufsize * sizeof(float)); + m_outbuf = (fftwf_complex*)fftw_malloc(m_bufsize * sizeof(fftwf_complex)); + m_window = new float[m_bufsize]; + + //create a hamming window + for (unsigned int i = 0; i < m_bufsize; i++) + m_window[i] = 0.54f - 0.46f * cosf(2.0f * M_PI * i / (m_bufsize - 1.0f)); + + Log("Building fft plan"); + int64_t start = GetTimeUs(); + m_plan = fftwf_plan_dft_r2c_1d(m_bufsize, m_fftin, m_outbuf, FFTW_MEASURE); + Log("Build fft plan in %.0f ms", (double)(GetTimeUs() - start) / 1000.0f); + } +} + +void Cfft::Free() +{ + delete[] m_inbuf; + fftw_free(m_fftin); + fftw_free(m_outbuf); + delete[] m_window; + m_inbuf = NULL; + m_fftin = NULL; + m_outbuf = NULL; + m_window = NULL; + m_bufsize = 0; + + if (m_plan) + { + fftwf_destroy_plan(m_plan); + m_plan = NULL; + } +} + +void Cfft::ApplyWindow() +{ + float* in = m_inbuf; + float* inend = m_inbuf + m_bufsize; + float* window = m_window; + float* out = m_fftin; + + while (in != inend) + *(out++) = *(in++);// * *(window++); +} + +void Cfft::AddSample(float sample) +{ + memmove(m_inbuf, m_inbuf + 1, (m_bufsize - 1) * sizeof(float)); + m_inbuf[m_bufsize - 1] = sample; +} + diff --git a/src/fft.h b/src/fft.h new file mode 100644 index 0000000..a574280 --- /dev/null +++ b/src/fft.h @@ -0,0 +1,46 @@ +/* + * bitvis + * Copyright (C) Bob 2012 + * + * bitvis is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bitvis 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef FFT_H +#define FFT_H + +#include +#include + +class Cfft +{ + public: + Cfft(); + ~Cfft(); + + void Allocate(unsigned int size); + void Free(); + void ApplyWindow(); + void AddSample(float sample); + + float* m_inbuf; + float* m_fftin; + float* m_window; + fftwf_complex* m_outbuf; + unsigned int m_bufsize; + fftwf_plan m_plan; + + private: + +}; +#endif //FFT_H diff --git a/src/jackclient.cpp b/src/jackclient.cpp new file mode 100644 index 0000000..3fe4050 --- /dev/null +++ b/src/jackclient.cpp @@ -0,0 +1,315 @@ +/* + * bobdsp + * Copyright (C) Bob 2012 + * + * bobdsp is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bobdsp 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef _GNU_SOURCE + #define _GNU_SOURCE //for pipe2 +#endif //_GNU_SOURCE +#include +#include +#include +#include +#include + +#include "util/inclstdint.h" +#include "util/misc.h" +#include "util/timeutils.h" +#include "util/log.h" +#include "util/lock.h" + +#include "jackclient.h" +#include "fft.h" + +using namespace std; + +CJackClient::CJackClient() +{ + m_name = "bitvis"; + m_client = NULL; + m_jackport = NULL; + m_connected = false; + m_wasconnected = true; + m_exitstatus = (jack_status_t)0; + m_samplerate = 0; + m_outsamplerate = 40000; + m_buf = NULL; + m_bufsize = 0; + m_bufupdated = false; + m_srcstate = NULL; + m_outsamples = 0; + + if (pipe2(m_pipe, O_NONBLOCK) == -1) + { + LogError("creating msg pipe for client \"%s\": %s", m_name.c_str(), GetErrno().c_str()); + m_pipe[0] = m_pipe[1] = -1; + } +} + +CJackClient::~CJackClient() +{ + Disconnect(); + + if (m_pipe[0] != -1) + close(m_pipe[0]); + if (m_pipe[1] != -1) + close(m_pipe[1]); +} + +bool CJackClient::Connect() +{ + m_connected = ConnectInternal(); + if (!m_connected) + Disconnect(); + else + m_wasconnected = true; + + return m_connected; +} + +bool CJackClient::ConnectInternal() +{ + if (m_connected) + return true; //already connected + + LogDebug("Connecting client \"%s\" to jackd", m_name.c_str()); + + //this is set in PJackInfoShutdownCallback(), init to 0 here so we know when the jack thread has exited + m_exitstatus = (jack_status_t)0; + m_exitreason.clear(); + + //try to connect to jackd + m_client = jack_client_open(m_name.substr(0, jack_client_name_size() - 1).c_str(), JackNoStartServer, NULL); + if (m_client == NULL) + { + if (m_wasconnected || g_printdebuglevel) + { + LogError("Client \"%s\" error connecting to jackd: \"%s\"", m_name.c_str(), GetErrno().c_str()); + m_wasconnected = false; //only print this to the log once + } + return false; + } + + //we want to know when the jack thread shuts down, so we can restart it + jack_on_info_shutdown(m_client, SJackInfoShutdownCallback, this); + + m_samplerate = jack_get_sample_rate(m_client); + + Log("Client \"%s\" connected to jackd, got name \"%s\", samplerate %" PRIi32, + m_name.c_str(), jack_get_client_name(m_client), m_samplerate); + + int returnv; + + //SJackProcessCallback gets called when jack has new audio data to process + returnv = jack_set_process_callback(m_client, SJackProcessCallback, this); + if (returnv != 0) + { + LogError("Client \"%s\" error %i setting process callback: \"%s\"", + m_name.c_str(), returnv, GetErrno().c_str()); + return false; + } + + m_jackport = jack_port_register(m_client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + if (m_jackport == NULL) + { + Log("Error registering jack port: %s", GetErrno().c_str()); + return false; + } + + int error; + m_srcstate = src_new(SRC_SINC_FASTEST, 1, &error); + + //everything set up, activate + returnv = jack_activate(m_client); + if (returnv != 0) + { + LogError("Client \"%s\" error %i activating client: \"%s\"", + m_name.c_str(), returnv, GetErrno().c_str()); + return false; + } + + return true; +} + +void CJackClient::Disconnect() +{ + if (m_client) + { + //deactivate the client before everything else + int returnv = jack_deactivate(m_client); + if (returnv != 0) + LogError("Client \"%s\" error %i deactivating client: \"%s\"", + m_name.c_str(), returnv, GetErrno().c_str()); + + //close the jack client + returnv = jack_client_close(m_client); + if (returnv != 0) + LogError("Client \"%s\" error %i closing client: \"%s\"", + m_name.c_str(), returnv, GetErrno().c_str()); + + m_client = NULL; + } + + m_connected = false; + m_exitstatus = (jack_status_t)0; + m_samplerate = 0; + + delete[] m_buf; + m_buf = NULL; + m_bufsize = 0; + m_bufupdated = false; + + if (m_srcstate) + { + src_delete(m_srcstate); + m_srcstate = NULL; + } +} + +//returns true when the message has been sent or pipe is broken +//returns false when the message write needs to be retried +bool CJackClient::WriteMessage(uint8_t msg) +{ + if (m_pipe[1] == -1) + return true; //can't write + + int returnv = write(m_pipe[1], &msg, 1); + if (returnv == 1) + return true; //write successful + + if (returnv == -1 && errno != EAGAIN) + { + LogError("Client \"%s\" error writing msg %s to pipe: \"%s\"", m_name.c_str(), MsgToString(msg), GetErrno().c_str()); + if (errno != EINTR) + { + close(m_pipe[1]); + m_pipe[1] = -1; + return true; //pipe broken + } + } + + return false; //need to try again +} + +ClientMessage CJackClient::GetMessage() +{ + if (m_pipe[0] == -1) + return MsgNone; + + uint8_t msg; + int returnv = read(m_pipe[0], &msg, 1); + if (returnv == 1) + { + return (ClientMessage)msg; + } + else if (returnv == -1 && errno != EAGAIN) + { + LogError("Client \"%s\" error reading msg from pipe: \"%s\"", m_name.c_str(), GetErrno().c_str()); + if (errno != EINTR) + { + close(m_pipe[0]); + m_pipe[0] = -1; + } + } + + return MsgNone; +} + +int CJackClient::SJackProcessCallback(jack_nframes_t nframes, void *arg) +{ + ((CJackClient*)arg)->PJackProcessCallback(nframes); + return 0; +} + +void CJackClient::PJackProcessCallback(jack_nframes_t nframes) +{ + if (m_bufsize < nframes) + { + delete[] m_buf; + m_buf = new float[nframes]; + m_bufsize = nframes; + } + + CLock lock(m_condition); + + float* jackptr = (float*)jack_port_get_buffer(m_jackport, nframes); + + SRC_DATA srcdata = {}; + srcdata.data_in = jackptr; + srcdata.data_out = m_buf; + srcdata.input_frames = nframes; + srcdata.output_frames = nframes; + srcdata.src_ratio = (double)m_outsamplerate / m_samplerate; + + src_process(m_srcstate, &srcdata); + m_outsamples = srcdata.output_frames_gen; + + m_bufupdated = true; + lock.Leave(); + m_condition.Signal(); +} + +int CJackClient::GetAudio(float*& buf, int& bufsize, int& samplerate) +{ + CLock lock(m_condition); + m_condition.Wait(1000000, m_bufupdated, false); + + if (!m_bufupdated) + return 0; + + samplerate = m_outsamplerate; + + if (bufsize < m_outsamples) + { + bufsize = m_outsamples; + buf = (float*)realloc(buf, bufsize * sizeof(float)); + } + + memcpy(buf, m_buf, m_outsamples * sizeof(float)); + + m_bufupdated = false; + + return m_outsamples; +} + +void CJackClient::SJackInfoShutdownCallback(jack_status_t code, const char *reason, void *arg) +{ + ((CJackClient*)arg)->PJackInfoShutdownCallback(code, reason); +} + +void CJackClient::PJackInfoShutdownCallback(jack_status_t code, const char *reason) +{ + //save the exit code, this will be read from the loop in main() + //make sure reason is saved before code, to make it thread safe + //since main() will read m_exitstatus first, then m_exitreason if necessary + m_exitreason = reason; + m_exitstatus = code; + + //send message to the main loop + //try for one second to make sure it gets there + int64_t start = GetTimeUs(); + do + { + if (WriteMessage(MsgExited)) + return; + + USleep(100); //don't busy spin + } + while (GetTimeUs() - start < 1000000); + + LogError("Client \"%s\" unable to write exit msg to pipe", m_name.c_str()); +} + diff --git a/src/jackclient.h b/src/jackclient.h new file mode 100644 index 0000000..85812db --- /dev/null +++ b/src/jackclient.h @@ -0,0 +1,79 @@ +/* + * bobdsp + * Copyright (C) Bob 2012 + * + * bobdsp is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bobdsp 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef JACKCLIENT_H +#define JACKCLIENT_H + +#include +#include +#include +#include + +#include "fft.h" +#include "clientmessage.h" +#include "util/condition.h" + +class CJackClient +{ + public: + CJackClient(); + ~CJackClient(); + + bool Connect(); + void Disconnect(); + bool IsConnected() { return m_connected; } + int MsgPipe() { return m_pipe[0]; } + ClientMessage GetMessage(); + + jack_status_t ExitStatus() { return m_exitstatus; } + const std::string& ExitReason() { return m_exitreason; } + + int Samplerate() { return m_samplerate; } + int GetAudio(float*& buf, int& bufsize, int& samplerate); + + private: + bool m_connected; + bool m_wasconnected; + jack_client_t* m_client; + jack_port_t* m_jackport; + std::string m_name; + int m_samplerate; + int m_outsamplerate; + jack_status_t m_exitstatus; + std::string m_exitreason; + int m_portevents; + int m_pipe[2]; + CCondition m_condition; + float* m_buf; + unsigned int m_bufsize; + bool m_bufupdated; + SRC_STATE* m_srcstate; + int m_outsamples; + + bool ConnectInternal(); + void CheckMessages(); + bool WriteMessage(uint8_t message); + + static int SJackProcessCallback(jack_nframes_t nframes, void *arg); + void PJackProcessCallback(jack_nframes_t nframes); + + static void SJackInfoShutdownCallback(jack_status_t code, const char *reason, void *arg); + void PJackInfoShutdownCallback(jack_status_t code, const char *reason); +}; + +#endif //JACKCLIENT_H diff --git a/src/main.cpp b/src/main.cpp index d5f0854..77d2c30 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,7 +16,16 @@ * with this program. If not, see . */ +#include "bitvis.h" + int main (int argc, char *argv[]) { + CBitVis bitvis(argc, argv); + + bitvis.Setup(); + bitvis.Process(); + bitvis.Cleanup(); + + return 0; } diff --git a/src/util/condition.cpp b/src/util/condition.cpp new file mode 100644 index 0000000..74fdf2b --- /dev/null +++ b/src/util/condition.cpp @@ -0,0 +1,99 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include +#include +#include "condition.h" +#include "config.h" + +CCondition::CCondition() +{ + pthread_cond_init(&m_cond, NULL); +} + +CCondition::~CCondition() +{ + pthread_cond_destroy(&m_cond); +} + +void CCondition::Signal() +{ + pthread_cond_signal(&m_cond); +} + +void CCondition::Broadcast() +{ + pthread_cond_broadcast(&m_cond); +} + +//should have locked before getting here +bool CCondition::Wait(int64_t usecs /*= -1*/) +{ + assert(m_refcount > 0); //if refcount is 0, then the mutex is not locked + + //our mutex is created with PTHREAD_MUTEX_RECURSIVE, which doesn't work with condition variables + //if the mutex is locked multiple times + //we keep a refcount, then we unlock enough times so that the mutex is locked only once + int refcount = m_refcount; + while (m_refcount > 1) + Unlock(); + + int result = 0; + if (usecs < 0) + { + //pthread_cond_wait will unlock and lock the mutex + m_refcount--; + pthread_cond_wait(&m_cond, &m_mutex); + m_refcount++; + } + else + { + //get the current time + struct timespec currtime; +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME) + clock_gettime(CLOCK_REALTIME, &currtime); +#else + struct timeval tv; + gettimeofday(&tv, NULL); + currtime.tv_sec = tv.tv_sec; + currtime.tv_nsec = tv.tv_usec * 1000; +#endif + + //add the number of microseconds + + currtime.tv_sec += usecs / 1000000; + currtime.tv_nsec += (usecs % 1000000) * 1000; + + if (currtime.tv_nsec >= 1000000000) + { + currtime.tv_sec += currtime.tv_nsec / 1000000000; + currtime.tv_nsec %= 1000000000; + } + + //pthread_cond_timedwait will unlock and lock the mutex + m_refcount--; + result = pthread_cond_timedwait(&m_cond, &m_mutex, &currtime); + m_refcount++; + } + + while (m_refcount < refcount) + Lock(); + + return result == 0; +} diff --git a/src/util/condition.h b/src/util/condition.h new file mode 100644 index 0000000..89a8eda --- /dev/null +++ b/src/util/condition.h @@ -0,0 +1,69 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef CCONDITION +#define CCONDITION + +#include "inclstdint.h" + +#include "mutex.h" +#include "timeutils.h" + +//pthread condition variable class +class CCondition : public CMutex +{ + public: + CCondition(); + ~CCondition(); + + void Signal(); + void Broadcast(); + bool Wait(int64_t usecs = -1); + + template + bool Wait(int64_t usecs, TPredicate& predicate, TPredicate compval) + { + if (usecs < 0) + { + while (predicate == compval) + Wait(); + + return true; + } + else + { + int64_t now = GetTimeUs(); + int64_t end = now + usecs; + while (now <= end && predicate == compval) + { + Wait(end - now); + if (predicate != compval) + return true; + else + now = GetTimeUs(); + } + + return predicate != compval; + } + } + + private: + pthread_cond_t m_cond; +}; + +#endif //CCONDITION diff --git a/src/util/inclstdint.h b/src/util/inclstdint.h new file mode 100644 index 0000000..0b6804f --- /dev/null +++ b/src/util/inclstdint.h @@ -0,0 +1,39 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +//file to include stdint.h, __STDC_CONSTANT_MACROS and __STDC_LIMIT_MACROS +//need to be defined before it + +#ifndef INCLSTDINT_H + #define INCLSTDINT_H + + #ifndef __STDC_CONSTANT_MACROS + #define __STDC_CONSTANT_MACROS + #endif + + #ifndef __STDC_LIMIT_MACROS + #define __STDC_LIMIT_MACROS + #endif + + #ifndef __STDC_FORMAT_MACROS + #define __STDC_FORMAT_MACROS + #endif + + #include + #include +#endif diff --git a/src/util/lock.h b/src/util/lock.h new file mode 100644 index 0000000..66259da --- /dev/null +++ b/src/util/lock.h @@ -0,0 +1,62 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef CLOCK +#define CLOCK + +#include "mutex.h" +#include "condition.h" + +class CLock +{ + public: + CLock(CMutex& mutex) : m_mutex(mutex) + { + m_haslock = false; + Enter(); + } + + ~CLock() + { + Leave(); + } + + void Enter() + { + if (!m_haslock) + { + m_mutex.Lock(); + m_haslock = true; + } + } + + void Leave() + { + if (m_haslock) + { + m_mutex.Unlock(); + m_haslock = false; + } + } + + private: + CMutex& m_mutex; + bool m_haslock; +}; + +#endif //CLOCK \ No newline at end of file diff --git a/src/util/log.cpp b/src/util/log.cpp new file mode 100644 index 0000000..2e0ab0f --- /dev/null +++ b/src/util/log.cpp @@ -0,0 +1,216 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "mutex.h" +#include "lock.h" +#include "misc.h" + +using namespace std; + +bool g_logtostderr = true; +bool g_printlogtofile = true; +bool g_printdebuglevel = false; +string g_logfilename; + +//as pointers, so we can allocate these after the locked memory is set up +static CMutex* g_logmutex; +static ofstream* g_logfile; + +static int g_logbuffsize; //size of the buffer +static char* g_logbuff; //buffer for vsnprintf +static vector* g_logstrings; //we save any log lines here while the log isn't open + +//returns hour:minutes:seconds:microseconds +string GetStrTime() +{ + struct timeval tv; + struct tm time; + time_t now; + + //get current time + gettimeofday(&tv, NULL); + now = tv.tv_sec; //seconds since EPOCH + localtime_r(&now, &time); //convert to hours, minutes, seconds + + char buff[16]; + snprintf(buff, sizeof(buff), "%02i:%02i:%02i.%06i", time.tm_hour, time.tm_min, time.tm_sec, (int)tv.tv_usec); + + return buff; +} + +bool InitLog(string filename, ofstream& logfile) +{ + string homepath; + if (!GetHomePath(homepath)) + { + PrintError("Unable to get home directory path"); + return false; + } + + string directory = homepath + ".bitvis/"; + string fullpath = directory + filename; + + //try to make the directory the log goes in + if (mkdir(directory.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) + { + //if it already exists we're ok + if (errno != EEXIST) + { + PrintError("unable to make directory " + directory + ":\n" + GetErrno()); + return false; + } + } + + //we keep around 5 old logfiles + for (int i = 4; i > 0; i--) + rename(string(fullpath + ".old." + ToString(i)).c_str(), string(fullpath + ".old." + ToString(i + 1)).c_str()); + + rename(fullpath.c_str(), string(fullpath + ".old.1").c_str()); + + //open the logfile in append mode + logfile.open(fullpath.c_str()); + if (logfile.fail()) + { + PrintError("unable to open " + fullpath + ":\n" + GetErrno()); + return false; + } + + Log("successfully set up logfile %s", fullpath.c_str()); + + g_logfilename = fullpath; + + return true; +} + +//we only want the function name and the namespace, so we search for '(' and get the string before that +string PruneFunction(string function) +{ + size_t parenpos = function.find('('); + size_t spacepos = function.rfind(' ', parenpos); + + if (parenpos == string::npos) + return function; + else if (spacepos == string::npos) //when called from a constructor, there's no return value, thus no space + return function.substr(0, parenpos); + else + return function.substr(spacepos + 1, parenpos - spacepos - 1); +} + +void SetLogFile(std::string filename) +{ + if (!g_logmutex) + g_logmutex = new CMutex; + + CLock lock(*g_logmutex); + + if (!g_logfile) + g_logfile = new ofstream; + + //deallocate, so it will be allocated in PrintLog gain, but this time in locked memory + free(g_logbuff); + g_logbuff = NULL; + g_logbuffsize = 0; + + if (!InitLog(filename, *g_logfile)) + g_printlogtofile = false; +} + +void PrintLog (const char* fmt, const char* function, LogLevel loglevel, ...) +{ + if (loglevel == LogLevelDebug && !g_printdebuglevel) + return; + + if (g_logmutex) + g_logmutex->Lock(); + + string logstr; + string funcstr; + va_list args; + int nrspaces; + + va_start(args, loglevel); + + //print to the logbuffer and check if our buffer is large enough + int neededbuffsize = vsnprintf(g_logbuff, g_logbuffsize, fmt, args); + if (neededbuffsize + 1 > g_logbuffsize) + { + g_logbuffsize = neededbuffsize + 1; + g_logbuff = (char*)(realloc(g_logbuff, g_logbuffsize)); //resize the buffer to the needed size + + va_end(args); //need to reinit or we will crash + va_start(args, loglevel); + vsnprintf(g_logbuff, g_logbuffsize, fmt, args); //write to the buffer again + } + + va_end(args); + + if (loglevel == LogLevelError) + logstr += "ERROR: "; + else if (loglevel == LogLevelDebug) + logstr += "DEBUG: "; + + if (g_logbuff) + logstr += g_logbuff; + + funcstr = "(" + PruneFunction(function) + ")"; + nrspaces = 34 - funcstr.length(); + if (nrspaces > 0) + funcstr.insert(funcstr.length(), nrspaces, ' '); + + if (g_logfile && g_logfile->is_open() && g_printlogtofile) + { + //print any saved log lines + if (g_logstrings) + { + for (vector::iterator it = g_logstrings->begin(); it != g_logstrings->end(); it++) + *g_logfile << *it << flush; + + delete g_logstrings; + g_logstrings = NULL; + } + //write the string to the logfile + *g_logfile << GetStrTime() << " " << funcstr << " " << logstr << '\n' << flush; + } + else if (g_printlogtofile) + { + //save the log line if the log isn't open yet + if (!g_logstrings) + g_logstrings = new vector; + g_logstrings->push_back(GetStrTime() + " " + funcstr + " " + logstr + '\n'); + } + + //print to stdout when requested + if (g_logtostderr) + cerr << funcstr << logstr << '\n' << flush; + + if (g_logmutex) + g_logmutex->Unlock(); +} diff --git a/src/util/log.h b/src/util/log.h new file mode 100644 index 0000000..67d6b01 --- /dev/null +++ b/src/util/log.h @@ -0,0 +1,44 @@ +/* + * bobdsp + * Copyright (C) Bob 2009 + * + * bobdsp is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bobdsp 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef LOG +#define LOG + +#include + +enum LogLevel +{ + LogLevelBasic, + LogLevelError, + LogLevelDebug, +}; + +//this has to be a macro, because we want __PRETTY_FUNCTION__ +#define Log(fmt, ...) PrintLog(fmt, __PRETTY_FUNCTION__, LogLevelBasic, ##__VA_ARGS__) +#define LogError(fmt, ...) PrintLog(fmt, __PRETTY_FUNCTION__, LogLevelError, ##__VA_ARGS__) +#define LogDebug(fmt, ...) g_printdebuglevel ? PrintLog(fmt, __PRETTY_FUNCTION__, LogLevelDebug, ##__VA_ARGS__) : (void)0 + +void PrintLog (const char* fmt, const char* function, LogLevel loglevel, ...) __attribute__ ((format (printf, 1, 4))); +void SetLogFile(std::string logfile); + +extern bool g_logtostderr; +extern bool g_printlogtofile; +extern bool g_printdebuglevel; +extern std::string g_logfilename; + +#endif //LOG diff --git a/src/util/misc.cpp b/src/util/misc.cpp new file mode 100644 index 0000000..751f7f7 --- /dev/null +++ b/src/util/misc.cpp @@ -0,0 +1,209 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include "misc.h" + +using namespace std; + +namespace UTILNAMESPACE +{ + void PrintError(const std::string& error) + { + std::cerr << "ERROR: " << error << "\n"; + } + + //get the first word (separated by whitespace) from string data and place that in word + //then remove that word from string data + bool GetWord(string& data, string& word) + { + stringstream datastream(data); + string end; + + datastream >> word; + if (datastream.fail()) + { + data.clear(); + return false; + } + + size_t pos = data.find(word) + word.length(); + + if (pos >= data.length()) + { + data.clear(); + return true; + } + + data = data.substr(pos); + + datastream.clear(); + datastream.str(data); + + datastream >> end; + if (datastream.fail()) + data.clear(); + + return true; + } + + //convert . or , to the current locale for correct conversion of ascii float + void ConvertFloatLocale(std::string& strfloat) + { + static struct lconv* locale = localeconv(); + + size_t pos = strfloat.find_first_of(",."); + + while (pos != string::npos) + { + strfloat.replace(pos, 1, 1, *locale->decimal_point); + pos++; + + if (pos >= strfloat.size()) + break; + + pos = strfloat.find_first_of(",.", pos); + } + } + + bool GetHomePath(std::string& homepath) + { + const char* homeptr = getenv("HOME"); + if (homeptr && strlen(homeptr) > 0) + { + homepath = PutSlashAtEnd(homeptr); + return true; + } + else + { + return false; + } + } + + std::string PutSlashAtEnd(const std::string& path) + { + if (path.empty()) + return "/"; + else if (path[path.length() - 1] != '/') + return path + '/'; + else + return path; + } + + std::string RemoveSlashAtEnd(const std::string& path) + { + if (path.length() > 0 && path[path.length() - 1] == '/') + return path.substr(0, path.length() - 1); + else + return path; + } + + std::string PutSlashAtStart(const std::string& path) + { + if (path.empty()) + return "/"; + else if (path[0] != '/') + return string("/") + path; + else + return path; + } + + std::string FileNameExtension(const std::string& path) + { + size_t pos = path.rfind('.'); + if (pos == string::npos || pos >= path.length() - 1) + return ""; + else + return path.substr(pos + 1); + } + + std::string ToLower(const std::string& in) + { + string out; + for (string::const_iterator it = in.begin(); it != in.end(); it++) + out += (char)tolower(*it); + + return out; + } + + bool StrToBool(const std::string& data, bool& value) + { + std::string data2 = data; + std::string word; + if (!GetWord(data2, word)) + return false; + + for (std::string::iterator it = word.begin(); it != word.end(); it++) + *it = tolower(*it); + + if (word == "1" || word == "true" || word == "on" || word == "yes") + { + value = true; + return true; + } + else if (word == "0" || word == "false" || word == "off" || word == "no") + { + value = false; + return true; + } + else + { + int ivalue; + if (StrToInt(word, ivalue)) + { + value = ivalue != 0; + return true; + } + } + + return false; + } + + //returns < 0 if a directory outside the root is accessed through .. + int DirLevel(const std::string& url) + { + size_t start = 0; + size_t pos; + + int level = 0; + while (1) + { + pos = url.find('/', start); + + string filename; + if (pos == string::npos) + filename = url.substr(start); + else if (pos > start) + filename = url.substr(start, pos - start); + + if (filename == "..") + level--; + else if (!filename.empty() && filename != ".") + level++; + + if (level < 0 || pos == string::npos || pos == url.length() - 1) + return level; + + start = pos + 1; + } + } +} diff --git a/src/util/misc.h b/src/util/misc.h new file mode 100644 index 0000000..80254f4 --- /dev/null +++ b/src/util/misc.h @@ -0,0 +1,184 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef MISCUTIL +#define MISCUTIL + +#include "inclstdint.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace UTILNAMESPACE +{ + void PrintError(const std::string& error); + bool GetWord(std::string& data, std::string& word); + void ConvertFloatLocale(std::string& strfloat); + bool GetHomePath(std::string& homepath); + std::string PutSlashAtEnd(const std::string& path); + std::string RemoveSlashAtEnd(const std::string& path); + std::string PutSlashAtStart(const std::string& path); + std::string FileNameExtension(const std::string& path); + std::string ToLower(const std::string& in); + bool StrToBool(const std::string& data, bool& value); + int DirLevel(const std::string& url); + + template + inline std::string ToString(Value value) + { + std::string data; + std::stringstream valuestream; + valuestream << value; + valuestream >> data; + return data; + } + + inline std::string ToString(bool value) + { + if (value) + return "true"; + else + return "false"; + } + + inline std::string GetErrno() + { + return strerror(errno); + } + + inline std::string GetErrno(int err) + { + return strerror(err); + } + + template + inline A Clamp(A value, B min, C max) + { + return value < max ? (value > min ? value : min) : max; + } + + template + inline A Max(A value1, B value2) + { + return value1 > value2 ? value1 : value2; + } + + template + inline A Max(A value1, B value2, C value3) + { + return (value1 > value2) ? (value1 > value3 ? value1 : value3) : (value2 > value3 ? value2 : value3); + } + + template + inline A Min(A value1, B value2) + { + return value1 < value2 ? value1 : value2; + } + + template + inline A Min(A value1, B value2, C value3) + { + return (value1 < value2) ? (value1 < value3 ? value1 : value3) : (value2 < value3 ? value2 : value3); + } + + template + inline T Abs(T value) + { + return value > 0 ? value : -value; + } + + template + inline A Round(B value) + { + if (value == 0.0) + { + return 0; + } + else if (value > 0.0) + { + return (A)(value + 0.5); + } + else + { + return (A)(value - 0.5); + } + } + + inline int32_t Round32(float value) + { + return lroundf(value); + } + + inline int32_t Round32(double value) + { + return lround(value); + } + + inline int64_t Round64(float value) + { + return llroundf(value); + } + + inline int64_t Round64(double value) + { + return llround(value); + } + + inline bool StrToInt(const std::string& data, int32_t& value) + { + return sscanf(data.c_str(), "%" PRIi32, &value) == 1; + } + + inline bool StrToInt(const std::string& data, int64_t& value) + { + return sscanf(data.c_str(), "%" PRIi64, &value) == 1; + } + + inline bool HexStrToInt(const std::string& data, int32_t& value) + { + return sscanf(data.c_str(), "%" PRIx32, &value) == 1; + } + + inline bool HexStrToInt(const std::string& data, int64_t& value) + { + return sscanf(data.c_str(), "%" PRIx64, &value) == 1; + } + + inline bool StrToFloat(const std::string& data, float& value) + { + return sscanf(data.c_str(), "%f", &value) == 1; + } + + inline bool StrToFloat(const std::string& data, double& value) + { + return sscanf(data.c_str(), "%lf", &value) == 1; + } +} + +using namespace UTILNAMESPACE; + +#endif //MISCUTIL diff --git a/src/util/mutex.cpp b/src/util/mutex.cpp new file mode 100644 index 0000000..ed54127 --- /dev/null +++ b/src/util/mutex.cpp @@ -0,0 +1,70 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include +#include "mutex.h" + +CMutex::CMutex() +{ + //make a recursive mutex + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&m_mutex, &attr); + + pthread_mutexattr_destroy(&attr); + + m_refcount = 0; +} + +CMutex::~CMutex() +{ + pthread_mutex_destroy(&m_mutex); +} + +void CMutex::Unlock() +{ + m_refcount--; + assert(m_refcount >= 0); + pthread_mutex_unlock(&m_mutex); +} + +bool CMutex::TryLock() +{ + if (pthread_mutex_trylock(&m_mutex) == 0) + { + m_refcount++; + assert(m_refcount > 0); + return true; + } + else + { + return false; + } +} + +bool CMutex::Lock() +{ + pthread_mutex_lock(&m_mutex); + m_refcount++; + assert(m_refcount > 0); + return true; +} + diff --git a/src/util/mutex.h b/src/util/mutex.h new file mode 100644 index 0000000..c1e5112 --- /dev/null +++ b/src/util/mutex.h @@ -0,0 +1,40 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef CMUTEX +#define CMUTEX + +#include "inclstdint.h" + +#include + +class CMutex +{ + public: + CMutex(); + ~CMutex(); + bool Lock(); + void Unlock(); + bool TryLock(); + + protected: + pthread_mutex_t m_mutex; + int m_refcount; +}; + +#endif //CMUTEX diff --git a/src/util/timeutils.cpp b/src/util/timeutils.cpp new file mode 100644 index 0000000..b5f67f5 --- /dev/null +++ b/src/util/timeutils.cpp @@ -0,0 +1,56 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "timeutils.h" + +void USleep(int64_t usecs, volatile bool* stop /*= NULL*/) +{ + if (usecs <= 0) + { + return; + } + else if (stop && usecs > 1000000) //when a pointer to a bool is passed, check it every second and stop when it's true + { + int64_t now = GetTimeUs(); + int64_t end = now + usecs; + + while (now < end) + { + struct timespec sleeptime = {}; + + if (*stop) + return; + else if (end - now >= 1000000) + sleeptime.tv_sec = 1; + else + sleeptime.tv_nsec = ((end - now) % 1000000) * 1000; + + nanosleep(&sleeptime, NULL); + now = GetTimeUs(); + } + } + else + { + struct timespec sleeptime; + sleeptime.tv_sec = usecs / 1000000; + sleeptime.tv_nsec = (usecs % 1000000) * 1000; + + nanosleep(&sleeptime, NULL); + } +} + diff --git a/src/util/timeutils.h b/src/util/timeutils.h new file mode 100644 index 0000000..07d2d88 --- /dev/null +++ b/src/util/timeutils.h @@ -0,0 +1,49 @@ +/* + * boblight + * Copyright (C) Bob 2009 + * + * boblight is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * boblight 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. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TIMEUTILS +#define TIMEUTILS + +#include "inclstdint.h" +#include "config.h" + +#include +#include + +inline int64_t GetTimeUs() +{ +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + return ((int64_t)time.tv_sec * 1000000LL) + (int64_t)(time.tv_nsec + 500) / 1000LL; +#else + struct timeval time; + gettimeofday(&time, NULL); + return ((int64_t)time.tv_sec * 1000000LL) + (int64_t)time.tv_usec; +#endif +} + +template +inline T GetTimeSec() +{ + return (T)GetTimeUs() / (T)1000000.0; +} + +void USleep(int64_t usecs, volatile bool* stop = NULL); + +#endif //TIMEUTILS diff --git a/wscript b/wscript index 379b2a4..e42c6bd 100644 --- a/wscript +++ b/wscript @@ -16,10 +16,15 @@ def configure(conf): conf.load('compiler_cxx') conf.check(header_name='jack/jack.h') + conf.check(header_name='fftw3.h') + conf.check(header_name='samplerate.h') - conf.check(lib='pthread', uselib_store='pthread', mandatory=False) - conf.check(lib='m', uselib_store='m', mandatory=False) + conf.check(lib='fftw3', uselib_store='fftw3') + conf.check(lib='fftw3f', uselib_store='fftw3f') conf.check(lib='jack', uselib_store='jack') + conf.check(lib='samplerate', uselib_store='samplerate') + conf.check(lib='m', uselib_store='m', mandatory=False) + conf.check(lib='pthread', uselib_store='pthread', mandatory=False) conf.check(function_name='clock_gettime', header_name='time.h', mandatory=False) conf.check(function_name='clock_gettime', header_name='time.h', lib='rt', uselib_store='rt', mandatory=False, @@ -28,8 +33,16 @@ def configure(conf): conf.write_config_header('config.h') def build(bld): - bld.program(source='src/main.cpp', - use=['m','pthread','rt', 'jack'], + bld.program(source='src/main.cpp\ + src/bitvis.cpp\ + src/jackclient.cpp\ + src/util/log.cpp\ + src/util/misc.cpp\ + src/util/mutex.cpp\ + src/util/timeutils.cpp\ + src/util/condition.cpp\ + src/fft.cpp', + use=['m','pthread','rt', 'jack', 'fftw3', 'fftw3f', 'samplerate'], includes='./src', cxxflags='-Wall -g -DUTILNAMESPACE=BitVisUtil', target='bitvis')