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