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 static const unsigned BAUDRATE = 3744;
24 static const 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 static const unsigned AUDIO_OVERSAMPLE = 4;
36 
37 // number of output bytes for silent parts
38 static const unsigned SHORT_SILENCE = OUTPUT_FREQUENCY * 1; // 1 second
39 static const unsigned LONG_SILENCE = OUTPUT_FREQUENCY * 2; // 2 seconds
40 
41 // number of 1-bits for headers
42 static const unsigned LONG_HEADER = 16000 / 2;
43 static const unsigned SHORT_HEADER = 4000 / 2;
44 
45 // headers definitions
46 static const byte CAS_HEADER [ 8] = { 0x1F,0xA6,0xDE,0xBA,0xCC,0x13,0x7D,0x74 };
47 static const byte ASCII_HEADER [10] = { 0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA };
48 static const byte BINARY_HEADER[10] = { 0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0,0xD0 };
49 static const 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  static const Clock<OUTPUT_FREQUENCY> zero(EmuTime::zero);
61  unsigned pos = zero.getTicksTill(time);
62  return pos < output.size() ? output[pos] * 256 : 0;
63 }
64 
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 {
74  return OUTPUT_FREQUENCY * AUDIO_OVERSAMPLE;
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(int s)
108 {
109  for (int i = 0; i < s; ++i) {
110  write1();
111  }
112 }
113 
114 // write silence
115 void CasImage::writeSilence(int s)
116 {
117  output.insert(end(output), s, 0);
118 }
119 
120 // write a byte
121 void CasImage::writeByte(byte b)
122 {
123  // one start bit
124  write0();
125  // eight data bits
126  for (auto i : xrange(8)) {
127  if (b & (1 << i)) {
128  write1();
129  } else {
130  write0();
131  }
132  }
133  // two stop bits
134  write1();
135  write1();
136 }
137 
138 // write data until a header is detected
139 bool CasImage::writeData(span<byte> buf, size_t& pos)
140 {
141  bool eof = false;
142  while ((pos + 8) <= buf.size()) {
143  if (memcmp(&buf[pos], CAS_HEADER, 8) == 0) {
144  return eof;
145  }
146  writeByte(buf[pos]);
147  if (buf[pos] == 0x1A) {
148  eof = true;
149  }
150  pos++;
151  }
152  while (pos < buf.size()) {
153  writeByte(buf[pos++]);
154  }
155  return false;
156 }
157 
158 void CasImage::convert(const Filename& filename, FilePool& filePool, CliComm& cliComm)
159 {
160  File file(filename);
161  auto buf = file.mmap();
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) <= buf.size()) {
169  if (memcmp(&buf[pos], CAS_HEADER, 8) == 0) {
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) <= buf.size()) {
178  // determine file type
180  if (memcmp(&buf[pos], ASCII_HEADER, 10) == 0) {
181  type = CassetteImage::ASCII;
182  } else if (memcmp(&buf[pos], BINARY_HEADER, 10) == 0) {
183  type = CassetteImage::BINARY;
184  } else if (memcmp(&buf[pos], BASIC_HEADER, 10) == 0) {
185  type = CassetteImage::BASIC;
186  }
187  if (firstFile) setFirstFileType(type);
188  switch (type) {
190  writeData(buf, pos);
191  bool eof;
192  do {
193  pos += 8;
194  writeSilence(SHORT_SILENCE);
195  writeHeader(SHORT_HEADER);
196  eof = writeData(buf, pos);
197  } while (!eof && ((pos + 8) <= buf.size()));
198  break;
201  writeData(buf, pos);
202  writeSilence(SHORT_SILENCE);
203  writeHeader(SHORT_HEADER);
204  pos += 8;
205  writeData(buf, pos);
206  break;
207  default:
208  // unknown file type: using long header
209  writeData(buf, pos);
210  break;
211  }
212  } else {
213  // unknown file type: using long header
214  writeData(buf, pos);
215  }
216  firstFile = false;
217  } else {
218  // skipping unhandled data, shouldn't occur in normal cas file
219  pos++;
220  issueWarning = true;
221  }
222  }
223  if (!headerFound) {
224  throw MSXException(filename.getOriginal(), ": not a valid CAS image");
225  }
226  if (issueWarning) {
227  cliComm.printWarning("Skipped unhandled data in ",
228  filename.getOriginal());
229  }
230 
231  // conversion successful, now calc sha1sum
232  setSha1Sum(filePool.getSha1Sum(file));
233 }
234 
235 } // namespace openmsx
unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition: Clock.hh:58
float getAmplificationFactorImpl() const override
Definition: CasImage.cc:92
auto xrange(T e)
Definition: xrange.hh:170
Definition: span.hh:34
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
Represents a clock with a fixed frequency.
Definition: Clock.hh:18
const std::string & getOriginal() const
Definition: Filename.hh:26
int16_t getSampleAt(EmuTime::param time) override
Definition: CasImage.cc:58
void append(Result &)
Definition: stl.hh:354
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, float **bufs, unsigned num) const override
Definition: CasImage.cc:77
Sha1Sum getSha1Sum(File &file)
Calculate sha1sum for the given File object.
Definition: FilePool.cc:545
void setFirstFileType(FileType type)
span< uint8_t > mmap()
Map file in memory.
Definition: File.cc:93
EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
Definition: Clock.hh:46
void printWarning(string_view message)
Definition: CliComm.cc:20
EmuTime getEndTime() const override
Definition: CasImage.cc:65
unsigned getFrequency() const override
Definition: CasImage.cc:72
auto end(const string_view &x)
Definition: string_view.hh:152
CasImage(const Filename &fileName, FilePool &filePool, CliComm &cliComm)
Definition: CasImage.cc:52