-
Notifications
You must be signed in to change notification settings - Fork 0
/
sheetOne.ts
200 lines (179 loc) · 7.84 KB
/
sheetOne.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/**
* ElementaryAudio
* NeverEngineLabs - Elem Funk Library
* author: @cristianvogel
*/
import { el, resolve, isNode } from '@elemaudio/core';
import type { Signal, StereoSignal } from './typeDeclarations';
/**
* @name constants
* @description Useful signal constants for normalised system
*/
const one: Signal = el.const({ key: 'one', value: 1 });
const negativeOne: Signal = el.const({ key: 'minusOne', value: -1 });
const zero: Signal = el.const({ key: 'zero', value: 0 });
const half: Signal = el.const({ key: 'half', value: 0.5 });
const negativeHalf: Signal = el.const({ key: 'minusHalf', value: -0.5 });
const halfSR: Signal = el.mul(0.5, el.sr());
/**
* ╠══════════════════════════════════════════╣
* @name stereoizeParam
* @description
* Cast a number to StereoSignal type
* ╠══════════════════════════════════════════╣
* @param value : number :: value to be casted
* ╠══════════════════════════════════════════╣
*/
export function stereoizeParam(value: number): StereoSignal {
const toSignal = el.const({ key: 'stereoize', value })
return { left: toSignal, right: toSignal };
}
/**
* ╠══════════════════════════════════════════╣
* @name stereoizeSignal
* @description
* Cast a single Signal to StereoSignal type
* ╠══════════════════════════════════════════╣
* @param signal : Signal :: signal to be casted
* ╠══════════════════════════════════════════╣
*/
export function stereoizeSignal(signal: Signal): StereoSignal {
return { left: signal, right: signal };
}
/**
* ╠══════════════════════════════════════════╣
* @name numberToSignal
* @description
* Cast a number to constant Signal
* ╠══════════════════════════════════════════╣
* @param key : string :: key for the node
* @param value : number :: value for the node
* ╠══════════════════════════════════════════╣
*/
export function numberToConstant(key: string, value: number): Signal {
return el.const({ key, value: value });
}
/**
* ╠══════════════════════════════════════════╣
* @name attenuate
* @description
* Attenuate by another a level signal,
* with smoothing true by default
* ╠══════════════════════════════════════════╣
* @param props { level, key, bypassSmoothing }
* @param level : Signal or number :: level to attenuate by
* @param key : string :: optional key for the node
* @param bypassSmoothing : boolean :: bypass default smoothing
* @param input : Signal or number :: input signal to attenuate
* ╠══════════════════════════════════════════╣
*/
export function attenuate(
props: {
level: Signal | number;
key?: string;
bypassSmoothing?: boolean
},
input: Signal | number
): Signal {
const level: Signal = resolve(props.bypassSmoothing ? props.level : el.sm(props.level));
return resolve(el.mul(level, input));
}
/**
* ╠══════════════════════════════════════════╣
* @name progressCounter
* @description
* Implements a parametised counter as an audio
* rate signal, with the side effect of emitting
* a snapshot of a normalised progress value
* emitted at a specified rate. This is an audio
* rate control signal, therefore it will also
* emit DC when rendered. One strategy is to render
* it with a secondary Elementary core, which is not
* connected to the audio output, and then use the snapshot
* to drive a UI progress bar or anything else code wise.
* The advantage of this approach is that the progress signal
* can be further modified or controlled by signal processing.
* ╠══════════════════════════════════════════╣
* @param props { run, totalDurMs, rate, startOffset }
* @param run [ signal or number ] :: run or pause the counter
* @param totalDurMs [ number ] :: total duration of the counter in milliseconds
* @param rate [ number ] :: snapshot rate in Hz
* @param startOffset [ number ] :: start offset in milliseconds
* ╠══════════════════════════════════════════╣
*/
export function progress(props: {
key?: string;
totalDurMs?: number;
run: Signal | number;
rate?: number;
startOffset?: number;
}): Signal {
let { run,
totalDurMs = 1000,
rate = 10,
startOffset = 0,
key = 'progress'
} = props;
run = isNode(run) ? run : resolve(el.const({ key: key + '_run', value: run as number }));
let progress = el.add(
el.counter({ key: key + '_count' }, run),
el.ms2samps(startOffset)
);
let normProgress = el.div({ key: key + '_div' }, progress, el.ms2samps(totalDurMs));
return (
el.snapshot({ key, name: 'progress' }, el.train(rate ? el.mul(rate, run) : run), normProgress)
);
}
/**
* ╠══════════════════════════════════════════╣
* @name clippedHann
* @description A clipped hann window with optional pre-scaling
* @param props { gain } :: optional gain before clipping
* @param index [ signal or number ] :: phase index into the hann window lookup table
* ╠══════════════════════════════════════════╣
*/
export function clippedHann(
props: {
key?: string;
gain?: number | Signal;
index: Signal | number,
}
): Signal {
let { key = 'clippedHann', gain = one, index } = props;
index = isNode(index) ? index : resolve(numberToConstant(key, index as unknown as number))
return resolve(
el.mul(gain, el.hann(index))
)
}
/**
* ╠══════════════════════════════════════════╣
* @name clipTo01
* @description Clip a signal to the range [0, 1] with optional pre-scaling
* @param signal optionally full range [-1,1] at input
* ╠══════════════════════════════════════════╣
*/
export function clipTo01(
props: {
prescale?: number | Signal,
fullRangeInput?: boolean;
},
input: Signal): Signal {
const prescale: number | Signal = props.prescale ? props.prescale : one;
const scaleAndOffset = props.fullRangeInput ? fullRangeTo01(input) as Signal : input;
const final = el.mul(prescale, scaleAndOffset) as Signal;
return resolve(
el.max(0, el.min(1, final))
)
}
/**
* ╠══════════════════════════════════════════╣
* @name fullRangeTo01
* @description Convert a full range signal to the range [0, 1]
* @param signal expects full range [-1,1] signal
* ╠══════════════════════════════════════════╣
*/
export function fullRangeTo01(input: Signal): Signal {
return resolve(
el.add(half, el.mul(half, input))
)
}