Skip to content

Commit

Permalink
feat(game/game-loader): load keysounds
Browse files Browse the repository at this point in the history
  • Loading branch information
dtinth committed Feb 18, 2015
1 parent f4bb774 commit c04a4e7
Show file tree
Hide file tree
Showing 14 changed files with 282 additions and 18 deletions.
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"jasmine": "^2.1.1",
"js-yaml": "^3.2.5",
"jscs": "^1.9.0",
"jshint": "^2.5.11",
"jshint": "^2.6.0",
"jshint-stylish": "^1.0.0",
"merge-stream": "^0.1.6",
"mocha": "^2.1.0",
Expand All @@ -66,6 +66,8 @@
"ractive-loader": "^0.5.6",
"sass-loader": "^0.3.1",
"script-loader": "^0.6.1",
"sinon": "git://github.com/cjohansen/Sinon.JS#sinon-2.0",
"sinon-chai": "^2.7.0",
"style-loader": "^0.8.2",
"through2": "^0.6.3",
"url-loader": "^0.5.5",
Expand All @@ -75,19 +77,21 @@
},
"dependencies": {
"audio-context": "^0.1.0",
"bluebird": "^2.9.6",
"bms": "^0.5.0",
"bluebird": "^2.9.9",
"bms": "^0.6.0",
"bytes": "^1.0.0",
"chance": "^0.7.3",
"co": "^4.3.1",
"data-structure": "^1.2.0",
"debug": "^2.1.1",
"jquery": "^2.1.3",
"keytime": "^0.1.0",
"lazy-property": "^1.0.0",
"once": "^1.3.1",
"pixi.js": "^2.2.3",
"ractive": "^0.6.1",
"ramda": "^0.9.1",
"throat": "^2.0.2",
"vue": "^0.11.4"
}
}
52 changes: 52 additions & 0 deletions spec/game/audio-loader_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

import AudioLoader from 'bemuse/game/audio-loader'

describe('AudioLoader', function() {

describe('load', function() {

var assets
var keysounds
var master
var loader

beforeEach(function() {
assets = { file: sinon.stub() }
keysounds = { files: sinon.stub() }
master = { sample: sinon.stub() }
loader = new AudioLoader(assets, master)
})

it('should load file when matches', function() {
keysounds.files.returns(['a.wav'])
assets.file.withArgs('a.wav').returns(Promise.resolve({
read: () => Promise.resolve('ok1')
}))
master.sample.withArgs('ok1').returns(Promise.resolve('ok2'))
return expect(loader.loadFrom(keysounds)).to.eventually.deep.eq({
'a.wav': 'ok2'
})
})

it('should try mp3', function() {
keysounds.files.returns(['a.wav'])
assets.file.withArgs('a.wav').returns(Promise.reject())
assets.file.withArgs('a.mp3').returns(Promise.resolve({
read: () => Promise.resolve('ok1')
}))
master.sample.withArgs('ok1').returns(Promise.resolve('ok2'))
return expect(loader.loadFrom(keysounds)).to.eventually.deep.eq({
'a.wav': 'ok2'
})
})

it('should not include failed matches', function() {
keysounds.files.returns(['a.wav'])
assets.file.withArgs('a.wav').returns(Promise.reject())
assets.file.withArgs('a.mp3').returns(Promise.reject())
return expect(loader.loadFrom(keysounds)).to.eventually.deep.eq({ })
})

})

})
1 change: 1 addition & 0 deletions spec/resources/fixtures/a/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"files":[{"name":"do.mp3","ref":[0,0,30093]},{"name":"mi.mp3","ref":[0,30093,60186]},{"name":"sol.mp3","ref":[0,60186,90279]}],"refs":[{"path":"mp3.0.ea6087c3.bemuse","hash":"ea6087c3379bce007e50c3100b99b83c"}]}
Binary file added spec/resources/fixtures/a/mp3.0.ea6087c3.bemuse
Binary file not shown.
30 changes: 30 additions & 0 deletions spec/resources/resources_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

import BemusePackageResources from 'bemuse/resources/bemuse-package'

describe('BemusePackageResources', function() {

describe('#file', function() {

var resources

beforeEach(function() {
resources = new BemusePackageResources('/spec/resources/fixtures/a/')
})

it('returns a file', function() {
return expect(resources.file('do.mp3')).to.be.fulfilled
})

it('rejects if file not found', function() {
return expect(resources.file('wow.mp3')).to.be.rejected
})

it('can be read', function() {
return expect(resources.file('mi.mp3')
.then(file => file.read())
.then(buffer => buffer.byteLength)).to.eventually.eq(30093)
})

})

})
56 changes: 56 additions & 0 deletions src/game/audio-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

import R from 'ramda'
import SamplingMaster from 'bemuse/sampling-master'
import throat from 'throat'

export class AudioLoader {
constructor(assets, master) {
this._assets = assets
this._master = master || new SamplingMaster()
}
loadFrom(keysounds) {
let files = keysounds.files()
let notifier = new ProgressNotifier(files.length,
this.audioTask, this.decodeTask)
let load = name => this._loadSample(name, notifier)
return Promise.map(files, load).then(R.fromPairs)
}
_loadSample(name, notifier) {
let decode = throat(8, buffer => this._master.sample(buffer))
return this._getFile(name)
.then(
file => file.read()
.tap(() => notifier.loaded(name))
.then(decode)
.tap(() => notifier.decoded(name))
.then(sample => [name, sample])
.catch(e => {
console.error('Unable to decode: ' + name, e)
return null
}),
() => null
)
}
_getFile(name) {
return this._assets.file(name)
.catch(() => this._assets.file(name.replace(/\.\w+$/, '.mp3')))
}
}

export default AudioLoader

function ProgressNotifier(total, load, decode) {
let a = 0
let b = 0
return {
loaded: function(name) {
a += 1
load.update({ current: a, total, progress: a / total })
void name
},
decoded: function(name) {
b += 1
decode.update({ text: 'Decoded ' + name, progress: b / total })
},
}
}
27 changes: 16 additions & 11 deletions src/game/game-loader.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@

import co from 'co'
import { basename } from 'path'
import BMS from 'bms'

import { EventEmitter } from 'events'
import TaskList from 'bemuse/task-list'
import LoadingContext from 'bemuse/boot/loading-context'
import download from 'bemuse/download'

import AudioLoader from './audio-loader'

export class GameLoader extends EventEmitter {
constructor() {
this._tasks = new TaskList()
this._tasks.on('progress', () => this.emit('progress'))
}
load(bms) {
load(song) {
return co(function*() {
let promises = {
graphics: this._loadEngine(),
song: this._loadSong(bms),
song: this._loadSong(song),
}
yield Promise.all([promises.graphics, promises.song])
}.bind(this))
Expand All @@ -35,21 +35,26 @@ export class GameLoader extends EventEmitter {
return { skin, context }
}.bind(this))
}
_loadSong(bms) {
_loadSong(song) {
let { bms, assets } = song
let tasks = {
bms: this._task('Loading ' + basename(bms)),
bms: this._task('Loading ' + bms.name),
pack: this._task('Loading song package'),
audio: this._task('Loading audio'),
bga: this._task('Loading BGA'),
decode: this._task('Decoding audio'),
}
return co(function*() {
let buffer = yield download(bms).as('arraybuffer', tasks.bms)
let source = yield readBMS(buffer)
let buffer = yield bms.read(tasks.bms)
let source = yield readBMS(buffer)
let compileResult = BMS.Compiler.compile(source)
let chart = compileResult.chart
let songInfo = BMS.SongInfo.fromBMSChart(chart)
void songInfo
let chart = compileResult.chart
let keysounds = BMS.Keysounds.fromBMSChart(chart)
let audioLoader = new AudioLoader(assets)
audioLoader.audioTask = tasks.audio
audioLoader.decodeTask = tasks.decode
let audio = yield audioLoader.loadFrom(keysounds)
console.log(audio)
}.bind(this))
}
_task(text) {
Expand Down
8 changes: 7 additions & 1 deletion src/game/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import SCENE_MANAGER from 'bemuse/scene-manager'
import LoadingScene from './loading-scene'
import GameLoader from './game-loader'

import URLResource from 'bemuse/resources/url'
import BemusePackageResources from 'bemuse/resources/bemuse-package'

export function main() {
let song = {
title: 'オリヴィアの幻術',
Expand All @@ -16,7 +19,10 @@ export function main() {
],
}
let loader = new GameLoader()
let promise = loader.load('/music/[aoi]olivia/olivia_SPpp.bml')
let promise = loader.load({
bms: new URLResource('/music/[aoi]olivia/olivia_SPpp.bml'),
assets: new BemusePackageResources('/music/[aoi]olivia/assets/'),
})
SCENE_MANAGER.display(new LoadingScene({ loader, song }))
promise.then(function() {
SCENE_MANAGER.display(null)
Expand Down
4 changes: 4 additions & 0 deletions src/polyfill/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ import Bluebird from 'bluebird'

global.DEBUG = debug
global.Promise = Bluebird

Promise.prototype.log = function(...args) {
return this.tap(value => console.log(...args.concat([value])))
}
84 changes: 84 additions & 0 deletions src/resources/bemuse-package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

import { resolve } from 'url'
import addLazyProperty from 'lazy-property'
import R from 'ramda'
import download from 'bemuse/download'
import readBlob from 'bemuse/read-blob'
import throat from 'throat'

export class BemusePackageResources {
constructor(url) {
let lazy = addLazyProperty.bind(null, this)
this._url = url
lazy('metadata', () =>
download(resolve(this._url, 'metadata.json')).as('text')
.then(str => JSON.parse(str)))
lazy('refs', () =>
this.metadata.then(
metadata => metadata.refs.map(spec =>
new Ref(this, spec))))
this.getBlob = throat(2, this.getBlob)
}
get url() {
return this._url
}
file(name) {
return this.metadata.then(metadata => {
let file = R.find(file => file.name === name, metadata.files)
if (!file) throw new Error('Unable to find: ' + name)
return new BemusePackageFileResource(this, file.ref)
})
}
getBlob(ref) {
let [index, start, end] = ref
return this.refs
.then(refs => refs[index])
.then(ref => ref.load())
.then(payload => payload.slice(start, end))
}
}

class BemusePackageFileResource {
constructor(resources, ref) {
this._resources = resources
this._ref = ref
}
read() {
return this._resources.getBlob(this._ref)
.then(blob => readBlob(blob).as('arraybuffer'))
}
}

class Ref {
constructor(resources, spec) {
this._load = () =>
this._promise || (this._promise =
download(resolve(resources.url, spec.path)).as('blob')
.then(getPayload))
}
load() {
return this._load()
}
}

export default BemusePackageResources

function getPayload(blob) {
return readBlob(blob.slice(0, 10)).as('text')
.then(magic => {
if (magic !== 'BEMUSEPACK') {
throw new Error('Invalid magic number')
}
return readBlob(blob.slice(10, 14)).as('arraybuffer')
})
.then(buffer => {
let array = new Uint8Array(buffer)
let length = array[0] +
(array[1] << 8) +
(array[2] << 16) +
(array[3] << 24)
return length
})
.then(metadataLength => blob.slice(14 + metadataLength))
}

17 changes: 17 additions & 0 deletions src/resources/url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

import download from 'bemuse/download'
import { basename } from 'path'

export class URLResource {
constructor(url) {
this._url = url
}
read(task) {
return download(this._url).as('arraybuffer', task)
}
get name() {
return basename(this._url)
}
}

export default URLResource
Loading

0 comments on commit c04a4e7

Please sign in to comment.