-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3a80d11
commit 1a482ca
Showing
5 changed files
with
301 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
/** | ||
* Copyright (C) Johannes Elliesen, 2024 | ||
* | ||
* This program 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 | ||
* any later version. | ||
* | ||
* This program 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 <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <fatfs.h> | ||
#include <util/FixedCapStr.h> | ||
|
||
template <typename FileIoProvider> | ||
class AudioSaveAndRecall | ||
{ | ||
public: | ||
using Filename = daisy::FixedCapStr<32>; | ||
typedef void (*DoneCallbackPtr)(void* context, bool wasSuccessful); | ||
|
||
AudioSaveAndRecall() | ||
{ | ||
} | ||
|
||
template <typename LooperType> | ||
void startSavingToFile(const Filename& filename, LooperType& looper, DoneCallbackPtr doneCallback, void* doneCallbackContext) | ||
{ | ||
static_assert(numChannels <= kMaxNumChannels); | ||
|
||
looperPtr_ = &looper; | ||
doneCallback_ = doneCallback; | ||
doneCallbackContext_ = doneCallbackContext; | ||
|
||
looper.preventRecording(); | ||
|
||
totalNumFrames_ = looper.getPlaybackLength(); | ||
|
||
fileIo_.openForWriting(filename); | ||
if (!writeWavHeader(totalNumFrames_, looper.getNumChannels(), looper.getSampleRate())) | ||
{ | ||
abort(); | ||
} | ||
|
||
writeOrReadFunc_ = [](AudioSaveAndRecall* storage, void* looperPtr) | ||
{ | ||
LooperType* looper = reinterpret_cast<LooperType*>(looperPtr); | ||
|
||
// wait while the current recording is still crossfading out | ||
if (looper->isRecording()) | ||
return { 0, false }; | ||
|
||
const auto numSamplesLeft = storage->totalNumFrames_ - storage->numFramesDone_; | ||
const auto numFramesInThisChunk = std::min(numSamplesLeft, kChunkSize); | ||
|
||
storage->prepareWriteBuffer(looper->getSampleStoragePtr(), storage->numFramesDone_, numFramesInThisChunk); | ||
storage->fileIo_.write(); | ||
|
||
const auto isNowDone = numSamplesLeft == numFramesInThisChunk; | ||
if (isNowDone) | ||
{ | ||
storage->fileIo_.closeFile(); | ||
} | ||
|
||
return { numFramesInThisChunk, isNowDone }; | ||
; | ||
}; | ||
} | ||
|
||
template <typename LooperType> | ||
void startReadingFromFile(const Filename& filename, LooperType& looper, DoneCallbackPtr doneCallback, void* doneCallbackContext) | ||
{ | ||
static_assert(numChannels <= kMaxNumChannels); | ||
|
||
looperPtr_ = &looper; | ||
doneCallback_ = doneCallback; | ||
doneCallbackContext_ = doneCallbackContext; | ||
|
||
looper.preventPlaybackAndRecording(); | ||
looper.stopRecordingImmediately(); | ||
|
||
fileIo_.openForReading(filename); | ||
// TODO: read file header | ||
|
||
totalNumFrames_ = 1000; // TODO | ||
|
||
writeOrReadFunc_ = [](AudioSaveAndRecall* storage, void* looperPtr) | ||
{ | ||
constexpr auto kChunkSize = 1000u; | ||
|
||
LooperType* looper = reinterpret_cast<LooperType*>(looperPtr); | ||
|
||
const auto numSamplesLeft = storage->totalNumFrames_ - storage->numFramesDone_; | ||
const auto numFramesInThisChunk = std::min(numSamplesLeft, kChunkSize); | ||
|
||
// TODO: read a chunk | ||
|
||
const auto isNowDone = numSamplesLeft == numFramesInThisChunk; | ||
if (isNowDone) | ||
{ | ||
storage->fileIo_.closeFile(); | ||
} | ||
|
||
return { numFramesInThisChunk, isNowDone }; | ||
}; | ||
} | ||
|
||
float getCurrentProgress() const | ||
{ | ||
if (totalNumFrames_ <= 0) | ||
return 0.0f; | ||
return float(numFramesDone_) / float(totalNumFrames_); | ||
} | ||
|
||
void readOrWriteNextChunk() | ||
{ | ||
if (writeOrReadFunc_) | ||
{ | ||
const auto result = writeOrReadFunc_(this, looperPtr_); | ||
|
||
numFramesDone_ += result.numFramesCompleted; | ||
|
||
if (result.isComplete) | ||
{ | ||
doneCallback_(doneCallbackContext_); | ||
writeOrReadFunc_ = nullptr; | ||
} | ||
} | ||
} | ||
|
||
private: | ||
void abort() | ||
{ | ||
fileIo_.closeFile(); | ||
if (doneCallback_) | ||
{ | ||
doneCallback_(doneCallbackContext_, false); | ||
} | ||
doneCallback_ = nullptr; | ||
doneCallbackContext_ = nullptr; | ||
looperPtr_ = nullptr; | ||
totalNumFrames_ = 0; | ||
numFramesDone_ = 0; | ||
} | ||
|
||
bool writeWavHeader(size_t totalNumFrames, size_t numChannelsPerFrame, int sampleRate) | ||
{ | ||
const auto byteRate = samplerate * numChannelsPerFrame * kBitsPerSample / 8; | ||
|
||
wavHeader_.ChunkId = kWavFileChunkId; /** "RIFF" */ | ||
wavHeader_.FileFormat = kWavFileWaveId; /** "WAVE" */ | ||
wavHeader_.SubChunk1ID = kWavFileSubChunk1Id; /** "fmt " */ | ||
wavHeader_.SubChunk1Size = 16; // for PCM | ||
wavHeader_.AudioFormat = WAVE_FORMAT_PCM; | ||
wavHeader_.NbrChannels = numChannelsPerFrame; | ||
wavHeader_.SampleRate = sampleRate; | ||
wavHeader_.ByteRate = byteRate; | ||
wavHeader_.BlockAlign = numChannelsPerFrame * kBitsPerSample / 8; | ||
wavHeader_.BitPerSample = bitsPerSample; | ||
wavHeader_.SubChunk2ID = kWavFileSubChunk2Id; /** "data" */ | ||
wavHeader_.SubCHunk2Size = totalNumFrames * numChannelsPerFrame * kBitsPerSample / 8; | ||
wavHeader_.FileSize = 36 + wavHeader_.SubCHunk2Size; | ||
|
||
return fileIo_.write(&wavHeader_, sizeof(wavHeader_)) == sizeof(wavHeader_); | ||
} | ||
|
||
template <size_t numChannels> | ||
bool prepareWriteBuffer(LooperStoragePtr<numChannels>& storage, const size_t startFrame, const size_t numFrames) | ||
{ | ||
float* inPtr[numChannels]; | ||
|
||
for (size_t ch = 0; ch < numChannels; ch++) | ||
inPtr[ch] = &storage.data[ch][startFrame]; | ||
|
||
int32_t* outPtr = &kWriteBuffer_[0]; | ||
|
||
size_t numFramesLeft = numFrames; | ||
while (numFramesLeft > 0) | ||
{ | ||
for (size_t ch = 0; ch < numChannels; ch++) | ||
{ | ||
*outPtr = f2s32(*inPtr[ch]); | ||
outPtr++; | ||
inPtr[ch]++; | ||
} | ||
numFramesLeft--; | ||
} | ||
} | ||
|
||
static constexpr auto kNumFramesPerChunk = 1000u; | ||
static constexpr auto kBitsPerSample = 32u; | ||
static constexpr auto kMaxNumChannels = 2u; | ||
static constexpr auto kWriteBufferSize = kNumFramesPerChunk * kMaxNumChannels * kBitsPerSample / 8u; | ||
|
||
WAV_FormatTypeDef wavHeader_; | ||
|
||
struct ReadOrWriteResult | ||
{ | ||
size_t numFramesCompleted = 0; | ||
bool isComplete = false; | ||
}; | ||
typedef ReadOrWriteResult (*WriteOrReadFuncPtr)(AudioSaveAndRecall* storage, void* looper); | ||
|
||
FileIoProvider fileIo_; | ||
|
||
size_t totalNumFrames_ = 0; | ||
size_t numFramesDone_ = 0; | ||
WriteOrReadFuncPtr writeOrReadFunc_ = nullptr; | ||
void* looperPtr_ = nullptr; | ||
DoneCallbackPtr doneCallback_ = nullptr; | ||
void* doneCallbackContext_ = nullptr; | ||
|
||
int32_t writeBuffer_[kWriteBufferSize_]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters