aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeon Fedden <lfedd001@gold.ac.uk>2016-10-11 10:47:28 +0100
committerLeon Fedden <lfedd001@gold.ac.uk>2016-10-11 10:47:28 +0100
commit3a48a1708930139c806b5b3a14ca57b8ad2a3a41 (patch)
tree168e72b721f5245ddd65bf17022aa5bf35b71e2f
parent7f4bec02beedeb71725c65191b8d76f24b56b9a3 (diff)
adding maxiRecorder
-rw-r--r--maximilian.cpp253
-rwxr-xr-xmaximilian.h33
-rw-r--r--maximilian_examples/21.Recording.cpp54
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.
+
+