diff --git a/package.json b/package.json index 992a81b..3ca5a72 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "devDependencies": { "@tauri-apps/cli": "^1.4.0", "@types/audioworklet": "^0.0.49", - "typescript": "^5.0.2", + "typescript": "^5.2.2", "vite": "^4.4.5" }, "dependencies": { diff --git a/src/API.ts b/src/API.ts index 6d30454..9c067f7 100644 --- a/src/API.ts +++ b/src/API.ts @@ -1513,7 +1513,7 @@ export class UserAPI { this._logMessage(message); }; - scale = getScaleNotes + scale = getScaleNotes; rate = (rate: number): void => { rate = rate; diff --git a/src/ArrayExtensions.ts b/src/ArrayExtensions.ts index b87e53a..36e53ac 100644 --- a/src/ArrayExtensions.ts +++ b/src/ArrayExtensions.ts @@ -1,4 +1,5 @@ import { type UserAPI } from "./API"; +import { safeScale, stepsToScale } from "zifferjs"; export {}; declare global { @@ -20,6 +21,8 @@ declare global { pick(): T; loop(index: number): T; shuffle(): this; + scale(name: string, base_note?: number): this; + scaleArp(scaleName: string): this; rotate(steps: number): this; unique(): this; in(value: T): boolean; @@ -239,6 +242,7 @@ export const makeArrayExtensions = (api: UserAPI) => { result.push(this[i]); } } + this.length = 0; this.push(...result); return this; @@ -341,3 +345,53 @@ export const makeArrayExtensions = (api: UserAPI) => { }; Array.prototype.rand = Array.prototype.random; }; + +Array.prototype.scale = function ( + scale: string = "major", + base_note: number = 0 +) { + /** + * @param scale - the scale name + * @param base_note - the base note to start at (MIDI note number) + * + * @returns notes from the desired scale + */ + + // This is a helper function to handle up or down octaviation. + const mod = (n: number, m: number) => ((n % m) + m) % m; + const selected_scale = stepsToScale(safeScale(scale)); + return this.map((value) => { + const octaveShift = Math.floor(value / selected_scale.length) * 12; + return ( + selected_scale[mod(value, selected_scale.length)] + + base_note + + octaveShift + ); + }); +}; + +Array.prototype.scaleArp = function ( + scaleName: string = "major", + boundary: number = 0 +) { + /* + * @param scaleName - the scale name + * @param mask - the length of the mask + * + * @returns arpeggiated notes from the scale + */ + const scale = stepsToScale(safeScale(scaleName)); + + let result = []; + + boundary = boundary > scale.length ? scale.length : boundary; + boundary = boundary == 0 ? scale.length : boundary; + + for (let j = 0; j < boundary; j++) { + for (let i = 0; i < this.length; i++) { + result.push(this[i] + scale[j]); + } + } + + return result; +}; diff --git a/src/Scales.ts b/src/Scales.ts deleted file mode 100644 index de046f8..0000000 --- a/src/Scales.ts +++ /dev/null @@ -1,78 +0,0 @@ -const SCALES: Record = { - major: [0, 2, 4, 5, 7, 9, 11], - naturalMinor: [0, 2, 3, 5, 7, 8, 10], - harmonicMinor: [0, 2, 3, 5, 7, 8, 11], - melodicMinor: [0, 2, 3, 5, 7, 9, 11], - dorian: [0, 2, 3, 5, 7, 9, 10], - phrygian: [0, 1, 3, 5, 7, 8, 10], - lydian: [0, 2, 4, 6, 7, 9, 11], - mixolydian: [0, 2, 4, 5, 7, 9, 10], - aeolian: [0, 2, 3, 5, 7, 8, 10], - locrian: [0, 1, 3, 5, 6, 8, 10], - wholeTone: [0, 2, 4, 6, 8, 10], - majorPentatonic: [0, 2, 4, 7, 9], - minorPentatonic: [0, 3, 5, 7, 10], - chromatic: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], - blues: [0, 3, 5, 6, 7, 10], - diminished: [0, 2, 3, 5, 6, 8, 9, 11], - neapolitanMinor: [0, 1, 3, 5, 7, 8, 11], - neapolitanMajor: [0, 1, 3, 5, 7, 9, 11], - enigmatic: [0, 1, 4, 6, 8, 10, 11], - doubleHarmonic: [0, 1, 4, 5, 7, 8, 11], - octatonic: [0, 2, 3, 5, 6, 8, 9, 11], - bebopDominant: [0, 2, 4, 5, 7, 9, 10, 11], - bebopMajor: [0, 2, 4, 5, 7, 8, 9, 11], - bebopMinor: [0, 2, 3, 5, 7, 8, 9, 11], - bebopDorian: [0, 2, 3, 4, 5, 7, 9, 10], - harmonicMajor: [0, 2, 4, 5, 7, 8, 11], - hungarianMinor: [0, 2, 3, 6, 7, 8, 11], - hungarianMajor: [0, 3, 4, 6, 7, 9, 10], - oriental: [0, 1, 4, 5, 6, 9, 10], - romanianMinor: [0, 2, 3, 6, 7, 9, 10], - spanishGypsy: [0, 1, 4, 5, 7, 8, 10], - jewish: [0, 1, 4, 5, 7, 8, 10], - hindu: [0, 2, 4, 5, 7, 8, 10], - japanese: [0, 1, 5, 7, 8], - hirajoshi: [0, 2, 3, 7, 8], - kumoi: [0, 2, 3, 7, 9], - inSen: [0, 1, 5, 7, 10], - iwato: [0, 1, 5, 6, 10], - yo: [0, 2, 5, 7, 9], - minorBlues: [0, 3, 5, 6, 7, 10], - algerian: [0, 2, 3, 5, 6, 7, 8, 11], - augmented: [0, 3, 4, 7, 8, 11], - balinese: [0, 1, 3, 7, 8], - byzantine: [0, 1, 4, 5, 7, 8, 11], - chinese: [0, 4, 6, 7, 11], - egyptian: [0, 2, 5, 7, 10], - eightToneSpanish: [0, 1, 3, 4, 5, 6, 8, 10], - hawaiian: [0, 2, 3, 5, 7, 9, 10], - hindustan: [0, 2, 4, 5, 7, 8, 10], - persian: [0, 1, 4, 5, 6, 8, 11], - eastIndianPurvi: [0, 1, 4, 6, 7, 8, 11], - orientalA: [0, 1, 4, 5, 6, 9, 10], -}; - -export function scale( - n: number, - scaleName: string = "major", - octave: number = 4 -): number { - /** - * Returns the MIDI note number for the given scale degree in the given scale. - * @param {number} n - The scale degree, where 0 is the tonic. - * @param {string} scaleName - The name of the scale. - * @param {number} octave - The octave number. - * @returns {number} The MIDI note number. - */ - const scale = SCALES[scaleName]; - - if (!scale) { - throw new Error(`Unknown scale ${scaleName}`); - } - - let index = n % scale.length; - if (index < 0) index += scale.length; // adjust for negative indexes - let additionalOctaves = Math.floor(n / scale.length); - return 60 + (octave + additionalOctaves) * 12 + scale[index]; -} diff --git a/src/documentation/patterns.ts b/src/documentation/patterns.ts index ffeeb48..2e07c9f 100644 --- a/src/documentation/patterns.ts +++ b/src/documentation/patterns.ts @@ -183,7 +183,75 @@ ${makeExample( beat(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out() `, true -)} + )} + +- scale(scale: string, mask: number): extrapolate a custom-masked scale from each list elements. _[0].scale("major", 3)_ returns _[0,2,4]_ + +${makeExample( + "Extrapolate a 3-elements Persian scale from 2 notes", + ` +beat(1) :: snd('gtr') + .note([0,5].scale("persian", 3).beat() + 50) + .out() +`, + true + )} + +- Currently supported scales : +| Scale name | Values | +|------------|------------------------| +| major | 0, 2, 4, 5, 7, 9, 11 +| naturalMinor | 0, 2, 3, 5, 7, 8, 10 +| harmonicMinor | 0, 2, 3, 5, 7, 8, 11 +| melodicMinor | 0, 2, 3, 5, 7, 9, 11 +| dorian | 0, 2, 3, 5, 7, 9, 10 +| phrygian | 0, 1, 3, 5, 7, 8, 10 +| lydian | 0, 2, 4, 6, 7, 9, 11 +| mixolydian | 0, 2, 4, 5, 7, 9, 10 +| aeolian | 0, 2, 3, 5, 7, 8, 10 +| locrian | 0, 1, 3, 5, 6, 8, 10 +| wholeTone | 0, 2, 4, 6, 8, 10 +| majorPentatonic | 0, 2, 4, 7, 9 +| minorPentatonic | 0, 3, 5, 7, 10 +| chromatic | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 +| blues | 0, 3, 5, 6, 7, 10 +| diminished | 0, 2, 3, 5, 6, 8, 9, 11 +| neapolitanMinor | 0, 1, 3, 5, 7, 8, 11 +| neapolitanMajor | 0, 1, 3, 5, 7, 9, 11 +| enigmatic | 0, 1, 4, 6, 8, 10, 11], +| doubleHarmonic | 0, 1, 4, 5, 7, 8, 11 +| octatonic | 0, 2, 3, 5, 6, 8, 9, 11 +| bebopDominant | 0, 2, 4, 5, 7, 9, 10, 11 +| bebopMajor | 0, 2, 4, 5, 7, 8, 9, 11 +| bebopMinor | 0, 2, 3, 5, 7, 8, 9, 11 +| bebopDorian | 0, 2, 3, 4, 5, 7, 9, 10 +| harmonicMajor | 0, 2, 4, 5, 7, 8, 11 +| hungarianMinor | 0, 2, 3, 6, 7, 8, 11 +| hungarianMajor | 0, 3, 4, 6, 7, 9, 10 +| oriental | 0, 1, 4, 5, 6, 9, 10 +| romanianMinor | 0, 2, 3, 6, 7, 9, 10 +| spanishGypsy | 0, 1, 4, 5, 7, 8, 10 +| jewish | 0, 1, 4, 5, 7, 8, 10 +| hindi | 0, 2, 4, 5, 7, 8, 10 +| japanese | 0, 1, 5, 7, 8 +| hirajoshi | 0, 2, 3, 7, 8 +| kumoi | 0, 2, 3, 7, 9 +| inSen | 0, 1, 5, 7, 10 +| iwato | 0, 1, 5, 6, 10 +| yo | 0, 2, 5, 7, 9 +| minorBlues | 0, 3, 5, 6, 7, 10 +| algerian | 0, 2, 3, 5, 6, 7, 8, 11 +| augmented | 0, 3, 4, 7, 8, 11 +| balinese | 0, 1, 3, 7, 8 +| byzantine | 0, 1, 4, 5, 7, 8, 11 +| chinese | 0, 4, 6, 7, 11 +| egyptian |0, 2, 5, 7, 10 +| eightToneSpanish | 0, 1, 3, 4, 5, 6, 8, 10 +| hawaiian | 0, 2, 3, 5, 7, 9, 10 +| hindustan | 0, 2, 4, 5, 7, 8, 10 +| persian | 0, 1, 4, 5, 6, 8, 11 +| eastIndianPurvi | 0, 1, 4, 6, 7, 8, 11 +| orientalA | 0, 1, 4, 5, 6, 9, 10 - add(): add a given amount to every list element. - sub(): add a given amount to every list element. diff --git a/src/examples/excerpts.ts b/src/examples/excerpts.ts index fb29854..62b332a 100644 --- a/src/examples/excerpts.ts +++ b/src/examples/excerpts.ts @@ -6,7 +6,7 @@ beat(.25) :: sound('wt_symetric:8') .cutoff(1500 + usine(1/8) * 5000) .lpadsr(16, 0.2, 0.2, 0.125/2, 0) .room(0.9).size(0.9).resonance(20) - .gain(0.3).out() + .gain(0.7).out() beat(1) :: sound('kick').n(4).out() beat(2) :: sound('snare').out() beat(.5) :: sound('hh').out()`, @@ -18,9 +18,9 @@ beat(2) :: app.hydra.osc(frequencies/100, 0.25, 0.5) .posterize([32,4,8,16].beat(2)).rotate(cpulse()) .kaleid([1,2,3].beat()).out()`, `// The real internet of things - Bubobubobubo -beat(.5) :: sound('STA6').cut(1).vel(0.4) +beat(.5) :: sound('STA6').cut(1).vel(0.8) .orbit(2).room(0.5).size(0.5).n(irand(1,4)) - .speed([0.15, 0.30].beat() * 1.5).loop([1,0] + .speed([0.15, 0.30].beat() * 3).loop([1,0] .beat(.125)).loopEnd([1,0.5].beat(2)).out() binrhythm(.5, 50) :: sound('shaker').out() binrhythm(.5, 52) :: sound('808bd').n(3).out() @@ -93,14 +93,11 @@ if (flip(8, 75)) { }`, `// Race day - Bubobubobubo bpm(125); -beat(.5) :: sound('STB6') - .n(irand(1,10)).speed(0.5).rel(1) - .sus(0.1).out() +beat(.5) :: sound('STB6').n(irand(1,10)).gain(1).out() rhythm(flip(4) ? 1 : .5, 5, 8) :: sound('kick').out() rhythm(flip(2) ? .5 : .25, 7, 8) :: sound('click') .vel(0.1 + utriangle(.25)).n(irand(1,5)).out() -rhythm(.5, 2, 8) :: sound('snare').out() -`, +rhythm(.5, 2, 8) :: sound('snare').out()`, `// Structure et approximation - Bubobubobubo beat(.25) :: sound('zzfx').zzfx( // Randomized chaos :) @@ -126,7 +123,7 @@ beat(rarely(12) ? .5 : .25) :: sound('ST22') .cutoff(irand(200, 5000)) .resonance(rand(0.2,0.8)) .room(0.9).size(1).orbit(2) - .speed(0.25).vel(0.3).end(0.5) + .speed(0.5).vel(0.6).end(0.5) .out() beat(.5) :: snd('dr') .n([0, 0, 0, 0, 2, 8].beat()) @@ -135,21 +132,19 @@ beat(flip(2) ? 1 : 0.75) :: snd('bd').n(2).out() beat(4) :: snd('snare').n(5) .delay(0.5).delayt(bpm() / 60 / 8) .delayfb(0.25).out() -`, - `// Atarism - Bubobubobubo +`, `// Atarism - Bubobubobubo bpm(85); let modifier = [.5, 1, 2].beat(8); let othermod = [1, .5, 4].beat(4); -beat(modifier / 2):: sound('STA9').n([0,2].beat(.5)).speed(0.5).vel(0.5).out() -beat(.5)::sound('STA9').n([0, 20].beat(.5)).speed([1,1.5].repeatAll(4).beat() / 4) - .cutoff(500 + usine(.25) * 3000).vel(0.5).out() +beat(modifier / 2):: sound('STA9').n([0,2].beat(.5)).vel(0.5).out() +beat(.5)::sound('STA9').n([0, 20].beat(.5)).speed([1,1.5].repeatAll(4).beat() /2) + .cutoff(500 + usine(.25) * 3000).vel(1).room(0.9).out() beat(modifier / 2):: sound('STA9') - .n([0,7].beat(.5)).speed(flip(othermod) ? 2 :4).vel(0.45).out() + .n([0,7].beat(.5)).speed(flip(othermod) ? 2 : 4).vel(1).out() rhythm(.25, 3, 8, 1) :: sound('STA9') - .note([30, 33].pick()).n(32).speed(0.5).out() + .note([30, 33].pick()).n(32).out() rhythm(othermod, 5, 8) :: sound('dr').n([0,1,2].beat()).out() -beat(1) :: sound('kick').vel(1).out() -`, +beat(1) :: sound('kick').vel(1).out()`, `// Ancient rhythms - Bubobubobubo beat(1) :: snd('kick').out(); beat(2) :: snd('sd').room(0.9).size(0.9).out(); diff --git a/src/main.ts b/src/main.ts index 6ae6b33..fc2e8e4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -66,6 +66,8 @@ const bindings = Object.keys(classMap).map((key) => ({ replace: (match, p1) => `<${key} class="${classMap[key]}" ${p1}>`, })); + + export class Editor { universes: Universes = template_universes; selected_universe: string; diff --git a/yarn.lock b/yarn.lock index 177b073..876c1e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1375,10 +1375,10 @@ tslib@^2.3.1, tslib@^2.6.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== -typescript@^5.0.2: - version "5.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== +typescript@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6"