This commit is contained in:
2023-10-12 01:23:20 +03:00
11 changed files with 312 additions and 73 deletions

View File

@ -20,7 +20,7 @@ Topos is a web-based application that lives [here](https://topos.raphaelforment.
## Disclaimer ## Disclaimer
**Topos** is a very young project. It is not ready for end users, do not expect stable features and/or user support. Contributors and curious people are welcome! The software is not stabilized at all for the moment but it is already possible to have fun playing with it. **Topos** is a fairly young project developed by two part time hobbyists :) Do not expect stable features and/or user support in the initial development stage. Contributors and curious people are welcome! The software is working quite well and we are continuously striving to improve it.
## Installation (for devs and contributors) ## Installation (for devs and contributors)

View File

@ -33,7 +33,7 @@
"postcss": "^8.4.27", "postcss": "^8.4.27",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"showdown-highlight": "^3.1.0", "showdown-highlight": "^3.1.0",
"superdough": "^0.9.8", "superdough": "^0.9.10",
"tailwind-highlightjs": "^2.0.1", "tailwind-highlightjs": "^2.0.1",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tone": "^14.8.49", "tone": "^14.8.49",

View File

@ -1806,4 +1806,76 @@ export class UserAPI {
*/ */
return array[(this.app.clock.time_position.bar + 1) % array.length]; return array[(this.app.clock.time_position.bar + 1) % array.length];
}; };
// =============================================================
// High Order Functions
// =============================================================
public shuffle = <T>(array: T[]): T[] => {
/**
* Returns a shuffled version of an array.
* @param array - The array to shuffle
* @returns A shuffled version of the array
*/
return array.sort(() => this.randomGen() - 0.5);
};
public reverse = <T>(array: T[]): T[] => {
/**
* Returns a reversed version of an array.
* @param array - The array to reverse
* @returns A reversed version of the array
*/
return array.reverse();
};
public rotate = <T>(n: number): Function => {
/**
* Returns a partially applied function that rotates an array by n.
*
*/
return (array: T[]): T[] => {
return array.slice(n, array.length).concat(array.slice(0, n));
};
};
public repeat = <T>(n: number): Function => {
/**
* Returns a partially applied function that repeats each element of an array n times.
*
*/
return (array: T[]): T[] => {
return array.flatMap((x) => Array(n).fill(x));
};
};
public repeatOdd = <T>(n: number): Function => {
/**
* Returns a partially applied function that repeats each even element of an array n times.
*
*/
return (array: T[]): T[] => {
return array.flatMap((x, i) => (i % 2 === 0 ? Array(n).fill(x) : x));
};
};
public repeatEven = <T>(n: number): Function => {
/**
* Returns a partially applied function that repeats each even element of an array n times.
*
*/
return (array: T[]): T[] => {
return array.flatMap((x, i) => (i % 2 !== 0 ? Array(n).fill(x) : x));
};
};
public palindrome = <T>(array: T[]): T[] => {
/**
* Returns a palindrome of an array.
* @param array - The array to palindrome
* @returns A palindrome of the array
*/
return array.concat(array.slice(0, array.length - 1).reverse());
};
} }

View File

@ -1,6 +1,6 @@
import { type UserAPI } from "./API"; import { type UserAPI } from "./API";
import { safeScale, stepsToScale } from "zifferjs"; import { safeScale, stepsToScale } from "zifferjs";
export { }; export {};
declare global { declare global {
interface Array<T> { interface Array<T> {
@ -12,13 +12,12 @@ declare global {
random(index: number): T; random(index: number): T;
rand(index: number): T; rand(index: number): T;
degrade(amount: number): T; degrade(amount: number): T;
repeatAll(amount: number): T; repeat(amount: number): T;
repeatPair(amount: number): T; repeatEven(amount: number): T;
repeatOdd(amount: number): T; repeatOdd(amount: number): T;
beat(division: number): T; beat(division: number): T;
b(division: number): T; b(division: number): T;
bar(): T; bar(): T;
pulse(): T;
pick(): T; pick(): T;
loop(index: number): T; loop(index: number): T;
shuffle(): this; shuffle(): this;
@ -26,26 +25,35 @@ declare global {
scaleArp(scaleName: string): this; scaleArp(scaleName: string): this;
rotate(steps: number): this; rotate(steps: number): this;
unique(): this; unique(): this;
in(value: T): boolean;
square(): number[]; square(): number[];
sqrt(): number[]; sqrt(): number[];
gen(min: number, max: number, times: number): number[]; gen(min: number, max: number, times: number): number[];
sometimes(func: Function): number[];
apply(func: Function): number[];
} }
} }
export const makeArrayExtensions = (api: UserAPI) => { export const makeArrayExtensions = (api: UserAPI) => {
Array.prototype.in = function <T>(this: T[], value: T): boolean { Array.prototype.square = function (): number[] {
return this.includes(value);
};
Array.prototype.square = function(): number[] {
/** /**
* @returns New array with squared values. * @returns New array with squared values.
*/ */
return this.map((x: number) => x * x); return this.map((x: number) => x * x);
}; };
Array.prototype.sqrt = function(): number[] { Array.prototype.sometimes = function (func: Function): number[] {
if (api.randomGen() < 0.5) {
return func(this);
} else {
return this;
}
};
Array.prototype.apply = function (func: Function): number[] {
return func(this);
};
Array.prototype.sqrt = function (): number[] {
/** /**
* @returns New array with square roots of values. Throws if any element is negative. * @returns New array with square roots of values. Throws if any element is negative.
*/ */
@ -54,7 +62,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this.map((x: number) => Math.sqrt(x)); return this.map((x: number) => Math.sqrt(x));
}; };
Array.prototype.add = function(amount: number): number[] { Array.prototype.add = function (amount: number): number[] {
/** /**
* @param amount - The value to add to each element in the array. * @param amount - The value to add to each element in the array.
* @returns New array with added values. * @returns New array with added values.
@ -62,7 +70,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this.map((x: number) => x + amount); return this.map((x: number) => x + amount);
}; };
Array.prototype.sub = function(amount: number): number[] { Array.prototype.sub = function (amount: number): number[] {
/** /**
* @param amount - The value to subtract from each element in the array. * @param amount - The value to subtract from each element in the array.
* @returns New array with subtracted values. * @returns New array with subtracted values.
@ -70,7 +78,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this.map((x: number) => x - amount); return this.map((x: number) => x - amount);
}; };
Array.prototype.mult = function(amount: number): number[] { Array.prototype.mult = function (amount: number): number[] {
/** /**
* @param amount - The value to multiply with each element in the array. * @param amount - The value to multiply with each element in the array.
* @returns New array with multiplied values. * @returns New array with multiplied values.
@ -78,7 +86,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this.map((x: number) => x * amount); return this.map((x: number) => x * amount);
}; };
Array.prototype.div = function(amount: number): number[] { Array.prototype.div = function (amount: number): number[] {
/** /**
* @param amount - The value to divide each element in the array by. * @param amount - The value to divide each element in the array by.
* @returns New array with divided values. Throws if division by zero. * @returns New array with divided values. Throws if division by zero.
@ -87,7 +95,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this.map((x: number) => x / amount); return this.map((x: number) => x / amount);
}; };
Array.prototype.pick = function() { Array.prototype.pick = function () {
/** /**
* Returns a random element from an array. * Returns a random element from an array.
* *
@ -96,7 +104,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this[Math.floor(api.randomGen() * this.length)]; return this[Math.floor(api.randomGen() * this.length)];
}; };
Array.prototype.gen = function(min: number, max: number, times: number) { Array.prototype.gen = function (min: number, max: number, times: number) {
/** /**
* Returns an array of random numbers. * Returns an array of random numbers.
* @param min - The minimum value of the random numbers * @param min - The minimum value of the random numbers
@ -113,25 +121,22 @@ export const makeArrayExtensions = (api: UserAPI) => {
); );
}; };
Array.prototype.bar = function() { Array.prototype.bar = function (value: number = 1) {
/** /**
* Returns an element from an array based on the current bar. * Returns an element from an array based on the current bar.
* *
* @param array - The array of values to pick from * @param array - The array of values to pick from
*/ */
return this[api.app.clock.time_position.bar % this.length]; if (value === 1) {
return this[api.app.clock.time_position.bar % this.length];
} else {
return this[
Math.floor(api.app.clock.time_position.bar / value) % this.length
];
}
}; };
Array.prototype.pulse = function() { Array.prototype.beat = function (divisor: number = 1) {
/**
* Returns an element from an array based on the current pulse.
*
* @param array - The array of values to pick from
*/
return this[api.app.clock.time_position.pulse % this.length];
};
Array.prototype.beat = function(divisor: number = 1) {
const chunk_size = divisor; // Get the first argument (chunk size) const chunk_size = divisor; // Get the first argument (chunk size)
const timepos = api.app.clock.pulses_since_origin; const timepos = api.app.clock.pulses_since_origin;
const slice_count = Math.floor( const slice_count = Math.floor(
@ -141,7 +146,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
}; };
Array.prototype.b = Array.prototype.beat; Array.prototype.b = Array.prototype.beat;
Array.prototype.shuffle = function() { Array.prototype.shuffle = function () {
/** /**
* Shuffles the array in place. * Shuffles the array in place.
* *
@ -160,7 +165,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this; return this;
}; };
Array.prototype.rotate = function(steps: number) { Array.prototype.rotate = function (steps: number) {
/** /**
* Rotates the array in place. * Rotates the array in place.
* *
@ -180,7 +185,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this; return this;
}; };
Array.prototype.unique = function() { Array.prototype.unique = function () {
/** /**
* Removes duplicate elements from the array in place. * Removes duplicate elements from the array in place.
* *
@ -213,7 +218,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
if (this.length <= 1) { if (this.length <= 1) {
return this; return this;
} }
for (let i = 0; i < this.length;) { for (let i = 0; i < this.length; ) {
const rand = api.randomGen() * 100; const rand = api.randomGen() * 100;
if (rand < amount) { if (rand < amount) {
if (this.length > 1) { if (this.length > 1) {
@ -228,7 +233,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this; return this;
}; };
Array.prototype.repeatAll = function <T>(this: T[], amount: number = 1) { Array.prototype.repeat = function <T>(this: T[], amount: number = 1) {
/** /**
* Repeats all elements in the array n times. * Repeats all elements in the array n times.
* *
@ -250,7 +255,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this; return this;
}; };
Array.prototype.repeatPair = function <T>(this: T[], amount: number = 1) { Array.prototype.repeatOdd = function <T>(this: T[], amount: number = 1) {
/** /**
* Repeats all elements in the array n times, except for the * Repeats all elements in the array n times, except for the
* elements at odd indexes. * elements at odd indexes.
@ -280,7 +285,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this; return this;
}; };
Array.prototype.repeatOdd = function <T>(this: T[], amount: number = 1) { Array.prototype.repeatEven = function <T>(this: T[], amount: number = 1) {
/** /**
* Repeats all elements in the array n times, except for the * Repeats all elements in the array n times, except for the
* elements at even indexes. * elements at even indexes.
@ -326,7 +331,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return left_to_right.concat(right_to_left); return left_to_right.concat(right_to_left);
}; };
Array.prototype.loop = function(index: number) { Array.prototype.loop = function (index: number) {
/** /**
* Returns an element from the array based on the index. * Returns an element from the array based on the index.
* The index will wrap over the array. * The index will wrap over the array.
@ -337,7 +342,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
return this[index % this.length]; return this[index % this.length];
}; };
Array.prototype.random = function() { Array.prototype.random = function () {
/** /**
* Returns a random element from the array. * Returns a random element from the array.
* *
@ -348,7 +353,7 @@ export const makeArrayExtensions = (api: UserAPI) => {
Array.prototype.rand = Array.prototype.random; Array.prototype.rand = Array.prototype.random;
}; };
Array.prototype.scale = function( Array.prototype.scale = function (
scale: string = "major", scale: string = "major",
base_note: number = 0 base_note: number = 0
) { ) {
@ -372,7 +377,7 @@ Array.prototype.scale = function(
}); });
}; };
Array.prototype.scaleArp = function( Array.prototype.scaleArp = function (
scaleName: string = "major", scaleName: string = "major",
boundary: number = 0 boundary: number = 0
) { ) {

View File

@ -8,6 +8,12 @@ import {
} from "zifferjs"; } from "zifferjs";
export abstract class Event { export abstract class Event {
/**
* The Event class is the base class for all events. It provides a set of
* functions that can be used to transform an Event. The functions are used
* to create a chain of transformations that are applied to the Event.
*/
seedValue: string | undefined = undefined; seedValue: string | undefined = undefined;
randomGen: Function = Math.random; randomGen: Function = Math.random;
app: Editor; app: Editor;
@ -20,7 +26,55 @@ export abstract class Event {
} }
} }
evenbar = (func: Function) => {
/**
* Returns a transformed Event if the current bar is even.
*
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
if (this.app.clock.time_position.bar % 2 === 0) {
return this.modify(func);
} else {
return this;
}
};
even = (func: Function) => {
/**
* Returns a transformed Event if the current beat is even.
* @param func - The function to be applied to the Event
* @returns The transformed Event
*
*/
if (this.app.clock.time_position.beat % 2 === 0) {
return this.modify(func);
} else {
return this;
}
};
odd = (func: Function) => {
/**
* Returns a transformed Event if the current beat is odd.
*
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
if (this.app.clock.time_position.beat % 2 !== 0) {
return this.modify(func);
} else {
return this;
}
};
odds = (probability: number, func: Function): Event => { odds = (probability: number, func: Function): Event => {
/**
* Returns a transformed Event with a given probability.
*
* @param probability - The probability of the Event being transformed
* @param func - The function to be applied to the Event
*/
if (this.randomGen() < probability) { if (this.randomGen() < probability) {
return this.modify(func); return this.modify(func);
} }
@ -29,57 +83,124 @@ export abstract class Event {
// @ts-ignore // @ts-ignore
never = (func: Function): Event => { never = (func: Function): Event => {
/**
* Never return a transformed Event.
*
* @param func - The function to be applied to the Event
* @remarks This function is here for user convenience.
*/
return this; return this;
}; };
almostNever = (func: Function): Event => { almostNever = (func: Function): Event => {
/**
* Returns a transformed Event with a probability of 0.025.
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
return this.odds(0.025, func); return this.odds(0.025, func);
}; };
rarely = (func: Function): Event => { rarely = (func: Function): Event => {
/**
* Returns a transformed Event with a probability of 0.1.
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
return this.odds(0.1, func); return this.odds(0.1, func);
}; };
scarcely = (func: Function): Event => { scarcely = (func: Function): Event => {
/**
* Returns a transformed Event with a probability of 0.25.
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
return this.odds(0.25, func); return this.odds(0.25, func);
}; };
sometimes = (func: Function): Event => { sometimes = (func: Function): Event => {
/**
* Returns a transformed Event with a probability of 0.5.
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
return this.odds(0.5, func); return this.odds(0.5, func);
}; };
often = (func: Function): Event => { often = (func: Function): Event => {
/**
* Returns a transformed Event with a probability of 0.75.
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
return this.odds(0.75, func); return this.odds(0.75, func);
}; };
frequently = (func: Function): Event => { frequently = (func: Function): Event => {
/**
* Returns a transformed Event with a probability of 0.9.
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
return this.odds(0.9, func); return this.odds(0.9, func);
}; };
almostAlways = (func: Function): Event => { almostAlways = (func: Function): Event => {
/**
* Returns a transformed Event with a probability of 0.985.
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
return this.odds(0.985, func); return this.odds(0.985, func);
}; };
always = (func: Function): Event => { always = (func: Function): Event => {
/**
* Returns a transformed Event with a probability of 1.
* @param func - The function to be applied to the Event
* @remarks This function is here for user convenience.
*/
return this.modify(func); return this.modify(func);
}; };
modify = (func: Function): Event => { modify = (func: Function): Event => {
/**
* Returns a transformed Event. This function is used internally by the
* other functions of this class. It is just a wrapper for the function
* application.
*/
return func(this); return func(this);
}; };
seed = (value: string | number): Event => { seed = (value: string | number): Event => {
/**
* This function is used to set the seed of the random number generator.
* @param value - The seed value
* @returns The Event
*/
this.seedValue = value.toString(); this.seedValue = value.toString();
this.randomGen = this.app.api.localSeededRandom(this.seedValue); this.randomGen = this.app.api.localSeededRandom(this.seedValue);
return this; return this;
}; };
clear = (): Event => { clear = (): Event => {
/**
* This function is used to clear the seed of the random number generator.
* @returns The Event
*/
this.app.api.clearLocalSeed(this.seedValue); this.app.api.clearLocalSeed(this.seedValue);
return this; return this;
}; };
apply = (func: Function): Event => { apply = (func: Function): Event => {
/**
* This function is used to apply a function to the Event.
* Simple function applicator
*
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
return this.modify(func); return this.modify(func);
}; };

View File

@ -267,9 +267,11 @@ export class SoundEvent extends AudibleEvent {
public cut = (value: number) => this.updateValue("cut", value); public cut = (value: number) => this.updateValue("cut", value);
public clip = (value: number) => this.updateValue("clip", value); public clip = (value: number) => this.updateValue("clip", value);
public n = (value: number) => this.updateValue("n", value); public n = (value: number) => this.updateValue("n", value);
public note = (value: number | string) => { public note = (value: number | string | null) => {
if (typeof value === "string") { if (typeof value === "string") {
return this.updateValue("note", noteNameToMidi(value)); return this.updateValue("note", noteNameToMidi(value));
} else if (typeof value == null || value == undefined) {
return this.updateValue("note", 0).updateValue("gain", 0);
} else { } else {
return this.updateValue("note", value); return this.updateValue("note", value);
} }
@ -299,10 +301,29 @@ export class SoundEvent extends AudibleEvent {
// Reverb management // Reverb management
public room = (value: number) => this.updateValue("room", value); public room = (value: number) => this.updateValue("room", value);
public rm = this.room; public rm = this.room
public size = (value: number) => this.updateValue("size", value); public roomfade = (value: number) => this.updateValue("roomfade", value);
public rfade = this.roomfade;
public roomlp = (value: number) => this.updateValue("roomlp", value);
public rlp = this.roomlp;
public roomdim = (value: number) => this.updateValue("roomdim", value);
public rdim = this.roomdim;
public size = (value: number) => this.updateValue("roomsize", value);
public sz = this.size; public sz = this.size;
// Compressor
public comp = (value: number) => this.updateValue('compressor', value);
public cmp = this.comp;
public ratio = (value: number) => this.updateValue('compressorRatio', value);
public rt = this.ratio;
public knee = (value: number) => this.updateValue('compressorKnee', value);
public kn = this.knee;
public compAttack = (value: number) => this.updateValue('compressorAttack', value);
public cmpa = this.compAttack;
public compRelease = (value: number) => this.updateValue('compressorRelease', value);
public cmpr = this.compRelease;
// Unit // Unit
public stretch = (beat: number) => { public stretch = (beat: number) => {
this.updateValue("unit", "c"); this.updateValue("unit", "c");

View File

@ -19,6 +19,8 @@ Topos is a free and open-source software distributed under [GPL-3.0](https://git
- Felix Roos for the [SuperDough](https://www.npmjs.com/package/superdough) audio engine. - Felix Roos for the [SuperDough](https://www.npmjs.com/package/superdough) audio engine.
- Frank Force for the [ZzFX](https://github.com/KilledByAPixel/ZzFX) synthesizer. - Frank Force for the [ZzFX](https://github.com/KilledByAPixel/ZzFX) synthesizer.
- Kristoffer Ekstrand for the [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) waveforms. - Kristoffer Ekstrand for the [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) waveforms.
- Ryan Kirkbride for some of the audio samples in the [Dough-Fox](https://github.com/Bubobubobubobubo/Dough-Fox) sample pack, taken from [here](https://github.com/Qirky/FoxDot/tree/master/FoxDot/snd).
- All the [Topos](https//github.com/Bubobubobubobubo/Topos) contributors! - All the [Topos](https//github.com/Bubobubobubobubo/Topos) contributors!
**Have fun!** **Have fun!**

View File

@ -246,17 +246,21 @@ beat(.5) && snd('sawtooth')
## Reverb ## Reverb
A basic reverberator that you can use to give some depth to your sounds. This simple reverb design has a _LoFI_ quality that can be quite useful on certain sounds. A good sounding reverb. This reverb unit is using a convolution that gets updated everytime you change a parameter.
For that reason, it is often a good idea to set fixed reverb values per orbit. Do not try to pattern the reverb too much.
| Method | Alias | Description | | Method | Alias | Description |
|------------|-------|---------------------------------| |------------|-------|---------------------------------|
| <ic>room</ic> | | The more, the bigger the reverb (between <ic>0</ic> and <ic>1</ic>.| | <ic>room</ic> | rm | Reverb level (between <ic>0</ic> and <ic>1</ic> |
| <ic>size</ic> | | Reverberation amount | | <ic>size</ic> | sz | Reverb room size of the reverb, between <ic>0</ic> and <ic>n</ic> |
| <ic>roomfade</ic> | | Reverb fade time, in seconds |
| <ic>roomlp</ic> | | Reverb lowpass starting frequency (in hertz) |
| <ic>roomdim</ic> | | Reverb lowpass frequency at -60db (in hertz) |
${makeExample( ${makeExample(
"Clapping in the cavern", "Clapping in the cavern",
` `
beat(2)::snd('cp').room(1).size(0.9).out() beat(2)::snd('cp').room(0.5).size(4).out()
`, `,
true true
)}; )};
@ -282,7 +286,22 @@ beat(1)::snd('kick').out()
true true
)}; )};
## Compression
This effect is leveraging the basic WebAudio compressor. More information can be found about it on the [DynamicsCompressorNode](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) page. This can be come quite complex :)
| Method | Alias | Description |
|------------|-----------|---------------------------------|
| <ic>comp</ic> | cmp | Compressor threshold value (dB) over which compressor operates |
| <ic>ratio</ic> | rt | Compressor ratio: input amount in dB needed for 1dB change in the output |
| <ic>knee</ic> | kn | dB value defining the range over which the signal transitions to compressed section |
| <ic>compAttack</ic> | cmpa | In seconds, time to decrease the gain by 10db |
| <ic>compRelease</ic> | cmpr | In seconds, time to increase the gain by 10db |
## Distorsion, saturation, destruction ## Distorsion, saturation, destruction
Three additional effects that are easy enough to understand. These effects are deteriorating the signal, making it easy to get digital or gritty audio sample playback or synthesizers destroyed beyond recognition. Be careful with your signal level!
| Method | Alias | Description | | Method | Alias | Description |
|------------|-----------|---------------------------------| |------------|-----------|---------------------------------|

View File

@ -6,11 +6,11 @@ export const patterns = (application: Editor): string => {
return ` return `
# Patterns # Patterns
Music really comes to life when you start playing with algorithmic patterns. They can be used to describe a melody, a rhythm, a texture, a set of custom parameters or anything else you can think of. Topos comes with a lot of different abstractions to deal with musical patterns of increasing complexity. Some knowledge of patterns and how to use them will help you to break out of simple loops and repeating structures. Playing with fixed values is fine but what if you could give more life to your parameters? Having parameters that can vary over time is important to describe melodies, rhythms, complex textures, etc. Topos comes with a lot of different abstractions to deal with musical patterns of increasing complexity. Some knowledge of patterns and how to use them will help you to break out of simple loops and repeating structures.
## Working with Arrays ## Arrays
JavaScript is using [Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) as a data structure for lists. Topos is extending them with custom methods that allow you to enter softly into a universe of musical patterns. These methods can often be chained to compose a more complex expression: <ic>[1, 2, 3].repeatOdd(5).palindrome().beat()</ic>. One of the most basic JavaScript data structures is [Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array). Topos is extending this data structure with custom methods that makes it usable for describing patterns that evolve over time. These methods can often be chained to compose more complex expressions: <ic>[1, 2, 3].repeatOdd(5).palindrome().beat()</ic>.
- <ic>beat(division: number)</ic>: this method will return the next value in the list every _n_ pulses. By default, <ic>1</ic> equals to one beat but integer and floating point number values are supported as well. This method is extremely powerful and can be used for many different purposes. Check out the examples. - <ic>beat(division: number)</ic>: this method will return the next value in the list every _n_ pulses. By default, <ic>1</ic> equals to one beat but integer and floating point number values are supported as well. This method is extremely powerful and can be used for many different purposes. Check out the examples.
@ -52,8 +52,7 @@ flip(4)::beat(2)::snd('pad').n(2).shape(.5).orbit(2).room(0.9).size(0.9).release
false false
)} )}
- <ic>pulse()</ic>: returns the index of the list corresponding to the current pulse (with wrapping). This method will return a different value for each pulse. - <ic>bar(value: number = 1)</ic>: returns the next value every bar (if <ic>value = 1</ic>). Using a larger value will return the next value every <ic>n</ic> bars.
- <ic>bar()</ic>: returns the index of the list corresponding to the current bar (with wrapping). This method will return a different value for each bar.
${makeExample( ${makeExample(
"A simple drumbeat in no time!", "A simple drumbeat in no time!",
@ -126,8 +125,8 @@ beat(.25)::snd('amencutup').n([1,2,3,4,5,6,7,8,9].degrade(20).loop($(1))).out()
true true
)} )}
- <ic>repeatAll(amount: number)</ic>: repeat every list elements _n_ times. - <ic>repeat(amount: number)</ic>: repeat every list elements _n_ times.
- <ic>repeatPair(amount: number)</ic>: repeaet every pair element of the list _n_ times. - <ic>repeatEven(amount: number)</ic>: repeaet every pair element of the list _n_ times.
- <ic>repeatOdd(amount: number)</ic>: repeaet every odd element of the list _n_ times. - <ic>repeatOdd(amount: number)</ic>: repeaet every odd element of the list _n_ times.
${makeExample( ${makeExample(
@ -183,31 +182,31 @@ ${makeExample(
beat(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out() beat(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).out()
`, `,
true true
)} )}
- <ic>scale(scale: string, base note: number)</ic>: Map each element of the list to the closest note of the slected scale. [0, 2, 3, 5 ].scale("major", 50) returns [50, 52, <ic>54</ic>, 55]. You can use western scale names like (Major, Minor, Minor pentatonic ...) or [zeitler](https://ianring.com/musictheory/scales/traditions/zeitler) scale names. Alternatively you can also use the integers as used by Ian Ring in his [study of scales](https://ianring.com/musictheory/scales/). - <ic>scale(scale: string, base note: number)</ic>: Map each element of the list to the closest note of the slected scale. [0, 2, 3, 5 ].scale("major", 50) returns [50, 52, <ic>54</ic>, 55]. You can use western scale names like (Major, Minor, Minor pentatonic ...) or [zeitler](https://ianring.com/musictheory/scales/traditions/zeitler) scale names. Alternatively you can also use the integers as used by Ian Ring in his [study of scales](https://ianring.com/musictheory/scales/).
${makeExample( ${makeExample(
"Mapping the note array to the E3 major scale", "Mapping the note array to the E3 major scale",
` `
beat(1) :: snd('gtr') beat(1) :: snd('gtr')
.note([0, 5, 2, 1, 7].scale("Major", 52).beat()) .note([0, 5, 2, 1, 7].scale("Major", 52).beat())
.out() .out()
`, `,
true true
)} )}
- <ic>scaleArp(scale: string, mask: number)</ic>: extrapolate a custom-masked scale from each list elements. [0].scale("major", 3) returns [0,2,4]. <ic>scaleArp</ic> supports the same scales as <ic>scale</ic>. - <ic>scaleArp(scale: string, mask: number)</ic>: extrapolate a custom-masked scale from each list elements. [0].scale("major", 3) returns [0,2,4]. <ic>scaleArp</ic> supports the same scales as <ic>scale</ic>.
${makeExample( ${makeExample(
"Extrapolate a 3-elements Mixolydian scale from 2 notes", "Extrapolate a 3-elements Mixolydian scale from 2 notes",
` `
beat(1) :: snd('gtr') beat(1) :: snd('gtr')
.note([0, 5].scaleArp("mixolydian", 3).beat() + 50) .note([0, 5].scaleArp("mixolydian", 3).beat() + 50)
.out() .out()
`, `,
true true
)} )}
- <ic>add()</ic>: add a given amount to every list element. - <ic>add()</ic>: add a given amount to every list element.

View File

@ -41,7 +41,7 @@ rhythm(.25, 6, 8) :: sound('shaker').end(0.25).out()
rhythm(.5,4,8) :: sound('bd').out()`, rhythm(.5,4,8) :: sound('bd').out()`,
`// Computer Music Classroom, Monday (8AM) -- Bubobubobubo `// Computer Music Classroom, Monday (8AM) -- Bubobubobubo
let ur = [0, 5, 12, 7, 5].beat(24), let ur = [0, 5, 12, 7, 5].beat(24),
fundamental = [0, 5, 10, 8, 6].repeatAll(4).bar(); fundamental = [0, 5, 10, 8, 6].repeat(4).bar();
beat(.25) :: sound('triangle') beat(.25) :: sound('triangle')
.note(ur + fundamental + 40).n(1 + $(1) % 16) .note(ur + fundamental + 40).n(1 + $(1) % 16)
.atk(0.05).sustain(0.1).release(0.1) .atk(0.05).sustain(0.1).release(0.1)
@ -77,7 +77,7 @@ beat([4, 2, 8].pick() / [2,1].bar()) :: sound('triangle')
`, `,
`// Super gentle computing aka Super-Zapping - Bubobubobubo `// Super gentle computing aka Super-Zapping - Bubobubobubo
let melody = [30,30,34,35,37].palindrome() let melody = [30,30,34,35,37].palindrome()
.beat() + [0, -12].repeatAll(2).beat(2) .beat() + [0, -12].repeat(2).beat(2)
if (flip(8, 75)) { if (flip(8, 75)) {
rhythm(.5, 4, 8) :: sound('ST71').n([5,6,8].beat(0.5)).gain(0.4).out() rhythm(.5, 4, 8) :: sound('ST71').n([5,6,8].beat(0.5)).gain(0.4).out()
beat(.5) :: sound('ST11').note(melody).gain(0.4) beat(.5) :: sound('ST11').note(melody).gain(0.4)
@ -115,7 +115,7 @@ rhythm(flip(2) ? .5 : .25, flip(4) ? 8 : 11, 12) :: sound('hat')
`// Part-Dieu - Bubobubobubo `// Part-Dieu - Bubobubobubo
bpm(90); bpm(90);
beat(rarely(12) ? .5 : .25) :: sound('ST22') beat(rarely(12) ? .5 : .25) :: sound('ST22')
.note([30, 30, 30, 31].repeatAll(8).beat(.5)) .note([30, 30, 30, 31].repeat(8).beat(.5))
.cut(1).n([19, 21].beat(.75)) .cut(1).n([19, 21].beat(.75))
.cutoff(irand(200, 5000)) .cutoff(irand(200, 5000))
.resonance(rand(0.2,0.8)) .resonance(rand(0.2,0.8))
@ -134,7 +134,7 @@ bpm(85);
let modifier = [.5, 1, 2].beat(8); let modifier = [.5, 1, 2].beat(8);
let othermod = [1, .5, 4].beat(4); let othermod = [1, .5, 4].beat(4);
beat(modifier / 2):: sound('STA9').n([0,2].beat(.5)).vel(0.5).out() beat(modifier / 2):: sound('STA9').n([0,2].beat(.5)).vel(0.5).out()
beat(.5)::sound('STA9').n([0, 20].beat(.5)).speed([1,1.5].repeatAll(4).beat() /2) beat(.5)::sound('STA9').n([0, 20].beat(.5)).speed([1,1.5].repeat(4).beat() /2)
.cutoff(500 + usine(.25) * 3000).vel(1).room(0.9).out() .cutoff(500 + usine(.25) * 3000).vel(1).room(0.9).out()
beat(modifier / 2):: sound('STA9') beat(modifier / 2):: sound('STA9')
.n([0,7].beat(.5)).speed(flip(othermod) ? 2 : 4).vel(1).out() .n([0,7].beat(.5)).speed(flip(othermod) ? 2 : 4).vel(1).out()
@ -193,7 +193,7 @@ beat([.25,.125, .5].beat(4))::snd('arpy:4')
.note(30 + [0,3,7,10, 12, 5, 7].beat()).speed(1.001).pan(1) .note(30 + [0,3,7,10, 12, 5, 7].beat()).speed(1.001).pan(1)
.cutoff(100 + usine(1/8) * 800).lpadsr(5, 0, [1/8, 1.16].beat(), 0, 0) .cutoff(100 + usine(1/8) * 800).lpadsr(5, 0, [1/8, 1.16].beat(), 0, 0)
.resonance(5).gain(0.4).end(0.8).room(0.9).size(0.9).n(3).out(); .resonance(5).gain(0.4).end(0.8).room(0.9).size(0.9).n(3).out();
beat(.5) :: snd('arpy').note([30, 33, 35].repeatAll(4).beat(1) - [24,12].beat(0.5)) beat(.5) :: snd('arpy').note([30, 33, 35].repeat(4).beat(1) - [24,12].beat(0.5))
.cutoff(500).lpadsr(8, 0.05, .125, 0, 0).out()`, `// Naïf et agréable -- Bubobubobubo .cutoff(500).lpadsr(8, 0.05, .125, 0, 0).out()`, `// Naïf et agréable -- Bubobubobubo
z1('1/8 024!3 035 024 0124').sound('wt_stereo') z1('1/8 024!3 035 024 0124').sound('wt_stereo')
.adsr(0, .4, 0.5, .4).gain(0.1) .adsr(0, .4, 0.5, .4).gain(0.1)

View File

@ -1272,10 +1272,10 @@ sucrase@^3.32.0:
pirates "^4.0.1" pirates "^4.0.1"
ts-interface-checker "^0.1.9" ts-interface-checker "^0.1.9"
superdough@^0.9.8: superdough@^0.9.10:
version "0.9.8" version "0.9.10"
resolved "https://registry.yarnpkg.com/superdough/-/superdough-0.9.8.tgz#de30e364b2613a15a46f35f359a2a0ce30d52611" resolved "https://registry.yarnpkg.com/superdough/-/superdough-0.9.10.tgz#9554964741c508b4c5d596fa8acbb2efea822250"
integrity sha512-3xy2LXAH4K0JMwwY4G0eJz+u1VIhSFxIE4YZL7E4isjUQZTN9Y0jpX17EzQgvFIW1V3PUJMkDiDgEDWZTRCTKg== integrity sha512-IGu0+fBXpSS4l4Q+4ATRhSFXnas7t2G6uc5Ry+keQ4G+nc6uK6twAP0YyBlSB4RUdGpZCIS1o0t8qJ7MaBg4gw==
dependencies: dependencies:
nanostores "^0.8.1" nanostores "^0.8.1"