├── wildwebmidi.data ├── wildwebmidi.js.mem ├── pre.js ├── post.js ├── makecfg.js ├── README.md ├── src ├── config.h └── wildwebmidi.c ├── make.js ├── web_audio_player.js └── index.html /wildwebmidi.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zz85/wild-web-midi/HEAD/wildwebmidi.data -------------------------------------------------------------------------------- /wildwebmidi.js.mem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zz85/wild-web-midi/HEAD/wildwebmidi.js.mem -------------------------------------------------------------------------------- /pre.js: -------------------------------------------------------------------------------- 1 | // wildwebmidi.js - port of wildmid to JavaScript using emscripten 2 | // by Joshua Koo http://github.com/zz85 http://twitter.com/blurspline 3 | 4 | // var wildwebmidi = (function() { -------------------------------------------------------------------------------- /post.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // return wildwebmidi; 4 | 5 | // })(); 6 | 7 | // self.wildwebmidi = wildwebmidi; // make accessible to other webworker scripts 8 | 9 | 10 | // Callbacks API 11 | 12 | /* 13 | * - circularBuffer.full() 14 | * 15 | * - updateProgress(current, total) // progress 16 | * 17 | * - processAudio(pointer, size) 18 | * 19 | * - completeConversion(status) 20 | * 21 | */ 22 | 23 | /* 24 | // Signals 25 | 26 | - pauseAudioAfterDrainingBuffer 27 | - seekSamples 28 | - signalStop 29 | */ -------------------------------------------------------------------------------- /makecfg.js: -------------------------------------------------------------------------------- 1 | console.log('bank 0\n'); 2 | 3 | file = '002_Electric_Grand_Piano'; // 4 | file = '004_Electric_Piano_1_Rhodes'; 5 | file = '005_Electric_Piano_2_Chorused_Yamaha_DX'; 6 | file = '006_Harpsichord'; // good 7 | 8 | file = '016_Hammond_Organ'; // good 9 | // file = '019_Church_Organ'; // good 10 | 11 | file = '001_Acoustic_Brite_Piano'; 12 | file = '000_Acoustic_Grand_Piano'; // ok 13 | 14 | 15 | for (i=0;i<127;i++) console.log(i + ' Tone_000/' + file + '.pat amp=120 pan=center') 16 | // for (i=0;i<127;i++) console.log(i + ' Tone_000/' + file + '.pat') 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Wild Web Midi 2 | -- 3 | 4 | Wild Web Midi is [@blurspline](http://twitter.com/blurspline) attempt to run a "wavetable" (sample based) software synthesizer in the browser in JavaScript. 5 | 6 | ### Try [Demo](http://zz85.github.io/wild-web-midi/) 7 | 8 | Currently this is archived by running emscripten on the WildMidi project, which uses Gravis Ultrasound patches. Instrument patches from [Freepats](http://freepats.zenvoid.org/) project is used for the demo. 9 | 10 | Dependencies 11 | - emscripten 12 | - wildmidi 13 | - freepats 14 | 15 | ```sh 16 | git clone git@github.com:Mindwerks/wildmidi.git 17 | wget http://freepats.zenvoid.org/freepats-20060219.zip 18 | node make 19 | ``` 20 | 21 | Updates 22 | - 22 October 2020 - Fix AudioContext creation for autoplay policy in Chrome >= 71 23 | - 31 July 2015 - Allow seeking in streaming web audio mode, improved player controls, added reverb and resampling 24 | - 21 July 2015 - Implementation of Circular Buffer Queue fixes clicks/pops/static with custom Web Audio Rendering 25 | - 19 July 2015 - switch to 44.1K rendering 26 | - 5 July 2015 - allows wave rendering and realtime web audio playback 27 | - 4 July 2015 - test openal and emterpreter support 28 | - 3 July 2015 - better memory usage (browser doesn't crash after repeated conversions!) 29 | - 2 July 2015 - update index and added sample midi files 30 | - 1 July 2015 - built for the browser. allow wav playback and download after conversion. 31 | - 30 June 2015 - compiled to JS with emscripten to allow running in node.js and browser 32 | 33 | TODO 34 | - Current playing time should use more accurate time buffers 35 | - Web Workers support 36 | - pass output through lame or other MP3 encoders for download 37 | - allow custom patches 38 | - selective download of patches (faster downloads) 39 | - integrate a nice player skin like https://jordaneldredge.com/projects/winamp2-js/ 40 | 41 | DONE 42 | - Slider to fast seek music + stop controls 43 | - stream audio to Web Audio API 44 | - wav playback from browser 45 | - saving of generated wav from browser 46 | - allow opening of midis from browser 47 | - decoding and conversion of midis 48 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* config.h -- generated from config.h.cmake */ 2 | 3 | /* Name of package */ 4 | #define PACKAGE "wildmidi" 5 | 6 | /* Define to the home page for this package. */ 7 | #define PACKAGE_URL "http://www.mindwerks.net/projects/wildmidi/" 8 | 9 | /* Define to the address where bug reports for this package should be sent. */ 10 | #define PACKAGE_BUGREPORT "https://github.com/Mindwerks/wildmidi/issues" 11 | 12 | /* Define to the full name of this package. */ 13 | #define PACKAGE_NAME "WildMidi" 14 | 15 | /* Define to the full name and version of this package. */ 16 | #define PACKAGE_STRING "WildMidi 0.4.0" 17 | 18 | /* Define to the one symbol short name of this package. */ 19 | #define PACKAGE_TARNAME "wildmidi" 20 | 21 | /* Define to the version of this package. */ 22 | #define PACKAGE_VERSION "0.4.0" 23 | 24 | /* Version number of package */ 25 | #define VERSION "0.4.0" 26 | 27 | /* Define this to the location of the wildmidi config file */ 28 | #define WILDMIDI_CFG "wildmidi.cfg" 29 | 30 | /* Define if the C compiler supports the `inline' keyword. */ 31 | #define HAVE_C_INLINE 32 | /* Define if the C compiler supports the `__inline__' keyword. */ 33 | #define HAVE_C___INLINE__ 34 | /* Define if the C compiler supports the `__inline' keyword. */ 35 | #define HAVE_C___INLINE 36 | #if !defined(HAVE_C_INLINE) && !defined(__cplusplus) 37 | # ifdef HAVE_C___INLINE__ 38 | # define inline __inline__ 39 | # elif defined(HAVE_C___INLINE) 40 | # define inline __inline 41 | # else 42 | # define inline 43 | # endif 44 | #endif 45 | 46 | /* Define if the compiler has the `__builtin_expect' built-in function */ 47 | #define HAVE___BUILTIN_EXPECT 48 | #ifndef HAVE___BUILTIN_EXPECT 49 | #define __builtin_expect(x,c) x 50 | #endif 51 | 52 | /* define this if you are running a bigendian system (motorola, sparc, etc) */ 53 | /* #undef WORDS_BIGENDIAN */ 54 | 55 | /* Define if you have the header file. */ 56 | #define HAVE_STDINT_H 57 | 58 | /* Define if you have the header file. */ 59 | #define HAVE_INTTYPES_H 60 | 61 | /* Define our audio drivers */ 62 | /* #undef HAVE_LINUX_SOUNDCARD_H */ 63 | /* #undef HAVE_SYS_SOUNDCARD_H */ 64 | /* #undef HAVE_MACHINE_SOUNDCARD_H */ 65 | /* #undef HAVE_SOUNDCARD_H */ 66 | 67 | /* #undef AUDIODRV_ALSA */ 68 | /* #undef AUDIODRV_OSS */ 69 | /* #define AUDIODRV_OPENAL */ 70 | -------------------------------------------------------------------------------- /make.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple script for running emcc on wildmidi 3 | * @author zz85 github.com/zz85 4 | */ 5 | 6 | var NODEJS = 0; 7 | 8 | var EMCC = '/usr/lib/emsdk_portable/emscripten/master/emcc'; 9 | 10 | var OPTIMIZE_FLAGS = ' -O2 '; 11 | 12 | var sources = [ 13 | 'wm_error.c', 14 | 'file_io.c', 15 | 'lock.c', 16 | 'wildmidi_lib.c', 17 | 'reverb.c', 18 | 'gus_pat.c', 19 | 'internal_midi.c', 20 | 'patches.c', 21 | 'f_xmidi.c', 22 | 'f_mus.c', 23 | 'f_hmp.c', 24 | 'f_midi.c', 25 | 'f_hmi.c', 26 | 'sample.c', 27 | 'mus2mid.c', 28 | 'xmi2mid.c', 29 | 30 | // 'wm_tty.c', 31 | // 'wildmidi.c', 32 | ].map(function(include) { 33 | return 'wildmidi/src/' + include; 34 | }); 35 | 36 | sources.push('src/wildwebmidi.c'); 37 | 38 | console.log('sources: ' + sources); 39 | 40 | var DEFINES = ''; 41 | 42 | var FLAGS = OPTIMIZE_FLAGS; 43 | 44 | var MEM = 64 * 1024 * 1024; // 64MB 45 | FLAGS += ' -s TOTAL_MEMORY=' + MEM + ' '; 46 | 47 | 48 | if (NODEJS) { 49 | DEFINES += ' -DNODEJS=1' 50 | } 51 | else { 52 | // browser 53 | FLAGS += ' --preload-file freepats '; 54 | FLAGS += ' --pre-js pre.js --post-js post.js ' 55 | } 56 | 57 | FLAGS += ' -s EMTERPRETIFY=1 '; 58 | FLAGS += ' -s EMTERPRETIFY_ASYNC=1 '; 59 | FLAGS += ' -s EMTERPRETIFY_WHITELIST="[\'_wildwebmidi\']" '; 60 | 61 | /* DEBUG FLAGS */ 62 | // var DEBUG_FLAGS = ' -g '; FLAGS += DEBUG_FLAGS; 63 | // FLAGS += ' -s ASSERTIONS=2 ' 64 | // FLAGS += ' --profiling-funcs ' 65 | // FLAGS += ' -s EMTERPRETIFY_ADVISE=1 ' 66 | // FLAGS += ' -s ALLOW_MEMORY_GROWTH=1'; 67 | // FLAGS += ' -s DEMANGLE_SUPPORT=1 '; 68 | 69 | var INCLUDES = ''; 70 | INCLUDES += '-Isrc '; 71 | // INCLUDES += '-I/System/Library/Frameworks/OpenAL.framework/Headers '; 72 | INCLUDES += '-Iwildmidi/include '; 73 | 74 | 75 | var compile_all = EMCC + ' ' + INCLUDES 76 | + sources.join(' ') 77 | + FLAGS + ' ' + DEFINES + ' -o wildwebmidi.js ' 78 | + ' -s EXPORTED_FUNCTIONS="[\'_wildwebmidi\']"' ; 79 | 80 | var 81 | exec = require('child_process').exec, 82 | child; 83 | 84 | function onExec(error, stdout, stderr) { 85 | if (stdout) console.log('stdout: ' + stdout); 86 | if (stderr) console.log('stderr: ' + stderr); 87 | if (error !== null) { 88 | console.log('exec error: ' + error); 89 | } else { 90 | nextJob(); 91 | } 92 | } 93 | 94 | function nextJob() { 95 | if (!jobs.length) { 96 | console.log('jobs done'); 97 | return; 98 | } 99 | var cmd = jobs.shift(); 100 | console.log('running ' + cmd); 101 | exec(cmd, onExec); 102 | } 103 | 104 | var jobs = [ 105 | compile_all 106 | ]; 107 | 108 | nextJob(); 109 | 110 | 111 | -------------------------------------------------------------------------------- /web_audio_player.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Circular Web Audio Buffer Queue 3 | */ 4 | function CircularAudioBuffer(slots, audioCtx) { 5 | slots = slots || 24; // number of buffers 6 | this.slots = slots; 7 | this.buffers = new Array(slots); 8 | 9 | this.reset(); 10 | 11 | for (var i = 0; i < slots; i++) { 12 | var buffer = audioCtx.createBuffer(channels, BUFFER, SAMPLE_RATE); 13 | this.buffers[i] = buffer; 14 | } 15 | } 16 | 17 | // pseudo empty all buffers 18 | CircularAudioBuffer.prototype.reset = function() { 19 | this.used = 0; 20 | this.filled = 0; 21 | }; 22 | 23 | // returns number of buffers that are filled 24 | CircularAudioBuffer.prototype.filledBuffers = function() { 25 | var fills = this.filled - this.used; 26 | if (fills < 0) fills += this.slots; 27 | return fills; 28 | } 29 | 30 | // returns whether buffers are all filled 31 | CircularAudioBuffer.prototype.full = function() { 32 | return this.filledBuffers() >= this.slots - 1; 33 | } 34 | 35 | // returns a reference to next available buffer to be filled 36 | CircularAudioBuffer.prototype.prepare = function() { 37 | if (this.full()) { 38 | console.log('buffers full!!') 39 | return 40 | } 41 | var buffer = this.buffers[this.filled++]; 42 | this.filled %= this.slots; 43 | return buffer; 44 | } 45 | 46 | // returns the next buffer in the queue 47 | CircularAudioBuffer.prototype.use = function() { 48 | if (!this.filledBuffers()) return; 49 | var buffer = this.buffers[this.used++]; 50 | this.used %= this.slots; 51 | return buffer; 52 | } 53 | 54 | 55 | /* 56 | * Web Audio Stuff 57 | */ 58 | 59 | var SAMPLE_RATE = 44100; 60 | var BUFFER = 4096; // buffer sample 61 | var channels = 2; 62 | 63 | // Create AudioContext and buffer source 64 | var audioCtx; 65 | var source; 66 | var scriptNode; 67 | var circularBuffer; 68 | var emptyBuffer; 69 | var audioIsInitted = false; 70 | 71 | function initAudio() { 72 | audioCtx = new window.AudioContext(); 73 | scriptNode = audioCtx.createScriptProcessor(BUFFER, 0, channels); 74 | scriptNode.onaudioprocess = onAudioProcess; 75 | 76 | source = audioCtx.createBufferSource(); 77 | circularBuffer = new CircularAudioBuffer(4, audioCtx); 78 | emptyBuffer = audioCtx.createBuffer(channels, BUFFER, SAMPLE_RATE); 79 | 80 | source.connect(scriptNode); 81 | source.start(0); 82 | } 83 | 84 | 85 | function startAudio() { 86 | scriptNode.connect(audioCtx.destination); 87 | } 88 | 89 | function pauseAudio() { 90 | scriptNode.disconnect(); 91 | } 92 | 93 | // Give the node a function to process audio events 94 | function onAudioProcess(audioProcessingEvent) { 95 | var generated = circularBuffer.use(); 96 | 97 | if (!generated && pauseAudioAfterDrainingBuffer) { 98 | // wait for remaining buffer to drain before disconnect audio 99 | pauseAudio(); 100 | pauseAudioAfterDrainingBuffer = false; 101 | signalStop = 0; 102 | if (callbackOnStop) callbackOnStop(); 103 | callbackOnStop = null; 104 | return; 105 | } 106 | if (!generated) { 107 | console.log('buffer under run!!') 108 | generated = emptyBuffer; 109 | } 110 | 111 | var outputBuffer = audioProcessingEvent.outputBuffer; 112 | var offset = 0; 113 | outputBuffer.copyToChannel(generated.getChannelData(0), 0, offset); 114 | outputBuffer.copyToChannel(generated.getChannelData(1), 1, offset); 115 | } 116 | 117 | /* 118 | // debugging stats 119 | setInterval(function() { 120 | console.log('audio queue', circularBuffer.filledBuffers()); 121 | }, 5000); 122 | */ 123 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Wild Web Midi - Quality streaming Midi to Wave converter with JS 7 | 83 | 84 | 85 |

Wild Web Midi

86 |
87 | WildMidi is a a wavetable synthesizer that is rather plays and converts midi at pretty good quality. "WildWebMidi" is a port of that runs in your browser in javascript. Open a midi or pick a midi sample below. 88 |
89 | 90 |
91 | 92 |
Downloading...
93 | 94 |
95 | 96 |
97 | 98 |
99 | 100 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
144 |
145 | 146 | 0:00 147 | 148 | 0:00 149 | 150 |
151 |
152 | 153 |
154 | 155 | 156 | 157 |
158 | 159 |
160 | 161 | 162 | or 165 | 166 | 167 |
168 |
169 | 170 | 187 | 188 |
189 | 190 |
191 | 192 |
Got any suggestions or found any bug? Check out project on github or follow @blurspline on twitter for updates 193 |

194 | 195 | Powered by wildmidi, emscripten, and Gravis Ultrasound patches. 196 | 197 | 198 | p.s. Only loading a piano instrument bank for now to reduce page loading time (2MB!). will add side loading for more instrument patches later 199 | 200 |
201 | 202 | 203 | 537 | 551 | 552 | 553 | 554 | 555 | -------------------------------------------------------------------------------- /src/wildwebmidi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * wildmidi.c -- Midi Player using the WildMidi Midi Processing Library 3 | * 4 | * Copyright (C) WildMidi Developers 2001-2015 5 | * 6 | * This file is part of WildMIDI. 7 | * 8 | * WildMIDI is free software: you can redistribute and/or modify the player 9 | * under the terms of the GNU General Public License and you can redistribute 10 | * and/or modify the library under the terms of the GNU Lesser General Public 11 | * License as published by the Free Software Foundation, either version 3 of 12 | * the licenses, or(at your option) any later version. 13 | * 14 | * WildMIDI is distributed in the hope that it will be useful, but WITHOUT 15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 16 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License and 17 | * the GNU Lesser General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License and the 20 | * GNU Lesser General Public License along with WildMIDI. If not, see 21 | * . 22 | */ 23 | 24 | #include "config.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #if !defined(_WIN32) && !defined(__DJGPP__) /* unix build */ 36 | static int msleep(unsigned long millisec); 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #if defined AUDIODRV_OPENAL 43 | # include 44 | # include 45 | #endif 46 | 47 | #endif /* !_WIN32, !__DJGPP__ (unix build) */ 48 | 49 | #include "wildmidi_lib.h" 50 | #include "filenames.h" 51 | 52 | static void completeConversion(int status); 53 | 54 | #include 55 | 56 | 57 | /* 58 | ============================== 59 | Audio Output Functions 60 | 61 | We have two 'drivers': first is the wav file writer which is 62 | always available. the second, if it is really compiled in, 63 | is the system audio output driver. only _one of the two_ can 64 | be active, not both. 65 | ============================== 66 | */ 67 | 68 | static unsigned int rate = 44100; // 32072; 69 | 70 | static int (*send_output)(int8_t *output_data, int output_size); 71 | static void (*close_output)(void); 72 | static void (*pause_output)(void); 73 | static void (*resume_output)(void); 74 | static int audio_fd = -1; 75 | 76 | static void pause_output_nop(void) { 77 | } 78 | static void resume_output_nop(void) { 79 | } 80 | 81 | /* 82 | MIDI Output Functions 83 | */ 84 | static char midi_file[1024]; 85 | 86 | static int write_midi_output(void *output_data, int output_size) { 87 | struct stat st; 88 | 89 | if (midi_file[0] == '\0') 90 | return (-1); 91 | 92 | /* 93 | * Test if file already exists 94 | */ 95 | if (stat(midi_file, &st) == 0) { 96 | fprintf(stderr, "\rError: %s already exists\r\n", midi_file); 97 | return (-1); 98 | } 99 | 100 | audio_fd = open(midi_file, (O_RDWR | O_CREAT | O_TRUNC), 101 | (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)); 102 | if (audio_fd < 0) { 103 | fprintf(stderr, "Error: unable to open file for writing (%s)\r\n", strerror(errno)); 104 | return (-1); 105 | } 106 | 107 | if (write(audio_fd, output_data, output_size) < 0) { 108 | fprintf(stderr, "\nERROR: failed writing midi (%s)\r\n", strerror(errno)); 109 | close(audio_fd); 110 | audio_fd = -1; 111 | return (-1); 112 | } 113 | 114 | close(audio_fd); 115 | audio_fd = -1; 116 | return (0); 117 | } 118 | 119 | /* 120 | Wav Output Functions 121 | */ 122 | 123 | static uint32_t wav_size; 124 | 125 | static int write_wav_output(int8_t *output_data, int output_size); 126 | static void close_wav_output(void); 127 | 128 | static int open_wav_output(char* wav_file) { 129 | uint8_t wav_hdr[] = { 130 | 0x52, 0x49, 0x46, 0x46, /* "RIFF" */ 131 | 0x00, 0x00, 0x00, 0x00, /* riffsize: pcm size + 36 (filled when closing.) */ 132 | 0x57, 0x41, 0x56, 0x45, /* "WAVE" */ 133 | 0x66, 0x6D, 0x74, 0x20, /* "fmt " */ 134 | 0x10, 0x00, 0x00, 0x00, /* length of this RIFF block: 16 */ 135 | 0x01, 0x00, /* wave format == 1 (WAVE_FORMAT_PCM) */ 136 | 0x02, 0x00, /* channels == 2 */ 137 | 0x00, 0x00, 0x00, 0x00, /* sample rate (filled below) */ 138 | 0x00, 0x00, 0x00, 0x00, /* bytes_per_sec: rate * channels * format bytes */ 139 | 0x04, 0x00, /* block alignment: channels * format bytes == 4 */ 140 | 0x10, 0x00, /* format bits == 16 */ 141 | 0x64, 0x61, 0x74, 0x61, /* "data" */ 142 | 0x00, 0x00, 0x00, 0x00 /* datasize: the pcm size (filled when closing.) */ 143 | }; 144 | 145 | if (wav_file[0] == '\0') 146 | return (-1); 147 | 148 | audio_fd = open(wav_file, (O_RDWR | O_CREAT | O_TRUNC), 149 | (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)); 150 | if (audio_fd < 0) { 151 | fprintf(stderr, "Error: unable to open file for writing (%s)\r\n", strerror(errno)); 152 | return (-1); 153 | } else { 154 | uint32_t bytes_per_sec; 155 | 156 | wav_hdr[24] = (rate) & 0xFF; 157 | wav_hdr[25] = (rate >> 8) & 0xFF; 158 | 159 | bytes_per_sec = rate * 4; 160 | wav_hdr[28] = (bytes_per_sec) & 0xFF; 161 | wav_hdr[29] = (bytes_per_sec >> 8) & 0xFF; 162 | wav_hdr[30] = (bytes_per_sec >> 16) & 0xFF; 163 | wav_hdr[31] = (bytes_per_sec >> 24) & 0xFF; 164 | } 165 | 166 | if (write(audio_fd, &wav_hdr, 44) < 0) { 167 | fprintf(stderr, "ERROR: failed writing wav header (%s)\r\n", strerror(errno)); 168 | close(audio_fd); 169 | audio_fd = -1; 170 | return (-1); 171 | } 172 | 173 | wav_size = 0; 174 | send_output = write_wav_output; 175 | close_output = close_wav_output; 176 | pause_output = pause_output_nop; 177 | resume_output = resume_output_nop; 178 | return (0); 179 | } 180 | 181 | static int write_wav_output(int8_t *output_data, int output_size) { 182 | #ifdef WORDS_BIGENDIAN 183 | /* libWildMidi outputs host-endian, *.wav must have little-endian. */ 184 | uint16_t *swp = (uint16_t *) output_data; 185 | int i = (output_size / 2) - 1; 186 | for (; i >= 0; --i) { 187 | swp[i] = (swp[i] << 8) | (swp[i] >> 8); 188 | } 189 | #endif 190 | if (write(audio_fd, output_data, output_size) < 0) { 191 | fprintf(stderr, "\nERROR: failed writing wav (%s)\r\n", strerror(errno)); 192 | close(audio_fd); 193 | audio_fd = -1; 194 | return (-1); 195 | } 196 | 197 | wav_size += output_size; 198 | return (0); 199 | } 200 | 201 | static void close_wav_output(void) { 202 | uint8_t wav_count[4]; 203 | if (audio_fd < 0) 204 | return; 205 | 206 | printf("Finishing and closing wav output\r"); 207 | wav_count[0] = (wav_size) & 0xFF; 208 | wav_count[1] = (wav_size >> 8) & 0xFF; 209 | wav_count[2] = (wav_size >> 16) & 0xFF; 210 | wav_count[3] = (wav_size >> 24) & 0xFF; 211 | lseek(audio_fd, 40, SEEK_SET); 212 | if (write(audio_fd, &wav_count, 4) < 0) { 213 | fprintf(stderr, "\nERROR: failed writing wav (%s)\r\n", strerror(errno)); 214 | goto end; 215 | } 216 | 217 | wav_size += 36; 218 | wav_count[0] = (wav_size) & 0xFF; 219 | wav_count[1] = (wav_size >> 8) & 0xFF; 220 | wav_count[2] = (wav_size >> 16) & 0xFF; 221 | wav_count[3] = (wav_size >> 24) & 0xFF; 222 | lseek(audio_fd, 4, SEEK_SET); 223 | if (write(audio_fd, &wav_count, 4) < 0) { 224 | fprintf(stderr, "\nERROR: failed writing wav (%s)\r\n", strerror(errno)); 225 | goto end; 226 | } 227 | 228 | end: printf("\n"); 229 | if (audio_fd >= 0) 230 | close(audio_fd); 231 | audio_fd = -1; 232 | } 233 | 234 | #if defined AUDIODRV_OPENAL 235 | 236 | #define NUM_BUFFERS 4 237 | 238 | static ALCdevice *device; 239 | static ALCcontext *context; 240 | static ALuint sourceId = 0; 241 | static ALuint buffers[NUM_BUFFERS]; 242 | static ALuint frames = 0; 243 | 244 | #define open_audio_output open_openal_output 245 | 246 | static void pause_output_openal(void) { 247 | alSourcePause(sourceId); 248 | } 249 | 250 | static int write_openal_output(int8_t *output_data, int output_size) { 251 | ALint processed, state; 252 | ALuint bufid; 253 | 254 | if (frames < NUM_BUFFERS) { /* initial state: fill the buffers */ 255 | alBufferData(buffers[frames], AL_FORMAT_STEREO16, output_data, 256 | output_size, rate); 257 | 258 | /* Now queue and start playback! */ 259 | if (++frames == NUM_BUFFERS) { 260 | alSourceQueueBuffers(sourceId, frames, buffers); 261 | alSourcePlay(sourceId); 262 | } 263 | return 0; 264 | } 265 | 266 | /* Get relevant source info */ 267 | alGetSourcei(sourceId, AL_SOURCE_STATE, &state); 268 | if (state == AL_PAUSED) { /* resume it, then.. */ 269 | alSourcePlay(sourceId); 270 | if (alGetError() != AL_NO_ERROR) { 271 | fprintf(stderr, "\nError restarting playback\r\n"); 272 | return (-1); 273 | } 274 | } 275 | 276 | processed = 0; 277 | while (processed == 0) { /* Wait until we have a processed buffer */ 278 | alGetSourcei(sourceId, AL_BUFFERS_PROCESSED, &processed); 279 | } 280 | 281 | /* Unqueue and handle each processed buffer */ 282 | alSourceUnqueueBuffers(sourceId, 1, &bufid); 283 | 284 | /* Read the next chunk of data, refill the buffer, and queue it 285 | * back on the source */ 286 | alBufferData(bufid, AL_FORMAT_STEREO16, output_data, output_size, rate); 287 | alSourceQueueBuffers(sourceId, 1, &bufid); 288 | if (alGetError() != AL_NO_ERROR) { 289 | fprintf(stderr, "\nError buffering data\r\n"); 290 | return (-1); 291 | } 292 | 293 | /* Make sure the source hasn't underrun */ 294 | alGetSourcei(sourceId, AL_SOURCE_STATE, &state); 295 | /*printf("STATE: %#08x - %d\n", state, queued);*/ 296 | if (state != AL_PLAYING) { 297 | ALint queued; 298 | 299 | /* If no buffers are queued, playback is finished */ 300 | alGetSourcei(sourceId, AL_BUFFERS_QUEUED, &queued); 301 | if (queued == 0) { 302 | fprintf(stderr, "\nNo buffers queued for playback\r\n"); 303 | return (-1); 304 | } 305 | 306 | alSourcePlay(sourceId); 307 | } 308 | 309 | return (0); 310 | } 311 | 312 | static void close_openal_output(void) { 313 | if (!context) return; 314 | printf("Shutting down sound output\r\n"); 315 | alSourceStop(sourceId); /* stop playing */ 316 | alSourcei(sourceId, AL_BUFFER, 0); /* unload buffer from source */ 317 | alDeleteBuffers(NUM_BUFFERS, buffers); 318 | alDeleteSources(1, &sourceId); 319 | alcDestroyContext(context); 320 | alcCloseDevice(device); 321 | context = NULL; 322 | device = NULL; 323 | frames = 0; 324 | } 325 | 326 | static int open_openal_output(void) { 327 | /* setup our audio devices and contexts */ 328 | device = alcOpenDevice(NULL); 329 | if (!device) { 330 | fprintf(stderr, "OpenAL: Unable to open default device.\r\n"); 331 | return (-1); 332 | } 333 | 334 | context = alcCreateContext(device, NULL); 335 | if (context == NULL || alcMakeContextCurrent(context) == ALC_FALSE) { 336 | if (context != NULL) 337 | alcDestroyContext(context); 338 | alcCloseDevice(device); 339 | context = NULL; 340 | device = NULL; 341 | fprintf(stderr, "OpenAL: Failed to create the default context.\r\n"); 342 | return (-1); 343 | } 344 | 345 | /* setup our sources and buffers */ 346 | alGenSources(1, &sourceId); 347 | alGenBuffers(NUM_BUFFERS, buffers); 348 | 349 | send_output = write_openal_output; 350 | close_output = close_openal_output; 351 | pause_output = pause_output_openal; 352 | resume_output = resume_output_nop; 353 | return (0); 354 | } 355 | #else /* no audio output driver compiled in: */ 356 | 357 | #define open_audio_output open_noaudio_output 358 | static int open_noaudio_output(void) { 359 | return -1; 360 | } 361 | 362 | #endif 363 | 364 | /* no audio output driver compiled in: */ 365 | 366 | static void do_version(void) { 367 | printf("\nWildMidi %s Open Source Midi Sequencer\n", PACKAGE_VERSION); 368 | printf("Copyright (C) WildMIDI Developers 2001-2015\n\n"); 369 | printf("WildMidi comes with ABSOLUTELY NO WARRANTY\n"); 370 | printf("This is free software, and you are welcome to redistribute it under\n"); 371 | printf("the terms and conditions of the GNU General Public License version 3.\n"); 372 | printf("For more information see COPYING\n\n"); 373 | printf("Report bugs to %s\n", PACKAGE_BUGREPORT); 374 | printf("WildMIDI homepage is at %s\n\n", PACKAGE_URL); 375 | } 376 | 377 | static char *config_file; 378 | 379 | 380 | static int send_output_to_js(int8_t *output_data, int output_size) { 381 | EM_ASM_({ 382 | processAudio($0, $1); 383 | }, output_data, output_size); 384 | return 0; 385 | } 386 | 387 | 388 | int wildwebmidi(char* midi_file, char* wav_file, int sleep) { 389 | 390 | #ifdef NODEJS 391 | // mount the current folder as a NODEFS instance 392 | // inside of emscripten 393 | EM_ASM( 394 | FS.mkdir('/working'); 395 | FS.mount(NODEFS, { root: '.' }, '/working'); 396 | ); 397 | #endif 398 | 399 | struct _WM_Info *wm_info; 400 | int i, res; 401 | int option_index = 0; 402 | uint16_t mixer_options = 0; 403 | void *midi_ptr; 404 | uint8_t master_volume = 127; 405 | int8_t *output_buffer; 406 | 407 | uint32_t apr_mins; 408 | uint32_t apr_secs; 409 | uint32_t count_diff; 410 | 411 | uint8_t test_midi = 0; 412 | uint8_t test_count = 0; 413 | uint8_t *test_data; 414 | uint8_t test_bank = 0; 415 | uint8_t test_patch = 0; 416 | 417 | unsigned long int seek_to_sample; 418 | int inpause = 0; 419 | char * ret_err = NULL; 420 | long libraryver; 421 | char * lyric = NULL; 422 | char *last_lyric = NULL; 423 | uint32_t last_lyric_length = 0; 424 | int8_t kareoke = 0; 425 | #define MAX_LYRIC_CHAR 128 426 | char lyrics[MAX_LYRIC_CHAR + 1]; 427 | #define MAX_DISPLAY_LYRICS 29 428 | char display_lyrics[MAX_DISPLAY_LYRICS + 1]; 429 | 430 | memset(lyrics,' ',MAX_LYRIC_CHAR); 431 | memset(display_lyrics,' ',MAX_DISPLAY_LYRICS); 432 | 433 | config_file = "/freepats/freepats.cfg"; 434 | 435 | // do_version(); 436 | 437 | printf("Initializing Sound System\n"); 438 | int output_wav = wav_file[0] != '\0'; 439 | if (output_wav) { 440 | if (open_wav_output(wav_file) == -1) { 441 | printf("Cannot open wave"); 442 | completeConversion(1); 443 | return (1); 444 | } 445 | } /* else if (open_audio_output() == -1) { 446 | printf("Cannot audio output"); 447 | completeConversion(1); 448 | return (1); 449 | }*/ else { 450 | send_output = send_output_to_js; 451 | } 452 | 453 | libraryver = WildMidi_GetVersion(); 454 | printf("Initializing libWildMidi %ld.%ld.%ld\n\n", 455 | (libraryver>>16) & 255, 456 | (libraryver>> 8) & 255, 457 | (libraryver ) & 255); 458 | 459 | if (WildMidi_Init(config_file, rate, mixer_options) == -1) { 460 | printf("Cannot WildMidi_Init"); 461 | completeConversion(1); 462 | return (1); 463 | } 464 | output_buffer = malloc(16384); 465 | if (output_buffer == NULL) { 466 | fprintf(stderr, "Not enough memory, exiting\n"); 467 | WildMidi_Shutdown(); 468 | completeConversion(1); 469 | return (1); 470 | } 471 | 472 | WildMidi_MasterVolume(master_volume); 473 | 474 | // while (optind < argc || test_midi) { 475 | WildMidi_ClearError(); 476 | if (!test_midi) { 477 | char *real_file = midi_file; 478 | printf("\rProcessing %s ", real_file); 479 | 480 | 481 | midi_ptr = WildMidi_Open(real_file); 482 | optind++; 483 | if (midi_ptr == NULL) { 484 | ret_err = WildMidi_GetError(); 485 | printf(" Error opening midi: %s\r\n",ret_err); 486 | } 487 | } 488 | wm_info = WildMidi_GetInfo(midi_ptr); 489 | 490 | apr_mins = wm_info->approx_total_samples / (rate * 60); 491 | apr_secs = (wm_info->approx_total_samples % (rate * 60)) / rate; 492 | mixer_options = wm_info->mixer_options; 493 | 494 | // WildMidi_SetOption(midi_ptr, (WM_MO_ENHANCED_RESAMPLING), (WM_MO_ENHANCED_RESAMPLING)); 495 | // WildMidi_SetOption(midi_ptr, (WM_MO_REVERB), (WM_MO_REVERB)); 496 | WildMidi_SetOption(midi_ptr, (WM_MO_REVERB | WM_MO_ENHANCED_RESAMPLING), (WM_MO_REVERB | WM_MO_ENHANCED_RESAMPLING)); 497 | 498 | printf("\r\n[Duration of midi approx %2um %2us Total]\r\n", apr_mins, apr_secs); 499 | fprintf(stderr, "\r"); 500 | 501 | memset(lyrics,' ',MAX_LYRIC_CHAR); 502 | memset(display_lyrics,' ',MAX_DISPLAY_LYRICS); 503 | 504 | while (1) { 505 | // exit loop when samples are finished 506 | count_diff = wm_info->approx_total_samples 507 | - wm_info->current_sample; 508 | 509 | if (count_diff == 0) 510 | break; 511 | 512 | // check buffers in streaming mode 513 | if (!output_wav) { 514 | if (EM_ASM_INT_V({ 515 | return signalStop; 516 | })) { 517 | // stop 518 | goto end2; 519 | } 520 | 521 | // support seaking 522 | unsigned long int seek = EM_ASM_INT_V({ 523 | if (seekSamples < ULONG_MAX) { 524 | var tmp = seekSamples; 525 | seekSamples = ULONG_MAX; 526 | circularBuffer.reset(); 527 | return tmp; 528 | } 529 | return ULONG_MAX; 530 | }); 531 | 532 | if (seek < 4294967295) { 533 | WildMidi_FastSeek(midi_ptr, &seek); 534 | } 535 | 536 | int buffer_full = EM_ASM_INT_V({ 537 | return circularBuffer.full(); 538 | }); 539 | 540 | if (buffer_full) { 541 | msleep(sleep); 542 | continue; 543 | } 544 | } 545 | 546 | if (inpause) { 547 | wm_info = WildMidi_GetInfo(midi_ptr); 548 | 549 | msleep(5); 550 | continue; 551 | } 552 | 553 | res = WildMidi_GetOutput(midi_ptr, output_buffer, 554 | (count_diff >= 4096)? 16384 : (count_diff * 4)); 555 | if (res <= 0) 556 | break; 557 | 558 | wm_info = WildMidi_GetInfo(midi_ptr); 559 | 560 | /* 561 | // TODO Support Lyrics 562 | lyric = WildMidi_GetLyric(midi_ptr); 563 | 564 | memcpy(lyrics, &lyrics[1], MAX_LYRIC_CHAR - 1); 565 | lyrics[MAX_LYRIC_CHAR - 1] = ' '; 566 | 567 | if ((lyric != NULL) && (lyric != last_lyric) && (kareoke)) { 568 | last_lyric = lyric; 569 | if (last_lyric_length != 0) { 570 | memcpy(lyrics, &lyrics[last_lyric_length], MAX_LYRIC_CHAR - last_lyric_length); 571 | } 572 | memcpy(&lyrics[MAX_DISPLAY_LYRICS], lyric, strlen(lyric)); 573 | last_lyric_length = strlen(lyric); 574 | } else { 575 | if (last_lyric_length != 0) last_lyric_length--; 576 | } 577 | 578 | memcpy(display_lyrics,lyrics,MAX_DISPLAY_LYRICS); 579 | display_lyrics[MAX_DISPLAY_LYRICS] = '\0'; 580 | */ 581 | 582 | 583 | // Update progress 584 | EM_ASM_({ 585 | updateProgress($0, $1, $2); 586 | }, wm_info->current_sample, wm_info->approx_total_samples, wm_info->total_midi_time); 587 | // wm_info->mixer_options 588 | 589 | 590 | if (send_output(output_buffer, res) < 0) { 591 | /* driver prints an error message already. */ 592 | printf("\r"); 593 | goto end2; 594 | } 595 | 596 | 597 | // this converts to setTimeout and lets browser breath! 598 | if (sleep > -1) msleep(sleep); 599 | 600 | // end while 601 | } 602 | 603 | // NEXT MIDI 604 | // fprintf(stderr, "\r\n"); 605 | if (WildMidi_Close(midi_ptr) == -1) { 606 | ret_err = WildMidi_GetError(); 607 | fprintf(stderr, "OOPS: failed closing midi handle!\r\n%s\r\n",ret_err); 608 | } 609 | memset(output_buffer, 0, 16384); 610 | // send_output(output_buffer, 16384); 611 | // } 612 | // end1: 613 | // memset(output_buffer, 0, 16384); 614 | // send_output(output_buffer, 16384); 615 | // msleep(5); 616 | 617 | end2: 618 | close_output(); 619 | free(output_buffer); 620 | if (WildMidi_Shutdown() == -1) { 621 | ret_err = WildMidi_GetError(); 622 | fprintf(stderr, "OOPS: failure shutting down libWildMidi\r\n%s\r\n", ret_err); 623 | WildMidi_ClearError(); 624 | } 625 | 626 | printf("ok \r\n"); 627 | completeConversion(0); 628 | 629 | return 0; 630 | } 631 | 632 | static void completeConversion(int status) { 633 | EM_ASM( 634 | completeConversion(status); 635 | ); 636 | } 637 | 638 | 639 | /* helper / replacement functions: */ 640 | static int msleep(unsigned long milisec) { 641 | emscripten_sleep(milisec); 642 | return 1; 643 | } 644 | --------------------------------------------------------------------------------