├── .gitignore ├── README.md ├── stringcatalog.h ├── stringcatalog.c ├── CMakeLists.txt ├── miditest.c ├── main.c ├── mod.h ├── .github └── workflows │ └── cmake-multi-platform.yml ├── midi.h ├── mod.c └── midi.c /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeFiles 2 | CMakeCache.txt 3 | cmake_install.cmake 4 | Makefile 5 | midi2mod 6 | *.mod 7 | *.midi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | midi2mod 3 | 4 | A utility to convert midi files to MOD files. 5 | 6 | The following contributions warranted legal paper exchanges with the Free Software Foundation. Also see files ChangeLog and THANKS. 7 | 8 | Heath Caldwell hncaldwell@csupomona.edu -------------------------------------------------------------------------------- /stringcatalog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * stringcatalog.h 3 | * 4 | */ 5 | 6 | #ifndef STRINGCATALOG_H 7 | #define STRINGCATALOG_H 8 | 9 | typedef struct { 10 | int key; 11 | char *value; 12 | } StringEntry; 13 | 14 | const char *get_string(int key, const StringEntry *catalog, size_t catalog_length); 15 | 16 | #endif /* STRINGCATALOG_H */ 17 | 18 | -------------------------------------------------------------------------------- /stringcatalog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * stringcatalog.c 3 | * 4 | */ 5 | 6 | #include 7 | 8 | #include "stringcatalog.h" 9 | 10 | const char * 11 | get_string(int key, const StringEntry *catalog, size_t catalog_length) 12 | { 13 | size_t i; 14 | for (i = 0; i < catalog_length; i++) { 15 | if (catalog[i].key == key) { 16 | return catalog[i].value; 17 | } 18 | } 19 | 20 | return NULL; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(midi2mod LANGUAGES C) 4 | 5 | set(MIDI_TEST OFF) 6 | 7 | if(MIDI_TEST) 8 | add_executable(midi2mod miditest.c midi.h midi.c stringcatalog.h stringcatalog.c mod.h mod.c) 9 | else() 10 | add_executable(midi2mod main.c midi.h midi.c stringcatalog.h stringcatalog.c mod.h mod.c) 11 | endif() 12 | 13 | if(UNIX) 14 | set(CMAKE_C_FLAGS "-lm") 15 | target_link_libraries(midi2mod m) 16 | endif() 17 | 18 | if(WIN32) 19 | target_link_libraries(midi2mod wsock32 ws2_32) 20 | endif() 21 | -------------------------------------------------------------------------------- /miditest.c: -------------------------------------------------------------------------------- 1 | /* 2 | * miditest.c 3 | * 4 | * Heath Caldwell 5 | * hncaldwell@csupomona.edu 6 | * 7 | */ 8 | 9 | #include "midi.h" 10 | 11 | int main(int argc, char **argv) 12 | { 13 | Midi midi; 14 | FILE *infile = fopen(argv[1], "rb"); 15 | int i, j; 16 | 17 | int read_status = read_midi_from_file(&midi, infile); 18 | fclose(infile); 19 | 20 | printf("Header: Format %d, %d tracks, division = %d.\n", midi.format, midi.num_tracks, midi.division); 21 | for(i=0; i < midi.num_tracks; i++) { 22 | if(midi.tracks[i]) { 23 | printf("\nTrack: %d events.\n", midi.tracks[i]->num_events); 24 | 25 | for(j=0; j < midi.tracks[i]->num_events; j++) { 26 | MidiEvent *event = midi.tracks[i]->events[j]; 27 | 28 | print_midi_event(stdout, event); 29 | } 30 | } 31 | } 32 | 33 | destroy_midi(&midi); 34 | 35 | return 0; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "midi.h" 3 | #include "mod.h" 4 | 5 | int main(int argc, char **argv) 6 | { 7 | char* outfile_name = "test.mod"; 8 | if (argc > 2) { 9 | outfile_name = argv[2]; 10 | } 11 | 12 | FILE* infile; 13 | FILE* outfile; 14 | 15 | #ifdef _WIN32 16 | fopen_s(&infile, argv[1], "rb"); 17 | fopen_s(&outfile, outfile_name, "wb"); 18 | #else 19 | infile = fopen(argv[1], "rb"); 20 | outfile = fopen(outfile_name, "wb"); 21 | #endif 22 | 23 | 24 | Midi midi; 25 | if (read_midi_from_file(&midi, infile)) { 26 | fclose(infile); 27 | return 1; 28 | } 29 | fclose(infile); 30 | 31 | 32 | Mod mod; 33 | int i; 34 | 35 | midi_to_mod(&mod, &midi); 36 | 37 | for (i=0; i<128; i++) { 38 | if (midi.patches[i].used) { 39 | printf("%d:\t%s\tmin: %d, max: %d\n", i, MIDI_PATCH_NAMES[i], midi.patches[i].min, midi.patches[i].max); 40 | } 41 | } 42 | 43 | write_mod_file(&mod, outfile); 44 | 45 | destroy_midi(&midi); 46 | 47 | fclose(outfile); 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /mod.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mod.h 3 | * 4 | */ 5 | 6 | #ifndef MOD_H 7 | #define MOD_H 8 | 9 | #include 10 | 11 | #include "midi.h" 12 | 13 | #define EF_VOLUME 0x0C 14 | #define EF_TEMPO 0x0F 15 | 16 | static const int PERIOD[128] = 17 | { 18 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | 1712,1616,1525,1440,1357,1281,1209,1141,1077,1017, 961, 907, 21 | 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, 22 | 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, 23 | 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113, 24 | 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0}; 29 | 30 | typedef struct { 31 | char name[23]; 32 | uint16_t length; // In words. 33 | int8_t fine_tune; // -8 to 7, 1/8ths of a semitone up or down. 34 | uint8_t volume; // 0 to 64, decibels = 20*log10(volume/64). 35 | uint16_t repeat_offset; // In words. 36 | uint16_t repeat_length; // In words. Only loops if this is greater than 1. 37 | } ModSample; 38 | 39 | typedef struct { 40 | uint16_t period; 41 | uint8_t sample; 42 | uint8_t effect; 43 | uint8_t effect_x; 44 | uint8_t effect_y; 45 | } ModCommand; 46 | 47 | typedef struct { 48 | ModCommand data[8][64]; // [channel][division] 49 | } ModPattern; 50 | 51 | typedef struct { 52 | char title[20]; 53 | ModSample *samples[32]; 54 | uint8_t song_length; // Number of patterns that the song consists of. 55 | uint8_t pattern_table[128]; 56 | uint8_t num_channels; 57 | 58 | uint8_t num_patterns; 59 | ModPattern patterns[128]; 60 | } Mod; 61 | 62 | typedef struct { 63 | long int time; // Absolute time of event. 64 | MidiEvent *event; 65 | } AbsoluteMidiEvent; 66 | 67 | int compare_absolute_midi_event(const void *a, const void *b); 68 | int midi_to_mod(Mod *, const Midi *); 69 | int write_mod_file(Mod *, FILE *); 70 | 71 | #endif /* MOD_H */ 72 | -------------------------------------------------------------------------------- /.github/workflows/cmake-multi-platform.yml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml 3 | name: CMake on multiple platforms 4 | 5 | on: 6 | push: 7 | branches: [ "master" ] 8 | pull_request: 9 | branches: [ "master" ] 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. 17 | fail-fast: false 18 | 19 | # Set up a matrix to run the following 3 configurations: 20 | # 1. 21 | # 2. 22 | # 3. 23 | # 24 | # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. 25 | matrix: 26 | os: [ubuntu-latest, windows-latest] 27 | build_type: [Release] 28 | c_compiler: [gcc, clang, cl] 29 | include: 30 | - os: windows-latest 31 | c_compiler: cl 32 | cpp_compiler: cl 33 | - os: ubuntu-latest 34 | c_compiler: gcc 35 | cpp_compiler: g++ 36 | - os: ubuntu-latest 37 | c_compiler: clang 38 | cpp_compiler: clang++ 39 | exclude: 40 | - os: windows-latest 41 | c_compiler: gcc 42 | - os: windows-latest 43 | c_compiler: clang 44 | - os: ubuntu-latest 45 | c_compiler: cl 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | 50 | - name: Set reusable strings 51 | # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. 52 | id: strings 53 | shell: bash 54 | run: | 55 | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" 56 | 57 | - name: Configure CMake 58 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 59 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 60 | run: > 61 | cmake -B ${{ steps.strings.outputs.build-output-dir }}-${{ matrix.os }}-${{ matrix.build_type }} 62 | -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} 63 | -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} 64 | -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} 65 | -S ${{ github.workspace }} 66 | 67 | - name: Build 68 | # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). 69 | run: cmake --build ${{ steps.strings.outputs.build-output-dir }}-${{ matrix.os }}-${{ matrix.build_type }} --config ${{ matrix.build_type }} 70 | 71 | - name: Test 72 | working-directory: ${{ steps.strings.outputs.build-output-dir }}-${{ matrix.os }}-${{ matrix.build_type }} 73 | # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). 74 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 75 | run: ctest --build-config ${{ matrix.build_type }} 76 | 77 | 78 | 79 | - name: Upload a Build Artifact 80 | uses: actions/upload-artifact@v4.3.6 81 | with: 82 | name: midi2mod-${{ matrix.os }}-${{ matrix.c_compiler }} 83 | path: ${{ steps.strings.outputs.build-output-dir }}-${{ matrix.os }}-${{ matrix.build_type }} 84 | 85 | -------------------------------------------------------------------------------- /midi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * midi.h 3 | * 4 | * Heath Caldwell 5 | * hncaldwell@csupomona.edu 6 | * 7 | */ 8 | 9 | #ifndef MIDI_H 10 | #define MIDI_H 11 | 12 | #include 13 | #include 14 | 15 | #include "stringcatalog.h" 16 | 17 | #define MIDI_EVENT 0x01 18 | #define MIDI_EVENT_SYSEX 0x02 19 | #define MIDI_EVENT_META 0x03 20 | 21 | /* Midi Event Commands */ 22 | 23 | #define MIDI_NOTEOFF 0x80 24 | #define MIDI_NOTEON 0x90 25 | #define MIDI_KEYAFTERTOUCH 0xA0 26 | #define MIDI_CONTROLCHANGE 0xB0 27 | #define MIDI_PATCHCHANGE 0xC0 28 | #define MIDI_CHANNELAFTERTOUCH 0xD0 29 | #define MIDI_PITCHWHEEL 0xE0 30 | 31 | #define MIDI_META 0xFF 32 | #define MIDI_META_SEQUENCENUMBER 0x00 33 | #define MIDI_META_TEXT 0x01 34 | #define MIDI_META_COPYRIGHT 0x02 35 | #define MIDI_META_TRACKNAME 0x03 36 | #define MIDI_META_INSTRUMENTNAME 0x04 37 | #define MIDI_META_LYRIC 0x05 38 | #define MIDI_META_MARKER 0x06 39 | #define MIDI_META_CUEPOINT 0x07 40 | #define MIDI_META_ENDTRACK 0x2F 41 | #define MIDI_META_SETTEMPO 0x51 42 | #define MIDI_META_TIMESIGNATURE 0x58 43 | #define MIDI_META_KEYSIGNATURE 0x59 44 | #define MIDI_META_SEQUENCERINFO 0x7F 45 | 46 | #define MIDI_SYSEX 0xF0 47 | #define MIDI_SYSEX_LITERAL 0xF7 48 | 49 | /* */ 50 | 51 | extern const char *MIDI_NOTE_STRING[]; 52 | #define midi_note_string(x) MIDI_NOTE_STRING[(x)] 53 | 54 | extern const int MIDI_EVENT_COMMAND_STRINGS_LENGTH; 55 | extern const StringEntry MIDI_EVENT_COMMAND_STRINGS[]; 56 | extern const int MIDI_META_COMMAND_STRINGS_LENGTH; 57 | extern const StringEntry MIDI_META_COMMAND_STRINGS[]; 58 | #define get_midi_event_command_string(x) get_string((x), MIDI_EVENT_COMMAND_STRINGS, MIDI_EVENT_COMMAND_STRINGS_LENGTH) 59 | #define get_midi_meta_command_string(x) get_string((x), MIDI_META_COMMAND_STRINGS, MIDI_META_COMMAND_STRINGS_LENGTH) 60 | 61 | typedef struct { 62 | uint8_t numerator; 63 | uint8_t denominator; 64 | uint8_t ticks_per_click; 65 | uint8_t n32_per_click; // Number of 32nd notes per click. 66 | } MidiTimeSignature; 67 | 68 | typedef struct { 69 | uint32_t delta_time; 70 | uint8_t type; 71 | uint8_t command; 72 | uint8_t channel; 73 | uint8_t meta_type; 74 | uint8_t note; 75 | uint8_t velocity; 76 | uint8_t patch; 77 | uint32_t tempo; 78 | MidiTimeSignature time_signature; 79 | 80 | uint32_t data_length; 81 | uint8_t *data; 82 | } MidiEvent; 83 | 84 | typedef struct { 85 | uint32_t num_events; 86 | MidiEvent **events; 87 | } MidiTrack; 88 | 89 | typedef struct { 90 | uint8_t used; /* Whether or not this patch is used in this midi */ 91 | uint8_t min; /* Lowest note used in this patch */ 92 | uint8_t max; /* Highest note used in this patch */ 93 | } MidiPatch; 94 | 95 | typedef struct { 96 | uint16_t format; 97 | uint16_t division; 98 | uint32_t num_tracks; 99 | MidiTrack **tracks; 100 | MidiPatch patches[128]; 101 | } Midi; 102 | 103 | int read_midi_from_file(Midi *, FILE *); 104 | int read_midi_header(Midi *, FILE *); 105 | int read_midi_track(MidiTrack *, MidiPatch *, int chan_patch[16], FILE *); 106 | 107 | int get_midi_event(MidiEvent *, MidiPatch *, int chan_patch[16], const uint8_t *); 108 | int get_vl_quantity(uint32_t* q, const uint8_t* head); 109 | 110 | void destroy_midi(Midi *); 111 | void destroy_midi_track(MidiTrack *); 112 | void destroy_midi_event(MidiEvent *); 113 | 114 | void print_midi_event(FILE *, const MidiEvent *); 115 | 116 | static const char *MIDI_PATCH_NAMES[] = { 117 | "Acoustic Grand Piano", "Bright Acoustic Piano", 118 | "Electric Grand Piano", "Honky-tonk Piano", 119 | "Electric Piano 1", "Electric Piano 2", 120 | "Harpsichord", "Clavi", "Celesta", "Glockenspiel", 121 | "Music Box", "Vibraphone", "Marimba", "Xylophone", 122 | "Tubular Bells", "Dulcimer", "Drawbar Organ", 123 | "Percussive Organ", "Rock Organ", "Church Organ", 124 | "Reed Organ", "Accordion", "Harmonica", 125 | "Tango Accordion", "Acoustic Guitar (nylon)", 126 | "Acoustic Guitar (steel)", 127 | "Electric Guitar (jazz)", 128 | "Electric Guitar (clean)", 129 | "Electric Guitar (muted)", 130 | "Overdriven Guitar", "Distortion Guitar", 131 | "Guitar harmonics", "Acoustic Bass", 132 | "Electric Bass (finger)", "Electric Bass (pick)", 133 | "Fretless Bass", "Slap Bass 1", "Slap Bass 2", 134 | "Synth Bass 1", "Synth Bass 2", "Violin", "Viola", 135 | "Cello", "Contrabass", "Tremolo Strings", 136 | "Pizzicato Strings", "Orchestral Harp", "Timpani", 137 | "String Ensemble 1", "String Ensemble 2", 138 | "SynthStrings 1", "SynthStrings 2", "Choir Aahs", 139 | "Voice Oohs", "Synth Voice", "Orchestra Hit", 140 | "Trumpet", "Trombone", "Tuba", "Muted Trumpet", 141 | "French Horn", "Brass Section", "SynthBrass 1", 142 | "SynthBrass 2", "Soprano Sax", "Alto Sax", 143 | "Tenor Sax", "Baritone Sax", "Oboe", 144 | "English Horn", "Bassoon", "Clarinet", "Piccolo", 145 | "Flute", "Recorder", "Pan Flute", 146 | "Blown Bottle", "Shakuhachi", "Whistle", "Ocarina", 147 | "Lead 1 (square)", "Lead 2 (sawtooth)", 148 | "Lead 3 (calliope)", "Lead 4 (chiff)", 149 | "Lead 5 (charang)", "Lead 6 (voice)", 150 | "Lead 7 (fifths)", "Lead 8 (bass + lead)", 151 | "Pad 1 (new age)", "Pad 2 (warm)", 152 | "Pad 3 (polysynth)", "Pad 4 (choir)", 153 | "Pad 5 (bowed)", "Pad 6 (metallic)", 154 | "Pad 7 (halo)", "Pad 8 (sweep)", 155 | "FX 1 (rain)", "FX 2 (soundtrack)", 156 | "FX 3 (crystal)", "FX 4 (atmosphere)", 157 | "FX 5 (brightness)", "FX 6 (goblins)", 158 | "FX 7 (echoes)", "FX 8 (sci-fi)", "Sitar", 159 | "Banjo", "Shamisen", "Koto", "Kalimba", "Bag pipe", 160 | "Fiddle", "Shanai", "Tinkle Bell", "Agogo", 161 | "Steel Drums", "Woodblock", "Taiko Drum", 162 | "Melodic Tom", "Synth Drum", "Reverse Cymbal", 163 | "Guitar Fret Noise", "Breath Noise", "Seashore", 164 | "Bird Tweet", "Telephone Ring", "Helicopter", 165 | "Applause", "Gunshot"}; 166 | 167 | #endif /* MIDI_H */ 168 | 169 | -------------------------------------------------------------------------------- /mod.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mod.c 3 | * 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "midi.h" 11 | #include "mod.h" 12 | 13 | #define _USE_MATH_DEFINES 14 | #include 15 | 16 | #ifdef _WIN32 17 | #include "winsock2.h" 18 | #else 19 | #include 20 | #endif 21 | 22 | int 23 | compare_absolute_midi_event(const void *a, const void *b) 24 | { 25 | return ((const AbsoluteMidiEvent *)a)->time - ((const AbsoluteMidiEvent *)b)->time; 26 | } 27 | 28 | int 29 | midi_to_mod(Mod *mod, const Midi *midi) 30 | { 31 | size_t i, j, k; 32 | size_t current_time; 33 | int current_pattern; 34 | int current_channel; 35 | size_t total_events; 36 | ModCommand command; 37 | AbsoluteMidiEvent *events; 38 | MidiEvent *event; 39 | short unsigned int ticks_per_beat = 64; 40 | char* env_ticks_per_beat = getenv("TICKS_PER_BEAT"); 41 | if (env_ticks_per_beat != NULL) { 42 | int custom_ticks_per_beat = atoi(env_ticks_per_beat); 43 | if (custom_ticks_per_beat > 0) { 44 | ticks_per_beat = custom_ticks_per_beat; 45 | } 46 | } 47 | uint8_t tempo; 48 | short int division; 49 | char *channel_occupied; // For tracking whether or not a channel is free to play a note. 50 | uint8_t midi_channel_sample[16]; // Current sample that each midi channel is using. 51 | 52 | // TODO: This should probably be done better. 53 | // To hold notes that are currently on [midi channel][note number]. 54 | struct { 55 | char on; 56 | short unsigned int channel; // Mod channel that it is on 57 | } current_note[16][128]; 58 | memset(current_note, 0, sizeof(current_note)); 59 | 60 | mod->num_channels = 8; 61 | 62 | total_events = 0; 63 | for (i = 0; i < midi->num_tracks; i++) { 64 | total_events += midi->tracks[i]->num_events; 65 | } 66 | events = calloc(total_events, sizeof(AbsoluteMidiEvent)); 67 | 68 | if (events == NULL) { 69 | fprintf(stderr, "Out of memory.\n"); 70 | return 1; 71 | } 72 | 73 | memset(mod->patterns, 0, sizeof(mod->patterns)); 74 | 75 | k = 0; 76 | for(i=0; i < midi->num_tracks; i++) { 77 | current_time = 0; 78 | 79 | for(j=0; j < midi->tracks[i]->num_events; j++) { 80 | events[k].event = midi->tracks[i]->events[j]; 81 | current_time += midi->tracks[i]->events[j]->delta_time; 82 | events[k++].time = current_time; 83 | } 84 | } 85 | 86 | qsort(events, total_events, sizeof(AbsoluteMidiEvent), compare_absolute_midi_event); 87 | 88 | memset(midi_channel_sample, 0, sizeof(midi_channel_sample)); 89 | 90 | channel_occupied = calloc(mod->num_channels, sizeof(char)); 91 | 92 | current_pattern = 0; 93 | for(i=0; i < total_events; i++) { 94 | event = events[i].event; 95 | 96 | //printf("%-10d ", events[i].time); 97 | //print_midi_event(stdout, event); 98 | 99 | current_pattern = 1.0*events[i].time / ticks_per_beat / 64; 100 | division = ((1.0*events[i].time / ticks_per_beat) - (current_pattern*64));// * 4; 101 | 102 | if(event->type == MIDI_EVENT) { 103 | // event->delta_time, event->command, event->channel 104 | 105 | if(event->command == MIDI_NOTEON) { 106 | // skip percussion. TAKE THIS OUT 107 | if(event->channel == 10) continue; 108 | 109 | //event->note, event->velocity 110 | if(current_note[event->channel][event->note].on) { 111 | current_channel = current_note[event->channel][event->note].channel; 112 | } else { 113 | current_channel = -1; 114 | for(j=0; j < mod->num_channels; j++) { 115 | command = mod->patterns[current_pattern].data[j][division]; 116 | if(!channel_occupied[j] && !command.sample && !command.effect) { 117 | current_channel = j; 118 | break; 119 | } 120 | } 121 | if(current_channel < 0) continue; 122 | } 123 | 124 | current_note[event->channel][event->note].on = 1; 125 | current_note[event->channel][event->note].channel = current_channel; 126 | channel_occupied[current_channel] = 1; 127 | 128 | if(event->note > 71) { 129 | command.sample = 30; 130 | command.period = PERIOD[event->note - 12]; 131 | } else { 132 | command.sample = midi_channel_sample[event->channel]; 133 | command.period = PERIOD[event->note]; 134 | } 135 | command.effect = EF_VOLUME; 136 | command.effect_x = ((event->velocity * 100 / 256) & 0xF0) >> 4; 137 | command.effect_y = (event->velocity * 100 / 256) & 0x0F; 138 | 139 | //command.effect = 0; 140 | //command.effect_x = 0; 141 | //command.effect_y = 0; 142 | 143 | mod->patterns[current_pattern].data[current_channel][division] = command; 144 | } else if(event->command == MIDI_NOTEOFF) { 145 | // skip percussion. TAKE THIS OUT 146 | if(event->channel == 10) continue; 147 | 148 | current_channel = current_note[event->channel][event->note].channel; 149 | current_note[event->channel][event->note].on = 0; 150 | channel_occupied[current_channel] = 0; 151 | 152 | command.sample = 0; 153 | command.period = 0; 154 | command.effect = EF_VOLUME; 155 | command.effect_x = 0; 156 | command.effect_y = 0; 157 | 158 | mod->patterns[current_pattern].data[current_channel][division] = command; 159 | } else if(event->command == MIDI_PATCHCHANGE) { 160 | // TODO: the sample number needs to be mapped from 161 | // event->patch. 162 | //midi_channel_sample[event->channel] = event->patch; 163 | midi_channel_sample[event->channel] = 1; 164 | } 165 | } else if (event->type == MIDI_EVENT_META) { 166 | // event->delta_time, event->meta_type 167 | 168 | // Text event. 169 | if( 170 | event->meta_type >= MIDI_META_TEXT && 171 | event->meta_type <= MIDI_META_CUEPOINT) { 172 | // event->data 173 | } else if(event->meta_type == MIDI_META_SETTEMPO) { 174 | // event->tempo 175 | tempo = (1.0f/event->tempo) * 60000000; 176 | fprintf(stderr, "Output tempo %"PRIu8"\n", tempo); 177 | 178 | current_channel = -1; 179 | for(j=0; j < mod->num_channels; j++) { 180 | command = mod->patterns[current_pattern].data[j][division]; 181 | if(!channel_occupied[j] && !command.sample && !command.effect) { 182 | current_channel = j; 183 | break; 184 | } 185 | } 186 | if(current_channel < 0) continue; 187 | 188 | command.sample = 0; 189 | command.period = 0; 190 | command.effect = EF_TEMPO; 191 | command.effect_x = (tempo >> 4) & 0x0F; 192 | command.effect_y = tempo & 0x0F; 193 | 194 | mod->patterns[current_pattern].data[current_channel][division] = command; 195 | //printf("tempo: %d\n", tempo); 196 | } else if(event->meta_type == MIDI_META_TIMESIGNATURE) { 197 | ticks_per_beat = event->time_signature.ticks_per_click; 198 | } else { 199 | fprintf(stderr, "%d/%d ", current_pattern, division); 200 | print_midi_event(stderr, event); 201 | } 202 | } else if (event->type == MIDI_EVENT_SYSEX) { 203 | // event->delta_time, event->command 204 | } else { 205 | //fprintf(stderr, "Unknown MIDI event:\t%d,\t%d,\t%x\n", event->delta_time, event->type, event->command); 206 | fprintf(stderr, "%d/%d ", current_pattern, division); 207 | print_midi_event(stderr, event); 208 | } 209 | } 210 | 211 | mod->num_patterns = current_pattern + 1; 212 | memset(mod->pattern_table, 0, sizeof(mod->pattern_table)); 213 | for(i=0; i < mod->num_patterns; i++) mod->pattern_table[i] = i; 214 | 215 | free(events); 216 | free(channel_occupied); 217 | 218 | return 0; 219 | } 220 | 221 | int 222 | write_mod_file(Mod *mod, FILE *outfile) 223 | { 224 | int i, d, c; 225 | char a[22]; 226 | char b; 227 | ModCommand *command; 228 | uint32_t data; 229 | uint16_t z = 0; 230 | 231 | // Title 232 | fwrite("Test\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", sizeof(char), 20, outfile); 233 | 234 | memset(a, 0, sizeof(a)); 235 | strcpy(a, "Sample "); 236 | for(i=0; i<31; i++) { 237 | a[7] = i+'a'; 238 | fwrite(a, sizeof(a), 1, outfile); 239 | uint16_t length = 8287; 240 | length = htons(length); 241 | fwrite(&length, sizeof(uint16_t), 1, outfile); 242 | fwrite(&z, sizeof(uint8_t), 1, outfile); 243 | uint8_t v = 64; 244 | fwrite(&v, sizeof(uint8_t), 1, outfile); 245 | fwrite(&z, sizeof(uint16_t), 1, outfile); 246 | fwrite(&length, sizeof(uint16_t), 1, outfile); 247 | } 248 | 249 | fwrite(&mod->num_patterns, sizeof(uint8_t), 1, outfile); 250 | b = 127; 251 | fwrite(&b, sizeof(uint8_t), 1, outfile); 252 | 253 | fwrite(mod->pattern_table, sizeof(uint8_t), 128, outfile); 254 | //fwrite("M.K.", sizeof(char), 4, outfile); 255 | fwrite("8CHN", sizeof(char), 4, outfile); 256 | 257 | for(i=0; i < mod->num_patterns; i++) { 258 | //for(i=0; i < 64; i++) { 259 | for(d=0; d<64; d++) { 260 | for(c=0; c < mod->num_channels; c++) { 261 | command = &(mod->patterns[i].data[c][d]); 262 | data = 263 | ((command->sample & 0xf0) << 24) | 264 | ((command->period & 0x0FFF) << 16) | 265 | ((command->sample & 0x0f) << 12) | 266 | ((command->effect & 0x0f) << 8) | 267 | ((command->effect_x & 0x0f) << 4) | 268 | (command->effect_y & 0x0f); 269 | 270 | data = htonl(data); 271 | fwrite(&data, sizeof(uint32_t), 1, outfile); 272 | } 273 | } 274 | } 275 | 276 | // Samples 277 | for(i=0; i<8; i++) { 278 | int8_t v = 0; 279 | fwrite(&v, sizeof(uint8_t), 1, outfile); 280 | fwrite(&v, sizeof(uint8_t), 1, outfile); 281 | for(d=2; d<16574; d++) { 282 | v = 128 * sin((1.0f * d / 16574) * 2 * M_PI * 1024); 283 | //printf("%d\n", v); 284 | fwrite(&v, sizeof(uint8_t), 1, outfile); 285 | } 286 | } 287 | for(i = 8; i < 31; i++) { 288 | int8_t v = 0; 289 | fwrite(&v, sizeof(uint8_t), 1, outfile); 290 | fwrite(&v, sizeof(uint8_t), 1, outfile); 291 | for (d = 2; d < 16574; d++) { 292 | v = 128 * sin((1.0 * d / 16574) * 2 * M_PI * 2048); 293 | //printf("%d\n", v); 294 | fwrite(&v, sizeof(uint8_t), 1, outfile); 295 | } 296 | } 297 | 298 | return 0; 299 | } 300 | 301 | -------------------------------------------------------------------------------- /midi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * midi.c 3 | * 4 | * Heath Caldwell 5 | * hncaldwell@csupomona.edu 6 | * 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "midi.h" 14 | #include "stringcatalog.h" 15 | 16 | #ifdef _WIN32 17 | #include "winsock2.h" 18 | #else 19 | #include 20 | #endif 21 | 22 | const char *MIDI_NOTE_STRING[128] = 23 | {"C,,,,", "C#,,,,", "D,,,,", "D#,,,,", "E,,,,", "F,,,,", "F#,,,,", "G,,,,", "G#,,,,", "A,,,,", "A#,,,,", "B,,,,", 24 | "C,,,", "C#,,,", "D,,,", "D#,,,", "E,,,", "F,,,", "F#,,,", "G,,,", "G#,,,", "A,,,", "A#,,,", "B,,,", 25 | "C,,", "C#,,", "D,,", "D#,,", "E,,", "F,,", "F#,,", "G,,", "G#,,", "A,,", "A#,,", "B,,", 26 | "C,", "C#,", "D,", "D#,", "E,", "F,", "F,#", "G,", "G#,", "A,", "A#,", "B,", 27 | "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", 28 | "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b", 29 | "c'", "c#'", "d'", "d#'", "e'", "f'", "f#'", "g'", "g#'", "a'", "a#'", "b'", 30 | "c''", "c#''", "d''", "d#''", "e''", "f''", "f#''", "g''", "g#''", "a''", "a#''", "b''", 31 | "c'''", "c#'''", "d'''", "d#'''", "e'''", "f'''", "f#'''", "g'''", "g#'''", "a'''", "a#'''", "b'''", 32 | "c''''", "c#''''", "d''''", "d#''''", "e''''", "f''''", "f#''''", "g''''", "g#''''", "a''''", "a#''''", "b''''", 33 | "c'''''", "c#'''''", "d'''''", "d#'''''", "e'''''", "f'''''", "f#'''''", "g'''''"}; 34 | 35 | const int MIDI_EVENT_COMMAND_STRINGS_LENGTH = 7; 36 | const StringEntry MIDI_EVENT_COMMAND_STRINGS[] = 37 | {{MIDI_NOTEOFF, "note off"}, 38 | {MIDI_NOTEON, "note on"}, 39 | {MIDI_KEYAFTERTOUCH, "key after touch"}, 40 | {MIDI_CONTROLCHANGE, "control change"}, 41 | {MIDI_PATCHCHANGE, "patch change"}, 42 | {MIDI_CHANNELAFTERTOUCH, "channel after touch"}, 43 | {MIDI_PITCHWHEEL, "pitch wheel"}}; 44 | 45 | const int MIDI_META_COMMAND_STRINGS_LENGTH = 13; 46 | const StringEntry MIDI_META_COMMAND_STRINGS[] = 47 | {{MIDI_META_SEQUENCENUMBER, "sequence number"}, 48 | {MIDI_META_TEXT, "text"}, 49 | {MIDI_META_COPYRIGHT, "copyright"}, 50 | {MIDI_META_TRACKNAME, "track name"}, 51 | {MIDI_META_INSTRUMENTNAME, "instrument name"}, 52 | {MIDI_META_LYRIC, "lyric"}, 53 | {MIDI_META_MARKER, "marker"}, 54 | {MIDI_META_CUEPOINT, "cue point"}, 55 | {MIDI_META_ENDTRACK, "end of track"}, 56 | {MIDI_META_SETTEMPO, "set tempo"}, 57 | {MIDI_META_TIMESIGNATURE, "time signature"}, 58 | {MIDI_META_KEYSIGNATURE, "key signature"}, 59 | {MIDI_META_SEQUENCERINFO, "sequencer specific information"}}; 60 | 61 | int 62 | read_midi_from_file(Midi *midi, FILE *infile) 63 | { 64 | MidiTrack *track; 65 | size_t i; 66 | int chan_patch[16]; 67 | 68 | if (read_midi_header(midi, infile)) { 69 | return 1; 70 | } 71 | 72 | memset(midi->patches, 0, sizeof(midi->patches)); 73 | memset(chan_patch, 0, sizeof(chan_patch)); 74 | for(i=0; i < midi->num_tracks; i++) { 75 | track = calloc(1, sizeof(MidiTrack)); 76 | if(!read_midi_track(track, midi->patches, chan_patch, infile)) 77 | midi->tracks[i] = track; 78 | } 79 | 80 | return 0; 81 | } 82 | 83 | int 84 | read_midi_header(Midi *midi, FILE *infile) 85 | { 86 | uint8_t MThd[4]; 87 | uint32_t size; 88 | uint16_t format; 89 | uint16_t num_tracks; 90 | uint16_t division; 91 | uint16_t i; 92 | 93 | if(!fread(MThd, sizeof(MThd), 1, infile)) { 94 | fprintf(stderr, "Unable to read midi midi.\n"); 95 | return 1; 96 | } 97 | 98 | if(MThd[0] != 'M' || MThd[1] != 'T' || MThd[2] != 'h' || MThd[3] != 'd') { 99 | fprintf(stderr, "Not a midi file.\n"); 100 | return 1; 101 | } 102 | 103 | if(!fread(&size, sizeof(size), 1, infile)) { 104 | fprintf(stderr, "Unable to read size.\n"); 105 | return 1; 106 | } 107 | size = ntohl(size); 108 | 109 | if(size != 6) { 110 | fprintf(stderr, "Header is of incorrect size.\n"); 111 | return 1; 112 | } 113 | 114 | if(!fread(&format, sizeof(format), 1, infile)) { 115 | fprintf(stderr, "Unable to read format.\n"); 116 | return 1; 117 | } 118 | format = ntohs(format); 119 | 120 | if(!fread(&num_tracks, sizeof(num_tracks), 1, infile)) { 121 | fprintf(stderr, "Unable to read tracks.\n"); 122 | return 1; 123 | } 124 | num_tracks = ntohs(num_tracks); 125 | 126 | if(!fread(&division, sizeof(division), 1, infile)) { 127 | fprintf(stderr, "Unable to read division.\n"); 128 | return 1; 129 | } 130 | division = ntohs(division); 131 | 132 | midi->format = format; 133 | midi->division = division; 134 | midi->num_tracks = num_tracks; 135 | midi->tracks = calloc(num_tracks, sizeof(MidiTrack)); 136 | if (midi->tracks == NULL) { 137 | fprintf(stderr, "Out of memory.\n"); 138 | return 1; 139 | } 140 | 141 | for (i = 0; i < num_tracks; i++) { 142 | midi->tracks[i] = NULL; 143 | } 144 | 145 | return 0; 146 | } 147 | 148 | // patches: 128 element array of MidiPatches 149 | // chan_patch: Mapping of channel number to patch number 150 | int 151 | read_midi_track(MidiTrack *track, MidiPatch *patches, int chan_patch[16], FILE *infile) 152 | { 153 | uint8_t MTrk[4]; 154 | uint32_t length; 155 | 156 | int32_t event_length; 157 | MidiEvent *event; 158 | 159 | uint8_t *data; 160 | uint8_t *head; 161 | 162 | track->num_events = 0; 163 | track->events = NULL; 164 | 165 | if(!fread(MTrk, sizeof(MTrk), 1, infile)) { 166 | fprintf(stderr, "Unable to read track header.\n"); 167 | return 1; 168 | } 169 | 170 | if(MTrk[0] != 'M' || MTrk[1] != 'T' || MTrk[2] != 'r' || MTrk[3] != 'k') { 171 | fprintf(stderr, "Not a track.\n"); 172 | return 1; 173 | } 174 | 175 | if(!fread(&length, sizeof(length), 1, infile)) { 176 | fprintf(stderr, "Unable to read track length.\n"); 177 | return 1; 178 | } 179 | length = ntohl(length); 180 | 181 | data = calloc(length, sizeof(uint8_t)); 182 | if (data == NULL) { 183 | fprintf(stderr, "Out of memory.\n"); 184 | return 1; 185 | } 186 | 187 | head = data; 188 | if(!fread(data, sizeof(uint8_t), length, infile)) { 189 | fprintf(stderr, "Unable to read track data.\n"); 190 | return 1; 191 | } 192 | 193 | while(head < data + length) { 194 | event = calloc(1, sizeof(MidiEvent)); 195 | 196 | event_length = get_midi_event(event, patches, chan_patch, head); 197 | if(event_length < 0) { 198 | fprintf(stderr, "Error reading event.\n"); 199 | return 1; 200 | } 201 | 202 | track->num_events++; 203 | 204 | track->events = realloc(track->events, track->num_events * sizeof(MidiEvent *)); 205 | if(track->events == NULL) { 206 | fprintf(stderr, "Out of memory.\n"); 207 | return 1; 208 | } 209 | 210 | track->events[(size_t)track->num_events - 1] = event; 211 | 212 | head += event_length; 213 | } 214 | 215 | free(data); 216 | 217 | return 0; 218 | } 219 | 220 | // patches: 128 element array of MidiPatches 221 | // chan_patch: Mapping of channel number to patch number 222 | int 223 | get_midi_event(MidiEvent *event, MidiPatch *patches, int chan_patch[16], const uint8_t *data) 224 | { 225 | uint8_t *head = (uint8_t *)data; 226 | uint8_t command; 227 | uint8_t type; 228 | uint32_t command_length; 229 | uint32_t delta_time; 230 | int quantity_length; 231 | MidiPatch *patch; 232 | 233 | event->data_length = 0; 234 | event->data = NULL; 235 | 236 | quantity_length = get_vl_quantity(&delta_time, head); 237 | if(quantity_length < 0) { 238 | fprintf(stderr, "Bad delta time.\n"); 239 | return 1; 240 | } 241 | head += quantity_length; 242 | event->delta_time = delta_time; 243 | 244 | command = *(head++); 245 | 246 | // If it is a midi event (not meta or sysex), then the first nibble of 247 | // command must only be checked to see if it is in the range. 248 | if (command >= MIDI_NOTEOFF && (command & 0xF0) <= MIDI_PITCHWHEEL) type = MIDI_EVENT; 249 | else if(command == MIDI_META) type = MIDI_EVENT_META; 250 | else if(command == MIDI_SYSEX || command == MIDI_SYSEX_LITERAL) type = MIDI_EVENT_SYSEX; 251 | else type = 0; // Unknown type. 252 | 253 | event->type = type; 254 | 255 | if(type == MIDI_EVENT) { 256 | event->channel = command & 0x0F; // The channel is the second nibble. 257 | command = command & 0xF0; // The command is the first nibble. 258 | 259 | if(command == MIDI_NOTEOFF || 260 | command == MIDI_NOTEON || 261 | command == MIDI_KEYAFTERTOUCH) { 262 | event->note = *head; 263 | event->velocity = *(head+1); 264 | 265 | patch = &patches[(size_t)chan_patch[event->channel]]; 266 | if(event->note < patch->min || !patch->min) 267 | patch->min = event->note; 268 | 269 | if(event->note > patch->max) 270 | patch->max = event->note; 271 | 272 | command_length = 2; 273 | } else if(command == MIDI_PATCHCHANGE) { 274 | event->patch = *head; 275 | patches[event->patch].used = 1; 276 | chan_patch[event->channel] = event->patch; 277 | command_length = 1; 278 | } else if(command == MIDI_CHANNELAFTERTOUCH) { 279 | command_length = 1; 280 | } else { 281 | // Comand has two bytes of data. 282 | command_length = 2; 283 | } 284 | } else if(type == MIDI_EVENT_META) { 285 | event->meta_type = *(head++); 286 | 287 | quantity_length = get_vl_quantity(&command_length, head); 288 | if(quantity_length < 0) { 289 | fprintf(stderr, "Bad command length.\n"); 290 | return -1; 291 | } 292 | head += quantity_length; 293 | 294 | if(event->meta_type >= MIDI_META_TEXT && // Text event 295 | event->meta_type <= MIDI_META_CUEPOINT) { // 296 | event->data_length = command_length; 297 | event->data = calloc(command_length + 1, sizeof(uint8_t)); 298 | if (event->data == NULL) { 299 | fprintf(stderr, "Out of memory.\n"); 300 | return 1; 301 | } 302 | 303 | memcpy(event->data, head, command_length); 304 | event->data[(size_t)command_length] = 0; 305 | } else if(event->meta_type == MIDI_META_SETTEMPO) { 306 | event->tempo = 0 | *head << 16 | *(head+1) << 8 | *(head+2); 307 | //fprintf(stderr, "Midi read tempo %"PRIu32"\n", event->tempo); 308 | } else if(event->meta_type == MIDI_META_TIMESIGNATURE) { 309 | event->time_signature.numerator = *head; 310 | event->time_signature.denominator = 0x01 << *(head+1); // 2^(*(head+1)) 311 | event->time_signature.ticks_per_click = *(head+2); 312 | event->time_signature.n32_per_click = *(head+3); 313 | } 314 | } else if(type == MIDI_EVENT_SYSEX) { 315 | quantity_length = get_vl_quantity(&command_length, head); 316 | if(quantity_length < 0) { 317 | fprintf(stderr, "Bad command length.\n"); 318 | return -1; 319 | } 320 | head += quantity_length; 321 | 322 | // Read command data. 323 | } 324 | 325 | event->command = command; 326 | head += command_length; 327 | 328 | // The difference between where we are and where we started is the 329 | // length of the event. 330 | return head - data; 331 | } 332 | 333 | // get_vl_quantity 334 | // Get variable length quantity. 335 | // 336 | // Reads the bytes pointed at by head as long as the first bit is set 337 | // until it reaches a byte with the first bit cleared (with a maximum of 338 | // 4 bytes), as defined in the MIDI standard. The last seven bits from 339 | // each byte are packed together into q. 340 | // 341 | // Takes: q - Place to store quantity. 342 | // head - Pointer to first byte to read. 343 | // 344 | // Returns: Number of bytes read. 345 | // Returns negative value on error. 346 | int 347 | get_vl_quantity(uint32_t *q, const uint8_t *head) 348 | { 349 | *q = 0; 350 | 351 | int i; 352 | for(i=0; i<4; i++) { 353 | *q = (*q << 7) | (*(head + i) & 0x7F); 354 | 355 | if(*(head + i) < 0x80) return i+1; // Return number of bytes used to build q. 356 | } 357 | 358 | return -1; 359 | } 360 | 361 | void 362 | destroy_midi(Midi *midi) 363 | { 364 | size_t i; 365 | for(i=0; i < midi->num_tracks; i++) { 366 | if(midi->tracks[i]) 367 | destroy_midi_track(midi->tracks[i]); 368 | } 369 | 370 | free(midi->tracks); 371 | } 372 | 373 | void 374 | destroy_midi_track(MidiTrack *track) 375 | { 376 | size_t i; 377 | for(i=0; i < track->num_events; i++) { 378 | destroy_midi_event(track->events[i]); 379 | } 380 | 381 | free(track->events); 382 | } 383 | 384 | void 385 | destroy_midi_event(MidiEvent *event) 386 | { 387 | free(event->data); 388 | free(event); 389 | } 390 | 391 | void 392 | print_midi_event(FILE *outfile, const MidiEvent *event) 393 | { 394 | if(event->type == MIDI_EVENT) { 395 | fprintf(outfile, "Midi event:\t%"PRIu32",\t%s,\t%"PRIu8"", event->delta_time, get_midi_event_command_string(event->command), event->channel); 396 | 397 | if(event->command == MIDI_NOTEOFF || 398 | event->command == MIDI_NOTEON || 399 | event->command == MIDI_KEYAFTERTOUCH) { 400 | fprintf(outfile, ",\tNote: %s (%"PRIu8")", midi_note_string(event->note), event->velocity); 401 | } else if(event->command == MIDI_PATCHCHANGE) { 402 | fprintf(outfile, ",\tPatch: %"PRIu8"", event->patch); 403 | } 404 | 405 | fprintf(outfile, "\n"); 406 | } else if (event->type == MIDI_EVENT_META) { 407 | fprintf(outfile, "Meta event:\t%"PRIu32",\t%s", event->delta_time, get_midi_meta_command_string(event->meta_type)); 408 | 409 | // Text event. 410 | if(event->meta_type >= MIDI_META_TEXT && 411 | event->meta_type <= MIDI_META_CUEPOINT) { 412 | fprintf(outfile, ",\t%s", event->data); 413 | } else if(event->meta_type == MIDI_META_SETTEMPO) { 414 | fprintf(outfile, ",\t%"PRIu32" microseconds/quarter note", event->tempo); 415 | } else if(event->meta_type == MIDI_META_TIMESIGNATURE) { 416 | fprintf(outfile, ",\t%"PRIu8"/%"PRIu8", %"PRIu8" ticks per beat, %"PRIu8" 32nd notes per beat.", 417 | event->time_signature.numerator, 418 | event->time_signature.denominator, 419 | event->time_signature.ticks_per_click, 420 | event->time_signature.n32_per_click); 421 | } 422 | 423 | fprintf(outfile, "\n"); 424 | } else if (event->type == MIDI_EVENT_SYSEX) { 425 | fprintf(outfile, "Sysex event:\t%"PRIu32",\t%"PRIu8"\n", event->delta_time, event->command); 426 | } else { 427 | fprintf(outfile, "Unknown event:\t%"PRIu32",\t%"PRIu8",\t%"PRIu8"\n", event->delta_time, event->type, event->command); 428 | } 429 | } 430 | 431 | --------------------------------------------------------------------------------