-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from discordjs-japan/threadpool-improvement
- Loading branch information
Showing
7 changed files
with
179 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,10 @@ | ||
import { Readable } from "node:stream"; | ||
import { StreamType, createAudioResource } from "@discordjs/voice"; | ||
import { AltJTalkConfig } from "node-altjtalk-binding"; | ||
import { Result, Task } from "./common"; | ||
import WorkerPool from "./worker-pool"; | ||
import { Synthesizer } from "./synthesizer"; | ||
import WorkerSynthesizer from "./worker-synthesizer"; | ||
|
||
export class SynthesisWorkerPool extends WorkerPool< | ||
Task, | ||
Result, | ||
AltJTalkConfig, | ||
object | ||
> { | ||
constructor(dictionary: string, model: string) { | ||
super( | ||
new URL("task", import.meta.url), | ||
{ | ||
dictionary, | ||
model, | ||
}, | ||
process.env.NUM_THREADS ? Number(process.env.NUM_THREADS) : 1, | ||
); | ||
this.on("data", ({ data }: Result) => { | ||
const resource = createAudioResource(new SynthesizedSoundStream(data), { | ||
inputType: StreamType.Raw, | ||
}); | ||
this.emit("synthesis", resource); | ||
}); | ||
} | ||
|
||
public dispatchSynthesis(inputText: string) { | ||
this.dispatchTask( | ||
{ | ||
inputText, | ||
option: { | ||
samplingFrequency: 48000, | ||
}, | ||
}, | ||
{}, | ||
); | ||
} | ||
} | ||
|
||
class SynthesizedSoundStream extends Readable { | ||
private pos: number = 0; | ||
private buf: Int16Array | null; | ||
constructor(buf: Int16Array) { | ||
super(); | ||
this.buf = buf; | ||
} | ||
_read(size: number = ((48000 * 2 * 2) / 1000) * 20) { | ||
if (!this.buf) { | ||
throw new Error("Stream ended"); | ||
} | ||
|
||
const offset = this.pos; | ||
let end = Math.ceil(size / 4); | ||
if (end + offset > this.buf.length) { | ||
end = this.buf.length - offset; | ||
} | ||
const buf = Buffer.alloc(end * 4); | ||
const dst = new Int16Array(buf.buffer); | ||
for (let i = 0; i < end; ++i) { | ||
const elem = this.buf[i + offset]; | ||
dst[i * 2] = elem; | ||
dst[i * 2 + 1] = elem; | ||
} | ||
this.push(buf); | ||
this.pos += end; | ||
if (this.pos == this.buf.length) { | ||
this.buf = null; | ||
this.push(null); | ||
} | ||
} | ||
_destroy() { | ||
this.buf = null; | ||
} | ||
if (!process.env.DICTIONARY || !process.env.MODEL) { | ||
throw new Error("Dictionary and model must be specified."); | ||
} | ||
export const synthesizer: Synthesizer = new WorkerSynthesizer( | ||
process.env.DICTIONARY, | ||
process.env.MODEL, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Message } from "discord.js"; | ||
import { SynthesisOption } from "node-altjtalk-binding"; | ||
|
||
export function createSynthesisOption(_: Message): SynthesisOption { | ||
return {}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Readable } from "node:stream"; | ||
|
||
export default class SynthesizedSoundStream extends Readable { | ||
private pos: number = 0; | ||
private buf: Int16Array | null; | ||
constructor(buf: Int16Array) { | ||
super(); | ||
this.buf = buf; | ||
} | ||
_read(size: number = ((48000 * 2 * 2) / 1000) * 20) { | ||
if (!this.buf) { | ||
throw new Error("Stream ended"); | ||
} | ||
|
||
const offset = this.pos; | ||
let end = Math.ceil(size / 4); | ||
if (end + offset > this.buf.length) { | ||
end = this.buf.length - offset; | ||
} | ||
const buf = Buffer.alloc(end * 4); | ||
const dst = new Int16Array(buf.buffer); | ||
for (let i = 0; i < end; ++i) { | ||
const elem = this.buf[i + offset]; | ||
dst[i * 2] = elem; | ||
dst[i * 2 + 1] = elem; | ||
} | ||
this.push(buf); | ||
this.pos += end; | ||
if (this.pos == this.buf.length) { | ||
this.buf = null; | ||
this.push(null); | ||
} | ||
} | ||
_destroy() { | ||
this.buf = null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { AudioResource } from "@discordjs/voice"; | ||
import { Message } from "discord.js"; | ||
|
||
export interface Synthesizer { | ||
dispatchSynthesis(message: Message): void; | ||
on<K extends keyof SynthesizerEvents>( | ||
event: K, | ||
listener: (...args: SynthesizerEvents[K]) => void, | ||
): this; | ||
} | ||
|
||
export interface SynthesizerEvents { | ||
synthesis: [resource: AudioResource, message: Message]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ | ||
import EventEmitter from "events"; | ||
import { StreamType, createAudioResource } from "@discordjs/voice"; | ||
import { Message } from "discord.js"; | ||
import { AltJTalkConfig } from "node-altjtalk-binding"; | ||
import { Result, Task } from "./common"; | ||
import { createSynthesisOption } from "./options"; | ||
import SynthesizedSoundStream from "./stream"; | ||
import { Synthesizer, SynthesizerEvents } from "./synthesizer"; | ||
import WorkerPool from "./worker-pool"; | ||
|
||
export default class WorkerSynthesizer | ||
extends EventEmitter | ||
implements Synthesizer | ||
{ | ||
workerPool: WorkerPool<Task, Result, AltJTalkConfig, Message>; | ||
|
||
constructor(dictionary: string, model: string) { | ||
super(); | ||
this.workerPool = new WorkerPool( | ||
new URL("task", import.meta.url), | ||
{ | ||
dictionary, | ||
model, | ||
}, | ||
process.env.NUM_THREADS ? Number(process.env.NUM_THREADS) : 1, | ||
); | ||
this.workerPool.on("data", ({ data }: Result, message) => { | ||
const resource = createAudioResource(new SynthesizedSoundStream(data), { | ||
inputType: StreamType.Raw, | ||
}); | ||
this.emit("synthesis", resource, message); | ||
}); | ||
} | ||
|
||
public dispatchSynthesis(message: Message) { | ||
const inputText = | ||
message.cleanContent.length > 200 | ||
? `${message.cleanContent.slice(0, 196)} 以下略` | ||
: message.cleanContent; | ||
const option = createSynthesisOption(message); | ||
|
||
this.workerPool.dispatchTask( | ||
{ | ||
inputText, | ||
option: { | ||
...option, | ||
samplingFrequency: 48000, | ||
}, | ||
}, | ||
message, | ||
); | ||
} | ||
} | ||
|
||
export default interface WorkerSynthesizer { | ||
on<K extends keyof SynthesizerEvents>( | ||
event: K, | ||
listener: (...args: SynthesizerEvents[K]) => void, | ||
): this; | ||
once<K extends keyof SynthesizerEvents>( | ||
event: K, | ||
listener: (...args: SynthesizerEvents[K]) => void, | ||
): this; | ||
off<K extends keyof SynthesizerEvents>( | ||
event: K, | ||
listener: (...args: SynthesizerEvents[K]) => void, | ||
): this; | ||
emit<K extends keyof SynthesizerEvents>( | ||
event: K, | ||
...args: SynthesizerEvents[K] | ||
): boolean; | ||
} |