Merge branch 'main' of https://github.com/Bubobubobubobubo/Topos
This commit is contained in:
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
98
src/API.ts
98
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();
|
||||
@ -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 => {
|
||||
/**
|
||||
@ -704,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
|
||||
*/
|
||||
@ -965,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
|
||||
|
||||
@ -143,7 +143,12 @@ Some functions can be leveraged to play rhythms without thinking too much about
|
||||
\`\`\`
|
||||
|
||||
- <icode>onbar(...values: number[])</icode>: returns <icode>true</icode> if the bar is currently equal to any of the specified values.
|
||||
- <icode>modbar(...values: number[])</icode>: returns <icode>true</icode> if the bar is currently a multiple of any of the specified values.
|
||||
- <icode>modbar(...values: number[]) or bmod(...)</icode>: returns <icode>true</icode> if the bar is currently a multiple of any of the specified values.
|
||||
- <icode>modpulse(...values: number[]) or pmod(...)</icode>: returns <icode>true</icode> if the pulse is currently a multiple of any of the specified values.
|
||||
|
||||
- <icode>div(chunk: number)</icode>: returns <icode>true</icode> for every pulse in intervals of given number of beats
|
||||
- <icode>divbar(chunk: number)</icode>: returns <icode>true</icode> for every pulse in intervals of given number of bars
|
||||
- <icode>divseq(...values: number[])</icode>: returns <icode>true</icode> for every pulse in intervals of given number of beats returning different value each time.
|
||||
|
||||
## Rhythm generators
|
||||
|
||||
@ -200,7 +205,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 <icode>note(number)</icode> function can be chained to _specify_ a midi note more. For instance, you can add a duration, a velocity, a channel, etc...:
|
||||
|
||||
@ -431,6 +436,11 @@ You can get the current position of the mouse on the screen by using the followi
|
||||
- <icode>mouseX()</icode>: the horizontal position of the mouse on the screen (as a floating point number).
|
||||
- <icode>mouseY()</icode>: the vertical position of the mouse on the screen (as a floating point number).
|
||||
|
||||
Current mouse position can also be used to generate notes:
|
||||
|
||||
- <icode>noteX()</icode>: returns a MIDI note number (0-127) based on the horizontal position of the mouse on the screen.
|
||||
- <icode>noteY()</icode>: 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 +483,22 @@ Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio w
|
||||
|
||||
There are some simple functions to play with probabilities.
|
||||
|
||||
- <icode>rand(min: number, max:number)</icode>: returns a random number between <icode>min</icode> and <icode>max</icode>. Shorthand _r()_.
|
||||
- <icode>irand(min: number, max:number)</icode>: returns a random integer between <icode>min</icode> and <icode>max</icode>. Shorthands _ir()_ or _rI()_.
|
||||
- <icode>prob(p: number)</icode>: return <icode>true</icode> _p_% of time, <icode>false</icode> in other cases.
|
||||
- <icode>toss()</icode>: throwing a coin. Head (<icode>true</icode>) or tails (<icode>false</icode>).
|
||||
- <icode>seed(val: number|string)</icode>: 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:
|
||||
|
||||
- <icode>odds(n: number, sec?: number)</icode>: returns true for every n (odds) (eg. 1/4 = 0.25) in given seconds (sec)
|
||||
- <icode>almostNever(sec?: number)</icode>: returns true 0.1% in given seconds (sec)
|
||||
- <icode>rarely(sec?: number)</icode>: returns true 1% in given seconds (sec)
|
||||
- <icode>scaresly(sec?: number)</icode>: returns true 10% in given seconds (sec)
|
||||
- <icode>sometimes(sec?: number)</icode>: returns true 50% in given seconds (sec)
|
||||
- <icode>often(sec?: number)</icode>: returns true 75% in given seconds (sec)
|
||||
- <icode>frequently(sec?: number)</icode>: returns true 90% in given seconds (sec)
|
||||
- <icode>almostAlways(sec?: number)</icode>: returns true 99% in given seconds (sec)
|
||||
|
||||
## Math functions
|
||||
|
||||
|
||||
26
src/Event.ts
26
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 => {
|
||||
|
||||
@ -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;
|
||||
|
||||
16
src/Note.ts
16
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;
|
||||
}
|
||||
|
||||
@ -73,7 +81,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;
|
||||
@ -82,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);
|
||||
if (bend) this.midiConnection.sendPitchBend(8192, channel);
|
||||
this.midiConnection.sendMidiNote(note, channel, velocity, duration, port, bend);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user