Bake window derives itself: %-knob fold, declick pad, Gate held to exhaustion, preview velocity; Hold is the one knob a loop needs
This commit is contained in:
@@ -20,14 +20,32 @@ namespace reasampler::instrument::bake {
|
||||
// long bake. ~5.5 minutes at 48 kHz, past any musical programmed note.
|
||||
inline constexpr std::int64_t kMaxBakeFrames = 16'000'000;
|
||||
|
||||
// The program a bake uses until the capture-signal popup ships: one quarter note at the
|
||||
// default velocity, opening at note-on, with the END offset derived from `dialed` — Gate's
|
||||
// release, or Trigger's play span, at `renderSampleRate` (the rate the bake will render at,
|
||||
// which is what the engine's frame counts are actually consumed against). Derived rather
|
||||
// than constant because the release knob alone spans two seconds, so any fixed tail cuts a
|
||||
// long decay mid-flight.
|
||||
// Whether the window needs a user-supplied hold. A Gate voice over an ACTIVE sustain loop
|
||||
// sounds for as long as it is held, by definition — there is no intrinsic end to derive, and
|
||||
// this is the ONLY case in which there isn't. Answered by the engine's own loop fold, so the
|
||||
// control that collects the hold cannot appear for a loop the voice would refuse.
|
||||
bool bakeWindowNeedsHold(PlayMode mode, const SampleLoop& loop, std::int64_t crossfadeFrames,
|
||||
std::int64_t frameCount);
|
||||
bool bakeWindowNeedsHold(const SampleData& dialed);
|
||||
|
||||
// The bake's programmed note, DERIVED from the dialed sound at `renderSampleRate` (the rate
|
||||
// the bake renders at, which is what the engine's frame counts are consumed against):
|
||||
//
|
||||
// Trigger — note length is nominal (note-off is ignored); the end offset carries the
|
||||
// whole play span, stretched by the deepest downward Varispeed offset.
|
||||
// Gate, loop — `hold` is the note length; the end offset is the release.
|
||||
// Gate, no loop— the read head runs off the source and frees the voice whatever the gate is
|
||||
// doing, so the note is rounded UP to the shortest programmable length that
|
||||
// outlasts the source. Holding longer than that sounds identical, which is
|
||||
// what makes the overshoot trailing silence rather than a different sound.
|
||||
//
|
||||
// Every case is padded by the voice's terminal declick ramp (kDeclickFrames): trailing
|
||||
// silence is free, and closing the window on the frame the ramp starts is a hard cut.
|
||||
// `hold` is read only in the Gate-with-loop case; `velocity` is the velocity the note fires
|
||||
// at, and it feeds the Varispeed stretch as well as the render.
|
||||
note::NoteProgram defaultBakeProgram(const SampleData& dialed, int renderSampleRate,
|
||||
note::Tempo tempo);
|
||||
note::Tempo tempo, note::Division hold,
|
||||
note::Velocity velocity);
|
||||
|
||||
// The render window in frames. TWO domains meet here: `totalFrames` is the captured FILE's
|
||||
// length, everything else counts RENDER frames from whichever comes first, note-on or the
|
||||
@@ -49,10 +67,23 @@ struct BakePlan {
|
||||
std::int64_t renderFrames() const { return leadInFrames + totalFrames; }
|
||||
};
|
||||
|
||||
// nullopt for a collapsed window, a non-positive rate, a window that rounds to no frames,
|
||||
// or one past kMaxBakeFrames — a buffer that is degenerate or unholdable is refused rather
|
||||
// than rendered. `rootNote` and the resolved velocity are clamped into MIDI range.
|
||||
std::optional<BakePlan> planBake(const note::ResolvedNote& resolved, int sampleRate,
|
||||
int rootNote);
|
||||
// Why a window was refused. The two are different user problems and read as different
|
||||
// sentences: an empty window is a programming mistake, a window past the ceiling is a legal
|
||||
// dialed sound that simply cannot be held in one pass.
|
||||
enum class BakeRefusal : std::uint8_t {
|
||||
None,
|
||||
EmptyWindow, // collapsed, a non-positive rate, or a window that rounds to no frames
|
||||
PastFrameCeiling, // representable but longer than kMaxBakeFrames
|
||||
};
|
||||
|
||||
// The one `ResolvedNote` + rate -> frames resolution. A degenerate or unholdable window is
|
||||
// refused rather than rendered; `refusal` is None iff `plan` holds one. `rootNote` and the
|
||||
// resolved velocity are clamped into MIDI range.
|
||||
struct PlannedBake {
|
||||
std::optional<BakePlan> plan;
|
||||
BakeRefusal refusal = BakeRefusal::None;
|
||||
};
|
||||
|
||||
PlannedBake planBake(const note::ResolvedNote& resolved, int sampleRate, int rootNote);
|
||||
|
||||
} // namespace reasampler::instrument::bake
|
||||
|
||||
Reference in New Issue
Block a user