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