Skip to content
This repository has been archived by the owner on May 21, 2019. It is now read-only.

Commit

Permalink
Begin work on recording puppet show #13, #4
Browse files Browse the repository at this point in the history
- Set up module for recording audio and all events
- Set up microphone
- Record to memory
  • Loading branch information
brianchirls committed Mar 3, 2017
1 parent c7cb0e0 commit 4652606
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"object-assign": "^4.0.1",
"open-iconic": "^1.1.1",
"raw-loader": "^0.5.1",
"recorderjs": "git+https://github.com/mattdiamond/Recorderjs.git",
"safe-json-stringify": "^1.0.3",
"style-loader": "^0.13.0",
"three": "^0.84.0",
Expand Down
2 changes: 2 additions & 0 deletions src/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
padding: 10px;
z-index: 1;
}

</style>
</head>

Expand All @@ -66,6 +67,7 @@

<div id="show-buttons">
<button id="new-show">New Puppet Show</button>
<button id="record">Record</button>
</div>
</body>

Expand Down
49 changes: 49 additions & 0 deletions src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require('imports?THREE=three!three/examples/js/vr/ViveController');

import SoundEffect from './sound-effect';
import PuppetShow from './puppet-show';
import PuppetShowRecorder from './puppet-show-recorder';

// Setup three.js WebGL renderer. Note: Antialiasing is a big performance hit.
// Only enable it if you actually need to.
Expand Down Expand Up @@ -312,6 +313,49 @@ document.getElementById('new-show').addEventListener('click', () => {
puppetShow.create();
});

/*
Set up recording
todo: nicer interface
todo: drop-down to select microphone/input if there's more than one (i.e. Vive)
todo: maybe load recorder code in a separate chunk, only on supported devices
*/
const puppetShowRecorder = new PuppetShowRecorder({
puppetShow,
audioContext
});

const recordButton = document.getElementById('record');
recordButton.disabled = true;
puppetShowRecorder
.on('ready', () => {
recordButton.disabled = false;
})
.on('error', () => {
recordButton.disabled = true;
// todo: report error. try again?
})
.on('start', () => {
recordButton.innerHTML = 'Stop';
})
.on('stop', () => {
recordButton.innerHTML = 'Reset';
})
.on('reset', () => {
recordButton.innerHTML = 'Record';
});

recordButton.addEventListener('click', () => {
if (puppetShowRecorder.recording) {
puppetShowRecorder.stop();
} else if (!puppetShowRecorder.currentTime) {
puppetShowRecorder.start();
} else {
// todo: require confirmation?
puppetShowRecorder.reset();
}
});

// Request animation frame loop function
let vrDisplay = null;
let lastRender = 0;
Expand Down Expand Up @@ -351,6 +395,11 @@ function animate(timestamp) {
renderer.render(scene, windowCamera);
}

// temp
if (puppetShowRecorder.recording) {
console.log(puppetShowRecorder.currentTime);
}

// Keep looping.
if (isPresenting) {
vrDisplay.requestAnimationFrame(animate);
Expand Down
7 changes: 7 additions & 0 deletions src/js/now.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

const now = typeof performance === 'undefined' ? Date.now : function () {
return performance.now();
};

export default now;
174 changes: 174 additions & 0 deletions src/js/puppet-show-recorder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
'use strict';

import Recorder from 'recorderjs';
import eventEmitter from 'event-emitter';
import now from './now';

function PuppetShowRecorder(options) {
/*
todo:
- record puppet positions
- record sound effects
- record background switches
- handle missing gUM implementation or no devices
- append recording
- save events and assets to puppetShow
- release microphone when page is in backround and not presenting?
*/

eventEmitter(this);

const me = this;

const {
puppetShow,
audioContext
} = options;

const recordConstraints = {
audio: {
channelCount: 1
}
};

const audioInputDevices = [];

// state
let ready = false;
let recording = false;
let startTime = 0;
let endTime = 0;

let audioInputDevice = null;
let audioStream = null;
let audioSource = null;
let audioRecorder = null;

function getAudioStream() {
navigator.mediaDevices.getUserMedia(recordConstraints).then(stream => {
console.log('Accessed Microphone');
// todo: update UI to show recording state

audioSource = audioContext.createMediaStreamSource(stream);

// need to connect to a destination. otherwise it won't process
// const zeroGain = audioContext.createGain();
// zeroGain.gain.value = 0.0;
// audioSource.connect(zeroGain);
// zeroGain.connect(audioContext.destination);

if (audioRecorder) {
audioRecorder.stop();
audioRecorder.clear();
// todo: what happens if we're recording?
}

audioRecorder = new Recorder(audioSource, {
numChannels: 1
});

const wasReady = ready;
ready = true;
if (!wasReady) {
me.emit('ready');
}
}).catch(err => {
console.error('Cannot access microphone', err);
// todo: fire error event; set state to not ready
me.emit('error', err);
});
}

this.init = () => {
navigator.mediaDevices.enumerateDevices().then(devices => {
audioInputDevices.length = 0;
devices.forEach(dev => {
if (dev.kind === 'audioinput') {
audioInputDevices.push(dev);

// todo: prioritize Vive or Rift mic
if (dev.deviceId === 'default' || !audioInputDevice) {
audioInputDevice = dev;
}
}
});

recordConstraints.audio.deviceId = audioInputDevice.deviceId;
// todo: get audio stream only if device changed and not recording
getAudioStream();
});
};

this.start = () => {
if (recording) {
// todo: throw error? or emit error event?
return;
}

if (!ready) {
throw new Error('PuppetShowRecorder: Not ready to record');
}

// todo: allow appending
this.reset();

recording = true;
startTime = now();
audioRecorder.record();

this.emit('start');
};

this.stop = () => {
if (!recording) {
// todo: throw error? or emit error event?
return;
}

recording = false;
endTime = now();
audioRecorder.stop();

// todo: save audio asset to puppetShow if not being cleared?

this.emit('stop');
};

this.reset = () => {
this.stop();

if (!this.currentTime) {
return;
}

if (audioRecorder) {
audioRecorder.clear();
}
startTime = 0;
endTime = 0;

// todo: clear data out of puppetShow

this.emit('reset');
};

// todo: query recording devices
// todo: select audio recording device

this.init();

Object.defineProperties(this, {
ready: {
get: () => ready
},
currentTime: {
get: () => ((recording ? now() : endTime) - startTime) / 1000
},
recording: {
get: () => recording
}
});
}

export default PuppetShowRecorder;

0 comments on commit 4652606

Please sign in to comment.