non-streaming-tts.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. /** @typedef {import('./types').OfflineTtsConfig} OfflineTtsConfig */
  2. /** @typedef {import('./types').OfflineTtsHandle} OfflineTtsHandle */
  3. /** @typedef {import('./types').TtsRequest} TtsRequest */
  4. /** @typedef {import('./types').GeneratedAudio} GeneratedAudio */
  5. const addon = require('./addon.js');
  6. /**
  7. * Internal symbol to mark async-created TTS instances.
  8. */
  9. const kFromAsyncFactory = Symbol('OfflineTts.fromAsync');
  10. class GenerationConfig {
  11. constructor(opts = {}) {
  12. Object.assign(this, opts);
  13. }
  14. }
  15. class OfflineTts {
  16. /**
  17. * Constructor (sync path).
  18. *
  19. * Users call:
  20. * new OfflineTts(config)
  21. *
  22. * Async factory calls this with an internal descriptor.
  23. *
  24. * @param {OfflineTtsConfig|Object} configOrInternal
  25. */
  26. constructor(configOrInternal) {
  27. if (configOrInternal && typeof configOrInternal === 'object' &&
  28. configOrInternal[kFromAsyncFactory]) {
  29. // ----- async factory path -----
  30. this.handle = configOrInternal.handle;
  31. this.config = configOrInternal.config;
  32. } else {
  33. // ----- sync constructor path -----
  34. this.config = configOrInternal;
  35. this.handle = addon.createOfflineTts(this.config);
  36. }
  37. // Common initialization
  38. this.numSpeakers = addon.getOfflineTtsNumSpeakers(this.handle);
  39. this.sampleRate = addon.getOfflineTtsSampleRate(this.handle);
  40. }
  41. /**
  42. * Create an OfflineTts asynchronously (non-blocking).
  43. * @param {OfflineTtsConfig} config
  44. * @returns {Promise<OfflineTts>}
  45. */
  46. static async createAsync(config) {
  47. const handle = await addon.createOfflineTtsAsync(config);
  48. return new OfflineTts({
  49. [kFromAsyncFactory]: true,
  50. handle,
  51. config,
  52. });
  53. }
  54. /**
  55. * Generate audio synchronously.
  56. * @param {TtsRequest} obj
  57. * @returns {GeneratedAudio}
  58. */
  59. generate(obj) {
  60. if (!obj || typeof obj !== 'object') {
  61. throw new TypeError('generate() expects an object');
  62. }
  63. // If generationConfig is present, use new API
  64. if (obj.generationConfig !== undefined) {
  65. return addon.offlineTtsGenerateWithConfig(this.handle, obj);
  66. }
  67. // Fallback to legacy path
  68. return addon.offlineTtsGenerate(this.handle, obj);
  69. }
  70. /**
  71. * Generate audio asynchronously with optional generationConfig and progress
  72. * callback
  73. *
  74. * The progress callback receives streaming audio chunks.
  75. *
  76. * @param {TtsRequest & { generationConfig?: GenerationConfig, onProgress?: (info: {
  77. * samples: Float32Array, progress: number }) => number | boolean | void
  78. * }} obj
  79. * @returns {Promise<GeneratedAudio>}
  80. */
  81. generateAsync(obj) {
  82. const {onProgress, ...rest} = obj;
  83. const hasConfig = obj.generationConfig !== undefined;
  84. const fn = hasConfig ? addon.offlineTtsGenerateAsyncWithConfig :
  85. addon.offlineTtsGenerateAsync;
  86. return fn(this.handle, {
  87. ...rest,
  88. callback: typeof onProgress === 'function' ?
  89. (info) => {
  90. const ret = onProgress(info);
  91. return ret === 0 || ret === false ? 0 : 1;
  92. } :
  93. undefined,
  94. });
  95. }
  96. }
  97. module.exports = {
  98. OfflineTts,
  99. GenerationConfig,
  100. }