Update YMFM to latest version as of 11/30/2024

This commit is contained in:
Jasmine Iwanek
2024-11-30 02:05:33 -05:00
parent e83a61d6a2
commit e2b86caf5d
8 changed files with 77 additions and 91 deletions

View File

@@ -42,12 +42,11 @@
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <array>
#include <memory>
#include <string>
#include <vector>
#define SNPRINTF_BUFFER_SIZE_CALC (256 - (end - &buffer[0]))
namespace ymfm
{
@@ -111,17 +110,6 @@ inline int32_t clamp(int32_t value, int32_t minval, int32_t maxval)
}
//-------------------------------------------------
// array_size - return the size of an array
//-------------------------------------------------
template<typename ArrayType, int ArraySize>
constexpr uint32_t array_size(ArrayType (&array)[ArraySize])
{
return ArraySize;
}
//-------------------------------------------------
// count_leading_zeros - return the number of
// leading zeros in a 32-bit value; CPU-optimized
@@ -256,7 +244,8 @@ inline int16_t roundtrip_fp(int32_t value)
// apply the shift back and forth to zero out bits that are lost
exponent -= 1;
return (value >> exponent) << exponent;
int32_t mask = (1 << exponent) - 1;
return value & ~mask;
}
@@ -352,7 +341,7 @@ public:
{
// create file
char name[20];
snprintf(name, sizeof(name), "wavlog-%02d.wav", m_index);
snprintf(&name[0], sizeof(name), "wavlog-%02d.wav", m_index);
FILE *out = fopen(name, "wb");
// make the wav file header
@@ -485,7 +474,7 @@ public:
class ymfm_engine_callbacks
{
public:
virtual ~ymfm_engine_callbacks() = default;
virtual ~ymfm_engine_callbacks() = default;
// timer callback; called by the interface when a timer fires
virtual void engine_timer_expired(uint32_t tnum) = 0;
@@ -509,6 +498,7 @@ class ymfm_interface
public:
virtual ~ymfm_interface() = default;
// the following functions must be implemented by any derived classes; the
// default implementations are sufficient for some minimal operation, but will
// likely need to be overridden to integrate with the outside world; they are

View File

@@ -267,7 +267,7 @@ public:
// assign operators
void assign(uint32_t index, fm_operator<RegisterType> *op)
{
assert(index < array_size(m_op));
assert(index < m_op.size());
m_op[index] = op;
if (op != nullptr)
op->set_choffs(m_choffs);
@@ -330,7 +330,7 @@ private:
uint32_t m_choffs; // channel offset in registers
int16_t m_feedback[2]; // feedback memory for operator 1
mutable int16_t m_feedback_in; // next input value for op 1 feedback (set in output)
fm_operator<RegisterType> *m_op[4]; // up to 4 operators
std::array<fm_operator<RegisterType> *, 4> m_op; // up to 4 operators
RegisterType &m_regs; // direct reference to registers
fm_engine_base<RegisterType> &m_owner; // reference to the owning engine
};

View File

@@ -839,12 +839,12 @@ void fm_channel<RegisterType>::save_restore(ymfm_saved_state &state)
template<class RegisterType>
void fm_channel<RegisterType>::keyonoff(uint32_t states, keyon_type type, uint32_t chnum)
{
for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++)
for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
if (m_op[opnum] != nullptr)
m_op[opnum]->keyonoff(bitfield(states, opnum), type);
if (debug::LOG_KEYON_EVENTS && ((debug::GLOBAL_FM_CHANNEL_MASK >> chnum) & 1) != 0)
for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++)
for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
if (m_op[opnum] != nullptr)
debug::log_keyon("%c%s\n", bitfield(states, opnum) ? '+' : '-', m_regs.log_keyon(m_choffs, m_op[opnum]->opoffs()).c_str());
}
@@ -860,7 +860,7 @@ bool fm_channel<RegisterType>::prepare()
uint32_t active_mask = 0;
// prepare all operators and determine if they are active
for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++)
for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
if (m_op[opnum] != nullptr)
if (m_op[opnum]->prepare())
active_mask |= 1 << opnum;
@@ -880,7 +880,7 @@ void fm_channel<RegisterType>::clock(uint32_t env_counter, int32_t lfo_raw_pm)
m_feedback[0] = m_feedback[1];
m_feedback[1] = m_feedback_in;
for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++)
for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
if (m_op[opnum] != nullptr)
m_op[opnum]->clock(env_counter, lfo_raw_pm);
@@ -888,7 +888,7 @@ void fm_channel<RegisterType>::clock(uint32_t env_counter, int32_t lfo_raw_pm)
useful temporary code for envelope debugging
if (m_choffs == 0x101)
{
for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++)
for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
{
auto &op = *m_op[((opnum & 1) << 1) | ((opnum >> 1) & 1)];
printf(" %c%03X%c%c ",
@@ -1473,14 +1473,11 @@ void fm_engine_base<RegisterType>::assign_operators()
template<class RegisterType>
void fm_engine_base<RegisterType>::update_timer(uint32_t tnum, uint32_t enable, int32_t delta_clocks)
{
uint32_t subtract = !!(tnum >> 15);
tnum &= 0x7fff;
// if the timer is live, but not currently enabled, set the timer
if (enable && !m_timer_running[tnum])
{
// period comes from the registers, and is different for each
uint32_t period = (tnum == 0) ? (1024 - subtract - m_regs.timer_a_value()) : 16 * (256 - subtract - m_regs.timer_b_value());
uint32_t period = (tnum == 0) ? (1024 - m_regs.timer_a_value()) : 16 * (256 - m_regs.timer_b_value());
// caller can also specify a delta to account for other effects
period += delta_clocks;
@@ -1507,6 +1504,8 @@ void fm_engine_base<RegisterType>::update_timer(uint32_t tnum, uint32_t enable,
template<class RegisterType>
void fm_engine_base<RegisterType>::engine_timer_expired(uint32_t tnum)
{
assert(tnum == 0 || tnum == 1);
// update status
if (tnum == 0 && m_regs.enable_timer_a())
set_reset_status(STATUS_TIMERA, 0);
@@ -1522,11 +1521,8 @@ void fm_engine_base<RegisterType>::engine_timer_expired(uint32_t tnum)
m_modified_channels |= 1 << chnum;
}
// Make sure the array does not go out of bounds to keep gcc happy
if ((tnum < 2) || (sizeof(m_timer_running) > (2 * sizeof(uint8_t)))) {
// reset
m_timer_running[tnum] = false;
}
// reset
m_timer_running[tnum] = false;
update_timer(tnum, 1, 0);
}

View File

@@ -386,9 +386,9 @@ std::string opl_registers_base<Revision>::log_keyon(uint32_t choffs, uint32_t op
uint32_t opnum = (opoffs & 31) - 2 * ((opoffs & 31) / 8) + 18 * bitfield(opoffs, 8);
char buffer[256];
char *end = &buffer[0];
int end = 0;
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%2u.%02u freq=%04X fb=%u alg=%X mul=%X tl=%02X ksr=%u ns=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u",
end += snprintf(&buffer[end], sizeof(buffer) - end, "%2u.%02u freq=%04X fb=%u alg=%X mul=%X tl=%02X ksr=%u ns=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u",
chnum, opnum,
ch_block_freq(choffs),
ch_feedback(choffs),
@@ -405,25 +405,25 @@ std::string opl_registers_base<Revision>::log_keyon(uint32_t choffs, uint32_t op
op_eg_sustain(opoffs));
if (OUTPUTS > 1)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " out=%c%c%c%c",
end += snprintf(&buffer[end], sizeof(buffer) - end, " out=%c%c%c%c",
ch_output_0(choffs) ? 'L' : '-',
ch_output_1(choffs) ? 'R' : '-',
ch_output_2(choffs) ? '0' : '-',
ch_output_3(choffs) ? '1' : '-');
if (op_lfo_am_enable(opoffs) != 0)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u", lfo_am_depth());
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", lfo_am_depth());
if (op_lfo_pm_enable(opoffs) != 0)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u", lfo_pm_depth());
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", lfo_pm_depth());
if (waveform_enable() && op_waveform(opoffs) != 0)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " wf=%u", op_waveform(opoffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=%u", op_waveform(opoffs));
if (is_rhythm(choffs))
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " rhy=1");
end += snprintf(&buffer[end], sizeof(buffer) - end, " rhy=1");
if (DYNAMIC_OPS)
{
operator_mapping map;
operator_map(map);
if (bitfield(map.chan[chnum], 16, 8) != 0xff)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " 4op");
end += snprintf(&buffer[end], sizeof(buffer) - end, " 4op");
}
return buffer;
@@ -685,9 +685,9 @@ std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
uint32_t opnum = opoffs;
char buffer[256];
char *end = &buffer[0];
int end = 0;
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u freq=%04X inst=%X fb=%u mul=%X",
end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X inst=%X fb=%u mul=%X",
chnum, opnum,
ch_block_freq(choffs),
ch_instrument(choffs),
@@ -695,11 +695,11 @@ std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
op_multiple(opoffs));
if (bitfield(opoffs, 0) == 1 || (is_rhythm(choffs) && choffs >= 6))
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " vol=%X", op_volume(opoffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " vol=%X", op_volume(opoffs));
else
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " tl=%02X", ch_total_level(choffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " tl=%02X", ch_total_level(choffs));
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " ksr=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u/%u",
end += snprintf(&buffer[end], sizeof(buffer) - end, " ksr=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u/%u",
op_ksr(opoffs),
op_ksl(opoffs),
op_attack_rate(opoffs),
@@ -710,13 +710,13 @@ std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
ch_sustain(choffs));
if (op_lfo_am_enable(opoffs))
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=1");
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=1");
if (op_lfo_pm_enable(opoffs))
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=1");
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=1");
if (op_waveform(opoffs) != 0)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " wf=1");
end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=1");
if (is_rhythm(choffs))
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " rhy=1");
end += snprintf(&buffer[end], sizeof(buffer) - end, " rhy=1");
return buffer;
}

View File

@@ -60,17 +60,17 @@ opm_registers::opm_registers() :
{
// waveform 0 is a sawtooth
uint8_t am = index ^ 0xff;
int8_t pm = int8_t(index);
uint8_t pm = index;
m_lfo_waveform[0][index] = am | (pm << 8);
// waveform 1 is a square wave
am = bitfield(index, 7) ? 0 : 0xff;
pm = int8_t(am ^ 0x80);
pm = am ^ 0x80;
m_lfo_waveform[1][index] = am | (pm << 8);
// waveform 2 is a triangle wave
am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1);
pm = int8_t(bitfield(index, 6) ? am : ~am);
pm = bitfield(index, 6) ? am : ~am;
m_lfo_waveform[2][index] = am | (pm << 8);
// waveform 3 is noise; it is filled in dynamically
@@ -330,7 +330,7 @@ uint32_t opm_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opd
if (pm_sensitivity < 6)
delta += lfo_raw_pm >> (6 - pm_sensitivity);
else
delta += lfo_raw_pm << (pm_sensitivity - 5);
delta += uint32_t(lfo_raw_pm) << (pm_sensitivity - 5);
}
// apply delta and convert to a frequency number
@@ -354,9 +354,9 @@ std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
uint32_t opnum = opoffs;
char buffer[256];
char *end = &buffer[0];
int end = 0;
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
chnum, opnum,
ch_block_freq(choffs),
op_detune2(opoffs),
@@ -376,14 +376,14 @@ std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
if (am)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0);
if (pm)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
if (am || pm)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
if (noise_enable() && opoffs == 31)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " noise=1");
end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1");
return buffer;
}

View File

@@ -409,9 +409,9 @@ std::string opn_registers_base<IsOpnA>::log_keyon(uint32_t choffs, uint32_t opof
}
char buffer[256];
char *end = &buffer[0];
int end = 0;
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u freq=%04X dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X",
end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X",
chnum, opnum,
block_freq,
op_detune(opoffs),
@@ -427,21 +427,21 @@ std::string opn_registers_base<IsOpnA>::log_keyon(uint32_t choffs, uint32_t opof
op_sustain_level(opoffs));
if (OUTPUTS > 1)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " out=%c%c",
end += snprintf(&buffer[end], sizeof(buffer) - end, " out=%c%c",
ch_output_0(choffs) ? 'L' : '-',
ch_output_1(choffs) ? 'R' : '-');
if (op_ssg_eg_enable(opoffs))
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " ssg=%X", op_ssg_eg_mode(opoffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " ssg=%X", op_ssg_eg_mode(opoffs));
bool am = (op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0);
if (am)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u", ch_lfo_am_sens(choffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", ch_lfo_am_sens(choffs));
bool pm = (ch_lfo_pm_sens(choffs) != 0);
if (pm)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u", ch_lfo_pm_sens(choffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", ch_lfo_pm_sens(choffs));
if (am || pm)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo=%02X", lfo_rate());
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X", lfo_rate());
if (multi_freq() && choffs == 2)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " multi=1");
end += snprintf(&buffer[end], sizeof(buffer) - end, " multi=1");
return buffer;
}

View File

@@ -339,9 +339,9 @@ std::string opq_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
uint32_t opnum = opoffs;
char buffer[256];
char *end = &buffer[0];
int end = 0;
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u freq=%04X dt=%+2d fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt=%+2d fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
chnum, opnum,
(opoffs & 1) ? ch_block_freq_24(choffs) : ch_block_freq_13(choffs),
int32_t(op_detune(opoffs)) - 0x20,
@@ -360,14 +360,14 @@ std::string opq_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
bool am = (lfo_enable() && op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0);
if (am)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u", ch_lfo_am_sens(choffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", ch_lfo_am_sens(choffs));
bool pm = (lfo_enable() && ch_lfo_pm_sens(choffs) != 0);
if (pm)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u", ch_lfo_pm_sens(choffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", ch_lfo_pm_sens(choffs));
if (am || pm)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo=%02X", lfo_rate());
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X", lfo_rate());
if (ch_reverb(choffs))
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " reverb");
end += snprintf(&buffer[end], sizeof(buffer) - end, " reverb");
return buffer;
}

View File

@@ -129,17 +129,17 @@ opz_registers::opz_registers() :
{
// waveform 0 is a sawtooth
uint8_t am = index ^ 0xff;
int8_t pm = int8_t(index);
uint8_t pm = index;
m_lfo_waveform[0][index] = am | (pm << 8);
// waveform 1 is a square wave
am = bitfield(index, 7) ? 0 : 0xff;
pm = int8_t(am ^ 0x80);
pm = am ^ 0x80;
m_lfo_waveform[1][index] = am | (pm << 8);
// waveform 2 is a triangle wave
am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1);
pm = int8_t(bitfield(index, 6) ? am : ~am);
pm = bitfield(index, 6) ? am : ~am;
m_lfo_waveform[2][index] = am | (pm << 8);
// waveform 3 is noise; it is filled in dynamically
@@ -555,16 +555,16 @@ std::string opz_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
uint32_t opnum = opoffs;
char buffer[256];
char *end = &buffer[0];
int end = 0;
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u", chnum, opnum);
end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u", chnum, opnum);
if (op_fix_mode(opoffs))
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " fixfreq=%X fine=%X shift=%X", op_fix_frequency(opoffs), op_fine(opoffs), op_fix_range(opoffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " fixfreq=%X fine=%X shift=%X", op_fix_frequency(opoffs), op_fine(opoffs), op_fix_range(opoffs));
else
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " freq=%04X dt2=%u fine=%X", ch_block_freq(choffs), op_detune2(opoffs), op_fine(opoffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " freq=%04X dt2=%u fine=%X", ch_block_freq(choffs), op_detune2(opoffs), op_fine(opoffs));
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
end += snprintf(&buffer[end], sizeof(buffer) - end, " dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
op_detune(opoffs),
ch_feedback(choffs),
ch_algorithm(choffs),
@@ -580,32 +580,32 @@ std::string opz_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
ch_output_1(choffs) ? 'R' : '-');
if (op_eg_shift(opoffs) != 0)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " egshift=%u", op_eg_shift(opoffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " egshift=%u", op_eg_shift(opoffs));
bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
if (am)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0);
if (pm)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
if (am || pm)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
bool am2 = (lfo2_am_depth() != 0 && ch_lfo2_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
if (am2)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am2=%u/%02X", ch_lfo2_am_sens(choffs), lfo2_am_depth());
end += snprintf(&buffer[end], sizeof(buffer) - end, " am2=%u/%02X", ch_lfo2_am_sens(choffs), lfo2_am_depth());
bool pm2 = (lfo2_pm_depth() != 0 && ch_lfo2_pm_sens(choffs) != 0);
if (pm2)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm2=%u/%02X", ch_lfo2_pm_sens(choffs), lfo2_pm_depth());
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm2=%u/%02X", ch_lfo2_pm_sens(choffs), lfo2_pm_depth());
if (am2 || pm2)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo2=%02X/%c", lfo2_rate(), "WQTN"[lfo2_waveform()]);
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo2=%02X/%c", lfo2_rate(), "WQTN"[lfo2_waveform()]);
if (op_reverb_rate(opoffs) != 0)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " rev=%u", op_reverb_rate(opoffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " rev=%u", op_reverb_rate(opoffs));
if (op_waveform(opoffs) != 0)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " wf=%u", op_waveform(opoffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=%u", op_waveform(opoffs));
if (noise_enable() && opoffs == 31)
end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " noise=1");
end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1");
return buffer;
}