Создаем скетч Processing для наблюдения за пульсом
Получаем еще более стабильный источник пульса: отключаем плату от USB и добавляем имитатор в скетч Processing. Вводим константу управления
final boolean SIM=true;
модернизируем setup()
if (SIM) { thread("imitator"); } else { println(Serial.list()); Serial port=new Serial(this, "COM3", 9600); port.bufferUntil('\n'); }
и добавляем код самого имитатора
void imitator() { while (true) { try { Thread.sleep((int)random(500, 1000)); } catch (Exception e) { } addKnock(millis()); } }
Мы опять бережем draw() от пустых задержек и код имитатора получается чуть объемнее своего коллеги на плате, но результат одинаков - стабильный пульс 60 ударов в минуту.
Реализуем аскетично-практичную визуализацию пульса. Выводим текущее значение и примитивно изображаем равномерно ползущую влево ленту регистрации: фиксируем количество точек экрана на секунду
final int pxPerSec=50;
в setup() указываем размер графической области скетча и пересчитываем длительность окна хранения отсчетов пульса так, что бы хранить столько отсчетов, сколько их можно нарисовать
size(600, 250); timeWindow=1000*width/pxPerSec;
и, наконец-то, рисуем
void draw() { background(0, 0, 0); int currTime=millis(); stroke(255); strokeWeight(3); synchronized(knocks) { for (int m : knocks) { int x=width-pxPerSec*(currTime-m)/1000; line(x, 50, x, 110); } fill(0, 255, 0); textSize(20); text(rate(), 10, 20); } }
синхронизировав и addKnock()
void addKnock(int m){ synchronized(knocks){ knocks.add(m); while(m-knocks.peek()>timeWindow) knocks.remove(); } }
Недостаточно аскетично?! Добавим суровых зеленых вертикальных полос секундных отсчетов и чуть менее зеленых, но все же спартанских отметок 1/5 секунды перед выводом отметок пульса
strokeWeight(1); for(int t=(currTime/1000)*1000-timeWindow;t<=currTime;t+=1000){ int x=width-pxPerSec*(currTime-t)/1000; stroke(0,100,0); for(int x1=x;x1<x+pxPerSec;x1+=pxPerSec/5){ line(x1, 40, x1, 120); } stroke(0,255,0); line(x, 30, x, 130); }
Последним штрихом дорисуем посекундный график пульса: устанавливаем количество пикселей на отсчет и интервал
final int pxPerRate=2; final int rateStoreInterval=1000;
сколько секунд требуется хранить
int maxRates=1;
вычисляем в setup()
maxRates=width/pxPerRate;
Метод и очередь для накапливания отсчетов:
ArrayDeque<Integer> rates=new ArrayDeque<Integer>(); int rateLast=0; int addRate(int r) { int ms=millis(); if (ms-rateLast>rateStoreInterval) { rates.add(r); while (rates.size ()>maxRates) rates.remove(); rateLast=ms; } return r; }
Для вызова addRate заменяем в draw()
text(rate(), 10, 20);
на
text(addRate(rate()), 10, 20);
и добавляем код рисования отсчетов. Для экономии места вычитаем 40 ударов в минуту.
int x=width-pxPerRate; strokeWeight(pxPerRate); for (Iterator i = rates.descendingIterator (); i.hasNext(); x-=pxPerRate+1) { line(x, height-5-(Integer)i.next()+rateShift, x, height-5); } stroke(0, 255, 0, 100); lineRate(60); lineRate(90); lineRate(120);
где
void lineRate(int r){ line(0, height-5-r+rateShift, width, height-5-r+rateShift); }
и
final int rateShift=40;
Слишком ровно, непорядочек. Для наглядности внесем разнообразие в имитатор - заменим
Thread.sleep(1000);
на
Thread.sleep((int)random(500,1000));
Полный текст:
import processing.serial.*; import java.util.*; final boolean SIM=true; final int pxPerSec=50; final int pxPerRate=2; final int rateStoreInterval=1000; final int rateShift=40; ArrayDeque<Integer> knocks=new ArrayDeque<Integer>(); ArrayDeque<Integer> rates=new ArrayDeque<Integer>(); int timeWindow=1000*10; int maxRates=1; int rateLast=0; void setup() { size(600, 250); if (SIM) { thread("imitator"); } else { println(Serial.list()); Serial port=new Serial(this, "COM3", 9600); port.bufferUntil('\n'); } timeWindow=1000*width/pxPerSec; maxRates=width/pxPerRate; } void draw() { background(0, 0, 0); int currTime=millis(); strokeWeight(1); for (int t= (currTime/1000)*1000-timeWindow; t<=currTime; t+=1000) { int x=width-pxPerSec*(currTime-t)/1000; stroke(0, 100, 0); for (int x1=x; x1<x+pxPerSec; x1+=pxPerSec/5) { line(x1, 40, x1, 120); } stroke(0, 255, 0); line(x, 30, x, 130); } stroke(255); strokeWeight(3); synchronized(knocks) { for (int m : knocks) { int x=width-pxPerSec*(currTime-m)/1000; line(x, 50, x, 110); } fill(0, 255, 0); textSize(20); text(addRate(rate()), 10, 20); } int x=width-pxPerRate; strokeWeight(pxPerRate); for (Iterator i = rates.descendingIterator (); i.hasNext(); x-=pxPerRate+1) { line(x, height-5-(Integer)i.next()+rateShift, x, height-5); } stroke(0, 255, 0, 100); lineRate(60); lineRate(90); lineRate(120); } void lineRate(int r) { line(0, height-5-r+rateShift, width, height-5-r+rateShift); } void serialEvent(Serial p) { p.readString(); addKnock(millis()); } void addKnock(int m) { synchronized(knocks) { knocks.add(m); while (m-knocks.peek ()>timeWindow) knocks.remove(); } } int rate() { if (knocks.size()>1) { return round(60f*1000*(knocks.size()-1)/(knocks.peekLast()-knocks.peekFirst())); } return 0; } void imitator() { while (true) { try { Thread.sleep((int)random(500, 1000)); } catch (Exception e) { } addKnock(millis()); } } int addRate(int r) { int ms=millis(); if (ms-rateLast>rateStoreInterval) { rates.add(r); while (rates.size ()>maxRates) rates.remove(); rateLast=ms; } return r; }
Запускаем
Теперь можно в Arduino IDE перезалить в плату скетч, отключив в нем эмулятор, поменять значение SIM на false в Processing, отключить ноутбук от сети, надеть на ухо клипсу и получить визуализацию реального пульса.
Но мы этого делать не станем, а перейдем к разработке БОС-игры.