/** * Cook a multistream (normally 5.1) wave */ void CookSurroundWave( USoundWave* SoundWave, FName FormatName, const IAudioFormat& Format, TArray<uint8>& Output) { check(!Output.Num()); #if WITH_EDITORONLY_DATA int32 i; uint32 SampleDataSize = 0; FWaveModInfo WaveInfo; TArray<TArray<uint8> > SourceBuffers; TArray<int32> RequiredChannels; uint8* RawWaveData = ( uint8* )SoundWave->RawData.Lock( LOCK_READ_ONLY ); // Front left channel is the master static_assert(SPEAKER_FrontLeft == 0, "Front-left speaker must be first."); // loop through channels to find which have data and which are required for (i = 0; i < SPEAKER_Count; i++) { FWaveModInfo WaveInfoInner; // Only mono files allowed if (WaveInfoInner.ReadWaveHeader(RawWaveData, SoundWave->ChannelSizes[i], SoundWave->ChannelOffsets[i]) && *WaveInfoInner.pChannels == 1) { if (SampleDataSize == 0) { // keep wave info/size of first channel data we find WaveInfo = WaveInfoInner; SampleDataSize = WaveInfo.SampleDataSize; } switch (i) { case SPEAKER_FrontLeft: case SPEAKER_FrontRight: case SPEAKER_LeftSurround: case SPEAKER_RightSurround: // Must have quadraphonic surround channels RequiredChannels.AddUnique(SPEAKER_FrontLeft); RequiredChannels.AddUnique(SPEAKER_FrontRight); RequiredChannels.AddUnique(SPEAKER_LeftSurround); RequiredChannels.AddUnique(SPEAKER_RightSurround); break; case SPEAKER_FrontCenter: case SPEAKER_LowFrequency: // Must have 5.1 surround channels for (int32 Channel = SPEAKER_FrontLeft; Channel <= SPEAKER_RightSurround; Channel++) { RequiredChannels.AddUnique(Channel); } break; case SPEAKER_LeftBack: case SPEAKER_RightBack: // Must have all previous channels for (int32 Channel = 0; Channel < i; Channel++) { RequiredChannels.AddUnique(Channel); } break; default: // unsupported channel count break; } } } if (SampleDataSize != 0) { int32 ChannelCount = 0; // Extract all the info for channels or insert blank data for( i = 0; i < SPEAKER_Count; i++ ) { FWaveModInfo WaveInfoInner; if( WaveInfoInner.ReadWaveHeader( RawWaveData, SoundWave->ChannelSizes[ i ], SoundWave->ChannelOffsets[ i ] ) && *WaveInfoInner.pChannels == 1 ) { ChannelCount++; TArray<uint8>& Input = *new (SourceBuffers) TArray<uint8>; Input.AddUninitialized(WaveInfoInner.SampleDataSize); FMemory::Memcpy(Input.GetData(), WaveInfoInner.SampleDataStart, WaveInfoInner.SampleDataSize); SampleDataSize = FMath::Min<uint32>(WaveInfoInner.SampleDataSize, SampleDataSize); } else if (RequiredChannels.Contains(i)) { // Add an empty channel for cooking ChannelCount++; TArray<uint8>& Input = *new (SourceBuffers) TArray<uint8>; Input.AddZeroed(SampleDataSize); } } // Only allow the formats that can be played back through if( ChannelCount == 4 || ChannelCount == 6 || ChannelCount == 7 || ChannelCount == 8 ) { UE_LOG(LogAudioDerivedData, Log, TEXT( "Cooking %d channels for: %s" ), ChannelCount, *SoundWave->GetFullName() ); FSoundQualityInfo QualityInfo = { 0 }; QualityInfo.Quality = SoundWave->CompressionQuality; QualityInfo.NumChannels = ChannelCount; QualityInfo.SampleRate = *WaveInfo.pSamplesPerSec; QualityInfo.SampleDataSize = SampleDataSize; QualityInfo.DebugName = SoundWave->GetFullName(); //@todo tighten up the checking for empty results here if(Format.CookSurround(FormatName, SourceBuffers, QualityInfo, Output)) { if (SoundWave->SampleRate != *WaveInfo.pSamplesPerSec) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Updated SoundWave->SampleRate during cooking %s." ), *SoundWave->GetFullName() ); SoundWave->SampleRate = *WaveInfo.pSamplesPerSec; } if (SoundWave->NumChannels != ChannelCount) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Updated SoundWave->NumChannels during cooking %s." ), *SoundWave->GetFullName() ); SoundWave->NumChannels = ChannelCount; } if (SoundWave->RawPCMDataSize != SampleDataSize * ChannelCount) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Updated SoundWave->RawPCMDataSize during cooking %s." ), *SoundWave->GetFullName() ); SoundWave->RawPCMDataSize = SampleDataSize * ChannelCount; } if (SoundWave->Duration != ( float )SampleDataSize / (SoundWave->SampleRate * sizeof( int16 ))) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Updated SoundWave->Duration during cooking %s." ), *SoundWave->GetFullName() ); SoundWave->Duration = ( float )SampleDataSize / (SoundWave->SampleRate * sizeof( int16 )); } } } else { UE_LOG(LogAudioDerivedData, Warning, TEXT( "No format available for a %d channel surround sound: %s" ), ChannelCount, *SoundWave->GetFullName() ); } } else { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Cooking surround sound failed: %s" ), *SoundWave->GetPathName() ); } SoundWave->RawData.Unlock(); #endif }
void USoundWaveThumbnailRenderer::Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget*, FCanvas* Canvas) { static bool bDrawChannels = true; static bool bDrawAsCurve = false; USoundWave* SoundWave = Cast<USoundWave>(Object); if (SoundWave != nullptr && SoundWave->NumChannels > 0) { // check if there is any raw sound data if( SoundWave->RawData.GetBulkDataSize() > 0 ) { FCanvasLineItem LineItem; LineItem.SetColor( FLinearColor::White ); // Lock raw wave data. uint8* RawWaveData = ( uint8* )SoundWave->RawData.Lock( LOCK_READ_ONLY ); int32 RawDataSize = SoundWave->RawData.GetBulkDataSize(); FWaveModInfo WaveInfo; // parse the wave data if( WaveInfo.ReadWaveHeader( RawWaveData, RawDataSize, 0 ) ) { const float SampleYScale = Height / (2.f * 32767 * (bDrawChannels ? SoundWave->NumChannels : 1)); int16* SamplePtr = reinterpret_cast<int16*>(WaveInfo.SampleDataStart); uint32 SampleCount = 0; uint32 SampleCounts[10] = {0}; if (SoundWave->NumChannels <= 2) { SampleCount = WaveInfo.SampleDataSize / (2 * SoundWave->NumChannels); } else { for (int32 ChannelIndex = 0; ChannelIndex < SoundWave->NumChannels; ++ChannelIndex) { SampleCounts[ChannelIndex] = SoundWave->ChannelSizes[ChannelIndex] / 2; SampleCount = FMath::Max(SampleCount, SampleCounts[ChannelIndex]); } } const uint32 SamplesPerX = (SampleCount / Width) + 1; float LastScaledSample[10] = {0.f}; for (uint32 XOffset = 0; XOffset < Width-1; ++XOffset ) { int64 SampleSum[10] = {0}; if (SoundWave->NumChannels <= 2) { for (uint32 PerXSampleIndex = 0; PerXSampleIndex < SamplesPerX; ++PerXSampleIndex) { for (int32 ChannelIndex = 0; ChannelIndex < SoundWave->NumChannels; ++ChannelIndex) { const int16 SampleValue = (bDrawAsCurve ? *SamplePtr : FMath::Abs(*SamplePtr)); SampleSum[ChannelIndex] += SampleValue; ++SamplePtr; } } } else { for (int32 ChannelIndex = 0; ChannelIndex < SoundWave->NumChannels; ++ChannelIndex) { if (SampleCounts[ChannelIndex] >= SamplesPerX) { for (uint32 PerXSampleIndex = 0; PerXSampleIndex < SamplesPerX; ++PerXSampleIndex) { int16 SampleValue =*(SamplePtr + (SamplesPerX * XOffset) + PerXSampleIndex + SoundWave->ChannelOffsets[ChannelIndex] / 2); if (!bDrawAsCurve) { SampleValue = FMath::Abs(SampleValue); } SampleSum[ChannelIndex] += SampleValue; } SampleCounts[ChannelIndex] -= SamplesPerX; } } } if (bDrawChannels) { for (int32 ChannelIndex = 0; ChannelIndex < SoundWave->NumChannels; ++ChannelIndex) { const float ScaledSample = static_cast<float>(SampleSum[ChannelIndex]) / SamplesPerX * SampleYScale; if (bDrawAsCurve) { if (XOffset > 0) { const float YCenter = Y + ((2 * ChannelIndex) + 1) * Height / (2.f * SoundWave->NumChannels); LineItem.Draw( Canvas, FVector2D(X + XOffset - 1, YCenter + LastScaledSample[ChannelIndex]), FVector2D(X + XOffset, YCenter + ScaledSample ) ); } LastScaledSample[ChannelIndex] = ScaledSample; } else if (ScaledSample > 0.001f) { const float YCenter = Y + ((2 * ChannelIndex) + 1) * Height / (2.f * SoundWave->NumChannels); LineItem.Draw( Canvas, FVector2D(X + XOffset, YCenter - ScaledSample), FVector2D(X + XOffset, YCenter + ScaledSample) ); } } } else { if (bDrawAsCurve) { float ScaledSampleSum = 0.f; int32 ActiveChannelCount = 0; for (int32 ChannelIndex = 0; ChannelIndex < SoundWave->NumChannels; ++ChannelIndex) { const float ScaledSample = static_cast<float>(SampleSum[ChannelIndex]) / SamplesPerX * SampleYScale; if (FMath::Abs(ScaledSample) > 0.001f) { ScaledSampleSum += ScaledSample; ++ActiveChannelCount; } } const float ScaledSample = (ActiveChannelCount > 0 ? ScaledSampleSum / ActiveChannelCount : 0.f); if (XOffset > 0) { const float YCenter = Y + 0.5f * Height; LineItem.Draw( Canvas, FVector2D(X + XOffset - 1, YCenter + LastScaledSample[0]), FVector2D(X + XOffset, YCenter + ScaledSample) ); } LastScaledSample[0] = ScaledSample; } else { float MaxScaledSample = 0.f; for (int32 ChannelIndex = 0; ChannelIndex < SoundWave->NumChannels; ++ChannelIndex) { const float ScaledSample = static_cast<float>(SampleSum[ChannelIndex]) / SamplesPerX * SampleYScale; MaxScaledSample = FMath::Max(MaxScaledSample, ScaledSample); } if (MaxScaledSample > 0.001f) { const float YCenter = Y + 0.5f * Height; LineItem.Draw( Canvas, FVector2D(X + XOffset, YCenter - MaxScaledSample), FVector2D(X + XOffset, YCenter + MaxScaledSample) ); } } } } } SoundWave->RawData.Unlock(); } } }
/** * Cook a simple mono or stereo wave */ static void CookSimpleWave(USoundWave* SoundWave, FName FormatName, const IAudioFormat& Format, TArray<uint8>& Output) { FWaveModInfo WaveInfo; TArray<uint8> Input; check(!Output.Num()); bool bWasLocked = false; // check if there is any raw sound data if( SoundWave->RawData.GetBulkDataSize() > 0 ) { // Lock raw wave data. uint8* RawWaveData = ( uint8* )SoundWave->RawData.Lock( LOCK_READ_ONLY ); bWasLocked = true; int32 RawDataSize = SoundWave->RawData.GetBulkDataSize(); // parse the wave data if( !WaveInfo.ReadWaveHeader( RawWaveData, RawDataSize, 0 ) ) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Only mono or stereo 16 bit waves allowed: %s (%d bytes)" ), *SoundWave->GetFullName(), RawDataSize ); } else { Input.AddUninitialized(WaveInfo.SampleDataSize); FMemory::Memcpy(Input.GetData(), WaveInfo.SampleDataStart, WaveInfo.SampleDataSize); } } if(!Input.Num()) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Can't cook %s because there is no source compressed or uncompressed PC sound data" ), *SoundWave->GetFullName() ); } else { FSoundQualityInfo QualityInfo = { 0 }; QualityInfo.Quality = SoundWave->CompressionQuality; QualityInfo.NumChannels = *WaveInfo.pChannels; QualityInfo.SampleRate = *WaveInfo.pSamplesPerSec; QualityInfo.SampleDataSize = Input.Num(); QualityInfo.DebugName = SoundWave->GetFullName(); // Cook the data. if(Format.Cook(FormatName, Input, QualityInfo, Output)) { //@todo tighten up the checking for empty results here if (SoundWave->SampleRate != *WaveInfo.pSamplesPerSec) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Updated SoundWave->SampleRate during cooking %s." ), *SoundWave->GetFullName() ); SoundWave->SampleRate = *WaveInfo.pSamplesPerSec; } if (SoundWave->NumChannels != *WaveInfo.pChannels) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Updated SoundWave->NumChannels during cooking %s." ), *SoundWave->GetFullName() ); SoundWave->NumChannels = *WaveInfo.pChannels; } if (SoundWave->RawPCMDataSize != Input.Num()) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Updated SoundWave->RawPCMDataSize during cooking %s." ), *SoundWave->GetFullName() ); SoundWave->RawPCMDataSize = Input.Num(); } if (SoundWave->Duration != ( float )SoundWave->RawPCMDataSize / (SoundWave->SampleRate * sizeof( int16 ) * SoundWave->NumChannels)) { UE_LOG(LogAudioDerivedData, Warning, TEXT( "Updated SoundWave->Duration during cooking %s." ), *SoundWave->GetFullName() ); SoundWave->Duration = ( float )SoundWave->RawPCMDataSize / (SoundWave->SampleRate * sizeof( int16 ) * SoundWave->NumChannels); } } } if (bWasLocked) { SoundWave->RawData.Unlock(); } }