diff --git a/src/sms/SampleCache.cc b/src/sms/SampleCache.cc index b3c5fca..7bfcce2 100644 --- a/src/sms/SampleCache.cc +++ b/src/sms/SampleCache.cc @@ -1,6 +1,6 @@ #include "SampleCache.hh" -#include +#include using namespace std; @@ -27,7 +27,7 @@ vector resample( int error = src_simple(&data, resample_method, num_channels); if (error) { - throw runtime_error(phosg::string_printf("src_simple failed (ratio=%g): %s", src_ratio, src_strerror(error))); + throw runtime_error(format("src_simple failed (ratio={}): {}", src_ratio, src_strerror(error))); } output_samples.resize(data.output_frames_gen * num_channels); diff --git a/src/sms/aaf.cc b/src/sms/aaf.cc index d577676..5b301a2 100644 --- a/src/sms/aaf.cc +++ b/src/sms/aaf.cc @@ -8,8 +8,9 @@ #include #include #include -#include #include +#include +#include #include #include "instrument.hh" @@ -154,7 +155,7 @@ pair> wsys_decode(const void* vdata, // try both Banks and Waves subdirectories static const vector directory_names({"Banks", "Waves"}); for (const auto& directory_name : directory_names) { - string aw_filename = phosg::string_printf("%s/%s/%s", base_directory, directory_name.c_str(), entry->filename); + string aw_filename = format("{}/{}/{}", base_directory, directory_name, entry->filename); try { aw_file_contents = phosg::load_file(aw_filename.c_str()); break; @@ -163,7 +164,7 @@ pair> wsys_decode(const void* vdata, } } if (aw_file_contents.empty()) { - throw runtime_error(phosg::string_printf("%s does not exist in any checked subdirectory", entry->filename)); + throw runtime_error(format("{} does not exist in any checked subdirectory", entry->filename)); } for (size_t y = 0; y < entry->wav_count; y++) { @@ -223,7 +224,7 @@ pair> wsys_decode(const void* vdata, } ret_snd.num_channels = is_stereo ? 2 : 1; } else { - throw runtime_error(phosg::string_printf("unknown wav entry type: 0x%" PRIX32, wav_entry->type)); + throw runtime_error(format("unknown wav entry type: 0x{:X}", wav_entry->type)); } } } @@ -266,7 +267,7 @@ unordered_map barc_decode(const void* vdata, throw invalid_argument("BARC data too small for header"); } - string sequence_archive_filename = phosg::string_printf("%s/Seqs/%s", base_directory, + string sequence_archive_filename = format("{}/Seqs/{}", base_directory, barc->archive_filename); phosg::scoped_fd sequence_archive_fd(sequence_archive_filename.c_str(), O_RDONLY); @@ -283,7 +284,7 @@ unordered_map barc_decode(const void* vdata, size_t suffix = 0; string effective_name = e.name; while (ret.count(effective_name)) { - effective_name = phosg::string_printf("%s@%zu", e.name, ++suffix); + effective_name = format("{}@{}", e.name, ++suffix); } ret.emplace(piecewise_construct, forward_as_tuple(effective_name), forward_as_tuple(x, std::move(data))); @@ -466,9 +467,9 @@ SoundEnvironment aaf_decode(const void* vdata, size_t size, const char* base_dir break; default: - throw invalid_argument(phosg::string_printf( - "unknown chunk type %.4s (%08X)", - reinterpret_cast(&chunk_type), chunk_type)); + throw invalid_argument(format( + "unknown chunk type {} ({:08X})", + string_view(reinterpret_cast(&chunk_type), 4), chunk_type)); } } @@ -536,7 +537,7 @@ SoundEnvironment baa_decode(const void* vdata, size_t size, const char* base_dir uint32_t offset = data_fields[field_offset++]; uint32_t end_offset = data_fields[field_offset++]; ret.sequence_programs.emplace(piecewise_construct, - forward_as_tuple(phosg::string_printf("seq%" PRIu32, id)), + forward_as_tuple(format("seq{}", id)), forward_as_tuple(id, string(reinterpret_cast(data + offset), end_offset - offset))); break; } @@ -558,9 +559,9 @@ SoundEnvironment baa_decode(const void* vdata, size_t size, const char* base_dir default: phosg::be_uint32_t chunk_type_be = chunk_type; - throw invalid_argument(phosg::string_printf( - "unknown chunk type %.4s (%08X)", - reinterpret_cast(&chunk_type_be), chunk_type)); + throw invalid_argument(format( + "unknown chunk type {} ({:08X})", + string_view(reinterpret_cast(&chunk_type_be), 4), chunk_type)); } } @@ -628,12 +629,12 @@ SoundEnvironment load_sound_environment(const char* base_directory) { // default.dol in a hex editor and copy the resulting data (through the end of // the sequence names) to sequence.barc in the Seqs directory { - string filename = phosg::string_printf("%s/Banks/pikibank.bx", base_directory); - if (phosg::isfile(filename)) { + string filename = format("{}/Banks/pikibank.bx", base_directory); + if (filesystem::is_regular_file(filename)) { string data = phosg::load_file(filename); auto env = bx_decode(data.data(), data.size(), base_directory); - data = phosg::load_file(phosg::string_printf("%s/Seqs/sequence.barc", base_directory)); + data = phosg::load_file(format("{}/Seqs/sequence.barc", base_directory)); env.sequence_programs = barc_decode(data.data(), data.size(), base_directory); return env; diff --git a/src/sms/instrument.cc b/src/sms/instrument.cc index 90b5b49..99280fc 100644 --- a/src/sms/instrument.cc +++ b/src/sms/instrument.cc @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include "afc.hh" @@ -330,8 +330,9 @@ Instrument ibnk_inst_decode(const void* vdata, size_t offset, size_t inst_id) { count = 0x64; } else { - throw invalid_argument(phosg::string_printf("unknown instrument format at %08zX: %.4s (%08X)", - offset, inst_data, *reinterpret_cast(inst_data))); + throw invalid_argument(format("unknown instrument format at {:08X}: {:.4} ({:08X})", + offset, string_view(reinterpret_cast(inst_data), 4), + *reinterpret_cast(inst_data))); } for (uint32_t x = 0; x < count; x++) { @@ -437,11 +438,11 @@ InstrumentBank ibnk_decode(const void* vdata) { offset += list_header->size + sizeof(ibnk_list_header); } else if (!memcmp(&chunk_header->magic, "BANK", 4)) { - throw runtime_error(phosg::string_printf("IBNK contains BANK at %08zX but it is not first", + throw runtime_error(format("IBNK contains BANK at {:08X} but it is not first", offset)); } else { - throw runtime_error(phosg::string_printf("unknown IBNK chunk type at %08zX: %.4s", + throw runtime_error(format("unknown IBNK chunk type at {:08X}: {:.4}", offset, reinterpret_cast(&chunk_header->magic))); } } diff --git a/src/sms/smssynth.cc b/src/sms/smssynth.cc index a336379..39b2956 100644 --- a/src/sms/smssynth.cc +++ b/src/sms/smssynth.cc @@ -12,8 +12,8 @@ #include #include #include -#include #include +#include #include #include @@ -132,7 +132,7 @@ void disassemble_set_perf( } else if (type == 0x03) { param_name = "panning"; } else { - param_name = phosg::string_printf("[%02hhX]", type); + param_name = format("[{:02X}]", type); } printf("%08zX: set_perf %s=", opcode_offset, param_name.c_str()); @@ -190,18 +190,18 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { uint8_t voice = r.get_u8(); // between 1 and 8 inclusive uint8_t vel = r.get_u8(); string note_name = phosg_audio::name_for_note(opcode); - disassembly = phosg::string_printf("note note=%s, voice=%hhu, vel=0x%02hhX", - note_name.c_str(), voice, vel); + disassembly = format("note note={}, voice={}, vel=0x{:02X}", + note_name, voice, vel); } else switch (opcode) { case 0x80: { uint8_t wait_time = r.get_u8(); - disassembly = phosg::string_printf("wait %hhu", wait_time); + disassembly = format("wait {}", wait_time); break; } case 0x88: { uint16_t wait_time = r.get_u16b(); - disassembly = phosg::string_printf("wait %hu", wait_time); + disassembly = format("wait {}", wait_time); break; } @@ -213,7 +213,7 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { case 0x86: case 0x87: { uint8_t voice = opcode & 7; - disassembly = phosg::string_printf("voice_off %hhu", voice); + disassembly = format("voice_off {}", voice); break; } @@ -260,24 +260,23 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { try { param_name = param_names.at(type); } catch (const out_of_range&) { - param_name = phosg::string_printf("[%02hhX]", type); + param_name = format("[{:02X}]", type); } - disassembly = phosg::string_printf("set_perf%s %s=", - is_extended ? "_ext" : " ", param_name.c_str()); + disassembly = format("set_perf{} {}=", + is_extended ? "_ext" : " ", param_name); if (data_type == 4) { - disassembly += phosg::string_printf("0x%02hhX (u8)", static_cast(value)); + disassembly += format("0x{:02X} (u8)", static_cast(value)); } else if (data_type == 8) { - disassembly += phosg::string_printf("0x%02hhX (s8)", static_cast(value)); + disassembly += format("0x{:02X} (s8)", static_cast(value)); } else if (data_type == 12) { - disassembly += phosg::string_printf("0x%04hX (s16)", static_cast(value)); + disassembly += format("0x{:04X} (s16)", static_cast(value)); } if (duration_flags == 2) { - disassembly += phosg::string_printf(", duration=0x%02hhX", static_cast(duration)); + disassembly += format(", duration=0x{:02X}", static_cast(duration)); } else if (duration == 3) { - disassembly += phosg::string_printf(", duration=0x%04hX", static_cast(duration)); + disassembly += format(", duration=0x{:04X}", static_cast(duration)); } - break; } @@ -297,21 +296,21 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { try { param_name = param_names.at(param); } catch (const out_of_range&) { - param_name = phosg::string_printf("[%02hhX]", param); + param_name = format("[{:02X}]", param); } - string value_str = (opcode & 0x08) ? phosg::string_printf("0x%04hX", value) : phosg::string_printf("0x%02hhX", static_cast(value)); - disassembly = phosg::string_printf("set_param %s, %s", - param_name.c_str(), value_str.c_str()); + string value_str = (opcode & 0x08) ? format("0x{:04X}", value) : format("0x{:02X}", static_cast(value)); + disassembly = format("set_param {}, {}", + param_name, value_str); break; } case 0xC1: { uint8_t track_id = r.get_u8(); uint32_t offset = r.get_u24b(); - disassembly = phosg::string_printf("start_track %hhu, offset=0x%" PRIX32, + disassembly = format("start_track {}, offset=0x{:X}", track_id, offset); - track_start_labels.emplace(offset, phosg::string_printf("track_%02hhX_start", track_id)); + track_start_labels.emplace(offset, format("track_{:02X}_start", track_id)); break; } @@ -320,11 +319,11 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { case 0xC7: case 0xC8: { const char* opcode_name = (opcode > 0xC4) ? "jmp " : "call"; - string conditional_str = (opcode & 1) ? "" : phosg::string_printf("cond=0x%02hhX, ", r.get_u8()); + string conditional_str = (opcode & 1) ? "" : format("cond=0x{:02X}, ", r.get_u8()); uint32_t offset = r.get_u24b(); - disassembly = phosg::string_printf("%s %soffset=0x%" PRIX32, - opcode_name, conditional_str.c_str(), offset); + disassembly = format("{} {}offset=0x{:X}", + opcode_name, conditional_str, offset); break; } @@ -333,20 +332,20 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { break; case 0xC6: { - string conditional_str = phosg::string_printf("cond=0x%02hhX", r.get_u8()); - disassembly = phosg::string_printf("ret %s", conditional_str.c_str()); + string conditional_str = format("cond=0x{:02X}", r.get_u8()); + disassembly = format("ret {}", conditional_str); break; } case 0xE7: { uint16_t arg = r.get_u16b(); - disassembly = phosg::string_printf("sync_gpu 0x%04hX", arg); + disassembly = format("sync_gpu 0x{:04X}", arg); break; } case 0xFD: { uint16_t pulse_rate = r.get_u16b(); - disassembly = phosg::string_printf("set_pulse_rate %hu", pulse_rate); + disassembly = format("set_pulse_rate {}", pulse_rate); break; } @@ -354,7 +353,7 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { case 0xFE: { uint16_t tempo = r.get_u16b(); uint64_t usec_pqn = 60000000 / tempo; - disassembly = phosg::string_printf("set_tempo %hu /* usecs per quarter note = %" PRIu64 " */", tempo, usec_pqn); + disassembly = format("set_tempo {} /* usecs per quarter note = {} */", tempo, usec_pqn); break; } @@ -382,26 +381,26 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { uint8_t port = r.get_u8(); uint8_t reg = r.get_u8(); uint8_t value = r.get_u8(); - disassembly = phosg::string_printf("%s r%hhu, %hhu, %hhu", opcode_names.at(opcode), + disassembly = format("{} r{}, {}, {}", opcode_names.at(opcode), reg, port, value); break; } case 0xD2: - disassembly = phosg::string_printf(".check_port_in 0x%hX", r.get_u16b()); + disassembly = format(".check_port_in 0x{:X}", r.get_u16b()); break; case 0xD3: - disassembly = phosg::string_printf(".check_port_ex 0x%hX", r.get_u16b()); + disassembly = format(".check_port_ex 0x{:X}", r.get_u16b()); break; case 0xD8: { uint8_t reg = r.get_u8(); int16_t val = r.get_s16b(); if (reg == 0x62) { - disassembly = phosg::string_printf("mov r98, %hd /* set_pulse_rate */", val); + disassembly = format("mov r98, {} /* set_pulse_rate */", val); } else { - disassembly = phosg::string_printf("mov r%hhu, 0x%hX", reg, val); + disassembly = format("mov r{}, 0x{:X}", reg, val); } break; } @@ -417,7 +416,7 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { } catch (const out_of_range&) { } - disassembly = phosg::string_printf("%s r%hhu, r%hhu", opcode_name, + disassembly = format("{} r{}, r{}", opcode_name, dst_reg, src_reg); break; } @@ -433,16 +432,16 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { } catch (const out_of_range&) { } - disassembly = phosg::string_printf("%s r%hhu, 0x%hX", opcode_name, + disassembly = format("{} r{}, 0x{:X}", opcode_name, dst_reg, val); break; } case 0xE2: - disassembly = phosg::string_printf("set_bank 0x%hX", r.get_u8()); + disassembly = format("set_bank 0x{:X}", r.get_u8()); break; case 0xE3: - disassembly = phosg::string_printf("set_instrument 0x%hX", r.get_u8()); + disassembly = format("set_instrument 0x{:X}", r.get_u8()); break; case 0xFB: { @@ -451,7 +450,7 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { while ((b = r.get_u8())) { s.push_back(b); } - disassembly = phosg::string_printf("debug_str \"%s\"", s.c_str()); + disassembly = format("debug_str \"{}\"", s); break; } @@ -464,7 +463,7 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { case 0xF1: case 0xF4: { uint8_t param = r.get_u8(); - disassembly = phosg::string_printf(".unknown 0x%02hhX, 0x%02hhX", + disassembly = format(".unknown 0x{:02X}, 0x{:02X}", opcode, param); break; } @@ -478,7 +477,7 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { case 0xE6: case 0xF9: { uint16_t param = r.get_u16b(); - disassembly = phosg::string_printf(".unknown 0x%02hhX, 0x%04hX", + disassembly = format(".unknown 0x{:02X}, 0x{:04X}", opcode, param); break; } @@ -488,7 +487,7 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { case 0xDD: case 0xEF: { uint32_t param = r.get_u24b(); - disassembly = phosg::string_printf(".unknown 0x%02hhX, 0x%06" PRIX32, + disassembly = format(".unknown 0x{:02X}, 0x{:06X}", opcode, param); break; } @@ -498,7 +497,7 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { case 0xB4: case 0xDF: { uint32_t param = r.get_u32b(); - disassembly = phosg::string_printf(".unknown 0x%02hhX, 0x%08" PRIX32, + disassembly = format(".unknown 0x{:02X}, 0x{:08X}", opcode, param); break; } @@ -507,26 +506,26 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { uint8_t param1 = r.get_u8(); if (param1 == 0x40) { uint16_t param2 = r.get_u16b(); - disassembly = phosg::string_printf(".unknown 0x%02hhX, 0x%02hhX, 0x%04hX", + disassembly = format(".unknown 0x{:02X}, 0x{:02X}, 0x{:04X}", opcode, param1, param2); } else if (param1 == 0x80) { uint32_t param2 = r.get_u32b(); - disassembly = phosg::string_printf(".unknown 0x%02hhX, 0x%02hhX, 0x%08" PRIX32, + disassembly = format(".unknown 0x{:02X}, 0x{:02X}, 0x{:08X}", opcode, param1, param2); } else { - disassembly = phosg::string_printf(".unknown 0x%02hhX, 0x%02hhX", + disassembly = format(".unknown 0x{:02X}, 0x{:02X}", opcode, param1); } break; } case 0xF0: { - disassembly = phosg::string_printf("wait %" PRIu64, read_variable_int(r)); + disassembly = format("wait {}", read_variable_int(r)); break; } default: - disassembly = phosg::string_printf(".unknown 0x%02hhX", opcode); + disassembly = format(".unknown 0x{:02X}", opcode); } if (disassembly.empty()) { @@ -537,7 +536,7 @@ void disassemble_bms(phosg::StringReader& r, int32_t default_bank = -1) { string data = r.pread(opcode_offset, opcode_size); string data_str; for (char ch : data) { - data_str += phosg::string_printf("%02X ", static_cast(ch)); + data_str += format("{:02X} ", static_cast(ch)); } data_str.resize(18, ' '); @@ -726,7 +725,7 @@ void disassemble_midi(phosg::StringReader& r) { printf(".meta 0x%hhX\n", type); } } else { - throw runtime_error(phosg::string_printf("invalid status byte: %02hhX", status)); + throw runtime_error(format("invalid status byte: {:02X}", status)); } } @@ -793,20 +792,24 @@ struct Channel { class Voice { public: Voice(size_t sample_rate, int8_t note, int8_t vel, bool decay_when_off, shared_ptr channel) + : Voice(sample_rate, note, vel, decay_when_off, 0.2f, channel) {} + Voice(size_t sample_rate, int8_t note, int8_t vel, bool decay_when_off, float decay_seconds, shared_ptr channel) : sample_rate(sample_rate), note(note), vel(vel), channel(channel), decay_when_off(decay_when_off), - note_off_decay_total(this->sample_rate / 5), + note_off_decay_total(static_cast(round( + static_cast(decay_seconds) * static_cast(this->sample_rate) + ))), note_off_decay_remaining(-1) {} virtual ~Voice() = default; virtual vector render(size_t count, float freq_mult, float volume_bias) = 0; void off() { - // TODO: for now we use a constant release time of 1/5 second; we probably - // should get this from the AAF somewhere but I don't know where + // TODO: for now we use a constant release time of 1/5 second except in SMS SONG resources; + // we probably should get this from the AAF somewhere but I don't know where this->note_off_decay_remaining = this->note_off_decay_total; } @@ -880,8 +883,8 @@ class SampleVoice : public Voice { public: SampleVoice(size_t sample_rate, shared_ptr env, shared_ptr> cache, uint16_t bank_id, uint16_t instrument_id, - int8_t note, int8_t vel, bool decay_when_off, shared_ptr channel) - : Voice(sample_rate, note, vel, decay_when_off, channel), + int8_t note, int8_t vel, bool decay_when_off, float decay_seconds, shared_ptr channel) + : Voice(sample_rate, note, vel, decay_when_off, decay_seconds, channel), instrument_bank(&env->instrument_banks.at(bank_id)), instrument(&this->instrument_bank->id_to_instrument.at(instrument_id)), key_region(&this->instrument->region_for_key(note)), @@ -895,9 +898,9 @@ class SampleVoice : public Voice { } if (this->vel_region->sound->num_channels != 1) { // TODO: this probably wouldn't be that hard to support - throw invalid_argument(phosg::string_printf( - "sampled sound is multi-channel: %s:%" PRIX32, - this->vel_region->sound->source_filename.c_str(), + throw invalid_argument(format( + "sampled sound is multi-channel: {}:{:X}", + this->vel_region->sound->source_filename, this->vel_region->sound->source_offset)); } } @@ -1085,6 +1088,7 @@ class Renderer { unordered_set solo_tracks; unordered_set disable_tracks; bool decay_when_off; + float decay_seconds; shared_ptr> cache; @@ -1097,7 +1101,7 @@ class Renderer { if (this->env) { try { SampleVoice* v = new SampleVoice(this->sample_rate, this->env, - this->cache, t->bank, t->instrument, key, vel, this->decay_when_off, c); + this->cache, t->bank, t->instrument, key, vel, this->decay_when_off, this->decay_seconds, c); t->voices[voice_id].reset(v); } catch (const out_of_range& e) { string key_str = phosg_audio::name_for_note(key); @@ -1141,6 +1145,7 @@ class Renderer { solo_tracks(solo_tracks), disable_tracks(disable_tracks), decay_when_off(decay_when_off), + decay_seconds(0.2f), cache(new SampleCache(resample_method)) {} virtual ~Renderer() = default; @@ -1226,8 +1231,8 @@ class Renderer { throw; } if (voice_samples.size() != step_samples.size()) { - throw logic_error(phosg::string_printf( - "voice produced incorrect sample count (returned %zu samples, expected %zu samples)", + throw logic_error(format( + "voice produced incorrect sample count (returned {} samples, expected {} samples)", voice_samples.size(), step_samples.size())); } if (!this->mute_tracks.count(t->id)) { @@ -1584,8 +1589,8 @@ class BMSRenderer : public Renderer { uint8_t track_id = t->r.get_u8(); uint32_t offset = t->r.get_u24b(); if (offset >= t->r.size()) { - throw invalid_argument(phosg::string_printf( - "cannot start track at pc=0x%" PRIX32 " (from pc=0x%zX)", + throw invalid_argument(format( + "cannot start track at pc=0x{:X} (from pc=0x{:X})", offset, t->r.where() - 5)); } @@ -1612,8 +1617,8 @@ class BMSRenderer : public Renderer { uint32_t offset = t->r.get_u24b(); if (offset >= t->r.size()) { - throw invalid_argument(phosg::string_printf( - "cannot jump to pc=0x%" PRIX32 " (from pc=0x%zX)", offset, + throw invalid_argument(format( + "cannot jump to pc=0x{:X} (from pc=0x{:X})", offset, t->r.where() - 5)); } @@ -1777,7 +1782,7 @@ class BMSRenderer : public Renderer { } default: - throw invalid_argument(phosg::string_printf("unknown opcode at offset 0x%zX: 0x%hhX", + throw invalid_argument(format("unknown opcode at offset 0x{:X}: 0x{:X}", t->r.where() - 1, opcode)); } } @@ -1802,6 +1807,7 @@ class MIDIRenderer : public Renderer { double freq_bias, double volume_bias, bool decay_when_off, + float decay_seconds, uint8_t percussion_instrument, bool allow_program_change) : Renderer( @@ -1823,6 +1829,7 @@ class MIDIRenderer : public Renderer { if (percussion_instrument) { this->channel_instrument[9] = percussion_instrument; } + this->decay_seconds = decay_seconds; phosg::StringReader r(this->midi_contents); @@ -2297,10 +2304,12 @@ int main(int argc, char** argv) { } else { // midi has some extra params; get them from the json if possible uint8_t percussion_instrument = 0; + float decay_seconds = 0.2f; bool allow_program_change = true; if (!env_json.is_null()) { percussion_instrument = env_json.get_int("percussion_instrument", 0); allow_program_change = env_json.get_bool("allow_program_change", true); + decay_seconds = env_json.get_float("note_decay", 12.0f) / 60.0f; tempo_bias *= env_json.get_float("tempo_bias", 1.0); } r.reset(new MIDIRenderer( @@ -2315,6 +2324,7 @@ int main(int argc, char** argv) { freq_bias, volume_bias, decay_when_off, + decay_seconds, percussion_instrument, allow_program_change)); }