Teensy 3.6 SD card - saving/loading structs?

Status
Not open for further replies.

sid8580

Well-known member
Hello all, been at this problem for 3 days now and think it's finally time to ask for help -

1. What is the simplest way to save a big struct (several variable types and enums, many variables in total) to SD? How about if it's a 2-dimensional array as well?

2. And the simplest way to then read it back from SD, and repopulate the struct it came from with the data?

I have no trouble at all saving/loading this data using the onboard EEPROM, but my project has grown and now the data for a single save now requires that entire space, need an SD card to function similarly. I know the SD (Teensy enhanced) library has the capabilities but I am lost. I'm actually a little surprised there isn't some tool or library function that just takes whatever you throw at it and saves it, like EEPROM.get and .put!

I can save data to a .txt file per the example ReadWrite easily enough. Should I be trying to store it all in human-readable ASCII .txt format? Or should I be casting it all to bytes, saving it to the file, and then dumping it back into the original array after loading? I figured the latter makes more sense, this project isn't a simple datalogger writing to text files as I've seen explained in most of the examples across the internet, this is device configuration data only meant for the device itself (and occasionally for me to debug).

The complete code is over 15k lines long (and the rest is working fine). I've tried to post this in a typical format, just the pieces that matter. If this isn't enough I will set up a standalone test program with these pieces alone later on and post that.

The first goal was just to save/load the variable "macro_global[0].MacroPolyphony". Thanks.

---

#include "SD.h"
#include <SPI.h>
File SDfile;
const int chipSelect = BUILTIN_SDCARD;

//============================//
// MACRO OBJECTS (MACRO GLOBALS) //
//============================//

// enumerate available polyphony options:
enum output_polyphony
{
POLYPHONY_MONO, // 0 - single note at a time, selected by a mapped sensor range
POLYPHONY_DUO, // 1 - two notes at a time, selected by mapped sensor ranges
POLYPHONY_POLY, // 2 - 3 notes at a time, selected by mapped sensor ranges
POLYPHONY_CHORD, // 3 - one chord at a time, selected by mapped sensor range
POLYPHONY_CONTROL // 4 - Control, CC only
};

// enumerate chromatic/CC quantizations:
enum output_quantization
{
QUANTIZE_NONE, // 0 - all notes/values available to output
QUANTIZE_SCALE, // 1 - scale, to be selectable
QUANTIZE_NOTELIST, // 2 - manually defined notes
QUANTIZE_CHORDLIST, // 3 - manually defined chords
QUANTIZE_NUMERIC // 4 - manually defined raw numeric values
};

// enumerate types for chord mode output:
enum chord_types
{
CHORD_DISABLED, // 0
MAJOR, // 1
MINOR, // 2
AUGMENTED_TRIAD, // 3
SUSPENDED_4TH, // 4
DIMINISHED_TRIAD, // 5
MAJOR_6TH, // 6
MINOR_6TH, // 7
MAJOR_7TH, // 8
MINOR_7TH, // 9
DOMINANT_7TH, // 10
DOMINANT_7TH_SUS4, // 11
DOMINANT_7TH_AUGMENTED_5TH, // 12
DOMINANT_7TH_FLATTENED_5TH, // 13
DIMINISHED_7TH, // 14
MINOR_7TH_FLATTENED_5TH, // 15
MINOR_MAJOR_7TH, // 16
MAJOR_9TH, // 17
MINOR_9TH, // 18
DOMINANT_9TH, // 19
NINTH_AUGMENTED_5TH, // 20
NINTH_FLATTENED_5TH, // 21
NINTH_ADD_6TH, // 22
MAJOR_11TH, // 23
MINOR_11TH, // 24
DOMINANT_11TH, // 25
ELEVENTH_FLATTENED_9TH, // 26
MAJOR_13TH, // 27
MINOR_13TH, // 28
DOMINANT_13TH, // 29
THIRTEENTH_FLATTENED_9TH // 30
};

// create data structure for globals like sensor calibration, per macro -
struct macro_globals
{

// User-definable polyphony options -
output_polyphony MacroPolyphony;

// User-definable output quantizations -
output_quantization MacroQuantizer;

// enumerated chord selection -
chord_types MacroChordType;

// MIDI channel acting as master for macro, needed for composite modeled output
unsigned char MacroMIDIchannel;

// calibation values per sensor, intended for use within the calibration UI
short CAPACITIVE_A_SensorInMax;
short CAPACITIVE_A_SensorInMin;
short CAPACITIVE_B_SensorInMax;
short CAPACITIVE_B_SensorInMin;
short GYRO_YAW_SensorInMax;
short GYRO_YAW_SensorInMin;
short GYRO_PITCH_SensorInMax;
short GYRO_PITCH_SensorInMin;
short GYRO_ROLL_SensorInMax;
short GYRO_ROLL_SensorInMin;
short GYRO_ACCEL_SensorInMax;
short GYRO_ACCEL_SensorInMin;
unsigned char CAPACITIVE_A_MIDIOutMax;
unsigned char CAPACITIVE_A_MIDIOutMin;
unsigned char CAPACITIVE_B_MIDIOutMax;
unsigned char CAPACITIVE_B_MIDIOutMin;
unsigned char GYRO_YAW_MIDIOutMax;
unsigned char GYRO_YAW_MIDIOutMin;
unsigned char GYRO_PITCH_MIDIOutMax;
unsigned char GYRO_PITCH_MIDIOutMin;
unsigned char GYRO_ROLL_MIDIOutMax;
unsigned char GYRO_ROLL_MIDIOutMin;
unsigned char GYRO_ACCEL_MIDIOutMax;
unsigned char GYRO_ACCEL_MIDIOutMin;
bool proxA_inverted;
bool proxB_inverted;
bool yaw_inverted;
bool pitch_inverted;
bool roll_inverted;
bool accel_inverted;

// default values:
macro_globals():
// these define how the macro can be used:
MacroPolyphony(POLYPHONY_MONO),
MacroQuantizer(QUANTIZE_NONE),
MacroChordType(CHORD_DISABLED),
// default mode is MONO, no output quantization

MacroMIDIchannel(9),

// sensor calibration data:
CAPACITIVE_A_SensorInMax(896), // == SensorIn values fall within each sensor's native range ==
CAPACITIVE_A_SensorInMin(127), // Capacitive sensor hardware range: 0-1023 (via MPR121.getFilteredData)
CAPACITIVE_B_SensorInMax(896),
CAPACITIVE_B_SensorInMin(127),
GYRO_YAW_SensorInMax(-179), // == SensorIn values fall within each sensor's native range ==
GYRO_YAW_SensorInMin(179), // Yaw, pitch and roll range from -180 to 180 degrees (360 for a complete rotation)
GYRO_PITCH_SensorInMax(-179),
GYRO_PITCH_SensorInMin(179),
GYRO_ROLL_SensorInMax(-179),
GYRO_ROLL_SensorInMin(179),
GYRO_ACCEL_SensorInMax(0),
GYRO_ACCEL_SensorInMin(0),
CAPACITIVE_A_MIDIOutMax(0), // MIDIOut values fall within 0-127
CAPACITIVE_A_MIDIOutMin(127), // The MIDI values are inverted to avoid having to rewrite the calibration UI
CAPACITIVE_B_MIDIOutMax(0),
CAPACITIVE_B_MIDIOutMin(127),
GYRO_YAW_MIDIOutMax(0),
GYRO_YAW_MIDIOutMin(127),
GYRO_PITCH_MIDIOutMax(0),
GYRO_PITCH_MIDIOutMin(127),
GYRO_ROLL_MIDIOutMax(0),
GYRO_ROLL_MIDIOutMin(127),
GYRO_ACCEL_MIDIOutMax(0),
GYRO_ACCEL_MIDIOutMin(127),
proxA_inverted(false),
proxB_inverted(false),
yaw_inverted(false),
pitch_inverted(false),
roll_inverted(false),
accel_inverted(false)
{}
};

// initialize an array of macro globals -
macro_globals macro_global[4];

void setup() {
if (!SD.begin(chipSelect)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
}

void loop() {
// load -
SDfile = SD.open("macro_global_0.sav");

char b[sizeof(macro_global[0].MacroPolyphony)];
SDfile.read(b, sizeof(macro_global[0].MacroPolyphony));
memcpy(&macro_global[0].MacroPolyphony, &b, sizeof(macro_global[0].MacroPolyphony));

Serial.print("Loading macro_global[0].MacroPolyphony : ");
Serial.println(macro_global[0].MacroPolyphony); //Display a value to see if it's correct
Serial.print("b : ");
Serial.println(b); //Display a value to see if it's correct

SDfile.close();

// save -
SDfile = SD.open("macro_global_0.sav", FILE_WRITE);
char b[sizeof(macro_global[0].MacroPolyphony)];
memcpy(&b, &macro_global[0].MacroPolyphony, sizeof(macro_global[0].MacroPolyphony));
SDfile.write(b, sizeof(macro_global[0].MacroPolyphony));

Serial.print("Saving macro_global[0].MacroPolyphony : ");
Serial.println(macro_global[0].MacroPolyphony); //Display a value to see if it's correct
Serial.print("b : ");
Serial.println(b); //Display a value to see if it's correct

SDfile.close();
}
 
One more thing... I'm really struggling with how reference variables and pointers work. No amount of reading seems to make it click. Maybe that's part of my problem with this, I don't know.

Worth mentioning because to anyone who is kind enough to answer... please, explain it to me like I'm 5 years old if the issue has to do with those concepts... thanks.
 
Perhaps the code from my sequencer project is of use to you, see : Zeus Commander SPS-16, MIDI step & pattern sequencer
The full code is available there, here's the save/load part:
Code:
// save current program to card at selected location
void saveProgram(int index){
  int structSize=0;
  int programSize=0;
  selectedProgram.tempo=sequencerTempo;
  selectedProgram.index=index;
  // open file for writing
  dataFile = SD.open(fileName[index], FILE_WRITE);
  // first write program metadata
  programSize=sizeof(selectedProgram);
  dataFile.write(&selectedProgram,programSize);
  // next write channel data
  for (int i=0;i<NUM_OF_CHANNELS;i++){
    structSize=sizeof(sequencerChannel[i]);
    dataFile.seek(programSize+i*structSize);
    dataFile.write(&sequencerChannel[i],structSize);
  }
  dataFile.close();
}

// load program from card
void loadProgram(int index){
  int structSize=0;
  int programSize=0;
  // open file for reading
  dataFile = SD.open(fileName[index]);
  programSize=sizeof(selectedProgram);
  dataFile.read(&selectedProgram,programSize);
  for (int i=0;i<NUM_OF_CHANNELS;i++){
    structSize=sizeof(sequencerChannel[i]);
    dataFile.seek(programSize + i*structSize);
    dataFile.read(&sequencerChannel[i],structSize);
  }
  dataFile.close(); 
  setSequencerTempo(selectedProgram.tempo);
  // set channel state LEDs
  refreshLeds=true;
  updateDisplayData=true;
  updateDisplayState=true;
  moveFaders();
}

The code saves and loads structs containing the sequence data. Essentially you determine the size of the struct, open the file and start writing using a pointer to the struct and the determined size.
Code:
  for (int i=0;i<NUM_OF_CHANNELS;i++){
    structSize=sizeof(sequencerChannel[i]);
    dataFile.seek(programSize+i*structSize);
    dataFile.write(&sequencerChannel[i],structSize);
  }
There're 8 channels, hence the loop and the seek command to append the data. A sequencer channel struct looks like this:
Code:
// struct for sequencer channel
struct zeusChannel{
  ZeusPattern   pattern[MAX_PATTERNS];            // array of patterns
  byte          currentPattern;                   // current (playing) pattern
  byte          selectedPattern;                  // separate selected pattern for prepare mode
  byte          currentRepeat;                    // current repeat of pattern
  byte          nextPattern;                      // next pattern, can override setting stored in pattern, also used for 'hold mode'
  byte          currentStep;                      // current step current pattern
  float         nextStepTime;                     // time in clock ticks when next step is to occur
  float         nextStepShift;                    // time in clock ticks the next step has been shifted, used for calculating the subsequent step
  ZeusStepEvent currentEvent;                     // current event, used for checking if note off needs to be sent
  byte          type;                             // group type: rythm, melody. Used for transpose and display control
  bool          active;                           // indicates if channel is active or muted
  byte          midiPort;                         // usb, midi 1, midi 2
} ;
typedef struct zeusChannel ZeusChannel;

As you can see this struct contains other structs in arrays. 16 pattern structs with 16 step structs each. IIRC one channel is about 16kB.
 
Gerrit -
First off, your project looks brilliant. Lovely aesthetic and useful ideas. I'd love to try it if I weren't on the other side of the world, and we may have more to talk about since the assistance you so graciously provided is toward the development of a 3D printed, wearable MIDI controller I've been working on for the last year and a half!

I tried implementing a simplified version of your load and save functions but keep getting compiler errors. I suspect I will work through them on my own with some more time (looks like the file.write function doesn't like my struct?) - in case it's something easily fixed here it is (complete test program this time, with the above code used but modified just to copy my 4 arrays of macro_globals, no header):

Code:
#include "SD.h"
#include <SPI.h>
File dataFile;
const int chipSelect = BUILTIN_SDCARD;

//============================//
// MACRO OBJECTS (MACRO GLOBALS) //
//============================//

// enumerate available polyphony options:
enum output_polyphony
{
POLYPHONY_MONO, // 0 - single note at a time, selected by a mapped sensor range
POLYPHONY_DUO, // 1 - two notes at a time, selected by mapped sensor ranges
POLYPHONY_POLY, // 2 - 3 notes at a time, selected by mapped sensor ranges
POLYPHONY_CHORD, // 3 - one chord at a time, selected by mapped sensor range
POLYPHONY_CONTROL // 4 - Control, CC only
};

// enumerate chromatic/CC quantizations:
enum output_quantization 
{
QUANTIZE_NONE, // 0 - all notes/values available to output
QUANTIZE_SCALE, // 1 - scale, to be selectable
QUANTIZE_NOTELIST, // 2 - manually defined notes
QUANTIZE_CHORDLIST, // 3 - manually defined chords
QUANTIZE_NUMERIC // 4 - manually defined raw numeric values
};

// enumerate types for chord mode output:
enum chord_types 
{
CHORD_DISABLED, // 0
MAJOR, // 1
MINOR, // 2
AUGMENTED_TRIAD, // 3
SUSPENDED_4TH, // 4
DIMINISHED_TRIAD, // 5
MAJOR_6TH, // 6
MINOR_6TH, // 7 
MAJOR_7TH, // 8
MINOR_7TH, // 9
DOMINANT_7TH, // 10
DOMINANT_7TH_SUS4, // 11
DOMINANT_7TH_AUGMENTED_5TH, // 12
DOMINANT_7TH_FLATTENED_5TH, // 13
DIMINISHED_7TH, // 14
MINOR_7TH_FLATTENED_5TH, // 15
MINOR_MAJOR_7TH, // 16
MAJOR_9TH, // 17
MINOR_9TH, // 18
DOMINANT_9TH, // 19
NINTH_AUGMENTED_5TH, // 20
NINTH_FLATTENED_5TH, // 21
NINTH_ADD_6TH, // 22
MAJOR_11TH, // 23
MINOR_11TH, // 24
DOMINANT_11TH, // 25
ELEVENTH_FLATTENED_9TH, // 26
MAJOR_13TH, // 27
MINOR_13TH, // 28
DOMINANT_13TH, // 29
THIRTEENTH_FLATTENED_9TH // 30
}; 

// create data structure for globals like sensor calibration, per macro - 
struct macro_globals
{

// User-definable polyphony options -
output_polyphony MacroPolyphony;

// User-definable output quantizations -
output_quantization MacroQuantizer;

// enumerated chord selection -
chord_types MacroChordType;

// MIDI channel acting as master for macro, needed for composite modeled output
unsigned char MacroMIDIchannel;

// calibation values per sensor, intended for use within the calibration UI 
short CAPACITIVE_A_SensorInMax;
short CAPACITIVE_A_SensorInMin; 
short CAPACITIVE_B_SensorInMax;
short CAPACITIVE_B_SensorInMin; 
short GYRO_YAW_SensorInMax;
short GYRO_YAW_SensorInMin; 
short GYRO_PITCH_SensorInMax;
short GYRO_PITCH_SensorInMin; 
short GYRO_ROLL_SensorInMax;
short GYRO_ROLL_SensorInMin; 
short GYRO_ACCEL_SensorInMax;
short GYRO_ACCEL_SensorInMin; 
unsigned char CAPACITIVE_A_MIDIOutMax;
unsigned char CAPACITIVE_A_MIDIOutMin; 
unsigned char CAPACITIVE_B_MIDIOutMax;
unsigned char CAPACITIVE_B_MIDIOutMin; 
unsigned char GYRO_YAW_MIDIOutMax;
unsigned char GYRO_YAW_MIDIOutMin; 
unsigned char GYRO_PITCH_MIDIOutMax;
unsigned char GYRO_PITCH_MIDIOutMin; 
unsigned char GYRO_ROLL_MIDIOutMax;
unsigned char GYRO_ROLL_MIDIOutMin; 
unsigned char GYRO_ACCEL_MIDIOutMax;
unsigned char GYRO_ACCEL_MIDIOutMin;
bool proxA_inverted;
bool proxB_inverted;
bool yaw_inverted;
bool pitch_inverted;
bool roll_inverted;
bool accel_inverted; 

// default values:
macro_globals():
// these define how the macro can be used:
MacroPolyphony(POLYPHONY_MONO),
MacroQuantizer(QUANTIZE_NONE), 
MacroChordType(CHORD_DISABLED), 
// default mode is MONO, no output quantization

MacroMIDIchannel(9),

// sensor calibration data:
CAPACITIVE_A_SensorInMax(896), // == SensorIn values fall within each sensor's native range ==
CAPACITIVE_A_SensorInMin(127), // Capacitive sensor hardware range: 0-1023 (via MPR121.getFilteredData)
CAPACITIVE_B_SensorInMax(896),
CAPACITIVE_B_SensorInMin(127), 
GYRO_YAW_SensorInMax(-179), // == SensorIn values fall within each sensor's native range ==
GYRO_YAW_SensorInMin(179), // Yaw, pitch and roll range from -180 to 180 degrees (360 for a complete rotation)
GYRO_PITCH_SensorInMax(-179),
GYRO_PITCH_SensorInMin(179), 
GYRO_ROLL_SensorInMax(-179),
GYRO_ROLL_SensorInMin(179), 
GYRO_ACCEL_SensorInMax(0),
GYRO_ACCEL_SensorInMin(0), 
CAPACITIVE_A_MIDIOutMax(0), // MIDIOut values fall within 0-127
CAPACITIVE_A_MIDIOutMin(127), // The MIDI values are inverted to avoid having to rewrite the calibration UI
CAPACITIVE_B_MIDIOutMax(0),
CAPACITIVE_B_MIDIOutMin(127), 
GYRO_YAW_MIDIOutMax(0), 
GYRO_YAW_MIDIOutMin(127), 
GYRO_PITCH_MIDIOutMax(0),
GYRO_PITCH_MIDIOutMin(127), 
GYRO_ROLL_MIDIOutMax(0),
GYRO_ROLL_MIDIOutMin(127), 
GYRO_ACCEL_MIDIOutMax(0),
GYRO_ACCEL_MIDIOutMin(127), 
proxA_inverted(false),
proxB_inverted(false),
yaw_inverted(false),
pitch_inverted(false),
roll_inverted(false),
accel_inverted(false)
{}
};

// initialize an array of macro globals -
macro_globals macro_global[4]; 

void setup() {
if (!SD.begin(chipSelect)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
}

void loop() {
saveProgram();
loadProgram();
}

void saveProgram(){
  int structSize=0;
  dataFile = SD.open("global_test.scv", FILE_WRITE);
  for (int i=0;i<4;i++){
    structSize=sizeof(macro_global[i]);
    dataFile.seek(i*structSize);
    dataFile.write(&macro_global[i],structSize);
  }
  dataFile.close();
}

// load program from card
void loadProgram(){
  int structSize=0;
  dataFile = SD.open("global_test.scv");
  for (int i=0;i<4;i++){
    structSize=sizeof(macro_global[i]);
    dataFile.seek(i*structSize);
    dataFile.read(&macro_global[i],structSize);
  }
  dataFile.close(); 
}

Thanks again for the code help, and for sharing your project, I'd love to see where it goes from here.
 
This is the compiler error:

Arduino: 1.8.7 (Mac OS X), TD: 1.44, Board: "Teensy 3.6, Serial + MIDI, 180 MHz, Faster, US English"

SDcard_struct1: In function 'void saveProgram()':
SDcard_struct1:180: error: no matching function for call to 'File::write(macro_globals*, int&)'
dataFile.write(&macro_global,structSize);
^
In file included from /Users/mos6502/Documents/Arduino/overthink.exe/Library_Test/SDcard_struct1/SDcard_struct1.ino:1:0:
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/libraries/SD/SD.h:38:18: note: candidate: virtual size_t File::write(uint8_t)
virtual size_t write(uint8_t);
^
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/libraries/SD/SD.h:38:18: note: candidate expects 1 argument, 2 provided
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/libraries/SD/SD.h:39:18: note: candidate: virtual size_t File::write(const uint8_t*, size_t)
virtual size_t write(const uint8_t *buf, size_t size);
^
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/libraries/SD/SD.h:39:18: note: no known conversion for argument 1 from 'macro_globals*' to 'const uint8_t* {aka const unsigned char*}'
In file included from /Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/Stream.h:24:0,
from /Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/HardwareSerial.h:252,
from /Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/WProgram.h:46,
from /var/folders/rw/534kw6kd6hs6chfgmjmjv79r0000gn/T/arduino_build_431010/pch/Arduino.h:6:
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/Print.h:63:9: note: candidate: size_t Print::write(const char*, size_t)
size_t write(const char *buffer, size_t size) { return write((const uint8_t *)buffer, size); }
^
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/Print.h:63:9: note: no known conversion for argument 1 from 'macro_globals*' to 'const char*'
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/Print.h:59:9: note: candidate: size_t Print::write(const char*)
size_t write(const char *str) { return write((const uint8_t *)str, strlen(str)); }
^
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/Print.h:59:9: note: candidate expects 1 argument, 2 provided
no matching function for call to 'File::write(macro_globals*, int&)'


Pretty sure the function doesn't like the input. Had been trying memcpy just before this in an attempt to serialize all of it a little earlier on...
 
Thanks :) The sequencer is pretty much finished, I recently added a step record mode (also multi channel) and the serial MIDI ports are also working.

I used the SDFat library in my code:
Code:
#include <SPI.h>
#include "SdFat.h"
..
SdFatSdioEX SD;
File dataFile;

Perhaps this could explain the errors you're getting.
 
My barebones example seems to be writing/reading files now, after re-aligning my library code around SDfat. There are 3 libraries I'd downloaded between the shuffle of example methods tried - the PJRC SD library, SDfat, SDfat2 and then there's the inbuilt IDE libraries and I think I managed to mix something up (there is a lot of overlap and I may have inadvertently merged something).

I'm about to clean it all up and make sure this still compiles, but I think I'm on the right track now. Thanks for your help.

Do you have any future plans for the sequencer? Or is it going to be one of a kind (at least for anyone who chooses to build one from your code/schematics/PCBs)?


The controller I'm working on translates its MIDI output from gyro/mag/accelerometer readings (yaw/pitch/roll/accel), plus capacitive sensors, an additional magnetometer meant for detecting localized magnetic fields (like a magnet being held or waved around on something) and possibly laser distance sensing. The lidar is still R&D, might not go there since it adds a lot of cost for an unstable beam/scan effect (mostly just for fun when paired with some visible lasers in a dark room; might reserve it for the one I build for myself alone vs. what I build for others, hehe) - the core of this is essentially yaw/pitch/roll plus a bank of capacitive sensors.

Short term goals (everything here works or almost works today) -
- 4 configurable macros x8 sensor mappings each,
- configurable in multiple ways, i.e. mono/duo/poly/chord output mixed with CC
- all macros can run simultaneously (well, until you blow up the MIDI bus with data!)
- selectable calibration zones for motion, assignable to output types
- MIDI data fed by single or composite sensor data
- encoder driver UI for configuration (on a small OLED screen)
- USB, DIN and wireless MIDI output (RFM69 based)
- 3D printed chassis/straps
- sensory feedback (LEDs + haptic) for interpreting motion and space as not to leave the user blindly waving about
- save/load sensor to output mappings, composite macro types, independent calibration zones (which you helped along :) )

Interim goals :
- convert from protoboard to PCB
- develop better/smarter translation algorithms for motion that are more in tune with musical expression and human expectation
around the relationships of motion and sound (tough since I'm no PhD but I brute force my way through until I find what works)
- onboard sequencer for recording this sensor translated MIDI, powerful enough to compose music with, not just play in the moment, and the sequencer integrates with active sensor input in a user-configurable manner (macros can be switched from sensor input translation to playback modes)

Long term goals:
- integration with other wearable devices that can interact with it
- audio/DSP mode, where the controller becomes an FX processor
- the final summit of the mountain kind of goal, is to eventually take all of this and make the whole device a digital synthesizer in its own right, fully self-contained, but that is far off and I will first stay focused on making a complete MIDI controller

This began as something I wanted to create, to make me feel like I've got sorcery powers while on stage (childhood dreams! you ought to see it connected to a MIDI to DMX conversion board, now that is fun...). But it has grown in scope into a pretty serious project that hopes to succeed in ways previous attempts at similar control methods have either failed, or have been too clunky and unintuitive to use for anything beyond noise/experimentation - there are many demo-like projects out there involving MIDI and audio, and some are very cool, but not many advance to the stage of being honestly and seriously useful in pushing forward the boundaries of musical expression in ways beyond novelty.

So there are my lofty egomaniacal goals that I might or might not be able to achieve, who knows, I've been working hard on it for about a year and a half. I found I really enjoy this stuff so that carries me along. With a lot of it working now as-is, I am primarily focused on how WELL it works at this stage, adding bells and whistles, etc.

Also... Big thanks to Paul S. for creating the Teensy, the essential core of the hardware without which I couldn't even come close to achieving these goals so far. I must also thank Bare Conductive for their Touch Board, that is where this began and I got the idea that "yes I can do this" - here is an old video of a song that includes the initial proof of concept Touch Board version of the controller, very simple and entirely separate at this point from the current Teensy 3.6-based version (hadn't even added the gyro yet).
*You may have to dig around to find the parts where I use the controller, unless you like the song :) -

It's occurred to me I could post all of this elsewhere in the forum to put the project out there, but I'm not worried about it for now... cut&pasted for later when I have more time to pay attention to details...
 
That's quite an ambitious project, good luck pulling it off!

Do you have any future plans for the sequencer? Or is it going to be one of a kind (at least for anyone who chooses to build one from your code/schematics/PCBs)?

Currently it's one of a kind and there are no plans to turn it into a product. The current version is designed to be DIY friendly and turning it into a product would require some serious redesigning beyond my capabilities.
 
If you're interested in discussing it, what are the barriers to turning it into a product, that you consider insurmountable (at least without more effort than perhaps seems worth it)? Just curious since I'm not sure what the ultimate endgame is for my device yet.
 
A little more testing later and everything is working great within the main code. Mixing up the libraries probably broke a couple of different methods I’d tried.
 
If you're interested in discussing it, what are the barriers to turning it into a product, that you consider insurmountable (at least without more effort than perhaps seems worth it)? Just curious since I'm not sure what the ultimate endgame is for my device yet.

I think there'd be no, or a very limited, market for it so it wouldn't make sense. In it's current incarnation the sequencer is large and very expensive, the motor faders, switches and displays add up very quickly and currently the market is focussed on small and inexpensive devices.

A little more testing later and everything is working great within the main code. Mixing up the libraries probably broke a couple of different methods I’d tried.

Good to hear that you were able to fix the problem.
 
I tried something similar yesterday (storing / recalling the state of a sequencer to SD card) and decided to use the arduinoJson library for this (https://arduinojson.org/). I think using a json format for storage has some advantages over dumping whole structs as binary data:

- easy to debug, as you can read your stored data
- full control over which variables of a class/struct you want to persist, and which you might want to skip
- i guess its easier to handle changes in the data-structure (forward/backwards compatibility), i.e. you will still be able to load a saved state, even after your struct has changed.

On the downside, its probably nowhere near as efficient/fast as the approach mentioned above.
 
Status
Not open for further replies.
Back
Top