优化Waveform渲染性能

This commit is contained in:
Nanako 2024-01-27 19:16:15 +08:00
parent e79b8f3ed2
commit 55d6cdaade
26 changed files with 461 additions and 178 deletions

View File

@ -5,24 +5,26 @@ cbuffer Params : register(b0)
float4 WaveColor;
float4 BgColor;
float LineUV;
float PeaksPerPixel;
};
[numthreads(1, 1, 1)]
void Main(uint3 id : SV_DispatchThreadID)
{
uint width, height;
Result.GetDimensions(width, height);
// float2 uv = float2(id.xy / float2(width, height));
// uv.y = abs(((1.0 - uv.y) - 0.5) * 2); // 居中
int X = id.x;
int Y = id.y;
// float4 color = lerp(BgColor, WaveColor, min(step(value.x, uv.y), step(uv.y , value.y)));
float Top = Samples[X * 2] + 1; // -1;
float Bottom = Samples[X * 2 + 1] + 1; // 1;
// Resample
uint TopIndex = X * 2;
uint BottomIndex = TopIndex + 1;
float Top = Samples[TopIndex] + 1; // -1;
float Bottom = Samples[BottomIndex] + 1; // 1;
Top = min(Top, 1);
Bottom = max(Bottom, 0);
Top *= height;
@ -30,13 +32,10 @@ void Main(uint3 id : SV_DispatchThreadID)
Bottom *= height;
Bottom *= 0.5;
if ((id.y <= Bottom && id.y >= Top) || (Y == height / 2))
{
Result[id.xy] = WaveColor;
}
else
{
Result[id.xy] = BgColor;
}
// (id.y >= Top && id.y <= Bottom)
float b1 = min(step(Top, Y), step(Y , Bottom));
// (Y == height / 2)
float b2 = min(step(Y, height / 2), step(height / 2, Y));
float b3 = max(b1, b2);
Result[id.xy] = lerp(BgColor, WaveColor, b3);
}

View File

@ -45,12 +45,11 @@ void main()
Bottom *= height;
Bottom *= 0.5;
if ((Y <= Bottom && Y >= Top) || (Y == int(height / 2)))
{
imageStore(Result, pos, WaveColor);
}
else
{
imageStore(Result, pos, BgColor);
}
// (id.y >= Top && id.y <= Bottom)
float b1 = min(step(Top, Y), step(Y , Bottom));
// (Y == height / 2)
float b2 = step(Y, height * LineUV) * step(height * LineUV, Y);
float b3 = max(b1, b2);
imageStore(Result, pos, lerp(BgColor, WaveColor, b3));
}

View File

@ -64,7 +64,7 @@ int RunArona( const TCHAR* CommandLine )
FWindowManager& WindowManager = FWindowManager::Get();
FCallRateLimiterManager& RateLimiterManager = FCallRateLimiterManager::Get();
// AronaTest();
AronaTest();
constexpr float FrameRate = 1.f / 360.0f;
FSlateApplication& SlateApplication = FSlateApplication::Get();

View File

@ -0,0 +1,66 @@
#include "PeakFile.h"
#include "PluginHost/Sampler.h"
#define MID_TO_LOW (16 * 16)
TArray<TArray<FSamplePeak>> GenPeakData(const TArray<TArray64<float>>& Data, int32 BlockSize)
{
TArray<TArray<FSamplePeak>> Out;
Out.SetNum(Data.Num());
for (int64 Channel = 0; Channel < Data.Num(); ++Channel)
{
const uint64 SourceNum = Data[Channel].Num();
uint64 BlockNum = SourceNum / BlockSize;
BlockNum += SourceNum % BlockSize ? 1 : 0;
Out[Channel].SetNum(BlockNum);
uint64 Index = 0;
for (uint64 j = 0; j < BlockNum; j++)
{
float Max = FLT_MIN_EXP;
float Min = FLT_MAX;
for (uint64 S = 0; S < BlockSize; ++S)
{
if (Index >= SourceNum) break;
const float& Sample = Data[Channel][Index];
if (Max < Sample) Max = Sample;
if (Min > Sample) Min = Sample;
++Index;
}
Out[Channel][j].Max = Max;
Out[Channel][j].Min = Min;
}
}
return Out;
}
void FWaveformRenderData::Generate(const TArray<TArray64<float>>& Data)
{
Buffer = GenPeakData(Data, BlockSize);
}
FWaveform::FWaveform(int32 LevelNum, int32 MaxBlockSize)
{
RenderData.SetNum(LevelNum);
int32 BeginBlock = MaxBlockSize;
for (int i = 0; i < LevelNum; ++i)
{
RenderData[i] = new FWaveformRenderData(BeginBlock);
BeginBlock /= 4;
}
}
FWaveform::~FWaveform()
{
for (int i = 0; i < RenderData.Num(); ++i)
delete RenderData[i];
}
void FWaveform::UpdatePeak(const TArray<TArray64<float>>& SampleBuffer)
{
for (int i = 0; i < RenderData.Num(); ++i)
RenderData[i]->Generate(SampleBuffer);
}

View File

@ -0,0 +1,101 @@
#pragma once
#include "CoreMinimal.h"
class FSampler;
struct FSamplePeak
{
float Min = 0;
float Max = 0;
void operator+=(const FSamplePeak& Other)
{
if (Min > Other.Min) Min = Other.Min;
if (Max < Other.Max) Max = Other.Max;
}
};
class FPeakBlock
{
public:
uint16 BlockNum;
uint16 Size;
uint16 Res;
uint16 Resolution;
TArray<void*> Buffer;
uint16 MaxLevel;
};
struct FWaveformBuffer
{
TArray<int16> Buffer;
void* HiResBuffer;
TArray<int8> RMSBuffer;
};
class FWaveformRenderData
{
public:
explicit FWaveformRenderData(int32 BlockSize)
: BlockSize(BlockSize)
{
}
virtual ~FWaveformRenderData() = default;
virtual void Generate(const TArray<TArray64<float>>& Data);
int32 GetMemSize() const
{
int32 Size = 0;
for (const auto& Array : Buffer)
{
Size += Array.GetAllocatedSize();
}
return Buffer.GetAllocatedSize() + Size;
}
const FSamplePeak& GetPeak(const uint16 Channel, const uint64 Frame) const
{
const int32 BlockIndex = Frame / BlockSize;
return Buffer[Channel][BlockIndex];
}
TArrayView<const FSamplePeak> GetPeakFromRange(const uint16 Channel, uint64 StartFrame, uint64 EndFrame) const
{
const int32 StartBlockIndex = StartFrame / BlockSize;
int32 EndBlockIndex = EndFrame / BlockSize;
EndBlockIndex = FMath::Min(EndBlockIndex, Buffer[Channel].Num() - 1);
// TArray<FSamplePeak> Out;
// Out.SetNum(EndBlockIndex - StartBlockIndex + 1);
// FMemory::Memcpy(Out.GetData(), Buffer[Channel].GetData() + StartBlockIndex, Out.Num() * sizeof(FSamplePeak));
return MakeArrayView(&Buffer[Channel][StartBlockIndex], EndBlockIndex - StartBlockIndex + 1);
}
TArray<TArray<FSamplePeak>> Buffer;
const int32 BlockSize; // 每一个块包含多少个Frame
};
class FWaveform
{
public:
FWaveform(int32 LevelNum, int32 MaxBlockSize = 128 * 128);
~FWaveform();
void UpdatePeak(const TArray<TArray64<float>>& SampleBuffer);
int32 GetMemSize() const
{
int32 Size = 0;
for (int i = 0; i < RenderData.Num(); ++i)
Size += RenderData[i]->GetMemSize();
return Size;
}
TArrayView<const FSamplePeak> GetPeakFromRange(const uint16 Channel, const int32 Width, const uint64 StartFrame, const uint64 EndFrame) const
{
const uint64 FrameCount = EndFrame - StartFrame;
const int32 BlockSize = FrameCount / Width;
for (int i = 0; i < RenderData.Num(); ++i)
if (RenderData[i]->BlockSize <= BlockSize)
return RenderData[i]->GetPeakFromRange(Channel, StartFrame, EndFrame);
return RenderData.Last()->GetPeakFromRange(Channel, StartFrame, EndFrame);
}
TArray<FWaveformRenderData*> RenderData;
};

View File

@ -13,29 +13,29 @@ void AronaTest()
UE_LOG(LogTemp, Log, TEXT("Audio Device%d: %s"), AudioDeviceInfo.DeviceIndex, *AudioDeviceInfo.Name);
}
const FString& MidiFilePath = TEXT("E:\\Projects\\Arona\\脑浆炸裂女孩 - 初音ミク.mid");
// const FString& MidiFilePath = TEXT("E:\\Projects\\Arona\\脑浆炸裂女孩 - 初音ミク.mid");
// const FString& PluginFilePath = TEXT("D:\\Projects\\Rolling\\4Front Piano x64.dll");
const FString& PluginFilePath = TEXT("F:\\VST\\VST64\\4Front Piano x64.dll");
// const FString& PluginFilePath = TEXT("F:\\VST\\VST64\\4Front Piano x64.dll");
FMidiFile Midi;
Midi.readFrom(MidiFilePath);
// FMidiFile Midi;
// Midi.readFrom(MidiFilePath);
FMidiSequencer& MidiSequencer = FMidiSequencer::Get();
// FMidiSequencer& MidiSequencer = FMidiSequencer::Get();
// MidiSequencer.SetTicksPerQuarter(Midi.getTimeFormat());
FMidiPattern* MidiPattern = MidiSequencer.NewMidiPattern();
MidiPattern->Name = TEXT("脑浆炸裂女孩 - 初音ミク");
// FMidiPattern* MidiPattern = MidiSequencer.NewMidiPattern();
// MidiPattern->Name = TEXT("脑浆炸裂女孩 - 初音ミク");
for (int i = 0; i < Midi.getNumTracks(); ++i)
{
FPluginHost* Plugin = FPluginHostList::Get().TryLoadPlugin(PluginFilePath);
FPluginHostList::Get().RegisterInstrument(Plugin);
const FMidiMessageSequence* Track = Midi.getTrack(i);
FMidiMessageSequence& Sequence = MidiPattern->GetSequence(Plugin);
Sequence.addSequence(*Track, 0);
Sequence.updateMatchedPairs();
}
// for (int i = 0; i < Midi.getNumTracks(); ++i)
// {
// FPluginHost* Plugin = FPluginHostList::Get().TryLoadPlugin(PluginFilePath);
// FPluginHostList::Get().RegisterInstrument(Plugin);
//
// const FMidiMessageSequence* Track = Midi.getTrack(i);
// FMidiMessageSequence& Sequence = MidiPattern->GetSequence(Plugin);
// Sequence.addSequence(*Track, 0);
// Sequence.updateMatchedPairs();
// }
// FPluginHost* Kontakt = FPluginHostList::Get().TryLoadPlugin(TEXT("I:\\VST\\VST64\\Kontakt.dll"));
// FPluginHostList::Get().RegisterInstrument(Kontakt);

View File

@ -91,20 +91,6 @@ void FMainWindow::Init()
[
SAssignNew(MainWindowCanvas, SConstraintCanvas)
]
+SOverlay::Slot()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
[
SNew(SWaveformViewer)
.Visibility(EVisibility::SelfHitTestInvisible)
].FillWidth(1.f)
// +SHorizontalBox::Slot()
// [
// SNew(SWaveformViewer)
// .Visibility(EVisibility::SelfHitTestInvisible)
// ].FillWidth(1.f)
]
]
);

View File

@ -20,6 +20,7 @@ void SPatternInstance::Construct(const FArguments& InArgs)
PatternInstance = InArgs._PatternInstance;
FrameToPixel = InArgs._FrameToPixel;
SnapFrame = InArgs._SnapFrame;
FrameViewRange = InArgs._FrameViewRange;
ChildSlot
[
@ -57,7 +58,7 @@ void SPatternInstance::Construct(const FArguments& InArgs)
+SVerticalBox::Slot()
.FillHeight(1.f)
[
InArgs._View
GetViewWidget()
]
]
];

View File

@ -38,7 +38,7 @@ public:
SLATE_ATTRIBUTE(float, FrameToPixel)
SLATE_ATTRIBUTE(AudioFrame, SnapFrame)
SLATE_ARGUMENT(TSharedRef<SWidget>, View)
SLATE_ATTRIBUTE(TRange<AudioFrame>, FrameViewRange)
SLATE_END_ARGS()
@ -49,8 +49,12 @@ public:
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
FPatternInstance* GetPatternInstance() const { return PatternInstance; }
virtual TSharedRef<SWidget> GetViewWidget() = 0;
virtual void RequestUpdate() = 0;
FPatternInstance* GetPatternInstance() const { return PatternInstance; }
TRange<AudioFrame> GetTimeRange() const { return PatternInstance->TimeRange; }
TRange<AudioFrame> GetFrameViewRange() const { return FrameViewRange.Get(); }
float ResizeHandleSize = 5.f;
protected:
virtual FReply OpenPatternMenu() { return FReply::Unhandled(); }
@ -61,6 +65,7 @@ protected:
TAttribute<float> FrameToPixel;
TAttribute<AudioFrame> SnapFrame;
TAttribute<TRange<AudioFrame>> FrameViewRange;
FVector2f MouseDownPos; // 屏幕坐标
AudioFrame BeginPatternEnd;

View File

@ -0,0 +1,51 @@
#include "SSamplePatternInstance.h"
#include "Pattern/SamplePatternInstance.h"
#include "UI/Widget/Thumbnail.h"
#include "UI/Widget/WaveformViewer.h"
FSamplePatternInstanceWaveformHandle::FSamplePatternInstanceWaveformHandle(SSamplePatternInstance* InPatternInstance):
PatternInstanceWidget(InPatternInstance)
{
}
TArrayView<const FSamplePeak> FSamplePatternInstanceWaveformHandle::GetWaveform(int32 SizeX)
{
FSamplePatternInstance* Instance = (FSamplePatternInstance*)PatternInstanceWidget->GetPatternInstance();
TRange<AudioFrame> FrameViewRange = PatternInstanceWidget->GetFrameViewRange();
const FSampler* Sampler = Instance->GetInstanceOwner()->GetSampler();
const TRange<AudioFrame>& TimeRange = Instance->TimeRange;
if (!RenderData)
{
RenderData = new FWaveform(7, 256 * 128);
RenderData->UpdatePeak(Sampler->GetSampleBuffer());
const float MemSize = RenderData->GetMemSize();
UE_LOG(LogTemp, Warning, TEXT("Waveform MemSize: %f MB"), MemSize / 1024 / 1024);
}
const AudioFrame InstancePos = Instance->GetMidiPos();
FrameViewRange = TRange<AudioFrame>(FrameViewRange.GetLowerBoundValue() - InstancePos, FrameViewRange.GetUpperBoundValue() - InstancePos);
const AudioFrame BeginFrame = FMath::Max(TimeRange.GetLowerBoundValue(), FrameViewRange.GetLowerBoundValue());
const AudioFrame EndFrame = FMath::Min(TimeRange.GetUpperBoundValue(), FrameViewRange.GetUpperBoundValue());
return RenderData->GetPeakFromRange(0, SizeX, BeginFrame, EndFrame);
}
SSamplePatternInstance::SSamplePatternInstance()
{
WaveformHandle = MakeShared<FSamplePatternInstanceWaveformHandle>(this);
}
TSharedRef<SWidget> SSamplePatternInstance::GetViewWidget()
{
return SAssignNew(WaveformViewer, SWaveformViewer)
.WaveformHandle(WaveformHandle);
}
void SSamplePatternInstance::RequestUpdate()
{
}
FReply SSamplePatternInstance::OpenPatternMenu()
{
return FReply::Handled();
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "SPatternInstance.h"
#include "PeakFile/PeakFile.h"
#include "UI/Widget/WaveformViewer.h"
class SSamplePatternInstance;
class FSamplePatternInstanceWaveformHandle : public IWaveformHandle
{
public:
FSamplePatternInstanceWaveformHandle(SSamplePatternInstance* InPatternInstance);
SSamplePatternInstance* PatternInstanceWidget;
virtual TArrayView<const FSamplePeak> GetWaveform(int32 SizeX) override;
FWaveform* RenderData = nullptr;
};
class ARONA_API SSamplePatternInstance : public SPatternInstance
{
public:
SSamplePatternInstance();
virtual TSharedRef<SWidget> GetViewWidget() override;
virtual void RequestUpdate() override;
protected:
virtual FReply OpenPatternMenu() override;
TSharedPtr<FSamplePatternInstanceWaveformHandle> WaveformHandle;
TSharedPtr<SWaveformViewer> WaveformViewer;
};

View File

@ -1 +0,0 @@
#include "SWaveformPatternInstance.h"

View File

@ -1,8 +0,0 @@
#pragma once
#include "SPatternInstance.h"
class ARONA_API SWaveformPatternInstance : public SPatternInstance
{
public:
};

View File

@ -3,6 +3,7 @@
#include "SPatternThumbnail.h"
#include "Async.h"
#include "SInvalidationPanel.h"
#include "SlateOptMacros.h"
#include "SMidiPatternThumbnail.h"
@ -44,7 +45,6 @@ void SAutoPatternThumbnail::SetPattern(FPattern* InPattern)
Thumbnail.ToSharedRef()
]
];
Thumbnail->Redraw();
}
FReply SAutoPatternThumbnail::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
@ -64,6 +64,16 @@ FVector2D SAutoPatternThumbnail::ComputeDesiredSize(float LayoutScaleMultiplier)
return FVector2D(0, 36) * LayoutScaleMultiplier;
}
void SAutoPatternThumbnail::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
if (bNeedRedraw)
{
Thumbnail->Redraw();
bNeedRedraw = false;
}
}
TSharedPtr<SPatternThumbnail> SAutoPatternThumbnail::CreateThumbnail(FPattern* InPattern)
{
TSharedPtr<SPatternThumbnail> Out;

View File

@ -59,10 +59,12 @@ public:
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override;
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
FPatternDelegate OnPatternClicked;
private:
TSharedPtr<SPatternThumbnail> CreateThumbnail(FPattern* InPattern);
TSharedPtr<SPatternThumbnail> Thumbnail;
FPattern* Pattern = nullptr;
bool bNeedRedraw = true;
};

View File

@ -3,32 +3,29 @@
#include "SSamplePatternThumbnail.h"
#include "Async.h"
#include "SlateApplication.h"
#include "SlateOptMacros.h"
#include "ExecutionTime.h"
#include "Pattern/SamplePatternInstance.h"
#include "PluginHost/Sampler.h"
#include "UI/Widget/SUpdatableImage.h"
#include "UI/Widget/Thumbnail.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
TArray<float> FSampleWaveformHandle::GetWaveform(int32 SizeX) const
TArrayView<const FSamplePeak> FSamplePatternWaveformHandle::GetWaveform(int32 SizeX)
{
const FSampler* Sampler = SampleInstance->GetInstanceOwner()->GetSampler();
TArray<TArray64<float>> Copy = Sampler->GetSampleBuffer(); // 拷贝以防在渲染时被修改
uint32 Count = Sampler->GetFrameCount();
TRange<uint32> Range = SampleInstance->GetRange();
return Thumbnail::GenerateWaveformData(SizeX, Copy, Count, Range);
if (!RenderData)
{
RenderData = new FWaveform(1, 1024);
RenderData->UpdatePeak(Sampler->GetSampleBuffer());
}
return RenderData->GetPeakFromRange(0, SizeX, 0, Sampler->GetFrameCount());
}
void SSamplePatternThumbnail::Construct(const FArguments& InArgs, FSampler* InSampler)
{
WaveformHandle = MakeShared<FSamplePatternWaveformHandle>(InSampler);
ChildSlot
[
SAssignNew(WaveformViewer, SWaveformViewer)
.WaveformHandle(WaveformHandle)
];
}

View File

@ -11,11 +11,16 @@
class FSampler;
class FSampleWaveformHandle : public IWaveformHandle
class FSamplePatternWaveformHandle : public IWaveformHandle
{
public:
FSamplePatternInstance* SampleInstance = nullptr;
virtual TArray<float> GetWaveform(int32 SizeX) const override;
FSamplePatternWaveformHandle(FSampler* InSampler)
: Sampler(InSampler)
{
}
FSampler* Sampler = nullptr;
FWaveform* RenderData = nullptr;
virtual TArrayView<const FSamplePeak> GetWaveform(int32 SizeX) override;
};
/**
@ -33,5 +38,6 @@ public:
virtual void Redraw() override;
private:
FSampleWaveformHandle* WaveformHandle = nullptr;
TSharedPtr<FSamplePatternWaveformHandle> WaveformHandle;
TSharedPtr<SWaveformViewer> WaveformViewer;
};

View File

@ -6,6 +6,7 @@
#include "EditTool/PlayListEditTool_Select.h"
#include "EditTool/PlayListEditTool_Write.h"
#include "UI/Widget/Pattern/SPatternInstance.h"
#include "UI/Widget/Pattern/SSamplePatternInstance.h"
#include "UI/Widget/PlayList/PlayListPanel/SPlayListPanel.h"
FPlayListEdit::FPlayListEdit(TSharedRef<SPlayListPanel> InPlayListPanel)
@ -155,14 +156,25 @@ FReply FPlayListEdit::OnPatternInstanceReleased(FPatternInstanceClickData D)
TSharedRef<SPatternInstance> FPlayListEdit::CreatePatternInstanceWidget(FPatternInstance* PatternInstance)
{
const TSharedRef<SPatternInstance> InstanceWidget = SNew(SPatternInstance)
.Texture(UpdatableTexture)
.PatternInstance(PatternInstance)
.FrameToPixel_Raw(this, &FPlayListEdit::GetSampleToPixelScaler)
.SnapFrame_Raw(this, &FPlayListEdit::GetSnapFrame)
.Clipping(EWidgetClipping::ClipToBoundsAlways);
TSharedPtr<SPatternInstance> PatternInstanceWidget;
switch (PatternInstance->GetOwner()->Type)
{
case EPatternType::Midi:
break;
case EPatternType::Automation:
break;
case EPatternType::Sample:
PatternInstanceWidget = SNew(SSamplePatternInstance)
.PatternInstance(PatternInstance)
.FrameViewRange(PlayListPanel.Pin()->GetViewRange())
.FrameToPixel_Raw(this, &FPlayListEdit::GetSampleToPixelScaler)
.SnapFrame_Raw(this, &FPlayListEdit::GetSnapFrame)
.Clipping(EWidgetClipping::ClipToBoundsAlways);
break;
}
return InstanceWidget;
return PatternInstanceWidget.ToSharedRef();
}
AudioFrame FPlayListEdit::GetSnapFrame() const

View File

@ -59,12 +59,6 @@ int32 SPlayListPanel::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedG
const AudioFrame RangeBegin = Range.GetLowerBoundValue();
const AudioFrame RangeEnd = Range.GetUpperBoundValue();
if (LastSize != Size)
{
LastSize = Size;
PlayListEdit->UpdateTexture(FrameToPixelScaler, TrackHeightManager.TrackHeight.Get());
}
++LayerId;
// 绘制轨道间隔线
for (int i = 0; i < TrackCount; ++i)
@ -107,6 +101,7 @@ int32 SPlayListPanel::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedG
);
}
++LayerId;
const AudioFrame BeatDelta = FMath::Fmod(RangeBegin, BeatFrameCount);
for (float i = RangeBegin - BeatDelta; i < RangeEnd; i += BeatFrameCount)
{
@ -115,7 +110,7 @@ int32 SPlayListPanel::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedG
Points.Add(FVector2f((i - RangeBegin) * FrameToPixelScaler, Size.Y));
FSlateDrawElement::MakeLines(
OutDrawElements,
++LayerId,
LayerId,
AllottedGeometry.ToPaintGeometry(),
Points,
ESlateDrawEffect::None,
@ -144,7 +139,7 @@ int32 SPlayListPanel::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedG
}
LayerId = PlayListEdit->OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
}
@ -214,16 +209,24 @@ AudioFrame SPlayListPanel::GetPixelToSampleScaler() const
FMargin SPlayListPanel::CalculatePatternInstancePos(FPatternInstance* PatternInstance) const
{
// const double Scaler = GetSampleToPixelScaler();
const double Scaler = GetSampleToPixelScaler();
check(PatternInstance->TrackIndex >= 0)
const TRange<AudioFrame>& Range = ViewRange.Get();
FMargin Offset;
Offset.Left = (PatternInstance->Pos - Range.GetLowerBoundValue()).Pos * GetSampleToPixelScaler(); // X
const AudioFrame XOffset = PatternInstance->Pos - Range.GetLowerBoundValue();
Offset.Left = XOffset.Pos * Scaler; // X
Offset.Left = FMath::Max(Offset.Left, 0.0f);
Offset.Top = TrackHeightManager.GetTrackPos(PatternInstance->TrackIndex); // Y
Offset.Bottom = TrackHeightManager.GetTrackHeight(PatternInstance->TrackIndex); // Height
Offset.Right = PatternInstance->GetLength().Pos * GetSampleToPixelScaler(); // Width
float LeftWidth = (PatternInstance->GetLength() + PatternInstance->Pos) - Range.GetLowerBoundValue();
LeftWidth = FMath::Min(PatternInstance->GetLength().Pos, LeftWidth);
Offset.Right = LeftWidth * Scaler; // Width
Offset.Right = FMath::Min(Offset.Right, GetTickSpaceGeometry().Size.X - Offset.Left);
return Offset;
}
@ -237,14 +240,33 @@ void SPlayListPanel::OnCreatePatternInstance(FPatternInstance* PatternInstance)
.Alignment(FVector2D(0, 0))
.Offset(Offset)
[
SNew(SInvalidationPanel)
[
// SNew(SInvalidationPanel)
// [
PlayListEdit->CreatePatternInstanceWidget(PatternInstance)
]
// ]
];
}
void SPlayListPanel::OnDeletePatternInstance(FPatternInstance* PatternInstance)
{
TPanelChildren<SConstraintCanvas::FSlot>* Children = (TPanelChildren<SConstraintCanvas::FSlot>*)TrackCanvas->GetChildren();
for (int i = 0; i < Children->Num(); ++i)
{
TSharedRef<SWidget> Widget = Children->GetChildAt(i);
// TSharedRef<SInvalidationPanel> InvalidationPanel = StaticCastSharedRef<SInvalidationPanel>(Widget);
// InvalidationPanel->SetCanCache(false);
// TSharedRef<SWidget> SharedRef = InvalidationPanel->GetChildren()->GetChildAt(0);
// InvalidationPanel->SetCanCache(true);
TSharedRef<SPatternInstance> InstanceWidget = StaticCastSharedRef<SPatternInstance>(Widget);
if (InstanceWidget->GetPatternInstance() == PatternInstance)
{
Children->RemoveAt(i);
break;
}
}
}
void SPlayListPanel::UpdateAllPatternInstance()
{
TPanelChildren<SConstraintCanvas::FSlot>* Children = (TPanelChildren<SConstraintCanvas::FSlot>*)TrackCanvas->GetChildren();
for (int i = 0; i < Children->Num(); ++i)
@ -255,11 +277,8 @@ void SPlayListPanel::OnDeletePatternInstance(FPatternInstance* PatternInstance)
TSharedRef<SWidget> SharedRef = InvalidationPanel->GetChildren()->GetChildAt(0);
InvalidationPanel->SetCanCache(true);
TSharedRef<SPatternInstance> InstanceWidget = StaticCastSharedRef<SPatternInstance>(SharedRef);
if (InstanceWidget->GetPatternInstance() == PatternInstance)
{
Children->RemoveAt(i);
break;
}
InstanceWidget->RequestUpdate();
// InstanceWidget->Invalidate(EInvalidateWidgetReason::Paint);
}
}

View File

@ -4,7 +4,6 @@
#include "CoreMinimal.h"
#include "FTrackHeightManager.h"
#include "PlayListPatternTextureManager.h"
#include "PlayListSelector.h"
#include "SCompoundWidget.h"
#include "Pattern/PatternInstance.h"
@ -49,8 +48,10 @@ public:
float GetMidiTickToPixelScaler() const { return 0.125f; }
AudioFrame GetSampleToPixelScaler() const;
AudioFrame GetPixelToSampleScaler() const;
TAttribute<TRange<AudioFrame>> GetViewRange() const { return ViewRange; }
virtual TSharedRef<SWidget> GetWidget() override { return AsShared(); }
void UpdateAllPatternInstance();
FTrackHeightManager TrackHeightManager;
@ -62,8 +63,6 @@ private:
void OnCreatePatternInstance(FPatternInstance* PatternInstance);
void OnDeletePatternInstance(FPatternInstance* PatternInstance);
mutable FVector2f LastSize;
TSharedPtr<SConstraintCanvas> TrackCanvas;
TSharedPtr<FPlayListEdit> PlayListEdit;

View File

@ -79,7 +79,7 @@ FReply SPlayList::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent&
{
if (Zooming)
{
const AudioFrame MinRange = FMidiSequencer::Get().GetBar();
const AudioFrame MinRange = FMidiSequencer::Get().GetBeat() * 4;
const TRange<AudioFrame>& CurrentRange = TargetFrameRange;
const AudioFrame WheelDelta = InMouseEvent.GetWheelDelta() * GetScalerLevel() * 2; // 乘以2是因为MousePosPercent

View File

@ -200,8 +200,6 @@ namespace Thumbnail
const double SampleZoom = (double)FrameRange.Size<int32>() / Width;
// const int32& BeginX = FMath::FloorToInt(BeginFrame / SampleZoom);
// const int32& EndX = FMath::Min(Width - 1, FMath::FloorToInt(EndFrame / SampleZoom));
for (int32 X = 0; X < Width; ++X)
{
if (FrameIndex >= EndFrame || FrameIndex >= FullFrameCount) break;
@ -252,6 +250,7 @@ namespace Thumbnail
const int64 BeginFrame = FrameRange.GetLowerBoundValue();
const int64 EndFrame = FrameRange.GetUpperBoundValue();
int64 FrameIndex = BeginFrame;
const uint16 ChannelCount = SampleBuffer.Num();
const double SampleZoom = (double)FrameRange.Size<int32>() / Width;
for (int32 X = 0; X < Width; ++X)
@ -259,16 +258,19 @@ namespace Thumbnail
if (FrameIndex >= EndFrame || FrameIndex >= FullFrameCount) break;
float cand_min = FLT_MAX;
float cand_max = FLT_MIN;
float cand_max = FLT_MIN_EXP;
int total_advance = SampleZoom;
do
{
if (FrameIndex >= EndFrame || FrameIndex >= FullFrameCount) break;
const float& Sample = SampleBuffer[0][FrameIndex];
if (cand_min > Sample)
cand_min = Sample;
if (cand_max < Sample)
cand_max = Sample;
for (uint16 Channel = 0; Channel < ChannelCount; ++Channel)
{
const float& Sample = SampleBuffer[Channel][FrameIndex];
if (cand_min > Sample)
cand_min = Sample;
if (cand_max < Sample)
cand_max = Sample;
}
if (total_advance > 0)
FrameIndex++;
}

View File

@ -1,32 +1,37 @@
 #include "WaveformViewer.h"
#include "WaveformViewer.h"
#include "Async.h"
#include "ExecutionTime.h"
#include "MaxElement.h"
#include "SlateComputeShader.h"
#include "SlatePixelShader.h"
#include "Thumbnail.h"
#include "Pattern/SamplePattern.h"
#include "Pattern/SamplePatternInstance.h"
SWaveformViewer::SWaveformViewer()
{
Param.WaveColor = FLinearColor(0.0f, 1.0f, 0.0f, 1.0f).ToFColorSRGB();
Param.BgColor = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f).ToFColorSRGB();
Param.LineUV = 0.0f;
}
void SWaveformViewer::Construct(const FArguments& InArgs)
{
Sampler.Load(TEXT("F:\\FL垃圾桶\\Kawaii Anokoga Kiniiranai.mp3"));
// Sampler.Load(TEXT("F:\\Sample\\cs1.6\\26_Kick_15_95_SP.wav"));
const TArray<TArray64<float>>& Buffer = Sampler.GetSampleBuffer();
Param.WaveColor = FLinearColor(0.0f, 1.0f, 1.0f, 1.0f).ToFColorSRGB();
Param.BgColor = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f).ToFColorSRGB();
WaveformHandle = InArgs._WaveformHandle;
FSlateRenderer* Renderer = FSlateApplication::Get().GetRenderer();
RenderTarget = Renderer->CreateRenderTargetTexture(16, 16, EPixelFormat::PF_R8G8B8A8);
RenderTarget->OnResizeDelegate.AddSP(this, &SWaveformViewer::OnResize);
RenderTarget = Renderer->CreateRenderTargetTexture(1, 1, EPixelFormat::PF_R8G8B8A8);
TArray<float> InitData;
InitData.SetNumZeroed(2);
if (Renderer->GetRendererAPI() == ERendererAPI::D3D11)
{
Params = Renderer->CreateComputeShaderParam(ComputeShader);
Params->Init();
Params->AddComputeShaderTextureParameter("Result", RenderTarget, 2);
Params->AddComputeShaderParam("Samples", Buffer[0].GetData(), Buffer[0].Num(), 0);
Params->AddComputeShaderParam("Samples", InitData.GetData(), InitData.Num(), 0);
Params->AddShaderParam("Params", (bool*)&Param, sizeof(FCSParam) / sizeof(bool));
ComputeShader = Renderer->CreateComputeShader(TEXT("Arona"), TEXT("WaveformCS"));
@ -38,7 +43,7 @@ void SWaveformViewer::Construct(const FArguments& InArgs)
Params = Renderer->CreateComputeShaderParam(ComputeShader);
Params->Init();
Params->AddComputeShaderTextureParameter("Result", RenderTarget, 2);
Params->AddComputeShaderParam("Samples", Buffer[0].GetData(), Buffer[0].Num(), 0);
Params->AddComputeShaderParam("Samples", InitData.GetData(), InitData.Num(), 0);
Params->AddComputeShaderParam("Params", (bool*)&Param, sizeof(FCSParam) / sizeof(bool), 1);
}
@ -48,52 +53,54 @@ void SWaveformViewer::Construct(const FArguments& InArgs)
void SWaveformViewer::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SLeafWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
UpdateTimer += InDeltaTime;
if (UpdateTimer > UpdateInterval && bNeedCompute)
{
UpdateTimer = 0.0f;
if (UpdateQueue.Peek())
Invalidate(EInvalidateWidgetReason::Paint);
}
Async(EAsyncExecution::TaskGraph, [this]()
{
const FIntPoint& Size = RenderTarget->GetSize();
UpdateTask Task;
Task.LineUV = 1.0f / Size.Y;
Task.Array = WaveformHandle->GetWaveform(Size.X);
Task.Size = Size;
UpdateQueue.Enqueue(Task);
});
bNeedCompute = false;
TArray<FSamplePeak> ScaleWaveform(const TArrayView<const FSamplePeak>& Waveform, const int32 SizeX)
{
TArray<FSamplePeak> Out;
Out.SetNum(SizeX);
const float Scale = (float)Waveform.Num() / SizeX;
float Index = 0;
for (int32 i = 0; i < SizeX; ++i)
{
const int32 Begin = FMath::FloorToInt(Index);
const int32 End = FMath::Min(FMath::CeilToInt(Index + Scale), Waveform.Num() - 1);
FSamplePeak& Peak = Out[i];
for (int32 j = Begin; j < End; ++j)
{
Peak += Waveform[j];
}
Index += Scale;
}
return Out;
}
int32 SWaveformViewer::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry,
const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId,
const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId,
const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
if (const UpdateTask* UpdateTask = UpdateQueue.Peek())
{
Param.LineUV = UpdateTask->LineUV;
Params->AddComputeShaderParam("Samples", UpdateTask->Array.GetData(), UpdateTask->Array.Num(), 0);
UpdateQueue.Pop();
const FIntPoint& Size = FIntPoint(AllottedGeometry.Size.X, AllottedGeometry.Size.Y);
if (Size.X <= 0 || Size.Y <= 0) return LayerId;
const TArrayView<const FSamplePeak>& SamplePeaks = WaveformHandle->GetWaveform(Size.X);
if (!SamplePeaks.Num()) return LayerId;
Param.LineUV = 1.0f / Size.Y;
Param.PeaksPerPixel = (float)SamplePeaks.Num() / Size.X;
const TArray<FSamplePeak>& Peaks = ScaleWaveform(SamplePeaks, Size.X);
Params->AddComputeShaderParam("Samples", (float*)Peaks.GetData(), Peaks.Num() * 2, 0);
if (FSlateApplication::Get().GetRenderer()->GetRendererAPI() == ERendererAPI::OpenGL)
Params->AddComputeShaderParam("Params", (bool*)&Param, sizeof(FCSParam) / sizeof(bool), 1);
else
Params->AddShaderParam("Params", (bool*)&Param, sizeof(FCSParam) / sizeof(bool));
Params->AddComputeShaderParam("Samples", UpdateTask->Array.GetData(), UpdateTask->Array.Num(), 0);
const FIntPoint& Size = UpdateTask->Size;
RenderTarget->Resize(Size);
FSlateDrawElement::MakeComputeShaderDispatch(OutDrawElements, LayerId, Size.X , Size.Y, 1, ComputeElement);
}
LayerId++;
RenderTarget->OnDrawViewport(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
FSlateDrawElement::MakeViewport(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), RenderTarget, ESlateDrawEffect::None, FLinearColor::White);
return LayerId;
}
void SWaveformViewer::OnResize(const FIntPoint& InSize) const
{
bNeedCompute = true;
}

View File

@ -1,5 +1,6 @@
#pragma once
#include "PixelShaderViewer.h"
#include "PeakFile/PeakFile.h"
#include "PluginHost/Sampler.h"
class FSamplePatternInstance;
@ -7,16 +8,18 @@ class FSamplePatternInstance;
class IWaveformHandle
{
public:
virtual TArray<float> GetWaveform(int32 SizeX) const = 0;
virtual ~IWaveformHandle() = default;
virtual TArrayView<const FSamplePeak> GetWaveform(int32 SizeX) = 0;
};
class ARONA_API SWaveformViewer : public SLeafWidget
{
public:
SWaveformViewer();
SLATE_BEGIN_ARGS(SWaveformViewer)
{
}
SLATE_ARGUMENT(TSharedPtr<IWaveformHandle>, WaveformHandle)
SLATE_END_ARGS()
/** Constructs this widget with InArgs */
@ -25,18 +28,11 @@ public:
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override { return RenderTarget->GetSize(); }
void OnResize(const FIntPoint& InSize) const;
void SetWaveformHandle(TSharedPtr<IWaveformHandle> InHandle) { WaveformHandle = InHandle; }
private:
TSharedPtr<FSlateRenderTarget> RenderTarget;
TSharedPtr<FSlateComputeShader> ComputeShader;
TSharedPtr<FSlateShaderParam> Params;
TSharedPtr<IComputeShaderSlateElement> ComputeElement;
FSampler Sampler;
mutable bool bNeedCompute = false;
float UpdateInterval = 0.1f;
float UpdateTimer = 0.0f;
struct UpdateTask
{
@ -51,6 +47,7 @@ private:
FLinearColor WaveColor;
FLinearColor BgColor;
float LineUV;
float PeaksPerPixel;
} mutable Param;
TSharedPtr<IWaveformHandle> WaveformHandle;
};

View File

@ -16,8 +16,9 @@ FSampler::FSampler()
IncomingRange = TRange<AudioFrame>(0, 0);
}
bool FSampler::Load(const FString& Path)
bool FSampler::Load(const FString& InPath)
{
Path = InPath;
#if PLATFORM_WINDOWS
SndfileHandle FileHandle(TCHAR_TO_WCHAR(*Path));
#else

View File

@ -3,16 +3,19 @@
#include "Midi/MidiType.h"
#include "Midi/Time/TimePos.h"
class FSampler : public FPluginHost
{
public:
typedef TArray<TArray64<float>> FSampleBuffer;
FSampler();
virtual bool Load(const FString& Path) override;
virtual bool Load(const FString& InPath) override;
virtual void UpdateSampleRate(float InSampleRate) override;
virtual void Process(int32 FrameNum) override;
const TArray<TArray64<float>>& GetSampleBuffer() const { return SampleBuffer; }
const FSampleBuffer& GetSampleBuffer() const { return SampleBuffer; }
float GetSampleInterlace(uint32 Index) const { return SampleBuffer[Index % ChannelCount][Index / ChannelCount]; }
@ -20,6 +23,7 @@ public:
float GetSampleRate() const { return SampleRate; }
uint32 GetFrameCount() const { return FrameCount; }
uint32 GetSampleCount() const { return FrameCount * ChannelCount; }
const FString& GetPath() const { return Path; }
uint32 IncomingOffset;
TRange<AudioFrame> IncomingRange;
@ -31,5 +35,6 @@ private:
uint32 FrameCount;
float SampleRate;
TArray<TArray64<float>> SampleBuffer;
FSampleBuffer SampleBuffer;
FString Path;
};