284 lines
9.1 KiB
C++
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;
|
|
}
|
|
} |