diff --git a/plugins/community/repos/ImpromptuModular/Makefile b/plugins/community/repos/ImpromptuModular/Makefile index e86a9e5d..260daa40 100644 --- a/plugins/community/repos/ImpromptuModular/Makefile +++ b/plugins/community/repos/ImpromptuModular/Makefile @@ -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 += diff --git a/plugins/community/repos/ImpromptuModular/README.md b/plugins/community/repos/ImpromptuModular/README.md index 11f210cc..ecab9047 100644 --- a/plugins/community/repos/ImpromptuModular/README.md +++ b/plugins/community/repos/ImpromptuModular/README.md @@ -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 -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 ![IM](res/img/PhraseSeq16.jpg) -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 + +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. + +![IM](res/img/AdvancedGateDetails.jpg) + +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 + +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 + +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) + diff --git a/plugins/community/repos/ImpromptuModular/make.objects b/plugins/community/repos/ImpromptuModular/make.objects index 346948e9..30ef0c56 100644 --- a/plugins/community/repos/ImpromptuModular/make.objects +++ b/plugins/community/repos/ImpromptuModular/make.objects @@ -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 \ diff --git a/plugins/community/repos/ImpromptuModular/res/AdvancedGateDetails.svg b/plugins/community/repos/ImpromptuModular/res/AdvancedGateDetails.svg new file mode 100644 index 00000000..06acaeff --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/AdvancedGateDetails.svg @@ -0,0 +1,813 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PULSES PER STEPS (PPS) - REQUIREMENTS + ALL + MULTIPLES OF 6 + ALL + ALL + + + + + + + + + + + M4 + M4 + M4 + M4: MULTIPLES OF 4 + diff --git a/plugins/community/repos/ImpromptuModular/res/dark/Clocked_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/Clocked_dark.svg index 53c10ee3..7f912975 100644 --- a/plugins/community/repos/ImpromptuModular/res/dark/Clocked_dark.svg +++ b/plugins/community/repos/ImpromptuModular/res/dark/Clocked_dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/GateSeq64_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/GateSeq64_dark.svg index a80aca89..e7568ad2 100644 --- a/plugins/community/repos/ImpromptuModular/res/dark/GateSeq64_dark.svg +++ b/plugins/community/repos/ImpromptuModular/res/dark/GateSeq64_dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq16_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq16_dark.svg index 85477ddf..b9a5bad4 100644 --- a/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq16_dark.svg +++ b/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq16_dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq32_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq32_dark.svg index 3c342d3c..8cbca21b 100644 --- a/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq32_dark.svg +++ b/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq32_dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/img/AdvancedGateDetails.jpg b/plugins/community/repos/ImpromptuModular/res/img/AdvancedGateDetails.jpg new file mode 100644 index 00000000..3fb00d21 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/AdvancedGateDetails.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/Clocked.jpg b/plugins/community/repos/ImpromptuModular/res/img/Clocked.jpg index 4f591160..d621df8c 100644 Binary files a/plugins/community/repos/ImpromptuModular/res/img/Clocked.jpg and b/plugins/community/repos/ImpromptuModular/res/img/Clocked.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/GateSeq64.jpg b/plugins/community/repos/ImpromptuModular/res/img/GateSeq64.jpg index 6201b7be..8d57d510 100644 Binary files a/plugins/community/repos/ImpromptuModular/res/img/GateSeq64.jpg and b/plugins/community/repos/ImpromptuModular/res/img/GateSeq64.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16.jpg b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16.jpg index b74cee6f..dd783c29 100644 Binary files a/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16.jpg and b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq32.jpg b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq32.jpg index 23f56fb0..6ee1504d 100644 Binary files a/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq32.jpg and b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq32.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/light/Clocked.svg b/plugins/community/repos/ImpromptuModular/res/light/Clocked.svg index a0979cff..028b3f50 100644 --- a/plugins/community/repos/ImpromptuModular/res/light/Clocked.svg +++ b/plugins/community/repos/ImpromptuModular/res/light/Clocked.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/EngTest1.svg b/plugins/community/repos/ImpromptuModular/res/light/EngTest1.svg deleted file mode 100644 index 51528c47..00000000 --- a/plugins/community/repos/ImpromptuModular/res/light/EngTest1.svg +++ /dev/null @@ -1,931 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/community/repos/ImpromptuModular/res/light/GateSeq64.svg b/plugins/community/repos/ImpromptuModular/res/light/GateSeq64.svg index 7f233fc4..bc0cbedb 100644 --- a/plugins/community/repos/ImpromptuModular/res/light/GateSeq64.svg +++ b/plugins/community/repos/ImpromptuModular/res/light/GateSeq64.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq16.svg b/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq16.svg index ed0f38d4..8b87964d 100644 --- a/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq16.svg +++ b/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq16.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq32.svg b/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq32.svg index 3bd8d3d8..ced5230f 100644 --- a/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq32.svg +++ b/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq32.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/src/BigButtonSeq.cpp b/plugins/community/repos/ImpromptuModular/src/BigButtonSeq.cpp index e2b6dc65..69173179 100644 --- a/plugins/community/repos/ImpromptuModular/src/BigButtonSeq.cpp +++ b/plugins/community/repos/ImpromptuModular/src/BigButtonSeq.cpp @@ -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() diff --git a/plugins/community/repos/ImpromptuModular/src/Clocked.cpp b/plugins/community/repos/ImpromptuModular/src/Clocked.cpp index ee427e36..c2fc2959 100644 --- a/plugins/community/repos/ImpromptuModular/src/Clocked.cpp +++ b/plugins/community/repos/ImpromptuModular/src/Clocked.cpp @@ -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("Use BPM Detection (as opposed to BPM CV)", CHECKMARK(module->bpmDetectionMode)); - detectItem->module = module; - menu->addChild(detectItem); - - BpmPpqnItem *detect4Item = MenuItem::create("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>(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(Vec(colRulerT1 + offsetLEDbezel, rowRuler1 + offsetLEDbezel), module, Clocked::RUN_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(colRulerT1 + offsetLEDbezel + offsetLEDbezelLight, rowRuler1 + offsetLEDbezel + offsetLEDbezelLight), module, Clocked::RUN_LIGHT)); - // PW master input - addInput(createDynamicPort(Vec(colRulerT2, rowRuler1), Port::INPUT, module, Clocked::PW_INPUTS + 0, &module->panelTheme)); + // BPM mode and light + addParam(ParamWidget::create(Vec(colRulerT2 + offsetTL1105, rowRuler1 + offsetTL1105), module, Clocked::BPMMODE_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(colRulerM1 + 62, rowRuler1 + offsetMediumLight), module, Clocked::BPMSYNC_LIGHT)); // Swing master knob addParam(createDynamicParam(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(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(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, Clocked::PW_INPUTS + 1, &module->panelTheme)); - addInput(expPorts[1] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, Clocked::PW_INPUTS + 2, &module->panelTheme)); - addInput(expPorts[2] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, Clocked::SWING_INPUTS + 0, &module->panelTheme)); - addInput(expPorts[3] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, Clocked::SWING_INPUTS + 1, &module->panelTheme)); - addInput(expPorts[4] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, Clocked::SWING_INPUTS + 2, &module->panelTheme)); - + addInput(expPorts[0] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, Clocked::PW_INPUTS + 0, &module->panelTheme)); + addInput(expPorts[1] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, Clocked::PW_INPUTS + 1, &module->panelTheme)); + addInput(expPorts[2] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, Clocked::PW_INPUTS + 2, &module->panelTheme)); + addInput(expPorts[3] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, Clocked::SWING_INPUTS + 0, &module->panelTheme)); + addInput(expPorts[4] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, Clocked::SWING_INPUTS + 1, &module->panelTheme)); + addInput(expPorts[5] = createDynamicPort(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 diff --git a/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.hpp b/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.hpp index 18bb866d..6e38a006 100644 --- a/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.hpp +++ b/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.hpp @@ -48,8 +48,8 @@ struct LadderFilter { // From Fundamental VCO.cpp //template -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; diff --git a/plugins/community/repos/ImpromptuModular/src/GateSeq64.cpp b/plugins/community/repos/ImpromptuModular/src/GateSeq64.cpp index 5ce5dafe..b6ad4e14 100644 --- a/plugins/community/repos/ImpromptuModular/src/GateSeq64.cpp +++ b/plugins/community/repos/ImpromptuModular/src/GateSeq64.cpp @@ -13,7 +13,7 @@ #include "ImpromptuModular.hpp" -#include "dsp/digital.hpp" +#include "PhraseSeqUtil.hpp" namespace rack_plugin_ImpromptuModular { @@ -26,10 +26,13 @@ struct GateSeq64 : Module { COPY_PARAM, PASTE_PARAM, RESET_PARAM, - PROB_KNOB_PARAM,// no longer used + PROB_PARAM, EDIT_PARAM, SEQUENCE_PARAM, CPMODE_PARAM, + // -- 0.6.9 ^^ + GMODELEFT_PARAM, + GMODERIGHT_PARAM, NUM_PARAMS }; enum InputIds { @@ -54,15 +57,24 @@ struct GateSeq64 : Module { P_LIGHT, RUN_LIGHT, RESET_LIGHT, + // -- 0.6.9 ^^ + ENUMS(GMODE_LIGHTS, 8 * 2),// room for GreenRed NUM_LIGHTS }; - enum DisplayStateIds {DISP_GATE, DISP_LENGTH, DISP_MODES, DISP_ROW_SEL}; - enum AttributeBitMasks {ATT_MSK_PROB = 0xFF, ATT_MSK_GATEP = 0x100, ATT_MSK_GATE = 0x200}; + enum DisplayStateIds {DISP_GATE, DISP_LENGTH, DISP_MODES}; + enum AttributeBitMasksGS {ATT_MSK_PROB = 0xFF, ATT_MSK_GATEP = 0x100, ATT_MSK_GATE = 0x200}; + static const int ATT_MSK_GATEMODE = 0x1C00;// 3 bits + static const int gateModeShift = 10; + // 1/4 DUO D2 TR1 TR2 TR3 TR23 TRI + const uint32_t advGateHitMaskGS[8] = {0x00003F, 0x03F03F, 0x03F000, 0x00000F, 0x000F00, 0x0F0000, 0x0F0F00, 0x0F0F0F}; + static const int blinkNumInit = 15; + // Need to save int panelTheme = 0; int expansion = 0; + int pulsesPerStep;// 1 means normal gate mode, alt choices are 4, 6, 12, 24 PPS (Pulses per step) bool running; int runModeSeq[16]; int runModeSong; @@ -79,28 +91,32 @@ struct GateSeq64 : Module { // No need to save int displayState; + int stepIndexEdit; int stepIndexRun; - int stepIndexWrite; int phraseIndexEdit; int phraseIndexRun; int stepIndexRunHistory;// no need to initialize int phraseIndexRunHistory;// no need to initialize - int cpBufAttributes[64] = {};// copy-paste one row or all rows - int cpBufLength;// copy-paste only one row + int attributesCPbuffer[64]; + int lengthCPbuffer; int modeCPbuffer; - long feedbackCP;// downward step counter for CP feedback + int countCP;// number of steps to paste (in case CPMODE_PARAM changes between copy and paste) + int startCP;// step to start paste (in case CPMODE_PARAM changes between copy and paste) long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste float resetLight = 0.0f; - long feedbackCPinit;// no need to initialize - int cpInfo;// copy = 1, paste = 2 long clockIgnoreOnReset; const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) - int displayProb;// -1 when prob can not be modified, 0 to 63 when prob can be changed. long displayProbInfo;// downward step counter for displayProb feedback int sequenceKnob = 0; - bool gateRandomEnable[4] = {}; + int gateCode[4]; long revertDisplay; + long editingPpqn;// 0 when no info, positive downward step counter timer when editing ppqn + int ppqnCount; + long blinkCount;// positive upward counter, reset to 0 when max reached + int blinkNum;// number of blink cycles to do, downward counter + int lightRefreshCounter; + static constexpr float CONFIG_PARAM_INIT_VALUE = 0.0f;// so that module constructor is coherent with widget initialization, since module created before widget int stepConfigLast; static constexpr float EDIT_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget @@ -118,14 +134,48 @@ struct GateSeq64 : Module { SchmittTrigger writeTrigger; SchmittTrigger write0Trigger; SchmittTrigger write1Trigger; + SchmittTrigger gModeLTrigger; + SchmittTrigger gModeRTrigger; + SchmittTrigger probTrigger; + HoldDetect modeHoldDetect; - inline bool getGate(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE) != 0;} - inline bool getGateP(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATEP) != 0;} - inline int getGatePVal(int seq, int step) {return attributes[seq][step] & ATT_MSK_PROB;} inline bool isEditingSequence(void) {return params[EDIT_PARAM].value > 0.5f;} - inline bool calcGateRandomEnable(bool gateP, int gatePVal) {return (randomUniform() < (((float)(gatePVal))/100.0f)) || !gateP;}// randomUniform is [0.0, 1.0), see include/util/common.hpp - + + inline bool getGateA(int attribute) {return (attribute & ATT_MSK_GATE) != 0;} + inline bool getGate(int seq, int step) {return getGateA(attributes[seq][step]);} + inline bool getGatePa(int attribute) {return (attribute & ATT_MSK_GATEP) != 0;} + inline bool getGateP(int seq, int step) {return getGatePa(attributes[seq][step]);} + inline int getGatePValA(int attribute) {return attribute & ATT_MSK_PROB;} + inline int getGatePVal(int seq, int step) {return getGatePValA(attributes[seq][step]);} + inline int getGateAMode(int attribute) {return (attribute & ATT_MSK_GATEMODE) >> gateModeShift;} + inline int getGateMode(int seq, int step) {return getGateAMode(attributes[seq][step]);} + + inline void setGate(int seq, int step, bool gateState) {attributes[seq][step] &= ~ATT_MSK_GATE; if (gateState) attributes[seq][step] |= ATT_MSK_GATE;} + inline void setGateP(int seq, int step, bool gatePState) {attributes[seq][step] &= ~ATT_MSK_GATEP; if (gatePState) attributes[seq][step] |= ATT_MSK_GATEP;} + inline void setGatePVal(int seq, int step, int pVal) {attributes[seq][step] &= ~ATT_MSK_PROB; attributes[seq][step] |= (pVal & ATT_MSK_PROB);} + inline void setGateMode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATEMODE; attributes[seq][step] |= (gateMode << gateModeShift);} + + inline int getAdvGateGS(int ppqnCount, int pulsesPerStep, int gateMode) { + uint32_t shiftAmt = ppqnCount * (24 / pulsesPerStep); + return (int)((advGateHitMaskGS[gateMode] >> shiftAmt) & (uint32_t)0x1); + } + inline int calcGateCode(int attribute, int ppqnCount, int pulsesPerStep) { + // -1 = gate off for whole step, 0 = gate off for current ppqn, 1 = gate on, 2 = clock high + if (ppqnCount == 0 && getGatePa(attribute) && !(randomUniform() < ((float)(getGatePValA(attribute))/100.0f)))// randomUniform is [0.0, 1.0), see include/util/common.hpp + return -1; + if (!getGateA(attribute)) + return 0; + if (pulsesPerStep == 1) + return 2;// clock high + return getAdvGateGS(ppqnCount, pulsesPerStep, getGateAMode(attribute)); + } + inline bool calcGate(int gateCode, SchmittTrigger clockTrigger) { + if (gateCode < 2) + return gateCode == 1; + return clockTrigger.isHigh(); + } + GateSeq64() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { onReset(); @@ -141,9 +191,10 @@ struct GateSeq64 : Module { else if (CONFIG_PARAM_INIT_VALUE > 0.5f)// 2x32 stepConfig = 2; stepConfigLast = stepConfig; + pulsesPerStep = 1; running = false; - stepIndexWrite = 0; runModeSong = MODE_FWD; + stepIndexEdit = 0; phraseIndexEdit = 0; sequence = 0; phrases = 4; @@ -156,19 +207,23 @@ struct GateSeq64 : Module { lengths[i] = 16 * stepConfig; } for (int i = 0; i < 64; i++) - cpBufAttributes[i] = 50; + attributesCPbuffer[i] = 50; initRun(stepConfig, true); - cpBufLength = 16; + lengthCPbuffer = 64; modeCPbuffer = MODE_FWD; - feedbackCP = 0l; + countCP = 64; + startCP = 0; displayState = DISP_GATE; - displayProb = -1; displayProbInfo = 0l; infoCopyPaste = 0l; revertDisplay = 0l; editingSequence = EDIT_PARAM_INIT_VALUE > 0.5f; editingSequenceLast = editingSequence; resetOnRun = false; + editingPpqn = 0l; + blinkCount = 0l; + blinkNum = 0; + lightRefreshCounter = 0; } @@ -180,54 +235,46 @@ struct GateSeq64 : Module { else if (params[CONFIG_PARAM].value > 0.5f)// 2x32 stepConfig = 2; running = (randomUniform() > 0.5f); - stepIndexWrite = 0; runModeSong = randomu32() % 5; + stepIndexEdit = 0; phraseIndexEdit = 0; sequence = randomu32() % 16; phrases = 1 + (randomu32() % 16); for (int i = 0; i < 16; i++) { for (int s = 0; s < 64; s++) { - attributes[i][s] = (randomu32() % 101) | (randomu32() & (ATT_MSK_GATEP | ATT_MSK_GATE)); + attributes[i][s] = (randomu32() % 101) | (randomu32() & (ATT_MSK_GATEP | ATT_MSK_GATE | ATT_MSK_GATEMODE)); } runModeSeq[i] = randomu32() % NUM_MODES; phrase[i] = randomu32() % 16; lengths[i] = 1 + (randomu32() % (16 * stepConfig)); } for (int i = 0; i < 64; i++) - cpBufAttributes[i] = 50; + attributesCPbuffer[i] = 50; initRun(stepConfig, true); - cpBufLength = 16; + lengthCPbuffer = 64; modeCPbuffer = MODE_FWD; - feedbackCP = 0l; + countCP = 64; + startCP = 0; displayState = DISP_GATE; - displayProb = -1; displayProbInfo = 0l; infoCopyPaste = 0l; revertDisplay = 0l; editingSequence = isEditingSequence(); editingSequenceLast = editingSequence; resetOnRun = false; + editingPpqn = 0l; } void initRun(int stepConfig, bool hard) {// run button activated or run edge in run input jack - if (hard) { + if (hard) phraseIndexRun = (runModeSong == MODE_REV ? phrases - 1 : 0); - if (editingSequence) - stepIndexRun = (runModeSeq[sequence] == MODE_REV ? lengths[sequence] - 1 : 0); - else - stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0); - } - for (int i = 0; i < 4; i++) - gateRandomEnable[i] = false; - if (editingSequence) { - for (int i = 0; i < 4; i += stepConfig) - gateRandomEnable[i] = calcGateRandomEnable(getGateP(sequence, (i * 16) + stepIndexRun), getGatePVal(sequence, (i * 16) + stepIndexRun)); - } - else { - for (int i = 0; i < 4; i += stepConfig) - gateRandomEnable[i] = calcGateRandomEnable(getGateP(phrase[phraseIndexRun], (i * 16) + stepIndexRun), getGatePVal(phrase[phraseIndexRun], (i * 16) + stepIndexRun)); - } + int seq = (editingSequence ? sequence : phrase[phraseIndexRun]); + if (hard) + stepIndexRun = (runModeSeq[seq] == MODE_REV ? lengths[seq] - 1 : 0); + ppqnCount = 0; + for (int i = 0; i < 4; i += stepConfig) + gateCode[i] = calcGateCode(attributes[seq][(i * 16) + stepIndexRun], 0, pulsesPerStep); clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); } @@ -241,6 +288,9 @@ struct GateSeq64 : Module { // expansion json_object_set_new(rootJ, "expansion", json_integer(expansion)); + // pulsesPerStep + json_object_set_new(rootJ, "pulsesPerStep", json_integer(pulsesPerStep)); + // running json_object_set_new(rootJ, "running", json_boolean(running)); @@ -298,6 +348,11 @@ struct GateSeq64 : Module { if (expansionJ) expansion = json_integer_value(expansionJ); + // pulsesPerStep + json_t *pulsesPerStepJ = json_object_get(rootJ, "pulsesPerStep"); + if (pulsesPerStepJ) + pulsesPerStep = json_integer_value(pulsesPerStepJ); + // running json_t *runningJ = json_object_get(rootJ, "running"); if (runningJ) @@ -381,13 +436,13 @@ struct GateSeq64 : Module { // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() void step() override { - static const float feedbackCPinitTime = 3.0f;// seconds static const float copyPasteInfoTime = 0.5f;// seconds static const float displayProbInfoTime = 3.0f;// seconds static const float revertDisplayTime = 0.7f;// seconds - float engineSampleRate = engineGetSampleRate(); - feedbackCPinit = (long) (feedbackCPinitTime * engineSampleRate); - long displayProbInfoInit = (long) (displayProbInfoTime * engineSampleRate); + static const float holdDetectTime = 2.0f;// seconds + float sampleRate = engineGetSampleRate(); + + //********** Buttons, knobs, switches and inputs ********** @@ -401,7 +456,6 @@ struct GateSeq64 : Module { if (stepConfigLast != stepConfig) { for (int i = 0; i < 16; i++) lengths[i] = 16 * stepConfig; - displayProb = -1; stepConfigLast = stepConfig; } @@ -411,8 +465,9 @@ struct GateSeq64 : Module { if (running) initRun(stepConfig, true); displayState = DISP_GATE; - displayProb = -1; editingSequenceLast = editingSequence; + if (editingSequence) + blinkNum = blinkNumInit; } // Seq CV input @@ -425,152 +480,93 @@ struct GateSeq64 : Module { running = !running; if (running) initRun(stepConfig, resetOnRun); + else + blinkNum = blinkNumInit; displayState = DISP_GATE; - displayProb = -1; } - // Mode/Length button - if (modesTrigger.process(params[MODES_PARAM].value)) { - if (displayState == DISP_GATE || displayState == DISP_ROW_SEL) - displayState = DISP_LENGTH; - else if (displayState == DISP_LENGTH) - displayState = DISP_MODES; - else - displayState = DISP_GATE; - displayProb = -1; + // No attach button here, so in in song mode assume attached whe running and in sequence mode show both stepIndexEdit (flashing cursor) and stepIndexRun (pale diff led) + if (running) {// && attached) { + if (!editingSequence) + phraseIndexEdit = phraseIndexRun; } - - - // Sequence knob (Main knob) - float seqParamValue = params[SEQUENCE_PARAM].value; - int newSequenceKnob = (int)roundf(seqParamValue * 7.0f); - if (seqParamValue == 0.0f)// true when constructor or fromJson() occured - sequenceKnob = newSequenceKnob; - int deltaKnob = newSequenceKnob - sequenceKnob; - if (deltaKnob != 0) { - if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example) - if (displayProb != -1 && editingSequence) { - int pval = getGatePVal(sequence, displayProb); - pval += deltaKnob * 2; - if (pval > 100) - pval = 100; - if (pval < 0) - pval = 0; - attributes[sequence][displayProb] = pval | (attributes[sequence][displayProb] & (ATT_MSK_GATE | ATT_MSK_GATEP)); - displayProbInfo = displayProbInfoInit; - } - else if (displayState == DISP_MODES) { - if (editingSequence) { - runModeSeq[sequence] += deltaKnob; - if (runModeSeq[sequence] < 0) runModeSeq[sequence] = 0; - if (runModeSeq[sequence] >= NUM_MODES) runModeSeq[sequence] = NUM_MODES - 1; - } - else { - runModeSong += deltaKnob; - if (runModeSong < 0) runModeSong = 0; - if (runModeSong >= 5) runModeSong = 5 - 1; - } - } - else if (displayState == DISP_LENGTH) { - if (editingSequence) { - lengths[sequence] += deltaKnob; - if (lengths[sequence] > (16 * stepConfig)) - lengths[sequence] = (16 * stepConfig); - if (lengths[sequence] < 1 ) lengths[sequence] = 1; - } - else { - phrases += deltaKnob; - if (phrases > 16) phrases = 16; - if (phrases < 1 ) phrases = 1; - //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1; - } + + // Copy button + if (copyTrigger.process(params[COPY_PARAM].value)) { + if (editingSequence) { + blinkNum = blinkNumInit; + if (params[CPMODE_PARAM].value > 1.5f) {// all + startCP = 0; + countCP = 64; + } + else if (params[CPMODE_PARAM].value < 0.5f) {// 4 + startCP = stepIndexEdit; + countCP = min(4, 16 - (startCP & 0xF)); } - else if (displayState == DISP_ROW_SEL) { + else {// row + startCP = stepIndexEdit & 0x30; + countCP = 16; } - else { - if (editingSequence) { - if (!inputs[SEQCV_INPUT].active) { - sequence += deltaKnob; - if (sequence < 0) sequence = 0; - if (sequence > 15) sequence = 15; - } - } - else { - phrase[phraseIndexEdit] += deltaKnob; - if (phrase[phraseIndexEdit] < 0) phrase[phraseIndexEdit] = 0; - if (phrase[phraseIndexEdit] > 15) phrase[phraseIndexEdit] = 15; - } - } + for (int i = 0, s = startCP; i < countCP; i++, s++) + attributesCPbuffer[i] = attributes[sequence][s]; + lengthCPbuffer = lengths[sequence]; + modeCPbuffer = runModeSeq[sequence]; + infoCopyPaste = (long) (copyPasteInfoTime * sampleRate / displayRefreshStepSkips); } - sequenceKnob = newSequenceKnob; + displayState = DISP_GATE; } - - // Copy, paste buttons - bool copyTrigged = copyTrigger.process(params[COPY_PARAM].value); - bool pasteTrigged = pasteTrigger.process(params[PASTE_PARAM].value); - if (editingSequence) { - if (copyTrigged || pasteTrigged) { - if (displayState == DISP_GATE) { - if (params[CPMODE_PARAM].value > 0.5f) {// if copy-paste in row mode - cpInfo = 0; - if (copyTrigged) cpInfo = 1; - if (pasteTrigged) cpInfo = 2; - displayState = DISP_ROW_SEL; - feedbackCP = feedbackCPinit; - } - else {// copy-paste in "all" mode - if (copyTrigged) { - for (int i = 0; i < 64; i++) - cpBufAttributes[i] = attributes[sequence][i]; - cpBufLength = lengths[sequence]; - modeCPbuffer = runModeSeq[sequence]; - infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); - } - else {// paste triggered - for (int i = 0; i < 64; i++) - attributes[sequence][i] = cpBufAttributes[i]; - lengths[sequence] = cpBufLength; - if (lengths[sequence] > 16 * stepConfig) - lengths[sequence] = 16 * stepConfig; - runModeSeq[sequence] = modeCPbuffer; - infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); - } - } + // Paste button + if (pasteTrigger.process(params[PASTE_PARAM].value)) { + if (editingSequence) { + blinkNum = blinkNumInit; + if (countCP <= 4) { + startCP = stepIndexEdit; + countCP = min(countCP, 16 - (startCP & 0xF)); } - else if (displayState == DISP_ROW_SEL) {// abort copy or paste - displayState = DISP_GATE; + else if (countCP == 16) { + startCP = stepIndexEdit & 0x30; } - displayProb = -1; + // else nothing to do for 64 + + for (int i = 0, s = startCP; i < countCP; i++, s++) + attributes[sequence][s] = attributesCPbuffer[i]; + if (params[CPMODE_PARAM].value > 1.5f) {// all + lengths[sequence] = lengthCPbuffer; + if (lengths[sequence] > 16 * stepConfig) + lengths[sequence] = 16 * stepConfig; + runModeSeq[sequence] = modeCPbuffer; + } + infoCopyPaste = (long) (-1 * copyPasteInfoTime * sampleRate / displayRefreshStepSkips); } + displayState = DISP_GATE; } - // Write inputs bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value); bool write0Trig = write0Trigger.process(inputs[WRITE0_INPUT].value); bool write1Trig = write1Trigger.process(inputs[WRITE1_INPUT].value); if (writeTrig || write0Trig || write1Trig) { if (editingSequence) { + blinkNum = blinkNumInit; if (writeTrig) {// higher priority than write0 and write1 if (inputs[PROB_INPUT].active) { - attributes[sequence][stepIndexWrite] = clamp( (int)round(inputs[PROB_INPUT].value * 10.0f), 0, 100); - attributes[sequence][stepIndexWrite] |= ATT_MSK_GATEP; + setGatePVal(sequence, stepIndexEdit, clamp( (int)round(inputs[PROB_INPUT].value * 10.0f), 0, 100) ); + setGateP(sequence, stepIndexEdit, true); } - else - attributes[sequence][stepIndexWrite] = 50; - if (inputs[GATE_INPUT].value >= 1.0f) - attributes[sequence][stepIndexWrite] |= ATT_MSK_GATE; + else{ + setGatePVal(sequence, stepIndexEdit, 50); + setGateP(sequence, stepIndexEdit, false); + } + setGate(sequence, stepIndexEdit, inputs[GATE_INPUT].value >= 1.0f); } else {// write1 or write0 - attributes[sequence][stepIndexWrite] = write1Trig ? ATT_MSK_GATE : 0; + setGate(sequence, stepIndexEdit, write1Trig); } // Autostep (after grab all active inputs) - stepIndexWrite += 1; - if (stepIndexWrite >= 64) - stepIndexWrite = 0; + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 64); } } - + // Step LED button presses int row = -1; int col = -1; @@ -584,48 +580,33 @@ struct GateSeq64 : Module { if (displayState == DISP_LENGTH) { col = stepPressed % (16 * stepConfig); lengths[sequence] = col + 1; - revertDisplay = (long) (revertDisplayTime * engineGetSampleRate()); - } - else if (displayState == DISP_ROW_SEL) { - row = stepPressed / 16;// copy-paste done on blocks of 16 even when in 2x32 or 1x64 config (and length is not copied) - if (cpInfo == 1) {// copy - for (int i = 0; i < 16; i++) { - cpBufAttributes[i] = attributes[sequence][row * 16 + i]; - } - } - else if (cpInfo == 2) {// paste - for (int i = 0; i < 16; i++) - attributes[sequence][row * 16 + i] = cpBufAttributes[i]; - } - displayState = DISP_GATE; + revertDisplay = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips); } else if (displayState == DISP_MODES) { } else { - stepIndexWrite = stepPressed; if (!getGate(sequence, stepPressed)) {// clicked inactive, so turn gate on - attributes[sequence][stepPressed] |= ATT_MSK_GATE; - attributes[sequence][stepPressed] &= ~ATT_MSK_GATEP; - displayProb = -1; + setGate(sequence, stepPressed, true); + if (getGateP(sequence, stepPressed)) + displayProbInfo = (long) (displayProbInfoTime * sampleRate / displayRefreshStepSkips); + else + displayProbInfo = 0l; } - else { - if (!getGateP(sequence, stepPressed)) {// clicked active, but not in prob mode - displayProb = stepPressed; - displayProbInfo = displayProbInfoInit; - attributes[sequence][stepPressed] |= ATT_MSK_GATEP; + else {// clicked active + if (stepIndexEdit == stepPressed && blinkNum != 0) {// only if coming from current step, turn off + setGate(sequence, stepPressed, false); + displayProbInfo = 0l; } - else {// clicked active, and in prob mode - if (displayProb != stepPressed) {// coming from elsewhere, so don't change any states, just show its prob - displayProb = stepPressed; - displayProbInfo = displayProbInfoInit; - } - else {// coming from current step, so turn off - attributes[sequence][stepPressed] &= ~(ATT_MSK_GATEP | ATT_MSK_GATE); - displayProb = -1; - } + else { + if (getGateP(sequence, stepPressed)) + displayProbInfo = (long) (displayProbInfoTime * sampleRate / displayRefreshStepSkips); + else + displayProbInfo = 0l; } } + stepIndexEdit = stepPressed; } + blinkNum = blinkNumInit; } else {// editing song row = stepPressed / 16; @@ -635,23 +616,143 @@ struct GateSeq64 : Module { phrases = col + 1; if (phrases > 16) phrases = 16; if (phrases < 1 ) phrases = 1; - //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities - revertDisplay = (long) (revertDisplayTime * engineGetSampleRate()); + revertDisplay = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips); } else if (displayState == DISP_MODES) { if (col >= 11 && col <= 15) runModeSong = col - 11; } else { - if (!running) { + //if (!running) { phraseIndexEdit = stepPressed - 48; - //if (phraseIndexEdit >= phrases)// Commented for full edit capabilities - //phraseIndexEdit = phrases - 1;// Commented for full edit capabilities - } + //} } } } } + + // Mode/Length button + if (modesTrigger.process(params[MODES_PARAM].value)) { + blinkNum = blinkNumInit; + if (displayState == DISP_GATE) + displayState = DISP_LENGTH; + else if (displayState == DISP_LENGTH) + displayState = DISP_MODES; + else + displayState = DISP_GATE; + //if (!running) { + modeHoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips)); + //} + } + + // Prob button + if (probTrigger.process(params[PROB_PARAM].value)) { + blinkNum = blinkNumInit; + if (editingSequence && getGate(sequence, stepIndexEdit)) { + if (getGateP(sequence, stepIndexEdit)) { + displayProbInfo = 0l; + setGateP(sequence, stepIndexEdit, false); + } + else { + displayProbInfo = (long) (displayProbInfoTime * sampleRate / displayRefreshStepSkips); + setGateP(sequence, stepIndexEdit, true); + } + } + } + + // GateMode buttons + // Left + if (gModeLTrigger.process(params[GMODELEFT_PARAM].value)) { + blinkNum = blinkNumInit; + if (pulsesPerStep != 1 && editingSequence && getGate(sequence, stepIndexEdit)) { + int gmode = getGateMode(sequence, stepIndexEdit); + gmode--; + if (gmode < 0) + gmode = 7; + setGateMode(sequence, stepIndexEdit, gmode); + } + } + // Right + if (gModeRTrigger.process(params[GMODERIGHT_PARAM].value)) { + blinkNum = blinkNumInit; + if (pulsesPerStep != 1 && editingSequence && getGate(sequence, stepIndexEdit)) { + int gmode = getGateMode(sequence, stepIndexEdit); + gmode++; + if (gmode > 7) + gmode = 0; + setGateMode(sequence, stepIndexEdit, gmode); + } + } + + + // Sequence knob (Main knob) + float seqParamValue = params[SEQUENCE_PARAM].value; + int newSequenceKnob = (int)roundf(seqParamValue * 7.0f); + if (seqParamValue == 0.0f)// true when constructor or fromJson() occured + sequenceKnob = newSequenceKnob; + int deltaKnob = newSequenceKnob - sequenceKnob; + if (deltaKnob != 0) { + if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example) + if (displayProbInfo != 0l && editingSequence) { + blinkNum = blinkNumInit; + int pval = getGatePVal(sequence, stepIndexEdit); + pval += deltaKnob * 2; + if (pval > 100) + pval = 100; + if (pval < 0) + pval = 0; + setGatePVal(sequence, stepIndexEdit, pval); + displayProbInfo = (long) (displayProbInfoTime * sampleRate / displayRefreshStepSkips); + } + else if (editingPpqn != 0) { + pulsesPerStep = indexToPps(ppsToIndex(pulsesPerStep) + deltaKnob);// indexToPps() does clamping + editingPpqn = (long) (2.5f * sampleRate / displayRefreshStepSkips); + } + else if (displayState == DISP_MODES) { + if (editingSequence) { + runModeSeq[sequence] += deltaKnob; + if (runModeSeq[sequence] < 0) runModeSeq[sequence] = 0; + if (runModeSeq[sequence] >= NUM_MODES) runModeSeq[sequence] = NUM_MODES - 1; + } + else { + runModeSong += deltaKnob; + if (runModeSong < 0) runModeSong = 0; + if (runModeSong >= 5) runModeSong = 5 - 1; + } + } + else if (displayState == DISP_LENGTH) { + if (editingSequence) { + lengths[sequence] += deltaKnob; + if (lengths[sequence] > (16 * stepConfig)) + lengths[sequence] = (16 * stepConfig); + if (lengths[sequence] < 1 ) lengths[sequence] = 1; + } + else { + phrases += deltaKnob; + if (phrases > 16) phrases = 16; + if (phrases < 1 ) phrases = 1; + } + } + else { + if (editingSequence) { + blinkNum = blinkNumInit; + if (!inputs[SEQCV_INPUT].active) { + sequence += deltaKnob; + if (sequence < 0) sequence = 0; + if (sequence > 15) sequence = 15; + } + } + else { + //if (!running) { + phrase[phraseIndexEdit] += deltaKnob; + if (phrase[phraseIndexEdit] < 0) phrase[phraseIndexEdit] = 0; + if (phrase[phraseIndexEdit] > 15) phrase[phraseIndexEdit] = 15; + //} + } + } + } + sequenceKnob = newSequenceKnob; + } //********** Clock and reset ********** @@ -659,174 +760,200 @@ struct GateSeq64 : Module { // Clock if (clockTrigger.process(inputs[CLOCK_INPUT].value)) { if (running && clockIgnoreOnReset == 0l) { - for (int i = 0; i < 4; i++) - gateRandomEnable[i] = false; - if (editingSequence) { - moveIndexRunMode(&stepIndexRun, lengths[sequence], runModeSeq[sequence], &stepIndexRunHistory); - for (int i = 0; i < 4; i += stepConfig) - gateRandomEnable[i] = calcGateRandomEnable(getGateP(sequence, (i * 16) + stepIndexRun), getGatePVal(sequence, (i * 16) + stepIndexRun)); + ppqnCount++; + if (ppqnCount >= pulsesPerStep) + ppqnCount = 0; + + int newSeq = sequence;// good value when editingSequence, overwrite if not editingSequence + if (ppqnCount == 0) { + if (editingSequence) { + moveIndexRunMode(&stepIndexRun, lengths[sequence], runModeSeq[sequence], &stepIndexRunHistory); + } + else { + if (moveIndexRunMode(&stepIndexRun, lengths[phrase[phraseIndexRun]], runModeSeq[phrase[phraseIndexRun]], &stepIndexRunHistory)) { + moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory); + stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0);// must always refresh after phraseIndexRun has changed + } + newSeq = phrase[phraseIndexRun]; + } } else { - if (moveIndexRunMode(&stepIndexRun, lengths[phrase[phraseIndexRun]], runModeSeq[phrase[phraseIndexRun]], &stepIndexRunHistory)) { - moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory); - stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0);// must always refresh after phraseIndexRun has changed - } - for (int i = 0; i < 4; i += stepConfig) - gateRandomEnable[i] = calcGateRandomEnable(getGateP(phrase[phraseIndexRun], (i * 16) + stepIndexRun), getGatePVal(phrase[phraseIndexRun], (i * 16) + stepIndexRun)); + if (!editingSequence) + newSeq = phrase[phraseIndexRun]; + } + for (int i = 0; i < 4; i += stepConfig) { + if (gateCode[i] != -1 || ppqnCount == 0) + gateCode[i] = calcGateCode(attributes[newSeq][(i * 16) + stepIndexRun], ppqnCount, pulsesPerStep); } } } // Reset if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) { + //stepIndexEdit = 0; //sequence = 0; - stepIndexWrite = 0; initRun(stepConfig, true);// must be after sequence reset resetLight = 1.0f; displayState = DISP_GATE; clockTrigger.reset(); } - else - resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); //********** Outputs and lights ********** // Gate outputs if (running) { - int seq = editingSequence ? sequence : phrase[phraseIndexRun]; - bool gateOut[4] = {false, false, false, false}; - for (int i = 0; i < 4; i += stepConfig) - gateOut[i] = gateRandomEnable[i] && clockTrigger.isHigh() && getGate(seq, (i * 16) + stepIndexRun); for (int i = 0; i < 4; i++) - outputs[GATE_OUTPUTS + i].value = gateOut[i] ? 10.0f : 0.0f; + outputs[GATE_OUTPUTS + i].value = calcGate(gateCode[i], clockTrigger) ? 10.0f : 0.0f; } else {// not running (no gates, no need to hear anything) for (int i = 0; i < 4; i++) outputs[GATE_OUTPUTS + i].value = 0.0f; } - - // Step LED button lights - int rowToLight = -1; - if (displayState == DISP_ROW_SEL) - rowToLight = CalcRowToLight(feedbackCP, feedbackCPinit); - for (int i = 0; i < 64; i++) { - row = i / (16 * stepConfig); - if (stepConfig == 2 && row == 1) - row++; - col = i % (16 * stepConfig); - if (editingSequence) { - if (displayState == DISP_LENGTH) { - if (col < (lengths[sequence] - 1)) - setGreenRed(STEP_LIGHTS + i * 2, 0.1f, 0.0f); - else if (col == (lengths[sequence] - 1)) - setGreenRed(STEP_LIGHTS + i * 2, 1.0f, 0.0f); - else - setGreenRed(STEP_LIGHTS + i * 2, 0.0f, 0.0f); - } - else if (displayState == DISP_ROW_SEL) { - if ((i / 16) == rowToLight) - setGreenRed(STEP_LIGHTS + i * 2, 1.0f, 0.0f); + + lightRefreshCounter++; + if (lightRefreshCounter > displayRefreshStepSkips) { + lightRefreshCounter = 0; + + // Step LED button lights + if (infoCopyPaste != 0l) { + for (int i = 0; i < 64; i++) { + if (i >= startCP && i < (startCP + countCP)) + setGreenRed(STEP_LIGHTS + i * 2, 0.5f, 0.0f); else setGreenRed(STEP_LIGHTS + i * 2, 0.0f, 0.0f); } - else { - float stepHereOffset = ((stepIndexRun == col) && running) ? 0.5f : 0.0f; - if (getGate(sequence, i)) { - if (i == displayProb && getGateP(sequence, i)) - setGreenRed(STEP_LIGHTS + i * 2, 0.4f, 1.0f - stepHereOffset); - else - setGreenRed(STEP_LIGHTS + i * 2, 1.0f - stepHereOffset, getGateP(sequence, i) ? (1.0f - stepHereOffset) : 0.0f); + } + else { + for (int i = 0; i < 64; i++) { + row = i >> (3 + stepConfig);//i / (16 * stepConfig);// optimized (not equivalent code, but in this case has same effect) + if (stepConfig == 2 && row == 1) + row++; + col = (((stepConfig - 1) << 4) | 0xF) & i;//i % (16 * stepConfig);// optimized + if (editingSequence) { + if (displayState == DISP_LENGTH) { + if (col < (lengths[sequence] - 1)) + setGreenRed(STEP_LIGHTS + i * 2, 0.1f, 0.0f); + else if (col == (lengths[sequence] - 1)) + setGreenRed(STEP_LIGHTS + i * 2, 1.0f, 0.0f); + else + setGreenRed(STEP_LIGHTS + i * 2, 0.0f, 0.0f); + } + else { + float stepHereOffset = ((stepIndexRun == col) && running) ? 0.5f : 1.0f; + long blinkCountMarker = (long) (0.67f * sampleRate / displayRefreshStepSkips); + if (getGate(sequence, i)) { + bool blinkEnableOn = (displayState != DISP_MODES) && (blinkCount < blinkCountMarker); + if (getGateP(sequence, i)) { + if (i == stepIndexEdit)// more orange than yellow + setGreenRed(STEP_LIGHTS + i * 2, blinkEnableOn ? 1.0f : 0.0f, blinkEnableOn ? 1.0f : 0.0f); + else// more yellow + setGreenRed(STEP_LIGHTS + i * 2, stepHereOffset, stepHereOffset); + } + else { + if (i == stepIndexEdit) + setGreenRed(STEP_LIGHTS + i * 2, blinkEnableOn ? 1.0f : 0.0f, 0.0f); + else + setGreenRed(STEP_LIGHTS + i * 2, stepHereOffset, 0.0f); + } + } + else { + if (i == stepIndexEdit && blinkCount > blinkCountMarker && displayState != DISP_MODES) + setGreenRed(STEP_LIGHTS + i * 2, 0.05f, 0.0f); + else + setGreenRed(STEP_LIGHTS + i * 2, ((stepIndexRun == col) && running) ? 0.1f : 0.0f, 0.0f); + } + } + } + else {// editing Song + if (displayState == DISP_LENGTH) { + row = i >> 4;//i / 16;// optimized + col = i & 0xF;//i % 16;// optimized + if (row == 3 && col < (phrases - 1)) + setGreenRed(STEP_LIGHTS + i * 2, 0.1f, 0.0f); + else if (row == 3 && col == (phrases - 1)) + setGreenRed(STEP_LIGHTS + i * 2, 1.0f, 0.0f); + else + setGreenRed(STEP_LIGHTS + i * 2, 0.0f, 0.0f); + } + else { + float green; + if (running) + green = (i == (phraseIndexRun + 48)) ? 1.0f : 0.0f; + else + green = (i == (phraseIndexEdit + 48)) ? 1.0f : 0.0f; + green += ((running && (col == stepIndexRun) && i != (phraseIndexEdit + 48)) ? 0.1f : 0.0f); + setGreenRed(STEP_LIGHTS + i * 2, clamp(green, 0.0f, 1.0f), 0.0f); + } } - else { - setGreenRed(STEP_LIGHTS + i * 2, stepHereOffset / 5.0f, 0.0f); - } } } - else {// editing Song - if (displayState == DISP_LENGTH) { - row = i / 16; - col = i % 16; - if (row == 3 && col < (phrases - 1)) - setGreenRed(STEP_LIGHTS + i * 2, 0.1f, 0.0f); - else if (row == 3 && col == (phrases - 1)) - setGreenRed(STEP_LIGHTS + i * 2, 1.0f, 0.0f); - else - setGreenRed(STEP_LIGHTS + i * 2, 0.0f, 0.0f); - } - else { - float green; - if (running) - green = (i == (phraseIndexRun + 48)) ? 1.0f : 0.0f; + + // GateType lights + if (pulsesPerStep != 1 && editingSequence && getGate(sequence, stepIndexEdit)) { + int gmode = getGateMode(sequence, stepIndexEdit); + for (int i = 0; i < 8; i++) { + if (i == gmode) { + if ( (pulsesPerStep == 4 && i > 2) || (pulsesPerStep == 6 && i <= 2) ) // pps requirement not met + setGreenRed(GMODE_LIGHTS + i * 2, 0.0f, 1.0f); + else + setGreenRed(GMODE_LIGHTS + i * 2, 1.0f, 0.0f); + } else - green = (i == (phraseIndexEdit + 48)) ? 1.0f : 0.0f; - green += ((running && (col == stepIndexRun) && i != (phraseIndexEdit + 48)) ? 0.1f : 0.0f); - setGreenRed(STEP_LIGHTS + i * 2, clamp(green, 0.0f, 1.0f), 0.0f); - } + setGreenRed(GMODE_LIGHTS + i * 2, 0.0f, 0.0f); + } + } + else { + for (int i = 0; i < 8; i++) + setGreenRed(GMODE_LIGHTS + i * 2, 0.0f, 0.0f); } - } - // Reset light - lights[RESET_LIGHT].value = resetLight; + // Reset light + lights[RESET_LIGHT].value = resetLight; + resetLight -= (resetLight / lightLambda) * engineGetSampleTime() * displayRefreshStepSkips; - // Run lights - lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; - - if (feedbackCP > 0l) - feedbackCP--; - else - feedbackCP = feedbackCPinit;// roll over + // Run lights + lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; - if (infoCopyPaste != 0l) { - if (infoCopyPaste > 0l) - infoCopyPaste --; - if (infoCopyPaste < 0l) - infoCopyPaste ++; - } - if (displayProbInfo > 0l) - displayProbInfo--; - else - displayProb = -1; + if (infoCopyPaste != 0l) { + if (infoCopyPaste > 0l) + infoCopyPaste --; + if (infoCopyPaste < 0l) + infoCopyPaste ++; + } + if (displayProbInfo > 0l) + displayProbInfo--; + if (modeHoldDetect.process(params[MODES_PARAM].value)) { + displayState = DISP_GATE; + editingPpqn = (long) (2.5f * sampleRate / displayRefreshStepSkips); + } + if (editingPpqn > 0l) + editingPpqn--; + if (revertDisplay > 0l) { + if (revertDisplay == 1) + displayState = DISP_GATE; + revertDisplay--; + } + if (blinkNum > 0) { + blinkCount++; + if (blinkCount >= (long) (1.0f * sampleRate / displayRefreshStepSkips)) { + blinkCount = 0l; + blinkNum--; + } + } + }// lightRefreshCounter + if (clockIgnoreOnReset > 0l) clockIgnoreOnReset--; - if (revertDisplay > 0l) { - if (revertDisplay == 1) - displayState = DISP_GATE; - revertDisplay--; - } + }// step() - void setGreenRed(int id, float green, float red) { + inline void setGreenRed(int id, float green, float red) { lights[id + 0].value = green; lights[id + 1].value = red; } - int CalcRowToLight(long feedbackCP, long feedbackCPinit) { - int rowToLight = -1; - long onDelta = feedbackCPinit / 14; - long onThreshold;// top based - - onThreshold = feedbackCPinit; - if (feedbackCP < onThreshold && feedbackCP > (onThreshold - onDelta)) - rowToLight = 0; - else { - onThreshold = feedbackCPinit * 3 / 4; - if (feedbackCP < onThreshold && feedbackCP > (onThreshold - onDelta)) - rowToLight = 1; - else { - onThreshold = feedbackCPinit * 2 / 4; - if (feedbackCP < onThreshold && feedbackCP > (onThreshold - onDelta)) - rowToLight = 2; - else { - onThreshold = feedbackCPinit * 1 / 4; - if (feedbackCP < onThreshold && feedbackCP > (onThreshold - onDelta)) - rowToLight = 3; - } - } - } - return rowToLight; - } };// GateSeq64 : module struct GateSeq64Widget : ModuleWidget { @@ -868,15 +995,18 @@ struct GateSeq64Widget : ModuleWidget { snprintf(displayStr, 4, "PST"); } } - else if (module->displayProb != -1) { - int prob = module->getGatePVal(module->sequence, module->displayProb); + else if (module->displayProbInfo != 0l) { + int prob = module->getGatePVal(module->sequence, module->stepIndexEdit); if ( prob>= 100) - snprintf(displayStr, 4, " 1"); + snprintf(displayStr, 4, "1,0"); else if (prob >= 1) snprintf(displayStr, 4, ",%2u", (unsigned) prob); else snprintf(displayStr, 4, " 0"); } + else if (module->editingPpqn != 0ul) { + snprintf(displayStr, 4, "x%2u", (unsigned) module->pulsesPerStep); + } else if (module->displayState == GateSeq64::DISP_LENGTH) { if (module->editingSequence) snprintf(displayStr, 4, "L%2u", (unsigned) module->lengths[module->sequence]); @@ -889,11 +1019,6 @@ struct GateSeq64Widget : ModuleWidget { else runModeToStr(module->runModeSong); } - else if (module->displayState == GateSeq64::DISP_ROW_SEL) { - snprintf(displayStr, 4, "CPY"); - if (module->cpInfo == 2) - snprintf(displayStr, 4, "PST"); - } else { int dispVal = 0; if (module->editingSequence) @@ -1017,79 +1142,91 @@ struct GateSeq64Widget : ModuleWidget { // ****** Top portion (2 switches and LED button array ****** - static const int rowRuler0 = 34; - static const int spacingRows = 36; + static const int rowRuler0 = 32; + static const int spacingRows = 34; static const int colRulerSteps = 15; static const int spacingSteps = 20; static const int spacingSteps4 = 4; - // Step LED buttons + // Step LED buttons and GateMode lights for (int y = 0; y < 4; y++) { int posX = colRulerSteps; for (int x = 0; x < 16; x++) { addParam(ParamWidget::create(Vec(posX, rowRuler0 + 8 + y * spacingRows - 4.4f), module, GateSeq64::STEP_PARAMS + y * 16 + x, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(posX + 4.4f, rowRuler0 + 8 + y * spacingRows), module, GateSeq64::STEP_LIGHTS + (y * 16 + x) * 2)); + + if (y == 3 && ((x & 0x1) == 0)) { + addChild(ModuleLightWidget::create>(Vec(posX + 3, 169), module, GateSeq64::GMODE_LIGHTS + x)); + } + posX += spacingSteps; if ((x + 1) % 4 == 0) posX += spacingSteps4; } } - + // ****** 5x3 Main bottom half Control section ****** static const int colRulerC0 = 25; - static const int colRulerSpacing = 72; - static const int colRulerC1 = colRulerC0 + colRulerSpacing; - static const int colRulerC2 = colRulerC1 + colRulerSpacing; - static const int colRulerC3 = colRulerC2 + colRulerSpacing; - static const int rowRulerC0 = 204; - static const int rowRulerSpacing = 56; + static const int colRulerC1 = 78; + static const int colRulerC2 = 126; + static const int colRulerC3 = 189; + static const int colRulerC4 = 241; + static const int rowRulerC0 = 206; + static const int rowRulerSpacing = 58; static const int rowRulerC1 = rowRulerC0 + rowRulerSpacing; static const int rowRulerC2 = rowRulerC1 + rowRulerSpacing; + + // GateMode buttons + addParam(ParamWidget::create(Vec(colRulerC0 - 10 , rowRulerC0 + offsetTL1105), module, GateSeq64::GMODELEFT_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(colRulerC0 + 20, rowRulerC0 + offsetTL1105), module, GateSeq64::GMODERIGHT_PARAM, 0.0f, 1.0f, 0.0f)); // Clock input - addInput(createDynamicPort(Vec(colRulerC0, rowRulerC0), Port::INPUT, module, GateSeq64::CLOCK_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerC0, rowRulerC1), Port::INPUT, module, GateSeq64::CLOCK_INPUT, &module->panelTheme)); // Reset CV - addInput(createDynamicPort(Vec(colRulerC0, rowRulerC1), Port::INPUT, module, GateSeq64::RESET_INPUT, &module->panelTheme)); - // Seq CV - addInput(createDynamicPort(Vec(colRulerC0, rowRulerC2), Port::INPUT, module, GateSeq64::SEQCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerC0, rowRulerC2), Port::INPUT, module, GateSeq64::RESET_INPUT, &module->panelTheme)); - - // Seq/Song selector - addParam(ParamWidget::create(Vec(colRulerC1 + hOffsetCKSS, rowRulerC0 - 2 + vOffsetCKSS), module, GateSeq64::EDIT_PARAM, 0.0f, 1.0f, GateSeq64::EDIT_PARAM_INIT_VALUE)); + + // Prob button + addParam(createDynamicParam(Vec(colRulerC1 + offsetCKD6b, rowRulerC0 + offsetCKD6b), module, GateSeq64::PROB_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); // Reset LED bezel and light - addParam(ParamWidget::create(Vec(colRulerC1 - 17 + offsetLEDbezel, rowRulerC1 + offsetLEDbezel), module, GateSeq64::RESET_PARAM, 0.0f, 1.0f, 0.0f)); - addChild(ModuleLightWidget::create>(Vec(colRulerC1 - 17 + offsetLEDbezel + offsetLEDbezelLight, rowRulerC1 + offsetLEDbezel + offsetLEDbezelLight), module, GateSeq64::RESET_LIGHT)); + addParam(ParamWidget::create(Vec(colRulerC1 + offsetLEDbezel, rowRulerC1 + offsetLEDbezel), module, GateSeq64::RESET_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(colRulerC1 + offsetLEDbezel + offsetLEDbezelLight, rowRulerC1 + offsetLEDbezel + offsetLEDbezelLight), module, GateSeq64::RESET_LIGHT)); + // Seq CV + addInput(createDynamicPort(Vec(colRulerC1, rowRulerC2), Port::INPUT, module, GateSeq64::SEQCV_INPUT, &module->panelTheme)); + + // Sequence knob + addParam(createDynamicParam(Vec(colRulerC2 + 1 + offsetIMBigKnob, rowRulerC0 + offsetIMBigKnob), module, GateSeq64::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme)); // Run LED bezel and light - addParam(ParamWidget::create(Vec(colRulerC1 + 17 + offsetLEDbezel, rowRulerC1 + offsetLEDbezel), module, GateSeq64::RUN_PARAM, 0.0f, 1.0f, 0.0f)); - addChild(ModuleLightWidget::create>(Vec(colRulerC1 + 17 + offsetLEDbezel + offsetLEDbezelLight, rowRulerC1 + offsetLEDbezel + offsetLEDbezelLight), module, GateSeq64::RUN_LIGHT)); + addParam(ParamWidget::create(Vec(colRulerC2 + offsetLEDbezel, rowRulerC1 + offsetLEDbezel), module, GateSeq64::RUN_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(colRulerC2 + offsetLEDbezel + offsetLEDbezelLight, rowRulerC1 + offsetLEDbezel + offsetLEDbezelLight), module, GateSeq64::RUN_LIGHT)); // Run CV - addInput(createDynamicPort(Vec(colRulerC1, rowRulerC2), Port::INPUT, module, GateSeq64::RUNCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerC2, rowRulerC2), Port::INPUT, module, GateSeq64::RUNCV_INPUT, &module->panelTheme)); // Sequence display SequenceDisplayWidget *displaySequence = new SequenceDisplayWidget(); - displaySequence->box.pos = Vec(colRulerC2 - 15, rowRulerC0 + 2 + vOffsetDisplay); + displaySequence->box.pos = Vec(colRulerC3 - 15, rowRulerC0 + vOffsetDisplay); displaySequence->box.size = Vec(55, 30);// 3 characters displaySequence->module = module; addChild(displaySequence); - // Sequence knob - addParam(createDynamicParam(Vec(colRulerC2 + 1 + offsetIMBigKnob, rowRulerC1 + offsetIMBigKnob), module, GateSeq64::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme)); - // Config switch (3 position) - addParam(ParamWidget::create(Vec(colRulerC2 + hOffsetCKSS, rowRulerC2 - 2 + vOffsetCKSSThree), module, GateSeq64::CONFIG_PARAM, 0.0f, 2.0f, GateSeq64::CONFIG_PARAM_INIT_VALUE));// 0.0f is top position + // Modes button + addParam(createDynamicParam(Vec(colRulerC3 + offsetCKD6b, rowRulerC1 + offsetCKD6b), module, GateSeq64::MODES_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Copy/paste buttons + addParam(ParamWidget::create(Vec(colRulerC3 - 10, rowRulerC2 + offsetTL1105), module, GateSeq64::COPY_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(colRulerC3 + 20, rowRulerC2 + offsetTL1105), module, GateSeq64::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); - // Modes button and light - addParam(createDynamicParam(Vec(colRulerC3 + offsetCKD6b, rowRulerC0 + offsetCKD6b), module, GateSeq64::MODES_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); - // Copy/paste buttons - addParam(ParamWidget::create(Vec(colRulerC3 - 10, rowRulerC1 + offsetTL1105), module, GateSeq64::COPY_PARAM, 0.0f, 1.0f, 0.0f)); - addParam(ParamWidget::create(Vec(colRulerC3 + 20, rowRulerC1 + offsetTL1105), module, GateSeq64::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); + // Seq/Song selector + addParam(ParamWidget::create(Vec(colRulerC4 + 2 + hOffsetCKSS, rowRulerC0 + vOffsetCKSS), module, GateSeq64::EDIT_PARAM, 0.0f, 1.0f, GateSeq64::EDIT_PARAM_INIT_VALUE)); + // Config switch (3 position) + addParam(ParamWidget::create(Vec(colRulerC4 + 2 + hOffsetCKSS, rowRulerC1 - 2 + vOffsetCKSSThree), module, GateSeq64::CONFIG_PARAM, 0.0f, 2.0f, GateSeq64::CONFIG_PARAM_INIT_VALUE));// 0.0f is top position // Copy paste mode - addParam(ParamWidget::create(Vec(colRulerC3 + 2 + hOffsetCKSS, rowRulerC2 - 3 + vOffsetCKSS), module, GateSeq64::CPMODE_PARAM, 0.0f, 1.0f, 1.0f)); + addParam(ParamWidget::create(Vec(colRulerC4 + 2 + hOffsetCKSS, rowRulerC2 + vOffsetCKSSThree), module, GateSeq64::CPMODE_PARAM, 0.0f, 2.0f, 2.0f)); // Outputs for (int iSides = 0; iSides < 4; iSides++) @@ -1119,6 +1256,12 @@ RACK_PLUGIN_MODEL_INIT(ImpromptuModular, GateSeq64) { /*CHANGE LOG +0.6.11: +step optimization of lights refresh + +0.6.10: +add advanced gate mode + 0.6.9: add FW2, FW3 and FW4 run modes for sequences (but not for song) diff --git a/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.cpp b/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.cpp index 029bafa7..cc9129b2 100644 --- a/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.cpp +++ b/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.cpp @@ -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; +} + diff --git a/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.hpp b/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.hpp index 80584d05..2626c146 100644 --- a/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.hpp +++ b/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.hpp @@ -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 diff --git a/plugins/community/repos/ImpromptuModular/src/MidiFileModule.cpp b/plugins/community/repos/ImpromptuModular/src/MidiFileModule.cpp index fac8e692..7bb22e9d 100644 --- a/plugins/community/repos/ImpromptuModular/src/MidiFileModule.cpp +++ b/plugins/community/repos/ImpromptuModular/src/MidiFileModule.cpp @@ -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 diff --git a/plugins/community/repos/ImpromptuModular/src/PhraseSeq16.cpp b/plugins/community/repos/ImpromptuModular/src/PhraseSeq16.cpp index 0a2214ef..c127e4d7 100644 --- a/plugins/community/repos/ImpromptuModular/src/PhraseSeq16.cpp +++ b/plugins/community/repos/ImpromptuModular/src/PhraseSeq16.cpp @@ -13,7 +13,7 @@ #include "ImpromptuModular.hpp" -#include "dsp/digital.hpp" +#include "PhraseSeqUtil.hpp" namespace rack_plugin_ImpromptuModular { @@ -84,7 +84,7 @@ struct PhraseSeq16 : Module { enum LightIds { ENUMS(STEP_PHRASE_LIGHTS, 16 * 2),// room for GreenRed ENUMS(OCTAVE_LIGHTS, 7),// octaves 1 to 7 - ENUMS(KEY_LIGHTS, 12), + ENUMS(KEY_LIGHTS, 12 * 2),// room for GreenRed RUN_LIGHT, RESET_LIGHT, ENUMS(GATE1_LIGHT, 2),// room for GreenRed @@ -100,17 +100,12 @@ struct PhraseSeq16 : Module { }; enum DisplayStateIds {DISP_NORMAL, DISP_MODE, DISP_TRANSPOSE, DISP_ROTATE}; - 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; // Need to save int panelTheme = 0; int expansion = 0; - int pulsesPerStep;// 1 means normal gate mode, alt choices are 4, 12, 24 PPS (Pulses per step) + int pulsesPerStep;// 1 means normal gate mode, alt choices are 4, 6, 12, 24 PPS (Pulses per step) bool running; int runModeSeq[16]; int runModeSong; @@ -155,11 +150,13 @@ struct PhraseSeq16 : Module { unsigned long clockPeriod;// counts number of step() calls upward from last clock (reset after clock processed) long tiedWarning;// 0 when no warning, positive downward step counter timer when warning int sequenceKnob = 0; - bool gate1RandomEnable; - long gate1HoldDetect;// 0 when not detecting, downward counter when detecting + int gate1Code; + int gate2Code; long editingGateLength;// 0 when no info, positive downward step counter timer when gate1, negative upward when gate2 + long editGateLengthTimeInitMult;// multiplier for extended setting of advanced gates long editingPpqn;// 0 when no info, positive downward step counter timer when editing ppqn int ppqnCount; + 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; @@ -187,18 +184,20 @@ struct PhraseSeq16 : Module { SchmittTrigger rotateTrigger; SchmittTrigger transposeTrigger; SchmittTrigger tiedTrigger; + HoldDetect modeHoldDetect; + HoldDetect gate1HoldDetect; + HoldDetect gate2HoldDetect; - inline bool getGate1(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1) != 0;} - inline bool getGate2(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE2) != 0;} - inline bool getGate1P(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1P) != 0;} - inline bool getTied(int seq, int step) {return (attributes[seq][step] & ATT_MSK_TIED) != 0;} inline bool isEditingSequence(void) {return params[EDIT_PARAM].value > 0.5f;} - inline bool calcGate1RandomEnable(bool gate1P) {return (randomUniform() < (params[GATE1_KNOB_PARAM].value)) || !gate1P;}// randomUniform is [0.0, 1.0), see include/util/common.hpp - inline int ppsToIndex() {return (pulsesPerStep == 24 ? 3 : (pulsesPerStep == 12 ? 2 : (pulsesPerStep == 4 ? 1 : 0)));}// map 1,4,12,24, to 0,1,2,3 - inline int indexToPps(int index) {return (index == 3 ? 24 : (index == 2 ? 12 : (index == 1 ? 4 : 1)));}// inverse map of above - inline int getGate1Mode(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1MODE) >> gate1ModeShift;} - inline int getGate2Mode(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE2MODE) >> gate2ModeShift;} + inline bool getGate1(int seq, int step) {return getGate1a(attributes[seq][step]);} + inline bool getGate1P(int seq, int step) {return getGate1Pa(attributes[seq][step]);} + inline bool getGate2(int seq, int step) {return getGate2a(attributes[seq][step]);} + inline bool getSlide(int seq, int step) {return getSlideA(attributes[seq][step]);} + inline bool getTied(int seq, int step) {return getTiedA(attributes[seq][step]);} + inline int getGate1Mode(int seq, int step) {return getGate1aMode(attributes[seq][step]);} + inline int getGate2Mode(int seq, int step) {return getGate2aMode(attributes[seq][step]);} + inline void setGate1Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE1MODE; attributes[seq][step] |= (gateMode << gate1ModeShift);} inline void setGate2Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE2MODE; attributes[seq][step] |= (gateMode << gate2ModeShift);} @@ -242,9 +241,12 @@ struct PhraseSeq16 : Module { editingSequence = EDIT_PARAM_INIT_VALUE > 0.5f; editingSequenceLast = editingSequence; resetOnRun = false; - gate1HoldDetect = 0l; - editingGateLength = 0l; + modeHoldDetect.reset(); + gate1HoldDetect.reset(); + gate2HoldDetect.reset(); + editGateLengthTimeInitMult = 1l; editingPpqn = 0l; + lightRefreshCounter = 0; } @@ -258,12 +260,11 @@ struct PhraseSeq16 : Module { for (int i = 0; i < 16; i++) { for (int s = 0; s < 16; s++) { cv[i][s] = ((float)(randomu32() % 7)) + ((float)(randomu32() % 12)) / 12.0f - 3.0f; - attributes[i][s] = randomu32() % 32;// 32 because 5 attributes + attributes[i][s] = randomu32() & 0x1FFF;// 5 bit for normal attributes + 2 * 4 bits for advanced gate modes if (getTied(i,s)) { attributes[i][s] = ATT_MSK_TIED;// clear other attributes if tied applyTiedStep(i, s, lengths[i]); } - // TODO Randomize gate lengths (even though randomize forces ppqn to 1, can be useful when set to other than 1 after a random) } runModeSeq[i] = randomu32() % NUM_MODES; phrase[i] = randomu32() % 16; @@ -286,27 +287,25 @@ struct PhraseSeq16 : Module { editingSequence = isEditingSequence(); editingSequenceLast = editingSequence; resetOnRun = false; - gate1HoldDetect = 0l; - editingGateLength = 0l; + modeHoldDetect.reset(); + gate1HoldDetect.reset(); + gate2HoldDetect.reset(); + editGateLengthTimeInitMult = 1l; editingPpqn = 0l; } void initRun(bool hard) {// run button activated or run edge in run input jack or edit mode toggled - if (hard) { + if (hard) phraseIndexRun = (runModeSong == MODE_REV ? phrases - 1 : 0); - if (editingSequence) - stepIndexRun = (runModeSeq[sequence] == MODE_REV ? lengths[sequence] - 1 : 0); - else - stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0); - } - gate1RandomEnable = false; + int seq = (editingSequence ? sequence : phrase[phraseIndexRun]); + if (hard) + stepIndexRun = (runModeSeq[seq] == MODE_REV ? lengths[seq] - 1 : 0); ppqnCount = 0; - if (editingSequence) - gate1RandomEnable = calcGate1RandomEnable(getGate1P(sequence, stepIndexRun)); - else - gate1RandomEnable = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun], stepIndexRun)); + gate1Code = calcGate1Code(attributes[seq][stepIndexRun], 0, pulsesPerStep, params[GATE1_KNOB_PARAM].value); + gate2Code = calcGate2Code(attributes[seq][stepIndexRun], 0, pulsesPerStep); clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + editingGateLength = 0l; } @@ -585,17 +584,16 @@ struct PhraseSeq16 : Module { static const float copyPasteInfoTime = 0.5f;// seconds static const float editLengthTime = 2.0f;// seconds static const float tiedWarningTime = 0.7f;// seconds - static const float gateHoldDetectTime = 2.0f;// seconds - static const float editGateLengthTime = 2.5f;// seconds - long tiedWarningInit = (long) (tiedWarningTime * sampleRate); + static const float holdDetectTime = 2.0f;// seconds + static const float editGateLengthTime = 4.0f;// seconds //********** Buttons, knobs, switches and inputs ********** // Notes: // * a tied step's attributes can not be modified by any of the following: - // write input, oct and keyboard buttons, gate1, gate1Prob, gate2 and slide buttons - // however, paste, transpose, rotate obviously can. + // write input, oct and keyboard buttons, gate1Prob and slide buttons + // however, paste, transpose, rotate obviously can, and gate1/2 can be turned back on if desired. // * Whenever cv[][] is modified or tied[] is activated for a step, call applyTiedStep(sequence,stepIndexEdit,steps) // Edit mode @@ -610,8 +608,6 @@ struct PhraseSeq16 : Module { // Seq CV input if (inputs[SEQCV_INPUT].active) { sequence = (int) clamp( round(inputs[SEQCV_INPUT].value * (16.0f - 1.0f) / 10.0f), 0.0f, (16.0f - 1.0f) ); - //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities - //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities } // Mode CV input if (inputs[MODECV_INPUT].active) { @@ -643,8 +639,7 @@ struct PhraseSeq16 : Module { // Copy button if (copyTrigger.process(params[COPY_PARAM].value)) { if (editingSequence) { - infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); - //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 + infoCopyPaste = (long) (copyPasteInfoTime * sampleRate / displayRefreshStepSkips); int sStart = stepIndexEdit; int sCount = 16; if (params[CPMODE_PARAM].value > 1.5f)// all @@ -669,7 +664,7 @@ struct PhraseSeq16 : 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++) { @@ -693,7 +688,7 @@ struct PhraseSeq16 : 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; } @@ -702,31 +697,23 @@ struct PhraseSeq16 : Module { bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value); if (writeTrig) { if (editingSequence) { - if (getTied(sequence,stepIndexEdit) & !inputs[TIEDCV_INPUT].active) - tiedWarning = tiedWarningInit; - else { - cv[sequence][stepIndexEdit] = inputs[CV_INPUT].value; - applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); - // Extra CVs from expansion panel: - if (inputs[TIEDCV_INPUT].active) - attributes[sequence][stepIndexEdit] = (inputs[TIEDCV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_TIED) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_TIED); - if (getTied(sequence, stepIndexEdit)) - attributes[sequence][stepIndexEdit] = ATT_MSK_TIED;// clear other attributes if tied - else { - if (inputs[GATE1CV_INPUT].active) - attributes[sequence][stepIndexEdit] = (inputs[GATE1CV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_GATE1) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_GATE1); - if (inputs[GATE2CV_INPUT].active) - attributes[sequence][stepIndexEdit] = (inputs[GATE2CV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_GATE2) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_GATE2); - if (inputs[SLIDECV_INPUT].active) - attributes[sequence][stepIndexEdit] = (inputs[SLIDECV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_SLIDE) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_SLIDE); - } - editingGate = (unsigned long) (gateTime * engineGetSampleRate()); - editingGateCV = cv[sequence][stepIndexEdit]; - editingGateKeyLight = -1; - // Autostep (after grab all active inputs) - if (params[AUTOSTEP_PARAM].value > 0.5f) - stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 16);//lengths[sequence]);// Commented for full edit capabilities - } + cv[sequence][stepIndexEdit] = inputs[CV_INPUT].value; + // Extra CVs from expansion panel: + if (inputs[TIEDCV_INPUT].active) + setTiedA(&attributes[sequence][stepIndexEdit], inputs[TIEDCV_INPUT].value > 1.0f); + if (inputs[GATE1CV_INPUT].active) + setGate1a(&attributes[sequence][stepIndexEdit], inputs[GATE1CV_INPUT].value > 1.0f); + if (inputs[GATE2CV_INPUT].active) + setGate2a(&attributes[sequence][stepIndexEdit], inputs[GATE2CV_INPUT].value > 1.0f); + if (inputs[SLIDECV_INPUT].active) + setSlideA(&attributes[sequence][stepIndexEdit], inputs[SLIDECV_INPUT].value > 1.0f); + applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); + editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + // Autostep (after grab all active inputs) + if (params[AUTOSTEP_PARAM].value > 0.5f) + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 16); } displayState = DISP_NORMAL; } @@ -742,35 +729,32 @@ struct PhraseSeq16 : 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; if (lengths[sequence] < 1 ) lengths[sequence] = 1; - //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities - //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities } else { phrases += delta; if (phrases > 16) phrases = 16; if (phrases < 1 ) phrases = 1; - //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities } } else { if (!running || !attached) {// don't move heads when attach and running if (editingSequence) { - stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + delta, 16);//lengths[sequence]);// Commented for full edit capabilities + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + delta, 16); 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; } } } else - phraseIndexEdit = moveIndex(phraseIndexEdit, phraseIndexEdit + delta, 16);//phrases);// Commented for full edit capabilities + phraseIndexEdit = moveIndex(phraseIndexEdit, phraseIndexEdit + delta, 16); } } } @@ -781,6 +765,9 @@ struct PhraseSeq16 : Module { displayState = DISP_MODE; else displayState = DISP_NORMAL; + //if (!running) { + modeHoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips)); + //} } if (transposeTrigger.process(params[TRAN_ROT_PARAM].value)) { if (editingSequence) { @@ -806,7 +793,7 @@ struct PhraseSeq16 : 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 ; @@ -819,18 +806,8 @@ struct PhraseSeq16 : Module { } } else if (editingPpqn != 0) { - editingPpqn = (long) (editGateLengthTime * engineGetSampleRate()); - pulsesPerStep = indexToPps(clamp(ppsToIndex() + deltaKnob, 0, 3)); - } - else if (editingGateLength != 0) { - if (editingGateLength > 0) { - editingGateLength = (long) (editGateLengthTime * engineGetSampleRate()); - setGate1Mode(sequence, stepIndexEdit, calcNewGateMode(getGate1Mode(sequence, stepIndexEdit), deltaKnob)); - } - else { - editingGateLength = (long) (-1 * editGateLengthTime * engineGetSampleRate()); - setGate2Mode(sequence, stepIndexEdit, calcNewGateMode(getGate2Mode(sequence, stepIndexEdit), deltaKnob)); - } + pulsesPerStep = indexToPps(ppsToIndex(pulsesPerStep) + deltaKnob);// indexToPps() does clamping + editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips); } else if (displayState == DISP_MODE) { if (editingSequence) { @@ -879,8 +856,6 @@ struct PhraseSeq16 : 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 { @@ -904,7 +879,7 @@ struct PhraseSeq16 : 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); @@ -912,7 +887,7 @@ struct PhraseSeq16 : 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; } @@ -923,16 +898,33 @@ struct PhraseSeq16 : Module { for (int i = 0; i < 12; i++) { if (keyTriggers[i].process(params[KEY_PARAMS + i].value)) { if (editingSequence) { - if (getTied(sequence,stepIndexEdit)) { + if (editingGateLength != 0l) { + int newMode = keyIndexToGateMode(i, pulsesPerStep); + if (editingGateLength > 0l) { + if (newMode != -1) + setGate1Mode(sequence, stepIndexEdit, newMode); + else + editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips); + editingGateLength = ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult); + } + else { + if (newMode != -1) + setGate2Mode(sequence, stepIndexEdit, newMode); + else + editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips); + editingGateLength = -1l * ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult); + } + } + else if (getTied(sequence,stepIndexEdit)) { 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) { @@ -948,14 +940,11 @@ struct PhraseSeq16 : 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; - if (!running) { - if (pulsesPerStep != 1) - editingGateLength = getGate1(sequence,stepIndexEdit) ? (long) (editGateLengthTime * engineGetSampleRate()) : 0l; - gate1HoldDetect = (long) (gateHoldDetectTime * engineGetSampleRate()); + toggleGate1a(&attributes[sequence][stepIndexEdit]); + //if (!running) { + if (pulsesPerStep != 1) { + editingGateLength = getGate1(sequence,stepIndexEdit) ? ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult) : 0l; + gate1HoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips)); } //} } @@ -964,21 +953,19 @@ struct PhraseSeq16 : Module { 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; + toggleGate1Pa(&attributes[sequence][stepIndexEdit]); } displayState = DISP_NORMAL; } if (gate2Trigger.process(params[GATE2_PARAM].value)) { if (editingSequence) { - //if (getTied(sequence,stepIndexEdit)) - //tiedWarning = tiedWarningInit; - //else { - attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE2; - if (!running) { - if (pulsesPerStep != 1) - editingGateLength = getGate2(sequence,stepIndexEdit) ? (long) (-1 * editGateLengthTime * engineGetSampleRate()) : 0l; + toggleGate2a(&attributes[sequence][stepIndexEdit]); + //if (!running) { + if (pulsesPerStep != 1) { + editingGateLength = getGate2(sequence,stepIndexEdit) ? -1l * ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult) : 0l; + gate2HoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips)); } //} } @@ -987,21 +974,21 @@ struct PhraseSeq16 : Module { 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; + toggleSlideA(&attributes[sequence][stepIndexEdit]); } displayState = DISP_NORMAL; } if (tiedTrigger.process(params[TIE_PARAM].value)) { if (editingSequence) { - attributes[sequence][stepIndexEdit] ^= ATT_MSK_TIED; + toggleTiedA(&attributes[sequence][stepIndexEdit]); if (getTied(sequence,stepIndexEdit)) { - attributes[sequence][stepIndexEdit] = ATT_MSK_TIED;// clear other attributes if tied + setGate1a(&attributes[sequence][stepIndexEdit], false); + setGate2a(&attributes[sequence][stepIndexEdit], false); + setSlideA(&attributes[sequence][stepIndexEdit], false); applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); } - else - attributes[sequence][stepIndexEdit] |= (ATT_MSK_GATE1 | ATT_MSK_GATE2); } displayState = DISP_NORMAL; } @@ -1011,15 +998,17 @@ struct PhraseSeq16 : Module { // Clock if (clockTrigger.process(inputs[CLOCK_INPUT].value)) { - if (ppqnCount >= (pulsesPerStep - 1)) { - if (running && clockIgnoreOnReset == 0l) { + if (running && clockIgnoreOnReset == 0l) { + ppqnCount++; + if (ppqnCount >= pulsesPerStep) + ppqnCount = 0; + + int newSeq = sequence;// good value when editingSequence, overwrite if not editingSequence + if (ppqnCount == 0) { float slideFromCV = 0.0f; - float slideToCV = 0.0f; if (editingSequence) { slideFromCV = cv[sequence][stepIndexRun]; moveIndexRunMode(&stepIndexRun, lengths[sequence], runModeSeq[sequence], &stepIndexRunHistory); - slideToCV = cv[sequence][stepIndexRun]; - gate1RandomEnable = calcGate1RandomEnable(getGate1P(sequence,stepIndexRun));// must be calculated on clock edge only } else { slideFromCV = cv[phrase[phraseIndexRun]][stepIndexRun]; @@ -1027,24 +1016,27 @@ struct PhraseSeq16 : Module { moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory); stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0);// must always refresh after phraseIndexRun has changed } - slideToCV = cv[phrase[phraseIndexRun]][stepIndexRun]; - gate1RandomEnable = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun],stepIndexRun));// must be calculated on clock edge only + newSeq = phrase[phraseIndexRun]; } // Slide - if ( (editingSequence && ((attributes[sequence][stepIndexRun] & ATT_MSK_SLIDE) != 0) ) || - (!editingSequence && ((attributes[phrase[phraseIndexRun]][stepIndexRun] & ATT_MSK_SLIDE) != 0) ) ) { - // avtivate sliding (slideStepsRemain can be reset, else runs down to 0, either way, no need to reinit) - slideStepsRemain = (unsigned long) (((float)clockPeriod) * params[SLIDE_KNOB_PARAM].value / 2.0f);// 0-T slide, where T is clock period (can be too long when user does clock gating) + if (getSlide(newSeq, stepIndexRun)) { + // activate sliding (slideStepsRemain can be reset, else runs down to 0, either way, no need to reinit) + slideStepsRemain = (unsigned long) (((float)clockPeriod * pulsesPerStep) * params[SLIDE_KNOB_PARAM].value / 2.0f);// 0-T slide, where T is clock period (can be too long when user does clock gating) //slideStepsRemain = (unsigned long) (engineGetSampleRate() * params[SLIDE_KNOB_PARAM].value );// 0-2s slide + float slideToCV = cv[newSeq][stepIndexRun]; slideCVdelta = (slideToCV - slideFromCV)/(float)slideStepsRemain; } } - clockPeriod = 0ul; - ppqnCount = 0; + else { + if (!editingSequence) + newSeq = phrase[phraseIndexRun]; + } + if (gate1Code != -1 || ppqnCount == 0) + gate1Code = calcGate1Code(attributes[newSeq][stepIndexRun], ppqnCount, pulsesPerStep, params[GATE1_KNOB_PARAM].value); + gate2Code = calcGate2Code(attributes[newSeq][stepIndexRun], ppqnCount, pulsesPerStep); } - else - ppqnCount++; + clockPeriod = 0ul; } clockPeriod++; @@ -1057,8 +1049,6 @@ struct PhraseSeq16 : Module { displayState = DISP_NORMAL; clockTrigger.reset(); } - else - resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); //********** Outputs and lights ********** @@ -1069,169 +1059,189 @@ struct PhraseSeq16 : Module { if (running) { float slideOffset = (slideStepsRemain > 0ul ? (slideCVdelta * (float)slideStepsRemain) : 0.0f); outputs[CV_OUTPUT].value = cv[seq][step] - slideOffset; - outputs[GATE1_OUTPUT].value = (clockTrigger.isHigh() && gate1RandomEnable && - ((attributes[seq][step] & ATT_MSK_GATE1) != 0)) ? 10.0f : 0.0f; - outputs[GATE2_OUTPUT].value = (clockTrigger.isHigh() && - ((attributes[seq][step] & ATT_MSK_GATE2) != 0)) ? 10.0f : 0.0f; + outputs[GATE1_OUTPUT].value = calcGate(gate1Code, clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f; + outputs[GATE2_OUTPUT].value = calcGate(gate2Code, clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f; } - else {// not running + else {// not running outputs[CV_OUTPUT].value = (editingGate > 0ul) ? editingGateCV : cv[seq][step]; 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 (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[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); } } - } - - // Gate1, Gate1Prob, Gate2, Slide and Tied lights - int attributesVal = attributes[sequence][stepIndexEdit]; - if (!editingSequence) - attributesVal = attributes[phrase[phraseIndexEdit]][stepIndexRun]; - // - lights[GATE1_LIGHT + 1].value = ((attributesVal & ATT_MSK_GATE1) != 0) ? 1.0f : 0.0f; - lights[GATE1_LIGHT + 0].value = ((pulsesPerStep != 1) ? lights[GATE1_LIGHT + 1].value : 0.0f); - lights[GATE1_PROB_LIGHT].value = ((attributesVal & ATT_MSK_GATE1P) != 0) ? 1.0f : 0.0f; - lights[GATE2_LIGHT + 1].value = ((attributesVal & ATT_MSK_GATE2) != 0) ? 1.0f : 0.0f; - lights[GATE2_LIGHT + 0].value = ((pulsesPerStep != 1) ? lights[GATE2_LIGHT + 1].value : 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; + + // 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); + if (editingGateLength != 0 && editingSequence) { + int modeLightIndex = gateModeToKeyLightIndex(attributes[sequence][stepIndexEdit], editingGateLength > 0l); + for (int i = 0; i < 12; i++) { + if (i == modeLightIndex) { + lights[KEY_LIGHTS + i * 2 + 0].value = editingGateLength > 0l ? 1.0f : 0.2f; + lights[KEY_LIGHTS + i * 2 + 1].value = editingGateLength > 0l ? 0.2f : 1.0f; + } + else { + lights[KEY_LIGHTS + i * 2 + 0].value = 0.0f; + if (i == keyLightIndex) + lights[KEY_LIGHTS + i * 2 + 1].value = 0.1f; + else + lights[KEY_LIGHTS + i * 2 + 1].value = 0.0f; + } + } + } + else { + for (int i = 0; i < 12; i++) { + lights[KEY_LIGHTS + i * 2 + 0].value = 0.0f; + 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 * 2 + 1].value = 0.0f; + else { + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips)); + lights[KEY_LIGHTS + i * 2 + 1].value = (warningFlashState && i == keyLightIndex) ? 1.0f : 0.0f; + } + else { + if (editingGate > 0ul && editingGateKeyLight != -1) + lights[KEY_LIGHTS + i * 2 + 1].value = (i == editingGateKeyLight ? ((float) editingGate / (float)(gateTime * sampleRate / displayRefreshStepSkips)) : 0.0f); + else + lights[KEY_LIGHTS + i * 2 + 1].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]; + // + setGateLight(getGate1a(attributesVal), GATE1_LIGHT); + setGateLight(getGate2a(attributesVal), GATE2_LIGHT); + lights[GATE1_PROB_LIGHT].value = getGate1Pa(attributesVal) ? 1.0f : 0.0f; + lights[SLIDE_LIGHT].value = getSlideA(attributesVal) ? 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 = getTiedA(attributesVal) ? 1.0f : 0.0f; - // Attach light - lights[ATTACH_LIGHT].value = (running && attached) ? 1.0f : 0.0f; - - // Reset light - lights[RESET_LIGHT].value = resetLight; + // 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 = running ? 1.0f : 0.0f; + + if (tiedWarning > 0l) + tiedWarning--; + if (editingLength > 0ul) + editingLength--; + if (editingGate > 0ul) + editingGate--; + if (infoCopyPaste != 0l) { + if (infoCopyPaste > 0l) + infoCopyPaste --; + if (infoCopyPaste < 0l) + infoCopyPaste ++; + } + if (modeHoldDetect.process(params[RUNMODE_PARAM].value)) { + displayState = DISP_NORMAL; + editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips); + } + if (gate1HoldDetect.process(params[GATE1_PARAM].value)) { + toggleGate1a(&attributes[sequence][stepIndexEdit]); + editGateLengthTimeInitMult = 1; + } + if (gate2HoldDetect.process(params[GATE2_PARAM].value)) { + toggleGate2a(&attributes[sequence][stepIndexEdit]); + editGateLengthTimeInitMult = 100; + } + if (editingPpqn > 0l) + editingPpqn--; + if (editingGateLength != 0l) { + if (editingGateLength > 0l) + editingGateLength --; + if (editingGateLength < 0l) + editingGateLength ++; + } + }// lightRefreshCounter - // Run light - 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--; - if (gate1HoldDetect > 0l) { - if (params[GATE1_PARAM].value < 0.5f) - gate1HoldDetect = 0l; - else { - if (gate1HoldDetect == 1l) { - attributes[sequence][stepIndexEdit] |= ATT_MSK_GATE1; - if (pulsesPerStep == 1) { - pulsesPerStep = 4;// default - } - editingPpqn = (long) (editGateLengthTime * engineGetSampleRate()); - } - gate1HoldDetect--; - } - } - if (editingGateLength != 0l) { - if (editingGateLength > 0l) - editingGateLength --; - if (editingGateLength < 0l) - editingGateLength ++; - } - if (editingPpqn > 0l) - editingPpqn--; }// step() @@ -1254,6 +1264,22 @@ struct PhraseSeq16 : Module { int calcNewGateMode(int currentGateMode, int deltaKnob) { return clamp(currentGateMode + deltaKnob, 0, NUM_GATES - 1); } + + inline void setGateLight(bool gateOn, int lightIndex) { + if (!gateOn) { + lights[lightIndex + 0].value = 0.0f; + lights[lightIndex + 1].value = 0.0f; + } + else if (pulsesPerStep == 1) { + lights[lightIndex + 0].value = 0.0f; + lights[lightIndex + 1].value = 1.0f; + } + else { + lights[lightIndex + 0].value = lightIndex == GATE1_LIGHT ? 1.0f : 0.2f; + lights[lightIndex + 1].value = lightIndex == GATE1_LIGHT ? 0.2f : 1.0f; + } + } + }; @@ -1278,10 +1304,6 @@ struct PhraseSeq16Widget : ModuleWidget { if (num >= 0 && num < NUM_MODES) snprintf(displayStr, 4, "%s", modeLabels[num].c_str()); } - void gateModeToStr(int num) { - if (num >= 0 && num < NUM_GATES) - snprintf(displayStr, 4, "%s", gateLabels[num].c_str()); - } void draw(NVGcontext *vg) override { NVGcolor textColor = prepareDisplay(vg, &box); @@ -1293,51 +1315,40 @@ struct PhraseSeq16Widget : 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 == PhraseSeq16::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->editingPpqn != 0ul) { - snprintf(displayStr, 4, "x%2u", (unsigned) module->pulsesPerStep); - } - else if (module->editingGateLength != 0l) { - if (module->editingGateLength > 0l) - gateModeToStr(module->getGate1Mode(module->sequence, module->stepIndexEdit)); - else - gateModeToStr(module->getGate2Mode(module->sequence, module->stepIndexEdit)); - } - else if (module->displayState == PhraseSeq16::DISP_TRANSPOSE) { - snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset)); - if (module->transposeOffset < 0) - displayStr[0] = '-'; - } - else if (module->displayState == PhraseSeq16::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->editingPpqn != 0ul) { + snprintf(displayStr, 4, "x%2u", (unsigned) module->pulsesPerStep); + } + else if (module->displayState == PhraseSeq16::DISP_MODE) { + if (module->editingSequence) + runModeToStr(module->runModeSeq[module->sequence]); + else + runModeToStr(module->runModeSong); + } + else if (module->displayState == PhraseSeq16::DISP_TRANSPOSE) { + snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset)); + if (module->transposeOffset < 0) + displayStr[0] = '-'; + } + else if (module->displayState == PhraseSeq16::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); } }; @@ -1416,7 +1427,7 @@ struct PhraseSeq16Widget : ModuleWidget { 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++) - rack::global_ui->app.gRackWidget->wireContainer->removeAllWires(expPorts[i]); + rack::global_ui->app.gRackWidget->wireContainer->removeAllWires(expPorts[i]); } oldExpansion = module->expansion; } @@ -1429,14 +1440,14 @@ struct PhraseSeq16Widget : ModuleWidget { oldExpansion = -1; // Main panel from Inkscape - panel = new DynamicSVGPanel(); - panel->mode = &module->panelTheme; + panel = new DynamicSVGPanel(); + panel->mode = &module->panelTheme; panel->expWidth = &expWidth; - panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/PhraseSeq16.svg"))); - panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/PhraseSeq16_dark.svg"))); - box.size = panel->box.size; + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/PhraseSeq16.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/PhraseSeq16_dark.svg"))); + box.size = panel->box.size; box.size.x = box.size.x - (1 - module->expansion) * expWidth; - addChild(panel); + addChild(panel); // Screws addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); @@ -1482,35 +1493,36 @@ struct PhraseSeq16Widget : ModuleWidget { } // Keys and Key lights static const int keyNudgeX = 7; - static const int keyNudgeY = 2; + static const int KeyBlackY = 103; + static const int KeyWhiteY = 141; static const int offsetKeyLEDx = 6; - static const int offsetKeyLEDy = 28; + static const int offsetKeyLEDy = 16; // Black keys and lights - addParam(ParamWidget::create( Vec(65+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 1, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(65+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 1)); - addParam(ParamWidget::create( Vec(93+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 3, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(93+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 3)); - addParam(ParamWidget::create( Vec(150+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 6, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(150+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 6)); - addParam(ParamWidget::create( Vec(178+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 8, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(178+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 8)); - addParam(ParamWidget::create( Vec(206+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 10, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(206+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 10)); + addParam(ParamWidget::create( Vec(65+keyNudgeX, KeyBlackY), module, PhraseSeq16::KEY_PARAMS + 1, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(65+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 1 * 2)); + addParam(ParamWidget::create( Vec(93+keyNudgeX, KeyBlackY), module, PhraseSeq16::KEY_PARAMS + 3, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(93+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 3 * 2)); + addParam(ParamWidget::create( Vec(150+keyNudgeX, KeyBlackY), module, PhraseSeq16::KEY_PARAMS + 6, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(150+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 6 * 2)); + addParam(ParamWidget::create( Vec(178+keyNudgeX, KeyBlackY), module, PhraseSeq16::KEY_PARAMS + 8, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(178+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 8 * 2)); + addParam(ParamWidget::create( Vec(206+keyNudgeX, KeyBlackY), module, PhraseSeq16::KEY_PARAMS + 10, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(206+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 10 * 2)); // White keys and lights - addParam(ParamWidget::create( Vec(51+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 0, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(51+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 0)); - addParam(ParamWidget::create( Vec(79+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 2, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(79+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 2)); - addParam(ParamWidget::create( Vec(107+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 4, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(107+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 4)); - addParam(ParamWidget::create( Vec(136+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 5, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(136+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 5)); - addParam(ParamWidget::create( Vec(164+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 7, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(164+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 7)); - addParam(ParamWidget::create( Vec(192+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 9, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(192+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 9)); - addParam(ParamWidget::create( Vec(220+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 11, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(220+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 11)); + addParam(ParamWidget::create( Vec(51+keyNudgeX, KeyWhiteY), module, PhraseSeq16::KEY_PARAMS + 0, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(51+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 0 * 2)); + addParam(ParamWidget::create( Vec(79+keyNudgeX, KeyWhiteY), module, PhraseSeq16::KEY_PARAMS + 2, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(79+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 2 * 2)); + addParam(ParamWidget::create( Vec(107+keyNudgeX, KeyWhiteY), module, PhraseSeq16::KEY_PARAMS + 4, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(107+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 4 * 2)); + addParam(ParamWidget::create( Vec(136+keyNudgeX, KeyWhiteY), module, PhraseSeq16::KEY_PARAMS + 5, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(136+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 5 * 2)); + addParam(ParamWidget::create( Vec(164+keyNudgeX, KeyWhiteY), module, PhraseSeq16::KEY_PARAMS + 7, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(164+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 7 * 2)); + addParam(ParamWidget::create( Vec(192+keyNudgeX, KeyWhiteY), module, PhraseSeq16::KEY_PARAMS + 9, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(192+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 9 * 2)); + addParam(ParamWidget::create( Vec(220+keyNudgeX, KeyWhiteY), module, PhraseSeq16::KEY_PARAMS + 11, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(220+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 11 * 2)); @@ -1519,7 +1531,7 @@ struct PhraseSeq16Widget : ModuleWidget { static const int rowRulerMK0 = 101;// Edit mode row static const int rowRulerMK1 = rowRulerMK0 + 56; // Run row static const int rowRulerMK2 = rowRulerMK1 + 54; // Reset row - static const int columnRulerMK0 = 276;// Edit mode column + static const int columnRulerMK0 = 277;// Edit mode column static const int columnRulerMK1 = columnRulerMK0 + 59;// Display column static const int columnRulerMK2 = columnRulerT3;// Run mode column @@ -1648,7 +1660,13 @@ RACK_PLUGIN_MODEL_INIT(ImpromptuModular, PhraseSeq16) { /*CHANGE LOG +0.6.11: +step optimization of lights refresh + + 0.6.10: +add advanced gate mode +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: diff --git a/plugins/community/repos/ImpromptuModular/src/PhraseSeq32.cpp b/plugins/community/repos/ImpromptuModular/src/PhraseSeq32.cpp index cab40249..8fc9a339 100644 --- a/plugins/community/repos/ImpromptuModular/src/PhraseSeq32.cpp +++ b/plugins/community/repos/ImpromptuModular/src/PhraseSeq32.cpp @@ -13,7 +13,7 @@ #include "ImpromptuModular.hpp" -#include "dsp/digital.hpp" +#include "PhraseSeqUtil.hpp" namespace rack_plugin_ImpromptuModular { @@ -76,7 +76,7 @@ struct PhraseSeq32 : Module { enum LightIds { ENUMS(STEP_PHRASE_LIGHTS, 32 * 2),// room for GreenRed ENUMS(OCTAVE_LIGHTS, 7),// octaves 1 to 7 - ENUMS(KEY_LIGHTS, 12), + ENUMS(KEY_LIGHTS, 12 * 2),// room for GreenRed RUN_LIGHT, RESET_LIGHT, ENUMS(GATE1_LIGHT, 2),// room for GreenRed @@ -89,17 +89,12 @@ struct PhraseSeq32 : Module { }; enum DisplayStateIds {DISP_NORMAL, DISP_MODE, DISP_LENGTH, DISP_TRANSPOSE, DISP_ROTATE}; - 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; // Need to save int panelTheme = 0; int expansion = 0; - int pulsesPerStep;// 1 means normal gate mode, alt choices are 4, 12, 24 PPS (Pulses per step) + int pulsesPerStep;// 1 means normal gate mode, alt choices are 4, 6, 12, 24 PPS (Pulses per step) bool running; int runModeSeq[32]; int runModeSong; @@ -144,13 +139,15 @@ struct PhraseSeq32 : Module { unsigned long clockPeriod;// counts number of step() calls upward from last clock (reset after clock processed) long tiedWarning;// 0 when no warning, positive downward step counter timer when warning int sequenceKnob = 0; - bool gate1RandomEnable[2]; + int gate1Code[2]; + int gate2Code[2]; bool attachedChanB; long revertDisplay; - long gate1HoldDetect;// 0 when not detecting, downward counter when detecting long editingGateLength;// 0 when no info, positive downward step counter timer when gate1, negative upward when gate2 + long editGateLengthTimeInitMult;// multiplier for extended setting of advanced gates long editingPpqn;// 0 when no info, positive downward step counter timer when editing ppqn int ppqnCount; + int lightRefreshCounter; static constexpr float CONFIG_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget int stepConfigLast; @@ -180,18 +177,20 @@ struct PhraseSeq32 : Module { SchmittTrigger transposeTrigger; SchmittTrigger tiedTrigger; SchmittTrigger stepTriggers[32]; + HoldDetect modeHoldDetect; + HoldDetect gate1HoldDetect; + HoldDetect gate2HoldDetect; - inline bool getGate1(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1) != 0;} - inline bool getGate2(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE2) != 0;} - inline bool getGate1P(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1P) != 0;} - inline bool getTied(int seq, int step) {return (attributes[seq][step] & ATT_MSK_TIED) != 0;} inline bool isEditingSequence(void) {return params[EDIT_PARAM].value > 0.5f;} - inline bool calcGate1RandomEnable(bool gate1P) {return (randomUniform() < (params[GATE1_KNOB_PARAM].value)) || !gate1P;}// randomUniform is [0.0, 1.0), see include/util/common.hpp - inline int ppsToIndex() {return (pulsesPerStep == 24 ? 3 : (pulsesPerStep == 12 ? 2 : (pulsesPerStep == 4 ? 1 : 0)));}// map 1,4,12,24, to 0,1,2,3 - inline int indexToPps(int index) {return (index == 3 ? 24 : (index == 2 ? 12 : (index == 1 ? 4 : 1)));}// inverse map of above - inline int getGate1Mode(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1MODE) >> gate1ModeShift;} - inline int getGate2Mode(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE2MODE) >> gate2ModeShift;} + inline bool getGate1(int seq, int step) {return getGate1a(attributes[seq][step]);} + inline bool getGate1P(int seq, int step) {return getGate1Pa(attributes[seq][step]);} + inline bool getGate2(int seq, int step) {return getGate2a(attributes[seq][step]);} + inline bool getSlide(int seq, int step) {return getSlideA(attributes[seq][step]);} + inline bool getTied(int seq, int step) {return getTiedA(attributes[seq][step]);} + inline int getGate1Mode(int seq, int step) {return getGate1aMode(attributes[seq][step]);} + inline int getGate2Mode(int seq, int step) {return getGate2aMode(attributes[seq][step]);} + inline void setGate1Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE1MODE; attributes[seq][step] |= (gateMode << gate1ModeShift);} inline void setGate2Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE2MODE; attributes[seq][step] |= (gateMode << gate2ModeShift);} @@ -243,9 +242,12 @@ struct PhraseSeq32 : Module { editingSequence = EDIT_PARAM_INIT_VALUE > 0.5f; editingSequenceLast = editingSequence; resetOnRun = false; - gate1HoldDetect = 0l; - editingGateLength = 0l; + modeHoldDetect.reset(); + gate1HoldDetect.reset(); + gate2HoldDetect.reset(); + editGateLengthTimeInitMult = 1l; editingPpqn = 0l; + lightRefreshCounter = 0; } @@ -264,12 +266,11 @@ struct PhraseSeq32 : Module { for (int i = 0; i < 32; i++) { for (int s = 0; s < 32; s++) { cv[i][s] = ((float)(randomu32() % 7)) + ((float)(randomu32() % 12)) / 12.0f - 3.0f; - attributes[i][s] = randomu32() % 32;// 32 because 5 attributes + attributes[i][s] = randomu32() & 0x1FFF;// 5 bit for normal attributes + 2 * 4 bits for advanced gate modes if (getTied(i,s)) { attributes[i][s] = ATT_MSK_TIED;// clear other attributes if tied applyTiedStep(i, s, lengths[i]); } - // TODO Randomize gate lengths (even though randomize forces ppqn to 1, can be useful when set to other than 1 after a random) } runModeSeq[i] = randomu32() % NUM_MODES; phrase[i] = randomu32() % 32; @@ -294,29 +295,27 @@ struct PhraseSeq32 : Module { editingSequence = isEditingSequence(); editingSequenceLast = editingSequence; resetOnRun = false; + modeHoldDetect.reset(); + gate1HoldDetect.reset(); + gate2HoldDetect.reset(); + editGateLengthTimeInitMult = 1l; + editingPpqn = 0l; } void initRun(int stepConfig, bool hard) {// run button activated or run edge in run input jack or edit mode toggled - if (hard) { + if (hard) phraseIndexRun = (runModeSong == MODE_REV ? phrases - 1 : 0); - if (editingSequence) - stepIndexRun = (runModeSeq[sequence] == MODE_REV ? lengths[sequence] - 1 : 0); - else - stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0); - } - gate1RandomEnable[0] = false; - gate1RandomEnable[1] = false; + int seq = (editingSequence ? sequence : phrase[phraseIndexRun]); + if (hard) + stepIndexRun = (runModeSeq[seq] == MODE_REV ? lengths[seq] - 1 : 0); ppqnCount = 0; - if (editingSequence) { - for (int i = 0; i < 2; i += stepConfig) - gate1RandomEnable[i] = calcGate1RandomEnable(getGate1P(sequence, (i * 16) + stepIndexRun)); - } - else { - for (int i = 0; i < 2; i += stepConfig) - gate1RandomEnable[i] = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun], (i * 16) + stepIndexRun)); + for (int i = 0; i < 2; i += stepConfig) { + gate1Code[i] = calcGate1Code(attributes[seq][(i * 16) + stepIndexRun], 0, pulsesPerStep, params[GATE1_KNOB_PARAM].value); + gate2Code[i] = calcGate2Code(attributes[seq][(i * 16) + stepIndexRun], 0, pulsesPerStep); } clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + editingGateLength = 0l; } @@ -530,17 +529,16 @@ struct PhraseSeq32 : Module { static const float copyPasteInfoTime = 0.5f;// seconds static const float revertDisplayTime = 0.7f;// seconds static const float tiedWarningTime = 0.7f;// seconds - static const float gateHoldDetectTime = 2.0f;// seconds - static const float editGateLengthTime = 2.5f;// seconds - long tiedWarningInit = (long) (tiedWarningTime * sampleRate); + static const float holdDetectTime = 2.0f;// seconds + static const float editGateLengthTime = 4.0f;// seconds //********** Buttons, knobs, switches and inputs ********** // Notes: // * a tied step's attributes can not be modified by any of the following: - // write input, oct and keyboard buttons, gate1, gate1Prob, gate2 and slide buttons - // however, paste, transpose, rotate obviously can. + // write input, oct and keyboard buttons, gate1Prob and slide buttons + // however, paste, transpose, rotate obviously can, and gate1/2 can be turned back on if desired. // * Whenever cv[][] is modified or tied[] is activated for a step, call applyTiedStep(sequence,stepIndexEdit,steps) @@ -568,8 +566,6 @@ struct PhraseSeq32 : Module { // Seq CV input if (inputs[SEQCV_INPUT].active) { sequence = (int) clamp( round(inputs[SEQCV_INPUT].value * (32.0f - 1.0f) / 10.0f), 0.0f, (32.0f - 1.0f) ); - //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities - //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities } // Mode CV input if (inputs[MODECV_INPUT].active) { @@ -604,8 +600,7 @@ struct PhraseSeq32 : Module { // Copy button if (copyTrigger.process(params[COPY_PARAM].value)) { if (editingSequence) { - infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); - //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 + infoCopyPaste = (long) (copyPasteInfoTime * sampleRate / displayRefreshStepSkips); int sStart = stepIndexEdit; int sCount = 32; if (params[CPMODE_PARAM].value > 1.5f)// all @@ -630,7 +625,7 @@ struct PhraseSeq32 : 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 == 32) ? 0 : stepIndexEdit); int sCount = countCP; for (int i = 0, s = sStart; i < countCP; i++, s++) { @@ -655,42 +650,24 @@ struct PhraseSeq32 : Module { bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value); if (writeTrig) { if (editingSequence) { - if (getTied(sequence,stepIndexEdit) & !inputs[TIEDCV_INPUT].active) - tiedWarning = tiedWarningInit; - else { - cv[sequence][stepIndexEdit] = inputs[CV_INPUT].value; - applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]); - // Extra CVs from expansion panel: - if (inputs[TIEDCV_INPUT].active) - attributes[sequence][stepIndexEdit] = (inputs[TIEDCV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_TIED) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_TIED); - if (getTied(sequence, stepIndexEdit)) - attributes[sequence][stepIndexEdit] = ATT_MSK_TIED;// clear other attributes if tied - else { - if (inputs[GATE1CV_INPUT].active) - attributes[sequence][stepIndexEdit] = (inputs[GATE1CV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_GATE1) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_GATE1); - if (inputs[GATE2CV_INPUT].active) - attributes[sequence][stepIndexEdit] = (inputs[GATE2CV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_GATE2) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_GATE2); - if (inputs[SLIDECV_INPUT].active) - attributes[sequence][stepIndexEdit] = (inputs[SLIDECV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_SLIDE) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_SLIDE); - } - editingGate = (unsigned long) (gateTime * engineGetSampleRate()); - editingGateCV = cv[sequence][stepIndexEdit]; - editingGateKeyLight = -1; - editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; - // Autostep (after grab all active inputs) - if (params[AUTOSTEP_PARAM].value > 0.5f) { - stepIndexEdit += 1; - //if (stepConfig == 1) { // Commented for full edit capabilities (whole if) - // if (stepIndexEdit >= lengths[sequence] && stepIndexEdit < 16)// if past length in chan A - // stepIndexEdit = 16; - // else if (stepIndexEdit >= 16 + lengths[sequence])// if past length in chan B (including >=32) - // stepIndexEdit = 0; - //} - //else // Commented for full edit capabilities - if (stepIndexEdit >= 32)//lengths[sequence])// Commented for full edit capabilities - stepIndexEdit = 0; - } - } + cv[sequence][stepIndexEdit] = inputs[CV_INPUT].value; + // Extra CVs from expansion panel: + if (inputs[TIEDCV_INPUT].active) + setTiedA(&attributes[sequence][stepIndexEdit], inputs[TIEDCV_INPUT].value > 1.0f); + if (inputs[GATE1CV_INPUT].active) + setGate1a(&attributes[sequence][stepIndexEdit], inputs[GATE1CV_INPUT].value > 1.0f); + if (inputs[GATE2CV_INPUT].active) + setGate2a(&attributes[sequence][stepIndexEdit], inputs[GATE2CV_INPUT].value > 1.0f); + if (inputs[SLIDECV_INPUT].active) + setSlideA(&attributes[sequence][stepIndexEdit], inputs[SLIDECV_INPUT].value > 1.0f); + applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]); + editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; + // Autostep (after grab all active inputs) + if (params[AUTOSTEP_PARAM].value > 0.5f) + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 32); } displayState = DISP_NORMAL; } @@ -713,14 +690,11 @@ struct PhraseSeq32 : Module { if (lengths[sequence] > (16 * stepConfig)) lengths[sequence] = (16 * stepConfig); if (lengths[sequence] < 1 ) lengths[sequence] = 1; lengths[sequence] = ((lengths[sequence] - 1) % (16 * stepConfig)) + 1; - //if ( (stepIndexEdit % (16 * stepConfig)) >= lengths[sequence])// Commented for full edit capabilities - //stepIndexEdit = (lengths[sequence] - 1) + 16 * (stepIndexEdit / (16 * stepConfig));// Commented for full edit capabilities } else { phrases += delta; if (phrases > 32) phrases = 32; if (phrases < 1 ) phrases = 1; - //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities } } else { @@ -729,18 +703,11 @@ struct PhraseSeq32 : Module { stepIndexEdit += delta; if (stepIndexEdit < 0) stepIndexEdit = ((stepConfig == 1) ? 16 : 0) + lengths[sequence] - 1; - //if (stepConfig == 1) {// Commented for full edit capabilities (whole if) - // if (stepIndexEdit >= lengths[sequence] && stepIndexEdit < 16)// if past length in chan A - // stepIndexEdit = delta > 0 ? 16 : lengths[sequence] - 1; - // else if (stepIndexEdit >= 16 + lengths[sequence])// if past length in chan B (including >=32) - // stepIndexEdit = delta > 0 ? 0 : 16 + lengths[sequence] - 1; - //} - //else// Commented for full edit capabilities - if (stepIndexEdit >= 32)//lengths[sequence]) // Commented for full edit capabilities - stepIndexEdit = 0; + if (stepIndexEdit >= 32) + stepIndexEdit = 0; 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; editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; @@ -748,7 +715,7 @@ struct PhraseSeq32 : Module { } } else - phraseIndexEdit = moveIndex(phraseIndexEdit, phraseIndexEdit + delta, 32);//phrases);// Commented for full edit capabilities + phraseIndexEdit = moveIndex(phraseIndexEdit, phraseIndexEdit + delta, 32); } } } @@ -761,25 +728,18 @@ struct PhraseSeq32 : Module { } if (stepPressed != -1) { if (displayState == DISP_LENGTH) { - if (editingSequence) { + if (editingSequence) lengths[sequence] = (stepPressed % (16 * stepConfig)) + 1; - //if ( (stepIndexEdit % (16 * stepConfig)) >= lengths[sequence])// Commented for full edit capabilities - //stepIndexEdit = (lengths[sequence] - 1) + 16 * (stepIndexEdit / (16 * stepConfig));// Commented for full edit capabilities - } - else { + else phrases = stepPressed + 1; - //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities - } - revertDisplay = (long) (revertDisplayTime * engineGetSampleRate()); + revertDisplay = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips); } else { if (!running || !attached) {// not running or detached if (editingSequence) { stepIndexEdit = stepPressed; - //if ( (stepIndexEdit % (16 * stepConfig)) >= lengths[sequence])// Commented for full edit capabilities - //stepIndexEdit = (lengths[sequence] - 1) + 16 * (stepIndexEdit / (16 * stepConfig));// Commented for full edit capabilities if (!getTied(sequence,stepIndexEdit)) {// play if non-tied step - editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips); editingGateCV = cv[sequence][stepIndexEdit]; editingGateKeyLight = -1; editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; @@ -787,8 +747,6 @@ struct PhraseSeq32 : Module { } else { phraseIndexEdit = stepPressed; - //if (phraseIndexEdit >= phrases)// Commented for full edit capabilities - //phraseIndexEdit = phrases - 1;// Commented for full edit capabilities } } else {// attached and running @@ -811,6 +769,9 @@ struct PhraseSeq32 : Module { displayState = DISP_MODE; else displayState = DISP_NORMAL; + //if (!running) { + modeHoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips)); + //} } // Transpose/Rotate button @@ -831,25 +792,15 @@ struct PhraseSeq32 : Module { // Sequence knob float seqParamValue = params[SEQUENCE_PARAM].value; - int newSequenceKnob = (int)roundf(seqParamValue * 7.0f); - if (seqParamValue == 0.0f)// true when constructor or fromJson() occured - sequenceKnob = newSequenceKnob; - int deltaKnob = newSequenceKnob - sequenceKnob; + int newSequenceKnob = int(seqParamValue * 7.0f + 0.5f); + if (seqParamValue == 0.0f)// true when constructor or fromJson() occured + sequenceKnob = newSequenceKnob; + int deltaKnob = newSequenceKnob - sequenceKnob; if (deltaKnob != 0) { if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example) if (editingPpqn != 0) { - editingPpqn = (long) (editGateLengthTime * engineGetSampleRate()); - pulsesPerStep = indexToPps(clamp(ppsToIndex() + deltaKnob, 0, 3)); - } - else if (editingGateLength != 0) { - if (editingGateLength > 0) { - editingGateLength = (long) (editGateLengthTime * engineGetSampleRate()); - setGate1Mode(sequence, stepIndexEdit, calcNewGateMode(getGate1Mode(sequence, stepIndexEdit), deltaKnob)); - } - else { - editingGateLength = (long) (-1 * editGateLengthTime * engineGetSampleRate()); - setGate2Mode(sequence, stepIndexEdit, calcNewGateMode(getGate2Mode(sequence, stepIndexEdit), deltaKnob)); - } + pulsesPerStep = indexToPps(ppsToIndex(pulsesPerStep) + deltaKnob);// indexToPps() does clamping + editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips); } else if (displayState == DISP_MODE) { if (editingSequence) { @@ -870,14 +821,11 @@ struct PhraseSeq32 : Module { lengths[sequence] += deltaKnob; if (lengths[sequence] > (16 * stepConfig)) lengths[sequence] = (16 * stepConfig); if (lengths[sequence] < 1 ) lengths[sequence] = 1; - //if ( (stepIndexEdit % (16 * stepConfig)) >= lengths[sequence])// Commented for full edit capabilities - //stepIndexEdit = (lengths[sequence] - 1) + 16 * (stepIndexEdit / (16 * stepConfig));// Commented for full edit capabilities } else { phrases += deltaKnob; if (phrases > 32) phrases = 32; if (phrases < 1 ) phrases = 1; - //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities } } else if (displayState == DISP_TRANSPOSE) { @@ -919,16 +867,6 @@ struct PhraseSeq32 : Module { sequence += deltaKnob; if (sequence < 0) sequence = 0; if (sequence >= 32) sequence = (32 - 1); - //if (stepConfig == 1) {// Commented for full edit capabilities (whole if/else) - // if (stepIndexEdit >= 16 && (stepIndexEdit - 16) >= lengths[sequence]) - // stepIndexEdit = 16 + lengths[sequence] - 1; - // else if (stepIndexEdit < 16 && (stepIndexEdit) >= lengths[sequence]) - // stepIndexEdit = lengths[sequence] - 1; - //} - //else { - // if (stepIndexEdit >= lengths[sequence]) - // stepIndexEdit = lengths[sequence] - 1; - //} } } else { @@ -947,7 +885,7 @@ struct PhraseSeq32 : Module { } } } - sequenceKnob = newSequenceKnob; + sequenceKnob = newSequenceKnob; } // Octave buttons @@ -961,7 +899,7 @@ struct PhraseSeq32 : 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); @@ -969,7 +907,7 @@ struct PhraseSeq32 : Module { cv[sequence][stepIndexEdit] = newCV; applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]); } - editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips); editingGateCV = cv[sequence][stepIndexEdit]; editingGateKeyLight = -1; editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; @@ -981,16 +919,33 @@ struct PhraseSeq32 : Module { for (int i = 0; i < 12; i++) { if (keyTriggers[i].process(params[KEY_PARAMS + i].value)) { if (editingSequence) { - if (getTied(sequence,stepIndexEdit)) { + if (editingGateLength != 0l) { + int newMode = keyIndexToGateMode(i, pulsesPerStep); + if (editingGateLength > 0l) { + if (newMode != -1) + setGate1Mode(sequence, stepIndexEdit, newMode); + else + editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips); + editingGateLength = ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult); + } + else { + if (newMode != -1) + setGate2Mode(sequence, stepIndexEdit, newMode); + else + editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips); + editingGateLength = -1l * ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult); + } + } + else if (getTied(sequence,stepIndexEdit)) { if (params[KEY_PARAMS + i].value > 1.5f) stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 32); else - tiedWarning = tiedWarningInit; + tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips); } else { cv[sequence][stepIndexEdit] = floor(cv[sequence][stepIndexEdit]) + ((float) i) / 12.0f; applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]); - editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips); editingGateCV = cv[sequence][stepIndexEdit]; editingGateKeyLight = -1; editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; @@ -1007,15 +962,12 @@ struct PhraseSeq32 : 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; - if (!running) { - if (pulsesPerStep != 1) - editingGateLength = getGate1(sequence,stepIndexEdit) ? (long) (editGateLengthTime * engineGetSampleRate()) : 0l; - gate1HoldDetect = (long) (gateHoldDetectTime * engineGetSampleRate()); - } + toggleGate1a(&attributes[sequence][stepIndexEdit]); + //if (!running) { + if (pulsesPerStep != 1) { + editingGateLength = getGate1(sequence,stepIndexEdit) ? ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult) : 0l; + gate1HoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips)); + } //} } displayState = DISP_NORMAL; @@ -1023,21 +975,19 @@ struct PhraseSeq32 : Module { 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; + toggleGate1Pa(&attributes[sequence][stepIndexEdit]); } displayState = DISP_NORMAL; } if (gate2Trigger.process(params[GATE2_PARAM].value)) { if (editingSequence) { - //if (getTied(sequence,stepIndexEdit)) - //tiedWarning = tiedWarningInit; - //else { - attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE2; - if (!running) { - if (pulsesPerStep != 1) - editingGateLength = getGate2(sequence,stepIndexEdit) ? (long) (-1 * editGateLengthTime * engineGetSampleRate()) : 0l; + toggleGate2a(&attributes[sequence][stepIndexEdit]); + //if (!running) { + if (pulsesPerStep != 1) { + editingGateLength = getGate2(sequence,stepIndexEdit) ? -1l * ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult) : 0l; + gate2HoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips)); } //} } @@ -1046,21 +996,21 @@ struct PhraseSeq32 : Module { 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; + toggleSlideA(&attributes[sequence][stepIndexEdit]); } displayState = DISP_NORMAL; } if (tiedTrigger.process(params[TIE_PARAM].value)) { if (editingSequence) { - attributes[sequence][stepIndexEdit] ^= ATT_MSK_TIED; + toggleTiedA(&attributes[sequence][stepIndexEdit]); if (getTied(sequence,stepIndexEdit)) { - attributes[sequence][stepIndexEdit] = ATT_MSK_TIED;// clear other attributes if tied + setGate1a(&attributes[sequence][stepIndexEdit], false); + setGate2a(&attributes[sequence][stepIndexEdit], false); + setSlideA(&attributes[sequence][stepIndexEdit], false); applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]); } - else - attributes[sequence][stepIndexEdit] |= (ATT_MSK_GATE1 | ATT_MSK_GATE2); } displayState = DISP_NORMAL; } @@ -1070,18 +1020,18 @@ struct PhraseSeq32 : Module { // Clock if (clockTrigger.process(inputs[CLOCK_INPUT].value)) { - if (ppqnCount >= (pulsesPerStep - 1)) { - if (running && clockIgnoreOnReset == 0l) { + if (running && clockIgnoreOnReset == 0l) { + ppqnCount++; + if (ppqnCount >= pulsesPerStep) + ppqnCount = 0; + + int newSeq = sequence;// good value when editingSequence, overwrite if not editingSequence + if (ppqnCount == 0) { float slideFromCV[2] = {0.0f, 0.0f}; - float slideToCV[2] = {0.0f, 0.0f}; if (editingSequence) { for (int i = 0; i < 2; i += stepConfig) slideFromCV[i] = cv[sequence][(i * 16) + stepIndexRun]; moveIndexRunMode(&stepIndexRun, lengths[sequence], runModeSeq[sequence], &stepIndexRunHistory); - for (int i = 0; i < 2; i += stepConfig) { - slideToCV[i] = cv[sequence][(i * 16) + stepIndexRun]; - gate1RandomEnable[i] = calcGate1RandomEnable(getGate1P(sequence, (i * 16) + stepIndexRun));// must be calculated on clock edge only - } } else { for (int i = 0; i < 2; i += stepConfig) @@ -1090,29 +1040,32 @@ struct PhraseSeq32 : Module { moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory); stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0);// must always refresh after phraseIndexRun has changed } - for (int i = 0; i < 2; i += stepConfig) { - slideToCV[i] = cv[phrase[phraseIndexRun]][(i * 16) + stepIndexRun]; - gate1RandomEnable[i] = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun], (i * 16) + stepIndexRun));// must be calculated on clock edge only - } + newSeq = phrase[phraseIndexRun]; } // Slide for (int i = 0; i < 2; i += stepConfig) { - if ( ( editingSequence && ((attributes[sequence][(i * 16) + stepIndexRun] & ATT_MSK_SLIDE) != 0) ) || - (!editingSequence && ((attributes[phrase[phraseIndexRun]][(i * 16) + stepIndexRun] & ATT_MSK_SLIDE) != 0) ) ) { - // avtivate sliding (slideStepsRemain can be reset, else runs down to 0, either way, no need to reinit) - slideStepsRemain[i] = (unsigned long) (((float)clockPeriod) * params[SLIDE_KNOB_PARAM].value / 2.0f);// 0-T slide, where T is clock period (can be too long when user does clock gating) + if (getSlide(newSeq, (i * 16) + stepIndexRun)) { + // activate sliding (slideStepsRemain can be reset, else runs down to 0, either way, no need to reinit) + slideStepsRemain[i] = (unsigned long) (((float)clockPeriod * pulsesPerStep) * params[SLIDE_KNOB_PARAM].value / 2.0f);// 0-T slide, where T is clock period (can be too long when user does clock gating) //slideStepsRemain[i] = (unsigned long) (engineGetSampleRate() * params[SLIDE_KNOB_PARAM].value );// 0-2s slide - slideCVdelta[i] = (slideToCV[i] - slideFromCV[i])/(float)slideStepsRemain[i]; + float slideToCV = cv[newSeq][(i * 16) + stepIndexRun]; + slideCVdelta[i] = (slideToCV - slideFromCV[i])/(float)slideStepsRemain[i]; } } } - clockPeriod = 0ul; - ppqnCount = 0; + else { + if (!editingSequence) + newSeq = phrase[phraseIndexRun]; + } + for (int i = 0; i < 2; i += stepConfig) { + if (gate1Code[i] != -1 || ppqnCount == 0) + gate1Code[i] = calcGate1Code(attributes[newSeq][(i * 16) + stepIndexRun], ppqnCount, pulsesPerStep, params[GATE1_KNOB_PARAM].value); + gate2Code[i] = calcGate2Code(attributes[newSeq][(i * 16) + stepIndexRun], ppqnCount, pulsesPerStep); + } } - else - ppqnCount++; - } + clockPeriod = 0ul; + } clockPeriod++; // Reset @@ -1124,8 +1077,6 @@ struct PhraseSeq32 : Module { displayState = DISP_NORMAL; clockTrigger.reset(); } - else - resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); //********** Outputs and lights ********** @@ -1137,24 +1088,15 @@ struct PhraseSeq32 : Module { float slideOffset[2]; for (int i = 0; i < 2; i += stepConfig) slideOffset[i] = (slideStepsRemain[i] > 0ul ? (slideCVdelta[i] * (float)slideStepsRemain[i]) : 0.0f); + outputs[CVA_OUTPUT].value = cv[seq][step] - slideOffset[0]; + outputs[GATE1A_OUTPUT].value = calcGate(gate1Code[0], clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f; + outputs[GATE2A_OUTPUT].value = calcGate(gate2Code[0], clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f; if (stepConfig == 1) { - outputs[CVA_OUTPUT].value = cv[seq][step] - slideOffset[0]; - outputs[GATE1A_OUTPUT].value = (clockTrigger.isHigh() && gate1RandomEnable[0] && - ((attributes[seq][step] & ATT_MSK_GATE1) != 0)) ? 10.0f : 0.0f; - outputs[GATE2A_OUTPUT].value = (clockTrigger.isHigh() && - ((attributes[seq][step] & ATT_MSK_GATE2) != 0)) ? 10.0f : 0.0f; outputs[CVB_OUTPUT].value = cv[seq][16 + step] - slideOffset[1]; - outputs[GATE1B_OUTPUT].value = (clockTrigger.isHigh() && gate1RandomEnable[1] && - ((attributes[seq][16 + step] & ATT_MSK_GATE1) != 0)) ? 10.0f : 0.0f; - outputs[GATE2B_OUTPUT].value = (clockTrigger.isHigh() && - ((attributes[seq][16 + step] & ATT_MSK_GATE2) != 0)) ? 10.0f : 0.0f; + outputs[GATE1B_OUTPUT].value = calcGate(gate1Code[1], clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f; + outputs[GATE2B_OUTPUT].value = calcGate(gate2Code[1], clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f; } else { - outputs[CVA_OUTPUT].value = cv[seq][step] - slideOffset[0]; - outputs[GATE1A_OUTPUT].value = (clockTrigger.isHigh() && gate1RandomEnable[0] && - ((attributes[seq][step] & ATT_MSK_GATE1) != 0)) ? 10.0f : 0.0f; - outputs[GATE2A_OUTPUT].value = (clockTrigger.isHigh() && - ((attributes[seq][step] & ATT_MSK_GATE2) != 0)) ? 10.0f : 0.0f; outputs[CVB_OUTPUT].value = 0.0f; outputs[GATE1B_OUTPUT].value = 0.0f; outputs[GATE2B_OUTPUT].value = 0.0f; @@ -1178,179 +1120,201 @@ struct PhraseSeq32 : Module { outputs[GATE2B_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; } } + for (int i = 0; i < 2; i++) + if (slideStepsRemain[i] > 0ul) + slideStepsRemain[i]--; + - // Step/phrase lights - if (infoCopyPaste != 0l) { - for (int i = 0; i < 32; i++) { - if ( (i >= stepIndexEdit && i < (stepIndexEdit + countCP)) || (countCP == 32) ) - 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) + lightRefreshCounter++; + if (lightRefreshCounter > displayRefreshStepSkips) { + lightRefreshCounter = 0; + + // Step/phrase lights + if (infoCopyPaste != 0l) { + for (int i = 0; i < 32; i++) { + if ( (i >= stepIndexEdit && i < (stepIndexEdit + countCP)) || (countCP == 32) ) + 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 < 32; i++) { - if (displayState == DISP_LENGTH) { - if (editingSequence) { - int col = i % (16 * stepConfig); - if (col < (lengths[sequence] - 1)) - setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.1f, 0.0f); - else if (col == (lengths[sequence] - 1)) - setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 1.0f, 0.0f); - else - setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.0f, 0.0f); + else { + for (int i = 0; i < 32; i++) { + int col = (stepConfig == 1 ? (i & 0xF) : i);//i % (16 * stepConfig);// optimized + if (displayState == DISP_LENGTH) { + if (editingSequence) { + if (col < (lengths[sequence] - 1)) + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.1f, 0.0f); + else if (col == (lengths[sequence] - 1)) + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 1.0f, 0.0f); + else + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.0f, 0.0f); + } + else { + if (i < phrases - 1) + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.1f, 0.0f); + else + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, (i == phrases - 1) ? 1.0f : 0.0f, 0.0f); + } } - else { - if (i < phrases - 1) - setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.1f, 0.0f); + else {// normal led display (i.e. not length) + float red = 0.0f; + float green = 0.0f; + + // Run cursor (green) + if (editingSequence) + green = ((running && (col == stepIndexRun)) ? 1.0f : 0.0f); + else { + green = ((running && (i == phraseIndexRun)) ? 1.0f : 0.0f); + green += ((running && (col == stepIndexRun) && i != phraseIndexEdit) ? 0.1f : 0.0f); + green = clamp(green, 0.0f, 1.0f); + } + // Edit cursor (red) + if (editingSequence) + red = (i == stepIndexEdit ? 1.0f : 0.0f); else - setGreenRed(STEP_PHRASE_LIGHTS + i * 2, (i == phrases - 1) ? 1.0f : 0.0f, 0.0f); + red = (i == phraseIndexEdit ? 1.0f : 0.0f); + + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, green, red); } } - else {// normal led display (i.e. not length) - float red = 0.0f; - float green = 0.0f; - - // Run cursor (green) - if (editingSequence) - green = ((running && (i % (16 * stepConfig)) == stepIndexRun)) ? 1.0f : 0.0f; - else { - green = ((running && (i == phraseIndexRun)) ? 1.0f : 0.0f); - green += ((running && ((i % (16 * stepConfig)) == stepIndexRun) && i != phraseIndexEdit) ? 0.1f : 0.0f); - green = clamp(green, 0.0f, 1.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 || (stepConfig == 1)))// no oct lights when song mode and either (detached [1] or stopped [2] or 2x16config [3]) + // [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 + // [3] makes no sense, which sequence would be displayed, top or bottom row! + 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; } - // Edit cursor (red) - if (editingSequence) - red = (i == stepIndexEdit ? 1.0f : 0.0f); - else - red = (i == phraseIndexEdit ? 1.0f : 0.0f); - - setGreenRed(STEP_PHRASE_LIGHTS + i * 2, green, red); + else + lights[OCTAVE_LIGHTS + i].value = (i == (6 - octLightIndex) ? 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 || (stepConfig == 1)))// no oct lights when song mode and either (detached [1] or stopped [2] or 2x16config [3]) - // [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 - // [3] makes no sense, which sequence would be displayed, top or bottom row! - 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; + + // Keyboard lights (can only show channel A when running attached in 1x16 mode, does not pose problem for all other situations) + 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); + if (editingGateLength != 0 && editingSequence) { + int modeLightIndex = gateModeToKeyLightIndex(attributes[sequence][stepIndexEdit], editingGateLength > 0l); + for (int i = 0; i < 12; i++) { + if (i == modeLightIndex) { + lights[KEY_LIGHTS + i * 2 + 0].value = editingGateLength > 0l ? 1.0f : 0.2f; + lights[KEY_LIGHTS + i * 2 + 1].value = editingGateLength > 0l ? 0.2f : 1.0f; + } + else { + lights[KEY_LIGHTS + i * 2 + 0].value = 0.0f; + if (i == keyLightIndex) + lights[KEY_LIGHTS + i * 2 + 1].value = 0.1f; + else + lights[KEY_LIGHTS + i * 2 + 1].value = 0.0f; + } } - else - lights[OCTAVE_LIGHTS + i].value = (i == (6 - octLightIndex) ? 1.0f : 0.0f); } - } - - // Keyboard lights (can only show channel A when running attached in 1x16 mode, does not pose problem for all other situations) - 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 || (stepConfig == 1)))// no oct lights when song mode and either (detached [1] or stopped [2] or 2x16config [3]) - // [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 - // [3] makes no sense, which sequence would be displayed, top or bottom row! - 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; - } - 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); + for (int i = 0; i < 12; i++) { + lights[KEY_LIGHTS + i * 2 + 0].value = 0.0f; + if (!editingSequence && (!attached || !running || (stepConfig == 1)))// no oct lights when song mode and either (detached [1] or stopped [2] or 2x16config [3]) + // [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 + // [3] makes no sense, which sequence would be displayed, top or bottom row! + lights[KEY_LIGHTS + i * 2 + 1].value = 0.0f; + else { + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips)); + lights[KEY_LIGHTS + i * 2 + 1].value = (warningFlashState && i == keyLightIndex) ? 1.0f : 0.0f; + } + else { + if (editingGate > 0ul && editingGateKeyLight != -1) + lights[KEY_LIGHTS + i * 2 + 1].value = (i == editingGateKeyLight ? ((float) editingGate / (float)(gateTime * sampleRate / displayRefreshStepSkips)) : 0.0f); + else + lights[KEY_LIGHTS + i * 2 + 1].value = (i == keyLightIndex ? 1.0f : 0.0f); + } + } } + } + + // Gate1, Gate1Prob, Gate2, Slide and Tied lights (can only show channel A when running attached in 1x16 mode, does not pose problem for all other situations) + int attributesVal = attributes[sequence][stepIndexEdit]; + if (!editingSequence) + attributesVal = attributes[phrase[phraseIndexEdit]][stepIndexRun]; + // + setGateLight(getGate1a(attributesVal), GATE1_LIGHT); + setGateLight(getGate2a(attributesVal), GATE2_LIGHT); + lights[GATE1_PROB_LIGHT].value = getGate1Pa(attributesVal) ? 1.0f : 0.0f; + lights[SLIDE_LIGHT].value = getSlideA(attributesVal) ? 1.0f : 0.0f; + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips)); + lights[TIE_LIGHT].value = (warningFlashState) ? 1.0f : 0.0f; } - } - - // Gate1, Gate1Prob, Gate2, Slide and Tied lights (can only show channel A when running attached in 1x16 mode, does not pose problem for all other situations) - int attributesVal = attributes[sequence][stepIndexEdit]; - if (!editingSequence) - attributesVal = attributes[phrase[phraseIndexEdit]][stepIndexRun]; - // - lights[GATE1_LIGHT + 1].value = ((attributesVal & ATT_MSK_GATE1) != 0) ? 1.0f : 0.0f; - lights[GATE1_LIGHT + 0].value = ((pulsesPerStep != 1) ? lights[GATE1_LIGHT + 1].value : 0.0f); - lights[GATE1_PROB_LIGHT].value = ((attributesVal & ATT_MSK_GATE1P) != 0) ? 1.0f : 0.0f; - lights[GATE2_LIGHT + 1].value = ((attributesVal & ATT_MSK_GATE2) != 0) ? 1.0f : 0.0f; - lights[GATE2_LIGHT + 0].value = ((pulsesPerStep != 1) ? lights[GATE2_LIGHT + 1].value : 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; + else + lights[TIE_LIGHT].value = getTiedA(attributesVal) ? 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 = running ? 1.0f : 0.0f; - - if (editingGate > 0ul) - editingGate--; - if (infoCopyPaste != 0l) { - if (infoCopyPaste > 0l) - infoCopyPaste --; - if (infoCopyPaste < 0l) - infoCopyPaste ++; - } - for (int i = 0; i < 2; i++) - if (slideStepsRemain[i] > 0ul) - slideStepsRemain[i]--; - if (clockIgnoreOnReset > 0l) - clockIgnoreOnReset--; - if (tiedWarning > 0l) - tiedWarning--; - if (gate1HoldDetect > 0l) { - if (params[GATE1_PARAM].value < 0.5f) - gate1HoldDetect = 0l; - else { - if (gate1HoldDetect == 1l) { - attributes[sequence][stepIndexEdit] |= ATT_MSK_GATE1; - if (pulsesPerStep == 1) { - pulsesPerStep = 4;// default - } - editingPpqn = (long) (editGateLengthTime * engineGetSampleRate()); - } - gate1HoldDetect--; + // 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 = running ? 1.0f : 0.0f; + + if (editingGate > 0ul) + editingGate--; + if (infoCopyPaste != 0l) { + if (infoCopyPaste > 0l) + infoCopyPaste --; + if (infoCopyPaste < 0l) + infoCopyPaste ++; } - } - if (editingGateLength != 0l) { - if (editingGateLength > 0l) - editingGateLength --; - if (editingGateLength < 0l) - editingGateLength ++; - } - if (editingPpqn > 0l) - editingPpqn--; - if (revertDisplay > 0l) { - if (revertDisplay == 1) + if (editingGateLength != 0l) { + if (editingGateLength > 0l) + editingGateLength --; + if (editingGateLength < 0l) + editingGateLength ++; + } + if (editingPpqn > 0l) + editingPpqn--; + if (tiedWarning > 0l) + tiedWarning--; + if (modeHoldDetect.process(params[RUNMODE_PARAM].value)) { displayState = DISP_NORMAL; - revertDisplay--; - } - + editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips); + } + if (gate1HoldDetect.process(params[GATE1_PARAM].value)) { + toggleGate1a(&attributes[sequence][stepIndexEdit]); + editGateLengthTimeInitMult = 1; + } + if (gate2HoldDetect.process(params[GATE2_PARAM].value)) { + toggleGate2a(&attributes[sequence][stepIndexEdit]); + editGateLengthTimeInitMult = 100; + } + if (revertDisplay > 0l) { + if (revertDisplay == 1) + displayState = DISP_NORMAL; + revertDisplay--; + } + }// lightRefreshCounter + + if (clockIgnoreOnReset > 0l) + clockIgnoreOnReset--; }// step() void setGreenRed(int id, float green, float red) { @@ -1377,6 +1341,22 @@ struct PhraseSeq32 : Module { int calcNewGateMode(int currentGateMode, int deltaKnob) { return clamp(currentGateMode + deltaKnob, 0, NUM_GATES - 1); } + + inline void setGateLight(bool gateOn, int lightIndex) { + if (!gateOn) { + lights[lightIndex + 0].value = 0.0f; + lights[lightIndex + 1].value = 0.0f; + } + else if (pulsesPerStep == 1) { + lights[lightIndex + 0].value = 0.0f; + lights[lightIndex + 1].value = 1.0f; + } + else { + lights[lightIndex + 0].value = lightIndex == GATE1_LIGHT ? 1.0f : 0.2f; + lights[lightIndex + 1].value = lightIndex == GATE1_LIGHT ? 0.2f : 1.0f; + } + } + }; @@ -1401,10 +1381,6 @@ struct PhraseSeq32Widget : ModuleWidget { if (num >= 0 && num < NUM_MODES) snprintf(displayStr, 4, "%s", modeLabels[num].c_str()); } - void gateModeToStr(int num) { - if (num >= 0 && num < NUM_GATES) - snprintf(displayStr, 4, "%s", gateLabels[num].c_str()); - } void draw(NVGcontext *vg) override { NVGcolor textColor = prepareDisplay(vg, &box); @@ -1416,51 +1392,40 @@ struct PhraseSeq32Widget : 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 == PhraseSeq32::DISP_MODE) { - if (module->editingSequence) - runModeToStr(module->runModeSeq[module->sequence]); - else - runModeToStr(module->runModeSong); - } - else if (module->displayState == PhraseSeq32::DISP_LENGTH) { - 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->editingPpqn != 0ul) { - snprintf(displayStr, 4, "x%2u", (unsigned) module->pulsesPerStep); - } - else if (module->editingGateLength != 0l) { - if (module->editingGateLength > 0l) - gateModeToStr(module->getGate1Mode(module->sequence, module->stepIndexEdit)); - else - gateModeToStr(module->getGate2Mode(module->sequence, module->stepIndexEdit)); - } - else if (module->displayState == PhraseSeq32::DISP_TRANSPOSE) { - snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset)); - if (module->transposeOffset < 0) - displayStr[0] = '-'; - } - else if (module->displayState == PhraseSeq32::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->editingPpqn != 0ul) { + snprintf(displayStr, 4, "x%2u", (unsigned) module->pulsesPerStep); + } + else if (module->displayState == PhraseSeq32::DISP_MODE) { + if (module->editingSequence) + runModeToStr(module->runModeSeq[module->sequence]); + else + runModeToStr(module->runModeSong); + } + else if (module->displayState == PhraseSeq32::DISP_LENGTH) { + 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 == PhraseSeq32::DISP_TRANSPOSE) { + snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset)); + if (module->transposeOffset < 0) + displayStr[0] = '-'; + } + else if (module->displayState == PhraseSeq32::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); } }; @@ -1612,35 +1577,36 @@ struct PhraseSeq32Widget : ModuleWidget { } // Keys and Key lights static const int keyNudgeX = 7; - static const int keyNudgeY = 2; + static const int KeyBlackY = 103; + static const int KeyWhiteY = 141; static const int offsetKeyLEDx = 6; - static const int offsetKeyLEDy = 28; + static const int offsetKeyLEDy = 16; // Black keys and lights - addParam(ParamWidget::create( Vec(65+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 1, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(65+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 1)); - addParam(ParamWidget::create( Vec(93+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 3, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(93+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 3)); - addParam(ParamWidget::create( Vec(150+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 6, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(150+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 6)); - addParam(ParamWidget::create( Vec(178+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 8, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(178+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 8)); - addParam(ParamWidget::create( Vec(206+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 10, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(206+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 10)); + addParam(ParamWidget::create( Vec(65+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 1, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(65+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 1 * 2)); + addParam(ParamWidget::create( Vec(93+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 3, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(93+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 3 * 2)); + addParam(ParamWidget::create( Vec(150+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 6, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(150+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 6 * 2)); + addParam(ParamWidget::create( Vec(178+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 8, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(178+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 8 * 2)); + addParam(ParamWidget::create( Vec(206+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 10, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(206+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 10 * 2)); // White keys and lights - addParam(ParamWidget::create( Vec(51+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 0, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(51+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 0)); - addParam(ParamWidget::create( Vec(79+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 2, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(79+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 2)); - addParam(ParamWidget::create( Vec(107+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 4, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(107+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 4)); - addParam(ParamWidget::create( Vec(136+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 5, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(136+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 5)); - addParam(ParamWidget::create( Vec(164+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 7, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(164+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 7)); - addParam(ParamWidget::create( Vec(192+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 9, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(192+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 9)); - addParam(ParamWidget::create( Vec(220+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 11, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(220+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 11)); + addParam(ParamWidget::create( Vec(51+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 0, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(51+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 0 * 2)); + addParam(ParamWidget::create( Vec(79+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 2, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(79+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 2 * 2)); + addParam(ParamWidget::create( Vec(107+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 4, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(107+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 4 * 2)); + addParam(ParamWidget::create( Vec(136+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 5, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(136+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 5 * 2)); + addParam(ParamWidget::create( Vec(164+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 7, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(164+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 7 * 2)); + addParam(ParamWidget::create( Vec(192+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 9, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(192+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 9 * 2)); + addParam(ParamWidget::create( Vec(220+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 11, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(220+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 11 * 2)); @@ -1654,7 +1620,7 @@ struct PhraseSeq32Widget : ModuleWidget { static const int columnRulerMK1 = 353;// Display column // Edit mode switch - addParam(ParamWidget::create(Vec(columnRulerMK0 + hOffsetCKSS, rowRulerMK0 + vOffsetCKSS), module, PhraseSeq32::EDIT_PARAM, 0.0f, 1.0f, PhraseSeq32::EDIT_PARAM_INIT_VALUE)); + addParam(ParamWidget::create(Vec(columnRulerMK0 + 2 + hOffsetCKSS, rowRulerMK0 + vOffsetCKSS), module, PhraseSeq32::EDIT_PARAM, 0.0f, 1.0f, PhraseSeq32::EDIT_PARAM_INIT_VALUE)); // Sequence display SequenceDisplayWidget *displaySequence = new SequenceDisplayWidget(); displaySequence->box.pos = Vec(columnRulerMK1-15, rowRulerMK0 + 3 + vOffsetDisplay); @@ -1665,9 +1631,9 @@ struct PhraseSeq32Widget : ModuleWidget { addParam(createDynamicParam(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK0 + 0 + offsetCKD6b), module, PhraseSeq32::RUNMODE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); // Autostep - addParam(ParamWidget::create(Vec(columnRulerMK0 + hOffsetCKSS, rowRulerMK1 + 7 + vOffsetCKSS), module, PhraseSeq32::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f)); + addParam(ParamWidget::create(Vec(columnRulerMK0 + 2 + hOffsetCKSS, rowRulerMK1 + 7 + vOffsetCKSS), module, PhraseSeq32::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f)); // Sequence knob - addParam(createDynamicParam(Vec(columnRulerMK1 + 1 + offsetIMBigKnob, rowRulerMK0 + 55 + offsetIMBigKnob), module, PhraseSeq32::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme)); + addParam(createDynamicParam(Vec(columnRulerMK1 + 1 + offsetIMBigKnob, rowRulerMK0 + 55 + offsetIMBigKnob), module, PhraseSeq32::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme)); // Transpose/rotate button addParam(createDynamicParam(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK1 + 4 + offsetCKD6b), module, PhraseSeq32::TRAN_ROT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); @@ -1780,6 +1746,13 @@ RACK_PLUGIN_MODEL_INIT(ImpromptuModular, PhraseSeq32) { /*CHANGE LOG +0.6.11: +step optimization of lights refresh + +0.6.10: +add advanced gate mode +unlock gates when tied (turn off when press tied, but allow to be turned back on) + 0.6.9: add FW2, FW3 and FW4 run modes for sequences (but not for song) right click on notes now does same as left click but with autostep diff --git a/plugins/community/repos/ImpromptuModular/src/PhraseSeqUtil.cpp b/plugins/community/repos/ImpromptuModular/src/PhraseSeqUtil.cpp new file mode 100644 index 00000000..259e2bc1 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/PhraseSeqUtil.cpp @@ -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 diff --git a/plugins/community/repos/ImpromptuModular/src/PhraseSeqUtil.hpp b/plugins/community/repos/ImpromptuModular/src/PhraseSeqUtil.hpp new file mode 100644 index 00000000..b20a9269 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/PhraseSeqUtil.hpp @@ -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 diff --git a/plugins/community/repos/ImpromptuModular/src/SemiModularSynth.cpp b/plugins/community/repos/ImpromptuModular/src/SemiModularSynth.cpp index 95f62a4d..8253d7a0 100644 --- a/plugins/community/repos/ImpromptuModular/src/SemiModularSynth.cpp +++ b/plugins/community/repos/ImpromptuModular/src/SemiModularSynth.cpp @@ -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: diff --git a/plugins/community/repos/ImpromptuModular/src/Tact.cpp b/plugins/community/repos/ImpromptuModular/src/Tact.cpp index 155ba18f..fc8fa1f9 100644 --- a/plugins/community/repos/ImpromptuModular/src/Tact.cpp +++ b/plugins/community/repos/ImpromptuModular/src/Tact.cpp @@ -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; } diff --git a/plugins/community/repos/ImpromptuModular/src/TwelveKey.cpp b/plugins/community/repos/ImpromptuModular/src/TwelveKey.cpp index 72be17bb..c88f94ca 100644 --- a/plugins/community/repos/ImpromptuModular/src/TwelveKey.cpp +++ b/plugins/community/repos/ImpromptuModular/src/TwelveKey.cpp @@ -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--; + } } }; diff --git a/plugins/community/repos/ImpromptuModular/src/WriteSeq32.cpp b/plugins/community/repos/ImpromptuModular/src/WriteSeq32.cpp index 2e173858..6fe16bbd 100644 --- a/plugins/community/repos/ImpromptuModular/src/WriteSeq32.cpp +++ b/plugins/community/repos/ImpromptuModular/src/WriteSeq32.cpp @@ -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--; } diff --git a/plugins/community/repos/ImpromptuModular/src/WriteSeq64.cpp b/plugins/community/repos/ImpromptuModular/src/WriteSeq64.cpp index 78d5759e..dc6973a3 100644 --- a/plugins/community/repos/ImpromptuModular/src/WriteSeq64.cpp +++ b/plugins/community/repos/ImpromptuModular/src/WriteSeq64.cpp @@ -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--; } diff --git a/vst2_bin/plugins/ImpromptuModular/README.md b/vst2_bin/plugins/ImpromptuModular/README.md index 11f210cc..ecab9047 100644 --- a/vst2_bin/plugins/ImpromptuModular/README.md +++ b/vst2_bin/plugins/ImpromptuModular/README.md @@ -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 -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 ![IM](res/img/PhraseSeq16.jpg) -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 + +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. + +![IM](res/img/AdvancedGateDetails.jpg) + +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 + +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 + +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) + diff --git a/vst2_bin/plugins/ImpromptuModular/res/AdvancedGateDetails.svg b/vst2_bin/plugins/ImpromptuModular/res/AdvancedGateDetails.svg new file mode 100644 index 00000000..06acaeff --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/AdvancedGateDetails.svg @@ -0,0 +1,813 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PULSES PER STEPS (PPS) - REQUIREMENTS + ALL + MULTIPLES OF 6 + ALL + ALL + + + + + + + + + + + M4 + M4 + M4 + M4: MULTIPLES OF 4 + diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/Clocked_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/Clocked_dark.svg index 53c10ee3..7f912975 100644 --- a/vst2_bin/plugins/ImpromptuModular/res/dark/Clocked_dark.svg +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/Clocked_dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/GateSeq64_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/GateSeq64_dark.svg index a80aca89..e7568ad2 100644 --- a/vst2_bin/plugins/ImpromptuModular/res/dark/GateSeq64_dark.svg +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/GateSeq64_dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq16_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq16_dark.svg index 85477ddf..b9a5bad4 100644 --- a/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq16_dark.svg +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq16_dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq32_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq32_dark.svg index 3c342d3c..8cbca21b 100644 --- a/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq32_dark.svg +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq32_dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/AdvancedGateDetails.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/AdvancedGateDetails.jpg new file mode 100644 index 00000000..3fb00d21 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/AdvancedGateDetails.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/Clocked.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/Clocked.jpg index 4f591160..d621df8c 100644 Binary files a/vst2_bin/plugins/ImpromptuModular/res/img/Clocked.jpg and b/vst2_bin/plugins/ImpromptuModular/res/img/Clocked.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/GateSeq64.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/GateSeq64.jpg index 6201b7be..8d57d510 100644 Binary files a/vst2_bin/plugins/ImpromptuModular/res/img/GateSeq64.jpg and b/vst2_bin/plugins/ImpromptuModular/res/img/GateSeq64.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16.jpg index b74cee6f..dd783c29 100644 Binary files a/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16.jpg and b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq32.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq32.jpg index 23f56fb0..6ee1504d 100644 Binary files a/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq32.jpg and b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq32.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/Clocked.svg b/vst2_bin/plugins/ImpromptuModular/res/light/Clocked.svg index a0979cff..028b3f50 100644 --- a/vst2_bin/plugins/ImpromptuModular/res/light/Clocked.svg +++ b/vst2_bin/plugins/ImpromptuModular/res/light/Clocked.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/EngTest1.svg b/vst2_bin/plugins/ImpromptuModular/res/light/EngTest1.svg deleted file mode 100644 index 51528c47..00000000 --- a/vst2_bin/plugins/ImpromptuModular/res/light/EngTest1.svg +++ /dev/null @@ -1,931 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/GateSeq64.svg b/vst2_bin/plugins/ImpromptuModular/res/light/GateSeq64.svg index 7f233fc4..bc0cbedb 100644 --- a/vst2_bin/plugins/ImpromptuModular/res/light/GateSeq64.svg +++ b/vst2_bin/plugins/ImpromptuModular/res/light/GateSeq64.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq16.svg b/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq16.svg index ed0f38d4..8b87964d 100644 --- a/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq16.svg +++ b/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq16.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq32.svg b/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq32.svg index 3bd8d3d8..ced5230f 100644 --- a/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq32.svg +++ b/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq32.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file