diff --git a/package.json b/package.json index 346aeef..602fa94 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "astring": "^1.8.6", "autoprefixer": "^10.4.14", "codemirror": "^6.0.1", + "lru-cache": "^10.0.1", "marked": "^7.0.3", "postcss": "^8.4.27", "showdown": "^2.1.0", @@ -36,7 +37,7 @@ "tailwindcss": "^3.3.3", "tone": "^14.8.49", "vite-plugin-markdown": "^2.1.0", - "zifferjs": "^0.0.11", + "zifferjs": "^0.0.12", "zzfx": "^1.2.0" } } diff --git a/src/API.ts b/src/API.ts index ba1b099..33f00eb 100644 --- a/src/API.ts +++ b/src/API.ts @@ -4,10 +4,10 @@ import { tryEvaluate } from "./Evaluator"; import { DrunkWalk } from "./Utils/Drunk"; import { scale } from "./Scales"; import { Editor } from "./main"; -import { Sound } from "./Sound"; -import { Note } from "./Note"; +import { Sound } from "./classes/Sound"; +import { Note } from "./classes/Note"; import { LRUCache } from "lru-cache"; -import { Player } from "./ZPlayer"; +import { Player } from "./classes/ZPlayer"; import { samples, initAudioOnFirstClick, diff --git a/src/Event.ts b/src/classes/Event.ts similarity index 56% rename from src/Event.ts rename to src/classes/Event.ts index ba98230..50c12de 100644 --- a/src/Event.ts +++ b/src/classes/Event.ts @@ -1,6 +1,7 @@ -import { type Editor } from './main'; +import { type Editor } from '../main'; +import { freqToMidi, resolvePitchBend, getScale, isScale, parseScala } from 'zifferjs'; -export class Event { +export abstract class Event { seedValue: string|undefined = undefined; randomGen: Function = Math.random; app: Editor; @@ -72,4 +73,50 @@ export class Event { return this; } +} + +export abstract class SoundEvent extends Event { + constructor(app: Editor) { + super(app); + } + + octave = (value: number): this => { + this.values['octave'] = value; + this.update(); + return this; + } + + key = (value: string): this => { + this.values['key'] = value; + this.update(); + return this; + } + + scale = (value: string): this => { + if(!isScale(value)) { + this.values.parsedScale = parseScala(value) as number[]; + } else { + this.values.scaleName = value; + this.values.parsedScale = getScale(value) as number[]; + } + this.update(); + return this; + } + + freq = (value: number): this => { + this.values['freq'] = value; + const midiNote = freqToMidi(value); + if(midiNote % 1 !== 0) { + this.values['note'] = Math.floor(midiNote); + this.values['bend'] = resolvePitchBend(midiNote)[1]; + } else { + this.values['note'] = midiNote; + } + return this; + } + + update = (): void => { + // Overwrite in subclasses + } + } \ No newline at end of file diff --git a/src/Note.ts b/src/classes/Note.ts similarity index 58% rename from src/Note.ts rename to src/classes/Note.ts index 39a5698..eed2025 100644 --- a/src/Note.ts +++ b/src/classes/Note.ts @@ -1,9 +1,9 @@ -import { Event } from './Event'; -import { type Editor } from './main'; -import { MidiConnection } from "./IO/MidiConnection"; -import { freqToMidi, midiToFreq, resolvePitchBend, noteFromPc, getScale, isScale, parseScala } from 'zifferjs'; +import { SoundEvent } from './Event'; +import { type Editor } from '../main'; +import { MidiConnection } from "../IO/MidiConnection"; +import { midiToFreq, noteFromPc } from 'zifferjs'; -export class Note extends Event { +export class Note extends SoundEvent { midiConnection: MidiConnection; constructor(input: number|object, public app: Editor) { @@ -18,11 +18,6 @@ export class Note extends Event { return this; } - duration = (value: number): this => { - this.values['duration'] = value; - return this; - } - sustain = (value: number): this => { this.values['sustain'] = value; return this; @@ -55,18 +50,6 @@ export class Note extends Event { } } - freq = (value: number): this => { - this.values['freq'] = value; - const midiNote = freqToMidi(value); - if(midiNote % 1 !== 0) { - this.values['note'] = Math.floor(midiNote); - this.values['bend'] = resolvePitchBend(midiNote)[1]; - } else { - this.values['note'] = midiNote; - } - return this; - } - bend = (value: number): this => { this.values['bend'] = value; return this; @@ -80,40 +63,14 @@ export class Note extends Event { } update = (): void => { - // TODO: Figure out why _ is sometimes added? - if(this.values.type === 'Pitch' || this.values.type === '_Pitch') { - const [note,bend] = noteFromPc(this.values.key!, this.values.pitch!, this.values.parsedScale!, this.values.octave!); + if(this.values.key && this.values.pitch && this.values.parsedScale && this.values.octave) { + const [note,bend] = noteFromPc(this.values.key, this.values.pitch, this.values.parsedScale, this.values.octave); this.values.note = note; this.values.freq = midiToFreq(note); - if(bend) { - this.values.bend = bend; - } + if(bend) this.values.bend = bend; } } - octave = (value: number): this => { - this.values['octave'] = value; - this.update(); - return this; - } - - key = (value: string): this => { - this.values['key'] = value; - this.update(); - return this; - } - - scale = (value: string): this => { - if(!isScale(value)) { - this.values.parsedScale = parseScala(value) as number[]; - } else { - this.values.scaleName = value; - this.values.parsedScale = getScale(value) as number[]; - } - this.update(); - return this; - } - out = (): void => { const note = this.values.note ? this.values.note : 60; const channel = this.values.channel ? this.values.channel : 0; diff --git a/src/Rest.ts b/src/classes/Rest.ts similarity index 83% rename from src/Rest.ts rename to src/classes/Rest.ts index 8898af6..649e989 100644 --- a/src/Rest.ts +++ b/src/classes/Rest.ts @@ -1,4 +1,4 @@ -import { type Editor } from './main'; +import { type Editor } from '../main'; import { Event } from "./Event"; export class Rest extends Event { @@ -23,6 +23,10 @@ export class Rest extends Event { // @ts-ignore return target[propKey]; }, + // @ts-ignore + set(target, propKey, value, receiver) { + return false; + } }); } diff --git a/src/Sound.ts b/src/classes/Sound.ts similarity index 89% rename from src/Sound.ts rename to src/classes/Sound.ts index e548842..889bcfc 100644 --- a/src/Sound.ts +++ b/src/classes/Sound.ts @@ -1,12 +1,13 @@ -import { type Editor } from './main'; -import { Event } from './Event'; +import { type Editor } from '../main'; +import { SoundEvent } from './Event'; +import { midiToFreq, noteFromPc } from 'zifferjs'; import { superdough, // @ts-ignore } from "superdough"; -export class Sound extends Event { +export class Sound extends SoundEvent { constructor(sound: string|object, public app: Editor) { super(app); @@ -26,12 +27,6 @@ export class Sound extends Event { }; dec = this.decay; - sustain = (value: number): this => { - this.values["sustain"] = value; - return this; - }; - sus = this.sustain; - release = (value: number): this => { this.values["release"] = value; return this; @@ -223,11 +218,19 @@ export class Sound extends Event { } }; - dur = (value: number): this => { + // NOTE: Sustain of the sound. duration() from the superclass Event is used to change the note length. + sustain = (value: number): this => { this.values["dur"] = value; return this; }; + update = (): void => { + if(this.values.key && this.values.pitch && this.values.parsedScale && this.values.octave) { + const [note,_] = noteFromPc(this.values.key, this.values.pitch, this.values.parsedScale, this.values.octave); + this.values.freq = midiToFreq(note); + } + } + out = (): object => { return superdough( this.values, @@ -236,5 +239,4 @@ export class Sound extends Event { ); }; - } \ No newline at end of file diff --git a/src/ZPlayer.ts b/src/classes/ZPlayer.ts similarity index 65% rename from src/ZPlayer.ts rename to src/classes/ZPlayer.ts index 3fccdfb..8386cb8 100644 --- a/src/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -1,5 +1,5 @@ import { Chord, Pitch, Rest as ZRest, Ziffers } from "zifferjs"; -import { Editor } from "./main"; +import { Editor } from "../main"; import { Event } from "./Event"; import { Sound } from "./Sound"; import { Note } from "./Note"; @@ -11,6 +11,8 @@ export class Player extends Event { callTime: number = 0; played: boolean = false; current!: Pitch|Chord|ZRest; + retro: boolean = false; + tick: number = 0; constructor(input: string, options: object, public app: Editor) { super(app); @@ -25,7 +27,13 @@ export class Player extends Event { } areWeThereYet = (): boolean => { - return (this.ziffers.notStarted() || this.app.api.epulse() > this.callTime+(this.current.duration*this.app.api.ppqn())) + const howAboutNow = (this.ziffers.notStarted() || this.app.api.epulse() > this.callTime+(this.current.duration*this.app.api.ppqn())); + if(howAboutNow) { + this.tick = 0; + } else { + this.tick++; + } + return howAboutNow; } sound(name: string) { @@ -33,7 +41,8 @@ export class Player extends Event { const event = this.next() as Pitch|Chord|ZRest; if(event instanceof Pitch) { // TODO: Quick hack. Select which attributes to use, but some ziffers stuff is needed for chaining key change etc. - return new Sound(event.asObject(), this.app).sound(name); + const obj = event.getExisting("freq","pitch","key","scale","octave"); + return new Sound(obj, this.app).sound(name); } else if(event instanceof Rest) { return Rest.createRestProxy(event.duration, this.app); } @@ -48,7 +57,8 @@ export class Player extends Event { const event = this.next() as Pitch|Chord|ZRest; if(event instanceof Pitch) { // TODO: Quick hack. Select which attributes to use, but some ziffers stuff is needed for chaining key change etc. - const note = new Note(event.asObject(), this.app); + const obj = event.getExisting("note","pitch","bend","key","scale","octave"); + const note = new Note(obj, this.app); return value ? note.note(value) : note; } else if(event instanceof ZRest) { return Rest.createRestProxy(event.duration, this.app); @@ -59,6 +69,28 @@ export class Player extends Event { } } + scale(name: string) { + this.ziffers.scale(name); + return this; + } + + key(name: string) { + this.ziffers.key(name); + return this; + } + + octave(value: number) { + this.ziffers.octave(value); + return this; + } + + retrograde() { + if(this.tick === 0 && this.ziffers.index === 0) { + this.ziffers.retrograde(); + } + return this; + } + out = (): void => { // TODO? } diff --git a/yarn.lock b/yarn.lock index 2bb49a0..e2fa885 100644 --- a/yarn.lock +++ b/yarn.lock @@ -957,7 +957,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lru-cache@^10.0.0: +lru-cache@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== @@ -1441,12 +1441,10 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -zifferjs@^0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.11.tgz#76cd8f371b65f2176606987cf6fe8e2d156d7d2d" - integrity sha512-JABze3JRHMIzO++4M1EKOJsrG/MzuLMN4ev6XqwJrCGXu7OVyRi3FG4fgA1WAesiuCr/ped/9zHIuodLNMlUOw== - dependencies: - lru-cache "^10.0.0" +zifferjs@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.12.tgz#af03ac66902a4467c9b37430eda710d97f30aad0" + integrity sha512-WoTiBXcmLDt6dPK6hy2bpuVXuY8pKewwv9zSM57t55Zel0dvqhm0Pi4t07V12tdByUb1IyHIxxDRvwC4RJ7q4Q== zzfx@^1.2.0: version "1.2.0"