dspx 1.3.7 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,6 +38,10 @@
38
38
  #include "adapters/IntegratorStage.h" // Integrator stage
39
39
  #include "adapters/SnrStage.h" // SNR stage
40
40
  #include "adapters/KalmanFilterStage.h" // Kalman Filter stage
41
+ #include "adapters/TimeAlignmentStage.h" // Time Alignment stage
42
+
43
+ #include <iostream>
44
+ #include <thread> // For std::this_thread in debug code
41
45
 
42
46
  namespace dsp
43
47
  {
@@ -51,6 +55,12 @@ namespace dsp
51
55
  #include <cstdlib>
52
56
  #include "utils/Toon.h"
53
57
 
58
+ // Helper function to check debug flag
59
+ inline bool isDebugEnabled()
60
+ {
61
+ return std::getenv("DSPX_DEBUG") != nullptr;
62
+ }
63
+
54
64
  // SIMD optimizations for timestamp interpolation
55
65
  // Priority: AVX2 (8-wide) > SSE (4-wide) > NEON (4-wide) > Scalar
56
66
  #if defined(__AVX2__) || (defined(_MSC_VER) && defined(__AVX2__))
@@ -108,13 +118,22 @@ namespace dsp
108
118
  DspPipeline::DspPipeline(const Napi::CallbackInfo &info)
109
119
  : Napi::ObjectWrap<DspPipeline>(info)
110
120
  {
111
- // std::cout << "[DEBUG] DspPipeline::Constructor - this=" << this
112
- // << ", creating pipeline" << std::endl;
121
+ if (isDebugEnabled())
122
+ {
123
+ std::cout << "[DEBUG] DspPipeline::Constructor - this=" << this
124
+ << ", creating pipeline" << std::endl;
125
+ }
113
126
  // Initialize the lock
114
127
  m_isBusy = std::make_shared<std::atomic<bool>>(false);
115
- // std::cout << "[DEBUG] DspPipeline::Constructor - m_isBusy=" << m_isBusy.get() << std::endl;
128
+ if (isDebugEnabled())
129
+ {
130
+ std::cout << "[DEBUG] DspPipeline::Constructor - m_isBusy=" << m_isBusy.get() << std::endl;
131
+ }
116
132
  InitializeStageFactories();
117
- // std::cout << "[DEBUG] DspPipeline::Constructor - complete, this=" << this << std::endl;
133
+ if (isDebugEnabled())
134
+ {
135
+ std::cout << "[DEBUG] DspPipeline::Constructor - complete, this=" << this << std::endl;
136
+ }
118
137
  }
119
138
 
120
139
  /**
@@ -1165,6 +1184,57 @@ namespace dsp
1165
1184
  return std::make_unique<adapters::KalmanFilterStage>(
1166
1185
  dimensions, processNoise, measurementNoise, initialError);
1167
1186
  };
1187
+
1188
+ // ===================================================================
1189
+ // Time Alignment Stage
1190
+ // ===================================================================
1191
+ m_stageFactories["timeAlignment"] = [](const Napi::Object &params)
1192
+ {
1193
+ float targetSampleRate = params.Has("targetSampleRate")
1194
+ ? params.Get("targetSampleRate").As<Napi::Number>().FloatValue()
1195
+ : 1000.0f;
1196
+
1197
+ adapters::InterpolationMethod interpMethod = adapters::InterpolationMethod::LINEAR;
1198
+ if (params.Has("interpolationMethod"))
1199
+ {
1200
+ std::string method = params.Get("interpolationMethod").As<Napi::String>().Utf8Value();
1201
+ if (method == "cubic")
1202
+ interpMethod = adapters::InterpolationMethod::CUBIC;
1203
+ else if (method == "sinc")
1204
+ interpMethod = adapters::InterpolationMethod::SINC;
1205
+ }
1206
+
1207
+ adapters::GapPolicy gapPolicy = adapters::GapPolicy::INTERPOLATE;
1208
+ if (params.Has("gapPolicy"))
1209
+ {
1210
+ std::string policy = params.Get("gapPolicy").As<Napi::String>().Utf8Value();
1211
+ if (policy == "error")
1212
+ gapPolicy = adapters::GapPolicy::ERROR;
1213
+ else if (policy == "zero-fill")
1214
+ gapPolicy = adapters::GapPolicy::ZERO_FILL;
1215
+ else if (policy == "hold")
1216
+ gapPolicy = adapters::GapPolicy::HOLD;
1217
+ else if (policy == "extrapolate")
1218
+ gapPolicy = adapters::GapPolicy::EXTRAPOLATE;
1219
+ }
1220
+
1221
+ float gapThreshold = params.Has("gapThreshold")
1222
+ ? params.Get("gapThreshold").As<Napi::Number>().FloatValue()
1223
+ : 1.5f;
1224
+
1225
+ adapters::DriftCompensation driftComp = adapters::DriftCompensation::NONE;
1226
+ if (params.Has("driftCompensation"))
1227
+ {
1228
+ std::string drift = params.Get("driftCompensation").As<Napi::String>().Utf8Value();
1229
+ if (drift == "regression")
1230
+ driftComp = adapters::DriftCompensation::REGRESSION;
1231
+ else if (drift == "pll")
1232
+ driftComp = adapters::DriftCompensation::PLL;
1233
+ }
1234
+
1235
+ return std::make_unique<adapters::TimeAlignmentStage>(
1236
+ targetSampleRate, interpMethod, gapPolicy, gapThreshold, driftComp);
1237
+ };
1168
1238
  }
1169
1239
 
1170
1240
  /**
@@ -1174,26 +1244,38 @@ namespace dsp
1174
1244
  Napi::Value DspPipeline::AddStage(const Napi::CallbackInfo &info)
1175
1245
  {
1176
1246
  Napi::Env env = info.Env();
1177
- // std::cout << "[DEBUG] DspPipeline::AddStage - this=" << this << std::endl;
1247
+ if (isDebugEnabled())
1248
+ {
1249
+ std::cout << "[DEBUG] DspPipeline::AddStage - this=" << this << std::endl;
1250
+ }
1178
1251
 
1179
1252
  // Check if pipeline is disposed
1180
1253
  if (m_disposed)
1181
1254
  {
1182
- // std::cout << "[DEBUG] AddStage - pipeline disposed, this=" << this << std::endl;
1255
+ if (isDebugEnabled())
1256
+ {
1257
+ std::cout << "[DEBUG] AddStage - pipeline disposed, this=" << this << std::endl;
1258
+ }
1183
1259
  Napi::Error::New(env, "Pipeline is disposed").ThrowAsJavaScriptException();
1184
1260
  return env.Undefined();
1185
1261
  }
1186
1262
 
1187
1263
  if (*m_isBusy)
1188
1264
  {
1189
- // std::cout << "[DEBUG] AddStage - pipeline busy, this=" << this << std::endl;
1265
+ if (isDebugEnabled())
1266
+ {
1267
+ std::cout << "[DEBUG] AddStage - pipeline busy, this=" << this << std::endl;
1268
+ }
1190
1269
  Napi::Error::New(env, "Cannot add stage while processing").ThrowAsJavaScriptException();
1191
1270
  return env.Undefined();
1192
1271
  }
1193
1272
 
1194
1273
  // 1. Get arguments from TypeScript
1195
1274
  std::string stageName = info[0].As<Napi::String>();
1196
- // std::cout << "[DEBUG] AddStage - stageName=" << stageName << ", this=" << this << std::endl;
1275
+ if (isDebugEnabled())
1276
+ {
1277
+ std::cout << "[DEBUG] AddStage - stageName=" << stageName << ", this=" << this << std::endl;
1278
+ }
1197
1279
  Napi::Object params = info[1].As<Napi::Object>();
1198
1280
 
1199
1281
  // 2. Look up the stage factory in the map
@@ -1234,12 +1316,18 @@ namespace dsp
1234
1316
  Napi::Value DspPipeline::AddFilterStage(const Napi::CallbackInfo &info)
1235
1317
  {
1236
1318
  Napi::Env env = info.Env();
1237
- // std::cout << "[DEBUG] DspPipeline::AddFilterStage - this=" << this << std::endl;
1319
+ if (isDebugEnabled())
1320
+ {
1321
+ std::cout << "[DEBUG] DspPipeline::AddFilterStage - this=" << this << std::endl;
1322
+ }
1238
1323
 
1239
1324
  // Check if pipeline is disposed
1240
1325
  if (m_disposed)
1241
1326
  {
1242
- // std::cout << "[DEBUG] AddFilterStage - pipeline disposed, this=" << this << std::endl;
1327
+ if (isDebugEnabled())
1328
+ {
1329
+ std::cout << "[DEBUG] AddFilterStage - pipeline disposed, this=" << this << std::endl;
1330
+ }
1243
1331
  Napi::Error::New(env, "Pipeline is disposed").ThrowAsJavaScriptException();
1244
1332
  return env.Undefined();
1245
1333
  }
@@ -1875,7 +1963,10 @@ namespace dsp
1875
1963
  m_timestampRef(std::move(timestampRef)),
1876
1964
  m_busyLock(busyLock)
1877
1965
  {
1878
- // std::cout << "[DEBUG] ProcessWorker::ProcessWorker - this=" << this << std::endl;
1966
+ if (isDebugEnabled())
1967
+ {
1968
+ std::cout << "[DEBUG] ProcessWorker::ProcessWorker - this=" << this << std::endl;
1969
+ }
1879
1970
  m_stageCount = m_stages.size();
1880
1971
  m_stageTypes.reserve(m_stageCount);
1881
1972
  for (const auto &stage : m_stages)
@@ -1888,11 +1979,14 @@ namespace dsp
1888
1979
  // This runs on a worker thread (not blocking the event loop)
1889
1980
  void Execute() override
1890
1981
  {
1891
- // std::cout << "[DEBUG] ProcessWorker::Execute - START, this=" << this
1892
- // << ", data=" << m_data << ", numSamples=" << m_numSamples
1893
- // << ", channels=" << m_channels << std::endl;
1894
- // std::cout << "[WORKER-" << std::this_thread::get_id() << "] Execute START (stages="
1895
- // << m_stages.size() << ")" << std::endl;
1982
+ if (isDebugEnabled())
1983
+ {
1984
+ std::cout << "[DEBUG] ProcessWorker::Execute - START, this=" << this
1985
+ << ", data=" << m_data << ", numSamples=" << m_numSamples
1986
+ << ", channels=" << m_channels << std::endl;
1987
+ std::cout << "[WORKER-" << std::this_thread::get_id() << "] Execute START (stages="
1988
+ << m_stages.size() << ")" << std::endl;
1989
+ }
1896
1990
 
1897
1991
  // CRITICAL FIX: Use a unique_ptr for timestamp ownership
1898
1992
  std::vector<float> generatedTimestamps;
@@ -1903,7 +1997,10 @@ namespace dsp
1903
1997
  // 1. Generate Timestamps if missing
1904
1998
  if (m_timestamps == nullptr)
1905
1999
  {
1906
- // std::cout << "[DEBUG] Execute - generating timestamps, sampleRate=" << m_sampleRate << std::endl;
2000
+ if (isDebugEnabled())
2001
+ {
2002
+ std::cout << "[DEBUG] Execute - generating timestamps, sampleRate=" << m_sampleRate << std::endl;
2003
+ }
1907
2004
 
1908
2005
  generatedTimestamps.resize(m_numSamples);
1909
2006
  double dt = (m_sampleRate > 0.0) ? (1000.0 / m_sampleRate) : 1.0;
@@ -1914,7 +2011,10 @@ namespace dsp
1914
2011
  }
1915
2012
 
1916
2013
  m_timestamps = generatedTimestamps.data();
1917
- // std::cout << "[DEBUG] Execute - timestamps generated, addr=" << m_timestamps << std::endl;
2014
+ if (isDebugEnabled())
2015
+ {
2016
+ std::cout << "[DEBUG] Execute - timestamps generated, addr=" << m_timestamps << std::endl;
2017
+ }
1918
2018
  }
1919
2019
 
1920
2020
  // 2. Process the buffer through all stages
@@ -1925,23 +2025,34 @@ namespace dsp
1925
2025
 
1926
2026
  const bool debugStageDumps = std::getenv("DSPX_DEBUG_STAGE_DUMPS") != nullptr;
1927
2027
 
1928
- // std::cout << "[DEBUG] Execute - processing through " << m_stages.size() << " stages" << std::endl;
2028
+ if (isDebugEnabled())
2029
+ {
2030
+ std::cout << "[DEBUG] Execute - processing through " << m_stages.size() << " stages" << std::endl;
2031
+ }
1929
2032
  for (size_t stageIdx = 0; stageIdx < m_stages.size(); ++stageIdx)
1930
2033
  {
1931
2034
  const auto &stage = m_stages[stageIdx];
1932
2035
 
1933
- // std::cout << "[DEBUG] Execute - stage " << stageIdx << ", type="
1934
- // << stage->getType() << ", addr=" << stage.get()
1935
- // << ", isResizing=" << stage->isResizing() << std::endl;
2036
+ if (isDebugEnabled())
2037
+ {
2038
+ std::cout << "[DEBUG] Execute - stage " << stageIdx << ", type="
2039
+ << stage->getType() << ", addr=" << stage.get()
2040
+ << ", isResizing=" << stage->isResizing() << std::endl;
2041
+ }
1936
2042
 
1937
2043
  if (stage->isResizing())
1938
2044
  {
1939
- // Calculate output size
1940
- size_t outputSize = stage->calculateOutputSize(currentSize);
1941
- float *outputBuffer = new float[outputSize];
2045
+ // Calculate output size estimate
2046
+ size_t estimatedSize = stage->calculateOutputSize(currentSize);
1942
2047
 
1943
- // std::cout << "[DEBUG] Execute - allocated output buffer, size=" << outputSize
1944
- // << ", addr=" << outputBuffer << std::endl;
2048
+ // Allocate buffer with estimate
2049
+ float *outputBuffer = new float[estimatedSize];
2050
+
2051
+ if (isDebugEnabled())
2052
+ {
2053
+ std::cout << "[DEBUG] Execute - allocated output buffer, size=" << estimatedSize
2054
+ << ", addr=" << outputBuffer << std::endl;
2055
+ }
1945
2056
 
1946
2057
  // CRITICAL: Save the PREVIOUS size before processResizing updates currentSize
1947
2058
  size_t prevSize = currentSize;
@@ -1951,14 +2062,34 @@ namespace dsp
1951
2062
  outputBuffer, actualOutputSize,
1952
2063
  m_channels, m_timestamps);
1953
2064
 
1954
- // std::cout << "[DEBUG] Execute - stage " << stageIdx << " resized: "
1955
- // << prevSize << " -> " << actualOutputSize // Use prevSize!
1956
- // << ", buffer=" << outputBuffer << std::endl;
2065
+ // Safety check: if actual size exceeds estimate, reallocate
2066
+ if (actualOutputSize > estimatedSize)
2067
+ {
2068
+ std::cerr << "[WARNING] Stage calculateOutputSize() underestimated: "
2069
+ << "estimated=" << estimatedSize
2070
+ << ", actual=" << actualOutputSize << std::endl;
2071
+
2072
+ // Reallocate with correct size and copy data
2073
+ float *newBuffer = new float[actualOutputSize];
2074
+ std::memcpy(newBuffer, outputBuffer, estimatedSize * sizeof(float));
2075
+ delete[] outputBuffer;
2076
+ outputBuffer = newBuffer;
2077
+ }
2078
+
2079
+ if (isDebugEnabled())
2080
+ {
2081
+ std::cout << "[DEBUG] Execute - stage " << stageIdx << " resized: "
2082
+ << prevSize << " -> " << actualOutputSize // Use prevSize!
2083
+ << ", buffer=" << outputBuffer << std::endl;
2084
+ }
1957
2085
 
1958
2086
  // Free previous temp buffer if we owned it
1959
2087
  if (usingTempBuffer && tempBuffer != nullptr)
1960
2088
  {
1961
- // std::cout << "[DEBUG] Execute - freeing previous temp buffer=" << tempBuffer << std::endl;
2089
+ if (isDebugEnabled())
2090
+ {
2091
+ std::cout << "[DEBUG] Execute - freeing previous temp buffer=" << tempBuffer << std::endl;
2092
+ }
1962
2093
  delete[] tempBuffer;
1963
2094
  }
1964
2095
 
@@ -1975,15 +2106,21 @@ namespace dsp
1975
2106
  int outputChannels = stage->getOutputChannels();
1976
2107
  if (outputChannels > 0)
1977
2108
  {
1978
- // std::cout << "[DEBUG] Execute - channels changed: " << m_channels
1979
- // << " -> " << outputChannels << std::endl;
2109
+ if (isDebugEnabled())
2110
+ {
2111
+ std::cout << "[DEBUG] Execute - channels changed: " << m_channels
2112
+ << " -> " << outputChannels << std::endl;
2113
+ }
1980
2114
  m_channels = outputChannels;
1981
2115
  }
1982
2116
 
1983
2117
  // Re-interpolate timestamps if needed
1984
2118
  if (m_timestamps != nullptr)
1985
2119
  {
1986
- // std::cout << "[DEBUG] Execute - reinterpolating timestamps" << std::endl;
2120
+ if (isDebugEnabled())
2121
+ {
2122
+ std::cout << "[DEBUG] Execute - reinterpolating timestamps" << std::endl;
2123
+ }
1987
2124
 
1988
2125
  double timeScale = stage->getTimeScaleFactor();
1989
2126
  size_t numOutputSamples = actualOutputSize / m_channels;
@@ -2008,21 +2145,31 @@ namespace dsp
2008
2145
  allocatedTimestamps = std::move(newTimestamps);
2009
2146
  m_timestamps = allocatedTimestamps->data();
2010
2147
 
2011
- // std::cout << "[DEBUG] Execute - timestamps reinterpolated (SIMD), new addr="
2012
- // << m_timestamps << std::endl;
2148
+ if (isDebugEnabled())
2149
+ {
2150
+ std::cout << "[DEBUG] Execute - timestamps reinterpolated (SIMD), new addr="
2151
+ << m_timestamps << std::endl;
2152
+ }
2013
2153
  }
2014
2154
  }
2015
2155
  else
2016
2156
  {
2017
2157
  // In-place processing
2018
- // std::cout << "[DEBUG] Execute - stage " << stageIdx << " in-place processing" << std::endl;
2158
+ if (isDebugEnabled())
2159
+ {
2160
+ std::cout << "[DEBUG] Execute - stage " << stageIdx << " in-place processing, buffer="
2161
+ << currentBuffer << ", size=" << currentSize << std::endl;
2162
+ }
2019
2163
  stage->process(currentBuffer, currentSize, m_channels, m_timestamps);
2020
2164
 
2021
2165
  if (debugStageDumps)
2022
2166
  {
2023
2167
  const char *stype = stage->getType();
2024
2168
  size_t toShow = std::min<size_t>(8, currentSize);
2025
- // std::cout << "[DUMP] after '" << stype << "':";
2169
+ if (isDebugEnabled())
2170
+ {
2171
+ std::cout << "[DUMP] after '" << stype << "':";
2172
+ }
2026
2173
  for (size_t i = 0; i < toShow; ++i)
2027
2174
  {
2028
2175
  std::cout << (i == 0 ? ' ' : ',') << currentBuffer[i];
@@ -2036,21 +2183,30 @@ namespace dsp
2036
2183
  m_finalSize = currentSize;
2037
2184
  m_ownsBuffer = usingTempBuffer;
2038
2185
 
2039
- // std::cout << "[DEBUG] Execute - COMPLETE, finalBuffer=" << m_finalBuffer
2040
- // << ", finalSize=" << m_finalSize << ", ownsBuffer=" << m_ownsBuffer << std::endl;
2186
+ if (isDebugEnabled())
2187
+ {
2188
+ std::cout << "[DEBUG] Execute - COMPLETE, finalBuffer=" << m_finalBuffer
2189
+ << ", finalSize=" << m_finalSize << ", ownsBuffer=" << m_ownsBuffer << std::endl;
2190
+ }
2041
2191
  }
2042
2192
  catch (const std::exception &e)
2043
2193
  {
2044
- // std::cout << "[DEBUG] Execute - EXCEPTION: " << e.what() << ", this=" << this << std::endl;
2045
- // std::cout << "[WORKER-" << std::this_thread::get_id() << "] EXCEPTION: " << e.what() << std::endl;
2194
+ if (isDebugEnabled())
2195
+ {
2196
+ std::cout << "[DEBUG] Execute - EXCEPTION: " << e.what() << ", this=" << this << std::endl;
2197
+ std::cout << "[WORKER-" << std::this_thread::get_id() << "] EXCEPTION: " << e.what() << std::endl;
2198
+ }
2046
2199
  SetError(e.what());
2047
2200
  }
2048
2201
  } // This runs on the main thread after Execute() completes
2049
2202
 
2050
2203
  void OnOK() override
2051
2204
  {
2052
- // std::cout << "[DEBUG] ProcessWorker::OnOK - START, this=" << this
2053
- // << ", finalBuffer=" << (void *)m_finalBuffer << ", finalSize=" << m_finalSize << std::endl;
2205
+ if (isDebugEnabled())
2206
+ {
2207
+ std::cout << "[DEBUG] ProcessWorker::OnOK - START, this=" << this
2208
+ << ", finalBuffer=" << (void *)m_finalBuffer << ", finalSize=" << m_finalSize << std::endl;
2209
+ }
2054
2210
  *m_busyLock = false; // unlock the pipeline
2055
2211
 
2056
2212
  Napi::Env env = Env();
@@ -2064,22 +2220,34 @@ namespace dsp
2064
2220
  // Clean up temporary buffer if we allocated one
2065
2221
  if (m_ownsBuffer)
2066
2222
  {
2067
- // std::cout << "[DEBUG] OnOK - deleting temp buffer=" << (void *)m_finalBuffer << std::endl;
2223
+ if (isDebugEnabled())
2224
+ {
2225
+ std::cout << "[DEBUG] OnOK - deleting owned temp buffer=" << (void *)m_finalBuffer << std::endl;
2226
+ }
2068
2227
  delete[] m_finalBuffer;
2069
2228
  }
2070
2229
 
2071
- // std::cout << "[DEBUG] OnOK - COMPLETE, resolving promise, this=" << this << std::endl;
2230
+ if (isDebugEnabled())
2231
+ {
2232
+ std::cout << "[DEBUG] ProcessWorker::OnOK - resolving promise, this=" << this << std::endl;
2233
+ }
2072
2234
  // Resolve the promise with the processed buffer
2073
2235
  m_deferred.Resolve(outputArray);
2074
2236
  }
2075
2237
 
2076
2238
  void OnError(const Napi::Error &error) override
2077
2239
  {
2078
- // std::cout << "[DEBUG] ProcessWorker::OnError - this=" << this
2079
- // << ", error=" << error.Message() << std::endl;
2240
+ if (isDebugEnabled())
2241
+ {
2242
+ std::cout << "[DEBUG] ProcessWorker::OnError - START, this=" << this
2243
+ << ", error=" << error.Message() << std::endl;
2244
+ }
2080
2245
  m_deferred.Reject(error.Value());
2081
2246
  *m_busyLock = false; // unlock the pipeline
2082
- // std::cout << "[DEBUG] OnError - COMPLETE, this=" << this << std::endl;
2247
+ if (isDebugEnabled())
2248
+ {
2249
+ std::cout << "[DEBUG] ProcessWorker::OnError - promise rejected, this=" << this << std::endl;
2250
+ }
2083
2251
  }
2084
2252
 
2085
2253
  private:
@@ -2117,19 +2285,28 @@ namespace dsp
2117
2285
  Napi::Value DspPipeline::ProcessAsync(const Napi::CallbackInfo &info)
2118
2286
  {
2119
2287
  Napi::Env env = info.Env();
2120
- // std::cout << "[DEBUG] DspPipeline::ProcessAsync - this=" << this << std::endl;
2288
+ if (isDebugEnabled())
2289
+ {
2290
+ std::cout << "[DEBUG] DspPipeline::ProcessAsync - this=" << this << std::endl;
2291
+ }
2121
2292
 
2122
2293
  // Check if pipeline is disposed
2123
2294
  if (m_disposed)
2124
2295
  {
2125
- // std::cout << "[DEBUG] ProcessAsync - pipeline disposed, this=" << this << std::endl;
2296
+ if (isDebugEnabled())
2297
+ {
2298
+ std::cout << "[DEBUG] ProcessAsync - pipeline disposed, this=" << this << std::endl;
2299
+ }
2126
2300
  Napi::Error::New(env, "Pipeline is disposed").ThrowAsJavaScriptException();
2127
2301
  return env.Undefined();
2128
2302
  }
2129
2303
 
2130
2304
  if (*m_isBusy)
2131
2305
  {
2132
- // std::cout << "[DEBUG] ProcessAsync - pipeline busy, this=" << this << std::endl;
2306
+ if (isDebugEnabled())
2307
+ {
2308
+ std::cout << "[DEBUG] ProcessAsync - pipeline busy, this=" << this << std::endl;
2309
+ }
2133
2310
  Napi::Error::New(env, "Pipeline is busy: Cannot call process() while another operation is running.").ThrowAsJavaScriptException();
2134
2311
  return env.Undefined();
2135
2312
  }
@@ -2198,13 +2375,22 @@ namespace dsp
2198
2375
  }
2199
2376
 
2200
2377
  *m_isBusy = true; // lock the pipeline
2201
- // std::cout << "[DEBUG] ProcessAsync - creating worker, data=" << (void *)data
2202
- // << ", numSamples=" << numSamples << ", channels=" << channels
2203
- // << ", this=" << this << std::endl;
2378
+
2379
+ if (isDebugEnabled())
2380
+ {
2381
+ std::cout << "[DEBUG] ProcessAsync - creating worker, data=" << (void *)data
2382
+ << ", numSamples=" << numSamples << ", channels=" << channels
2383
+ << ", this=" << this << std::endl;
2384
+ }
2204
2385
 
2205
2386
  ProcessWorker *worker = new ProcessWorker(env, std::move(deferred), m_stages, data, timestamps, sampleRate, numSamples, channels, std::move(bufferRef), std::move(timestampRef), m_isBusy);
2206
- // std::cout << "[DEBUG] ProcessAsync - queuing worker=" << (void *)worker
2207
- // << ", this=" << this << std::endl;
2387
+
2388
+ if (isDebugEnabled())
2389
+ {
2390
+ std::cout << "[DEBUG] ProcessAsync - queuing worker=" << (void *)worker
2391
+ << ", this=" << this << std::endl;
2392
+ }
2393
+
2208
2394
  worker->Queue();
2209
2395
 
2210
2396
  return promise;
@@ -2222,19 +2408,28 @@ namespace dsp
2222
2408
  Napi::Value DspPipeline::ProcessSync(const Napi::CallbackInfo &info)
2223
2409
  {
2224
2410
  Napi::Env env = info.Env();
2225
- // std::cout << "[DEBUG] DspPipeline::ProcessSync - this=" << this << std::endl;
2411
+ if (isDebugEnabled())
2412
+ {
2413
+ std::cout << "[DEBUG] DspPipeline::ProcessSync - this=" << this << std::endl;
2414
+ }
2226
2415
 
2227
2416
  // Check if pipeline is disposed
2228
2417
  if (m_disposed)
2229
2418
  {
2230
- // std::cout << "[DEBUG] ProcessSync - pipeline disposed, this=" << this << std::endl;
2419
+ if (isDebugEnabled())
2420
+ {
2421
+ std::cout << "[DEBUG] ProcessSync - pipeline disposed, this=" << this << std::endl;
2422
+ }
2231
2423
  Napi::Error::New(env, "Pipeline is disposed").ThrowAsJavaScriptException();
2232
2424
  return env.Undefined();
2233
2425
  }
2234
2426
 
2235
2427
  if (*m_isBusy)
2236
2428
  {
2237
- // std::cout << "[DEBUG] ProcessSync - pipeline busy, this=" << this << std::endl;
2429
+ if (isDebugEnabled())
2430
+ {
2431
+ std::cout << "[DEBUG] ProcessSync - pipeline busy, this=" << this << std::endl;
2432
+ }
2238
2433
  Napi::Error::New(env, "Pipeline is busy: Cannot call processSync() while an async operation is running.").ThrowAsJavaScriptException();
2239
2434
  return env.Undefined();
2240
2435
  }
@@ -2361,13 +2556,19 @@ namespace dsp
2361
2556
  Napi::Value DspPipeline::SaveState(const Napi::CallbackInfo &info)
2362
2557
  {
2363
2558
  Napi::Env env = info.Env();
2364
- // std::cout << "[DEBUG] DspPipeline::SaveState - this=" << this
2365
- // << ", stages=" << m_stages.size() << std::endl;
2559
+ if (isDebugEnabled())
2560
+ {
2561
+ std::cout << "[DEBUG] DspPipeline::SaveState - this=" << this
2562
+ << ", stages=" << m_stages.size() << std::endl;
2563
+ }
2366
2564
 
2367
2565
  // Check if pipeline is disposed
2368
2566
  if (m_disposed)
2369
2567
  {
2370
- // std::cout << "[DEBUG] SaveState - pipeline disposed, this=" << this << std::endl;
2568
+ if (isDebugEnabled())
2569
+ {
2570
+ std::cout << "[DEBUG] SaveState - pipeline disposed, this=" << this << std::endl;
2571
+ }
2371
2572
  Napi::Error::New(env, "Pipeline is disposed").ThrowAsJavaScriptException();
2372
2573
  return env.Undefined();
2373
2574
  }
@@ -2452,21 +2653,45 @@ namespace dsp
2452
2653
  }
2453
2654
 
2454
2655
  /**
2455
- * Load pipeline state from JSON string or TOON Buffer with inline validation.
2456
- * NEW BEHAVIOR: Only loads state if saved stages match current stages EXACTLY.
2457
- * This prevents pipeline corruption from incompatible state.
2656
+ * Load pipeline state from JSON string or TOON Buffer with validation.
2657
+ *
2658
+ * RESILIENCE FEATURES:
2659
+ * - Validates stage types and count before loading
2660
+ * - TOON path: Validates upfront, deserializes in-place
2661
+ * - JSON path: Build newStages → Validate → Atomic swap (fully transactional)
2662
+ * - Prevents pipeline corruption from incompatible state
2663
+ *
2664
+ * BEHAVIOR:
2665
+ * - Only loads state if saved stages match current stages EXACTLY (type and count)
2666
+ * - TOON: Validates all stages before deserialization, fails fast on mismatch
2667
+ * - JSON: Builds temporary stages, only swaps if fully successful
2668
+ * - Throws error with detailed message if validation or loading fails
2458
2669
  *
2459
- * If stage types or count don't match, throws an error instead of trying to merge.
2670
+ * ERROR HANDLING:
2671
+ * - Stage count mismatch: Abort immediately (TOON may have partial changes)
2672
+ * - Stage type mismatch: Abort immediately (TOON may have partial changes)
2673
+ * - Deserialization error: Throws (TOON may have partial state changes)
2674
+ * - Note: TOON format modifies stages in-place, cannot rollback
2675
+ *
2676
+ * @param info[0] - JSON string or TOON Buffer containing pipeline state
2677
+ * @returns Boolean - true if successful
2678
+ * @throws Error if validation or deserialization fails
2460
2679
  */
2461
2680
  Napi::Value DspPipeline::LoadState(const Napi::CallbackInfo &info)
2462
2681
  {
2463
2682
  Napi::Env env = info.Env();
2464
- // std::cout << "[DEBUG] DspPipeline::LoadState - this=" << this
2465
- // << ", current stages=" << m_stages.size() << std::endl;
2683
+ if (isDebugEnabled())
2684
+ {
2685
+ std::cout << "[DEBUG] DspPipeline::LoadState - this=" << this
2686
+ << ", current stages=" << m_stages.size() << std::endl;
2687
+ }
2466
2688
  // Check if pipeline is disposed
2467
2689
  if (m_disposed)
2468
2690
  {
2469
- // std::cout << "[DEBUG] LoadState - pipeline disposed, this=" << this << std::endl;
2691
+ if (isDebugEnabled())
2692
+ {
2693
+ std::cout << "[DEBUG] LoadState - pipeline disposed, this=" << this << std::endl;
2694
+ }
2470
2695
  Napi::Error::New(env, "Pipeline is disposed").ThrowAsJavaScriptException();
2471
2696
  return env.Undefined();
2472
2697
  }
@@ -2505,28 +2730,42 @@ namespace dsp
2505
2730
 
2506
2731
  if (toonDataPtr != nullptr && toonDataLen > 0)
2507
2732
  {
2733
+ // TOON binary path with upfront validation
2508
2734
  try
2509
2735
  {
2510
2736
  const bool debugToon = std::getenv("DSPX_DEBUG_TOON") != nullptr;
2737
+
2738
+ if (debugToon)
2739
+ {
2740
+ std::cout << "[TOON] Parsing and validating TOON buffer" << std::endl;
2741
+ }
2742
+
2511
2743
  dsp::toon::Deserializer deserializer(toonDataPtr, toonDataLen);
2512
2744
  deserializer.consumeToken(dsp::toon::T_OBJECT_START);
2513
2745
  std::string key = deserializer.readString();
2514
2746
  double timestamp = deserializer.readDouble();
2515
2747
  key = deserializer.readString();
2516
2748
  int32_t savedStageCount = deserializer.readInt32();
2749
+
2750
+ // Validate stage count upfront
2751
+ if (static_cast<size_t>(savedStageCount) != m_stages.size())
2752
+ {
2753
+ throw std::runtime_error("TOON Load: Stage count mismatch. Saved state has " +
2754
+ std::to_string(savedStageCount) + " stages, current has " +
2755
+ std::to_string(m_stages.size()) + ".");
2756
+ }
2757
+
2517
2758
  key = deserializer.readString();
2518
2759
  deserializer.consumeToken(dsp::toon::T_ARRAY_START);
2519
2760
 
2520
- // SIMPLER APPROACH: Validate and load in a single pass
2521
- // For each saved stage, check type matches before deserializing
2761
+ // Validate stage types and deserialize
2522
2762
  size_t stageIdx = 0;
2523
2763
  while (deserializer.peekToken() != dsp::toon::T_ARRAY_END)
2524
2764
  {
2525
- // Check we haven't exceeded current stage count
2526
2765
  if (stageIdx >= m_stages.size())
2527
2766
  {
2528
- throw std::runtime_error("TOON Load: Stage count mismatch. Saved state has more stages than current pipeline (" +
2529
- std::to_string(m_stages.size()) + ").");
2767
+ throw std::runtime_error("TOON Load: Unexpected extra stage in buffer at index " +
2768
+ std::to_string(stageIdx) + ".");
2530
2769
  }
2531
2770
 
2532
2771
  deserializer.consumeToken(dsp::toon::T_OBJECT_START);
@@ -2539,8 +2778,7 @@ namespace dsp
2539
2778
  {
2540
2779
  throw std::runtime_error("TOON Load: Stage type mismatch at index " +
2541
2780
  std::to_string(stageIdx) + ". Expected '" + currentType +
2542
- "', got '" + savedType +
2543
- "'. Cannot load incompatible state.");
2781
+ "', got '" + savedType + "'.");
2544
2782
  }
2545
2783
 
2546
2784
  deserializer.readString(); // "state" key
@@ -2551,25 +2789,19 @@ namespace dsp
2551
2789
  << "]: type='" << savedType << "'" << std::endl;
2552
2790
  }
2553
2791
 
2554
- // Deserialize directly into the existing stage
2792
+ // Deserialize into the existing stage
2555
2793
  m_stages[stageIdx]->deserializeToon(deserializer);
2556
2794
 
2557
2795
  deserializer.consumeToken(dsp::toon::T_OBJECT_END);
2558
2796
  stageIdx++;
2559
2797
  }
2560
2798
 
2561
- // Verify we loaded all current stages
2799
+ // Final validation
2562
2800
  if (stageIdx != m_stages.size())
2563
2801
  {
2564
- throw std::runtime_error("TOON Load: Stage count mismatch. Saved state has " +
2565
- std::to_string(stageIdx) + " stages, current has " +
2566
- std::to_string(m_stages.size()) + ".");
2567
- }
2568
-
2569
- if (debugToon)
2570
- {
2571
- std::cout << "[TOON] Validation passed and loaded " << stageIdx
2572
- << " stages successfully" << std::endl;
2802
+ throw std::runtime_error("TOON Load: Stage count mismatch after parsing. Expected " +
2803
+ std::to_string(m_stages.size()) + " stages, parsed " +
2804
+ std::to_string(stageIdx) + ".");
2573
2805
  }
2574
2806
 
2575
2807
  deserializer.consumeToken(dsp::toon::T_ARRAY_END);
@@ -2577,15 +2809,16 @@ namespace dsp
2577
2809
 
2578
2810
  if (debugToon)
2579
2811
  {
2580
- std::cout << "[TOON] Load complete. Restored state into "
2581
- << stageIdx << " stages" << std::endl;
2812
+ std::cout << "[TOON] Successfully loaded " << stageIdx << " stages." << std::endl;
2582
2813
  }
2814
+
2583
2815
  return Napi::Boolean::New(env, true);
2584
2816
  }
2585
2817
  catch (const std::exception &e)
2586
2818
  {
2587
- Napi::Error::New(env, std::string("TOON Load Failed: ") + e.what()).ThrowAsJavaScriptException();
2588
- return Napi::Boolean::New(env, false);
2819
+ std::string errorMsg = std::string("TOON Load failed: ") + e.what();
2820
+ Napi::Error::New(env, errorMsg).ThrowAsJavaScriptException();
2821
+ return env.Undefined();
2589
2822
  }
2590
2823
  }
2591
2824
 
@@ -2662,6 +2895,7 @@ namespace dsp
2662
2895
  auto newStage = it->second(stageState);
2663
2896
 
2664
2897
  // Restore the full internal buffer state
2898
+ // NOTE: Validation errors here should propagate (fail-fast)
2665
2899
  newStage->deserializeState(stageState);
2666
2900
 
2667
2901
  // Add to our new pipeline
@@ -2669,7 +2903,18 @@ namespace dsp
2669
2903
  }
2670
2904
  catch (const std::exception &e)
2671
2905
  {
2672
- // If reconstruction fails (e.g. missing params), log warning and skip
2906
+ // Check if this is a validation error (contains keywords like "validation", "mismatch")
2907
+ std::string errorMsg = e.what();
2908
+ if (errorMsg.find("validation") != std::string::npos ||
2909
+ errorMsg.find("mismatch") != std::string::npos ||
2910
+ errorMsg.find("Validation") != std::string::npos ||
2911
+ errorMsg.find("Mismatch") != std::string::npos)
2912
+ {
2913
+ // Validation error - propagate it (fail the entire load)
2914
+ throw;
2915
+ }
2916
+
2917
+ // Construction error (e.g., missing params) - log warning and skip
2673
2918
  std::cerr << "Warning: Failed to reconstruct stage " << savedType
2674
2919
  << ": " << e.what() << std::endl;
2675
2920
  }
@@ -2710,13 +2955,19 @@ namespace dsp
2710
2955
  Napi::Value DspPipeline::ClearState(const Napi::CallbackInfo &info)
2711
2956
  {
2712
2957
  Napi::Env env = info.Env();
2713
- // std::cout << "[DEBUG] DspPipeline::ClearState - this=" << this
2714
- // << ", stages=" << m_stages.size() << std::endl;
2958
+ if (isDebugEnabled())
2959
+ {
2960
+ std::cout << "[DEBUG] DspPipeline::ClearState - this=" << this
2961
+ << ", stages=" << m_stages.size() << std::endl;
2962
+ }
2715
2963
 
2716
2964
  // Check if pipeline is disposed
2717
2965
  if (m_disposed)
2718
2966
  {
2719
- // std::cout << "[DEBUG] ClearState - pipeline disposed, this=" << this << std::endl;
2967
+ if (isDebugEnabled())
2968
+ {
2969
+ std::cout << "[DEBUG] ClearState - pipeline disposed, this=" << this << std::endl;
2970
+ }
2720
2971
  Napi::Error::New(env, "Pipeline is disposed").ThrowAsJavaScriptException();
2721
2972
  return env.Undefined();
2722
2973
  }
@@ -2724,13 +2975,20 @@ namespace dsp
2724
2975
  // Reset all stages
2725
2976
  for (size_t i = 0; i < m_stages.size(); ++i)
2726
2977
  {
2727
- // std::cout << "[DEBUG] ClearState - resetting stage " << i
2728
- // << ", addr=" << m_stages[i].get() << std::endl;
2978
+ if (isDebugEnabled())
2979
+ {
2980
+ std::cout << "[DEBUG] ClearState - resetting stage " << i
2981
+ << ", type=" << m_stages[i]->getType()
2982
+ << ", addr=" << m_stages[i].get() << std::endl;
2983
+ }
2729
2984
  m_stages[i]->reset();
2730
2985
  }
2731
2986
 
2732
- // std::cout << "[DEBUG] Pipeline state cleared (" << m_stages.size()
2733
- // << " stages reset), this=" << this << std::endl;
2987
+ if (isDebugEnabled())
2988
+ {
2989
+ std::cout << "[DEBUG] Pipeline state cleared (" << m_stages.size()
2990
+ << " stages reset), this=" << this << std::endl;
2991
+ }
2734
2992
 
2735
2993
  return env.Undefined();
2736
2994
  }
@@ -2826,27 +3084,40 @@ namespace dsp
2826
3084
  Napi::Value DspPipeline::Dispose(const Napi::CallbackInfo &info)
2827
3085
  {
2828
3086
  Napi::Env env = info.Env();
2829
- // std::cout << "[DEBUG] DspPipeline::Dispose - this=" << this
2830
- // << ", stages=" << m_stages.size() << ", disposed=" << m_disposed << std::endl;
3087
+
3088
+ if (isDebugEnabled())
3089
+ {
3090
+ std::cout << "[DEBUG] DspPipeline::Dispose - this=" << this
3091
+ << ", stages=" << m_stages.size() << ", disposed=" << m_disposed << std::endl;
3092
+ }
2831
3093
 
2832
3094
  // Already disposed - silently succeed (idempotent behavior)
2833
3095
  if (m_disposed)
2834
3096
  {
2835
- // std::cout << "[DEBUG] Dispose - already disposed, this=" << this << std::endl;
3097
+ if (isDebugEnabled())
3098
+ {
3099
+ std::cout << "[DEBUG] Dispose - already disposed, this=" << this << std::endl;
3100
+ }
2836
3101
  return env.Undefined();
2837
3102
  }
2838
3103
 
2839
3104
  // Cannot dispose while processing is in progress
2840
3105
  if (*m_isBusy)
2841
3106
  {
2842
- // std::cout << "[DEBUG] Dispose - pipeline busy, cannot dispose, this=" << this << std::endl;
3107
+ if (isDebugEnabled())
3108
+ {
3109
+ std::cout << "[DEBUG] Dispose - pipeline busy, cannot dispose, this=" << this << std::endl;
3110
+ }
2843
3111
  Napi::Error::New(env, "Cannot dispose pipeline: process() is still running.")
2844
3112
  .ThrowAsJavaScriptException();
2845
3113
  return env.Undefined();
2846
3114
  }
2847
3115
 
2848
- // std::cout << "[DEBUG] Dispose - clearing " << m_stages.size()
2849
- // << " stages, this=" << this << std::endl;
3116
+ if (isDebugEnabled())
3117
+ {
3118
+ std::cout << "[DEBUG] Dispose - clearing " << m_stages.size()
3119
+ << " stages, this=" << this << std::endl;
3120
+ }
2850
3121
  // Clear all stages - triggers RAII cleanup of all stage resources
2851
3122
  // This will:
2852
3123
  // - Free all stage internal buffers
@@ -2855,14 +3126,20 @@ namespace dsp
2855
3126
  // - Free all detachable buffers
2856
3127
  // - Free timestamp and resize buffers
2857
3128
  m_stages.clear();
2858
- // std::cout << "[DEBUG] Dispose - stages cleared, this=" << this << std::endl;
3129
+ if (isDebugEnabled())
3130
+ {
3131
+ std::cout << "[DEBUG] Dispose - stages cleared, this=" << this << std::endl;
3132
+ }
2859
3133
 
2860
3134
  // Reset busy flag (defensive programming)
2861
3135
  *m_isBusy = false;
2862
3136
 
2863
3137
  // Mark as disposed to prevent further operations
2864
3138
  m_disposed = true;
2865
- // std::cout << "[DEBUG] Dispose - complete, this=" << this << std::endl;
3139
+ if (isDebugEnabled())
3140
+ {
3141
+ std::cout << "[DEBUG] Dispose - complete, this=" << this << std::endl;
3142
+ }
2866
3143
 
2867
3144
  return env.Undefined();
2868
3145
  }