openMSX
CasImage.cc
Go to the documentation of this file.
1 #include "CasImage.hh"
2 #include "File.hh"
3 #include "Filename.hh"
4 #include "CliComm.hh"
5 #include "Clock.hh"
6 #include "MSXException.hh"
7 #include "xrange.hh"
8 #include <cstring> // for memcmp
9 
10 namespace openmsx {
11 
12 // output settings
13 
14 // a higher baudrate doesn't work anymore, but it is unclear why, because 4600
15 // baud should work (known from Speedsave 4000 and Turbo 5000 programs).
16 // 3765 still works on a Toshiba HX-10 and Philips NMS 8250, but not on a
17 // Panasonic FS-A1WSX, on which 3763 is the max. National CF-2000 has 3762 as
18 // the max. Let's take 3760 then as a safe value.
19 // UPDATE: that seems to break RUN"CAS:" type of programs. 3744 seems to work
20 // for those as well (we don't understand why yet)
21 static const unsigned BAUDRATE = 3744;
22 static const unsigned OUTPUT_FREQUENCY = 4 * BAUDRATE; // 4 samples per bit
23 // We oversample the audio signal for better sound quality (especially in
24 // combination with the hq resampler). Without oversampling the audio output
25 // could contain portions like this:
26 // -1, +1, -1, +1, -1, +1, ...
27 // So it contains a signal at the Nyquist frequency. The hq resampler contains
28 // a low-pass filter, and (all practically implementable) filters cut off a
29 // portion of the spectrum near the Nyquist freq. So this high freq signal was
30 // lost after the hq resampler. After oversampling, the signal looks like this:
31 // -1, -1, -1, -1, +1, +1, +1, +1, -1, -1, -1, -1, ...
32 // So every sample repeated 4 times.
33 static const unsigned AUDIO_OVERSAMPLE = 4;
34 
35 // number of output bytes for silent parts
36 static const unsigned SHORT_SILENCE = OUTPUT_FREQUENCY * 1; // 1 second
37 static const unsigned LONG_SILENCE = OUTPUT_FREQUENCY * 2; // 2 seconds
38 
39 // number of 1-bits for headers
40 static const unsigned LONG_HEADER = 16000 / 2;
41 static const unsigned SHORT_HEADER = 4000 / 2;
42 
43 // headers definitions
44 static const byte CAS_HEADER [ 8] = { 0x1F,0xA6,0xDE,0xBA,0xCC,0x13,0x7D,0x74 };
45 static const byte ASCII_HEADER [10] = { 0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA };
46 static const byte BINARY_HEADER[10] = { 0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0 };
47 static const byte BASIC_HEADER [10] = { 0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3,0xD3 };
48 
49 
50 CasImage::CasImage(const Filename& filename, FilePool& filePool, CliComm& cliComm)
51 {
53  convert(filename, filePool, cliComm);
54 }
55 
57 {
58  static const Clock<OUTPUT_FREQUENCY> zero(EmuTime::zero);
59  unsigned pos = zero.getTicksTill(time);
60  return pos < output.size() ? output[pos] * 256 : 0;
61 }
62 
64 {
66  clk += unsigned(output.size());
67  return clk.getTime();
68 }
69 
70 unsigned CasImage::getFrequency() const
71 {
72  return OUTPUT_FREQUENCY * AUDIO_OVERSAMPLE;
73 }
74 
75 void CasImage::fillBuffer(unsigned pos, int** bufs, unsigned num) const
76 {
77  size_t nbSamples = output.size();
78  if ((pos / AUDIO_OVERSAMPLE) < nbSamples) {
79  for (auto i : xrange(num)) {
80  bufs[0][i] = ((pos / AUDIO_OVERSAMPLE) < nbSamples)
81  ? output[pos / AUDIO_OVERSAMPLE] * 256
82  : 0;
83  ++pos;
84  }
85  } else {
86  bufs[0] = nullptr;
87  }
88 }
89 
90 void CasImage::write0()
91 {
92  output.push_back( 127);
93  output.push_back( 127);
94  output.push_back(-127);
95  output.push_back(-127);
96 }
97 void CasImage::write1()
98 {
99  output.push_back( 127);
100  output.push_back(-127);
101  output.push_back( 127);
102  output.push_back(-127);
103 }
104 
105 // write a header signal
106 void CasImage::writeHeader(int s)
107 {
108  for (int i = 0; i < s; ++i) {
109  write1();
110  }
111 }
112 
113 // write silence
114 void CasImage::writeSilence(int s)
115 {
116  output.insert(output.end(), s, 0);
117 }
118 
119 // write a byte
120 void CasImage::writeByte(byte b)
121 {
122  // one start bit
123  write0();
124  // eight data bits
125  for (auto i : xrange(8)) {
126  if (b & (1 << i)) {
127  write1();
128  } else {
129  write0();
130  }
131  }
132  // two stop bits
133  write1();
134  write1();
135 }
136 
137 // write data until a header is detected
138 bool CasImage::writeData(const byte* buf, size_t size, size_t& pos)
139 {
140  bool eof = false;
141  while ((pos + 8) <= size) {
142  if (!memcmp(&buf[pos], CAS_HEADER, 8)) {
143  return eof;
144  }
145  writeByte(buf[pos]);
146  if (buf[pos] == 0x1A) {
147  eof = true;
148  }
149  pos++;
150  }
151  while (pos < size) {
152  writeByte(buf[pos++]);
153  }
154  return false;
155 }
156 
157 void CasImage::convert(const Filename& filename, FilePool& filePool, CliComm& cliComm)
158 {
159  File file(filename);
160  size_t size;
161  const byte* buf = file.mmap(size);
162 
163  // search for a header in the .cas file
164  bool issueWarning = false;
165  bool headerFound = false;
166  bool firstFile = true;
167  size_t pos = 0;
168  while ((pos + 8) <= size) {
169  if (!memcmp(&buf[pos], CAS_HEADER, 8)) {
170  // it probably works fine if a long header is used for every
171  // header but since the msx bios makes a distinction between
172  // them, we do also (hence a lot of code).
173  headerFound = true;
174  pos += 8;
175  writeSilence(LONG_SILENCE);
176  writeHeader(LONG_HEADER);
177  if ((pos + 10) <= size) {
178  // determine file type
180  if (!memcmp(&buf[pos], ASCII_HEADER, 10)) {
181  type = CassetteImage::ASCII;
182  } else if (!memcmp(&buf[pos], BINARY_HEADER, 10)) {
183  type = CassetteImage::BINARY;
184  } else if (!memcmp(&buf[pos], BASIC_HEADER, 10)) {
185  type = CassetteImage::BASIC;
186  }
187  if (firstFile) setFirstFileType(type);
188  switch (type) {
190  writeData(buf, size, pos);
191  bool eof;
192  do {
193  pos += 8;
194  writeSilence(SHORT_SILENCE);
195  writeHeader(SHORT_HEADER);
196  eof = writeData(buf, size, pos);
197  } while (!eof && ((pos + 8) <= size));
198  break;
201  writeData(buf, size, pos);
202  writeSilence(SHORT_SILENCE);
203  writeHeader(SHORT_HEADER);
204  pos += 8;
205  writeData(buf, size, pos);
206  break;
207  default:
208  // unknown file type: using long header
209  writeData(buf, size, pos);
210  break;
211  }
212  } else {
213  // unknown file type: using long header
214  writeData(buf, size, pos);
215  }
216  firstFile = false;
217  } else {
218  // should not occur
219  PRT_DEBUG("CAS2WAV: skipping unhandled data");
220  pos++;
221  issueWarning = true;
222  }
223  }
224  if (!headerFound) {
225  throw MSXException(filename.getOriginal() +
226  ": not a valid CAS image");
227  }
228  if (issueWarning) {
229  cliComm.printWarning("Skipped unhandled data in " +
230  filename.getOriginal());
231  }
232 
233  // conversion successful, now calc sha1sum
234  file.setFilePool(filePool);
235  setSha1Sum(file.getSha1Sum());
236 }
237 
238 } // namespace openmsx