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