Download:
child 3:ad7823dfcd63
parent 1:650e14814ce9
2:7fa4190db862
Anton Shestakov <av6@dwimlabs.net>, Tue, 11 Oct 2016 13:11:15 +0800
index: import from old repo

3 файлов изменено, 498 вставок(+), 0 удалений(-) [+]
index.html file | annotate | diff | comparison | revisions
player.js file | annotate | diff | comparison | revisions
ui.js file | annotate | diff | comparison | revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/index.html Tue Oct 11 13:11:15 2016 +0800
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <title></title>
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/2.27.1/css/uikit.almost-flat.min.css" integrity="sha384-hrLVrxKzBjSFFTm2bMf7t1TAyUVf9Hv+yDqZijq5cHYKSUFBblWLYoR+zP6norH4" crossorigin="anonymous">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/2.27.1/css/components/progress.almost-flat.min.css" integrity="sha384-J/f3CwGyaF4W+K2A4OYysNUVZc5JTqxIPUHxId/X1xRtmH+fL0G1LIz6dKQ0KOXu" crossorigin="anonymous">
+
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js" integrity="sha384-rY/jv8mMhqDabXSo+UCggqKtdmBfd3qC2/KvyTDNQ6PcUJXaxK1tMepoQda4g5vB" crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js" integrity="sha384-FZY+KSLVXVyc1qAlqH9oCx1JEOlQh6iXfw3o2n3Iy32qGjXmUPWT9I0Z9e9wxYe3" crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js" integrity="sha384-NNt9ocJfZhIg2c5PbM5G2a3tTaeXhEfqCHWHNB7htzaWKn8MwFkzVyGdzLA8QMX7" crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/rivets/0.9.4/rivets.bundled.min.js" integrity="sha384-h8VhFgh1492NvUr69qjzDDE84qcMeOzm5zfLrp/+88XTF4SOz3RsopnxNW6IazIL" crossorigin="anonymous"></script>
+
+ <style>
+ .uk-progress-bar {
+ -webkit-transition: none;
+ transition: none;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="uk-container uk-container-center">
+ <div class="uk-grid uk-margin-top">
+ <div class="uk-width-medium-1-2">
+ <div class="uk-scrollable-box">
+ <ul class="uk-nav uk-nav-side" data-files></ul>
+ </div>
+ <script>
+ var files = [
+ "nsf/Shovel_Knight_Music.nsfe",
+ ];
+ for (var i = 0; i < files.length; i++) {
+ var path = files[i];
+ var fn = path.slice(path.lastIndexOf('/') + 1);
+ var $a = $('<a>').attr('href', '#' + path).text(fn);
+ $('[data-files]').append($('<li>').append($a));
+ }
+ </script>
+ </div>
+ <div class="uk-width-medium-1-2">
+ <div class="uk-panel uk-panel-box uk-panel-box-secondary uk-hidden" data-front>
+ <h3 class="uk-panel-title" rv-text="player:track:song"></h3>
+ <p>
+ <span rv-text="player:track:author"></span>
+ <span rv-if="player:track:game">- <span rv-text="player:track:game"></span></span>
+ <span rv-if="player:track:system">(<span rv-text="player:track:system"></span>)</span>
+ </p>
+ <div class="uk-button-group">
+ <button class="uk-button" rv-on-click="front.fastBackward"><i class="uk-icon-fast-backward"></i></button>
+ <button class="uk-button" rv-class-uk-active="player:track:status | is 'playing'" rv-on-click="front.play"><i class="uk-icon-play"></i></button>
+ <button class="uk-button" rv-class-uk-active="player:track:status | is 'stopped'" rv-on-click="front.pause"><i class="uk-icon-pause"></i></button>
+ <button class="uk-button" rv-on-click="front.stop"><i class="uk-icon-stop"></i></button>
+ <button class="uk-button" rv-on-click="front.fastForward"><i class="uk-icon-fast-forward"></i></button>
+ </div>
+ <div class="uk-button-group">
+ <button class="uk-button" rv-class-uk-active="player:autoplay" rv-on-click="front.toggleAutoplay"><i class="uk-icon-long-arrow-right"></i></button>
+ <button class="uk-button" rv-class-uk-active="player:repeat" rv-on-click="front.toggleRepeat"><i class="uk-icon-repeat"></i></button>
+ <button class="uk-button" rv-class-uk-active="player:random" rv-on-click="front.toggleRandom"><i class="uk-icon-random"></i></button>
+ </div>
+ <table class="uk-text-nowrap uk-margin">
+ <tr>
+ <td><span class="uk-margin-small-right" rv-text="player:track:position | time"></span></td>
+ <td class="uk-width-1-1">
+ <div class="uk-progress uk-progress-mini uk-margin-remove">
+ <div rv-class-uk-progress-bar="player:track:position" rv-width="player:track:position | percent player:track:play_length"></div>
+ </div>
+ </td>
+ <td><span class="uk-margin-small-left" rv-text="player:track:play_length | time"></span></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+
+ <table class="uk-table uk-table-condensed">
+ <thead>
+ <tr>
+ <td></td>
+ <td>#</td>
+ <td>Title</td>
+ <td>Author</td>
+ <td>Game</td>
+ <td>System</td>
+ <td>Copyright</td>
+ <td>Dumper</td>
+ <td>Length</td>
+ </tr>
+ </thead>
+ <tbody data-tracks></tbody>
+ </table>
+
+ <pre style="white-space: pre-wrap; word-break: break-all; word-wrap: break-word;" data-debug></pre>
+ </div>
+
+ <script type="text/template" id="track-template">
+ <tr class="uk-visible-hover" rv-class-uk-active="track:status | is 'playing'">
+ <td><a class="uk-link-muted uk-invisible" rv-on-click="view.play"><i class="uk-icon-play"></i></a></td>
+ <td rv-text="track:id"></td>
+ <td rv-text="track:song"></td>
+ <td rv-text="track:author"></td>
+ <td rv-text="track:game"></td>
+ <td rv-text="track:system"></td>
+ <td rv-text="track:copyright"></td>
+ <td rv-text="track:dumper"></td>
+ <td rv-text="track:play_length | time"></td>
+ </tr>
+ </script>
+
+ <script src="/ui.js"></script>
+ <script src="/libgme.Oz.js"></script>
+ <script src="/player.js"></script>
+
+ <script>
+ $(function() {
+ var player = window.player = new Player();
+
+ var front = window.front = new FrontView({
+ el: '[data-front]',
+ model: player
+ });
+
+ var trackList = window.trackList = new TrackListView({
+ el: '[data-tracks]',
+ collection: player.tracks
+ });
+
+ var Router = Backbone.Router.extend({
+ routes: {
+ '*url': 'load'
+ },
+
+ load: function(url) {
+ loadArrayBuffer(url, function(xhr) {
+ player.load(xhr.response);
+ front.$el.removeClass('uk-hidden');
+ $('[data-debug]').text(JSON.stringify(player.tracks.toJSON()));
+ if (player.tracks.length > 0) {
+ player.startTrack(0);
+ if (player.get('autoplay')) {
+ player.play();
+ }
+ }
+ }, function(xhr) {
+ console.log('fail');
+ });
+ }
+ });
+
+ var router = window.router = new Router();
+ Backbone.history.start();
+ });
+ </script>
+ </body>
+</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/player.js Tue Oct 11 13:11:15 2016 +0800
@@ -0,0 +1,193 @@
+/* Based on mozplayer.js by Daniel Svensson, dsvensson@gmail.com */
+
+function parseMetadata(ref) {
+ var offset = 0;
+
+ var read_int32 = function() {
+ var value = Module.getValue(ref + offset, 'i32');
+ offset += 4;
+ return value;
+ };
+
+ var read_string = function() {
+ var value = Module.Pointer_stringify(Module.getValue(ref + offset, 'i8*'));
+ offset += 4;
+ return value;
+ };
+
+ var res = {};
+
+ res.length = read_int32();
+ res.intro_length = read_int32();
+ res.loop_length = read_int32();
+ res.play_length = read_int32();
+
+ offset += 4 * 12; // skip reserved fields
+
+ res.system = read_string();
+ res.game = read_string();
+ res.song = read_string();
+ res.author = read_string();
+ res.copyright = read_string();
+ res.comment = read_string();
+ res.dumper = read_string();
+
+ return res;
+}
+
+function loadArrayBuffer(url, done, fail) {
+ var xhr = new XMLHttpRequest();
+
+ xhr.open('GET', url, true);
+ xhr.responseType = 'arraybuffer';
+ xhr.onload = function() {
+ if (this.status != 200) {
+ fail(this);
+ } else {
+ done(this);
+ }
+ };
+ xhr.send();
+}
+
+
+var Player = Backbone.Model.extend({
+ // libgme output is in stereo only
+ channels: 2,
+ // largest signed short integer value (16 bits), for scaling signal
+ i16Max: Math.pow(2, 16 - 1) - 1,
+ tracks: new Tracks(),
+ defaults: {
+ autoplay: false,
+ repeat: false,
+ random: false,
+ track: new Track()
+ },
+
+ initialize: function() {
+ window.AudioContext = (window.AudioContext || window.webkitAudioContext);
+ if (window.AudioContext) {
+ this.ctx = new window.AudioContext();
+ this.ctxSize = 1024 * 4;
+ this.bufferSize = this.ctxSize * this.channels;
+ this.ref = Module._malloc(4);
+ this.buffer = Module._malloc(this.bufferSize * 2);
+ console.debug('Using Web Audio API');
+ } else {
+ console.error('Web Audio API is not supported, try another browser');
+ }
+ },
+
+ load: function(data) {
+ this.unload();
+
+ var payload = new Uint8Array(data);
+ var error = Module.ccall('gme_open_data', 'string', ['array', 'number', 'number', 'number'], [payload, payload.length, this.ref, this.ctx.sampleRate]);
+ if (error !== '') {
+ console.error('gme_open_data failed: ' + error);
+ return;
+ }
+
+ this.emu = Module.getValue(this.ref, 'i32');
+ var trackCount = Module.ccall('gme_track_count', 'number', ['number'], [this.emu]);
+
+ var tis = [];
+ for (var i = 0; i < trackCount; i++) {
+ var trackInfo = this.getTrackInfo(i);
+ trackInfo.id = i;
+ tis.push(trackInfo);
+ }
+ this.tracks.reset(tis);
+ },
+
+ unload: function() {
+ if (this.emu !== null) {
+ Module.ccall('gme_delete', 'number', ['number'], [this.emu]);
+ delete this.emu;
+ }
+ },
+
+ getTrackInfo: function(track) {
+ var error = Module.ccall('gme_track_info', 'string', ['number', 'number', 'number'], [this.emu, this.ref, track]);
+ if (error !== '') {
+ console.error('gme_track_info failed: ' + error);
+ return;
+ }
+ var memory = Module.getValue(this.ref, '*');
+ var result = parseMetadata(memory);
+ Module.ccall('gme_free_info', 'number', ['number'], [memory]);
+ return result;
+ },
+
+ startTrack: function(track) {
+ this.stop();
+
+ var error = Module.ccall('gme_start_track', 'string', ['number', 'number'], [this.emu, track]);
+ if (error !== '') {
+ console.error('gme_start_track failed: ' + error);
+ return;
+ }
+ this.set('track', this.tracks.get(track));
+ this.get('track').set('status', 'stopped');
+ },
+
+ isTrackEnded: function() {
+ return Module.ccall('gme_track_ended', 'number', ['number'], [this.emu]) === 1;
+ },
+
+ seek: function(msec) {
+ var error = Module.ccall('gme_seek', 'string', ['number', 'number'], [this.emu, msec]);
+ if (error !== '') {
+ console.error('gme_seek failed: ' + error);
+ }
+ },
+
+ tell: function() {
+ return Module.ccall('gme_tell', 'number', ['number'], [this.emu]);
+ },
+
+ voiceCount: function() {
+ return Module.ccall('gme_voice_count', 'number', ['number'], [this.emu]);
+ },
+
+ stop: function() {
+ if (this.node) {
+ this.node.disconnect();
+ delete this.node;
+ }
+ this.get('track').set({'status': 'stopped', 'position': this.tell()});
+ },
+
+ play: function() {
+ var limit = this.i16Max;
+ var player = this;
+
+ this.node = this.ctx.createScriptProcessor(this.ctxSize, 0, this.channels);
+ this.node.onaudioprocess = function(e) {
+ if (player.isTrackEnded()) {
+ player.stop();
+ player.trigger('ended');
+ return;
+ }
+
+ var channels = [e.outputBuffer.getChannelData(0), e.outputBuffer.getChannelData(1)];
+
+ var error = Module.ccall('gme_play', 'string', ['number', 'number', 'number'], [player.emu, player.bufferSize, player.buffer]);
+ if (error !== '') {
+ console.error('gme_play failed: ' + error);
+ return;
+ }
+
+ for (var i = 0; i < player.bufferSize; i++) {
+ for (var n = 0; n < e.outputBuffer.numberOfChannels; n++) {
+ channels[n][i] = Module.getValue(player.buffer + i * e.outputBuffer.numberOfChannels * 2 + n * 2, 'i16') / limit;
+ }
+ }
+
+ player.get('track').set('position', player.tell());
+ };
+
+ this.node.connect(this.ctx.destination);
+ this.get('track').set('status', 'playing');
+ }
+});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui.js Tue Oct 11 13:11:15 2016 +0800
@@ -0,0 +1,148 @@
+rivets.adapters[':'] = {
+ observe: function(obj, keypath, callback) {
+ obj.on('change:' + keypath, callback);
+ },
+ unobserve: function(obj, keypath, callback) {
+ obj.off('change:' + keypath, callback);
+ },
+ get: function(obj, keypath) {
+ return obj.get(keypath);
+ },
+ set: function(obj, keypath, value) {
+ obj.set(keypath, value);
+ }
+};
+
+rivets.formatters.time = function(ms) {
+ if (isNaN(ms)) {
+ return '0:00';
+ }
+ var s = Math.round(ms / 1000);
+ var m = Math.floor(s / 60);
+ s = s % 60;
+ if (s < 10) {
+ s = '0' + s;
+ }
+ return m + ':' + s;
+};
+
+rivets.formatters.percent = function(value, total) {
+ var ratio = value / total;
+ if (isNaN(ratio) || !isFinite(ratio)) {
+ return null;
+ } else {
+ return Math.min(ratio, 1.0) * 100 + '%';
+ }
+};
+
+rivets.formatters.is = function(a, b) {
+ return a === b;
+};
+
+rivets.binders.width = function(el, value) {
+ el.style.width = value;
+};
+
+var Track = Backbone.Model.extend({
+ defaults: {
+ song: 'untitled'
+ }
+});
+
+var Tracks = Backbone.Collection.extend({
+ model: Track
+});
+
+var FrontView = Backbone.View.extend({
+ initialize: function() {
+ this.rivet = rivets.bind(this.el, {player: this.model, front: this});
+ this.listenTo(this.model, 'ended', this.advance);
+ },
+ remove: function() {
+ this.rivet.unbind();
+ Backbone.View.prototype.remove.apply(this);
+ },
+ advance: function() {
+ this.fastForward(null, {player: this.model});
+ },
+
+ fastBackward: function(event, context) {
+ var trackNumber = (context.player.get('track').get('id') || 0) - 1;
+ if (trackNumber < 0 && context.player.get('repeat')) {
+ trackNumber = context.player.tracks.length - 1;
+ }
+ if (trackNumber >= 0 && trackNumber < context.player.tracks.length) {
+ context.player.startTrack(trackNumber);
+ context.player.play();
+ }
+ },
+ fastForward: function(event, context) {
+ var trackNumber = (context.player.get('track').get('id') || 0) + 1;
+ if (trackNumber >= context.player.tracks.length && context.player.get('repeat')) {
+ trackNumber = 0;
+ }
+ if (trackNumber >= 0 && trackNumber < context.player.tracks.length) {
+ context.player.startTrack(trackNumber);
+ context.player.play();
+ }
+ },
+ play: function(event, context) {
+ context.player.play();
+ },
+ pause: function(event, context) {
+ context.player.stop();
+ },
+ stop: function(event, context) {
+ context.player.seek(0);
+ context.player.stop();
+ },
+ toggleAutoplay: function(event, context) {
+ context.player.set('autoplay', !context.player.get('autoplay'));
+ },
+ toggleRepeat: function(event, context) {
+ context.player.set('repeat', !context.player.get('repeat'));
+ },
+ toggleRandom: function(event, context) {
+ context.player.set('random', !context.player.get('random'));
+ }
+});
+
+var TrackView = Backbone.View.extend({
+ templateEl: $($.trim($('#track-template').html())),
+
+ initialize: function() {
+ var $el = this.templateEl.clone();
+ this.setElement($el);
+ this.rivet = rivets.bind(this.el, {track: this.model, view: this});
+ },
+ remove: function() {
+ this.rivet.unbind();
+ Backbone.View.prototype.remove.apply(this);
+ },
+
+ play: function(event, context) {
+ window.player.startTrack(context.track.get('id'));
+ window.player.play();
+ }
+});
+
+var TrackListView = Backbone.View.extend({
+ initialize: function() {
+ this.listenTo(this.collection, 'reset', this.render);
+ },
+ render: function(collection, options) {
+ _(options.previousModels).each(function(model) {
+ model.view.remove();
+ });
+ this.$el.empty();
+ this.addAll();
+ return this;
+ },
+ addOne: function(track) {
+ track.view = new TrackView({model: track});
+ this.$el.append(track.view.el);
+ },
+ addAll: function() {
+ this.collection.each(this.addOne, this);
+ }
+});