Writing and reading large struct to and from SD card

Status
Not open for further replies.

Gerrit

Well-known member
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:

sequencer-test-setup.jpg

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?
 
Turned out that my test was to simple, I forgot to seek to the correct position.
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);
    dataFile.seek(i*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);
    dataFile.seek(i*structSize);
    result=dataFile.read(&sequencerChannel[i],structSize);
    Serial.print("\tRead\t");
    Serial.println(result);
  }
  dataFile.close();
}

Now I can restore the data after the power has been off.
 
Status
Not open for further replies.
Back
Top