Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 3 of 3

Thread: Writing and reading large struct to and from SD card

  1. #1
    Senior Member
    Join Date
    Jan 2017
    Location
    Maastricht
    Posts
    118

    Writing and reading large struct to and from SD card

    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:

    Click image for larger version. 

Name:	sequencer-test-setup.jpg 
Views:	12 
Size:	125.5 KB 
ID:	14218

    The complete code for the master Teensy: 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?

  2. #2
    Senior Member
    Join Date
    Jan 2017
    Location
    Maastricht
    Posts
    118
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •