#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 NoteRange = TRange::Empty(); int32 EndTime = 0; const TMap& AllSequence = MidiPattern->GetSequence(); for (const auto& Sequence : AllSequence) { TRange 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() + 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 NoteRange = MidiMessageSequence->GetNoteRange(); if (NoteRange.GetLowerBoundValue() == FMidiMessage::MaxNoteNumber && NoteRange.GetUpperBoundValue() == 0) return; const int32 NoteRangeSize = NoteRange.Size() + 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>& SampleBuffer, const int32 Channels, int64 SampleCount, TRange SampleRange) { const auto& ImageSize = ImageData.TextureSize; const uint32 FrameCount = SampleCount / Channels; const float XScaler = static_cast(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>& SampleBuffer, const int32 Channels, int64 FullFrameCount, TRange 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() / 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; 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 GenerateWaveformData(int32 Width, const TArray>& SampleBuffer, int64 FullFrameCount, TRange FrameRange) { TArray Result; // X: Top Y: Bottom Result.Reset(Width * 2); const int64 BeginFrame = FrameRange.GetLowerBoundValue(); const int64 EndFrame = FrameRange.GetUpperBoundValue(); int64 FrameIndex = BeginFrame; const double SampleZoom = (double)FrameRange.Size() / 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); Result.Add(cand_min); Result.Add(cand_max); } return Result; } }