openMSX
WavWriter.cc
Go to the documentation of this file.
1#include "WavWriter.hh"
2#include "MSXException.hh"
3#include "Math.hh"
4#include "Mixer.hh"
5#include "endian.hh"
6#include "enumerate.hh"
7#include "narrow.hh"
8#include "one_of.hh"
9#include "ranges.hh"
10#include "vla.hh"
11#include "xrange.hh"
12#include <array>
13#include <vector>
14
15namespace openmsx {
16
18 unsigned channels, unsigned bits, unsigned frequency)
19 : file(filename, "wb")
20{
21 // write wav header
22 struct WavHeader {
23 std::array<char, 4> chunkID; // + 0 'RIFF'
24 Endian::L32 chunkSize; // + 4 total size
25 std::array<char, 4> format; // + 8 'WAVE'
26 std::array<char, 4> subChunk1ID; // +12 'fmt '
27 Endian::L32 subChunk1Size; // +16 = 16 (fixed)
28 Endian::L16 audioFormat; // +20 = 1 (fixed)
29 Endian::L16 numChannels; // +22
30 Endian::L32 sampleRate; // +24
31 Endian::L32 byteRate; // +28
32 Endian::L16 blockAlign; // +32
33 Endian::L16 bitsPerSample; // +34
34 std::array<char, 4> subChunk2ID; // +36 'data'
35 Endian::L32 subChunk2Size; // +40
36 } header;
37
38 ranges::copy(std::string_view("RIFF"), header.chunkID);
39 header.chunkSize = 0; // actual value filled in later
40 ranges::copy(std::string_view("WAVE"), header.format);
41 ranges::copy(std::string_view("fmt "), header.subChunk1ID);
42 header.subChunk1Size = 16;
43 header.audioFormat = 1;
44 header.numChannels = narrow<uint16_t>(channels);
45 header.sampleRate = frequency;
46 header.byteRate = (channels * frequency * bits) / 8;
47 header.blockAlign = narrow<uint16_t>((channels * bits) / 8);
48 header.bitsPerSample = narrow<uint16_t>(bits);
49 ranges::copy(std::string_view("data"), header.subChunk2ID);
50 header.subChunk2Size = 0; // actual value filled in later
51
52 file.write(std::span{&header, 1});
53}
54
56{
57 try {
58 // data chunk must have an even number of bytes
59 if (bytes & 1) {
60 std::array<uint8_t, 1> pad = {0};
61 file.write(pad);
62 }
63
64 flush(); // write header
65 } catch (MSXException&) {
66 // ignore, can't throw from destructor
67 }
68}
69
71{
72 Endian::L32 totalSize((bytes + 44 - 8 + 1) & ~1); // round up to even number
73 Endian::L32 wavSize(bytes);
74
75 file.seek(4);
76 file.write(std::span{&totalSize, 1});
77 file.seek(40);
78 file.write(std::span{&wavSize, 1});
79 file.seek(file.getSize()); // SEEK_END
80 file.flush();
81}
82
83void Wav8Writer::write(std::span<const uint8_t> buffer)
84{
85 file.write(buffer);
86 bytes += narrow<uint32_t>(buffer.size_bytes());
87}
88
89void Wav16Writer::write(std::span<const int16_t> buffer)
90{
91 if constexpr (Endian::BIG) {
92 // Variable length arrays (VLA) are part of c99 but not of c++
93 // (not even c++11). Some compilers (like gcc) do support VLA
94 // in c++ mode, others (like VC++) don't. Still other compilers
95 // (like clang) only support VLA for POD types.
96 // To side-step this issue we simply use a std::vector, this
97 // code is anyway not performance critical.
98 //VLA(Endian::L16, buf, samples); // doesn't work in clang
99 std::vector<Endian::L16> buf(buffer.begin(), buffer.end());
100 file.write(std::span{buf});
101 } else {
102 file.write(buffer);
103 }
104 bytes += narrow<uint32_t>(buffer.size_bytes());
105}
106
107static int16_t float2int16(float f)
108{
109 return Math::clipToInt16(lrintf(32768.0f * f));
110}
111
112void Wav16Writer::write(std::span<const float> buffer, float amp)
113{
114 std::vector<Endian::L16> buf_(buffer.size());
115 std::span buf{buf_};
116 ranges::transform(buffer, buf.data(), [=](float f) { return float2int16(f * amp); });
117 file.write(buf);
118 bytes += narrow<uint32_t>(buf.size_bytes());
119}
120
121void Wav16Writer::write(std::span<const StereoFloat> buffer, float ampLeft, float ampRight)
122{
123 std::vector<Endian::L16> buf(buffer.size() * 2);
124 for (auto [i, s] : enumerate(buffer)) { // TODO use zip() in the future
125 buf[2 * i + 0] = float2int16(s.left * ampLeft);
126 buf[2 * i + 1] = float2int16(s.right * ampRight);
127 }
128 std::span s{buf};
129 file.write(s);
130 bytes += narrow<uint32_t>(s.size_bytes());
131}
132
133void Wav16Writer::writeSilence(uint32_t samples)
134{
135 VLA(int16_t, buf, samples);
136 ranges::fill(buf, 0);
137 file.write(buf);
138 bytes += narrow<uint32_t>(buf.size_bytes());
139}
140
141} // namespace openmsx
void seek(size_t pos)
Move read/write pointer to the specified position.
Definition File.cc:117
void write(std::span< const uint8_t > buffer)
Write to file.
Definition File.cc:97
size_t getSize()
Returns the size of this file.
Definition File.cc:112
void flush()
Force a write of all buffered data to disk.
Definition File.cc:132
This class represents a filename.
Definition Filename.hh:20
void write(std::span< const int16_t > buffer)
Definition WavWriter.cc:89
void writeSilence(uint32_t samples)
Definition WavWriter.cc:133
void write(std::span< const uint8_t > buffer)
Definition WavWriter.cc:83
void flush()
Flush data to file and update header.
Definition WavWriter.cc:70
WavWriter(const Filename &filename, unsigned channels, unsigned bits, unsigned frequency)
Definition WavWriter.cc:17
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
Definition enumerate.hh:28
constexpr bool BIG
Definition endian.hh:16
int16_t clipToInt16(T x)
Clip x to range [-32768,32767].
Definition Math.hh:47
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
This file implemented 3 utility functions:
Definition Autofire.cc:11
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:307
auto transform(InputRange &&range, OutputIter out, UnaryOperation op)
Definition ranges.hh:271
constexpr auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:252
#define VLA(TYPE, NAME, LENGTH)
Definition vla.hh:12