Запись выходов "терменвокса" в MIDI-файл на SD-карте

Я делаю музыкальный инструмент (симулятор терменвокса). Я хочу, чтобы была возможность "записи", которая хранит громкость и ноту, которую играет пользователь, в MIDI-файл. Обратите внимание, что на самом деле это не запись звука, а только данные о частоте и громкости в определенный момент времени.

До сих пор я провел некоторые исследования и нашел, как воспроизводить MIDI-файлы (что полезно, и я планирую взять это на себя) и читать / записывать на SD-карты. Однако я не нашел источника, который позволял бы мне создавать и создавать MIDI-файлы.

Как я должен выполнить эту функцию?

Технические характеристики:

  • Я использую Arduino Nano, у меня тоже есть Uno, если это необходимо
  • SD-карта представляет собой 64-гигабайтную micro SD, вставленную в SD-адаптер, с припаянными к ней штифтами заголовка. (Я слишком дешев, чтобы купить настоящий щит)
  • Ранее это был XCSD, я отформатировал его в FAT32, чтобы он был совместим с Arduino

Также: я записываю с самодельной копии терменвокса, которая в основном представляет собой два ультразвуковых датчика громкости и высоты тона. Является ли это "слишком простым" для типа MIDI-файла? (Правка: я думаю, это слишком продвинуто)

EDIT: Этот проект был заброшен из-за отсутствия хорошего способа играть между полутонами (т.Е. На определенных частотах), что мне нужно для моего терменвокса. Хотя существует функция изгиба высоты тона, величина изгиба высоты тона приводит к различным изменениям частоты в зависимости от приемника.

Вместо этого я создам свой собственный файл для записи / воспроизведения, потому что использование метода pitch bend + note с использованием MIDI - это слишком много работы, и он все равно не будет совместим с другими устройствами.

Ответ Ника Гэммона действительно хорош для тех, кому нужно только записывать / воспроизводить определенные тона и полутона в формате MIDI.

Спасибо

, 👍1

Обсуждение

Изучите синтаксис правильно сконструированного MIDI-файла и напишите скетч Arduino для создания файла на SD-карте. Возможно, это легче сказать, чем сделать, поскольку я подозреваю, что артист редко воспроизводит музыкальную фразу одним и тем же способом дважды., @st2000

@st2000 Какая разница, если в другой раз они сыграют по-другому? Запишите, что они на самом деле играли. То, что они могли бы сыграть в другой раз, не имеет значения., @Mark Smith

Какой Arduino вы имеете в виду? В наши дни их довольно много., @Nick Gammon

Вы правы, @MarkSmith. Что касается MIDI-записей, я всегда хочу записать запись на ноты. Поэтому я постоянно думаю о том, "что такое четвертная нота". Подобно тому, как GPS вашего автомобиля привязывает вас к ближайшей дороге, несмотря на ваше фактическое местоположение. Если это здесь не нужно - вы правы - просто запишите то, что воспроизводится, и воспроизведите его., @st2000

Обновил вопрос спецификациями., @David Liao

Я просто хочу записать все, что воспроизводится., @David Liao

@st2000 Я попытался изучить синтаксис, загрузив MIDI-файл из Интернета и прочитав его с помощью текстового редактора. Текст состоял даже не из букв, так что я просто махнул на это рукой., @David Liao

Если кто-нибудь делал это раньше, я думаю, я могу потратить время, чтобы прочитать о синтаксисе MIDI. Если музыка простая, можно ли сделать это без каких-либо закодированных символов?, @David Liao

Я делал это раньше, и это совсем не сложно. Единственное, что мне потребовалось некоторое время, чтобы понять, - это то, как "время" кодируется в midi-файлах. Взгляните на это [here](https://stackoverflow.com/questions/44142513/writing-midi-file-from-scratch-using-hexadecimal) , кто-то создавал простой midi-файл с нуля в шестнадцатеричном редакторе, @gabonator

Если у вас нет интерфейса SD-карты, то я надеюсь, что вы используете переключатели уровней для линий передачи данных. Вот так: http://www.gammon.com.au/images/Arduino/Micro_SD_breakout.png Фишка справа выполняет сдвиг уровня., @Nick Gammon

@NickGammon Что именно делает сдвиг уровня? Я использую интерфейс SPI для связи и резисторы в регуляторах напряжения, чтобы снизить его до 3,3 В. Я в основном сделал это из этого: https://nathan.chantrell.net/20111128/diy-micro-sd-shield-for-arduino/, @David Liao

Сдвиг уровня предназначен для (скажем) запуска устройства 3,3 В от устройства 5 В. Ваши резисторы, действующие как делители напряжения, уменьшат напряжение 5 В до 3,3 В. Это устройство полагается на то, что Arduino распознает 3,3 В как ВЫСОКОЕ, что является небольшим пределом (минимум для ВЫСОКОГО - 3 В, если вы работаете на 5 В). Правильный регулятор уровня фактически сдвигает напряжение 3,3 В до 5 В и 5 В до 3,3 В в зависимости от того, в какую сторону распространяется сигнал. Вы можете купить их довольно дешево или приготовить их: http://www.hobbytronics.co.uk/mosfet-voltage-level-converter, @Nick Gammon

Спасибо за объяснение, я тоже займусь этим вопросом, хотя на данный момент того, что у меня есть, кажется достаточным., @David Liao


2 ответа


Лучший ответ:

5

Короткий ответ: "да, это возможно". Есть много гаджетов, которые записывают MIDI. В них должны быть микропроцессоры и что-то вроде SD-карты, которая сама по себе похожа на EEPROM.

Возможно, у вас возникли проблемы с сохранением заметок в ОЗУ и их последующей записью на SD-карту достаточно быстро, чтобы не потерять некоторые из них. Знание скорости поступления заметок, скорости SD-карты и типа Arduino поможет ответить, будет ли работать ваш конкретный случай.

Я написал скетч, который читает MIDI, который показывает, что определенно возможно получать данные MIDI и отображать его. Видео его работы находится здесь.

Я лично не знаю формат файлов MIDI, но это хорошо задокументировано.

(Редактировать) Сейчас знаю - см. ниже.

Запись на SD-карты хорошо задокументирована. Для того, чтобы ваша идея заработала, нужно взять вещи (например, мой код для декодирования MIDI), код для записи на SD-карты и знать правильный формат.


(Отредактировано для добавления)

См. Стандартную спецификацию формата MIDI-файлов. 1.1 для примера создания MIDI-данных. Также см. Формат файла MIDI и Стандартный формат MIDI-файла (SMF)

Ваша основная техника должна быть (согласно предложению gabonator):

  • Запишите фрагмент заголовка в файл, например:

      4D 54 68 64   // MThd
      00 00 00 06   // длина чанка
      00 00         // формат 0
      00 01         // одна дорожка
      00 60         // 96 импульсов на четверть
    
  • Записать фрагмент трека в файл (длина будет обновлена позже)

      4D 54 72 6B   // МТРк
      00 00 00 00   // <-- длина будет обновлена позже
    
  • Запишите конфигурации прибора (с нулевыми значениями дельты времени), например, с этой страницы:

      00  FF 58 04  04 02 18 08  // 4 байта; 4/4 раза; 24 MIDI-такта/щелчка,
                                 // 8 32-х нот/ 24 MIDI-такта
                                 // (24 MIDI-такта = 1 кроше = 1 удар)
      00  FF 51 03  07 A1 20     // 3 байта: 500 000 мкс/четверть
                                 // = 120 ударов в минуту (500000 в шестнадцатеричном формате это 0x07A120)
      00  C0 05                  // Ch.1 Изменение программы 5 = GM
                                 // Патч 6 = Электрическое пианино 2
    
  • Когда игрок что-то играет, вычтите время, когда он в последний раз что-то делал, из текущего времени. Это даст вам разницу во времени.

  • Конвертировать разницу во времени в тики. Из строки заголовка выше мы имеем 96 импульсов на четвертную ноту (PPQ) и 500 000 мкс (500 мс) на четвертную ноту. Таким образом, одна секунда будет двумя четвертными нотами (2 удара в секунду и, следовательно, 120 ударов в минуту), а одна секунда, следовательно, будет 192 такта). Следовательно, количество тиков, основанное на разнице во времени в миллисекундах, будет:

      unsigned long ticks = (time_difference * 192) / 1000
    
  • Выведите дельту времени в виде числа переменной длины, где старший бит устанавливается для каждого байта по мере необходимости, пока не останется значение <= 0x7F. На странице выше приведен пример кода для этого. Например, для разницы времени в 199 тиков:

      Decimal time of 199 ticks -> 0xC7
      0xC7 -> 0b11000111
      Since that exceeds 127, split into two bytes:
    
      10000001  (the high order bits)
       ^^^^^^^  <- high order bits (the MSB must be one)
      01000111  (the low-order 7 bits)
       ^^^^^^^  <- low order bits (the MSB must be zero)
    
      Result is 0x81 0x47
    

    Более длительное время может потребовать еще больше байтов.

    Что-то вроде этого должно сделать это (адаптировано со связанной страницы). Просто измените строку Serial.write для вывода в файл на диске.

      void WriteVarLen (unsigned long value)
        {
        unsigned long buffer;
    
        buffer = value & 0x7f;
        while ((value >>= 7) > 0)
          {
          buffer <<= 8;
          buffer |= 0x80;
          buffer |= value & 0x7f;
          }
    
        while (true)
          {
          Serial.write (buffer & 0xFF);
          if (buffer & 0x80)
            buffer >>= 8;
          else
            break;
          }
        }   // конец WriteVarLen
    
  • Немедленно следите за изменением времени по "примечанию" или "примечание выключено" байт (в зависимости от того, начали или остановили воспроизведение ноты). Например, "примечание" для канала 1 будет 0x90, потому что 0x9n — это примечание к команде, а n — номер канала, относительный ноль. "примечание выключено" для канала 1 будет 0x80.

  • Следуйте "примечанию к" или "примечание выключено" по номеру ноты (другими словами, какая нота была только что сыграна или отпущена) — один байт.

      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
    

    Обычно средним C считается C4 (C в 4-й октаве), однако очевидно, что это может варьироваться в зависимости от устройства.

  • Затем выведите скорость (один байт). Скорость (десятичная) 32 будет тихой, 64 — средней, 96 — громкой. Диапазон скоростей составляет от 0 до 127.

  • Повторяйте вышеописанное (кроме записи фрагментов и конфигурации), пока игрок не решит прекратить игру.

  • Напишите "конец трека" маркер:

      00  FF 2F 00    // Дельта времени ноль, конец трека
    
  • Затем вернитесь к началу файла +, где находится длина байтов (18 байтов в файле, судя по всему), и обновите длину байтов (4 байта).


Пример создания MIDI-файла

Этот пример кода записывает MIDI-файл. В качестве устройства ввода я использовал 16-клавишную клавиатуру вот такого вида:

Матрица клавиатуры

Столбцы были подключены к контактам 0, 1, 2, 3 Arduino Uno, а строки — к контактам 4, 5, 6, 7. Это позволяет ленточному кабелю от клавиатуры удерживать контакты в естественном порядке. Если вы используете другие контакты, просто измените массив keys в скетче.

Библиотека для клавиатур, которые я использовал, доступна по адресу https://github.com/nickgammon/Keypad_Matrix

.

Эта конкретная библиотека поддерживает одновременное нажатие n клавиш, а также события нажатия и нажатия клавиши. Это особенно полезно для музыки, поскольку позволяет одновременно удерживать несколько клавиш, и будут генерироваться соответствующие события. Очевидно, вам нужно событие нажатия клавиши, чтобы сгенерировать "заметку о" и событие нажатия клавиши для создания "отключения примечания".

Я разложил ключи так:

C     C#    D     D#
E     F     F#    G
G#    A     A#    B
Down  Soft  Up    Record
  • Есть "запись" Светодиод, подключенный к A0, который загорается, когда вы начинаете запись (нажимая кнопку записи).

  • Нажмите кнопку "Запись" еще раз, чтобы остановить запись и завершить запись файла. Важно правильно остановить запись, а не просто выключить устройство, потому что длина файла должна быть записана рядом с началом.

  • Нажмите (и удерживайте) кнопку "Вниз" рядом с другой нотой, чтобы играть на октаву ниже. Нажмите (и удерживайте) кнопку "Вверх" рядом с другой нотой, чтобы сыграть на октаву выше.

  • Нажмите (и удерживайте) кнопку Soft рядом с другой нотой, чтобы играть с меньшей скоростью.

Использование матрицы клавиатуры логично, потому что большинство клавиатурных устройств используют матрицу, чтобы уменьшить количество проводов, которые вам нужно проложить.

Обратите внимание, что матрице нужны диоды, подключенные к каждому переключателю, как описано здесь, поскольку в противном случае, если вы нажмете 3 и более ключей одновременно вы получите «призрак»; ключи.

В моем случае я вскрыл стандартную клавиатуру и впаял туда диоды.


Я также использовал переходник Micro-SD, подключенный к контактам SPI Arduino, вот так:

Адаптер Micro SD


Библиотека SDFat была заархивирована и хранится здесь: http://gammon.com.au/Arduino. /SdFat-master.zip (2,2 Мб)

Разархивируйте этот файл и из папок внутри него скопируйте папку SdFat в ваши "библиотеки" папка (которая находится в папке вашего альбома для рисования Arduino, а не в папке приложения Arduino). Затем перезапустите Arduino IDE.

Код определенно компилируется с этим (с несколькими предупреждениями). Более поздняя библиотека из https://github.com/greiman/SdFat может быть лучше, но я ее не проверял.< /p>


Для отслеживания номеров файлов я использовал библиотеку EEPROMAnything, копию которой можно найти по адресу http://gammon. .com.au/Arduino/EEPROMAnything.zip

Каждый раз, когда вы начинаете новую запись, к номеру файла добавляется единица, которая после достижения 9999 увеличивается до 0000.


Если возникла ошибка при инициализации файловой системы (например, нет SD-карты), светодиод медленно мигает. Если файл не может быть создан, светодиод быстро мигает.


Вы можете выбрать MIDI-инструмент, изменив строку:

const byte PATCH_NUMBER = 4;   // электрическое пианино 1

Код

/*
Скетч для записи в MIDI-файл с инструмента.
Автор: Ник Гэммон
Дата: 10 мая 2018 г.

Copyright 2018 Ник Гэммон.

РАЗРЕШЕНИЕ НА РАСПРОСТРАНЕНИЕ

Настоящим предоставляется бесплатное разрешение любому лицу, получающему копию этого программного обеспечения.
и связанные с ним файлы документации («Программное обеспечение») для работы с Программным обеспечением без ограничений,
включая, помимо прочего, права на использование, копирование, изменение, слияние, публикацию, распространение, сублицензирование,
и/или продавать копии Программного обеспечения, а также разрешать лицам, которым предоставляется Программное обеспечение, делать это,
при соблюдении следующих условий:

Вышеприведенное уведомление об авторских правах и это уведомление о разрешении должны быть включены в
все копии или существенные части Программного обеспечения.


ОГРАНИЧЕНИЕ ОТВЕТСТВЕННОСТИ

Программное обеспечение предоставляется «как есть», без каких-либо явных или подразумеваемых гарантий.
включая, но не ограничиваясь гарантиями товарного состояния, пригодности для конкретного
цель и ненарушение. Ни при каких обстоятельствах авторы или правообладатели не несут ответственности
за любые претензии, убытки или другую ответственность, будь то в действии контракта,
деликта или иным образом, вытекающие из, из или в связи с программным обеспечением
или использование или другие операции с программным обеспечением.

*/


#include <Keypad_Matrix.h>
#include <SdFat.h>
#include <EEPROM.h>
#include <EEPROMAnything.h>

// Номера MIDI см.: http://www.pjb.com.au/muscript/gm.html
const byte PATCH_NUMBER = 4;   // электрическое пианино 1

const byte RECORDING_LED = A0;

// нижняя строка меняет дело
const char DOWN_OCTAVE  = '-';
const char SOFT         = 's';
const char UP_OCTAVE    = '+';
const char RECORD       = 'r';


// как на клавиатуре расположены клавиши
// заглавные буквы для диезов
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},  // - = октава вниз, s = мягко, + = октава вверх, r = начать/остановить запись
};

const byte colPins[COLS] = {0, 1, 2, 3}; //подключаем к колонке распиновку клавиатуры
const byte rowPins[ROWS] = {4, 5, 6, 7}; //подключаемся к распиновке ряда клавиатуры

// Создаем клавиатуру
Keypad_Matrix kpd = Keypad_Matrix( makeKeymap (keys), rowPins, colPins, ROWS, COLS );

// объект файловой системы
SdFat sd;
const byte chipSelect = SS;

// текущий файл, в который мы записываем
SdFile myFile;
// мы записываем прямо сейчас?
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;   // канал 1
const byte MIDI_NOTE_OFF = 0x80;  // канал 1
 
struct
  {
  char magic [4]; // МТд
  uint32_t length;
  uint16_t format;
  uint16_t tracks;
  uint16_t PPQ;    // импульсов на четвертную ноту
  } chunkHeader;

struct
  {
  char magic [4]; // МТРк
  uint32_t length;
  } trackHeader;

unsigned long trackHeaderPosition;
unsigned long timeLastAction;
int           nextFileNumber;

uint32_t changeEndianness32(uint32_t val)
{
  return (val << 24) |
        ((val <<  8) & 0x00ff0000) |
        ((val >>  8) & 0x0000ff00) |
        ((val >> 24) & 0x000000ff);
} // конец измененияEndianness32

uint16_t changeEndianness16(uint16_t val)
{
  return (val << 8) | ((val >> 8) & 0x00ff);
} // конец измененияEndianness16
    
void showError (const int delayTime)
  {
  while (true)
    {
    digitalWrite (RECORDING_LED, HIGH);
    delay (delayTime);
    digitalWrite (RECORDING_LED, LOW);
    delay (delayTime);
    }
  
  } // конец showError
  
void startRecording ()
  {
  EEPROM_readAnything (0, nextFileNumber);
  if (nextFileNumber > 9999 || nextFileNumber < 0)
    nextFileNumber = 0;

  char name[15];
  sprintf (name, "SONG%04d.MID", nextFileNumber++);
    
  // открываем файл для записи
  if (!myFile.open(name, O_WRITE | O_CREAT | O_TRUNC))
    showError (100);  // быстрая вспышка - никогда не возвращается

  // обновить EEPROM для следующего раза
  EEPROM_writeAnything (0, nextFileNumber);

  recording = true;
  digitalWrite (RECORDING_LED, HIGH);
  memcpy (chunkHeader.magic, "MThd", 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 (&chunkHeader, sizeof (chunkHeader));
  memcpy (trackHeader.magic, "MTrk", sizeof (trackHeader.magic));
  trackHeader.length = 0;   // для заполнения позже
  trackHeaderPosition = myFile.curPosition ();
  myFile.write (&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 (знаменатель 2 равен 2^2)
    byte clocksPerClick = 24;  // MIDI часы/щелчок
    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 };  // то есть: 500000 мкс на такт
    }  tempo;

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

  myFile.write (&timeSignature, sizeof timeSignature);
  myFile.write (&tempo, sizeof tempo);
  myFile.write (&programChange, sizeof programChange);
  timeLastAction = millis ();
  } // конец начала записи
  
void stopRecording ()
  {
  struct
    {
    byte deltaTime = 0;
    byte metaCode [2] = { MIDI_META_EVENT, MIDI_END_OF_TRACK };
    byte length = 0;
    }  endOfTrack;

  // записываем "конец трека" сообщение
  myFile.write (&endOfTrack, sizeof endOfTrack);
  // найти, где мы находимся
  unsigned long finalPosition = myFile.curPosition ();
  // вернуться туда, где находится заголовок трека
  myFile.seekSet (trackHeaderPosition);
  // вычисляем длину данных (исключая заголовки)
  trackHeader.length = changeEndianness32 (finalPosition - trackHeaderPosition - sizeof (trackHeader));
  // обновляем длину файла
  myFile.write (&trackHeader, sizeof (trackHeader));

  myFile.close ();
  // обеспечим сброс на диск
  myFile.SdBaseFile::sync();

  // запись больше не ведется, выключите светодиод записи
  recording = false;
  digitalWrite (RECORDING_LED, LOW);
  } // конец остановки записи

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

  buffer = value & 0x7f;
  while ((value >>= 7) > 0)
  {
    buffer <<= 8;
    buffer |= 0x80;
    buffer |= value & 0x7f;
  } // конец времени

  while (true)
  {
    myFile.write ((byte) (buffer & 0xFF));
    if (buffer & 0x80)
      buffer >>= 8;
    else
      break;
  } // конец времени

}   // конец записиVarLen

// превращаем код клавиатуры в номер ноты
byte codeToNote (const char which)
{
  char notes [] = "cCdDefFgGaAb";
  char * pos = strchr (notes, which);
  if (pos == NULL)
    return 0;
  return pos - notes + 60;  // начинаем с середины C (C4)
} // конец codeToNote

// делаем пометку включенной или выключенной
void doNoteAction (const char which, const byte action)
{
  // нам нужно вычислить, сколько миллисекунд прошло
  unsigned long now = millis ();
  unsigned long deltaTime = now - timeLastAction;
  // помним, когда мы это делали
  timeLastAction = now;
  // записываем дельту времени
  writeVarLen ((deltaTime * 192) / 1000);

  // примечание к информации
  struct
    {
    byte noteOn;
    byte whichNote;
    byte velocity;
    } playNote;
    
  playNote.noteOn = action;
  playNote.whichNote = codeToNote (which);
  if (kpd.isKeyDown (DOWN_OCTAVE))
    playNote.whichNote -= 12;  // на одну октаву вниз
  else if (kpd.isKeyDown (UP_OCTAVE))
    playNote.whichNote += 12;  // на одну октаву вверх

  playNote.velocity = 64;      // средняя скорость
  if (kpd.isKeyDown (SOFT))
    playNote.velocity = 32;    // мягче
    
  myFile.write (&playNote, sizeof (playNote));
} // конец doNoteAction

void startPlaying (const char which)
  {
  doNoteAction (which, MIDI_NOTE_ON);
  } // конец начала воспроизведения

void stopPlaying (const char which)
  {
  doNoteAction (which, MIDI_NOTE_OFF);
  } // конец остановки воспроизведения
  
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;
    } // конец переключателя, на какой клавише
  } // конец 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;
    } // конец переключателя, на какой клавише
  } // конец keyUp

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

  if (!sd.begin (chipSelect, SPI_HALF_SPEED))
     showError (500);  // никогда не возвращается

} // конец настройки

void loop() 
{
  kpd.scan ();
} // конец цикла

Я записываю с самодельной копии терменвокса, который в основном представляет собой показания двух ультразвуковых датчиков для громкости и высоты тона. Это "слишком просто"? для типа файла MIDI?

Похоже, это возможно с помощью MIDI "изменения высоты тона" сообщение. Вам нужно будет определить, где находится значение вашей ноты (предположительно большую часть времени между естественными нотами, такими как C и C#), а затем отправить сообщение о бэнде или "Изменение колеса высоты тона" (0xE0 для канала 1), чтобы изменить основную ноту. Основную ноту нужно будет обновить, если вы переместите ее более чем на пару полутонов.

См.:

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

В моем проекте на самом деле ничего не подключено к MIDI-порту; это просто чтение/запись в файлы., @David Liao

Хотя то, что ты сделал, действительно круто, @David Liao

Ах хорошо. Так что мой ответ на самом деле не касается вашего вопроса. Мне пришла в голову мысль, что ваше устройство выводит MIDI-коды и вы хотите превратить их в файл на диске., @Nick Gammon

Я обновил свой ответ, чтобы дать более подробную информацию о том, как вы можете это сделать., @Nick Gammon

Я обновил свой ответ, чтобы привести пример полного скетча, который делает это., @Nick Gammon

Ух ты. Такого развёрнутого ответа я никак не ожидал. Дайте мне несколько дней, чтобы все понять. У меня ограниченный опыт во всем этом, но я постараюсь понять, смогу ли я это понять!, @David Liao

Итак, я думаю, что понимаю, как работает синтаксис MIDI. На терменвоксе нота всегда будет включена, так что байт всегда будет равен 0x90, верно?, @David Liao

Я читаю об изменении высоты тона, а также пытаюсь понять, как преобразовать значение частоты в ноту + изменение высоты тона ... также где мне написать байт изменения высоты тона - после байта номера ноты?, @David Liao

Нет, это две разные команды. 0x90 - "примечание". Не было бы смысла отправлять много «заметок», если бы они не предназначались для разных заметок. А питчбенд — это отдельная команда. Если бы диапазон высоты тона был достаточно большим, вы могли бы просто сделать одну «ноту на», а затем просто подтянуть ее вверх или вниз по мере движения руки, но диапазон может быть слишком мал для этого. Он может просто изгибаться от C к D. Я думаю, вам придется поэкспериментировать с этим. Планируете ли вы воспроизвести это на другом MIDI-устройстве или просто воспроизвести на своем терамине?, @Nick Gammon

Ультразвуковые датчики не очень хороши, поэтому нота постоянно колеблется, даже если рука, управляющая ею, не двигается. Я все равно хочу, чтобы он все равно их записывал, так почему же не уместно отправлять много «примечаний»? Частота обновления ультразвуковых датчиков довольно высокая, я бы предположил, что около 15 мс на ноту., @David Liao

Во второй ссылке, которую вы мне дали, говорится: «Величина изменения высоты тона, вызванная значением высоты тона, определяется принимающим устройством». Так что, я думаю, мне придется поэкспериментировать, чтобы увидеть, насколько одно значение высоты тона изменяет частоту, но, вероятно, оно не сможет продвинуться на несколько октав, на что я надеялся., @David Liao

Если значение изменения высоты тона преобразуется в различные изменения частоты в зависимости от типа воспроизводимого приемника, то это противоречит цели использования MIDI для записи и воспроизведения. Я думаю, что мог бы просто использовать какой-то пользовательский формат файла, так как он все равно не будет совместим с другими инструментами..., @David Liao

Да, для записи и воспроизведения на вашем собственном устройстве пользовательский формат файла был бы намного проще. Вы могли бы просто записать необработанные данные с датчиков (и разницу во времени). Затем, когда они переходят к воспроизведению, заставьте код думать, что эти необработанные данные только что были получены, и он должен воспроизвести их идеально., @Nick Gammon

Этот ответ - чистое золото. Я только что построил сквозной MIDI-рекордер, который находится между устройством с миди-выходом (например, новаторским ключом запуска) и устройством с миди-входом (например, аудиоинтерфейсом ASIO), так что даже если вы не используете что-то, что приходит с «постоянной записью», такой как FL Studio или Ableton, вы не потеряете тот сладкий рифф, который вы дурачили час назад. Для простоты я записывал данные в виде файла CSV, а не в формате .mid, но благодаря вашему ответу, который сегодня изменился., @Mike 'Pomax' Kamermans


3

Да, прочитайте спецификацию формата MIDI-файла. Создайте midi-файл с одной дорожкой и добавьте только команды note press / release / pitch или volume changes и delta times (здесь объясняется обратное преобразование delta times в миллисекунды). Когда вы закончите запись, просто найдите начало файла и обновите значение длины дорожки (общий размер добавленного буфера midi-команд).

,