| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
|
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
|
| 64 |
|
| 65 |
|
| 66 |
|
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
|
| 71 |
|
| 72 |
|
| 73 |
|
| 74 |
|
| 75 |
|
| 76 |
|
| 77 |
#include "VLM5030.hh" |
| 78 |
|
| 79 |
#include "DeviceConfig.hh" |
| 80 |
#include "FileOperations.hh" |
| 81 |
#include "XMLElement.hh" |
| 82 |
#include "serialize.hh" |
| 83 |
|
| 84 |
#include "cstd.hh" |
| 85 |
#include "narrow.hh" |
| 86 |
#include "one_of.hh" |
| 87 |
#include "random.hh" |
| 88 |
#include "ranges.hh" |
| 89 |
#include "xrange.hh" |
| 90 |
|
| 91 |
#include <algorithm> |
| 92 |
#include <array> |
| 93 |
#include <cmath> |
| 94 |
|
| 95 |
namespace openmsx { |
| 96 |
|
| 97 |
|
| 98 |
static constexpr int FR_SIZE = 4; |
| 99 |
|
| 100 |
static constexpr uint8_t IP_SIZE_SLOWER = 240 / FR_SIZE; |
| 101 |
static constexpr uint8_t IP_SIZE_SLOW = 200 / FR_SIZE; |
| 102 |
static constexpr uint8_t IP_SIZE_NORMAL = 160 / FR_SIZE; |
| 103 |
static constexpr uint8_t IP_SIZE_FAST = 120 / FR_SIZE; |
| 104 |
static constexpr uint8_t IP_SIZE_FASTER = 80 / FR_SIZE; |
| 105 |
|
| 106 |
|
| 107 |
|
| 108 |
|
| 109 |
|
| 110 |
|
| 111 |
|
| 112 |
|
| 113 |
static constexpr std::array<uint8_t, 8> VLM5030_speed_table = |
| 114 |
{ |
| 115 |
IP_SIZE_NORMAL, |
| 116 |
IP_SIZE_FAST, |
| 117 |
IP_SIZE_FASTER, |
| 118 |
IP_SIZE_FASTER, |
| 119 |
IP_SIZE_NORMAL, |
| 120 |
IP_SIZE_SLOWER, |
| 121 |
IP_SIZE_SLOW, |
| 122 |
IP_SIZE_SLOW |
| 123 |
}; |
| 124 |
|
| 125 |
|
| 126 |
|
| 127 |
|
| 128 |
|
| 129 |
|
| 130 |
static constexpr std::array<uint16_t, 0x20> energyTable = |
| 131 |
{ |
| 132 |
0, 2, 4, 6, 10, 12, 14, 18, |
| 133 |
22, 26, 30, 34, 38, 44, 48, 54, |
| 134 |
62, 68, 76, 84, 94,102,114,124, |
| 135 |
136,150,164,178,196,214,232,254 |
| 136 |
}; |
| 137 |
|
| 138 |
|
| 139 |
static constexpr std::array<uint8_t, 0x20> pitchTable = |
| 140 |
{ |
| 141 |
1, |
| 142 |
22, |
| 143 |
23, 24, 25, 26, 27, 28, 29, 30, |
| 144 |
32, 34, 36, 38, 40, 42, 44, 46, |
| 145 |
50, 54, 58, 62, 66, 70, 74, 78, |
| 146 |
86, 94, 102,110,118,126 |
| 147 |
}; |
| 148 |
|
| 149 |
static constexpr std::array<int16_t, 64> K1_table = { |
| 150 |
-24898, -25672, -26446, -27091, -27736, -28252, -28768, -29155, |
| 151 |
-29542, -29929, -30316, -30574, -30832, -30961, -31219, -31348, |
| 152 |
-31606, -31735, -31864, -31864, -31993, -32122, -32122, -32251, |
| 153 |
-32251, -32380, -32380, -32380, -32509, -32509, -32509, -32509, |
| 154 |
24898, 23995, 22963, 21931, 20770, 19480, 18061, 16642, |
| 155 |
15093, 13416, 11610, 9804, 7998, 6063, 3999, 1935, |
| 156 |
0, -1935, -3999, -6063, -7998, -9804, -11610, -13416, |
| 157 |
-15093, -16642, -18061, -19480, -20770, -21931, -22963, -23995 |
| 158 |
}; |
| 159 |
static constexpr std::array<int16_t, 32> K2_table = { |
| 160 |
0, -3096, -6321, -9417, -12513, -15351, -18061, -20770, |
| 161 |
-23092, -25285, -27220, -28897, -30187, -31348, -32122, -32638, |
| 162 |
0, 32638, 32122, 31348, 30187, 28897, 27220, 25285, |
| 163 |
23092, 20770, 18061, 15351, 12513, 9417, 6321, 3096 |
| 164 |
}; |
| 165 |
static constexpr std::array<int16_t, 16> K3_table = { |
| 166 |
0, -3999, -8127, -12255, -16384, -20383, -24511, -28639, |
| 167 |
32638, 28639, 24511, 20383, 16254, 12255, 8127, 3999 |
| 168 |
}; |
| 169 |
static constexpr std::array<int16_t, 8> K5_table = { |
| 170 |
0, -8127, -16384, -24511, 32638, 24511, 16254, 8127 |
| 171 |
}; |
| 172 |
|
| 173 |
unsigned VLM5030::getBits(unsigned sBit, unsigned bits) const |
| 174 |
{ |
| 175 |
unsigned offset = address + (sBit / 8); |
| 176 |
unsigned data = rom[(offset + 0) & address_mask] + |
| 177 |
rom[(offset + 1) & address_mask] * 256; |
| 178 |
data >>= (sBit & 7); |
| 179 |
data &= (0xFF >> (8 - bits)); |
| 180 |
return data; |
| 181 |
} |
| 182 |
|
| 183 |
|
| 184 |
int VLM5030::parseFrame() |
| 185 |
{ |
| 186 |
|
| 187 |
old_energy = new_energy; |
| 188 |
old_pitch = new_pitch; |
| 189 |
old_k = new_k; |
| 190 |
|
| 191 |
if (uint8_t cmd = rom[address & address_mask]; |
| 192 |
cmd & 0x01) { |
| 193 |
|
| 194 |
new_energy = new_pitch = 0; |
| 195 |
std::ranges::fill(new_k, 0); |
| 196 |
++address; |
| 197 |
if (cmd & 0x02) { |
| 198 |
|
| 199 |
return 0; |
| 200 |
} else { |
| 201 |
|
| 202 |
int nums = ((cmd >> 2) + 1) * 2; |
| 203 |
return nums * FR_SIZE; |
| 204 |
} |
| 205 |
} |
| 206 |
|
| 207 |
new_pitch = narrow_cast<uint8_t>((pitchTable[getBits(1, 5)] + pitch_offset) & 0xff); |
| 208 |
|
| 209 |
new_energy = energyTable[getBits(6, 5)]; |
| 210 |
|
| 211 |
|
| 212 |
new_k[9] = K5_table[getBits(11, 3)]; |
| 213 |
new_k[8] = K5_table[getBits(14, 3)]; |
| 214 |
new_k[7] = K5_table[getBits(17, 3)]; |
| 215 |
new_k[6] = K5_table[getBits(20, 3)]; |
| 216 |
new_k[5] = K5_table[getBits(23, 3)]; |
| 217 |
new_k[4] = K5_table[getBits(26, 3)]; |
| 218 |
new_k[3] = K3_table[getBits(29, 4)]; |
| 219 |
new_k[2] = K3_table[getBits(33, 4)]; |
| 220 |
new_k[1] = K2_table[getBits(37, 5)]; |
| 221 |
new_k[0] = K1_table[getBits(42, 6)]; |
| 222 |
|
| 223 |
address += 6; |
| 224 |
return FR_SIZE; |
| 225 |
} |
| 226 |
|
| 227 |
|
| 228 |
void VLM5030::generateChannels(std::span<float*> bufs, unsigned num) |
| 229 |
{ |
| 230 |
|
| 231 |
using enum Phase; |
| 232 |
if (phase == IDLE) { |
| 233 |
bufs[0] = nullptr; |
| 234 |
return; |
| 235 |
} |
| 236 |
|
| 237 |
int buf_count = 0; |
| 238 |
|
| 239 |
|
| 240 |
if (phase == one_of(RUN, STOP)) { |
| 241 |
|
| 242 |
while (num > 0) { |
| 243 |
|
| 244 |
if (sample_count == 0) { |
| 245 |
if (phase == STOP) { |
| 246 |
phase = END; |
| 247 |
sample_count = 1; |
| 248 |
goto phase_stop; |
| 249 |
} |
| 250 |
sample_count = frame_size; |
| 251 |
|
| 252 |
if (interp_count == 0) { |
| 253 |
|
| 254 |
interp_count = narrow_cast<uint8_t>(parseFrame()); |
| 255 |
if (interp_count == 0) { |
| 256 |
|
| 257 |
interp_count = FR_SIZE; |
| 258 |
sample_count = frame_size; |
| 259 |
phase = STOP; |
| 260 |
} |
| 261 |
|
| 262 |
current_energy = old_energy; |
| 263 |
current_pitch = old_pitch; |
| 264 |
copy_to_range(old_k, current_k); |
| 265 |
|
| 266 |
if (current_energy == 0) { |
| 267 |
target_energy = 0; |
| 268 |
target_pitch = narrow_cast<uint8_t>(current_pitch); |
| 269 |
std::ranges::transform(current_k, target_k.data(), [](int k) { return int16_t(k); }); |
| 270 |
} else { |
| 271 |
|
| 272 |
target_energy = new_energy; |
| 273 |
target_pitch = new_pitch; |
| 274 |
target_k = new_k; |
| 275 |
} |
| 276 |
} |
| 277 |
|
| 278 |
|
| 279 |
interp_count -= interp_step; |
| 280 |
|
| 281 |
int interp_effect = FR_SIZE - (interp_count % FR_SIZE); |
| 282 |
current_energy = old_energy + (target_energy - old_energy) * interp_effect / FR_SIZE; |
| 283 |
if (old_pitch > 1) { |
| 284 |
current_pitch = old_pitch + (target_pitch - old_pitch) * interp_effect / FR_SIZE; |
| 285 |
} |
| 286 |
for (auto i : xrange(10)) |
| 287 |
current_k[i] = old_k[i] + (target_k[i] - old_k[i]) * interp_effect / FR_SIZE; |
| 288 |
} |
| 289 |
|
| 290 |
int current_val = [&] { |
| 291 |
if (old_energy == 0) { |
| 292 |
|
| 293 |
return 0; |
| 294 |
} else if (old_pitch <= 1) { |
| 295 |
|
| 296 |
return random_bool() ? int(current_energy) |
| 297 |
: -int(current_energy); |
| 298 |
} else { |
| 299 |
|
| 300 |
return (pitch_count == 0) ? int(current_energy) : 0; |
| 301 |
} |
| 302 |
}(); |
| 303 |
|
| 304 |
|
| 305 |
std::array<int, 11> u; |
| 306 |
u[10] = current_val; |
| 307 |
for (int i = 9; i >= 0; --i) { |
| 308 |
u[i] = u[i + 1] - ((current_k[i] * x[i]) / 32768); |
| 309 |
} |
| 310 |
for (int i = 9; i >= 1; --i) { |
| 311 |
x[i] = x[i - 1] + ((current_k[i - 1] * u[i - 1]) / 32768); |
| 312 |
} |
| 313 |
x[0] = u[0]; |
| 314 |
|
| 315 |
|
| 316 |
bufs[0][buf_count] = narrow<float>(std::clamp(u[0], -511, 511)); |
| 317 |
++buf_count; |
| 318 |
--sample_count; |
| 319 |
++pitch_count; |
| 320 |
if (pitch_count >= current_pitch) { |
| 321 |
pitch_count = 0; |
| 322 |
} |
| 323 |
--num; |
| 324 |
} |
| 325 |
|
| 326 |
} |
| 327 |
phase_stop: |
| 328 |
switch (phase) { |
| 329 |
using enum Phase; |
| 330 |
case SETUP: |
| 331 |
if (sample_count <= num) { |
| 332 |
sample_count = 0; |
| 333 |
|
| 334 |
phase = WAIT; |
| 335 |
} else { |
| 336 |
sample_count -= narrow<uint8_t>(num); |
| 337 |
} |
| 338 |
break; |
| 339 |
case END: |
| 340 |
if (sample_count <= num) { |
| 341 |
sample_count = 0; |
| 342 |
pin_BSY = false; |
| 343 |
phase = IDLE; |
| 344 |
} else { |
| 345 |
sample_count -= narrow<uint8_t>(num); |
| 346 |
} |
| 347 |
break; |
| 348 |
default: |
| 349 |
break; |
| 350 |
} |
| 351 |
|
| 352 |
while (num > 0) { |
| 353 |
bufs[0][buf_count++] = 0; |
| 354 |
--num; |
| 355 |
} |
| 356 |
} |
| 357 |
|
| 358 |
float VLM5030::getAmplificationFactorImpl() const |
| 359 |
{ |
| 360 |
return 1.0f / (1 << 9); |
| 361 |
} |
| 362 |
|
| 363 |
|
| 364 |
void VLM5030::setupParameter(uint8_t param) |
| 365 |
{ |
| 366 |
|
| 367 |
parameter = param; |
| 368 |
|
| 369 |
|
| 370 |
if (param & 2) { |
| 371 |
interp_step = 4; |
| 372 |
} else if (param & 1) { |
| 373 |
interp_step = 2; |
| 374 |
} else { |
| 375 |
interp_step = 1; |
| 376 |
} |
| 377 |
|
| 378 |
|
| 379 |
frame_size = VLM5030_speed_table[(param >> 3) & 7]; |
| 380 |
|
| 381 |
|
| 382 |
if (param & 0x80) { |
| 383 |
pitch_offset = -8; |
| 384 |
} else if (param & 0x40) { |
| 385 |
pitch_offset = 8; |
| 386 |
} else { |
| 387 |
pitch_offset = 0; |
| 388 |
} |
| 389 |
} |
| 390 |
|
| 391 |
void VLM5030::reset() |
| 392 |
{ |
| 393 |
phase = Phase::RESET; |
| 394 |
address = 0; |
| 395 |
vcu_addr_h = 0; |
| 396 |
pin_BSY = false; |
| 397 |
|
| 398 |
old_energy = old_pitch = 0; |
| 399 |
new_energy = new_pitch = 0; |
| 400 |
current_energy = current_pitch = 0; |
| 401 |
target_energy = target_pitch = 0; |
| 402 |
std::ranges::fill(old_k, 0); |
| 403 |
std::ranges::fill(new_k, 0); |
| 404 |
std::ranges::fill(current_k, 0); |
| 405 |
std::ranges::fill(target_k, 0); |
| 406 |
interp_count = sample_count = pitch_count = 0; |
| 407 |
std::ranges::fill(x, 0); |
| 408 |
|
| 409 |
setupParameter(0x00); |
| 410 |
} |
| 411 |
|
| 412 |
|
| 413 |
bool VLM5030::getBSY(EmuTime time) const |
| 414 |
{ |
| 415 |
const_cast<VLM5030*>(this)->updateStream(time); |
| 416 |
return pin_BSY; |
| 417 |
} |
| 418 |
|
| 419 |
|
| 420 |
void VLM5030::writeData(uint8_t data) |
| 421 |
{ |
| 422 |
latch_data = data; |
| 423 |
} |
| 424 |
|
| 425 |
void VLM5030::writeControl(uint8_t data, EmuTime time) |
| 426 |
{ |
| 427 |
updateStream(time); |
| 428 |
setRST((data & 0x01) != 0); |
| 429 |
setVCU((data & 0x04) != 0); |
| 430 |
setST ((data & 0x02) != 0); |
| 431 |
} |
| 432 |
|
| 433 |
|
| 434 |
void VLM5030::setRST(bool pin) |
| 435 |
{ |
| 436 |
if (pin_RST) { |
| 437 |
if (!pin) { |
| 438 |
pin_RST = false; |
| 439 |
setupParameter(latch_data); |
| 440 |
} |
| 441 |
} else { |
| 442 |
if (pin) { |
| 443 |
pin_RST = true; |
| 444 |
if (pin_BSY) { |
| 445 |
reset(); |
| 446 |
} |
| 447 |
} |
| 448 |
} |
| 449 |
} |
| 450 |
|
| 451 |
|
| 452 |
void VLM5030::setVCU(bool pin) |
| 453 |
{ |
| 454 |
|
| 455 |
pin_VCU = pin; |
| 456 |
} |
| 457 |
|
| 458 |
|
| 459 |
void VLM5030::setST(bool pin) |
| 460 |
{ |
| 461 |
if (pin_ST == pin) { |
| 462 |
|
| 463 |
return; |
| 464 |
} |
| 465 |
if (!pin) { |
| 466 |
|
| 467 |
pin_ST = false; |
| 468 |
if (pin_VCU) { |
| 469 |
|
| 470 |
vcu_addr_h = narrow<uint16_t>((latch_data << 8) + 0x01); |
| 471 |
} else { |
| 472 |
|
| 473 |
if (vcu_addr_h) { |
| 474 |
|
| 475 |
address = (vcu_addr_h & 0xff00) + latch_data; |
| 476 |
vcu_addr_h = 0; |
| 477 |
} else { |
| 478 |
|
| 479 |
int table = (latch_data & 0xfe) + ((int(latch_data) & 1) << 8); |
| 480 |
address = uint16_t((rom[(table + 0) & address_mask] << 8) | |
| 481 |
(rom[(table + 1) & address_mask] << 0)); |
| 482 |
} |
| 483 |
|
| 484 |
sample_count = frame_size; |
| 485 |
interp_count = FR_SIZE; |
| 486 |
|
| 487 |
|
| 488 |
phase = Phase::RUN; |
| 489 |
} |
| 490 |
} else { |
| 491 |
|
| 492 |
pin_ST = true; |
| 493 |
|
| 494 |
phase = Phase::SETUP; |
| 495 |
sample_count = 1; |
| 496 |
pin_BSY = true; |
| 497 |
} |
| 498 |
} |
| 499 |
|
| 500 |
|
| 501 |
static XMLElement* getRomConfig( |
| 502 |
DeviceConfig& config, const std::string& name, std::string_view romFilename) |
| 503 |
{ |
| 504 |
auto& doc = config.getXMLDocument(); |
| 505 |
auto* voiceROMconfig = doc.allocateElement(doc.allocateString(name)); |
| 506 |
voiceROMconfig->setFirstAttribute(doc.allocateAttribute("id", "name")); |
| 507 |
auto* romElement = voiceROMconfig->setFirstChild(doc.allocateElement("rom")); |
| 508 |
romElement->setFirstChild(doc.allocateElement( |
| 509 |
"sha1", "4f36d139ee4baa7d5980f765de9895570ee05f40")) |
| 510 |
->setNextSibling(doc.allocateElement( |
| 511 |
"filename", |
| 512 |
doc.allocateString(tmpStrCat(FileOperations::stripExtension(romFilename), "_voice.rom")))) |
| 513 |
->setNextSibling(doc.allocateElement( |
| 514 |
"filename", "keyboardmaster/voice.rom")); |
| 515 |
return voiceROMconfig; |
| 516 |
} |
| 517 |
|
| 518 |
static constexpr auto INPUT_RATE = unsigned(cstd::round(3579545 / 440.0)); |
| 519 |
|
| 520 |
VLM5030::VLM5030(const std::string& name_, static_string_view desc, |
| 521 |
std::string_view romFilename, DeviceConfig& config) |
| 522 |
: ResampledSoundDevice(config.getMotherBoard(), name_, desc, 1, INPUT_RATE, false) |
| 523 |
, config2(config, *getRomConfig(config, name_, romFilename)) |
| 524 |
, rom(name_ + " ROM", "rom", config2) |
| 525 |
{ |
| 526 |
reset(); |
| 527 |
phase = Phase::IDLE; |
| 528 |
|
| 529 |
assert(rom.size() != 0); |
| 530 |
address_mask = narrow<unsigned>(rom.size() - 1); |
| 531 |
|
| 532 |
registerSound(config); |
| 533 |
} |
| 534 |
|
| 535 |
VLM5030::~VLM5030() |
| 536 |
{ |
| 537 |
unregisterSound(); |
| 538 |
} |
| 539 |
|
| 540 |
static constexpr auto phaseInfo = std::to_array<enum_string<VLM5030::Phase>>({ |
| 541 |
{ "RESET", VLM5030::Phase::RESET }, |
| 542 |
{ "IDLE", VLM5030::Phase::IDLE }, |
| 543 |
{ "SETUP", VLM5030::Phase::SETUP }, |
| 544 |
{ "WAIT", VLM5030::Phase::WAIT }, |
| 545 |
{ "RUN", VLM5030::Phase::RUN }, |
| 546 |
{ "STOP", VLM5030::Phase::STOP }, |
| 547 |
{ "END", VLM5030::Phase::END }, |
| 548 |
}); |
| 549 |
SERIALIZE_ENUM(VLM5030::Phase, phaseInfo); |
| 550 |
|
| 551 |
|
| 552 |
|
| 553 |
template<typename Archive> |
| 554 |
void VLM5030::serialize(Archive& ar, unsigned version) |
| 555 |
{ |
| 556 |
ar.serialize("address_mask", address_mask, |
| 557 |
"frame_size", frame_size, |
| 558 |
"pitch_offset", pitch_offset, |
| 559 |
"current_energy", current_energy, |
| 560 |
"current_pitch", current_pitch, |
| 561 |
"current_k", current_k, |
| 562 |
"x", x, |
| 563 |
"address", address, |
| 564 |
"vcu_addr_h", vcu_addr_h, |
| 565 |
"old_k", old_k, |
| 566 |
"new_k", new_k, |
| 567 |
"target_k", target_k, |
| 568 |
"old_energy", old_energy, |
| 569 |
"new_energy", new_energy, |
| 570 |
"target_energy", target_energy, |
| 571 |
"old_pitch", old_pitch, |
| 572 |
"new_pitch", new_pitch, |
| 573 |
"target_pitch", target_pitch, |
| 574 |
"interp_step", interp_step, |
| 575 |
"interp_count", interp_count, |
| 576 |
"sample_count", sample_count, |
| 577 |
"pitch_count", pitch_count, |
| 578 |
"latch_data", latch_data, |
| 579 |
"parameter", parameter, |
| 580 |
"pin_BSY", pin_BSY, |
| 581 |
"pin_ST", pin_ST, |
| 582 |
"pin_VCU", pin_VCU, |
| 583 |
"pin_RST", pin_RST); |
| 584 |
if (ar.versionAtLeast(version, 2)) { |
| 585 |
ar.serialize("phase", phase); |
| 586 |
} else { |
| 587 |
assert(Archive::IS_LOADER); |
| 588 |
int tmp = 0; |
| 589 |
ar.serialize("phase", tmp); |
| 590 |
switch (tmp) { |
| 591 |
using enum Phase; |
| 592 |
case 0: phase = RESET; break; |
| 593 |
case 1: phase = IDLE; break; |
| 594 |
case 2: phase = SETUP; break; |
| 595 |
case 3: phase = WAIT; break; |
| 596 |
case 4: phase = RUN; break; |
| 597 |
case 5: phase = STOP; break; |
| 598 |
default: phase = END; break; |
| 599 |
} |
| 600 |
} |
| 601 |
} |
| 602 |
|
| 603 |
INSTANTIATE_SERIALIZE_METHODS(VLM5030); |
| 604 |
|
| 605 |
} |