reset repository cause it broke

This commit is contained in:
Brandon Switzer
2019-07-31 16:50:00 -04:00
commit 32fa744a73
54 changed files with 3146 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

340
.gitignore vendored Normal file
View File

@@ -0,0 +1,340 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb

BIN
ControlBox/ControlBox.ino Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="ControlBox.ino" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="input.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="lcd.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="midi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="serial.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="setting.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="input.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="lcd.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="midi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="serial.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="setting.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="__vm\.ControlBox.vsarduino.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,85 @@
/*
Editor: https://www.visualmicro.com/
This file is for intellisense purpose only.
Visual micro (and the arduino ide) ignore this code during compilation. This code is automatically maintained by visualmicro, manual changes to this file will be overwritten
The contents of the _vm sub folder can be deleted prior to publishing a project
All non-arduino files created by visual micro and all visual studio project or solution files can be freely deleted and are not required to compile a sketch (do not delete your own code!).
Note: debugger breakpoints are stored in '.sln' or '.asln' files, knowledge of last uploaded breakpoints is stored in the upload.vmps.xml file. Both files are required to continue a previous debug session without needing to compile and upload again
Hardware: Arduino Leonardo, Platform=avr, Package=arduino
*/
#if defined(_VMICRO_INTELLISENSE)
#ifndef _VSARDUINO_H_
#define _VSARDUINO_H_
#define __AVR_atmega32u4__
#define __AVR_ATmega32U4__
#define __AVR_ATmega32u4__
#define F_CPU 16000000L
#define ARDUINO 10809
#define ARDUINO_AVR_LEONARDO
#define ARDUINO_ARCH_AVR
#define USB_VID 0x2341
#define USB_PID 0x8036
#define __cplusplus 201103L
#define _Pragma(x)
#define __AVR__
#define __inline__
#define __asm__(...)
#define __extension__
#define __inline__
#define __volatile__
#define GCC_VERSION 40902
#define __cplusplus 201103L
#define volatile(va_arg)
#define _CONST
#define __builtin_va_start
#define __builtin_va_end
#define __attribute__(...)
#define NOINLINE __attribute__((noinline))
#define prog_void
#define PGM_VOID_P int
#ifndef __builtin_constant_p
#define __builtin_constant_p __attribute__((__const__))
#endif
#ifndef __builtin_strlen
#define __builtin_strlen __attribute__((__const__))
#endif
#define NEW_H
typedef void *__builtin_va_list;
//extern "C" void __cxa_pure_virtual() {;}
typedef int div_t;
typedef int ldiv_t;
typedef void *__builtin_va_list;
//extern "C" void __cxa_pure_virtual() {;}
#include "arduino.h"
#include <pins_arduino.h>
//#undef F
//#define F(string_literal) ((const PROGMEM char *)(string_literal))
#undef PSTR
#define PSTR(string_literal) ((const PROGMEM char *)(string_literal))
//typedef unsigned char uint8_t;
//typedef unsigned int uint8_t;
#define pgm_read_byte(address_short) uint8_t()
#define pgm_read_word(address_short) uint16_t()
#define pgm_read_dword(address_short) uint32_t()
#define pgm_read_float(address_short) float()
#define pgm_read_ptr(address_short) short()
#include "ControlBox.ino"
#endif
#endif

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

134
ControlBox/input.cpp Normal file
View File

@@ -0,0 +1,134 @@
#include <EEPROM.h>
#include "input.h"
#include "lcd.h"
#include "serial.h"
#include "setting.h"
void handleInput(int inputPin);
extern const int UP_PIN = 4;
extern const int DOWN_PIN = 6;
extern const int LEFT_PIN = 7;
extern const int RIGHT_PIN = 5;
extern const int RESET_PIN = 8;
extern const int VOLUME_PIN = 10;
struct LastPressed
{
unsigned long up = 0;
unsigned long down = 0;
unsigned long left = 0;
unsigned long right = 0;
unsigned long reset1 = 0;
}; LastPressed lastPressed;
unsigned long lastPressedOverall = 0;
int lastAnalog;
void initializeInputs()
{
pinMode(UP_PIN, INPUT);
pinMode(DOWN_PIN, INPUT);
pinMode(LEFT_PIN, INPUT);
pinMode(RIGHT_PIN, INPUT);
pinMode(RESET_PIN, INPUT);
lastAnalog = (analogRead(VOLUME_PIN) / 5);
}
void checkForInput()
{
unsigned long ms = millis();
const int BUTTON_COOLDOWN = 500;
extern const bool DEBUG_MODE;
if(digitalRead(UP_PIN) == HIGH && lastPressed.up + BUTTON_COOLDOWN <= ms)
handleInput(UP_PIN);
else if(digitalRead(DOWN_PIN) == HIGH && lastPressed.down + BUTTON_COOLDOWN <= ms)
handleInput(DOWN_PIN);
else if(digitalRead(LEFT_PIN) == HIGH && lastPressed.left + BUTTON_COOLDOWN <= ms)
handleInput(LEFT_PIN);
else if(digitalRead(RIGHT_PIN) == HIGH && lastPressed.right + BUTTON_COOLDOWN <= ms)
handleInput(RIGHT_PIN);
else if(digitalRead(RESET_PIN) == HIGH && lastPressed.reset1 + BUTTON_COOLDOWN <= ms)
handleInput(RESET_PIN);
else
{
const int ANALOG_CHANGE_RATE = 5;
const int ANALOG_WITHIN_RANGE = 20;
const int ANALOG_CHANGE_TIME = BUTTON_COOLDOWN * 2;
int analogValue = analogRead(VOLUME_PIN) / 5; //conform analog value from 0-200
if(DEBUG_MODE) Serial.println(analogValue);
if((analogValue >= (lastAnalog + ANALOG_CHANGE_RATE) || analogValue <= (lastAnalog - ANALOG_CHANGE_RATE)) && //if volume has changed enough
(analogValue < (lastAnalog + ANALOG_WITHIN_RANGE) && analogValue >(lastAnalog - ANALOG_WITHIN_RANGE)) && //if volume hasn't changed too much for it to be a glitch
lastPressedOverall + ANALOG_CHANGE_TIME <= ms) //if another button hasn't been pressed within a certain period for it to affect the reading
{
//conform analog to certain values if they are within range
if(analogValue <= 105 && analogValue >= 95)
analogValue = 100;
else if(analogValue > 195)
analogValue = 200;
else if(analogValue < 5)
analogValue = 0;
lastAnalog = analogValue;
handleInput(VOLUME_PIN); //implied that lastAnalog has also been changed
}
}
}
void handleInput(int inputPin)
{
unsigned long ms = millis();
switch(inputPin)
{
case UP_PIN:
if(menuState == MenuStates::SETTINGS) //prevent settings from changing while not showing
{
lastPressed.up = ms;
lastPressedOverall = ms;
changeSetting(1);
sendSerialToMain(SerialConstants::SETTING_HEADER, currentMenu, EEPROM.read(currentMenu));
updateDisplay();
}
break;
case DOWN_PIN:
if(menuState == MenuStates::SETTINGS) //prevent settings from changing while not showing
{
lastPressed.down = ms;
lastPressedOverall = ms;
changeSetting(-1);
sendSerialToMain(SerialConstants::SETTING_HEADER, currentMenu, EEPROM.read(currentMenu));
updateDisplay();
}
break;
case LEFT_PIN:
lastPressed.left = ms;
lastPressedOverall = ms;
currentMenu--;
if(currentMenu < 0)
currentMenu = NUM_OF_MENUS - 1;
menuState = MenuStates::SETTINGS;
updateDisplay();
break;
case RIGHT_PIN:
lastPressed.right = ms;
lastPressedOverall = ms;
currentMenu++;
if(currentMenu >= NUM_OF_MENUS)
currentMenu == 0;
menuState = MenuStates::SETTINGS;
updateDisplay();
break;
case RESET_PIN:
lastPressed.reset1 = ms;
exitScreen = ms + SPECIAL_MENU_TIMEOUT;
sendSerialToMain(SerialConstants::RESET_HEADER, 127, 127);
menuState = MenuStates::RESET;
updateDisplay();
break;
case VOLUME_PIN:
exitScreen = ms + SPECIAL_MENU_TIMEOUT;
menuState = MenuStates::VOLUME;
sendSerialToMain(SerialConstants::VOLUME_HEADER, lastAnalog, lastAnalog);
updateDisplay();
break;
}
}

13
ControlBox/input.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef INPUT_H
#define INPUT_H
#include <Arduino.h>
extern unsigned long lastPressedOverall;
extern int lastAnalog;
void initializeInputs();
void checkForInput();
#endif

113
ControlBox/lcd.cpp Normal file
View File

@@ -0,0 +1,113 @@
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include "lcd.h"
#include "input.h"
#include "setting.h"
extern LiquidCrystal_I2C lcd;
const String MENU_NAMES[] ={
"Handle Notes",
"Schedule Notes",
"Min Accepted Vel",
"Solenoid PWM %",
"Min Startup Ms",
"Max Startup Ms",
"Velocity Var",
"Min Off Ms",
"Max Off Ms",
"Fast Off Ms",
"Pedal On Ms",
"Pedal Off Ms",
"Note Timeout",
"Sustain Timeout",
"Auto Reset Mins",
"Max Left Keys",
"Max Right Keys"
};
MenuStates menuState = MenuStates::WELCOME;
int currentMenu = -1; //initialize current menu as invalid
const int NUM_OF_MENUS = sizeof(MENU_NAMES) / sizeof(MENU_NAMES[0]);
const int SETTING_MENU_TIMEOUT = 20000;
extern const int SPECIAL_MENU_TIMEOUT = 3000;
unsigned long exitScreen;
void initializeLCD()
{
lcd.begin();
lcd.backlight();
}
void printHomeScreen()
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Welcome to");
lcd.setCursor(0, 1);
lcd.print("Player Piano");
}
void updateDisplay()
{
lcd.clear();
switch(menuState)
{
case MenuStates::WELCOME:
printHomeScreen();
break;
case MenuStates::SETTINGS:
lcd.print(MENU_NAMES[currentMenu]);
lcd.setCursor(0, 1);
//see if the current menu is true or false or just a number
if(currentMenu != static_cast<int>(SettingID::SCHEDULE_NOTES) &&
currentMenu != static_cast<int>(SettingID::HANDLE_NOTES)) //if current menu is not a bool menu
lcd.print(EEPROM.read(currentMenu));
else
{
if(EEPROM.read(currentMenu))
lcd.print("True");
else
lcd.print("False");
}
break;
case MenuStates::VOLUME:
lcd.setCursor(0, 0);
lcd.print("Volume");
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(lastAnalog);
lcd.print("% ");
break;
case MenuStates::RESET:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Resetting Keys");
lcd.setCursor(0, 1);
lcd.print("...");
break;
}
}
void checkSchedule()
{
unsigned long ms = millis();
if(ms >= lastPressedOverall + SETTING_MENU_TIMEOUT && lastPressedOverall > 0)
{
lastPressedOverall = 0;
if(menuState == MenuStates::SETTINGS) //if another screen isn't scheduled
{
menuState = MenuStates::WELCOME;
updateDisplay();
}
}
if(ms >= exitScreen && exitScreen > 0)
{
exitScreen = 0;
if(lastPressedOverall == 0) //if setting menu is timed out
menuState = MenuStates::WELCOME;
else
menuState = MenuStates::SETTINGS;
updateDisplay();
}
}

25
ControlBox/lcd.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef LCD_H
#define LCD_H
#include <Arduino.h>
enum class MenuStates
{
WELCOME,
SETTINGS,
VOLUME,
RESET
}; extern MenuStates menuState;
extern const String MENU_NAMES[];
extern const int SPECIAL_MENU_TIMEOUT;
extern const int NUM_OF_MENUS;
extern int currentMenu;
extern unsigned long exitScreen;
void initializeLCD();
void printHomeScreen();
void updateDisplay();
void checkSchedule();
#endif

41
ControlBox/midi.cpp Normal file
View File

@@ -0,0 +1,41 @@
#include <MIDIUSB.h>
#include "midi.h"
#include "serial.h"
void decodeMidi(uint8_t header, uint8_t byte1, uint8_t byte2, uint8_t byte3);
void checkForMidiUSB()
{
midiEventPacket_t rx; //midi data struct from midiUSB libray
do
{
rx = MidiUSB.read(); //get queued info from USB
if(rx.header != 0)
{
decodeMidi(rx.header, rx.byte1, rx.byte2, rx.byte3);
}
} while(rx.header != 0);
}
void decodeMidi(uint8_t header, uint8_t byte1, uint8_t byte2, uint8_t byte3)
{
const uint8_t NOTE_ON_HEADER = 9;
const uint8_t NOTE_OFF_HEADER = 8;
const uint8_t CONTROL_CHANGE_HEADER = 8;
const uint8_t SUSTAIN_STATUS_BYTE = 176;
const uint8_t MIN_NOTE_PITCH = 21;
//note that ESP32 does bounds checking
switch(header)
{
case NOTE_ON_HEADER:
sendSerialToMain(SerialConstants::NOTE_HEADER, byte2 - MIN_NOTE_PITCH, byte3);
break;
case NOTE_OFF_HEADER:
sendSerialToMain(SerialConstants::NOTE_HEADER, byte2 - MIN_NOTE_PITCH, 0);
break;
case SUSTAIN_STATUS_BYTE:
if(byte1 == SUSTAIN_STATUS_BYTE)
sendSerialToMain(SerialConstants::SUSTAIN_HEADER, byte3, byte3);
break;
}
}

8
ControlBox/midi.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef MIDI_H
#define MIDI_H
#include <Arduino.h>
void checkForMidiUSB();
#endif

17
ControlBox/serial.cpp Normal file
View File

@@ -0,0 +1,17 @@
#include "lcd.h"
namespace SerialConstants
{
extern const byte NOTE_HEADER = 201;
extern const byte SUSTAIN_HEADER = 202;
extern const byte SETTING_HEADER = 203;
extern const byte RESET_HEADER = 204;
extern const byte VOLUME_HEADER = 205;
}
void sendSerialToMain(byte header, byte setting, byte value)
{
Serial1.write(header);
Serial1.write(setting);
Serial1.write(value);
}

18
ControlBox/serial.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef SERIAL_H
#define SERIAL_H
#include <Arduino.h>
namespace SerialConstants
{
extern const uint8_t NOTE_HEADER;
extern const uint8_t SUSTAIN_HEADER;
extern const uint8_t SETTING_HEADER;
extern const uint8_t RESET_HEADER;
extern const uint8_t VOLUME_HEADER;
}
void sendSerialToMain(uint8_t header, uint8_t setting, uint8_t value);
void sendAllSettings();
#endif

24
ControlBox/setting.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include <EEPROM.h>
#include "setting.h"
#include "lcd.h"
#include "serial.h"
#include "input.h"
void changeSetting(int changeBy)
{
if(currentMenu != static_cast<int>(SettingID::SCHEDULE_NOTES) &&
currentMenu != static_cast<int>(SettingID::HANDLE_NOTES)) //if current menu is not a bool menu
EEPROM.write(currentMenu, EEPROM.read(currentMenu) + changeBy);
else
EEPROM.write(currentMenu, !(static_cast<bool>(EEPROM.read(currentMenu))));
}
void sendAllSettings()
{
for(int index = 0; index < NUM_OF_MENUS; index++)
{
sendSerialToMain(SerialConstants::SETTING_HEADER, index, EEPROM.read(index));
delay(50);
}
sendSerialToMain(SerialConstants::VOLUME_HEADER, lastAnalog, lastAnalog);
}

29
ControlBox/setting.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef SETTING_H
#define SETTING_H
#include <Arduino.h>
enum class SettingID //same order as menus
{
HANDLE_NOTES,
SCHEDULE_NOTES,
MIN_ACCEPTED_VEL,
PWM_PERCENT,
MIN_STARTUP_MS,
MAX_STARTUP_MS,
VELOCITY_VAR,
MIN_OFF_MS,
MAX_OFF_MS,
FAST_OFF_MS,
PEDAL_ON_MS,
PEDAL_OFF_MS,
NOTE_TIMEOUT,
SUSTAIN_TIMEOUT,
AUTO_RESET_MINS,
MAX_LEFT_KEYS,
MAX_RIGHT_KEYS
};
void changeSetting(int changeBy);
#endif

80
ESP32/ESP32.ino Normal file
View File

@@ -0,0 +1,80 @@
#pragma GCC diagnostic push
#pragma GCC diagnostic warning "-fpermissive"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include "sustain.h"
#include "settings.h"
#include "serial.h"
#include "note.h"
#include "midi.h"
#include "bluetooth.h"
#include "main.h"
#pragma GCC diagnostic pop
const bool DEBUG_MODE = false;
void resetAll()
{
acceptMidi = false; //turn midi off during reset to prevent errors
for(int noteIndex = 0; noteIndex < 88; noteIndex++)
{
notes[noteIndex].resetSchedule();
}
Note::resetInstances();
sustain.resetSchedule();
msReset();
acceptMidi = true;
}
void flashLED()
{
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
}
void setup()
{
const int SHIFT_REGISTER_POWER_PIN = 12;
const int SUSTAIN_PIN = 13;
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(38400);
initializeBluetooth();
//create sustain PWM output. this can't be done by the Pro Micro because the shift registers are filled up
ledcSetup(0, 100, 8);
ledcAttachPin(SUSTAIN_PIN, 0);
delay(500); //give pro micro time to inititalize before giving power to shift registers
pinMode(SHIFT_REGISTER_POWER_PIN, OUTPUT);
digitalWrite(SHIFT_REGISTER_POWER_PIN, HIGH);
std::vector<unsigned long> testy[6];
}
void loop()
{
checkForSerial();
for(int noteIndex = 0; noteIndex < 88; noteIndex++)
{
notes[noteIndex].checkSchedule();
notes[noteIndex].checkForErrors();
}
sustain.checkSchedule();
sustain.checkForErrors();
if(millis() >= nextReset)
{
//first reset will happen immediately and midi will begin to accept after this
resetAll();
nextReset = millis() + Setting::autoResetMs;
}
}

140
ESP32/ESP32.vcxproj Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="__vm\.ESP32.vsarduino.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bluetooth.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="midi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="note.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="serial.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="settings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="sustain.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="main.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="bluetooth.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="midi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="note.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="serial.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="sustain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="ESP32.ino" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,109 @@
/*
Editor: https://www.visualmicro.com/
This file is for intellisense purpose only.
Visual micro (and the arduino ide) ignore this code during compilation. This code is automatically maintained by visualmicro, manual changes to this file will be overwritten
The contents of the _vm sub folder can be deleted prior to publishing a project
All non-arduino files created by visual micro and all visual studio project or solution files can be freely deleted and are not required to compile a sketch (do not delete your own code!).
Note: debugger breakpoints are stored in '.sln' or '.asln' files, knowledge of last uploaded breakpoints is stored in the upload.vmps.xml file. Both files are required to continue a previous debug session without needing to compile and upload again
Hardware: Node32s, Platform=esp32, Package=espressif
*/
#if defined(_VMICRO_INTELLISENSE)
#ifndef _VSARDUINO_H_
#define _VSARDUINO_H_
#define __ESP32_esp32__
#define __ESP32_ESP32__
#define ESP_PLATFORM
#define HAVE_CONFIG_H
#define F_CPU 240000000L
#define ARDUINO 10809
#define ARDUINO_Node32s
#define ARDUINO_ARCH_ESP32
#define ESP32
#define CORE_DEBUG_LEVEL 0
#define __cplusplus 201103L
#define _Pragma(x)
#undef __cplusplus
#define __cplusplus 201103L
#define __STDC__
#define __ARM__
#define __arm__
#define __inline__
#define __asm__(...)
#define __extension__
#define __ATTR_PURE__
#define __ATTR_CONST__
#define __volatile__
#define __ASM
#define __INLINE
#define __attribute__(noinline)
//#define _STD_BEGIN
//#define EMIT
#define WARNING
#define _Lockit
#define __CLR_OR_THIS_CALL
#define C4005
#define _NEW
typedef bool _Bool;
typedef int _read;
typedef int _seek;
typedef int _write;
typedef int _close;
typedef int __cleanup;
//#define inline
#define __builtin_clz
#define __builtin_clzl
#define __builtin_clzll
#define __builtin_labs
#define __builtin_va_list
typedef int __gnuc_va_list;
#define __ATOMIC_ACQ_REL
#define __CHAR_BIT__
#define _EXFUN()
typedef unsigned char byte;
extern "C" void __cxa_pure_virtual() {;}
typedef long __INTPTR_TYPE__ ;
typedef long __UINTPTR_TYPE__ ;
typedef long __SIZE_TYPE__ ;
typedef long __PTRDIFF_TYPE__;
typedef long pthread_t;
typedef long pthread_key_t;
typedef long pthread_once_t;
typedef long pthread_mutex_t;
typedef long pthread_mutex_t;
typedef long pthread_cond_t;
#include "arduino.h"
#include <pins_arduino.h>
//#include "..\generic\Common.h"
//#include "..\generic\pins_arduino.h"
//#undef F
//#define F(string_literal) ((const PROGMEM char *)(string_literal))
//#undef PSTR
//#define PSTR(string_literal) ((const PROGMEM char *)(string_literal))
//current vc++ does not understand this syntax so use older arduino example for intellisense
//todo:move to the new clang/gcc project types.
#define interrupts() sei()
#define noInterrupts() cli()
#include "ESP32.ino"
#endif
#endif

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

135
ESP32/bluetooth.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include <esp_gatt_defs.h>
#include <esp_gap_ble_api.h>
#include <esp_bt_main.h>
#include <esp_gatts_api.h>
#include <string.h>
#include "bluetooth.h"
#include "midi.h"
#include "main.h"
uint8_t attrStr[] ={ 0x00 }; //value range of a characteristic
esp_attr_value_t gattsAttrVal =
{
.attr_max_len = 0xFF, //max value of a characteristic
.attr_len = sizeof(attrStr),
.attr_value = attrStr,
};
uint8_t serviceUUID128[32] =
{
0x00, 0xC7, 0xC4, 0x4E, 0xE3, 0x6C, 0x51, 0xA7, 0x33, 0x4B, 0xE8, 0xED, 0x5A, 0x0E, 0xB8, 0x03,
0xF3, 0x6B, 0x10, 0x9D, 0x66, 0xF2, 0xA9, 0xA1, 0x12, 0x41, 0x68, 0x38, 0xDB, 0xE5, 0x72, 0x77
};
uint8_t rawAdvData[] =
{
0x02, 0x01, 0x06, 0x11, 0x07, 0x00, 0xC7, 0xC4, 0x4E, 0xE3, 0x6C, 0x51, 0xA7, 0x33, 0x4B,
0xE8, 0xED, 0x5A, 0x0E, 0xB8, 0x03,
};
uint8_t rawScanRspData[] ={ 0x0c, 0x09, 'P','l','a','y','e','r',' ','P','i','a','n','o' };
esp_bt_uuid_t characteristicUUID;
esp_gatt_perm_t permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
esp_gatt_char_prop_t property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
esp_ble_adv_params_t BLEAdvParams;
static void gattsEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gattsInterface, esp_ble_gatts_cb_param_t* params);
void gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* params);
void initializeBluetooth()
{
//writing the parameters for the bluetooth on BLEAdvParams, which a struct
BLEAdvParams.adv_int_min = 0x20;
BLEAdvParams.adv_int_max = 0x30;
BLEAdvParams.adv_type = ADV_TYPE_IND;
BLEAdvParams.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
BLEAdvParams.channel_map = ADV_CHNL_ALL;
BLEAdvParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY;
characteristicUUID.len = ESP_UUID_LEN_128;
for(int i=0; i < 16; i++)
characteristicUUID.uuid.uuid128[i] = serviceUUID128[i + 16];
btStart();
esp_bluedroid_init();
esp_bluedroid_enable();
esp_ble_gatts_register_callback(gattsEventHandler); //log the callback function for gatts even handling
esp_ble_gap_register_callback(gapEventHandler); //log the callback function for gap event handling
esp_ble_gatts_app_register(0);
}
static void gattsEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gattsInterface, esp_ble_gatts_cb_param_t* params)
{
switch(event)
{
case ESP_GATTS_REG_EVT: //create service
if(params->reg.status != ESP_GATT_OK)
return;
esp_gatt_srvc_id_t serviceID;
serviceID.is_primary = true;
serviceID.id.inst_id = 0x00;
serviceID.id.uuid.len = ESP_UUID_LEN_128;
for(int i=0; i < 16; i++)
serviceID.id.uuid.uuid.uuid128[i] = serviceUUID128[i];
esp_ble_gap_set_device_name("Player Piano");
esp_ble_gap_config_adv_data_raw(rawAdvData, sizeof(rawAdvData));
esp_ble_gap_config_scan_rsp_data_raw(rawScanRspData, sizeof(rawScanRspData));
esp_ble_gatts_create_service(gattsInterface, &serviceID, 4);
break;
case ESP_GATTS_READ_EVT: //when main device requests data
esp_gatt_rsp_t response;
memset(&response, 0, sizeof(esp_gatt_rsp_t));
response.attr_value.handle = params->read.handle;
response.attr_value.len = 1;
response.attr_value.value[0] = 0;
esp_ble_gatts_send_response(gattsInterface, params->read.conn_id, params->read.trans_id, ESP_GATT_OK, &response);
break;
case ESP_GATTS_WRITE_EVT: //when main device sends data
extern bool acceptMidi;
if(acceptMidi)
decodeBluetooth(params->write.len, params->write.value);
break;
case ESP_GATTS_CREATE_EVT: //start service and characteristic
esp_ble_gatts_add_char(params->create.service_handle, &characteristicUUID, permissions, property, &gattsAttrVal, NULL);
esp_ble_gatts_start_service(params->create.service_handle);
break;
case ESP_GATTS_DISCONNECT_EVT: //advertise again on disconnect
esp_ble_gap_start_advertising(&BLEAdvParams);
break;
case ESP_GATTS_CONNECT_EVT: //connection
esp_ble_conn_update_params_t conn_params;
memcpy(conn_params.bda, params->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.latency = 0;
conn_params.max_int = 0x30;
conn_params.min_int = 0x20;
conn_params.timeout = 500;
esp_ble_gap_update_conn_params(&conn_params);
//if(DEBUG_MODE) flashLED();
break;
default:
break;
}
}
void gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* params)
{
switch(event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
esp_ble_gap_start_advertising(&BLEAdvParams);
break;
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
esp_ble_gap_start_advertising(&BLEAdvParams);
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
esp_ble_gap_start_advertising(&BLEAdvParams);
break;
default:
break;
}
}

8
ESP32/bluetooth.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef BLUETOOTH_H
#define BLUETOOTH_H
#include <Arduino.h>
void initializeBluetooth();
#endif

8
ESP32/main.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef MAIN_H
#define MAIN_H
extern const bool DEBUG_MODE;
void resetAll();
void flashLED();
#endif

70
ESP32/midi.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include "midi.h"
#include "note.h"
#include "sustain.h"
#include "settings.h"
#include "main.h"
void sendBluetoothToSerial(int lengthM, uint8_t* message);
void decodeBluetooth(int lengthM, uint8_t* message)
{
const uint8_t MIN_NOTE_STATUS_BYTE = 128;
const uint8_t MAX_NOTE_STATUS_BYTE = 159;
const uint8_t BEGIN_NOTE_OFF_BYTE = 143;
const uint8_t CONTROL_CHANGE_BYTE = 176;
const uint8_t SUSTAIN_DATA_BYTE = 64;
const uint8_t MIN_NOTE_PITCH = 21;
const uint8_t MAX_NOTE_PITCH = 108;
const uint8_t MIN_NOTE_VELOCITY = Setting::minNoteVelocity;
const uint8_t MAX_NOTE_VELOCITY = 127;
const uint8_t RESET_LENGTH = 152;
const uint8_t RESET_LENGTH2 = 154;
if(DEBUG_MODE) sendBluetoothToSerial(lengthM, message);
for(int index = 1; index < lengthM; index++)
{
if(message[index] >= MIN_NOTE_STATUS_BYTE && message[index] <= MAX_NOTE_STATUS_BYTE)
{
for(int noteIndex = index + 1; noteIndex < lengthM; noteIndex+=2)
{
int velocityIndex = noteIndex + 1;
//go through message to find notes
if(message[noteIndex] >= MIN_NOTE_PITCH && message[noteIndex] <= MAX_NOTE_PITCH &&
message[velocityIndex] <= MAX_NOTE_VELOCITY)
{
int note = message[noteIndex] - MIN_NOTE_PITCH;
uint8_t velocity = message[velocityIndex];
if(DEBUG_MODE) Serial.print("Detected note: ");
if(DEBUG_MODE) Serial.print(note);
if(DEBUG_MODE) Serial.print("with velocity: ");
if(DEBUG_MODE) Serial.println(velocity);
//check if the status byte is off -- anything less than 144 means off -- or if the velocity is 0
if(message[index] <= BEGIN_NOTE_OFF_BYTE || velocity == 0)
notes[note].prepareToSchedule(0);
else if(velocity >= MIN_NOTE_VELOCITY)
notes[note].prepareToSchedule(velocity);
} else
break;
//break if note not detected (new status byte is found)
}
} else if(message[index] == CONTROL_CHANGE_BYTE && message[index + 1] == SUSTAIN_DATA_BYTE)
{
uint8_t sustainVelocityIndex = index + 2;
//sustain.prepareToSchedule(message[sustainVelocityIndex]);
}
}
//sytnhesia sends specific lengths of data when it stops playing a midi
if(lengthM == RESET_LENGTH || lengthM == RESET_LENGTH2)
resetAll();
}
void sendBluetoothToSerial(int lengthM, uint8_t* message)
{
Serial.println("-----------------------");
for(int index = 1; index < lengthM; index++)
{
Serial.println(message[index], DEC);
}
}

8
ESP32/midi.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef MIDI_H
#define MIDI_H
#include <Arduino.h>
void decodeBluetooth(int lengthM, uint8_t* message);
#endif

284
ESP32/note.cpp Normal file
View File

@@ -0,0 +1,284 @@
#include "note.h"
#include "serial.h"
#include "main.h"
Note notes[88];
int Note::scheduledLeftNotes = 0;
int Note::scheduledRightNotes = 0;
int Note::idGenerator = 0;
int Note::noteVelocityMs[127];
Note::Note()
{
id = idGenerator;
idGenerator++;
//initialize one row of vectors so program won't crash while comparing
//initialize note as off by default
for(int index = 0; index < 6; index++)
{
schedule[index].reserve(8);
schedule[index].resize(1);
}
schedule[OFF].push_back(millis());
}
void Note::prepareToSchedule(uint8_t velocity)
{
if(Setting::handleNotes)
{
calculateVolume(velocity);
if(Setting::scheduleNotes)
{
if(velocity == 0)
scheduleNote(velocity);
else if(canBeScheduled())
scheduleNote(velocity);
} else
sendMidiToProMicro(id, velocity);
} else
sendMidiToProMicro(id, velocity); //without modifying the volume
}
void Note::checkSchedule()
{
//reverse stack behavior: erases stack from the bottom
unsigned long ms = millis();
if(schedule[OFF].size() > 1 && schedule[DEACTIVATION].size() > 1 &&
ms >= schedule[OFF].at(1) && schedule[OFF].at(1) >= schedule[DEACTIVATION].at(1)) //first because sometimes off and on times are the same
{
schedule[DEACTIVATION].erase(++schedule[DEACTIVATION].begin());
}
if(schedule[STARTUP].size() > 1 && schedule[OFF].size() > 1 && ms >= schedule[STARTUP].at(1) &&
schedule[STARTUP].at(1) >= schedule[OFF].at(1))
{
schedule[OFF].erase(++schedule[OFF].begin());
sendMidiToProMicro(id, 127);
}
if(schedule[ACTIVATION].size() > 1 && schedule[STARTUP].size() > 1 && schedule[VELOCITY].size() > 1 &&
ms >= schedule[ACTIVATION].at(1) && schedule[ACTIVATION].at(1) >= schedule[STARTUP].at(1))
{
schedule[STARTUP].erase(++schedule[STARTUP].begin());
sendMidiToProMicro(id, schedule[VELOCITY].at(1));
}
if(schedule[ON].size() > 1 && schedule[ACTIVATION].size() > 1 &&
ms >= schedule[ON].at(1) && schedule[ON].at(1) >= schedule[ACTIVATION].at(1))
{
schedule[ACTIVATION].erase(++schedule[ACTIVATION].begin());
schedule[VELOCITY].erase(++schedule[VELOCITY].begin());
sendMidiToProMicro(id, 127);
}
if(schedule[DEACTIVATION].size() > 1 && schedule[ON].size() > 1 &&
ms >= schedule[DEACTIVATION].at(1) && schedule[DEACTIVATION].at(1) >= schedule[ON].at(1))
{
schedule[ON].erase(++schedule[ON].begin());
sendMidiToProMicro(id, 0);
}
}
void Note::checkForErrors()
{
unsigned long ms = millis();
if(ms >= timeSinceActivation + Setting::noteTimeoutMs && timeSinceActivation > 0) resetSchedule();
if(schedule[ON].size() > 1 && ms >= schedule[ON].at(1) + Setting::noteTimeoutMs) resetSchedule();
}
void Note::resetSchedule()
{
if(DEBUG_MODE) Serial.print("Resetting schedule for note: ");
if(DEBUG_MODE) Serial.println(id);
for(int index = 0; index < 6; index++)
{
schedule[index].resize(1);
schedule[index].at(0) = 0;
}
schedule[OFF].push_back(millis());
if(timeSinceActivation > 0)
updateInstance(false);
timeSinceActivation = 0;
instances = 0;
sendMidiToProMicro(id, 0);
}
void Note::resetInstances()
{
//warning: only call this function in conjunction with resetSchedule()!
scheduledLeftNotes = 0;
scheduledRightNotes = 0;
}
void Note::scheduleNote(uint8_t velocity)
{
if(DEBUG_MODE) sendScheduleToSerial();
unsigned long ms = millis();
unsigned long msAndDelay = ms + fullDelay;
using namespace Setting;
if(velocity > 0) //if note on command
{
int velocityMs = noteVelocityMs[velocity - 1];
instances++;
if(instances == 1) //if note is scheduled to deactivate (was 0 before instances++)
{
if(msAndDelay - velocityMs - startupMs >= schedule[OFF].back()) //if new note can be scheduled with current scheduling
{
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION].push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
timeSinceActivation == ms;
updateInstance(true);
} else if(msAndDelay - deactivateMs - velocityMs - startupMs >= schedule[ON].back()) //if current scheduling can be modified to still schedule the new note
{
schedule[DEACTIVATION].push_back(msAndDelay - velocityMs - startupMs - deactivateMs);
schedule[DEACTIVATION].erase(----schedule[DEACTIVATION].end());
schedule[OFF]. push_back(msAndDelay - velocityMs - startupMs);
schedule[OFF]. erase(----schedule[OFF].end());
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION]. push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
timeSinceActivation == ms;
updateInstance(true);
} else if(msAndDelay - fastDeactivateMs - velocityMs - startupMs >= schedule[ACTIVATION].back()) //if current scheduling can be modified with fast deactivation to schedule the new note
{
schedule[ON]. push_back(msAndDelay - velocityMs - startupMs - fastDeactivateMs);
schedule[ON]. erase(----schedule[ON].end());
schedule[DEACTIVATION].push_back(msAndDelay - velocityMs - startupMs - fastDeactivateMs);
schedule[DEACTIVATION].erase(----schedule[DEACTIVATION].end());
schedule[OFF]. push_back(msAndDelay - velocityMs - startupMs);
schedule[OFF]. erase(----schedule[OFF].end());
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION]. push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
timeSinceActivation == ms;
updateInstance(true);
}
} else //note is scheduled to activate and not deactivate
{
if(msAndDelay - deactivateMs - velocityMs - startupMs >= schedule[ON].back()) //if current scheduling can be modified to still schedule the new note
{
schedule[DEACTIVATION].push_back(msAndDelay - velocityMs - startupMs - deactivateMs);
schedule[OFF]. push_back(msAndDelay - velocityMs - startupMs);
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION]. push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
} else if(msAndDelay - fastDeactivateMs - velocityMs - startupMs >= schedule[ACTIVATION].back() && schedule[ACTIVATION].back() > 0) //if current scheduling can be modified with fast deactivation to still schedule the new note
{
schedule[ON]. push_back(msAndDelay - velocityMs - startupMs - fastDeactivateMs);
schedule[ON]. erase(----schedule[ON].end());
schedule[DEACTIVATION].push_back(msAndDelay - velocityMs - startupMs - fastDeactivateMs);
schedule[OFF]. push_back(msAndDelay - velocityMs - startupMs);
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION]. push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
}
}
} else if(instances > 0 /*&& velocity == 0*/) //if note off command and note is not already off
{
if(instances > 1) //if this isn't the last instance
{
//remove instance and exit
instances--;
} else //this is the last instance of the note and it should be scheduled
{
instances = 0;
timeSinceActivation == 0;
updateInstance(false);
if(msAndDelay - fastDeactivateMs >= schedule[ACTIVATION].back() && msAndDelay - fastDeactivateMs <= schedule[ON].back() && schedule[ACTIVATION].back() > 0) //if it's efficient to use fast deactivation
{
schedule[ON]. push_back(msAndDelay - fastDeactivateMs);
schedule[ON]. erase(----schedule[ON].end());
schedule[DEACTIVATION].push_back(msAndDelay - fastDeactivateMs);
schedule[OFF]. push_back(msAndDelay);
} else if(msAndDelay - deactivateMs >= schedule[ON].back()) //if regular deactivation works
{
schedule[DEACTIVATION].push_back(msAndDelay - deactivateMs);
schedule[OFF]. push_back(msAndDelay);
} else //if all else fails the key shouldn't stay stuck on
{
if(schedule[ACTIVATION].back() > 0)
{
//immediately deactivate the key as soon as it makes sound
schedule[ON]. push_back(schedule[ACTIVATION].back());
schedule[ON]. erase(----schedule[ON].end());
schedule[DEACTIVATION].push_back(schedule[ACTIVATION].back());
schedule[OFF]. push_back(schedule[ACTIVATION].back() + fastDeactivateMs);
} else //this should never happen
{
schedule[DEACTIVATION].push_back(msAndDelay);
schedule[OFF]. push_back(msAndDelay + deactivateMs);
}
}
}
}
if(DEBUG_MODE) sendScheduleToSerial();
}
void Note::calculateVolume(uint8_t& velocity)
{
if(velocity > 0)
{
velocity = round((velocity * Setting::volume) / (double)100);
if(velocity > 127)
velocity = 127;
else if(velocity < 1)
velocity = 1;
}
}
void Note::updateInstance(bool state)
{
if(id < 44)
{
if(state)
scheduledLeftNotes++;
else
scheduledLeftNotes--;
} else
{
if(state)
scheduledRightNotes++;
else
scheduledRightNotes--;
}
}
void Note::sendScheduleToSerial()
{
Serial.println("-----------------------");
Serial.print("Schedule for note: ");
Serial.print(id);
Serial.print(" At the time: ");
Serial.println(millis());
for(int i = 0; i < 6; i++)
{
for(int j = 0; j < schedule[i].size(); j++)
{
Serial.print(schedule[i].at(j), DEC);
Serial.print(" ");
}
Serial.println(" ");
Serial.print(" ");
}
}
bool Note::canBeScheduled()
{
if(id < 44)
{
if(scheduledLeftNotes < Setting::maxLeftNotes)
return true;
} else
if(id >= 44)
{
if(scheduledRightNotes < Setting::maxRightNotes)
return true;
}
return false;
}

52
ESP32/note.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef NOTE_H
#define NOTE_H
#include <vector>
#include <Arduino.h>
#include "settings.h"
class Note
{
private:
typedef std::vector<unsigned long> scheduleV_t;
enum ScheduleID
{
STARTUP,
ACTIVATION,
VELOCITY,
ON,
DEACTIVATION,
OFF
};
scheduleV_t schedule[6];
int id = 0;
int startupMs = Setting::maxStartupMs;
int deactivateMs = Setting::maxDeactivateMs;
int instances = 0;
static int scheduledLeftNotes;
static int scheduledRightNotes;
static int idGenerator;
static int noteVelocityMs[127];
unsigned long timeSinceActivation = 0;
void scheduleNote(uint8_t velocity);
void calculateVolume(uint8_t& volume);
void updateInstance(bool state);
void sendScheduleToSerial();
bool canBeScheduled();
public:
Note();
void setStartupMs(int ms) { startupMs = ms; }
void setDeactivateMs(int ms) { deactivateMs = ms; }
void prepareToSchedule(uint8_t velocity);
void checkSchedule();
void checkForErrors();
void resetSchedule();
static void resetInstances();
static void setNoteVelocityMs(int velocity, int ms) { noteVelocityMs[velocity] = ms; }
static int getNoteVelocityMs(int velocity) { return noteVelocityMs[velocity]; }
};
extern Note notes[88];
#endif

67
ESP32/serial.cpp Normal file
View File

@@ -0,0 +1,67 @@
#include "serial.h"
#include "note.h"
#include "sustain.h"
#include "settings.h"
#include "main.h"
void sendMidiToProMicro(byte note, byte velocity);
//serial is always handled in bytes, not uint8_t because serial is dumb
extern const byte NOTE_HEADER = 201;
extern const byte SUSTAIN_HEADER = 202;
extern const byte SETTING_HEADER = 203;
extern const byte RESET_HEADER = 204;
extern const byte VOLUME_HEADER = 205;
extern const byte END_HEADER = 206;
void checkForSerial()
{
while(Serial.available() > 2)
{
uint8_t header = Serial.read();
if(header >= NOTE_HEADER && header <= VOLUME_HEADER) //make sure the first byte is a header
{
uint8_t byte1 = Serial.read(); //only declare these if the first byte is a header
uint8_t byte2 = Serial.read(); //otherwise the program will keep looping looking for one
switch(header)
{
case NOTE_HEADER:
if(byte1 >= 0 && byte1 <= 87 &&
byte2 >= Setting::minNoteVelocity && byte2 <= 127)
notes[byte1].prepareToSchedule(byte2);
break;
case SUSTAIN_HEADER:
sustain.prepareToSchedule(byte2);
break;
case SETTING_HEADER:
updateSetting(static_cast<SettingID::SettingID>(byte1), byte2);
break;
case RESET_HEADER:
resetAll();
break;
case VOLUME_HEADER:
setVolume(byte2);
break;
}
}
}
}
void sendMidiToProMicro(byte note, byte velocity)
{
//invert notes to be compatible with shift registers
byte compatibleNote = (note * -1) + 87;
byte message[3] ={ NOTE_HEADER, compatibleNote, velocity };
//note that velocity is conformed on Pro Micro
Serial.write(NOTE_HEADER);
Serial.write(compatibleNote);
Serial.write(velocity);
//Serial.write(END_HEADER);
}
void customSerialToProMicro(byte header, byte byte1, byte byte2)
{
Serial.write(header);
Serial.write(byte1);
Serial.write(byte2);
}

10
ESP32/serial.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef SERIAL_H
#define SERIAL_H
#include <Arduino.h>
void checkForSerial();
void sendMidiToProMicro(byte note, byte velocity);
void customSerialToProMicro(byte header, byte note, byte velocity);
#endif

121
ESP32/settings.cpp Normal file
View File

@@ -0,0 +1,121 @@
#include "settings.h"
#include "settings.h"
#include "note.h"
#include "serial.h"
//settings within the program
int fullDelay = 0;
bool acceptMidi = false;
unsigned long nextReset = 0; //first reset will happen immediately
//settings changed by the control box
namespace Setting
{
bool handleNotes = true;
bool scheduleNotes = true;
int minNoteVelocity = 4;
int minStartupMs = 18;
int maxStartupMs = 18;
int velocityVar = 35;
int minDeactivateMs = 75;
int maxDeactivateMs = 80;
int fastDeactivateMs = 52;
int sustainOnMs = 91;
int sustainOffMs = 50;
int noteTimeoutMs = 10000;
int sustainTimeoutMs = 30000;
int autoResetMs = 360000;
int maxLeftNotes = 26;
int maxRightNotes = 22;
int volume = 100;
}
void setVolume(int newVolume) { Setting::volume = newVolume; }
void updateSetting(SettingID::SettingID setting, int value)
{
using namespace SettingID;
using namespace Setting;
switch(setting)
{
case HANDLE_NOTES:
scheduleNotes = value;
break;
case SCHEDULE_NOTES:
scheduleNotes = value;
break;
case MIN_ACCEPTED_VEL:
minNoteVelocity = value;
break;
case PWM_PERCENT:
//only setting passed to pro micro
extern const byte SETTING_HEADER;
customSerialToProMicro(SETTING_HEADER, value, value);
break;
case MIN_STARTUP_MS:
minStartupMs = value;
msReset();
break;
case MAX_STARTUP_MS:
maxStartupMs = value;
msReset();
break;
case VELOCITY_VAR:
velocityVar = value;
msReset();
break;
case MIN_DEACTIVATE_MS:
minDeactivateMs = value;
msReset();
break;
case MAX_DEACTIVATE_MS:
maxDeactivateMs = value;
msReset();
break;
case FAST_DEACTIVATE_MS:
fastDeactivateMs = value;
break;
case SUSTAIN_ON_MS:
sustainOnMs = value;
break;
case SUSTAIN_OFF_MS:
sustainOffMs = value;
break;
case NOTE_TIMEOUT_MS:
noteTimeoutMs = value * 1000;
break;
case SUSTAIN_TIMEOUT_MS:
sustainTimeoutMs = value * 1000;
break;
case AUTO_RESET_MS:
autoResetMs = value * 60000;
break;
case MAX_LEFT_NOTES:
maxLeftNotes = value;
break;
case MAX_RIGHT_NOTES:
maxRightNotes = value;
break;
}
}
void msReset()
{
using namespace Setting;
for(int noteIndex = 0; noteIndex < 88; noteIndex++)
{
//calculate the ms for each key linearly. Calculate this now to process less later
notes[noteIndex].setStartupMs((noteIndex * (minStartupMs - maxStartupMs) / 88) + maxStartupMs);
notes[noteIndex].setDeactivateMs((noteIndex * (maxDeactivateMs - minDeactivateMs) / 88) + minDeactivateMs);
}
for(int velocityIndex = 0; velocityIndex < 127; velocityIndex++)
{
//function created through graphing velocity times and creating a function that best fit
Note::setNoteVelocityMs(velocityIndex, round(((-25 * velocityIndex) / (double)127) + velocityVar));
}
//calculate the total maximum time for a note cycle as a reference when scheduling keys
fullDelay = maxStartupMs + Note::getNoteVelocityMs(0) + maxDeactivateMs;
}

60
ESP32/settings.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include <Arduino.h>
namespace SettingID //so a using statement can be used in the switch statement
{
enum SettingID
{
HANDLE_NOTES,
SCHEDULE_NOTES,
MIN_ACCEPTED_VEL,
PWM_PERCENT,
MIN_STARTUP_MS,
MAX_STARTUP_MS,
VELOCITY_VAR,
MIN_DEACTIVATE_MS,
MAX_DEACTIVATE_MS,
FAST_DEACTIVATE_MS,
SUSTAIN_ON_MS,
SUSTAIN_OFF_MS,
NOTE_TIMEOUT_MS,
SUSTAIN_TIMEOUT_MS,
AUTO_RESET_MS,
MAX_LEFT_NOTES,
MAX_RIGHT_NOTES
};
}
extern int fullDelay;
extern bool acceptMidi;
extern unsigned long nextReset;
namespace Setting
{
extern bool handleNotes;
extern bool scheduleNotes;
extern int minNoteVelocity;
extern int minSolenoidPWM;
extern int minStartupMs;
extern int maxStartupMs;
extern int velocityVar;
extern int minDeactivateMs;
extern int maxDeactivateMs;
extern int fastDeactivateMs;
extern int sustainOnMs;
extern int sustainOffMs;
extern int noteTimeoutMs;
extern int sustainTimeoutMs;
extern int autoResetMs;
extern int maxLeftNotes;
extern int maxRightNotes;
extern int volume;
}
void setVolume(int newVolume);
void updateSetting(SettingID::SettingID setting, int value);
void msReset();
#endif

122
ESP32/sustain.cpp Normal file
View File

@@ -0,0 +1,122 @@
#include "sustain.h"
#include "sustain.h"
#include "serial.h"
#include "settings.h"
Sustain sustain;
Sustain::Sustain()
{
//initialize one row of vectors so program won't crash while comparing
//initialize sustain as off by default
for(int index = 0; index < 4; index++)
{
schedule[index].reserve(6);
schedule[index].resize(1);
}
schedule[OFF].push_back(millis());
}
void Sustain::prepareToSchedule(uint8_t velocity)
{
const uint8_t SUSTAIN_MIN_VELOCITY = 64;
if(Setting::handleNotes && Setting::scheduleNotes)
{
if(velocity < SUSTAIN_MIN_VELOCITY)
{
scheduleSustain(false);
} else if(instances == 0) //only schedule if sustain is off
scheduleSustain(true);
} else
{
if(velocity < SUSTAIN_MIN_VELOCITY)
ledcWrite(0, 0);
else
ledcWrite(0, 255);
}
}
void Sustain::checkSchedule()
{
unsigned long ms = millis();
//less checks for sustain because it's slower and less important
if(schedule[OFF].size() > 1 && schedule[DEACTIVATION].size() > 1 &&
ms >= schedule[OFF].at(1) && schedule[OFF].at(1) >= schedule[DEACTIVATION].at(1))
{
schedule[DEACTIVATION].erase(schedule[DEACTIVATION].begin()++);
ledcWrite(0, 0);
}
if(schedule[ACTIVATION].size() > 1 && schedule[OFF].size() > 1 &&
ms >= schedule[ACTIVATION].at(1) && schedule[ACTIVATION].at(1) >= schedule[OFF].at(1))
{
schedule[OFF].erase(schedule[OFF].begin()++);
ledcWrite(0, 255);
}
if(schedule[ON].size() > 1 && schedule[ACTIVATION].size() > 1 &&
ms >= schedule[ON].at(1) && schedule[ON].at(1) >= schedule[ACTIVATION].at(1))
{
schedule[ACTIVATION].erase(schedule[ACTIVATION].begin()++);
}
if(schedule[DEACTIVATION].size() > 1 && schedule[ON].size() > 1 &&
ms >= schedule[DEACTIVATION].at(1) && schedule[DEACTIVATION].at(1) >= schedule[ON].at(1))
{
schedule[ON].erase(schedule[ON].begin()++);
ledcWrite(0, 30);
}
}
void Sustain::checkForErrors()
{
unsigned long ms = millis();
if(ms >= timeSinceActivation + Setting::sustainTimeoutMs && timeSinceActivation > 0) resetSchedule();
if(schedule[ON].size() > 1) if(ms >= schedule[ON].at(1) + Setting::sustainTimeoutMs) resetSchedule();
}
void Sustain::resetSchedule()
{
for(int index = 0; index < 4; index++)
schedule[index].resize(1);
schedule[OFF].push_back(millis());
timeSinceActivation = 0;
instances = 0;
ledcWrite(0, 0);
}
void Sustain::scheduleSustain(bool state)
{
unsigned long ms = millis();
unsigned long msAndDelay = ms + fullDelay;
using namespace Setting;
if(state)
{
instances++;
if(msAndDelay - sustainOnMs >= schedule[OFF].back()) //if sustain can be scheduled with current scheduling
{
schedule[ACTIVATION].push_back(msAndDelay - sustainOnMs);
schedule[ON]. push_back(msAndDelay);
timeSinceActivation = ms;
} else if(msAndDelay - sustainOffMs - sustainOnMs >= schedule[ON].back()) //if current scheduling can be modified to still schedule the sustain
{
schedule[ON]. push_back(msAndDelay - sustainOnMs - sustainOffMs);
schedule[ON]. erase(----schedule[ON].end());
schedule[OFF]. push_back(msAndDelay - sustainOnMs);
schedule[OFF]. erase(----schedule[OFF].end());
schedule[ACTIVATION].push_back(msAndDelay - sustainOnMs);
schedule[ON]. push_back(msAndDelay);
timeSinceActivation = ms;
}
} else if(instances > 0) //if sustain off command and sustain is not already off
{
instances == 0;
if(msAndDelay - sustainOffMs >= schedule[ON].back()) //if sustain can be ideally deactivated
{
schedule[DEACTIVATION].push_back(msAndDelay - sustainOffMs);
schedule[OFF]. push_back(msAndDelay);
} else //deactivate sustain anyways so it's not stuck on
{
schedule[DEACTIVATION].push_back(msAndDelay);
schedule[OFF]. push_back(msAndDelay + sustainOffMs);
}
}
}

33
ESP32/sustain.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef SUSTAIN_H
#define SUSTAIN_H
#include <Arduino.h>
#include <vector>
class Sustain
{
private:
typedef std::vector<unsigned long> scheduleV_t;
enum ScheduleID
{
ACTIVATION,
ON,
DEACTIVATION,
OFF
};
scheduleV_t schedule[4];
int instances = 0;
unsigned long timeSinceActivation = 0;
void scheduleSustain(bool state);
public:
Sustain();
void prepareToSchedule(uint8_t velocity);
void checkSchedule();
void checkForErrors();
void resetSchedule();
};
extern Sustain sustain;
#endif

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Brandon Switzer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

37
Piano Project.sln Normal file
View File

@@ -0,0 +1,37 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29025.244
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ESP32", "ESP32\ESP32.vcxproj", "{C5F80730-F44F-4478-BDAE-6634EFC2CA88}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProMicro", "ProMicro\ProMicro.vcxproj", "{62539448-1BC7-47A2-A21E-53DE25ACD53F}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ControlBox", "ControlBox\ControlBox.vcxproj", "{F084E901-4464-411A-9291-371A63C4AA95}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.ActiveCfg = Debug|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.Build.0 = Debug|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.ActiveCfg = Release|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.Build.0 = Release|Win32
{62539448-1BC7-47A2-A21E-53DE25ACD53F}.Debug|x86.ActiveCfg = Debug|Win32
{62539448-1BC7-47A2-A21E-53DE25ACD53F}.Debug|x86.Build.0 = Debug|Win32
{62539448-1BC7-47A2-A21E-53DE25ACD53F}.Release|x86.ActiveCfg = Release|Win32
{62539448-1BC7-47A2-A21E-53DE25ACD53F}.Release|x86.Build.0 = Release|Win32
{F084E901-4464-411A-9291-371A63C4AA95}.Debug|x86.ActiveCfg = Debug|Win32
{F084E901-4464-411A-9291-371A63C4AA95}.Debug|x86.Build.0 = Debug|Win32
{F084E901-4464-411A-9291-371A63C4AA95}.Release|x86.ActiveCfg = Release|Win32
{F084E901-4464-411A-9291-371A63C4AA95}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DEECECCC-4575-4EF1-9891-F9580C4AD9FE}
EndGlobalSection
EndGlobal

30
ProMicro/ProMicro.ino Normal file
View File

@@ -0,0 +1,30 @@
#include <MIDIUSB.h>
#include "shiftRegister.h"
#include "serial.h"
#include "midi.h"
extern const bool DEBUG_MODE = false;
void setup()
{
intitializeRegisters();
delay(2000);
Serial.begin(38400);
Serial1.begin(38400);
pinMode(19, OUTPUT);
pinMode(20, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(19, HIGH);
digitalWrite(20, HIGH);
if(DEBUG_MODE)
testRegisters();
}
void loop()
{
checkForMidiUSB();
checkForSerial();
}

133
ProMicro/ProMicro.vcxproj Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="ProMicro.ino" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="__vm\.ProMicro.vsarduino.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="midi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="serial.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shiftRegister.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="midi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="serial.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="shiftRegister.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,85 @@
/*
Editor: https://www.visualmicro.com/
This file is for intellisense purpose only.
Visual micro (and the arduino ide) ignore this code during compilation. This code is automatically maintained by visualmicro, manual changes to this file will be overwritten
The contents of the _vm sub folder can be deleted prior to publishing a project
All non-arduino files created by visual micro and all visual studio project or solution files can be freely deleted and are not required to compile a sketch (do not delete your own code!).
Note: debugger breakpoints are stored in '.sln' or '.asln' files, knowledge of last uploaded breakpoints is stored in the upload.vmps.xml file. Both files are required to continue a previous debug session without needing to compile and upload again
Hardware: Arduino Leonardo, Platform=avr, Package=arduino
*/
#if defined(_VMICRO_INTELLISENSE)
#ifndef _VSARDUINO_H_
#define _VSARDUINO_H_
#define __AVR_atmega32u4__
#define __AVR_ATmega32U4__
#define __AVR_ATmega32u4__
#define F_CPU 16000000L
#define ARDUINO 10809
#define ARDUINO_AVR_LEONARDO
#define ARDUINO_ARCH_AVR
#define USB_VID 0x2341
#define USB_PID 0x8036
#define __cplusplus 201103L
#define _Pragma(x)
#define __AVR__
#define __inline__
#define __asm__(...)
#define __extension__
#define __inline__
#define __volatile__
#define GCC_VERSION 40902
#define __cplusplus 201103L
#define volatile(va_arg)
#define _CONST
#define __builtin_va_start
#define __builtin_va_end
#define __attribute__(...)
#define NOINLINE __attribute__((noinline))
#define prog_void
#define PGM_VOID_P int
#ifndef __builtin_constant_p
#define __builtin_constant_p __attribute__((__const__))
#endif
#ifndef __builtin_strlen
#define __builtin_strlen __attribute__((__const__))
#endif
#define NEW_H
typedef void *__builtin_va_list;
//extern "C" void __cxa_pure_virtual() {;}
typedef int div_t;
typedef int ldiv_t;
typedef void *__builtin_va_list;
//extern "C" void __cxa_pure_virtual() {;}
#include "arduino.h"
#include <pins_arduino.h>
//#undef F
//#define F(string_literal) ((const PROGMEM char *)(string_literal))
#undef PSTR
#define PSTR(string_literal) ((const PROGMEM char *)(string_literal))
//typedef unsigned char uint8_t;
//typedef unsigned int uint8_t;
#define pgm_read_byte(address_short) uint8_t()
#define pgm_read_word(address_short) uint16_t()
#define pgm_read_dword(address_short) uint32_t()
#define pgm_read_float(address_short) float()
#define pgm_read_ptr(address_short) short()
#include "ProMicro.ino"
#endif
#endif

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

70
ProMicro/midi.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include <MIDIUSB.h>
#include "midi.h"
#include "shiftRegister.h"
#include "serial.h"
void decodeMidi(uint8_t header, uint8_t byte1, uint8_t byte2, uint8_t byte3);
extern const bool DEBUG_MODE;
void checkForMidiUSB()
{
midiEventPacket_t rx; //midi data struct from midiUSB libray
do
{
rx = MidiUSB.read(); //get queued info from USB
if(rx.header != 0)
{
decodeMidi(rx.header, rx.byte1, rx.byte2, rx.byte3);
}
} while(rx.header != 0);
}
void decodeMidi(uint8_t header, uint8_t byte1, uint8_t byte2, uint8_t byte3)
{
if(DEBUG_MODE)
{
String message[] =
{
"-----------",
static_cast<String>(header),
static_cast<String>(byte1),
static_cast<String>(byte2),
static_cast<String>(byte3),
"-----------"
};
sendSerialToUSB(message, 6);
}
const uint8_t NOTE_ON_HEADER = 9;
const uint8_t NOTE_OFF_HEADER = 8;
const uint8_t CONTROL_CHANGE_HEADER = 8;
const uint8_t SUSTAIN_STATUS_BYTE = 176;
const uint8_t MIN_NOTE_PITCH = 21;
const uint8_t MAX_NOTE_PITCH = 108;
switch(header)
{
case NOTE_ON_HEADER:
if(byte2 >= MIN_NOTE_PITCH && byte2 <= MAX_NOTE_PITCH &&
byte3 >= 0 && byte3 <= 127)
{
uint8_t note = (byte2 - MIN_NOTE_PITCH) * -1 + 87;
activateNote(note, byte3);
}
break;
case NOTE_OFF_HEADER:
if(byte2 >= MIN_NOTE_PITCH && byte2 <= MAX_NOTE_PITCH)
{
uint8_t note = (byte2 - MIN_NOTE_PITCH) * -1 + 87;
activateNote(note, 0);
}
break;
case SUSTAIN_STATUS_BYTE:
if(byte1 == SUSTAIN_STATUS_BYTE)
{
extern const uint8_t SUSTAIN_HEADER;
sendSerialToMain(SUSTAIN_HEADER, byte3, byte3);
}
break;
}
}

8
ProMicro/midi.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef MIDI_H
#define MIDI_H
#include <Arduino.h>
void checkForMidiUSB();
#endif

48
ProMicro/serial.cpp Normal file
View File

@@ -0,0 +1,48 @@
#include "serial.h"
#include "shiftRegister.h"
extern const bool DEBUG_MODE;
extern const uint8_t SUSTAIN_HEADER = 202;
void checkForSerial()
{
const byte NOTE_HEADER = 201;
const byte SETTING_HEADER = 203;
while(Serial1.available() > 2)
{
uint8_t header = Serial1.read();
if(header == NOTE_HEADER)
{
uint8_t note = Serial1.read();
uint8_t velocity = Serial1.read();
activateNote(note, velocity);
if(DEBUG_MODE)
{
Serial.println(note);
Serial.println(velocity);
Serial.println("----------------");
}
} else
if(header == SETTING_HEADER)
{
uint8_t value1 = Serial1.read();
uint8_t value2 = Serial1.read();
//Pro Micro knows that only one setting is being sent
pwmPercent = value2;
}
}
}
void sendSerialToMain(byte header, byte setting, byte value)
{
Serial1.print(header);
Serial1.print(setting);
Serial1.print(value);
}
void sendSerialToUSB(String* message, int lengthOfMessage)
{
for(int index = 0; index < lengthOfMessage; index++)
Serial.println(message[index]);
}

10
ProMicro/serial.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef SERIAL_H
#define SERIAL_H
#include <Arduino.h>
void checkForSerial();
void sendSerialToMain(byte header, byte setting, byte value);
void sendSerialToUSB(String* message, int lengthOfMessage);
#endif

View File

@@ -0,0 +1,48 @@
//Latchpin: Pin 10 goes to second register input
//Datapin: Pin 16 goes to first register input
//Clockpin: Pin 15 goes to third register input
const int ShiftPWM_latchPin=18; //values assigned before includes
const bool ShiftPWM_invertOutputs = false;
const bool ShiftPWM_balanceLoad = false;
#include <ShiftPWM.h>
#include <CShiftPWM.h>
#include "shiftRegister.h"
void conformVelocity(uint8_t& velocity);
extern uint8_t pwmPercent = 45;
const char MAX_PWM = 235;
void intitializeRegisters()
{
const char PWM_FREQUENCY = 75;
const int NUM_REGISTERS = 11;
ShiftPWM.SetAmountOfRegisters(NUM_REGISTERS);
ShiftPWM.Start(PWM_FREQUENCY, MAX_PWM);
}
void activateNote(uint8_t note, uint8_t velocity)
{
if(velocity > 0)
conformVelocity(velocity);
ShiftPWM.SetOne(note, velocity);
}
void conformVelocity(uint8_t& velocity)
{
//conforms velocity from 0-127 to 0-255 while taking into account the minimum possible solenoid PWM
const double MIN_PWM = round(MAX_PWM * pwmPercent / static_cast<double>(100));
velocity = round(velocity * (MAX_PWM - MIN_PWM) / static_cast<double>(127) + MIN_PWM);
}
void testRegisters()
{
for(int led = 0; led < 88; led++)
{
ShiftPWM.SetOne(led, MAX_PWM);
delay(50);
ShiftPWM.SetOne(led, 0);
}
}

12
ProMicro/shiftRegister.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef SHIFT_REGISTER_H
#define SHIFT_REGISTER_H
#include <Arduino.h>
extern uint8_t pwmPercent;
void intitializeRegisters();
void activateNote(uint8_t note, uint8_t velocity);
void testRegisters();
#endif

35
README.md Normal file
View File

@@ -0,0 +1,35 @@
# Piano Project
Arduino Code for a DIY Player Piano.
See more information about it here: brandonswitzer.squarespace.com/player-piano
Role of each of the Arduinos:
ESP32:
- Receives and decodes bluetooth message
- Schedules notes
- Handles sustain
- Handles setting changes
Pro Micro:
- Receives data from ESP32 and/or USB
- Activates notes using shift registers
Control Box (Pro Micro):
- Interface for changing settings
- Receives data from USB and sends it to ESP32
- Reset button
Required Arduino Libraries:
- ShiftPWM (Pro Micro)
- MIDIUSB (Pro Micro & Control Box)
- LiquidCrystal_I2C (Control Box)