From ef7bfe2668d9a47a235f3bae1010fda640c22b71 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Mon, 21 Aug 2023 20:55:38 +0300 Subject: [PATCH 1/6] Added some methods to docs --- src/API.ts | 3 ++- src/Documentation.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/API.ts b/src/API.ts index 7690c02..ae3cdf8 100644 --- a/src/API.ts +++ b/src/API.ts @@ -578,9 +578,10 @@ export class UserAPI { */ return this.randomGen() * (max - min) + min; }; - irand = this.randI + irand = this.randI; rI = this.randI; r = this.rand; + ir = this.randI; seed = (seed: string | number): void => { /** diff --git a/src/Documentation.ts b/src/Documentation.ts index e0b10f9..60c1072 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -200,7 +200,7 @@ You can use Topos to play MIDI thanks to the [WebMIDI API](https://developer.moz sometimes() && mod(.25) && note(seqbeat(64, 67, 69) + 24).duration(0.05).out() \`\`\` -### Note chaining +## Note chaining The note(number) function can be chained to _specify_ a midi note more. For instance, you can add a duration, a velocity, a channel, etc...: @@ -431,6 +431,11 @@ 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). +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. + ## 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. @@ -473,8 +478,11 @@ Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio w 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()_. - prob(p: number): return true _p_% of time, false in other cases. - toss(): throwing a coin. Head (true) or tails (false). +- 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. ## Math functions From bfc32f6210adefcc9e6463648532d72a19163a5a Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Mon, 21 Aug 2023 23:28:34 +0300 Subject: [PATCH 2/6] Modified and documented boolean chance operators --- src/API.ts | 82 +++++++++++++++++++++++++++++++------------- src/Documentation.ts | 11 ++++++ src/Event.ts | 26 +++++++++++--- 3 files changed, 91 insertions(+), 28 deletions(-) diff --git a/src/API.ts b/src/API.ts index ae3cdf8..e4781b0 100644 --- a/src/API.ts +++ b/src/API.ts @@ -705,55 +705,91 @@ export class UserAPI { // Probability functions // ============================================================= - public almostNever = (): boolean => { + public odds = (n: number, sec: number = 15): boolean => { + /** + * Returns true n% of the time. + * + * @param n - The probability of returning true. 1/4 = 25% = 0.25, 80/127 = 62.9% = 0.6299212598425197, etc... + * @param sec - The time frame in seconds + * @returns True n% of the time + */ + return this.randomGen() < n*this.ppqn()/(this.ppqn()*sec); + }; + + public almostNever = (sec: number = 15): boolean => { + /** + * Returns true 2.5% of the time in given time frame. + * + * @param sec - The time frame in seconds + * @returns True 2.5% of the time + */ + return this.randomGen() < 0.025*this.ppqn()/(this.ppqn()*sec); + }; + + public rarely = (sec: number = 15): boolean => { /** * Returns true 10% of the time. - * - * @returns True 10% of the time + * + * @param sec - The time frame in seconds + * @returns True 10% of the time. */ - return this.randomGen() > 0.9; + return this.randomGen() < 0.1*this.ppqn()/(this.ppqn()*sec); }; - public sometimes = (): boolean => { - /** - * Returns true 50% of the time. - * - * @returns True 50% of the time - */ - return this.randomGen() > 0.5; - }; - - public rarely = (): boolean => { + public scarcely = (sec: number = 15): boolean => { /** * Returns true 25% of the time. - * + * + * @param sec - The time frame in seconds * @returns True 25% of the time */ - return this.randomGen() > 0.75; + return this.randomGen() < 0.25*this.ppqn()/(this.ppqn()*sec); }; - public often = (): boolean => { + public sometimes = (sec: number = 15): boolean => { + /** + * Returns true 50% of the time. + * + * @param sec - The time frame in seconds + * @returns True 50% of the time + */ + return this.randomGen() < 0.5*this.ppqn()/(this.ppqn()*sec); + }; + + public often = (sec: number = 15): boolean => { /** * Returns true 75% of the time. - * + * + * @param sec - The time frame in seconds * @returns True 75% of the time */ - return this.randomGen() > 0.25; + return this.randomGen() < 0.75*this.ppqn()/(this.ppqn()*sec); }; - public almostAlways = (): boolean => { + public frequently = (sec: number = 15): boolean => { /** * Returns true 90% of the time. - * + * + * @param sec - The time frame in seconds * @returns True 90% of the time */ - return this.randomGen() > 0.1; + return this.randomGen() < 0.9*this.ppqn()/(this.ppqn()*sec); + }; + + public almostAlways = (sec: number = 15): boolean => { + /** + * Returns true 98.5% of the time. + * + * @param sec - The time frame in seconds + * @returns True 98.5% of the time + */ + return this.randomGen() < 0.985*this.ppqn()/(this.ppqn()*sec); }; public dice = (sides: number): number => { /** * Returns the value of a dice roll with n sides. - * + * * @param sides - The number of sides on the dice * @returns The value of a dice roll with n sides */ diff --git a/src/Documentation.ts b/src/Documentation.ts index 60c1072..59162a4 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -484,6 +484,17 @@ There are some simple functions to play with probabilities. - toss(): throwing a coin. Head (true) or tails (false). - 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: + +- 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) +- rarely(sec?: number): returns true 1% in given seconds (sec) +- scaresly(sec?: number): returns true 10% in given seconds (sec) +- sometimes(sec?: number): returns true 50% in given seconds (sec) +- often(sec?: number): returns true 75% in given seconds (sec) +- frequently(sec?: number): returns true 90% in given seconds (sec) +- almostAlways(sec?: number): returns true 99% in given seconds (sec) + ## Math functions - max(...values: number[]): number: returns the maximum value of a list of numbers. diff --git a/src/Event.ts b/src/Event.ts index f1732b6..a5da575 100644 --- a/src/Event.ts +++ b/src/Event.ts @@ -12,23 +12,39 @@ export class Event { } } - sometimesBy = (probability: number, func: Function): Event => { + odds = (probability: number, func: Function): Event => { if(this.randomGen() < probability) { return this.modify(func); } return this; } - sometimes = (func: Function): Event => { - return this.sometimesBy(0.5, func); + almostNever = (func: Function): Event => { + return this.odds(0.025, func); } rarely = (func: Function): Event => { - return this.sometimesBy(0.1, func); + return this.odds(0.1, func); + } + + scarcely = (func: Function): Event => { + return this.odds(0.25, func); + } + + sometimes = (func: Function): Event => { + return this.odds(0.5, func); } often = (func: Function): Event => { - return this.sometimesBy(0.9, func); + return this.odds(0.75, func); + } + + frequently = (func: Function): Event => { + return this.odds(0.9, func); + } + + almostAlways = (func: Function): Event => { + return this.odds(0.985, func); } modify = (func: Function): Event => { From 803a97b7ec594ae2df80ce8ccac7422dbfb68960 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Tue, 22 Aug 2023 01:00:58 +0300 Subject: [PATCH 3/6] Renamed slice to div, added pmod and added documentation. --- src/API.ts | 13 ++++++++++--- src/Documentation.ts | 7 ++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/API.ts b/src/API.ts index e4781b0..cb06d71 100644 --- a/src/API.ts +++ b/src/API.ts @@ -483,7 +483,7 @@ export class UserAPI { // Sequencer related functions // ============================================================= - public slice = (chunk: number): boolean => { + public div = (chunk: number): boolean => { const time_pos = this.epulse(); const current_chunk = Math.floor( time_pos / Math.floor(chunk * this.ppqn()) @@ -491,13 +491,13 @@ export class UserAPI { return current_chunk % 2 === 0; }; - public barslice = (chunk: number): boolean => { + public divbar = (chunk: number): boolean => { const time_pos = this.bar() - 1; const current_chunk = Math.floor(time_pos / chunk); return current_chunk % 2 === 0; }; - public seqslice = (...args: any): any => { + public divseq = (...args: any): any => { const chunk_size = args[0]; // Get the first argument (chunk size) const elements = args.slice(1); // Get the rest of the arguments as an array const timepos = this.epulse(); @@ -1002,12 +1002,19 @@ export class UserAPI { return results.some((value) => value === true); }; + public modpulse = (...n: number[]): boolean => { + const results: boolean[] = n.map((value) => this.epulse() % value === 0); + return results.some((value) => value === true); + }; + pmod = this.modpulse; + public modbar = (...n: number[]): boolean => { const results: boolean[] = n.map( (value) => this.bar() % Math.floor(value * this.ppqn()) === 0 ); return results.some((value) => value === true); }; + bmod = this.modbar; // ============================================================= // Rythmic generators diff --git a/src/Documentation.ts b/src/Documentation.ts index 59162a4..452081b 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -143,7 +143,12 @@ Some functions can be leveraged to play rhythms without thinking too much about \`\`\` - onbar(...values: number[]): returns true if the bar is currently equal to any of the specified values. -- modbar(...values: number[]): returns true if the bar is currently a multiple of any of the specified values. +- modbar(...values: number[]) or bmod(...): returns true if the bar is currently a multiple of any of the specified values. +- modpulse(...values: number[]) or pmod(...): returns true if the pulse is currently a multiple of any of the specified values. + +- div(chunk: number): returns true for every pulse in intervals of given number of beats +- divbar(chunk: number): returns true for every pulse in intervals of given number of bars +- divseq(...values: number[]): returns true for every pulse in intervals of given number of beats returning different value each time. ## Rhythm generators From 1f11719de58bbdcdf350eda0f8ee82a6b4490355 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Tue, 22 Aug 2023 01:31:27 +0300 Subject: [PATCH 4/6] Fixed duration & pitch bend in midi --- src/IO/MidiConnection.ts | 10 +++++++--- src/Note.ts | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/IO/MidiConnection.ts b/src/IO/MidiConnection.ts index b238a66..33669fa 100644 --- a/src/IO/MidiConnection.ts +++ b/src/IO/MidiConnection.ts @@ -129,7 +129,7 @@ export class MidiConnection{ }); } - public sendMidiNote(noteNumber: number, channel: number, velocity: number, duration: number, port: number|string = this.currentOutputIndex): void { + public sendMidiNote(noteNumber: number, channel: number, velocity: number, duration: number, port: number|string = this.currentOutputIndex, bend: number|undefined = undefined): void { /** * Sending a MIDI Note on/off message with the same note number and channel. Automatically manages * the note off message after the specified duration. @@ -151,9 +151,12 @@ export class MidiConnection{ // Send Note On output.send(noteOnMessage); + if(bend) this.sendPitchBend(bend, channel, port); + // Schedule Note Off const timeoutId = setTimeout(() => { output.send(noteOffMessage); + if(bend) this.sendPitchBend(8192, channel, port); delete this.scheduledNotes[noteNumber]; }, (duration - 0.02) * 1000); @@ -181,7 +184,7 @@ export class MidiConnection{ } } - public sendPitchBend(value: number, channel: number): void { + public sendPitchBend(value: number, channel: number, port: number|string = this.currentOutputIndex): void { /** * Sends a MIDI Pitch Bend message to the currently selected MIDI output. * @@ -195,7 +198,8 @@ export class MidiConnection{ if (channel < 0 || channel > 15) { console.error('Invalid MIDI channel. Channel must be in the range 0-15.'); } - const output = this.midiOutputs[this.currentOutputIndex]; + if(typeof port === 'string') port = this.getMidiOutputIndex(port); + const output = this.midiOutputs[port]; if (output) { const lsb = value & 0x7F; const msb = (value >> 7) & 0x7F; diff --git a/src/Note.ts b/src/Note.ts index b087a66..fb11453 100644 --- a/src/Note.ts +++ b/src/Note.ts @@ -73,7 +73,7 @@ export class Note extends Event { const velocity = this.values.velocity ? this.values.velocity : 100; const duration = this.values.duration ? - this.values.duration * Math.floor(this.app.clock.pulse_duration * this.app.api.ppqn()) : + this.values.duration * this.app.clock.pulse_duration * this.app.api.ppqn() : this.app.clock.pulse_duration * this.app.api.ppqn(); const bend = this.values.bend ? this.values.bend : undefined; @@ -84,7 +84,7 @@ export class Note extends Event { if (bend) this.midiConnection.sendPitchBend(bend, channel); this.midiConnection.sendMidiNote(note, channel, velocity, duration, port); - if (bend) this.midiConnection.sendPitchBend(8192, channel); + //if (bend) this.midiConnection.sendPitchBend(8192, channel); } } \ No newline at end of file From 160dd66c3331b8373975789215b60498ad3720bf Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Tue, 22 Aug 2023 02:11:14 +0300 Subject: [PATCH 5/6] Added freq method to note --- package.json | 2 +- src/Note.ts | 12 ++++++++++-- yarn.lock | 8 ++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index ae199c4..6b14fe9 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "tailwindcss": "^3.3.3", "tone": "^14.8.49", "vite-plugin-markdown": "^2.1.0", - "zifferjs": "^0.0.9", + "zifferjs": "^0.0.10", "zzfx": "^1.2.0" } } diff --git a/src/Note.ts b/src/Note.ts index fb11453..85d54f9 100644 --- a/src/Note.ts +++ b/src/Note.ts @@ -1,6 +1,8 @@ import { Event } from './Event'; import { type Editor } from './main'; import { MidiConnection } from "./IO/MidiConnection"; +import { freqToMidi, resolvePitchBend } from 'zifferjs'; + export class Note extends Event { values: { [key: string]: any }; midiConnection: MidiConnection; @@ -49,9 +51,15 @@ export class Note extends Event { } } - // TODO: Add bend freq = (value: number): this => { this.values['freq'] = value; + const midiNote = freqToMidi(value); + if(midiNote % 1 !== 0) { + this.values['note'] = Math.floor(midiNote); + this.values['bend'] = resolvePitchBend(midiNote)[1]; + } else { + this.values['note'] = midiNote; + } return this; } @@ -83,7 +91,7 @@ export class Note extends Event { this.midiConnection.getCurrentMidiPortIndex(); if (bend) this.midiConnection.sendPitchBend(bend, channel); - this.midiConnection.sendMidiNote(note, channel, velocity, duration, port); + this.midiConnection.sendMidiNote(note, channel, velocity, duration, port, bend); //if (bend) this.midiConnection.sendPitchBend(8192, channel); } diff --git a/yarn.lock b/yarn.lock index f5c0699..32cc326 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1441,10 +1441,10 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -zifferjs@^0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.9.tgz#47037cee6dd161838dd236bdbc3eda9b099e2281" - integrity sha512-XS/JAc9nkmoiRaT/YFuX7r1ROvApQnY5BxOKyenAeDATvKZ80sIoXUw48U27KTsuJIsiPInNm5RieJGCJkoVmQ== +zifferjs@^0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.10.tgz#b8c2617f5fc8fb4422f311702785c47b752a920e" + integrity sha512-kaMWRZcsAXXpPFjDoVtS3sQ5bZs+S7t3ejd8+WZV/nc52y/vXe/QcKAjT+jYCHGq8J1WMCITDn6OnVfswqJ8Ig== dependencies: lru-cache "^10.0.0" From 566d6ce79b4b6ffda7d6eea704355fc311fb0b5e Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Tue, 22 Aug 2023 09:31:51 +0300 Subject: [PATCH 6/6] Remove useless code --- src/Note.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Note.ts b/src/Note.ts index 85d54f9..bfe380a 100644 --- a/src/Note.ts +++ b/src/Note.ts @@ -90,9 +90,7 @@ export class Note extends Event { this.midiConnection.getMidiOutputIndex(this.values.port) : this.midiConnection.getCurrentMidiPortIndex(); - if (bend) this.midiConnection.sendPitchBend(bend, channel); this.midiConnection.sendMidiNote(note, channel, velocity, duration, port, bend); - //if (bend) this.midiConnection.sendPitchBend(8192, channel); } } \ No newline at end of file