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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
|
// This file is part of the 64k demo project.
// It tests tracker timing and synchronization using MockAudioBackend.
// Verifies pattern triggers occur at correct times with proper BPM scaling.
#include "audio/mock_audio_backend.h"
#include "audio/audio.h"
#include "audio/synth.h"
#include "audio/tracker.h"
#include <assert.h>
#include <stdio.h>
#include <cmath>
#if !defined(STRIP_ALL)
// Helper: Check if a timestamp exists in events within tolerance
static bool has_event_at_time(const std::vector<VoiceTriggerEvent>& events,
float expected_time, float tolerance = 0.001f) {
for (const auto& evt : events) {
if (std::abs(evt.timestamp_sec - expected_time) < tolerance) {
return true;
}
}
return false;
}
// Helper: Count events at a specific time
static int count_events_at_time(const std::vector<VoiceTriggerEvent>& events,
float expected_time, float tolerance = 0.001f) {
int count = 0;
for (const auto& evt : events) {
if (std::abs(evt.timestamp_sec - expected_time) < tolerance) {
count++;
}
}
return count;
}
// Helper: Get all unique timestamps in events
static std::vector<float> get_unique_timestamps(
const std::vector<VoiceTriggerEvent>& events, float tolerance = 0.001f) {
std::vector<float> timestamps;
for (const auto& evt : events) {
bool found = false;
for (float ts : timestamps) {
if (std::abs(evt.timestamp_sec - ts) < tolerance) {
found = true;
break;
}
}
if (!found) {
timestamps.push_back(evt.timestamp_sec);
}
}
return timestamps;
}
void test_basic_event_recording() {
printf("Test: Basic event recording with mock backend...\n");
MockAudioBackend backend;
audio_set_backend(&backend);
tracker_init();
synth_init();
// Trigger at t=0.0 (should trigger initial patterns)
tracker_update(0.0f);
const auto& events = backend.get_events();
printf(" Events triggered at t=0.0: %zu\n", events.size());
// Verify we got some events
assert(events.size() > 0);
// All events at t=0 should have timestamp near 0
for (const auto& evt : events) {
assert(evt.timestamp_sec < 0.1f); // Within 100ms of start
}
printf(" ✓ Basic event recording works\n");
}
void test_progressive_triggering() {
printf("Test: Progressive pattern triggering...\n");
MockAudioBackend backend;
audio_set_backend(&backend);
tracker_init();
synth_init();
// Update at t=0
tracker_update(0.0f);
const size_t events_at_0 = backend.get_events().size();
printf(" Events at t=0.0: %zu\n", events_at_0);
// Update at t=1.0
tracker_update(1.0f);
const size_t events_at_1 = backend.get_events().size();
printf(" Events at t=1.0: %zu\n", events_at_1);
// Update at t=2.0
tracker_update(2.0f);
const size_t events_at_2 = backend.get_events().size();
printf(" Events at t=2.0: %zu\n", events_at_2);
// Events should accumulate (or at least not decrease)
assert(events_at_1 >= events_at_0);
assert(events_at_2 >= events_at_1);
printf(" ✓ Events accumulate over time\n");
}
void test_simultaneous_triggers() {
printf("Test: SIMULTANEOUS pattern triggers at same time...\n");
MockAudioBackend backend;
audio_set_backend(&backend);
tracker_init();
synth_init();
// Clear and update to first trigger point
backend.clear_events();
tracker_update(0.0f);
const auto& events = backend.get_events();
if (events.size() == 0) {
printf(" No events at t=0.0, skipping test\n");
return;
}
// Check if we have multiple events at t=0
const int simultaneous_count = count_events_at_time(events, 0.0f, 0.001f);
printf(" Simultaneous events at t=0.0: %d out of %zu total\n",
simultaneous_count, events.size());
if (simultaneous_count > 1) {
// Verify all simultaneous events have EXACTLY the same timestamp
const float first_timestamp = events[0].timestamp_sec;
float max_delta = 0.0f;
for (size_t i = 1; i < events.size(); ++i) {
const float delta = std::abs(events[i].timestamp_sec - first_timestamp);
max_delta = std::fmaxf(max_delta, delta);
}
printf(" Maximum timestamp delta: %.6f seconds (%.3f ms)\n", max_delta,
max_delta * 1000.0f);
// Simultaneous events should have sub-millisecond timing
assert(max_delta < 0.001f); // Less than 1ms difference
printf(" ✓ All simultaneous events within 1ms of each other\n");
} else {
printf(" ℹ Only one event at t=0.0, cannot verify simultaneity\n");
}
}
void test_timing_monotonicity() {
printf("Test: Event timestamps are monotonically increasing...\n");
MockAudioBackend backend;
audio_set_backend(&backend);
tracker_init();
synth_init();
// Update through several time points
for (float t = 0.0f; t <= 5.0f; t += 0.5f) {
tracker_update(t);
}
const auto& events = backend.get_events();
printf(" Total events recorded: %zu\n", events.size());
// Verify timestamps are monotonically increasing (non-decreasing)
for (size_t i = 1; i < events.size(); ++i) {
assert(events[i].timestamp_sec >= events[i - 1].timestamp_sec);
}
printf(" ✓ All timestamps monotonically increasing\n");
}
void test_seek_simulation() {
printf("Test: Seek/fast-forward simulation...\n");
MockAudioBackend backend;
audio_set_backend(&backend);
audio_init();
tracker_init();
synth_init();
// Simulate seeking to t=3.0s by rendering silent audio
// This should trigger all patterns in range [0, 3.0]
const float seek_target = 3.0f;
// Update tracker progressively (simulating real playback)
float t = 0.0f;
const float step = 0.1f;
while (t <= seek_target) {
tracker_update(t);
// Simulate audio rendering
float dummy_buffer[512 * 2];
synth_render(dummy_buffer, 512);
t += step;
}
const auto& events = backend.get_events();
printf(" Events triggered during seek to %.1fs: %zu\n", seek_target,
events.size());
// Should have triggered multiple patterns
assert(events.size() > 0);
// All events should be before seek target time
for (const auto& evt : events) {
// Events can be slightly after due to synth processing
assert(evt.timestamp_sec <= seek_target + 0.5f);
}
audio_shutdown();
printf(" ✓ Seek simulation works correctly\n");
}
void test_timestamp_clustering() {
printf("Test: Analyzing timestamp clustering...\n");
MockAudioBackend backend;
audio_set_backend(&backend);
tracker_init();
synth_init();
// Update through the first 4 seconds
for (float t = 0.0f; t <= 4.0f; t += 0.1f) {
tracker_update(t);
}
const auto& events = backend.get_events();
printf(" Total events: %zu\n", events.size());
// Get unique timestamps
auto unique_timestamps = get_unique_timestamps(events, 0.001f);
printf(" Unique trigger times: %zu\n", unique_timestamps.size());
// For each unique timestamp, count how many events occurred
for (float ts : unique_timestamps) {
const int count = count_events_at_time(events, ts, 0.001f);
if (count > 1) {
printf(" %.3fs: %d simultaneous events\n", ts, count);
}
}
printf(" ✓ Timestamp clustering analyzed\n");
}
void test_render_integration() {
printf("Test: Integration with audio_render_silent...\n");
MockAudioBackend backend;
audio_set_backend(&backend);
audio_init();
tracker_init();
synth_init();
// Trigger some patterns
tracker_update(0.0f);
const size_t events_before = backend.get_events().size();
// Render 1 second of silent audio
audio_render_silent(1.0f);
// Check that backend time advanced
const float backend_time = backend.get_current_time();
printf(" Backend time after 1s render: %.3fs\n", backend_time);
assert(backend_time >= 0.9f && backend_time <= 1.1f);
// Trigger more patterns after time advance
tracker_update(1.0f);
const size_t events_after = backend.get_events().size();
printf(" Events before: %zu, after: %zu\n", events_before, events_after);
assert(events_after >= events_before);
audio_shutdown();
printf(" ✓ audio_render_silent integration works\n");
}
#endif /* !defined(STRIP_ALL) */
int main() {
#if !defined(STRIP_ALL)
printf("Running Tracker Timing tests...\n\n");
test_basic_event_recording();
test_progressive_triggering();
test_simultaneous_triggers();
test_timing_monotonicity();
test_seek_simulation();
test_timestamp_clustering();
test_render_integration();
printf("\n✅ All Tracker Timing tests PASSED\n");
return 0;
#else
printf("Tracker Timing tests skipped (STRIP_ALL enabled)\n");
return 0;
#endif /* !defined(STRIP_ALL) */
}
|