diff --git a/.gitignore b/.gitignore index 4bc1577..b20bcbd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ out/ toolchain say /wip + +# Nintendo DS +.DS_Store diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..100f609 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Mac", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "macos-clang-arm64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..57bb0da --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [{ + "type": "lldb", + "request": "launch", + "name": "DECtalk for NodeJS", + "preLaunchTask": "npm: build:dev - node", + "program": "/usr/bin/env", + "args": [ + "node", + "${workspaceFolder}/node/src/index.js" + ] + }] +} diff --git a/node/.gitignore b/node/.gitignore new file mode 100644 index 0000000..b38db2f --- /dev/null +++ b/node/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +build/ diff --git a/node/binding.gyp b/node/binding.gyp new file mode 100644 index 0000000..fb32659 --- /dev/null +++ b/node/binding.gyp @@ -0,0 +1,35 @@ +{ + "targets": [ + { + "target_name": "dectalk", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ + "src/dectalk.cc", + "'../src/'+f).join(' ')\")" + ], + "include_dirs": [ + "../include", + "= 21" + } + }, + "node_modules/node-api-headers": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.8.0.tgz", + "integrity": "sha512-jfnmiKWjRAGbdD1yQS28bknFM1tbHC1oucyuMPjmkEs+kpiu76aRs40WlTmBmyEgzDM76ge1DQ7XJ3R5deiVjQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/node/package.json b/node/package.json new file mode 100644 index 0000000..0f30dc9 --- /dev/null +++ b/node/package.json @@ -0,0 +1,19 @@ +{ + "name": "hello_world", + "version": "0.0.0", + "description": "Node.js Addons Example #1", + "main": "src/index.js", + "private": true, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + }, + "scripts": { + "build": "node-gyp build", + "build:dev": "node-gyp build --debug" + }, + "gypfile": true, + "devDependencies": { + "node-api-headers": "^1.8.0" + } +} diff --git a/node/src/dectalk.cc b/node/src/dectalk.cc new file mode 100644 index 0000000..e28651a --- /dev/null +++ b/node/src/dectalk.cc @@ -0,0 +1,57 @@ +#include + +extern "C" { + #include "epsonapi.h" +} + +Napi::FunctionReference callback; +bool callbackWasSet = false; + +short* audio_callback(short *data, long length, int phoneme) { + Napi::Env env = callback.Env(); + + Napi::Buffer array = Napi::Buffer::New(env, data, length); + + callback.Call({ array }); +} + +Napi::Value setCallback(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + callback = Napi::Persistent(info[0].As()); + callbackWasSet = true; + + return Napi::String::New(env, "Done"); +} + +Napi::Value say(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + Napi::String text = info[0].As(); + Napi::Number format = info[1].As(); + + const std::string cppstr = text.ToString().Utf8Value(); + const char* input = cppstr.c_str(); + const int fmt = format.Int32Value(); + + int result = TextToSpeechStart((char*) input, NULL, fmt); + + if (result == 0) TextToSpeechSync(); + + return Napi::Number::New(env, result); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "setCallback"), + Napi::Function::New(env, setCallback)); + + exports.Set(Napi::String::New(env, "say"), + Napi::Function::New(env, say)); + + TextToSpeechInit(audio_callback, NULL); + + return exports; +} + +// Register and initialize native add-on +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/node/src/index.d.ts b/node/src/index.d.ts new file mode 100644 index 0000000..01cd9b3 --- /dev/null +++ b/node/src/index.d.ts @@ -0,0 +1,4 @@ +interface ILibDectalk { + setCallback: (callback: (buffer: Buffer) => void) => void; + say: (text: string, mode: 0 | 1) => number; +} diff --git a/node/src/index.js b/node/src/index.js new file mode 100644 index 0000000..be6d6c9 --- /dev/null +++ b/node/src/index.js @@ -0,0 +1,56 @@ +import bindings from "bindings"; +import fs from "node:fs" + +/** @type {ILibDectalk} */ +const libdectalk = bindings("dectalk"); + +const toBytes = (number) => { + return [0xff & number, + 0xff & (number >> 8), + 0xff & (number >> 16), + 0xff & (number >> 24)] +} + +/** @type {(text: string) => Promise} */ +const say = (text) => { + const format = 1; + const audioBuffer = []; + let dataLength = 0; + + /** @type {(chunkData: Buffer) => void} */ + const callback = (chunkData) => { + // copy the buffer immediately before its destroyed + audioBuffer.push(Buffer.from(chunkData)); + dataLength += chunkData.byteLength; + } + + libdectalk.setCallback(callback) + libdectalk.say(text, format); + + const sampleRate = format ? 11025 : 8000 + const bufferLength = 44 + dataLength; + + // Generate a WAV header + const header = Buffer.from([ + 0x52, 0x49, 0x46, 0x46, // RIFF + ...toBytes(bufferLength), // WAV size + 0x57, 0x41, 0x56, 0x45, // WAVE + 0x66, 0x6d, 0x74, 0x20, // fmt + 0x10, 0x00, 0x00, 0x00, // fmt chunk size + 0x01, 0x00, 0x01, 0x00, // Audio format 1=PCM & Number of channels 1=Mono + ...toBytes(sampleRate), // Sampling Frequency in Hz + ...toBytes(sampleRate * 2), // bytes per second + 0x02, 0x00, 0x10, 0x00, // 2=16-bit mono & Number of bits per sample + 0x64, 0x61, 0x74, 0x61, // data + ...toBytes(dataLength) // data size + ]) + + return Buffer.concat([header, ...audioBuffer]) +} + +const buffer = say("This is a message from Node J S"); +fs.writeFileSync("first.wav", buffer); + +const buffer2 = say("This is a second message from Node J S"); +fs.writeFileSync("second.wav", buffer2); +console.log("end of programme"); diff --git a/wasm/main.c b/wasm/main.c index c7df6e0..9cba493 100644 --- a/wasm/main.c +++ b/wasm/main.c @@ -18,7 +18,7 @@ EM_JS(void, js_audio_callback, (short* data, int length, int phoneme, int buffer }); EMSCRIPTEN_KEEPALIVE -short* audio_callback(short *data, long length) { +short* audio_callback(short *data, long length, int phoneme) { // Get current phoneme and pass it to JavaScript along with audio data int current_phoneme = last_phoneme & 0x00FF;