| 86 | const auto * value = object.find(field); |
| 87 | if (value != nullptr && !value->is_null()) { |
| 88 | options[option_key] = minitts::cli::json_option_string(*value); |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | std::vector<uint8_t> encode_pcm16_wav(const engine::runtime::AudioBuffer & audio) { |
| 93 | if (audio.sample_rate <= 0) { |
| 94 | throw std::runtime_error("audio output sample rate must be positive"); |
| 95 | } |
| 96 | if (audio.channels <= 0) { |
| 97 | throw std::runtime_error("audio output channel count must be positive"); |
| 98 | } |
| 99 | if (audio.samples.size() % static_cast<size_t>(audio.channels) != 0) { |
| 100 | throw std::runtime_error("audio output sample count must be divisible by channel count"); |
| 101 | } |
| 102 | |
| 103 | const uint16_t channels = static_cast<uint16_t>(audio.channels); |
| 104 | const uint16_t bits_per_sample = 16; |
| 105 | const uint32_t data_bytes = static_cast<uint32_t>(audio.samples.size() * sizeof(int16_t)); |
| 106 | const uint32_t riff_size = 36 + data_bytes; |
| 107 | const uint32_t byte_rate = static_cast<uint32_t>(audio.sample_rate) * channels * bits_per_sample / 8; |
| 108 | const uint16_t block_align = channels * bits_per_sample / 8; |
| 109 | |
| 110 | std::vector<uint8_t> out; |
| 111 | out.reserve(44 + data_bytes); |
| 112 | auto append_bytes = [&](const void * data, size_t size) { |
| 113 | const auto * bytes = static_cast<const uint8_t *>(data); |
| 114 | out.insert(out.end(), bytes, bytes + size); |
| 115 | }; |
| 116 | auto append_u16 = [&](uint16_t value) { append_bytes(&value, sizeof(value)); }; |
| 117 | auto append_u32 = [&](uint32_t value) { append_bytes(&value, sizeof(value)); }; |
| 118 | |
| 119 | out.insert(out.end(), {'R', 'I', 'F', 'F'}); |
| 120 | append_u32(riff_size); |
| 121 | out.insert(out.end(), {'W', 'A', 'V', 'E', 'f', 'm', 't', ' '}); |
| 122 | append_u32(16); |
| 123 | append_u16(1); |
| 124 | append_u16(channels); |
| 125 | append_u32(static_cast<uint32_t>(audio.sample_rate)); |
| 126 | append_u32(byte_rate); |
| 127 | append_u16(block_align); |
| 128 | append_u16(bits_per_sample); |
| 129 | out.insert(out.end(), {'d', 'a', 't', 'a'}); |
| 130 | append_u32(data_bytes); |
| 131 | for (float sample : audio.samples) { |
| 132 | sample = std::max(-1.0F, std::min(1.0F, sample)); |
| 133 | const auto pcm = static_cast<int16_t>(std::lrint(sample * 32767.0F)); |
| 134 | append_bytes(&pcm, sizeof(pcm)); |
| 135 | } |
| 136 | return out; |
no test coverage detected