mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-17 22:25:37 +00:00
Common: Add WAVWriter class
This commit is contained in:
parent
6a6aa72f3a
commit
c7a74cabaa
|
@ -54,6 +54,8 @@ add_library(common
|
||||||
timestamp.cpp
|
timestamp.cpp
|
||||||
timestamp.h
|
timestamp.h
|
||||||
types.h
|
types.h
|
||||||
|
wav_writer.cpp
|
||||||
|
wav_Writer.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
<ClInclude Include="timestamp.h" />
|
<ClInclude Include="timestamp.h" />
|
||||||
<ClInclude Include="types.h" />
|
<ClInclude Include="types.h" />
|
||||||
<ClInclude Include="cd_xa.h" />
|
<ClInclude Include="cd_xa.h" />
|
||||||
|
<ClInclude Include="wav_writer.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="assert.cpp" />
|
<ClCompile Include="assert.cpp" />
|
||||||
|
@ -102,6 +103,7 @@
|
||||||
<ClCompile Include="string_util.cpp" />
|
<ClCompile Include="string_util.cpp" />
|
||||||
<ClCompile Include="timer.cpp" />
|
<ClCompile Include="timer.cpp" />
|
||||||
<ClCompile Include="timestamp.cpp" />
|
<ClCompile Include="timestamp.cpp" />
|
||||||
|
<ClCompile Include="wav_writer.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="bitfield.natvis" />
|
<Natvis Include="bitfield.natvis" />
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="hash_combine.h" />
|
<ClInclude Include="hash_combine.h" />
|
||||||
<ClInclude Include="progress_callback.h" />
|
<ClInclude Include="progress_callback.h" />
|
||||||
|
<ClInclude Include="wav_writer.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="jit_code_buffer.cpp" />
|
<ClCompile Include="jit_code_buffer.cpp" />
|
||||||
|
@ -100,6 +101,7 @@
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="cd_image_chd.cpp" />
|
<ClCompile Include="cd_image_chd.cpp" />
|
||||||
<ClCompile Include="progress_callback.cpp" />
|
<ClCompile Include="progress_callback.cpp" />
|
||||||
|
<ClCompile Include="wav_writer.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="bitfield.natvis" />
|
<Natvis Include="bitfield.natvis" />
|
||||||
|
|
115
src/common/wav_writer.cpp
Normal file
115
src/common/wav_writer.cpp
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
#include "wav_writer.h"
|
||||||
|
#include "file_system.h"
|
||||||
|
#include "log.h"
|
||||||
|
Log_SetChannel(WAVWriter);
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct WAV_HEADER
|
||||||
|
{
|
||||||
|
u32 chunk_id; // RIFF
|
||||||
|
u32 chunk_size;
|
||||||
|
u32 format; // WAVE
|
||||||
|
|
||||||
|
struct FormatChunk
|
||||||
|
{
|
||||||
|
u32 chunk_id; // "fmt "
|
||||||
|
u32 chunk_size;
|
||||||
|
u16 audio_format; // pcm = 1
|
||||||
|
u16 num_channels;
|
||||||
|
u32 sample_rate;
|
||||||
|
u32 byte_rate;
|
||||||
|
u16 block_align;
|
||||||
|
u16 bits_per_sample;
|
||||||
|
} fmt_chunk;
|
||||||
|
|
||||||
|
struct DataChunkHeader
|
||||||
|
{
|
||||||
|
u32 chunk_id; // "data "
|
||||||
|
u32 chunk_size;
|
||||||
|
} data_chunk_header;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
WAVWriter::WAVWriter() = default;
|
||||||
|
|
||||||
|
WAVWriter::~WAVWriter()
|
||||||
|
{
|
||||||
|
if (IsOpen())
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WAVWriter::Open(const char* filename, u32 sample_rate, u32 num_channels)
|
||||||
|
{
|
||||||
|
if (IsOpen())
|
||||||
|
Close();
|
||||||
|
|
||||||
|
m_file = FileSystem::OpenCFile(filename, "wb");
|
||||||
|
if (!m_file)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_sample_rate = sample_rate;
|
||||||
|
m_num_channels = num_channels;
|
||||||
|
|
||||||
|
if (!WriteHeader())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to write header to file");
|
||||||
|
m_sample_rate = 0;
|
||||||
|
m_num_channels = 0;
|
||||||
|
std::fclose(m_file);
|
||||||
|
m_file = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WAVWriter::Close()
|
||||||
|
{
|
||||||
|
if (!IsOpen())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (std::fseek(m_file, 0, SEEK_SET) != 0 || !WriteHeader())
|
||||||
|
Log_ErrorPrintf("Failed to re-write header on file, file may be unplayable");
|
||||||
|
|
||||||
|
std::fclose(m_file);
|
||||||
|
m_file = nullptr;
|
||||||
|
m_sample_rate = 0;
|
||||||
|
m_num_channels = 0;
|
||||||
|
m_num_frames = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WAVWriter::WriteFrames(const s16* samples, u32 num_frames)
|
||||||
|
{
|
||||||
|
const u32 num_frames_written =
|
||||||
|
static_cast<u32>(std::fwrite(samples, sizeof(s16) * m_num_channels, num_frames, m_file));
|
||||||
|
if (num_frames_written != num_frames)
|
||||||
|
Log_ErrorPrintf("Only wrote %u of %u frames to output file", num_frames_written);
|
||||||
|
|
||||||
|
m_num_frames += num_frames_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WAVWriter::WriteHeader()
|
||||||
|
{
|
||||||
|
const u32 data_size = sizeof(SampleType) * m_num_channels * m_num_frames;
|
||||||
|
|
||||||
|
WAV_HEADER header = {};
|
||||||
|
header.chunk_id = 0x46464952; // 0x52494646
|
||||||
|
header.chunk_size = sizeof(WAV_HEADER) - 8 + data_size;
|
||||||
|
header.format = 0x45564157; // 0x57415645
|
||||||
|
header.fmt_chunk.chunk_id = 0x20746d66; // 0x666d7420
|
||||||
|
header.fmt_chunk.chunk_size = sizeof(header.fmt_chunk) - 8;
|
||||||
|
header.fmt_chunk.audio_format = 1;
|
||||||
|
header.fmt_chunk.num_channels = static_cast<u16>(m_num_channels);
|
||||||
|
header.fmt_chunk.sample_rate = m_sample_rate;
|
||||||
|
header.fmt_chunk.byte_rate = m_sample_rate * m_num_channels * sizeof(SampleType);
|
||||||
|
header.fmt_chunk.block_align = static_cast<u16>(m_num_channels * sizeof(SampleType));
|
||||||
|
header.fmt_chunk.bits_per_sample = 16;
|
||||||
|
header.data_chunk_header.chunk_id = 0x61746164; // 0x64617461
|
||||||
|
header.data_chunk_header.chunk_size = data_size;
|
||||||
|
|
||||||
|
return (std::fwrite(&header, sizeof(header), 1, m_file) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
31
src/common/wav_writer.h
Normal file
31
src/common/wav_writer.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
#include "types.h"
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
class WAVWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WAVWriter();
|
||||||
|
~WAVWriter();
|
||||||
|
|
||||||
|
ALWAYS_INLINE bool IsOpen() const { return (m_file != nullptr); }
|
||||||
|
|
||||||
|
bool Open(const char* filename, u32 sample_rate, u32 num_channels);
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
void WriteFrames(const s16* samples, u32 num_frames);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using SampleType = s16;
|
||||||
|
|
||||||
|
bool WriteHeader();
|
||||||
|
|
||||||
|
std::FILE* m_file = nullptr;
|
||||||
|
u32 m_sample_rate = 0;
|
||||||
|
u32 m_num_channels = 0;
|
||||||
|
u32 m_num_frames = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
Loading…
Reference in a new issue