I've come with some code for storing a struct to SD card and reading it again, it al seems to work but I can't believe that it's really this simple.
The context is a midi step & pattern sequencer I'm working on. Here's a picture of the test setup:
The complete code for the master Teensy: View attachment SPS-8_Master_180712.zip
The motor fader panel is controlled by a separate Teensy.
The data for the channels, patterns and steps are stored in structs:
The toplevel is a channel. The number of channels , patterns and step does not change. The SDFat library is used:
For saving and loading I came up with this:
The output on the serial monitor:
Am I missing something? Is it really this simple?
The context is a midi step & pattern sequencer I'm working on. Here's a picture of the test setup:
The complete code for the master Teensy: View attachment SPS-8_Master_180712.zip
The motor fader panel is controlled by a separate Teensy.
The data for the channels, patterns and steps are stored in structs:
Code:
#define MIN_TEMPO 10 // Minimum allowed tempo
#define MAX_TEMPO 250 // Maximum tempo setting
#define MAX_STEPS 8 // number of steps in pattern
#define MAX_PATTERNS 8 // number of patterns in program
#define NUM_OF_PARAMETERS 6 // number of step paramaters
#define NUM_OF_CHANNELS 4 // number of sequencer channels
// type enumerations
enum stepParameters{NOTE,VELOCITY,LENGTH,POSITION,CTRLA,CTRLB}; // enumeration of all step parameters
enum controlTypes {NONE,CTRL,NRPN,MWISYSEX,TEMPODIV}; // control types
// struct for sequencer step
struct zeusStep{
int value[NUM_OF_PARAMETERS]; // array for parameter values, order as in enum stepParameters. Type is int to handle NRPN control values
bool active; // boolean indicating if step is active i.e. the note should be played
};
typedef struct zeusStep ZeusStep;
struct zeusControllerEvent{
int value;
bool smoothing; // indicates if smoothing is active for this controller
unsigned int smoothInterval; // smoothing interval in milliseconds for controller smoothing
unsigned int smoothCount; // number of smoothing events
int smoothStep; // step value, one if possible, larger there is not enough time to the next step
elapsedMillis smoothingTimer; // timer for controller smoothing events
};
typedef struct zeusControllerEvent ZeusControllerEvent;
// controller definition
struct zeusController{
char name[16]={' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; // name of definition e.g. Prophet LP Cutoff, OB-6 filter mode etc.
byte type; // controller type, can be CTRL, NRPN, MWISYSEX etc.
int number; // controller number, int type for NRPN handling
int minValue; // minimum allowed value
int maxValue; // maximum allowed value
byte midiChannel; // midi channel for controller
bool smoothing; // smoothing of controller values by interpolation between steps, default is false
};
typedef struct zeusController ZeusController;
// struct for sequencer pattern
struct zeusPattern{
ZeusStep step[MAX_STEPS]; // array containing steps
byte repeat; // number of times pattern will be repeated
byte next; // next pattern to be played after last repeat
int length; // number of steps in pattern
float tempoDevide; // tempo devision, for half, quarter, π etc. and also 0.5 for double
byte noteMidiChannel; // midi channel for note events
ZeusController control[2]; // controller definitions for pattern
};
typedef struct zeusPattern ZeusPattern;
// struct for holding data for next step event
struct zeusStepEvent{
int pitch; // pitch is required for sending correct note off if step data is transposed in the meantime
int velocity; // velocity value
int length; // length in percent, used for legato play
int position; // position
ZeusControllerEvent controlEvent[2]; // control events
int pattern; // pattern to handle pattern switching
bool playing; // note is playing and waiting for note off
bool pending; // note is pending timed note on event, independent of playing to support legato play
unsigned long noteOnTime; // planned time in milliseconds for note off event,used for active event if event is between clock ticks
unsigned long noteOffTime; // planned time in milliseconds for note off event,used for active event
};
typedef struct zeusStepEvent ZeusStepEvent;
// struct for sequencer channel
struct zeusChannel{
ZeusPattern pattern[MAX_PATTERNS]; // array of patterns
byte currentPattern; // current (playing) pattern
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
bool active; // indicates if channel is active or muted
} ;
typedef struct zeusChannel ZeusChannel;
int selectedParameter=NOTE;
int selectedChannel=0;
int selectedPattern=0;
ZeusChannel sequencerChannel[NUM_OF_CHANNELS];
float sequencerTempo=120; // tempo as float
float externalTempo; // tempo of incoming midisync
unsigned long sixteenthNote; // length of a sixteenth note in milliseconds
int meterNumerator;
int meterDenominator;
bool sequencerRunning=false; // status of sequencer
elapsedMillis midiTimer; // timer for midi clock, used to calculate tempo
bool externalSync; // true is there is a midi clock signal
int externalSyncCounter; // needed because midi beat clock stabilises after the first two ticks
bool waitForClock=false; // used to start sequencer at first incoming clock event
elapsedMicros internalTimer; // internal clock tick timer
unsigned int tickInterval; // tick interval in microseconds. Set by internal tempo or external clock, used for shift and length calculations
unsigned int clockTick=0; // clock tick counter, 96 ticks per note
The toplevel is a channel. The number of channels , patterns and step does not change. The SDFat library is used:
Code:
#include "SdFat.h"
SdFatSdioEX SD;
File dataFile;
For saving and loading I came up with this:
Code:
void saveProgram(){
int structSize=0;
int result;
// open file for writing
dataFile = SD.open("TEST.DAT", FILE_WRITE);
for (int i=0;i<NUM_OF_CHANNELS;i++){
structSize=sizeof(sequencerChannel[i]);
Serial.print("Size\t");
Serial.print(structSize);
result=dataFile.write(&sequencerChannel[i],structSize);
Serial.print("\tWrite\t");
Serial.println(result);
}
dataFile.close();
}
void loadProgram(){
int structSize=0;
int result;
// open file for writing
dataFile = SD.open("TEST.DAT");
for (int i=0;i<NUM_OF_CHANNELS;i++){
structSize=sizeof(sequencerChannel[i]);
Serial.print("Size\t");
Serial.print(structSize);
result=dataFile.read(&sequencerChannel[i],structSize);
Serial.print("\tRead\t");
Serial.println(result);
}
dataFile.close();
}
The output on the serial monitor:
Code:
Size 2592 Write 2592
Size 2592 Write 2592
Size 2592 Write 2592
Size 2592 Write 2592
Size 2592 Read 2592
Size 2592 Read 2592
Size 2592 Read 2592
Size 2592 Read 2592
Am I missing something? Is it really this simple?