@dgck81lnn/koishi-plugin-music 0.1.6 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/synth.js +33 -15
- package/lib/index.d.ts +3 -2
- package/lib/index.js +33 -15
- package/package.json +1 -1
- package/browser/index.html +0 -8
package/browser/synth.js
CHANGED
|
@@ -9,32 +9,50 @@ async function synth(notes, { noise } = {}) {
|
|
|
9
9
|
|
|
10
10
|
cmp.connect(ctx.destination)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
let noiseBuf
|
|
13
|
+
const getNoise = () => {
|
|
14
|
+
if (!noiseBuf) {
|
|
15
|
+
noiseBuf = ctx.createBuffer(1, Math.min(seconds, 10) * sampleRate, sampleRate)
|
|
16
|
+
for (let data = noiseBuf.getChannelData(0), i = 0; i < data.length; i++)
|
|
17
|
+
data[i] = Math.random() * 2 - 1
|
|
18
|
+
}
|
|
19
|
+
return noiseBuf
|
|
20
|
+
}
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
gain
|
|
22
|
+
for (const note of notes) {
|
|
23
|
+
if (!(note.frequency && note.end > note.start && note.gain > 0)) continue
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
osc.start(note.start)
|
|
24
|
-
osc.stop(note.end)
|
|
25
|
+
const gain = ctx.createGain()
|
|
25
26
|
gain.gain.setValueAtTime(note.gain, note.start)
|
|
26
27
|
gain.gain.linearRampToValueAtTime(0, note.end)
|
|
28
|
+
gain.connect(cmp)
|
|
29
|
+
|
|
30
|
+
let src
|
|
31
|
+
if (note.frequency < 0) {
|
|
32
|
+
src = ctx.createBufferSource()
|
|
33
|
+
src.buffer = getNoise()
|
|
34
|
+
src.loop = true
|
|
35
|
+
src.playbackRate.value = -note.frequency / sampleRate
|
|
36
|
+
src.connect(gain)
|
|
37
|
+
} else {
|
|
38
|
+
src = ctx.createOscillator()
|
|
39
|
+
src.setPeriodicWave(wav)
|
|
40
|
+
src.frequency.value = note.frequency
|
|
41
|
+
src.connect(gain)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
src.start(note.start)
|
|
45
|
+
src.stop(note.end)
|
|
46
|
+
src.addEventListener("ended", () => gain.disconnect())
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
if (noise) {
|
|
30
50
|
// Add some white noise to avoid QQ's voice message encoding issues
|
|
31
|
-
|
|
32
|
-
for (let data = noiseBuf.getChannelData(0), i = 0; i < data.length; i++)
|
|
33
|
-
data[i] = Math.random() * 2 - 1
|
|
51
|
+
getNoise()
|
|
34
52
|
const noiseGain = ctx.createGain()
|
|
35
53
|
noiseGain.gain.value = 0.005
|
|
36
54
|
const noiseSrc = ctx.createBufferSource()
|
|
37
|
-
noiseSrc.buffer =
|
|
55
|
+
noiseSrc.buffer = getNoise()
|
|
38
56
|
noiseSrc.loop = true
|
|
39
57
|
noiseSrc.connect(noiseGain)
|
|
40
58
|
noiseGain.connect(ctx.destination)
|
package/lib/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Computed, Context, Schema } from "koishi";
|
|
2
|
-
export declare const name = "
|
|
2
|
+
export declare const name = "musicjs";
|
|
3
3
|
export declare const inject: string[];
|
|
4
4
|
export interface Config {
|
|
5
|
-
evalCommand:
|
|
5
|
+
evalCommand: string;
|
|
6
|
+
evalReturnMethod: "expr" | "process.stdout";
|
|
6
7
|
noise: Computed<boolean>;
|
|
7
8
|
}
|
|
8
9
|
export declare const Config: Schema<Config>;
|
package/lib/index.js
CHANGED
|
@@ -3,14 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Config = exports.inject = exports.name = void 0;
|
|
4
4
|
exports.apply = apply;
|
|
5
5
|
const koishi_1 = require("koishi");
|
|
6
|
+
const promises_1 = require("fs/promises");
|
|
6
7
|
const path_1 = require("path");
|
|
7
|
-
|
|
8
|
-
exports.name = "music";
|
|
8
|
+
exports.name = "musicjs";
|
|
9
9
|
exports.inject = ["puppeteer"];
|
|
10
10
|
exports.Config = koishi_1.Schema.object({
|
|
11
|
-
evalCommand: koishi_1.Schema.
|
|
12
|
-
.
|
|
11
|
+
evalCommand: koishi_1.Schema.string()
|
|
12
|
+
.role("textarea")
|
|
13
|
+
.default("glot --language=javascript")
|
|
13
14
|
.description("用于安全执行 js 代码的指令。"),
|
|
15
|
+
evalReturnMethod: koishi_1.Schema.union([
|
|
16
|
+
koishi_1.Schema.from("process.stdout").description("process.stdout.write()(适用 glot 等指令)"),
|
|
17
|
+
koishi_1.Schema.from("expr").description("表达式(适用 eval 等指令)"),
|
|
18
|
+
])
|
|
19
|
+
.default("process.stdout")
|
|
20
|
+
.description("在 js 代码执行指令中返回结果的方式。"),
|
|
14
21
|
noise: koishi_1.Schema.computed(Boolean)
|
|
15
22
|
.default(true)
|
|
16
23
|
.description("是否添加白噪音来尝试规避 QQ 的语音编码杂音问题。"),
|
|
@@ -38,10 +45,14 @@ const gutterFunc = (f) => {
|
|
|
38
45
|
this.noteHz(baseFrequency * ratio, beats);
|
|
39
46
|
},
|
|
40
47
|
noteHz(frequency, beats) {
|
|
41
|
-
|
|
48
|
+
if (![frequency, beats].every(Number.isFinite))
|
|
49
|
+
return;
|
|
50
|
+
notes.push({ start: time, end: (time += (60 / +bpm) * beats), frequency, gain });
|
|
42
51
|
},
|
|
43
52
|
rest(beats) {
|
|
44
|
-
|
|
53
|
+
if (!Number.isFinite(beats))
|
|
54
|
+
return;
|
|
55
|
+
time += (60 / +bpm) * beats;
|
|
45
56
|
},
|
|
46
57
|
get bpm() {
|
|
47
58
|
return bpm;
|
|
@@ -88,6 +99,7 @@ const gutterFunc = (f) => {
|
|
|
88
99
|
};
|
|
89
100
|
function apply(ctx, config) {
|
|
90
101
|
ctx.i18n.define("zh", require("./locales/zh"));
|
|
102
|
+
const synthCode = (0, promises_1.readFile)((0, path_1.resolve)(__dirname, "../browser/synth.js"), "utf-8");
|
|
91
103
|
ctx
|
|
92
104
|
.command("musicjs <code:rawtext>", { strictOptions: true })
|
|
93
105
|
.action(async ({ session }, code) => {
|
|
@@ -100,9 +112,9 @@ function apply(ctx, config) {
|
|
|
100
112
|
return koishi_1.h.text(String(e));
|
|
101
113
|
}
|
|
102
114
|
let gutteredCode = `(${gutterFunc})(function($){with($){\n${code}\n}})`;
|
|
103
|
-
if (config.
|
|
115
|
+
if (config.evalReturnMethod === "process.stdout")
|
|
104
116
|
gutteredCode = `process.stdout.write(${gutteredCode})`;
|
|
105
|
-
ctx.logger.debug(config.evalCommand, gutteredCode);
|
|
117
|
+
ctx.logger.debug("%o (%s) %o", config.evalCommand, config.evalReturnMethod, gutteredCode);
|
|
106
118
|
const evalArgv = koishi_1.Argv.parse(config.evalCommand);
|
|
107
119
|
evalArgv.tokens.push({
|
|
108
120
|
content: koishi_1.h.escape(gutteredCode),
|
|
@@ -120,12 +132,18 @@ function apply(ctx, config) {
|
|
|
120
132
|
return koishi_1.h.text(data);
|
|
121
133
|
}
|
|
122
134
|
const page = await ctx.puppeteer.page();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
try {
|
|
136
|
+
const opt = {
|
|
137
|
+
noise: session.resolve(config.noise),
|
|
138
|
+
};
|
|
139
|
+
ctx.logger.debug("synth options: %o", opt);
|
|
140
|
+
const base64 = (await page.evaluate(
|
|
141
|
+
// prettier-ignore
|
|
142
|
+
`${await synthCode}; synth(${data}, ${JSON.stringify(opt)}).then(encodeWav).then(arrayBufferToBase64)`));
|
|
143
|
+
return koishi_1.h.audio("data:audio/wav;base64," + base64);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
page.close();
|
|
147
|
+
}
|
|
130
148
|
});
|
|
131
149
|
}
|
package/package.json
CHANGED