Writing "theremin" outputs to a MIDI file on an SD card
The short answer would be &quot;yes, it is possible&quot;. There are lots of gadgets around that record MIDI. They would have microprocessors in them, and they would have something like an SD card, which itself is like EEPROM in concept.
You may possibly have problems with storing the notes in RAM, and then writing them to the SD card fast enough not to lose some. Knowing the rate at which notes would arrive, the speed of the SD card, and the type of Arduino would help to answer if your specific case was likely to work.
I wrote a sketch that reads MIDI which shows that it is certainly possible to receive MIDI data and display it. Video of it running is here.
I don't personally know the format of MIDI files, but it is well documented.
(Edit) I do now - see further down.
Writing to SD cards is well documented. Making your idea work would be a case of taking stuff (like my code to decode MIDI), code to write to SD cards, and knowing the correct format.

(Edited to add)
See Standard MIDI-File Format Spec. 1.1 for an example of producing MIDI data. Also see The MIDI File Format and the Standard MIDI File (SMF) Format
Your basic technique would need to be (as suggested by gabonator):

Write a header chunk to the file, for example:
  4D 54 68 64   // MThd
  00 00 00 06   // chunk length
  00 00         // format 0
  00 01         // one track
  00 60         // 96 pulses per quarter-note


Write a track chunk to the file (the length will be updated later)
  4D 54 72 6B   // MTrk
  00 00 00 00   // &lt;-- length to be updated later


Write instrument configurations (with time delta values of zero), as an example from that page:
  00  FF 58 04  04 02 18 08  // 4 bytes; 4/4 time; 24 MIDI clocks/click, 
                             // 8 32nd notes/ 24 MIDI clocks 
                             // (24 MIDI clocks = 1 crotchet = 1 beat)
  00  FF 51 03  07 A1 20     // 3 bytes: 500,000 µsec/ quarter note 
                             // = 120 beats/minute (500000 in hex is 0x07A120)
  00  C0 05                  // Ch.1 Program Change 5 = GM 
                             // Patch 6 = Electric Piano 2


When the player plays something, subtract the time that s/he last did something from the time now. This will give you the delta time.

Convert the time difference to ticks. From the header line above we have 96 pulses per quarter note (PPQ), and 500,000 µs (500 ms) per quarter note. Thus one second would be two quarter notes (2 beats per second, and therefore 120 beats per minute) and one second is therefore 192 ticks). Therefore the number of ticks, based on the time difference in milliseconds, would be:
  unsigned long ticks = (time_difference * 192) / 1000


Output the delta time as a variable length number, where the high order bit is set for each byte, as necessary, until what is left is &lt;= 0x7F. The page above gives sample code for that. For example, for a delta time of 199 ticks:
  Decimal time of 199 ticks -&gt; 0xC7
  0xC7 -&gt; 0b11000111
  Since that exceeds 127, split into two bytes:

  10000001  (the high order bits)
   ^^^^^^^  &lt;- high order bits (the MSB must be one)
  01000111  (the low-order 7 bits)
   ^^^^^^^  &lt;- low order bits (the MSB must be zero)

  Result is 0x81 0x47

Longer times may require even more bytes.
Something like this should do it (adapted from the linked page). Just change the Serial.write line to output to your disk file.
  void WriteVarLen (unsigned long value)
    {
    unsigned long buffer;

    buffer = value &amp; 0x7f;
    while ((value &gt;&gt;= 7) &gt; 0)
      {
      buffer &lt;&lt;= 8;
      buffer |= 0x80;
      buffer |= value &amp; 0x7f;
      }

    while (true)
      {
      Serial.write (buffer &amp; 0xFF);
      if (buffer &amp; 0x80)
        buffer &gt;&gt;= 8;
      else
        break;
      }
    }   // end of WriteVarLen


Follow the delta time immediately by the &quot;note on&quot; or &quot;note off&quot; byte (depending on whether they started or stopped playing a note). For example, &quot;note on&quot; for channel 1 would be 0x90 because 0x9n is a note on command and n is the channel number, zero relative. A &quot;note off&quot; for channel 1 would be 0x80.

Follow the &quot;note on&quot; or &quot;note off&quot; by the note number (in other words, which note was just played or released) - one byte.
  Octave #  Note Numbers in decimal
            C   C#  D   D#  E   F   F#  G   G#  A   A#  B
  -1         0   1   2   3   4   5   6   7   8   9  10  11
  0         12  13  14  15  16  17  18  19  20  21  22  23
  1         24  25  26  27  28  29  30  31  32  33  34  35
  2         36  37  38  39  40  41  42  43  44  45  46  47
  3         48  49  50  51  52  53  54  55  56  57  58  59
  4         60  61  62  63  64  65  66  67  68  69  70  71
  5         72  73  74  75  76  77  78  79  80  81  82  83
  6         84  85  86  87  88  89  90  91  92  93  94  95
  7         96  97  98  99 100 101 102 103 104 105 106 107
  8        108 109 110 111 112 113 114 115 116 117 118 119
  9        120 121 122 123 124 125 126 127

Typically middle C is considered to be C4 (C in the 4th octave), however apparently that can vary between devices.

Then output the velocity (one byte). A velocity of (decimal) 32 would be soft, 64 would be medium, 96 would be loud. The range for velocities is 0 to 127.

Repeat the above (except for writing the chunks and the configuration) until the player decides to stop playing.

Write an &quot;end of track&quot; marker:
  00  FF 2F 00    // Delta time zero, end of track


Then seek back to the start of the file + where the length bytes are (18 bytes into the file by the looks of it) and update the length bytes (4 bytes).



Example of producing a MIDI file
This example code writes out a MIDI file. As an input device I used a 16-key keypad like this:

The columns were connected to pins 0, 1, 2, 3 of the Arduino Uno, and the rows to pins 4, 5, 6, 7. This makes the ribbon cable from the keypad keep the pins in a natural order. If you use different pins just change the keys array in the sketch.
The library for keypads that I used is available at https://github.com/nickgammon/Keypad_Matrix
This particular library supports n-key rollover and key-down and key-up events. That is particularly useful for music, because it lets you hold multiple keys down at once, and the appropriate events will be generated. Obviously you need a key-down event to generate &quot;note on&quot; and a key-up event to generate &quot;note-off&quot;.
I laid out the keys like this:
C     C#    D     D#
E     F     F#    G
G#    A     A#    B
Down  Soft  Up    Record


There is a &quot;recording&quot; LED connected to A0 which lights up when you start recording (by pressing the Record button).

Press the Record button again to stop recording and finalize the file. It is important to stop recording properly and not just power off the device, because the length of the file needs to be written near the start.

Press (and hold) Down alongside with another note to play an octave lower. Press (and hold) Up alongside with another note to play an octave higher.

Press (and hold) Soft alongside with another note to play at a softer velocity.


Using a keypad matrix is logical, because most keyboard devices would use a matrix, to reduce the number of wires you have to run.
Note that the matrix needs diodes wired for each switch as described here because otherwise if you press 3 or more keys at once you will get &quot;ghost&quot; keys.
In my case I opened up the commercial keypad and soldered in the diodes.

I also used a Micro-SD breakout adapter, connected to the SPI pins of the Arduino, like this:


The SDFat library was an archived one that I keep here: http://gammon.com.au/Arduino/SdFat-master.zip (2.2 Mb)
Unzip that file, and from within the folders inside it, copy the SdFat folder into your &quot;libraries&quot; folder (which is under your Arduino sketchbook folder - not inside the Arduino application folder). Then restart the Arduino IDE.
The code definitely compiles with that (with a handful of warnings). The later library from https://github.com/greiman/SdFat may be an improvement but I haven't tested that.

To keep track of file numbers I used the EEPROMAnything library, a copy of which can be found at http://gammon.com.au/Arduino/EEPROMAnything.zip
Each time you start a new recording it adds one to the file number, which wraps around to 0000 once it reaches 9999.

If there is an error initializing the file system (eg. no SD card) then the LED blinks slowly. If the file can't be created the LED blinks rapidly.

You can choose a MIDI instrument by changing the line:
const byte PATCH_NUMBER = 4;   // electric piano 1


Code
/*
 Sketch to write to a MIDI file from an instrument.
 Author: Nick Gammon
 Date:   10th May 2018

 Copyright 2018 Nick Gammon.

 PERMISSION TO DISTRIBUTE

 Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 and associated documentation files (the &quot;Software&quot;), to deal in the Software without restriction,
 including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.


 LIMITATION OF LIABILITY

 The software is provided &quot;as is&quot;, without warranty of any kind, express or implied,
 including but not limited to the warranties of merchantability, fitness for a particular
 purpose and noninfringement. In no event shall the authors or copyright holders be liable
 for any claim, damages or other liability, whether in an action of contract,
 tort or otherwise, arising from, out of or in connection with the software
 or the use or other dealings in the software.

*/


#include &lt;Keypad_Matrix.h&gt;
#include &lt;SdFat.h&gt;
#include &lt;EEPROM.h&gt;
#include &lt;EEPROMAnything.h&gt;

// For MIDI numbers see: http://www.pjb.com.au/muscript/gm.html
const byte PATCH_NUMBER = 4;   // electric piano 1

const byte RECORDING_LED = A0;

// bottom row changes things
const char DOWN_OCTAVE  = '-';
const char SOFT         = 's';
const char UP_OCTAVE    = '+';
const char RECORD       = 'r';


// how the keypad has its keys laid out
// capital letters are for sharps
const byte ROWS = 4;
const byte COLS = 4;
const char keys[ROWS][COLS] = {
  {'c', 'C', 'd', 'D'},
  {'e', 'f', 'F', 'g'},
  {'G', 'a', 'A', 'b'},
  {DOWN_OCTAVE, SOFT, UP_OCTAVE, RECORD},  // - = down octave, s = soft, + = up octave, r = start/stop record
};

const byte colPins[COLS] = {0, 1, 2, 3}; //connect to the column pinouts of the keypad
const byte rowPins[ROWS] = {4, 5, 6, 7}; //connect to the row pinouts of the keypad

// Create the Keypad
Keypad_Matrix kpd = Keypad_Matrix( makeKeymap (keys), rowPins, colPins, ROWS, COLS );

// file system object
SdFat sd;
const byte chipSelect = SS;

// the current file we are recording to
SdFile myFile;
// are we recording right now?
bool recording = false;

const byte MIDI_META_EVENT = 0xFF;
const byte MIDI_SET_TEMPO  = 0x51;
const byte MIDI_TIME_SIGNATURE = 0x58;
const byte MIDI_END_OF_TRACK = 0x2F;
const byte MIDI_PROGRAM_CHANGE = 0xC0;
const byte MIDI_NOTE_ON = 0x90;   // channel 1
const byte MIDI_NOTE_OFF = 0x80;  // channel 1
 
struct
  {
  char magic [4]; // MThd
  uint32_t length;
  uint16_t format;
  uint16_t tracks;
  uint16_t PPQ;    // pulses per quarter note  
  } chunkHeader;

struct
  {
  char magic [4]; // MTrk
  uint32_t length;
  } trackHeader;

unsigned long trackHeaderPosition;
unsigned long timeLastAction;
int           nextFileNumber;

uint32_t changeEndianness32(uint32_t val)
{
  return (val &lt;&lt; 24) |
        ((val &lt;&lt;  8) &amp; 0x00ff0000) |
        ((val &gt;&gt;  8) &amp; 0x0000ff00) |
        ((val &gt;&gt; 24) &amp; 0x000000ff);
} // end of changeEndianness32

uint16_t changeEndianness16(uint16_t val)
{
  return (val &lt;&lt; 8) | ((val &gt;&gt; 8) &amp; 0x00ff);
} // end of changeEndianness16
    
void showError (const int delayTime)
  {
  while (true)
    {
    digitalWrite (RECORDING_LED, HIGH);
    delay (delayTime);
    digitalWrite (RECORDING_LED, LOW);
    delay (delayTime);
    }
  
  } // end of showError
  
void startRecording ()
  {
  EEPROM_readAnything (0, nextFileNumber);
  if (nextFileNumber &gt; 9999 || nextFileNumber &lt; 0)
    nextFileNumber = 0;

  char name[15];
  sprintf (name, &quot;SONG%04d.MID&quot;, nextFileNumber++);
    
  // open the file for writing
  if (!myFile.open(name, O_WRITE | O_CREAT | O_TRUNC))
    showError (100);  // fast flash - never returns

  // update EEPROM for next time
  EEPROM_writeAnything (0, nextFileNumber);

  recording = true;
  digitalWrite (RECORDING_LED, HIGH);
  memcpy (chunkHeader.magic, &quot;MThd&quot;, sizeof (chunkHeader.magic));
  chunkHeader.length = changeEndianness32 (
                       sizeof (chunkHeader.format) + 
                       sizeof (chunkHeader.tracks) +
                       sizeof (chunkHeader.PPQ));
                       
  chunkHeader.format = changeEndianness16 (0);
  chunkHeader.tracks = changeEndianness16 (1);
  chunkHeader.PPQ = changeEndianness16 (96); 
  myFile.write (&amp;chunkHeader, sizeof (chunkHeader));
  memcpy (trackHeader.magic, &quot;MTrk&quot;, sizeof (trackHeader.magic));
  trackHeader.length = 0;   // to be filled in later
  trackHeaderPosition = myFile.curPosition ();
  myFile.write (&amp;trackHeader, sizeof (trackHeader));

  struct
    {
    byte deltaTime = 0;
    byte metaCode [2] = { MIDI_META_EVENT, MIDI_TIME_SIGNATURE };
    byte length = 4;
    byte sig [2] = { 4, 2 };  // 4/4 time (denominator of 2 is 2^2)
    byte clocksPerClick = 24;  // MIDI clocks/click
    byte clocksPerBeat  = 8;
    }  timeSignature;

 struct
    {
    byte deltaTime = 0;
    byte metaCode [2] = { MIDI_META_EVENT, MIDI_SET_TEMPO };
    byte length = 3;
    byte usPerQuarterNote [3] = { 0x07, 0xA1, 0x20 };  // that is: 500000 µs per beat
    }  tempo;

 struct
    {
    byte deltaTime = 0;
    byte message =   MIDI_PROGRAM_CHANGE;
    byte patchNumber = PATCH_NUMBER;
    }  programChange;

  myFile.write (&amp;timeSignature, sizeof timeSignature);
  myFile.write (&amp;tempo, sizeof tempo);
  myFile.write (&amp;programChange, sizeof programChange);
  timeLastAction = millis ();
  } // end of startRecording
  
void stopRecording ()
  {
  struct
    {
    byte deltaTime = 0;
    byte metaCode [2] = { MIDI_META_EVENT, MIDI_END_OF_TRACK };
    byte length = 0;
    }  endOfTrack;

  // write out &quot;end of track&quot; message
  myFile.write (&amp;endOfTrack, sizeof endOfTrack);
  // find where we are
  unsigned long finalPosition = myFile.curPosition ();
  // go back to where the track header is
  myFile.seekSet (trackHeaderPosition);
  // work out how long the data was (excluding the headers)
  trackHeader.length = changeEndianness32 (finalPosition - trackHeaderPosition - sizeof (trackHeader));
  // update the file length
  myFile.write (&amp;trackHeader, sizeof (trackHeader));

  myFile.close ();
  // ensure flushed to disk
  myFile.SdBaseFile::sync();

  // no longer recording, turn off recording LED
  recording = false;
  digitalWrite (RECORDING_LED, LOW);
  } // end of stopRecording

void writeVarLen(unsigned long value)
{
  unsigned long buffer;

  buffer = value &amp; 0x7f;
  while ((value &gt;&gt;= 7) &gt; 0)
  {
    buffer &lt;&lt;= 8;
    buffer |= 0x80;
    buffer |= value &amp; 0x7f;
  } // end of while

  while (true)
  {
    myFile.write ((byte) (buffer &amp; 0xFF));
    if (buffer &amp; 0x80)
      buffer &gt;&gt;= 8;
    else
      break;
  } // end of while

}   // end of writeVarLen

// turn the keypad code into a note number
byte codeToNote (const char which)
{
  char notes [] = &quot;cCdDefFgGaAb&quot;;
  char * pos = strchr (notes, which);
  if (pos == NULL)
    return 0;
  return pos - notes + 60;  // start at middle C (C4)
} // end of codeToNote

// do a note on or note off
void doNoteAction (const char which, const byte action)
{
  // we need to work out how many milliseconds have elapsed
  unsigned long now = millis ();
  unsigned long deltaTime = now - timeLastAction;
  // remember when we did this
  timeLastAction = now;
  // write out the delta time
  writeVarLen ((deltaTime * 192) / 1000);

  // note information
  struct
    {
    byte noteOn;
    byte whichNote;
    byte velocity;
    } playNote;
    
  playNote.noteOn = action;
  playNote.whichNote = codeToNote (which);
  if (kpd.isKeyDown (DOWN_OCTAVE))
    playNote.whichNote -= 12;  // one octave down
  else if (kpd.isKeyDown (UP_OCTAVE))
    playNote.whichNote += 12;  // one octave up

  playNote.velocity = 64;      // medium velocity
  if (kpd.isKeyDown (SOFT))
    playNote.velocity = 32;    // softer
    
  myFile.write (&amp;playNote, sizeof (playNote));
} // end of doNoteAction

void startPlaying (const char which)
  {
  doNoteAction (which, MIDI_NOTE_ON);
  } // end of startPlaying

void stopPlaying (const char which)
  {
  doNoteAction (which, MIDI_NOTE_OFF);
  } // end of stopPlaying
  
void keyDown (const char which)
  {
  switch (which)
    {
    case 'c':
    case 'C':
    case 'd':
    case 'D':
    case 'e':
    case 'f':
    case 'F':
    case 'g':
    case 'G':
    case 'a':
    case 'A':
    case 'b':
      startPlaying (which);
      break;

    default:
      break;
    } // end of switch on which key    
  } // end of keyDown

void keyUp (const char which)
  {
  switch (which)
    {
    case RECORD:  
      if (recording)
        stopRecording ();
      else       
        startRecording ();
      break;

    case 'c':
    case 'C':
    case 'd':
    case 'D':
    case 'e':
    case 'f':
    case 'F':
    case 'g':
    case 'G':
    case 'a':
    case 'A':
    case 'b':
      stopPlaying (which);
      break;

    default:
      break;
    } // end of switch on which key
  } // end of keyUp

void setup() 
{
  kpd.begin ();
  kpd.setKeyDownHandler (keyDown);
  kpd.setKeyUpHandler   (keyUp);
  pinMode (RECORDING_LED, OUTPUT);

  if (!sd.begin (chipSelect, SPI_HALF_SPEED))
     showError (500);  // never returns

} // end of setup

void loop() 
{
  kpd.scan ();
} // end of loop



I am recording from a self built replica of a theremin, which is basically two ultrasonic sensor readings for volume and pitch. Is this &quot;too basic&quot; for a MIDI file type?

It looks like this could be possible by using the MIDI &quot;pitch bend&quot; message. You would need to determine where your note value was (presumably most of the time between the natural notes like C and C#) and then send a bend message or &quot;Pitch Wheel Change&quot; (0xE0 for channel 1) to modify the main note. The main note would need to be updated if you moved more than a couple of semitones away.
See:

https://en.wikipedia.org/wiki/Pitch_wheel
http://sites.uci.edu/camp2014/2014/04/30/managing-midi-pitchbend-messages/