diff --git a/node/README.md b/node/README.md index 0922d19..1af648b 100644 --- a/node/README.md +++ b/node/README.md @@ -9,14 +9,12 @@ npm install clap-cpp ## Usage ```js -const { Model, decodeAudio } = require('clap-cpp'); +const { CLAP } = require('clap-cpp'); -const model = new Model('/path/to/model.gguf', 4); +const model = new CLAP('/path/to/model.gguf', 4); try { - console.log(model.projectionDimension, model.audioSampleRate); - const text = model.text('a piano playing'); // owned Float32Array - const { samples, sampleRate } = decodeAudio('/path/to/audio.wav'); - const audio = model.audio(samples, sampleRate); // automatically resampled + const text = model.encodeText('a piano playing'); // owned Float32Array + const audio = model.encodeAudio('/path/to/audio.wav'); // CLAP projections are unit-normalized, so their dot product is cosine. const similarity = text.reduce((sum, x, i) => sum + x * audio[i], 0); console.log(similarity); @@ -24,3 +22,13 @@ try { model.close(); // optional early release; garbage collection also frees weights } ``` + +The package exports only `CLAP`. Its inference API consists of: + +- `encodeText(textOrTokens)`: accepts a string, `Int32Array` or `number[]` token IDs. +- `encodeAudio(path, cropOffset = 0)`: decodes WAV/MP3/FLAC by contents, downmixes to mono and automatically resamples for the model. +- `encodeAudio(pcm, sampleRate = 48000, cropOffset = 0)`: accepts mono `Float32Array` samples, with automatic resampling. + +Both methods return owned `Float32Array` embeddings. Crop offsets are measured in samples; support depends on the model architecture. `close()` is idempotent; encoding after closing throws. + +Inference is synchronous and blocks the calling JavaScript thread. Use a separate model per worker for parallel execution. Tokenization, audio decoding, resampling, frame features and model metadata are not separate public APIs. diff --git a/node/bindings.cpp b/node/bindings.cpp index f896dd5..e6cfc08 100644 --- a/node/bindings.cpp +++ b/node/bindings.cpp @@ -74,29 +74,16 @@ Napi::Float32Array array(Napi::Env env, const std::vector &v) { if (!v.empty()) std::memcpy(output.Data(), v.data(), v.size() * sizeof(float)); return output; } -Napi::Object frames(Napi::Env env, const clap::AudioFrames &f) { - auto output = Napi::Object::New(env); - output.Set("values", array(env, f.values)); - output.Set("frames", Napi::Number::New(env, f.frames)); - output.Set("dimension", Napi::Number::New(env, f.dimension)); - return output; -} - -class Model : public Napi::ObjectWrap { +class CLAP : public Napi::ObjectWrap { public: static Napi::Function define(Napi::Env env) { - return DefineClass(env, "Model", { - InstanceAccessor<&Model::projection_dimension>("projectionDimension"), - InstanceAccessor<&Model::audio_sample_rate>("audioSampleRate"), - InstanceMethod<&Model::tokenize>("tokenize"), - InstanceMethod<&Model::text>("text"), - InstanceMethod<&Model::audio>("audio"), - InstanceMethod<&Model::audio_frames>("audioFrames"), - InstanceMethod<&Model::clamp3_features>("clamp3Features"), - InstanceMethod<&Model::close>("close") + return DefineClass(env, "CLAP", { + InstanceMethod<&CLAP::encode_text>("encodeText"), + InstanceMethod<&CLAP::encode_audio>("encodeAudio"), + InstanceMethod<&CLAP::close>("close") }); } - explicit Model(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) { + explicit CLAP(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) { guarded(info.Env(), [&]() -> Napi::Value { auto p = path(info[0]); int threads = info[1].IsUndefined() ? 0 : static_cast(integer(info[1], "threads", 0, std::numeric_limits::max())); @@ -109,27 +96,10 @@ class Model : public Napi::ObjectWrap { // own ObjectWrap. There is no globally shared model handle or borrowed output. std::unique_ptr model_; clap::CLAP &get() { - if (!model_) throw std::runtime_error("Model is closed"); + if (!model_) throw std::runtime_error("CLAP is closed"); return *model_; } - Napi::Value projection_dimension(const Napi::CallbackInfo &info) { - return guarded(info.Env(), [&]() -> Napi::Value { return Napi::Number::New(info.Env(), get().projection_dimension()); }); - } - Napi::Value audio_sample_rate(const Napi::CallbackInfo &info) { - return guarded(info.Env(), [&]() -> Napi::Value { return Napi::Number::New(info.Env(), get().audio_sample_rate()); }); - } - Napi::Value tokenize(const Napi::CallbackInfo &info) { - return guarded(info.Env(), [&]() -> Napi::Value { - auto &m = get(); - auto s = string(info[0], "text"); - auto n = info[1].IsUndefined() ? 512 : integer(info[1], "maxLength", 1, std::numeric_limits::max()); - auto ids = m.tokenize(s, n); - auto out = Napi::Int32Array::New(info.Env(), ids.size()); - for (std::size_t i = 0; i < ids.size(); ++i) out[i] = ids[i]; - return out; - }); - } - Napi::Value text(const Napi::CallbackInfo &info) { + Napi::Value encode_text(const Napi::CallbackInfo &info) { return guarded(info.Env(), [&]() -> Napi::Value { // Array element getters can reenter JS and close this model. Parse // completely before taking a native reference, then recheck ownership. @@ -141,28 +111,21 @@ class Model : public Napi::ObjectWrap { return array(info.Env(), get().encode_text(ids)); }); } - Napi::Value audio(const Napi::CallbackInfo &info) { + Napi::Value encode_audio(const Napi::CallbackInfo &info) { return guarded(info.Env(), [&]() -> Napi::Value { - auto &m = get(); + if (info[0].IsString()) { + const auto filename = path(info[0]); + const auto crop = info[1].IsUndefined() ? 0 : integer(info[1], "cropOffset", 0, + std::min(9007199254740991.0, static_cast(std::numeric_limits::max()))); + auto &m = get(); + auto decoded = clap::decode_audio(filename); + return array(info.Env(), m.encode_audio(decoded.samples, decoded.sample_rate, crop)); + } auto pcm = samples(info[0]); int sr = optional_rate(info[1], 48000); auto crop = info[2].IsUndefined() ? 0 : integer(info[2], "cropOffset", 0, std::min(9007199254740991.0, static_cast(std::numeric_limits::max()))); - return array(info.Env(), m.encode_audio(pcm, sr, crop)); - }); - } - Napi::Value audio_frames(const Napi::CallbackInfo &info) { - return guarded(info.Env(), [&]() -> Napi::Value { - auto &m = get(); - auto pcm = samples(info[0]); - return frames(info.Env(), m.audio_frames(pcm, optional_rate(info[1], 48000))); - }); - } - Napi::Value clamp3_features(const Napi::CallbackInfo &info) { - return guarded(info.Env(), [&]() -> Napi::Value { - auto &m = get(); - auto pcm = samples(info[0]); - return frames(info.Env(), m.clamp3_features(pcm, optional_rate(info[1], 24000))); + return array(info.Env(), get().encode_audio(pcm, sr, crop)); }); } Napi::Value close(const Napi::CallbackInfo &info) { @@ -171,22 +134,7 @@ class Model : public Napi::ObjectWrap { }; Napi::Object initialize(Napi::Env env, Napi::Object exports) { - exports.Set("Model", Model::define(env)); - exports.Set("decodeAudio", Napi::Function::New(env, [](const Napi::CallbackInfo &info) { - return guarded(info.Env(), [&]() -> Napi::Value { - auto decoded = clap::decode_audio(path(info[0])); - auto result = Napi::Object::New(info.Env()); - result.Set("samples", array(info.Env(), decoded.samples)); - result.Set("sampleRate", Napi::Number::New(info.Env(), decoded.sample_rate)); - return result; - }); - }, "decodeAudio")); - exports.Set("resampleAudio", Napi::Function::New(env, [](const Napi::CallbackInfo &info) { - return guarded(info.Env(), [&]() -> Napi::Value { - clap::Audio input{samples(info[0]), rate(info[1])}; - return array(info.Env(), clap::resample_audio(input, optional_rate(info[2], 48000)).samples); - }); - }, "resampleAudio")); + exports.Set("CLAP", CLAP::define(env)); return exports; } } // namespace diff --git a/node/index.d.ts b/node/index.d.ts index 5195bba..beb67a0 100644 --- a/node/index.d.ts +++ b/node/index.d.ts @@ -1,32 +1,13 @@ -/** Owned, flat time-major values: values.length === frames * dimension. */ -export interface AudioFrames { - values: Float32Array; - frames: number; - dimension: number; -} -export interface Audio { - samples: Float32Array; - sampleRate: number; -} /** Synchronous CPU inference. Each call blocks the calling JavaScript thread. */ -export class Model { +export class CLAP { /** threads=0 selects the native default; negative/fractional values are rejected. */ constructor(path: string, threads?: number); - readonly projectionDimension: number; - readonly audioSampleRate: number; - /** Native tokenizer; maxLength defaults to 512. Returns owned token IDs. */ - tokenize(text: string, maxLength?: number): Int32Array; - text(input: string | Int32Array | number[]): Float32Array; - /** Mono PCM. sampleRate defaults to 48000; cropOffset defaults to 0 samples. */ - audio(pcm: Float32Array, sampleRate?: number, cropOffset?: number): Float32Array; - /** Available only for architectures exposing frame encodings. Default: 48000 Hz. */ - audioFrames(pcm: Float32Array, sampleRate?: number): AudioFrames; - /** MERT-v1 all-layer mean features for CLaMP3. Default: 24000 Hz. */ - clamp3Features(pcm: Float32Array, sampleRate?: number): AudioFrames; - /** Idempotent. All other methods/properties throw after close. */ + /** Returns an owned embedding. Text is tokenized internally. */ + encodeText(input: string | Int32Array | number[]): Float32Array; + /** Mono PCM, automatically resampled. Defaults: 48000 Hz, cropOffset=0 samples. */ + encodeAudio(pcm: Float32Array, sampleRate?: number, cropOffset?: number): Float32Array; + /** Decode WAV/MP3/FLAC by contents, downmix and resample internally. */ + encodeAudio(path: string, cropOffset?: number): Float32Array; + /** Idempotent early release; encoding methods throw after close. */ close(): void; } -/** WAV/MP3/FLAC decoded by contents, downmixed to mono, original sample rate. */ -export function decodeAudio(path: string): Audio; -/** Owned output. targetRate defaults to 48000; both rates must be positive integers. */ -export function resampleAudio(pcm: Float32Array, sourceRate: number, targetRate?: number): Float32Array; diff --git a/node/package.json b/node/package.json index 2bc4ce3..f73fd96 100644 --- a/node/package.json +++ b/node/package.json @@ -6,13 +6,13 @@ "main": "index.js", "types": "index.d.ts", "engines": { "node": ">=22.0.0" }, - "files": ["index.js", "index.d.ts", "bindings.cpp", "CMakeLists.txt", "scripts", "native", "native-tests", "test/addon.test.js", "test/packaging.test.js", "test/generate-fixtures.py", "test/types.ts", "test/tsconfig.json", "README.md", "LICENSE", "THIRD_PARTY_LICENSES"], + "files": ["index.js", "index.d.ts", "bindings.cpp", "CMakeLists.txt", "scripts", "native", "native-tests", "test/addon.test.js", "test/surface.test.js", "test/packaging.test.js", "test/generate-fixtures.py", "test/types.ts", "test/tsconfig.json", "README.md", "LICENSE", "THIRD_PARTY_LICENSES"], "scripts": { "install": "cmake-js compile", "build": "cmake-js compile", "rebuild": "cmake-js rebuild", "test:fixtures": "python3 test/generate-fixtures.py", - "test": "node --test test/addon.test.js", + "test": "node --test test/addon.test.js test/surface.test.js", "test:packaging": "node --test test/packaging.test.js", "test:types": "tsc -p test/tsconfig.json", "prepack": "node scripts/vendor.js" diff --git a/node/test/addon.test.js b/node/test/addon.test.js index b248e3c..ad7165b 100644 --- a/node/test/addon.test.js +++ b/node/test/addon.test.js @@ -5,7 +5,7 @@ const path = require('node:path'); const fs = require('node:fs'); const { spawnSync } = require('node:child_process'); const root = process.env.CLAP_CPP_MODULE || '..'; -const { Model, decodeAudio, resampleAudio } = require(root); +const { CLAP } = require(root); const fixtures = path.join(__dirname, 'fixtures'); const modelPath = path.join(fixtures, 'clap.gguf'); const mertPath = path.join(fixtures, 'mert.gguf'); @@ -20,54 +20,45 @@ function near(a, b, tolerance = 2e-5) { a.forEach((x, i) => assert.ok(Math.abs(x - b[i]) < tolerance, `${i}: ${x} != ${b[i]}`)); } const pcm = Float32Array.from({ length: 24000 }, (_, i) => 0.2 * Math.sin(i * 0.031)); -test('native exports and strict constructor validation', () => { - assert.equal(typeof Model, 'function'); - for (const p of [undefined, null, 1, '', 'a\0b']) assert.throws(() => new Model(p)); - for (const n of [-1, 0.5, NaN, Infinity, 2147483648, '1', null]) assert.throws(() => new Model(modelPath, n)); - assert.throws(() => Model(modelPath)); - assert.throws(() => new Model(path.join(fixtures, 'missing.gguf'))); +test('strict constructor validation', () => { + assert.equal(typeof CLAP, 'function'); + for (const p of [undefined, null, 1, '', 'a\0b']) assert.throws(() => new CLAP(p)); + for (const n of [-1, 0.5, NaN, Infinity, 2147483648, '1', null]) assert.throws(() => new CLAP(modelPath, n)); + assert.throws(() => CLAP(modelPath)); + assert.throws(() => new CLAP(path.join(fixtures, 'missing.gguf'))); }); -test('real CLAP tokenization and text inference, with owned outputs', () => { - const m = new Model(modelPath, 1); - assert.equal(m.projectionDimension, 3); - assert.equal(m.audioSampleRate, 48000); - assert.throws(() => { m.projectionDimension = 5; }, TypeError); - const ids = m.tokenize('hello', 32); - assert.ok(ids instanceof Int32Array); - assert.ok(ids.length > 2); - assert.equal(ids[0], 0); - assert.equal(ids.at(-1), 2); - assert.ok(m.tokenize('你好 🎵', 32).length > 2); - const a = m.text('hello'); +test('real CLAP text inference with owned outputs and scalar oracle', () => { + const m = new CLAP(modelPath, 1); + const a = m.encodeText('hello'); vector(a, 3); - near(a, m.text(ids)); - near(a, m.text(Array.from(ids))); - assert.ok(Math.abs(Math.hypot(...a) - 1) < 1e-5); const oracle = path.join(fixtures, 'reference.json'); if (fs.existsSync(oracle)) { const ref = JSON.parse(fs.readFileSync(oracle, 'utf8')); - near(m.text(ref.ids), ref.values); + near(a, m.encodeText(ref.ids)); + near(a, m.encodeText(Int32Array.from(ref.ids))); + near(a, ref.values); } - const b = m.text('hello'); + vector(m.encodeText('你好 🎵'), 3); + assert.ok(Math.abs(Math.hypot(...a) - 1) < 1e-5); + const b = m.encodeText('hello'); a.fill(99); - near(b, m.text('hello')); + near(b, m.encodeText('hello')); m.close(); m.close(); vector(b, 3); - for (const f of [() => m.text('x'), () => m.tokenize('x'), () => m.audio(pcm), - () => m.audioFrames(pcm), () => m.clamp3Features(pcm), () => m.projectionDimension, - () => m.audioSampleRate]) assert.throws(f, /closed/i); + for (const f of [() => m.encodeText('x'), () => m.encodeAudio(pcm), + () => m.encodeAudio(path.join(fixtures, 'stereo.wav'))]) assert.throws(f, /closed/i); }); -test('text rejects a model closed by a reentrant token array getter', () => { +test('encodeText rejects a model closed by a reentrant token array getter', () => { const child = spawnSync(process.execPath, ['-e', ` const assert = require('node:assert/strict'); - const { Model } = require(${JSON.stringify(require.resolve(root))}); - const m = new Model(${JSON.stringify(modelPath)}, 1); + const { CLAP } = require(${JSON.stringify(require.resolve(root))}); + const m = new CLAP(${JSON.stringify(modelPath)}, 1); const ids = [0, 2]; let calls = 0; Object.defineProperty(ids, 0, { get() { ++calls; m.close(); return 0; } }); - assert.throws(() => m.text(ids), /closed/i); + assert.throws(() => m.encodeText(ids), /closed/i); assert.equal(calls, 1); - assert.throws(() => m.text('hello'), /closed/i); + assert.throws(() => m.encodeText('hello'), /closed/i); m.close(); console.log('reentrant close rejected safely'); `], { encoding: 'utf8', timeout: 30000 }); @@ -76,72 +67,36 @@ test('text rejects a model closed by a reentrant token array getter', () => { assert.equal(child.status, 0, child.stderr); assert.match(child.stdout, /reentrant close rejected safely/); }); -test('CLAP accepts actual waveforms and rejects unsupported frame APIs', () => { - const m = new Model(modelPath, 1); - vector(m.audio(pcm, 24000), 3); - vector(m.audio(pcm, 24000, 0), 3); - assert.throws(() => m.audioFrames(pcm, 24000)); - assert.throws(() => m.clamp3Features(pcm, 24000)); +test('CLAP encodes waveforms and audio paths with automatic resampling', () => { + const m = new CLAP(modelPath, 1); + vector(m.encodeAudio(pcm, 24000), 3); + near(m.encodeAudio(pcm, 24000), m.encodeAudio(pcm, 24000, 0)); + const filename = path.join(fixtures, 'stereo.wav'); + const expected = m.encodeAudio(new Float32Array(24000).fill(0.25), 24000); + near(m.encodeAudio(filename), expected); + near(m.encodeAudio(filename, 0), expected); m.close(); }); -test('MERT actual frame, pooled and CLaMP3-feature inference', () => { - const m = new Model(mertPath, 1); - assert.equal(m.audioSampleRate, 24000); - assert.equal(m.projectionDimension, 4); - const f = m.audioFrames(pcm, 24000); - assert.deepEqual(Object.keys(f).sort(), ['dimension', 'frames', 'values']); - assert.equal(f.dimension, 4); - assert.ok(f.frames > 1); - vector(f.values, f.frames * f.dimension); - const pooled = m.audio(pcm, 24000); - vector(pooled, 4); - near(pooled, Array.from({ length: 4 }, (_, d) => { - let sum = 0; for (let t = 0; t < f.frames; ++t) sum += f.values[t * 4 + d]; - return sum / f.frames; - })); - const c = m.clamp3Features(pcm, 24000); - assert.equal(c.frames, 1); assert.equal(c.dimension, 4); vector(c.values, 4); - assert.throws(() => m.tokenize('hello')); - assert.throws(() => m.text('hello')); - m.close(); vector(f.values, f.frames * 4); +test('MERT exposes pooled audio through encodeAudio', () => { + const m = new CLAP(mertPath, 1); + vector(m.encodeAudio(pcm, 24000), 4); + assert.throws(() => m.encodeText('hello')); + m.close(); }); test('strict method input validation and native exceptions', () => { - const m = new Model(modelPath, 1); + const m = new CLAP(modelPath, 1); for (const x of [undefined, null, {}, [], new Uint8Array([1]), new Float32Array(), - new Float32Array([NaN]), new Float32Array([Infinity])]) { - assert.throws(() => m.audio(x)); assert.throws(() => m.audioFrames(x)); - assert.throws(() => m.clamp3Features(x)); - } + new Float32Array([NaN]), new Float32Array([Infinity])]) assert.throws(() => m.encodeAudio(x)); for (const rate of [0, -1, 0.5, NaN, Infinity, 2147483648, '24000', null]) { - assert.throws(() => m.audio(pcm, rate)); - assert.throws(() => m.audioFrames(pcm, rate)); - assert.throws(() => m.clamp3Features(pcm, rate)); + assert.throws(() => m.encodeAudio(pcm, rate)); + } + for (const offset of [-1, 0.5, Infinity, NaN, 2 ** 53, '0', null]) { + assert.throws(() => m.encodeAudio(pcm, 24000, offset)); + assert.throws(() => m.encodeAudio(path.join(fixtures, 'stereo.wav'), offset)); } - for (const offset of [-1, 0.5, Infinity, NaN, 2 ** 53, '0', null]) assert.throws(() => m.audio(pcm, 24000, offset)); for (const x of [null, undefined, {}, 3, [], [-1], [0.5], [NaN], [Infinity], [2147483648], - ['1'], new Float32Array([1]), new Int32Array([-1])]) assert.throws(() => m.text(x)); - assert.throws(() => m.text([999999])); - for (const x of [null, 1, undefined]) assert.throws(() => m.tokenize(x)); - for (const n of [0, -1, 0.5, NaN, Infinity, 2 ** 53, '1', null]) assert.throws(() => m.tokenize('x', n)); + ['1'], new Float32Array([1]), new Int32Array([-1])]) assert.throws(() => m.encodeText(x)); + assert.throws(() => m.encodeText([999999])); + for (const p of ['', 'a\0b', path.join(fixtures, 'missing.wav'), modelPath]) assert.throws(() => m.encodeAudio(p)); m.close(); }); -test('decode mono/stereo WAV, resample and retain owned PCM', () => { - const a = decodeAudio(path.join(fixtures, 'stereo.wav')); - assert.equal(a.sampleRate, 24000); - vector(a.samples, 24000); - assert.ok(Math.abs(a.samples[0] - 0.25) < 1e-4); - const same = resampleAudio(a.samples, 24000, 24000); - assert.deepEqual(same, a.samples); - same[0] = 99; assert.notEqual(a.samples[0], 99); - const b = resampleAudio(pcm, 24000, 48000); - vector(b, 48000); - vector(resampleAudio(pcm, 24000), 48000); - vector(resampleAudio(pcm, 24000, 16000), 16000); - assert.equal(resampleAudio(new Float32Array([0.5]), 24000, 48000).length, 2); -}); -test('audio utility strict validation and decoder error translation', () => { - for (const p of [undefined, null, 1, '', 'a\0b', path.join(fixtures, 'missing.wav'), modelPath]) assert.throws(() => decodeAudio(p)); - for (const x of [null, [], new Float64Array([1]), new Float32Array(), new Float32Array([NaN])]) assert.throws(() => resampleAudio(x, 24000)); - for (const r of [undefined, null, 0, -1, 0.5, Infinity, NaN, 2147483648, '24000']) assert.throws(() => resampleAudio(pcm, r)); - for (const r of [null, 0, -1, 0.5, Infinity, NaN, 2147483648, '24000']) assert.throws(() => resampleAudio(pcm, 24000, r)); -}); diff --git a/node/test/surface.test.js b/node/test/surface.test.js new file mode 100644 index 0000000..335b48d --- /dev/null +++ b/node/test/surface.test.js @@ -0,0 +1,11 @@ +'use strict'; +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const binding = require(process.env.CLAP_CPP_MODULE || '..'); + +test('only CLAP and its encoding/lifecycle methods are public', () => { + assert.deepEqual(Object.keys(binding), ['CLAP']); + assert.equal(binding.CLAP.name, 'CLAP'); + assert.deepEqual(Object.getOwnPropertyNames(binding.CLAP.prototype).sort(), + ['constructor', 'encodeAudio', 'encodeText', 'close'].sort()); +}); diff --git a/node/test/types.ts b/node/test/types.ts index 08ffd2b..b8aa54b 100644 --- a/node/test/types.ts +++ b/node/test/types.ts @@ -1,19 +1,26 @@ -import { Model, decodeAudio, resampleAudio, AudioFrames } from '..'; -const model = new Model('model.gguf', 1); -const ids: Int32Array = model.tokenize('hello'); -const embedding: Float32Array = model.text(ids); -model.text('hello'); model.text([0, 2]); -const decoded = decodeAudio('clip.wav'); -const pcm: Float32Array = resampleAudio(decoded.samples, decoded.sampleRate); -const frames: AudioFrames = model.audioFrames(pcm); -model.audio(pcm, 48000, 0); model.clamp3Features(pcm, 24000); -const dimension: number = model.projectionDimension; -const sampleRate: number = model.audioSampleRate; +import { CLAP } from '..'; +const model = new CLAP('model.gguf', 1); +const embedding: Float32Array = model.encodeText('hello'); +model.encodeText([0, 2]); +model.encodeText(new Int32Array([0, 2])); +model.encodeAudio(new Float32Array(24000), 24000, 0); +model.encodeAudio('clip.wav'); +model.encodeAudio('clip.flac', 0); model.close(); -// @ts-expect-error read-only native property -model.projectionDimension = 2; // @ts-expect-error PCM must have F32 storage -model.audio(new Float64Array(1)); +model.encodeAudio(new Float64Array(1)); // @ts-expect-error tokens cannot be strings -model.text(['hello']); -void [embedding, frames, dimension, sampleRate]; +model.encodeText(['hello']); +// @ts-expect-error old method is not public +model.text('hello'); +// @ts-expect-error tokenizer is not public +model.tokenize('hello'); +// @ts-expect-error model metadata is not public +model.projectionDimension; +// @ts-expect-error frame encodings are not public +model.audioFrames(new Float32Array(1)); +// @ts-expect-error utility is not exported +import { decodeAudio } from '..'; +// @ts-expect-error old class is not exported +import { Model } from '..'; +void embedding;