143 lines
3.5 KiB
C++
143 lines
3.5 KiB
C++
#include "Sampler.h"
|
|
|
|
#include "Paths.h"
|
|
#include "Singleton/PortAudioAPI.h"
|
|
#include "Singleton/MidiSequencer.h"
|
|
#include "sndfile.hh"
|
|
#include "samplerate.h"
|
|
|
|
|
|
FSampler::FSampler()
|
|
{
|
|
ChannelCount = 0;
|
|
FrameCount = 0;
|
|
SampleRate = 0;
|
|
IncomingOffset = 0;
|
|
IncomingRange = TRange<AudioFrame>(0, 0);
|
|
}
|
|
|
|
bool FSampler::Load(const FString& Path)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
SndfileHandle FileHandle(TCHAR_TO_WCHAR(*Path));
|
|
#else
|
|
SndfileHandle FileHandle(TCHAR_TO_UTF8(*FilePath));
|
|
#endif
|
|
|
|
if (FileHandle.frames() == 0)
|
|
return false;
|
|
|
|
Name = FPaths::GetBaseFilename(Path);
|
|
Vendor = FName("Nanako");
|
|
|
|
FrameCount = FileHandle.frames();
|
|
ChannelCount = FileHandle.channels();
|
|
SampleRate = FileHandle.samplerate();
|
|
|
|
TArray64<float> TempBuffer;
|
|
TempBuffer.SetNumUninitialized(FrameCount * ChannelCount);
|
|
FileHandle.readf(TempBuffer.GetData(), FrameCount * ChannelCount);
|
|
|
|
SetFromInterlaceBuffer(TempBuffer, FrameCount);
|
|
|
|
UpdateSampleRate(FPortAudioAPI::Get().SampleRate);
|
|
UpdateBlockSize(FPortAudioAPI::Get().BlockSize);
|
|
|
|
ChannelInterface = new FChannelInterface(0, ChannelCount);
|
|
|
|
FMidiSequencer::Get().NewSamplePattern(this);
|
|
return true;
|
|
}
|
|
|
|
void FSampler::UpdateSampleRate(float InSampleRate)
|
|
{
|
|
if (InSampleRate == SampleRate)
|
|
return;
|
|
int32 Error = 0;
|
|
SRC_STATE* Converter = src_new(SRC_LINEAR, ChannelCount, &Error);
|
|
if (!Converter || Error != 0)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Error creating sample converter: %hs"), src_strerror(Error));
|
|
return;
|
|
}
|
|
const double Ratio = InSampleRate / SampleRate;
|
|
|
|
TArray64<float> OutBuffer;
|
|
OutBuffer.SetNumUninitialized(GetSampleCount() * Ratio);
|
|
|
|
TArray<float> InterlaceBuffer = GetInterlaceBuffer();
|
|
|
|
SRC_DATA SrcData;
|
|
SrcData.data_in = InterlaceBuffer.GetData();
|
|
SrcData.data_out = OutBuffer.GetData();
|
|
SrcData.input_frames = FrameCount;
|
|
SrcData.output_frames = FrameCount * Ratio;
|
|
SrcData.src_ratio = Ratio;
|
|
|
|
Error = src_process(Converter, &SrcData);
|
|
|
|
if (Error != 0)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Error on Resampling process: %hs"), src_strerror(Error));
|
|
return;
|
|
}
|
|
|
|
// Clean up:
|
|
src_delete(Converter);
|
|
|
|
SetFromInterlaceBuffer(OutBuffer, SrcData.output_frames_gen);
|
|
|
|
SampleRate = InSampleRate;
|
|
FrameCount = SrcData.output_frames_gen;
|
|
}
|
|
|
|
void FSampler::Process(int32 FrameNum)
|
|
{
|
|
const int32 Size = IncomingRange.Size<int32>();
|
|
if (Size == 0)
|
|
return;
|
|
|
|
// FrameNum = FMath::Min(Size, FrameNum);
|
|
float** OutputHeader = ChannelInterface->GetOutputHeader(); // 非交错缓冲区, SampleBuffer为交错缓冲区
|
|
const uint64 PatternPos = IncomingRange.GetLowerBoundValue();
|
|
const uint64 PatternEndPos = IncomingRange.GetUpperBoundValue();
|
|
|
|
for (int i = 0; i < ChannelCount; ++i)
|
|
{
|
|
FMemory::Memcpy(OutputHeader[i] + IncomingOffset, SampleBuffer[i].GetData() + PatternPos, Size * sizeof(float));
|
|
}
|
|
|
|
IncomingMidi.clear();
|
|
IncomingOffset = 0;
|
|
IncomingRange = TRange<AudioFrame>(0, 0);
|
|
}
|
|
|
|
TArray<float> FSampler::GetInterlaceBuffer() const
|
|
{
|
|
TArray<float> Out;
|
|
Out.SetNumUninitialized(FrameCount * ChannelCount);
|
|
for (int i = 0; i < ChannelCount; ++i)
|
|
{
|
|
for (uint32 j = 0; j < FrameCount; ++j)
|
|
{
|
|
Out[j * ChannelCount + i] = SampleBuffer[i][j];
|
|
}
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
void FSampler::SetFromInterlaceBuffer(const TArray64<float>& InterlaceBuffer, uint32 FrameNum)
|
|
{
|
|
SampleBuffer.SetNum(ChannelCount);
|
|
for (int i = 0; i < ChannelCount; ++i)
|
|
{
|
|
TArray64<float>& ChannelBuffer = SampleBuffer[i];
|
|
ChannelBuffer.SetNum(FrameNum);
|
|
for (uint64 j = 0; j < FrameNum; ++j)
|
|
{
|
|
ChannelBuffer[j] = InterlaceBuffer[j * ChannelCount + i];
|
|
}
|
|
}
|
|
FrameCount = FrameNum;
|
|
}
|