From 0bc3b819b6f9e08f3c217f96da5f696f10bad869 Mon Sep 17 00:00:00 2001 From: chee Date: Wed, 17 Mar 2021 11:58:44 +0000 Subject: [PATCH] Session 8.2 --- bleepbloopmachine/CMakeLists.txt | 6 + bleepbloopmachine/src/controls.cc | 96 +++++++++ bleepbloopmachine/src/controls.h | 40 ++++ bleepbloopmachine/src/freq.h | 134 +++++++++++++ bleepbloopmachine/src/main.cc | 318 ++++++++++++++++++++++++++---- 5 files changed, 561 insertions(+), 33 deletions(-) create mode 100644 bleepbloopmachine/src/controls.cc create mode 100644 bleepbloopmachine/src/controls.h create mode 100644 bleepbloopmachine/src/freq.h diff --git a/bleepbloopmachine/CMakeLists.txt b/bleepbloopmachine/CMakeLists.txt index 1e243b2..d737808 100644 --- a/bleepbloopmachine/CMakeLists.txt +++ b/bleepbloopmachine/CMakeLists.txt @@ -1,5 +1,11 @@ cmake_minimum_required(VERSION 3.19.0) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) project(bleepbloopmachine CXX) add_executable(bleepbloopmachine src/main.cc) +add_library(controls src/controls.h src/controls.cc) target_link_arduino_libraries(bleepbloopmachine AUTO_PUBLIC) +target_link_arduino_libraries(controls AUTO_PUBLIC) +target_link_libraries(bleepbloopmachine PRIVATE controls) target_enable_arduino_upload(bleepbloopmachine) diff --git a/bleepbloopmachine/src/controls.cc b/bleepbloopmachine/src/controls.cc new file mode 100644 index 0000000..f82addf --- /dev/null +++ b/bleepbloopmachine/src/controls.cc @@ -0,0 +1,96 @@ +#include "controls.h" + +void Controls::begin() { + pinMode(BUTTON_CLOCK, OUTPUT); + digitalWrite(BUTTON_CLOCK, HIGH); + pinMode(BUTTON_LATCH, OUTPUT); + digitalWrite(BUTTON_LATCH, HIGH); + pinMode(BUTTON_DATA, INPUT); +} + +int16_t Controls::readJoystickX(uint8_t sampling) { + float reading = 0; + + if (JOYSTICK_X >= 0) { + for (int i = 0; i < sampling; i++) { + reading += analogRead(JOYSTICK_X); + } + reading /= sampling; + + reading -= JOYSTICK_X_CENTER; + } + + return reading; +} + +int16_t Controls::readJoystickY(uint8_t sampling) { + float reading = 0; + + if (JOYSTICK_Y >= 0) { + for (int i = 0; i < sampling; i++) { + reading += analogRead(JOYSTICK_Y); + } + reading /= sampling; + + reading -= JOYSTICK_Y_CENTER; + } + + return reading; +} + +uint32_t Controls::readButtons(void) { + uint32_t buttons = 0; + + uint8_t shift_buttons = 0; + + digitalWrite(BUTTON_LATCH, LOW); + // delayMicroseconds(1); + digitalWrite(BUTTON_LATCH, HIGH); + // delayMicroseconds(1); + + for (int i = 0; i < 8; i++) { + shift_buttons <<= 1; + shift_buttons |= digitalRead(BUTTON_DATA); + digitalWrite(BUTTON_CLOCK, HIGH); + // delayMicroseconds(1); + digitalWrite(BUTTON_CLOCK, LOW); + // delayMicroseconds(1); + } + + if (shift_buttons & BUTTON_SHIFTMASK_B) { + buttons |= PAD_B; + } + if (shift_buttons & BUTTON_SHIFTMASK_A) { + buttons |= PAD_A; + } + if (shift_buttons & BUTTON_SHIFTMASK_SELECT) { + buttons |= PAD_SELECT; + } + if (shift_buttons & BUTTON_SHIFTMASK_START) { + buttons |= PAD_START; + } + + int16_t x = readJoystickX(); + if (x > 350) + buttons |= PAD_RIGHT; + else if (x < -350) + buttons |= PAD_LEFT; + int16_t y = readJoystickY(); + if (y > 350) + buttons |= PAD_DOWN; + else if (y < -350) + buttons |= PAD_UP; + + // TODO add encoder in here when it's pinned + + last_buttons = curr_buttons; + curr_buttons = buttons; + justpressed_buttons = (last_buttons ^ curr_buttons) & curr_buttons; + justreleased_buttons = (last_buttons ^ curr_buttons) & last_buttons; + + return buttons; +} + +uint32_t Controls::justPressed() { return justpressed_buttons; } + +uint32_t Controls::justReleased() { return justreleased_buttons; } diff --git a/bleepbloopmachine/src/controls.h b/bleepbloopmachine/src/controls.h new file mode 100644 index 0000000..3cd2065 --- /dev/null +++ b/bleepbloopmachine/src/controls.h @@ -0,0 +1,40 @@ +#pragma once +#include +#define BUTTON_CLOCK 48 +#define BUTTON_DATA 49 +#define BUTTON_LATCH 50 +#define BUTTON_SHIFTMASK_B 0x80 +#define BUTTON_SHIFTMASK_A 0x40 +#define BUTTON_SHIFTMASK_START 0x20 +#define BUTTON_SHIFTMASK_SELECT 0x10 + +#define JOYSTICK_X A11 +#define JOYSTICK_Y A10 + +#define JOYSTICK_X_CENTER 512 +#define JOYSTICK_Y_CENTER 512 + +#define PAD_A 0x01 +#define PAD_B 0x02 +#define PAD_SELECT 0x04 +#define PAD_START 0x08 +#define PAD_UP 0x10 +#define PAD_DOWN 0x20 +#define PAD_LEFT 0x40 +#define PAD_RIGHT 0x80 + +class Controls { +public: + void begin(); + int16_t readJoystickX(uint8_t sampling = 3); + int16_t readJoystickY(uint8_t sampling = 3); + uint32_t readButtons(); + uint32_t justPressed(); + uint32_t justReleased(); + +private: + uint32_t last_buttons; + uint32_t curr_buttons; + uint32_t justpressed_buttons; + uint32_t justreleased_buttons; +}; diff --git a/bleepbloopmachine/src/freq.h b/bleepbloopmachine/src/freq.h new file mode 100644 index 0000000..a234edf --- /dev/null +++ b/bleepbloopmachine/src/freq.h @@ -0,0 +1,134 @@ +float freq[12][9] = { + { + 16.3516, + 32.7032, + 65.40639, + 130.8128, + 261.6256, + 523.2511, + 1046.502, + 2093.005, + 4186.009, + }, + { + 17.32391, + 34.64783, + 69.29566, + 138.5913, + 277.1826, + 554.3653, + 1108.731, + 2217.461, + 4434.922, + }, + { + 18.35405, + 36.7081, + 73.41619, + 146.8324, + 293.6648, + 587.3295, + 1174.659, + 2349.318, + 4698.636, + }, + { + 19.44544, + 38.89087, + 77.78175, + 155.5635, + 311.127, + 622.254, + 1244.508, + 2489.016, + 4978.032, + }, + { + 20.60172, + 41.20344, + 82.40689, + 164.8138, + 329.6276, + 659.2551, + 1318.51, + 2637.02, + 5274.041, + }, + { + 21.82676, + 43.65353, + 87.30706, + 174.6141, + 349.2282, + 698.4565, + 1396.913, + 2793.826, + 5587.652, + }, + { + 23.12465, + 46.2493, + 92.49861, + 184.9972, + 369.9944, + 739.9888, + 1479.978, + 2959.955, + 5919.911, + }, + { + 24.49971, + 48.99943, + 97.99886, + 195.9977, + 391.9954, + 783.9909, + 1567.982, + 3135.963, + 6271.927, + }, + { + 25.95654, + 51.91309, + 103.8262, + 207.6523, + 415.3047, + 830.6094, + 1661.219, + 3322.438, + 6644.875, + }, + { + 27.5, + 55.0, + 110.0, + 220.0, + 440.0, + 880.0, + 1760.0, + 3520.0, + 7040.0, + }, + { + 29.13524, + 58.27047, + 116.5409, + 233.0819, + 466.1638, + 932.3275, + 1864.655, + 3729.31, + 7458.62, + }, + { + 30.86771, + 61.73541, + 123.4708, + 246.9417, + 493.8833, + 987.7666, + 1975.533, + 3951.066, + 7902.133, + }, +}; diff --git a/bleepbloopmachine/src/main.cc b/bleepbloopmachine/src/main.cc index ea093f1..dde6e81 100644 --- a/bleepbloopmachine/src/main.cc +++ b/bleepbloopmachine/src/main.cc @@ -1,44 +1,283 @@ #include "main.h" +#include "controls.h" + #include #include #include #include -#define TFT_CS 44 -#define TFT_RST 46 -#define TFT_DC 45 -#define TFT_BACKLIGHT 47 -//#include "Adafruit_SPIFlash.h" +#define SPEAKER_ENABLE 51 + +#define NEOPIXEL_PIN 8 +#define NEOPIXEL_LENGTH 5 + +#define LIGHT_SENSOR A7 +#define BATTERY_SENSOR A6 + +#define RIGHT_AUDIO_PIN A0 +#define LEFT_AUDIO_PIN A1 + +#define SD_CS 4 + #include "Adafruit_TinyUSB.h" #include "AudioStream.h" #include "output_dacs.h" +#include "freq.h" #include #include -Adafruit_ST7735 display = Adafruit_ST7735(&SPI1, TFT_CS, TFT_DC, TFT_RST); +#define DISPLAY_CS 44 +#define DISPLAY_RST 46 +#define DISPLAY_DC 45 +#define DISPLAY_BACKLIGHT 47 +#define DISPLAY_HEIGHT 160 +#define DISPLAY_WIDTH 128 + +Adafruit_ST7735 display = + Adafruit_ST7735(&SPI1, DISPLAY_CS, DISPLAY_DC, DISPLAY_RST); + +enum class Waveform { + sine, + sawtooth, + square, + triangle, + arbitrary, + pulse, + sawtoothReverse, + sampleHold +}; + +int wrapping_add(int cur, int max, int inc = 1, int min = 0) { + auto nxt = cur + inc; + return nxt <= max ? nxt : min; +} +int wrapping_sub(int cur, int max, int inc = 1, int min = 0) { + auto nxt = cur - inc; + return nxt >= min ? nxt : max; +} -class Wave {}; +String note_names[] = {"c", "c#", "d", "d#", "e", "f", + "f#", "g", "g#", "a", "a#", "b"}; +String octave_names[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8"}; + +#define BLOCK_SIZE 20 +#define GRID_OFFSET_X 36 +#define GRID_OFFSET_Y 20 +#define GRID_GAP 2 +#define STANDARD_BLOCK_COLOR 0xfebb +#define TICK_BLOCK_COLOR 0xfc53 + +struct Point { + int x; + int y; +}; + +Point getCoord(int idx) { + int x = idx % 4; + int y = idx / 4; + return Point{ + x : GRID_OFFSET_X + (x * (BLOCK_SIZE + GRID_GAP)), + y : GRID_OFFSET_Y + (y * (BLOCK_SIZE + GRID_GAP)) + }; +} + +class SoundBlock { +public: + int note = 0; + int octave = 3; + bool active = false; + AudioSynthWaveform *synth; + AudioEffectEnvelope *env; + Waveform waveform; + float frequency() { return freq[note][octave]; } + void play() { + if (!active) { + return; + } + AudioNoInterrupts(); + synth->frequency(frequency()); + synth->begin(0.2, frequency(), (int)waveform); + env->noteOn(); + AudioInterrupts(); + } + void activate() { active = true; } + void noteUp() { note = wrapping_add(note, 11); } + void noteDown() { note = wrapping_sub(note, 11); } + void octaveUp() { octave = wrapping_add(octave, 8); } + void octaveDown() { octave = wrapping_sub(octave, 8); } + SoundBlock() {} + SoundBlock(AudioSynthWaveform *s, Waveform w, AudioEffectEnvelope *e) + : synth{s}, waveform{w}, env{e} {} +}; + +class Wave { +public: + Waveform form; + String name; + SoundBlock sounds[16]; + AudioSynthWaveform &synth; + AudioEffectEnvelope &env; + int rate = 4; + int lastStepBlockIndex = 0; + Wave(String n, AudioSynthWaveform &s, Waveform f, AudioEffectEnvelope &e) + : name{n}, form{f}, synth{s}, env{e} { + for (auto i = 0; i < 16; i++) { + sounds[i] = SoundBlock(&synth, form, &env); + } + env.attack(100.0); + env.decay(10.0); + env.sustain(0.0); + } + int stepBlockIndex(int currentStep) { return currentStep / rate % 16; } + void play(int blockIndex) { + Serial.println(blockIndex); + Serial.println(sounds[0].frequency()); + auto sound = sounds[blockIndex]; + sound.play(); + } + void step(int currentTick) { + auto idx = stepBlockIndex(currentTick); + if (idx != lastStepBlockIndex) { + lastStepBlockIndex = idx; + play(idx); + } + } + void updateDisplay(Adafruit_GFX *display) { + for (int i = 0; i < 0xf; i++) { + auto sound = sounds[i]; + if (sound.active) { + Point point = getCoord(i); + String note = note_names[sound.note]; + display->drawChar(point.x + 2, point.y + 2, note[0], ST7735_BLACK, + ST7735_BLACK, 1); + if (note.length() == 2) { + display->drawChar(point.x + 9, point.y + 2, '#', ST7735_BLACK, + ST7735_BLACK, 1); + } + display->fillRect(point.x + (sound.octave * 2), point.y, + (sound.octave * 2), 2, ST7735_BLACK); + } + } + } +}; + +// TODO make the synth and env and patch as part of creating a Wave +AudioSynthWaveform synth_q; +AudioSynthWaveform synth_p; +AudioSynthWaveform synth_s; +AudioSynthWaveform synth_n; + +AudioEffectEnvelope env_q; +AudioEffectEnvelope env_p; +AudioEffectEnvelope env_s; +AudioEffectEnvelope env_n; + +Wave wave_q("q", synth_q, Waveform::square, env_q); +Wave wave_p("p", synth_p, Waveform::pulse, env_p); +Wave wave_s("s", synth_s, Waveform::sine, env_s); +Wave wave_n("n", synth_n, Waveform::sampleHold, env_n); class BleepBloopMachine { +public: menu_mode mode = menu_mode::live; float bpm = 120.0; - int current_tick = 0; - int bar_of_ticks = 255; - int selected_sound_index = 0; - int selected_menu1_control = 0; - int selected_menu2_control = 0; - int selected_wave_index = 0; - // Wave[] & waves[4]; - // TODO this needs to be whatever - int last_keys; + // TODO ask abe how to write this without brackets + long lastMicros = micros(); + long stepLength() { return ((1 / bpm) / 16) * 60000000; } + int selectedSoundIndex = 0; + int selectedMenu1Control = 0; + int selectedMenu2Control = 0; + Wave &selectedWave = wave_q; + int selectedBlockIndex = 0; + int lastSelectedBlockIndex = 0; + int currentStep = 0; + void step() { + auto m = micros(); + if (m - lastMicros >= stepLength()) { + currentStep += 1; + if (currentStep > 63) { + currentStep = 0; + } + lastMicros = m; + wave_q.step(currentStep); + wave_p.step(currentStep); + wave_s.step(currentStep); + wave_n.step(currentStep); + updateGrid(); + } + } + void updateGrid() { + AudioNoInterrupts(); + auto stepBlockIndex = selectedWave.stepBlockIndex(currentStep); + auto stepCoord = getCoord(stepBlockIndex); + display.fillRect(stepCoord.x, stepCoord.y, BLOCK_SIZE, BLOCK_SIZE, + TICK_BLOCK_COLOR); + auto lastStepCord = getCoord(wrapping_sub(stepBlockIndex, 0xf)); + display.fillRect(lastStepCord.x, lastStepCord.y, BLOCK_SIZE, BLOCK_SIZE, + STANDARD_BLOCK_COLOR); + // then the selection box + auto selectCoord = getCoord(selectedBlockIndex); + display.drawRect(selectCoord.x, selectCoord.y, BLOCK_SIZE, BLOCK_SIZE, + ST7735_BLACK); + // then the labels + selectedWave.updateDisplay(&display); + AudioInterrupts(); + } + void handleButtons(uint32_t held, uint32_t released) { + bool ab_mode = held & PAD_A && held & PAD_B; + bool a_mode = !ab_mode && held & PAD_A; + bool b_mode = !ab_mode && held & PAD_B; + bool none_mode = !ab_mode && !a_mode && !b_mode; + switch (mode) { + case menu_mode::live: + if (none_mode) { + if (released == PAD_B) { + selectedWave.sounds[selectedBlockIndex].activate(); + } + if (released & PAD_UP && selectedBlockIndex > 0x3) { + selectedBlockIndex -= 4; + } + if (released & PAD_DOWN && selectedBlockIndex < 0xc) { + selectedBlockIndex += 4; + } + if (released & PAD_LEFT && selectedBlockIndex > 0x0) { + selectedBlockIndex -= 1; + } + if (released & PAD_RIGHT && selectedBlockIndex < 0xf) { + selectedBlockIndex += 1; + } + } + if (b_mode) { + if (released & PAD_UP) { + selectedWave.sounds[selectedBlockIndex].noteUp(); + } + if (released & PAD_DOWN) { + selectedWave.sounds[selectedBlockIndex].noteDown(); + } + if (released & PAD_LEFT) { + selectedWave.sounds[selectedBlockIndex].octaveDown(); + } + if (released & PAD_RIGHT) { + selectedWave.sounds[selectedBlockIndex].octaveUp(); + } + } + return; + } + } }; -// pygamer specific -Adafruit_NeoPixel neopixels(5, 8, NEO_GRB); +BleepBloopMachine machine; + +Adafruit_NeoPixel neopixels(NEOPIXEL_LENGTH, NEOPIXEL_PIN, NEO_GRB); -/* there are only monophonic noises on this one so far :) +AudioConnection weq_patch(synth_q, env_q); +AudioConnection wep_patch(synth_p, env_p); +AudioConnection wes_patch(synth_s, env_s); +AudioConnection wen_patch(synth_n, env_n); + +/* there are only monophonic sounds on this one so far :) * but this will need updated for: * - chords * - panning @@ -49,31 +288,44 @@ AudioMixer4 p_mixer; AudioMixer4 s_mixer; AudioMixer4 n_mixer; */ + AudioMixer4 mixer; + +AudioConnection emq_patch(env_q, 0, mixer, 0); +AudioConnection emp_patch(env_p, 0, mixer, 1); +AudioConnection ems_patch(env_s, 0, mixer, 2); +AudioConnection emn_patch(env_n, 0, mixer, 3); + AudioOutputAnalogStereo headphones; AudioConnection left_ear_patch(mixer, 0, headphones, 0); AudioConnection right_ear_patch(mixer, 0, headphones, 1); +uint16_t readLightSensor() { return analogRead(LIGHT_SENSOR); } + +float readBatterySensor() { + return ((float)analogRead(BATTERY_SENSOR) / 1023.0) * 2.0 * 3.3; +} + +Controls controls; + void setup() { + Serial.begin(115200); + AudioMemory(18); + controls.begin(); display.initR(INITR_BLACKTAB); display.setRotation(1); - pinMode(TFT_BACKLIGHT, OUTPUT); - digitalWrite(TFT_BACKLIGHT, HIGH); - display.fillScreen(ST77XX_BLACK); - display.fillScreen(ST77XX_YELLOW); - display.drawCircle(0, 10, 20, ST77XX_BLACK); - // arcada.arcadaBegin(); - // arcada.filesysBeginMSD(); - // arcada.displayBegin(); - // arcada.setBacklight(255); - // arcada.drawBMP((char *)"icon.bmp", 0, 0); + pinMode(DISPLAY_BACKLIGHT, OUTPUT); + digitalWrite(DISPLAY_BACKLIGHT, HIGH); + display.fillScreen(ST77XX_WHITE); neopixels.begin(); neopixels.setBrightness(180); } void loop() { - // arcada.readButtons(); - // uint8_t buttons = arcada.pressed(); - // int16_t joyX = arcada.readJoystickX(); - // int16_t joyY = arcada.readJoystickY(); + uint32_t buttons = controls.readButtons(); + + uint32_t justReleased = controls.justReleased(); + + machine.handleButtons(buttons, justReleased); + machine.step(); }