Skip to content

Commit

Permalink
update key() and tune() methods to accept custom fp scales
Browse files Browse the repository at this point in the history
  • Loading branch information
nnirror committed Nov 12, 2023
1 parent 1c7d64c commit ac002a1
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 96 deletions.
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,16 @@ You need to connect the MIDI device you want to use before starting Facet.
- example:
- `$('example').ramp(36,72,32).chord('maj7').add((bars%4)*12).key('F# major').note(50,100,1);`
---
- **key** ( _key_and_scale_ )
- given an input FacetPattern with data in the range of MIDI note numbers (0-127), translate all its values so they now adhere to the supplied `key_and_scale` (e.g. "C major"). The `key()` method uses the TonalJS npm package as a scale dictionary.
- possible keys: "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"
- **key** ( _key_letter_, _key_scale_ )
- translates a FacetPattern with data in the range of MIDI note numbers (0-127) so all its values now adhere to the supplied `key_letter` and `key_scale`.
- `key_letter` values: "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"
- `key_scale` values can either be a string (see list below) or a FacetPattern containing 1-12 binary numbers (see examples).
- possible scales: ["major pentatonic", "major", "minor", "major blues", "minor blues", "melodic minor", "harmonic minor", "bebop", "diminished", "dorian", "lydian", "mixolydian", "phrygian", "locrian", "ionian pentatonic", "mixolydian pentatonic", "ritusen", "egyptian", "neopolitan major pentatonic", "vietnamese 1", "pelog", "kumoijoshi", "hirajoshi", "iwato", "in-sen", "lydian pentatonic", "malkos raga", "locrian pentatonic", "minor pentatonic", "minor six pentatonic", "flat three pentatonic", "flat six pentatonic", "scriabin", "whole tone pentatonic", "lydian #5P pentatonic", "lydian dominant pentatonic", "minor #7M pentatonic", "super locrian pentatonic", "minor hexatonic", "augmented", "piongio", "prometheus neopolitan", "prometheus", "mystery #1", "six tone symmetric", "whole tone", "messiaen's mode #5", "locrian major", "double harmonic lydian", "altered", "locrian #2", "mixolydian b6", "lydian dominant", "lydian augmented", "dorian b2", "ultralocrian", "locrian 6", "augmented heptatonic", "dorian #4", "lydian diminished", "leading whole tone", "lydian minor", "phrygian dominant", "balinese", "neopolitan major", "harmonic major", "double harmonic major", "hungarian minor", "hungarian major", "oriental", "flamenco", "todi raga", "persian", "enigmatic", "major augmented", "lydian #9", "messiaen's mode #4", "purvi raga", "spanish heptatonic", "bebop minor", "bebop major", "bebop locrian", "minor bebop", "ichikosucho", "minor six diminished", "half-whole diminished", "kafi raga", "messiaen's mode #6", "composite blues", "messiaen's mode #3", "messiaen's mode #7", "chromatic"]
- example: `$('example').randsamp('808').reduce(32).scale(36,51).key("F# bebop").note();`
- example: `$('example').noise(16).scale(30,80).key('c', _.from([1])).note(); // octave scale, via custom FacetPattern`
- example: `$('example').noise(16).scale(30,80).key('c', _.from([1,0,0,0,0,0,0,0,0,0,0,0])).note(); // equivalent to the above custom octave scale; the padded zeroes are optional`
- example: `$('example').noise(16).scale(30,80).key('c', _.from([1,0,0,0,0,0,1])).note(); // octave + perfect fifth scale, via custom FacetPattern`
- example: `$('example').noise(16).scale(30,80).key('c', _.from([1,0,0,0,0,1,0,0,0,0,0,1])).note(); // octave + tritone + major seventh, via custom FacetPattern`
---
- **osc** ( _address_ )
- sends a packet of OSC data to OSC address `address` for every value in the FacetPattern's data.
Expand Down Expand Up @@ -871,12 +876,12 @@ When a generator takes a FacetPattern or an array as an argument, it uses that p
- `$('example').from([0,1,2,3]).truncate(2); // now 2 values long`
- `$('example').from([0,1,2,3]).truncate(6); // still 4 values long`
---
- **tune** ( _key_and_scale_ = "c major", _binThreshold_ = 0.005 )
- applies a spectral gate to the FacetPattern, muting any frequency bins that do not closely map onto the supplied `key_and_scale`.
- The set of possible scales is listed in the `key()` method.
- `binThreshold` controls how close a bin frequency must be to the supplied scale's harmonic content in order to be kept. For example, if `binThreshold` is set to 0.1, then a bin frequency must be within 10% of a harmonic in the supplied `key_and_scale` in order to be kept.
- **tune** ( _note_ = "c", _binThreshold_ = 0.005 )
- applies a spectral gate to the FacetPattern, muting any frequency bins that do not closely map onto the supplied `note`.
- `note` values: "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"
- `binThreshold` controls how close a bin frequency must be to the supplied note's harmonics in order to be kept. For example, if `binThreshold` is set to 0.1, then a bin frequency must be within 10% of a harmonic in the supplied `note` in order to be kept.
- example:
- `$('example').noise(n1).tune('c major',0.0001).play(); // tuning a whole note of noise to c major`
- `$('example').noise(n1).tune('c',0.0001).play(); // tuning a whole note of noise to c`
---
- **unique** ( )
- returns the set of unique values in the FacetPattern.
Expand Down
146 changes: 62 additions & 84 deletions js/FacetPattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class FacetPattern {
this.do_not_regenerate = false;
this.env = this.getEnv();
this.executed_successfully = true;
this.key_data = false;
this.key_letter = false;
this.key_scale = false;
this.is_stopped = false;
this.loops_since_generation = 1;
this.original_data = [];
Expand Down Expand Up @@ -797,47 +798,8 @@ class FacetPattern {
return this;
}

key (key_string = "C major") {
let chroma_key = Scale.get(key_string).chroma;
let key_letter = key_string.split(' ')[0].toLowerCase();

if ( key_letter == 'a' ) {
chroma_key = this.stringLeftRotate(chroma_key,3);
}
else if ( key_letter == 'a#' ) {
chroma_key = this.stringLeftRotate(chroma_key,2);
}
else if ( key_letter == 'b' ) {
chroma_key = this.stringLeftRotate(chroma_key,1);
}
else if ( key_letter == 'c' ) {
// no rotation needed, chroma defaults to c at root
}
else if ( key_letter == 'c#' ) {
chroma_key = this.stringRightRotate(chroma_key,1);
}
else if ( key_letter == 'd' ) {
chroma_key = this.stringRightRotate(chroma_key,2);
}
else if ( key_letter == 'd#' ) {
chroma_key = this.stringRightRotate(chroma_key,3);
}
else if ( key_letter == 'e' ) {
chroma_key = this.stringRightRotate(chroma_key,4);
}
else if ( key_letter == 'f' ) {
chroma_key = this.stringRightRotate(chroma_key,5);
}
else if ( key_letter == 'f#' ) {
chroma_key = this.stringRightRotate(chroma_key,6);
}
else if ( key_letter == 'g' ) {
chroma_key = this.stringRightRotate(chroma_key,7);
}
else if ( key_letter == 'g#' ) {
chroma_key = this.stringRightRotate(chroma_key,8);
}

key (key_letter = "C", key_scale = "major") {
let chroma_key = this.parseKeyAndScale(key_letter,key_scale);
// check the modulo 12 of each variable. if it's 0, move it up 1 and try again. try 12 times then quit
let key_sequence = [];
for (let [k, step] of Object.entries(this.data)) {
Expand All @@ -864,9 +826,9 @@ class FacetPattern {
key_sequence.push(-1);
}
}
this.key_data = key_string;
this.key_scale = key_scale;
this.key_letter = key_letter;
this.data = key_sequence;
this.clip(0,127);
return this;
}

Expand Down Expand Up @@ -3310,6 +3272,60 @@ rechunk (numChunks, probability = 1) {
// END special operations

// BEGIN utility functions used in other methods
parseKeyAndScale(key_letter = "C", key_scale = "major") {
let chroma_key;
key_letter = key_letter.toLowerCase();
// if key_string is facet_pattern, create chroma_key from that
if ( this.isFacetPattern(key_scale ) ) {
if ( key_scale.data.length < 12 ) {
key_scale.append(new FacetPattern().from(0).dup(11)).truncate(12);
}
chroma_key = key_scale.data.join();
chroma_key = chroma_key.replace(/,/g, "");
}
else {
chroma_key = Scale.get(key_scale).chroma;
}

if ( key_letter == 'a' ) {
chroma_key = this.stringLeftRotate(chroma_key,3);
}
else if ( key_letter == 'a#' ) {
chroma_key = this.stringLeftRotate(chroma_key,2);
}
else if ( key_letter == 'b' ) {
chroma_key = this.stringLeftRotate(chroma_key,1);
}
else if ( key_letter == 'c' ) {
// no rotation needed, chroma defaults to c at root
}
else if ( key_letter == 'c#' ) {
chroma_key = this.stringRightRotate(chroma_key,1);
}
else if ( key_letter == 'd' ) {
chroma_key = this.stringRightRotate(chroma_key,2);
}
else if ( key_letter == 'd#' ) {
chroma_key = this.stringRightRotate(chroma_key,3);
}
else if ( key_letter == 'e' ) {
chroma_key = this.stringRightRotate(chroma_key,4);
}
else if ( key_letter == 'f' ) {
chroma_key = this.stringRightRotate(chroma_key,5);
}
else if ( key_letter == 'f#' ) {
chroma_key = this.stringRightRotate(chroma_key,6);
}
else if ( key_letter == 'g' ) {
chroma_key = this.stringRightRotate(chroma_key,7);
}
else if ( key_letter == 'g#' ) {
chroma_key = this.stringRightRotate(chroma_key,8);
}
return chroma_key;
}

getMaximumValue () {
let max = Math.max.apply(Math, this.data);
let min = Math.abs(Math.min.apply(Math, this.data));
Expand Down Expand Up @@ -3662,46 +3678,8 @@ fgate(binThresholds) {
return this;
}

tune (key_string = "C major", binThreshold = 0.005) {
let chroma_key = Scale.get(key_string).chroma;
let key_letter = key_string.split(' ')[0].toLowerCase();

if ( key_letter == 'a' ) {
chroma_key = this.stringLeftRotate(chroma_key,3);
}
else if ( key_letter == 'a#' ) {
chroma_key = this.stringLeftRotate(chroma_key,2);
}
else if ( key_letter == 'b' ) {
chroma_key = this.stringLeftRotate(chroma_key,1);
}
else if ( key_letter == 'c' ) {
// no rotation needed, chroma defaults to c at root
}
else if ( key_letter == 'c#' ) {
chroma_key = this.stringRightRotate(chroma_key,1);
}
else if ( key_letter == 'd' ) {
chroma_key = this.stringRightRotate(chroma_key,2);
}
else if ( key_letter == 'd#' ) {
chroma_key = this.stringRightRotate(chroma_key,3);
}
else if ( key_letter == 'e' ) {
chroma_key = this.stringRightRotate(chroma_key,4);
}
else if ( key_letter == 'f' ) {
chroma_key = this.stringRightRotate(chroma_key,5);
}
else if ( key_letter == 'f#' ) {
chroma_key = this.stringRightRotate(chroma_key,6);
}
else if ( key_letter == 'g' ) {
chroma_key = this.stringRightRotate(chroma_key,7);
}
else if ( key_letter == 'g#' ) {
chroma_key = this.stringRightRotate(chroma_key,8);
}
tune (key_letter = "C", binThreshold = 0.005) {
let chroma_key = this.parseKeyAndScale(key_letter,new FacetPattern().from(1));
chroma_key = chroma_key.split('');
let notes_in_key = [];
let octave_count = 0;
Expand Down
12 changes: 10 additions & 2 deletions js/transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ app.post('/bpm', (req, res) => {

app.post('/stop', (req, res) => {
event_register = [];
patterns_for_next_loop = {};
transport_on = false;
voice_allocator = initializeVoiceAllocator();
if ( typeof midioutput !== 'undefined' ) {
Expand Down Expand Up @@ -357,8 +358,15 @@ function applyNextPatterns () {
for (var c = 0; c < posted_pattern.chord_intervals.length; c++) {
let note_to_add = note_data.note + posted_pattern.chord_intervals[c];
// check if key needs to be locked
if ( posted_pattern.key_data !== false ) {
note_to_add = new FacetPattern().from(note_to_add).key(posted_pattern.key_data).data[0];
if ( posted_pattern.key_scale !== false ) {
if ( typeof posted_pattern.key_scale == 'object' ) {
// scale made from FP
note_to_add = new FacetPattern().from(note_to_add).key(posted_pattern.key_letter,new FacetPattern().from(posted_pattern.key_scale.data)).data[0];
}
else {
// scale from string
note_to_add = new FacetPattern().from(note_to_add).key(posted_pattern.key_letter,posted_pattern.key_scale).data[0];
}
}

event_register[facet_pattern_name].push(
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "facet",
"version": "1.1.4",
"version": "1.1.5",
"description": "Facet is a live coding system for algorithmic music",
"main": "index.js",
"directories": {
Expand Down

0 comments on commit ac002a1

Please sign in to comment.