TinyML Multiple Impulses: A Model Handle Does Not Isolate Continuous-Inference Windows
Selecting a different Edge Impulse handle does not establish a separate continuous audio history in the implementation examined here. Two ongoing streams can reach the same rolling feature storage even when every call runs serially. Before scheduling both streams, identify which state actually belongs to the handle.
This analysis pins Edge Impulse SDK v1.95.22 to commit 732b2eac85c1874746fa6fa5a2876de207c59e39. Its scope is calls reaching the same compiled continuous-inference implementation and shared function instance. No export, build, microphone capture, model execution or dual-stream experiment was performed. It is not a claim about every SDK version or backend.
State the deployment requirement precisely
A deployment may need to choose a model for one complete recording, classify independently captured complete windows, or maintain two ongoing slice-based histories. Those requirements need different evidence. A successful example of model selection does not prove the third contract.
Suppose streams A and B each need their own recent audio window. The application alternates slices using two handles:
Proposed call order, not an observed execution trace:
A1 via handle A
B1 via handle B
A2 via handle A
B2 via handle B
For acceptance, the next A window must represent A’s own history at that position. Merely remembering which handle produced the final label is insufficient. The source of each retained feature matters before classification begins.
Follow the handle to the actual owners
The handle definition contains an impulse state object, an impulse pointer, post-processing state and input parameters. It would be incorrect to say that handles contain no state.
However, the continuous classifier implementation declares static_features_matrix inside process_impulse_continuous. That function-static matrix is initialized using the first invocation’s nn_input_frame_size. The feature counter, classifier_continuous_features_written, is also shared rather than stored in the handle. The handle-taking public overload calls this implementation.
The audio DSP implementation adds shared partial-frame storage: a pointer, size and index. Its MFCC slice path uses those fields across invocations and rolls retained feature output. A new model handle does not automatically supply another copy of these separately declared objects.
| State under review | Owner shown in the pinned source |
|---|---|
| Impulse and post-processing state | Handle fields |
| Rolling feature matrix | Function-static object |
| Accumulated feature count | Shared classifier variable |
| Continuous audio partial frame | Shared DSP variables |

Separate call serialization from history isolation
A mutex can prevent two invocations from overlapping. It cannot allocate another persistent matrix or choose a different residual frame for each stream. With serial interleaving through the same instance, the ownership mismatch therefore remains. This is a source-level condition analysis, not a measured misclassification claim.
Start the integration review with compatible, equal-sized configurations. Different nn_input_frame_size values introduce an additional concern because the static matrix’s construction uses the first invocation. Reject or resolve that structural mismatch before execution; the source observation does not establish a particular crash, overflow or failure rate.
Resetting at every switch is also a different contract. run_classifier_init(handle) resets the shared feature counter and clears shared continuous-audio state before initializing the selected handle. The clear routine frees the residual frame and zeroes its fields; it takes no handle. The initializer does not reconstruct the function-static feature matrix.
Consequently, initializing B is not a mechanism for preserving A’s ongoing continuity. Resetting one shared history between independent sessions may be appropriate elsewhere, but it does not maintain two live histories simultaneously.
Choose an architecture with explicit storage ownership
One option is one continuous owner with one active history. Switching sources then ends that history under an explicit application policy. This does not satisfy a requirement to retain both streams concurrently.
Another architecture owns complete input windows separately and evaluates the ordinary handle-selected classification path. Edge Impulse’s multi-impulse generator, pinned independently to commit c63623c446f3e27047c0cf2873ad788293d8d2bb, generates sequential process_impulse calls with each impulse’s complete input length and callback. That illustrates complete-window dispatch. It does not demonstrate isolated continuous windows, and this generator revision was not tested with the newer SDK here.
Complete-window ownership still needs memory, latency, result-lifetime and backend review. It is not a thread-safety certification or a drop-in repair. A design retaining two continuous histories instead needs a verified implementation with genuinely separate persistent state throughout the relevant path.
Verify feature provenance before final labels
A proposed controlled fixture first establishes isolated A-only and B-only feature-window baselines in fresh independent runtime instances. Then compare the proposed alternating deployment at equivalent positions in each stream’s history. Preserve copied feature evidence before later calls can change shared storage.
Record state addresses, dimensions, stream identity and initialization events. Include an initialization of B while A is active, and inspect which shared fields change. Final labels alone are inadequate: different feature windows can receive the same label. The acceptance result must establish independent histories, not simply error-free calls or plausible scores.