Phase S voices: user-set polyphony, mono stack (retrig|legato), isolated preview card, idle-drain retirement; unity bypass preview-only (v7)
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio> // snprintf (Phase S voice-count readout)
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -63,6 +64,7 @@ constexpr UINT kSyncTimerIntervalMs = 500;
|
||||
constexpr int kTitleHeight = 26;
|
||||
constexpr int kHeroWaveformHeight = 150; // the enlarged Sample-face hero (was a 72px strip)
|
||||
constexpr int kClusterHeight = 52; // root strip + preview + channel toggle
|
||||
constexpr int kVoiceDeckHeight = 26; // Phase S provisional voice deck band
|
||||
constexpr int kStripBandHeight = 40; // the keyboard-strip band height (root strip + zone strip)
|
||||
constexpr int kNavButtonWidth = 62; // Browse / Zone / Back title-band buttons
|
||||
|
||||
@@ -161,6 +163,9 @@ void ReaSamplerEditor::refreshFromBank() {
|
||||
selectedId_ = processor_->selectedSampleId();
|
||||
map_ = processor_->performanceMap();
|
||||
channelMode_ = processor_->channelMode();
|
||||
voiceCount_ = processor_->voiceCount(); // Phase S voice-deck snapshot
|
||||
voiceMode_ = processor_->voiceMode();
|
||||
monoTrigger_ = processor_->monoTrigger();
|
||||
if (selectedZone_ >= static_cast<int>(map_.zones.size())) selectedZone_ = -1;
|
||||
// Drop a filter that names a bank no longer present.
|
||||
if (!activeFilterBankId_.empty()) {
|
||||
@@ -728,6 +733,7 @@ struct SampleBands {
|
||||
Rect hero; // the hero waveform + S-VIEW-3 envelope overlay
|
||||
Rect velCurve; // the S-VIEW-10 velocity-curve editor box (empty when suppressed)
|
||||
Rect cluster; // root strip + preview-trigger + velocity knob + channel toggle
|
||||
Rect voice; // Phase S voice deck (PROVISIONAL): voice count + Poly/Mono + Retrig/Legato
|
||||
Rect control; // the param control strip (Mode / Pitch / AHDSR|Trigger / AD pitch / keyTrack)
|
||||
};
|
||||
SampleBands computeSampleBands(int w, int h) {
|
||||
@@ -759,6 +765,12 @@ SampleBands computeSampleBands(int w, int h) {
|
||||
const int clusterH = (std::min)(kClusterHeight, (std::max)(0, h - y));
|
||||
b.cluster = Rect{0, y, w, y + clusterH};
|
||||
y += clusterH;
|
||||
// Phase S voice deck: a narrow PROVISIONAL band between the cluster and the param strip
|
||||
// (voice count stepper + Poly/Mono + Retrig/Legato). The Wave B recompose owns the final
|
||||
// placement; carving a distinct band keeps the wiring self-contained and easy to relocate.
|
||||
const int voiceH = (std::min)(kVoiceDeckHeight, (std::max)(0, h - y));
|
||||
b.voice = Rect{0, y, w, y + voiceH};
|
||||
y += voiceH;
|
||||
b.control = Rect{kPad, y, w - kPad, h};
|
||||
return b;
|
||||
}
|
||||
@@ -868,6 +880,43 @@ ChannelToggleRects channelToggleRects(const Rect& area) {
|
||||
return {mono, stereo};
|
||||
}
|
||||
|
||||
// Phase S voice deck (PROVISIONAL — the Wave B recompose owns the final placement): one row of
|
||||
// per-instance voice-system controls inside the `voice` band. Left to right: a "Voices" label,
|
||||
// a [-] step-down button, the count readout, a [+] step-up button, then a two-segment
|
||||
// [Poly|Mono] toggle, then a two-segment [Retrig|Legato] toggle (live only in Mono). Both draw
|
||||
// + hit-test derive from this ONE formula so they never drift (the channelToggleRects pattern).
|
||||
constexpr int kVoiceSegW = 52;
|
||||
constexpr int kVoiceSegH = 18;
|
||||
constexpr int kVoiceStepW = 18; // the [-] / [+] stepper buttons
|
||||
constexpr int kVoiceCountW = 30; // the numeric readout between them
|
||||
constexpr int kVoiceLabelW = 44; // the "Voices" caption
|
||||
constexpr int kVoiceGap = 16; // gap between the stepper / toggle groups
|
||||
struct VoiceDeckRects {
|
||||
Rect label; // "Voices" caption (decorative)
|
||||
Rect minus; // step the count down
|
||||
Rect count; // the numeric readout (decorative)
|
||||
Rect plus; // step the count up
|
||||
Rect poly; // VoiceMode::Poly segment
|
||||
Rect mono; // VoiceMode::Mono segment
|
||||
Rect retrig; // MonoTrigger::Retrigger segment
|
||||
Rect legato; // MonoTrigger::Legato segment
|
||||
};
|
||||
VoiceDeckRects voiceDeckRects(const Rect& band) {
|
||||
const int top = band.top + (band.height() - kVoiceSegH) / 2;
|
||||
const int bot = top + kVoiceSegH;
|
||||
int x = band.left + kPad;
|
||||
VoiceDeckRects r;
|
||||
r.label = Rect{x, top, x + kVoiceLabelW, bot}; x = r.label.right;
|
||||
r.minus = Rect{x, top, x + kVoiceStepW, bot}; x = r.minus.right + 2;
|
||||
r.count = Rect{x, top, x + kVoiceCountW, bot}; x = r.count.right + 2;
|
||||
r.plus = Rect{x, top, x + kVoiceStepW, bot}; x = r.plus.right + kVoiceGap;
|
||||
r.poly = Rect{x, top, x + kVoiceSegW, bot}; x = r.poly.right;
|
||||
r.mono = Rect{x, top, x + kVoiceSegW, bot}; x = r.mono.right + kVoiceGap;
|
||||
r.retrig = Rect{x, top, x + kVoiceSegW, bot}; x = r.retrig.right;
|
||||
r.legato = Rect{x, top, x + kVoiceSegW, bot};
|
||||
return r;
|
||||
}
|
||||
|
||||
// Draw the pastel spectral keyboard-strip background (Phase L, L3) — the signature surface.
|
||||
// Fills each MIDI key column with its spectral hue (spectralColor over note/127), then draws
|
||||
// faint per-octave hairline ticks for orientation. Shared by the setup face + the Zones strip
|
||||
@@ -1115,6 +1164,55 @@ void ReaSamplerEditor::paintSample(LICE_IBitmap* bmp, int w, int h) {
|
||||
kitTextCentered(bmp, chan.stereo, "Stereo", Font::Label, isStereo ? Role::BgBase : Role::TextPrimary);
|
||||
}
|
||||
|
||||
// --- Phase S voice deck (PROVISIONAL placement; Wave B owns the final composition) -----
|
||||
if (bands.voice.height() > 0) {
|
||||
fillSurface(bmp, toKitBox(bands.voice), Role::BgPanel, InteractionState::Rest);
|
||||
const VoiceDeckRects vd = voiceDeckRects(bands.voice);
|
||||
kitTextCentered(bmp, vd.label, "Voices", Font::Micro, Role::TextDim);
|
||||
// Count stepper: [-] N [+]. The steppers grey out at the range edges (the shared
|
||||
// pure-core kMin/kMaxVoiceCount — one spelling with the engine + the state bytes).
|
||||
const bool canDown = voiceCount_ > kMinVoiceCount;
|
||||
const bool canUp = voiceCount_ < kMaxVoiceCount;
|
||||
fillSurface(bmp, toKitBox(vd.minus), Role::BgCell,
|
||||
canDown ? InteractionState::Rest : InteractionState::Disabled);
|
||||
fillSurface(bmp, toKitBox(vd.plus), Role::BgCell,
|
||||
canUp ? InteractionState::Rest : InteractionState::Disabled);
|
||||
kitTextCentered(bmp, vd.minus, "-", Font::Label,
|
||||
canDown ? Role::TextPrimary : Role::TextDim);
|
||||
kitTextCentered(bmp, vd.plus, "+", Font::Label,
|
||||
canUp ? Role::TextPrimary : Role::TextDim);
|
||||
char countBuf[8];
|
||||
snprintf(countBuf, sizeof(countBuf), "%d", voiceCount_);
|
||||
kitTextCentered(bmp, vd.count, countBuf, Font::ValueMono, Role::TextPrimary);
|
||||
// Poly | Mono voice-mode toggle (the channel-toggle grammar: active segment lit).
|
||||
const bool isMono = (voiceMode_ == VoiceMode::Mono);
|
||||
fillSurface(bmp, toKitBox(vd.poly), Role::BgCell,
|
||||
!isMono ? InteractionState::Active : InteractionState::Rest);
|
||||
fillSurface(bmp, toKitBox(vd.mono), Role::BgCell,
|
||||
isMono ? InteractionState::Active : InteractionState::Rest);
|
||||
kitTextCentered(bmp, vd.poly, "Poly", Font::Label,
|
||||
!isMono ? Role::BgBase : Role::TextPrimary);
|
||||
kitTextCentered(bmp, vd.mono, "Mono", Font::Label,
|
||||
isMono ? Role::BgBase : Role::TextPrimary);
|
||||
// Retrig | Legato mono-takeover toggle — meaningful only in Mono; drawn Disabled
|
||||
// (inert) in Poly so the dependency reads at a glance.
|
||||
const bool isLegato = (monoTrigger_ == MonoTrigger::Legato);
|
||||
const InteractionState retrigState =
|
||||
!isMono ? InteractionState::Disabled
|
||||
: (!isLegato ? InteractionState::Active : InteractionState::Rest);
|
||||
const InteractionState legatoState =
|
||||
!isMono ? InteractionState::Disabled
|
||||
: (isLegato ? InteractionState::Active : InteractionState::Rest);
|
||||
fillSurface(bmp, toKitBox(vd.retrig), Role::BgCell, retrigState);
|
||||
fillSurface(bmp, toKitBox(vd.legato), Role::BgCell, legatoState);
|
||||
kitTextCentered(bmp, vd.retrig, "Retrig", Font::Label,
|
||||
!isMono ? Role::TextDim
|
||||
: (!isLegato ? Role::BgBase : Role::TextPrimary));
|
||||
kitTextCentered(bmp, vd.legato, "Legato", Font::Label,
|
||||
!isMono ? Role::TextDim
|
||||
: (isLegato ? Role::BgBase : Role::TextPrimary));
|
||||
}
|
||||
|
||||
// --- The "Modes-and-down" control strip (S-VIEW-2: moved from Zone) --------------------
|
||||
paintControls(bmp, bands.control, zone);
|
||||
}
|
||||
@@ -1873,6 +1971,39 @@ void ReaSamplerEditor::onMouseDown(int x, int y) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase S voice deck (PROVISIONAL). Every edit writes through the processor setter,
|
||||
// which rebuilds the engine off-thread via the drain-slot swap (ringing tails survive);
|
||||
// the local snapshot updates in step so the deck repaints without waiting for a sync tick.
|
||||
if (bands.voice.height() > 0 && contains(bands.voice, x, y)) {
|
||||
const VoiceDeckRects vd = voiceDeckRects(bands.voice);
|
||||
if (contains(vd.minus, x, y) && voiceCount_ > kMinVoiceCount) {
|
||||
voiceCount_ -= 1;
|
||||
processor_->setVoiceCount(voiceCount_);
|
||||
invalidate();
|
||||
} else if (contains(vd.plus, x, y) && voiceCount_ < kMaxVoiceCount) {
|
||||
voiceCount_ += 1;
|
||||
processor_->setVoiceCount(voiceCount_);
|
||||
invalidate();
|
||||
} else if (contains(vd.poly, x, y)) {
|
||||
voiceMode_ = VoiceMode::Poly;
|
||||
processor_->setVoiceMode(VoiceMode::Poly);
|
||||
invalidate();
|
||||
} else if (contains(vd.mono, x, y)) {
|
||||
voiceMode_ = VoiceMode::Mono;
|
||||
processor_->setVoiceMode(VoiceMode::Mono);
|
||||
invalidate();
|
||||
} else if (voiceMode_ == VoiceMode::Mono && contains(vd.retrig, x, y)) {
|
||||
monoTrigger_ = MonoTrigger::Retrigger; // inert (Disabled) in Poly
|
||||
processor_->setMonoTrigger(MonoTrigger::Retrigger);
|
||||
invalidate();
|
||||
} else if (voiceMode_ == VoiceMode::Mono && contains(vd.legato, x, y)) {
|
||||
monoTrigger_ = MonoTrigger::Legato;
|
||||
processor_->setMonoTrigger(MonoTrigger::Legato);
|
||||
invalidate();
|
||||
}
|
||||
return; // the deck band swallows its clicks (no fall-through to the hero/markers)
|
||||
}
|
||||
|
||||
// S-VIEW-10: the velocity-curve editor beside the hero. Every in-box click is an edit
|
||||
// (grab / Alt-delete / add-at-cursor), so materialize the one-zone site first (the
|
||||
// mirror of the control strip's ensureSampleZone path).
|
||||
|
||||
Reference in New Issue
Block a user