aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--maximilian.cpp576
-rwxr-xr-xmaximilian.h93
-rw-r--r--maximilian_examples/21.Recording.cpp9
3 files changed, 385 insertions, 293 deletions
diff --git a/maximilian.cpp b/maximilian.cpp
index 350a258..9f847cb 100644
--- a/maximilian.cpp
+++ b/maximilian.cpp
@@ -2003,259 +2003,325 @@ void maxiSampler::trigger() {
}
-/////*************************************************************
-/////
-///// Init all variables
-/////
-/////*************************************************************
-//maxiRecorder::maxiRecorder() :
-// bufferSize(maxiSettings::sampleRate * 2),
-// bufferQueueSize(3),
-// bufferIndex(0),
-// doRecord(true),
-// recordedAmountFrames(0)
-//{
-//
-//}
-//
-/////*************************************************************
-/////
-///// Free resources in RAII manner
-/////
-/////*************************************************************
-//maxiRecorder::~maxiRecorder()
-//{
-// if (isRecording()) saveToWav();
-// freeResources();
-//}
-//
-/////*************************************************************
-/////
-///// Free resources if they exist
-/////
-/////*************************************************************
-////void maxiRecorder::freeResources()
-////{
-//// if (savedBuffers.size() > 0)
-//// {
-//// for (int i = 0; i < savedBuffers.size(); ++i)
-//// {
-//// delete[] savedBuffers.front();
-//// savedBuffers.pop();
-//// }
-//// }
-////}
-//
-/////*************************************************************
-/////
-///// get if we are recording or not
-/////
-/////*************************************************************
-//bool maxiRecorder::isRecording() const
-//{
-// return doRecord;
-//}
-//
-/////*************************************************************
-/////
-///// get if we are recording or not
-/////
-/////*************************************************************
-//void maxiRecorder::setup(std::string _filename)
-//{
-// filename = _filename;
-//}
-//
-/////*************************************************************
-/////
-///// This whacks the update method into a detached thread which
-///// will ensure there are enough buffers available in the queue.
-/////
-/////*************************************************************
-////void maxiRecorder::startRecording()
-////{
-//// for (int i = 0; i < bufferQueueSize; ++i)
-//// {
-//// enqueueBuffer();
-//// }
-////
-//// std::thread daemon(&maxiRecorder::update, this);
-//// daemon.detach();
-////}
-//
-/////*************************************************************
-/////
-///// stops the recording thread by allowing the stack to unwind
-///// in update()
-/////
-/////*************************************************************
-//void maxiRecorder::stopRecording()
-//{
-// doRecord = false;
-//}
-//
-/////*************************************************************
-/////
-///// Shamelessly hacked together largely using this link
-///// http://stackoverflow.com/questions/22226872/two-problems-
-///// when-writing-to-wav-c
-/////
-/////*************************************************************
-//template <typename T>
-//void maxiRecorder::write(std::ofstream& _stream, const T& _t) {
-// _stream.write((const char*)&_t, sizeof(T));
-//}
-//
-//void maxiRecorder::saveToWav()
-//{
-// if (isRecording()) stopRecording();
-// std::vector<double> pcmData = getProcessedData();
-// std::vector<short> pcmDataInt;
-//
-// pcmDataInt.resize(pcmData.size());
-//
-// for (int i = 0; i < pcmData.size(); ++i)
-// pcmDataInt[i] = (short) (pcmData[i] * 32767);
-//
-// int sampleRate = maxiSettings::sampleRate;
-// short channels = maxiSettings::channels;
-// int buffSize = pcmDataInt.size() * 2;
-//
-// std::ofstream stream(filename.c_str(), std::ios::binary);
-//
-// /* Header */
-// stream.write("RIFF", 4);
-// write<int>(stream, 36 + buffSize);
-// stream.write("WAVE", 4);
-//
-// /* Format Chunk */
-// stream.write("fmt ", 4);
-// write<int>(stream, 16);
-// write<short>(stream, 1);
-// write<short>(stream, channels);
-// write<int>(stream, sampleRate);
-// write<int>(stream, sampleRate * channels * sizeof(short));
-// write<short>(stream, channels * sizeof(short));
-// write<short>(stream, 8 * sizeof(short));
-//
-// /* Data Chunk */
-// stream.write("data", 4);
-// stream.write((const char*) &buffSize, 4);
-// stream.write((const char*) pcmDataInt.data(), buffSize);
-//
-// std::cout << "Wrote " << buffSize
-// << " bytes to " << filename.c_str()
-// << std::endl;
-//}
-//
-/////*************************************************************
-/////
-///// This takes the queue of user generated arrays and whacks
-///// it into a vector of doubles which is easier to transverse
-///// and work with.
-/////
-/////*************************************************************
-//std::vector<double> maxiRecorder::getProcessedData()
-//{
-// std::vector<double> userData;
-// int dataSize = savedBuffers.size() * bufferSize;
-// userData.resize(dataSize);
-//
-// int savedIndex = 0;
-// for (int i = 0; i < dataSize; ++i)
-// {
-// if (i > 0 && i % bufferSize == 0)
-// {
-// savedIndex = 0;
-// savedBuffers.pop();
-// }
-//
-// userData[i] = savedBuffers.front()[savedIndex];
-// ++savedIndex;
-// }
-//
-// double lastFrame = userData[userData.size() - 1];
-// while (lastFrame == 0)
-// {
-// userData.pop_back();
-// lastFrame = userData[userData.size() - 1];
-// }
-//
-// return userData;
-//}
-//
-/////*************************************************************
-/////
-///// You don't need to worry about this, this runs in a
-///// detached thread after calling startRecording() and means
-///// we can allocate new memory without interupting the real
-///// time audio callback
-/////
-/////*************************************************************
-//void maxiRecorder::update()
-//{
-// auto ms = std::chrono::milliseconds((long) (1000. / bufferSize / maxiSettings::sampleRate));
-// while (doRecord)
-// {
-// while (bufferQueueSize > bufferQueue.size())
-// {
-// enqueueBuffer();
-// }
-// std::this_thread::sleep_for(ms);
-// }
-// std::cout << "Finishing Recording..." << std::endl;
-//}
-//
-/////*************************************************************
-/////
-///// Pass the buffer of audio to this method and it will write
-///// it to the right place. This method is real-time safe and
-///// is overriden for ofx's / port's floats array or maximilian's
-///// / rtaudio's double
-/////
-/////*************************************************************
-//void maxiRecorder::passData(double* _in, int _inBufferSize)
-//{
-// for (int i = 0; i < _inBufferSize; ++i)
-// {
-// if (bufferIndex >= bufferSize)
-// {
-// bufferQueue.pop();
-// bufferIndex = 0;
-// }
-// bufferQueue.front()[bufferIndex] = _in[i];
-// ++bufferIndex;
-// ++recordedAmountFrames;
-// }
-//}
-//void maxiRecorder::passData(float* _in, int _inBufferSize)
-//{
-// for (int i = 0; i < _inBufferSize; ++i)
-// {
-// if (bufferIndex >= bufferSize)
-// {
-// bufferQueue.pop();
-// bufferIndex = 0;
-// }
-// bufferQueue.front()[bufferIndex] = (double) _in[i];
-// ++bufferIndex;
-// ++recordedAmountFrames;
-// }
-//}
-//
-/////*************************************************************
-/////
-///// The method to instanciate new memory and ensure the correct
-///// structures have the correct addresses.
-/////
-/////*************************************************************
-//void maxiRecorder::enqueueBuffer()
-//{
-// double* b = new double[bufferSize];
-// bufferQueue.push(b);
-// savedBuffers.push(b);
-//}
-//
-//
-//
+///*************************************************************
+///
+/// Init most variables
+///
+///*************************************************************
+maxiRecorder::maxiRecorder() :
+bufferSize(maxiSettings::sampleRate * 2),
+bufferQueueSize(3),
+bufferIndex(0),
+recordedAmountFrames(0),
+threadRunning(false)
+{
+
+}
+
+///*************************************************************
+///
+/// Free resources in RAII manner and save to wav when this
+/// object lifetime is up!
+///
+///*************************************************************
+maxiRecorder::~maxiRecorder()
+{
+ if (isRecording()) saveToWav();
+ freeResources();
+}
+
+///*************************************************************
+///
+/// Free resources only if they exist; may be called multiple
+/// times in it's lifetime becasuse of destructor.
+///
+///*************************************************************
+void maxiRecorder::freeResources()
+{
+ if (savedBuffers.size() > 0)
+ {
+ for (int i = 0; i < savedBuffers.size(); ++i)
+ {
+ delete[] savedBuffers.front();
+ savedBuffers.pop();
+ }
+ }
+}
+
+///*************************************************************
+///
+/// Simply return if we are recording or not
+///
+///*************************************************************
+bool maxiRecorder::isRecording() const
+{
+ return doRecord;
+}
+
+///*************************************************************
+///
+/// Set the filename (and path)
+///
+///*************************************************************
+void maxiRecorder::setup(std::string _filename)
+{
+ filename = _filename;
+}
+
+///*************************************************************
+///
+/// You don't need to worry calling about this, this runs in a
+/// detached thread after calling startRecording() and means
+/// we can allocate new memory without interupting the real
+/// time audio callback. Rather than have multiple ifdefs to
+/// split up the same function to use different OS's threading
+/// services I just redefined the thing because it looked
+/// cleaner.
+///
+/// A pointer to the instanciated object from this class is
+/// passed as '_context'. We do this because we are calling this
+/// function from a static helper func and need to access object
+/// specific variables.
+///
+///*************************************************************
+#if defined(OS_IS_UNIX)
+void* maxiRecorder::update(void* _context)
+{
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+ maxiRecorder* _this = static_cast<maxiRecorder*>(_context);
+ _this->threadRunning = true;
+ while (_this->doRecord)
+ {
+ usleep((long)(10000. / bufferSize / maxiSettings::sampleRate));
+ while (_this->bufferQueueSize > _this->bufferQueue.size())
+ {
+ _this->enqueueBuffer();
+ }
+ }
+ std::cout << "\nFinishing Recording..." << std::endl;
+ return 0;
+}
+#elif defined(OS_IS_WIN)
+void* maxiRecorder::update(void* _context)
+{
+ maxiRecorder* _this = static_cast<maxiRecorder*>(_context);
+ _this->threadRunning = true;
+ long time = (long)(1000. / bufferSize / maxiSettings::sampleRate);
+ while (_this->doRecord)
+ {
+ Sleep(time);
+ while (_this->bufferQueueSize > _this->bufferQueue.size())
+ {
+ _this->enqueueBuffer();
+ }
+ }
+ std::cout << "\nFinishing Recording..." << std::endl;
+ return 0;
+}
+#endif
+
+///*************************************************************
+///
+/// This whacks the update method into a detached thread which
+/// will ensure there are enough buffers available in the queue.
+///
+///*************************************************************
+void maxiRecorder::startRecording()
+{
+ doRecord = true;
+ for (int i = 0; i < bufferQueueSize; ++i)
+ {
+ enqueueBuffer();
+ }
+
+#if defined(OS_IS_UNIX)
+ if (pthread_create(&daemon, NULL, &maxiRecorder::update_pthread_helper, this) != 0)
+ std::cout << "pthread created incorrectly" << std::endl;
+ if (pthread_detach(daemon) != 0)
+ std::cout << "pthread detached incorrectly" << std::endl;
+#elif defined(OS_IS_WIN)
+ unsigned threadID;
+ daemonHandle = (HANDLE) _beginthreadex(NULL, 0, &maxiRecorder::update_pthread_helper, (void *) this, 0, &threadID);
+#endif
+
+
+}
+
+///*************************************************************
+///
+/// stops the recording thread by allowing the stack to unwind
+/// in update() and stops the Windows thread if necessary
+///
+///*************************************************************
+void maxiRecorder::stopRecording()
+{
+#if defined(OS_IS_WIN)
+ CloseHandle(daemonHandle);
+#endif
+ doRecord = false;
+}
+
+///*************************************************************
+///
+/// Shamelessly hacked together largely using this link
+/// http://stackoverflow.com/questions/22226872/two-problems-
+/// when-writing-to-wav-c
+///
+/// I haven't bothered to change the syntax much here and the
+/// templated write function was too convenient during my
+/// struggle to get my head around wav headers so I've kept it
+/// very similar to the attached code.
+///
+/// A clear optimisation would be to make getProcessedData()
+/// return a vector of shorts to prevent another loop
+///
+///*************************************************************
+template <typename T>
+void maxiRecorder::write(std::ofstream& _stream, const T& _t) {
+ _stream.write((const char*)&_t, sizeof(T));
+}
+
+void maxiRecorder::saveToWav()
+{
+ if (isRecording()) stopRecording();
+ if (threadRunning)
+ {
+#if defined(OS_IS_UNIX)
+ pthread_cancel(daemon);
+#endif
+ threadRunning = false;
+ }
+ std::vector<double> pcmData = getProcessedData();
+ std::vector<short> pcmDataInt;
+
+ pcmDataInt.resize(pcmData.size());
+
+ for (int i = 0; i < pcmData.size(); ++i)
+ pcmDataInt[i] = (short) (pcmData[i] * 32767);
+
+ int sampleRate = maxiSettings::sampleRate;
+ short channels = maxiSettings::channels;
+ int buffSize = pcmDataInt.size() * 2;
+
+ std::ofstream stream(filename.c_str(), std::ios::binary);
+
+ if (stream.is_open())
+ {
+ /* Header */
+ stream.write("RIFF", 4);
+ write<int>(stream, 36 + buffSize);
+ stream.write("WAVE", 4);
+
+ /* Format Chunk */
+ stream.write("fmt ", 4);
+ write<int>(stream, 16);
+ write<short>(stream, 1);
+ write<short>(stream, channels);
+ write<int>(stream, sampleRate);
+ write<int>(stream, sampleRate * channels * sizeof(short));
+ write<short>(stream, channels * sizeof(short));
+ write<short>(stream, 8 * sizeof(short));
+
+ /* Data Chunk */
+ stream.write("data", 4);
+ stream.write((const char*) &buffSize, 4);
+ stream.write((const char*) pcmDataInt.data(), buffSize);
+
+ stream.close();
+
+ std::cout << "Wrote " << buffSize
+ << " bytes to " << filename.c_str()
+ << std::endl;
+ }
+ else
+ {
+ std::cerr << "Failed to open file: " << strerror(errno) << std::endl;
+ }
+}
+
+///*************************************************************
+///
+/// This takes the queue of user generated arrays and whacks
+/// it into a vector of doubles which is easier to transverse
+/// and work with.
+///
+///*************************************************************
+std::vector<double> maxiRecorder::getProcessedData()
+{
+ std::vector<double> userData;
+ int dataSize = savedBuffers.size() * bufferSize;
+ userData.resize(dataSize);
+
+ int savedIndex = 0;
+ for (int i = 0; i < dataSize; ++i)
+ {
+ if (i > 0 && i % bufferSize == 0)
+ {
+ savedIndex = 0;
+ savedBuffers.pop();
+ }
+
+ userData[i] = savedBuffers.front()[savedIndex];
+ ++savedIndex;
+ }
+
+ double lastFrame = userData[userData.size() - 1];
+ while (lastFrame == 0)
+ {
+ userData.pop_back();
+ lastFrame = userData[userData.size() - 1];
+ }
+
+ return userData;
+}
+
+///*************************************************************
+///
+/// Pass the buffer of audio to this method and it will write
+/// it to the right place. This method is real-time safe and
+/// is overriden for ofx's / port's floats array or maximilian's
+/// / rtaudio's double
+///
+///*************************************************************
+void maxiRecorder::passData(double* _in, int _inBufferSize)
+{
+ for (int i = 0; i < _inBufferSize; ++i)
+ {
+ if (bufferIndex >= bufferSize)
+ {
+ bufferQueue.pop();
+ bufferIndex = 0;
+ }
+ bufferQueue.front()[bufferIndex] = _in[i];
+ ++bufferIndex;
+ ++recordedAmountFrames;
+ }
+}
+void maxiRecorder::passData(float* _in, int _inBufferSize)
+{
+ for (int i = 0; i < _inBufferSize; ++i)
+ {
+ if (bufferIndex >= bufferSize)
+ {
+ bufferQueue.pop();
+ bufferIndex = 0;
+ }
+ bufferQueue.front()[bufferIndex] = (double) _in[i];
+ ++bufferIndex;
+ ++recordedAmountFrames;
+ }
+}
+
+///*************************************************************
+///
+/// The method to instanciate new memory and ensure the correct
+/// structures have the correct addresses.
+///
+///*************************************************************
+void maxiRecorder::enqueueBuffer()
+{
+ double* b = new double[bufferSize];
+ bufferQueue.push(b);
+ savedBuffers.push(b);
+}
diff --git a/maximilian.h b/maximilian.h
index 6cb7628..d2db173 100755
--- a/maximilian.h
+++ b/maximilian.h
@@ -36,14 +36,26 @@
//#define MAXIMILIAN_PORTAUDIO
#define MAXIMILIAN_RT_AUDIO
-
#include <iostream>
#include <fstream>
#include <string.h>
#include <cstdlib>
#include "math.h"
+#include <cerrno>
+#include <queue>
+#include <vector>
+
+#if !defined(_WIN32) && (defined(unix) || defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
+#define OS_IS_UNIX true
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
#ifdef _WIN32 //|| _WIN64
+#define OS_IS_WIN true
#include <algorithm>
+#include <Windows.h>
+#include <process.h>
#endif
using namespace std;
@@ -795,39 +807,50 @@ public:
bool tick;
};
-//
-//class maxiRecorder
-//{
-//public:
-// maxiRecorder();
-// ~maxiRecorder();
-//
-// void setup(std::string _filename);
-// void startRecording();
-// void stopRecording();
-// bool isRecording() const;
-// void passData(double* _in, int _inBufferSize);
-// void passData(float* _in, int _inBufferSize);
-// void saveToWav();
-//
-//protected:
-//// std::vector<double> getProcessedData();
-// void update();
-// void enqueueBuffer();
-// void freeResources();
-//
-//private:
-// template <typename T>
-// void write(std::ofstream& _stream, const T& _t);
-// const int bufferQueueSize;
-// const int bufferSize;
-// long int bufferIndex;
-// long int recordedAmountFrames;
-//// std::queue<double*> bufferQueue;
-//// std::queue<double*> savedBuffers;
-// bool doRecord;
-// std::string filename;
-//};
-//
+
+class maxiRecorder
+{
+public:
+ maxiRecorder();
+ ~maxiRecorder();
+
+ void setup(std::string _filename);
+ void startRecording();
+ void stopRecording();
+ bool isRecording() const;
+ void passData(double* _in, int _inBufferSize);
+ void passData(float* _in, int _inBufferSize);
+ void saveToWav();
+
+private:
+ template <typename T>
+ void write(std::ofstream& _stream, const T& _t);
+ void* update(void* _context);
+ std::vector<double> getProcessedData();
+ void enqueueBuffer();
+ void freeResources();
+ bool threadRunning;
+ const int bufferQueueSize;
+ const int bufferSize;
+ long int bufferIndex;
+ long int recordedAmountFrames;
+ std::queue<double*> bufferQueue;
+ std::queue<double*> savedBuffers;
+ bool doRecord;
+ std::string filename;
+#if defined(OS_IS_UNIX)
+ pthread_t daemon;
+ static void* update_pthread_helper(void* _context)
+#elif defined(OS_IS_WIN)
+ HANDLE daemonHandle;
+ static unsigned __stdcall
+ update_pthread_helper(void* _context)
+#endif
+ {
+ maxiRecorder* _this = static_cast<maxiRecorder*>(_context);
+ _this->update(_this);
+ return 0;
+ }
+};
#endif
diff --git a/maximilian_examples/21.Recording.cpp b/maximilian_examples/21.Recording.cpp
index 82e0e68..27c9bca 100644
--- a/maximilian_examples/21.Recording.cpp
+++ b/maximilian_examples/21.Recording.cpp
@@ -19,9 +19,12 @@ void setup() {
// Call setup here, make sure you do this so the recorder
// knows where to write the file. Currently the recorder
// will write the wav file to the directory that this file
- // is in but you can pass any absolute file path in you'd
- // like to.
- recorder.setup("hey.wav");
+ // is in if you use linux but with mac and windows I
+ // strongly reccomend putting an absolute file path to the
+ // directory you want to write to. Also, when in Windows,
+ // remember to do double '\' characters because they
+ // count as an escape which will nullify any path you write
+ recorder.setup("lovesong.wav");
// This must be called to start the asynchronous thread to
// manage the recorder's internal memory