Wideband autonomous SDR analysis engine forked from sdr-visual-suite
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

129 satır
3.5KB

  1. // ring-player-processor.js — AudioWorklet processor for LiveListenWS
  2. // Runs on the audio rendering thread, immune to main-thread blocking.
  3. class RingPlayerProcessor extends AudioWorkletProcessor {
  4. constructor(options) {
  5. super();
  6. const ch = options.processorOptions?.channels || 1;
  7. this._channels = ch;
  8. // 500ms ring buffer at sampleRate
  9. this._ringSize = Math.ceil(sampleRate * ch * 0.5);
  10. this._ring = new Float32Array(this._ringSize);
  11. this._writePos = 0;
  12. this._readPos = 0;
  13. this._started = false;
  14. this._fadeGain = 1.0;
  15. this._startThreshold = Math.ceil(sampleRate * ch * 0.2); // 200ms
  16. this.port.onmessage = (e) => {
  17. if (e.data.type === 'pcm') {
  18. this._pushSamples(e.data.samples);
  19. }
  20. };
  21. }
  22. _available() {
  23. return (this._writePos - this._readPos + this._ringSize) % this._ringSize;
  24. }
  25. _pushSamples(float32arr) {
  26. const ring = this._ring;
  27. const size = this._ringSize;
  28. const n = float32arr.length;
  29. // Overrun: advance read cursor to make room
  30. const used = this._available();
  31. const free = size - used - 1;
  32. if (n > free) {
  33. this._readPos = (this._readPos + (n - free)) % size;
  34. }
  35. let w = this._writePos;
  36. // Fast path: contiguous write
  37. if (w + n <= size) {
  38. ring.set(float32arr, w);
  39. w += n;
  40. if (w >= size) w = 0;
  41. } else {
  42. // Wrap around
  43. const first = size - w;
  44. ring.set(float32arr.subarray(0, first), w);
  45. ring.set(float32arr.subarray(first), 0);
  46. w = n - first;
  47. }
  48. this._writePos = w;
  49. if (!this._started && this._available() >= this._startThreshold) {
  50. this._started = true;
  51. }
  52. }
  53. process(inputs, outputs, parameters) {
  54. const output = outputs[0];
  55. const outLen = output[0]?.length || 128;
  56. const ch = this._channels;
  57. const ring = this._ring;
  58. const size = this._ringSize;
  59. if (!this._started) {
  60. for (let c = 0; c < output.length; c++) output[c].fill(0);
  61. return true;
  62. }
  63. const need = outLen * ch;
  64. const avail = this._available();
  65. if (avail < need) {
  66. // Underrun: play what we have with fade-out, fill rest with silence
  67. const have = avail;
  68. const haveFrames = Math.floor(have / ch);
  69. const fadeLen = Math.min(64, haveFrames);
  70. const fadeStart = haveFrames - fadeLen;
  71. let r = this._readPos;
  72. for (let i = 0; i < haveFrames; i++) {
  73. let env = this._fadeGain;
  74. if (i >= fadeStart) {
  75. env *= 1.0 - (i - fadeStart) / fadeLen;
  76. }
  77. for (let c = 0; c < ch; c++) {
  78. if (c < output.length) {
  79. output[c][i] = ring[r] * env;
  80. }
  81. r = (r + 1) % size;
  82. }
  83. }
  84. this._readPos = r;
  85. // Silence the rest
  86. for (let i = haveFrames; i < outLen; i++) {
  87. for (let c = 0; c < output.length; c++) output[c][i] = 0;
  88. }
  89. this._fadeGain = 0;
  90. return true;
  91. }
  92. // Normal path
  93. let r = this._readPos;
  94. const fadeInLen = (this._fadeGain < 1.0) ? Math.min(64, outLen) : 0;
  95. for (let i = 0; i < outLen; i++) {
  96. let env = 1.0;
  97. if (i < fadeInLen) {
  98. env = this._fadeGain + (1.0 - this._fadeGain) * (i / fadeInLen);
  99. }
  100. for (let c = 0; c < ch; c++) {
  101. if (c < output.length) {
  102. output[c][i] = ring[r] * env;
  103. }
  104. r = (r + 1) % size;
  105. }
  106. }
  107. this._readPos = r;
  108. this._fadeGain = 1.0;
  109. return true;
  110. }
  111. }
  112. registerProcessor('ring-player-processor', RingPlayerProcessor);