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(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
openmsx::CassetteImage::UNKNOWN
@ UNKNOWN
Definition: CassetteImage.hh:14
xrange
auto xrange(T e)
Definition: xrange.hh:170
openmsx::Clock::getTime
constexpr EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
Definition: Clock.hh:46
Clock.hh
openmsx::BASIC_HEADER
constexpr byte BASIC_HEADER[10]
Definition: CasImage.cc:49
CasImage.hh
openmsx::CassetteImage::setFirstFileType
void setFirstFileType(FileType type)
Definition: CassetteImage.hh:37
openmsx::LONG_HEADER
constexpr unsigned LONG_HEADER
Definition: CasImage.cc:42
openmsx::BINARY_HEADER
constexpr byte BINARY_HEADER[10]
Definition: CasImage.cc:48
MSXException.hh
Filename.hh
openmsx::LONG_SILENCE
constexpr unsigned LONG_SILENCE
Definition: CasImage.cc:39
openmsx::CasImage::getEndTime
EmuTime getEndTime() const override
Definition: CasImage.cc:65
span
Definition: span.hh:126
openmsx::CassetteImage::BASIC
@ BASIC
Definition: CassetteImage.hh:14
openmsx::SHORT_SILENCE
constexpr unsigned SHORT_SILENCE
Definition: CasImage.cc:38
openmsx::CassetteImage::setSha1Sum
void setSha1Sum(const Sha1Sum &sha1sum)
Definition: CassetteImage.cc:19
File.hh
openmsx::CassetteImage::BINARY
@ BINARY
Definition: CassetteImage.hh:14
openmsx::filename
constexpr const char *const filename
Definition: FirmwareSwitch.cc:10
openmsx::SHORT_HEADER
constexpr unsigned SHORT_HEADER
Definition: CasImage.cc:43
openmsx::CasImage::getAmplificationFactorImpl
float getAmplificationFactorImpl() const override
Definition: CasImage.cc:92
FilePool.hh
openmsx::CassetteImage::FileType
FileType
Definition: CassetteImage.hh:14
openmsx::BAUDRATE
constexpr unsigned BAUDRATE
Definition: CasImage.cc:23
span::size
constexpr index_type size() const noexcept
Definition: span.hh:296
openmsx::CasImage::fillBuffer
void fillBuffer(unsigned pos, float **bufs, unsigned num) const override
Definition: CasImage.cc:77
openmsx::Filename
Filename
Definition: Filename.cc:50
openmsx::CassetteImage::ASCII
@ ASCII
Definition: CassetteImage.hh:14
openmsx::OUTPUT_FREQUENCY
constexpr unsigned OUTPUT_FREQUENCY
Definition: CasImage.cc:24
openmsx::CliComm
Definition: CliComm.hh:11
detail::append
void append(Result &)
Definition: stl.hh:335
openmsx::CAS_HEADER
constexpr byte CAS_HEADER[8]
Definition: CasImage.cc:46
openmsx::FilePool
Definition: FilePool.hh:16
stl.hh
openmsx::CasImage::getFrequency
unsigned getFrequency() const override
Definition: CasImage.cc:72
openmsx::CasImage::getSampleAt
int16_t getSampleAt(EmuTime::param time) override
Definition: CasImage.cc:58
CliComm.hh
openmsx::Filename
This class represents a filename.
Definition: Filename.hh:18
openmsx::Clock
Represents a clock with a fixed frequency.
Definition: Clock.hh:19
openmsx::ASCII_HEADER
constexpr byte ASCII_HEADER[10]
Definition: CasImage.cc:47
openmsx::AUDIO_OVERSAMPLE
constexpr unsigned AUDIO_OVERSAMPLE
Definition: CasImage.cc:35
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
xrange.hh
openmsx::CasImage::CasImage
CasImage(const Filename &fileName, FilePool &filePool, CliComm &cliComm)
Definition: CasImage.cc:52