added output recording feature. moved settings to a separate popup panel instead of a menu

This commit is contained in:
essej
2022-06-14 19:11:57 -04:00
parent aff633ff39
commit af5efd8b09
21 changed files with 3309 additions and 263 deletions

View File

@ -294,6 +294,20 @@ m_bufferingthread("pspluginprebufferthread"), m_is_stand_alone_offline(is_stand_
startTimer(1, 40);
}
#if (JUCE_IOS)
m_defaultRecordDir = File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName();
#elif (JUCE_ANDROID)
auto parentDir = File::getSpecialLocation (File::userApplicationDataDirectory);
parentDir = parentDir.getChildFile("Recordings");
m_defaultRecordDir = parentDir.getFullPathName();
#else
auto parentDir = File::getSpecialLocation (File::userMusicDirectory);
parentDir = parentDir.getChildFile("PaulXStretch");
m_defaultRecordDir = parentDir.getFullPathName();
#endif
//m_defaultCaptureDir = parentDir.getChildFile("Captures").getFullPathName();
m_show_technical_info = m_propsfile->m_props_file->getBoolValue("showtechnicalinfo", false);
DBG("Constructed PS plugin");
@ -398,6 +412,9 @@ ValueTree PaulstretchpluginAudioProcessor::getStateTree(bool ignoreoptions, bool
storeToTreeProperties(paramtree, nullptr, "restoreplaystate", m_restore_playstate);
storeToTreeProperties(paramtree, nullptr, "autofinishrecord", m_auto_finish_record);
paramtree.setProperty("defRecordDir", m_defaultRecordDir, nullptr);
return paramtree;
}
@ -439,7 +456,11 @@ void PaulstretchpluginAudioProcessor::setStateFromTree(ValueTree tree)
}
getFromTreeProperties(tree, "waveviewrange", m_wave_view_range);
getFromTreeProperties(tree, getParameters());
#if !(JUCE_IOS || JUCE_ANDROID)
setDefaultRecordingDirectory(tree.getProperty("defRecordDir", m_defaultRecordDir));
#endif
}
int prebufamt = tree.getProperty("prebufamount", 2);
if (prebufamt == -1)
@ -676,23 +697,32 @@ void PaulstretchpluginAudioProcessor::saveCaptureBuffer()
int inchans = jmin(getMainBusNumInputChannels(), getIntParameter(cpi_num_inchans)->get());
if (inchans < 1)
return;
WavAudioFormat wavformat;
std::unique_ptr<AudioFormat> audioFormat;
String fextension;
int bitsPerSample = std::min(32, m_defaultRecordingBitsPerSample);
if (m_defaultRecordingFormat == FileFormatWAV) {
audioFormat = std::make_unique<WavAudioFormat>();
fextension = ".wav";
}
else {
audioFormat = std::make_unique<FlacAudioFormat>();
fextension = ".flac";
bitsPerSample = std::min(24, bitsPerSample);
}
String outfn;
String filename = String("pxs_") + Time::getCurrentTime().formatted("%Y-%m-%d_%H.%M.%S");
filename = File::createLegalFileName(filename);
if (m_capture_location.isEmpty()) {
File capdir;
#if JUCE_IOS
capdir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory);
outfn = capdir.getChildFile("Captures").getNonexistentChildFile(filename, ".wav").getFullPathName();
#else
capdir = m_propsfile->m_props_file->getFile().getParentDirectory();
outfn = capdir.getChildFile("Captures").getNonexistentChildFile(filename, ".wav").getFullPathName();
#endif
File capdir(m_defaultRecordDir);
outfn = capdir.getChildFile("Captures").getNonexistentChildFile(filename, fextension).getFullPathName();
}
else {
outfn = File(m_capture_location).getNonexistentChildFile(filename, ".wav").getFullPathName();
outfn = File(m_capture_location).getNonexistentChildFile(filename, fextension).getFullPathName();
}
File outfile(outfn);
outfile.create();
@ -700,8 +730,8 @@ void PaulstretchpluginAudioProcessor::saveCaptureBuffer()
{
m_capture_save_state = 1;
auto outstream = outfile.createOutputStream();
auto writer = unique_from_raw(wavformat.createWriterFor(outstream.get(), getSampleRateChecked(),
inchans, 32, {}, 0));
auto writer = unique_from_raw(audioFormat->createWriterFor(outstream.get(), getSampleRateChecked(),
inchans, bitsPerSample, {}, 0));
if (writer != nullptr)
{
outstream.release(); // the writer takes ownership
@ -1274,6 +1304,19 @@ void PaulstretchpluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, M
ed->m_sonogram.addAudioBlock(buffer);
}
*/
// output to file writer if necessary
if (m_writingPossible.load()) {
const ScopedTryLock sl (m_writerLock);
if (sl.isLocked())
{
if (m_activeMixWriter.load() != nullptr) {
m_activeMixWriter.load()->write (buffer.getArrayOfReadPointers(), buffer.getNumSamples());
}
m_elapsedRecordSamples += buffer.getNumSamples();
}
}
}
//==============================================================================
@ -1306,7 +1349,7 @@ void PaulstretchpluginAudioProcessor::setDirty()
toggleBool(getBoolParameter(cpi_markdirty));
}
void PaulstretchpluginAudioProcessor::setRecordingEnabled(bool b)
void PaulstretchpluginAudioProcessor::setInputRecordingEnabled(bool b)
{
ScopedLock locker(m_cs);
int lenbufframes = getSampleRateChecked()*m_max_reclen;
@ -1334,7 +1377,7 @@ void PaulstretchpluginAudioProcessor::setRecordingEnabled(bool b)
}
}
double PaulstretchpluginAudioProcessor::getRecordingPositionPercent()
double PaulstretchpluginAudioProcessor::getInputRecordingPositionPercent()
{
if (m_is_recording_pending==false)
return 0.0;
@ -1411,13 +1454,13 @@ void PaulstretchpluginAudioProcessor::timerCallback(int id)
if (capture == true && m_is_recording_pending == false && !m_is_recording_finished)
{
DBG("start recording");
setRecordingEnabled(true);
setInputRecordingEnabled(true);
return;
}
if (capture == false && m_is_recording_pending == true && !m_is_recording_finished)
{
DBG("stop recording");
setRecordingEnabled(false);
setInputRecordingEnabled(false);
return;
}
@ -1517,6 +1560,145 @@ void PaulstretchpluginAudioProcessor::finishRecording(int lenrecording, bool nos
}
}
bool PaulstretchpluginAudioProcessor::startRecordingToFile(File & file, RecordFileFormat fileformat)
{
if (!m_recordingThread) {
m_recordingThread = std::make_unique<TimeSliceThread>("Recording Thread");
m_recordingThread->startThread();
}
stopRecordingToFile();
bool ret = false;
// Now create a WAV writer object that writes to our output stream...
//WavAudioFormat audioFormat;
std::unique_ptr<AudioFormat> audioFormat;
std::unique_ptr<AudioFormat> wavAudioFormat;
int qualindex = 0;
int bitsPerSample = std::min(32, m_defaultRecordingBitsPerSample);
if (getSampleRate() <= 0)
{
return false;
}
File usefile = file;
if (fileformat == FileFormatDefault) {
fileformat = m_defaultRecordingFormat;
}
m_totalRecordingChannels = getMainBusNumOutputChannels();
if (m_totalRecordingChannels == 0) {
m_totalRecordingChannels = 2;
}
if (fileformat == FileFormatFLAC && m_totalRecordingChannels > 8) {
// flac doesn't support > 8 channels
fileformat = FileFormatWAV;
}
if (fileformat == FileFormatFLAC || (fileformat == FileFormatAuto && file.getFileExtension().toLowerCase() == ".flac")) {
audioFormat = std::make_unique<FlacAudioFormat>();
bitsPerSample = std::min(24, bitsPerSample);
usefile = file.withFileExtension(".flac");
}
else if (fileformat == FileFormatWAV || (fileformat == FileFormatAuto && file.getFileExtension().toLowerCase() == ".wav")) {
audioFormat = std::make_unique<WavAudioFormat>();
usefile = file.withFileExtension(".wav");
}
else if (fileformat == FileFormatOGG || (fileformat == FileFormatAuto && file.getFileExtension().toLowerCase() == ".ogg")) {
audioFormat = std::make_unique<OggVorbisAudioFormat>();
qualindex = 8; // 256k
usefile = file.withFileExtension(".ogg");
}
else {
m_lastError = TRANS("Could not find format for filename");
DBG(m_lastError);
return false;
}
bool userwriting = false;
// Create an OutputStream to write to our destination file...
usefile.deleteFile();
if (auto fileStream = std::unique_ptr<FileOutputStream> (usefile.createOutputStream()))
{
if (auto writer = audioFormat->createWriterFor (fileStream.get(), getSampleRate(), m_totalRecordingChannels, bitsPerSample, {}, qualindex))
{
fileStream.release(); // (passes responsibility for deleting the stream to the writer object that is now using it)
// Now we'll create one of these helper objects which will act as a FIFO buffer, and will
// write the data to disk on our background thread.
m_threadedMixWriter.reset (new AudioFormatWriter::ThreadedWriter (writer, *m_recordingThread, 65536));
DBG("Started recording only mix file " << usefile.getFullPathName());
file = usefile;
ret = true;
} else {
m_lastError.clear();
m_lastError << TRANS("Error creating writer for ") << usefile.getFullPathName();
DBG(m_lastError);
}
} else {
m_lastError.clear();
m_lastError << TRANS("Error creating output file: ") << usefile.getFullPathName();
DBG(m_lastError);
}
if (ret) {
// And now, swap over our active writer pointers so that the audio callback will start using it..
const ScopedLock sl (m_writerLock);
m_elapsedRecordSamples = 0;
m_activeMixWriter = m_threadedMixWriter.get();
m_writingPossible.store(m_activeMixWriter);
//DBG("Started recording file " << usefile.getFullPathName());
}
return ret;
}
bool PaulstretchpluginAudioProcessor::stopRecordingToFile()
{
// First, clear this pointer to stop the audio callback from using our writer object..
{
const ScopedLock sl (m_writerLock);
m_activeMixWriter = nullptr;
m_writingPossible.store(false);
}
bool didit = false;
if (m_threadedMixWriter) {
// Now we can delete the writer object. It's done in this order because the deletion could
// take a little time while remaining data gets flushed to disk, and we can't be blocking
// the audio callback while this happens.
m_threadedMixWriter.reset();
DBG("Stopped recording mix file");
didit = true;
}
return didit;
}
bool PaulstretchpluginAudioProcessor::isRecordingToFile()
{
return (m_activeMixWriter.load() != nullptr);
}
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new PaulstretchpluginAudioProcessor();