@@ -850,7 +850,13 @@ void LfpDisplaySplitter::beginAnimation()
850850 eventState = 0 ;
851851 }
852852
853- startTimer (20 );
853+ // Adaptive timer: slower refresh for high channel counts to reduce CPU load
854+ // Base: 50Hz (20ms), >384 channels: ~30Hz (33ms)
855+ int refreshIntervalMs = 20 ;
856+ if (nChans > 384 )
857+ refreshIntervalMs = 33 ; // ~30Hz for 256+ channels
858+
859+ startTimer (refreshIntervalMs);
854860
855861 reachedEnd = true ;
856862}
@@ -1162,7 +1168,7 @@ void LfpDisplaySplitter::updateScreenBuffer()
11621168 int triggerTime = hasTrigger ? int (*triggerTimeOpt) : -1 ;
11631169
11641170 int maxSamples = screenBufferWidth;
1165- int displayWidth = lfpDisplay->lfpChannelBitmap .getWidth ();
1171+ const int displayWidth = lfpDisplay->lfpChannelBitmap .getWidth ();
11661172
11671173 if (triggerChannel >= 0 )
11681174 {
@@ -1175,6 +1181,14 @@ void LfpDisplaySplitter::updateScreenBuffer()
11751181 processor->acknowledgeTrigger (splitID);
11761182 }
11771183
1184+ // Pre-compute loop invariants
1185+ const float ratio = sampleRate * timebase / float (displayWidth); // samples / pixel
1186+ const float invRatio = 1 .0f / ratio;
1187+ const int maxSamplesMinusOne = maxSamples - 1 ;
1188+ const bool isPaused = lfpDisplay->isPaused ();
1189+ const bool isTriggeredMode = triggerChannel >= 0 ;
1190+ const bool shouldAverage = isTriggeredMode && trialAveraging;
1191+
11781192 for (int channel = 0 ; channel <= nChans; channel++) // pull one extra channel for event display
11791193 {
11801194 int dbi = displayBufferIndex[channel]; // display buffer index from the last round of drawing
@@ -1196,10 +1210,8 @@ void LfpDisplaySplitter::updateScreenBuffer()
11961210 // if (channel == 0)
11971211 // std::cout << newSamples << " new samples." << std::endl;
11981212
1199- // this number is crucial -- converting from samples to values (in px) for the screen buffer:
1200- float ratio = sampleRate * timebase / float (displayWidth); // samples / pixel
1201-
1202- float pixelsToFill = float (newSamples) / ratio; // M pixels to update
1213+ // ratio is pre-computed above the channel loop
1214+ float pixelsToFill = float (newSamples) * invRatio; // M pixels to update
12031215
12041216 int sbi = screenBufferIndex[channel];
12051217
@@ -1235,7 +1247,7 @@ void LfpDisplaySplitter::updateScreenBuffer()
12351247 if (newSamples < 0 )
12361248 newSamples += displayBufferSize;
12371249
1238- pixelsToFill = newSamples / ratio ;
1250+ pixelsToFill = newSamples * invRatio ;
12391251 subSampleOffset = 0 ;
12401252
12411253 // rewind screen buffer to the far left
@@ -1306,57 +1318,70 @@ void LfpDisplaySplitter::updateScreenBuffer()
13061318 {
13071319 float i;
13081320
1321+ // Get raw pointers for this channel to avoid repeated getSample() calls
1322+ const float * displayData = displayBuffer->getReadPointer (channel);
1323+ float * meanWritePtr = (channel < nChans) ? screenBufferMean->getWritePointer (channel) : nullptr ;
1324+ float * minWritePtr = (channel < nChans) ? screenBufferMin->getWritePointer (channel) : nullptr ;
1325+ float * maxWritePtr = (channel < nChans) ? screenBufferMax->getWritePointer (channel) : nullptr ;
1326+ const float * meanReadPtr = meanWritePtr;
1327+ const float * minReadPtr = minWritePtr;
1328+ const float * maxReadPtr = maxWritePtr;
1329+ float * eventWritePtr = eventDisplayBuffer->getWritePointer (0 );
1330+
1331+ const bool isEventChannel = (channel == nChans);
1332+ const bool shouldClearBuffers = ! isTriggeredMode || numTrials == 0 || ! trialAveraging;
1333+ const float trialGain = (numTrials > 0 ) ? static_cast <float > (numTrials) : 1 .0f ;
1334+
13091335 for (i = 0 ; i < pixelsToFill; i++)
13101336 {
1311- if (! lfpDisplay-> isPaused () )
1337+ if (! isPaused)
13121338 {
1313- if (channel == nChans )
1339+ if (isEventChannel )
13141340 {
1315- eventDisplayBuffer-> clear ( 0 , sbi, 1 ) ;
1341+ eventWritePtr[ sbi] = 0 . 0f ;
13161342 }
13171343 else
13181344 {
1319- if (triggerChannel < 0 || numTrials == 0 || trialAveraging == false )
1345+ if (shouldClearBuffers )
13201346 {
1321- screenBufferMean-> clear (channel, sbi, 1 ) ;
1322- screenBufferMin-> clear (channel, sbi, 1 ) ;
1323- screenBufferMax-> clear (channel, sbi, 1 ) ;
1347+ meanWritePtr[ sbi] = 0 . 0f ;
1348+ minWritePtr[ sbi] = 0 . 0f ;
1349+ maxWritePtr[ sbi] = 0 . 0f ;
13241350 }
13251351 else
13261352 {
1327- screenBufferMean-> applyGain (channel, sbi, 1 , numTrials) ;
1328- screenBufferMin-> applyGain (channel, sbi, 1 , numTrials) ;
1329- screenBufferMax-> applyGain (channel, sbi, 1 , numTrials) ;
1353+ meanWritePtr[ sbi] *= trialGain ;
1354+ minWritePtr[ sbi] *= trialGain ;
1355+ maxWritePtr[ sbi] *= trialGain ;
13301356 }
13311357 }
13321358
1333- if (ratio < 1.0 ) // less than one sample per pixel
1359+ if (ratio < 1 .0f ) // less than one sample per pixel
13341360 {
1335- if (channel == nChans )
1361+ if (isEventChannel )
13361362 {
1337- eventDisplayBuffer-> setSample ( 0 , sbi, displayBuffer-> getSample (channel, dbi)) ;
1363+ eventWritePtr[ sbi] = displayData[ dbi] ;
13381364 }
13391365 else
13401366 {
1341- float alpha = subSampleOffset;
1342- float invAlpha = 1 .0f - alpha;
1367+ const float alpha = subSampleOffset;
1368+ const float invAlpha = 1 .0f - alpha;
13431369
13441370 int lastIndex = dbi - 1 ;
13451371
13461372 if (lastIndex < 0 )
13471373 {
1348- lastIndex = displayBufferSize;
1374+ lastIndex = displayBufferSize - 1 ;
13491375 continue ;
13501376 }
13511377
1352- float val0 = displayBuffer->getSample (channel, lastIndex);
1353- float val1 = displayBuffer->getSample (channel, dbi);
1354-
1355- float val = invAlpha * val0 + alpha * val1;
1378+ const float val0 = displayData[lastIndex];
1379+ const float val1 = displayData[dbi];
1380+ const float val = invAlpha * val0 + alpha * val1;
13561381
1357- screenBufferMean-> addSample (channel, sbi, val) ;
1358- screenBufferMin-> addSample (channel, sbi, val) ;
1359- screenBufferMax-> addSample (channel, sbi, val) ;
1382+ meanWritePtr[ sbi] += val;
1383+ minWritePtr[ sbi] += val;
1384+ maxWritePtr[ sbi] += val;
13601385 }
13611386
13621387 subSampleOffset += ratio;
@@ -1371,85 +1396,143 @@ void LfpDisplaySplitter::updateScreenBuffer()
13711396 else
13721397 { // more than one sample per pixel
13731398
1374- float sample_min = 10000000 ;
1375- float sample_max = -10000000 ;
1376- float sample_sum = 0 ;
1377- float sampleCount = 0 ;
1399+ float sample_min = 1e10f;
1400+ float sample_max = -1e10f;
1401+ float sample_sum = 0 .0f ;
13781402
13791403 subSampleOffset += ratio;
13801404
1381- if (subSampleOffset <= 1 .0f )
1405+ // Calculate how many samples we need to process for this pixel
1406+ int samplesToProcess = static_cast <int > (subSampleOffset);
1407+ if (sampleNumber + samplesToProcess > newSamples)
1408+ samplesToProcess = newSamples - sampleNumber;
1409+
1410+ if (samplesToProcess <= 0 )
13821411 {
1383- sample_sum = displayBuffer-> getSample (channel, dbi) ;
1384- sample_min = sample_sum ;
1385- sample_max = sample_sum ;
1386- sampleCount = 1 . 0f ;
1412+ subSampleOffset -= 1 . 0f ;
1413+ dbi = (dbi + 1 ) % displayBufferSize ;
1414+ sampleNumber++ ;
1415+ continue ;
13871416 }
13881417
1389- bool foundIt = false ;
1418+ // Check if data is contiguous (doesn't wrap around buffer)
1419+ const int samplesUntilWrap = displayBufferSize - dbi;
13901420
1391- while (subSampleOffset > 1 . 0f && sampleNumber < newSamples )
1421+ if (samplesToProcess >= 4 && samplesToProcess <= samplesUntilWrap )
13921422 {
1393- sampleNumber++;
1423+ // SIMD path: contiguous data, use vectorized operations
1424+ const float * dataPtr = displayData + dbi;
13941425
1395- float sample_current = displayBuffer->getSample (channel, dbi);
1426+ // Use JUCE's SIMD-accelerated min/max finder
1427+ juce::Range<float > minMax = juce::FloatVectorOperations::findMinAndMax (dataPtr, samplesToProcess);
1428+ sample_min = minMax.getStart ();
1429+ sample_max = minMax.getEnd ();
13961430
1397- sample_sum = sample_sum + sample_current;
1431+ // SIMD sum - process 4 floats at a time
1432+ int idx = 0 ;
1433+ const int simdWidth = 4 ;
1434+ const int simdIterations = samplesToProcess / simdWidth;
13981435
1399- if (sample_min >= sample_current )
1436+ if (simdIterations > 0 )
14001437 {
1401- sample_min = sample_current;
1438+ float sumAccum[4 ] = { 0 .0f , 0 .0f , 0 .0f , 0 .0f };
1439+
1440+ for (int s = 0 ; s < simdIterations; s++)
1441+ {
1442+ sumAccum[0 ] += dataPtr[idx];
1443+ sumAccum[1 ] += dataPtr[idx + 1 ];
1444+ sumAccum[2 ] += dataPtr[idx + 2 ];
1445+ sumAccum[3 ] += dataPtr[idx + 3 ];
1446+ idx += simdWidth;
1447+ }
1448+
1449+ sample_sum = sumAccum[0 ] + sumAccum[1 ] + sumAccum[2 ] + sumAccum[3 ];
14021450 }
14031451
1404- if (sample_max <= sample_current)
1452+ // Handle remaining samples
1453+ for (; idx < samplesToProcess; idx++)
14051454 {
1406- sample_max = sample_current ;
1455+ sample_sum += dataPtr[idx] ;
14071456 }
14081457
1409- subSampleOffset -= 1 .0f ;
1458+ dbi = (dbi + samplesToProcess) % displayBufferSize;
1459+ sampleNumber += samplesToProcess;
1460+ subSampleOffset -= static_cast <float > (samplesToProcess);
1461+ }
1462+ else
1463+ {
1464+ // Scalar path: handle wrap-around or small sample counts
1465+ // Process first chunk (up to buffer end)
1466+ const int firstChunk = std::min (samplesToProcess, samplesUntilWrap);
14101467
1411- dbi += 1 ;
1412- dbi %= displayBufferSize;
1468+ for (int s = 0 ; s < firstChunk; s++)
1469+ {
1470+ const float sample_current = displayData[dbi + s];
1471+ sample_sum += sample_current;
1472+
1473+ if (sample_current < sample_min)
1474+ sample_min = sample_current;
1475+ if (sample_current > sample_max)
1476+ sample_max = sample_current;
1477+ }
1478+
1479+ // Process second chunk (from buffer start) if wrapped
1480+ const int secondChunk = samplesToProcess - firstChunk;
1481+ for (int s = 0 ; s < secondChunk; s++)
1482+ {
1483+ const float sample_current = displayData[s];
1484+ sample_sum += sample_current;
1485+
1486+ if (sample_current < sample_min)
1487+ sample_min = sample_current;
1488+ if (sample_current > sample_max)
1489+ sample_max = sample_current;
1490+ }
14131491
1414- sampleCount += 1 .0f ;
1492+ dbi = (dbi + samplesToProcess) % displayBufferSize;
1493+ sampleNumber += samplesToProcess;
1494+ subSampleOffset -= static_cast <float > (samplesToProcess);
14151495 }
14161496
1417- float sample_mean = sample_sum / sampleCount;
1497+ const float sampleCount = static_cast <float > (samplesToProcess);
1498+ const float sample_mean = sample_sum / sampleCount;
14181499
14191500 // update event channel
1420- if (channel == nChans )
1501+ if (isEventChannel )
14211502 {
1422- eventDisplayBuffer-> setSample ( 0 , sbi, sample_max) ;
1503+ eventWritePtr[ sbi] = sample_max;
14231504 }
14241505 else
14251506 {
14261507 if (sbi > 0 )
14271508 {
1428- if (sample_max < screenBufferMin->getSample (channel, sbi - 1 ))
1429- sample_max = screenBufferMin->getSample (channel, sbi - 1 );
1509+ const int prevSbi = sbi - 1 ;
1510+ if (sample_max < minReadPtr[prevSbi])
1511+ sample_max = minReadPtr[prevSbi];
14301512
1431- if (sample_min > screenBufferMax-> getSample (channel, sbi - 1 ) )
1432- sample_min = screenBufferMax-> getSample (channel, sbi - 1 ) ;
1513+ if (sample_min > maxReadPtr[prevSbi] )
1514+ sample_min = maxReadPtr[prevSbi] ;
14331515 }
14341516
1435- screenBufferMean-> addSample (channel, sbi, sample_mean) ;
1436- screenBufferMin-> addSample (channel, sbi, sample_min) ;
1437- screenBufferMax-> addSample (channel, sbi, sample_max) ;
1517+ meanWritePtr[ sbi] += sample_mean;
1518+ minWritePtr[ sbi] += sample_min;
1519+ maxWritePtr[ sbi] += sample_max;
14381520 }
14391521 }
14401522
1441- if (triggerChannel >= 0 && trialAveraging == true && channel != nChans )
1523+ if (shouldAverage && ! isEventChannel )
14421524 {
1443- screenBufferMean->applyGain (channel, sbi, 1 , 1 / (numTrials + 1 ));
1444- screenBufferMin->applyGain (channel, sbi, 1 , 1 / (numTrials + 1 ));
1445- screenBufferMax->applyGain (channel, sbi, 1 , 1 / (numTrials + 1 ));
1525+ const float avgGain = 1 .0f / (numTrials + 1 .0f );
1526+ meanWritePtr[sbi] *= avgGain;
1527+ minWritePtr[sbi] *= avgGain;
1528+ maxWritePtr[sbi] *= avgGain;
14461529 }
14471530
14481531 sbi++;
14491532
1450- if (triggerChannel >= 0 )
1533+ if (isTriggeredMode )
14511534 {
1452- if (sbi == maxSamples - 1 )
1535+ if (sbi == maxSamplesMinusOne )
14531536 {
14541537 // std::cout << "CH " << channel << " reached end: " << maxSamples << " samples " << std::endl;
14551538
0 commit comments