Browse Source

maddius joy

master
marius 5 months ago
parent
commit
d9ddd04632
  1. 47
      maddius/maddius_joy/.devcontainer/Dockerfile
  2. 45
      maddius/maddius_joy/.devcontainer/devcontainer.json
  3. 4
      maddius/maddius_joy/.gitignore
  4. 6
      maddius/maddius_joy/CMakeLists.txt
  5. 83
      maddius/maddius_joy/README.md
  6. 147
      maddius/maddius_joy/dependencies.lock
  7. 3
      maddius/maddius_joy/main/CMakeLists.txt
  8. 282
      maddius/maddius_joy/main/controls.cpp
  9. 26
      maddius/maddius_joy/main/controls.h
  10. 127
      maddius/maddius_joy/main/ezButton.cpp
  11. 69
      maddius/maddius_joy/main/ezButton.h
  12. 5
      maddius/maddius_joy/main/idf_component.yml
  13. 174
      maddius/maddius_joy/main/tusb_midi_main.c
  14. 13
      maddius/maddius_joy/pytest_usb_device_midi.py
  15. 2394
      maddius/maddius_joy/sdkconfig
  16. 1
      maddius/maddius_joy/sdkconfig.defaults
  17. 69
      maddius/maddius_matrix_control/dependencies.lock
  18. 218
      qlc+/joy.qxw

47
maddius/maddius_joy/.devcontainer/Dockerfile

@ -0,0 +1,47 @@
FROM espressif/idf
ARG DEBIAN_FRONTEND=nointeractive
ARG CONTAINER_USER=esp
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN apt-get update \
&& apt install -y -q \
cmake \
git \
libglib2.0-0 \
libnuma1 \
libpixman-1-0 \
&& rm -rf /var/lib/apt/lists/*
# QEMU
ENV QEMU_REL=esp_develop_8.2.0_20240122
ENV QEMU_SHA256=e7c72ef5705ad1444d391711088c8717fc89f42e9bf6d1487f9c2a326b8cfa83
ENV QEMU_DIST=qemu-xtensa-softmmu-${QEMU_REL}-x86_64-linux-gnu.tar.xz
ENV QEMU_URL=https://github.com/espressif/qemu/releases/download/esp-develop-8.2.0-20240122/${QEMU_DIST}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN wget --no-verbose ${QEMU_URL} \
&& echo "${QEMU_SHA256} *${QEMU_DIST}" | sha256sum --check --strict - \
&& tar -xf $QEMU_DIST -C /opt \
&& rm ${QEMU_DIST}
ENV PATH=/opt/qemu/bin:${PATH}
RUN groupadd --gid $USER_GID $CONTAINER_USER \
&& adduser --uid $USER_UID --gid $USER_GID --disabled-password --gecos "" ${CONTAINER_USER} \
&& usermod -a -G root $CONTAINER_USER && usermod -a -G dialout $CONTAINER_USER
RUN chmod -R 775 /opt/esp/python_env/
USER ${CONTAINER_USER}
ENV USER=${CONTAINER_USER}
WORKDIR /home/${CONTAINER_USER}
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]

45
maddius/maddius_joy/.devcontainer/devcontainer.json

@ -0,0 +1,45 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.183.0/containers/ubuntu
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
// Add the IDs of extensions you want installed when the container is created
"workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind",
/* the path of workspace folder to be opened after container is running
*/
"workspaceFolder": "${localWorkspaceFolder}",
"mounts": [
"source=extensionCache,target=/root/.vscode-server/extensions,type=volume"
],
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.espIdfPath": "/opt/esp/idf",
"idf.customExtraPaths": "",
"idf.pythonBinPath": "/opt/esp/python_env/idf5.3_py3.10_env/bin/python",
"idf.toolsPath": "/opt/esp",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension"
],
},
"codespaces": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.espIdfPath": "/opt/esp/idf",
"idf.customExtraPaths": "",
"idf.pythonBinPath": "/opt/esp/python_env/idf5.3_py3.10_env/bin/python",
"idf.toolsPath": "/opt/esp",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension"
],
}
},
"runArgs": ["--privileged"]
}

4
maddius/maddius_joy/.gitignore vendored

@ -0,0 +1,4 @@
.pio
.vscode
build
managed_components

6
maddius/maddius_joy/CMakeLists.txt

@ -0,0 +1,6 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(maddius_joy)

83
maddius/maddius_joy/README.md

@ -0,0 +1,83 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# TinyUSB MIDI Device Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example shows how to set up ESP chip to work as a USB MIDI Device.
It outputs a MIDI note sequence via the native USB port.
As a USB stack, a TinyUSB component is used.
## How to use example
### Hardware Required
Any ESP board that have USB-OTG supported.
#### Pin Assignment
_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below.
See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments).
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```bash
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## MIDI output
You can use several programs on your computer to listen to the ESP's MIDI output depending on your operating system, e.g.:
* Windows: `MIDI-OX`
* Linux: `qsynth` with `qjackctl`
* macOS: `SimpleSynth`
## Example Output
After the flashing you should see the output at idf monitor:
```
I (285) example: USB initialization
I (285) tusb_desc:
┌─────────────────────────────────┐
│ USB Device Descriptor Summary │
├───────────────────┬─────────────┤
│bDeviceClass │ 0 │
├───────────────────┼─────────────┤
│bDeviceSubClass │ 0 │
├───────────────────┼─────────────┤
│bDeviceProtocol │ 0 │
├───────────────────┼─────────────┤
│bMaxPacketSize0 │ 64 │
├───────────────────┼─────────────┤
│idVendor │ 0x303a │
├───────────────────┼─────────────┤
│idProduct │ 0x4008 │
├───────────────────┼─────────────┤
│bcdDevice │ 0x100 │
├───────────────────┼─────────────┤
│iManufacturer │ 0x1 │
├───────────────────┼─────────────┤
│iProduct │ 0x2 │
├───────────────────┼─────────────┤
│iSerialNumber │ 0x3 │
├───────────────────┼─────────────┤
│bNumConfigurations │ 0x1 │
└───────────────────┴─────────────┘
I (455) TinyUSB: TinyUSB Driver installed
I (465) example: USB initialization DONE
```
Disconnect UART-to-USB port and connect the native USB port to a computer then the device should show up as a USB MIDI Device while outputting notes.

147
maddius/maddius_joy/dependencies.lock

@ -0,0 +1,147 @@
dependencies:
chmorgan/esp-libhelix-mp3:
component_hash: cbb76089dc2c5749f7b470e2e70aedc44c9da519e04eb9a67d4c7ec275229e53
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.0.3
espressif/arduino-esp32:
component_hash: c518a55e52a50d662a7c71202e42a90a64bd2122a08089cbb1f76d925e812afc
source:
service_url: https://api.components.espressif.com/
type: service
version: 3.0.1
espressif/cbor:
component_hash: 440f4ee4504841cc9b4f3a8ef755776a612ac9dace355514c68b999868f990ff
source:
service_url: https://api.components.espressif.com/
type: service
version: 0.6.0~1
espressif/esp-dsp:
component_hash: 3e7bbd487f1357a1d4944d0c85966d049501ea281b8a4c7f93f7cfedd5b7f23d
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.4.12
espressif/esp-sr:
component_hash: 16af25295f531495fe7e36cf2e8c8bb49629be42c38a56f55f4ded825ada0ffc
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.7.1
espressif/esp-zboss-lib:
component_hash: c7020f631cd4ad25e159ddb812bdc80906b5b424457abc81c9b8b0e92687837a
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.3.2
espressif/esp-zigbee-lib:
component_hash: 752dbc8abb2befd2d49ee902051699fac5e32c6962950d64cd888d75d6566ca0
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.3.2
espressif/esp_diag_data_store:
component_hash: 8849195251dbb8a2d7268335277cfa310cef36e4ac1e90cd59ad3be4269a30d7
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.0.1
espressif/esp_diagnostics:
component_hash: 2350938db074002ef088c05cd32d80a528185e47afdb3642427156256c3e13c5
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.1.0
espressif/esp_insights:
component_hash: 72ece1dcf0146275e1df976c713a9b193af0907931f3810957ee68a0a84b077d
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.1.0
espressif/esp_modem:
component_hash: e48da33fee082dd9d9a97a354a228057e07a14ac108766b40ad84e018205410a
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.1.0
espressif/esp_rainmaker:
component_hash: 232425d1c9b9dbdaa5c109b3e359fba0e6956e35fcf22725f07ed7e4389e1191
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.1.0
espressif/esp_schedule:
component_hash: 80d3694717a537156f61a072dfe1d1498a2a4f32819a5735bf8a50bcadf79941
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.1.1
espressif/esp_secure_cert_mgr:
component_hash: a20007d67e65a000670ab77e45d7554c943eb8dcb0abeada0a57dd9adac3a703
source:
service_url: https://api.components.espressif.com/
type: service
version: 2.4.1
espressif/esp_tinyusb:
component_hash: 5e37c53a10518f40b288a48afec4ab549784be9e6eb74add699a30395543418a
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.0.3
espressif/jsmn:
component_hash: d80350c41bbaa827c98a25b6072df00884e72f54885996fab4a4f0aebce6b6c3
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.1.0
espressif/json_generator:
component_hash: 45033e1c199b13f1c8c1b544fb7d4e2df6a8e3071ebdcb1b22582b61a7974ff2
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.1.2
espressif/json_parser:
component_hash: d74b81729ad06ec11ff5eb5b1b0d7df1d00e6027fc11471f4b139c70dcf1b1e4
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.0.3
espressif/mdns:
component_hash: 31117d76cae83a6d83ffd7f035f6fdae5bd05b914fc30b641afeb208b84de19a
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.3.2
espressif/qrcode:
component_hash: 3b493771bc5d6ad30cbf87c25bf784aada8a08c941504355b55d6b75518ed7bc
source:
service_url: https://api.components.espressif.com/
type: service
version: 0.1.0~2
espressif/rmaker_common:
component_hash: 021c02465e4996dd3292db36ea47b5e3122e7e2d3514797735a96bc12598b44f
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.4.5
espressif/tinyusb:
component_hash: 256fd8aee92ae9f1014538b8601508907a2da386b64f6d42f35a67f9288d1b20
source:
service_url: https://api.components.espressif.com/
type: service
version: 0.15.0~9
idf:
component_hash: null
source:
type: idf
version: 5.1.4
joltwallet/littlefs:
component_hash: 8373229a6c63ccc08747117cd937b35de5721ba49468e4ca29dad006235e655e
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.14.5
manifest_hash: 534b60a796c994201c1fe175ee341e4e13962f7a0dcbdbb550f369327daedf28
target: esp32s3
version: 1.0.0

3
maddius/maddius_joy/main/CMakeLists.txt

@ -0,0 +1,3 @@
idf_component_register(SRCS "tusb_midi_main.c" "controls.cpp" "ezButton.cpp"
INCLUDE_DIRS "."
)

282
maddius/maddius_joy/main/controls.cpp

@ -0,0 +1,282 @@
// #include "Adafruit_Keypad.h"
// #include "led_strip_rmt_ws2812.cpp"
// #include "ESPRotary.h"
#include <Wire.h> // I2C
#include "driver/gpio.h"
#include "controls.h"
#include "tinyusb.h"
#include <ezButton.h>
#define LED_STRIP_LED_NUMBERS 30
#define I2C_INTERRUPT_REQUEST 57 //
#define ROWS 5 // rows
#define COLS 6 // columns
#define CLICKS_PER_STEP 4 // this number depends on your rotary encoder
#define BUTTON_PIN 1
#define BUTTON_DEBOUNCE 20
// buttons
ezButton button[1] = {{7}};
// joystick
#define BUTTON_PIN2 41
#define JOY_X 40
#define JOY_Y 39
// I2C
#define I2C_SDA 37
#define I2C_SCL 36
#define I2C_INTERUPT GPIO_NUM_38
static const char *TAG = "controls";
// #define SERIAL_SPEED 115200
uint8_t controls[512] = {0};
// Basic MIDI Messages
#define NOTE_OFF 0x80
#define NOTE_ON 0x90
#define SET_CONTROL 0xB0
void setControl(uint16_t controlNum, uint8_t value)
{
uint16_t keynum = controlNum;
bool litValue = false;
if (value > 0)
{
litValue = true;
}
if ((controlNum / 6) % 2 == 1)
{
keynum = controlNum + 5 - (controlNum % 6) * 2;
}
ESP_LOGI(TAG, "begin litValue %i ,%i, %i", (controlNum % 6), controlNum, keynum);
// lit[keynum] = litValue; // invert neopixel status
controls[controlNum] = value;
}
void task_read_all(void *args)
{
// init
static uint8_t const cable_num = 0; // MIDI jack associated with USB endpoint
static uint8_t const channel = 0; // 0 for channel 1
bool changeDetected = false;
// BUTTON
for (int i = 0; i < 1; i++)
{
button[i].setDebounceTime(BUTTON_DEBOUNCE);
}
uint8_t buttonLastState[9] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t buttonNextState[9] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t buttonExternalState[9] = {0};
// FADER
ESP_LOGI(TAG, "begin ");
uint16_t threshold = 250;
uint16_t speed = 100;
uint16_t joy_x_init = 0;
uint16_t joy_y_init = 0;
uint16_t joy_zoom_init = 0;
for (int i=0;i<10;i++)
{
joy_x_init += analogRead(4);
joy_y_init += analogRead(5);
joy_zoom_init += analogRead(6);
}
joy_x_init = joy_x_init/10;
joy_y_init = joy_y_init/10;
joy_zoom_init = joy_zoom_init/10;
ESP_LOGI(TAG, "joy_x_init %i",joy_x_init);
ESP_LOGI(TAG, "joy_y_init %i",joy_y_init);
ESP_LOGI(TAG, "joy_zoom_init %i",joy_zoom_init);
uint16_t joy_x = 0;
uint16_t joy_y = 0;
uint16_t joy_zoom = 0;
int16_t joy_x_dir = 0;
int16_t joy_y_dir = 0;
int16_t joy_zoom_dir = 0;
float joy_float_x = 0;
float joy_float_y = 0;
float joy_float_zoom = 0;
// uint8_t controls[64] = 0;
// uint8_t controls[65] = 0;
// uint8_t controls[66] = 0;
uint8_t faderLastState[3] = {0, 0, 0};
for (;;)
{
// BUTTON
for (int i = 0; i < 1; i++)
{
// uint8_t buttonValue = digitalRead(buttonPins[i]);
// buttonNextState[i] = buttonValue ? 0 : 255;
button[i].loop();
if (button[i].isPressed())
{
buttonNextState[i] = 255;
}
else if (button[i].isReleased())
{
buttonNextState[i] = 0;
}
// Serial.print("r");
// Serial.print(read);
// Serial.print(": ");
// Serial.println(faderNextState[i]);
}
// JOY
if(controls[64] != (uint8_t)round(joy_float_x)){
ESP_LOGI(TAG, "joy_float_x = controls[64] %f %i",joy_float_x,controls[64]);
joy_float_x = controls[64];
}
if(controls[65] != (uint8_t)round(joy_float_y)){
joy_float_y = controls[65];
}
joy_x = analogRead(4);
joy_y = analogRead(5);
joy_x_dir = joy_x_init-joy_x;
joy_y_dir = joy_y_init-joy_y;
if(abs(joy_x_dir)>threshold){
joy_x_dir = joy_x_dir+ (joy_x_dir <0 ? threshold:-threshold);
// ESP_LOGI(TAG, "joy_x_dir %i",joy_x_dir);
joy_float_x = joy_float_x + ((float)joy_x_dir / 900000 * speed);
if(joy_float_x > 255){
joy_float_x = 255;
}
if(joy_float_x < 0){
joy_float_x = 0;
}
}
if(abs(joy_y_dir)>threshold){
joy_y_dir = joy_y_dir+ (joy_y_dir <0 ? threshold:-threshold);
// ESP_LOGI(TAG, "joy_y_dir %i",joy_y_dir);
joy_float_y = joy_float_y + ((float)joy_y_dir / 900000 * speed);
if(joy_float_y > 255){
joy_float_y = 255;
}
if(joy_float_y < 0){
joy_float_y = 0;
}
}
if(controls[64] != (uint8_t)round(joy_float_x)){
controls[64] = (uint8_t)round(joy_float_x);
ESP_LOGI(TAG, "controls[64] %i",controls[64]);
uint8_t control[3] = {SET_CONTROL | channel, 64, (uint8_t)(controls[64]>>1)};
tud_midi_stream_write(cable_num, control, 3);
}
if(controls[65] != (uint8_t)round(joy_float_y)){
controls[65] = (uint8_t)round(joy_float_y);
ESP_LOGI(TAG, "controls[65] %i",controls[65]);
uint8_t control[3] = {SET_CONTROL | channel, 65, (uint8_t)(controls[65]>>1)};
tud_midi_stream_write(cable_num, control, 3);
}
// ZOOM
if(controls[66] != (uint8_t)round(joy_float_zoom)){
joy_float_zoom = controls[66];
}
joy_zoom = analogRead(6);
joy_zoom_dir = joy_zoom_init-joy_zoom;
if(abs(joy_zoom_dir)>threshold){
joy_zoom_dir = joy_zoom_dir+ (joy_zoom_dir <0 ? threshold:-threshold);
joy_float_zoom = joy_float_zoom + ((float)joy_zoom_dir / 900000 * speed);
if(joy_float_zoom > 255){
joy_float_zoom = 255;
}
if(joy_float_zoom < 0){
joy_float_zoom = 0;
}
}
if(controls[66] != (uint8_t)round(joy_float_zoom)){
controls[66] = (uint8_t)round(joy_float_zoom);
ESP_LOGI(TAG, "controls[66] %i",controls[66]);
uint8_t control[3] = {SET_CONTROL | channel, 66, (uint8_t)(controls[66]>>1)};
tud_midi_stream_write(cable_num, control, 3);
}
// WIRE I2C
bool nextChangeDetected = false;
// better only on change?
for (uint8_t i = 0; i < 1; i++)
{
// Buttons
if (buttonNextState[i] != buttonLastState[i])
{
buttonLastState[i] = buttonNextState[i];
Serial.print("B");
Serial.print(i);
Serial.print(": ");
Serial.println(buttonNextState[i]);
nextChangeDetected = true;
uint8_t control[3] = {SET_CONTROL | channel, i, 127};
ESP_LOGI(TAG, "buttonNextState[%i] %i",i,buttonNextState[i]);
tud_midi_stream_write(cable_num, control, 3);
}
if (buttonLastState[i] == 255 || buttonExternalState[i] == 255)
{
// digitalWrite(ledPins[i], HIGH);
}
else
{
// digitalWrite(ledPins[i], LOW);
}
}
if (nextChangeDetected && !changeDetected)
{
changeDetected = nextChangeDetected;
// pinMode(I2C_INTERRUPT_REQUEST, OUTPUT);
// resetIRQCount++;
}
else if (!changeDetected)
{
// pinMode(I2C_INTERRUPT_REQUEST, INPUT);
}
vTaskDelay(1);
}
// bool lastAvailable = false;
// I2C
// Wire.begin(I2C_SDA, I2C_SCL, 400000L);
// pinMode(I2C_INTERUPT, INPUT_PULLUP); // i2c available
// for (;;)
// {
// // i2c
// bool nextAvailable = gpio_get_level(I2C_INTERUPT) == 0;
// if (nextAvailable) // || lastAvailable
// {
// // ESP_LOGI(TAG, "i2cAvailable: %i", i2cAvailable);
// // fader9
// Wire.requestFrom(55, 18);
// while (Wire.available())
// { // peripheral may send less than requested
// int c = Wire.read(); // receive a byte
// ESP_LOGI(TAG, "value: %i", c);
// }
// // button8
// Wire.requestFrom(56, 8);
// while (Wire.available())
// { // peripheral may send less than requested
// int c = Wire.read(); // receive a byte
// ESP_LOGI(TAG, "value: %i", c);
// }
// // lastAvailable = nextAvailable;
// }
// // task delay time?
// vTaskDelay(1);
// }
}
void init_controls(controls_config *cfg)
{
// ESP_LOGI(TAG, "cfg.matrix_keys.led_io: %i", cfg->matrix_keys.led_io);
for (int i = 0; i < 9; i++)
{
button[i].setDebounceTime(BUTTON_DEBOUNCE);
}
}

26
maddius/maddius_joy/main/controls.h

@ -0,0 +1,26 @@
#include <stdlib.h>
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct
{
uint16_t *row_io;
uint16_t rows;
uint16_t *col_io;
uint16_t cols;
uint16_t led_io;
} matrix_keys_config;
typedef struct
{
matrix_keys_config matrix_keys;
} controls_config;
void setControl(uint16_t controlNum, uint8_t value);
void task_read_all(void *args);
void task_led_strip(void *arg);
void init_controls(controls_config *cfg);
#ifdef __cplusplus
}
#endif

127
maddius/maddius_joy/main/ezButton.cpp

@ -0,0 +1,127 @@
/*
* Copyright (c) 2019, ArduinoGetStarted.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the ArduinoGetStarted.com nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY ARDUINOGETSTARTED.COM "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ARDUINOGETSTARTED.COM BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <ezButton.h>
ezButton::ezButton(int pin): ezButton(pin, INPUT_PULLUP) {};
ezButton::ezButton(int pin, int mode) {
btnPin = pin;
debounceTime = 0;
count = 0;
countMode = COUNT_FALLING;
pinMode(btnPin, mode);
previousSteadyState = digitalRead(btnPin);
lastSteadyState = previousSteadyState;
lastFlickerableState = previousSteadyState;
lastDebounceTime = 0;
}
void ezButton::setDebounceTime(unsigned long time) {
debounceTime = time;
}
int ezButton::getState(void) {
return lastSteadyState;
}
int ezButton::getStateRaw(void) {
return digitalRead(btnPin);
}
bool ezButton::isPressed(void) {
if(previousSteadyState == HIGH && lastSteadyState == LOW)
return true;
else
return false;
}
bool ezButton::isReleased(void) {
if(previousSteadyState == LOW && lastSteadyState == HIGH)
return true;
else
return false;
}
void ezButton::setCountMode(int mode) {
countMode = mode;
}
unsigned long ezButton::getCount(void) {
return count;
}
void ezButton::resetCount(void) {
count = 0;
}
void ezButton::loop(void) {
// read the state of the switch/button:
int currentState = digitalRead(btnPin);
unsigned long currentTime = millis();
// check to see if you just pressed the button
// (i.e. the input went from LOW to HIGH), and you've waited long enough
// since the last press to ignore any noise:
// If the switch/button changed, due to noise or pressing:
if (currentState != lastFlickerableState) {
// reset the debouncing timer
lastDebounceTime = currentTime;
// save the the last flickerable state
lastFlickerableState = currentState;
}
if ((currentTime - lastDebounceTime) >= debounceTime) {
// whatever the reading is at, it's been there for longer than the debounce
// delay, so take it as the actual current state:
// save the the steady state
previousSteadyState = lastSteadyState;
lastSteadyState = currentState;
}
if(previousSteadyState != lastSteadyState){
if(countMode == COUNT_BOTH)
count++;
else if(countMode == COUNT_FALLING){
if(previousSteadyState == HIGH && lastSteadyState == LOW)
count++;
}
else if(countMode == COUNT_RISING){
if(previousSteadyState == LOW && lastSteadyState == HIGH)
count++;
}
}
}

69
maddius/maddius_joy/main/ezButton.h

@ -0,0 +1,69 @@
/*
* Copyright (c) 2019, ArduinoGetStarted.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the ArduinoGetStarted.com nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY ARDUINOGETSTARTED.COM "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ARDUINOGETSTARTED.COM BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ezButton_h
#define ezButton_h
#include <Arduino.h>
#define COUNT_FALLING 0
#define COUNT_RISING 1
#define COUNT_BOTH 2
class ezButton
{
private:
int btnPin;
unsigned long debounceTime;
unsigned long count;
int countMode;
int previousSteadyState; // the previous steady state from the input pin, used to detect pressed and released event
int lastSteadyState; // the last steady state from the input pin
int lastFlickerableState; // the last flickerable state from the input pin
unsigned long lastDebounceTime; // the last time the output pin was toggled
public:
ezButton(int pin);
ezButton(int pin, int mode);
void setDebounceTime(unsigned long time);
int getState(void);
int getStateRaw(void);
bool isPressed(void);
bool isReleased(void);
void setCountMode(int mode);
unsigned long getCount(void);
void resetCount(void);
void loop(void);
};
#endif

5
maddius/maddius_joy/main/idf_component.yml

@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
esp_tinyusb: "~1.0.0"
idf: "^5.1"
espressif/arduino-esp32: "^3.0.0"

174
maddius/maddius_joy/main/tusb_midi_main.c

@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: 2019 Ha Thach (tinyusb.org)
*
* SPDX-License-Identifier: MIT
*
* SPDX-FileContributor: 2022-2023 Espressif Systems (Shanghai) CO LTD
*/
#include <stdlib.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "esp_timer.h"
#include "controls.h"
static const char *TAG = "example";
/** Helper defines **/
// Interface counter
enum interface_count {
#if CFG_TUD_MIDI
ITF_NUM_MIDI = 0,
ITF_NUM_MIDI_STREAMING,
#endif
ITF_COUNT
};
// USB Endpoint numbers
enum usb_endpoints {
// Available USB Endpoints: 5 IN/OUT EPs and 1 IN EP
EP_EMPTY = 0,
#if CFG_TUD_MIDI
EPNUM_MIDI,
#endif
};
/** TinyUSB descriptors **/
#define TUSB_DESCRIPTOR_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_MIDI * TUD_MIDI_DESC_LEN)
// Basic MIDI Messages
#define NOTE_OFF 0x80
#define NOTE_ON 0x90
#define SET_CONTROL 0xB0
/**
* @brief String descriptor
*/
static const char* s_str_desc[5] = {
// array of pointer to string descriptors
(char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"TinyUSB", // 1: Manufacturer
"TinyUSB Device", // 2: Product
"123456", // 3: Serials, should use chip ID
"Example MIDI device", // 4: MIDI
};
/**
* @brief Configuration descriptor
*
* This is a simple configuration descriptor that defines 1 configuration and a MIDI interface
*/
static const uint8_t s_midi_cfg_desc[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_COUNT, 0, TUSB_DESCRIPTOR_TOTAL_LEN, 0, 100),
// Interface number, string index, EP Out & EP In address, EP size
TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 4, EPNUM_MIDI, (0x80 | EPNUM_MIDI), 64),
};
static void midi_task_read_example(void *arg)
{
// The MIDI interface always creates input and output port/jack descriptors
// regardless of these being used or not. Therefore incoming traffic should be read
// (possibly just discarded) to avoid the sender blocking in IO
uint8_t packet[4];
bool read = false;
for (;;) {
vTaskDelay(1);
while (tud_midi_available()) {
read = tud_midi_packet_read(packet);
if (read) {
if(packet[1] == SET_CONTROL){
setControl(packet[2], packet[3]<<1);
ESP_LOGI(TAG, "setControl(packet[2]%i, packet[3]%i<1/%i)", packet[2], packet[3],packet[3]<<1);
}
ESP_LOGI(TAG, "Read - Time (ms since boot): %lld, Data: %02hhX %02hhX %02hhX %02hhX",
esp_timer_get_time(), packet[0], packet[1], packet[2], packet[3]);
}
}
}
}
static void periodic_midi_write_example_cb(void *arg)
{
// Example melody stored as an array of note values
uint8_t const note_sequence[] = {
74, 78, 81, 86, 90, 93, 98, 102, 57, 61, 66, 69, 73, 78, 81, 85, 88, 92, 97, 100, 97, 92, 88, 85, 81, 78,
74, 69, 66, 62, 57, 62, 66, 69, 74, 78, 81, 86, 90, 93, 97, 102, 97, 93, 90, 85, 81, 78, 73, 68, 64, 61,
56, 61, 64, 68, 74, 78, 81, 86, 90, 93, 98, 102
};
static uint8_t const cable_num = 0; // MIDI jack associated with USB endpoint
static uint8_t const channel = 0; // 0 for channel 1
static uint32_t note_pos = 0;
// Previous positions in the note sequence.
int previous = note_pos - 1;
// If we currently are at position 0, set the
// previous position to the last note in the sequence.
if (previous < 0) {
previous = sizeof(note_sequence) - 1;
}
// Send Note On for current position at full velocity (127) on channel 1.
ESP_LOGI(TAG, "Writing MIDI data %d", note_sequence[note_pos]);
if (tud_midi_mounted()) {
uint8_t note_on[3] = {NOTE_ON | channel, note_sequence[note_pos], 127};
tud_midi_stream_write(cable_num, note_on, 3);
// Send Note Off for previous note.
uint8_t note_off[3] = {NOTE_OFF | channel, note_sequence[previous], 0};
tud_midi_stream_write(cable_num, note_off, 3);
}
// Increment position
note_pos++;
// If we are at the end of the sequence, start over.
if (note_pos >= sizeof(note_sequence)) {
note_pos = 0;
}
}
void app_main(void)
{
ESP_LOGI(TAG, "USB initialization");
tinyusb_config_t const tusb_cfg = {
.device_descriptor = NULL, // If device_descriptor is NULL, tinyusb_driver_install() will use Kconfig
.string_descriptor = s_str_desc,
.external_phy = false,
.configuration_descriptor = s_midi_cfg_desc,
};
// .string_descriptor_count = sizeof(s_str_desc) / sizeof(s_str_desc[0]),
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB initialization DONE");
// // Periodically send MIDI packets
// int const tempo = 286;
// const esp_timer_create_args_t periodic_midi_args = {
// .callback = &periodic_midi_write_example_cb,
// /* name is optional, but may help identify the timer when debugging */
// .name = "periodic_midi"
// };
// ESP_LOGI(TAG, "MIDI write task init");
// esp_timer_handle_t periodic_midi_timer;
// ESP_ERROR_CHECK(esp_timer_create(&periodic_midi_args, &periodic_midi_timer));
// ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_midi_timer, tempo * 1000));
// Read recieved MIDI packets
ESP_LOGI(TAG, "MIDI read task init");
xTaskCreate(midi_task_read_example, "midi_task_read_example", 8 * 1024, NULL, 5, NULL);
ESP_LOGI(TAG, "read inputs task init");
xTaskCreate(task_read_all, "task_read_all", 8 * 1024, NULL, 21, NULL);
}

13
maddius/maddius_joy/pytest_usb_device_midi.py

@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32s2
@pytest.mark.usb_device
def test_usb_device_midi_example(dut: Dut) -> None:
dut.expect_exact('USB initialization DONE')
dut.expect_exact('MIDI write task init')
dut.expect_exact('MIDI read task init')
dut.expect_exact('Writing MIDI data 74')

2394
maddius/maddius_joy/sdkconfig

File diff suppressed because it is too large Load Diff

1
maddius/maddius_joy/sdkconfig.defaults

@ -0,0 +1 @@
CONFIG_TINYUSB_MIDI_COUNT=1

69
maddius/maddius_matrix_control/dependencies.lock

@ -1,69 +0,0 @@
dependencies:
chmorgan/esp-libhelix-mp3:
component_hash: cbb76089dc2c5749f7b470e2e70aedc44c9da519e04eb9a67d4c7ec275229e53
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.0.3
espressif/button:
component_hash: ba2b069cc8d7d485f3d528c5187fd80599144e8d9ef53a883bc09b46cd608aab
source:
service_url: https://api.components.espressif.com/
type: service
version: 3.1.3
espressif/cmake_utilities:
component_hash: 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f
source:
service_url: https://api.components.espressif.com/
type: service
version: 0.5.3
espressif/esp_tinyusb:
component_hash: f8682cfddb743297ec1350d7cc52dff6b535c737c60c6a388cdcf2e9dc0eca4b
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.4.2~2
espressif/esp_websocket_client:
component_hash: af69df6a85b26a1e129814ba033fd0c704cdf925b114d9bf90f885db1bb3f8a9
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.2.1
espressif/jsmn:
component_hash: d80350c41bbaa827c98a25b6072df00884e72f54885996fab4a4f0aebce6b6c3
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.1.0
espressif/json_parser:
component_hash: d74b81729ad06ec11ff5eb5b1b0d7df1d00e6027fc11471f4b139c70dcf1b1e4
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.0.3
espressif/led_strip:
component_hash: ed1d5c6113fa545e20c7be17e6e7c09d43b18fcb43068e2b2b27a412de6a405a
source:
service_url: https://api.components.espressif.com/
type: service
version: 2.5.2
espressif/mdns:
component_hash: 810ec139689ae93bf42520d05de4855fbb68f7140ef67797d91d8d61829589cb
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.2.2
espressif/tinyusb:
component_hash: 632ddbd9c73bee5005e7684eed3142649a08d1f304dbe42abed8110b7384e2c2
source:
service_url: https://api.components.espressif.com/
type: service
version: 0.15.0~3
idf:
component_hash: null
source:
type: idf
version: 5.1.2
manifest_hash: 4ed6e24795a74233b024b7c04a08daf95687434889ce45bc5fc86b8074fae5f6
target: esp32s3
version: 1.0.0

218
qlc+/joy.qxw

@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Workspace>
<Workspace xmlns="http://www.qlcplus.org/Workspace" CurrentWindow="InputOutputManager">
<Creator>
<Name>Q Light Controller Plus</Name>
<Version>4.12.7</Version>
<Author>maxei</Author>
</Creator>
<Engine>
<InputOutputMap>
<Universe Name="Universe 1" ID="0">
<Input Plugin="OSC" UID="169.254.14.165" Line="1" Profile="Maddius JOY"/>
<Output Plugin="ArtNet" UID="169.254.14.165" Line="1"/>
</Universe>
<Universe Name="Universe 2" ID="1"/>
<Universe Name="Universe 3" ID="2"/>
<Universe Name="Universe 4" ID="3">
<Input Plugin="MIDI" UID="Example MIDI device" Line="0" Profile="Maddius JOY"/>
<Feedback Plugin="MIDI" UID="Example MIDI device" Line="1"/>
</Universe>
</InputOutputMap>
<Fixture>
<Manufacturer>Varytec</Manufacturer>
<Model>BAT.PAR 6 RGBUV</Model>
<Mode>1 channel</Mode>
<ID>0</ID>
<Name>BAT.PAR 6 RGBUV</Name>
<Universe>0</Universe>
<Address>0</Address>
<Channels>1</Channels>
</Fixture>
<Fixture>
<Manufacturer>UKing</Manufacturer>
<Model>LED Spot Moving Head 100W</Model>
<Mode>9 Channel</Mode>
<ID>1</ID>
<Name>moving</Name>
<Universe>0</Universe>
<Address>1</Address>
<Channels>9</Channels>
</Fixture>
<Function ID="0" Type="RGBMatrix" Name="Neue RGB Matrix 0">
<Speed FadeIn="0" FadeOut="0" Duration="500"/>
<Direction>Forward</Direction>
<RunOrder>Loop</RunOrder>
<Algorithm Type="Script">Stripes</Algorithm>
<MonoColor>4294901760</MonoColor>
<ControlMode>RGB</ControlMode>
<FixtureGroup>4294967295</FixtureGroup>
</Function>
<Function ID="1" Type="EFX" Name="Neuer Effekt 1">
<PropagationMode>Parallel</PropagationMode>
<Speed FadeIn="0" FadeOut="0" Duration="20000"/>
<Direction>Forward</Direction>
<RunOrder>Loop</RunOrder>
<Algorithm>Circle</Algorithm>
<Width>127</Width>
<Height>127</Height>
<Rotation>0</Rotation>
<StartOffset>0</StartOffset>
<IsRelative>0</IsRelative>
<Axis Name="X">
<Offset>127</Offset>
<Frequency>2</Frequency>
<Phase>90</Phase>
</Axis>
<Axis Name="Y">
<Offset>127</Offset>
<Frequency>3</Frequency>
<Phase>0</Phase>
</Axis>
</Function>
<Function ID="2" Type="Scene" Name="Neue Szene 2">
<Speed FadeIn="0" FadeOut="0" Duration="0"/>
<FixtureVal ID="0">0,179</FixtureVal>
</Function>
<Function ID="3" Type="Script" Name="Neues Skript 3">
<Speed FadeIn="0" FadeOut="0" Duration="0"/>
<Direction>Forward</Direction>
<RunOrder>Loop</RunOrder>
</Function>
<Function ID="4" Type="Scene" Name="Neue Szene 4">
<Speed FadeIn="0" FadeOut="0" Duration="0"/>
<FixtureVal ID="0">0,52</FixtureVal>
</Function>
<Function ID="5" Type="Scene" Name="New Scene" Hidden="True">
<Speed FadeIn="0" FadeOut="0" Duration="0"/>
</Function>
<Function ID="6" Type="Sequence" Name="Neue Sequenz 6" BoundScene="5">
<Speed FadeIn="0" FadeOut="0" Duration="0"/>
<Direction>Forward</Direction>
<RunOrder>Loop</RunOrder>
<SpeedModes FadeIn="Default" FadeOut="Default" Duration="Common"/>
<Step Number="0" FadeIn="0" Hold="0" FadeOut="0" Values="0"/>
<Step Number="1" FadeIn="0" Hold="0" FadeOut="0" Values="0"/>
</Function>
<Function ID="7" Type="Chaser" Name="Neuer Chaser 7">
<Speed FadeIn="0" FadeOut="0" Duration="100000"/>
<Direction>Forward</Direction>
<RunOrder>Loop</RunOrder>
<SpeedModes FadeIn="Default" FadeOut="Default" Duration="Common"/>
<Step Number="0" FadeIn="0" Hold="0" FadeOut="0">2</Step>
<Step Number="1" FadeIn="0" Hold="0" FadeOut="0">4</Step>
</Function>
</Engine>
<VirtualConsole>
<Frame Caption="">
<Appearance>
<FrameStyle>None</FrameStyle>
<ForegroundColor>Default</ForegroundColor>
<BackgroundColor>Default</BackgroundColor>
<BackgroundImage>None</BackgroundImage>
<Font>Default</Font>
</Appearance>
<Button Caption="Schalter 0" ID="0" Icon="">
<WindowState Visible="False" X="290" Y="160" Width="50" Height="50"/>
<Appearance>
<FrameStyle>None</FrameStyle>
<ForegroundColor>Default</ForegroundColor>
<BackgroundColor>Default</BackgroundColor>
<BackgroundImage>None</BackgroundImage>
<Font>Default</Font>
</Appearance>
<Function ID="4294967295"/>
<Action>Toggle</Action>
<Intensity Adjust="False">100</Intensity>
</Button>
<Frame Caption="" ID="1">
<Appearance>
<FrameStyle>Sunken</FrameStyle>
<ForegroundColor>Default</ForegroundColor>
<BackgroundColor>Default</BackgroundColor>
<BackgroundImage>None</BackgroundImage>
<Font>Default</Font>
</Appearance>
<WindowState Visible="False" X="475" Y="140" Width="845" Height="665"/>
<AllowChildren>True</AllowChildren>
<AllowResize>True</AllowResize>
<ShowHeader>True</ShowHeader>
<ShowEnableButton>True</ShowEnableButton>
<Collapsed>False</Collapsed>
<Disabled>False</Disabled>
<Button Caption="Neue RGB Matrix 0" ID="2" Icon="">
<WindowState Visible="False" X="45" Y="95" Width="50" Height="50"/>
<Appearance>
<FrameStyle>None</FrameStyle>
<ForegroundColor>Default</ForegroundColor>
<BackgroundColor>Default</BackgroundColor>
<BackgroundImage>None</BackgroundImage>
<Font>Default</Font>
</Appearance>
<Function ID="4"/>
<Action>Toggle</Action>
<Intensity Adjust="False">100</Intensity>
<Input Universe="3" Channel="0"/>
</Button>
<XYPad Caption="XY Pad" ID="3" InvertedAppearance="0">
<WindowState Visible="False" X="165" Y="140" Width="230" Height="230"/>
<Appearance>
<FrameStyle>Sunken</FrameStyle>
<ForegroundColor>Default</ForegroundColor>
<BackgroundColor>Default</BackgroundColor>
<BackgroundImage>None</BackgroundImage>
<Font>Default</Font>
</Appearance>
<Pan Position="114">
<Input Universe="3" Channel="64"/>
</Pan>
<Tilt Position="162">
<Input Universe="3" Channel="65"/>
</Tilt>
</XYPad>
<Slider Caption="Schieberegler 4" ID="4" WidgetStyle="Slider" InvertedAppearance="false">
<WindowState Visible="False" X="435" Y="140" Width="85" Height="230"/>
<Appearance>
<FrameStyle>Sunken</FrameStyle>
<ForegroundColor>Default</ForegroundColor>
<BackgroundColor>Default</BackgroundColor>
<BackgroundImage>None</BackgroundImage>
<Font>Default</Font>
</Appearance>
<Input Universe="3" Channel="66"/>
<SliderMode ValueDisplayStyle="Exact" ClickAndGoType="None">Playback</SliderMode>
<Level LowLimit="0" HighLimit="255" Value="0"/>
<Playback>
<Function>4294967295</Function>
</Playback>
</Slider>
<CueList Caption="Cue-Liste" ID="5">
<WindowState Visible="False" X="245" Y="395" Width="300" Height="220"/>
<Appearance>
<FrameStyle>Sunken</FrameStyle>
<ForegroundColor>Default</ForegroundColor>
<BackgroundColor>Default</BackgroundColor>
<BackgroundImage>None</BackgroundImage>
<Font>Default</Font>
</Appearance>
<Chaser>7</Chaser>
<NextPrevBehavior>3</NextPrevBehavior>
<Next>
<Input Universe="3" Channel="0"/>
</Next>
<Previous/>
<Playback/>
<Stop/>
<SlidersMode>Steps</SlidersMode>
</CueList>
</Frame>
</Frame>
<Properties>
<Size Width="1920" Height="1080"/>
<GrandMaster ChannelMode="Intensity" ValueMode="Reduce" SliderMode="Normal"/>
</Properties>
</VirtualConsole>
<SimpleDesk>
<Engine/>
</SimpleDesk>
</Workspace>
Loading…
Cancel
Save