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