commit 32fa744a73475e5b51692702432e3d22dbbe3890 Author: Brandon Switzer Date: Wed Jul 31 16:50:00 2019 -0400 reset repository cause it broke diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ce6fdd --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/ControlBox/ControlBox.ino b/ControlBox/ControlBox.ino new file mode 100644 index 0000000..1e62b34 Binary files /dev/null and b/ControlBox/ControlBox.ino differ diff --git a/ControlBox/ControlBox.vcxproj b/ControlBox/ControlBox.vcxproj new file mode 100644 index 0000000..e7fd689 --- /dev/null +++ b/ControlBox/ControlBox.vcxproj @@ -0,0 +1,137 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {F084E901-4464-411A-9291-371A63C4AA95} + ControlBox + ControlBox + + + + Application + true + MultiByte + + + + + Application + false + true + MultiByte + + + + + Application + true + MultiByte + + + + + Application + false + true + MultiByte + + + + + Application + false + true + MultiByte + v141 + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + $(ProjectDir)..\ControlBox;C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\EEPROM\src;C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\Wire\src;$(ProjectDir)..\..\..\..\Arduino\libraries\Arduino-LiquidCrystal-I2C-library-master;$(ProjectDir)..\..\..\..\Arduino\libraries\MIDIUSB-master\src;C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\Wire\src\utility;C:\Program Files (x86)\Arduino\libraries;$(ProjectDir)..\..\..\..\Arduino\libraries;C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries;C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino;C:\Program Files (x86)\Arduino\hardware\arduino\avr\variants\leonardo;C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include;C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\avr;C:\Program Files (x86)\Arduino\hardware\tools\avr\lib\gcc\avr\4.8.1\include;C:\Program Files (x86)\Arduino\hardware\tools\avr\lib\gcc\avr\4.9.2\include;C:\Program Files (x86)\Arduino\hardware\tools\avr\lib\gcc\avr\4.9.3\include;%(AdditionalIncludeDirectories) + $(ProjectDir)__vm\.ControlBox.vsarduino.h;%(ForcedIncludeFiles) + true + __AVR_atmega32u4__;__AVR_ATmega32U4__;__AVR_ATmega32u4__;F_CPU=16000000L;ARDUINO=10809;ARDUINO_AVR_LEONARDO;ARDUINO_ARCH_AVR;USB_VID=0x2341;USB_PID=0x8036;__cplusplus=201103L;_VMICRO_INTELLISENSE;%(PreprocessorDefinitions) + + + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + true + + + + + + + VisualMicroDebugger + + + + CppCode + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ControlBox/ControlBox.vcxproj.filters b/ControlBox/ControlBox.vcxproj.filters new file mode 100644 index 0000000..dc51a0f --- /dev/null +++ b/ControlBox/ControlBox.vcxproj.filters @@ -0,0 +1,57 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/ControlBox/__vm/.ControlBox.vsarduino.h b/ControlBox/__vm/.ControlBox.vsarduino.h new file mode 100644 index 0000000..139c80c --- /dev/null +++ b/ControlBox/__vm/.ControlBox.vsarduino.h @@ -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 +//#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 diff --git a/ControlBox/__vm/Compile.vmps.xml b/ControlBox/__vm/Compile.vmps.xml new file mode 100644 index 0000000..5f2bbd7 --- /dev/null +++ b/ControlBox/__vm/Compile.vmps.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ControlBox/__vm/Configuration.Debug.vmps.xml b/ControlBox/__vm/Configuration.Debug.vmps.xml new file mode 100644 index 0000000..e66b43e --- /dev/null +++ b/ControlBox/__vm/Configuration.Debug.vmps.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/ControlBox/input.cpp b/ControlBox/input.cpp new file mode 100644 index 0000000..da76dc8 --- /dev/null +++ b/ControlBox/input.cpp @@ -0,0 +1,134 @@ +#include +#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; + } +} diff --git a/ControlBox/input.h b/ControlBox/input.h new file mode 100644 index 0000000..113f5d4 --- /dev/null +++ b/ControlBox/input.h @@ -0,0 +1,13 @@ +#ifndef INPUT_H +#define INPUT_H + +#include + +extern unsigned long lastPressedOverall; + +extern int lastAnalog; + +void initializeInputs(); +void checkForInput(); + +#endif \ No newline at end of file diff --git a/ControlBox/lcd.cpp b/ControlBox/lcd.cpp new file mode 100644 index 0000000..450822b --- /dev/null +++ b/ControlBox/lcd.cpp @@ -0,0 +1,113 @@ +#include +#include +#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(SettingID::SCHEDULE_NOTES) && + currentMenu != static_cast(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(); + } +} diff --git a/ControlBox/lcd.h b/ControlBox/lcd.h new file mode 100644 index 0000000..7c58a77 --- /dev/null +++ b/ControlBox/lcd.h @@ -0,0 +1,25 @@ +#ifndef LCD_H +#define LCD_H + +#include + +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 \ No newline at end of file diff --git a/ControlBox/midi.cpp b/ControlBox/midi.cpp new file mode 100644 index 0000000..a0260a4 --- /dev/null +++ b/ControlBox/midi.cpp @@ -0,0 +1,41 @@ +#include +#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; + } +} diff --git a/ControlBox/midi.h b/ControlBox/midi.h new file mode 100644 index 0000000..4cab924 --- /dev/null +++ b/ControlBox/midi.h @@ -0,0 +1,8 @@ +#ifndef MIDI_H +#define MIDI_H + +#include + +void checkForMidiUSB(); + +#endif \ No newline at end of file diff --git a/ControlBox/serial.cpp b/ControlBox/serial.cpp new file mode 100644 index 0000000..5e5ef1b --- /dev/null +++ b/ControlBox/serial.cpp @@ -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); +} diff --git a/ControlBox/serial.h b/ControlBox/serial.h new file mode 100644 index 0000000..707e59d --- /dev/null +++ b/ControlBox/serial.h @@ -0,0 +1,18 @@ +#ifndef SERIAL_H +#define SERIAL_H + +#include + +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 \ No newline at end of file diff --git a/ControlBox/setting.cpp b/ControlBox/setting.cpp new file mode 100644 index 0000000..ab50b21 --- /dev/null +++ b/ControlBox/setting.cpp @@ -0,0 +1,24 @@ +#include +#include "setting.h" +#include "lcd.h" +#include "serial.h" +#include "input.h" + +void changeSetting(int changeBy) +{ + if(currentMenu != static_cast(SettingID::SCHEDULE_NOTES) && + currentMenu != static_cast(SettingID::HANDLE_NOTES)) //if current menu is not a bool menu + EEPROM.write(currentMenu, EEPROM.read(currentMenu) + changeBy); + else + EEPROM.write(currentMenu, !(static_cast(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); +} diff --git a/ControlBox/setting.h b/ControlBox/setting.h new file mode 100644 index 0000000..039be30 --- /dev/null +++ b/ControlBox/setting.h @@ -0,0 +1,29 @@ +#ifndef SETTING_H +#define SETTING_H + +#include + +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 \ No newline at end of file diff --git a/ESP32/ESP32.ino b/ESP32/ESP32.ino new file mode 100644 index 0000000..bbb22d6 --- /dev/null +++ b/ESP32/ESP32.ino @@ -0,0 +1,80 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic warning "-fpermissive" +#include +#include +#include +#include +#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 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; + } +} diff --git a/ESP32/ESP32.vcxproj b/ESP32/ESP32.vcxproj new file mode 100644 index 0000000..807d4ec --- /dev/null +++ b/ESP32/ESP32.vcxproj @@ -0,0 +1,140 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {C5F80730-F44F-4478-BDAE-6634EFC2CA88} + ESP32 + ESP32 + + + + Application + true + MultiByte + + + + + Application + false + true + MultiByte + + + + + Application + true + MultiByte + + + + + Application + false + true + MultiByte + + + + + Application + false + true + MultiByte + v141 + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + $(ProjectDir)..\ESP32;C:\Program Files (x86)\Arduino\libraries;$(ProjectDir)..\..\..\..\Arduino\libraries;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\libraries;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\cores\esp32;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\cores\esp32\libb64;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\variants\node32s;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\config;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\bluedroid;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\app_trace;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\app_update;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\bootloader_support;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\bt;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\driver;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\esp32;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\esp_adc_cal;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\ethernet;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\fatfs;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\freertos;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\heap;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\jsmn;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\log;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\mdns;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\mbedtls;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\mbedtls_port;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\newlib;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\nvs_flash;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\openssl;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\soc;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\spi_flash;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\sdmmc;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\spiffs;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\tcpip_adapter;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\ulp;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\vfs;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\wear_levelling;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\xtensa-debug-module;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\console;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\coap;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\wpa_supplicant;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\expat;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\json;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\nghttp;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\sdk\include\lwip;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\xtensa-esp32-elf\lib\gcc\xtensa-esp32-elf\5.2.0\include;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\xtensa-esp32-elf\xtensa-esp32-elf\include\c++\5.2.0\xtensa-esp32-elf;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\xtensa-esp32-elf\xtensa-esp32-elf\include;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\xtensa-esp32-elf\xtensa-esp32-elf\include\c++\5.2.0;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\xtensa-esp32-elf\include\c++\5.2.0;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\xtensa-esp32-elf\include;$(ProjectDir)..\..\..\..\Arduino\hardware\espressif\esp32\tools\xtensa-esp32-elf\include\include;%(AdditionalIncludeDirectories) + $(ProjectDir)__vm\.ESP32.vsarduino.h;%(ForcedIncludeFiles) + true + __ESP32_esp32__;__ESP32_ESP32__;ESP_PLATFORM;HAVE_CONFIG_H;F_CPU=240000000L;ARDUINO=10809;ARDUINO_Node32s;ARDUINO_ARCH_ESP32;ESP32;CORE_DEBUG_LEVEL=0;__cplusplus=201103L;_VMICRO_INTELLISENSE;%(PreprocessorDefinitions) + + + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + true + + + + + + + VisualMicroDebugger + + + + CppCode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ESP32/ESP32.vcxproj.filters b/ESP32/ESP32.vcxproj.filters new file mode 100644 index 0000000..eb40aec --- /dev/null +++ b/ESP32/ESP32.vcxproj.filters @@ -0,0 +1,66 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + \ No newline at end of file diff --git a/ESP32/__vm/.ESP32.vsarduino.h b/ESP32/__vm/.ESP32.vsarduino.h new file mode 100644 index 0000000..2498748 --- /dev/null +++ b/ESP32/__vm/.ESP32.vsarduino.h @@ -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 + +//#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 diff --git a/ESP32/__vm/Compile.vmps.xml b/ESP32/__vm/Compile.vmps.xml new file mode 100644 index 0000000..c861f79 --- /dev/null +++ b/ESP32/__vm/Compile.vmps.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ESP32/__vm/Configuration.Debug.vmps.xml b/ESP32/__vm/Configuration.Debug.vmps.xml new file mode 100644 index 0000000..16ae6d8 --- /dev/null +++ b/ESP32/__vm/Configuration.Debug.vmps.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/ESP32/__vm/Upload.vmps.xml b/ESP32/__vm/Upload.vmps.xml new file mode 100644 index 0000000..c861f79 --- /dev/null +++ b/ESP32/__vm/Upload.vmps.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ESP32/bluetooth.cpp b/ESP32/bluetooth.cpp new file mode 100644 index 0000000..d1c8573 --- /dev/null +++ b/ESP32/bluetooth.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#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; + } +} \ No newline at end of file diff --git a/ESP32/bluetooth.h b/ESP32/bluetooth.h new file mode 100644 index 0000000..97e3f3f --- /dev/null +++ b/ESP32/bluetooth.h @@ -0,0 +1,8 @@ +#ifndef BLUETOOTH_H +#define BLUETOOTH_H + +#include + +void initializeBluetooth(); + +#endif \ No newline at end of file diff --git a/ESP32/main.h b/ESP32/main.h new file mode 100644 index 0000000..00b4df7 --- /dev/null +++ b/ESP32/main.h @@ -0,0 +1,8 @@ +#ifndef MAIN_H +#define MAIN_H + +extern const bool DEBUG_MODE; +void resetAll(); +void flashLED(); + +#endif \ No newline at end of file diff --git a/ESP32/midi.cpp b/ESP32/midi.cpp new file mode 100644 index 0000000..9b6300a --- /dev/null +++ b/ESP32/midi.cpp @@ -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); + } +} \ No newline at end of file diff --git a/ESP32/midi.h b/ESP32/midi.h new file mode 100644 index 0000000..57af23d --- /dev/null +++ b/ESP32/midi.h @@ -0,0 +1,8 @@ +#ifndef MIDI_H +#define MIDI_H + +#include + +void decodeBluetooth(int lengthM, uint8_t* message); + +#endif \ No newline at end of file diff --git a/ESP32/note.cpp b/ESP32/note.cpp new file mode 100644 index 0000000..eb4ffb0 --- /dev/null +++ b/ESP32/note.cpp @@ -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; +} \ No newline at end of file diff --git a/ESP32/note.h b/ESP32/note.h new file mode 100644 index 0000000..cd406da --- /dev/null +++ b/ESP32/note.h @@ -0,0 +1,52 @@ +#ifndef NOTE_H +#define NOTE_H + +#include +#include +#include "settings.h" + +class Note +{ +private: + typedef std::vector 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 \ No newline at end of file diff --git a/ESP32/serial.cpp b/ESP32/serial.cpp new file mode 100644 index 0000000..88cbed2 --- /dev/null +++ b/ESP32/serial.cpp @@ -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(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); +} \ No newline at end of file diff --git a/ESP32/serial.h b/ESP32/serial.h new file mode 100644 index 0000000..58e6d68 --- /dev/null +++ b/ESP32/serial.h @@ -0,0 +1,10 @@ +#ifndef SERIAL_H +#define SERIAL_H + +#include + +void checkForSerial(); +void sendMidiToProMicro(byte note, byte velocity); +void customSerialToProMicro(byte header, byte note, byte velocity); + +#endif \ No newline at end of file diff --git a/ESP32/settings.cpp b/ESP32/settings.cpp new file mode 100644 index 0000000..1972264 --- /dev/null +++ b/ESP32/settings.cpp @@ -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; +} + + diff --git a/ESP32/settings.h b/ESP32/settings.h new file mode 100644 index 0000000..fc27a3a --- /dev/null +++ b/ESP32/settings.h @@ -0,0 +1,60 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include + +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 \ No newline at end of file diff --git a/ESP32/sustain.cpp b/ESP32/sustain.cpp new file mode 100644 index 0000000..64f7339 --- /dev/null +++ b/ESP32/sustain.cpp @@ -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); + } + } +} \ No newline at end of file diff --git a/ESP32/sustain.h b/ESP32/sustain.h new file mode 100644 index 0000000..b9ee162 --- /dev/null +++ b/ESP32/sustain.h @@ -0,0 +1,33 @@ +#ifndef SUSTAIN_H +#define SUSTAIN_H + +#include +#include + +class Sustain +{ +private: + typedef std::vector 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 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b932024 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/Piano Project.sln b/Piano Project.sln new file mode 100644 index 0000000..b819f71 --- /dev/null +++ b/Piano Project.sln @@ -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 diff --git a/ProMicro/ProMicro.ino b/ProMicro/ProMicro.ino new file mode 100644 index 0000000..4fe9926 --- /dev/null +++ b/ProMicro/ProMicro.ino @@ -0,0 +1,30 @@ +#include +#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(); +} \ No newline at end of file diff --git a/ProMicro/ProMicro.vcxproj b/ProMicro/ProMicro.vcxproj new file mode 100644 index 0000000..4949ca4 --- /dev/null +++ b/ProMicro/ProMicro.vcxproj @@ -0,0 +1,133 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {62539448-1BC7-47A2-A21E-53DE25ACD53F} + ProMicro + ProMicro + + + + Application + true + MultiByte + + + + + Application + false + true + MultiByte + + + + + Application + true + MultiByte + + + + + Application + false + true + MultiByte + + + + + Application + false + true + MultiByte + v141 + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + $(ProjectDir)..\ProMicro;$(ProjectDir)..\..\..\..\Arduino\libraries\MIDIUSB-master\src;$(ProjectDir)..\..\..\..\Arduino\libraries\ShiftPWM-master;C:\Program Files (x86)\Arduino\libraries;$(ProjectDir)..\..\..\..\Arduino\libraries;C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries;C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino;C:\Program Files (x86)\Arduino\hardware\arduino\avr\variants\leonardo;C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include;C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\avr;C:\Program Files (x86)\Arduino\hardware\tools\avr\lib\gcc\avr\4.8.1\include;C:\Program Files (x86)\Arduino\hardware\tools\avr\lib\gcc\avr\4.9.2\include;C:\Program Files (x86)\Arduino\hardware\tools\avr\lib\gcc\avr\4.9.3\include;%(AdditionalIncludeDirectories) + $(ProjectDir)__vm\.ProMicro.vsarduino.h;%(ForcedIncludeFiles) + true + __AVR_atmega32u4__;__AVR_ATmega32U4__;__AVR_ATmega32u4__;F_CPU=16000000L;ARDUINO=10809;ARDUINO_AVR_LEONARDO;ARDUINO_ARCH_AVR;USB_VID=0x2341;USB_PID=0x8036;__cplusplus=201103L;_VMICRO_INTELLISENSE;%(PreprocessorDefinitions) + + + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + true + + + + + + + VisualMicroDebugger + + + + CppCode + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ProMicro/ProMicro.vcxproj.filters b/ProMicro/ProMicro.vcxproj.filters new file mode 100644 index 0000000..9810ef6 --- /dev/null +++ b/ProMicro/ProMicro.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/ProMicro/__vm/.ProMicro.vsarduino.h b/ProMicro/__vm/.ProMicro.vsarduino.h new file mode 100644 index 0000000..9b9c09b --- /dev/null +++ b/ProMicro/__vm/.ProMicro.vsarduino.h @@ -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 +//#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 diff --git a/ProMicro/__vm/Compile.vmps.xml b/ProMicro/__vm/Compile.vmps.xml new file mode 100644 index 0000000..4e912c8 --- /dev/null +++ b/ProMicro/__vm/Compile.vmps.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ProMicro/__vm/Configuration.Debug.vmps.xml b/ProMicro/__vm/Configuration.Debug.vmps.xml new file mode 100644 index 0000000..0ffe7f3 --- /dev/null +++ b/ProMicro/__vm/Configuration.Debug.vmps.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/ProMicro/__vm/Upload.vmps.xml b/ProMicro/__vm/Upload.vmps.xml new file mode 100644 index 0000000..4e912c8 --- /dev/null +++ b/ProMicro/__vm/Upload.vmps.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ProMicro/midi.cpp b/ProMicro/midi.cpp new file mode 100644 index 0000000..703d8f3 --- /dev/null +++ b/ProMicro/midi.cpp @@ -0,0 +1,70 @@ +#include +#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(header), + static_cast(byte1), + static_cast(byte2), + static_cast(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; + } +} \ No newline at end of file diff --git a/ProMicro/midi.h b/ProMicro/midi.h new file mode 100644 index 0000000..4cab924 --- /dev/null +++ b/ProMicro/midi.h @@ -0,0 +1,8 @@ +#ifndef MIDI_H +#define MIDI_H + +#include + +void checkForMidiUSB(); + +#endif \ No newline at end of file diff --git a/ProMicro/serial.cpp b/ProMicro/serial.cpp new file mode 100644 index 0000000..9e50722 --- /dev/null +++ b/ProMicro/serial.cpp @@ -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]); +} + diff --git a/ProMicro/serial.h b/ProMicro/serial.h new file mode 100644 index 0000000..26e1b87 --- /dev/null +++ b/ProMicro/serial.h @@ -0,0 +1,10 @@ +#ifndef SERIAL_H +#define SERIAL_H + +#include + +void checkForSerial(); +void sendSerialToMain(byte header, byte setting, byte value); +void sendSerialToUSB(String* message, int lengthOfMessage); + +#endif \ No newline at end of file diff --git a/ProMicro/shiftRegister.cpp b/ProMicro/shiftRegister.cpp new file mode 100644 index 0000000..74c8790 --- /dev/null +++ b/ProMicro/shiftRegister.cpp @@ -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 +#include +#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(100)); + velocity = round(velocity * (MAX_PWM - MIN_PWM) / static_cast(127) + MIN_PWM); +} + +void testRegisters() +{ + for(int led = 0; led < 88; led++) + { + ShiftPWM.SetOne(led, MAX_PWM); + delay(50); + ShiftPWM.SetOne(led, 0); + } +} + + diff --git a/ProMicro/shiftRegister.h b/ProMicro/shiftRegister.h new file mode 100644 index 0000000..1351335 --- /dev/null +++ b/ProMicro/shiftRegister.h @@ -0,0 +1,12 @@ +#ifndef SHIFT_REGISTER_H +#define SHIFT_REGISTER_H + +#include + +extern uint8_t pwmPercent; + +void intitializeRegisters(); +void activateNote(uint8_t note, uint8_t velocity); +void testRegisters(); + +#endif \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..406d9b3 --- /dev/null +++ b/README.md @@ -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)