99static constexpr int FR_SIZE = 4;
101static constexpr uint8_t IP_SIZE_SLOWER = 240 / FR_SIZE;
102static constexpr uint8_t IP_SIZE_SLOW = 200 / FR_SIZE;
103static constexpr uint8_t IP_SIZE_NORMAL = 160 / FR_SIZE;
104static constexpr uint8_t IP_SIZE_FAST = 120 / FR_SIZE;
105static constexpr uint8_t IP_SIZE_FASTER = 80 / FR_SIZE;
114static constexpr std::array<uint8_t, 8> VLM5030_speed_table =
131static constexpr std::array<uint16_t, 0x20> energyTable =
133 0, 2, 4, 6, 10, 12, 14, 18,
134 22, 26, 30, 34, 38, 44, 48, 54,
135 62, 68, 76, 84, 94,102,114,124,
136 136,150,164,178,196,214,232,254
140static constexpr std::array<uint8_t, 0x20> pitchTable =
144 23, 24, 25, 26, 27, 28, 29, 30,
145 32, 34, 36, 38, 40, 42, 44, 46,
146 50, 54, 58, 62, 66, 70, 74, 78,
147 86, 94, 102,110,118,126
150static constexpr std::array<int16_t, 64> K1_table = {
151 -24898, -25672, -26446, -27091, -27736, -28252, -28768, -29155,
152 -29542, -29929, -30316, -30574, -30832, -30961, -31219, -31348,
153 -31606, -31735, -31864, -31864, -31993, -32122, -32122, -32251,
154 -32251, -32380, -32380, -32380, -32509, -32509, -32509, -32509,
155 24898, 23995, 22963, 21931, 20770, 19480, 18061, 16642,
156 15093, 13416, 11610, 9804, 7998, 6063, 3999, 1935,
157 0, -1935, -3999, -6063, -7998, -9804, -11610, -13416,
158 -15093, -16642, -18061, -19480, -20770, -21931, -22963, -23995
160static constexpr std::array<int16_t, 32> K2_table = {
161 0, -3096, -6321, -9417, -12513, -15351, -18061, -20770,
162 -23092, -25285, -27220, -28897, -30187, -31348, -32122, -32638,
163 0, 32638, 32122, 31348, 30187, 28897, 27220, 25285,
164 23092, 20770, 18061, 15351, 12513, 9417, 6321, 3096
166static constexpr std::array<int16_t, 16> K3_table = {
167 0, -3999, -8127, -12255, -16384, -20383, -24511, -28639,
168 32638, 28639, 24511, 20383, 16254, 12255, 8127, 3999
170static constexpr std::array<int16_t, 8> K5_table = {
171 0, -8127, -16384, -24511, 32638, 24511, 16254, 8127
174unsigned VLM5030::getBits(
unsigned sBit,
unsigned bits)
const
176 unsigned offset = address + (sBit / 8);
177 unsigned data = rom[(offset + 0) & address_mask] +
178 rom[(offset + 1) & address_mask] * 256;
180 data &= (0xFF >> (8 - bits));
185int VLM5030::parseFrame()
188 old_energy = new_energy;
189 old_pitch = new_pitch;
192 if (uint8_t cmd = rom[address & address_mask];
195 new_energy = new_pitch = 0;
203 int nums = ((cmd >> 2) + 1) * 2;
204 return nums * FR_SIZE;
208 new_pitch = narrow_cast<uint8_t>((pitchTable[getBits(1, 5)] + pitch_offset) & 0xff);
210 new_energy = energyTable[getBits(6, 5)];
213 new_k[9] = K5_table[getBits(11, 3)];
214 new_k[8] = K5_table[getBits(14, 3)];
215 new_k[7] = K5_table[getBits(17, 3)];
216 new_k[6] = K5_table[getBits(20, 3)];
217 new_k[5] = K5_table[getBits(23, 3)];
218 new_k[4] = K5_table[getBits(26, 3)];
219 new_k[3] = K3_table[getBits(29, 4)];
220 new_k[2] = K3_table[getBits(33, 4)];
221 new_k[1] = K2_table[getBits(37, 5)];
222 new_k[0] = K1_table[getBits(42, 6)];
229void VLM5030::generateChannels(std::span<float*> bufs,
unsigned num)
245 if (sample_count == 0) {
251 sample_count = frame_size;
253 if (interp_count == 0) {
255 interp_count = narrow_cast<uint8_t>(parseFrame());
256 if (interp_count == 0) {
258 interp_count = FR_SIZE;
259 sample_count = frame_size;
263 current_energy = old_energy;
264 current_pitch = old_pitch;
267 if (current_energy == 0) {
269 target_pitch = narrow_cast<uint8_t>(current_pitch);
273 target_energy = new_energy;
274 target_pitch = new_pitch;
280 interp_count -= interp_step;
282 int interp_effect = FR_SIZE - (interp_count % FR_SIZE);
283 current_energy = old_energy + (target_energy - old_energy) * interp_effect / FR_SIZE;
285 current_pitch = old_pitch + (target_pitch - old_pitch) * interp_effect / FR_SIZE;
288 current_k[i] = old_k[i] + (target_k[i] - old_k[i]) * interp_effect / FR_SIZE;
291 int current_val = [&] {
292 if (old_energy == 0) {
295 }
else if (old_pitch <= 1) {
298 : -int(current_energy);
301 return (pitch_count == 0) ? int(current_energy) : 0;
306 std::array<int, 11> u;
308 for (
int i = 9; i >= 0; --i) {
309 u[i] = u[i + 1] - ((current_k[i] * x[i]) / 32768);
311 for (
int i = 9; i >= 1; --i) {
312 x[i] = x[i - 1] + ((current_k[i - 1] * u[i - 1]) / 32768);
317 bufs[0][buf_count] = narrow<float>(std::clamp(u[0], -511, 511));
321 if (pitch_count >= current_pitch) {
332 if (sample_count <= num) {
337 sample_count -= narrow<uint8_t>(num);
341 if (sample_count <= num) {
346 sample_count -= narrow<uint8_t>(num);
354 bufs[0][buf_count++] = 0;
359float VLM5030::getAmplificationFactorImpl()
const
361 return 1.0f / (1 << 9);
365void VLM5030::setupParameter(uint8_t param)
373 }
else if (param & 1) {
380 frame_size = VLM5030_speed_table[(param >> 3) & 7];
385 }
else if (param & 0x40) {
399 old_energy = old_pitch = 0;
400 new_energy = new_pitch = 0;
401 current_energy = current_pitch = 0;
402 target_energy = target_pitch = 0;
407 interp_count = sample_count = pitch_count = 0;
410 setupParameter(0x00);
429 setRST((data & 0x01) != 0);
430 setVCU((data & 0x04) != 0);
431 setST ((data & 0x02) != 0);
435void VLM5030::setRST(
bool pin)
440 setupParameter(latch_data);
453void VLM5030::setVCU(
bool pin)
460void VLM5030::setST(
bool pin)
471 vcu_addr_h = narrow<uint16_t>((latch_data << 8) + 0x01);
476 address = (vcu_addr_h & 0xff00) + latch_data;
480 int table = (latch_data & 0xfe) + ((
int(latch_data) & 1) << 8);
481 address = uint16_t((rom[(table + 0) & address_mask] << 8) |
482 (rom[(table + 1) & address_mask] << 0));
485 sample_count = frame_size;
486 interp_count = FR_SIZE;
502static XMLElement* getRomConfig(
503 DeviceConfig& config,
const std::string& name, std::string_view romFilename)
505 auto& doc = config.getXMLDocument();
506 auto* voiceROMconfig = doc.allocateElement(doc.allocateString(name));
508 auto* romElement = voiceROMconfig->setFirstChild(doc.allocateElement(
"rom"));
509 romElement->setFirstChild(doc.allocateElement(
510 "sha1",
"4f36d139ee4baa7d5980f765de9895570ee05f40"))
511 ->setNextSibling(doc.allocateElement(
514 ->setNextSibling(doc.allocateElement(
515 "filename",
"keyboardmaster/voice.rom"));
516 return voiceROMconfig;
519static constexpr auto INPUT_RATE = unsigned(
cstd::round(3579545 / 440.0));
522 std::string_view romFilename,
const DeviceConfig& config)
524 , rom(name_ +
" ROM",
"rom",
DeviceConfig(config, *getRomConfig(const_cast<
DeviceConfig&>(config), name_, romFilename)))
529 assert(rom.
size() != 0);
530 address_mask = narrow<unsigned>(rom.
size() - 1);
540static constexpr std::initializer_list<enum_string<VLM5030::Phase>> phaseInfo = {
553template<
typename Archive>
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,
564 "vcu_addr_h", vcu_addr_h,
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,
584 if (ar.versionAtLeast(version, 2)) {
585 ar.serialize(
"phase", phase);
587 assert(Archive::IS_LOADER);
589 ar.serialize(
"phase", tmp);
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;
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)
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)
constexpr auto copy(InputRange &&range, OutputIter out)
bool random_bool()
Return a random boolean value.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
TemporaryString tmpStrCat(Ts &&... ts)
constexpr auto xrange(T e)