AronaSlate/Source/Arona/UI/Widget/Thumbnail.cpp

284 lines
9.1 KiB
C++

#include "Thumbnail.h"
#include "Render/UpdatableTexture.h"
#include "Midi/MidiMessageSequence.h"
#include "Pattern/MidiPattern.h"
#include "PluginHost/PluginHost.h"
namespace Thumbnail
{
void GenerateMidiPatternThumbnail(const FMidiPattern* MidiPattern, FImageData& Data, FColor NoteColor)
{
TRange<int32> NoteRange = TRange<int32>::Empty();
int32 EndTime = 0;
const TMap<FPluginHost*, FMidiMessageSequence>& AllSequence = MidiPattern->GetSequence();
for (const auto& Sequence : AllSequence)
{
TRange<int32> CurrentNoteRange = Sequence.Value.GetNoteRange();
const int32 Min = FMath::Min(NoteRange.GetLowerBoundValue(), CurrentNoteRange.GetLowerBoundValue());
const int32 Max = FMath::Max(NoteRange.GetUpperBoundValue(), CurrentNoteRange.GetUpperBoundValue());
NoteRange.SetLowerBound(Min);
NoteRange.SetUpperBound(Max);
EndTime = FMath::Max(EndTime, Sequence.Value.getEndTime());
}
const int32 NoteRangeSize = NoteRange.Size<int32>() + 1;
const FIntPoint& ImageSize = Data.TextureSize;
// 根据音符范围计算每个音符的高度
float NoteHeight;
if (NoteRangeSize == 1)
{
NoteHeight = ImageSize.Y * 0.2f;
NoteRange.SetLowerBound(NoteRange.GetLowerBoundValue() - 2);
NoteRange.SetUpperBound(NoteRange.GetUpperBoundValue() + 2);
}
else
{
NoteHeight = ImageSize.Y / (float)NoteRangeSize;
}
for (const auto& Tuple : AllSequence)
{
const FMidiMessageSequence& MidiMessageSequence = Tuple.Value;
// 绘制音符
for (int32 i = 0; i < MidiMessageSequence.getNumEvents(); ++i)
{
FMidiMessageSequence::MidiEventHolder* MidiEventHolder = MidiMessageSequence.getEventPointer(i);
const FMidiMessageSequence::MidiEventHolder* NoteOffObject = MidiEventHolder->noteOffObject;
if (!NoteOffObject)
continue;
FMidiMessage& MidiEvent = MidiEventHolder->message;
const double EventTime = MidiEvent.getTimeStamp();
const double EventDuration = NoteOffObject->message.getTimeStamp() - EventTime;
// if (EventTime > EndTime)
// break;
const int32 Note = MidiEvent.getNoteNumber() - NoteRange.GetLowerBoundValue();
const float NoteY = ImageSize.Y - (Note * NoteHeight) - NoteHeight;
const float NoteWidth = EventDuration / EndTime * ImageSize.X;
const float NoteX = EventTime / EndTime * ImageSize.X;
for (int32 x = NoteX; x < NoteX + NoteWidth; ++x)
{
for (int32 y = NoteY; y < NoteY + NoteHeight; ++y)
{
#if !UE_BUILD_SHIPPING
check(x < ImageSize.X)
check(y < ImageSize.Y)
#endif
Data.DrawPixel(x, y, NoteColor);
}
}
}
}
}
void GenerateMidiThumbnail(const FMidiMessageSequence* MidiMessageSequence, FImageData& Data)
{
TRange<int32> NoteRange = MidiMessageSequence->GetNoteRange();
if (NoteRange.GetLowerBoundValue() == FMidiMessage::MaxNoteNumber && NoteRange.GetUpperBoundValue() == 0)
return;
const int32 NoteRangeSize = NoteRange.Size<int32>() + 1;
const FIntPoint& ImageSize = Data.TextureSize;
const double EndTime = MidiMessageSequence->getEndTime();
// 根据音符范围计算每个音符的高度
float NoteHeight;
if (NoteRangeSize == 1)
{
NoteHeight = ImageSize.Y * 0.2f;
NoteRange.SetLowerBound(NoteRange.GetLowerBoundValue() - 2);
NoteRange.SetUpperBound(NoteRange.GetUpperBoundValue() + 2);
}
else
{
NoteHeight = ImageSize.Y / (float)NoteRangeSize;
}
// 绘制音符
for (int32 i = 0; i < MidiMessageSequence->getNumEvents(); ++i)
{
FMidiMessageSequence::MidiEventHolder* MidiEventHolder = MidiMessageSequence->getEventPointer(i);
const FMidiMessageSequence::MidiEventHolder* NoteOffObject = MidiEventHolder->noteOffObject;
if (!NoteOffObject)
continue;
FMidiMessage& MidiEvent = MidiEventHolder->message;
const double EventTime = MidiEvent.getTimeStamp();
const double EventDuration = NoteOffObject->message.getTimeStamp() - EventTime;
// if (EventTime > EndTime)
// break;
const int32 Note = MidiEvent.getNoteNumber() - NoteRange.GetLowerBoundValue();
const float NoteY = ImageSize.Y - (Note * NoteHeight) - NoteHeight;
// const float NoteHeightHalf = NoteHeight * 0.5f;
const int32 NoteVelocity = MidiEvent.getVelocity();
const float NoteWidth = EventDuration / EndTime * ImageSize.X;
const float NoteX = EventTime / EndTime * ImageSize.X;
const FColor NoteColor = FColor::MakeRedToGreenColorFromScalar((float)NoteVelocity / 127.f);
for (int32 x = NoteX; x < NoteX + NoteWidth; ++x)
{
for (int32 y = NoteY; y < NoteY + NoteHeight; ++y)
{
#if !UE_BUILD_SHIPPING
check(x < ImageSize.X)
check(y < ImageSize.Y)
#endif
Data.DrawPixel(x, y, NoteColor);
}
}
}
}
void GenerateWaveformRange1(FImageData& ImageData, const TArray<TArray<float>>& SampleBuffer, const int32 Channels, int64 SampleCount, TRange<int32> SampleRange)
{
const auto& ImageSize = ImageData.TextureSize;
const uint32 FrameCount = SampleCount / Channels;
const float XScaler = static_cast<float>(ImageSize.X) / FrameCount;
const int32 HalfChannelHeight = ImageSize.Y / (Channels * 2);
const uint32 BeginFrame = SampleRange.GetLowerBoundValue() / Channels;
int32 End = SampleRange.GetUpperBoundValue();
if (XScaler > 0.003)
{
// 绘制波形(连线)
for (int Channel = 0; Channel < Channels; ++Channel)
{
const float& Clamp = FMath::Clamp(SampleBuffer[Channel][BeginFrame], -1, 1);
float LastY = (Clamp + 1) * HalfChannelHeight + HalfChannelHeight * Channel * 2;
float LastX = BeginFrame * XScaler;
for (uint32 i = BeginFrame + 1; i < FrameCount; i++)
{
const int32 X = i / Channels * XScaler;
if (X >= ImageSize.X)
continue;
const float& Element = FMath::Clamp(SampleBuffer[Channel][i], -1, 1);
float Y = (Element + 1) * HalfChannelHeight + Channel * HalfChannelHeight * 2 + 1;
Y = FMath::Min(Y, ImageSize.Y - 1);
ImageData.DrawLine(FIntPoint(LastX, LastY), FIntPoint(X, Y), FColor::Green);
LastX = X;
LastY = Y;
}
}
}
else
{
// 绘制波形(点)
for (int32 Channel = 0; Channel < Channels; ++Channel)
{
for (uint32 i = BeginFrame; i < FrameCount; i++)
{
const float& Element = FMath::Clamp(SampleBuffer[Channel][i], -1, 1);
const int32 Y = (Element + 1) * HalfChannelHeight + Channel * HalfChannelHeight * 2;
const int32 X = i * XScaler;
if (X >= ImageSize.X)
continue;
ImageData.DrawPixel(X, FMath::Min(Y, ImageData.TextureSize.Y - 1), FColor::Green);
}
}
}
}
void GenerateWaveformRange(FImageData& ImageData, const TArray<TArray64<float>>& SampleBuffer, const int32 Channels, int64 FullFrameCount, TRange<int32> FrameRange)
{
const int Width = ImageData.TextureSize.X;
const int Height = ImageData.TextureSize.Y;
const float HalfHeight = Height * 0.5f;
const int64 BeginFrame = FrameRange.GetLowerBoundValue();
const int64 EndFrame = FrameRange.GetUpperBoundValue();
int64 FrameIndex = BeginFrame;
const double SampleZoom = (double)FrameRange.Size<int32>() / Width;
for (int32 X = 0; X < Width; ++X)
{
if (FrameIndex >= EndFrame || FrameIndex >= FullFrameCount) break;
float cand_min = FLT_MAX;
float cand_max = FLT_MIN;
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;
if (total_advance > 0)
FrameIndex++;
}
while (--total_advance > 0);
int32 y1 = (cand_min + 1) * HalfHeight;
int32 y2 = (cand_max + 1) * HalfHeight;
if (y1 > y2)
{
y2 ^= y1;
y1 ^= y2;
y2 ^= y1;
}
if (y1 < 0) y1 = 0;
if (y1 >= Height) y1 = Height - 1;
if (y2 < 0) y2 = 0;
if (y2 >= Height) y2 = Height - 1;
const int s1 = Height - y1 - 1;
const int s2 = Height - y2 - 1;
for (int Y = s2; Y <= s1; Y++)
{
ImageData.DrawPixel(X, Y, FColor::Green);
}
}
}
TArray<float> GenerateWaveformData(int32 Width, const TArray<TArray64<float>>& SampleBuffer, int64 FullFrameCount, TRange<uint32> FrameRange)
{
TArray<float> Result; // X: Top Y: Bottom
Result.Reset(Width * 2);
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)
{
if (FrameIndex >= EndFrame || FrameIndex >= FullFrameCount) break;
float cand_min = FLT_MAX;
float cand_max = FLT_MIN_EXP;
int total_advance = SampleZoom;
do
{
if (FrameIndex >= EndFrame || FrameIndex >= FullFrameCount) break;
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++;
}
while (--total_advance > 0);
Result.Add(cand_min);
Result.Add(cand_max);
}
return Result;
}
}