diff --git a/package.json b/package.json index 85fbb0a..f9f5c0a 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "tone": "^14.8.49", "unique-names-generator": "^4.7.1", "vite-plugin-markdown": "^2.1.0", - "zifferjs": "^0.0.53", + "zifferjs": "^0.0.54", "zyklus": "^0.1.4", "zzfx": "^1.2.0" } diff --git a/src/API.ts b/src/API.ts index 78904c0..c33509b 100644 --- a/src/API.ts +++ b/src/API.ts @@ -146,6 +146,7 @@ export class UserAPI { : (this.app.selectedExample as string); } this.stop(); + this.resetAllFromCache(); this.play(); }; @@ -168,6 +169,7 @@ export class UserAPI { this.stop(); this.play(); this.app.exampleIsPlaying = true; + this.resetAllFromCache(); evaluateOnce(this.app, code as string); }; diff --git a/src/classes/AbstractEvents.ts b/src/classes/AbstractEvents.ts index 2cdb769..057cbf2 100644 --- a/src/classes/AbstractEvents.ts +++ b/src/classes/AbstractEvents.ts @@ -8,7 +8,7 @@ import { } from "zifferjs"; import { SkipEvent } from "./SkipEvent"; import { SoundParams } from "./SoundEvent"; -import { centsToSemitones, ratiosToSemitones } from "zifferjs/src/scale"; +import { centsToSemitones, edoToSemitones, ratiosToSemitones } from "zifferjs/src/scale"; export type EventOperation = (instance: T, ...args: any[]) => void; @@ -295,7 +295,9 @@ export abstract class AudibleEvent extends AbstractEvent { value = Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs]; } this.values["pitch"] = value; - if (this.values.key && this.values.parsedScale) this.update(); + this.values["originalPitch"] = value; + this.defaultPitchKeyScale(); + this.update(); return this; }; @@ -310,7 +312,7 @@ export abstract class AudibleEvent extends AbstractEvent { if (kwargs.length > 0) { value = Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs]; } - this.values["octave"] = value; + this.values["paramOctave"] = value; if ( this.values.key && (this.values.pitch || this.values.pitch === 0) && @@ -338,6 +340,12 @@ export abstract class AudibleEvent extends AbstractEvent { return this; }; + defaultPitchKeyScale() { + if (!this.values.key) this.values.key = 60; + if (!(this.values.pitch || this.values.pitch === 0)) this.values.pitch = 0; + if (!this.values.parsedScale) this.values.parsedScale = safeScale("major"); + } + scale = ( value: string | number | (number | string)[], ...kwargs: (string | number)[] @@ -355,30 +363,24 @@ export abstract class AudibleEvent extends AbstractEvent { } else if (Array.isArray(value)) { this.values.parsedScale = value.map((v) => safeScale(v)); } - if (this.values.key && (this.values.pitch || this.values.pitch === 0)) { - this.update(); - } + this.defaultPitchKeyScale(); + this.update(); return this; }; - updateKeyAndPitch() { - if (!this.values.key) this.values.key = 60; - if (!(this.values.pitch || this.values.pitch === 0)) this.values.pitch = 0; - } - semitones(values: number|number[], ...rest: number[]) { const scaleValues = typeof values === "number" ? [values, ...rest] : values; this.values.parsedScale = safeScale(scaleValues); - this.updateKeyAndPitch(); + this.defaultPitchKeyScale(); this.update(); return this; } steps = this.semitones; - + cents(values: number|number[], ...rest: number[]) { const scaleValues = typeof values === "number" ? [values, ...rest] : values; this.values.parsedScale = safeScale(centsToSemitones(scaleValues)); - this.updateKeyAndPitch(); + this.defaultPitchKeyScale(); this.update(); return this; } @@ -386,11 +388,18 @@ export abstract class AudibleEvent extends AbstractEvent { ratios(values: number|number[], ...rest: number[]) { const scaleValues = typeof values === "number" ? [values, ...rest] : values; this.values.parsedScale = safeScale(ratiosToSemitones(scaleValues)); - this.updateKeyAndPitch(); + this.defaultPitchKeyScale(); this.update(); return this; } + edo(value: number, intervals: string|number[] = new Array(value).fill(1)) { + this.values.parsedScale = edoToSemitones(value, intervals); + this.defaultPitchKeyScale(); + this.update(); + return this; + } + protected updateValue(key: string, value: T | T[] | null): this { if (value == null) return this; this.values[key] = value; diff --git a/src/classes/MidiEvent.ts b/src/classes/MidiEvent.ts index df8fa37..b59c8c2 100644 --- a/src/classes/MidiEvent.ts +++ b/src/classes/MidiEvent.ts @@ -1,7 +1,7 @@ import { AudibleEvent } from "./AbstractEvents"; import { type Editor } from "../main"; import { MidiConnection } from "../IO/MidiConnection"; -import { noteFromPc } from "zifferjs"; +import { resolvePitchClass } from "zifferjs"; import { filterObject, arrayOfObjectsToObjectWithArrays, @@ -84,31 +84,32 @@ export class MidiEvent extends AudibleEvent { }; update = (): void => { - // Get key, pitch, parsedScale and octave from this.values object const filteredValues = filterObject(this.values, [ "key", "pitch", + "originalPitch", "parsedScale", - "octave", + "addedOctave" ]); const events = objectWithArraysToArrayOfObjects(filteredValues, [ "parsedScale", ]); - events.forEach((event) => { - const [note, bend] = noteFromPc( - (event.key as number) || "C4", - (event.pitch as number) || 0, - (event.parsedScale as number[]) || event.scale || "MAJOR", - (event.octave as number) || 0, + events.forEach((soundEvent) => { + const resolvedPitchClass = resolvePitchClass( + (soundEvent.key || "C4"), + (soundEvent.originalPitch || soundEvent.pitch || 0), + (soundEvent.parsedScale || soundEvent.scale || "MAJOR"), + (soundEvent.addedOctave || 0) ); - event.note = note; - if (bend) event.bend = bend; + soundEvent.note = resolvedPitchClass.note; + soundEvent.pitch = resolvedPitchClass.pitch; + soundEvent.octave = resolvedPitchClass.octave; }); const newArrays = arrayOfObjectsToObjectWithArrays(events) as MidiParams; - + this.values.note = newArrays.note; if (newArrays.bend) this.values.bend = newArrays.bend; }; diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 68dda78..664aea9 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -6,7 +6,7 @@ import { arrayOfObjectsToObjectWithArrays, objectWithArraysToArrayOfObjects, } from "../Utils/Generic"; -import { midiToFreq, noteFromPc } from "zifferjs"; +import { midiToFreq, resolvePitchClass } from "zifferjs"; import { superdough, @@ -22,10 +22,13 @@ export type SoundParams = { note?: number | number[]; freq?: number | number[]; pitch?: number | number[]; + originalPitch?: number | number[]; key?: string; scale?: string; parsedScale?: number[]; octave?: number | number[]; + addedOctave?: number | number[]; + pitchOctave?: number | number[]; }; export class SoundEvent extends AudibleEvent { @@ -387,27 +390,36 @@ export class SoundEvent extends AudibleEvent { const filteredValues = filterObject(this.values, [ "key", "pitch", + "originalPitch", "parsedScale", + "addedOctave", "octave", + "paramOctave" ]); const events = objectWithArraysToArrayOfObjects(filteredValues, [ "parsedScale", ]); - events.forEach((event) => { - const [note, _] = noteFromPc( - (event.key as number) || "C4", - (event.pitch as number) || 0, - (event.parsedScale as number[]) || event.scale || "MAJOR", - (event.octave as number) || 0, + events.forEach((soundEvent) => { + const resolvedPitchClass = resolvePitchClass( + (soundEvent.key || "C4"), + (soundEvent.originalPitch || soundEvent.pitch || 0), + (soundEvent.parsedScale || soundEvent.scale || "MAJOR"), + (soundEvent.paramOctave || 0)+(soundEvent.addedOctave || 0) ); - event.note = note; - event.freq = midiToFreq(note); + soundEvent.note = resolvedPitchClass.note; + soundEvent.freq = midiToFreq(resolvedPitchClass.note); + soundEvent.pitch = resolvedPitchClass.pitch; + soundEvent.octave = resolvedPitchClass.octave; }); const newArrays = arrayOfObjectsToObjectWithArrays(events) as SoundParams; this.values.note = newArrays.note; this.values.freq = newArrays.freq; + this.values.pitch = newArrays.pitch; + this.values.octave = newArrays.octave; + this.values.pitchOctave = newArrays.pitchOctave; + }; out = (orbit?: number | number[]): void => { diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index 1ae14e8..efdb6d2 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -186,9 +186,12 @@ export class Player extends AbstractEvent { "freq", "note", "pitch", + "originalPitch", "key", "scale", "octave", + "pitchOctave", + "addedOctave", "parsedScale", ) as SoundParams; @@ -205,9 +208,12 @@ export class Player extends AbstractEvent { "freq", "note", "pitch", + "originalPitch", "key", "scale", "octave", + "pitchOctave", + "addedOctave", "parsedScale", ); }) as SoundParams[]; @@ -236,10 +242,13 @@ export class Player extends AbstractEvent { const obj = event.getExisting( "note", "pitch", + "originalPitch", "bend", "key", "scale", "octave", + "pitchOctave", + "addedOctave", "parsedScale", ) as MidiParams; if (event instanceof Pitch) { @@ -281,6 +290,11 @@ export class Player extends AbstractEvent { return this; } + edo(value: number, scale: string|number[] = new Array(value).fill(1)) { + if (this.atTheBeginning()) this.ziffers.edo(value, scale); + return this; + } + key(name: string) { if (this.atTheBeginning()) this.ziffers.key(name); return this; diff --git a/src/documentation/patterns/patterns.ts b/src/documentation/patterns/patterns.ts index 5e8a986..9488879 100644 --- a/src/documentation/patterns/patterns.ts +++ b/src/documentation/patterns/patterns.ts @@ -170,6 +170,26 @@ ${makeExample( true, )} +- edo(number, scale?: string|number[]): Create scale from equal divisions of the octave. Creates chromatic scale by default. + +${makeExample( + "Play pitches from scale created from equal divisions of the octave", + ` + z0("e bd bd ").sound().out() +flipbar(1) :: rhythm(.25,14,16) :: sound("ST10:30").stretch(3).gain(0.5) +.pitch([0,10,r(20,40),r(100,200),r(-200,200),r(200,300),200,r(3,666)].beat([1.0,0.5,0.25].bar(6))) + .octave(r(-6,6)) + .edo(666,"rocritonic") + .out() +rhythm(2.0,26,32) :: sound("ST20").n([22,5,24,34,31,5,11,19].pick()).stretch(rI(1,6)) +.pitch(rI(127,300)) + .edo(666) + .out() +`, + true, +)} + + - scale(scale: string, base note: number): Map each element of the list to the closest note of the slected scale. [0, 2, 3, 5 ].scale("major", 50) returns [50, 52, 54, 55]. You can use western scale names like (Major, Minor, Minor pentatonic ...) or [zeitler](https://ianring.com/musictheory/scales/traditions/zeitler) scale names. Alternatively you can also use the integers as used by Ian Ring in his [study of scales](https://ianring.com/musictheory/scales/). ${makeExample( diff --git a/yarn.lock b/yarn.lock index 4c6034a..e7de0e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4028,10 +4028,10 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -zifferjs@^0.0.53: - version "0.0.53" - resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.53.tgz#5e6e8a80ef90cd59ed607ab7cf02592efc03b08d" - integrity sha512-XGREU1ClhaatEZaek6gTBcwaNfFkUZLENWhXILPjLCODXzFoC6D/qaYCaIegcJdGgqB7At3DqID4yNwFLRPeqQ== +zifferjs@^0.0.54: + version "0.0.54" + resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.54.tgz#2dd4b43820f85d797c13dd35d933476ecacdb146" + integrity sha512-vo1I12VvW4yFdVJTVnrfOxeOpWq7tIMZ67BfXxcK0t9GveLi+3GrF1zjowq8WCDssVgw+lQHEjdGVhO5FbK3RA== zyklus@^0.1.4: version "0.1.4"