Skip to content

Commit 925e9f1

Browse files
add module
1 parent f79c919 commit 925e9f1

9 files changed

Lines changed: 1112 additions & 2 deletions

File tree

claude.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Software Eurorack modular synthesizer. Modules pass voltages; sound only at outp
5050

5151
**Self-contained modules**: Each module folder contains DSP + UI definition in one file. Modules export metadata, `createDSP()` factory, and declarative `ui` config.
5252

53-
**Available modules**: `clk` (clock) · `div` (divider) · `lfo` · `nse` (noise) · `sh` (sample&hold) · `quant` (quantizer) · `arp` (arpeggiator) · `seq` (sequencer) · `euclid` (euclidean rhythm) · `logic` (AND/OR gates) · `mult` (signal splitter) · `vco` · `vcf` · `fold` (wavefolder) · `ring` (ring mod) · `rnd` (random) · `envf` (envelope follower) · `func` (function generator) · `adsr` · `vca` · `atten` (attenuverter) · `slew` · `mix` · `dly` (delay) · `verb` (reverb) · `chorus` · `phaser` · `flanger` · `crush` (bit crusher) · `db` (VU meter) · `pwm` (pulse width mod) · `turing` (random looping seq) · `ochd` (8x LFO) · `kick` · `snare` · `hat` · `scope` · `out`
53+
**Available modules**: `clk` (clock) · `div` (divider) · `lfo` · `nse` (noise) · `sh` (sample&hold) · `quant` (quantizer) · `arp` (arpeggiator) · `seq` (sequencer) · `euclid` (euclidean rhythm) · `logic` (AND/OR gates) · `mult` (signal splitter) · `vco` · `vcf` · `fold` (wavefolder) · `ring` (ring mod) · `rnd` (random) · `envf` (envelope follower) · `func` (function generator) · `adsr` · `vca` · `atten` (attenuverter) · `slew` · `mix` · `dly` (delay) · `verb` (reverb) · `chorus` · `phaser` · `flanger` · `crush` (bit crusher) · `db` (VU meter) · `pwm` (pulse width mod) · `turing` (random looping seq) · `ochd` (8x LFO) · `cmp2` (window comparator) · `kick` · `snare` · `hat` · `scope` · `out`
5454

5555
## Project Structure
5656

@@ -262,6 +262,7 @@ export default {
262262
| pwm | in, pwmCV | out, inv |
263263
| turing | clock, lockCV | cv, pulse |
264264
| ochd | rateCV | out1, out2, out3, out4, out5, out6, out7, out8 |
265+
| cmp2 | in1, in2, shiftCV1, sizeCV1, shiftCV2, sizeCV2 | out1, not1, out2, not2, and, or, xor, ff |
265266
| kick | trigger, pitchCV, decayCV, toneCV | out |
266267
| snare | trigger, toneCV, decayCV | out |
267268
| hat | trigger, decayCV | out |

node_modules/.vite/vitest/results.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

research/modules/compare2.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Compare 2 - Dual Window Comparator
2+
3+
Based on Joranalogue Audio Design Compare 2.
4+
5+
## Overview
6+
7+
A dual window comparator that checks if an input voltage falls between two threshold levels (the "window"). Unlike a simple comparator that triggers above a single threshold, a window comparator activates when the input is within a defined range. Includes a logic section combining both comparators' outputs.
8+
9+
## Sources
10+
11+
- [Joranalogue Official Product Page](https://joranalogue.com/products/compare-2)
12+
- [ModularGrid](https://modulargrid.net/e/joranalogue-audio-design-compare-2)
13+
- [Waveform Magazine Review](https://waveformmagazine.com/waveform-reviews/joranalogue-compare-2/)
14+
- [Elevator Sound](https://www.elevatorsound.com/product/joranalogue-compare-2-eurorack-dual-comparator-module/)
15+
- [Manua.ls Manual](https://www.manua.ls/joranalogue/compare-2/manual)
16+
17+
## Specifications
18+
19+
- **Width**: 8HP
20+
- **Depth**: 30mm
21+
- **Power**: +12V: 20mA, -12V: 15mA
22+
23+
## How Window Comparator Works
24+
25+
A window comparator has two thresholds: a lower and an upper. The output activates when the input voltage is between these thresholds (inside the "window").
26+
27+
```
28+
Upper threshold ----+----
29+
| | Window (output HIGH when input is here)
30+
Lower threshold ----+----
31+
```
32+
33+
### Shift and Size Parameters
34+
35+
The window is defined by two parameters:
36+
- **Shift**: Moves the entire window up or down (sets the center point)
37+
- **Size**: Sets the distance between upper and lower thresholds (window width)
38+
39+
```
40+
Upper threshold = Shift + (Size / 2)
41+
Lower threshold = Shift - (Size / 2)
42+
```
43+
44+
Example with Shift=0V, Size=2V:
45+
- Upper threshold = +1V
46+
- Lower threshold = -1V
47+
- Output HIGH when input is between -1V and +1V
48+
49+
## Controls
50+
51+
### Per Comparator (x2)
52+
53+
| Control | Function | Range |
54+
|---------|----------|-------|
55+
| Shift knob | Center of detection window | -5V to +5V (0V at center) |
56+
| Size knob | Width of detection window | 0V to 10V |
57+
| Shift CV | Modulates shift | Added to knob |
58+
| Size CV | Modulates size | Added to knob |
59+
60+
### Inputs
61+
62+
| Input | Function |
63+
|-------|----------|
64+
| In (left) | Signal input for comparator 1 |
65+
| In (right) | Signal input for comparator 2 (normalled from left) |
66+
| Shift CV (left) | CV for shift 1 (normalled to right) |
67+
| Size CV (left) | CV for size 1 (normalled to right) |
68+
| Shift CV (right) | CV for shift 2 |
69+
| Size CV (right) | CV for size 2 |
70+
71+
### Outputs
72+
73+
| Output | Function |
74+
|--------|----------|
75+
| Out (per comparator) | Gate when input is INSIDE window (10V in our implementation) |
76+
| Not (per comparator) | Gate when input is OUTSIDE window (inverted) |
77+
| AND | Gate when BOTH comparators' Out are HIGH |
78+
| OR | Gate when EITHER comparator's Out is HIGH |
79+
| XOR | Gate when exactly ONE comparator's Out is HIGH |
80+
| FF (Flip-Flop) | Toggles on rising edge of XOR output |
81+
82+
**Note**: Real Joranalogue outputs +5V gates; our implementation uses 10V to match system standard (0/10V gates).
83+
84+
## LED Behavior
85+
86+
Three-color LEDs per comparator:
87+
- **Blue** (state=0): Input voltage is BELOW the window
88+
- **Red** (state=1): Input voltage is ABOVE the window
89+
- **White** (state=0.5): Input voltage is INSIDE the window
90+
- **Off** (state=-1): Window size is negative AND signal is within this "negative window"
91+
92+
Logic section has LEDs for AND, OR, XOR, FF outputs (on/off only).
93+
94+
## Algorithm
95+
96+
### Window Comparator Logic
97+
98+
```javascript
99+
function windowCompare(input, shift, size) {
100+
const halfSize = size / 2;
101+
const lower = shift - halfSize;
102+
const upper = shift + halfSize;
103+
104+
// Inside window = output HIGH
105+
// Zero size means window is closed - never triggers
106+
const inside = size > 0 && input >= lower && input <= upper;
107+
108+
return {
109+
out: inside ? 10 : 0, // 10V gate when inside (system standard)
110+
not: inside ? 0 : 10, // 10V gate when outside
111+
state: input < lower ? 'below' : (input > upper ? 'above' : 'inside')
112+
};
113+
}
114+
```
115+
116+
### Logic Section
117+
118+
```javascript
119+
function logic(out1, out2, lastFF) {
120+
const a = out1 > 0;
121+
const b = out2 > 0;
122+
123+
return {
124+
and: (a && b) ? 10 : 0,
125+
or: (a || b) ? 10 : 0,
126+
xor: (a !== b) ? 10 : 0,
127+
ff: lastFF // Toggle on rising edge of out1
128+
};
129+
}
130+
```
131+
132+
### Flip-Flop Behavior
133+
134+
The FF output toggles state on each rising edge of the **XOR** signal (per manual):
135+
- When XOR goes from LOW to HIGH, FF toggles
136+
- FF stays at its current state otherwise
137+
- This means FF toggles when one comparator changes state while the other stays the same
138+
139+
## Implementation Notes
140+
141+
### Normalization
142+
143+
- Left input is normalled to right input (same signal to both comparators if right not patched)
144+
- Left Shift CV is normalled to right Shift CV
145+
- Left Size CV is normalled to right Size CV
146+
147+
This allows modulating both comparators with single CV sources.
148+
149+
### Negative Size
150+
151+
When Size is negative (or CV pushes it negative), the window is "inverted" - the upper threshold is below the lower threshold. In this case, no input can be "inside" the window, so Out stays LOW and Not stays HIGH.
152+
153+
### Edge Cases
154+
155+
- Size = 0: Window is infinitely thin, practically never triggers
156+
- Very large Size: Window encompasses full input range, always triggers
157+
- Shift at extremes with large Size: Window clips at voltage rails
158+
159+
## Suggested Module Spec
160+
161+
```javascript
162+
{
163+
id: 'cmp2',
164+
name: 'CMP2',
165+
hp: 8,
166+
category: 'utility',
167+
168+
params: {
169+
shift1: 0, // -5 to +5
170+
size1: 5, // 0 to 10
171+
shift2: 0,
172+
size2: 5
173+
},
174+
175+
inputs: {
176+
in1: 'cv',
177+
in2: 'cv', // normalled from in1
178+
shiftCV1: 'cv',
179+
sizeCV1: 'cv',
180+
shiftCV2: 'cv', // normalled from shiftCV1
181+
sizeCV2: 'cv' // normalled from sizeCV1
182+
},
183+
184+
outputs: {
185+
out1: 'gate',
186+
not1: 'gate',
187+
out2: 'gate',
188+
not2: 'gate',
189+
and: 'gate',
190+
or: 'gate',
191+
xor: 'gate',
192+
ff: 'gate'
193+
},
194+
195+
leds: ['state1', 'state2', 'and', 'or', 'xor', 'ff']
196+
}
197+
```
198+
199+
## Patch Ideas
200+
201+
1. **Rhythm from LFO**: Feed øchd into Compare 2, adjust window to extract gates at specific parts of the waveform
202+
2. **Pulse width modulation**: Feed audio into input, adjust Size to change duty cycle of output pulses
203+
3. **Frequency doubler**: Small window at zero crossing doubles trigger rate
204+
4. **Complex gates**: Use logic outputs to combine two rhythm patterns
205+
5. **Voltage-controlled swing**: Modulate Shift with slow LFO to vary when gates fire
206+
6. **Audio-rate digital ring mod**: Feed two audio signals, use XOR output

src/js/config/patches/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import testDb from './test-db.js';
4343
import testPwm from './test-pwm.js';
4444
import testTuring from './test-turing.js';
4545
import testOchd from './test-ochd.js';
46+
import testCmp2 from './test-cmp2.js';
4647
import testAttenuverter from './test-attenuverter.js';
4748
import testSlew from './test-slew.js';
4849

@@ -108,6 +109,7 @@ export const FACTORY_PATCHES = {
108109
[testPwm.name]: testPwm,
109110
[testTuring.name]: testTuring,
110111
[testOchd.name]: testOchd,
112+
[testCmp2.name]: testCmp2,
111113
[testAttenuverter.name]: testAttenuverter,
112114
[testSlew.name]: testSlew,
113115

src/js/config/patches/test-cmp2.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Test Patch: CMP2
3+
*
4+
* Demonstrates the dual window comparator extracting gates from LFOs.
5+
* øchd provides organic modulation, CMP2 converts to rhythmic gates.
6+
* OR output gives longer sustained gates for smoother sound.
7+
* Asymmetric windows (shift1=-1, shift2=+1) create interesting rhythms.
8+
*/
9+
export default {
10+
name: 'Test: CMP2',
11+
factory: true,
12+
state: {
13+
modules: [
14+
{ type: 'ochd', instanceId: 'ochd', row: 1 },
15+
{ type: 'cmp2', instanceId: 'cmp2', row: 1 },
16+
{ type: 'vco', instanceId: 'vco', row: 1 },
17+
{ type: 'vcf', instanceId: 'vcf', row: 1 },
18+
{ type: 'adsr', instanceId: 'adsr', row: 1 },
19+
{ type: 'vca', instanceId: 'vca', row: 1 },
20+
{ type: 'out', instanceId: 'out', row: 1 }
21+
],
22+
knobs: {
23+
ochd: { rate: 0.73 },
24+
cmp2: { shift1: -1, size1: 3.93, shift2: 1, size2: 3.47 },
25+
vco: { coarse: 0.34, fine: -1.04, glide: 0 },
26+
vcf: { cutoff: 0.58, resonance: 0.15 },
27+
adsr: { attack: 0, decay: 0.24, sustain: 0.29, release: 0.49 },
28+
vca: { ch1Gain: 0.8, ch2Gain: 0 },
29+
out: { volume: 0.6 }
30+
},
31+
switches: {},
32+
cables: [
33+
// LFO to comparator inputs - faster LFOs for rhythmic interest
34+
{ fromModule: 'ochd', fromPort: 'out1', toModule: 'cmp2', toPort: 'in1' },
35+
{ fromModule: 'ochd', fromPort: 'out3', toModule: 'cmp2', toPort: 'in2' },
36+
// OR output - HIGH when either comparator is active
37+
{ fromModule: 'cmp2', fromPort: 'or', toModule: 'adsr', toPort: 'gate' },
38+
// Audio path
39+
{ fromModule: 'vco', fromPort: 'triangle', toModule: 'vcf', toPort: 'audio' },
40+
{ fromModule: 'vcf', fromPort: 'lpf', toModule: 'vca', toPort: 'ch1In' },
41+
{ fromModule: 'adsr', fromPort: 'env', toModule: 'vca', toPort: 'ch1CV' },
42+
{ fromModule: 'vca', fromPort: 'ch1Out', toModule: 'out', toPort: 'L' },
43+
{ fromModule: 'vca', fromPort: 'ch1Out', toModule: 'out', toPort: 'R' }
44+
]
45+
}
46+
};

src/js/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export const DEFAULT_MODULE_ORDER = [
104104
'pwm',
105105
'turing',
106106
'ochd',
107+
'cmp2',
107108
'kick',
108109
'snare',
109110
'hat',

0 commit comments

Comments
 (0)