27 const TArray<uint8>& InPCMData,
28 const int32 InSampleRate,
29 const int32 InChannel,
30 const int32 InBitsPerSample)
32 TArray<uint8> WavData;
34 const int32 ByteRate = InSampleRate * InChannel * InBitsPerSample / 8;
35 const int32 BlockAlign = InChannel * InBitsPerSample / 8;
36 const int32 DataSize = InPCMData.Num();
37 const int32 ChunkSize = 36 + DataSize;
40 WavData.Append(
reinterpret_cast<const uint8*
>(
"RIFF"), 4);
41 WavData.Append(
reinterpret_cast<const uint8*
>(&ChunkSize), 4);
42 WavData.Append(
reinterpret_cast<const uint8*
>(
"WAVE"), 4);
45 WavData.Append(
reinterpret_cast<const uint8*
>(
"fmt "), 4);
46 int32 SubChunk1Size = 16;
47 int16 AudioFormat = 1;
49 WavData.Append(
reinterpret_cast<const uint8*
>(&SubChunk1Size), 4);
50 WavData.Append(
reinterpret_cast<const uint8*
>(&AudioFormat), 2);
51 WavData.Append(
reinterpret_cast<const uint8*
>(&InChannel), 2);
52 WavData.Append(
reinterpret_cast<const uint8*
>(&InSampleRate), 4);
53 WavData.Append(
reinterpret_cast<const uint8*
>(&ByteRate), 4);
54 WavData.Append(
reinterpret_cast<const uint8*
>(&BlockAlign), 2);
55 WavData.Append(
reinterpret_cast<const uint8*
>(&InBitsPerSample), 2);
56 WavData.Append(
reinterpret_cast<const uint8*
>(
"data"), 4);
57 WavData.Append(
reinterpret_cast<const uint8*
>(&DataSize), 4);
59 WavData.Append(InPCMData);
66 if (InWavData.Num() == 0)
68 PRINTLOG( TEXT(
"WavData is empty, nothing to save."));
72 FString FileName = InFileName;
73 if (FileName.IsEmpty())
76 const FDateTime Now = FDateTime::Now();
77 FileName = FString::Printf(TEXT(
"Voice_%04d%02d%02d_%02d%02d%02d.wav"),
78 Now.GetYear(), Now.GetMonth(), Now.GetDay(),
79 Now.GetHour(), Now.GetMinute(), Now.GetSecond()
83 FString FolderPath = FPaths::ProjectSavedDir() /
VOICE_LOG;
85 IFileManager::Get().MakeDirectory(*FolderPath,
true);
87 FString FullPath = FolderPath / FileName;
88 if (FFileHelper::SaveArrayToFile(InWavData, *FullPath))
91 FString AbsolutePath = FPaths::ConvertRelativePathToFull(FullPath);
92 PRINTLOG( TEXT(
"Saved WAV file: %s"), *AbsolutePath);
97 PRINTLOG( TEXT(
"Failed to save WAV file: %s"), *FullPath);
104 if (WavData.Num() < 44)
106 PRINTLOG( TEXT(
"Invalid WAV data (too small)"));
110 const uint8* RawData = WavData.GetData();
120 uint16 BitsPerSample =
ReadUInt16(RawData, 34);
123 int32 DataChunkOffset = 36;
124 while (DataChunkOffset + 8 < WavData.Num())
127 RawData[DataChunkOffset] |
128 (RawData[DataChunkOffset + 1] << 8) |
129 (RawData[DataChunkOffset + 2] << 16) |
130 (RawData[DataChunkOffset + 3] << 24);
132 uint32 ChunkSize =
ReadUInt32(RawData, DataChunkOffset + 4);
135 if (ChunkID ==
'atad')
141 DataChunkOffset += (8 + ChunkSize);
144 if (DataChunkOffset + 8 >= WavData.Num())
146 PRINTLOG( TEXT(
"WAV data chunk not found"));
150 const int32 DataStart = DataChunkOffset + 8;
151 const int32 DataSize = WavData.Num() - DataStart;
154 USoundWave* SoundWave = NewObject<USoundWave>();
157 PRINTLOG( TEXT(
"Failed to create USoundWave"));
161 SoundWave->SoundGroup = ESoundGroup::SOUNDGROUP_Voice;
162 SoundWave->DecompressionType = EDecompressionType::DTYPE_Procedural;
163 SoundWave->bLooping =
false;
165 SoundWave->NumChannels = NumChannels;
166 SoundWave->Duration = (float)DataSize / (SampleRate * NumChannels * (BitsPerSample / 8));
167 SoundWave->SetSampleRate(SampleRate);
168 SoundWave->RawPCMDataSize = DataSize;
171 uint8* PCMData = (uint8*)FMemory::Malloc(DataSize);
172 FMemory::Memcpy(PCMData, RawData + DataStart, DataSize);
173 SoundWave->RawPCMData = PCMData;
181 const FDateTime Now = FDateTime::Now();
182 const FString FileName = FString::Printf(TEXT(
"TTS_Output_%04d%02d%02d_%02d%02d%02d.wav"),
183 Now.GetYear(), Now.GetMonth(), Now.GetDay(),
184 Now.GetHour(), Now.GetMinute(), Now.GetSecond()
186 const FString FolderPath = FPaths::ProjectSavedDir() /
VOICE_LOG;
187 IFileManager::Get().MakeDirectory(*FolderPath,
true);
188 const FString SavePath = FolderPath / FileName;
190 if (!FFileHelper::SaveArrayToFile(AudioData, *SavePath))
196 PRINTLOG(TEXT(
"TTS WAV 저장 완료: %s"), *SavePath);
198 if (AudioData.Num() < 44)
200 PRINTLOG(TEXT(
"Invalid WAV data (too small)"));
204 const uint8* RawData = AudioData.GetData();
209 uint16 BitsPerSample =
ReadUInt16(RawData, 34);
212 int32 DataChunkOffset = 36;
213 while (DataChunkOffset + 8 < AudioData.Num())
216 const char* ChunkIDStr = (
const char*)(RawData + DataChunkOffset);
218 if (strncmp(ChunkIDStr,
"data", 4) == 0)
224 uint32 ChunkSize =
ReadUInt32(RawData, DataChunkOffset + 4);
225 DataChunkOffset += (8 + ChunkSize);
228 if (DataChunkOffset + 8 >= AudioData.Num())
230 PRINTLOG(TEXT(
"WAV 'data' chunk not found"));
234 const uint32 DataSize =
ReadUInt32(RawData, DataChunkOffset + 4);
235 const int32 DataStart = DataChunkOffset + 8;
237 if (DataStart + (int32)DataSize > AudioData.Num())
239 PRINTLOG(TEXT(
"Invalid WAV data chunk size"));
244 USoundWaveProcedural* SoundWave = NewObject<USoundWaveProcedural>();
247 PRINTLOG(TEXT(
"Failed to create USoundWaveProcedural"));
252 const float BytesPerSample = BitsPerSample / 8.0f;
253 const float ActualDuration = (float)DataSize / (SampleRate * NumChannels * BytesPerSample);
255 SoundWave->SetSampleRate(SampleRate);
256 SoundWave->NumChannels = NumChannels;
257 SoundWave->Duration = ActualDuration;
258 SoundWave->SoundGroup = ESoundGroup::SOUNDGROUP_Voice;
259 SoundWave->bLooping =
false;
262 SoundWave->QueueAudio(RawData + DataStart, DataSize);
268 if (InSampleRate == OutSampleRate)
272 const int32 NumSamples = InPCMData.Num() /
sizeof(int16);
273 const int16* InSamples =
reinterpret_cast<const int16*
>(InPCMData.GetData());
276 const double ResampleRatio =
static_cast<double>(OutSampleRate) /
static_cast<double>(InSampleRate);
277 const int32 OutNumSamples = FMath::CeilToInt(NumSamples * ResampleRatio);
279 TArray<uint8> OutPCMData;
280 OutPCMData.Reserve(OutNumSamples *
sizeof(int16));
283 for (int32 i = 0; i < OutNumSamples; ++i)
285 const double SourceIndex = i / ResampleRatio;
286 const int32 Index0 = FMath::FloorToInt(SourceIndex);
287 const int32 Index1 = FMath::Min(Index0 + 1, NumSamples - 1);
288 const double Fraction = SourceIndex - Index0;
291 const int16 Sample0 = InSamples[Index0];
292 const int16 Sample1 = InSamples[Index1];
293 const int16 InterpolatedSample = FMath::RoundToInt(Sample0 + (Sample1 - Sample0) * Fraction);
296 const uint8* SampleBytes =
reinterpret_cast<const uint8*
>(&InterpolatedSample);
297 OutPCMData.Append(SampleBytes,
sizeof(int16));
306 const int32 NumSamples = InStereoPCMData.Num() /
sizeof(int16);
308 if (NumSamples % 2 != 0)
310 PRINTLOG(TEXT(
"[ConvertStereoToMono] Invalid stereo PCM data size (not even number of samples)"));
311 return InStereoPCMData;
314 const int16* InSamples =
reinterpret_cast<const int16*
>(InStereoPCMData.GetData());
315 const int32 NumMonoSamples = NumSamples / 2;
317 TArray<uint8> MonoPCMData;
318 MonoPCMData.Reserve(NumMonoSamples *
sizeof(int16));
321 for (int32 i = 0; i < NumMonoSamples; ++i)
323 const int16 LeftSample = InSamples[i * 2];
324 const int16 RightSample = InSamples[i * 2 + 1];
327 const int32 AverageSample = (
static_cast<int32
>(LeftSample) +
static_cast<int32
>(RightSample)) / 2;
328 const int16 MonoSample =
static_cast<int16
>(FMath::Clamp(AverageSample, -32768, 32767));
331 const uint8* SampleBytes =
reinterpret_cast<const uint8*
>(&MonoSample);
332 MonoPCMData.Append(SampleBytes,
sizeof(int16));
335 PRINTLOG(TEXT(
"[ConvertStereoToMono] Converted %d stereo samples to %d mono samples"), NumSamples, NumMonoSamples);