|
|
|
|
@ -35,7 +35,8 @@ void CallDelay::fail(const char* reason) {
|
|
|
|
|
void CallDelay::reset() { |
|
|
|
|
st.clear(); st.setTempo(1); |
|
|
|
|
spanHead = spanCount = trim = 0; |
|
|
|
|
written = cursor = generated = consumed = stBase = stFed = entry = queuedInput = elapsed = 0; |
|
|
|
|
written = cursor = generated = consumed = stBase = stFed = entry = queuedInput = elapsed = deviceSamples = 0; |
|
|
|
|
phaseGoal = 0; |
|
|
|
|
playedSource = cutPhase = 0; tempo = 1; |
|
|
|
|
for (auto& b : buckets) b = {}; |
|
|
|
|
firstSpan = lastCopy = false; mode = Mode::Direct; |
|
|
|
|
@ -44,9 +45,12 @@ void CallDelay::reset() {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
CallDelay::Stats CallDelay::stats() const { |
|
|
|
|
return {mode, tempo, std::max(0.0, written - playedSource), cuts, entered, exited}; |
|
|
|
|
unsigned phase = (unsigned)((cursor % Frame + Frame - deviceSamples % Frame) % Frame); |
|
|
|
|
return {mode, tempo, std::max(0.0, written - playedSource), cuts, entered, exited, phase}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void CallDelay::silence(unsigned count) { deviceSamples += count; } |
|
|
|
|
|
|
|
|
|
/* Сохраняем контекст и ещё не выданный исходный хвост даже после передачи в DSP. */ |
|
|
|
|
bool CallDelay::fill() { |
|
|
|
|
uint64_t keep = cursor > Context ? cursor - Context : 0; |
|
|
|
|
@ -119,6 +123,8 @@ void CallDelay::endStretch() {
|
|
|
|
|
if (st.numSamples() || spanCount || (!firstSpan && !lastCopy)) { fail("unsafe stretch exit"); return; } |
|
|
|
|
st.clear(); mode = Mode::Direct; cutPhase = 0; |
|
|
|
|
tempo = std::min(tempo, 1.02); ++exited; |
|
|
|
|
int64_t offset = (int64_t)cursor - (int64_t)deviceSamples; |
|
|
|
|
phaseGoal = (int64_t)std::ceil((double)offset / Frame) * Frame; |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call_delay: stretch->direct source=%llu recovered=%.2fms tempo=%.4f", |
|
|
|
|
(unsigned long long)cursor, (written - cursor) / 48.0, tempo); |
|
|
|
|
} |
|
|
|
|
@ -152,25 +158,30 @@ unsigned CallDelay::direct(int16_t* out, unsigned count, unsigned reserve, unsig
|
|
|
|
|
if (cutPhase >= 1) { |
|
|
|
|
cutPhase -= std::floor(cutPhase); |
|
|
|
|
uint64_t available = written - cursor + queuedInput; |
|
|
|
|
if (available > (uint64_t)reserve + remaining - done && (written - cursor > 1 || fill())) { |
|
|
|
|
int64_t offset = (int64_t)cursor - (int64_t)deviceSamples; |
|
|
|
|
if (offset < phaseGoal && available > (uint64_t)reserve + remaining - done |
|
|
|
|
&& (written - cursor > 1 || fill())) { |
|
|
|
|
finish(); ++cursor; ++cuts; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (!run) first = cursor; |
|
|
|
|
out[done++] = to_pcm(raw[cursor++ % Capacity]); ++run; |
|
|
|
|
cutPhase += tempo - 1; |
|
|
|
|
out[done++] = to_pcm(raw[cursor++ % Capacity]); ++run; ++deviceSamples; |
|
|
|
|
if ((int64_t)cursor - (int64_t)deviceSamples < phaseGoal) cutPhase += std::min(.02, tempo - 1); |
|
|
|
|
else cutPhase = 0; |
|
|
|
|
} |
|
|
|
|
finish(); |
|
|
|
|
return done; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Нижняя огибающая за 150..200мс убирает пилу пакетизации без зависимости от размера pull. */ |
|
|
|
|
double CallDelay::lowDepth(double depth) { |
|
|
|
|
double CallDelay::lowDepth(double depth, double& inventory) { |
|
|
|
|
uint64_t time = elapsed / (Rate / 20); |
|
|
|
|
auto& b = buckets[time % 4]; |
|
|
|
|
if (b.time != time) b = {time, depth}; |
|
|
|
|
else b.low = std::min(b.low, depth); |
|
|
|
|
for (const auto& item : buckets) if (item.time <= time && time - item.time < 4) depth = std::min(depth, item.low); |
|
|
|
|
if (b.time != time) b = {time, depth, inventory}; |
|
|
|
|
else { b.low = std::min(b.low, depth); b.inventory = std::min(b.inventory, inventory); } |
|
|
|
|
for (const auto& item : buckets) if (item.time <= time && time - item.time < 4) { |
|
|
|
|
depth = std::min(depth, item.low); inventory = std::min(inventory, item.inventory); |
|
|
|
|
} |
|
|
|
|
return depth; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -179,10 +190,16 @@ unsigned CallDelay::pull(int16_t* out, unsigned count, uint64_t queued, unsigned
|
|
|
|
|
if (!out || !count) { fail("invalid pull"); return 0; } |
|
|
|
|
queuedInput = queued; |
|
|
|
|
double depth = stats().pending + queued; |
|
|
|
|
double excess = std::max(0.0, lowDepth(std::max(0.0, depth - count)) - reserve); |
|
|
|
|
double inventory = (double)written + queued - deviceSamples - count; |
|
|
|
|
double excess = std::max(0.0, lowDepth(std::max(0.0, depth - count), inventory) - reserve); |
|
|
|
|
bool stretch = excess >= 120 * 48; |
|
|
|
|
if (mode == Mode::Stretch && (excess <= 40 * 48 || depth < count + 60 * 48)) mode = Mode::Drain; |
|
|
|
|
double target = mode == Mode::Direct && !stretch ? 1 + std::min(.02, excess / (2 * Rate)) |
|
|
|
|
int64_t offset = (int64_t)cursor - (int64_t)deviceSamples; |
|
|
|
|
// Цель кратна кадру: прекращаем ускорение точно на границе, а не при произвольной глубине.
|
|
|
|
|
phaseGoal = (int64_t)std::max(std::ceil((double)offset / Frame), std::floor((inventory - reserve) / Frame)) * Frame; |
|
|
|
|
double correction = (double)(phaseGoal - offset); |
|
|
|
|
double directTempo = correction > 0 ? 1 + std::min(.02, std::max(.001, correction / (2 * Rate))) : 1; |
|
|
|
|
double target = mode == Mode::Direct && !stretch ? directTempo |
|
|
|
|
: 1 + .6 * std::max(0.0, std::min(1.0, (excess / 48 - 15) / 925)); |
|
|
|
|
double step = .6 * count / (Rate * .5); |
|
|
|
|
tempo += std::max(-step, std::min(step, target - tempo)); |
|
|
|
|
@ -194,7 +211,10 @@ unsigned CallDelay::pull(int16_t* out, unsigned count, uint64_t queued, unsigned
|
|
|
|
|
// Начало звонка сначала выдаёт контекст: ни один начальный сэмпл не теряется.
|
|
|
|
|
if (stretch && cursor >= Context) { beginStretch(); continue; } |
|
|
|
|
unsigned take = std::min(count - done, Frame); |
|
|
|
|
if (stretch) take = (unsigned)std::min<uint64_t>(take, Context - cursor); |
|
|
|
|
if (stretch) { |
|
|
|
|
take = (unsigned)std::min<uint64_t>(take, Context - cursor); |
|
|
|
|
phaseGoal = (int64_t)cursor - (int64_t)deviceSamples; |
|
|
|
|
} |
|
|
|
|
unsigned n = direct(out + done, take, reserve, count - done); |
|
|
|
|
done += n; |
|
|
|
|
if (n < take) break; |
|
|
|
|
@ -204,7 +224,7 @@ unsigned CallDelay::pull(int16_t* out, unsigned count, uint64_t queued, unsigned
|
|
|
|
|
float pcm[Frame]; |
|
|
|
|
unsigned n = st.receiveSamples(pcm, std::min(count - done, Frame)); |
|
|
|
|
for (unsigned i = 0; i < n; ++i) out[done + i] = to_pcm(pcm[i]); |
|
|
|
|
consume(n); done += n; |
|
|
|
|
consume(n); done += n; deviceSamples += n; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
elapsed += count; |
|
|
|
|
|