Browse Source

improved everything. new ui, known bug: curve forms are not applied to midi note

Zoadian 11 years ago
parent
commit
0374636807

+ 6 - 0
drumduino.sln

@@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 2012
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "drumduino", "drumduino\drumduino.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "drumduino_firmware", "..\drumduino_firmware\drumduino_firmware.vcxproj", "{2C655178-8BFB-4C03-A05E-CE1942A80465}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Win32 = Debug|Win32
@@ -13,6 +15,10 @@ Global
 		{B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|Win32.Build.0 = Debug|Win32
 		{B12702AD-ABFB-343A-A199-8E24837244A3}.Release|Win32.ActiveCfg = Release|Win32
 		{B12702AD-ABFB-343A-A199-8E24837244A3}.Release|Win32.Build.0 = Release|Win32
+		{2C655178-8BFB-4C03-A05E-CE1942A80465}.Debug|Win32.ActiveCfg = Debug|Win32
+		{2C655178-8BFB-4C03-A05E-CE1942A80465}.Debug|Win32.Build.0 = Debug|Win32
+		{2C655178-8BFB-4C03-A05E-CE1942A80465}.Release|Win32.ActiveCfg = Release|Win32
+		{2C655178-8BFB-4C03-A05E-CE1942A80465}.Release|Win32.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 92 - 0
drumduino/channel.cpp

@@ -0,0 +1,92 @@
+#include "stdafx.h"
+#include "channel.h"
+
+#include "curve.h"
+
+
+
+Channel::Channel(int channel, ChannelSettings& channelSettings, QWidget* parent)
+	: QWidget(parent)
+	, _channel(channel)
+	, _channelSettings(channelSettings)
+	, _curvePlot(new QCustomPlot(this))
+{
+	ui.setupUi(this);
+	ui.layoutCurvePlot->addWidget(_curvePlot);
+
+	QStringList notes;
+	notes << "C" << "C#" << "D" << "D#" << "E" << "F" << "F#" << "G" << "G#" << "A" << "A#" << "B";
+
+	QStringList oktaves;
+
+	for(int i = 0; i < 128; ++i) {
+		oktaves << QString::number(int(i / 12) - 1) + " " + notes[i % 12] + "";
+	}
+
+	ui.cbNote->clear();
+	ui.cbNote->addItems(oktaves);
+
+	_curvePlot->addGraph();
+	_curvePlot->xAxis->setRange(0, 127);
+	_curvePlot->yAxis->setRange(0, 127);
+	_curvePlot->setMinimumHeight(60);
+
+	_curvePlot->axisRect()->setAutoMargins(QCP::msNone);
+	_curvePlot->axisRect()->setMargins(QMargins(1, 1, 1, 1));
+
+	ui.leName->setPlaceholderText("Channel " + QString::number(channel));
+
+	updateUi();
+
+	connect(ui.cbSensor, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int index) {_channelSettings.type = (Type)index; updateUi(); });
+
+	connect(ui.cbNote, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int index) {_channelSettings.note = index; updateUi(); });
+
+	connect(ui.dialThresold, &QDial::valueChanged, [this](int value) {_channelSettings.thresold = value; updateUi(); });
+
+	connect(ui.dialScanTime, &QDial::valueChanged, [this](int value) {_channelSettings.scanTime = value; updateUi(); });
+
+	connect(ui.dialMaskTime, &QDial::valueChanged, [this](int value) {_channelSettings.maskTime = value; updateUi(); });
+
+	connect(ui.cbCurveType, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int index) { _channelSettings.curveType = (Curve)index; updateUi(); });
+
+	connect(ui.dialOffset, &QDial::valueChanged, [this](int value) {_channelSettings.curveValue = value; updateUi(); });
+
+}
+
+Channel::~Channel()
+{
+
+}
+
+void Channel::updateUi()
+{
+	ui.cbSensor->setCurrentIndex(_channelSettings.type);
+
+	ui.cbNote->setCurrentIndex(_channelSettings.note);
+
+	ui.labelThresold->setText(QString::number(_channelSettings.thresold));
+	ui.dialThresold->setValue(_channelSettings.thresold);
+
+	ui.labelScanTime->setText(QString::number(_channelSettings.scanTime));
+	ui.dialScanTime->setValue(_channelSettings.scanTime);
+
+	ui.labelMaskTime->setText(QString::number(_channelSettings.maskTime));
+	ui.dialMaskTime->setValue(_channelSettings.maskTime);
+
+	ui.cbCurveType->setCurrentIndex(_channelSettings.curveType);
+
+	ui.labelOffset->setText(QString::number(_channelSettings.curveValue));
+	ui.dialOffset->setValue(_channelSettings.curveValue);
+
+	QVector<qreal> x(127);
+	QVector<qreal> y(127);
+
+	for(auto i = 0; i < 127; ++i) {
+		x[i] = i;
+		y[i] = calcCurve(_channelSettings.curveType, i, _channelSettings.curveValue);
+	}
+
+	_curvePlot->graph(0)->setData(x, y);
+	_curvePlot->replot();
+}

+ 28 - 0
drumduino/channel.h

@@ -0,0 +1,28 @@
+#ifndef CHANNEL_H
+#define CHANNEL_H
+
+#include <QWidget>
+#include "ui_channel.h"
+
+#include "settings.h"
+#include "qcustomplot.h"
+
+class Channel : public QWidget
+{
+	Q_OBJECT
+private:
+	int _channel;
+	ChannelSettings& _channelSettings;
+	QCustomPlot* _curvePlot;
+
+public:
+	Channel(int channel, ChannelSettings& channelSettings, QWidget *parent = 0);
+	~Channel();
+
+private:
+	Ui::channel ui;
+
+	void updateUi();
+};
+
+#endif // CHANNEL_H

+ 299 - 0
drumduino/channel.ui

@@ -0,0 +1,299 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>channel</class>
+ <widget class="QWidget" name="channel">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>112</width>
+    <height>559</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>channel</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLineEdit" name="leName">
+     <property name="text">
+      <string/>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+     <property name="placeholderText">
+      <string>Snare</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QFormLayout" name="formLayout">
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_9">
+       <property name="text">
+        <string>Note</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QComboBox" name="cbNote">
+       <item>
+        <property name="text">
+         <string>C#</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QComboBox" name="cbSensor">
+       <property name="currentText">
+        <string>Mute</string>
+       </property>
+       <property name="frame">
+        <bool>true</bool>
+       </property>
+       <item>
+        <property name="text">
+         <string>Mute</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Piezo</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+     <item row="0" column="0">
+      <widget class="QLabel" name="label_10">
+       <property name="text">
+        <string>Sensor</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Sensor</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_3">
+      <item row="5" column="0">
+       <widget class="QDial" name="dialMaskTime">
+        <property name="maximum">
+         <number>127</number>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QDial" name="dialThresold">
+        <property name="maximum">
+         <number>127</number>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="0">
+       <widget class="QDial" name="dialScanTime">
+        <property name="maximum">
+         <number>127</number>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="QLabel" name="labelThresold">
+        <property name="text">
+         <string>50</string>
+        </property>
+        <property name="scaledContents">
+         <bool>false</bool>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item row="5" column="1">
+       <widget class="QLabel" name="labelMaskTime">
+        <property name="text">
+         <string>127</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="1">
+       <widget class="QLabel" name="labelScanTime">
+        <property name="text">
+         <string>127</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="0">
+       <widget class="QLabel" name="label_21">
+        <property name="text">
+         <string>Thresold</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="label_8">
+        <property name="text">
+         <string>Scan Time</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="0">
+       <widget class="QLabel" name="label_11">
+        <property name="text">
+         <string>Mask Time</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
+       </widget>
+      </item>
+     </layout>
+     <zorder>label_21</zorder>
+     <zorder>dialThresold</zorder>
+     <zorder>label_8</zorder>
+     <zorder>dialScanTime</zorder>
+     <zorder>label_11</zorder>
+     <zorder>dialMaskTime</zorder>
+     <zorder>labelThresold</zorder>
+     <zorder>labelMaskTime</zorder>
+     <zorder>labelScanTime</zorder>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_2">
+     <property name="title">
+      <string>Curve</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_2">
+      <item row="3" column="0">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Factor</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="0" colspan="2">
+       <widget class="QComboBox" name="cbCurveType">
+        <item>
+         <property name="text">
+          <string>Normal</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>Exp</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>Log</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>Sigma</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>Flat</string>
+         </property>
+        </item>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <widget class="QLabel" name="labelOffset">
+        <property name="text">
+         <string>127</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="1">
+       <widget class="QLabel" name="labelFactor">
+        <property name="text">
+         <string>127</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0">
+       <widget class="QDial" name="dialOffset">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="maximum">
+         <number>127</number>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Offset</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="0">
+       <widget class="QDial" name="dialFactor">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="maximum">
+         <number>127</number>
+        </property>
+       </widget>
+      </item>
+      <item row="5" column="0" colspan="2">
+       <layout class="QVBoxLayout" name="layoutCurvePlot"/>
+      </item>
+     </layout>
+     <zorder>cbCurveType</zorder>
+     <zorder>dialOffset</zorder>
+     <zorder>label</zorder>
+     <zorder>labelOffset</zorder>
+     <zorder>dialFactor</zorder>
+     <zorder>label_2</zorder>
+     <zorder>labelFactor</zorder>
+     <zorder>verticalLayoutWidget</zorder>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+</ui>

+ 43 - 0
drumduino/curve.h

@@ -0,0 +1,43 @@
+#pragma once
+
+
+inline byte calcCurve(Curve curve, int Value, int Form)
+{
+	int ret = 0;
+
+	float x = Value * 8.0;
+	float f = ((float)Form) / 64.0; //[1;127]->[0.;2.0]
+
+	switch(curve) {
+		//[0-1023]x[0-127]
+		case 0:
+			ret = x * f / 16.0;
+			break;
+
+		case 1:
+			ret = (127.0 / (exp(2.0 * f) - 1)) * (exp(f * x / 512.0) - 1.0);
+			break; //Exp 4*(exp(x/256)-1)
+
+		case 2:
+			ret = log(1.0 + (f * x / 128.0)) * (127.0 / log((8 * f) + 1));
+			break; //Log 64*log(1+x/128)
+
+		case 3:
+			ret = (127.0 / (1.0 + exp(f * (512.0 - x) / 64.0)));
+			break; //Sigma
+
+		case 4:
+			ret = (64.0 - ((8.0 / f) * log((1024 / (1 + x)) - 1)));
+			break; //Flat
+
+		case eXTRA:
+			ret = (x + 0x20) * f / 16.0;
+
+	}
+
+	if(ret <= 0) { return 0; }
+
+	if(ret >= 127) { return 127; } //127
+
+	return ret;
+}

+ 495 - 101
drumduino/drumduino.cpp

@@ -3,62 +3,438 @@
 
 #include "porttab.h"
 
+#include "channel.h"
+#include "curve.h"
+
+
 size_t mapChannels(size_t channel)
 {
-	return channel;
-	//size_t port = channel / 8;
-	//size_t chan = channel % 8;
+	size_t port = channel / CHAN_CNT;
+	size_t chan = channel % CHAN_CNT;
+
+	const size_t pinMapping[8] = {2, 4, 1, 6, 0, 7, 3, 5};
+	return port * CHAN_CNT + pinMapping[chan];
+}
+
+bool readNextFrame(std::shared_ptr<Serial>& serial, DrumduinoProc& proc)
+{
+AGAIN:
+	auto available = serial->available();
+
+	if(available < 2 + PORT_CNT * CHAN_CNT) {
+		return false;
+	}
+
+	byte sentinel;
+	serial->readBytes(&sentinel, 1);
+
+	if(sentinel != 0xf0) {
+		goto AGAIN;
+	}
+
+	byte manufacturer;
+	serial->readBytes(&manufacturer, 1);
+
+	auto& frame = proc.frameBuffer[proc.frameCounter % BufferSize];
+	serial->readBytes(frame.data(), frame.size());
+
+	return true;
+}
+
+void sendSysexPrescalerThrottle(std::shared_ptr<Serial>& serial, byte prescaler, byte throttle)
+{
+	byte msg[] = {0xf0, 42, prescaler, throttle, 0xF7};
+	serial->write(msg, sizeof(msg));
+}
+
+void midiNoteOn(std::shared_ptr<MidiOut>& midiOut, byte channel, byte note, byte velocity)
+{
+	byte data[] = {0x90 | channel, 0x7f & note , 0x7f & velocity };
+	std::vector<byte> message(sizeof(data));
+	memcpy(message.data(), data, message.size());
+	midiOut->send(message);
+}
+
+void processFrame(std::shared_ptr<MidiOut>& midiOut, DrumduinoProc& proc, const Settings& settings)
+{
+	const auto& lastFrame = proc.frameBuffer[(proc.frameCounter - 1) % BufferSize];
+	const auto& currentFrame = proc.frameBuffer[proc.frameCounter % BufferSize];
 
-	//const size_t pinMapping[8] = {2, 4, 1, 6, 0, 7, 3, 5};
-	//return port * CHAN_CNT + pinMapping[chan] - 1;
+	for(auto channel = 0; channel < PORT_CNT * CHAN_CNT; ++channel) {
+		const auto& lastValue = lastFrame[mapChannels(channel)];
+		const auto& currentValue = currentFrame[mapChannels(channel)];
+
+		auto& state = proc.states[channel];
+		auto& triggerFrame = proc.triggers[channel];
+		auto& maxValue = proc.maxs[channel];
+
+		const auto& channelSettings = settings.channelSettings[channel];
+
+		switch(channelSettings.type) {
+			case TypePiezo: {
+				switch(state) {
+					// In this state we wait for a signal to trigger
+					case StateAwait: {
+STATE_AGAIN:
+
+						if(currentValue < lastValue + channelSettings.thresold) {
+							break;
+						}
+
+						state = StateScan;
+						triggerFrame = proc.frameCounter;
+						maxValue = currentValue;
+						//fallthrough
+					}
+
+					// In this state we measure the value for the given time period to get the max value
+					case StateScan: {
+						if(proc.frameCounter < triggerFrame + channelSettings.scanTime) {
+							maxValue = std::max(currentValue, maxValue);
+							break;
+						}
+
+						midiNoteOn(midiOut, settings.midiChannel, channelSettings.note, maxValue);
+						state = StateMask;
+						//fallthrough
+
+					}
+
+					// In this state we do nothing to prevent retriggering
+					case StateMask: {
+						if(proc.frameCounter < triggerFrame + channelSettings.scanTime + channelSettings.maskTime) {
+							break;
+						}
+
+						state = StateAwait;
+						goto STATE_AGAIN;
+					}
+
+					default: {
+						throw std::exception("not a valid state!");
+					}
+				}
+			}
+		}
+	}
 }
 
 
-drumduino::drumduino(QWidget* parent)
+
+Drumduino::Drumduino(QWidget* parent)
 	: QMainWindow(parent)
-	, _lasttime(QDateTime::currentMSecsSinceEpoch())
 {
 	ui.setupUi(this);
 
+	ui.cbPrescaler->setCurrentIndex(_settings.prescaler);
+	ui.sbThrottle->setValue(_settings.throttle);
+
+	connect(ui.cbPrescaler, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int index) {
+		_settings.prescaler = index;
+		sendSysexPrescalerThrottle(_serial, _settings.prescaler, _settings.throttle);
+	});
+
+	connect(ui.sbThrottle, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this](int value) {
+		_settings.throttle = value;
+		sendSysexPrescalerThrottle(_serial, _settings.prescaler, _settings.throttle);
+	});
+
+	// Setup Channel Settings
+	for(auto channel = 0; channel < PORT_CNT * CHAN_CNT; ++channel) {
+		_settings.channelSettings[channel].note = channel;
+	}
+
+	// Setup Channels
+	for(auto port = 0; port < PORT_CNT; ++port) {
+		auto wgtPort = ui.tabWidget->widget(ui.tabWidget->addTab(new PortTab(), "Port " + QString::number(port)));
+
+		for(auto pin = 0; pin < CHAN_CNT; ++pin) {
+			auto channel = port * CHAN_CNT + pin;
+			auto wgtChannel = new Channel(channel, _settings.channelSettings[channel], wgtPort);
+			wgtPort->layout()->addWidget(wgtChannel);
+		}
+	}
+
+	// action Save
+	connect(ui.actionSave, &QAction::triggered, [this]() {
+		auto fileName = QFileDialog::getSaveFileName(this, "Save", 0, tr("drumduino (*.edrum)"));
+
+		QFile file(fileName);
+		file.write((const char*)&_settings, sizeof(_settings));
+		file.close();
+	});
+
+	// action Load
+	connect(ui.actionLoad, &QAction::triggered, [this]() {
+		auto fileName = QFileDialog::getOpenFileName(this, "Open", 0, tr("drumduino (*.edrum)"));
+		QFile file(fileName);
+		file.read((char*)&_settings, sizeof(_settings));
+		file.close();
+	});
+
+
+
+	_serial = std::make_shared<Serial>(L"COM3", 115200);
+	_midiOut = std::make_shared<MidiOut>(1);
+
+
+	_drumduinoThread = new DrumduinoThread(this, [this]() {
+		if(readNextFrame(_serial, _proc)) {
+			processFrame(_midiOut, _proc, _settings);
+
+#if 1
+			_proc.stateBuffer[_proc.frameCounter % BufferSize] = _proc.states;
+#endif
+
+			++_proc.frameCounter;
+		}
+	});
+	_drumduinoThread->start();
+
+
+
+#if 1
+	{
+		std::array<QCustomPlot*, PORT_CNT* CHAN_CNT> plots;
+
+		for(auto port = 0; port < PORT_CNT; ++port) {
+			auto wgtPort = ui.tabWidget->widget(ui.tabWidget->addTab(new PortTab(), "Graph_Port " + QString::number(port)));
+
+			auto table = new QTableWidget(CHAN_CNT, 1, wgtPort);
+			table->horizontalHeader()->setStretchLastSection(true);
+			table->verticalHeader()->setMinimumHeight(100);
+			wgtPort->layout()->addWidget(table);
+
+			for(auto pin = 0; pin < CHAN_CNT; ++pin) {
+				auto channel = port * CHAN_CNT + pin;
+				auto wgtPlot = new QCustomPlot(table);
+				wgtPlot->addGraph();
+				table->setRowHeight(pin, 127);
+				table->setCellWidget(pin, 0, wgtPlot);
+
+				wgtPlot->xAxis->setRange(0, BufferSize);
+				wgtPlot->yAxis->setRange(0, 127);
+				wgtPlot->yAxis2->setRange(0, 2);
+				wgtPlot->yAxis2->setVisible(true);
+
+				auto stateGraph = wgtPlot->addGraph(wgtPlot->xAxis, wgtPlot->yAxis2);
+				stateGraph->setPen(QPen(Qt::red));
+				stateGraph->setLineStyle(QCPGraph::LineStyle::lsStepLeft);
+
+				plots[port * CHAN_CNT + pin] = wgtPlot;
+			}
+		}
+
+		QTimer* timer = new QTimer(this);
+		connect(timer, &QTimer::timeout, [this, plots]() {
+			auto currentIndex = _proc.frameCounter % BufferSize;
+			QVector<qreal> x(BufferSize);
+			QVector<qreal> y(BufferSize);
+			QVector<qreal> s(BufferSize);
+
+			for(auto i = 0; i < BufferSize; ++i) {
+				x[i] = i;
+			}
+
+			for(auto i = 0; i < PORT_CNT * CHAN_CNT; ++i) {
+				if(plots[i]->isVisible()) {
+					auto channel = mapChannels(i);
+					plots[i]->xAxis->setRange(x.front(), x.back());
+
+					for(auto k = 0; k < BufferSize; ++k) {
+						y[k] = _proc.frameBuffer[k][channel];
+						s[k] = _proc.stateBuffer[k][i];
+					}
+
+					plots[i]->graph(0)->setData(x, y);
+					plots[i]->graph(1)->setData(x, s);
+
+					plots[i]->replot();
+				}
+			}
+		});
+		timer->start(1000 / 12);
+	}
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#if 0
+
+	return;
+
+
+
+	_startTime = QDateTime::currentMSecsSinceEpoch();
+
+	for(auto i = 0; i < 5; ++i) {
+		_settings.channelSettings[0].type = TypePiezo;
+	}
+
 	for(auto i = 0; i < PORT_CNT; ++i) {
 		ui.tabWidget->addTab(new PortTab(), "Port_" + QString::number(i));
+		auto tab = ui.tabWidget->widget(i);
+		auto table = tab->findChild<QTableWidget*>("tableWidget");
+		table->setRowCount(8);
+		table->setColumnCount(9);
+
+		QStringList headers;
+		headers << "type" << "note" << "thresold" << "scanTime" << "maskTime" << "CurveType" << "CurveValue" << "CurveForm" << "Graph";
+		table->setHorizontalHeaderLabels(headers);
 	}
 
-	for(auto i = 0; i < PORT_CNT * CHAN_CNT; ++i) {
-		_plots.push_back(new QCustomPlot(ui.tabWidget->widget(i / 8)));
-		_plots.back()->addGraph();
+	for(auto channel = 0; channel < PORT_CNT * CHAN_CNT; ++channel) {
+
+		_settings.channelSettings[channel].note = 35 + channel;
+
+
+		auto tab = ui.tabWidget->widget(channel / 8);
+		auto table = tab->findChild<QTableWidget*>("tableWidget");
+		table->setRowHeight(channel % 8, 110);
+
+		_plots.push_back(new QCustomPlot(table));
+
 
-		_plots.back()->xAxis->setRange(0, 1024);
-		_plots.back()->yAxis->setRange(0, 127);
-		_plots.back()->resize(1100, 100);
-		_plots.back()->move(0, i % 8 * 100);
+		auto valueGraph = _plots.back()->addGraph();
+		valueGraph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ScatterShape::ssDisc, 3));
+
+		_plots.back()->xAxis->setRange(0, BufferSize);
+		_plots.back()->yAxis->setRange(0, 256);
+		_plots.back()->yAxis2->setRange(0, 2);
+		_plots.back()->yAxis2->setVisible(true);
+
+		auto stateGraph = _plots.back()->addGraph(_plots.back()->xAxis, _plots.back()->yAxis2);
+		stateGraph->setPen(QPen(Qt::red));
+		stateGraph->setLineStyle(QCPGraph::LineStyle::lsStepLeft);
+
+
+
+		auto wgtType = new QComboBox(table);
+		auto wgtNote = new QSpinBox(table);
+		auto wgtThresold = new QSpinBox(table);
+		auto wgtScanTime = new QSpinBox(table);
+		auto wgtMaskTime = new QSpinBox(table);
+		auto wgtCurveType = new QComboBox(table);
+		auto wgtCurveValue = new QSpinBox(table);
+
+		QStringList types;
+		types << "Disabled" << "Piezo";
+		wgtType->addItems(types);
+
+		QStringList curveTypes;
+		curveTypes << "Normal" << "Exp" << "Log" << "Sigma" << "Flat" << "eXTRA",
+		           wgtCurveType->addItems(curveTypes);
+
+		auto curveForm = new QCustomPlot(table);
+		curveForm->addGraph();
+		curveForm->xAxis->setRange(0, 127);
+		curveForm->yAxis->setRange(0, 127);
+
+		table->setCellWidget(channel % 8, 0, wgtType);
+		table->setCellWidget(channel % 8, 1, wgtNote);
+		table->setCellWidget(channel % 8, 2, wgtThresold);
+		table->setCellWidget(channel % 8, 3, wgtScanTime);
+		table->setCellWidget(channel % 8, 4, wgtMaskTime);
+		table->setCellWidget(channel % 8, 5, wgtCurveType);
+		table->setCellWidget(channel % 8, 6, wgtCurveValue);
+		table->setCellWidget(channel % 8, 7, curveForm);
+		table->setCellWidget(channel % 8, 8, _plots.back());
+
+
+
+		wgtType->setCurrentIndex(_settings.channelSettings[channel].type);
+		wgtNote->setValue(_settings.channelSettings[channel].note);
+		wgtThresold->setValue(_settings.channelSettings[channel].thresold);
+		wgtScanTime->setValue(_settings.channelSettings[channel].scanTime);
+		wgtMaskTime->setValue(_settings.channelSettings[channel].maskTime);
+		wgtCurveType->setCurrentIndex(_settings.channelSettings[channel].curveType);
+		wgtCurveValue->setMaximum(256);
+		wgtCurveValue->setValue(_settings.channelSettings[channel].curveValue);
+
+		auto fnReplotCurveForm = [this, channel, curveForm]() {
+			QVector<qreal> x(127);
+			QVector<qreal> y(127);
+
+			for(auto i = 0; i < 127; ++i) {
+				x[i] = i;
+				y[i] = calcCurve(_settings.channelSettings[channel].curveType, i, _settings.channelSettings[channel].curveValue);
+			}
+
+			curveForm->graph(0)->setData(x, y);
+			curveForm->replot();
+		};
+		fnReplotCurveForm();
+
+
+		connect(wgtType, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this, channel](int i) mutable { _settings.channelSettings[channel].type = (Type)i; });
+		connect(wgtNote, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this, channel](int i) mutable { _settings.channelSettings[channel].note = i; });
+		connect(wgtThresold, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this, channel](int i) mutable { _settings.channelSettings[channel].thresold = i; });
+		connect(wgtScanTime, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this, channel](int i) mutable { _settings.channelSettings[channel].scanTime = i; });
+		connect(wgtMaskTime, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this, channel](int i) mutable { _settings.channelSettings[channel].maskTime = i; });
+		connect(wgtCurveType, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this, channel, curveForm, fnReplotCurveForm](int v) mutable {
+			_settings.channelSettings[channel].curveType = (Curve)v;
+			fnReplotCurveForm();
+		});
+		connect(wgtCurveValue, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this, channel, fnReplotCurveForm](int i) mutable {
+			_settings.channelSettings[channel].curveValue = i;
+			fnReplotCurveForm();
+		});
 	}
 
 
 	_serial = std::make_shared<Serial>(L"COM3", 115200);
 	_midiOut = std::make_shared<MidiOut>(1);
 
+
+	_workerThread = new WorkerThread(this);
+	_workerThread->start();
+
+#if 0
 	{
 		QTimer* timer = new QTimer(this);
 		_lasttime = QDateTime::currentMSecsSinceEpoch();
 		connect(timer, &QTimer::timeout, this, &drumduino::serialRead);
-		timer->start(0);
+		timer->start(1);
 	}
+#endif
 
+	{
+		QTimer* timer = new QTimer(this);
+		connect(timer, &QTimer::timeout, this, &drumduino::updateGraph);
+		timer->start(1000 / 12);
+	}
 
-	//{
-	//  QTimer* timer = new QTimer(this);
-	//  connect(timer, &QTimer::timeout, this, &drumduino::updateGraph);
-	//  timer->start(1000);
-	//}
+	ui.chkUpdateGraph->QCheckBox::setCheckState(_updateGraph ? Qt::Checked : Qt::Unchecked);
+	connect(ui.chkUpdateGraph, &QCheckBox::stateChanged, [this](int s) {
+		_updateGraph = s == Qt::Checked;
+	});
+
+#endif
 }
 
-drumduino::~drumduino()
+Drumduino::~Drumduino()
 {
-
+	_drumduinoThread->stop();
+	_drumduinoThread->wait();
 }
 
-void drumduino::serialRead()
+
+
+
+#if 0
+
+void Drumduino::serialRead()
 {
 AGAIN:
 	auto available = _serial->available();
@@ -75,60 +451,70 @@ AGAIN:
 	}
 
 	//now we have a full frame
-
 	std::array<byte, PORT_CNT* CHAN_CNT> frame;
 	_serial->readBytes(frame.data(), frame.size());
 
-	auto currentIndex = _currentFrame % 1024;
+	auto currentIndex = _currentFrame % BufferSize;
 	handleFrame(frame, currentIndex);
 
 
-	//for(size_t i = 0; i < PORT_CNT * CHAN_CNT; ++i) {
-	//  _frameBuffer[mapChannels(i)][currentIndex] = frame[i];
-	//}
+	for(size_t i = 0; i < PORT_CNT * CHAN_CNT; ++i) {
+		_stateBuffer[i][currentIndex] = _states[i];
+		_maxVal[i] = std::max(_maxVal[i], frame[i]);
+	}
 
 	++_currentFrame;
 
-	//auto now = QDateTime::currentMSecsSinceEpoch();
-
-	//float deltaT = now - _lasttime;
+//	goto AGAIN;
+}
 
-	//float fps = _currentFrame / deltaT * 1000;
 
-	//setWindowTitle(QString::number(fps));
-}
 
-void drumduino::updateGraph()
+void Drumduino::updateGraph()
 {
-	QVector<qreal> x(1024);
-	QVector<qreal> y(1024);
+	if(!_updateGraph) {
+		return;
+	}
+
+	auto currentIndex = _currentFrame % BufferSize;
+	QVector<qreal> x(BufferSize);
+	QVector<qreal> y(BufferSize);
+	QVector<qreal> s(BufferSize);
 
-	for(auto i = 0; i < 1024; ++i) {
+	for(auto i = 0; i < BufferSize; ++i) {
 		x[i] = i;
 	}
 
 	for(auto i = 0; i < PORT_CNT * CHAN_CNT; ++i) {
-		if(_plots[i]->isVisible()) {
-			for(auto k = 0; k < 1024; ++k) {
-				y[k] = _frameBuffer[i][k];
-			}
-
-			_plots[i]->graph(0)->setData(x, y);
+		_plots[i]->xAxis->setRange(x.front(), x.back());
 
-			_plots[i]->replot();
+		//if(_plots[i]->isVisible()) {
+		for(auto k = 0; k < BufferSize; ++k) {
+			y[k] = _frameBuffer[i][k];
+			s[k] = _stateBuffer[i][k];
 		}
+
+		_plots[i]->graph(0)->setData(x, y);
+		_plots[i]->graph(1)->setData(x, s);
+
+		_plots[i]->replot();
+		//}
 	}
 }
 
 
+#endif
 
+#if 0
 
-void drumduino::handleFrame(const std::array<byte, PORT_CNT* CHAN_CNT>& frame, const uint64_t currentIndex)
+void Drumduino::handleFrame(const std::array<byte, PORT_CNT* CHAN_CNT>& frame, const uint64_t currentIndex)
 {
 
 	auto fnMidiNoteOn = [this](size_t channel, byte newValue) {
-		auto note = 50 + channel;
-		auto velocity = newValue;
+		const auto& channelSettings = _settings.channelSettings[channel];
+
+		auto note = channelSettings.note;
+		auto velocity = calcCurve(channelSettings.curveType, newValue / 2, channelSettings.curveValue);
 
 		byte data[] = {0x90 | channel, 0x7f & note , 0x7f & velocity };
 		std::vector<byte> message(sizeof(data));
@@ -140,63 +526,71 @@ void drumduino::handleFrame(const std::array<byte, PORT_CNT* CHAN_CNT>& frame, c
 
 
 	for(auto channel = 0; channel < PORT_CNT * CHAN_CNT; ++channel) {
-		auto curTime = QDateTime::currentMSecsSinceEpoch();
-		auto newValue = frame[channel];
-		auto lastValue = _frameBuffer[mapChannels(channel)][currentIndex];
+		//const auto curTime = QDateTime::currentMSecsSinceEpoch();
+		const auto curTime = _currentFrame;
+		const auto newValue = frame[mapChannels(channel)];
+		auto& lastValue = _frameBuffer[channel][(currentIndex - 1) % BufferSize];
+		auto& nextValue = _frameBuffer[channel][currentIndex];
 
 		auto& state = _states[channel];
-		auto& trigger = _triggers[channel];
-		auto& max = _max[channel];
-
-
-		//switch(channelSettings.type) {
-		//  case TypePiezo: {
-		switch(state) {
-			// In this state we wait for a signal to trigger
-			case StateAwait: {
-				if(newValue > lastValue + 35) {
-					state = StateScan;
-					trigger = curTime;
+		auto& triggerFrame = _triggers[channel];
+		auto& maxValue = _max[channel];
+
+		const auto& channelSettings = _settings.channelSettings[channel];
+
+		switch(channelSettings.type) {
+			case TypePiezo: {
+				switch(state) {
+					// In this state we wait for a signal to trigger
+					case StateAwait: {
+						if(newValue > lastValue + channelSettings.thresold) {
+							state = StateScan;
+							triggerFrame = curTime;
+							maxValue = newValue;
+
+							if(channelSettings.scanTime == 0) {
+								fnMidiNoteOn(channel, maxValue);
+							}
+						}
+
+						break;
+					}
+
+					// In this state we measure the value for the given time period to get the max value
+					case StateScan: {
+						if(curTime > triggerFrame + channelSettings.scanTime) {
+							if(channelSettings.scanTime != 0) {
+								fnMidiNoteOn(channel, maxValue);
+							}
+
+							state = StateMask;
+						}
+
+						else {
+							maxValue = std::max(newValue, maxValue);
+						}
+
+						break;
+					}
+
+					// In this state we do nothing to prevent retriggering
+					case StateMask: {
+						if(curTime > triggerFrame + channelSettings.scanTime + channelSettings.maskTime) {
+							state = StateAwait;
+						}
+
+						break;
+					}
+
+					default: {
+						state = StateAwait;
+					}
 				}
 
-				lastValue = newValue;
-				max = newValue;
-
-				break;
-			}
-
-			// In this state we measure the value for the given time period to get the max value
-			case StateScan: {
-				if(curTime < trigger + 25) {
-					max = newValue > max ? newValue : max;
-				}
-
-				else {
-					fnMidiNoteOn(channel, max);
-					state = StateMask;
-				}
-
-				break;
-			}
-
-			// In this state we do nothing to prevent retriggering
-			case StateMask: {
-				if(curTime >= trigger + 25 + 30) {
-					state = StateAwait;
-					lastValue = newValue;
-				}
-
-				break;
+				nextValue = newValue;
 			}
 		}
-
-		//  }
-		//}
-
-
-
-
-
-
 	}
-}
+}
+
+#endif

+ 97 - 8
drumduino/drumduino.h

@@ -7,6 +7,9 @@
 #include "midi.h"
 #include "serial.h"
 #include "qcustomplot.h"
+#include "settings.h"
+
+enum { BufferSize = 1024 };
 
 enum State {
 	StateAwait,
@@ -14,35 +17,121 @@ enum State {
 	StateMask,
 };
 
+class DrumduinoThread;
+
+struct DrumduinoProc {
+	uint64_t frameCounter;
+	std::array<std::array<byte, PORT_CNT* CHAN_CNT>, BufferSize>  frameBuffer;
+	std::array<State, PORT_CNT* CHAN_CNT> states;
+	std::array<uint64_t, PORT_CNT* CHAN_CNT> triggers;
+	std::array<byte, PORT_CNT* CHAN_CNT> maxs;
+
+	std::array<std::array<State, PORT_CNT* CHAN_CNT>, BufferSize>  stateBuffer;
 
-class drumduino : public QMainWindow
+	DrumduinoProc()
+		: frameCounter(0)
+	{
+		for(auto& fb : frameBuffer) {
+			fb.fill(0);
+		}
+
+		states.fill(StateAwait);
+		triggers.fill(0);
+		maxs.fill(0);
+
+		for(auto& sb : stateBuffer) {
+			sb.fill(StateAwait);
+		}
+	}
+};
+
+class Drumduino : public QMainWindow
 {
 	Q_OBJECT
 
 public:
-	drumduino(QWidget* parent = 0);
-	~drumduino();
+	Drumduino(QWidget* parent = 0);
+	~Drumduino();
 
 private:
 	Ui::drumduinoClass ui;
-	std::vector<QCustomPlot*> _plots;
 
+	std::shared_ptr<Serial> _serial;
+	std::shared_ptr<MidiOut> _midiOut;
+
+	DrumduinoThread* _drumduinoThread;
+
+	Settings _settings;
+	DrumduinoProc _proc;
+
+private:
+	bool readFrame(std::array<byte, PORT_CNT* CHAN_CNT>& frame);
+
+private:
+
+
+
+
+#if 0
+
+public:
+	std::vector<QCustomPlot*> _plots;
 
+	bool _updateGraph;
 	qint64 _lasttime;
+	qint64 _startTime;
 
 private:
-	std::shared_ptr<Serial> _serial;
-	std::shared_ptr<MidiOut> _midiOut;
 
 	uint64_t _currentFrame;
-	std::array<std::array<byte, 1024>, PORT_CNT* CHAN_CNT> _frameBuffer;
+	std::array<std::array<byte, BufferSize>, PORT_CNT* CHAN_CNT> _frameBuffer;
+	std::array<std::array<State, BufferSize>, PORT_CNT* CHAN_CNT> _stateBuffer;
 	std::array<State, PORT_CNT* CHAN_CNT> _states;
 	std::array<uint64_t, PORT_CNT* CHAN_CNT> _triggers;
 	std::array<byte, PORT_CNT* CHAN_CNT> _max;
-private:
+
+
+	std::array<byte, PORT_CNT* CHAN_CNT> _maxVal;
+
+public:
+
+
+
 	void serialRead();
 	void updateGraph();
 	void handleFrame(const std::array<byte, PORT_CNT* CHAN_CNT>& frame, const uint64_t currentIndex);
+#endif
+};
+
+
+class DrumduinoThread : public QThread
+{
+	Q_OBJECT
+
+private:
+	Drumduino* _drumduino;
+	bool _run;
+	std::function<void()> _fnCall;
+
+public:
+	DrumduinoThread(Drumduino* drumduino, std::function<void()> fnCall)
+		: QThread(drumduino)
+		, _drumduino(drumduino)
+		, _run(true)
+		, _fnCall(fnCall)
+	{}
+
+	void run() Q_DECL_OVERRIDE {
+		for(; _run;)
+		{
+			_fnCall();
+		}
+	}
+
+	void stop()
+	{
+		_run = false;
+	}
 };
 
 #endif // DRUMDUINO_H

+ 127 - 43
drumduino/drumduino.ui

@@ -1,49 +1,133 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
-  <class>drumduinoClass</class>
-  <widget class="QMainWindow" name="drumduinoClass">
-    <property name="geometry">
-      <rect>
-        <x>0</x>
-        <y>0</y>
-        <width>902</width>
-        <height>635</height>
-      </rect>
-    </property>
-    <property name="windowTitle">
-      <string>drumduino</string>
-    </property>
-    <widget class="QWidget" name="centralWidget">
-      <layout class="QVBoxLayout" name="verticalLayout">
+ <class>drumduinoClass</class>
+ <widget class="QMainWindow" name="drumduinoClass">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>902</width>
+    <height>635</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>drumduino</string>
+  </property>
+  <widget class="QWidget" name="centralWidget">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="0" column="1">
+       <widget class="QComboBox" name="cbPrescaler">
         <item>
-          <widget class="QTabWidget" name="tabWidget">
-          </widget>
+         <property name="text">
+          <string>2</string>
+         </property>
         </item>
-      </layout>
-    </widget>
-    <widget class="QMenuBar" name="menuBar">
-      <property name="geometry">
-        <rect>
-          <x>0</x>
-          <y>0</y>
-          <width>902</width>
-          <height>21</height>
-        </rect>
-      </property>
-    </widget>
-    <widget class="QToolBar" name="mainToolBar">
-      <attribute name="toolBarArea">
-        <enum>TopToolBarArea</enum>
-      </attribute>
-      <attribute name="toolBarBreak">
-        <bool>false</bool>
-      </attribute>
-    </widget>
-    <widget class="QStatusBar" name="statusBar"/>
+        <item>
+         <property name="text">
+          <string>4</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>8</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>16</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>32</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>64</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>128</string>
+         </property>
+        </item>
+       </widget>
+      </item>
+      <item row="0" column="2">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Throttle</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="0">
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Prescaler</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="3">
+       <widget class="QSpinBox" name="sbThrottle">
+        <property name="minimum">
+         <number>1</number>
+        </property>
+        <property name="maximum">
+         <number>127</number>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </item>
+    <item>
+     <widget class="QTabWidget" name="tabWidget"/>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menuBar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>902</width>
+     <height>21</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QToolBar" name="mainToolBar">
+   <property name="movable">
+    <bool>false</bool>
+   </property>
+   <property name="floatable">
+    <bool>true</bool>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="actionSave"/>
+   <addaction name="actionLoad"/>
   </widget>
-  <layoutdefault spacing="6" margin="11"/>
-  <resources>
-    <include location="drumduino.qrc"/>
-  </resources>
-  <connections/>
+  <widget class="QStatusBar" name="statusBar"/>
+  <action name="actionSave">
+   <property name="text">
+    <string>Save</string>
+   </property>
+  </action>
+  <action name="actionLoad">
+   <property name="text">
+    <string>Load</string>
+   </property>
+  </action>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources>
+  <include location="drumduino.qrc"/>
+ </resources>
+ <connections/>
 </ui>

+ 32 - 0
drumduino/drumduino.vcxproj

@@ -82,7 +82,11 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
+    <ClCompile Include="channel.cpp" />
     <ClCompile Include="drumduino.cpp" />
+    <ClCompile Include="GeneratedFiles\Debug\moc_channel.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="GeneratedFiles\Debug\moc_drumduino.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
     </ClCompile>
@@ -98,6 +102,9 @@
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
       </PrecompiledHeader>
     </ClCompile>
+    <ClCompile Include="GeneratedFiles\Release\moc_channel.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="GeneratedFiles\Release\moc_drumduino.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
     </ClCompile>
@@ -118,6 +125,18 @@
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
+    <CustomBuild Include="channel.h">
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing channel.h...</Message>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\moc.exe"  "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../channel.h"  -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_PRINTSUPPORT_LIB -DQT_WIDGETS_LIB "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtPrintSupport" "-I$(QTDIR)\include\QtWidgets"</Command>
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Moc%27ing channel.h...</Message>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe"  "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../channel.h"  -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_PRINTSUPPORT_LIB -DQT_WIDGETS_LIB "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtPrintSupport" "-I$(QTDIR)\include\QtWidgets"</Command>
+    </CustomBuild>
+    <ClInclude Include="curve.h" />
+    <ClInclude Include="GeneratedFiles\ui_channel.h" />
     <ClInclude Include="GeneratedFiles\ui_drumduino.h" />
     <ClInclude Include="GeneratedFiles\ui_porttab.h" />
     <ClInclude Include="midi.h" />
@@ -152,6 +171,7 @@
       <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
       <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe"  "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../drumduino.h"  -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_PRINTSUPPORT_LIB -DQT_WIDGETS_LIB "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtPrintSupport" "-I$(QTDIR)\include\QtWidgets"</Command>
     </CustomBuild>
+    <ClInclude Include="settings.h" />
     <ClInclude Include="stdafx.h" />
   </ItemGroup>
   <ItemGroup>
@@ -194,6 +214,18 @@
       <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
     </CustomBuild>
   </ItemGroup>
+  <ItemGroup>
+    <CustomBuild Include="channel.ui">
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Uic%27ing %(Identity)...</Message>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Uic%27ing %(Identity)...</Message>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
+    </CustomBuild>
+  </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>

+ 24 - 0
drumduino/drumduino.vcxproj.filters

@@ -77,6 +77,15 @@
     <ClCompile Include="GeneratedFiles\Release\moc_porttab.cpp">
       <Filter>Generierte Dateien\Release</Filter>
     </ClCompile>
+    <ClCompile Include="channel.cpp">
+      <Filter>Source Dateien</Filter>
+    </ClCompile>
+    <ClCompile Include="GeneratedFiles\Debug\moc_channel.cpp">
+      <Filter>Generierte Dateien\Debug</Filter>
+    </ClCompile>
+    <ClCompile Include="GeneratedFiles\Release\moc_channel.cpp">
+      <Filter>Generierte Dateien\Release</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="stdafx.h">
@@ -94,6 +103,15 @@
     <ClInclude Include="GeneratedFiles\ui_porttab.h">
       <Filter>Generierte Dateien</Filter>
     </ClInclude>
+    <ClInclude Include="settings.h">
+      <Filter>Header Dateien</Filter>
+    </ClInclude>
+    <ClInclude Include="GeneratedFiles\ui_channel.h">
+      <Filter>Generierte Dateien</Filter>
+    </ClInclude>
+    <ClInclude Include="curve.h">
+      <Filter>Header Dateien</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <CustomBuild Include="drumduino.h">
@@ -114,6 +132,12 @@
     <CustomBuild Include="porttab.ui">
       <Filter>Form Dateien</Filter>
     </CustomBuild>
+    <CustomBuild Include="channel.h">
+      <Filter>Header Dateien</Filter>
+    </CustomBuild>
+    <CustomBuild Include="channel.ui">
+      <Filter>Form Dateien</Filter>
+    </CustomBuild>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="drumduino.rc" />

+ 1 - 1
drumduino/main.cpp

@@ -5,7 +5,7 @@
 int main(int argc, char *argv[])
 {
 	QApplication a(argc, argv);
-	drumduino w;
+	Drumduino w;
 	w.show();
 	return a.exec();
 }

+ 13 - 15
drumduino/porttab.ui

@@ -1,23 +1,21 @@
-<UI version="4.0" >
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
  <class>PortTab</class>
- <widget class="QWidget" name="PortTab" >
-  <property name="objectName" >
-   <string notr="true">PortTab</string>
-  </property>
-  <property name="geometry" >
+ <widget class="QWidget" name="PortTab">
+  <property name="geometry">
    <rect>
-	<x>0</x>
-	<y>0</y>
-	<width>400</width>
-	<height>300</height>
+    <x>0</x>
+    <y>0</y>
+    <width>733</width>
+    <height>539</height>
    </rect>
   </property>
-  <property name="windowTitle" >
+  <property name="windowTitle">
    <string>PortTab</string>
-  </property>  
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout"/>
  </widget>
- <layoutDefault spacing="6" margin="11" />
- <pixmapfunction></pixmapfunction>
+ <layoutdefault spacing="6" margin="11"/>
  <resources/>
  <connections/>
-</UI>
+</ui>

+ 12 - 1
drumduino/serial.cpp

@@ -56,9 +56,20 @@ size_t Serial::available()
 size_t Serial::readBytes(byte* data, size_t size)
 {
 	auto len = std::min(size, available());
-	
+
 	DWORD bytesRead;
 	::ReadFile(_hSerial, data, len, &bytesRead, NULL);
 
 	return bytesRead;
+}
+
+size_t Serial::write(const byte* data, size_t size)
+{
+	DWORD bytesSend;
+
+	if(!WriteFile(_hSerial, (void*)data, size, &bytesSend, 0)) {
+		ClearCommError(_hSerial, &_errors, &_status);
+	}
+
+	return bytesSend;
 }

+ 1 - 0
drumduino/serial.h

@@ -17,5 +17,6 @@ public:
 public:
 	size_t available();
 	size_t readBytes(byte* data, size_t size);
+	size_t write(const byte* data, size_t size);
 };
 

+ 54 - 0
drumduino/settings.h

@@ -0,0 +1,54 @@
+#pragma once
+
+//========================================================================================================================
+//========================================================================================================================
+// Settings
+//========================================================================================================================
+//========================================================================================================================
+enum Type {
+	TypeDisabled,
+	TypePiezo,
+};
+
+enum Curve {
+	Normal,
+	Exp,
+	Log,
+	Sigma,
+	Flat,
+	eXTRA,
+};
+
+struct ChannelSettings {
+	Type type;
+	uint8_t note;
+	uint8_t thresold;
+	qint64 scanTime;
+	qint64 maskTime;
+	Curve curveType;
+	int curveValue;
+
+	ChannelSettings()
+		: type(TypeDisabled)
+		, note(35)
+		, thresold(70)
+		, scanTime(2)
+		, maskTime(5)
+		, curveType(Normal)
+		, curveValue(127)
+	{}
+};
+
+struct Settings {
+	uint8_t midiChannel;
+	byte prescaler;
+	byte throttle;
+	ChannelSettings channelSettings[PORT_CNT* CHAN_CNT];
+
+	Settings()
+		: prescaler(2)
+		, throttle(1)
+		, midiChannel(1)
+	{
+	}
+};