WIP: more beunsize map work
This commit is contained in:
parent
4dd4db6f65
commit
d79c6421e5
8 changed files with 222 additions and 47 deletions
|
|
@ -3,8 +3,8 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node dist/index.js",
|
"start": "node --expose-gc dist/index.js",
|
||||||
"dev": "tsc-watch --onSuccess \"npm start\""
|
"dev": "tsc-watch --onSuccess \"npm start beunsize-ma3\""
|
||||||
},
|
},
|
||||||
"author": "Rik Berkelder <mail@riksolo.com> (https://riksolo.com)",
|
"author": "Rik Berkelder <mail@riksolo.com> (https://riksolo.com)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ export class MidiControl<T extends MessageType> {
|
||||||
this.device.input.setMaxListeners(50);
|
this.device.input.setMaxListeners(50);
|
||||||
|
|
||||||
if (type === MessageType.NoteOnOff) {
|
if (type === MessageType.NoteOnOff) {
|
||||||
|
|
||||||
this.addListener(MessageType.NoteOn)
|
this.addListener(MessageType.NoteOn)
|
||||||
this.addListener(MessageType.NoteOff)
|
this.addListener(MessageType.NoteOff)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -90,7 +89,7 @@ export class MidiControl<T extends MessageType> {
|
||||||
|
|
||||||
const output = this.outputs[this.page];
|
const output = this.outputs[this.page];
|
||||||
if (!output || output === undefined) return;
|
if (!output || output === undefined) return;
|
||||||
output(data);
|
output(outData);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import osc from "osc";
|
||||||
|
|
||||||
interface IOSCEvent {
|
interface IOSCEvent {
|
||||||
address: string;
|
address: string;
|
||||||
handler: (message: osc.ResponseMessage<osc.MessageArg[]>) => void;
|
handler: (data: string | number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OSCDevice {
|
export class OSCDevice {
|
||||||
|
|
@ -20,10 +20,17 @@ export class OSCDevice {
|
||||||
this.port.on('error', (e) => { console.error('osc error', e) })
|
this.port.on('error', (e) => { console.error('osc error', e) })
|
||||||
|
|
||||||
this.port.on('message', msg => {
|
this.port.on('message', msg => {
|
||||||
// console.log('msg', msg)
|
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
if (msg.address !== listener.address) continue;
|
if (msg.address !== listener.address) continue;
|
||||||
listener.handler(msg);
|
if (msg.args[0] !== undefined) {
|
||||||
|
let value: string | number | undefined;
|
||||||
|
if (typeof msg.args[0] === 'object' && ['string', 'number'].includes(typeof msg.args[0].value)) value = msg.args[0].value as string | number;
|
||||||
|
if (typeof msg.args[0] === 'string' || typeof msg.args[0] === 'number') value = msg.args[0];
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
listener.handler(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
@ -52,7 +59,7 @@ export class OSCDevice {
|
||||||
this.port.send({ address });
|
this.port.send({ address });
|
||||||
}
|
}
|
||||||
|
|
||||||
public addListener(address: string, handler: IOSCEvent['handler']): number {
|
public addListener(address: string, handler: (data: string | number) => void): number {
|
||||||
const newLength = this.listeners.push({
|
const newLength = this.listeners.push({
|
||||||
address,
|
address,
|
||||||
handler
|
handler
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ async function main() {
|
||||||
console.log('inputs', midi.getInputs())
|
console.log('inputs', midi.getInputs())
|
||||||
console.log('outputs', midi.getOutputs())
|
console.log('outputs', midi.getOutputs())
|
||||||
|
|
||||||
|
const mapName = process.argv[process.argv.length - 1]
|
||||||
|
|
||||||
const mapping = await import('./mappings/xr18-home.js');
|
const mapping = await import(`./mappings/${mapName}.js`);
|
||||||
mapping.default()
|
mapping.default()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Uncaught error', e)
|
console.error('Uncaught error', e)
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,63 @@
|
||||||
import * as midi from 'easymidi'
|
import * as midi from 'easymidi'
|
||||||
import { MessageType, MidiControl, MidiDevice } from "../devices/Midi.js"
|
import { MessageType, MidiControl, MidiDevice } from "../devices/Midi.js"
|
||||||
import { OSCDevice } from "../devices/OSC.js"
|
import { OSCDevice } from "../devices/OSC.js"
|
||||||
import { mapNumber } from '../utilityFunctions.js'
|
import { delay, mapNumber, numberRange, retry } from '../utilityFunctions.js'
|
||||||
|
|
||||||
|
|
||||||
|
function ma3HardKey(ma3: OSCDevice, keycode: string, status: boolean) {
|
||||||
|
ma3.sendString('/cmd', `Plugin "RBOSCKeys" "${keycode.toUpperCase()} ${status ? "press" : "release"}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ApcColor {
|
||||||
|
Off = 0,
|
||||||
|
Green = 1,
|
||||||
|
Red = 3,
|
||||||
|
Yellow = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const MA3_FADER_MIN = 0;
|
||||||
|
const MA3_FADER_MAX = 100;
|
||||||
|
|
||||||
export default async function mapping() {
|
export default async function mapping() {
|
||||||
|
|
||||||
// DEVICES
|
const ma3 = new OSCDevice('0.0.0.0', '127.0.0.1', 8002, 8000)
|
||||||
|
|
||||||
const xtouch = new MidiDevice('X Touch Compact', true)
|
const tryXTouch = async () => {
|
||||||
const xtouch_loop = new MidiDevice('XTouch-loop', true, true)
|
const [xtouch, xtouch_loop] = await retry(() => {
|
||||||
const keyboard = new MidiDevice('idobo')
|
return [
|
||||||
|
new MidiDevice('X Touch Compact', true),
|
||||||
|
new MidiDevice('XTouch-loop', true, true)
|
||||||
|
]
|
||||||
|
}, 1000, 'xtouch')
|
||||||
|
|
||||||
const ma3 = new OSCDevice('0.0.0.0', '127.0.0.1', 3000, 3001)
|
xtouchMapping(xtouch, xtouch_loop, ma3);
|
||||||
|
}
|
||||||
|
|
||||||
// UTILITY FUNCTIONS
|
|
||||||
|
|
||||||
|
const tryKeyboard = async () => {
|
||||||
|
const keyboard = await retry(() => new MidiDevice('idobo'), 1000, 'keyboard');
|
||||||
|
keyboardMapping(keyboard, ma3)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const tryApcMini = async () => {
|
||||||
|
const apcmini = await retry(() => new MidiDevice('APC MINI', true), 1000, 'apc mini');
|
||||||
|
apcminiMapping(apcmini, ma3)
|
||||||
|
}
|
||||||
|
|
||||||
|
//tryXTouch();
|
||||||
|
//tryKeyboard();
|
||||||
|
tryApcMini();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function xtouchMapping(xtouch: MidiDevice, xtouch_loop: MidiDevice, ma3: OSCDevice) {
|
||||||
const noteButtonFeedback = (note: number, address: string) => {
|
const noteButtonFeedback = (note: number, address: string) => {
|
||||||
const button = xtouch.addControl(MessageType.NoteOnOff, { note });
|
const button = xtouch.addControl(MessageType.NoteOnOff, { note });
|
||||||
|
|
||||||
ma3.addListener(address, message => {
|
ma3.addListener(address, data => {
|
||||||
const value = message.args[0]?.value
|
const value = data
|
||||||
if (typeof value !== 'number') return;
|
if (typeof value !== 'number') return;
|
||||||
|
|
||||||
button.handleFeedback(0, { value: value === 1 ? true : false })
|
button.handleFeedback(0, { value: value === 1 ? true : false })
|
||||||
|
|
@ -30,22 +68,19 @@ export default async function mapping() {
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ma3HardKey = (keycode: string, status: boolean) => {
|
|
||||||
ma3.sendString('/cmd', `Call Plugin "RBOSCKeys" "${keycode} ${status ? "press" : "release"}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MAPPINGS
|
// MAPPINGS
|
||||||
|
|
||||||
// passthroughs for encoders plugin
|
// passthroughs for encoders plugin
|
||||||
[21, 22, 23, 24, 25, 26].forEach(encoder => {
|
numberRange(21, 26).forEach(encoder => {
|
||||||
xtouch.addControl(MessageType.CC, { controller: encoder }).addOutput(0, message => {
|
xtouch.addControl(MessageType.CC, { controller: encoder }).addOutput(0, message => {
|
||||||
xtouch_loop.output?.send(MessageType.CC, message)
|
xtouch_loop.output?.send(MessageType.CC, message)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Executor Faders
|
// Executor Faders
|
||||||
const faders = [0, 1, 2, 3, 4, 5, 6, 7, 8].map(f => xtouch.addControl(MessageType.CC, { controller: f }))
|
const faders = numberRange(0, 8).map(f => xtouch.addControl(MessageType.CC, { controller: f }))
|
||||||
|
|
||||||
faders.forEach((fader, index) => {
|
faders.forEach((fader, index) => {
|
||||||
const firstExec = 201;
|
const firstExec = 201;
|
||||||
|
|
@ -53,12 +88,11 @@ export default async function mapping() {
|
||||||
const address = `/Page/Fader${exec}`;
|
const address = `/Page/Fader${exec}`;
|
||||||
|
|
||||||
fader.addOutput(0, (message) => {
|
fader.addOutput(0, (message) => {
|
||||||
ma3.sendFloat(address, mapNumber(message.value, 0, 127, 0, 1));
|
ma3.sendFloat(address, mapNumber(message.value, 0, 127, MA3_FADER_MIN, MA3_FADER_MAX));
|
||||||
})
|
})
|
||||||
|
|
||||||
ma3.addListener(address, d => {
|
ma3.addListener(address, value => {
|
||||||
const value = (d.args as any)[0]?.value;
|
if (typeof value === 'number') {
|
||||||
if (value !== undefined) {
|
|
||||||
const mappedValue = mapNumber(value, 0, 1, 0, 127);
|
const mappedValue = mapNumber(value, 0, 1, 0, 127);
|
||||||
fader.handleFeedback(0, { value: mappedValue })
|
fader.handleFeedback(0, { value: mappedValue })
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +102,10 @@ export default async function mapping() {
|
||||||
|
|
||||||
// Executor Keys
|
// Executor Keys
|
||||||
const executorButtons: MidiControl<MessageType.NoteOnOff>[][] = [
|
const executorButtons: MidiControl<MessageType.NoteOnOff>[][] = [
|
||||||
[16, 17, 18, 19, 20, 21, 22, 23],
|
numberRange(16, 23),
|
||||||
[24, 25, 26, 27, 28, 29, 30, 31],
|
numberRange(24, 31),
|
||||||
[32, 33, 34, 35, 36, 37, 38, 39],
|
numberRange(32, 39),
|
||||||
[40, 41, 42, 43, 44, 45, 46, 47, 48],
|
numberRange(40, 48)
|
||||||
].map((row, rowIndex) => row.map((note, noteIndex) => {
|
].map((row, rowIndex) => row.map((note, noteIndex) => {
|
||||||
const rowLookup = [401, 301, 201, 101];
|
const rowLookup = [401, 301, 201, 101];
|
||||||
const address = `/Page/Key${rowLookup[rowIndex] ?? 101 + noteIndex}`
|
const address = `/Page/Key${rowLookup[rowIndex] ?? 101 + noteIndex}`
|
||||||
|
|
@ -86,13 +120,13 @@ export default async function mapping() {
|
||||||
{ note: 50, key: 'PAGE_DOWN' },
|
{ note: 50, key: 'PAGE_DOWN' },
|
||||||
{ note: 51, key: '' },
|
{ note: 51, key: '' },
|
||||||
{ note: 52, key: '' },
|
{ note: 52, key: '' },
|
||||||
{ note: 53, key: '' },
|
{ note: 53, key: 'DEF_GOBACK' },
|
||||||
{ note: 53, key: '' },
|
{ note: 53, key: 'DEF_GO' },
|
||||||
]
|
]
|
||||||
.map(i => {
|
.map(i => {
|
||||||
const button = xtouch.addControl(MessageType.NoteOnOff, { note: i.note })
|
const button = xtouch.addControl(MessageType.NoteOnOff, { note: i.note })
|
||||||
button.addOutput(0, (message) => {
|
button.addOutput(0, (message) => {
|
||||||
ma3HardKey(i.key, message.value)
|
ma3HardKey(ma3, i.key, message.value)
|
||||||
})
|
})
|
||||||
return button
|
return button
|
||||||
})
|
})
|
||||||
|
|
@ -110,17 +144,15 @@ export default async function mapping() {
|
||||||
ma3.sendInt(address, mapNumber(message.value, 0, 1, -1, 1))
|
ma3.sendInt(address, mapNumber(message.value, 0, 1, -1, 1))
|
||||||
})
|
})
|
||||||
|
|
||||||
ma3.addListener(feedbackAddress, message => {
|
ma3.addListener(feedbackAddress, data => {
|
||||||
if (message.args[0] === undefined || message.args[0].type !== 'f') return;
|
if (typeof data !== 'number') return;
|
||||||
const value = message.args[0].value;
|
|
||||||
|
|
||||||
encoder.handleFeedback(0, { value: mapNumber(value, 0, 1, 0, 127) })
|
encoder.handleFeedback(0, { value: mapNumber(data, 0, 1, 0, 127) })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function keyboardMapping(keyboard: MidiDevice, ma3: OSCDevice) {
|
||||||
// HARDKEYS
|
|
||||||
|
|
||||||
const keymap: string[][] = [
|
const keymap: string[][] = [
|
||||||
['pause', 'goback', 'go', 'fixture', 'channel', 'group', 'menu', 'oops', 'num7', 'num8', 'num9', 'plus', 'highlight', 'on', 'off'],
|
['pause', 'goback', 'go', 'fixture', 'channel', 'group', 'menu', 'oops', 'num7', 'num8', 'num9', 'plus', 'highlight', 'on', 'off'],
|
||||||
['learn', 'gobackfast', 'gofast', 'preset', 'sequence', 'cue', 'update', 'esc', 'num4', 'num5', 'num6', 'thru', 'solo', 'move', 'copy'],
|
['learn', 'gobackfast', 'gofast', 'preset', 'sequence', 'cue', 'update', 'esc', 'num4', 'num5', 'num6', 'thru', 'solo', 'move', 'copy'],
|
||||||
|
|
@ -131,8 +163,8 @@ export default async function mapping() {
|
||||||
|
|
||||||
const xkeymap: number[] =
|
const xkeymap: number[] =
|
||||||
[
|
[
|
||||||
291, 292, 293, 294, 295, 296, 297, 298,
|
...numberRange(291, 298),
|
||||||
191, 192, 193, 194, 195, 196, 197, 198
|
...numberRange(191, 198)
|
||||||
];
|
];
|
||||||
|
|
||||||
keymap.forEach((row, rowIndex) => {
|
keymap.forEach((row, rowIndex) => {
|
||||||
|
|
@ -156,7 +188,7 @@ export default async function mapping() {
|
||||||
keyboard
|
keyboard
|
||||||
.addControl(MessageType.CC, { controller: totalIndex })
|
.addControl(MessageType.CC, { controller: totalIndex })
|
||||||
.addOutput(0, message => {
|
.addOutput(0, message => {
|
||||||
ma3HardKey(key.toUpperCase(), message.value ? true : false);
|
ma3HardKey(ma3, key.toUpperCase(), message.value ? true : false);
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +196,106 @@ export default async function mapping() {
|
||||||
totalIndex++
|
totalIndex++
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apcminiMapping(apcmini: MidiDevice, ma3: OSCDevice) {
|
||||||
|
const APC_FIRST_EXEC = 201;
|
||||||
|
|
||||||
}
|
apcmini.input.on('cc', m => console.dir(m));
|
||||||
|
apcmini.input.on('noteon', m => console.dir(m));
|
||||||
|
apcmini.input.on('noteoff', m => console.dir(m));
|
||||||
|
|
||||||
|
// playback faders
|
||||||
|
|
||||||
|
numberRange(48, 56).forEach((controller, index) => {
|
||||||
|
const fader = apcmini.addControl(MessageType.CC, { controller });
|
||||||
|
const exec = APC_FIRST_EXEC + index;
|
||||||
|
const address = `/Page/Fader${exec}`;
|
||||||
|
|
||||||
|
fader.addOutput(0, (message) => {
|
||||||
|
ma3.sendFloat(address, mapNumber(message.value, 2, 125, MA3_FADER_MIN, MA3_FADER_MAX));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// playback fader buttons
|
||||||
|
[...numberRange(64, 71), 98].forEach((note, index) => {
|
||||||
|
const button = apcmini.addControl(MessageType.NoteOnOff, { note });
|
||||||
|
const exec = APC_FIRST_EXEC + index;
|
||||||
|
const address = `/Page/Key${exec}`
|
||||||
|
|
||||||
|
button.addOutput(0, message => {
|
||||||
|
ma3.sendInt(address, message.value ? 1 : 0);
|
||||||
|
})
|
||||||
|
|
||||||
|
ma3.addListener(address, value => {
|
||||||
|
button.handleFeedback(0, { velocity: value ? 3 : 0, value: true })
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// main key mapping, upside down
|
||||||
|
|
||||||
|
const rowColors = [
|
||||||
|
ApcColor.Red,
|
||||||
|
ApcColor.Red,
|
||||||
|
ApcColor.Red,
|
||||||
|
ApcColor.Off,
|
||||||
|
ApcColor.Off,
|
||||||
|
ApcColor.Off,
|
||||||
|
ApcColor.Yellow,
|
||||||
|
ApcColor.Yellow,
|
||||||
|
];
|
||||||
|
|
||||||
|
[
|
||||||
|
numberRange(101, 108),
|
||||||
|
numberRange(301, 308),
|
||||||
|
numberRange(401, 408),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
numberRange(191, 198),
|
||||||
|
numberRange(291, 298),
|
||||||
|
].forEach((rowContent, row) => {
|
||||||
|
const rowFirstCC = row * 8;
|
||||||
|
if (rowContent !== null) rowContent.forEach((exec, index) => {
|
||||||
|
const button = apcmini.addControl(MessageType.NoteOnOff, { note: rowFirstCC + index })
|
||||||
|
const address = `/Page/Key${exec}`
|
||||||
|
|
||||||
|
button.addOutput(0, value => {
|
||||||
|
ma3.sendInt(address, value.value ? 1 : 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
ma3.addListener(address, value => {
|
||||||
|
let color: number = ApcColor.Off;
|
||||||
|
|
||||||
|
if (rowColors[row]) {
|
||||||
|
if (value === 1) color = rowColors[row]
|
||||||
|
if (value === 2) color = ApcColor.Green
|
||||||
|
}
|
||||||
|
|
||||||
|
button.handleFeedback(0,
|
||||||
|
{ value: true, velocity: color }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstCommandNote = 82;
|
||||||
|
// Command Keys
|
||||||
|
[
|
||||||
|
'clear',
|
||||||
|
'next',
|
||||||
|
'blind',
|
||||||
|
'highlight',
|
||||||
|
'store',
|
||||||
|
'update',
|
||||||
|
'def_goback',
|
||||||
|
'def_go'
|
||||||
|
].forEach((key, index) => {
|
||||||
|
const note = firstCommandNote + index;
|
||||||
|
|
||||||
|
apcmini.addControl(MessageType.NoteOnOff, { note }).addOutput(0, data => {
|
||||||
|
ma3HardKey(ma3, key, data.value);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export default async function mapping() {
|
||||||
|
|
||||||
const fader2way = (fader: number, page: number, addr: string, max: number = 1) => {
|
const fader2way = (fader: number, page: number, addr: string, max: number = 1) => {
|
||||||
faders[fader]?.addOutput(page, d => setLevel(addr, d.value, max));
|
faders[fader]?.addOutput(page, d => setLevel(addr, d.value, max));
|
||||||
odev.addListener(addr, d => { levelFeedback(fader, page, (d.args as any)[0], max) });
|
odev.addListener(addr, d => { if (typeof d === 'number') levelFeedback(fader, page, d, max) });
|
||||||
|
|
||||||
odev.sendNull(addr);
|
odev.sendNull(addr);
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +150,7 @@ export default async function mapping() {
|
||||||
})
|
})
|
||||||
|
|
||||||
odev.addListener(addr, d => {
|
odev.addListener(addr, d => {
|
||||||
control.handleFeedback(page, { velocity: (d.args as any)[0] === 0 ? 127 : 0 });
|
control.handleFeedback(page, { velocity: d === 0 ? 127 : 0 });
|
||||||
})
|
})
|
||||||
|
|
||||||
odev.sendNull(addr);
|
odev.sendNull(addr);
|
||||||
|
|
|
||||||
2
src/types/osc/osc.d.ts
vendored
2
src/types/osc/osc.d.ts
vendored
|
|
@ -37,6 +37,8 @@ type MessageArg =
|
||||||
| MessageArgString
|
| MessageArgString
|
||||||
| MessageArgInt
|
| MessageArgInt
|
||||||
| MessageArgBytes
|
| MessageArgBytes
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
|
||||||
type SendMessage<ARG_A = MessageArg[]> = {
|
type SendMessage<ARG_A = MessageArg[]> = {
|
||||||
address: string
|
address: string
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,38 @@ export function mapNumber(value: number, fromMin: number, fromMax: number, toMin
|
||||||
|
|
||||||
const mapped = (value / absFromMax) * absToMax;
|
const mapped = (value / absFromMax) * absToMax;
|
||||||
return mapped + toMin;
|
return mapped + toMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delay(t: number) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function retry<T>(create: () => T, time: number, desc?: string): Promise<T> {
|
||||||
|
|
||||||
|
let thing: T | undefined;
|
||||||
|
|
||||||
|
while (!thing) {
|
||||||
|
try {
|
||||||
|
thing = create();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error in retry ${desc ?? 'unnamed'}: ${e}`)
|
||||||
|
await delay(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thing;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function numberRange(from: number, to: number): number[] {
|
||||||
|
let array = [];
|
||||||
|
for (let i = from; i <= to; i++) {
|
||||||
|
array.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function numbersFrom(from: number, amount: number): number[] {
|
||||||
|
return numberRange(from, from + amount - 1)
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue