96static constexpr int FR_SIZE = 4;
98static constexpr uint8_t IP_SIZE_SLOWER = 240 / FR_SIZE;
99static constexpr uint8_t IP_SIZE_SLOW = 200 / FR_SIZE;
100static constexpr uint8_t IP_SIZE_NORMAL = 160 / FR_SIZE;
101static constexpr uint8_t IP_SIZE_FAST = 120 / FR_SIZE;
102static constexpr uint8_t IP_SIZE_FASTER = 80 / FR_SIZE;
122static constexpr std::array<uint8_t, 8> VLM5030_speed_table =
139static constexpr std::array<uint16_t, 0x20> energyTable =
141 0, 2, 4, 6, 10, 12, 14, 18,
142 22, 26, 30, 34, 38, 44, 48, 54,
143 62, 68, 76, 84, 94,102,114,124,
144 136,150,164,178,196,214,232,254
148static constexpr std::array<uint8_t, 0x20> pitchTable =
152 23, 24, 25, 26, 27, 28, 29, 30,
153 32, 34, 36, 38, 40, 42, 44, 46,
154 50, 54, 58, 62, 66, 70, 74, 78,
155 86, 94, 102,110,118,126
158static constexpr std::array<int16_t, 64> K1_table = {
159 -24898, -25672, -26446, -27091, -27736, -28252, -28768, -29155,
160 -29542, -29929, -30316, -30574, -30832, -30961, -31219, -31348,
161 -31606, -31735, -31864, -31864, -31993, -32122, -32122, -32251,
162 -32251, -32380, -32380, -32380, -32509, -32509, -32509, -32509,
163 24898, 23995, 22963, 21931, 20770, 19480, 18061, 16642,
164 15093, 13416, 11610, 9804, 7998, 6063, 3999, 1935,
165 0, -1935, -3999, -6063, -7998, -9804, -11610, -13416,
166 -15093, -16642, -18061, -19480, -20770, -21931, -22963, -23995
168static constexpr std::array<int16_t, 32> K2_table = {
169 0, -3096, -6321, -9417, -12513, -15351, -18061, -20770,
170 -23092, -25285, -27220, -28897, -30187, -31348, -32122, -32638,
171 0, 32638, 32122, 31348, 30187, 28897, 27220, 25285,
172 23092, 20770, 18061, 15351, 12513, 9417, 6321, 3096
174static constexpr std::array<int16_t, 16> K3_table = {
175 0, -3999, -8127, -12255, -16384, -20383, -24511, -28639,
176 32638, 28639, 24511, 20383, 16254, 12255, 8127, 3999
178static constexpr std::array<int16_t, 8> K5_table = {
179 0, -8127, -16384, -24511, 32638, 24511, 16254, 8127
182unsigned VLM5030::getBits(
unsigned sBit,
unsigned bits)
184 unsigned offset = address + (sBit / 8);
185 unsigned data = rom[(offset + 0) & address_mask] +
186 rom[(offset + 1) & address_mask] * 256;
188 data &= (0xFF >> (8 - bits));
193int VLM5030::parseFrame()
196 old_energy = new_energy;
197 old_pitch = new_pitch;
200 uint8_t cmd = rom[address & address_mask];
203 new_energy = new_pitch = 0;
211 int nums = ((cmd >> 2) + 1) * 2;
212 return nums * FR_SIZE;
216 new_pitch = narrow_cast<uint8_t>((pitchTable[getBits(1, 5)] + pitch_offset) & 0xff);
218 new_energy = energyTable[getBits(6, 5)];
221 new_k[9] = K5_table[getBits(11, 3)];
222 new_k[8] = K5_table[getBits(14, 3)];
223 new_k[7] = K5_table[getBits(17, 3)];
224 new_k[6] = K5_table[getBits(20, 3)];
225 new_k[5] = K5_table[getBits(23, 3)];
226 new_k[4] = K5_table[getBits(26, 3)];
227 new_k[3] = K3_table[getBits(29, 4)];
228 new_k[2] = K3_table[getBits(33, 4)];
229 new_k[1] = K2_table[getBits(37, 5)];
230 new_k[0] = K1_table[getBits(42, 6)];
237void VLM5030::generateChannels(std::span<float*> bufs,
unsigned num)
252 if (sample_count == 0) {
258 sample_count = frame_size;
260 if (interp_count == 0) {
262 interp_count = narrow_cast<uint8_t>(parseFrame());
263 if (interp_count == 0) {
265 interp_count = FR_SIZE;
266 sample_count = frame_size;
270 current_energy = old_energy;
271 current_pitch = old_pitch;
274 if (current_energy == 0) {
276 target_pitch = narrow_cast<uint8_t>(current_pitch);
280 target_energy = new_energy;
281 target_pitch = new_pitch;
287 interp_count -= interp_step;
289 int interp_effect = FR_SIZE - (interp_count % FR_SIZE);
290 current_energy = old_energy + (target_energy - old_energy) * interp_effect / FR_SIZE;
292 current_pitch = old_pitch + (target_pitch - old_pitch) * interp_effect / FR_SIZE;
295 current_k[i] = old_k[i] + (target_k[i] - old_k[i]) * interp_effect / FR_SIZE;
298 int current_val = [&] {
299 if (old_energy == 0) {
302 }
else if (old_pitch <= 1) {
305 : -int(current_energy);
308 return (pitch_count == 0) ? int(current_energy) : 0;
313 std::array<int, 11> u;
315 for (
int i = 9; i >= 0; --i) {
316 u[i] = u[i + 1] - ((current_k[i] * x[i]) / 32768);
318 for (
int i = 9; i >= 1; --i) {
319 x[i] = x[i - 1] + ((current_k[i - 1] * u[i - 1]) / 32768);
324 bufs[0][buf_count] = narrow<float>(
std::clamp(u[0], -511, 511));
328 if (pitch_count >= current_pitch) {
338 if (sample_count <= num) {
343 sample_count -= narrow<uint8_t>(num);
347 if (sample_count <= num) {
352 sample_count -= narrow<uint8_t>(num);
357 bufs[0][buf_count++] = 0;
362float VLM5030::getAmplificationFactorImpl()
const
364 return 1.0f / (1 << 9);
368void VLM5030::setupParameter(uint8_t param)
376 }
else if (param & 1) {
383 frame_size = VLM5030_speed_table[(param >> 3) & 7];
388 }
else if (param & 0x40) {
402 old_energy = old_pitch = 0;
403 new_energy = new_pitch = 0;
404 current_energy = current_pitch = 0;
405 target_energy = target_pitch = 0;
410 interp_count = sample_count = pitch_count = 0;
413 setupParameter(0x00);
432 setRST((data & 0x01) != 0);
433 setVCU((data & 0x04) != 0);
434 setST ((data & 0x02) != 0);
438void VLM5030::setRST(
bool pin)
443 setupParameter(latch_data);
456void VLM5030::setVCU(
bool pin)
463void VLM5030::setST(
bool pin)
474 vcu_addr_h = narrow<uint16_t>((latch_data << 8) + 0x01);
479 address = (vcu_addr_h & 0xff00) + latch_data;
483 int table = (latch_data & 0xfe) + ((
int(latch_data) & 1) << 8);
484 address = uint16_t((rom[(table + 0) & address_mask] << 8) |
485 (rom[(table + 1) & address_mask] << 0));
488 sample_count = frame_size;
489 interp_count = FR_SIZE;
505static XMLElement* getRomConfig(
506 DeviceConfig& config,
const std::string& name, std::string_view romFilename)
508 auto& doc = config.getXMLDocument();
509 auto* voiceROMconfig = doc.allocateElement(doc.allocateString(name));
511 auto* romElement = voiceROMconfig->setFirstChild(doc.allocateElement(
"rom"));
512 romElement->setFirstChild(doc.allocateElement(
513 "sha1",
"4f36d139ee4baa7d5980f765de9895570ee05f40"))
514 ->setNextSibling(doc.allocateElement(
517 ->setNextSibling(doc.allocateElement(
518 "filename",
"keyboardmaster/voice.rom"));
519 return voiceROMconfig;
522static constexpr auto INPUT_RATE = unsigned(
cstd::round(3579545 / 440.0));
525 std::string_view romFilename,
const DeviceConfig& config)
527 , rom(name_ +
" ROM",
"rom",
DeviceConfig(config, *getRomConfig(const_cast<
DeviceConfig&>(config), name_, romFilename)))
532 assert(rom.
size() != 0);
533 address_mask = narrow<unsigned>(rom.
size() - 1);
543template<
typename Archive>
546 ar.serialize(
"address_mask", address_mask,
547 "frame_size", frame_size,
548 "pitch_offset", pitch_offset,
549 "current_energy", current_energy,
550 "current_pitch", current_pitch,
551 "current_k", current_k,
554 "vcu_addr_h", vcu_addr_h,
557 "target_k", target_k,
558 "old_energy", old_energy,
559 "new_energy", new_energy,
560 "target_energy", target_energy,
561 "old_pitch", old_pitch,
562 "new_pitch", new_pitch,
563 "target_pitch", target_pitch,
564 "interp_step", interp_step,
565 "interp_count", interp_count,
566 "sample_count", sample_count,
567 "pitch_count", pitch_count,
568 "latch_data", latch_data,
569 "parameter", parameter,
void updateStream(EmuTime::param time)
void unregisterSound()
Unregisters this sound device with the Mixer.
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
void writeData(uint8_t data)
latch control data
bool getBSY(EmuTime::param time) const
get BSY pin level
VLM5030(const std::string &name, static_string_view desc, std::string_view romFilename, const DeviceConfig &config)
void writeControl(uint8_t data, EmuTime::param time)
set RST / VCU / ST pins
void serialize(Archive &ar, unsigned version)
XMLAttribute * setFirstAttribute(XMLAttribute *attribute)
constexpr double round(double x)
constexpr vecN< N, T > clamp(const vecN< N, T > &x, const vecN< N, T > &minVal, const vecN< N, T > &maxVal)
string_view stripExtension(string_view path)
Returns the path without extension.
This file implemented 3 utility functions:
constexpr void fill(ForwardRange &&range, const T &value)
auto copy(InputRange &&range, OutputIter out)
bool random_bool()
Return a random boolean value.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
TemporaryString tmpStrCat(Ts &&... ts)
constexpr auto xrange(T e)