From 5ac8f4b0c458ae58c77afaede4d0a01adbf04013 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Mon, 28 Aug 2023 15:13:43 +0300 Subject: [PATCH 01/21] Refactor ziffers to use z0-z16 methods --- package.json | 2 +- src/API.ts | 49 +++++++++++++++++++++++++++++++++++------- src/classes/ZPlayer.ts | 47 +++++++++++++++++++++++++++++++++------- yarn.lock | 7 +++--- 4 files changed, 84 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index f0e1838..0828600 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "tone": "^14.8.49", "unique-names-generator": "^4.7.1", "vite-plugin-markdown": "^2.1.0", - "zifferjs": "^0.0.14", + "zifferjs": "link:../zifferjs", "zzfx": "^1.2.0" } } diff --git a/src/API.ts b/src/API.ts index 1d65b41..4d19d45 100644 --- a/src/API.ts +++ b/src/API.ts @@ -7,7 +7,7 @@ import { Editor } from "./main"; import { SoundEvent } from "./classes/SoundEvent"; import { NoteEvent } from "./classes/MidiEvent"; import { LRUCache } from "lru-cache"; -import { Player } from "./classes/ZPlayer"; +import { InputOptions, Player } from "./classes/ZPlayer"; import { samples, initAudioOnFirstClick, @@ -300,26 +300,59 @@ export class UserAPI { // Ziffers related functions // ============================================================= + public generateCacheKey = (...args: any[]): string => { + return args.map((arg) => JSON.stringify(arg)).join(","); + }; + public z = ( input: string, - options: { [key: string]: string | number } = {} + options: InputOptions = {}, + id: number|string = "" ) => { - const generateCacheKey = (...args: any[]): string => { - return args.map((arg) => JSON.stringify(arg)).join(","); - }; - const key = generateCacheKey(input, options); + const zid = "z"+id.toString(); + const key = id==="" ? this.generateCacheKey(input, options) : zid; + let player; + if (this.app.api.patternCache.has(key)) { player = this.app.api.patternCache.get(key) as Player; - } else { + if(player.input!==input) { + player = undefined; + } + } + + if (!player) { player = new Player(input, options, this.app); this.app.api.patternCache.set(key, player); } - if(player) player.updateLastCallTime(); + + + if(typeof id === "number") player.zid = zid; + + player.updateLastCallTime(); + return player; }; + public z0 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 0); + public z1 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 1); + public z2 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 2); + public z3 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 3); + public z4 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 4); + public z5 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 5); + public z6 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 6); + public z7 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 7); + public z8 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 8); + public z9 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 9); + public z10 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 10); + public z11 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 11); + public z12 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 12); + public z13 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 13); + public z14 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 14); + public z15 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 15); + public z16 = (input: string, opts: InputOptions = {}) => this.z(input, opts, 16); + // ============================================================= // Counter and iteration // ============================================================= diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index ee4b9f9..9643f4f 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -6,10 +6,13 @@ import { SoundEvent } from "./SoundEvent"; import { NoteEvent } from "./MidiEvent"; import { RestEvent } from "./RestEvent"; +export type InputOptions = { [key: string]: string | number }; + export class Player extends Event { input: string; ziffers: Ziffers; - firstCallTime: number = 0; + initCallTime: number = 0; + startCallTime: number = 0; lastCallTime: number = 0; waitTime = 0; startBeat: number = 0; @@ -17,10 +20,13 @@ export class Player extends Event { current!: Pitch|Chord|ZRest; retro: boolean = false; index: number = -1; + zid: string|undefined = undefined; + options: InputOptions = {}; - constructor(input: string, options: object, public app: Editor) { + constructor(input: string, options: InputOptions, public app: Editor) { super(app); this.input = input; + this.options = options; this.ziffers = new Ziffers(input, options); } @@ -30,7 +36,7 @@ export class Player extends Event { } nextEndTime(): number { - return this.firstCallTime + this.ticks; + return this.startCallTime + this.ticks; } updateLastCallTime(): void { @@ -54,6 +60,26 @@ export class Player extends Event { return this.app.clock.convertPulseToSecond(pulse); } + firstRun = (): boolean => { + return this.origin()<=0 && this.notStarted(); + } + + atTheBeginning = (): boolean => { + return this.pulse()===0 && this.ziffers.index===0; + } + + origin = (): number => { + return this.app.clock.pulses_since_origin+1; + } + + pulse = (): number => { + return this.app.clock.time_position.pulse; + } + + nextBeat = (): number => { + return this.app.clock.next_beat_in_ticks; + } + // Check if it's time to play the event areWeThereYet = (): boolean => { // If clock has stopped @@ -68,14 +94,15 @@ export class Player extends Event { this.notStarted() && (this.app.clock.time_position.pulse === 1 || this.app.clock.pulses_since_origin+1 >= this.app.clock.next_beat_in_ticks) && - (this.app.clock.pulses_since_origin+1 >= this.firstCallTime+this.waitTime) + (this.app.clock.pulses_since_origin+1 >= this.waitTime) ) || ( // If pattern is already playing this.current && - this.pulseToSecond(this.app.clock.pulses_since_origin+1) >= + (this.pulseToSecond(this.app.clock.pulses_since_origin+1) >= this.pulseToSecond(this.lastCallTime) + - (this.current.duration*4) * this.pulseToSecond(this.app.api.ppqn()) + (this.current.duration*4) * this.pulseToSecond(this.app.api.ppqn())) && + (this.app.clock.pulses_since_origin+1 >= this.waitTime) ) ); @@ -83,7 +110,11 @@ export class Player extends Event { this.index = howAboutNow ? this.index+1 : this.index; if(howAboutNow && this.notStarted()) { - this.firstCallTime = this.app.clock.pulses_since_origin+1; + this.initCallTime = this.app.clock.pulses_since_origin+1; + } + + if(this.atTheBeginning()) { + this.startCallTime = this.app.clock.pulses_since_origin; } return howAboutNow; @@ -155,7 +186,7 @@ export class Player extends Event { } } */ - this.waitTime = Math.ceil(value*4*this.app.clock.ppqn); + this.waitTime = this.origin() + Math.ceil(value*4*this.app.clock.ppqn); } return this; diff --git a/yarn.lock b/yarn.lock index 347cbc0..50ca12a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1446,10 +1446,9 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -zifferjs@^0.0.14: - version "0.0.14" - resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.14.tgz#7876c799a08e799be7af22b65f4cb6f0b44f79ca" - integrity sha512-CpS3zTm8Btm8aTxd7sSUgVCF/S/jJ3hqwgp7uRzbZI8k6yJWhzo/rjMlEZoOmeBhs7Qy4XsVk7pfrLdS8AAIVA== +"zifferjs@link:../zifferjs": + version "0.0.0" + uid "" zzfx@^1.2.0: version "1.2.0" From cb12c4d8cc0b0c1e3633a0f46ac064eaf270f73e Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Mon, 28 Aug 2023 17:19:27 +0300 Subject: [PATCH 02/21] Documenting ziffers --- index.html | 1 + src/Documentation.ts | 122 ++++++++++++++++++++++++++++++++++++++ src/classes/MidiEvent.ts | 17 +++--- src/classes/SoundEvent.ts | 16 ++--- src/classes/ZPlayer.ts | 4 +- src/main.ts | 3 +- 6 files changed, 142 insertions(+), 21 deletions(-) diff --git a/index.html b/index.html index 53d366b..edf4918 100644 --- a/index.html +++ b/index.html @@ -120,6 +120,7 @@

Samples

Synths

Patterns

+

Ziffers

MIDI

Functions

Shortcuts

diff --git a/src/Documentation.ts b/src/Documentation.ts index 1a2af7c..eca95a4 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1057,6 +1057,127 @@ mod(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out `; +const ziffers: string = ` +# Ziffers + +Ziffers is a musical number based notation developed especially for live coding. It is a very powerful and flexibly notation for describing musical patterns in a short string. + +## Notation + +Basic notation consists of numbers and letters using spaces as separators. + +**Pitches:** Single digits 0-9 or multiple digits escaped {10 11 21} +**Durations:** a-z or decimals: 0.25 = q (quarter), 0.5 = h (half), 1 = w (whole), etc. +**Octave:** ^ _ (up and down) +**Accidentals:** # b +**Rest:** r + +NOTE! Some of the features are still unsupported. For full syntax see article on Ziffers. + +## Keys and scales + +Ziffers supports all the keys and scales. Keys can be defined by using scientific pitch notation, for example 'F3'. Western style (1490 scales) can be used with the scale named named after greek modes and extended by William Zeitler (see full list): + +* Lydian +* Mixolydian +* Aeolian +* Locrian +* Ionian +* Dorian +* Phrygian +* Soryllic +* Modimic +* Ionalian +* ... + +or by most traditional western names: + +* Major +* Minor +* Minor pentatonic +* Major pentatonic +* Harmonic minor +* Harmonic major +* Melodic minor +* Melodic major +* Whole +* Blues minor +* Blues major +* ... + +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 + +## Methods + +Ziffers numbered methods **(z0-z16)** can be used to parse and play patterns. Each method is individually cached and can be used to play patterns simultaniously. + +## Chaining and options + +Ziffers patterns can be chained to sound() and midi() to produce different outputs. Chaining is often alternative for passing in options, which can be more efficient. Methods available for chaining are: +* key() - for changing key +* scale() - for chaning scale +* octave() - for changing octave +* sound() - for outputting pattern as sounds (See Sound) +* midi() - for outputting pattern as midi (See Midi) + +## 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('s 0 e 1 q 2 h 3 w 4').sound('sine').scale("locrian").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("ionian").out() +`, + true +)} + +${makeExample( + "Rest and octaves", + ` +z1('q 0 ^ e0 r _ 0 _ r 4 ^4 4').sound('sine').scale("ionian").out() +`, + true +)} + +`; + + const synths: string = ` # Synthesizers @@ -1418,6 +1539,7 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st samples: samples, synths: synths, patterns: patterns, + ziffers: ziffers, midi: midi, functions: functions, reference: reference, diff --git a/src/classes/MidiEvent.ts b/src/classes/MidiEvent.ts index 6fdfa15..eed14af 100644 --- a/src/classes/MidiEvent.ts +++ b/src/classes/MidiEvent.ts @@ -42,10 +42,10 @@ export class NoteEvent extends AudibleEvent { const funcResult = func(this); if(funcResult instanceof Object) { return funcResult; - } else { func(this.values); + this.update(); return this; } } @@ -63,12 +63,15 @@ export class NoteEvent extends AudibleEvent { } update = (): void => { - 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; - } + const [note, bend] = noteFromPc( + this.values.key || "C4", + this.values.pitch || 0, + this.values.parsedScale || "MAJOR", + this.values.octave || 0 + ); + this.values.note = note; + this.values.freq = midiToFreq(note); + if(bend) this.values.bend = bend; } out = (): void => { diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 5ca34e5..4daff76 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -225,6 +225,7 @@ export class SoundEvent extends AudibleEvent { if (funcResult instanceof Object) return funcResult; else { func(this.values); + this.update(); return this; } }; @@ -237,20 +238,13 @@ export class SoundEvent extends AudibleEvent { sus = this.sustain; 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.key || "C4", + this.values.pitch || 0, + this.values.parsedScale || "MAJOR", + this.values.octave || 0 ); this.values.freq = midiToFreq(note); - } }; out = (): object => { diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index 9643f4f..c7d0506 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -124,7 +124,7 @@ export class Player extends Event { if(this.areWeThereYet()) { const event = this.next() as Pitch|Chord|ZRest; if(event instanceof Pitch) { - const obj = event.getExisting("freq","pitch","key","scale","octave"); + const obj = event.getExisting("freq","pitch","key","scale","octave","parsedScale"); return new SoundEvent(obj, this.app).sound(name); } else if(event instanceof ZRest) { return RestEvent.createRestProxy(event.duration, this.app); @@ -138,7 +138,7 @@ export class Player extends Event { if(this.areWeThereYet()) { const event = this.next() as Pitch|Chord|ZRest; if(event instanceof Pitch) { - const obj = event.getExisting("note","pitch","bend","key","scale","octave"); + const obj = event.getExisting("note","pitch","bend","key","scale","octave","parsedScale"); const note = new NoteEvent(obj, this.app); return value ? note.note(value) : note; } else if(event instanceof ZRest) { diff --git a/src/main.ts b/src/main.ts index d180bff..18f059c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -565,6 +565,7 @@ export class Editor { "samples", "synths", "patterns", + "ziffers", "midi", "functions", "reference", @@ -989,5 +990,5 @@ window.addEventListener("beforeunload", () => { // app._mouseY = event.clientY; // } -onmousemove = function(e){console.log("mouse location:", e.clientX, e.clientY)} +// onmousemove = function(e){console.log("mouse location:", e.clientX, e.clientY)} From 0d370b121cf7e38e629a7cda8ef2779825ec2028 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Mon, 28 Aug 2023 17:51:55 +0300 Subject: [PATCH 03/21] More ziffers docs --- src/Documentation.ts | 67 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index eca95a4..b3388c1 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1074,9 +1074,16 @@ Basic notation consists of numbers and letters using spaces as separators. NOTE! Some of the features are still unsupported. For full syntax see article on Ziffers. +## Algorithmic operations + +Ziffers provides shorthands for many numeric and algorithimic operations such as evaluating random numbers and creating sequences using list operations: + +* **List operations:** (3 2 1)+(2 5) Cartesian operation using + operator (All javascript operators supported). +* **Random numbers:** (4,6) Random number between 4 and 6 + ## Keys and scales -Ziffers supports all the keys and scales. Keys can be defined by using scientific pitch notation, for example 'F3'. Western style (1490 scales) can be used with the scale named named after greek modes and extended by William Zeitler (see full list): +Ziffers supports all the keys and scales. Keys can be defined by using scientific pitch notation, for example 'F3'. Western style (1490 scales) can be with scale names named after greek modes and extended by William Zeitler (see full list): * Lydian * Mixolydian @@ -1110,6 +1117,8 @@ Microtonal scales can be defined using >(2 3) (0 5 2)%(2 3)').sound('sine') +.scale("Bebop major") +.out() +`, + true +)} + `; From e7c4ebe42fa81f406deabb7072472a5b693c2aca Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Mon, 28 Aug 2023 19:56:22 +0300 Subject: [PATCH 04/21] Update zifferjs --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 0828600..0448b48 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "tone": "^14.8.49", "unique-names-generator": "^4.7.1", "vite-plugin-markdown": "^2.1.0", - "zifferjs": "link:../zifferjs", + "zifferjs": "^0.0.15", "zzfx": "^1.2.0" } } diff --git a/yarn.lock b/yarn.lock index 50ca12a..9622421 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1446,9 +1446,10 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -"zifferjs@link:../zifferjs": - version "0.0.0" - uid "" +zifferjs@^0.0.15: + version "0.0.15" + resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.15.tgz#ee2d0dc8b3e6f1f4609e680d18274993304bc728" + integrity sha512-9gYrT2Chb6PbH7KBGGari9gRQlu+qHwD4JXierqSLSuE61NckQ6h5eo3AgRrmYDGX3pBQWdAzv7g1C/21H9BgQ== zzfx@^1.2.0: version "1.2.0" From 62e2441c166a8285789277210a0215ad01cfdef8 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 19:03:19 +0200 Subject: [PATCH 05/21] Updating beginning of Ziffers page --- src/Documentation.ts | 51 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index 4b71613..5b27951 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1152,19 +1152,52 @@ true const ziffers: string = ` # Ziffers -Ziffers is a musical number based notation developed especially for live coding. It is a very powerful and flexibly notation for describing musical patterns in a short string. - +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", +``, true)} + ## Notation -Basic notation consists of numbers and letters using spaces as separators. +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. -**Pitches:** Single digits 0-9 or multiple digits escaped {10 11 21} -**Durations:** a-z or decimals: 0.25 = q (quarter), 0.5 = h (half), 1 = w (whole), etc. -**Octave:** ^ _ (up and down) -**Accidentals:** # b -**Rest:** r +| Syntax | Symbol | Description | +|------------ |--------|------------------------| +| **Pitches** | 0-9 {10 11 21} | Numbers or escaped numbers in curly brackets | +| **Duration** | a b c to z | Each letter of the alphabet is a rhythm (see table) | +| **Duration** | 0.25 = q, 0.5 = h | Floating point numbers can also be used as durations | +| **Octave** | ^ _ | ^ for octave up and _ for octave down | +| **Accidentals** | # b | Sharp and flats, just like with regular music notation :smile: | +| **Rest** | r | Rest / silences | + +**Note:** Some features are still unsupported. For full syntax see article about Ziffers. + + +${makeExample( + "Pitches from 0 to 9", +``, true)} + +${makeExample( + "Escaped pitches using curly brackets", +``, false)} + +${makeExample( + "Durations using letters and floating point numbers", +``, false)} + +${makeExample( + "Disco was invented thanks to Ziffers", +``, false)} + +${makeExample( + "Accidentals and rests for nice melodies", +``, false)} -NOTE! Some of the features are still unsupported. For full syntax see article on Ziffers. ## Algorithmic operations From 5efad461de394ba585ea6b7645ca62c79f1a16fa Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 19:42:17 +0200 Subject: [PATCH 06/21] that was long --- src/Documentation.ts | 94 ++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index 5b27951..d60b6f9 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1180,19 +1180,31 @@ The basic Ziffer notation is entirely written in JavaScript strings (_e.g_ (3 2 1)+(2 5) Cartesian operation using + operator (All javascript operators supported). * **Random numbers:** (4,6) Random number between 4 and 6 ## Keys and scales -Ziffers supports all the keys and scales. Keys can be defined by using scientific pitch notation, for example 'F3'. Western style (1490 scales) can be with scale names named after greek modes and extended by William Zeitler (see full list): +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: -* Lydian -* Mixolydian -* Aeolian -* Locrian -* Ionian -* Dorian -* Phrygian -* Soryllic -* Modimic -* Ionalian -* ... +| Scale name | Intervals | +|------------|------------------------| +| Lydian | 2221221 | +| Mixolydian | 2212212 | +| Aeolian  | 2122122 | +| Locrian | 1221222 | +| Ionian | 2212221 | +| Dorian | 2122212 | +| Phrygian | 1222122 | +| Soryllic | 11122122| +| Modimic | 412122 | +| Ionalian   | 1312122 | -or by most traditional western names: + -* Major -* Minor -* Minor pentatonic -* Major pentatonic -* Harmonic minor -* Harmonic major -* Melodic minor -* Melodic major -* Whole -* Blues minor -* Blues major -* ... +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 | 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 - -See examples for how to use scales. +- **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 ## Methods @@ -1288,7 +1302,6 @@ z1('0 3 2 4',{key: 'D3', scale: 'minor pentatonic'}).sound('sine').out() ${makeExample( "Duration chars", ` -z1('s 0 e 1 q 2 h 3 w 4').sound('sine').scale("locrian").out() `, true )} @@ -1322,17 +1335,6 @@ z1('q 0 3 {10 14} e 8 4 {5 10 12 14 7 0}').sound('sine') true )} -${makeExample( - "Scale arrays", - ` -z1('q 0 6 3 2 e 3 6 q 3 6 s 8 4 2 1').sound('sine') -.fmi([1,2,4,8].pick()) -.scale([2,1,1,2,2,4,3]) -.out() -`, - true -)} - - Algorithmic operations ${makeExample( From 6e2d98eed1271a58b267faee92252ab581eec49a Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 20:23:35 +0200 Subject: [PATCH 07/21] add chaining page --- index.html | 4 +++- src/Documentation.ts | 9 ++++++++- src/main.ts | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 59c50a2..f20d31c 100644 --- a/index.html +++ b/index.html @@ -116,9 +116,11 @@

Sound

Samples

Synths

+

MIDI

+

Chaining

+

Patterns

Ziffers

-

MIDI

Functions

Shortcuts

Reference

diff --git a/src/Documentation.ts b/src/Documentation.ts index d60b6f9..c6a32fe 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1278,7 +1278,8 @@ Ziffers patterns can be chained to sound() and midi() Date: Mon, 28 Aug 2023 20:31:54 +0200 Subject: [PATCH 08/21] cool ziffers examples --- src/Documentation.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index c6a32fe..fee1c27 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1215,9 +1215,28 @@ ${makeExample( Ziffers provides shorthands for **many** numeric and algorithimic operations such as evaluating random numbers and creating sequences using list operations: -* **List operations:** (3 2 1)+(2 5) Cartesian operation using + operator (All javascript operators supported). +* **List operations:** Cartesian operation (_e.g._ (3 2 1)+(2 5)) using the + operator. All the arithmetic operators are supported. + +${makeExample( + "Cartesian operation for melodic generation", +` +z1("q 0 s (3 2 1)+(2 5) q 0 s (4 5 6)-(2 3)").sound('sine') + .scale('minor').fmi(2).fmh(2).room(0.5).size(0.5).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') + .scale('minor').fmi(2).fmh(2).room(0.5) + .size(0.5).sustain(0.1) .delay(0.5) + .delay(0.125).delayfb(0.25).out(); +mod(.5) :: snd(['kick', 'hat'].div(.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: @@ -1234,6 +1253,8 @@ Ziffers supports all the keys and scales. Keys can be defined by using [scientif | Soryllic | 11122122| | Modimic | 412122 | | Ionalian   | 1312122 | +| ... | And it goes on for **1490** scales | + From db660e93081e63540bb12d8d5268c37879480929 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 20:39:03 +0200 Subject: [PATCH 09/21] some more things --- src/Documentation.ts | 53 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index fee1c27..1f43352 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1255,6 +1255,17 @@ Ziffers supports all the keys and scales. Keys can be defined by using [scientif | 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(); +mod(.5) :: snd(['kick', 'hat'].div(.5)).out() +`, true)} + + @@ -1274,23 +1285,37 @@ You can also use more traditional 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 -## Methods -Ziffers numbered methods **(z0-z16)** can be used to parse and play patterns. Each method is individually cached and can be used to play patterns simultaniously. +${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(); +mod(1, 1.75) :: snd(['kick', 'hat'].div(1)).out() +`, true)} -## Chaining and options +## 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. They can be synchronized together by using a **cue** system. By default, each Ziffers expression will have a different duration. This system is thus necessary to make everything fit together in a loop-based environment like Topos. -Ziffers patterns can be chained to sound() and midi() to produce different outputs. Chaining is often alternative for passing in options, which can be more efficient. Methods available for chaining are: -* key() - for changing key -* scale() - for chaning scale -* octave() - for changing octave -* sound() - for outputting pattern as sounds (See Sound) -* midi() - for outputting pattern as midi (See Midi) ## Examples @@ -1756,6 +1781,16 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st const chaining: string = ` # Chaining + +## Ziffers +Ziffers patterns can be chained to sound() and midi() to produce different outputs. Chaining is often alternative for passing in options, which can be more efficient. Methods available for chaining are: +* key() - for changing key +* scale() - for chaning scale +* octave() - for changing octave +* sound() - for outputting pattern as sounds (See Sound) +* midi() - for outputting pattern as midi (See Midi) + + `; return { From 4fdd69c2816b26ce3cc78ee62045a0ebea1e5b2d Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 20:45:21 +0200 Subject: [PATCH 10/21] do not erase the welcome universe entirely, only global file --- src/main.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 9ffed1c..f71fe60 100644 --- a/src/main.ts +++ b/src/main.ts @@ -192,9 +192,11 @@ export class Editor { // Loading the universe from local storage // ================================================================================ - this.universes = { ...template_universes, ...this.settings.universes }; + this.universes = { ...this.settings.universes, ...template_universes }; this.selected_universe = "Welcome"; this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`; + + // Picking a random example to populate the landing page let random_example = examples[Math.floor(Math.random() * examples.length)]; this.universes[this.selected_universe].global.committed = random_example; this.universes[this.selected_universe].global.candidate = random_example; From 5725ff3d92dd4e46895ee17e385c945b1e707a3c Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 21:10:05 +0200 Subject: [PATCH 11/21] beginning midi refactor --- src/Documentation.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index 1f43352..0db17c9 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -572,9 +572,23 @@ if((bar() % 4) > 1) { const midi: string = ` # MIDI - + You can use Topos to play MIDI thanks to the [WebMIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API). You can currently send notes, control change, program change and so on. You can also send a MIDI Clock to your MIDI devices or favorite DAW. Note that Topos is also capable of playing MIDI using **Ziffers** which provides a better syntax for melodic expression. - + +**Important note:** for the examples on this page to work properly, you will need to configure your web browser to output **MIDI** on the right port. You will also need to make sure to have a synthesizer ready to receive MIDI data (hardware or software). You can use softwares like [VCVRack](https://vcvrack.com/), [Dexed](https://asb2m10.github.io/dexed/), [Surge](https://surge-synthesizer.github.io/) or [SunVox](https://www.warmplace.ru/soft/sunvox/) to get enough instruments for a lifetime. + +## MIDI Configuration + +Your web browser is capable of sending and receiving MIDI information through the [Web MIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API). The support for MIDI on browsers is a bit shaky. Please, take some time to configure and test. To our best knowledge, **Chrome** is currently leading on this feature, followed closely by **Firefox**. The other major web browsers are also starting to support this API. **There are two important functions for configuration:** + +- midi_outputs(): prints the list of available MIDI devices to the web console. You will have to open the web console using ${key_shortcut("Ctrl+Shift+I")} or sometimes ${key_shortcut("F12")}. You can also open it from the menu of your web browser. + +${makeExample( + "Listing MIDI outputs", +` +log(midi_outputs()) +`, true)} + ## Notes - midi(note: number|object): send a MIDI Note. Object can take parameters {note: number, channel: number, port: number|string, velocity: number}. From a772f9665941e1aa97cff2a7690957c19e01c433 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 21:18:15 +0200 Subject: [PATCH 12/21] first version of the print on screen thingie --- src/API.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/API.ts b/src/API.ts index e63cf0a..b0383a1 100644 --- a/src/API.ts +++ b/src/API.ts @@ -49,6 +49,8 @@ export class UserAPI { public localSeeds = new Map(); public patternCache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 5 }); private errorTimeoutID: number = 0; + private printTimeoutID: number = 0; + MidiConnection: MidiConnection = new MidiConnection(); load: samples; @@ -94,7 +96,9 @@ export class UserAPI { _reportError = (error: any): void => { console.log(error); clearTimeout(this.errorTimeoutID); + clearTimeout(this.printTimeoutID); this.app.error_line.innerHTML = error as string; + this.app.error_line.style.color = "color-red-800"; this.app.error_line.classList.remove("hidden"); this.errorTimeoutID = setTimeout( () => this.app.error_line.classList.add("hidden"), @@ -102,6 +106,19 @@ export class UserAPI { ); }; + _logMessage = (message: any): void => { + console.log(message); + clearTimeout(this.errorTimeoutID); + clearTimeout(this.printTimeoutID); + this.app.error_line.innerHTML = message as string; + this.app.error_line.style.color = "color-white"; + this.app.error_line.classList.remove("hidden"); + this.printTimeoutID = setTimeout( + () => this.app.error_line.classList.add("hidden"), + 4000 + ); + }; + // ============================================================= // Time functions // ============================================================= @@ -223,7 +240,7 @@ export class UserAPI { * * @returns A list of available MIDI outputs */ - console.log(this.MidiConnection.listMidiOutputs()); + this._logMessage(this.MidiConnection.listMidiOutputs()); return this.MidiConnection.midiOutputs; }; @@ -1260,7 +1277,10 @@ export class UserAPI { snd = this.sound; samples = samples; - log = console.log; + log = (message: any) => { + console.log(message); + this._logMessage(message); + } scale = scale; From bb4b8d33db70a02171a69b34e961b32d8fba3e95 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 21:36:03 +0200 Subject: [PATCH 13/21] minor tweaks --- src/API.ts | 4 ++-- src/Documentation.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/API.ts b/src/API.ts index b0383a1..c137aaa 100644 --- a/src/API.ts +++ b/src/API.ts @@ -108,10 +108,10 @@ export class UserAPI { _logMessage = (message: any): void => { console.log(message); - clearTimeout(this.errorTimeoutID); clearTimeout(this.printTimeoutID); + clearTimeout(this.errorTimeoutID); this.app.error_line.innerHTML = message as string; - this.app.error_line.style.color = "color-white"; + this.app.error_line.style.color = "white"; this.app.error_line.classList.remove("hidden"); this.printTimeoutID = setTimeout( () => this.app.error_line.classList.add("hidden"), diff --git a/src/Documentation.ts b/src/Documentation.ts index 0db17c9..90dda94 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -959,7 +959,7 @@ Audio samples are dynamically loaded from the web. By default, Topos is providin ## Available audio samples -Samples can take a few seconds to load. Please wait if you are not hearing anything. +Samples can take a few seconds to load. Please wait if you are not hearing anything. Lower your volume, take it slow. Some sounds might be harsh.
${injectAvailableSamples(application)}
From bd5272c6000d6e2a5fb61677b8caf4058b3160d7 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Mon, 28 Aug 2023 22:38:57 +0300 Subject: [PATCH 14/21] Document chaining --- src/Documentation.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Documentation.ts b/src/Documentation.ts index c6a32fe..6ab4668 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1735,6 +1735,38 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st const chaining: string = ` # Chaining +Method chaining can be used to manipulate object returned by both sound() and midi() functions. Probability functions are chained to apply different functions randomly. Probability functions are named as global probability functions (see Probabilities), but take a function as an input. + +## Chaining sound events + +All functions from the sound object can be used to modify the event, for example: + +${makeExample( + "Modifying sound events with probabilities",  +`mod(.5) && sound('hh') + .odds(1/4, s => s.speed(irand(1,4))) + .rarely(s => s.crush(3)) + .out()`, true)}; + +${makeExample( + "Modifying sound events with probabilities",  + `mod(.5) && sound('tink').note(60) + .often(s => s.note(62)) + .sometimes(s => s.note(64).n(irand(1,4))) + .out()`, true)}; + +## Chaining midi events + +All functions from the midi object can be used to modify the event with the probabilities. Values can also be incremented using += notation. + + ${makeExample( + "Modifying midi events with probabilities",  + `mod(.5) && midi(60).channel(1) + .odds(1/4, n => n.channel(2)) + .often(n => n.note+=4) + .sometimes(s => s.velocity(irand(50,100)) + .out()`, true)}; + `; return { From 38f7613a53628854bd362e8e77026979e7b2f862 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 21:43:14 +0200 Subject: [PATCH 15/21] adding more about MIDI configuration --- src/API.ts | 3 +-- src/Documentation.ts | 15 +++++++++++++-- src/IO/MidiConnection.ts | 9 +++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/API.ts b/src/API.ts index c137aaa..1ab2cc7 100644 --- a/src/API.ts +++ b/src/API.ts @@ -234,14 +234,13 @@ export class UserAPI { // MIDI related functions // ============================================================= - public midi_outputs = (): Array => { + public midi_outputs = (): void => { /** * Prints a list of available MIDI outputs in the console. * * @returns A list of available MIDI outputs */ this._logMessage(this.MidiConnection.listMidiOutputs()); - return this.MidiConnection.midiOutputs; }; public midi_output = (outputName: string): void => { diff --git a/src/Documentation.ts b/src/Documentation.ts index 90dda94..023755a 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -581,14 +581,25 @@ You can use Topos to play MIDI thanks to the [WebMIDI API](https://developer.moz Your web browser is capable of sending and receiving MIDI information through the [Web MIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API). The support for MIDI on browsers is a bit shaky. Please, take some time to configure and test. To our best knowledge, **Chrome** is currently leading on this feature, followed closely by **Firefox**. The other major web browsers are also starting to support this API. **There are two important functions for configuration:** -- midi_outputs(): prints the list of available MIDI devices to the web console. You will have to open the web console using ${key_shortcut("Ctrl+Shift+I")} or sometimes ${key_shortcut("F12")}. You can also open it from the menu of your web browser. +- midi_outputs(): prints the list of available MIDI devices on the screen. You will have to open the web console using ${key_shortcut("Ctrl+Shift+I")} or sometimes ${key_shortcut("F12")}. You can also open it from the menu of your web browser. **Note:** close the docs to see it printed. + ${makeExample( "Listing MIDI outputs", ` -log(midi_outputs()) +midi_outputs() `, true)} +- midi_output(output_name: string): enter your desired output to connect to it. + +${makeExample( + "Listing MIDI outputs", +` +midi_output("MIDI Rocket-Trumpet") +`, true)} + +That's it! You are now ready to play with MIDI. + ## Notes - midi(note: number|object): send a MIDI Note. Object can take parameters {note: number, channel: number, port: number|string, velocity: number}. diff --git a/src/IO/MidiConnection.ts b/src/IO/MidiConnection.ts index 33669fa..57e3eb8 100644 --- a/src/IO/MidiConnection.ts +++ b/src/IO/MidiConnection.ts @@ -119,14 +119,15 @@ export class MidiConnection{ } } - public listMidiOutputs(): void { + public listMidiOutputs(): string { /** * Lists all available MIDI outputs to the console. */ - console.log('Available MIDI Outputs:'); + let final_string = 'Available MIDI Outputs: '; this.midiOutputs.forEach((output, index) => { - console.log(`${index + 1}. ${output.name}`); + final_string += `(${index + 1}) ${output.name} `; }); + return final_string; } public sendMidiNote(noteNumber: number, channel: number, velocity: number, duration: number, port: number|string = this.currentOutputIndex, bend: number|undefined = undefined): void { @@ -260,4 +261,4 @@ export class MidiConnection{ console.error('MIDI output not available.'); } } - } \ No newline at end of file + } From 2356060075625bbed98a26fb36656069db326957 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 21:49:10 +0200 Subject: [PATCH 16/21] before pull --- src/Documentation.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Documentation.ts b/src/Documentation.ts index 023755a..04df295 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -601,6 +601,9 @@ midi_output("MIDI Rocket-Trumpet") That's it! You are now ready to play with MIDI. ## Notes + +The most basic MIDI event is the note. MIDI notes traditionally take three parameters: _note_ (from 0 to 127), _velocity_ (from 0 to 127) and _channel_ (from 0 to 15). + - midi(note: number|object): send a MIDI Note. Object can take parameters {note: number, channel: number, port: number|string, velocity: number}. ${makeExample( From b7a6149cc1e19ab1c4ece0c561151bd32587ecd9 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Mon, 28 Aug 2023 23:02:59 +0300 Subject: [PATCH 17/21] New ziffers version. Adds accidentals --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0448b48..d4a378e 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "tone": "^14.8.49", "unique-names-generator": "^4.7.1", "vite-plugin-markdown": "^2.1.0", - "zifferjs": "^0.0.15", + "zifferjs": "^0.0.16", "zzfx": "^1.2.0" } } diff --git a/yarn.lock b/yarn.lock index 9622421..f2ec551 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1446,10 +1446,10 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -zifferjs@^0.0.15: - version "0.0.15" - resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.15.tgz#ee2d0dc8b3e6f1f4609e680d18274993304bc728" - integrity sha512-9gYrT2Chb6PbH7KBGGari9gRQlu+qHwD4JXierqSLSuE61NckQ6h5eo3AgRrmYDGX3pBQWdAzv7g1C/21H9BgQ== +zifferjs@^0.0.16: + version "0.0.16" + resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.16.tgz#befa11eb923a04a3ee97eb7844bd70b5bb2752bf" + integrity sha512-pxcQKqdW9sMRj8d2GGUGsPnkSg4bgi9+5pp3dicURqwUwsgCUfY2vXLWRF9LKM8K3mjzO37V0nHZIqQ3LyYLKg== zzfx@^1.2.0: version "1.2.0" From 389c415aaba0ed01633b61f7448cc1f45a1c6fa0 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 22:23:51 +0200 Subject: [PATCH 18/21] finish midi page --- src/Documentation.ts | 70 ++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index 22e38b9..696649d 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -602,10 +602,34 @@ That's it! You are now ready to play with MIDI. ## Notes -The most basic MIDI event is the note. MIDI notes traditionally take three parameters: _note_ (from 0 to 127), _velocity_ (from 0 to 127) and _channel_ (from 0 to 15). +The most basic MIDI event is the note. MIDI notes traditionally take three parameters: _note_ (from 0 to 127), _velocity_ (from 0 to 127) and _channel_ (from 0 to 15). MIDI notes are quite important and can be used for a lot of different things. You can use them to trigger a synthesizer, a drum machine, a robot, or anything really! -- midi(note: number|object): send a MIDI Note. Object can take parameters {note: number, channel: number, port: number|string, velocity: number}. +- midi(note: number|object): send a MIDI Note. This function is quite bizarre. It can be written and used in many different ways. You can pass form one up to three arguments in different forms. +${makeExample( + "MIDI note using one parameter: note", +` +// Configure your MIDI first! +// => midi_output("MIDI Bus 1") +rhythm(.5, 5, 8) :: midi(50).out() +`, true)} + +${makeExample( + "MIDI note using three parameters: note, velocity, channel", +` +// MIDI Note 50, Velocity 50 + LFO, Channel 0 +rhythm(.5, 5, 8) :: midi(50, 50 + usine(.5) * 20, 0).out() +`, false)} + +${makeExample( + "MIDI note by passing an object", +` +// MIDI Note 50, Velocity 50 + LFO, Channel 0 +rhythm(.5, 5, 8) :: midi({note: 50, velocity: 50 + usine(.5) * 20, channel: 0}).out() +`, false)} + +We can now have some fun and starting playing a small piano piece: + ${makeExample( "Playing some piano", ` @@ -617,24 +641,7 @@ sometimes() && mod(.25) && midi(seqbeat(64, 67, 69) + 24).sustain(0.05).out() `, true )} - -## Note chaining - -The midi(number|object) function can be chained to _specify_ a midi note more. For instance, you can add a duration, a velocity, a channel, etc... by chaining: - -${makeExample( - "MIDI Caterpillar", - ` -mod(0.25) && midi(60) - .sometimes(n=>n.note(irand(40,60))) - .sustain(0.05) - .channel(2) - .port("bespoke") - .out() -`, - true -)} - + ## Control and Program Changes - control_change({control: number, value: number, channel: number}): send a MIDI Control Change. This function takes a single object argument to specify the control message (_e.g._ control_change({control: 1, value: 127, channel: 1})). @@ -642,8 +649,8 @@ mod(0.25) && midi(60) ${makeExample( "Imagine that I am tweaking an hardware synthesizer!", ` -control_change({control: [24,25].pick(), value: rI(1,120), channel: 1}))}) -control_change({control: [30,35].pick(), value: rI(1,120) / 2, channel: 1}))}) +control_change({control: [24,25].pick(), value: irand(1,120), channel: 1}) +control_change({control: [30,35].pick(), value: irand(1,120) / 2, channel: 1}) `, true )} @@ -663,6 +670,15 @@ program_change([1,2,3,4,5,6,7,8].pick(), 1) - sysex(...number[]): send a MIDI System Exclusive message. This function takes any number of arguments to specify the message (_e.g._ sysex(0x90, 0x40, 0x7f)). + +${makeExample( + "Nobody can say that we don't support Sysex messages!", +` +sysex(0x90, 0x40, 0x7f) +`, + true +)} + ## Clock - midi_clock(): send a MIDI Clock message. This function is used to synchronize Topos with other MIDI devices or DAWs. @@ -674,14 +690,6 @@ mod(.25) && midi_clock() // Sending clock to MIDI device from the global buffer `, true )} - - -## MIDI Output Selection - -- midi_outputs(): Prints a list of available MIDI outputs. You can then use any output name to select the MIDI output you wish to use. **Note:** this function will print to the console. You can open the console by pressing ${key_shortcut( - "Ctrl + Shift + I" - )} in many web browsers. -- midi_output(output_name: string): Selects the MIDI output to use. You can use the midi_outputs() function to get a list of available MIDI outputs first. If the MIDI output is not available, the function will do nothing and keep on with the currently selected MIDI Port. `; const sound: string = ` @@ -1838,7 +1846,7 @@ All functions from the midi object can be used to modify the event with the prob `mod(.5) && midi(60).channel(1) .odds(1/4, n => n.channel(2)) .often(n => n.note+=4) - .sometimes(s => s.velocity(irand(50,100)) + .sometimes(s => s.velocity(irand(50,100))) .out()`, true)}; ## Ziffers From 9082e03a6abba6597e5e59417be4a95fa72c9850 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 22:42:20 +0200 Subject: [PATCH 19/21] pushing new examples --- src/Documentation.ts | 64 +++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index 696649d..153dd45 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1817,47 +1817,63 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st const chaining: string = ` # Chaining -Method chaining can be used to manipulate object returned by both sound() and midi() functions. Probability functions are chained to apply different functions randomly. Probability functions are named as global probability functions (see Probabilities), but take a function as an input. +Method chaining can be used to manipulate objects returned by both sound() and midi() functions. Think of it as another way to create interesting musical patterns! Method chaining, unlike patterns, is acting on the sound chain level and is not really dependant on time. You can combine chaining and good old patterns if you want! + +Probability functions can be chained to apply different modifiers randomly. Probability functions are named as global probability functions (see **Probabilities** in the **Function** page) but take a function as an input. ## Chaining sound events All functions from the sound object can be used to modify the event, for example: - ${makeExample( "Modifying sound events with probabilities",  -`mod(.5) && sound('hh') +` +mod(.5) && sound('numbers') .odds(1/4, s => s.speed(irand(1,4))) .rarely(s => s.crush(3)) - .out()`, true)}; - + .out() +`, true)} ${makeExample( - "Modifying sound events with probabilities",  - `mod(.5) && sound('tink').note(60) - .often(s => s.note(62)) - .sometimes(s => s.note(64).n(irand(1,4))) - .out()`, true)}; + "Chance to change to a different note",  + ` +rhythm(.5, 3, 8) && sound('pluck').note(38).out() +mod(.5) && sound('pluck').note(60) + .often(s => s.note(57)) + .sometimes(s => s.note(64).n(irand(1,4))) + .note(62) + .out()`, false)} ## Chaining midi events -All functions from the midi object can be used to modify the event with the probabilities. Values can also be incremented using += notation. +All the functions from the MIDI object can be used to modify the event with probabilities. Values can also be incremented using += notation. - ${makeExample( - "Modifying midi events with probabilities",  - `mod(.5) && midi(60).channel(1) - .odds(1/4, n => n.channel(2)) - .often(n => n.note+=4) - .sometimes(s => s.velocity(irand(50,100))) - .out()`, true)}; +${makeExample( +"Modifying midi events with probabilities",  +`mod(.5) && midi(60).channel(1) + .odds(1/4, n => n.channel(2)) + .often(n => n.note+=4) + .sometimes(s => s.velocity(irand(50,100))) + .out()`, true)}; ## Ziffers -Ziffers patterns can be chained to sound() and midi() to produce different outputs. Chaining is often alternative for passing in options, which can be more efficient. Methods available for chaining are: -* key() - for changing key -* scale() - for chaning scale -* octave() - for changing octave -* sound() - for outputting pattern as sounds (See Sound) -* midi() - for outputting pattern as midi (See Midi) +Ziffers patterns can be chained to sound() and midi() as well. Chaining is often used as an alternative to passing values in objects as an option, which can be super cumbersome. The available chaining methods are: +* key(key: string): for changing key (_e.g._ "C" or "F#") +* scale(scale: string): for changing the current scale (_e.g._ "rocritonic" or "pentatonic") +* octave(n: number): for changing octave (_e.g._ 0 or 2) +* sound(): for outputting pattern as a Sound (See **Sounds**) +* midi() - for outputting pattern as MIDI (See **MIDI**) + +${makeExample( +"Ziffer player using a sound chain and probabilities!",  +` +z1('s 0 5 7 0 3 7 0 2 7 0 1 7 0 1 6 5 4 3 2') + .octave([0, 1].div(2) - 1) + .scale('pentatonic').sound('pluck') + .odds(1/4, n => n.delay(0.5).delayt(0.25)) + .odds(1/2, n => n.speed(0.5)) + .room(0.5).size(0.5).out() +`, true)}; `; return { From 4627c5c2b18570b1567fdeaa21412aa48c77c17c Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 22:55:24 +0200 Subject: [PATCH 20/21] accidentals example --- src/Documentation.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index 153dd45..6dfa87e 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1244,7 +1244,13 @@ mod(3) :: snd('cp').room(0.5).size(0.5).orbit(2).out() ${makeExample( "Accidentals and rests for nice melodies", -``, false)} +` +z1('e 0 s 1 b2 3 e 0 s 1 b2 4') + .scale('major').sound('sine') + .fmi(usine(.5)).fmh(2) + .delay(0.5).delayt(1.25) + .sustain(0.1).out() +`, false)} ## Algorithmic operations From 9e993e8579a22c53b99f262243df95b8c4c82766 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 28 Aug 2023 23:29:20 +0200 Subject: [PATCH 21/21] adding some examples --- src/Documentation.ts | 122 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 6 deletions(-) diff --git a/src/Documentation.ts b/src/Documentation.ts index 6dfa87e..fee8a6a 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -1217,14 +1217,14 @@ The basic Ziffer notation is entirely written in JavaScript strings (_e.g_ delete_variable(name: string)
: deletes a global variable from storage. - clear_variables(): clear **ALL** variables. **This is a destructive operation**! +**Note:** since this example is running in the documentation, we cannot take advantage of the multiple scripts paradigm. Try to send a variable from the global file to the local file n°6. + +${makeExample( + "Setting a global variable", +` +v('my_cool_variable', 2) +`, true)} + +${makeExample( + "Getting that variable back and printing!", +` +// Note that we just use one argument +log(v('my_cool_variable')) +`, false)} + + ## Counter and iterators You will often need to use iterators and/or counters to index over data structures (getting a note from a list of notes, etc...). There are functions ready to be used for this. Each script also comes with its own iterator that you can access using the i variable. **Note:** the script iteration count is **not** resetted between sessions. It will continue to increase the more you play, even if you just picked up an old project. @@ -1682,14 +1698,49 @@ You will often need to use iterators and/or counters to index over data structur - drunk_min(min: number): sets the minimum value. - drunk_wrap(wrap: boolean): whether to wrap the drunk walk to 0 once the upper limit is reached or not. +**Note:** Counters also come with a secret syntax. They can be called with the **$** symbol! + +${makeExample( + "Iterating over a list of samples using a counter", +` +rhythm(.25, 6, 8) :: sound('dr').n($(1)).end(.25).out() +`, true)} + +${makeExample( + "Using a more complex counter", +` +// Limit is 20, step is 5 +rhythm(.25, 6, 8) :: sound('dr').n($(1, 20, 5)).end(.25).out() +`, false)} + +${makeExample( + "Calling the drunk mechanism", +` +// Limit is 20, step is 5 +rhythm(.25, 6, 8) :: sound('dr').n(drunk()).end(.25).out() +`, false)} + ## Scripts You can control scripts programatically. This is the core concept of Topos after all! - script(...number: number[]): call one or more scripts (_e.g. script(1,2,3,4)). Once called, scripts will be evaluated once. There are nine local scripts by default. You cannot call the global script nor the initialisation script. - - clear_script(number): deletes the given script. - copy_script(from: number, to: number): copies a local script denoted by its number to another local script. **This is a destructive operation!** + +${makeExample( + "Calling a script! The most important feature!", +` +mod(1) :: script(1) +`, true)} + +${makeExample( + "Calling mutliple scripts at the same time.", +` +mod(1) :: script(1, 3, 5) +`, false)} + + ## Mouse @@ -1698,11 +1749,33 @@ You can get the current position of the mouse on the screen by using the followi - mouseX(): the horizontal position of the mouse on the screen (as a floating point number). - mouseY(): the vertical position of the mouse on the screen (as a floating point number). +${makeExample( + "FM Synthesizer controlled using the mouse", +` +mod(.25) :: sound('sine') + .fmi(mouseX() / 100) + .fmh(mouseY() / 100) + .vel(0.2) + .room(0.9).out() +`, true)} + Current mouse position can also be used to generate notes: - noteX(): returns a MIDI note number (0-127) based on the horizontal position of the mouse on the screen. - noteY(): returns a MIDI note number (0-127) based on the vertical position of the mouse on the screen. + +${makeExample( + "The same synthesizer, with note control!", +` +mod(.25) :: sound('sine') + .fmi(mouseX() / 100) + .note(noteX()) + .fmh(mouseY() / 100) + .vel(0.2) + .room(0.9).out() +`, true)} + ## Low Frequency Oscillators Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio workstation or synthesizer. Topos implements some basic waveforms you can play with to automatically modulate your paremeters. @@ -1749,11 +1822,32 @@ There are some simple functions to play with probabilities. - rand(min: number, max:number): returns a random number between min and max. Shorthand _r()_. - irand(min: number, max:number): returns a random integer between min and max. Shorthands _ir()_ or _rI()_. + +${makeExample( + "Bleep bloop, what were you expecting?", +` +rhythm(0.125, 10, 16) :: sound('sid').n(4).note(50 + irand(50, 62) % 8).out() +`, true)} + + - prob(p: number): return true _p_% of time, false in other cases. - toss(): throwing a coin. Head (true) or tails (false). + + +${makeExample( + "The Teletype experience!", +` +prob(50) :: script(1); +prob(60) :: script(2); +prob(80) :: script(toss() ? script(3) : script(4)) +`, true)} + - seed(val: number|string): sets the seed of the random number generator. You can use a number or a string. The same seed will always return the same sequence of random numbers. - -Chance operators returning a boolean value are also available: + + +## Chance operators + +Chance operators returning a boolean value are also available. They are super important because they also exist for another mechanism called **chaining**. Checkout the **Chaining** page to learn how to use them in different contexts! - odds(n: number, sec?: number): returns true for every n (odds) (eg. 1/4 = 0.25) in given seconds (sec) - almostNever(sec?: number): returns true 0.1% in given seconds (sec) @@ -1774,7 +1868,23 @@ Chance operators returning a boolean value are also available: ## Delay functions - delay(ms: number, func: Function): void: Delays the execution of a function by a given number of milliseconds. + +${makeExample( + "Phased woodblocks", +` +// Some very low-budget version of phase music +mod(.5) :: delay(usine(.125) * 80, () => sound('east').out()) +mod(.5) :: delay(50, () => sound('east').out()) +`, true)} + - delayr(ms: number, nb: number, func: Function): void: Delays the execution of a function by a given number of milliseconds, repeated a given number of times. + +${makeExample( + "Another woodblock texture", +` +mod(1) :: delayr(50, 4, () => sound('east').speed([0.5,.25].beat()).out()) +div(2) :: mod(2) :: delayr(150, 4, () => sound('east').speed([0.5,.25].beat() * 4).out()) +`, true)}; `; const reference: string = `