diff --git a/package.json b/package.json index 4d437ed..1af25d4 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "tone": "^14.8.49", "unique-names-generator": "^4.7.1", "vite-plugin-markdown": "^2.1.0", - "zifferjs": "^0.0.28", + "zifferjs": "^0.0.30", "zzfx": "^1.2.0" } } diff --git a/src/classes/MidiEvent.ts b/src/classes/MidiEvent.ts index 2321df0..1294d57 100644 --- a/src/classes/MidiEvent.ts +++ b/src/classes/MidiEvent.ts @@ -3,6 +3,14 @@ import { type Editor } from "../main"; import { MidiConnection } from "../IO/MidiConnection"; import { midiToFreq, noteFromPc } from "zifferjs"; +export type MidiParams = { + note: number; + bend?: number; + channel?: number; + port?: number; + sustain?: number; +} + export class MidiEvent extends AudibleEvent { midiConnection: MidiConnection; @@ -13,7 +21,7 @@ export class MidiEvent extends AudibleEvent { this.midiConnection = app.api.MidiConnection; } - chord = (value: number[]): this => { + chord = (value: MidiParams[]): this => { this.values["chord"] = value; return this; }; @@ -79,9 +87,12 @@ export class MidiEvent extends AudibleEvent { }; out = (): void => { - function play(note: number, event: MidiEvent): void { - const channel = event.values.channel ? event.values.channel : 0; + function play(event: MidiEvent, params?: MidiParams): void { + const paramChannel = params && params.channel ? params.channel : 0; + const channel = event.values.channel ? event.values.channel : paramChannel; const velocity = event.values.velocity ? event.values.velocity : 100; + const paramNote = params && params.note ? params.note : 60; + const note = event.values.note ? event.values.note : paramNote; const sustain = event.values.sustain ? event.values.sustain * @@ -106,12 +117,11 @@ export class MidiEvent extends AudibleEvent { } if(this.values.chord) { - this.values.chord.forEach((note: number) => { - play(note, this); + this.values.chord.forEach((p: MidiParams) => { + play(this, p); }); } else { - const note = this.values.note ? this.values.note : 60; - play(note, this); + play(this); } }; diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 60ed0d9..91c1dcf 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -7,6 +7,11 @@ import { // @ts-ignore } from "superdough"; +export type SoundParams = { + dur: number; + s?: string; +} + export class SoundEvent extends AudibleEvent { constructor(sound: string | object, public app: Editor) { super(app); diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index 4c3a862..28f345b 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -2,8 +2,8 @@ import { Chord, Pitch, Rest as ZRest, Ziffers } from "zifferjs"; import { Editor } from "../main"; import { Event } from "./AbstractEvents"; import { SkipEvent } from "./SkipEvent"; -import { SoundEvent } from "./SoundEvent"; -import { MidiEvent } from "./MidiEvent"; +import { SoundEvent, SoundParams } from "./SoundEvent"; +import { MidiEvent, MidiParams } from "./MidiEvent"; import { RestEvent } from "./RestEvent"; export type InputOptions = { [key: string]: string | number }; @@ -131,7 +131,7 @@ export class Player extends Event { return areWeThereYet; }; - sound(name: string) { + sound(name?: string) { if (this.areWeThereYet()) { const event = this.next() as Pitch | Chord | ZRest; @@ -145,8 +145,10 @@ export class Player extends Event { "octave", "parsedScale" ); + if(event.sound) name = event.sound as string; + if(event.soundIndex) obj.n = event.soundIndex; obj.dur = noteLengthInSeconds; - return new SoundEvent(obj, this.app).sound(name); + return new SoundEvent(obj, this.app).sound(name || "sine"); } else if (event instanceof Chord) { const pitches = event.pitches.map((p) => { return p.getExisting( @@ -158,7 +160,9 @@ export class Player extends Event { "parsedScale" ); }); - return new SoundEvent({dur: noteLengthInSeconds}, this.app).chord(pitches).sound(name); + const sound: SoundParams = {dur: noteLengthInSeconds}; + if(name) sound.s = name; + return new SoundEvent(sound, this.app).chord(pitches); } else if (event instanceof ZRest) { return RestEvent.createRestProxy(event.duration, this.app); } @@ -177,15 +181,16 @@ export class Player extends Event { "key", "scale", "octave", - "parsedScale" + "parsedScale", ); if (event instanceof Pitch) { + if(event.soundIndex) obj.channel = event.soundIndex; const note = new MidiEvent(obj, this.app); return value ? note.note(value) : note; } else if (event instanceof ZRest) { return RestEvent.createRestProxy(event.duration, this.app); } else if (event instanceof Chord) { - const pitches = event.notes(); + const pitches = event.midiChord() as MidiParams[]; return new MidiEvent(obj, this.app).chord(pitches); } } else { diff --git a/src/documentation/midi.ts b/src/documentation/midi.ts index 26eef0d..49a74ed 100644 --- a/src/documentation/midi.ts +++ b/src/documentation/midi.ts @@ -32,7 +32,7 @@ midi_outputs() - midi_output(output_name: string): enter your desired output to connect to it. ${makeExample( - "Listing MIDI outputs", + "Changing MIDI output", ` midi_output("MIDI Rocket-Trumpet") `, @@ -137,5 +137,26 @@ beat(.25) && midi_clock() // Sending clock to MIDI device from the global buffer `, true )} + +## Using midi with ziffers + +Ziffers offers some shorthands for defining channels within the patterns. See Ziffers for more information. + +${makeExample( + "Using midi with ziffers", + ` + z1('0 2 e 5 2 q 4 2').midi().port(2).channel(4).out() + `, + true +)} + +${makeExample( + "Setting the channel within the pattern", + ` + z1('(0 2 e 5 2):0 (4 2):1').midi().out() + `, + true +)} + `; }; diff --git a/src/documentation/ziffers.ts b/src/documentation/ziffers.ts index 460d71b..c8e720d 100644 --- a/src/documentation/ziffers.ts +++ b/src/documentation/ziffers.ts @@ -29,8 +29,10 @@ The basic Ziffer notation is entirely written in JavaScript strings (_e.g_ " | **Rest** | r | Rest / silences | | **Repeat** | :1-9 | Repeat the item 1-9 times | | **Chords** | [1-9]+ / [iv]+ / [AG]+name | Multiple pitches grouped together, roman numerals or named chords | +| **Samples** | [a-z0-9_]+ | Samples can be used pitched or unpitched | +| **Index/Channel** | [a-z0-9]+:[0-9]* | Samples or midi channel can be changed using a colon | -**Note:** Some features are still unsupported. For full syntax see article about Ziffers. +**Note:** Some features are experimental and some are still unsupported. For full / prior syntax see article about Ziffers. ${makeExample( "Pitches from 0 to 9", @@ -409,6 +411,76 @@ z1('q (0 3 1 5)+(2 5) e (0 5 2)*(2 3) (0 5 2)>>(2 3) (0 5 2)%(2 3)').sound('sine true )} +## Samples + +Samples can be patterned using the sample names or using @-operator for assigning sample to a pitch. Sample index can be changed using the : operator. + +${makeExample( + "Sampled drums", + ` + z1('bd [hh hh]').octave(-2).sound('sine').out() + `, + true +)} + +${makeExample( + "More complex pattern", + ` + z1('bd [hh >]').octave(-2).sound('sine').out() + `, + true +)} + +${makeExample( + "Pitched samples", + ` + z1('0@sax 3@sax 2@sax 6@sax') + .octave(-1).sound() + .adsr(0.25,0.125,0.125,0.25).out() + `, + true +)} + +${makeExample( + "Pitched samples from list operation", + ` + z1('e (0 3 -1 4)+(-1 0 2 1)@sine') + .key('G4') + .scale('110 220 320 450') + .sound().out() + `, + true +)} + +${makeExample( + "Pitched samples with list notation", + ` + z1('e (0 2 6 3 5 -2)@sax (0 2 6 3 5 -2)@arp') + .octave(-1).sound() + .adsr(0.25,0.125,0.125,0.25).out() + `, + true +)} + +${makeExample( + "Sample indices", + ` + z1('e 1:2 4:3 6:2') + .octave(-1).sound("east").out() + `, + true +)} + +${makeExample( + "Pitched samples with sample indices", + ` +z1('_e 1@east:2 4@bd:3 6@arp:2 9@baa').sound().out() +`, + true +)} + + + ## String prototypes You can also use string prototypes as an alternative syntax for creating Ziffers patterns diff --git a/yarn.lock b/yarn.lock index a18dbf2..cbb9307 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1451,10 +1451,10 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -zifferjs@^0.0.28: - version "0.0.28" - resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.28.tgz#4f4c4578ff6b01c6860320532967a2e0d8394a32" - integrity sha512-0wCi2+eIeqO6IIXDzjLh6Aes4uEAxm1CoXnKYayfJuUkLIpkq5NOGSNl/toubxmoqi+0HgJsjfdd4johhATMnQ== +zifferjs@^0.0.30: + version "0.0.30" + resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.30.tgz#92f5ee8e207e1316a8ffb0ca8ede0e88bca4cf47" + integrity sha512-CPRswWxl3hxvmBJDXdWyV1QNFWcWVSyVMMUZw1obZo2UaHQzw2cqINcRoZ5S4BMSYPgZUsJt0Efw1U8/dR9dOg== zzfx@^1.2.0: version "1.2.0"