Paraphony: one filter row per channel, and the gate a shared envelope needs #108

Open
opened 2026-10-04 20:13:02 +00:00 by bcox · 0 comments
Owner

bcox asked on 2026-10-04 how a Poly-800's shared filter envelope would be done in Tymbal, and then how far a "paraphonic" switch could carry it. This ticket records the answer so a spec can start from it. Nothing here is built, measured or listened to. It is an assessment from reading the tree at a9725d90 on main. The hardware claims are from the sources at the end, read in that session; the DSP claims are from the tree's own comments and tests.

Summary

A shared filter is already expressible as two slots. What is missing is the gate, and the thing worth building is the switch that hides the wiring.

  • Today: a voice-scope VA layer, then a channel-scope VA layer whose osc A is VaShape::kInput reading the first slot's tap. That gives one filter row for the whole chord, which is the half that cannot be faked.
  • The gap: the held voice gets one note-on -- key 60, full velocity, untuned (dsp/voice_allocator.h:917) -- and is never released. Its envelopes reach sustain at program load and stay there. Nothing retriggers them, and a paraphonic machine's whole character is that each key press hits the one filter EG.
  • The proposal: a per-layer paraphonic flag that splits the VA layer at the filter row, plus a trigger mode (single/all) that gates the shared half. The trigger mode also defines which note's per-voice sources the shared half latches, which is the keyboard assigner's sample-and-hold and settles VCF keytrack at channel scope.
  • The split yields two VCAs, and that is correct rather than a compromise: it is the difference between a Poly-800 and an Omni.
  • The cost structure is the point: the ladder's forced 4x oversampling is paid once per channel instead of once per voice. A divide-down machine's "unlimited" polyphony becomes affordable for the same reason it was in 1978.

What the hardware does

Poly-800 Omni-2
Amplitude 8 digital volume generators in the MSM5232 tone chip, six-stage ADBSSR, one instance per DCO, keyed per note Strings: attack/release per key. Synth section: one ADSR for every note in the chord
Filter One 24 dB/oct lowpass, one EG, single/multi trigger on that EG One 4-pole 24 dB/oct VCF, plus a 2-pole bass filter
VCA Exactly one analog VCA, integrated with the VCF in the NJM2069, after the sum. A master gain, not a per-voice one Shared with the filter: notes pass through the same filter and the same amplifier envelope

Two consequences that shape the design:

  • "Shared" is about instances, not parameters. Poly-800 EG1 is one parameter set with eight instances; EG2 is one parameter set with one instance. Tymbal already does the first -- a patch's envelope spec instantiated per voice is just how voices work -- so paraphony is machinery for the second and nothing else.
  • The Poly-800's shared VCA is static. All eight per-voice gains are upstream of the filter and nothing on the keyboard gates the post-filter VCA, so a resonant Poly-800 whistles with no keys down. Authenticity is therefore a patch choice: leave the shared VCA open and the ladder bleeds; gate it and the machine is cleaner than the original.
  • The Omni-2's synth section is the literal "share everything past the mixer", which is why its chords soften at the attack.

What is already here

Need Where it lives State
A layer that runs once per channel dsp::LayerScope, the held voice in dsp/voice_allocator.h Built; patches/sympathetic_strings.h and patches/soundboard.h prove both directions
Every note's output summed, across the boundary the allocator's per-channel tap row, one sample late on both edges Built
A layer stage that is the bus VaShape::kInput (dsp/va_oscillator.h:1383) Built
A channel-wide load kSharedChannelLoad (dsp/mod_source.h:88), followed by dsp/va_sag.h Built
Channel-wide modulators kSharedChannelLfo1/2 Built
A VCA that can close a ringing filter the amplitude folded into the filter row's output gains (dsp/va_layer.h:52), pinned by VaLayer.TheEnvelopeSilencesARingingFilter Built
Trigger vocabulary VoiceMode::retrigger_envelope, and the legato ReferenceNote(true, …) path at dsp/voice_allocator.h:372 Built, for steals
Voice-count limits VoiceMode::polyphony (dsp/layer.h:267) Built
A gate at channel scope -- Missing
A split layer -- Missing

patches/poly800_strings.h is not a counterexample: it is one voice-scope VA layer with a 2-pole SVF at a fixed cutoff, no filter envelope at all, and sag.channel_load routed to amplitude only. It models the shared supply, which its header says plainly, and not the shared filter. It is the obvious first caller here.

The work

1. A channel gate for the held voice

  • Trigger on a channel note-on. single fires only when the channel had no un-released note voice; all fires on every note-on.
  • The predicate is held keys, not kSharedChannelLoad. That source deliberately counts released-but-ringing voices, which is right for sag and wrong for a trigger: a two-second release would swallow the next phrase's attack. slot.released is already tracked.
  • Retrigger envelopes only, legato-style. A plain non-legato note-on into the held voice would zero the filter row's integrators mid-chord -- a click, and it wipes the self-oscillation.
  • Release on last key up, not on each key up. Both modes share that rule; they differ only on the attack side.
  • Fire once per note event, ahead of the unison fan-out, or unison 8 fires eight triggers on one sample.
  • A steal is a note-on, so it retriggers under all. That is authentic.
  • The gate is a control event in the pool's walk, not a tap, so it stays sample-accurate while the audio crossing stays one sample late. The EG opening one sample before its excitation is the harmless direction.

2. The trigger mode as the assigner's sample-and-hold

The shared half cannot read per-voice sources -- whose velocity, whose key? The hardware's answer is the S&H on the keyboard assigner, and the trigger mode already names it: under all the shared half latches the note that last triggered it, under single the first note of the phrase. One field defines the gate and the latched CVs together, and VCF keytrack, velocity -> cutoff and note -> cutoff all become well-defined at channel scope. Destinations past the cut then accept channel sources plus the latched set; the editor can grey out the rest.

3. The split itself

  • Where the cut falls. The chain is sources -> cross-mod -> level -> ring -> noise -> wide sum -> requantize -> [shaper, pre-row] -> filter 1 -> [shaper, inter-filter] -> filter 2 -> VCA -> (+ post-filter bus x amplitude) -> [shaper, post-row] -> decimate. The cut is after requantize and takes the whole shaper/filter/VCA block with it -- defined as the block, not as a stage, because the shaper has three insert points and one of them is pre-row.
  • Two VCAs, and the front one is legal. The front half has no recursive element, so a time-varying gain there is just a gain and the smearing argument at dsp/va_layer.h:52 does not apply. The second argument in that comment -- a resonant filter cannot be closed by an upstream VCA -- does apply, and is exactly the Poly-800's behavior.
  • The post-filter bus needs a ruling. Each oscillator emits two sums; the post-filter one skips the row and joins after the VCA, scaled by the same amplitude. Under the split its scope is ambiguous: the oscillator is in front, the gain it is scaled by is behind.
  • Oversampling splits per half. The ladder forces 4x on the shared half only; front halves stay 1x. The crossing is at output rate with a zero-order hold, which is what kInput already does, so the two-slot idiom has already proved that path.
  • Sum gain law. Unity per voice is the default: with every per-voice gain applied ahead of the filter, chord size genuinely drives the ladder harder, and that is the character. 1/N should be available and should not be the default.
  • A short AR in the front half is not optional for the Omni case. A fully static front VCA makes each note-on a hard gate. The hardware's answer is the divider keyers, which is a fast attack/release per key and nothing more.

4. Invariants that become half-rules

Scope is one enum per slot today, which is what keeps HeldSelects/Sounds simple and gives "a note voice never starts a channel-scope slot" its teeth. A paraphonic slot is rendered by both roles with different halves live, so those predicates have to learn about halves. The tap row needs no new storage: a paraphonic slot produces on the note side and consumes on the held side at the same slot index, which is one tap per slot per channel either way, and the graph stays acyclic because the front half never reads the back.

5. Format

Additive: a per-layer flag, a trigger mode, a sum gain law. Minor bump, absent fields read as voice scope and unity.

The two machines, as patches

Front VCA Shared VCA Shared EG Trigger
Poly-800 per-voice ADBSSR open cutoff only all
Omni synth section fast AR keyer shared ADSR cutoff single
Omni strings per-key AR open -- (fixed voicing filter) --

A full Omni is then two or three paraphonic layers in one patch -- strings, synth, and the bass section as a mono layer with its own 2-pole filter -- which is how the instrument is laid out. Korg Mono/Poly and the Solina-family string machines fall in the same envelope.

Open questions

  • Per layer or per patch. Per layer matches scope[]'s granularity and lets one patch hold a paraphonic Poly-800 part beside an ordinary poly pad; two paraphonic layers read the same note-on events with their own trigger modes.
  • Does the shared half get its own envelope and LFO bank, or does it reuse envelopes 2-5 with a scope rule? The second is cheaper and makes "which env is channel-wide" a routing question rather than a structural one.
  • Sag belongs in the shared half, since it already follows the channel's load. Confirm nothing in front wants it.
  • Whether paraphony is VA-only to start. The concept generalizes to any engine with a filter after a mixer, but the VA layer is the only one that needs it for a named machine.
  • Whether the shared VCA defaults open (authentic) or gated (well-behaved). The open default means the held voice renders a self-oscillating ladder with no notes down, which the pool already permits -- it never retires the held voice and renders slots their engine reports inactive.

Sources

Addendum, 2026-10-05: three cases from the third set of instruments, and a correction

From the research recorded in bcox's moregaps.md that day: the Korg Trident, Delta, Polysix and Mono/Poly, the Elka Synthex, and the ARP Omni-2's service manual, all read as archive.org OCR by research agents and not re-checked here. Engine references are to f6aa8eba. Nothing below is built.

Three cases the trigger vocabulary above does not cover

  1. A trigger that waits for a count of keys. The Trident's brass section has a "silence note" count: no brass sounds until that many keys are down. Its trigger select is first-note-only, multiple, that threshold, or an external trigger. So single wants a parameter, the held-key count the gate fires at, with one as today's meaning.

  2. The shared filter tracking the highest key. The Delta's keyboard follower moves cutoff "in proportion to the pitch of the highest key played", and the Poly-800's tracking follows the highest note too. §2's latch rule -- all latches the last note, single the first -- gives neither. A third rule: the highest held key (and the lowest, for symmetry), re-evaluated on every note-on and note-off rather than latched. This is also the source #114 wants for its "other key".

  3. A modulator delay restarted only by the first key after all are up. The Polysix's MG delay is "initiated whenever all keys are released and new key(s) are then depressed"; the Synthex's LFO 1 delay runs from the first note, and above delay 1 the first note also restarts the waveform. These are consumers of §1's single gate for something that is not an envelope: a channel LFO's restart and its delay. The gate should therefore be a source any channel-scope thing can read as an edge, not a call wired to the shared half's envelopes alone. #116 proposes that edge as a general trigger kind.

Correction to "What the hardware does" and "The two machines, as patches"

The Omni strings row says attack and release per key, shared VCA open, no shared envelope. The Omni-2 service manual, as read, says otherwise:

  • Release is per key: a 22 µF capacitor per key sets it, driving that key's 4' and 8' gates.
  • Attack is shared: one String AR and one String VCA after the three delay lines "to control the attack", with AR Suppression forcing the AR to release fully between key depressions.
  • The synth section's release is that same per-key capacitor, set by the string Release slider, which the manual says "also effects synthesizer release" -- not a separate fast AR keyer.

So the strings need the channel gate too: single, with an attack-only shared envelope, and the per-key release in the front half. The manual confirms the rest of the Omni column: one 4075 VCF at 24 dB per octave, one ADSR for filter and VCA in the synth section, the single-trigger switch, and a two-pole bass filter.

More machines that land here

Mono/Poly (poly and unison/share modes: four keys through one filter and one EG pair), the Trident's brass, both Delta sections (synth and strings, each with its own single/multiple trigger), the Polymoog's one VCF (each key also has an amplifier and a filter of its own), and the Pro 2's and Pro 3's paraphonic modes, where each oscillator has its own amplitude envelope into the shared filter -- §3's front-half VCA per oscillator rather than per voice. Duophony (Odyssey, CAT, Quadra's lead, Teisco S-110F) is not this ticket: it keeps both oscillators in one voice on two keys, and is #114.

bcox asked on 2026-10-04 how a Poly-800's shared filter envelope would be done in Tymbal, and then how far a "paraphonic" switch could carry it. This ticket records the answer so a spec can start from it. **Nothing here is built, measured or listened to.** It is an assessment from reading the tree at `a9725d90` on `main`. The hardware claims are from the sources at the end, read in that session; the DSP claims are from the tree's own comments and tests. ## Summary A shared filter is already expressible as two slots. What is missing is the gate, and the thing worth building is the switch that hides the wiring. - Today: a voice-scope VA layer, then a channel-scope VA layer whose osc A is `VaShape::kInput` reading the first slot's tap. That gives one filter row for the whole chord, which is the half that cannot be faked. - The gap: the held voice gets one note-on -- key 60, full velocity, untuned (`dsp/voice_allocator.h:917`) -- and is never released. Its envelopes reach sustain at program load and stay there. Nothing retriggers them, and a paraphonic machine's whole character is that each key press hits the one filter EG. - The proposal: a per-layer **paraphonic** flag that splits the VA layer at the filter row, plus a **trigger mode (single/all)** that gates the shared half. The trigger mode also defines which note's per-voice sources the shared half latches, which is the keyboard assigner's sample-and-hold and settles VCF keytrack at channel scope. - The split yields two VCAs, and that is correct rather than a compromise: it is the difference between a Poly-800 and an Omni. - The cost structure is the point: the ladder's forced 4x oversampling is paid once per channel instead of once per voice. A divide-down machine's "unlimited" polyphony becomes affordable for the same reason it was in 1978. ## What the hardware does | | Poly-800 | Omni-2 | |---|---|---| | Amplitude | 8 digital volume generators in the MSM5232 tone chip, six-stage ADBSSR, one instance per DCO, keyed per note | Strings: attack/release per key. Synth section: one ADSR for every note in the chord | | Filter | One 24 dB/oct lowpass, one EG, **single/multi trigger** on that EG | One 4-pole 24 dB/oct VCF, plus a 2-pole bass filter | | VCA | Exactly one analog VCA, integrated with the VCF in the NJM2069, after the sum. A master gain, not a per-voice one | Shared with the filter: notes pass through the same filter *and* the same amplifier envelope | Two consequences that shape the design: - **"Shared" is about instances, not parameters.** Poly-800 EG1 is one parameter set with eight instances; EG2 is one parameter set with one instance. Tymbal already does the first -- a patch's envelope spec instantiated per voice is just how voices work -- so paraphony is machinery for the second and nothing else. - **The Poly-800's shared VCA is static.** All eight per-voice gains are upstream of the filter and nothing on the keyboard gates the post-filter VCA, so a resonant Poly-800 whistles with no keys down. Authenticity is therefore a patch choice: leave the shared VCA open and the ladder bleeds; gate it and the machine is cleaner than the original. - The Omni-2's synth section is the literal "share everything past the mixer", which is why its chords soften at the attack. ## What is already here | Need | Where it lives | State | |---|---|---| | A layer that runs once per channel | `dsp::LayerScope`, the held voice in `dsp/voice_allocator.h` | Built; `patches/sympathetic_strings.h` and `patches/soundboard.h` prove both directions | | Every note's output summed, across the boundary | the allocator's per-channel tap row, one sample late on both edges | Built | | A layer stage that *is* the bus | `VaShape::kInput` (`dsp/va_oscillator.h:1383`) | Built | | A channel-wide load | `kSharedChannelLoad` (`dsp/mod_source.h:88`), followed by `dsp/va_sag.h` | Built | | Channel-wide modulators | `kSharedChannelLfo1/2` | Built | | A VCA that can close a ringing filter | the amplitude folded into the filter row's output gains (`dsp/va_layer.h:52`), pinned by `VaLayer.TheEnvelopeSilencesARingingFilter` | Built | | Trigger vocabulary | `VoiceMode::retrigger_envelope`, and the legato `ReferenceNote(true, …)` path at `dsp/voice_allocator.h:372` | Built, for steals | | Voice-count limits | `VoiceMode::polyphony` (`dsp/layer.h:267`) | Built | | A gate at channel scope | -- | **Missing** | | A split layer | -- | **Missing** | `patches/poly800_strings.h` is not a counterexample: it is one voice-scope VA layer with a 2-pole SVF at a fixed cutoff, no filter envelope at all, and `sag.channel_load` routed to amplitude only. It models the shared *supply*, which its header says plainly, and not the shared *filter*. It is the obvious first caller here. ## The work ### 1. A channel gate for the held voice - Trigger on a channel note-on. **single** fires only when the channel had no un-released note voice; **all** fires on every note-on. - The predicate is held keys, not `kSharedChannelLoad`. That source deliberately counts released-but-ringing voices, which is right for sag and wrong for a trigger: a two-second release would swallow the next phrase's attack. `slot.released` is already tracked. - Retrigger **envelopes only**, legato-style. A plain non-legato note-on into the held voice would zero the filter row's integrators mid-chord -- a click, and it wipes the self-oscillation. - Release on last key up, not on each key up. Both modes share that rule; they differ only on the attack side. - Fire once per note event, ahead of the unison fan-out, or unison 8 fires eight triggers on one sample. - A steal is a note-on, so it retriggers under **all**. That is authentic. - The gate is a control event in the pool's walk, not a tap, so it stays sample-accurate while the audio crossing stays one sample late. The EG opening one sample before its excitation is the harmless direction. ### 2. The trigger mode as the assigner's sample-and-hold The shared half cannot read per-voice sources -- whose velocity, whose key? The hardware's answer is the S&H on the keyboard assigner, and the trigger mode already names it: under **all** the shared half latches the note that last triggered it, under **single** the first note of the phrase. One field defines the gate and the latched CVs together, and VCF keytrack, velocity -> cutoff and note -> cutoff all become well-defined at channel scope. Destinations past the cut then accept channel sources plus the latched set; the editor can grey out the rest. ### 3. The split itself - **Where the cut falls.** The chain is `sources -> cross-mod -> level -> ring -> noise -> wide sum -> requantize -> [shaper, pre-row] -> filter 1 -> [shaper, inter-filter] -> filter 2 -> VCA -> (+ post-filter bus x amplitude) -> [shaper, post-row] -> decimate`. The cut is after `requantize` and takes the whole shaper/filter/VCA block with it -- defined as the block, not as a stage, because the shaper has three insert points and one of them is pre-row. - **Two VCAs, and the front one is legal.** The front half has no recursive element, so a time-varying gain there is just a gain and the smearing argument at `dsp/va_layer.h:52` does not apply. The second argument in that comment -- a resonant filter cannot be closed by an upstream VCA -- does apply, and is exactly the Poly-800's behavior. - **The post-filter bus needs a ruling.** Each oscillator emits two sums; the post-filter one skips the row and joins after the VCA, scaled by the same amplitude. Under the split its scope is ambiguous: the oscillator is in front, the gain it is scaled by is behind. - **Oversampling splits per half.** The ladder forces 4x on the shared half only; front halves stay 1x. The crossing is at output rate with a zero-order hold, which is what `kInput` already does, so the two-slot idiom has already proved that path. - **Sum gain law.** Unity per voice is the default: with every per-voice gain applied ahead of the filter, chord size genuinely drives the ladder harder, and that is the character. 1/N should be available and should not be the default. - **A short AR in the front half is not optional** for the Omni case. A fully static front VCA makes each note-on a hard gate. The hardware's answer is the divider keyers, which is a fast attack/release per key and nothing more. ### 4. Invariants that become half-rules Scope is one enum per slot today, which is what keeps `HeldSelects`/`Sounds` simple and gives "a note voice never starts a channel-scope slot" its teeth. A paraphonic slot is rendered by both roles with different halves live, so those predicates have to learn about halves. The tap row needs no new storage: a paraphonic slot produces on the note side and consumes on the held side at the same slot index, which is one tap per slot per channel either way, and the graph stays acyclic because the front half never reads the back. ### 5. Format Additive: a per-layer flag, a trigger mode, a sum gain law. Minor bump, absent fields read as voice scope and unity. ## The two machines, as patches | | Front VCA | Shared VCA | Shared EG | Trigger | |---|---|---|---|---| | Poly-800 | per-voice ADBSSR | open | cutoff only | all | | Omni synth section | fast AR keyer | shared ADSR | cutoff | single | | Omni strings | per-key AR | open | -- (fixed voicing filter) | -- | A full Omni is then two or three paraphonic layers in one patch -- strings, synth, and the bass section as a mono layer with its own 2-pole filter -- which is how the instrument is laid out. Korg Mono/Poly and the Solina-family string machines fall in the same envelope. ## Open questions - Per layer or per patch. Per layer matches `scope[]`'s granularity and lets one patch hold a paraphonic Poly-800 part beside an ordinary poly pad; two paraphonic layers read the same note-on events with their own trigger modes. - Does the shared half get its own envelope and LFO bank, or does it reuse envelopes 2-5 with a scope rule? The second is cheaper and makes "which env is channel-wide" a routing question rather than a structural one. - Sag belongs in the shared half, since it already follows the channel's load. Confirm nothing in front wants it. - Whether paraphony is VA-only to start. The concept generalizes to any engine with a filter after a mixer, but the VA layer is the only one that needs it for a named machine. - Whether the shared VCA defaults open (authentic) or gated (well-behaved). The open default means the held voice renders a self-oscillating ladder with no notes down, which the pool already permits -- it never retires the held voice and renders slots their engine reports inactive. ## Sources - https://en.wikipedia.org/wiki/Korg_Poly-800 - https://forum.vintagesynth.com/viewtopic.php?f=1&t=34545 -- one analog VCA plus eight digital volume generators; the NJM2069 and MSM5232 attributions - https://www.gearnews.com/classic-gear-korg-poly-800-the-affordable-80s-poly/ -- the per-DCO DEGs, ADBSSR, and the filter EG's single/multi trigger - https://en.wikipedia.org/wiki/ARP_Omni - https://en.wikipedia.org/wiki/Paraphony - https://vintagetechnologyarchive.com/synth/arp/omni-2/ ## Addendum, 2026-10-05: three cases from the third set of instruments, and a correction From the research recorded in bcox's `moregaps.md` that day: the Korg Trident, Delta, Polysix and Mono/Poly, the Elka Synthex, and the ARP Omni-2's service manual, all read as archive.org OCR by research agents and not re-checked here. Engine references are to `f6aa8eba`. Nothing below is built. ### Three cases the trigger vocabulary above does not cover 1. **A trigger that waits for a count of keys.** The Trident's brass section has a "silence note" count: no brass sounds until that many keys are down. Its trigger select is first-note-only, multiple, that threshold, or an external trigger. So **single** wants a parameter, the held-key count the gate fires at, with one as today's meaning. 2. **The shared filter tracking the highest key.** The Delta's keyboard follower moves cutoff "in proportion to the pitch of the highest key played", and the Poly-800's tracking follows the highest note too. §2's latch rule -- **all** latches the last note, **single** the first -- gives neither. A third rule: the highest held key (and the lowest, for symmetry), re-evaluated on every note-on and note-off rather than latched. This is also the source #114 wants for its "other key". 3. **A modulator delay restarted only by the first key after all are up.** The Polysix's MG delay is "initiated whenever all keys are released and new key(s) are then depressed"; the Synthex's LFO 1 delay runs from the first note, and above delay 1 the first note also restarts the waveform. These are consumers of §1's **single** gate for something that is not an envelope: a channel LFO's restart and its delay. The gate should therefore be a source any channel-scope thing can read as an edge, not a call wired to the shared half's envelopes alone. #116 proposes that edge as a general trigger kind. ### Correction to "What the hardware does" and "The two machines, as patches" The Omni strings row says attack and release per key, shared VCA open, no shared envelope. The Omni-2 service manual, as read, says otherwise: - Release is per key: a 22 µF capacitor per key sets it, driving that key's 4' and 8' gates. - Attack is shared: one String AR and one String VCA after the three delay lines "to control the attack", with AR Suppression forcing the AR to release fully between key depressions. - The synth section's release is that same per-key capacitor, set by the string Release slider, which the manual says "also effects synthesizer release" -- not a separate fast AR keyer. So the strings need the channel gate too: **single**, with an attack-only shared envelope, and the per-key release in the front half. The manual confirms the rest of the Omni column: one 4075 VCF at 24 dB per octave, one ADSR for filter and VCA in the synth section, the single-trigger switch, and a two-pole bass filter. ### More machines that land here Mono/Poly (poly and unison/share modes: four keys through one filter and one EG pair), the Trident's brass, both Delta sections (synth and strings, each with its own single/multiple trigger), the Polymoog's one VCF (each key also has an amplifier and a filter of its own), and the Pro 2's and Pro 3's paraphonic modes, where each oscillator has its own amplitude envelope into the shared filter -- §3's front-half VCA per oscillator rather than per voice. Duophony (Odyssey, CAT, Quadra's lead, Teisco S-110F) is not this ticket: it keeps both oscillators in one voice on two keys, and is #114.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
bcox/tymbal#108
No description provided.