Skip to content

Commit

Permalink
Locks affect randomised pitches, update README
Browse files Browse the repository at this point in the history
  • Loading branch information
jhoar committed Nov 28, 2017
1 parent 6236f32 commit 9eacc6c
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 63 deletions.
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,42 @@ Scared by the anarchy of unconstrained tone? Want to come back to that familiar
* OUT: Quantised note (1V/OCT)
* Top row of outputs: Gates triggered according to the scale degree of the note quantised

This is largely done, will probably do some internal plumbing to use PulseGenerators in the future.
## Arpeggiator

Need some more predictable variety in your patches? Why not try the Arpeggiator from Amalgamated Harmonics? Guaranteed 17.5% more music per hour.

How it is (supposed to) work is the following:

* The Arpeggiator plays a Sequence, composed on 1 or more Cycles, each Cycle consisting of up to 6 notes.
* At the bottom there are 6 inputs which represent up to 6 pitches (1V/OCT) which will played during a Cycle. The DIR switch controls whether the pitch are read left-to-right (up position) or right-to-down (down). Empty inputs are ignored.
* A Cycle will repeated a number of times according to the STEPS input.
* Each subsequent Cycle after the first will be shifted by the number of semi-tones controlled by the DIST input. The shift is either up or down depending on the setting of the Seq DIR switch. This should be replaced with a wider selection of patterns
* A Sequence is triggered from a gate on the TRIG input.
* The Sequence is... err... sequenced from the CLOCK input, so the CLOCK source should be running faster the TRIG source.
* The Arpeggiator plays a sequence, composed on 1 or more arpeggios, each arpeggio consisting of up to 6 notes.
* At the bottom there are 6 inputs which represent up to 6 pitches (1V/OCT) which will played in an arpeggios.
* The ARP switch controls whether the pitch are played left-to-right (L-R), right-to-left (R-L) or a random selection of the pitches and selected (RND). In the RND position pitches can be repeated. Empty inputs are ignored.
* A arpeggio will repeated a number of times according to the STEPS input.
* Each subsequent arpeggio after the first will be shifted by the number of semi-tones controlled by the DIST input. The shift is either up or down depending on the setting of the SEQ switch; ASC shifts the arpeggio up, DSC shifts the arpeggio down and RND shifts the arpeggio up or down 50/50; in this position it is a random walk
* A sequence is triggered from a gate on the TRIG input.
* The sequence is... err... sequenced from the CLOCK input, so the CLOCK source should be running faster the TRIG source.
* The output pitch is on the OUT output (1V/OCT), with a gate fired on the GATE.
* When a cycle completes, a gate if fired on the EOC output, and similarly when the whole sequence completes there is gate on the EOS output
* The LOCK button locks the pitches to the value in the current cycle, both within the running sequence and after, rather like turning the probability knob on the Turing Machine to the far right. When unlocked the pitches will be scanned at the start of every cycle.
* When an arpeggio completes, a gate is fired on the EOC output, and similarly when the whole sequence completes there is gate on the EOS output
* The LOCK button locks the pitches, SEQ and ARP settings to the value in the current arpeggio, both within the running sequence and after. When unlocked the pitches will be scanned at the start of every cycle.

A small display shows the active STEPS and DIST in the currently active sequence, with the values being read from the inputs shown in square brackets. The active values of the SEQ and ARP parameters are alos shown

In summary:

* P1 - P6: Input pitches; these will be scanned left to right and form the notes to be played in each cycle. Empty inputs are ignored
* STEPS: Number of repetitions of a cycle (max 16)
* DIST: Number of semi-tones used for shifting pitches between cycles (max 10)
* DIR: Scan the pitches left-to-right (up) or right-to-left (down)
* CLOCK: Input clock for the sequenced notes
* ARP: Scan the pitches left-to-right (L-R) or right-to-left (R-L), or a random selection of pitches (RND)
* SEQ: Shifts the arpeggio up (ASC), down (DSC) or randonly up or down (RND)
* CLOCK: Input clock for the arpeggio
* TRIG: Input gate to trigger the sequence
* LOCK: Lock the pitches
* LOCK: Lock the pitches, SEQ and ARP setting, including the randomised notes

* OUT: Output sequenced note (1V/OCT)
* GATE: Output gate fired for each sequenced note.
* EOC: Output gate fired when a cycle ends
* EOS: Output gate fired when the whole series of cycles is finished

Almost something usable. The gaping void in the middle of the panel is where the selection mechanism for patterns will go. I should stop procrastinating and do it.
This is complete for the time being. There are a few new ideas which might materialise in the future.

### Notes

Expand Down
124 changes: 74 additions & 50 deletions src/Arpeggiator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ struct Arpeggiator : Module {
NUM_LIGHTS
};

int PATT_UP[17] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
int PATT_DN[17] = {0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10,-11,-12,-13,-14,-15,-16};

Arpeggiator() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
void step() override;

Expand All @@ -66,7 +63,6 @@ struct Arpeggiator : Module {

int inputSDir;
int sDir = 0;
int *sDirection; // FIXME eventually remove this

int inputStep = 0;
int nStep = 0;
Expand All @@ -86,6 +82,7 @@ struct Arpeggiator : Module {
int cycleI = 0;
int stepsRemaining;
int cycleRemaining;
int currDist = 0;

bool debug = false;
int stepX = 0;
Expand Down Expand Up @@ -148,6 +145,7 @@ void Arpeggiator::step() {
newCycle = true;
stepI = 0;
cycleI = 0;
currDist = 0;

// Set flag to advance sequence
advance = true;
Expand All @@ -157,46 +155,38 @@ void Arpeggiator::step() {
std::cout << "Advance from Trigger" << std::endl;
}

// Need this to stop clock gates from immediately advancing sequence. Probably should be a pulse
// Need this to stop clock gates from immediately advancing sequence, and for resetting the stepcount.
// Probably should be a pulse
wasTriggered = true;

}

// Set the pitches
// if there is a new cycle and the pitches are unlocked or we have been triggered

bool readCycleSettings = false;

// if there is a new cycle and the pitches are unlocked, or we have been triggered
if (isTriggered && !locked) {
if (debug) {
std::cout << "Read pitches from trigger: " << isTriggered << std::endl;
}
readCycleSettings = true;
}

if (isClocked && isRunning && newCycle && !locked) {
if (isClocked && newCycle && !locked) {
if (debug) {
std::cout << "Read pitches from clock: " << isClocked << isRunning << newCycle << !locked << std::endl;
}
readCycleSettings = true;
}

// Capture the settings
if (readCycleSettings) {

// Freeze sequence params
nStep = inputStep;
nDist = inputDist;
pDir = inputPDir;
sDir = inputSDir;

// Deal with RND setting // FIXME this will change as random will work differently
if (sDir == 1) {
if (rand() % 2 == 0) {
sDir = 0;
} else {
sDir = 2;
}
}


cycleLength = 0;

// Read out voltages
Expand All @@ -219,11 +209,13 @@ void Arpeggiator::step() {

}

// Slightly hacky but of code to catch case when there are no steps, or we have been triggered after being set to 0 steps
if (nStep == 0) {
return; // No steps, abort
} else {
// If we had reachd the end of the sequence or just transitioned from 0 step length, reset the counter
if (stepsRemaining == 0) {
if (stepsRemaining == 0 || wasTriggered) {
if (debug) { std::cout << "Hard reset stepsRemaining" << std::endl; }
stepsRemaining = nStep;
}
}
Expand All @@ -235,6 +227,7 @@ void Arpeggiator::step() {
advance = true;
}

// Advance the sequence
if (advance) {

if (debug) { std::cout << "Advance S: " << stepI <<
Expand All @@ -243,38 +236,54 @@ void Arpeggiator::step() {
" cRemain: " << cycleRemaining << std::endl;
}

// Starting a new cycle
if (newCycle) {

if (debug) { std::cout << "New Cycle: " << newCycle << std::endl; }

// Read the pattern
// Calculate the subsequent pitches, need direction, number of steps and step size
switch (sDir) {
case 0: sDirection = PATT_DN; break;
case 1: // This might happen on reset, if there is not valid input
case 2: sDirection = PATT_UP; break;
default: sDirection = PATT_UP;
// Deal with RND setting, when sDir == 1, force it up or down
if (sDir == 1) {
if (rand() % 2 == 0) {
sDir = 0;
} else {
sDir = 2;
}
}

for (int i = 0; i < cycleLength; i++) {

int target;
// Read the pitches according to direction, but we should do this for the sequence?
switch (pDir) {
case 0: target = cycleLength - i - 1; break; // DOWN
case 2: target = i; break; // UP
default: target = i; break; // For random case, read randomly from array, so order does not matter

// Only starting moving after the first cycle
if (stepI) {
switch (sDir) {
case 0: currDist--; break;
case 2: currDist++; break;
default: ;
}

float dV = semiTone * nDist * sDirection[stepI];
pitches[i] = clampf(inputPitches[target] + dV, -10.0, 10.0);
}

if (!locked) {// Pitches are locked, and so is the order. This keeps randomly generated arps fixed when locked
for (int i = 0; i < cycleLength; i++) {

int target;

// Read the pitches according to direction, but we should do this for the sequence?
switch (pDir) {
case 0: target = cycleLength - i - 1; break; // DOWN
case 1: target = rand() % cycleLength; break; // RANDOM
case 2: target = i; break; // UP
default: target = i; break; // For random case, read randomly from array, so order does not matter
}

// How many semi-tones do we need to shift
float dV = semiTone * nDist * currDist;
pitches[i] = clampf(inputPitches[target] + dV, -10.0, 10.0);

if (debug) {
std::cout << i << " stepI: " <<
" dV:" << dV <<
" in: " << inputPitches[target] <<
" out: " << pitches[target] << std::endl;
}
if (debug) {
std::cout << i << " stepI: " << stepI <<
" dV:" << dV <<
" target: " << target <<
" in: " << inputPitches[target] <<
" out: " << pitches[target] << std::endl;
}
}
}

if (debug) {
Expand All @@ -285,29 +294,38 @@ void Arpeggiator::step() {
std::cout << std::endl;
}

// Done, reset flag
newCycle = false;

}

if (pDir == 1) {
outVolts = pitches[rand() % cycleLength];
} else {
outVolts = pitches[cycleI];
}
// Finally set the out voltage
outVolts = pitches[cycleI];

if (debug) { std::cout << "V = " << outVolts << std::endl; }

// Update counters
cycleI++;
cycleRemaining--;

// Pulse the output gate
gatePulse.trigger(5e-3);

// Reached the end of the cycle
if (cycleRemaining == 0) {

// Completed 1 step
stepI++;
stepsRemaining--;

/// Reset the cycle counters
cycleRemaining = cycleLength;
cycleI = 0;

// Need to start a new cycle
newCycle = true;

// Pulse the EOC gate
eocPulse.trigger(5e-3);
if (debug) {
std::cout << "Cycle Finished S: " << stepI <<
Expand All @@ -318,6 +336,7 @@ void Arpeggiator::step() {
}
}

// Reached the end of the sequence
if (stepsRemaining == 0) {
if (debug) {
std::cout << "Sequence Finished S: " << stepI <<
Expand All @@ -326,12 +345,17 @@ void Arpeggiator::step() {
" cRemain: " << cycleRemaining <<
" flag:" << isRunning << std::endl;
}
// Update the flag
isRunning = false;

// Pulse the EOS gate
eosPulse.trigger(5e-3);
}

}


// Set the outputs
float delta = 1.0 / engineGetSampleRate();

bool sPulse = eosPulse.process(delta);
Expand Down

0 comments on commit 9eacc6c

Please sign in to comment.