Skip to content

Commit

Permalink
Play/Pause/Stop buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
dumbmatter committed Jun 12, 2016
1 parent 1d7d90d commit 8a0cf73
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 41 deletions.
105 changes: 69 additions & 36 deletions src/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ const React = require('react');
const {SimpleFilter, SoundTouch} = require('../lib/soundtouch');
const ErrorAlert = require('./ErrorAlert.jsx');
const FilenameLabel = require('./FilenameLabel.jsx');
const TrackControls = require('./TrackControls.jsx');

const BUFFER_SIZE = 4096;

class App extends React.Component {
constructor(props) {
super(props);

this.state = {
bufferSource: undefined,
action: 'stop',
error: undefined,
filename: undefined,
pitch: 0.8,
Expand All @@ -18,6 +21,7 @@ class App extends React.Component {
};

this.audioContext = new AudioContext();
this.scriptProcessor = this.audioContext.createScriptProcessor(BUFFER_SIZE, 2, 2);

this.soundTouch = new SoundTouch();
this.soundTouch.pitch = this.state.pitch;
Expand All @@ -34,16 +38,13 @@ class App extends React.Component {
});
}


playSound(buffer) {
setBuffer(buffer) {
const bufferSource = this.audioContext.createBufferSource();
bufferSource.buffer = buffer;

const BUFFER_SIZE = 4096;
const node = this.audioContext.createScriptProcessor(BUFFER_SIZE, 2, 2);
const samples = new Float32Array(BUFFER_SIZE * 2);

const source = {
this.source = {
extract: (target, numFrames, position) => {
const l = buffer.getChannelData(0);
const r = buffer.getChannelData(1);
Expand All @@ -54,34 +55,45 @@ class App extends React.Component {
return Math.min(numFrames, l.length - position);
},
};
const f = new SimpleFilter(source, this.soundTouch);

node.onaudioprocess = e => {
this.f = new SimpleFilter(this.source, this.soundTouch);
this.scriptProcessor.onaudioprocess = e => {
const l = e.outputBuffer.getChannelData(0);
const r = e.outputBuffer.getChannelData(1);
const framesExtracted = f.extract(samples, BUFFER_SIZE);
const framesExtracted = this.f.extract(samples, BUFFER_SIZE);
if (framesExtracted === 0) {
node.disconnect(this.audioContext.destination);
this.pause();
}
for (let i = 0; i < framesExtracted; i++) {
l[i] = samples[i * 2];
r[i] = samples[i * 2 + 1];
}
};

node.connect(this.audioContext.destination);
// bufferSource.connect(this.audioContext.destination);
// bufferSource.start(0);
this.play();
}

play() {
if (this.state.action !== 'play') {
this.scriptProcessor.connect(this.audioContext.destination);
this.emitter.emit('state', {action: 'play'});
}
}

this.setState({bufferSource});
pause() {
if (this.state.action === 'play') {
this.scriptProcessor.disconnect(this.audioContext.destination);
this.emitter.emit('state', {action: 'pause'});
}
}

stop() {
this.pause();
this.f.sourcePosition = 0;
this.emitter.emit('state', {action: 'stop'});
}

handleFileChange(e) {
if (e.target.files.length > 0) {
if (this.state.bufferSource) {
this.state.bufferSource.stop();
}

this.emitter.emit('status', 'Reading file...');
this.emitter.emit('state', {
error: undefined,
Expand Down Expand Up @@ -113,24 +125,20 @@ class App extends React.Component {
return;
}

this.playSound(buffer);
this.setBuffer(buffer);
};
}
}

handlePitchChange(e) {
const pitch = e.target.value;
if (this.state.bufferSource) {
this.soundTouch.pitch = pitch;
}
this.soundTouch.pitch = pitch;
this.setState({pitch});
}

handleTempoChange(e) {
const tempo = e.target.value;
if (this.state.bufferSource) {
this.soundTouch.tempo = tempo;
}
this.soundTouch.tempo = tempo;
this.setState({tempo});
}

Expand All @@ -154,36 +162,61 @@ class App extends React.Component {
</label>
<FilenameLabel error={this.state.error} filename={this.state.filename} />
</div>

<ErrorAlert error={this.state.error} />

<div className="row">
<div className="col-sm-3 col-md-2" style={{paddingTop: '7px'}}>
<div className="col-xs-5 col-sm-3 col-lg-2" style={{paddingTop: '7px'}}>
<TrackControls
action={this.state.action}
error={this.state.error}
filename={this.state.filename}
onPlay={() => this.play()}
onPause={() => this.pause()}
onStop={() => this.stop()}
/>
</div>
<div className="col-xs-7 col-sm-9 col-lg-10">
<input
className="form-control"
type="range"
min="0.05"
max="2"
step="0.05"
defaultValue={this.state.pitch}
onChange={e => this.handlePitchChange(e)}
/>
</div>
</div>

<div className="row">
<div className="col-xs-5 col-sm-3 col-lg-2" style={{paddingTop: '7px'}}>
Pitch ({this.state.pitch}x)
</div>
<div className="col-sm-9 col-md-10">
<div className="col-xs-7 col-sm-9 col-lg-10">
<input
className="form-control"
id="speed-slider"
type="range"
min="0.1"
min="0.05"
max="2"
step="0.1"
step="0.05"
defaultValue={this.state.pitch}
onChange={e => this.handlePitchChange(e)}
/>
</div>
</div>

<div className="row">
<div className="col-sm-3 col-md-2" style={{paddingTop: '7px'}}>
<div className="col-xs-5 col-sm-3 col-lg-2" style={{paddingTop: '7px'}}>
Tempo ({this.state.tempo}x)
</div>
<div className="col-sm-9 col-md-10">
<div className="col-xs-7 col-sm-9 col-lg-10">
<input
className="form-control"
id="speed-slider"
type="range"
min="0.1"
min="0.05"
max="2"
step="0.1"
step="0.05"
defaultValue={this.state.tempo}
onChange={e => this.handleTempoChange(e)}
/>
Expand Down
57 changes: 57 additions & 0 deletions src/components/TrackControls.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const React = require('react');

const TrackControls = props => {
const disabled = props.error !== undefined || props.filename === undefined;
console.log(disabled, props);

let playOrPause;
if (props.action === 'play') {
playOrPause = (
<button
className="btn btn-secondary btn-sm"
disabled={disabled}
onClick={props.onPause}
>
Pause
</button>
);
} else {
playOrPause = (
<button
className="btn btn-secondary btn-sm"
disabled={disabled}
onClick={props.onPlay}
>
Play
</button>
);
}

return (
<div>
{playOrPause}
<button
className="btn btn-secondary btn-sm"
disabled={disabled}
onClick={props.onStop}
style={{marginLeft: '0.25em'}}
>
Stop
</button>
</div>
);
};

TrackControls.propTypes = {
action: React.PropTypes.string,
error: React.PropTypes.shape({
type: React.PropTypes.string.isRequired,
message: React.PropTypes.string.isRequired,
}),
filename: React.PropTypes.string,
onPause: React.PropTypes.func,
onPlay: React.PropTypes.func,
onStop: React.PropTypes.func,
};

module.exports = TrackControls;
2 changes: 1 addition & 1 deletion src/css/my.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.filename-label {
padding-top: 16px;
vertical-align: 2px;
}
}
9 changes: 5 additions & 4 deletions src/lib/soundtouch.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ FifoSampleBuffer.prototype = {
},

clear: function() {
this.receive(frameCount);
this.receive(this._frameCount);
this.rewind();
},

Expand Down Expand Up @@ -1003,8 +1003,8 @@ function SoundTouch() {

extend(SoundTouch.prototype, {
clear: function () {
rateTransposer.clear();
tdStretch.clear();
this.rateTransposer.clear();
this.tdStretch.clear();
},

clone: function () {
Expand Down Expand Up @@ -1108,5 +1108,6 @@ extend(SoundTouch.prototype, {
}
});

// This is the only part that was added for screw
// This is the only part that was added for screw (plus fixing a couple typos where `this` was
// erroneously not used)
module.exports = {SimpleFilter, SoundTouch};

0 comments on commit 8a0cf73

Please sign in to comment.