@@ -2,7 +2,7 @@ | |||
SLUG = ImpromptuModular | |||
# Must follow the format in the Versioning section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||
VERSION = 0.6.9 | |||
VERSION = 0.6.10 | |||
# FLAGS will be passed to both the C and C++ compiler | |||
FLAGS += | |||
@@ -2,7 +2,7 @@ | |||
Modules for [VCV Rack](https://vcvrack.com), available in the [plugin manager](https://vcvrack.com/plugins.html). | |||
Version 0.6.9 | |||
Version 0.6.10 | |||
[//]: # (!!!!!UPDATE VERSION NUMBER IN MAKEFILE ALSO!!!!! 120% Zoom for jpgs) | |||
@@ -19,7 +19,8 @@ Impromptu Modular is not a single-person endeavor: | |||
* Thanks to **Xavier Belmont** for suggesting improvements to the modules, for testing/bug-reports, for the concept design of the SMS16 module and the blank panel, and for graciously providing the dark panels of all modules. | |||
* Thanks to **Steve Baker** for many fruitful discussions regarding the BPM Detection method in Clocked, testing and improvements that were suggested for that module. | |||
* Thanks to **Omri Cohen** for testing and suggesting improvements to the modules, and for the [PhraseSeq16/32 tutorial](https://www.youtube.com/watch?v=N8_rMNzsS7w). | |||
* Thanks also to **Latif Fital**, **Alfredo Santamaria**, **Nay Seven**, **Alberto Zamora**, **Clément Foulc** for suggesting improvements to the modules, bug reports and testing. | |||
* Thanks to **Latif Karoumi** for [testing](https://www.youtube.com/watch?v=5PZCXvWlFZM) and suggesting improvement to the modules, particularly the advanced gate modes in the GateSeq64 and PhraseSeq sequencers. | |||
* Thanks also to **Pyer (Pierre Collard)**, **Alfredo Santamaria**, **Nay Seven**, **Alberto Zamora**, **Clément Foulc**, **Espen Storo**, **Wouter Spekkink**, **John Melcher** for suggesting improvements to the modules, bug reports and testing. | |||
@@ -65,7 +66,7 @@ Such sequencers have two main inputs that allow the capturing of (pitch) CVs, as | |||
When **AUTOSTEP** is activated, the sequencer automatically advances one step right on each write. For example, to automatically capture the notes played on a keyboard, send the midi keyboard's CV into the sequencer's CV IN, and send the keyboard's gate signal into the sequencer's Write input. With Autostep activated, each key-press will be automatically entered in sequence. An alternative way of automatically stepping the sequencer each time a note is entered is to send the gate signal of the keyboard to both the write and ">" inputs. | |||
When Run is activated, the sequencer automatically starts playing in the current step position, provided **RESET on RUN** is not checked in the right-click menu; sequencers will start at the first step when this option is checked. All edge sensitive inputs have a threshold of 1V. In all sequencers, the duration of the gates corresponds to the pulse width (high time) of the clock signal. | |||
When Run is activated, the sequencer automatically starts playing in the current step position, provided **RESET on RUN** is not checked in the right-click menu; sequencers will start at the first step when this option is checked. All edge sensitive inputs have a threshold of 1V. In all sequencers, the duration of the gates normally corresponds to the pulse width (high time) of the clock signal. When sequencers offer an **Advanced gate mode** and this mode is activated, the pulse width of the clock signal has no effect on the sequencer. | |||
Many modules feature an **Expansion panel** to provide additional CV inputs for the module (available in the right-click menu of the module). An extra 4 HP is added on the right side of the module, thus it is advisable to first make room in your Rack for this. | |||
@@ -141,14 +142,14 @@ The PW and Swing **CV inputs** (some are available in the expansion panel) are 0 | |||
### External synchronization <a id="clocked-sync"></a> | |||
By default, the clock's BPM input is level sensitive and follows [Rack standards for BPM CVs](https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies). For synchronizing Clocked an external clock signal, an optional setting is available in the right-click menu called "Use **BPM Detection** (as opposed to **BPM CV**)". When using this option in a chain of Clocked modules, all modules must have the option checked. The green LED to the right of the main BPM display will light up when this mode is enabled and a cable is connected to the BPM input. | |||
By default, the clock's BPM input is level sensitive and follows [Rack standards for BPM CVs](https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies). For synchronizing Clocked to an external clock signal, an optional setting is available using the MODE button located below the BPM input jack, and selecting a mode other than "CV". When using a chain of Clocked modules, all modules must have the same mode setting. The LED to the right of the MODE button will light up when the sync mode is enabled and a cable is connected to the BPM input. When no cable is connected to the BPM input jack, the MODE button has no effect and the BPM CV mode is implicit. When a cable is connected, the possible settings are: P4, P8, P12, P24, where the number indicates the number of pulses per step of the external clock source. | |||
When using BPM detection, Clocked syncs itself to the incoming clock pulse, and will stay synchronized, as opposed to just calculating the BPM from the external source. This means that it will not drift (or that it will drift in time with the incoming pulses if they drift), and it should stay perfectly synchronized over time; it also allows for latency compensation. Here are a few points to keep in mind when using BPM Detection. | |||
When using external clock synchronization, Clocked syncs itself to the incoming clock pulse, and will stay synchronized, as opposed to just calculating the BPM from the external source. This means that it will not drift (or that it will drift in time with the incoming pulses if they drift), and it should stay perfectly synchronized over time; it also allows for latency compensation. Here are a few points to keep in mind when using clock synchronization. | |||
1. When using the BPM detection mode, Clocked can not be manually turned on, it will autostart on the first pulse it receives. | |||
1. Clocked can not be manually turned on in clock sync mode, it will autostart on the first pulse it receives. | |||
1. Clocked will automatically stop when the pulses stop, but in order to detect this, it take a small amount of time. To stop the clock quickly, you can simply send a pulse to the RUN CV input, and if the clock is running, it will turn off. | |||
1. The external clock must be capable of sending clocks at a minumum of 4 pulses per quarter note (PPQN) and should not have any swing. | |||
1. For low clock BPMs, synchronization may take some time if the external clock changes markedly from the last BPM it was synchronized to. Making gradual tempo changes is always recommended, and increasing the PPQN setting may also help. An other method is to first prime Clocked with is correct BPM to let it learn the new BPM, so that all further runs at that BPM will sync perfectly. | |||
1. For low clock BPMs, synchronization may take some time if the external clock changes markedly from the last BPM it was synchronized to. Making gradual tempo changes is always recommended, and increasing the PPQN setting may also help. An other method consists in priming Clocked with is correct BPM first, to let it learn the new BPM, so that all further runs at that BPM will sync perfectly. | |||
([Back to module list](#modules)) | |||
@@ -158,7 +159,7 @@ When using BPM detection, Clocked syncs itself to the incoming clock pulse, and | |||
 | |||
A 16 phrase sequencer module, where each phrase is an index into a set of 16 sequences of 16 steps (maximum). CVs can be entered via a CV input when using an external keyboard controller or via the built-in keyboard on the module itself. If you need a 256-step sequence in a single module, this is the sequencer for you! With two separate gates per step, gate 2 is perfect for using as an accent if desired. When notes are entered with the *right mouse button* instead of the left button, the sequencer automatically moves to the next step. | |||
A 16 phrase sequencer module, where each phrase is an index into a set of 16 sequences of 16 steps (maximum). CVs can be entered via a CV input when using an external keyboard controller or via the built-in keyboard on the module itself. If you need a 256-step sequence in a single module, this is the sequencer for you! With two separate gates per step, gate 2 is perfect for using as an accent if desired. When notes are entered with the **right mouse button** instead of the left button, the sequencer automatically moves to the next step. | |||
The following block diagram shows how sequences and phrases relate to each other to create a song. In the diagram, a 12-bar blues pattern is created by setting the song length to 12, the step lengths to 8 (not visible in the figure), and then creating 4 sequences. The 12 phrases are indexes into the 4 sequences that were created. (Not sure anyone plays blues in a modular synth, but it shows the idea at least!) | |||
@@ -176,7 +177,7 @@ Familiarity with the Fundamental SEQ-3 sequencer is recommended, as some operati | |||
* **ATTACH**: Allows the edit head to follow the run head (Attach on). The position of the edit head is shown with a red LED, and the position of the run head is shown with a green LED. When in Seq mode, the actual content of the step corresponding to the edit head position (i.e. note, oct, gates, slide) can be modified in real time whether the sequencer is running or not. The edit head automatically follows the run head when Attach is on, or can manually positioned by using the < and > buttons when Attach is off. | |||
* **MODE**: This controls the run mode of the sequences and the song (one setting for each sequence and one for the song). The modes are: FWD (forward), REV (reverse), PPG (ping-pong, also called forward-reverse), BRN (Brownian random), RND (random), FW2 (forward, play twice), FW3 (play three times) and FW4 (four times). For example, setting the run mode to FWD for sequences and to RND for the song will play the phrases that are part of a song randomly, and the probability of a given phrase playing is proportional to the number of times it appears in the song. For sequences, the FW2, FW3 and FW4 modes can be used to repeat sequences more easily without consuming additional phrases in the song. These last three modes are not available for the song's run mode however. | |||
* **MODE**: This controls the run mode of the sequences and the song (one setting for each sequence and one for the song). The modes are: FWD (forward), REV (reverse), PPG (ping-pong, also called forward-reverse), BRN (Brownian random), RND (random), FW2 (forward, play twice), FW3 (play three times) and FW4 (four times). For example, setting the run mode to FWD for sequences and to RND for the song will play the phrases that are part of a song randomly, and the probability of a given phrase playing is proportional to the number of times it appears in the song. For sequences, the FW2, FW3 and FW4 modes can be used to repeat sequences more easily without consuming additional phrases in the song. These last three modes are not available for the song's run mode however. Holding the MODE button for **two seconds** allows the selection of the clock resolution, and is the mechanism used to enable the [advanced gate mode](#advanced-gate-mode-ps). | |||
* **TRAN/ROT**: Transpose/Rotate the currently selected sequence up-down/left-right by a given number of semi-tones/steps. The main knob is used to set the transposition/rotation amount. Only available in Seq mode. | |||
@@ -188,7 +189,22 @@ Familiarity with the Fundamental SEQ-3 sequencer is recommended, as some operati | |||
* **SLIDE**: Portamento between CVs of successive steps. Slide can be activated for a given step using the slide button. The slide duration can be set using the slide knob. The slide duration can range from 0 to T seconds, where T is the duration of a clock period (the default is 10% of T). This knob's setting is not memorized for each step and applies to the sequencer as a whole. | |||
* **TIED STEP**: When CVs are intended to be held across subsequent steps, this button can be used to tie the CV of the previous step to the current step; when tied, the gates of the current step are automatically turned off. If the CV of the head note changes, all consecutive tied notes are updated automatically. | |||
* **TIED STEP**: When CVs are intended to be held across subsequent steps, this button can be used to tie the CV of the previous step to the current step. When tied is turned on for a step, the gates of that step are automatically turned off, but can be manually turned back on if desired. When tied, if the CV of the head note changes, all consecutive tied notes are updated automatically. | |||
### Advanced gate mode<a id="advanced-gate-mode-ps"></a> | |||
Holding the MODE button for **two seconds** allows the selection of the clock resolution, in number of pulses per step (PPS). When set to a value greater than 1, which unlocks the advanced gate mode, the sequencer will skip this many clock pulses before advancing to the next step. In such cases, a mutliplied clock must be supplied in order to keep the same tempo in the sequencer. In advanced gate mode, the pulse width of the clock is not used and has no effect on the gates. | |||
In the advanced gate mode, the Gate1 and Gate2 lights will be a different color, and the onboard keyboard can be used not only to enter note values, but also to select one of the 12 types of gates for a given step. Advanced gates can only be set while in Seq mode and when the sequencer is stopped. Here are the different gate types and their minimum PPS requirements. | |||
 | |||
All PPS settings will work for the half and full gates (the D and F keys) as well as triggers (the B key). A full gate remains high during the entire step, and if the next step's gate is active, then the gate continues without interruption into that next step. When PPS requirements are not met, the sequencer will not allow invalid gate types to be entered on the keyboard. For example, if PPS is set to 6, then the 75% gate (the E key) can not be selected. Selecting a PPS value of 12 or 24 will ensure that all gate types can be used (i.e. that all PPS requirements are met irrespective of the gate type chosen). | |||
The gate type for a given step can be selected during a short time interval after a given gate has just been turned on using the Gate1 or Gate2 buttons. If a gate is already turned on and its gate type is to be edited, clicking the gate button twice will allow it to be edited while keeping it in the same state. The onboard keyboard will temporarily show a yellow/orange light corresponding to the current gate type for that step; during this time the gate type can be changed. | |||
Since the editing time for the advanced gate mode is kept rather short (4s), holding the Gate2 button for 2s will set that default time interval to 400s. Holding Gate1 for 2s will revert to the default time of 4s. The extended time feature is useful when the gate modes for multiple steps of a sequence are to be editied or reviewed in a single pass, for example. | |||
([Back to module list](#modules)) | |||
@@ -216,15 +232,22 @@ When the 1x32 configuration is selected, only the top channel outputs are used ( | |||
A 64 step gate sequencer with the ability to define **probabilities** for each step. A configuration switch allows the sequencer to output quad 16 step sequences, dual 32 step sequences or single 64 step sequences. To see the sequencer in action and for a tutorial on how it works, please see [this segment](https://www.youtube.com/watch?v=bjqWwTKqERQ&t=6111s) of Nigel Sixsmith's Talking Rackheads episode 10. | |||
When activating a given step by clicking it once, it will turn green showing that the step is on. Clicking the step again turns it yellow, and the main display shows the probability associated with this step. While the probability remains shown, the probability can be adjusted with the main knob, in 0.02 increments, between 0 and 1. When a yellow step is selected, clicking it again will turn it off. | |||
When activating a given step by clicking it once, it will turn green showing that the step is on. Clicking the _"p"_ button turns it yellow, and the main display shows the probability associated with this step. While the probability remains shown, the probability can be adjusted with the main knob, in 0.02 increments, between 0 and 1. When a yellow step is selected, clicking the _"p"_ button again will turn it off. | |||
This sequencer also features the song mode found in [PhraseSeq16](#phrase-seq-16); 16 phrases can be defined, where a phrase is an index into a set of 16 sequences. In GateSeq64, the song steps are shown using the fourth row of steps, overlapped with the actual sequence progression in lighter shades in the lights. | |||
The **SEQ** CV input and run **MODES** are identical to those found in PhraseSeq16, and selecting sequence lengths is done in the same manner as described in [PhraseSeq32](#phrase-seq-32). Copy-pasting **ALL** also copies the run mode and length of a given sequence, along with gate states and probabilities, whereas only gates and probabilities are copied when **ROW** is selected. | |||
The **SEQ** CV input and run **MODES** are identical to those found in PhraseSeq16, and selecting sequence lengths is done in the same manner as described in [PhraseSeq32](#phrase-seq-32). Copy-pasting ALL also copies the run mode and length of a given sequence, along with gate states and probabilities, whereas only gates and probabilities are copied when 4 or ROW are selected. | |||
When running in the 4x16 configuration, each of the four rows is sent to the four **GATE** output jacks (jacks 1 to 4, with jack 1 being the top-most jack). In the 2x32 configuration, jacks 1 and 3 are used, and in the 1x64 configuration, only jack 1 is used (top-most jack). The pulse width of the gates emitted corresponds to the pulse width of the clock. | |||
Although no **write** capabilities appear in the main part of the module, automatically storing patterns into the sequencer can be performed using the CV inputs in the **expansion panel**. A write cursor is implicitly stepped forward on each write, and can be repositioned at the first step by pressing the reset button, or at an arbitrary step by simply clicking that given step. | |||
Although no **write** capabilities appear in the main part of the module, automatically storing patterns into the sequencer can be performed using the CV inputs in the **expansion panel**. The cursor is stepped forward on each write, and can be repositioned at the first step by pressing the reset button, or at an arbitrary step by simply clicking that given step. When the cursor is not flashing, clicking any step will make it appear. | |||
### Advanced gate mode<a id="advanced-gate-mode-gs"></a> | |||
Holding the MODE button for **two seconds** allows the selection of the clock resolution, in number of pulses per step (PPS). When set to a value greater than 1, which unlocks the advanced gate mode, the sequencer will skip this many clock pulses before advancing to the next step. In such cases, a mutliplied clock must be supplied in order to keep the same tempo in the sequencer. In advanced gate mode, the pulse width of the clock is not used and has no effect on the gates. | |||
The PPS be a multiple of 4 for the first three gate types, while the PPS be a multiple of 6 for the last five gate types. A chosen gate type not meeting its required pulse rate will have a red LED beside it to indicate this (normally it is green). When a gate type is red, the sequencer will still emit a (possibly empty) gate pattern for that step, but with no guarantee that it will match the type that was selected. All gate types can be used when selecting a PPS value of 12 or 24. | |||
([Back to module list](#modules)) | |||
@@ -260,6 +283,8 @@ Here are a few more details on some of the uses of the buttons. The sequencer us | |||
**FILL**: plays continuous triggers for the given channel as long as the button is kept pressed. By default the fills are not written to memory and are only for playback; however, an option to allow the writing of fill steps to memory is available in the right-click menu. | |||
The BIG and DEL buttons are **quantized to the nearest beat**. Without quantization, button presses typically affect the current beat (step) of the sequencer. With quantized buttons, they affect the nearest beat. For example, pressing the big button 1 microsecond before a beat would normally record the beat in the current step and not the next one that is about to occur, which is actually the closest. For the quantization to work properly, the sequencer must recieve a stable clock of at least 30 BPM. When this is not the case, the option is automatically disabled internally. When manually advancing the clock to program a sequence in non real-time, for example, the option has no effect and the current step is always the target of a button press. | |||
([Back to module list](#modules)) | |||
@@ -315,3 +340,17 @@ WriteSeq64 has dual clock inputs, where each controls a pair of channels. When n | |||
Ideas: The first part of the famous [Piano Phase](https://en.wikipedia.org/wiki/Piano_Phase) piece by Steve Reich can be easily programmed into the sequencer by entering the twelve notes into channel 1 with a keyboard, setting STEPS to 12, copy-pasting channel 1 into channel 3, and then driving each clock input with two LFOs that have ever so slightly different frequencies. Exercise left to the reader! | |||
([Back to module list](#modules)) | |||
# Hall of Fame <a id="hall-of-fame"></a> | |||
Here are a few videos featuring Impromptu Modular, which I find particularly very inspiring and interesting (listed in no particular order). Many talented Rackheads have made tracks using Impromptu modules, and this list could in fact be quite long. I have no formal criteria for why a track ends up in the list or doesn't. | |||
* **Nigel Sixsmith**, [Talking Rackheads episode 8, PS16 MegaPatch Play Out](https://www.youtube.com/watch?v=KOpo2oUPTjg&t=5504s) | |||
* **Omri Cohen**, [Arvo Pärt - Spiegel im Spiegel VCV Rack Cover](https://www.youtube.com/watch?v=6bm4LjRYDmI) | |||
* **Isaac Hu**, [In the Hall of the Mountain King](https://www.youtube.com/watch?v=fxYc0H5i6HA) | |||
* **John Melcher**, [Steppes](https://www.youtube.com/watch?v=ruo4s_Hyhrw) | |||
@@ -14,6 +14,7 @@ ALL_OBJ= \ | |||
src/MidiFileModule.o \ | |||
src/PhraseSeq16.o \ | |||
src/PhraseSeq32.o \ | |||
src/PhraseSeqUtil.o \ | |||
src/SemiModularSynth.o \ | |||
src/Tact.o \ | |||
src/TwelveKey.o \ | |||
@@ -0,0 +1,813 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="220" | |||
height="183" | |||
viewBox="0 0 58.208549 48.418911" | |||
version="1.1" | |||
id="svg321" | |||
sodipodi:docname="AdvancedGateDetails.svg" | |||
inkscape:version="0.92.3 (2405546, 2018-03-11)"> | |||
<metadata | |||
id="metadata325"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
<dc:title /> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<sodipodi:namedview | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1" | |||
objecttolerance="10" | |||
gridtolerance="10" | |||
guidetolerance="10" | |||
inkscape:pageopacity="0" | |||
inkscape:pageshadow="2" | |||
inkscape:window-width="1920" | |||
inkscape:window-height="1017" | |||
id="namedview323" | |||
showgrid="false" | |||
inkscape:zoom="3.1174557" | |||
inkscape:cx="144.03182" | |||
inkscape:cy="84.993533" | |||
inkscape:window-x="-8" | |||
inkscape:window-y="-8" | |||
inkscape:window-maximized="1" | |||
inkscape:current-layer="svg321" | |||
showguides="true" | |||
inkscape:guide-bbox="true" /> | |||
<defs | |||
id="defs89"> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="a"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix2" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="b"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix5" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="c"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix8" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="d"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix11" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="e"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix14" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="f"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix17" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="g"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix20" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="h"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix23" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="i"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix26" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="j"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix29" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="k"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix32" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="l"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix35" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="m"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix38" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="n"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix41" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="o"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix44" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="p"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix47" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="q"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix50" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="r"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix53" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="s"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix56" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="t"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix59" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="u"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix62" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="v"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix65" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="w"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix68" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="x"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix71" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="y"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix74" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="z"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix77" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="A"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix80" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="B"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix83" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="C"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix86" /> | |||
</filter> | |||
</defs> | |||
<text | |||
id="text95" | |||
word-spacing="0" | |||
letter-spacing="0" | |||
font-size="10.583" | |||
font-weight="400" | |||
y="34.662903" | |||
x="64.782875" | |||
style="font-weight:400;font-size:10.58300018px;line-height:1.25;font-family:sans-serif;letter-spacing:0;word-spacing:0;stroke-width:0.26499999" /> | |||
<rect | |||
y="12.95517" | |||
x="2.9069221" | |||
height="22.225079" | |||
width="52.916325" | |||
rx="0.60890275" | |||
ry="0.60897696" | |||
id="rect117" | |||
style="fill:#333333;stroke-width:1.000314;paint-order:markers stroke fill" /> | |||
<path | |||
d="m 15.230183,13.882545 0.03219,9.537557 2.327392,1.289025 v 9.560551 h -6.358203 v -9.560551 l 2.370542,-1.289025 v -9.537557 z m 22.398581,0 0.03288,9.537557 2.326708,1.289025 v 9.560551 h -6.35821 v -9.560551 l 2.370543,-1.289025 v -9.537557 z m 5.806151,0 -0.03288,9.537557 -2.326707,1.289025 v 9.560551 h 6.35752 v -9.560551 l -2.369854,-1.289025 v -9.537557 z m -35.6499211,0 0.03288,9.537557 2.3267081,1.289025 v 9.560551 H 3.7863718 v -9.560551 l -0.0055,-10.826582 h 2.3753371 z m 13.2465461,0 -0.03288,9.537557 -2.326707,1.289025 v 9.560551 h 6.35752 v -9.560551 l 0.0048,-10.826582 h -2.375337 z m 9.152035,0 0.03288,9.537557 2.326708,1.289025 v 9.560551 h -6.357525 v -9.560551 l -0.0055,-10.826582 h 2.376022 z m 20.69653,0 -0.03288,9.537557 -2.326707,1.289025 v 9.560551 h 6.35752 v -9.560551 l 0.0048,-10.826582 h -2.375337 z" | |||
id="path119" | |||
inkscape:connector-curvature="0" | |||
style="fill:#999999;stroke:#000000;stroke-width:0.26458582" | |||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" /> | |||
<g | |||
id="g169" | |||
transform="matrix(0.26458426,0,0,0.26458426,-6.0187653,3.9901329)"> | |||
<g | |||
id="g58"> | |||
<line | |||
id="line52" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="58.888561" | |||
y1="122.37562" | |||
x1="58.888561" /> | |||
<line | |||
id="line54" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="53.964432" | |||
y1="122.37562" | |||
x1="53.964432" /> | |||
<line | |||
id="line56" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="49.040298" | |||
y1="122.37562" | |||
x1="49.040298" /> | |||
</g> | |||
<path | |||
id="path96" | |||
style="fill:none;stroke:#272727;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 54.003,145.25461 v -5.2617 a 1.11729,1.11729 0 0 1 1.1173,-1.11729 h 2.68954 a 1.11729,1.11729 0 0 1 1.11729,1.11729 v 4.1444 a 1.1173,1.1173 0 0 0 1.1173,1.1173 H 73.6995" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 11.854922,38.056573 V 36.66441 a 0.29561735,0.29561735 0 0 1 0.29562,-0.295618 h 2.014447 a 0.29561735,0.29561735 0 0 1 0.295617,0.295618 v 1.096543 a 0.29561999,0.29561999 0 0 0 0.29562,0.29562 h 2.310085" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
id="path98" /> | |||
<g | |||
id="g183" | |||
transform="matrix(0.26458426,0,0,0.26458426,-10.607803,3.9901329)"> | |||
<g | |||
id="g74"> | |||
<line | |||
id="line68" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="132.77985" | |||
y1="122.37562" | |||
x1="132.77985" /> | |||
<line | |||
id="line70" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="122.93159" | |||
y1="122.37562" | |||
x1="122.93159" /> | |||
<line | |||
id="line72" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="118.00746" | |||
y1="122.37562" | |||
x1="118.00746" /> | |||
</g> | |||
<path | |||
id="path100" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 127.89428,145.25461 v -5.2617 a 1.11729,1.11729 0 0 1 1.11729,-1.11729 h 12.5378 a 1.11729,1.11729 0 0 1 1.1173,1.11729 v 4.1444 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 3.80684" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="m 26.749066,38.056572 v -1.392163 a 0.29561735,0.29561735 0 0 1 0.295618,-0.295617 h 4.620152 a 0.29561735,0.29561735 0 0 1 0.29562,0.295617 v 1.392163" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
id="path102" /> | |||
<g | |||
id="g194" | |||
transform="matrix(0.26458426,0,0,0.26458426,-13.778451,3.9901329)"> | |||
<line | |||
id="line4" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="201.02985" | |||
y1="122.37562" | |||
x1="201.02985" /> | |||
<path | |||
id="path104" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 196.14428,145.25461 v -5.2617 a 1.11729,1.11729 0 0 1 1.11729,-1.11729 h 2.68954 a 1.11729,1.11729 0 0 1 1.1173,1.11729 v 4.1444 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 2.68954 a 1.1173,1.1173 0 0 0 1.1173,-1.1173 v -4.1444 a 1.11729,1.11729 0 0 1 1.11729,-1.11729 h 2.68954 a 1.11729,1.11729 0 0 1 1.1173,1.11729 v 4.1444 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 3.80684" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g140" | |||
transform="matrix(0.26458426,0,0,0.26458426,-3.8684196,9.9159005)"> | |||
<g | |||
id="g14"> | |||
<line | |||
id="line8" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="136.35571" | |||
y1="0.5" | |||
x1="136.35571" /> | |||
<line | |||
id="line10" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="149.48431" | |||
y1="0.5" | |||
x1="149.48431" /> | |||
<line | |||
id="line12" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="142.92365" | |||
y1="0.5" | |||
x1="142.92365" /> | |||
</g> | |||
<path | |||
id="path32" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 144.59874,23.379 v 0 -5.2617 A 1.1173,1.1173 0 0 1 145.716,17 h 1.04816 a 1.1173,1.1173 0 0 1 1.1173,1.1173 v 4.14439 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 15.29647" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g126" | |||
transform="matrix(0.26458426,0,0,0.26458426,36.52507,36.236497)"> | |||
<g | |||
id="g40"> | |||
<line | |||
id="line34" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="39.228661" | |||
y1="0.5" | |||
x1="39.228661" /> | |||
<line | |||
id="line36" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="24.45627" | |||
y1="0.5" | |||
x1="24.45627" /> | |||
<line | |||
id="line38" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="19.532141" | |||
y1="0.5" | |||
x1="19.532141" /> | |||
</g> | |||
<path | |||
transform="translate(-14.81094,-16.5)" | |||
id="path84" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
d="m 34.34308,23.379 h 8.731 a 1.11729,1.11729 0 0 0 1.11729,-1.1173 V 18.1173 A 1.1173,1.1173 0 0 1 45.30864,17 h 2.68954 a 1.11729,1.11729 0 0 1 1.11729,1.1173 v 4.14439 a 1.1173,1.1173 0 0 0 1.1173,1.1173 h 3.80683" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g358" | |||
transform="translate(-6.7638893,-11.032398)"> | |||
<g | |||
id="g22-4" | |||
transform="matrix(0.26458426,0,0,0.26458426,-2.729345,16.582661)"> | |||
<line | |||
id="line16-2" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="179.22273" | |||
y1="17" | |||
x1="179.22273" /> | |||
<line | |||
id="line18-9" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="198.91927" | |||
y1="17" | |||
x1="198.91927" /> | |||
<line | |||
id="line20-1" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="192.3586" | |||
y1="17" | |||
x1="192.3586" /> | |||
</g> | |||
<path | |||
id="path108-6" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
d="m 44.690171,22.768376 h 1.432131 a 0.30500215,0.30500215 0 0 0 0.305,-0.304999 v -1.077784 a 0.30499951,0.30499951 0 0 1 0.304999,-0.305 h 0.258565 a 0.30499951,0.30499951 0 0 1 0.305,0.305 v 1.077781 a 0.30499951,0.30499951 0 0 0 0.304999,0.305 h 2.300695" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g351" | |||
transform="translate(-8.8805593,-11.032398)"> | |||
<g | |||
id="g30-4" | |||
transform="matrix(0.26458426,0,0,0.26458426,-2.729345,16.582661)"> | |||
<line | |||
id="line24-4" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="215.2529" | |||
y1="17" | |||
x1="215.2529" /> | |||
<line | |||
id="line26-9" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="221.82082" | |||
y1="17" | |||
x1="221.82082" /> | |||
<line | |||
id="line28-5" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="234.94942" | |||
y1="17" | |||
x1="234.94942" /> | |||
</g> | |||
<path | |||
id="path110-3" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
d="m 54.223185,22.768376 h 3.169259 a 0.30499951,0.30499951 0 0 0 0.304999,-0.304999 v -1.077784 a 0.30499951,0.30499951 0 0 1 0.305,-0.305 h 0.258567 a 0.30499951,0.30499951 0 0 1 0.305,0.305 v 1.077781 a 0.30499951,0.30499951 0 0 0 0.304999,0.305 h 0.563565" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="7.3508749" | |||
y="42.490799" | |||
id="text245"><tspan | |||
sodipodi:role="line" | |||
id="tspan243" | |||
x="7.3508749" | |||
y="52.146332" | |||
style="stroke-width:0.26458427" /></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="3.1753566" | |||
y="3.9635246" | |||
id="text249"><tspan | |||
sodipodi:role="line" | |||
id="tspan247" | |||
x="3.1753566" | |||
y="3.9635246" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">PULSES PER STEPS (PPS) - REQUIREMENTS</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="12.148573" | |||
y="41.321423" | |||
id="text249-5-3"><tspan | |||
sodipodi:role="line" | |||
id="tspan247-9-6" | |||
x="12.148573" | |||
y="41.321423" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">ALL</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="19.675114" | |||
y="8.6498356" | |||
id="text249-5-8"><tspan | |||
sodipodi:role="line" | |||
id="tspan247-9-4" | |||
x="19.675114" | |||
y="8.6498356" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">MULTIPLES OF 6</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="49.741993" | |||
y="41.321423" | |||
id="text249-5-3-8"><tspan | |||
sodipodi:role="line" | |||
id="tspan247-9-6-2" | |||
x="49.741993" | |||
y="41.321423" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">ALL</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="27.327574" | |||
y="41.332336" | |||
id="text249-5-3-3"><tspan | |||
sodipodi:role="line" | |||
id="tspan247-9-6-7" | |||
x="27.327574" | |||
y="41.332336" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">ALL</tspan></text> | |||
<g | |||
id="g198-7" | |||
transform="matrix(0.26458426,0,0,0.26458426,-49.235941,-22.33047)"> | |||
<line | |||
id="line6-2" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="236.47182" | |||
y1="122.37562" | |||
x1="236.47182" /> | |||
<path | |||
id="path106-8" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 231.58623,145.25461 v -5.2617 a 1.11729,1.11729 0 0 1 1.1173,-1.11729 h 1.04816 a 1.11729,1.11729 0 0 1 1.1173,1.11729 v 4.1444 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 1.04816 a 1.1173,1.1173 0 0 0 1.1173,-1.1173 v -4.1444 a 1.11729,1.11729 0 0 1 1.1173,-1.11729 h 1.04816 a 1.11729,1.11729 0 0 1 1.11729,1.11729 v 4.1444 a 1.1173,1.1173 0 0 0 1.1173,1.1173 H 243.6 a 1.1173,1.1173 0 0 0 1.1173,-1.1173 v -4.1444 a 1.11729,1.11729 0 0 1 1.11729,-1.11729 h 1.04816 A 1.11729,1.11729 0 0 1 248,139.99291 v 4.1444 a 1.1173,1.1173 0 0 0 1.1173,1.1173 h 2.16545" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g1007" | |||
transform="translate(-45.905405,-6.2726063)"> | |||
<line | |||
id="line6-2-3" | |||
style="fill:none;stroke:#a8a8a8;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
y2="18.029966" | |||
x2="66.630135" | |||
y1="16.342186" | |||
x1="66.630135" /> | |||
<line | |||
id="line6-2-3-3" | |||
style="fill:none;stroke:#a8a8a8;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
y2="18.029966" | |||
x2="61.418743" | |||
y1="16.342186" | |||
x1="61.418743" /> | |||
<path | |||
sodipodi:nodetypes="ccccccccssccccccsc" | |||
id="path106-8-9" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
d="m 61.418742,18.029966 c 2.141299,0 -0.951586,0 1.441511,0 0.163266,0 0.295619,-0.132353 0.29562,-0.295619 v -1.096544 c 0,-0.163266 0.132354,-0.295618 0.29562,-0.295617 h 0.277326 c 0.163265,0 0.295618,0.132352 0.295618,0.295617 v 1.096544 c 10e-7,0.163266 0.132354,0.295619 0.29562,0.295619 h 0.27734 c 0.163266,0 0.295619,-0.132353 0.29562,-0.295619 v -1.096544 c 0,-0.163265 0.132352,-0.295617 0.295617,-0.295617 h 0.277327 c 0.16326,6e-6 0.295606,0.132357 0.295606,0.295617 v 1.096544 c 10e-7,0.163266 0.132354,0.295619 0.29562,0.295619 h 0.572944" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="m 49.106667,36.368793 v 1.392161 a 0.29562,0.29562 0 0 0 0.29562,0.295619 h 4.915769" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
id="path94-9" /> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="5.1611581" | |||
y="41.337326" | |||
id="text1040"><tspan | |||
sodipodi:role="line" | |||
id="tspan1038" | |||
x="5.1611581" | |||
y="41.337326" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">M4</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="20.233774" | |||
y="41.303471" | |||
id="text1040-4"><tspan | |||
sodipodi:role="line" | |||
id="tspan1038-5" | |||
x="20.233774" | |||
y="41.303471" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">M4</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="38.53788" | |||
y="41.303474" | |||
id="text1040-8"><tspan | |||
sodipodi:role="line" | |||
id="tspan1038-4" | |||
x="38.53788" | |||
y="41.303474" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">M4</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="17.403894" | |||
y="46.498486" | |||
id="text1072"><tspan | |||
sodipodi:role="line" | |||
id="tspan1070" | |||
x="17.403894" | |||
y="46.498486" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">M4: MULTIPLES OF 4</tspan></text> | |||
</svg> |
@@ -14,7 +14,7 @@ | |||
#include "ImpromptuModular.hpp" | |||
#include "dsp/digital.hpp" | |||
#include "PhraseSeqUtil.hpp" | |||
namespace rack_plugin_ImpromptuModular { | |||
@@ -91,6 +91,7 @@ struct BigButtonSeq : Module { | |||
PulseGenerator outLightPulse; | |||
PulseGenerator bigPulse; | |||
PulseGenerator bigLightPulse; | |||
int lightRefreshCounter; | |||
inline void toggleGate(int chan) {gates[chan][bank[chan]] ^= (((uint64_t)1) << (uint64_t)indexStep);} | |||
@@ -122,6 +123,7 @@ struct BigButtonSeq : Module { | |||
outLightPulse.reset(); | |||
bigPulse.reset(); | |||
bigLightPulse.reset(); | |||
lightRefreshCounter = 0; | |||
onReset(); | |||
} | |||
@@ -378,38 +380,49 @@ struct BigButtonSeq : Module { | |||
//********** Outputs and lights ********** | |||
// Gate outputs | |||
bool bigPulseState = bigPulse.process((float)sampleTime); | |||
bool bigLightPulseState = bigLightPulse.process((float)sampleTime); | |||
bool outPulseState = outPulse.process((float)sampleTime); | |||
bool outLightPulseState = outLightPulse.process((float)sampleTime); | |||
// Gate and light outputs | |||
for (int i = 0; i < 6; i++) { | |||
bool gate = getGate(i); | |||
bool outSignal = (((gate || (i == chan && fillPressed)) && outPulseState) || (gate && bigPulseState && i == chan)); | |||
bool outLight = (((gate || (i == chan && fillPressed)) && outLightPulseState) || (gate && bigLightPulseState && i == chan)); | |||
outputs[CHAN_OUTPUTS + i].value = outSignal ? 10.0f : 0.0f; | |||
lights[(CHAN_LIGHTS + i) * 2 + 1].setBrightnessSmooth(outLight ? 1.0f : 0.0f); | |||
lights[(CHAN_LIGHTS + i) * 2 + 0].setBrightnessSmooth(i == chan ? (1.0f - lights[(CHAN_LIGHTS + i) * 2 + 1].value) / 2.0f : 0.0f); | |||
} | |||
// Big button lights | |||
lights[BIG_LIGHT].value = bank[chan] == 1 ? 1.0f : 0.0f; | |||
lights[BIGC_LIGHT].value = bigLight; | |||
lightRefreshCounter++; | |||
if (lightRefreshCounter > displayRefreshStepSkips) { | |||
lightRefreshCounter = 0; | |||
// Gate light outputs | |||
bool bigLightPulseState = bigLightPulse.process((float)sampleTime * displayRefreshStepSkips); | |||
bool outLightPulseState = outLightPulse.process((float)sampleTime * displayRefreshStepSkips); | |||
for (int i = 0; i < 6; i++) { | |||
bool gate = getGate(i); | |||
bool outLight = (((gate || (i == chan && fillPressed)) && outLightPulseState) || (gate && bigLightPulseState && i == chan)); | |||
lights[(CHAN_LIGHTS + i) * 2 + 1].setBrightnessSmooth(outLight ? 1.0f : 0.0f, displayRefreshStepSkips); | |||
lights[(CHAN_LIGHTS + i) * 2 + 0].value = (i == chan ? (1.0f - lights[(CHAN_LIGHTS + i) * 2 + 1].value) / 2.0f : 0.0f); | |||
} | |||
// Big button lights | |||
lights[BIG_LIGHT].value = bank[chan] == 1 ? 1.0f : 0.0f; | |||
lights[BIGC_LIGHT].value = bigLight; | |||
// Metronome light | |||
lights[METRONOME_LIGHT + 1].value = metronomeLightStart; | |||
lights[METRONOME_LIGHT + 0].value = metronomeLightDiv; | |||
// Metronome light | |||
lights[METRONOME_LIGHT + 1].value = metronomeLightStart; | |||
lights[METRONOME_LIGHT + 0].value = metronomeLightDiv; | |||
if (clockIgnoreOnReset > 0l) | |||
clockIgnoreOnReset--; | |||
bigLight -= (bigLight / lightLambda) * (float)sampleTime * displayRefreshStepSkips; | |||
metronomeLightStart -= (metronomeLightStart / lightLambda) * (float)sampleTime * displayRefreshStepSkips; | |||
metronomeLightDiv -= (metronomeLightDiv / lightLambda) * (float)sampleTime * displayRefreshStepSkips; | |||
} | |||
bigLight -= (bigLight / lightLambda) * (float)sampleTime; | |||
metronomeLightStart -= (metronomeLightStart / lightLambda) * (float)sampleTime; | |||
metronomeLightDiv -= (metronomeLightDiv / lightLambda) * (float)sampleTime; | |||
clockTime += sampleTime; | |||
if (clockIgnoreOnReset > 0l) | |||
clockIgnoreOnReset--; | |||
scheduledReset = false; | |||
}// step() | |||
@@ -12,7 +12,6 @@ | |||
#include "ImpromptuModular.hpp" | |||
#include "dsp/digital.hpp" | |||
namespace rack_plugin_ImpromptuModular { | |||
@@ -186,6 +185,8 @@ struct Clocked : Module { | |||
RESET_PARAM, | |||
RUN_PARAM, | |||
ENUMS(DELAY_PARAMS, 4),// index 0 is unused | |||
// -- 0.6.9 ^^ | |||
BPMMODE_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
@@ -206,7 +207,8 @@ struct Clocked : Module { | |||
enum LightIds { | |||
RESET_LIGHT, | |||
RUN_LIGHT, | |||
ENUMS(CLK_LIGHTS, 4),// master is index 0 | |||
ENUMS(CLK_LIGHTS, 4),// master is index 0 (not used) | |||
ENUMS(BPMSYNC_LIGHT, 2),// room for GreenRed | |||
NUM_LIGHTS | |||
}; | |||
@@ -256,6 +258,10 @@ struct Clocked : Module { | |||
double extIntervalTime; | |||
double timeoutTime; | |||
long cantRunWarning;// 0 when no warning, positive downward step counter timer when warning | |||
long editingBpmMode;// 0 when no edit bpmMode, downward step counter timer when edit, negative upward when show can't edit ("--") | |||
int lightRefreshCounter; | |||
SchmittTrigger bpmModeTrigger; | |||
// called from the main thread (step() can not be called until all modules created) | |||
@@ -291,6 +297,8 @@ struct Clocked : Module { | |||
extIntervalTime = 0.0; | |||
timeoutTime = 2.0 / ppqn + 0.1; | |||
cantRunWarning = 0ul; | |||
bpmModeTrigger.reset(); | |||
lightRefreshCounter = 0; | |||
onReset(); | |||
} | |||
@@ -441,7 +449,6 @@ struct Clocked : Module { | |||
void step() override { | |||
double sampleRate = (double)engineGetSampleRate(); | |||
double sampleTime = 1.0 / sampleRate;// do this here since engineGetSampleRate() returns float | |||
long cantRunWarningInit = (long) (0.7 * engineGetSampleRate()); | |||
// Scheduled reset (just the parts that do not have a place below in rest of function) | |||
if (scheduledReset) { | |||
@@ -453,6 +460,7 @@ struct Clocked : Module { | |||
runPulse.reset(); | |||
bpmDetectTrigger.reset(); | |||
cantRunWarning = 0l; | |||
editingBpmMode = 0l; | |||
} | |||
// Run button | |||
@@ -467,7 +475,7 @@ struct Clocked : Module { | |||
} | |||
} | |||
else | |||
cantRunWarning = cantRunWarningInit; | |||
cantRunWarning = (long) (0.7 * sampleRate / displayRefreshStepSkips); | |||
} | |||
// Reset (has to be near top because it sets steps to 0, and 0 not a real step (clock section will move to 1 before reaching outputs) | |||
@@ -475,10 +483,33 @@ struct Clocked : Module { | |||
resetLight = 1.0f; | |||
resetPulse.trigger(0.001f); | |||
resetClocked(); | |||
} | |||
// BPM mode | |||
if (bpmModeTrigger.process(params[BPMMODE_PARAM].value)) { | |||
if (inputs[BPM_INPUT].active) { | |||
if (editingBpmMode != 0ul) {// force active before allow change | |||
if (bpmDetectionMode == false) { | |||
bpmDetectionMode = true; | |||
ppqn = 4; | |||
} | |||
else { | |||
if (ppqn == 4) | |||
ppqn = 8; | |||
else if (ppqn == 8) | |||
ppqn = 12; | |||
else if (ppqn == 12) | |||
ppqn = 24; | |||
else | |||
bpmDetectionMode = false; | |||
} | |||
} | |||
editingBpmMode = (long) (3.0 * sampleRate / displayRefreshStepSkips); | |||
} | |||
else | |||
editingBpmMode = (long) (-1.5 * sampleRate / displayRefreshStepSkips); | |||
} | |||
else | |||
resetLight -= (resetLight / lightLambda) * (float)sampleTime; | |||
// BPM input and knob | |||
float newMasterLength = masterLength; | |||
if (inputs[BPM_INPUT].active) { | |||
@@ -566,7 +597,7 @@ struct Clocked : Module { | |||
} | |||
if (newSwingVal != swingVal[i]) { | |||
swingVal[i] = newSwingVal; | |||
swingInfo[i] = (long) (swingInfoTime * (float)sampleRate);// trigger swing info on channel i | |||
swingInfo[i] = (long) (swingInfoTime * (float)sampleRate / displayRefreshStepSkips);// trigger swing info on channel i | |||
delayInfo[i] = 0l;// cancel delayed being displayed (if so) | |||
} | |||
if (i > 0) { | |||
@@ -577,7 +608,7 @@ struct Clocked : Module { | |||
} | |||
if (newDelayKnobIndex != delayKnobIndexes[i]) { | |||
delayKnobIndexes[i] = newDelayKnobIndex; | |||
delayInfo[i] = (long) (delayInfoTime * (float)sampleRate);// trigger delay info on channel i | |||
delayInfo[i] = (long) (delayInfoTime * (float)sampleRate / displayRefreshStepSkips);// trigger delay info on channel i | |||
swingInfo[i] = 0l;// cancel swing being displayed (if so) | |||
} | |||
} | |||
@@ -664,44 +695,60 @@ struct Clocked : Module { | |||
for (int i = 0; i < 4; i++) | |||
outputs[CLK_OUTPUTS + i].value = 0.0f; | |||
} | |||
for (int i = 0; i < 4; i++) | |||
clk[i].stepClock(); | |||
// Chaining outputs | |||
outputs[RESET_OUTPUT].value = (resetPulse.process((float)sampleTime) ? 10.0f : 0.0f); | |||
outputs[RUN_OUTPUT].value = (runPulse.process((float)sampleTime) ? 10.0f : 0.0f); | |||
outputs[BPM_OUTPUT].value = inputs[BPM_INPUT].active ? inputs[BPM_INPUT].value : log2f(1.0f / masterLength); | |||
// Reset light | |||
lights[RESET_LIGHT].value = resetLight; | |||
// Run light | |||
lights[RUN_LIGHT].value = running; | |||
// BPM light | |||
if (cantRunWarning > 0l) { | |||
bool warningFlashState = calcWarningFlash(cantRunWarning, cantRunWarningInit); | |||
lights[CLK_LIGHTS + 0].value = (warningFlashState) ? 1.0f : 0.0f; | |||
} | |||
else | |||
lights[CLK_LIGHTS + 0].value = (bpmDetectionMode && inputs[BPM_INPUT].active) ? 1.0f : 0.0f; | |||
// ratios synched lights | |||
for (int i = 1; i < 4; i++) { | |||
lights[CLK_LIGHTS + i].value = (syncRatios[i] && running) ? 1.0f: 0.0f; | |||
} | |||
lightRefreshCounter++; | |||
if (lightRefreshCounter > displayRefreshStepSkips) { | |||
lightRefreshCounter = 0; | |||
// Incr/decr all counters related to step() | |||
for (int i = 0; i < 4; i++) { | |||
clk[i].stepClock(); | |||
if (swingInfo[i] > 0) | |||
swingInfo[i]--; | |||
if (delayInfo[i] > 0) | |||
delayInfo[i]--; | |||
} | |||
if (cantRunWarning > 0l) | |||
cantRunWarning--; | |||
// Reset light | |||
lights[RESET_LIGHT].value = resetLight; | |||
resetLight -= (resetLight / lightLambda) * (float)sampleTime * displayRefreshStepSkips; | |||
// Run light | |||
lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; | |||
// BPM light | |||
bool warningFlashState = true; | |||
if (cantRunWarning > 0l) | |||
warningFlashState = calcWarningFlash(cantRunWarning, (long) (0.7 * sampleRate / displayRefreshStepSkips)); | |||
lights[BPMSYNC_LIGHT + 0].value = (bpmDetectionMode && warningFlashState && inputs[BPM_INPUT].active) ? 1.0f : 0.0f; | |||
if (editingBpmMode < 0l) | |||
lights[BPMSYNC_LIGHT + 1].value = 1.0f; | |||
else | |||
lights[BPMSYNC_LIGHT + 1].value = (bpmDetectionMode && warningFlashState && inputs[BPM_INPUT].active) ? (float)((ppqn - 4)*(ppqn - 4))/400.0f : 0.0f; | |||
// ratios synched lights | |||
for (int i = 1; i < 4; i++) { | |||
lights[CLK_LIGHTS + i].value = (syncRatios[i] && running) ? 1.0f: 0.0f; | |||
} | |||
// Incr/decr all counters related to step() | |||
for (int i = 0; i < 4; i++) { | |||
if (swingInfo[i] > 0) | |||
swingInfo[i]--; | |||
if (delayInfo[i] > 0) | |||
delayInfo[i]--; | |||
} | |||
if (cantRunWarning > 0l) | |||
cantRunWarning--; | |||
if (editingBpmMode != 0l) { | |||
if (editingBpmMode > 0l) | |||
editingBpmMode--; | |||
else | |||
editingBpmMode++; | |||
} | |||
}// lightRefreshCounter | |||
scheduledReset = false; | |||
} | |||
}// step() | |||
}; | |||
@@ -710,7 +757,7 @@ struct ClockedWidget : ModuleWidget { | |||
DynamicSVGPanel *panel; | |||
int oldExpansion; | |||
int expWidth = 60; | |||
IMPort* expPorts[5]; | |||
IMPort* expPorts[6]; | |||
struct RatioDisplayWidget : TransparentWidget { | |||
@@ -770,7 +817,18 @@ struct ClockedWidget : ModuleWidget { | |||
} | |||
} | |||
else {// BPM to display | |||
snprintf(displayStr, 4, "%3u", (unsigned) round(120.0f / module->masterLength)); | |||
if (module->editingBpmMode != 0l) { | |||
if (module->editingBpmMode > 0l) { | |||
if (!module->bpmDetectionMode) | |||
snprintf(displayStr, 4, " CV"); | |||
else | |||
snprintf(displayStr, 4, "P%2u", (unsigned) module->ppqn); | |||
} | |||
else | |||
snprintf(displayStr, 4, " --"); | |||
} | |||
else | |||
snprintf(displayStr, 4, "%3u", (unsigned)((120.0f / module->masterLength) + 0.5f)); | |||
} | |||
} | |||
displayStr[3] = 0;// more safety | |||
@@ -800,56 +858,12 @@ struct ClockedWidget : ModuleWidget { | |||
module->displayDelayNoteMode = !module->displayDelayNoteMode; | |||
} | |||
}; | |||
struct BpmDetectionItem : MenuItem { | |||
Clocked *module; | |||
void onAction(EventAction &e) override { | |||
module->bpmDetectionMode = !module->bpmDetectionMode; | |||
if (module->bpmDetectionMode && module->running) { | |||
module->running = false; | |||
module->resetClocked(); | |||
} | |||
} | |||
}; | |||
struct EmitResetItem : MenuItem { | |||
Clocked *module; | |||
void onAction(EventAction &e) override { | |||
module->emitResetOnStopRun = !module->emitResetOnStopRun; | |||
} | |||
}; | |||
struct BpmPpqnItem : MenuItem { | |||
Clocked *module; | |||
int oldPpqn = -1; | |||
void onAction(EventAction &e) override { | |||
if (module->ppqn == 4) { | |||
module->ppqn = 8; | |||
} | |||
else if (module->ppqn == 8) { | |||
module->ppqn = 24; | |||
} | |||
else { | |||
module->ppqn = 4; | |||
} | |||
} | |||
void step() override { | |||
if (oldPpqn != module->ppqn) { | |||
oldPpqn = module->ppqn; | |||
if (oldPpqn == 4) { | |||
text = "- BPM detection PPQN: <4>, 8, 24"; | |||
} | |||
else if (module->ppqn == 8) { | |||
text = "- BPM detection PPQN: 4, <8>, 24"; | |||
} | |||
else if (module->ppqn == 24) { | |||
text = "- BPM detection PPQN: 4, 8, <24>"; | |||
} | |||
else { | |||
text = "- BPM detection PPQN: *error*"; | |||
} | |||
} | |||
MenuItem::step(); | |||
} | |||
}; | |||
Menu *createContextMenu() override { | |||
Menu *menu = ModuleWidget::createContextMenu(); | |||
@@ -889,14 +903,6 @@ struct ClockedWidget : ModuleWidget { | |||
erItem->module = module; | |||
menu->addChild(erItem); | |||
BpmDetectionItem *detectItem = MenuItem::create<BpmDetectionItem>("Use BPM Detection (as opposed to BPM CV)", CHECKMARK(module->bpmDetectionMode)); | |||
detectItem->module = module; | |||
menu->addChild(detectItem); | |||
BpmPpqnItem *detect4Item = MenuItem::create<BpmPpqnItem>("PPQN", CHECKMARK(false)); | |||
detect4Item->module = module; | |||
menu->addChild(detect4Item); | |||
menu->addChild(new MenuLabel());// empty line | |||
MenuLabel *expansionLabel = new MenuLabel(); | |||
@@ -913,7 +919,7 @@ struct ClockedWidget : ModuleWidget { | |||
void step() override { | |||
if(module->expansion != oldExpansion) { | |||
if (oldExpansion!= -1 && module->expansion == 0) {// if just removed expansion panel, disconnect wires to those jacks | |||
for (int i = 0; i < 5; i++) | |||
for (int i = 0; i < 6; i++) | |||
rack::global_ui->app.gRackWidget->wireContainer->removeAllWires(expPorts[i]); | |||
} | |||
oldExpansion = module->expansion; | |||
@@ -929,11 +935,11 @@ struct ClockedWidget : ModuleWidget { | |||
// Main panel from Inkscape | |||
panel = new DynamicSVGPanel(); | |||
panel->mode = &module->panelTheme; | |||
panel->expWidth = &expWidth; | |||
panel->expWidth = &expWidth; | |||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/Clocked.svg"))); | |||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/Clocked_dark.svg"))); | |||
box.size = panel->box.size; | |||
box.size.x = box.size.x - (1 - module->expansion) * expWidth; | |||
box.size.x = box.size.x - (1 - module->expansion) * expWidth; | |||
addChild(panel); | |||
// Screws | |||
@@ -986,8 +992,6 @@ struct ClockedWidget : ModuleWidget { | |||
displayRatios[0]->module = module; | |||
displayRatios[0]->knobIndex = 0; | |||
addChild(displayRatios[0]); | |||
// BPM external pulses lock light | |||
addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(colRulerT4 + 11 + 62, rowRuler0 + 10), module, Clocked::CLK_LIGHTS + 0)); | |||
// Row 1 | |||
// Reset LED bezel and light | |||
@@ -996,8 +1000,9 @@ struct ClockedWidget : ModuleWidget { | |||
// Run LED bezel and light | |||
addParam(ParamWidget::create<LEDBezel>(Vec(colRulerT1 + offsetLEDbezel, rowRuler1 + offsetLEDbezel), module, Clocked::RUN_PARAM, 0.0f, 1.0f, 0.0f)); | |||
addChild(ModuleLightWidget::create<MuteLight<GreenLight>>(Vec(colRulerT1 + offsetLEDbezel + offsetLEDbezelLight, rowRuler1 + offsetLEDbezel + offsetLEDbezelLight), module, Clocked::RUN_LIGHT)); | |||
// PW master input | |||
addInput(createDynamicPort<IMPort>(Vec(colRulerT2, rowRuler1), Port::INPUT, module, Clocked::PW_INPUTS + 0, &module->panelTheme)); | |||
// BPM mode and light | |||
addParam(ParamWidget::create<TL1105>(Vec(colRulerT2 + offsetTL1105, rowRuler1 + offsetTL1105), module, Clocked::BPMMODE_PARAM, 0.0f, 1.0f, 0.0f)); | |||
addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(Vec(colRulerM1 + 62, rowRuler1 + offsetMediumLight), module, Clocked::BPMSYNC_LIGHT)); | |||
// Swing master knob | |||
addParam(createDynamicParam<IMSmallKnob>(Vec(colRulerT3 + offsetIMSmallKnob, rowRuler1 + offsetIMSmallKnob), module, Clocked::SWING_PARAMS + 0, -1.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// PW master knob | |||
@@ -1040,15 +1045,15 @@ struct ClockedWidget : ModuleWidget { | |||
addOutput(createDynamicPort<IMPort>(Vec(colRulerT5, rowRuler5), Port::OUTPUT, module, Clocked::CLK_OUTPUTS + 3, &module->panelTheme)); | |||
// Expansion module | |||
static const int rowRulerExpTop = 65; | |||
static const int rowSpacingExp = 60; | |||
static const int rowRulerExpTop = 60; | |||
static const int rowSpacingExp = 50; | |||
static const int colRulerExp = 497 - 30 -150;// Clocked is (2+10)HP less than PS32 | |||
addInput(expPorts[0] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, Clocked::PW_INPUTS + 1, &module->panelTheme)); | |||
addInput(expPorts[1] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, Clocked::PW_INPUTS + 2, &module->panelTheme)); | |||
addInput(expPorts[2] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, Clocked::SWING_INPUTS + 0, &module->panelTheme)); | |||
addInput(expPorts[3] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, Clocked::SWING_INPUTS + 1, &module->panelTheme)); | |||
addInput(expPorts[4] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, Clocked::SWING_INPUTS + 2, &module->panelTheme)); | |||
addInput(expPorts[0] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, Clocked::PW_INPUTS + 0, &module->panelTheme)); | |||
addInput(expPorts[1] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, Clocked::PW_INPUTS + 1, &module->panelTheme)); | |||
addInput(expPorts[2] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, Clocked::PW_INPUTS + 2, &module->panelTheme)); | |||
addInput(expPorts[3] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, Clocked::SWING_INPUTS + 0, &module->panelTheme)); | |||
addInput(expPorts[4] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, Clocked::SWING_INPUTS + 1, &module->panelTheme)); | |||
addInput(expPorts[5] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 5), Port::INPUT, module, Clocked::SWING_INPUTS + 2, &module->panelTheme)); | |||
} | |||
}; | |||
@@ -1063,6 +1068,10 @@ RACK_PLUGIN_MODEL_INIT(ImpromptuModular, Clocked) { | |||
/*CHANGE LOG | |||
0.6.10: | |||
add ppqn setting of 12 | |||
move master PW to expansion panel and move BPM mode from right-click menu to main pannel button | |||
0.6.9: | |||
new approach to BPM Detection (all slaves must enable Use BPM Detect if master does, and same ppqn) | |||
choice of 4, 8, 24 PPQN when using BPM detection | |||
@@ -48,8 +48,8 @@ struct LadderFilter { | |||
// From Fundamental VCO.cpp | |||
//template <int OVERSAMPLE, int QUALITY> | |||
static const int OVERSAMPLE = 16; | |||
static const int QUALITY = 16; | |||
static const int OVERSAMPLE = 8; | |||
static const int QUALITY = 8; | |||
struct VoltageControlledOscillator { | |||
bool analog = false; | |||
bool soft = false; | |||
@@ -7,6 +7,7 @@ | |||
//See ./res/fonts/ for font licenses | |||
//*********************************************************************************************** | |||
#include "ImpromptuModular.hpp" | |||
RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, Tact); | |||
@@ -27,8 +28,8 @@ RACK_PLUGIN_INIT(ImpromptuModular) { | |||
RACK_PLUGIN_INIT_WEBSITE("https://github.com/MarcBoule/ImpromptuModular"); | |||
RACK_PLUGIN_INIT_MANUAL("https://github.com/MarcBoule/ImpromptuModular"); | |||
RACK_PLUGIN_INIT_VERSION("0.6.11"); | |||
//p->addModel(modelEngTest1); | |||
RACK_PLUGIN_MODEL_ADD(ImpromptuModular, Tact); | |||
RACK_PLUGIN_MODEL_ADD(ImpromptuModular, TwelveKey); | |||
RACK_PLUGIN_MODEL_ADD(ImpromptuModular, Clocked); | |||
@@ -142,126 +143,12 @@ NVGcolor prepareDisplay(NVGcontext *vg, Rect *box) { | |||
return textColor; | |||
} | |||
int moveIndex(int index, int indexNext, int numSteps) { | |||
if (indexNext < 0) | |||
index = numSteps - 1; | |||
else | |||
{ | |||
if (indexNext - index >= 0) { // if moving right or same place | |||
if (indexNext >= numSteps) | |||
index = 0; | |||
else | |||
index = indexNext; | |||
} | |||
else { // moving left | |||
if (indexNext >= numSteps) | |||
index = numSteps - 1; | |||
else | |||
index = indexNext; | |||
} | |||
} | |||
return index; | |||
} | |||
bool moveIndexRunMode(int* index, int numSteps, int runMode, int* history) { | |||
bool crossBoundary = false; | |||
int numRuns;// for FWx | |||
switch (runMode) { | |||
case MODE_REV :// reverse; history base is 1000 (not needed) | |||
(*history) = 1000; | |||
(*index)--; | |||
if ((*index) < 0) { | |||
(*index) = numSteps - 1; | |||
crossBoundary = true; | |||
} | |||
break; | |||
case MODE_PPG :// forward-reverse; history base is 2000 | |||
if ((*history) != 2000 && (*history) != 2001) // 2000 means going forward, 2001 means going reverse | |||
(*history) = 2000; | |||
if ((*history) == 2000) {// forward phase | |||
(*index)++; | |||
if ((*index) >= numSteps) { | |||
(*index) = numSteps - 1; | |||
(*history) = 2001; | |||
} | |||
} | |||
else {// it is 2001; reverse phase | |||
(*index)--; | |||
if ((*index) < 0) { | |||
(*index) = 0; | |||
(*history) = 2000; | |||
crossBoundary = true; | |||
} | |||
} | |||
break; | |||
case MODE_BRN :// brownian random; history base is 3000 | |||
if ( (*history) < 3000 || ((*history) > (3000 + numSteps)) ) | |||
(*history) = 3000 + numSteps; | |||
(*index) += (randomu32() % 3) - 1; | |||
if ((*index) >= numSteps) { | |||
(*index) = 0; | |||
} | |||
if ((*index) < 0) { | |||
(*index) = numSteps - 1; | |||
} | |||
(*history)--; | |||
if ((*history) <= 3000) { | |||
(*history) = 3000 + numSteps; | |||
crossBoundary = true; | |||
} | |||
break; | |||
case MODE_RND :// random; history base is 4000 | |||
if ( (*history) < 4000 || ((*history) > (4000 + numSteps)) ) | |||
(*history) = 4000 + numSteps; | |||
(*index) = (randomu32() % numSteps) ; | |||
(*history)--; | |||
if ((*history) <= 4000) { | |||
(*history) = 4000 + numSteps; | |||
crossBoundary = true; | |||
} | |||
break; | |||
case MODE_FW2 :// forward twice | |||
case MODE_FW3 :// forward three times | |||
case MODE_FW4 :// forward four times | |||
numRuns = 5002 + runMode - MODE_FW2; | |||
if ( (*history) < 5000 || (*history) >= numRuns ) // 5000 means first pass, 5001 means 2nd pass, etc... | |||
(*history) = 5000; | |||
(*index)++; | |||
if ((*index) >= numSteps) { | |||
(*index) = 0; | |||
(*history)++; | |||
if ((*history) >= numRuns) { | |||
(*history) = 5000; | |||
crossBoundary = true; | |||
} | |||
} | |||
break; | |||
default :// MODE_FWD forward; history base is 0 (not needed) | |||
(*history) = 0; | |||
(*index)++; | |||
if ((*index) >= numSteps) { | |||
(*index) = 0; | |||
crossBoundary = true; | |||
} | |||
} | |||
return crossBoundary; | |||
} | |||
bool calcWarningFlash(long count, long countInit) { | |||
bool warningFlashState = true; | |||
if (count > (countInit * 2l / 4l) && count < (countInit * 3l / 4l)) | |||
warningFlashState = false; | |||
else if (count < (countInit * 1l / 4l)) | |||
warningFlashState = false; | |||
return warningFlashState; | |||
} | |||
bool warningFlashState = true; | |||
if (count > (countInit * 2l / 4l) && count < (countInit * 3l / 4l)) | |||
warningFlashState = false; | |||
else if (count < (countInit * 1l / 4l)) | |||
warningFlashState = false; | |||
return warningFlashState; | |||
} | |||
@@ -13,6 +13,7 @@ | |||
#include "rack.hpp" | |||
#include "IMWidgets.hpp" | |||
#include "dsp/digital.hpp" | |||
using namespace rack; | |||
@@ -23,12 +24,8 @@ static const float lightLambda = 0.075f; | |||
static const std::string lightPanelID = "Classic"; | |||
static const std::string darkPanelID = "Dark-valor"; | |||
static const std::string expansionMenuLabel = "Extra CVs (requires +4HP to the right!)"; | |||
enum RunModeIds {MODE_FWD, MODE_REV, MODE_PPG, MODE_BRN, MODE_RND, MODE_FW2, MODE_FW3, MODE_FW4, NUM_MODES}; | |||
static const std::string modeLabels[NUM_MODES]={"FWD","REV","PPG","BRN","RND","FW2","FW3","FW4"}; | |||
enum GateModeIds {GATE_24, GATE_34, GATE_44, GATE_14, GATE_TRIG, GATE_DUO, GATE_DU1, GATE_DU2, | |||
GATE_TRIPLET, GATE_TRIP1, GATE_TRIP2, GATE_TRIP3, GATE_TRIP4, GATE_TRIP5, GATE_TRIP6, NUM_GATES}; | |||
static const std::string gateLabels[NUM_GATES]={"2/4","3/4","4/4","1/4","TRG","DUO","DU1","DU2", | |||
"TRP","TR1","TR2","TR3","TR4","TR5","TR6"}; | |||
static const int displayRefreshStepSkips = 200; | |||
// Constants for displaying notes | |||
@@ -220,7 +217,7 @@ struct GiantLight2 : BASE { | |||
} | |||
}; | |||
// Other | |||
// Other widgets | |||
struct InvisibleKey : MomentarySwitch { | |||
InvisibleKey() { | |||
@@ -230,7 +227,7 @@ struct InvisibleKey : MomentarySwitch { | |||
struct InvisibleKeySmall : MomentarySwitch { | |||
InvisibleKeySmall() { | |||
box.size = Vec(23, 50); | |||
box.size = Vec(23, 38); | |||
} | |||
void onMouseDown(EventMouseDown &e) override; | |||
void onMouseUp(EventMouseUp &e) override; | |||
@@ -249,9 +246,37 @@ struct ScrewHole : TransparentWidget { | |||
}; | |||
// Other | |||
struct HoldDetect { | |||
long modeHoldDetect;// 0 when not detecting, downward counter when detecting | |||
void reset() { | |||
modeHoldDetect = 0l; | |||
} | |||
void start(long startValue) { | |||
modeHoldDetect = startValue; | |||
} | |||
bool process(float paramValue) { | |||
bool ret = false; | |||
if (modeHoldDetect > 0l) { | |||
if (paramValue < 0.5f) | |||
modeHoldDetect = 0l; | |||
else { | |||
if (modeHoldDetect == 1l) { | |||
ret = true; | |||
} | |||
modeHoldDetect--; | |||
} | |||
} | |||
return ret; | |||
} | |||
}; | |||
NVGcolor prepareDisplay(NVGcontext *vg, Rect *box); | |||
int moveIndex(int index, int indexNext, int numSteps); | |||
bool moveIndexRunMode(int* index, int numSteps, int runMode, int* history); | |||
bool calcWarningFlash(long count, long countInit); | |||
#endif |
@@ -26,7 +26,6 @@ https://github.com/IohannRabeson/VCVRack-Simple/commit/2d33e97d2e344d2926548a0b9 | |||
#include "ImpromptuModular.hpp" | |||
#include "dsp/digital.hpp" | |||
#include "midifile/MidiFile.h" | |||
#include "osdialog.h" | |||
#include <iostream> | |||
@@ -0,0 +1,133 @@ | |||
//*********************************************************************************************** | |||
//Impromptu Modular: Modules for VCV Rack by Marc Boulé | |||
//*********************************************************************************************** | |||
#include "PhraseSeqUtil.hpp" | |||
namespace rack_plugin_ImpromptuModular { | |||
int moveIndex(int index, int indexNext, int numSteps) { | |||
if (indexNext < 0) | |||
index = numSteps - 1; | |||
else | |||
{ | |||
if (indexNext - index >= 0) { // if moving right or same place | |||
if (indexNext >= numSteps) | |||
index = 0; | |||
else | |||
index = indexNext; | |||
} | |||
else { // moving left | |||
if (indexNext >= numSteps) | |||
index = numSteps - 1; | |||
else | |||
index = indexNext; | |||
} | |||
} | |||
return index; | |||
} | |||
bool moveIndexRunMode(int* index, int numSteps, int runMode, int* history) { | |||
bool crossBoundary = false; | |||
int numRuns;// for FWx | |||
switch (runMode) { | |||
case MODE_REV :// reverse; history base is 1000 (not needed) | |||
(*history) = 1000; | |||
(*index)--; | |||
if ((*index) < 0) { | |||
(*index) = numSteps - 1; | |||
crossBoundary = true; | |||
} | |||
break; | |||
case MODE_PPG :// forward-reverse; history base is 2000 | |||
if ((*history) != 2000 && (*history) != 2001) // 2000 means going forward, 2001 means going reverse | |||
(*history) = 2000; | |||
if ((*history) == 2000) {// forward phase | |||
(*index)++; | |||
if ((*index) >= numSteps) { | |||
(*index) = numSteps - 1; | |||
(*history) = 2001; | |||
} | |||
} | |||
else {// it is 2001; reverse phase | |||
(*index)--; | |||
if ((*index) < 0) { | |||
(*index) = 0; | |||
(*history) = 2000; | |||
crossBoundary = true; | |||
} | |||
} | |||
break; | |||
case MODE_BRN :// brownian random; history base is 3000 | |||
if ( (*history) < 3000 || ((*history) > (3000 + numSteps)) ) | |||
(*history) = 3000 + numSteps; | |||
(*index) += (randomu32() % 3) - 1; | |||
if ((*index) >= numSteps) { | |||
(*index) = 0; | |||
} | |||
if ((*index) < 0) { | |||
(*index) = numSteps - 1; | |||
} | |||
(*history)--; | |||
if ((*history) <= 3000) { | |||
(*history) = 3000 + numSteps; | |||
crossBoundary = true; | |||
} | |||
break; | |||
case MODE_RND :// random; history base is 4000 | |||
if ( (*history) < 4000 || ((*history) > (4000 + numSteps)) ) | |||
(*history) = 4000 + numSteps; | |||
(*index) = (randomu32() % numSteps) ; | |||
(*history)--; | |||
if ((*history) <= 4000) { | |||
(*history) = 4000 + numSteps; | |||
crossBoundary = true; | |||
} | |||
break; | |||
case MODE_FW2 :// forward twice | |||
case MODE_FW3 :// forward three times | |||
case MODE_FW4 :// forward four times | |||
numRuns = 5002 + runMode - MODE_FW2; | |||
if ( (*history) < 5000 || (*history) >= numRuns ) // 5000 means first pass, 5001 means 2nd pass, etc... | |||
(*history) = 5000; | |||
(*index)++; | |||
if ((*index) >= numSteps) { | |||
(*index) = 0; | |||
(*history)++; | |||
if ((*history) >= numRuns) { | |||
(*history) = 5000; | |||
crossBoundary = true; | |||
} | |||
} | |||
break; | |||
default :// MODE_FWD forward; history base is 0 (not needed) | |||
(*history) = 0; | |||
(*index)++; | |||
if ((*index) >= numSteps) { | |||
(*index) = 0; | |||
crossBoundary = true; | |||
} | |||
} | |||
return crossBoundary; | |||
} | |||
int keyIndexToGateMode(int keyIndex, int pulsesPerStep) { | |||
if (pulsesPerStep == 4 && (keyIndex == 1 || keyIndex == 3 || keyIndex == 6 || keyIndex == 8 || keyIndex == 10)) | |||
return -1; | |||
if (pulsesPerStep == 6 && (keyIndex == 0 || keyIndex == 4 || keyIndex == 7 || keyIndex == 9)) | |||
return -1; | |||
return keyIndex;// keyLight index now matches gate modes, so no mapping table needed anymore | |||
} | |||
} // namespace rack_plugin_ImpromptuModular |
@@ -0,0 +1,113 @@ | |||
//*********************************************************************************************** | |||
//Impromptu Modular: Modules for VCV Rack by Marc Boulé | |||
//*********************************************************************************************** | |||
#include "dsp/digital.hpp" | |||
using namespace rack; | |||
namespace rack_plugin_ImpromptuModular { | |||
// General constants | |||
enum RunModeIds {MODE_FWD, MODE_REV, MODE_PPG, MODE_BRN, MODE_RND, MODE_FW2, MODE_FW3, MODE_FW4, NUM_MODES}; | |||
static const std::string modeLabels[NUM_MODES]={"FWD","REV","PPG","BRN","RND","FW2","FW3","FW4"}; | |||
static const int NUM_GATES = 12; | |||
static const uint32_t advGateHitMask[NUM_GATES] = | |||
{0x00003F, 0x0F0F0F, 0x000FFF, 0x0F0F00, 0x03FFFF, 0xFFFFFF, 0x00000F, 0x03F03F, 0x000F00, 0x03F000, 0x0F0000, 0}; | |||
// 25% TRI 50% T23 75% FUL TR1 DUO TR2 D2 TR3 TRIG | |||
enum AttributeBitMasks {ATT_MSK_GATE1 = 0x01, ATT_MSK_GATE1P = 0x02, ATT_MSK_GATE2 = 0x04, ATT_MSK_SLIDE = 0x08, ATT_MSK_TIED = 0x10};// 5 bits | |||
static const int ATT_MSK_GATE1MODE = 0x01E0;// 4 bits | |||
static const int gate1ModeShift = 5; | |||
static const int ATT_MSK_GATE2MODE = 0x1E00;// 4 bits | |||
static const int gate2ModeShift = 9; | |||
// Inline methods | |||
inline bool getGate1a(int attribute) {return (attribute & ATT_MSK_GATE1) != 0;} | |||
inline bool getGate1Pa(int attribute) {return (attribute & ATT_MSK_GATE1P) != 0;} | |||
inline bool getGate2a(int attribute) {return (attribute & ATT_MSK_GATE2) != 0;} | |||
inline bool getSlideA(int attribute) {return (attribute & ATT_MSK_SLIDE) != 0;} | |||
inline bool getTiedA(int attribute) {return (attribute & ATT_MSK_TIED) != 0;} | |||
inline int getGate1aMode(int attribute) {return (attribute & ATT_MSK_GATE1MODE) >> gate1ModeShift;} | |||
inline int getGate2aMode(int attribute) {return (attribute & ATT_MSK_GATE2MODE) >> gate2ModeShift;} | |||
inline void setGate1a(int *attribute, bool gate1State) {(*attribute) &= ~ATT_MSK_GATE1; if (gate1State) (*attribute) |= ATT_MSK_GATE1;} | |||
inline void setGate1Pa(int *attribute, bool gate1PState) {(*attribute) &= ~ATT_MSK_GATE1P; if (gate1PState) (*attribute) |= ATT_MSK_GATE1P;} | |||
inline void setGate2a(int *attribute, bool gate2State) {(*attribute) &= ~ATT_MSK_GATE2; if (gate2State) (*attribute) |= ATT_MSK_GATE2;} | |||
inline void setSlideA(int *attribute, bool slideState) {(*attribute) &= ~ATT_MSK_SLIDE; if (slideState) (*attribute) |= ATT_MSK_SLIDE;} | |||
inline void setTiedA(int *attribute, bool tiedState) {(*attribute) &= ~ATT_MSK_TIED; if (tiedState) (*attribute) |= ATT_MSK_TIED;} | |||
inline void toggleGate1a(int *attribute) {(*attribute) ^= ATT_MSK_GATE1;} | |||
inline void toggleGate1Pa(int *attribute) {(*attribute) ^= ATT_MSK_GATE1P;} | |||
inline void toggleGate2a(int *attribute) {(*attribute) ^= ATT_MSK_GATE2;} | |||
inline void toggleSlideA(int *attribute) {(*attribute) ^= ATT_MSK_SLIDE;} | |||
inline void toggleTiedA(int *attribute) {(*attribute) ^= ATT_MSK_TIED;} | |||
inline int ppsToIndex(int pulsesPerStep) {// map 1,4,6,12,24, to 0,1,2,3,4 | |||
if (pulsesPerStep == 1) return 0; | |||
if (pulsesPerStep == 4) return 1; | |||
if (pulsesPerStep == 6) return 2; | |||
if (pulsesPerStep == 12) return 3; | |||
return 4; | |||
} | |||
inline int indexToPps(int index) {// inverse map of ppsToIndex() | |||
index = clamp(index, 0, 4); | |||
if (index == 0) return 1; | |||
if (index == 1) return 4; | |||
if (index == 2) return 6; | |||
if (index == 3) return 12; | |||
return 24; | |||
} | |||
inline bool calcGate(int gateCode, SchmittTrigger clockTrigger, unsigned long clockStep, float sampleRate) { | |||
if (gateCode < 2) | |||
return gateCode == 1; | |||
if (gateCode == 2) | |||
return clockTrigger.isHigh(); | |||
return clockStep < (unsigned long) (sampleRate * 0.001f); | |||
} | |||
inline int getAdvGate(int ppqnCount, int pulsesPerStep, int gateMode) { | |||
if (gateMode == 11) | |||
return ppqnCount == 0 ? 3 : 0; | |||
uint32_t shiftAmt = ppqnCount * (24 / pulsesPerStep); | |||
return (int)((advGateHitMask[gateMode] >> shiftAmt) & (uint32_t)0x1); | |||
} | |||
inline int calcGate1Code(int attribute, int ppqnCount, int pulsesPerStep, float randKnob) { | |||
// -1 = gate off for whole step, 0 = gate off for current ppqn, 1 = gate on, 2 = clock high, 3 = trigger | |||
if (ppqnCount == 0 && getGate1Pa(attribute) && !(randomUniform() < randKnob))// randomUniform is [0.0, 1.0), see include/util/common.hpp | |||
return -1;// must do this first in this method since it will kill rest of step if prob turns off the step | |||
if (!getGate1a(attribute)) | |||
return 0; | |||
if (pulsesPerStep == 1) | |||
return 2;// clock high | |||
return getAdvGate(ppqnCount, pulsesPerStep, getGate1aMode(attribute)); | |||
} | |||
inline int calcGate2Code(int attribute, int ppqnCount, int pulsesPerStep) { | |||
// 0 = gate off, 1 = clock high, 2 = trigger, 3 = gate on | |||
if (!getGate2a(attribute)) | |||
return 0; | |||
if (pulsesPerStep == 1) | |||
return 2;// clock high | |||
return getAdvGate(ppqnCount, pulsesPerStep, getGate2aMode(attribute)); | |||
} | |||
inline int gateModeToKeyLightIndex(int attribute, bool isGate1) {// keyLight index now matches gate modes, so no mapping table needed anymore | |||
return isGate1 ? getGate1aMode(attribute) : getGate2aMode(attribute); | |||
} | |||
// Other methods (code in PhraseSeqUtil.cpp) | |||
int moveIndex(int index, int indexNext, int numSteps); | |||
bool moveIndexRunMode(int* index, int numSteps, int runMode, int* history); | |||
int keyIndexToGateMode(int keyIndex, int pulsesPerStep); | |||
} // namespace rack_plugin_ImpromptuModular |
@@ -15,7 +15,7 @@ | |||
#include "ImpromptuModular.hpp" | |||
#include "FundamentalUtil.hpp" | |||
#include "PhraseSeqUtil.hpp" | |||
namespace rack_plugin_ImpromptuModular { | |||
struct SemiModularSynth : Module { | |||
@@ -223,6 +223,7 @@ struct SemiModularSynth : Module { | |||
long tiedWarning;// 0 when no warning, positive downward step counter timer when warning | |||
int sequenceKnob = 0; | |||
bool gate1RandomEnable; | |||
int lightRefreshCounter; | |||
static constexpr float EDIT_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget | |||
bool editingSequence; | |||
@@ -320,6 +321,7 @@ struct SemiModularSynth : Module { | |||
editingSequence = EDIT_PARAM_INIT_VALUE > 0.5f; | |||
editingSequenceLast = editingSequence; | |||
resetOnRun = false; | |||
lightRefreshCounter = 0; | |||
// VCO | |||
// none | |||
@@ -577,14 +579,14 @@ struct SemiModularSynth : Module { | |||
// Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() | |||
void step() override { | |||
float sampleRate = engineGetSampleRate(); | |||
// SEQUENCER | |||
static const float gateTime = 0.4f;// seconds | |||
static const float copyPasteInfoTime = 0.5f;// seconds | |||
static const float editLengthTime = 2.0f;// seconds | |||
static const float tiedWarningTime = 0.7f;// seconds | |||
long tiedWarningInit = (long) (tiedWarningTime * engineGetSampleRate()); | |||
//********** Buttons, knobs, switches and inputs ********** | |||
@@ -635,7 +637,7 @@ struct SemiModularSynth : Module { | |||
// Copy button | |||
if (copyTrigger.process(params[COPY_PARAM].value)) { | |||
if (editingSequence) { | |||
infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); | |||
infoCopyPaste = (long) (copyPasteInfoTime * sampleRate / displayRefreshStepSkips); | |||
//CPinfo must be set to 0 for copy/paste all, and 0x1ii for copy/paste 4 at pos ii, 0x2ii for copy/paste 8 at 0xii | |||
int sStart = stepIndexEdit; | |||
int sCount = 16; | |||
@@ -661,7 +663,7 @@ struct SemiModularSynth : Module { | |||
// Paste button | |||
if (pasteTrigger.process(params[PASTE_PARAM].value)) { | |||
if (editingSequence) { | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * sampleRate / displayRefreshStepSkips); | |||
int sStart = ((countCP == 16) ? 0 : stepIndexEdit); | |||
int sCount = countCP; | |||
for (int i = 0, s = sStart; i < countCP; i++, s++) { | |||
@@ -685,7 +687,7 @@ struct SemiModularSynth : Module { | |||
if (editingLength > 0ul) | |||
editingLength = 0ul;// allow user to quickly leave editing mode when re-press | |||
else | |||
editingLength = (unsigned long) (editLengthTime * engineGetSampleRate()); | |||
editingLength = (unsigned long) (editLengthTime * sampleRate / displayRefreshStepSkips); | |||
displayState = DISP_NORMAL; | |||
} | |||
@@ -695,11 +697,11 @@ struct SemiModularSynth : Module { | |||
if (writeTrig) { | |||
if (editingSequence) { | |||
if (getTied(sequence,stepIndexEdit)) | |||
tiedWarning = tiedWarningInit; | |||
tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips); | |||
else { | |||
cv[sequence][stepIndexEdit] = inputs[CV_INPUT].value; | |||
applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); | |||
editingGate = (unsigned long) (gateTime * engineGetSampleRate()); | |||
editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips); | |||
editingGateCV = cv[sequence][stepIndexEdit]; | |||
editingGateKeyLight = -1; | |||
// Autostep (after grab all active inputs) | |||
@@ -721,7 +723,7 @@ struct SemiModularSynth : Module { | |||
} | |||
if (delta != 0) { | |||
if (editingLength > 0ul) { | |||
editingLength = (unsigned long) (editLengthTime * engineGetSampleRate());// restart editing length timer | |||
editingLength = (unsigned long) (editLengthTime * sampleRate / displayRefreshStepSkips);// restart editing length timer | |||
if (editingSequence) { | |||
lengths[sequence] += delta; | |||
if (lengths[sequence] > 16) lengths[sequence] = 16; | |||
@@ -742,7 +744,7 @@ struct SemiModularSynth : Module { | |||
stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + delta, 16);//lengths[sequence]);// Commented for full edit capabilities | |||
if (!getTied(sequence,stepIndexEdit)) {// play if non-tied step | |||
if (!writeTrig) {// in case autostep when simultaneous writeCV and stepCV (keep what was done in Write Input block above) | |||
editingGate = (unsigned long) (gateTime * engineGetSampleRate()); | |||
editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips); | |||
editingGateCV = cv[sequence][stepIndexEdit]; | |||
editingGateKeyLight = -1; | |||
} | |||
@@ -785,7 +787,7 @@ struct SemiModularSynth : Module { | |||
if (deltaKnob != 0) { | |||
if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example) | |||
if (editingLength > 0ul) { | |||
editingLength = (unsigned long) (editLengthTime * engineGetSampleRate());// restart editing length timer | |||
editingLength = (unsigned long) (editLengthTime * sampleRate / displayRefreshStepSkips);// restart editing length timer | |||
if (editingSequence) { | |||
lengths[sequence] += deltaKnob; | |||
if (lengths[sequence] > 16) lengths[sequence] = 16 ; | |||
@@ -842,8 +844,6 @@ struct SemiModularSynth : Module { | |||
sequence += deltaKnob; | |||
if (sequence < 0) sequence = 0; | |||
if (sequence >= 16) sequence = (16 - 1); | |||
//if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities | |||
//stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities | |||
} | |||
} | |||
else { | |||
@@ -867,7 +867,7 @@ struct SemiModularSynth : Module { | |||
if (newOct >= 0 && newOct <= 6) { | |||
if (editingSequence) { | |||
if (getTied(sequence,stepIndexEdit)) | |||
tiedWarning = tiedWarningInit; | |||
tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips); | |||
else { | |||
float newCV = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages | |||
newCV = newCV - floor(newCV) + (float) (newOct - 3); | |||
@@ -875,7 +875,7 @@ struct SemiModularSynth : Module { | |||
cv[sequence][stepIndexEdit] = newCV; | |||
applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); | |||
} | |||
editingGate = (unsigned long) (gateTime * engineGetSampleRate()); | |||
editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips); | |||
editingGateCV = cv[sequence][stepIndexEdit]; | |||
editingGateKeyLight = -1; | |||
} | |||
@@ -890,12 +890,12 @@ struct SemiModularSynth : Module { | |||
if (params[KEY_PARAMS + i].value > 1.5f) | |||
stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 16); | |||
else | |||
tiedWarning = tiedWarningInit; | |||
tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips); | |||
} | |||
else { | |||
cv[sequence][stepIndexEdit] = floor(cv[sequence][stepIndexEdit]) + ((float) i) / 12.0f; | |||
applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); | |||
editingGate = (unsigned long) (gateTime * engineGetSampleRate()); | |||
editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips); | |||
editingGateCV = cv[sequence][stepIndexEdit]; | |||
editingGateKeyLight = -1; | |||
if (params[KEY_PARAMS + i].value > 1.5f) { | |||
@@ -911,17 +911,14 @@ struct SemiModularSynth : Module { | |||
// Gate1, Gate1Prob, Gate2, Slide and Tied buttons | |||
if (gate1Trigger.process(params[GATE1_PARAM].value)) { | |||
if (editingSequence) { | |||
if (getTied(sequence,stepIndexEdit)) | |||
tiedWarning = tiedWarningInit; | |||
else | |||
attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE1; | |||
attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE1; | |||
} | |||
displayState = DISP_NORMAL; | |||
} | |||
if (gate1ProbTrigger.process(params[GATE1_PROB_PARAM].value)) { | |||
if (editingSequence) { | |||
if (getTied(sequence,stepIndexEdit)) | |||
tiedWarning = tiedWarningInit; | |||
tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips); | |||
else | |||
attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE1P; | |||
} | |||
@@ -929,17 +926,14 @@ struct SemiModularSynth : Module { | |||
} | |||
if (gate2Trigger.process(params[GATE2_PARAM].value)) { | |||
if (editingSequence) { | |||
if (getTied(sequence,stepIndexEdit)) | |||
tiedWarning = tiedWarningInit; | |||
else | |||
attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE2; | |||
attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE2; | |||
} | |||
displayState = DISP_NORMAL; | |||
} | |||
if (slideTrigger.process(params[SLIDE_BTN_PARAM].value)) { | |||
if (editingSequence) { | |||
if (getTied(sequence,stepIndexEdit)) | |||
tiedWarning = tiedWarningInit; | |||
tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips); | |||
else | |||
attributes[sequence][stepIndexEdit] ^= ATT_MSK_SLIDE; | |||
} | |||
@@ -1005,8 +999,6 @@ struct SemiModularSynth : Module { | |||
displayState = DISP_NORMAL; | |||
clockTrigger.reset(); | |||
} | |||
else | |||
resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); | |||
//********** Outputs and lights ********** | |||
@@ -1027,135 +1019,142 @@ struct SemiModularSynth : Module { | |||
outputs[GATE1_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; | |||
outputs[GATE2_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; | |||
} | |||
if (slideStepsRemain > 0ul) | |||
slideStepsRemain--; | |||
// Step/phrase lights | |||
if (infoCopyPaste != 0l) { | |||
for (int i = 0; i < 16; i++) { | |||
if ( (i >= stepIndexEdit && i < (stepIndexEdit + countCP)) || (countCP == 16) ) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.5f;// Green when copy interval | |||
else | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.0f; // Green (nothing) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f;// Red (nothing) | |||
} | |||
} | |||
else { | |||
for (int i = 0; i < 16; i++) { | |||
if (editingLength > 0ul) { | |||
// Length (green) | |||
if (editingSequence) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((i < lengths[sequence]) ? 0.5f : 0.0f); | |||
else | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((i < phrases) ? 0.5f : 0.0f); | |||
// Nothing (red) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f; | |||
} | |||
else { | |||
// Run cursor (green) | |||
if (editingSequence) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((running && (i == stepIndexRun)) ? 1.0f : 0.0f); | |||
else { | |||
float green = ((running && (i == phraseIndexRun)) ? 1.0f : 0.0f); | |||
green += ((running && (i == stepIndexRun) && i != phraseIndexEdit) ? 0.1f : 0.0f); | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = clamp(green, 0.0f, 1.0f); | |||
} | |||
// Edit cursor (red) | |||
if (editingSequence) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = (i == stepIndexEdit ? 1.0f : 0.0f); | |||
lightRefreshCounter++; | |||
if (lightRefreshCounter > displayRefreshStepSkips) { | |||
lightRefreshCounter = 0; | |||
// Step/phrase lights | |||
if (infoCopyPaste != 0l) { | |||
for (int i = 0; i < 16; i++) { | |||
if ( (i >= stepIndexEdit && i < (stepIndexEdit + countCP)) || (countCP == 16) ) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.5f;// Green when copy interval | |||
else | |||
lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = (i == phraseIndexEdit ? 1.0f : 0.0f); | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.0f; // Green (nothing) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f;// Red (nothing) | |||
} | |||
} | |||
} | |||
// Octave lights | |||
float octCV = 0.0f; | |||
if (editingSequence) | |||
octCV = cv[sequence][stepIndexEdit]; | |||
else | |||
octCV = cv[phrase[phraseIndexEdit]][stepIndexRun]; | |||
int octLightIndex = (int) floor(octCV + 3.0f); | |||
for (int i = 0; i < 7; i++) { | |||
if (!editingSequence && (!attached || !running))// no oct lights when song mode and either (detached [1] or stopped [2]) | |||
// [1] makes no sense, can't mod steps and stepping though seq that may not be playing | |||
// [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display | |||
lights[OCTAVE_LIGHTS + i].value = 0.0f; | |||
else { | |||
if (tiedWarning > 0l) { | |||
bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); | |||
lights[OCTAVE_LIGHTS + i].value = (warningFlashState && (i == (6 - octLightIndex))) ? 1.0f : 0.0f; | |||
for (int i = 0; i < 16; i++) { | |||
if (editingLength > 0ul) { | |||
// Length (green) | |||
if (editingSequence) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((i < lengths[sequence]) ? 0.5f : 0.0f); | |||
else | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((i < phrases) ? 0.5f : 0.0f); | |||
// Nothing (red) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f; | |||
} | |||
else { | |||
// Run cursor (green) | |||
if (editingSequence) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((running && (i == stepIndexRun)) ? 1.0f : 0.0f); | |||
else { | |||
float green = ((running && (i == phraseIndexRun)) ? 1.0f : 0.0f); | |||
green += ((running && (i == stepIndexRun) && i != phraseIndexEdit) ? 0.1f : 0.0f); | |||
lights[STEP_PHRASE_LIGHTS + (i<<1)].value = clamp(green, 0.0f, 1.0f); | |||
} | |||
// Edit cursor (red) | |||
if (editingSequence) | |||
lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = (i == stepIndexEdit ? 1.0f : 0.0f); | |||
else | |||
lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = (i == phraseIndexEdit ? 1.0f : 0.0f); | |||
} | |||
} | |||
else | |||
lights[OCTAVE_LIGHTS + i].value = (i == (6 - octLightIndex) ? 1.0f : 0.0f); | |||
} | |||
} | |||
// Keyboard lights | |||
float cvValOffset; | |||
if (editingSequence) | |||
cvValOffset = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages | |||
else | |||
cvValOffset = cv[phrase[phraseIndexEdit]][stepIndexRun] + 10.0f;//to properly handle negative note voltages | |||
int keyLightIndex = (int) clamp( roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0.0f, 11.0f); | |||
for (int i = 0; i < 12; i++) { | |||
if (!editingSequence && (!attached || !running))// no keyboard lights when song mode and either (detached [1] or stopped [2]) | |||
// [1] makes no sense, can't mod steps and stepping though seq that may not be playing | |||
// [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display | |||
lights[KEY_LIGHTS + i].value = 0.0f; | |||
else { | |||
if (tiedWarning > 0l) { | |||
bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); | |||
lights[KEY_LIGHTS + i].value = (warningFlashState && i == keyLightIndex) ? 1.0f : 0.0f; | |||
// Octave lights | |||
float octCV = 0.0f; | |||
if (editingSequence) | |||
octCV = cv[sequence][stepIndexEdit]; | |||
else | |||
octCV = cv[phrase[phraseIndexEdit]][stepIndexRun]; | |||
int octLightIndex = (int) floor(octCV + 3.0f); | |||
for (int i = 0; i < 7; i++) { | |||
if (!editingSequence && (!attached || !running))// no oct lights when song mode and either (detached [1] or stopped [2]) | |||
// [1] makes no sense, can't mod steps and stepping though seq that may not be playing | |||
// [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display | |||
lights[OCTAVE_LIGHTS + i].value = 0.0f; | |||
else { | |||
if (tiedWarning > 0l) { | |||
bool warningFlashState = calcWarningFlash(tiedWarning, (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips)); | |||
lights[OCTAVE_LIGHTS + i].value = (warningFlashState && (i == (6 - octLightIndex))) ? 1.0f : 0.0f; | |||
} | |||
else | |||
lights[OCTAVE_LIGHTS + i].value = (i == (6 - octLightIndex) ? 1.0f : 0.0f); | |||
} | |||
} | |||
// Keyboard lights | |||
float cvValOffset; | |||
if (editingSequence) | |||
cvValOffset = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages | |||
else | |||
cvValOffset = cv[phrase[phraseIndexEdit]][stepIndexRun] + 10.0f;//to properly handle negative note voltages | |||
int keyLightIndex = (int) clamp( roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0.0f, 11.0f); | |||
for (int i = 0; i < 12; i++) { | |||
if (!editingSequence && (!attached || !running))// no keyboard lights when song mode and either (detached [1] or stopped [2]) | |||
// [1] makes no sense, can't mod steps and stepping though seq that may not be playing | |||
// [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display | |||
lights[KEY_LIGHTS + i].value = 0.0f; | |||
else { | |||
if (editingGate > 0ul && editingGateKeyLight != -1) | |||
lights[KEY_LIGHTS + i].value = (i == editingGateKeyLight ? ((float) editingGate / (float)(gateTime * engineGetSampleRate())) : 0.0f); | |||
else | |||
lights[KEY_LIGHTS + i].value = (i == keyLightIndex ? 1.0f : 0.0f); | |||
if (tiedWarning > 0l) { | |||
bool warningFlashState = calcWarningFlash(tiedWarning, (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips)); | |||
lights[KEY_LIGHTS + i].value = (warningFlashState && i == keyLightIndex) ? 1.0f : 0.0f; | |||
} | |||
else { | |||
if (editingGate > 0ul && editingGateKeyLight != -1) | |||
lights[KEY_LIGHTS + i].value = (i == editingGateKeyLight ? ((float) editingGate / (float)(gateTime * sampleRate / displayRefreshStepSkips)) : 0.0f); | |||
else | |||
lights[KEY_LIGHTS + i].value = (i == keyLightIndex ? 1.0f : 0.0f); | |||
} | |||
} | |||
} | |||
// Gate1, Gate1Prob, Gate2, Slide and Tied lights | |||
int attributesVal = attributes[sequence][stepIndexEdit]; | |||
if (!editingSequence) | |||
attributesVal = attributes[phrase[phraseIndexEdit]][stepIndexRun]; | |||
// | |||
lights[GATE1_LIGHT].value = ((attributesVal & ATT_MSK_GATE1) != 0) ? 1.0f : 0.0f; | |||
lights[GATE1_PROB_LIGHT].value = ((attributesVal & ATT_MSK_GATE1P) != 0) ? 1.0f : 0.0f; | |||
lights[GATE2_LIGHT].value = ((attributesVal & ATT_MSK_GATE2) != 0) ? 1.0f : 0.0f; | |||
lights[SLIDE_LIGHT].value = ((attributesVal & ATT_MSK_SLIDE) != 0) ? 1.0f : 0.0f; | |||
if (tiedWarning > 0l) { | |||
bool warningFlashState = calcWarningFlash(tiedWarning, (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips)); | |||
lights[TIE_LIGHT].value = (warningFlashState) ? 1.0f : 0.0f; | |||
} | |||
} | |||
else | |||
lights[TIE_LIGHT].value = ((attributesVal & ATT_MSK_TIED) != 0) ? 1.0f : 0.0f; | |||
// Attach light | |||
lights[ATTACH_LIGHT].value = (running && attached) ? 1.0f : 0.0f; | |||
// Reset light | |||
lights[RESET_LIGHT].value = resetLight; | |||
resetLight -= (resetLight / lightLambda) * engineGetSampleTime() * displayRefreshStepSkips; | |||
// Run light | |||
lights[RUN_LIGHT].value = lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; | |||
if (editingLength > 0ul) | |||
editingLength--; | |||
if (editingGate > 0ul) | |||
editingGate--; | |||
if (infoCopyPaste != 0l) { | |||
if (infoCopyPaste > 0l) | |||
infoCopyPaste --; | |||
if (infoCopyPaste < 0l) | |||
infoCopyPaste ++; | |||
} | |||
if (tiedWarning > 0l) | |||
tiedWarning--; | |||
}// lightRefreshCounter | |||
// Gate1, Gate1Prob, Gate2, Slide and Tied lights | |||
int attributesVal = attributes[sequence][stepIndexEdit]; | |||
if (!editingSequence) | |||
attributesVal = attributes[phrase[phraseIndexEdit]][stepIndexRun]; | |||
// | |||
lights[GATE1_LIGHT].value = ((attributesVal & ATT_MSK_GATE1) != 0) ? 1.0f : 0.0f; | |||
lights[GATE1_PROB_LIGHT].value = ((attributesVal & ATT_MSK_GATE1P) != 0) ? 1.0f : 0.0f; | |||
lights[GATE2_LIGHT].value = ((attributesVal & ATT_MSK_GATE2) != 0) ? 1.0f : 0.0f; | |||
lights[SLIDE_LIGHT].value = ((attributesVal & ATT_MSK_SLIDE) != 0) ? 1.0f : 0.0f; | |||
if (tiedWarning > 0l) { | |||
bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); | |||
lights[TIE_LIGHT].value = (warningFlashState) ? 1.0f : 0.0f; | |||
} | |||
else | |||
lights[TIE_LIGHT].value = ((attributesVal & ATT_MSK_TIED) != 0) ? 1.0f : 0.0f; | |||
// Attach light | |||
lights[ATTACH_LIGHT].value = (running && attached) ? 1.0f : 0.0f; | |||
// Reset light | |||
lights[RESET_LIGHT].value = resetLight; | |||
// Run light | |||
lights[RUN_LIGHT].value = lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; | |||
if (editingLength > 0ul) | |||
editingLength--; | |||
if (editingGate > 0ul) | |||
editingGate--; | |||
if (infoCopyPaste != 0l) { | |||
if (infoCopyPaste > 0l) | |||
infoCopyPaste --; | |||
if (infoCopyPaste < 0l) | |||
infoCopyPaste ++; | |||
} | |||
if (slideStepsRemain > 0ul) | |||
slideStepsRemain--; | |||
if (clockIgnoreOnReset > 0l) | |||
clockIgnoreOnReset--; | |||
if (tiedWarning > 0l) | |||
tiedWarning--; | |||
// VCO | |||
@@ -1346,42 +1345,37 @@ struct SemiModularSynthWidget : ModuleWidget { | |||
nvgText(vg, textPos.x, textPos.y, "~~~", NULL); | |||
nvgFillColor(vg, textColor); | |||
if (module->infoCopyPaste != 0l) { | |||
if (module->infoCopyPaste > 0l) {// if copy display "CPY" | |||
if (module->infoCopyPaste > 0l) | |||
snprintf(displayStr, 4, "CPY"); | |||
} | |||
else {// if paste display "PST" | |||
else | |||
snprintf(displayStr, 4, "PST"); | |||
} | |||
} | |||
else { | |||
if (module->displayState == SemiModularSynth::DISP_MODE) { | |||
if (module->editingSequence) | |||
runModeToStr(module->runModeSeq[module->sequence]); | |||
else | |||
runModeToStr(module->runModeSong); | |||
} | |||
else if (module->editingLength > 0ul) { | |||
if (module->editingSequence) | |||
snprintf(displayStr, 4, "L%2u", (unsigned) module->lengths[module->sequence]); | |||
else | |||
snprintf(displayStr, 4, "L%2u", (unsigned) module->phrases); | |||
} | |||
else if (module->displayState == SemiModularSynth::DISP_TRANSPOSE) { | |||
snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset)); | |||
if (module->transposeOffset < 0) | |||
displayStr[0] = '-'; | |||
} | |||
else if (module->displayState == SemiModularSynth::DISP_ROTATE) { | |||
snprintf(displayStr, 4, ")%2u", (unsigned) abs(module->rotateOffset)); | |||
if (module->rotateOffset < 0) | |||
displayStr[0] = '('; | |||
} | |||
else {// DISP_NORMAL | |||
snprintf(displayStr, 4, " %2u", (unsigned) (module->editingSequence ? | |||
module->sequence : module->phrase[module->phraseIndexEdit]) + 1 ); | |||
} | |||
else if (module->editingLength > 0ul) { | |||
if (module->editingSequence) | |||
snprintf(displayStr, 4, "L%2u", (unsigned) module->lengths[module->sequence]); | |||
else | |||
snprintf(displayStr, 4, "L%2u", (unsigned) module->phrases); | |||
} | |||
else if (module->displayState == SemiModularSynth::DISP_MODE) { | |||
if (module->editingSequence) | |||
runModeToStr(module->runModeSeq[module->sequence]); | |||
else | |||
runModeToStr(module->runModeSong); | |||
} | |||
else if (module->displayState == SemiModularSynth::DISP_TRANSPOSE) { | |||
snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset)); | |||
if (module->transposeOffset < 0) | |||
displayStr[0] = '-'; | |||
} | |||
else if (module->displayState == SemiModularSynth::DISP_ROTATE) { | |||
snprintf(displayStr, 4, ")%2u", (unsigned) abs(module->rotateOffset)); | |||
if (module->rotateOffset < 0) | |||
displayStr[0] = '('; | |||
} | |||
else {// DISP_NORMAL | |||
snprintf(displayStr, 4, " %2u", (unsigned) (module->editingSequence ? | |||
module->sequence : module->phrase[module->phraseIndexEdit]) + 1 ); | |||
} | |||
displayStr[3] = 0;// more safety | |||
nvgText(vg, textPos.x, textPos.y, displayStr, NULL); | |||
} | |||
}; | |||
@@ -1758,7 +1752,11 @@ RACK_PLUGIN_MODEL_INIT(ImpromptuModular, SemiModularSynth) { | |||
/*CHANGE LOG | |||
0.6.11: | |||
step optimization of lights refresh | |||
0.6.10: | |||
unlock gates when tied (turn off when press tied, but allow to be turned back on) | |||
allow main knob to also change length when length editing is active | |||
0.6.9: | |||
@@ -10,7 +10,6 @@ | |||
#include "ImpromptuModular.hpp" | |||
#include "dsp/digital.hpp" | |||
namespace rack_plugin_ImpromptuModular { | |||
@@ -70,7 +69,7 @@ struct Tact : Module { | |||
SchmittTrigger recallTriggers[2]; | |||
PulseGenerator eocPulses[2]; | |||
float paramReadRequest[2]; | |||
int lightRefreshCounter; | |||
inline bool isLinked(void) {return params[LINK_PARAM].value > 0.5f;} | |||
inline bool isExpSliding(void) {return params[EXP_PARAM].value > 0.5f;} | |||
@@ -91,6 +90,7 @@ struct Tact : Module { | |||
eocPulses[i].reset(); | |||
paramReadRequest[i] = -10.0f;// -10.0f when no request being made, value to read otherwize | |||
} | |||
lightRefreshCounter = 0; | |||
onReset(); | |||
} | |||
@@ -186,7 +186,6 @@ struct Tact : Module { | |||
void step() override { | |||
float sampleRate = engineGetSampleRate(); | |||
float sampleTime = engineGetSampleTime(); | |||
long initInfoStore = (long) (storeInfoTime * sampleRate); | |||
// Scheduled reset (just the parts that do not have a place below in rest of function) | |||
if (scheduledReset) { | |||
@@ -207,7 +206,7 @@ struct Tact : Module { | |||
if (storeTriggers[i].process(params[STORE_PARAMS + i].value)) { | |||
if ( !(i == 1 && isLinked()) ) {// ignore right channel store-button press when linked | |||
storeCV[i] = cv[i]; | |||
infoStore = initInfoStore * (i == 0 ? 1l : -1l); | |||
infoStore = (long) (storeInfoTime * sampleRate / displayRefreshStepSkips) * (i == 0 ? 1l : -1l); | |||
} | |||
} | |||
} | |||
@@ -288,32 +287,37 @@ struct Tact : Module { | |||
} | |||
// Tactile lights | |||
if (infoStore > 0l) | |||
setTLightsStore(0, infoStore, initInfoStore); | |||
else | |||
setTLights(0); | |||
if (infoStore < 0l) | |||
setTLightsStore(1, infoStore * -1l, initInfoStore); | |||
else | |||
setTLights(1); | |||
// CV input lights | |||
for (int i = 0; i < 2; i++) | |||
lights[CVIN_LIGHTS + i * 2].value = infoCVinLight[i]; | |||
for (int i = 0; i < 2; i++) { | |||
infoCVinLight[i] -= (infoCVinLight[i] / lightLambda) * engineGetSampleTime(); | |||
lightRefreshCounter++; | |||
if (lightRefreshCounter > displayRefreshStepSkips) { | |||
lightRefreshCounter = 0; | |||
// Tactile lights | |||
if (infoStore > 0l) | |||
setTLightsStore(0, infoStore, (long) (storeInfoTime * sampleRate / displayRefreshStepSkips) ); | |||
else | |||
setTLights(0); | |||
if (infoStore < 0l) | |||
setTLightsStore(1, infoStore * -1l, (long) (storeInfoTime * sampleRate / displayRefreshStepSkips) ); | |||
else | |||
setTLights(1); | |||
if (infoStore != 0l) { | |||
if (infoStore > 0l) | |||
infoStore --; | |||
if (infoStore < 0l) | |||
infoStore ++; | |||
} | |||
// CV input lights | |||
for (int i = 0; i < 2; i++) | |||
lights[CVIN_LIGHTS + i * 2].value = infoCVinLight[i]; | |||
for (int i = 0; i < 2; i++) { | |||
infoCVinLight[i] -= (infoCVinLight[i] / lightLambda) * sampleTime * displayRefreshStepSkips; | |||
} | |||
} | |||
if (isLinked()) { | |||
cv[1] = clamp(params[TACT_PARAMS + 1].value, 0.0f, 10.0f); | |||
} | |||
if (infoStore != 0l) { | |||
if (infoStore > 0l) | |||
infoStore --; | |||
if (infoStore < 0l) | |||
infoStore ++; | |||
} | |||
scheduledReset = false; | |||
} | |||
@@ -15,7 +15,6 @@ | |||
#include "ImpromptuModular.hpp" | |||
#include "dsp/digital.hpp" | |||
namespace rack_plugin_ImpromptuModular { | |||
@@ -54,6 +53,7 @@ struct TwelveKey : Module { | |||
//float gateLight = 0.0f; | |||
unsigned long noteLightCounter;// 0 when no key to light, downward step counter timer when key lit | |||
int lastKeyPressed;// 0 to 11 | |||
int lightRefreshCounter; | |||
SchmittTrigger keyTriggers[12]; | |||
@@ -72,6 +72,7 @@ struct TwelveKey : Module { | |||
stateInternal = inputs[GATE_INPUT].active ? false : true; | |||
noteLightCounter = 0ul; | |||
lastKeyPressed = 0; | |||
lightRefreshCounter = 0; | |||
} | |||
void onRandomize() override { | |||
@@ -145,7 +146,7 @@ struct TwelveKey : Module { | |||
if (keyTriggers[i].process(params[KEY_PARAMS + i].value)) { | |||
cv = ((float)(octaveNum - 4)) + ((float) i) / 12.0f; | |||
stateInternal = true; | |||
noteLightCounter = (unsigned long) (noteLightTime * engineGetSampleRate()); | |||
noteLightCounter = (unsigned long) (noteLightTime * engineGetSampleRate() / displayRefreshStepSkips); | |||
lastKeyPressed = i; | |||
} | |||
} | |||
@@ -182,12 +183,17 @@ struct TwelveKey : Module { | |||
// Octave output | |||
outputs[OCT_OUTPUT].value = round( (float)(octaveNum + 1) ); | |||
// Key lights | |||
for (int i = 0; i < 12; i++) | |||
lights[KEY_LIGHTS + i].value = (( i == lastKeyPressed && (noteLightCounter > 0ul || params[KEY_PARAMS + i].value > 0.5f)) ? 1.0f : 0.0f); | |||
if (noteLightCounter > 0ul) | |||
noteLightCounter--; | |||
lightRefreshCounter++; | |||
if (lightRefreshCounter > displayRefreshStepSkips) { | |||
lightRefreshCounter = 0; | |||
// Key lights | |||
for (int i = 0; i < 12; i++) | |||
lights[KEY_LIGHTS + i].value = (( i == lastKeyPressed && (noteLightCounter > 0ul || params[KEY_PARAMS + i].value > 0.5f)) ? 1.0f : 0.0f); | |||
if (noteLightCounter > 0ul) | |||
noteLightCounter--; | |||
} | |||
} | |||
}; | |||
@@ -9,7 +9,7 @@ | |||
#include "ImpromptuModular.hpp" | |||
#include "dsp/digital.hpp" | |||
#include "PhraseSeqUtil.hpp" | |||
namespace rack_plugin_ImpromptuModular { | |||
@@ -79,7 +79,7 @@ struct WriteSeq32 : Module { | |||
int pendingPaste;// 0 = nothing to paste, 1 = paste on clk, 2 = paste on seq, destination channel in next msbits | |||
long clockIgnoreOnReset; | |||
const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) | |||
int lightRefreshCounter; | |||
SchmittTrigger clockTrigger; | |||
SchmittTrigger resetTrigger; | |||
@@ -115,6 +115,7 @@ struct WriteSeq32 : Module { | |||
pendingPaste = 0; | |||
clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); | |||
resetOnRun = false; | |||
lightRefreshCounter = 0; | |||
} | |||
void onRandomize() override { | |||
@@ -258,7 +259,7 @@ struct WriteSeq32 : Module { | |||
// Copy button | |||
if (copyTrigger.process(params[COPY_PARAM].value)) { | |||
infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); | |||
infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips); | |||
for (int s = 0; s < 32; s++) { | |||
cvCPbuffer[s] = cv[indexChannel][s]; | |||
gateCPbuffer[s] = gates[indexChannel][s]; | |||
@@ -269,7 +270,7 @@ struct WriteSeq32 : Module { | |||
if (pasteTrigger.process(params[PASTE_PARAM].value)) { | |||
if (params[PASTESYNC_PARAM].value < 0.5f || indexChannel == 3) { | |||
// Paste realtime, no pending to schedule | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips); | |||
for (int s = 0; s < 32; s++) { | |||
cv[indexChannel][s] = cvCPbuffer[s]; | |||
gates[indexChannel][s] = gateCPbuffer[s]; | |||
@@ -367,7 +368,7 @@ struct WriteSeq32 : Module { | |||
// Pending paste on clock or end of seq | |||
if ( ((pendingPaste&0x3) == 1) || ((pendingPaste&0x3) == 2 && indexStep == 0) ) { | |||
int pasteChannel = pendingPaste>>2; | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips); | |||
for (int s = 0; s < 32; s++) { | |||
cv[pasteChannel][s] = cvCPbuffer[s]; | |||
gates[pasteChannel][s] = gateCPbuffer[s]; | |||
@@ -411,40 +412,46 @@ struct WriteSeq32 : Module { | |||
} | |||
} | |||
int index = (indexChannel == 3 ? indexStepStage : indexStep); | |||
// Window lights | |||
for (int i = 0; i < 4; i++) { | |||
lights[WINDOW_LIGHTS + i].value = ((i == (index >> 3))?1.0f:0.0f); | |||
} | |||
// Step and gate lights | |||
for (int index8 = 0, iGate = 0; index8 < 8; index8++) { | |||
lights[STEP_LIGHTS + index8].value = (index8 == (index&0x7)) ? 1.0f : 0.0f; | |||
iGate = (index&0x18) | index8; | |||
lights[GATE_LIGHTS + index8].value = (gates[indexChannel][iGate] && iGate < numSteps) ? 1.0f : 0.0f; | |||
} | |||
lightRefreshCounter++; | |||
if (lightRefreshCounter > displayRefreshStepSkips) { | |||
lightRefreshCounter = 0; | |||
int index = (indexChannel == 3 ? indexStepStage : indexStep); | |||
// Window lights | |||
for (int i = 0; i < 4; i++) { | |||
lights[WINDOW_LIGHTS + i].value = ((i == (index >> 3))?1.0f:0.0f); | |||
} | |||
// Step and gate lights | |||
for (int index8 = 0, iGate = 0; index8 < 8; index8++) { | |||
lights[STEP_LIGHTS + index8].value = (index8 == (index&0x7)) ? 1.0f : 0.0f; | |||
iGate = (index&0x18) | index8; | |||
lights[GATE_LIGHTS + index8].value = (gates[indexChannel][iGate] && iGate < numSteps) ? 1.0f : 0.0f; | |||
} | |||
// Channel lights | |||
lights[CHANNEL_LIGHTS + 0].value = (indexChannel == 0) ? 1.0f : 0.0f;// green | |||
lights[CHANNEL_LIGHTS + 1].value = (indexChannel == 1) ? 1.0f : 0.0f;// yellow | |||
lights[CHANNEL_LIGHTS + 2].value = (indexChannel == 2) ? 1.0f : 0.0f;// orange | |||
lights[CHANNEL_LIGHTS + 3].value = (indexChannel == 3) ? 1.0f : 0.0f;// blue | |||
// Channel lights | |||
lights[CHANNEL_LIGHTS + 0].value = (indexChannel == 0) ? 1.0f : 0.0f;// green | |||
lights[CHANNEL_LIGHTS + 1].value = (indexChannel == 1) ? 1.0f : 0.0f;// yellow | |||
lights[CHANNEL_LIGHTS + 2].value = (indexChannel == 2) ? 1.0f : 0.0f;// orange | |||
lights[CHANNEL_LIGHTS + 3].value = (indexChannel == 3) ? 1.0f : 0.0f;// blue | |||
// Run light | |||
lights[RUN_LIGHT].value = running; | |||
// Write allowed light | |||
lights[WRITE_LIGHT + 0].value = (canEdit)?1.0f:0.0f; | |||
lights[WRITE_LIGHT + 1].value = (canEdit)?0.0f:1.0f; | |||
// Pending paste light | |||
lights[PENDING_LIGHT].value = (pendingPaste == 0 ? 0.0f : 1.0f); | |||
if (infoCopyPaste != 0l) { | |||
if (infoCopyPaste > 0l) | |||
infoCopyPaste --; | |||
if (infoCopyPaste < 0l) | |||
infoCopyPaste ++; | |||
} | |||
// Run light | |||
lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; | |||
// Write allowed light | |||
lights[WRITE_LIGHT + 0].value = (canEdit)?1.0f:0.0f; | |||
lights[WRITE_LIGHT + 1].value = (canEdit)?0.0f:1.0f; | |||
// Pending paste light | |||
lights[PENDING_LIGHT].value = (pendingPaste == 0 ? 0.0f : 1.0f); | |||
if (infoCopyPaste != 0l) { | |||
if (infoCopyPaste > 0l) | |||
infoCopyPaste --; | |||
if (infoCopyPaste < 0l) | |||
infoCopyPaste ++; | |||
} | |||
}// lightRefreshCounter | |||
if (clockIgnoreOnReset > 0l) | |||
clockIgnoreOnReset--; | |||
} | |||
@@ -9,7 +9,7 @@ | |||
#include "ImpromptuModular.hpp" | |||
#include "dsp/digital.hpp" | |||
#include "PhraseSeqUtil.hpp" | |||
namespace rack_plugin_ImpromptuModular { | |||
@@ -82,6 +82,7 @@ struct WriteSeq64 : Module { | |||
const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) | |||
int stepKnob = 0; | |||
int stepsKnob = 0; | |||
int lightRefreshCounter; | |||
SchmittTrigger clock12Trigger; | |||
@@ -121,6 +122,7 @@ struct WriteSeq64 : Module { | |||
pendingPaste = 0; | |||
clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); | |||
resetOnRun = false; | |||
lightRefreshCounter = 0; | |||
} | |||
void onRandomize() override { | |||
@@ -280,7 +282,7 @@ struct WriteSeq64 : Module { | |||
// Copy button | |||
if (copyTrigger.process(params[COPY_PARAM].value)) { | |||
infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); | |||
infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips); | |||
for (int s = 0; s < 64; s++) { | |||
cvCPbuffer[s] = cv[indexChannel][s]; | |||
gateCPbuffer[s] = gates[indexChannel][s]; | |||
@@ -292,7 +294,7 @@ struct WriteSeq64 : Module { | |||
if (pasteTrigger.process(params[PASTE_PARAM].value)) { | |||
if (params[PASTESYNC_PARAM].value < 0.5f || indexChannel == 4) { | |||
// Paste realtime, no pending to schedule | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips); | |||
for (int s = 0; s < 64; s++) { | |||
cv[indexChannel][s] = cvCPbuffer[s]; | |||
gates[indexChannel][s] = gateCPbuffer[s]; | |||
@@ -395,7 +397,7 @@ struct WriteSeq64 : Module { | |||
if ( ((pendingPaste&0x3) == 1) || ((pendingPaste&0x3) == 2 && indexStep[indexChannel] == 0) ) { | |||
if ( (clk12step && (indexChannel == 0 || indexChannel == 1)) || | |||
(clk34step && (indexChannel == 2 || indexChannel == 3)) ) { | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); | |||
infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips); | |||
int pasteChannel = pendingPaste>>2; | |||
for (int s = 0; s < 64; s++) { | |||
cv[pasteChannel][s] = cvCPbuffer[s]; | |||
@@ -420,8 +422,6 @@ struct WriteSeq64 : Module { | |||
clock34Trigger.reset(); | |||
clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); | |||
} | |||
else | |||
resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); | |||
//********** Outputs and lights ********** | |||
@@ -452,28 +452,35 @@ struct WriteSeq64 : Module { | |||
} | |||
} | |||
// Gate light | |||
lights[GATE_LIGHT].value = gates[indexChannel][indexStep[indexChannel]] ? 1.0f : 0.0f; | |||
// Reset light | |||
lights[RESET_LIGHT].value = resetLight; | |||
lightRefreshCounter++; | |||
if (lightRefreshCounter > displayRefreshStepSkips) { | |||
lightRefreshCounter = 0; | |||
// Run light | |||
lights[RUN_LIGHT].value = running; | |||
// Write allowed light | |||
lights[WRITE_LIGHT + 0].value = (canEdit)?1.0f:0.0f; | |||
lights[WRITE_LIGHT + 1].value = (canEdit)?0.0f:1.0f; | |||
// Pending paste light | |||
lights[PENDING_LIGHT].value = (pendingPaste == 0 ? 0.0f : 1.0f); | |||
// Gate light | |||
lights[GATE_LIGHT].value = gates[indexChannel][indexStep[indexChannel]] ? 1.0f : 0.0f; | |||
// Reset light | |||
lights[RESET_LIGHT].value = resetLight; | |||
resetLight -= (resetLight / lightLambda) * engineGetSampleTime() * displayRefreshStepSkips; | |||
// Run light | |||
lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; | |||
// Write allowed light | |||
lights[WRITE_LIGHT + 0].value = (canEdit)?1.0f:0.0f; | |||
lights[WRITE_LIGHT + 1].value = (canEdit)?0.0f:1.0f; | |||
// Pending paste light | |||
lights[PENDING_LIGHT].value = (pendingPaste == 0 ? 0.0f : 1.0f); | |||
if (infoCopyPaste != 0l) { | |||
if (infoCopyPaste > 0l) | |||
infoCopyPaste --; | |||
if (infoCopyPaste < 0l) | |||
infoCopyPaste ++; | |||
} | |||
}// lightRefreshCounter | |||
if (infoCopyPaste != 0l) { | |||
if (infoCopyPaste > 0l) | |||
infoCopyPaste --; | |||
if (infoCopyPaste < 0l) | |||
infoCopyPaste ++; | |||
} | |||
if (clockIgnoreOnReset > 0l) | |||
clockIgnoreOnReset--; | |||
} | |||
@@ -2,7 +2,7 @@ | |||
Modules for [VCV Rack](https://vcvrack.com), available in the [plugin manager](https://vcvrack.com/plugins.html). | |||
Version 0.6.9 | |||
Version 0.6.10 | |||
[//]: # (!!!!!UPDATE VERSION NUMBER IN MAKEFILE ALSO!!!!! 120% Zoom for jpgs) | |||
@@ -19,7 +19,8 @@ Impromptu Modular is not a single-person endeavor: | |||
* Thanks to **Xavier Belmont** for suggesting improvements to the modules, for testing/bug-reports, for the concept design of the SMS16 module and the blank panel, and for graciously providing the dark panels of all modules. | |||
* Thanks to **Steve Baker** for many fruitful discussions regarding the BPM Detection method in Clocked, testing and improvements that were suggested for that module. | |||
* Thanks to **Omri Cohen** for testing and suggesting improvements to the modules, and for the [PhraseSeq16/32 tutorial](https://www.youtube.com/watch?v=N8_rMNzsS7w). | |||
* Thanks also to **Latif Fital**, **Alfredo Santamaria**, **Nay Seven**, **Alberto Zamora**, **Clément Foulc** for suggesting improvements to the modules, bug reports and testing. | |||
* Thanks to **Latif Karoumi** for [testing](https://www.youtube.com/watch?v=5PZCXvWlFZM) and suggesting improvement to the modules, particularly the advanced gate modes in the GateSeq64 and PhraseSeq sequencers. | |||
* Thanks also to **Pyer (Pierre Collard)**, **Alfredo Santamaria**, **Nay Seven**, **Alberto Zamora**, **Clément Foulc**, **Espen Storo**, **Wouter Spekkink**, **John Melcher** for suggesting improvements to the modules, bug reports and testing. | |||
@@ -65,7 +66,7 @@ Such sequencers have two main inputs that allow the capturing of (pitch) CVs, as | |||
When **AUTOSTEP** is activated, the sequencer automatically advances one step right on each write. For example, to automatically capture the notes played on a keyboard, send the midi keyboard's CV into the sequencer's CV IN, and send the keyboard's gate signal into the sequencer's Write input. With Autostep activated, each key-press will be automatically entered in sequence. An alternative way of automatically stepping the sequencer each time a note is entered is to send the gate signal of the keyboard to both the write and ">" inputs. | |||
When Run is activated, the sequencer automatically starts playing in the current step position, provided **RESET on RUN** is not checked in the right-click menu; sequencers will start at the first step when this option is checked. All edge sensitive inputs have a threshold of 1V. In all sequencers, the duration of the gates corresponds to the pulse width (high time) of the clock signal. | |||
When Run is activated, the sequencer automatically starts playing in the current step position, provided **RESET on RUN** is not checked in the right-click menu; sequencers will start at the first step when this option is checked. All edge sensitive inputs have a threshold of 1V. In all sequencers, the duration of the gates normally corresponds to the pulse width (high time) of the clock signal. When sequencers offer an **Advanced gate mode** and this mode is activated, the pulse width of the clock signal has no effect on the sequencer. | |||
Many modules feature an **Expansion panel** to provide additional CV inputs for the module (available in the right-click menu of the module). An extra 4 HP is added on the right side of the module, thus it is advisable to first make room in your Rack for this. | |||
@@ -141,14 +142,14 @@ The PW and Swing **CV inputs** (some are available in the expansion panel) are 0 | |||
### External synchronization <a id="clocked-sync"></a> | |||
By default, the clock's BPM input is level sensitive and follows [Rack standards for BPM CVs](https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies). For synchronizing Clocked an external clock signal, an optional setting is available in the right-click menu called "Use **BPM Detection** (as opposed to **BPM CV**)". When using this option in a chain of Clocked modules, all modules must have the option checked. The green LED to the right of the main BPM display will light up when this mode is enabled and a cable is connected to the BPM input. | |||
By default, the clock's BPM input is level sensitive and follows [Rack standards for BPM CVs](https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies). For synchronizing Clocked to an external clock signal, an optional setting is available using the MODE button located below the BPM input jack, and selecting a mode other than "CV". When using a chain of Clocked modules, all modules must have the same mode setting. The LED to the right of the MODE button will light up when the sync mode is enabled and a cable is connected to the BPM input. When no cable is connected to the BPM input jack, the MODE button has no effect and the BPM CV mode is implicit. When a cable is connected, the possible settings are: P4, P8, P12, P24, where the number indicates the number of pulses per step of the external clock source. | |||
When using BPM detection, Clocked syncs itself to the incoming clock pulse, and will stay synchronized, as opposed to just calculating the BPM from the external source. This means that it will not drift (or that it will drift in time with the incoming pulses if they drift), and it should stay perfectly synchronized over time; it also allows for latency compensation. Here are a few points to keep in mind when using BPM Detection. | |||
When using external clock synchronization, Clocked syncs itself to the incoming clock pulse, and will stay synchronized, as opposed to just calculating the BPM from the external source. This means that it will not drift (or that it will drift in time with the incoming pulses if they drift), and it should stay perfectly synchronized over time; it also allows for latency compensation. Here are a few points to keep in mind when using clock synchronization. | |||
1. When using the BPM detection mode, Clocked can not be manually turned on, it will autostart on the first pulse it receives. | |||
1. Clocked can not be manually turned on in clock sync mode, it will autostart on the first pulse it receives. | |||
1. Clocked will automatically stop when the pulses stop, but in order to detect this, it take a small amount of time. To stop the clock quickly, you can simply send a pulse to the RUN CV input, and if the clock is running, it will turn off. | |||
1. The external clock must be capable of sending clocks at a minumum of 4 pulses per quarter note (PPQN) and should not have any swing. | |||
1. For low clock BPMs, synchronization may take some time if the external clock changes markedly from the last BPM it was synchronized to. Making gradual tempo changes is always recommended, and increasing the PPQN setting may also help. An other method is to first prime Clocked with is correct BPM to let it learn the new BPM, so that all further runs at that BPM will sync perfectly. | |||
1. For low clock BPMs, synchronization may take some time if the external clock changes markedly from the last BPM it was synchronized to. Making gradual tempo changes is always recommended, and increasing the PPQN setting may also help. An other method consists in priming Clocked with is correct BPM first, to let it learn the new BPM, so that all further runs at that BPM will sync perfectly. | |||
([Back to module list](#modules)) | |||
@@ -158,7 +159,7 @@ When using BPM detection, Clocked syncs itself to the incoming clock pulse, and | |||
 | |||
A 16 phrase sequencer module, where each phrase is an index into a set of 16 sequences of 16 steps (maximum). CVs can be entered via a CV input when using an external keyboard controller or via the built-in keyboard on the module itself. If you need a 256-step sequence in a single module, this is the sequencer for you! With two separate gates per step, gate 2 is perfect for using as an accent if desired. When notes are entered with the *right mouse button* instead of the left button, the sequencer automatically moves to the next step. | |||
A 16 phrase sequencer module, where each phrase is an index into a set of 16 sequences of 16 steps (maximum). CVs can be entered via a CV input when using an external keyboard controller or via the built-in keyboard on the module itself. If you need a 256-step sequence in a single module, this is the sequencer for you! With two separate gates per step, gate 2 is perfect for using as an accent if desired. When notes are entered with the **right mouse button** instead of the left button, the sequencer automatically moves to the next step. | |||
The following block diagram shows how sequences and phrases relate to each other to create a song. In the diagram, a 12-bar blues pattern is created by setting the song length to 12, the step lengths to 8 (not visible in the figure), and then creating 4 sequences. The 12 phrases are indexes into the 4 sequences that were created. (Not sure anyone plays blues in a modular synth, but it shows the idea at least!) | |||
@@ -176,7 +177,7 @@ Familiarity with the Fundamental SEQ-3 sequencer is recommended, as some operati | |||
* **ATTACH**: Allows the edit head to follow the run head (Attach on). The position of the edit head is shown with a red LED, and the position of the run head is shown with a green LED. When in Seq mode, the actual content of the step corresponding to the edit head position (i.e. note, oct, gates, slide) can be modified in real time whether the sequencer is running or not. The edit head automatically follows the run head when Attach is on, or can manually positioned by using the < and > buttons when Attach is off. | |||
* **MODE**: This controls the run mode of the sequences and the song (one setting for each sequence and one for the song). The modes are: FWD (forward), REV (reverse), PPG (ping-pong, also called forward-reverse), BRN (Brownian random), RND (random), FW2 (forward, play twice), FW3 (play three times) and FW4 (four times). For example, setting the run mode to FWD for sequences and to RND for the song will play the phrases that are part of a song randomly, and the probability of a given phrase playing is proportional to the number of times it appears in the song. For sequences, the FW2, FW3 and FW4 modes can be used to repeat sequences more easily without consuming additional phrases in the song. These last three modes are not available for the song's run mode however. | |||
* **MODE**: This controls the run mode of the sequences and the song (one setting for each sequence and one for the song). The modes are: FWD (forward), REV (reverse), PPG (ping-pong, also called forward-reverse), BRN (Brownian random), RND (random), FW2 (forward, play twice), FW3 (play three times) and FW4 (four times). For example, setting the run mode to FWD for sequences and to RND for the song will play the phrases that are part of a song randomly, and the probability of a given phrase playing is proportional to the number of times it appears in the song. For sequences, the FW2, FW3 and FW4 modes can be used to repeat sequences more easily without consuming additional phrases in the song. These last three modes are not available for the song's run mode however. Holding the MODE button for **two seconds** allows the selection of the clock resolution, and is the mechanism used to enable the [advanced gate mode](#advanced-gate-mode-ps). | |||
* **TRAN/ROT**: Transpose/Rotate the currently selected sequence up-down/left-right by a given number of semi-tones/steps. The main knob is used to set the transposition/rotation amount. Only available in Seq mode. | |||
@@ -188,7 +189,22 @@ Familiarity with the Fundamental SEQ-3 sequencer is recommended, as some operati | |||
* **SLIDE**: Portamento between CVs of successive steps. Slide can be activated for a given step using the slide button. The slide duration can be set using the slide knob. The slide duration can range from 0 to T seconds, where T is the duration of a clock period (the default is 10% of T). This knob's setting is not memorized for each step and applies to the sequencer as a whole. | |||
* **TIED STEP**: When CVs are intended to be held across subsequent steps, this button can be used to tie the CV of the previous step to the current step; when tied, the gates of the current step are automatically turned off. If the CV of the head note changes, all consecutive tied notes are updated automatically. | |||
* **TIED STEP**: When CVs are intended to be held across subsequent steps, this button can be used to tie the CV of the previous step to the current step. When tied is turned on for a step, the gates of that step are automatically turned off, but can be manually turned back on if desired. When tied, if the CV of the head note changes, all consecutive tied notes are updated automatically. | |||
### Advanced gate mode<a id="advanced-gate-mode-ps"></a> | |||
Holding the MODE button for **two seconds** allows the selection of the clock resolution, in number of pulses per step (PPS). When set to a value greater than 1, which unlocks the advanced gate mode, the sequencer will skip this many clock pulses before advancing to the next step. In such cases, a mutliplied clock must be supplied in order to keep the same tempo in the sequencer. In advanced gate mode, the pulse width of the clock is not used and has no effect on the gates. | |||
In the advanced gate mode, the Gate1 and Gate2 lights will be a different color, and the onboard keyboard can be used not only to enter note values, but also to select one of the 12 types of gates for a given step. Advanced gates can only be set while in Seq mode and when the sequencer is stopped. Here are the different gate types and their minimum PPS requirements. | |||
 | |||
All PPS settings will work for the half and full gates (the D and F keys) as well as triggers (the B key). A full gate remains high during the entire step, and if the next step's gate is active, then the gate continues without interruption into that next step. When PPS requirements are not met, the sequencer will not allow invalid gate types to be entered on the keyboard. For example, if PPS is set to 6, then the 75% gate (the E key) can not be selected. Selecting a PPS value of 12 or 24 will ensure that all gate types can be used (i.e. that all PPS requirements are met irrespective of the gate type chosen). | |||
The gate type for a given step can be selected during a short time interval after a given gate has just been turned on using the Gate1 or Gate2 buttons. If a gate is already turned on and its gate type is to be edited, clicking the gate button twice will allow it to be edited while keeping it in the same state. The onboard keyboard will temporarily show a yellow/orange light corresponding to the current gate type for that step; during this time the gate type can be changed. | |||
Since the editing time for the advanced gate mode is kept rather short (4s), holding the Gate2 button for 2s will set that default time interval to 400s. Holding Gate1 for 2s will revert to the default time of 4s. The extended time feature is useful when the gate modes for multiple steps of a sequence are to be editied or reviewed in a single pass, for example. | |||
([Back to module list](#modules)) | |||
@@ -216,15 +232,22 @@ When the 1x32 configuration is selected, only the top channel outputs are used ( | |||
A 64 step gate sequencer with the ability to define **probabilities** for each step. A configuration switch allows the sequencer to output quad 16 step sequences, dual 32 step sequences or single 64 step sequences. To see the sequencer in action and for a tutorial on how it works, please see [this segment](https://www.youtube.com/watch?v=bjqWwTKqERQ&t=6111s) of Nigel Sixsmith's Talking Rackheads episode 10. | |||
When activating a given step by clicking it once, it will turn green showing that the step is on. Clicking the step again turns it yellow, and the main display shows the probability associated with this step. While the probability remains shown, the probability can be adjusted with the main knob, in 0.02 increments, between 0 and 1. When a yellow step is selected, clicking it again will turn it off. | |||
When activating a given step by clicking it once, it will turn green showing that the step is on. Clicking the _"p"_ button turns it yellow, and the main display shows the probability associated with this step. While the probability remains shown, the probability can be adjusted with the main knob, in 0.02 increments, between 0 and 1. When a yellow step is selected, clicking the _"p"_ button again will turn it off. | |||
This sequencer also features the song mode found in [PhraseSeq16](#phrase-seq-16); 16 phrases can be defined, where a phrase is an index into a set of 16 sequences. In GateSeq64, the song steps are shown using the fourth row of steps, overlapped with the actual sequence progression in lighter shades in the lights. | |||
The **SEQ** CV input and run **MODES** are identical to those found in PhraseSeq16, and selecting sequence lengths is done in the same manner as described in [PhraseSeq32](#phrase-seq-32). Copy-pasting **ALL** also copies the run mode and length of a given sequence, along with gate states and probabilities, whereas only gates and probabilities are copied when **ROW** is selected. | |||
The **SEQ** CV input and run **MODES** are identical to those found in PhraseSeq16, and selecting sequence lengths is done in the same manner as described in [PhraseSeq32](#phrase-seq-32). Copy-pasting ALL also copies the run mode and length of a given sequence, along with gate states and probabilities, whereas only gates and probabilities are copied when 4 or ROW are selected. | |||
When running in the 4x16 configuration, each of the four rows is sent to the four **GATE** output jacks (jacks 1 to 4, with jack 1 being the top-most jack). In the 2x32 configuration, jacks 1 and 3 are used, and in the 1x64 configuration, only jack 1 is used (top-most jack). The pulse width of the gates emitted corresponds to the pulse width of the clock. | |||
Although no **write** capabilities appear in the main part of the module, automatically storing patterns into the sequencer can be performed using the CV inputs in the **expansion panel**. A write cursor is implicitly stepped forward on each write, and can be repositioned at the first step by pressing the reset button, or at an arbitrary step by simply clicking that given step. | |||
Although no **write** capabilities appear in the main part of the module, automatically storing patterns into the sequencer can be performed using the CV inputs in the **expansion panel**. The cursor is stepped forward on each write, and can be repositioned at the first step by pressing the reset button, or at an arbitrary step by simply clicking that given step. When the cursor is not flashing, clicking any step will make it appear. | |||
### Advanced gate mode<a id="advanced-gate-mode-gs"></a> | |||
Holding the MODE button for **two seconds** allows the selection of the clock resolution, in number of pulses per step (PPS). When set to a value greater than 1, which unlocks the advanced gate mode, the sequencer will skip this many clock pulses before advancing to the next step. In such cases, a mutliplied clock must be supplied in order to keep the same tempo in the sequencer. In advanced gate mode, the pulse width of the clock is not used and has no effect on the gates. | |||
The PPS be a multiple of 4 for the first three gate types, while the PPS be a multiple of 6 for the last five gate types. A chosen gate type not meeting its required pulse rate will have a red LED beside it to indicate this (normally it is green). When a gate type is red, the sequencer will still emit a (possibly empty) gate pattern for that step, but with no guarantee that it will match the type that was selected. All gate types can be used when selecting a PPS value of 12 or 24. | |||
([Back to module list](#modules)) | |||
@@ -260,6 +283,8 @@ Here are a few more details on some of the uses of the buttons. The sequencer us | |||
**FILL**: plays continuous triggers for the given channel as long as the button is kept pressed. By default the fills are not written to memory and are only for playback; however, an option to allow the writing of fill steps to memory is available in the right-click menu. | |||
The BIG and DEL buttons are **quantized to the nearest beat**. Without quantization, button presses typically affect the current beat (step) of the sequencer. With quantized buttons, they affect the nearest beat. For example, pressing the big button 1 microsecond before a beat would normally record the beat in the current step and not the next one that is about to occur, which is actually the closest. For the quantization to work properly, the sequencer must recieve a stable clock of at least 30 BPM. When this is not the case, the option is automatically disabled internally. When manually advancing the clock to program a sequence in non real-time, for example, the option has no effect and the current step is always the target of a button press. | |||
([Back to module list](#modules)) | |||
@@ -315,3 +340,17 @@ WriteSeq64 has dual clock inputs, where each controls a pair of channels. When n | |||
Ideas: The first part of the famous [Piano Phase](https://en.wikipedia.org/wiki/Piano_Phase) piece by Steve Reich can be easily programmed into the sequencer by entering the twelve notes into channel 1 with a keyboard, setting STEPS to 12, copy-pasting channel 1 into channel 3, and then driving each clock input with two LFOs that have ever so slightly different frequencies. Exercise left to the reader! | |||
([Back to module list](#modules)) | |||
# Hall of Fame <a id="hall-of-fame"></a> | |||
Here are a few videos featuring Impromptu Modular, which I find particularly very inspiring and interesting (listed in no particular order). Many talented Rackheads have made tracks using Impromptu modules, and this list could in fact be quite long. I have no formal criteria for why a track ends up in the list or doesn't. | |||
* **Nigel Sixsmith**, [Talking Rackheads episode 8, PS16 MegaPatch Play Out](https://www.youtube.com/watch?v=KOpo2oUPTjg&t=5504s) | |||
* **Omri Cohen**, [Arvo Pärt - Spiegel im Spiegel VCV Rack Cover](https://www.youtube.com/watch?v=6bm4LjRYDmI) | |||
* **Isaac Hu**, [In the Hall of the Mountain King](https://www.youtube.com/watch?v=fxYc0H5i6HA) | |||
* **John Melcher**, [Steppes](https://www.youtube.com/watch?v=ruo4s_Hyhrw) | |||
@@ -0,0 +1,813 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="220" | |||
height="183" | |||
viewBox="0 0 58.208549 48.418911" | |||
version="1.1" | |||
id="svg321" | |||
sodipodi:docname="AdvancedGateDetails.svg" | |||
inkscape:version="0.92.3 (2405546, 2018-03-11)"> | |||
<metadata | |||
id="metadata325"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
<dc:title /> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<sodipodi:namedview | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1" | |||
objecttolerance="10" | |||
gridtolerance="10" | |||
guidetolerance="10" | |||
inkscape:pageopacity="0" | |||
inkscape:pageshadow="2" | |||
inkscape:window-width="1920" | |||
inkscape:window-height="1017" | |||
id="namedview323" | |||
showgrid="false" | |||
inkscape:zoom="3.1174557" | |||
inkscape:cx="144.03182" | |||
inkscape:cy="84.993533" | |||
inkscape:window-x="-8" | |||
inkscape:window-y="-8" | |||
inkscape:window-maximized="1" | |||
inkscape:current-layer="svg321" | |||
showguides="true" | |||
inkscape:guide-bbox="true" /> | |||
<defs | |||
id="defs89"> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="a"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix2" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="b"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix5" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="c"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix8" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="d"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix11" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="e"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix14" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="f"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix17" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="g"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix20" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="h"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix23" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="i"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix26" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="j"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix29" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="k"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix32" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="l"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix35" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="m"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix38" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="n"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix41" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="o"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix44" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="p"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix47" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="q"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix50" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="r"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix53" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="s"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix56" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="t"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix59" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="u"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix62" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="v"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix65" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="w"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix68" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="x"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix71" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="y"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix74" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="z"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix77" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="A"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix80" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="B"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix83" /> | |||
</filter> | |||
<filter | |||
height="1" | |||
width="1" | |||
y="0" | |||
x="0" | |||
filterUnits="objectBoundingBox" | |||
id="C"> | |||
<feColorMatrix | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
in="SourceGraphic" | |||
id="feColorMatrix86" /> | |||
</filter> | |||
</defs> | |||
<text | |||
id="text95" | |||
word-spacing="0" | |||
letter-spacing="0" | |||
font-size="10.583" | |||
font-weight="400" | |||
y="34.662903" | |||
x="64.782875" | |||
style="font-weight:400;font-size:10.58300018px;line-height:1.25;font-family:sans-serif;letter-spacing:0;word-spacing:0;stroke-width:0.26499999" /> | |||
<rect | |||
y="12.95517" | |||
x="2.9069221" | |||
height="22.225079" | |||
width="52.916325" | |||
rx="0.60890275" | |||
ry="0.60897696" | |||
id="rect117" | |||
style="fill:#333333;stroke-width:1.000314;paint-order:markers stroke fill" /> | |||
<path | |||
d="m 15.230183,13.882545 0.03219,9.537557 2.327392,1.289025 v 9.560551 h -6.358203 v -9.560551 l 2.370542,-1.289025 v -9.537557 z m 22.398581,0 0.03288,9.537557 2.326708,1.289025 v 9.560551 h -6.35821 v -9.560551 l 2.370543,-1.289025 v -9.537557 z m 5.806151,0 -0.03288,9.537557 -2.326707,1.289025 v 9.560551 h 6.35752 v -9.560551 l -2.369854,-1.289025 v -9.537557 z m -35.6499211,0 0.03288,9.537557 2.3267081,1.289025 v 9.560551 H 3.7863718 v -9.560551 l -0.0055,-10.826582 h 2.3753371 z m 13.2465461,0 -0.03288,9.537557 -2.326707,1.289025 v 9.560551 h 6.35752 v -9.560551 l 0.0048,-10.826582 h -2.375337 z m 9.152035,0 0.03288,9.537557 2.326708,1.289025 v 9.560551 h -6.357525 v -9.560551 l -0.0055,-10.826582 h 2.376022 z m 20.69653,0 -0.03288,9.537557 -2.326707,1.289025 v 9.560551 h 6.35752 v -9.560551 l 0.0048,-10.826582 h -2.375337 z" | |||
id="path119" | |||
inkscape:connector-curvature="0" | |||
style="fill:#999999;stroke:#000000;stroke-width:0.26458582" | |||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" /> | |||
<g | |||
id="g169" | |||
transform="matrix(0.26458426,0,0,0.26458426,-6.0187653,3.9901329)"> | |||
<g | |||
id="g58"> | |||
<line | |||
id="line52" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="58.888561" | |||
y1="122.37562" | |||
x1="58.888561" /> | |||
<line | |||
id="line54" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="53.964432" | |||
y1="122.37562" | |||
x1="53.964432" /> | |||
<line | |||
id="line56" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="49.040298" | |||
y1="122.37562" | |||
x1="49.040298" /> | |||
</g> | |||
<path | |||
id="path96" | |||
style="fill:none;stroke:#272727;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 54.003,145.25461 v -5.2617 a 1.11729,1.11729 0 0 1 1.1173,-1.11729 h 2.68954 a 1.11729,1.11729 0 0 1 1.11729,1.11729 v 4.1444 a 1.1173,1.1173 0 0 0 1.1173,1.1173 H 73.6995" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 11.854922,38.056573 V 36.66441 a 0.29561735,0.29561735 0 0 1 0.29562,-0.295618 h 2.014447 a 0.29561735,0.29561735 0 0 1 0.295617,0.295618 v 1.096543 a 0.29561999,0.29561999 0 0 0 0.29562,0.29562 h 2.310085" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
id="path98" /> | |||
<g | |||
id="g183" | |||
transform="matrix(0.26458426,0,0,0.26458426,-10.607803,3.9901329)"> | |||
<g | |||
id="g74"> | |||
<line | |||
id="line68" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="132.77985" | |||
y1="122.37562" | |||
x1="132.77985" /> | |||
<line | |||
id="line70" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="122.93159" | |||
y1="122.37562" | |||
x1="122.93159" /> | |||
<line | |||
id="line72" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="118.00746" | |||
y1="122.37562" | |||
x1="118.00746" /> | |||
</g> | |||
<path | |||
id="path100" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 127.89428,145.25461 v -5.2617 a 1.11729,1.11729 0 0 1 1.11729,-1.11729 h 12.5378 a 1.11729,1.11729 0 0 1 1.1173,1.11729 v 4.1444 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 3.80684" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="m 26.749066,38.056572 v -1.392163 a 0.29561735,0.29561735 0 0 1 0.295618,-0.295617 h 4.620152 a 0.29561735,0.29561735 0 0 1 0.29562,0.295617 v 1.392163" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
id="path102" /> | |||
<g | |||
id="g194" | |||
transform="matrix(0.26458426,0,0,0.26458426,-13.778451,3.9901329)"> | |||
<line | |||
id="line4" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="201.02985" | |||
y1="122.37562" | |||
x1="201.02985" /> | |||
<path | |||
id="path104" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 196.14428,145.25461 v -5.2617 a 1.11729,1.11729 0 0 1 1.11729,-1.11729 h 2.68954 a 1.11729,1.11729 0 0 1 1.1173,1.11729 v 4.1444 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 2.68954 a 1.1173,1.1173 0 0 0 1.1173,-1.1173 v -4.1444 a 1.11729,1.11729 0 0 1 1.11729,-1.11729 h 2.68954 a 1.11729,1.11729 0 0 1 1.1173,1.11729 v 4.1444 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 3.80684" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g140" | |||
transform="matrix(0.26458426,0,0,0.26458426,-3.8684196,9.9159005)"> | |||
<g | |||
id="g14"> | |||
<line | |||
id="line8" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="136.35571" | |||
y1="0.5" | |||
x1="136.35571" /> | |||
<line | |||
id="line10" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="149.48431" | |||
y1="0.5" | |||
x1="149.48431" /> | |||
<line | |||
id="line12" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="142.92365" | |||
y1="0.5" | |||
x1="142.92365" /> | |||
</g> | |||
<path | |||
id="path32" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 144.59874,23.379 v 0 -5.2617 A 1.1173,1.1173 0 0 1 145.716,17 h 1.04816 a 1.1173,1.1173 0 0 1 1.1173,1.1173 v 4.14439 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 15.29647" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g126" | |||
transform="matrix(0.26458426,0,0,0.26458426,36.52507,36.236497)"> | |||
<g | |||
id="g40"> | |||
<line | |||
id="line34" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="39.228661" | |||
y1="0.5" | |||
x1="39.228661" /> | |||
<line | |||
id="line36" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="24.45627" | |||
y1="0.5" | |||
x1="24.45627" /> | |||
<line | |||
id="line38" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="6.8789902" | |||
x2="19.532141" | |||
y1="0.5" | |||
x1="19.532141" /> | |||
</g> | |||
<path | |||
transform="translate(-14.81094,-16.5)" | |||
id="path84" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
d="m 34.34308,23.379 h 8.731 a 1.11729,1.11729 0 0 0 1.11729,-1.1173 V 18.1173 A 1.1173,1.1173 0 0 1 45.30864,17 h 2.68954 a 1.11729,1.11729 0 0 1 1.11729,1.1173 v 4.14439 a 1.1173,1.1173 0 0 0 1.1173,1.1173 h 3.80683" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g358" | |||
transform="translate(-6.7638893,-11.032398)"> | |||
<g | |||
id="g22-4" | |||
transform="matrix(0.26458426,0,0,0.26458426,-2.729345,16.582661)"> | |||
<line | |||
id="line16-2" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="179.22273" | |||
y1="17" | |||
x1="179.22273" /> | |||
<line | |||
id="line18-9" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="198.91927" | |||
y1="17" | |||
x1="198.91927" /> | |||
<line | |||
id="line20-1" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="192.3586" | |||
y1="17" | |||
x1="192.3586" /> | |||
</g> | |||
<path | |||
id="path108-6" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
d="m 44.690171,22.768376 h 1.432131 a 0.30500215,0.30500215 0 0 0 0.305,-0.304999 v -1.077784 a 0.30499951,0.30499951 0 0 1 0.304999,-0.305 h 0.258565 a 0.30499951,0.30499951 0 0 1 0.305,0.305 v 1.077781 a 0.30499951,0.30499951 0 0 0 0.304999,0.305 h 2.300695" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g351" | |||
transform="translate(-8.8805593,-11.032398)"> | |||
<g | |||
id="g30-4" | |||
transform="matrix(0.26458426,0,0,0.26458426,-2.729345,16.582661)"> | |||
<line | |||
id="line24-4" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="215.2529" | |||
y1="17" | |||
x1="215.2529" /> | |||
<line | |||
id="line26-9" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="221.82082" | |||
y1="17" | |||
x1="221.82082" /> | |||
<line | |||
id="line28-5" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="23.37899" | |||
x2="234.94942" | |||
y1="17" | |||
x1="234.94942" /> | |||
</g> | |||
<path | |||
id="path110-3" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
d="m 54.223185,22.768376 h 3.169259 a 0.30499951,0.30499951 0 0 0 0.304999,-0.304999 v -1.077784 a 0.30499951,0.30499951 0 0 1 0.305,-0.305 h 0.258567 a 0.30499951,0.30499951 0 0 1 0.305,0.305 v 1.077781 a 0.30499951,0.30499951 0 0 0 0.304999,0.305 h 0.563565" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="7.3508749" | |||
y="42.490799" | |||
id="text245"><tspan | |||
sodipodi:role="line" | |||
id="tspan243" | |||
x="7.3508749" | |||
y="52.146332" | |||
style="stroke-width:0.26458427" /></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="3.1753566" | |||
y="3.9635246" | |||
id="text249"><tspan | |||
sodipodi:role="line" | |||
id="tspan247" | |||
x="3.1753566" | |||
y="3.9635246" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">PULSES PER STEPS (PPS) - REQUIREMENTS</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="12.148573" | |||
y="41.321423" | |||
id="text249-5-3"><tspan | |||
sodipodi:role="line" | |||
id="tspan247-9-6" | |||
x="12.148573" | |||
y="41.321423" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">ALL</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="19.675114" | |||
y="8.6498356" | |||
id="text249-5-8"><tspan | |||
sodipodi:role="line" | |||
id="tspan247-9-4" | |||
x="19.675114" | |||
y="8.6498356" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">MULTIPLES OF 6</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="49.741993" | |||
y="41.321423" | |||
id="text249-5-3-8"><tspan | |||
sodipodi:role="line" | |||
id="tspan247-9-6-2" | |||
x="49.741993" | |||
y="41.321423" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">ALL</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;line-height:1.25;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="27.327574" | |||
y="41.332336" | |||
id="text249-5-3-3"><tspan | |||
sodipodi:role="line" | |||
id="tspan247-9-6-7" | |||
x="27.327574" | |||
y="41.332336" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">ALL</tspan></text> | |||
<g | |||
id="g198-7" | |||
transform="matrix(0.26458426,0,0,0.26458426,-49.235941,-22.33047)"> | |||
<line | |||
id="line6-2" | |||
style="fill:none;stroke:#a8a8a8;stroke-linecap:round;stroke-linejoin:round" | |||
y2="128.75461" | |||
x2="236.47182" | |||
y1="122.37562" | |||
x1="236.47182" /> | |||
<path | |||
id="path106-8" | |||
style="fill:none;stroke:#333333;stroke-linecap:round;stroke-linejoin:round" | |||
transform="translate(-14.81094,-16.5)" | |||
d="m 231.58623,145.25461 v -5.2617 a 1.11729,1.11729 0 0 1 1.1173,-1.11729 h 1.04816 a 1.11729,1.11729 0 0 1 1.1173,1.11729 v 4.1444 a 1.11729,1.11729 0 0 0 1.11729,1.1173 h 1.04816 a 1.1173,1.1173 0 0 0 1.1173,-1.1173 v -4.1444 a 1.11729,1.11729 0 0 1 1.1173,-1.11729 h 1.04816 a 1.11729,1.11729 0 0 1 1.11729,1.11729 v 4.1444 a 1.1173,1.1173 0 0 0 1.1173,1.1173 H 243.6 a 1.1173,1.1173 0 0 0 1.1173,-1.1173 v -4.1444 a 1.11729,1.11729 0 0 1 1.11729,-1.11729 h 1.04816 A 1.11729,1.11729 0 0 1 248,139.99291 v 4.1444 a 1.1173,1.1173 0 0 0 1.1173,1.1173 h 2.16545" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
id="g1007" | |||
transform="translate(-45.905405,-6.2726063)"> | |||
<line | |||
id="line6-2-3" | |||
style="fill:none;stroke:#a8a8a8;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
y2="18.029966" | |||
x2="66.630135" | |||
y1="16.342186" | |||
x1="66.630135" /> | |||
<line | |||
id="line6-2-3-3" | |||
style="fill:none;stroke:#a8a8a8;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
y2="18.029966" | |||
x2="61.418743" | |||
y1="16.342186" | |||
x1="61.418743" /> | |||
<path | |||
sodipodi:nodetypes="ccccccccssccccccsc" | |||
id="path106-8-9" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
d="m 61.418742,18.029966 c 2.141299,0 -0.951586,0 1.441511,0 0.163266,0 0.295619,-0.132353 0.29562,-0.295619 v -1.096544 c 0,-0.163266 0.132354,-0.295618 0.29562,-0.295617 h 0.277326 c 0.163265,0 0.295618,0.132352 0.295618,0.295617 v 1.096544 c 10e-7,0.163266 0.132354,0.295619 0.29562,0.295619 h 0.27734 c 0.163266,0 0.295619,-0.132353 0.29562,-0.295619 v -1.096544 c 0,-0.163265 0.132352,-0.295617 0.295617,-0.295617 h 0.277327 c 0.16326,6e-6 0.295606,0.132357 0.295606,0.295617 v 1.096544 c 10e-7,0.163266 0.132354,0.295619 0.29562,0.295619 h 0.572944" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="m 49.106667,36.368793 v 1.392161 a 0.29562,0.29562 0 0 0 0.29562,0.295619 h 4.915769" | |||
style="fill:none;stroke:#333333;stroke-width:0.26458427;stroke-linecap:round;stroke-linejoin:round" | |||
id="path94-9" /> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="5.1611581" | |||
y="41.337326" | |||
id="text1040"><tspan | |||
sodipodi:role="line" | |||
id="tspan1038" | |||
x="5.1611581" | |||
y="41.337326" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">M4</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="20.233774" | |||
y="41.303471" | |||
id="text1040-4"><tspan | |||
sodipodi:role="line" | |||
id="tspan1038-5" | |||
x="20.233774" | |||
y="41.303471" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">M4</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="38.53788" | |||
y="41.303474" | |||
id="text1040-8"><tspan | |||
sodipodi:role="line" | |||
id="tspan1038-4" | |||
x="38.53788" | |||
y="41.303474" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">M4</tspan></text> | |||
<text | |||
xml:space="preserve" | |||
style="font-style:normal;font-weight:normal;font-size:10.58337021px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458427" | |||
x="17.403894" | |||
y="46.498486" | |||
id="text1072"><tspan | |||
sodipodi:role="line" | |||
id="tspan1070" | |||
x="17.403894" | |||
y="46.498486" | |||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82223225px;font-family:Sniglet;-inkscape-font-specification:'Sniglet, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458427">M4: MULTIPLES OF 4</tspan></text> | |||
</svg> |