diff --git a/index.html b/index.html index 8ce117c..69ecb72 100644 --- a/index.html +++ b/index.html @@ -178,7 +178,7 @@ -

Patterns

+

Array patterns

MIDI

OSC

@@ -192,7 +192,19 @@

Probabilities

Chaining

Functions

-

Ziffers

+ + +
+ Ziffers +
+

Basics

+

Scales

+

Rhythm

+

Algorithmic

+

Tonnetz

+
+
+ diff --git a/src/Documentation.ts b/src/Documentation.ts index bf2dafa..808bcd8 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -31,7 +31,13 @@ import { functions } from "./documentation/functions"; import { variables } from "./documentation/variables"; import { probabilities } from "./documentation/probabilities"; import { lfos } from "./documentation/lfos"; -import { ziffers } from "./documentation/ziffers"; +import { ziffers_basics } from "./documentation/patterns/ziffers/ziffers_basics"; +import { ziffers_scales } from "./documentation/patterns/ziffers/ziffers_scales"; +import { ziffers_rhythm } from "./documentation/patterns/ziffers/ziffers_rhythm"; +import { ziffers_algorithmic } from "./documentation/patterns/ziffers/ziffers_algorithmic"; + +import { ziffers_tonnetz } from "./documentation/patterns/ziffers/ziffers_tonnetz"; + import { synths } from "./documentation/synths"; // Setting up the Markdown converter with syntax highlighting @@ -91,7 +97,11 @@ export const documentation_factory = (application: Editor) => { synths: synths(application), chaining: chaining(application), patterns: patterns(application), - ziffers: ziffers(application), + ziffers_basics: ziffers_basics(application), + ziffers_scales: ziffers_scales(application), + ziffers_algorithmic: ziffers_algorithmic(application), + ziffers_rhythm: ziffers_rhythm(application), + ziffers_tonnetz: ziffers_tonnetz(application), midi: midi(application), osc: osc(application), lfos: lfos(application), diff --git a/src/InterfaceLogic.ts b/src/InterfaceLogic.ts index 28d2cf0..4ea11f2 100644 --- a/src/InterfaceLogic.ts +++ b/src/InterfaceLogic.ts @@ -502,7 +502,11 @@ export const installInterfaceLogic = (app: Editor) => { "synths", "chaining", "patterns", - "ziffers", + "ziffers_basics", + "ziffers_scales", + "ziffers_rhythm", + "ziffers_algorithmic", + "ziffers_tonnetz", "midi", "osc", "functions", diff --git a/src/Utils/Generic.ts b/src/Utils/Generic.ts index e6c9190..fd1cfe7 100644 --- a/src/Utils/Generic.ts +++ b/src/Utils/Generic.ts @@ -85,3 +85,6 @@ export function filterObject( Object.entries(obj).filter(([key]) => filter.includes(key)), ); } + +export const GeneratorType = (function*(){yield undefined;}).constructor; +export const GeneratorIteratorType = (function*(){yield undefined;}).prototype.constructor; \ No newline at end of file diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index 8ce6f74..15317d6 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -5,7 +5,7 @@ import { SkipEvent } from "./SkipEvent"; import { SoundEvent, SoundParams } from "./SoundEvent"; import { MidiEvent, MidiParams } from "./MidiEvent"; import { RestEvent } from "./RestEvent"; -import { arrayOfObjectsToObjectWithArrays } from "../Utils/Generic"; +import { GeneratorIteratorType, GeneratorType, arrayOfObjectsToObjectWithArrays } from "../Utils/Generic"; import { TonnetzSpaces } from "zifferjs/src/tonnetz"; export type InputOptions = { [key: string]: string | number }; @@ -39,9 +39,11 @@ export class Player extends AbstractEvent { } else if (typeof input === "number") { this.input = input; this.ziffers = Ziffers.fromNumber(input, options); - } else { + } else if (input.constructor === GeneratorType || input.constructor === GeneratorIteratorType){ this.ziffers = Ziffers.fromGenerator(input, options); this.input = this.ziffers.input; + } else { + throw new Error("Invalid input"); } this.zid = zid; } diff --git a/src/documentation/patterns.ts b/src/documentation/patterns.ts index 9e5e437..3d9e45e 100644 --- a/src/documentation/patterns.ts +++ b/src/documentation/patterns.ts @@ -4,7 +4,7 @@ import { makeExampleFactory } from "../Documentation"; export const patterns = (application: Editor): string => { const makeExample = makeExampleFactory(application); return ` -# Patterns +# Array patterns **Topos** is using arrays as a way to make dynamic patterns of data (rhythms, melodies, etc). It means that the following: diff --git a/src/documentation/patterns/ziffers/ziffers_algorithmic.ts b/src/documentation/patterns/ziffers/ziffers_algorithmic.ts new file mode 100644 index 0000000..b452ad5 --- /dev/null +++ b/src/documentation/patterns/ziffers/ziffers_algorithmic.ts @@ -0,0 +1,62 @@ +import { type Editor } from "../../../main"; +import { makeExampleFactory } from "../../../Documentation"; + +export const ziffers_algorithmic = (application: Editor): string => { + const makeExample = makeExampleFactory(application); + return ` +# Algorithmic operations + + Ziffers provides shorthands for **many** numeric and algorithimic operations such as evaluating random numbers and creating sequences using list operations: + + * **List operations:** Cartesian operation (_e.g._ (3 2 1)+(2 5)) using the + operator. All the arithmetic operators are supported. + + ${makeExample( + "Element-wise operations for melodic generation", + ` + z1("1/8 _ 0 (0 1 3)+(1 2) 0 (2 3 5)-(1 2)").sound('sine') + .scale('pentatonic').fmi([0.25,0.5].beat(2)).fmh([2,4].beat(2)) + .room(0.9).size(0.9).sustain(0.1).delay(0.5).delay(0.125) + .delayfb(0.25).out(); + `, + true, + )} + + ${makeExample( + "List operations", + ` + 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') + .scale("Bebop major") + .out() + `, + true, + )} + + * **Random numbers:** (4,6) Random number between 4 and 6 + + ${makeExample( + "Random numbers, true computer music at last!", + ` + z1("s _ (0,8) 0 0 (0,5) 0 0").sound('sine') + .adsr(0, .1, 0, 0).scale('minor') + .fmdec(0.25).fmi(2).fmh([0.5, 0.25].beat(2)) + .room(0.9).size(0.5).sustain(0.1) .delay(0.5) + .delay(0.125).delayfb(0.25).out(); + beat(.5) :: snd(['kick', 'hat'].beat(.5)).out() + `, + true, + )} + + ${makeExample( + "Random numbers", + ` + z1('q 0 (2,4) 4 (5,9)').sound('sine') + .scale("Bebop minor") + .out() + `, + true, + )} + + * **Variables:** A=(0 2 3 4) Assign a list to a variable + +`; +}; diff --git a/src/documentation/patterns/ziffers/ziffers_basics.ts b/src/documentation/patterns/ziffers/ziffers_basics.ts new file mode 100644 index 0000000..0eacf5e --- /dev/null +++ b/src/documentation/patterns/ziffers/ziffers_basics.ts @@ -0,0 +1,296 @@ +import { type Editor } from "../../../main"; +import { makeExampleFactory } from "../../../Documentation"; + +export const ziffers_basics = (application: Editor): string => { + const makeExample = makeExampleFactory(application); + return ` +# Ziffers + +Ziffers is a **musical number based notation** tuned for _live coding_. It is a very powerful and flexible notation for describing musical patterns in very few characters. Number based musical notation has a long history and has been used for centuries as a shorthand technique for music notation. Amiika has written [papers](https://zenodo.org/record/7841945) and other documents describing his system. It is currently implemented for many live coding platforms including [Sardine](https://sardine.raphaelforment.fr) (Raphaël Forment) and [Sonic Pi](https://sonic-pi.net/) (Sam Aaron). Ziffers can be used for: + +- composing melodies using using **classical music notation and concepts**. +- exploring **generative / aleatoric / stochastic** melodies and applying them to sounds and synths. +- embracing a different mindset and approach to time and **patterning**. + +${makeExample( + "Super Fancy Ziffers example", + ` +z1('1/8 024!3 035 024 0124').sound('wt_stereo') + .adsr(0, .4, 0.5, .4).gain(0.1) + .lpadsr(4, 0, .2, 0, 0) + .cutoff(5000 + usine(1/2) * 2000) + .n([1,2,4].beat(4)).out() +z2('<1/8 1/16> __ 0 <(^) (^ ^)> (0,8)').sound('wt_stereo') + .adsr(0, .5, 0.5, .4).gain(0.2) + .lpadsr(4, 0, .2, 0, 0).n(14) + .cutoff(200 + usine(1/2) * 4000) + .n([1,2,4].beat(4)).o(2).room(0.9).out() +let osci = 1500 + usine(1/2) * 2000; +z3('can can:2').sound().gain(1).cutoff(osci).out() +z4('1/4 kick kick snare kick').sound().gain(1).cutoff(osci).out() +`, + true, +)} + +## Notation + +The basic Ziffer notation is entirely written in JavaScript strings (_e.g_ "0 1 2"). It consists mostly of numbers and letters. The whitespace character is used as a separator. Instead of note names, Ziffer is using numbers to represent musical pitch and letters to represent musical durations. Alternatively, _floating point numbers_ can also be used to represent durations. + +| Syntax | Symbol | Description | +|------------ |--------|------------------------| +| **Pitches** | 0-9 {10 11 21} | Numbers or escaped numbers in curly brackets | +| **Duration** | 0.25, 0.5 | Floating point numbers can also be used as durations | +| **Duration** | 1/4, 1/16 | Fractions can be used as durations | +| **Subdivision** | [1 [2 3]] | Durations can be subdivided using square brackets | +| **Cycles** | 1 <2 4> | Cycle values within the pattern | +| **Octave** | ^ _ | ^ for octave up and _ for octave down | +| **Accidentals** | # b | Sharp and flats, just like with regular music notation :smile: | +| **Rest** | r | Rest / silences | +| **Repeat** | !1-9 | Repeat the item 1 to 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 experimental and some are still unsupported. For full / prior syntax see article about Ziffers. + +${makeExample( + "Pitches from 0 to 9", + ` +z1('0.25 0 1 2 3 4 5 6 7 8 9').sound('wt_stereo') + .adsr(0, .1, 0, 0).out()`, + true, +)} + +${makeExample( + "Escaped pitches using curly brackets", + `z1('_ _ 0 {9 10 11} 4 {12 13 14}') + .sound('wt_05').pan(r(0,1)) + .cutoff(usaw(1/2) * 4000) + .room(0.9).size(0.9).out()`, + false, +)} + +${makeExample( + "Durations using fractions and floating point numbers", + ` +z1('1/8 0 2 4 0 2 4 1/4 0 3 5 0.25 _ 0 7 0 7') + .sound('square').delay(0.5).delayt(1/8) + .adsr(0, .1, 0, 0).delayfb(0.45).out() +`, + false, +)} + +${makeExample( + "Disco was invented thanks to Ziffers", + ` +z1('e _ _ 0 ^ 0 _ 0 ^ 0').sound('jvbass').out() +beat(1)::snd('bd').out(); beat(2)::snd('sd').out() +beat(3) :: snd('cp').room(0.5).size(0.5).orbit(2).out() +`, + false, +)} + +${makeExample( + "Accidentals and rests for nice melodies", + ` +z1('^ 1/8 0 1 b2 3 4 _ 4 b5 4 3 b2 1 0') + .scale('major').sound('triangle') + .cutoff(500).lpadsr(5, 0, 1/12, 0, 0) + .fmi(0.5).fmh(2).delay(0.5).delayt(1/3) + .adsr(0, .1, 0, 0).out() +`, + false, +)} + +${makeExample( + "Repeat items n-times", + ` +z1('1/8 _ _ 0!4 3!4 4!4 3!4') + .scale('major').sound('wt_oboe') + .shape(0.2).sustain(0.1).out() +z2('1/8 _ 0!4 5!4 4!2 7!2') + .scale('major').sound('wt_oboe') + .shape(0.2).sustain(0.1).out() +`, + false, +)} + +${makeExample( + "Subdivided durations", + ` +z1('w [0 [5 [3 7]]] h [0 4]') + .scale('major').sound('sine') + .fmi(usine(.5)).fmh(2).out() +`, + false, +)} + +## Chords + + Chords can be build by grouping pitches or using roman numeral notation, or by using named chords. + + ${makeExample( + "Chords from pitches", + ` + z1('1.0 024 045 058 046 014') + .sound('sine').adsr(0.5, 1, 0, 0) + .room(0.5).size(0.9) + .scale("minor").out() + `, + true + )} + + ${makeExample( + "Chords from roman numerals", + ` + z1('2/4 i vi ii v') + .sound('triangle').adsr(0.2, 0.3, 0, 0) + .room(0.5).size(0.9).scale("major").out() + `, + true + )} + + ${makeExample( + "Named chords with repeats", + ` + z1('0.25 Bmaj7!2 D7!2 _ Gmaj7!2 Bb7!2 ^ Ebmaj7!2') + .sound('square').room(0.5).cutoff(500) + .lpadsr(4, 0, .4, 0, 0).size(0.9) + .scale("major").out() + `, + true + )} + + ${makeExample( + "Transposing chords", + ` + z1('q Amin!2').key(["A2", "E2"].beat(4)) + .sound('sawtooth').cutoff(500) + .lpadsr(2, 0, .5, 0, 0, 0).out()`, + )} + + ${makeExample( + "Chord transposition with roman numerals", + ` + z1('i i v%-4 v%-2 vi%-5 vi%-3 iv%-2 iv%-1') + .sound('triangle').adsr(1/16, 1/5, 0.1, 0) + .delay(0.5).delayt([1/8, 1/4].beat(4)) + .delayfb(0.5).out() + beat(4) :: sound('breaks165').stretch(4).out() + `, + )} + + ${makeExample( + "Chord transposition with named chords", + ` + z1('1/4 Cmin!3 Fmin!3 Fmin%-1 Fmin%-2 Fmin%-1') + .sound("sine").bpf(500 + usine(1/4) * 2000) + .out() + `, + )} + + ${makeExample( + "Programmatic inversions", + ` + z1('1/6 i v 1/3 vi iv').invert([1,-1,-2,0].beat(4)) + .sound("sawtooth").cutoff(1000) + .lpadsr(2, 0, .2, 0, 0).out() + `, + )} + +## Synchronization + +Ziffers numbered methods **(z0-z16)** can be used to parse and play patterns. Each method is individually cached and can be used to play multiple patterns simultaneously. By default, each Ziffers expression can have a different duration. This system is thus necessary to make everything fit together in a loop-based environment like Topos. + +Numbered methods are synced automatically to **z0** method if it exsists. Syncing can also be done manually by using either the wait method, which will always wait for the current pattern to finish before starting the next cycle, or the sync method will only wait for the synced pattern to finish on the first time. + +${makeExample( + "Automatic sync to z0", + ` +z0('w 0 8').sound('peri').out() +z1('e 0 4 5 9').sound('bell').out() +`, + true, +)} + +${makeExample( + "Sync with wait", + ` +z1('w 0 5').sound('pluck').release(0.1).sustain(0.25).out() +z2('q 6 3').wait(z1).sound('sine').release(0.16).sustain(0.55).out() +`, + true, +)} + +${makeExample( + "Sync on first run", + ` + z1('w __ 0 5 9 3').sound('bin').out() + z2('q __ 4 2 e 6 3 q 6').sync(z1).sound('east').out() +`, + true, +)} + +## Examples + +- Basic notation + +${makeExample( + "Simple method chaining", + ` +z1('0 1 2 3').key('G3') + .scale('minor').sound('sine').out() +`, + true, +)} + +${makeExample( + "More complex chaining", + ` +z1('0 1 2 3 4').key('G3').scale('minor').sound('sine').often(n => n.pitch+=3).rarely(s => s.delay(0.5)).out() +`, + true, +)} + +${makeExample( + "Simple options", + ` +z1('0 3 2 4',{key: 'D3', scale: 'minor pentatonic'}).sound('sine').out() +`, + true, +)} + +${makeExample( + "Rest and octaves", + ` +z1('q 0 ^ e0 r _ 0 _ r 4 ^4 4') +.sound('sine').scale("godian").out() +`, + true, +)} + +${makeExample( + "Rests with durations", + ` + z1('q 0 4 e^r 3 e3 0.5^r h4 1/4^r e 5 r 0.125^r 0') + .sound('sine').scale("aeryptian").out() + `, + true, +)} + +## String prototypes + +You can also use string prototypes as an alternative syntax for creating Ziffers patterns + +${makeExample( + "String prototypes", + ` + "q 0 e 5 2 6 2 q 3".z0().sound('sine').out() + "q 2 7 8 6".z1().octave(-1).sound('sine').out() + "q 2 7 8 6".z2({key: "C2", scale: "aeolian"}).sound('sine').scale("minor").out() +`, + true, +)} + +`; +}; diff --git a/src/documentation/patterns/ziffers/ziffers_rhythm.ts b/src/documentation/patterns/ziffers/ziffers_rhythm.ts new file mode 100644 index 0000000..3bdca15 --- /dev/null +++ b/src/documentation/patterns/ziffers/ziffers_rhythm.ts @@ -0,0 +1,157 @@ +import { type Editor } from "../../../main"; +import { makeExampleFactory } from "../../../Documentation"; + +export const ziffers_rhythm = (application: Editor): string => { + const makeExample = makeExampleFactory(application); + return ` +# Rhythm + +Ziffers combines rhythmic and melodic notation into a single pattern language. This means that you can use the same pattern to describe both the rhythm and the melody of a musical phrase similarly to the way it is done in traditional music notation. + +${makeExample( + "Duration chars", + ` + z1('q 0 0 4 4 5 5 h4 q 3 3 2 2 1 1 h0').sound('sine').out() +`, + true, +)} + +${makeExample( + "Fraction durations", + ` + z1('1/4 0 0 4 4 5 5 2/4 4 1/4 3 3 2 2 1 1 2/4 0') + .sound('sine').out() +`, + true, +)} + +${makeExample( + "Decimal durations", + ` +z1('0.25 5 1 2 6 0.125 3 8 0.5 4 1.0 0') +.sound('sine').scale("galian").out() +`, + true, +)} + +## List of all duration characters + +Ziffers maps the following duration characters to the corresponding note lengths. + +| Character | Fraction | Duration | Name (US) | Name (UK) | +| ----- | ----- | ------- | ----- | +| m.. | 14/1 | 14.0 | Double dotted maxima | Double dotted Large +| m. | 12/1 | 12.0 | Dotted maxima | Dotted Large | +| m | 8/1 | 8.0 | Maxima | Large | +| l.. | 7/1 | 7.0 | Double dotted long note | Double dotted longa | +| l. | 6/1 | 6.0 | Long dotted note | Longa dotted | +| l | 4/1 | 4.0 | Long | Longa | +| p | 8/3 | 2.6666 | Triplet maxima | Triplet longa | +| d.. | 7/2 | 3.5 | Double dotted long note | Double dotted breve | +| d. | 3/3 | 3.0 | Double whole note | Double breve | +| d | 2/1 | 2.0 | Double whole note | Breve | +| c | 4/3 | 1.3333 | Triplet long | Triplet breve | +| w.. | 7/4 | 1.75 | Double dotted whole note | Double dotted breve | +| w. | 3/2 | 1.5 | Dotted whole note | Dotted breve | +| w | 1/1 | 1.0 | Whole note | Semibreve | +| y | 2/3 | 0.6666 | Triplet half | Triplet semibreve | +| h.. | 7/8 | 0.875 | Double dotted half note | Double dotted minim | +| h. | 3/4 | 0.75 | Dotted half note | Dotted minim | +| h | 1/2 | 0.5 | Half note  | Minim | +| n | 1/3 | 0.3333 | Triplet whole | Triplet minim | +| q.. | 7/16 | 0.4375 | Double dotted quarter note | Double dotted crotchet | +| q. | 3/8 | 0.375 | Dotted quarter note | Dotted crotchet | +| q | 1/4 | 0.25 | Quarter note | Crotchet | +| a | 1/6 | 0.1666 | Triplet quarter | Triplet crochet  | +| e.. | 7/32 | 0.2187 | Double dotted eighth note | Double dotted quaver | +| e. | 3/16 | 0.1875 | Dotted eighth note | Dotted quaver | +| e | 1/8 | 0.125 | 8th note | Quaver | +| f | 1/12 | 0.0833 | Triplet 8th | Triplet quaver | +| s.. | 7/64 | 0.1093 | Double dotted sixteenth note | Double dotted semiquaver | +| s. | 3/32 | 0.0937 | Dotted sixteenth note | Dotted semiquaver | +| s | 1/16 | 0.0625 | 16th note | Semiquaver | +| x | 1/24 | 0.0416 | Triplet 16th | Triplet semiquaver | +| t.. | 7/128 | 0.0546 | Double dotted thirty-second note | Double dotted demisemiquaver | +| t. | 3/64 | 0.0468 | Dotted thirty-second note | Dotted demisemiquaver | +| t | 1/32 | 0.0312 | 32th note | Demisemiquaver | +| g | 1/48 | 0.0208 | Triplet 32th | Triplet demi-semiquaver | +| u.. | 7/256 | 0.0273 | Double dotted sixty-fourth note | Double dotted hemidemisemiquaver | +| u. | 3/128 | 0.0234 | Dotted sixty-fourth note | Dotted hemidemisemiquaver | +| u | 1/64 | 0.0156 | 64th note | Hemidemisemiquaver | +| j | 1/96 | 0.0104 | Triplet 64th | Triplet hemidemisemiquaver | +| o.. | 7/512 | 0.0136 | Double dotted 128th note | Double dotted semihemidemisemiquaver | +| o. | 3/256 | 0.0117 | Dotted 128th note | Dotted semihemidemisemiquaver | +| o | 1/128 | 0.0078 | 128th note | Semihemidemisemiquaver | +| k | 1/192 | 0.0052 | Triplet 128th | Triplet semihemidemisemiquaver | +| z | 0/1 | 0.0 | No length | No length | + +## 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, +)} + +`; +}; diff --git a/src/documentation/patterns/ziffers/ziffers_scales.ts b/src/documentation/patterns/ziffers/ziffers_scales.ts new file mode 100644 index 0000000..6afef9f --- /dev/null +++ b/src/documentation/patterns/ziffers/ziffers_scales.ts @@ -0,0 +1,101 @@ +import { type Editor } from "../../../main"; +import { makeExampleFactory } from "../../../Documentation"; + +export const ziffers_scales = (application: Editor): string => { + const makeExample = makeExampleFactory(application); + return ` +# Scales + + Ziffers supports all the keys and scales. Keys can be defined by using [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation), for example F3. Western style (1490 scales) can be with scale names named after greek modes and extended by [William Zeitler](https://ianring.com/musictheory/scales/traditions/zeitler). You will never really run out of scales to play with using Ziffers. Here is a short list of some possible scales that you can play with: + + | Scale name | Intervals | + |------------|------------------------| + | Lydian | 2221221 | + | Mixolydian | 2212212 | + | Aeolian | 2122122 | + | Locrian | 1221222 | + | Ionian | 2212221 | + | Dorian | 2122212 | + | Phrygian | 1222122 | + | Soryllic | 11122122| + | Modimic | 412122 | + | Ionalian | 1312122 | + | ... | And it goes on for **1490** scales | + + ${makeExample( + "What the hell is the Modimic scale?", + ` + z1("s (0,8) 0 0 (0,5) 0 0").sound('sine') + .scale('modimic').fmi(2).fmh(2).room(0.5) + .size(0.5).sustain(0.1) .delay(0.5) + .delay(0.125).delayfb(0.25).out(); + beat(.5) :: snd(['kick', 'hat'].beat(.5)).out() + `, + true, + )} + + You can also use more traditional western names: + + | Scale name | Intervals | + |------------|------------------------| + | Major | 2212221 | + | Minor | 2122122 | + | Minor pentatonic | 32232 | + | Harmonic minor | 2122131| + | Harmonic major | 2212131| + | Melodic minor | 2122221| + | Melodic major | 2212122| + | Whole | 222222 | + | Blues minor | 321132 | + | Blues major | 211323 | + + ${makeExample( + "Let's fall back to a classic blues minor scale", + ` + z1("s (0,8) 0 0 (0,5) 0 0").sound('sine') + .scale('blues minor').fmi(2).fmh(2).room(0.5) + .size(0.5).sustain(0.25).delay(0.25) + .delay(0.25).delayfb(0.5).out(); + beat(1, 1.75) :: snd(['kick', 'hat'].beat(1)).out() + `, + true, + )} + + Microtonal scales can be defined using Scala format or by extended notation defined by Sevish Scale workshop, for example: + + - **Young:** 106. 198. 306.2 400.1 502. 604. 697.9 806.1 898.1 1004.1 1102. 1200. + - **Wendy carlos:** 17/16 9/8 6/5 5/4 4/3 11/8 3/2 13/8 5/3 7/4 15/8 2/1 + + + ${makeExample( + "Wendy Carlos, here we go!", + ` + z1("s ^ (0,8) 0 0 _ (0,5) 0 0").sound('sine') + .scale('17/16 9/8 6/5 5/4 4/3 11/8 3/2 13/8 5/3 7/4 15/8 2/1').fmi(2).fmh(2).room(0.5) + .size(0.5).sustain(0.15).delay(0.1) + .delay(0.25).delayfb(0.5).out(); + beat(1, 1.75) :: snd(['kick', 'hat'].beat(1)).out() + `, + true, + )} + +${makeExample( + "Werckmeister scale in Scala format", + ` + const werckmeister = "107.82 203.91 311.72 401.955 503.91 605.865 701.955 809.775 900. 1007.82 1103.91 1200." + + z0('s (0,3) ^ 0 3 ^ 0 (3,6) 0 _ (3,5) 0 _ 3 ^ 0 (3,5) ^ 0 6 0 _ 3 0') + .key('C3') + .scale(werckmeister) + .sound('sine') + .fmi(1 + usine(0.5) * irand(1,10)) + .cutoff(100 + usine(.5) * 100) + .out() + + onbeat(1,1.5,3) :: sound('bd').cutoff(100 + usine(.25) * 1000).out() +`, + true, +)} + +`; +}; diff --git a/src/documentation/patterns/ziffers/ziffers_tonnetz.ts b/src/documentation/patterns/ziffers/ziffers_tonnetz.ts new file mode 100644 index 0000000..0ddc400 --- /dev/null +++ b/src/documentation/patterns/ziffers/ziffers_tonnetz.ts @@ -0,0 +1,20 @@ +import { type Editor } from "../../../main"; +import { makeExampleFactory } from "../../../Documentation"; + +export const ziffers_tonnetz = (application: Editor): string => { + const makeExample = makeExampleFactory(application); + return ` +# Tonnetz + +* TBD + +${makeExample( + "Triad transformations", + ` +z1('i').tonnetz("p l r").sound('wt_stereo') + .adsr(0, .1, 0, 0).out()`, + true, +)} + +`; +}; diff --git a/src/documentation/ziffers.ts b/src/documentation/ziffers.ts deleted file mode 100644 index ee66a04..0000000 --- a/src/documentation/ziffers.ts +++ /dev/null @@ -1,554 +0,0 @@ -import { type Editor } from "../main"; -import { makeExampleFactory } from "../Documentation"; - -export const ziffers = (application: Editor): string => { - const makeExample = makeExampleFactory(application); - return ` -# Ziffers - -Ziffers is a **musical number based notation** tuned for _live coding_. It is a very powerful and flexible notation for describing musical patterns in very few characters. Number based musical notation has a long history and has been used for centuries as a shorthand technique for music notation. Amiika has written [papers](https://zenodo.org/record/7841945) and other documents describing his system. It is currently implemented for many live coding platforms including [Sardine](https://sardine.raphaelforment.fr) (Raphaël Forment) and [Sonic Pi](https://sonic-pi.net/) (Sam Aaron). Ziffers can be used for: - -- composing melodies using using **classical music notation and concepts**. -- exploring **generative / aleatoric / stochastic** melodies and applying them to sounds and synths. -- embracing a different mindset and approach to time and **patterning**. - -${makeExample( - "Super Fancy Ziffers example", - ` -z1('1/8 024!3 035 024 0124').sound('wt_stereo') - .adsr(0, .4, 0.5, .4).gain(0.1) - .lpadsr(4, 0, .2, 0, 0) - .cutoff(5000 + usine(1/2) * 2000) - .n([1,2,4].beat(4)).out() -z2('<1/8 1/16> __ 0 <(^) (^ ^)> (0,8)').sound('wt_stereo') - .adsr(0, .5, 0.5, .4).gain(0.2) - .lpadsr(4, 0, .2, 0, 0).n(14) - .cutoff(200 + usine(1/2) * 4000) - .n([1,2,4].beat(4)).o(2).room(0.9).out() -let osci = 1500 + usine(1/2) * 2000; -z3('can can:2').sound().gain(1).cutoff(osci).out() -z4('1/4 kick kick snare kick').sound().gain(1).cutoff(osci).out() -`, - true, -)} - -## Notation - -The basic Ziffer notation is entirely written in JavaScript strings (_e.g_ "0 1 2"). It consists mostly of numbers and letters. The whitespace character is used as a separator. Instead of note names, Ziffer is using numbers to represent musical pitch and letters to represent musical durations. Alternatively, _floating point numbers_ can also be used to represent durations. - -| Syntax | Symbol | Description | -|------------ |--------|------------------------| -| **Pitches** | 0-9 {10 11 21} | Numbers or escaped numbers in curly brackets | -| **Duration** | 0.25, 0.5 | Floating point numbers can also be used as durations | -| **Duration** | 1/4, 1/16 | Fractions can be used as durations | -| **Subdivision** | [1 [2 3]] | Durations can be subdivided using square brackets | -| **Octave** | ^ _ | ^ for octave up and _ for octave down | -| **Accidentals** | # b | Sharp and flats, just like with regular music notation :smile: | -| **Rest** | r | Rest / silences | -| **Repeat** | !1-9 | Repeat the item 1 to 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 experimental and some are still unsupported. For full / prior syntax see article about Ziffers. - -${makeExample( - "Pitches from 0 to 9", - ` -z1('0.25 0 1 2 3 4 5 6 7 8 9').sound('wt_stereo') - .adsr(0, .1, 0, 0).out()`, - true, -)} - -${makeExample( - "Escaped pitches using curly brackets", - `z1('_ _ 0 {9 10 11} 4 {12 13 14}') - .sound('wt_05').pan(r(0,1)) - .cutoff(usaw(1/2) * 4000) - .room(0.9).size(0.9).out()`, - false, -)} - -${makeExample( - "Durations using fractions and floating point numbers", - ` -z1('1/8 0 2 4 0 2 4 1/4 0 3 5 0.25 _ 0 7 0 7') - .sound('square').delay(0.5).delayt(1/8) - .adsr(0, .1, 0, 0).delayfb(0.45).out() -`, - false, -)} - -${makeExample( - "Disco was invented thanks to Ziffers", - ` -z1('e _ _ 0 ^ 0 _ 0 ^ 0').sound('jvbass').out() -beat(1)::snd('bd').out(); beat(2)::snd('sd').out() -beat(3) :: snd('cp').room(0.5).size(0.5).orbit(2).out() -`, - false, -)} - -${makeExample( - "Accidentals and rests for nice melodies", - ` -z1('^ 1/8 0 1 b2 3 4 _ 4 b5 4 3 b2 1 0') - .scale('major').sound('triangle') - .cutoff(500).lpadsr(5, 0, 1/12, 0, 0) - .fmi(0.5).fmh(2).delay(0.5).delayt(1/3) - .adsr(0, .1, 0, 0).out() -`, - false, -)} - -${makeExample( - "Repeat items n-times", - ` -z1('1/8 _ _ 0!4 3!4 4!4 3!4') - .scale('major').sound('wt_oboe') - .shape(0.2).sustain(0.1).out() -z2('1/8 _ 0!4 5!4 4!2 7!2') - .scale('major').sound('wt_oboe') - .shape(0.2).sustain(0.1).out() -`, - false, -)} - -${makeExample( - "Subdivided durations", - ` -z1('w [0 [5 [3 7]]] h [0 4]') - .scale('major').sound('sine') - .fmi(usine(.5)).fmh(2).out() -`, - false, -)} - -## Chords - -Chords can be build by grouping pitches or using roman numeral notation, or by using named chords. - -${makeExample( - "Chords from pitches", - ` -z1('1.0 024 045 058 046 014') - .sound('sine').adsr(0.5, 1, 0, 0) - .room(0.5).size(0.9) - .scale("minor").out() -`, -)} - -${makeExample( - "Chords from roman numerals", - ` -z1('2/4 i vi ii v') - .sound('triangle').adsr(0.2, 0.3, 0, 0) - .room(0.5).size(0.9).scale("major").out() -`, -)} - -${makeExample( - "Named chords with repeats", - ` -z1('0.25 Bmaj7!2 D7!2 _ Gmaj7!2 Bb7!2 ^ Ebmaj7!2') - .sound('square').room(0.5).cutoff(500) - .lpadsr(4, 0, .4, 0, 0).size(0.9) - .scale("major").out() -`, -)} - -${makeExample( - "Transposing chords", - ` -z1('q Amin!2').key(["A2", "E2"].beat(4)) - .sound('sawtooth').cutoff(500) - .lpadsr(2, 0, .5, 0, 0, 0).out()`, -)} - -${makeExample( - "Chord transposition with roman numerals", - ` -z1('i i v%-4 v%-2 vi%-5 vi%-3 iv%-2 iv%-1') - .sound('triangle').adsr(1/16, 1/5, 0.1, 0) - .delay(0.5).delayt([1/8, 1/4].beat(4)) - .delayfb(0.5).out() -beat(4) :: sound('breaks165').stretch(4).out() -`, -)} - -${makeExample( - "Chord transposition with named chords", - ` -z1('1/4 Cmin!3 Fmin!3 Fmin%-1 Fmin%-2 Fmin%-1') - .sound("sine").bpf(500 + usine(1/4) * 2000) - .out() -`, -)} - -${makeExample( - "Programmatic inversions", - ` -z1('1/6 i v 1/3 vi iv').invert([1,-1,-2,0].beat(4)) - .sound("sawtooth").cutoff(1000) - .lpadsr(2, 0, .2, 0, 0).out() - `, -)} - -## Algorithmic operations - -Ziffers provides shorthands for **many** numeric and algorithimic operations such as evaluating random numbers and creating sequences using list operations: - -* **List operations:** Cartesian operation (_e.g._ (3 2 1)+(2 5)) using the + operator. All the arithmetic operators are supported. - -${makeExample( - "Element-wise operations for melodic generation", - ` -z1("1/8 _ 0 (0 1 3)+(1 2) 0 (2 3 5)-(1 2)").sound('sine') - .scale('pentatonic').fmi([0.25,0.5].beat(2)).fmh([2,4].beat(2)) - .room(0.9).size(0.9).sustain(0.1).delay(0.5).delay(0.125) - .delayfb(0.25).out(); -`, - true, -)} - -* **Random numbers:** (4,6) Random number between 4 and 6 - -${makeExample( - "Random numbers, true computer music at last!", - ` -z1("s _ (0,8) 0 0 (0,5) 0 0").sound('sine') - .adsr(0, .1, 0, 0).scale('minor') - .fmdec(0.25).fmi(2).fmh([0.5, 0.25].beat(2)) - .room(0.9).size(0.5).sustain(0.1) .delay(0.5) - .delay(0.125).delayfb(0.25).out(); -beat(.5) :: snd(['kick', 'hat'].beat(.5)).out() -`, - true, -)} - -## Keys and scales - -Ziffers supports all the keys and scales. Keys can be defined by using [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation), for example F3. Western style (1490 scales) can be with scale names named after greek modes and extended by [William Zeitler](https://ianring.com/musictheory/scales/traditions/zeitler). You will never really run out of scales to play with using Ziffers. Here is a short list of some possible scales that you can play with: - -| Scale name | Intervals | -|------------|------------------------| -| Lydian | 2221221 | -| Mixolydian | 2212212 | -| Aeolian | 2122122 | -| Locrian | 1221222 | -| Ionian | 2212221 | -| Dorian | 2122212 | -| Phrygian | 1222122 | -| Soryllic | 11122122| -| Modimic | 412122 | -| Ionalian | 1312122 | -| ... | And it goes on for **1490** scales | - -${makeExample( - "What the hell is the Modimic scale?", - ` -z1("s (0,8) 0 0 (0,5) 0 0").sound('sine') - .scale('modimic').fmi(2).fmh(2).room(0.5) - .size(0.5).sustain(0.1) .delay(0.5) - .delay(0.125).delayfb(0.25).out(); -beat(.5) :: snd(['kick', 'hat'].beat(.5)).out() -`, - true, -)} - - - - - -You can also use more traditional western names: - - -| Scale name | Intervals | -|------------|------------------------| -| Major | 2212221 | -| Minor | 2122122 | -| Minor pentatonic | 32232 | -| Harmonic minor | 2122131| -| Harmonic major | 2212131| -| Melodic minor | 2122221| -| Melodic major | 2212122| -| Whole | 222222 | -| Blues minor | 321132 | -| Blues major | 211323 | - - -${makeExample( - "Let's fall back to a classic blues minor scale", - ` -z1("s (0,8) 0 0 (0,5) 0 0").sound('sine') - .scale('blues minor').fmi(2).fmh(2).room(0.5) - .size(0.5).sustain(0.25).delay(0.25) - .delay(0.25).delayfb(0.5).out(); -beat(1, 1.75) :: snd(['kick', 'hat'].beat(1)).out() -`, - true, -)} - -Microtonal scales can be defined using Scala format or by extended notation defined by Sevish Scale workshop, for example: - -- **Young:** 106. 198. 306.2 400.1 502. 604. 697.9 806.1 898.1 1004.1 1102. 1200. -- **Wendy carlos:** 17/16 9/8 6/5 5/4 4/3 11/8 3/2 13/8 5/3 7/4 15/8 2/1 - - -${makeExample( - "Wendy Carlos, here we go!", - ` -z1("s ^ (0,8) 0 0 _ (0,5) 0 0").sound('sine') - .scale('17/16 9/8 6/5 5/4 4/3 11/8 3/2 13/8 5/3 7/4 15/8 2/1').fmi(2).fmh(2).room(0.5) - .size(0.5).sustain(0.15).delay(0.1) - .delay(0.25).delayfb(0.5).out(); -beat(1, 1.75) :: snd(['kick', 'hat'].beat(1)).out() -`, - true, -)} - -## Synchronization - -Ziffers numbered methods **(z0-z16)** can be used to parse and play patterns. Each method is individually cached and can be used to play multiple patterns simultaneously. By default, each Ziffers expression can have a different duration. This system is thus necessary to make everything fit together in a loop-based environment like Topos. - -Numbered methods are synced automatically to **z0** method if it exsists. Syncing can also be done manually by using either the wait method, which will always wait for the current pattern to finish before starting the next cycle, or the sync method will only wait for the synced pattern to finish on the first time. - -${makeExample( - "Automatic sync to z0", - ` -z0('w 0 8').sound('peri').out() -z1('e 0 4 5 9').sound('bell').out() -`, - true, -)} - -${makeExample( - "Sync with wait", - ` -z1('w 0 5').sound('pluck').release(0.1).sustain(0.25).out() -z2('q 6 3').wait(z1).sound('sine').release(0.16).sustain(0.55).out() -`, - true, -)} - -${makeExample( - "Sync on first run", - ` - z1('w __ 0 5 9 3').sound('bin').out() - z2('q __ 4 2 e 6 3 q 6').sync(z1).sound('east').out() -`, - true, -)} - -## Examples - -- Basic notation - -${makeExample( - "Simple method chaining", - ` -z1('0 1 2 3').key('G3') - .scale('minor').sound('sine').out() -`, - true, -)} - -${makeExample( - "More complex chaining", - ` -z1('0 1 2 3 4').key('G3').scale('minor').sound('sine').often(n => n.pitch+=3).rarely(s => s.delay(0.5)).out() -`, - true, -)} - -${makeExample( - "Simple options", - ` -z1('0 3 2 4',{key: 'D3', scale: 'minor pentatonic'}).sound('sine').out() -`, - true, -)} - -${makeExample( - "Duration chars", - ` - z1('q 0 0 4 4 5 5 h4 q 3 3 2 2 1 1 h0').sound('sine').out() -`, - true, -)} - -${makeExample( - "Fraction durations", - ` - z1('1/4 0 0 4 4 5 5 2/4 4 1/4 3 3 2 2 1 1 2/4 0') - .sound('sine').out() -`, - true, -)} - -${makeExample( - "Decimal durations", - ` -z1('0.25 5 1 2 6 0.125 3 8 0.5 4 1.0 0') -.sound('sine').scale("galian").out() -`, - true, -)} - -${makeExample( - "Rest and octaves", - ` -z1('q 0 ^ e0 r _ 0 _ r 4 ^4 4') -.sound('sine').scale("godian").out() -`, - true, -)} - -${makeExample( - "Rests with durations", - ` - z1('q 0 4 e^r 3 e3 0.5^r h4 1/4^r e 5 r 0.125^r 0') - .sound('sine').scale("aeryptian").out() - `, - true, -)} - -- Scales - -${makeExample( - "Microtonal scales", - ` -z1('q 0 3 {10 14} e 8 4 {5 10 12 14 7 0}').sound('sine') -.fmi([1,2,4,8].pick()) -.scale("17/16 9/8 6/5 5/4 4/3 11/8 3/2 13/8 5/3 7/4 15/8 2/1") -.out() -`, - true, -)} - -${makeExample( - "Scala scale from variable", - ` - const werckmeister = "107.82 203.91 311.72 401.955 503.91 605.865 701.955 809.775 900. 1007.82 1103.91 1200." - - z0('s (0,3) ^ 0 3 ^ 0 (3,6) 0 _ (3,5) 0 _ 3 ^ 0 (3,5) ^ 0 6 0 _ 3 0') - .key('C3') - .scale(werckmeister) - .sound('sine') - .fmi(1 + usine(0.5) * irand(1,10)) - .cutoff(100 + usine(.5) * 100) - .out() - - onbeat(1,1.5,3) :: sound('bd').cutoff(100 + usine(.25) * 1000).out() -`, - true, -)} - -- Algorithmic operations - -${makeExample( - "Random numbers", - ` -z1('q 0 (2,4) 4 (5,9)').sound('sine') -.scale("Bebop minor") -.out() -`, - true, -)} - -${makeExample( - "List operations", - ` -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') -.scale("Bebop major") -.out() -`, - 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 - -${makeExample( - "String prototypes", - ` - "q 0 e 5 2 6 2 q 3".z0().sound('sine').out() - "q 2 7 8 6".z1().octave(-1).sound('sine').out() - "q 2 7 8 6".z2({key: "C2", scale: "aeolian"}).sound('sine').scale("minor").out() -`, - true, -)} - -`; -};