diff options
| author | Leon Fedden <lfedd001@gold.ac.uk> | 2016-10-11 10:47:28 +0100 |
|---|---|---|
| committer | Leon Fedden <lfedd001@gold.ac.uk> | 2016-10-11 10:47:28 +0100 |
| commit | 3a48a1708930139c806b5b3a14ca57b8ad2a3a41 (patch) | |
| tree | 168e72b721f5245ddd65bf17022aa5bf35b71e2f | |
| parent | 7f4bec02beedeb71725c65191b8d76f24b56b9a3 (diff) | |
adding maxiRecorder
| -rw-r--r-- | maximilian.cpp | 253 | ||||
| -rwxr-xr-x | maximilian.h | 33 | ||||
| -rw-r--r-- | maximilian_examples/21.Recording.cpp | 54 |
3 files changed, 340 insertions, 0 deletions
diff --git a/maximilian.cpp b/maximilian.cpp index 8f358f7..9c45515 100644 --- a/maximilian.cpp +++ b/maximilian.cpp @@ -2003,6 +2003,259 @@ 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); +} + diff --git a/maximilian.h b/maximilian.h index 5f1d2f7..d7d8558 100755 --- a/maximilian.h +++ b/maximilian.h @@ -796,5 +796,38 @@ public: }; +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; +}; + #endif diff --git a/maximilian_examples/21.Recording.cpp b/maximilian_examples/21.Recording.cpp new file mode 100644 index 0000000..82e0e68 --- /dev/null +++ b/maximilian_examples/21.Recording.cpp @@ -0,0 +1,54 @@ +#include "maximilian.h" + +// Here we define a double floating value that will contain our +// frame of lovely maximilian generated audio +double out; + +// Our oscillator to fill our frame up with my favourite wave +maxiOsc osc; + +// Our ramp to modulate the oscillators +maxiOsc ramp; + +// We declare our recorder object here, which will call it's +// default constructor. +maxiRecorder recorder; + +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"); + + // This must be called to start the asynchronous thread to + // manage the recorder's internal memory + recorder.startRecording(); +} + +void play(double *output) { + + // A pulse wave!!! Yay + out = osc.pulse(90, ramp.phasor(.2)); + + // Fill our output buffer + output[0]=out; + output[1]=out; + + // After we have filled our output array, send the array + // and the size of the array (in this case the amount of + // channels, but in ofx or juce you might need to do + // something like channels*bufferSize). + recorder.passData(output, maxiSettings::channels); +} + +// We don't need to worry about telling the recorder to stop; +// when the stack unwinds and the maximillian program stops, +// the recorder will have its destructor called and the wav +// will be written for you. If you would like to do something +// more dynamic, look at the class definition in maximilian.h - +// the api allows for stricter control of the object. + + |
