Adding sample patterning to ziffers

This commit is contained in:
2023-09-26 20:02:00 +03:00
parent 2843b65122
commit bf360247c8
7 changed files with 134 additions and 21 deletions

View File

@ -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"
}
}

View File

@ -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);
}
};

View File

@ -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);

View File

@ -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 {

View File

@ -32,7 +32,7 @@ midi_outputs()
- <ic>midi_output(output_name: string)</ic>: 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
)}
`;
};

View File

@ -29,8 +29,10 @@ The basic Ziffer notation is entirely written in JavaScript strings (_e.g_ <ic>"
| **Rest** | <ic>r</ic> | Rest / silences |
| **Repeat** | <ic>:1-9</ic> | Repeat the item 1-9 times |
| **Chords** | <ic>[1-9]+ / [iv]+ / [AG]+name</ic> | Multiple pitches grouped together, roman numerals or named chords |
| **Samples** | <ic>[a-z0-9_]+</ic> | Samples can be used pitched or unpitched |
| **Index/Channel** | <ic>[a-z0-9]+:[0-9]*</ic> | Samples or midi channel can be changed using a colon |
**Note:** Some features are still unsupported. For full syntax see article about <a href="https://zenodo.org/record/7841945" target="_blank">Ziffers</a>.
**Note:** Some features are experimental and some are still unsupported. For full / prior syntax see article about <a href="https://zenodo.org/record/7841945" target="_blank">Ziffers</a>.
${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 <c>@</c>-operator for assigning sample to a pitch. Sample index can be changed using the <c>:</c> operator.
${makeExample(
"Sampled drums",
`
z1('bd [hh hh]').octave(-2).sound('sine').out()
`,
true
)}
${makeExample(
"More complex pattern",
`
z1('bd [hh <hh <cp cp:2>>]').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

View File

@ -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"