├── .python-version ├── .envrc ├── .gitignore ├── .travis.yml ├── .gitmodules ├── platformio.ini ├── README.md ├── Makefile └── src └── MIDIsync.ino /.python-version: -------------------------------------------------------------------------------- 1 | 2.7.10 2 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | export PLATFORMIO_BUILD_FLAGS="-O3 -Wall -flto=3" 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-* 2 | .DS_Store 3 | .pioenvs 4 | .sconsign.dblite 5 | /*.o 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | sudo: false 5 | cache: 6 | pip: true 7 | directories: 8 | - "~/.platformio" 9 | env: 10 | - PLATFORMIO_CI_SRC=src PLATFORMIO_BUILD_FLAGS="-O3 -Wall -flto=3" 11 | install: 12 | - pip install -U setuptools pip 13 | - pip install -U platformio 14 | script: 15 | - platformio ci --board=leonardo --board=uno --lib="lib/Average" --lib="lib/Button" --lib="lib/Fsm" --lib="lib/LEDFader" --lib="lib/MIDI" --lib="lib/StackArray" --lib="lib/Streaming" --lib="lib/digitalWriteFast" 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/Average"] 2 | path = lib/Average 3 | url = https://github.com/MajenkoLibraries/Average.git 4 | [submodule "lib/Button"] 5 | path = lib/Button 6 | url = https://github.com/JChristensen/Button.git 7 | [submodule "lib/MIDI"] 8 | path = lib/MIDI 9 | url = https://github.com/FortySevenEffects/arduino_midi_library.git 10 | [submodule "lib/Streaming"] 11 | path = lib/Streaming 12 | url = https://github.com/scottdky/Streaming.git 13 | [submodule "lib/StackArray"] 14 | path = lib/StackArray 15 | url = https://github.com/oogre/StackArray.git 16 | [submodule "lib/LEDFader"] 17 | path = lib/LEDFader 18 | url = https://github.com/jgillick/arduino-LEDFader.git 19 | [submodule "lib/Fsm"] 20 | path = lib/Fsm 21 | url = https://github.com/jonblack/arduino-fsm 22 | [submodule "lib/digitalWriteFast"] 23 | path = lib/digitalWriteFast 24 | url = https://github.com/mpflaga/Arduino-digitalWriteFast.git 25 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # MIDIsync Configuration File 3 | # 4 | # http://docs.platformio.org/en/latest/projectconf.html 5 | # 6 | 7 | # http://docs.platformio.org/en/latest/projectconf.html#platformio 8 | [platformio] 9 | auto_update_libraries = Yes 10 | auto_update_platforms = Yes 11 | check_libraries_interval = 1 12 | check_platformio_interval = 1 13 | check_platforms_interval = 1 14 | enable_telemetry = No 15 | 16 | # http://docs.platformio.org/en/latest/platforms/atmelavr.html 17 | [env:autogen_uno] 18 | platform = atmelavr 19 | framework = arduino 20 | board = uno 21 | #targets = upload 22 | #upload_port = /dev/cu.usbmodemFD121 23 | 24 | # http://docs.platformio.org/en/latest/platforms/atmelavr.html 25 | [env:autogen_leonardo] 26 | platform = atmelavr 27 | framework = arduino 28 | board = leonardo 29 | #targets = upload 30 | #upload_port = /dev/cu.usbmodemFD121 31 | 32 | # # http://docs.platformio.org/en/latest/platforms/teensy.html 33 | # [env:autogen_teensy] 34 | # platform = teensy 35 | # framework = arduino 36 | # board = teensy31 37 | # #targets = upload 38 | # #upload_port = /dev/cu.usbmodemFD121 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Arduino "MIDIsync" MIDI and CV Master Clock 2 | = 3 | ɔ 2012-2017 Tom Hensel Hamburg, Germany - License at https://opensource.org/licenses/BSD-3-Clause 4 | 5 | At least running on 6 | - [Arduino Uno](http://arduino.cc/en/Main/arduinoBoardUno) [![Build Status: Arduino Uno, Leonardo](https://travis-ci.org/gretel/MIDIsync.svg)](https://travis-ci.org/gretel/MIDIsync) 7 | - [Teensy 3.1](https://www.pjrc.com/store/teensy31.html) [![Build Status: Teensy 3.1](https://travis-ci.org/gretel/MIDIsync.svg?branch=teensy31)](https://travis-ci.org/gretel/MIDIsync) 8 | 9 | Should be compatible with the MIDI-Shield from [Sparkfun](https://www.sparkfun.com/products/9595). 10 | 11 | Libraries are included as *git submodules*. To clone the project and it's submodules please do: 12 | 13 | ```shell 14 | $ git clone https://github.com/gretel/MIDIsync.git 15 | $ cd MIDIsync 16 | $ git submodule init 17 | $ git submodule update 18 | ``` 19 | 20 | Building and uploading can be done easily using [platformio](http://platformio.org). 21 | 22 | > Currently, `python 2.7.10` is required as well as `pip`. 23 | 24 | Please edit `platformio.ini` first to reflect your serial port. 25 | 26 | ```shell 27 | $ pip install -U pip setuptools 28 | $ pip install -U platformio 29 | $ cd MIDIsync 30 | $ platformio run 31 | ... 32 | ``` 33 | 34 | Working on schematics.. _Please assist if you have the time and skills!_ 35 | 36 | My devices running it (nicknamed "Hensel MODEL-01") have the following features: 37 | 38 | - High-precision Master Clock 39 | - MIDI Clock Output 40 | - CV/Gate Output (0V/5V) 41 | - Micro-adjustable Tempo (endless rotary knob, two speeds) 42 | - Tap-Tempo Function (dropout protection, auto-smoothing) 43 | - Control MIDI Equipment (start, continue, stop) 44 | - Settings can be saved in non-volatile memory 45 | - Jumbo Tri-Color LEDs for Tempo and Status display 46 | - MIDI-Thru (latency-free hardware circuit) 47 | - Tolerant power input (7-12V DC) and hardware power switch 48 | - Low-power design, runs on batteries and USB-power (adapter required) 49 | - Energy-efficient power supply included 50 | 51 | Please get in touch with us on *Gitter*: 52 | 53 | [![Join the chat at https://gitter.im/gretel/MIDIsync](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gretel/MIDIsync?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #_______________________________________________________________________________ 2 | # 3 | # edam's Arduino makefile 4 | #_______________________________________________________________________________ 5 | # version 0.6dev 6 | # 7 | # Copyright (C) 2011, 2012, 2013 Tim Marston . 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy 10 | # of this software and associated documentation files (the "Software"), to deal 11 | # in the Software without restriction, including without limitation the rights 12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | # copies of the Software, and to permit persons to whom the Software is 14 | # furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in 17 | # all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | #_______________________________________________________________________________ 28 | # 29 | # 30 | # This is a general purpose makefile for use with Arduino hardware and 31 | # software. It works with the arduino-1.0 and later software releases. It 32 | # should work GNU/Linux and OS X. To download the latest version of this 33 | # makefile visit the following website where you can also find documentation on 34 | # it's use. (The following text can only really be considered a reference.) 35 | # 36 | # http://ed.am/dev/make/arduino-mk 37 | # 38 | # This makefile can be used as a drop-in replacement for the Arduino IDE's 39 | # build system. To use it, just copy arduino.mk in to your project directory. 40 | # Or, you could save it somewhere (I keep mine at ~/src/arduino.mk) and create 41 | # a symlink to it in your project directory, named "Makefile". For example: 42 | # 43 | # $ ln -s ~/src/arduino.mk Makefile 44 | # 45 | # The Arduino software (version 1.0 or later) is required. On GNU/Linux you 46 | # can probably install the software from your package manager. If you are 47 | # using Debian (or a derivative), try `apt-get install arduino`. Otherwise, 48 | # you can download the Arduino software manually from http://arduino.cc/. It 49 | # is suggested that you install it at ~/opt/arduino (or /Applications on OS X) 50 | # if you are unsure. 51 | # 52 | # If you downloaded the Arduino software manually and unpacked it somewhere 53 | # other than ~/opt/arduino (or /Applications), you will need to set up the 54 | # ARDUINODIR environment variable to be the path where you unpacked it. (If 55 | # unset, ARDUINODIR defaults to some sensible places). You could set this in 56 | # your ~/.profile by adding something like this: 57 | # 58 | # export ARDUINODIR=~/somewhere/arduino-1.0 59 | # 60 | # For each project, you will also need to set BOARD to the type of Arduino 61 | # you're building for. Type `make boards` for a list of acceptable values. 62 | # For example: 63 | # 64 | # $ export BOARD=uno 65 | # $ make 66 | # 67 | # You may also need to set SERIALDEV if it is not detected correctly. 68 | # 69 | # The presence of a .ino (or .pde) file causes the arduino.mk to automatically 70 | # determine values for SOURCES, TARGET and LIBRARIES. Any .c, .cc and .cpp 71 | # files in the project directory (or any "util" or "utility" subdirectories) 72 | # are automatically included in the build and are scanned for Arduino libraries 73 | # that have been #included. Note, there can only be one .ino (or .pde) file in 74 | # a project directory and if you want to be compatible with the Arduino IDE, it 75 | # should be called the same as the directory name. 76 | # 77 | # Alternatively, if you want to manually specify build variables, create a 78 | # Makefile that defines SOURCES and LIBRARIES and then includes arduino.mk. 79 | # (There is no need to define TARGET). You can also specify the BOARD here, if 80 | # the project has a specific one. Here is an example Makefile: 81 | # 82 | # SOURCES := main.cc other.cc 83 | # LIBRARIES := EEPROM 84 | # BOARD := pro5v 85 | # include ~/src/arduino.mk 86 | # 87 | # Here is a complete list of configuration parameters: 88 | # 89 | # ARDUINODIR The path where the Arduino software is installed on your system. 90 | # 91 | # ARDUINOCONST The Arduino software version, as an integer, used to define the 92 | # ARDUINO version constant. This defaults to 100 if undefined. 93 | # 94 | # AVRDUDECONF The avrdude.conf to use. If undefined, this defaults to a guess 95 | # based on where avrdude is. If set empty, no avrdude.conf is 96 | # passed to avrdude (so the system default is used). 97 | # 98 | # AVRDUDEFLAGS Specify any additional flags for avrdude. The usual flags, 99 | # required to build the project, will be appended to this. 100 | # 101 | # AVRTOOLSPATH A space-separated list of directories that is searched in order 102 | # when looking for the avr build tools. This defaults to PATH, 103 | # followed by subdirectories in ARDUINODIR. 104 | # 105 | # BOARD Specify a target board type. Run `make boards` to see available 106 | # board types. 107 | # 108 | # CPPFLAGS Specify any additional flags for the compiler. The usual flags, 109 | # required to build the project, will be appended to this. 110 | # 111 | # LINKFLAGS Specify any additional flags for the linker. The usual flags, 112 | # required to build the project, will be appended to this. 113 | # 114 | # LIBRARIES A list of Arduino libraries to build and include. This is set 115 | # automatically if a .ino (or .pde) is found. 116 | # 117 | # LIBRARYPATH A space-separated list of directories that is searched in order 118 | # when looking for Arduino libraries. This defaults to "libs", 119 | # "libraries" (in the project directory), then your sketchbook 120 | # "libraries" directory, then the Arduino libraries directory. 121 | # 122 | # SERIALDEV The POSIX device name of the serial device that is the Arduino. 123 | # If unspecified, an attempt is made to guess the name of a 124 | # connected Arduino's serial device, which may work in some cases. 125 | # 126 | # SOURCES A list of all source files of whatever language. The language 127 | # type is determined by the file extension. This is set 128 | # automatically if a .ino (or .pde) is found. 129 | # 130 | # TARGET The name of the target file. This is set automatically if a 131 | # .ino (or .pde) is found, but it is not necessary to set it 132 | # otherwise. 133 | # 134 | # This makefile also defines the following goals for use on the command line 135 | # when you run make: 136 | # 137 | # all This is the default if no goal is specified. It builds the 138 | # target. 139 | # 140 | # target Builds the target. 141 | # 142 | # upload Uploads the target (building it, as necessary) to an attached 143 | # Arduino. 144 | # 145 | # clean Deletes files created during the build. 146 | # 147 | # boards Display a list of available board names, so that you can set the 148 | # BOARD environment variable appropriately. 149 | # 150 | # monitor Start `screen` on the serial device. This is meant to be an 151 | # equivalent to the Arduino serial monitor. 152 | # 153 | # size Displays size information about the built target. 154 | # 155 | # bootloader Burns the bootloader for your board to it. 156 | # 157 | # Builds the specified file, either an object file or the target, 158 | # from those that that would be built for the project. 159 | #_______________________________________________________________________________ 160 | # 161 | 162 | # default arduino software directory, check software exists 163 | ifndef ARDUINODIR 164 | ARDUINODIR := $(firstword $(wildcard ~/opt/arduino /usr/share/arduino \ 165 | /Applications/Arduino.app/Contents/Resources/Java \ 166 | $(HOME)/Applications/Arduino.app/Contents/Resources/Java)) 167 | endif 168 | ifeq "$(wildcard $(ARDUINODIR)/hardware/arduino/avr/boards.txt)" "" 169 | $(error ARDUINODIR is not set correctly; arduino software not found) 170 | endif 171 | 172 | # default arduino version 173 | ARDUINOCONST ?= 100 174 | 175 | # default path for avr tools 176 | AVRTOOLSPATH ?= $(subst :, , $(PATH)) $(ARDUINODIR)/hardware/tools \ 177 | $(ARDUINODIR)/hardware/tools/avr/bin 178 | 179 | # default path to find libraries 180 | LIBRARYPATH ?= libraries libs $(SKETCHBOOKDIR)/libraries $(ARDUINODIR)/libraries 181 | 182 | # default serial device to a poor guess (something that might be an arduino) 183 | SERIALDEVGUESS := 0 184 | ifndef SERIALDEV 185 | SERIALDEV := $(firstword $(wildcard \ 186 | /dev/ttyACM? /dev/ttyUSB? /dev/tty.usbserial* /dev/tty.usbmodem*)) 187 | SERIALDEVGUESS := 1 188 | endif 189 | 190 | # no board? 191 | ifndef BOARD 192 | ifneq "$(MAKECMDGOALS)" "boards" 193 | ifneq "$(MAKECMDGOALS)" "clean" 194 | $(error BOARD is unset. Type 'make boards' to see possible values) 195 | endif 196 | endif 197 | endif 198 | 199 | # obtain board parameters from the arduino boards.txt file 200 | BOARDSFILE := $(ARDUINODIR)/hardware/arduino/avr/boards.txt 201 | readboardsparam = $(shell sed -ne "s/$(BOARD).$(1)=\(.*\)/\1/p" $(BOARDSFILE)) 202 | BOARD_BUILD_MCU := $(call readboardsparam,build.mcu) 203 | BOARD_BUILD_FCPU := $(call readboardsparam,build.f_cpu) 204 | BOARD_BUILD_VARIANT := $(call readboardsparam,build.variant) 205 | BOARD_UPLOAD_SPEED := $(call readboardsparam,upload.speed) 206 | BOARD_UPLOAD_PROTOCOL := $(call readboardsparam,upload.protocol) 207 | BOARD_USB_VID := $(call readboardsparam,build.vid) 208 | BOARD_USB_PID := $(call readboardsparam,build.pid) 209 | BOARD_BOOTLOADER_UNLOCK := $(call readboardsparam,bootloader.unlock_bits) 210 | BOARD_BOOTLOADER_LOCK := $(call readboardsparam,bootloader.lock_bits) 211 | BOARD_BOOTLOADER_LFUSES := $(call readboardsparam,bootloader.low_fuses) 212 | BOARD_BOOTLOADER_HFUSES := $(call readboardsparam,bootloader.high_fuses) 213 | BOARD_BOOTLOADER_EFUSES := $(call readboardsparam,bootloader.extended_fuses) 214 | BOARD_BOOTLOADER_PATH := $(call readboardsparam,bootloader.path) 215 | BOARD_BOOTLOADER_FILE := $(call readboardsparam,bootloader.file) 216 | 217 | # obtain preferences from the IDE's preferences.txt 218 | PREFERENCESFILE := $(firstword $(wildcard \ 219 | $(HOME)/.arduino/preferences.txt $(HOME)/Library/Arduino/preferences.txt)) 220 | ifneq "$(PREFERENCESFILE)" "" 221 | readpreferencesparam = $(shell sed -ne "s/$(1)=\(.*\)/\1/p" $(PREFERENCESFILE)) 222 | SKETCHBOOKDIR := $(call readpreferencesparam,sketchbook.path) 223 | endif 224 | 225 | # invalid board? 226 | ifeq "$(BOARD_BUILD_MCU)" "" 227 | ifneq "$(MAKECMDGOALS)" "boards" 228 | ifneq "$(MAKECMDGOALS)" "clean" 229 | $(error BOARD is invalid. Type 'make boards' to see possible values) 230 | endif 231 | endif 232 | endif 233 | 234 | # auto mode? 235 | INOFILE := $(wildcard *.ino *.pde) 236 | ifdef INOFILE 237 | ifneq "$(words $(INOFILE))" "1" 238 | $(error There is more than one .pde or .ino file in this directory!) 239 | endif 240 | 241 | # automatically determine sources and targeet 242 | TARGET := $(basename $(INOFILE)) 243 | SOURCES := $(INOFILE) \ 244 | $(wildcard *.c *.cc *.cpp *.C) \ 245 | $(wildcard $(addprefix util/, *.c *.cc *.cpp *.C)) \ 246 | $(wildcard $(addprefix utility/, *.c *.cc *.cpp *.C)) 247 | 248 | # automatically determine included libraries 249 | LIBRARIES := $(filter $(notdir $(wildcard $(addsuffix /*, $(LIBRARYPATH)))), \ 250 | $(shell sed -ne "s/^ *\# *include *[<\"]\(.*\)\.h[>\"]/\1/p" $(SOURCES))) 251 | 252 | endif 253 | 254 | # software 255 | findsoftware = $(firstword $(wildcard $(addsuffix /$(1), $(AVRTOOLSPATH)))) 256 | CC := $(call findsoftware,avr-gcc) 257 | CXX := $(call findsoftware,avr-g++) 258 | LD := $(call findsoftware,avr-ld) 259 | AR := $(call findsoftware,avr-ar) 260 | OBJCOPY := $(call findsoftware,avr-objcopy) 261 | AVRDUDE := $(call findsoftware,avrdude) 262 | AVRSIZE := $(call findsoftware,avr-size) 263 | 264 | # directories 265 | ARDUINOCOREDIR := $(ARDUINODIR)/hardware/arduino/avr/cores/arduino 266 | LIBRARYDIRS := $(foreach lib, $(LIBRARIES), \ 267 | $(firstword $(wildcard $(addsuffix /$(lib), $(LIBRARYPATH))))) 268 | LIBRARYDIRS += $(addsuffix /utility, $(LIBRARYDIRS)) 269 | 270 | # files 271 | TARGET := $(if $(TARGET),$(TARGET),a.out) 272 | OBJECTS := $(addsuffix .o, $(basename $(SOURCES))) 273 | DEPFILES := $(patsubst %, .dep/%.dep, $(SOURCES)) 274 | ARDUINOLIB := .lib/arduino.a 275 | ARDUINOLIBOBJS := $(foreach dir, $(ARDUINOCOREDIR) $(LIBRARYDIRS), \ 276 | $(patsubst %, .lib/%.o, $(wildcard $(addprefix $(dir)/, *.c *.cpp)))) 277 | BOOTLOADERHEX := $(addprefix \ 278 | $(ARDUINODIR)/hardware/arduino/bootloaders/$(BOARD_BOOTLOADER_PATH)/, \ 279 | $(BOARD_BOOTLOADER_FILE)) 280 | 281 | # avrdude confifuration 282 | ifeq "$(AVRDUDECONF)" "" 283 | ifeq "$(AVRDUDE)" "$(ARDUINODIR)/hardware/tools/avr/bin/avrdude" 284 | AVRDUDECONF := $(ARDUINODIR)/hardware/tools/avr/etc/avrdude.conf 285 | else 286 | AVRDUDECONF := $(wildcard $(AVRDUDE).conf) 287 | endif 288 | endif 289 | 290 | # flags 291 | # http://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung 292 | # http://www.tty1.net/blog/2008-04-29-avr-gcc-optimisations_en.html 293 | CPPFLAGS := -Os -Wall -Wundef -Wl,--relax -Wl,--gc-sections 294 | CPPFLAGS += --param inline-call-cost=2 -finline-limit=3 -fno-inline-small-functions 295 | CPPFLAGS += -fpack-struct -fshort-enums 296 | CPPFLAGS += -ffunction-sections -fdata-sections 297 | CPPFLAGS += -fno-tree-scev-cprop -fno-tree-ter -fno-caller-saves -fno-exceptions 298 | CPPFLAGS += -fno-move-loop-invariants -fno-tree-loop-optimize -fno-jump-tables 299 | CPPFLAGS += -funsigned-char -funsigned-bitfields 300 | CPPFLAGS += -ffreestanding -mcall-prologues 301 | CPPFLAGS += -mmcu=$(BOARD_BUILD_MCU) 302 | CPPFLAGS += -DF_CPU=$(BOARD_BUILD_FCPU) -DARDUINO=$(ARDUINOCONST) 303 | CPPFLAGS += -DUSB_VID=$(BOARD_USB_VID) -DUSB_PID=$(BOARD_USB_PID) 304 | CPPFLAGS += -I. -Iutil -Iutility -I $(ARDUINOCOREDIR) 305 | CPPFLAGS += -I $(ARDUINODIR)/hardware/arduino/avr/variants/$(BOARD_BUILD_VARIANT)/ 306 | CPPFLAGS += $(addprefix -I , $(LIBRARYDIRS)) 307 | CPPDEPFLAGS = -MMD -MP -MF .dep/$<.dep 308 | CPPINOFLAGS := -x c++ -include $(ARDUINOCOREDIR)/Arduino.h 309 | AVRDUDEFLAGS := $(addprefix -C , $(AVRDUDECONF)) -DV 310 | AVRDUDEFLAGS += -p $(BOARD_BUILD_MCU) -P $(SERIALDEV) 311 | AVRDUDEFLAGS += -c $(BOARD_UPLOAD_PROTOCOL) -b $(BOARD_UPLOAD_SPEED) 312 | LINKFLAGS := -Os -Wl,--gc-sections -Wl,--relax -mmcu=$(BOARD_BUILD_MCU) 313 | LINKFLAGS += -ffunction-sections 314 | 315 | # figure out which arg to use with stty (for OS X, GNU and busybox stty) 316 | #STTYFARG := $(shell stty --help 2>&1 | \ 317 | # grep -q 'illegal option' && echo -f || echo -F) 318 | STTYFARG := -f 319 | 320 | # include dependencies 321 | ifneq "$(MAKECMDGOALS)" "clean" 322 | -include $(DEPFILES) 323 | endif 324 | 325 | # default rule 326 | .DEFAULT_GOAL := all 327 | 328 | #_______________________________________________________________________________ 329 | # RULES 330 | 331 | .PHONY: all target upload clean boards monitor size bootloader 332 | 333 | all: target 334 | 335 | target: $(TARGET).hex 336 | 337 | upload: target 338 | @echo "\nUploading to board..." 339 | @test -n "$(SERIALDEV)" || { \ 340 | echo "error: SERIALDEV could not be determined automatically." >&2; \ 341 | exit 1; } 342 | @test 0 -eq $(SERIALDEVGUESS) || { \ 343 | echo "*GUESSING* at serial device:" $(SERIALDEV); \ 344 | echo; } 345 | ifeq "$(BOARD_BOOTLOADER_PATH)" "caterina" 346 | stty $(STTYFARG) $(SERIALDEV) speed 1200 347 | sleep 1 348 | else 349 | stty $(STTYFARG) $(SERIALDEV) hupcl 2>/dev/null 350 | endif 351 | $(AVRDUDE) $(AVRDUDEFLAGS) -U flash:w:$(TARGET).hex:i 352 | 353 | clean: 354 | rm -f $(OBJECTS) 355 | rm -f $(TARGET).elf $(TARGET).hex $(ARDUINOLIB) *~ 356 | rm -rf .lib .dep 357 | 358 | boards: 359 | @echo "Available values for BOARD:" 360 | @sed -nEe '/^#/d; /^[^.]+\.name=/p' $(BOARDSFILE) | \ 361 | sed -Ee 's/([^.]+)\.name=(.*)/\1 \2/' \ 362 | -e 's/(.{12}) *(.*)/\1 \2/' 363 | 364 | monitor: 365 | @test -n "$(SERIALDEV)" || { \ 366 | echo "error: SERIALDEV could not be determined automatically." >&2; \ 367 | exit 1; } 368 | @test -n `which screen` || { \ 369 | echo "error: can't find GNU screen, you might need to install it." >&2 \ 370 | exit 1; } 371 | @test 0 -eq $(SERIALDEVGUESS) || { \ 372 | echo "*GUESSING* at serial device:" $(SERIALDEV); \ 373 | echo; } 374 | screen $(SERIALDEV) 375 | 376 | size: $(TARGET).elf 377 | echo && $(AVRSIZE) --format=avr --mcu=$(BOARD_BUILD_MCU) $(TARGET).elf 378 | 379 | bootloader: 380 | @echo "Burning bootloader to board..." 381 | @test -n "$(SERIALDEV)" || { \ 382 | echo "error: SERIALDEV could not be determined automatically." >&2; \ 383 | exit 1; } 384 | @test 0 -eq $(SERIALDEVGUESS) || { \ 385 | echo "*GUESSING* at serial device:" $(SERIALDEV); \ 386 | echo; } 387 | stty $(STTYFARG) $(SERIALDEV) hupcl 2>/dev/null 388 | $(AVRDUDE) $(AVRDUDEFLAGS) -U lock:w:$(BOARD_BOOTLOADER_UNLOCK):m 389 | $(AVRDUDE) $(AVRDUDEFLAGS) -eU lfuse:w:$(BOARD_BOOTLOADER_LFUSES):m 390 | $(AVRDUDE) $(AVRDUDEFLAGS) -U hfuse:w:$(BOARD_BOOTLOADER_HFUSES):m 391 | ifneq "$(BOARD_BOOTLOADER_EFUSES)" "" 392 | $(AVRDUDE) $(AVRDUDEFLAGS) -U efuse:w:$(BOARD_BOOTLOADER_EFUSES):m 393 | endif 394 | ifneq "$(BOOTLOADERHEX)" "" 395 | $(AVRDUDE) $(AVRDUDEFLAGS) -U flash:w:$(BOOTLOADERHEX):i 396 | endif 397 | $(AVRDUDE) $(AVRDUDEFLAGS) -U lock:w:$(BOARD_BOOTLOADER_LOCK):m 398 | 399 | # building the target 400 | 401 | $(TARGET).hex: $(TARGET).elf 402 | $(OBJCOPY) -O ihex -R .eeprom $< $@ 403 | 404 | .INTERMEDIATE: $(TARGET).elf 405 | 406 | $(TARGET).elf: $(ARDUINOLIB) $(OBJECTS) 407 | $(CC) $(LINKFLAGS) $(OBJECTS) $(ARDUINOLIB) -lm -o $@ 408 | 409 | %.o: %.c 410 | mkdir -p .dep/$(dir $<) 411 | $(COMPILE.c) $(CPPDEPFLAGS) -o $@ $< 412 | 413 | %.o: %.cpp 414 | mkdir -p .dep/$(dir $<) 415 | $(COMPILE.cpp) $(CPPDEPFLAGS) -o $@ $< 416 | 417 | %.o: %.cc 418 | mkdir -p .dep/$(dir $<) 419 | $(COMPILE.cpp) $(CPPDEPFLAGS) -o $@ $< 420 | 421 | %.o: %.C 422 | mkdir -p .dep/$(dir $<) 423 | $(COMPILE.cpp) $(CPPDEPFLAGS) -o $@ $< 424 | 425 | %.o: %.ino 426 | mkdir -p .dep/$(dir $<) 427 | $(COMPILE.cpp) $(CPPDEPFLAGS) -o $@ $(CPPINOFLAGS) $< 428 | 429 | %.o: %.pde 430 | mkdir -p .dep/$(dir $<) 431 | $(COMPILE.cpp) $(CPPDEPFLAGS) -o $@ $(CPPINOFLAGS) $< 432 | 433 | # building the arduino library 434 | 435 | $(ARDUINOLIB): $(ARDUINOLIBOBJS) 436 | $(AR) rcs $@ $? 437 | 438 | .lib/%.c.o: %.c 439 | mkdir -p $(dir $@) 440 | $(COMPILE.c) -o $@ $< 441 | 442 | .lib/%.cpp.o: %.cpp 443 | mkdir -p $(dir $@) 444 | $(COMPILE.cpp) -o $@ $< 445 | 446 | .lib/%.cc.o: %.cc 447 | mkdir -p $(dir $@) 448 | $(COMPILE.cpp) -o $@ $< 449 | 450 | .lib/%.C.o: %.C 451 | mkdir -p $(dir $@) 452 | $(COMPILE.cpp) -o $@ $< 453 | -------------------------------------------------------------------------------- /src/MIDIsync.ino: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------------------------------------------- 2 | "Hensel CLOCK-01" 3 | Arduino "MIDIsync" - MIDI Master Clock Generator 4 | https://github.com/gretel/MIDIsync 5 | ɔ 2012-2015 Tom Hensel Hamburg, Germany 6 | CC BY-SA 3.0 http://creativecommons.org/licenses/by-sa/3.0/ 7 | --------------------------------------------------------------------------------------------------*/ 8 | 9 | // id 10 | #define ID "HENSEL_CLOCK-01" 11 | #define VERSION 0411201501 12 | #define DEBUG 0 13 | 14 | // "hardware abstraction layer" :) 15 | #define BOARD_LED 13 16 | #define BTN_STATE 4 17 | #define BTN_TEMPO 2 18 | #define ENC_A 14 19 | #define ENC_B 15 20 | #define ENC_PORT PINC 21 | #define GATE_PIN 8 22 | #define LED_A 5 23 | #define LED_B 6 24 | #define LED_C 9 25 | #define LED_D 10 26 | 27 | // button debounce time 28 | #define BTN_DBNC 30 29 | // button hold threshold 30 | #define BTN_HOLD_THRESH 256 31 | 32 | // each encoder click corresponds to a value 33 | #define ENCODER_VAL 12 34 | // when pushed down (shif) 35 | #define ENCODER_VAL_SHIFT 96 36 | 37 | // number of data points 38 | #define TAP_FILTER_POINTS 16 39 | // time window to count beats as subsequent 40 | #define TAP_WINDOW 1944000 41 | 42 | // min/max cycle duration 43 | #define CYCLE_TIME_MIN 6750 44 | #define CYCLE_TIME_MAX 129600 45 | // default cycle time on configuration reset 46 | #define DEFAULT_CYCLE_TIME 24300 47 | 48 | // clocks per quarter note 49 | // TODO: implement different clocks 50 | #define DEFAULT_CPQN 24 51 | 52 | #define MIDI_PORT Serial 53 | #define MIDI_BPS 31250 54 | 55 | #if DEBUG 56 | #define DEBUG_RX 11 57 | #define DEBUG_TX 12 58 | #define DEBUG_SPEED 38400 59 | #define DEBUG_INTERVAL 500000 60 | #endif 61 | 62 | // includes 63 | #include 64 | #include 65 | #include 66 | #include 67 | 68 | #if DEBUG 69 | #include 70 | #include "Streaming.h" 71 | #endif 72 | #include "Average.h" 73 | #include "Button.h" 74 | #include "digitalWriteFast.h" 75 | #include "Fsm.h" 76 | #include "LEDFader.h" 77 | #include "Curve.h" 78 | #include "MIDI.h" 79 | #include "StackArray.h" 80 | 81 | // MIDI library settings 82 | struct MidiSettings : public midi::DefaultSettings 83 | { 84 | static const bool UseRunningStatus = false; 85 | static const bool Use1ByteParsing = true; 86 | static const unsigned SysExMaxSize = 2; 87 | 88 | }; 89 | // instantiate interface 90 | MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial, midi_if, MidiSettings); 91 | 92 | // use hardware registers for frequent changing values 93 | register uint8_t counter asm("r2"); 94 | register uint8_t cpqn asm("r3"); 95 | register uint32_t cycle asm("r4"); 96 | 97 | // the state the machine is currently in 98 | uint8_t state; 99 | // the state the machine will change to during the next cycle 100 | uint8_t nextState; 101 | 102 | // definiton of states 103 | #define STATE_HALT 0 104 | #define STATE_STOP 1 105 | #define STATE_START 2 106 | #define STATE_CONTINUE 3 107 | // declaration of states 108 | State state_halt(s_halt_enter, &s_halt_exit); 109 | State state_stop(s_stop_enter, NULL); 110 | State state_start(s_start_enter, NULL); 111 | State state_continue(s_continue_enter, NULL); 112 | 113 | // initialize state machine 114 | Fsm machine(&state_halt); 115 | 116 | // this data is going to saved to and loaded from the EEPROM 117 | struct config_t 118 | { 119 | uint32_t version; 120 | uint32_t cycle; 121 | uint8_t state; 122 | uint8_t cpqn; 123 | } config; 124 | 125 | // holding a key will give the press of another a different meaning 126 | uint8_t inputContext; 127 | enum input_t { BUTTON_CLICK, BUTTON_TRANSPORT_HOLD }; 128 | 129 | // one button to tap tempo (encoder press) 130 | Button tempoButton = Button(BTN_TEMPO, false, false, BTN_DBNC); 131 | // and one to control the transport 132 | Button transportButton = Button(BTN_STATE, false, false, BTN_DBNC); 133 | // smooth changing the tempo on taps 134 | Average clockFilter(TAP_FILTER_POINTS); 135 | 136 | // total amount of LEDs 137 | // two LEDs with two emitters each (bicolor) 138 | #define LED_NUM 4 139 | // give a name to each 140 | enum leds_t { LED_LEFT_A, LED_LEFT_B, LED_RIGHT_A, LED_RIGHT_B }; 141 | // definiton of LED interfaces 142 | LEDFader leds[LED_NUM] = { 143 | LEDFader(LED_A), 144 | LEDFader(LED_B), 145 | LEDFader(LED_C), 146 | LEDFader(LED_D) 147 | }; 148 | 149 | // an event of light (control LEDs) 150 | struct led_event 151 | { 152 | uint8_t id; 153 | uint8_t val; 154 | uint16_t dur; 155 | }; 156 | // priority queueing for light emission events (whoa) 157 | enum stack_action_t { ACT_PUSH, ACT_UNSHIFT, ACT_REPLACE }; 158 | // events will be stacked and processed when applicable 159 | StackArray stack; 160 | 161 | #if DEBUG 162 | // initialize secondary serial port for debbuging output 163 | // WARNING: SoftwareSerial adds lots of latency - timming will jitter! 164 | SoftwareSerial debugSerial(DEBUG_RX, DEBUG_TX); 165 | uint32_t debugTime; 166 | uint32_t loopCycle; 167 | 168 | void printDiag() 169 | { 170 | const uint16_t duration = micros() - loopCycle; 171 | debugSerial 172 | << " COUNT: " << micros() 173 | << " LOOP: " << "\033[4m" << duration << "\033[0m" 174 | << " STATE: " << "\033[7m" << state << "\033[0m" 175 | << " COUNTER: " << counter 176 | << " CYCL_DUR: " << cycle 177 | << " INPUT_STATE: " << inputContext 178 | << endl; 179 | } 180 | #endif 181 | 182 | void reset() 183 | { 184 | // use watchdog timer for reset 185 | wdt_disable(); 186 | wdt_enable(WDTO_15MS); 187 | while (1) {} 188 | } 189 | 190 | void alert() 191 | { 192 | // light up all LEDs 193 | for (uint8_t i = 0; i < LED_NUM; i++) { 194 | LEDFader *led = &leds[i]; 195 | led->set_value(255); 196 | led->update(); 197 | } 198 | } 199 | 200 | void awaitRelease() 201 | { 202 | do 203 | { 204 | // wait for buttons to be released so we can finally get on 205 | delay(50); 206 | transportButton.read(); 207 | tempoButton.read(); 208 | } 209 | while (transportButton.isPressed() || tempoButton.isPressed()); 210 | } 211 | 212 | void writeEprom() 213 | { 214 | #if DEBUG 215 | debugSerial << "WRITE_EPROM" << endl; 216 | #endif 217 | digitalWriteFast(BOARD_LED, HIGH); 218 | // actually write 219 | eeprom_write_block((const void*)&config, (void*)0, sizeof(config)); 220 | delay(50); // paranoia 221 | digitalWriteFast(BOARD_LED, LOW); 222 | } 223 | 224 | void resetConfig() 225 | { 226 | #if DEBUG 227 | debugSerial << "RESET_CONFIG" << endl; 228 | #endif 229 | // initialize configuration structure 230 | config.version = (uint32_t)VERSION; 231 | config.cpqn = (uint8_t)DEFAULT_CPQN; 232 | config.cycle = (uint32_t)DEFAULT_CYCLE_TIME; 233 | config.state = (uint8_t)STATE_STOP; 234 | writeEprom(); 235 | } 236 | 237 | void saveConfig() 238 | { 239 | config.version = VERSION; 240 | config.cpqn = cpqn; 241 | config.cycle = cycle; 242 | // enforce valid state so we won't hang on startup 243 | switch (state) 244 | { 245 | case STATE_START: 246 | case STATE_CONTINUE: 247 | config.state = STATE_START; 248 | break; 249 | default: 250 | config.state = STATE_STOP; 251 | break; 252 | } 253 | #if DEBUG 254 | debugSerial << "SAVE_CONFIG:VERSION => " << config.version << " AUTOSTART => " << config.state << " CPQN =>" << config.cpqn << " CYCLE => " << config.cycle << endl; 255 | #endif 256 | writeEprom(); 257 | } 258 | 259 | void loadConfig() 260 | { 261 | // read configuration 262 | eeprom_read_block((void*)&config, (void*)0, sizeof(config)); 263 | #if DEBUG 264 | debugSerial << "LOAD_CONFIG:VERSION => " << config.version << " AUTOSTART => " << config.state << " CPQN =>" << config.cpqn << " CYCLE => " << config.cycle << endl; 265 | #endif 266 | } 267 | 268 | /* http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino 269 | returns change in encoder state (-1,0,1) */ 270 | int8_t readEncoder() 271 | { 272 | static int8_t enc_states[] = 273 | { 274 | 0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0 275 | }; 276 | static uint8_t old_AB; 277 | old_AB <<= 2; // remember previous state 278 | old_AB |= (ENC_PORT & 0x03); // add current state 279 | return (enc_states[(old_AB & 0x0f)]); 280 | } 281 | 282 | void setState(uint8_t s) 283 | { 284 | #if DEBUG 285 | debugSerial << "SET_STATE:" << s << endl; 286 | #endif 287 | nextState = s; 288 | } 289 | 290 | void setCycle(uint32_t t) 291 | { 292 | t = constrain(t, CYCLE_TIME_MIN, CYCLE_TIME_MAX); 293 | #if DEBUG 294 | debugSerial << "SET_CYCLE:" << cycle << "=>" << t << endl; 295 | #endif 296 | cycle = t; 297 | } 298 | 299 | void queueLight(uint8_t a, uint8_t id, uint8_t val, uint16_t dur) 300 | { 301 | // compose event 302 | led_event e; 303 | e.id = id; 304 | e.val = val; 305 | e.dur = dur; 306 | switch(a) 307 | { 308 | case ACT_PUSH: 309 | // add to stack (ordered queue) 310 | stack.push(e); 311 | break; 312 | case ACT_UNSHIFT: 313 | // add to top of stack (priority) 314 | stack.unshift(e); 315 | break; 316 | case ACT_REPLACE: 317 | // clear stack and add (exclusive) 318 | while (!stack.isEmpty()) stack.pop(); 319 | stack.push(e); 320 | break; 321 | } 322 | } 323 | 324 | void s_halt_enter() 325 | { 326 | midi_if.sendRealTime(midi::Stop); 327 | queueLight(ACT_REPLACE, LED_LEFT_A, 0, 100); 328 | queueLight(ACT_PUSH, LED_LEFT_B, 0, 100); 329 | queueLight(ACT_PUSH, LED_RIGHT_A, 220, 150); 330 | queueLight(ACT_PUSH, LED_RIGHT_B, 0, 100); 331 | } 332 | 333 | void s_halt_exit() 334 | { 335 | midi_if.sendRealTime(midi::TuneRequest); 336 | delay(50); 337 | } 338 | 339 | void s_stop_enter() 340 | { 341 | midi_if.sendRealTime(midi::Stop); 342 | queueLight(ACT_REPLACE, LED_RIGHT_A, 255, 150); 343 | queueLight(ACT_PUSH, LED_RIGHT_B, 0, 100); 344 | } 345 | 346 | void s_start_enter() 347 | { 348 | midi_if.sendRealTime(midi::Start); 349 | queueLight(ACT_REPLACE, LED_RIGHT_A, 0, 100); 350 | queueLight(ACT_PUSH, LED_RIGHT_B, 255, 150); 351 | } 352 | 353 | void s_continue_enter() 354 | { 355 | midi_if.sendRealTime(midi::Continue); 356 | queueLight(ACT_REPLACE, LED_RIGHT_A, 200, 100); 357 | queueLight(ACT_PUSH, LED_RIGHT_B, 255, 250); 358 | } 359 | 360 | extern void __attribute__((noreturn)) 361 | setup() 362 | { 363 | // be paranoid in regards to latency 364 | power_spi_disable(); 365 | power_twi_disable(); 366 | power_adc_disable(); 367 | 368 | // begin of setup() - enable board LED 369 | pinMode(BOARD_LED, OUTPUT); 370 | digitalWriteFast(BOARD_LED, HIGH); 371 | 372 | // configure input/output pins 373 | pinMode(ENC_A, INPUT); 374 | digitalWriteFast(ENC_A, HIGH); 375 | pinMode(ENC_B, INPUT); 376 | digitalWriteFast(ENC_B, HIGH); 377 | pinMode(GATE_PIN, OUTPUT); 378 | 379 | #if DEBUG 380 | // setup secondary serial 381 | pinMode(DEBUG_RX, INPUT); 382 | pinMode(DEBUG_TX, OUTPUT); 383 | debugSerial.begin(DEBUG_SPEED); 384 | // identifcation please 385 | debugSerial << ID << ":" << VERSION << endl; 386 | #endif 387 | 388 | // setup each LED 389 | for (uint8_t i = 0; i < LED_NUM; i++) { 390 | LEDFader *led = &leds[i]; 391 | // smooth fading 392 | led->set_curve(&Curve::exponential); 393 | }; 394 | 395 | // setup state transitions 396 | machine.add_transition(&state_start, &state_stop, STATE_STOP, NULL); 397 | machine.add_transition(&state_continue, &state_stop, STATE_STOP, NULL); 398 | machine.add_transition(&state_halt, &state_stop, STATE_STOP, NULL); 399 | machine.add_transition(&state_start, &state_halt, STATE_HALT, NULL); 400 | machine.add_transition(&state_continue, &state_halt, STATE_HALT, NULL); 401 | machine.add_transition(&state_stop, &state_start, STATE_START, NULL); 402 | machine.add_transition(&state_stop, &state_continue, STATE_CONTINUE, NULL); 403 | 404 | // load configuration from non-volatile memory (5th dimensional eternity like in interstellargh) 405 | loadConfig(); 406 | // check if data has been written and loaded using the same firmware version 407 | // or if the reset combo is being pressed on startup 408 | if ((config.version != (uint32_t)VERSION) || (transportButton.isPressed() && tempoButton.isPressed())) 409 | { 410 | alert(); 411 | // actually reset 412 | resetConfig(); 413 | awaitRelease(); 414 | reset(); 415 | } 416 | 417 | // MIDI you, MIDI me! 418 | midi_if.begin(); 419 | midi_if.turnThruOff(); 420 | // 'If a Tune Request command is sent, all the MIDI instruments in the system that have a tuning routine 421 | // will give themselves a quick checkover and retune to their own internal reference' 422 | // http://www.soundonsound.com/sos/1995_articles/oct95/midibasics3.html 423 | midi_if.sendRealTime(midi::TuneRequest); 424 | 425 | // copy and set 426 | cpqn = config.cpqn; 427 | setCycle(config.cycle); 428 | // end of setup() - disable board led (oldschool diag) 429 | digitalWriteFast(BOARD_LED, LOW); 430 | // recall saved state 431 | setState(config.state); 432 | } 433 | 434 | extern void __attribute__((noreturn)) 435 | loop() 436 | { 437 | // will things change? 438 | if (state != nextState) 439 | { 440 | #if DEBUG 441 | debugSerial << "TRANSITION:" << state << "=>" << nextState << endl; 442 | #endif 443 | // yep, apply change 444 | state = nextState; 445 | machine.trigger(state); 446 | } 447 | 448 | // will store encoder's pulses 449 | static int8_t encCounter; 450 | // will store last time the clock cycled 451 | static uint32_t lastCycleAt; 452 | // time of last tap 453 | static uint32_t lastTapTime; 454 | // check if clock has passed 455 | const uint32_t intDiff = micros() - lastCycleAt; 456 | 457 | switch(state) 458 | { 459 | case STATE_HALT: 460 | // nop, don't clock 461 | break; 462 | case STATE_START: 463 | case STATE_CONTINUE: 464 | case STATE_STOP: 465 | if (intDiff >= cycle) 466 | { 467 | // yep, store 468 | lastCycleAt = micros(); 469 | // output clock message 470 | midi_if.sendRealTime(midi::Clock); 471 | // count clicks per quarter note (cpqn) 472 | if(++counter == 1) 473 | { 474 | // raise control voltage 475 | digitalWriteFast(GATE_PIN, LOW); 476 | // light tempo LEDs up 477 | queueLight(ACT_PUSH, LED_LEFT_A, 240, 0); 478 | queueLight(ACT_PUSH, LED_LEFT_B, 240, 0); 479 | } 480 | // 1/4 note counted? 481 | else if(counter == cpqn) 482 | { 483 | // reset 484 | counter = 0; 485 | } 486 | // 1/8 note counted (half-time)? 487 | else if(counter == cpqn / 4) 488 | { 489 | // drop control voltage 490 | digitalWriteFast(GATE_PIN, HIGH); 491 | // turn tempo LEDs off 492 | queueLight(ACT_PUSH, LED_LEFT_A, 0, 50); 493 | queueLight(ACT_PUSH, LED_LEFT_B, 0, 50); 494 | } 495 | } 496 | 497 | uint16_t changeValue = ENCODER_VAL; 498 | // check if encoder is moved 499 | int8_t encData = readEncoder(); 500 | if (encData) 501 | { 502 | // change value rapidly while button is pressed 503 | if (tempoButton.pressedFor(BTN_HOLD_THRESH)) 504 | { 505 | lastTapTime = 0; 506 | changeValue = ENCODER_VAL_SHIFT; 507 | } 508 | 509 | // add to counter 510 | encCounter += encData; 511 | switch (encCounter) 512 | { 513 | // turned left, increase (slower) 514 | case 4: 515 | encCounter = 0; 516 | setCycle(cycle + changeValue); 517 | queueLight(ACT_REPLACE, LED_LEFT_A, 200, 0); 518 | break; 519 | // turned right, decrease (faster) 520 | case -4: 521 | encCounter = 0; 522 | setCycle(cycle - changeValue); 523 | queueLight(ACT_REPLACE, LED_LEFT_B, 220, 0); 524 | break; 525 | } 526 | } 527 | 528 | if(tempoButton.wasPressed()) 529 | { 530 | if(lastTapTime > 0) 531 | { 532 | // time passed since last beat 533 | const uint32_t tapDiff = micros() - lastTapTime; 534 | // check if cycle duration time gets larger 535 | if(tapDiff < TAP_WINDOW) 536 | { 537 | // store value (and have it smoothied) 538 | clockFilter.push(tapDiff); 539 | // TODO optimize: division 540 | const uint32_t newCycle = clockFilter.mean() / cpqn; 541 | // yep, change cycle duration 542 | if(newCycle - 500 > cycle) 543 | { 544 | queueLight(ACT_REPLACE, LED_LEFT_A, 220, 0); 545 | queueLight(ACT_UNSHIFT, LED_LEFT_B, 0, 0); 546 | } 547 | else if(newCycle + 500 < cycle) 548 | { 549 | queueLight(ACT_REPLACE, LED_LEFT_A, 0, 0); 550 | queueLight(ACT_UNSHIFT, LED_LEFT_B, 240, 0); 551 | } 552 | else 553 | { 554 | queueLight(ACT_REPLACE, LED_LEFT_A, 240, 0); 555 | queueLight(ACT_UNSHIFT, LED_LEFT_B, 240, 0); 556 | } 557 | setCycle(newCycle); 558 | } 559 | } 560 | // store current time 561 | lastTapTime = micros(); 562 | } 563 | break; 564 | } 565 | 566 | // switch according to context 567 | switch(inputContext) 568 | { 569 | case BUTTON_CLICK: 570 | // check if transport button is being hold 571 | if (transportButton.pressedFor(BTN_HOLD_THRESH)) 572 | { 573 | // yep, change context (consider next time) 574 | inputContext = BUTTON_TRANSPORT_HOLD; 575 | } 576 | else 577 | { 578 | // check if button has been pressed 579 | if (transportButton.wasReleased()) 580 | { 581 | // switch according to state 582 | switch (state) 583 | { 584 | case STATE_START: 585 | case STATE_CONTINUE: 586 | case STATE_HALT: 587 | setState(STATE_STOP); 588 | break; 589 | case STATE_STOP: 590 | setState(STATE_CONTINUE); 591 | break; 592 | } 593 | // reset context 594 | inputContext = BUTTON_CLICK; 595 | } 596 | } 597 | break; 598 | case BUTTON_TRANSPORT_HOLD: 599 | // check if button was released now 600 | if (transportButton.wasReleased()) 601 | { 602 | switch(state) 603 | { 604 | case STATE_STOP: 605 | setState(STATE_START); 606 | break; 607 | case STATE_START: 608 | case STATE_CONTINUE: 609 | setState(STATE_HALT); 610 | break; 611 | } 612 | // reset context 613 | inputContext = BUTTON_CLICK; 614 | } 615 | // check for 'save config' combo 616 | else if (tempoButton.pressedFor(3000)) 617 | { 618 | alert(); 619 | saveConfig(); 620 | setState(STATE_HALT); 621 | awaitRelease(); 622 | reset(); 623 | } 624 | break; 625 | } 626 | 627 | // check if somebody is knocking on the midi input 628 | if (state != STATE_HALT && midi_if.read()) 629 | { 630 | // yep, watcha want 631 | const uint8_t command = midi_if.getType(); 632 | // serve on some commands 633 | switch(command) 634 | { 635 | case midi::Stop: 636 | setState(STATE_STOP); 637 | break; 638 | case midi::Start: 639 | setState(STATE_START); 640 | break; 641 | case midi::Continue: 642 | setState(STATE_CONTINUE); 643 | break; 644 | } 645 | } 646 | 647 | // check if something is stacked 648 | if(!stack.isEmpty()) 649 | { 650 | // yep, let's go for the event 651 | led_event l = stack.pop(); 652 | // cast according LED interface 653 | LEDFader *led = &leds[l.id]; 654 | // fade to color for duration of 655 | led->fade(l.val, l.dur); 656 | } 657 | 658 | // update drive of each LED 659 | for (uint8_t i = 0; i < LED_NUM; i++) { 660 | LEDFader *led = &leds[i]; 661 | led->update(); 662 | } 663 | 664 | // read buttons 665 | tempoButton.read(); 666 | transportButton.read(); 667 | 668 | #if DEBUG 669 | // output debugging stuff every few cycles 670 | if ((micros() - debugTime) >= DEBUG_INTERVAL) 671 | { 672 | printDiag(); 673 | // store last time 674 | debugTime = micros(); 675 | } 676 | loopCycle = micros(); 677 | #endif 678 | } 679 | 680 | // end 681 | --------------------------------------------------------------------------------