Bake window: derived note lengths carry exact durations, not ladder rungs — a long take is no longer cut at 384 beats
Hold keeps its picker. Also: one home for the %-fold, duration-ordered Hold travel, and a corrupt tail degrades to absent rather than fabricating one.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "core/instrument/engine/loop/loop_span.h" // resolveLoop (the one sustain-loop fold)
|
||||
#include "core/instrument/engine/voice.h" // kDeclickFrames (the terminal ramp length)
|
||||
#include "core/instrument/map/trigger_seam.h" // the one Trigger span formula + %-fold
|
||||
#include "core/instrument/map/trigger_seam.h" // triggerPlayLength (the one span formula)
|
||||
|
||||
namespace reasampler::instrument::bake {
|
||||
|
||||
@@ -71,9 +71,8 @@ bool bakeWindowNeedsHold(const SampleData& dialed) {
|
||||
}
|
||||
|
||||
NoteProgram defaultBakeProgram(const SampleData& dialed, int renderSampleRate,
|
||||
note::Tempo tempo, note::Division hold,
|
||||
note::Velocity velocity) {
|
||||
NoteProgram p; // 1/4 straight, capture opening at note-on
|
||||
note::Division hold, note::Velocity velocity) {
|
||||
NoteProgram p; // a quarter note, capture opening at note-on
|
||||
p.velocity = velocity;
|
||||
if (renderSampleRate <= 0) return p;
|
||||
const double rate = static_cast<double>(renderSampleRate);
|
||||
@@ -86,25 +85,24 @@ NoteProgram defaultBakeProgram(const SampleData& dialed, int renderSampleRate,
|
||||
double endOffsetSeconds = 0.0;
|
||||
if (dialed.play.playMode == PlayMode::Trigger) {
|
||||
// Trigger ignores note-off entirely: the sound ends when the read head reaches the
|
||||
// play span's end, which has nothing to do with the note's length — so the end
|
||||
// offset is whatever is left after the note, positive or negative.
|
||||
const std::int64_t span = map::triggerPlayLength(
|
||||
map::effectiveLengthFraction(dialed.play), frameCount, start);
|
||||
endOffsetSeconds = static_cast<double>(span) / rate * stretch -
|
||||
tempo.beatsToSeconds(note::divisionBeats(p.length));
|
||||
// play span's end. The note is that span, so the window closes on the sound rather
|
||||
// than on a length the voice never consulted.
|
||||
const std::int64_t span =
|
||||
map::triggerPlayLength(effectiveLengthFraction(dialed.play), frameCount, start);
|
||||
p.length = note::lengthOfSeconds(static_cast<double>(span) / rate * stretch);
|
||||
} else if (bakeWindowNeedsHold(dialed)) {
|
||||
// The loop cycles for as long as the note is held, so the hold IS the length, and the
|
||||
// release is the one stage that runs after note-off.
|
||||
p.length = hold;
|
||||
p.length = note::lengthOfDivision(hold);
|
||||
endOffsetSeconds = releaseSeconds;
|
||||
} else {
|
||||
// Gate with no loop: the read head runs off the source and frees the voice whether or
|
||||
// not the gate is still down, so the maximal sound is the whole post-start span held.
|
||||
// Rounding the note UP to a programmable length that outlasts it costs nothing — the
|
||||
// voice is already gone by then — while a shorter note releases mid-attack.
|
||||
// Exact, not a ladder rung: a source longer than the ladder's top rung would otherwise
|
||||
// take that rung and release mid-sound, and rounding up to one costs trailing silence
|
||||
// on every other source.
|
||||
const std::int64_t postStart = (std::max)(std::int64_t{0}, frameCount - start);
|
||||
const double exhaustSeconds = static_cast<double>(postStart) / rate * stretch;
|
||||
p.length = note::shortestDivisionAtLeast(tempo.secondsToBeats(exhaustSeconds));
|
||||
p.length = note::lengthOfSeconds(static_cast<double>(postStart) / rate * stretch);
|
||||
endOffsetSeconds = releaseSeconds;
|
||||
}
|
||||
// The voice rings its last output out over kDeclickFrames instead of hard-cutting it, and
|
||||
|
||||
Reference in New Issue
Block a user