openMSX
YM2413Test.cc
Go to the documentation of this file.
1 #include "YM2413Okazaki.hh"
2 #include "YM2413Burczynski.hh"
3 #include "WavWriter.hh"
4 #include "WavData.hh"
5 #include "Filename.hh"
6 #include <cstdint>
7 #include <vector>
8 #include <string>
9 #include <iostream>
10 
11 using namespace std;
12 using namespace openmsx;
13 
14 
15 // global vars
16 string coreName;
17 string testName;
18 
19 
20 static const unsigned CHANNELS = 11;
21 
22 
23 struct RegWrite
24 {
25  RegWrite(byte reg_, byte val_) : reg(reg_), val(val_) {}
28 };
29 using RegWrites = vector<RegWrite>;
30 struct LogEvent
31 {
32  vector<RegWrite> regWrites;
33  unsigned samples; // number of samples between this and next event
34 };
35 using Log = vector<LogEvent>;
36 using Samples = vector<int>;
37 
38 
39 static void error(const string& message)
40 {
41  cout << message << '\n';
42 }
43 
44 
45 static void saveWav(const string& filename, const Samples& data)
46 {
47  WavWriter writer(Filename(filename), 1, 16, 3579545 / 72);
48  writer.write16mono(&data[0], data.size());
49 }
50 
51 static void loadWav(const string& filename, Samples& data)
52 {
53  WavData wav(filename);
54  assert(wav.getFreq() == 3579545 / 72);
55 
56  auto rawData = wav.getData();
57  data.assign(rawData, rawData + wav.getSize());
58 }
59 
60 static void loadWav(Samples& data)
61 {
62  string filename = coreName + '-' + testName + ".wav";
63  loadWav(filename, data);
64 }
65 
66 static void createSilence(const Log& log, Samples& result)
67 {
68  unsigned size = 0;
69  for (auto& l : log) {
70  size += l.samples;
71  }
72  result.resize(size);
73 }
74 
75 
76 static void test(YM2413Core& core, const Log& log,
77  const Samples* expectedSamples[CHANNELS])
78 {
79  cout << " test " << testName << " ...\n";
80 
81  Samples generatedSamples[CHANNELS];
82 
83  for (auto& l : log) {
84  // write registers
85  for (auto& w : l.regWrites) {
86  core.writeReg(w.reg, w.val);
87  }
88 
89  unsigned samples = l.samples;
90 
91  // setup buffers
92  int* bufs[CHANNELS];
93  unsigned oldSize = generatedSamples[0].size();
94  for (unsigned i = 0; i < CHANNELS; ++i) {
95  generatedSamples[i].resize(oldSize + samples);
96  bufs[i] = &generatedSamples[i][oldSize];
97  }
98 
99  // actually generate samples
100  core.generateChannels(bufs, samples);
101  }
102 
103  // amplify generated data
104  // (makes comparison between different cores easier)
105  unsigned factor = core.getAmplificationFactor();
106  for (unsigned i = 0; i < CHANNELS; ++i) {
107  for (unsigned j = 0; j < generatedSamples[i].size(); ++j) {
108  int s = generatedSamples[i][j];
109  s *= factor;
110  assert(s == int16_t(s)); // shouldn't overflow 16-bit
111  generatedSamples[i][j] = s;
112  }
113  }
114 
115  // verify generated samples
116  for (unsigned i = 0; i < CHANNELS; ++i) {
117  string msg = strCat("Error in channel ", i, ": ");
118  bool err = false;
119  if (generatedSamples[i].size() != expectedSamples[i]->size()) {
120  strAppend(msg, "wrong size, expected ", expectedSamples[i]->size(),
121  " but got ", generatedSamples[i].size());
122  err = true;
123  } else if (generatedSamples[i] != *expectedSamples[i]) {
124  strAppend(msg, "Wrong data");
125  err = true;
126  }
127  if (err) {
128  string filename = strCat(
129  "bad-", coreName, '-', testName,
130  "-ch", i, ".wav");
131  strAppend(msg, " writing data to ", filename);
132  error(msg);
133  saveWav(filename, generatedSamples[i]);
134  }
135  }
136 }
137 
138 static void testSingleChannel(YM2413Core& core, const Log& log,
139  const Samples& channelData, unsigned channelNum)
140 {
141  Samples silence;
142  createSilence(log, silence);
143 
144  const Samples* samples[CHANNELS];
145  for (unsigned i = 0; i < CHANNELS; ++i) {
146  if (i == channelNum) {
147  samples[i] = &channelData;
148  } else {
149  samples[i] = &silence;
150  }
151  }
152 
153  test(core, log, samples);
154 }
155 
156 
157 static void testSilence(YM2413Core& core)
158 {
159  testName = "silence";
160  Log log;
161  {
162  LogEvent event;
163  // no register writes
164  event.samples = 1000;
165  log.push_back(event);
166  }
167  Samples silence;
168  createSilence(log, silence);
169 
170  const Samples* samples[CHANNELS];
171  for (unsigned i = 0; i < CHANNELS; ++i) {
172  samples[i] = &silence;
173  }
174 
175  test(core, log, samples);
176 }
177 
178 static void testViolin(YM2413Core& core)
179 {
180  testName = "violin";
181  Log log;
182  {
183  LogEvent event;
184  event.regWrites.emplace_back(0x30, 0x10); // instrument / volume
185  event.regWrites.emplace_back(0x10, 0xAD); // frequency
186  event.regWrites.emplace_back(0x20, 0x14); // key-on / frequency
187  event.samples = 11000;
188  log.push_back(event);
189  }
190  {
191  LogEvent event;
192  event.regWrites.emplace_back(0x20, 0x16); // change freq
193  event.samples = 11000;
194  log.push_back(event);
195  }
196  {
197  LogEvent event;
198  event.regWrites.emplace_back(0x20, 0x06); // key-off
199  event.samples = 11000;
200  log.push_back(event);
201  }
202  Samples gold;
203  loadWav(gold);
204 
205  testSingleChannel(core, log, gold, 0);
206 }
207 
208 template<typename CORE, typename FUNC> void testOnCore(FUNC f)
209 {
210  CORE core;
211  f(core);
212 }
213 
214 template<typename CORE> static void testAll(const string& coreName_)
215 {
216  coreName = coreName_;
217  cout << "Testing YM2413 core " << coreName << '\n';
218  testOnCore<CORE>(testSilence);
219  testOnCore<CORE>(testViolin);
220  cout << '\n';
221 }
222 
223 int main()
224 {
225  testAll<YM2413Okazaki:: YM2413>("Okazaki");
226  testAll<YM2413Burczynski::YM2413>("Burczynski");
227  return 0;
228 }
byte reg
Definition: YM2413Test.cc:26
unsigned samples
Definition: YM2413Test.cc:33
unsigned getSize() const
Definition: WavData.hh:33
Base class for writing WAV files.
Definition: WavWriter.hh:14
virtual void writeReg(byte reg, byte value)=0
Write to a YM2413 register.
unsigned getFreq() const
Definition: WavData.hh:32
RegWrite(byte reg_, byte val_)
Definition: YM2413Test.cc:25
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
Abstract interface for the YM2413 core.
Definition: YM2413Core.hh:26
vector< RegWrite > regWrites
Definition: YM2413Test.cc:32
vector< int > Samples
Definition: YM2413Test.cc:36
STL namespace.
vector< RegWrite > RegWrites
Definition: YM2413Test.cc:29
constexpr double log(double x)
Definition: cstd.hh:465
virtual void generateChannels(float *bufs[11], unsigned num)=0
Generate the sound output.
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:648
string coreName
Definition: YM2413Test.cc:16
constexpr auto data(C &c) -> decltype(c.data())
Definition: span.hh:69
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
int main()
Definition: YM2413Test.cc:223
void testOnCore(FUNC f)
Definition: YM2413Test.cc:208
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
vector< LogEvent > Log
Definition: YM2413Test.cc:35
constexpr auto size(const C &c) -> decltype(c.size())
Definition: span.hh:62
virtual float getAmplificationFactor() const =0
Returns normalization factor.
byte val
Definition: YM2413Test.cc:27
string testName
Definition: YM2413Test.cc:17