@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 CHANGED
@@ -9,32 +9,50 @@ async function synth(notes, { noise } = {}) {
9
9
 
10
10
  cmp.connect(ctx.destination)
11
11
 
12
- for (const note of notes) {
13
- const osc = ctx.createOscillator()
14
- const gain = ctx.createGain()
15
-
16
- osc.setPeriodicWave(wav)
17
- osc.frequency.value = note.frequency
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
- osc.connect(gain)
20
- gain.connect(cmp)
22
+ for (const note of notes) {
23
+ if (!(note.frequency && note.end > note.start && note.gain > 0)) continue
21
24
 
22
- osc.addEventListener("ended", () => osc.disconnect())
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
- const noiseBuf = ctx.createBuffer(1, Math.min(seconds, 10) * sampleRate, sampleRate)
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 = noiseBuf
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 = "music";
2
+ export declare const name = "musicjs";
3
3
  export declare const inject: string[];
4
4
  export interface Config {
5
- evalCommand: "glot" | "eval";
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
- const url_1 = require("url");
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.union(["glot", "eval"])
12
- .default("glot")
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
- notes.push({ start: time, end: (time += (beats / +bpm) * 60), frequency, gain });
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
- time += (beats / +bpm) * 60;
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.evalCommand !== "eval")
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
- await page.goto((0, url_1.pathToFileURL)((0, path_1.resolve)(__dirname, "../browser/index.html")).href);
124
- const opt = {
125
- noise: session.resolve(config.noise),
126
- };
127
- const base64 = (await page.evaluate(`synth(${data}, ${JSON.stringify(opt)}).then(encodeWav).then(arrayBufferToBase64)`));
128
- page.close().catch(() => { });
129
- return koishi_1.h.audio("data:audio/wav;base64," + base64);
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dgck81lnn/koishi-plugin-music",
3
3
  "description": "Synthesize melodies in Koishi",
4
- "version": "0.1.6",
4
+ "version": "0.2.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -1,8 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- </head>
5
- <body>
6
- <script src="synth.js"></script>
7
- </body>
8
- </html>