Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/deploy_cpp_libs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ jobs:
steps:
- name: Clone Repository
uses: actions/checkout@v2
- name: Setup Cmake
uses: jwlawson/actions-setup-cmake@v1.13
- name: Setup CMake
uses: lukka/get-cmake@v4.3.2
with:
cmake-version: '3.21.x'
cmakeVersion: '3.21.4'
- name: Install Ninja
if: (matrix.os == 'macos-14')
run: |
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/run_android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:
sudo -H apt-get install -y ninja-build zip unzip python3-setuptools python3-pygments
env:
DEBIAN_FRONTEND: noninteractive
- name: Setup Cmake
uses: jwlawson/actions-setup-cmake@v1.4
- name: Setup CMake
uses: lukka/get-cmake@v4.3.2
with:
cmake-version: '3.16.x'
cmakeVersion: '3.21.4'
- name: Install NDK
run: |
echo "y" | sudo -H ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;${ANDROID_NDK_VERSION}" --sdk_root=${ANDROID_SDK_ROOT}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/run_libftdi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ jobs:
sudo -H apt-get install -y python3-setuptools python3-pygments libftdi1-dev
env:
DEBIAN_FRONTEND: noninteractive
- name: Setup Cmake
uses: jwlawson/actions-setup-cmake@v1.4
- name: Setup CMake
uses: lukka/get-cmake@v4.3.2
with:
cmake-version: '3.16.x'
cmakeVersion: '3.21.4'
- name: Compile BrainFlow
run: |
mkdir $GITHUB_WORKSPACE/build
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/run_matlab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v2
- name: Setup Cmake
uses: jwlawson/actions-setup-cmake@v1.4
- name: Setup CMake
uses: lukka/get-cmake@v4.3.2
with:
cmake-version: '3.16.x'
cmakeVersion: '3.21.4'
- name: Compile BrainFlow
run: |
mkdir $GITHUB_WORKSPACE/build
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/run_unix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ jobs:
with:
version: '1'
arch: 'default'
- name: Setup Cmake
uses: jwlawson/actions-setup-cmake@v1.13
- name: Setup CMake
uses: lukka/get-cmake@v4.3.2
with:
cmake-version: '3.21.x'
cmakeVersion: '3.21.4'
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/valgrind.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
# compile and prepare env
- name: Clone Repository
uses: actions/checkout@v2
- name: Setup Cmake
uses: jwlawson/actions-setup-cmake@v1.4
- name: Setup CMake
uses: lukka/get-cmake@v4.3.2
with:
cmake-version: '3.16.x'
cmakeVersion: '3.21.4'
- name: Install Dependencies
run: |
sudo -H apt-get update -y
Expand Down
12 changes: 7 additions & 5 deletions src/board_controller/muse/inc/muse_anthena.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ class MuseAnthena : public BLELibBoard
std::condition_variable cv;
std::vector<std::pair<simpleble_uuid_t, simpleble_uuid_t>> notified_characteristics;
std::pair<simpleble_uuid_t, simpleble_uuid_t> control_characteristics;
bool timestamp_initialized;
uint32_t first_device_tick;
double first_host_timestamp;
double last_eeg_timestamp;
double last_aux_timestamp;
double last_anc_timestamp;
double last_battery;
std::string muse_preset;
bool enable_low_latency;
Expand All @@ -57,10 +57,12 @@ class MuseAnthena : public BLELibBoard
std::string bytes_to_string (const uint8_t *data, size_t size);
void handle_data_notification (const uint8_t *data, size_t size);
void parse_sensor_payload (
uint8_t tag, uint8_t sequence_num, uint32_t device_tick, const uint8_t *data, size_t size);
uint8_t tag, uint8_t sequence_num, double host_timestamp, const uint8_t *data, size_t size);
bool get_sensor_config (uint8_t tag, SensorConfig &config);
int get_optics_canonical_index (uint8_t tag, int channel);
double get_sample_timestamp (uint32_t device_tick, int sample_index, double sampling_rate);
void reset_timestamps ();
static double get_sample_timestamp (double last_timestamp, double current_timestamp,
int sample_index, int n_samples, double sampling_rate);

public:
MuseAnthena (int board_id, struct BrainFlowInputParams params);
Expand Down
1 change: 0 additions & 1 deletion src/board_controller/muse/inc/muse_anthena_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@
// info for equations
#define MUSE_ANTHENA_ACCELEROMETER_SCALE_FACTOR 0.0000610352
#define MUSE_ANTHENA_GYRO_SCALE_FACTOR -0.0074768
#define MUSE_ANTHENA_DEVICE_CLOCK_HZ 256000.0
#define MUSE_ANTHENA_EEG_SCALE_FACTOR (1450.0 / 16383.0)
#define MUSE_ANTHENA_OPTICS_SCALE_FACTOR 1.0
74 changes: 46 additions & 28 deletions src/board_controller/muse/muse_anthena.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,7 @@ MuseAnthena::MuseAnthena (int board_id, struct BrainFlowInputParams params)
muse_adapter = NULL;
muse_peripheral = NULL;
is_streaming = false;
timestamp_initialized = false;
first_device_tick = 0;
first_host_timestamp = 0.0;
reset_timestamps ();
last_battery = 0.0;
muse_preset = "p1041";
enable_low_latency = true;
Expand Down Expand Up @@ -486,9 +484,7 @@ int MuseAnthena::start_stream (int buffer_size, const char *streamer_params)
if (res == (int)BrainFlowExitCodes::STATUS_OK)
{
std::lock_guard<std::mutex> callback_guard (callback_lock);
timestamp_initialized = false;
first_device_tick = 0;
first_host_timestamp = 0.0;
reset_timestamps ();
last_battery = 0.0;
}
if (res == (int)BrainFlowExitCodes::STATUS_OK)
Expand Down Expand Up @@ -555,7 +551,7 @@ int MuseAnthena::stop_stream ()
res = (int)BrainFlowExitCodes::STREAM_ALREADY_RUN_ERROR;
}
is_streaming = false;
timestamp_initialized = false;
reset_timestamps ();
return res;
}

Expand Down Expand Up @@ -744,8 +740,7 @@ void MuseAnthena::handle_data_notification (const uint8_t *data, size_t size)

const uint8_t *packet = data + offset;
uint8_t packet_index = packet[1];
uint32_t device_tick =
cast_32bit_to_uint32_little_endian ((const unsigned char *)(packet + 2));
double packet_host_timestamp = get_timestamp ();
uint8_t primary_tag = packet[9];
const uint8_t *packet_data = packet + PACKET_HEADER_SIZE;
size_t packet_data_size = packet_len - PACKET_HEADER_SIZE;
Expand All @@ -758,8 +753,8 @@ void MuseAnthena::handle_data_notification (const uint8_t *data, size_t size)
primary_config.variable_length ? packet_data_size : primary_config.data_len;
if ((primary_data_len > 0) && (primary_data_len <= packet_data_size))
{
parse_sensor_payload (
primary_tag, packet_index, device_tick, packet_data, primary_data_len);
parse_sensor_payload (primary_tag, packet_index, packet_host_timestamp, packet_data,
primary_data_len);
packet_data_offset = primary_data_len;
}
else
Expand Down Expand Up @@ -799,7 +794,7 @@ void MuseAnthena::handle_data_notification (const uint8_t *data, size_t size)
break;
}

parse_sensor_payload (tag, subpacket_index, device_tick,
parse_sensor_payload (tag, subpacket_index, packet_host_timestamp,
packet_data + packet_data_offset + SUBPACKET_HEADER_SIZE, sensor_data_len);
packet_data_offset += SUBPACKET_HEADER_SIZE + sensor_data_len;
}
Expand All @@ -809,7 +804,7 @@ void MuseAnthena::handle_data_notification (const uint8_t *data, size_t size)
}

void MuseAnthena::parse_sensor_payload (
uint8_t tag, uint8_t sequence_num, uint32_t device_tick, const uint8_t *data, size_t size)
uint8_t tag, uint8_t sequence_num, double host_timestamp, const uint8_t *data, size_t size)
{
SensorConfig config;
if (!get_sensor_config (tag, config))
Expand Down Expand Up @@ -872,10 +867,11 @@ void MuseAnthena::parse_sensor_payload (
}
}
}
package[(size_t)timestamp_channel] =
get_sample_timestamp (device_tick, sample, config.sampling_rate);
package[(size_t)timestamp_channel] = get_sample_timestamp (
last_eeg_timestamp, host_timestamp, sample, config.n_samples, config.sampling_rate);
push_package (package.data (), (int)BrainFlowPresets::DEFAULT_PRESET);
}
last_eeg_timestamp = host_timestamp;
return;
}

Expand Down Expand Up @@ -911,10 +907,11 @@ void MuseAnthena::parse_sensor_payload (
(double)raw * MUSE_ANTHENA_GYRO_SCALE_FACTOR;
}
}
package[(size_t)timestamp_channel] =
get_sample_timestamp (device_tick, sample, config.sampling_rate);
package[(size_t)timestamp_channel] = get_sample_timestamp (
last_aux_timestamp, host_timestamp, sample, config.n_samples, config.sampling_rate);
push_package (package.data (), (int)BrainFlowPresets::AUXILIARY_PRESET);
}
last_aux_timestamp = host_timestamp;
return;
}

Expand Down Expand Up @@ -947,26 +944,47 @@ void MuseAnthena::parse_sensor_payload (
}
}

package[(size_t)timestamp_channel] =
get_sample_timestamp (device_tick, sample, config.sampling_rate);
package[(size_t)timestamp_channel] = get_sample_timestamp (
last_anc_timestamp, host_timestamp, sample, config.n_samples, config.sampling_rate);
push_package (package.data (), (int)BrainFlowPresets::ANCILLARY_PRESET);
}
last_anc_timestamp = host_timestamp;
}
}

double MuseAnthena::get_sample_timestamp (
uint32_t device_tick, int sample_index, double sampling_rate)
void MuseAnthena::reset_timestamps ()
{
if (!timestamp_initialized)
last_eeg_timestamp = -1.0;
last_aux_timestamp = -1.0;
last_anc_timestamp = -1.0;
}

double MuseAnthena::get_sample_timestamp (double last_timestamp, double current_timestamp,
int sample_index, int n_samples, double sampling_rate)
{
if ((n_samples <= 0) || (sampling_rate <= 0.0))
{
return current_timestamp;
}

if (last_timestamp <= 0.0)
{
return current_timestamp - (double)(n_samples - sample_index - 1) / sampling_rate;
}

if (current_timestamp <= last_timestamp)
{
return current_timestamp;
}

double sample_timestamp = last_timestamp + (double)(sample_index + 1) / sampling_rate;
if (sample_timestamp <= current_timestamp)
{
timestamp_initialized = true;
first_device_tick = device_tick;
first_host_timestamp = get_timestamp ();
return sample_timestamp;
Comment on lines +980 to +983
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep per-packet timestamp interpolation monotonic

The new get_sample_timestamp logic can emit decreasing timestamps within a single payload because it decides per sample whether to use last + (sample+1)/sampling_rate or the interpolated step fallback. When current_timestamp - last_timestamp is smaller than n_samples/sampling_rate but larger than some early sample offsets (a normal jitter case), early samples take the fixed-rate path while later ones switch to interpolation, which can move backwards (e.g., with n_samples=4, sampling_rate=256, dt=10ms: sample 1 is 7.8125ms, sample 2 becomes 7.5ms). This breaks monotonic timestamps and can corrupt downstream time-based processing.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex adress this feedback

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Addressed the reviewer’s monotonicity concern in get_sample_timestamp by switching from per-sample fallback selection to a single mode per payload decision. Now the function uses fixed sampling-rate spacing only if the packet’s wall-clock span can cover the nominal payload duration; otherwise it interpolates all samples with a uniform step across the packet interval. This prevents backward timestamps within the same packet under jitter.
  • Kept existing guard behavior for invalid sample/rate inputs, first-packet handling, and non-increasing packet timestamps unchanged.
  • Committed the fix on the current branch with commit 4b7b650 and created a follow-up PR message via make_pr summarizing the change.

Testing

  • git diff --check -- src/board_controller/muse/muse_anthena.cpp
  • git status --short && git diff -- src/board_controller/muse/muse_anthena.cpp
  • git add src/board_controller/muse/muse_anthena.cpp && git commit -m "Fix Muse Anthena per-packet timestamp monotonicity"

View task →

}

uint32_t elapsed_ticks = device_tick - first_device_tick;
return first_host_timestamp + (double)elapsed_ticks / MUSE_ANTHENA_DEVICE_CLOCK_HZ +
(double)sample_index / sampling_rate;
double step = (current_timestamp - last_timestamp) / (double)n_samples;
return last_timestamp + step * (double)(sample_index + 1);
}

std::string MuseAnthena::bytes_to_string (const uint8_t *data, size_t size)
Expand Down
Loading