File size: 9,786 Bytes
67faadf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include "word_stream_piper.hpp"
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <random>
#include <stdexcept>
using namespace MNN;
using namespace MNN::Express;

static const int MASK_SCALES[4] = {1, 8, 64, 256};
static const int NDP = 4096, NZ = 4096;  // noise table lengths (positions wrap)

static double msSince(std::chrono::steady_clock::time_point t) {
  return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - t).count();
}

WordStreamPiper::WordStreamPiper(const std::string& encoderPath, const std::string& decoderPath, int backend, int threads) {
  gpu_ = backend == 1;
  auto type = gpu_ ? MNN_FORWARD_OPENCL : MNN_FORWARD_CPU;
  // OpenCL reads numThread as mode flags, not a thread count.
  int mode = gpu_ ? (MNN_GPU_MEMORY_BUFFER | MNN_GPU_TUNING_NONE) : threads;
  BackendConfig bc;
  exec_ = Executor::newExecutor(type, bc, mode);
  if (!exec_) throw std::runtime_error(gpu_ ? "MNN OpenCL executor unavailable" : "MNN CPU executor unavailable");
  ExecutorScope scope(exec_);
  ScheduleConfig sc; sc.type = type; sc.backupType = MNN_FORWARD_CPU; sc.numThread = mode;
  BackendConfig rbc; sc.backendConfig = &rbc;
  rt_.reset(Executor::RuntimeManager::createRuntimeManager(sc), Executor::RuntimeManager::destroy);
  if (!rt_) throw std::runtime_error("MNN runtime creation failed");
  if (gpu_) rt_->setCache(decoderPath + ".opencl-cache");
  Module::Config cfg; cfg.shapeMutable = false;  // both graphs have fixed shapes
  enc_.reset(Module::load({"input", "input_lengths", "scales", "noise_dp", "noise_z"}, {"z", "durations", "frames"},
                          encoderPath.c_str(), rt_, &cfg));
  if (!enc_) throw std::runtime_error("Encoder load failed");
  dec_.reset(Module::load({"z", "valid_mask_1", "valid_mask_8", "valid_mask_64", "valid_mask_256"}, {"output"},
                          decoderPath.c_str(), rt_, &cfg));
  if (!dec_) throw std::runtime_error("Decoder load failed");
  inIds_ = _Input({1, N_IDS}, NCHW, halide_type_of<int>());
  inLen_ = _Input({1}, NCHW, halide_type_of<int>());
  inScales_ = _Input({3}, NCHW, halide_type_of<float>());
  inNoiseDp_ = _Input({1, 2, N_IDS}, NCHW, halide_type_of<float>());
  inNoiseZ_ = _Input({1, C, N_FRAMES}, NCHW, halide_type_of<float>());
  decZ_ = _Input({1, C, DEC_FRAMES}, NCHW, halide_type_of<float>());
  for (int i = 0; i < 4; ++i) decMask_[i] = _Input({1, 1, DEC_FRAMES * MASK_SCALES[i]}, NCHW, halide_type_of<float>());
  reset(0.667f, 1.f, 0.8f, 2, 1, 0, 1234);
}

void WordStreamPiper::reset(float noise, float length, float noisew, int ctxWords, int lookahead, int xfade, uint32_t seed,

                            int firstWords, int laterWords) {
  firstWords_ = std::max(1, firstWords); laterWords_ = std::max(1, laterWords);
  scales_[0] = noise; scales_[1] = length; scales_[2] = noisew;
  ctxWords_ = std::max(0, ctxWords); look_ = std::max(0, lookahead); xfade_ = std::max(0, xfade);
  finished_ = false; ids_.clear(); wordStarts_.clear(); committed_ = 0; committedFrames_ = 0; tail_.clear();
  std::mt19937 rng(seed); std::normal_distribution<float> nd;
  noiseDp_.resize(2 * NDP); for (auto& v : noiseDp_) v = nd(rng);
  noiseZ_.resize((size_t)C * NZ); for (auto& v : noiseZ_) v = nd(rng);
}

void WordStreamPiper::addWord(const std::vector<int>& ids) {
  if (finished_) throw std::runtime_error("Stream already finished");
  wordStarts_.push_back((int)ids_.size());
  ids_.insert(ids_.end(), ids.begin(), ids.end());
}

void WordStreamPiper::finish(const std::vector<int>& eosIds) {
  if (!finished_) { ids_.insert(ids_.end(), eosIds.begin(), eosIds.end()); finished_ = true; }
}

void WordStreamPiper::encodeWindow(int ctx, int n) {
  int* x = inIds_->writeMap<int>();
  std::fill(x, x + N_IDS, 0);
  std::copy(ids_.begin() + ctx, ids_.begin() + ctx + n, x);
  inLen_->writeMap<int>()[0] = n;
  std::copy(scales_, scales_ + 3, inScales_->writeMap<float>());
  float* dp = inNoiseDp_->writeMap<float>();
  for (int c = 0; c < 2; ++c)
    for (int i = 0; i < N_IDS; ++i) dp[c * N_IDS + i] = noiseDp_[c * NDP + (ctx + i) % NDP];
  // Window frame 0 is roughly the global frame where the context starts.
  long g0 = std::max(0L, committedFrames_ - 2L * DEC_CTX * ctxWords_);
  float* nz = inNoiseZ_->writeMap<float>();
  for (int c = 0; c < C; ++c)
    for (int f = 0; f < N_FRAMES; ++f) nz[c * N_FRAMES + f] = noiseZ_[(size_t)c * NZ + (g0 + f) % NZ];
  auto out = enc_->onForward({inIds_, inLen_, inScales_, inNoiseDp_, inNoiseZ_});
  if (out.size() < 3 || out[0] == nullptr) throw std::runtime_error("Encoder returned nothing");
  const float* z = out[0]->readMap<float>();
  const float* d = out[1]->readMap<float>();
  const float* f = out[2]->readMap<float>();
  if (!z || !d || !f) throw std::runtime_error("Encoder output read failed");
  z_.assign(z, z + (size_t)C * N_FRAMES);
  dur_.assign(d, d + N_IDS);
  validFrames_ = std::min(N_FRAMES, (int)f[0]);
}

std::vector<float> WordStreamPiper::decodeRange(int start, int end, int limit) {
  std::vector<float> pcm;
  pcm.reserve((size_t)(end - start) * HOP);
  for (int s = start; s < end; s += DEC_EMIT) {
    int e = std::min(end, s + DEC_EMIT);
    int a = std::max(0, s - DEC_CTX), b = std::min(limit, e + DEC_CTX);
    int dst = DEC_CTX - (s - a), len = b - a;
    float* w = decZ_->writeMap<float>();
    std::memset(w, 0, sizeof(float) * C * DEC_FRAMES);
    for (int c = 0; c < C; ++c) std::memcpy(w + c * DEC_FRAMES + dst, z_.data() + (size_t)c * N_FRAMES + a, len * sizeof(float));
    for (int i = 0; i < 4; ++i) {
      int sc = MASK_SCALES[i];
      float* m = decMask_[i]->writeMap<float>();
      std::memset(m, 0, sizeof(float) * DEC_FRAMES * sc);
      std::fill(m + dst * sc, m + (dst + len) * sc, 1.f);
    }
    auto out = dec_->onForward({decZ_, decMask_[0], decMask_[1], decMask_[2], decMask_[3]});
    if (out.empty() || out[0] == nullptr) throw std::runtime_error("Decoder returned nothing");
    const float* y = out[0]->readMap<float>();
    if (!y) throw std::runtime_error("Decoder output read failed");
    pcm.insert(pcm.end(), y + DEC_CTX * HOP, y + DEC_CTX * HOP + (size_t)(e - s) * HOP);
  }
  return pcm;
}

std::vector<float> WordStreamPiper::step() {
  ExecutorScope scope(exec_);
  info_.clear();
  const int total = (int)ids_.size();
  if (committed_ >= total) return {};

  // Left context: back ctxWords word starts, but keep it within a third of the window.
  int ctx = committed_;
  {
    int count = 0;
    for (int i = (int)wordStarts_.size() - 1; i >= 0 && count < ctxWords_; --i)
      if (wordStarts_[i] < committed_) { ctx = wordStarts_[i]; ++count; }
    ctx = std::max(ctx, committed_ - N_IDS / 3);
  }
  const int n = std::min(N_IDS, total - ctx);
  const bool windowReachesEnd = ctx + n == total;

  // Candidate commit points: word starts after the commit point with enough look-ahead.
  const bool windowFull = total - ctx >= N_IDS;
  auto candidates = [&](int minWords) {
    std::vector<int> v;
    for (int b : wordStarts_) {
      if (b <= committed_ || b >= ctx + n) continue;
      int after = 0, words = 0;
      for (int s : wordStarts_) { if (s > b) ++after; if (s >= committed_ && s < b) ++words; }
      if (words >= minWords && after >= look_) v.push_back(b);
    }
    return v;
  };
  // Wait until enough words can be committed together (fewer, larger windows),
  // unless the window is already full or the text has ended.
  std::vector<int> ok = candidates(finished_ ? 1 : (committed_ == 0 ? firstWords_ : laterWords_));
  if (ok.empty() && (windowFull || finished_)) ok = candidates(1);
  if (ok.empty() && !finished_ && !windowFull) return {};

  auto t0 = std::chrono::steady_clock::now();
  encodeWindow(ctx, n);
  double encMs = msSince(t0);

  std::vector<int> cum(n + 1, 0);
  for (int i = 0; i < n; ++i) cum[i + 1] = cum[i] + (int)dur_[i];
  const int limit = validFrames_;
  const int f0 = std::min(cum[committed_ - ctx], limit);

  // Choose the furthest target whose audio still has decoder right context in this window.
  int target = -1, f1 = 0;
  if (finished_ && windowReachesEnd && cum[n] <= N_FRAMES) {
    target = total; f1 = limit;
  } else {
    for (int i = (int)ok.size() - 1; i >= 0; --i) {
      int fe = cum[ok[i] - ctx];
      if (fe <= limit - DEC_CTX) { target = ok[i]; f1 = fe; break; }
    }
    if (target < 0 && (finished_ || (int)ids_.size() - ctx >= N_IDS)) {
      // A single stretch too long for one window: cut at the last ID that fits.
      for (int i = n - 1; i > committed_ - ctx; --i)
        if (cum[i] <= limit - DEC_CTX) { target = ctx + i; f1 = cum[i]; break; }
    }
  }
  if (target <= committed_ || f1 <= f0) return {};

  auto t1 = std::chrono::steady_clock::now();
  int ext = std::min(f1 + xfade_, limit);
  std::vector<float> out = decodeRange(f0, ext, limit);
  double decMs = msSince(t1);
  size_t body = (size_t)(f1 - f0) * HOP;
  std::vector<float> nextTail(out.begin() + std::min(body, out.size()), out.end());
  out.resize(std::min(body, out.size()));
  if (!tail_.empty()) {
    size_t k = std::min(tail_.size(), out.size());
    for (size_t i = 0; i < k; ++i) { float r = (float)i / (float)k; out[i] = tail_[i] * (1 - r) + out[i] * r; }
  }
  tail_ = std::move(nextTail);
  if (gpu_) rt_->updateCache();

  char buf[200];
  std::snprintf(buf, sizeof buf, "ids[%d:%d] commit %d->%d frames %d->%d of %d; enc %.1f ms dec %.1f ms; %.2f s",
                ctx, ctx + n, committed_, target, f0, f1, limit, encMs, decMs, out.size() / 22050.0);
  info_ = buf;
  committed_ = target;
  committedFrames_ += f1 - f0;
  return out;
}