вторник, 6 сентября 2011 г.

Дружим Delphi и Bioexplorer

Решил немного вспомнить о основной теме блога, а именно разработке ПО под Emotiv и вопросах с ним связанных.
Сегодня я напишу о том как подружить свою программу написанную в Delphi и BioExplorer.

Зачем это может потребоваться. Ну к примеру когда вы решите написать гениальную программу распознавания и обработки мозговых волн, Вы (как впрочем и Я) обнаружите уйму элементарных вещей, которые необходмо реализовать в своей программе.

Отрисовка графиков, преобразование Фурье, цифровые фильтры, расчеты амплитуды, корреляции и дисперсии. И реализовать то их не сложно, но на это уходит время, которое не тратишь на проверку своей идеи. В итоге пока добрался до проверки идеи проходит месяц, а в итоге выясняется, что идея не только не гениальная, а совсем наоборот.

Вобщем, чтобы не происходило изобретания велосипеда, лучше уж взять BioExplorer и для начала воспользоваться всем, что в него уже понапихали уважаемые разработчики.
А в своей программе пока реализовать и проверить только то, что в нем - нет.

В принципе крутые программеры, могут дальше не читать, ничего нового они не узнают. Пост предназначен для тех, кто никогда не работал с ActiveX и не знает некоторых нюансов передачи данных между компонентами написаными на C++ и Delphi)

Итак, данные из BioExplorer-а можно брать посредством ActiveX компонента. Который появляется в системе автоматом при установке BioExplorer-a.

Но перед тем как его можно использовать в Delphi. Его необходимо добавить в список компонентов.

Для этого надо:
1) Зайти в меню Component и выбрать пункт "Import ActiveX Control"
2) В появившемся окошке выбрать "BioExplorerClient ActiveX Control v 1.0" и нажать кнопочку Install
3) Далее Delphi спросит установить компонент в существующий пак компонентов dclusr.dpk или в новый. Жмите OK (Гурманы могут сделать в отдельный.)
4) Затем Вам предложат перекомпилировать пак. Жмем ОК - мозг не включаем.
5) Смотрим на диалоговое окно которое поздравляет нас с добавлением новых компонентом.

В итоге на верхней линейке компонентов в закладке "ActiveX" появится иконка с невнятной надписью OCX - это то что нам надо.
При наведении на нее должна высвечиваться подсказка "Bioexplorer Client"

Уверенно жмем в иконку и кидаем компонент на форму.
Поздравляю половина работы сделана :)

Доработаем наше пустое приложение таким образом чтобы оно могло рисовать на графике TChart
данные которые приходят из BioExplorer.

Ставим на форму компонент TChart.

Прописываем FormCreate:
procedure TForm1.FormCreate(Sender: TObject);
begin
  BioExplorerClient1.Initialize;
  BioExplorerClient1.Connect('127.0.0.1');
end;

Затем прописываем обработчики добавления и удаления каналов
procedure TForm1.BioExplorerClient1ChannelAdded(ASender: TObject;
  ChannelID: Integer; const Name: WideString; SampleRate: Double);
var
  newser : TLineSeries;
begin
  newser := TLineSeries.Create(Chart1);
  newser.Tag := ChannelID;
  newser.Title := String(Name);
  Chart1.AddSeries(newser);
end;

procedure TForm1.BioExplorerClient1ChannelRemoved(ASender: TObject;
  ChannelID: Integer);
var
  i : Integer;
begin
  for i := 0 to Chart1.SeriesList.Count-1 do
  begin
    if Chart1.SeriesList[i].Tag = ChannelID then
        Chart1.RemoveSeries(Chart1.SeriesList[i]);
  end;
end;
Они будут добавлять и удалять графики в нашем приложении.
Tag нам нужен для того чтобы потом находить в какой график добавлять наши данные.

Ну и самое веселое обработчик прихода данных BioExplorerClient1ChannelSamples

procedure TForm1.BioExplorerClient1ChannelSamples(ASender: TObject;
  ChannelID: Integer; var Samples: Double; SampleCount: Integer);
var
  i : Integer;
  SamplePtr : PDouble;
  targetser : TChartSeries;
begin
  for i := 0 to Chart1.SeriesList.Count-1 do
  begin
    if Chart1.SeriesList[i].Tag = ChannelID then
      targetser := Chart1.SeriesList[i];
  end;

  SamplePtr :=  @Samples;
  for i := 0 to SampleCount-1 do
  begin
    targetser.Add(PDouble(Integer(SamplePtr)+i*SizeOf(double))^);
  end;
end;

Первый цикл ищет по номеру Tag-а в какую серию будут помещены данные.

Второй цикл перебирает значения в приходящем массиве Samples (не совсем массиве, точнее совсем не массиве :)).

Почему так приходится извращаться с указателями . Все просто - Delphi не любит работать с указателями , а C++ очень любит передавать массивы данных через указатели. И данные нам приходят в переменной Samples, но она содержит не одно число а несколько. Сколько их указывает переменная SampleCount.

В итоге мы строкой, получаем из переменной указатель на нее :
SamplePtr :=  @Samples;

А этой строкой, преобразуем этот указатель в число прибавляем к нему смещение на нужное количество элементов и преобразуем этот указатель обратно.
PDouble(Integer(SamplePtr)+i*SizeOf(double))^

Не красиво? Но работает и от этого никуда не деться.

И последний штрих ставим компонент Timer1 который раз в секунду будет проверять факт связи с BE и пытаться установить это подключение.
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if BioExplorerClient1.IsConnected > 0 then
  begin
    BioExplorerClient1.Connect('127.0.0.1');
  end
end;

Все запускаем BioExplorer.

Ставим объект Server подключаем к нему объект Source и какой нибудь фильтр, жмем Play и радуемся.


Мы сделали это!


А те кто это сделать поленился могут скачать готовый проект Delphi и дизайн для BioExplorer.

Также по теме можно почитать пост "Emotiv EPOC: укрощение под Delphi".

Да прибудет с Вами СИЛА на пути взлома КОДА МОЗГА :)

P.S. За идею оформления кода в блоге отдельное спасибо "Запискам дебианщика"

2 комментария:

  1. А знаешь, какой вопрос обычно задают люди, прочитав такое описание? - "Как поставить на форму компонент TChart" (c) ;-)

    ОтветитьУдалить
  2. Берешь в левую руку форму, в правую TChart и ставишь одно на другое :)

    ОтветитьУдалить