summaryrefslogtreecommitdiff
path: root/app/client/modules/samplernn/views
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2018-06-05 20:11:11 +0200
committerJules Laplace <julescarbon@gmail.com>2018-06-05 20:11:11 +0200
commit18321f234e5261af43624b67c99a4dee3c060ed8 (patch)
tree12268da123e441d07e7d15f6c24210bc3f594522 /app/client/modules/samplernn/views
parent24ec0726bf254f4e4398cf2bfa0c794978367744 (diff)
move views into own folder and begin prising away dataset code
Diffstat (limited to 'app/client/modules/samplernn/views')
-rw-r--r--app/client/modules/samplernn/views/samplernn.graph.js159
-rw-r--r--app/client/modules/samplernn/views/samplernn.import.js145
-rw-r--r--app/client/modules/samplernn/views/samplernn.new.js35
-rw-r--r--app/client/modules/samplernn/views/samplernn.results.js84
-rw-r--r--app/client/modules/samplernn/views/samplernn.show.js118
5 files changed, 541 insertions, 0 deletions
diff --git a/app/client/modules/samplernn/views/samplernn.graph.js b/app/client/modules/samplernn/views/samplernn.graph.js
new file mode 100644
index 0000000..821f1cb
--- /dev/null
+++ b/app/client/modules/samplernn/views/samplernn.graph.js
@@ -0,0 +1,159 @@
+import { h, Component } from 'preact'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+
+import { lerp, norm, randint, randrange } from '../../../util'
+
+import * as samplernnActions from '../samplernn.actions'
+
+import Group from '../../../common/group.component'
+import Slider from '../../../common/slider.component'
+import Select from '../../../common/select.component'
+import Button from '../../../common/button.component'
+import { FileList } from '../../../common/fileList.component'
+import TextInput from '../../../common/textInput.component'
+
+class SampleRNNGraph extends Component {
+ constructor(props){
+ super()
+ props.actions.load_loss()
+ }
+ render(){
+ this.refs = {}
+ return (
+ <div className='app lossGraph'>
+ <div className='heading'>
+ <h3>SampleRNN Loss Graph</h3>
+ <canvas ref={(ref) => this.refs['canvas'] = ref} />
+ </div>
+ </div>
+ )
+ }
+ componentDidUpdate(){
+ const { lossReport } = this.props.samplernn
+ if (! lossReport) return
+ const canvas = this.refs.canvas
+ canvas.width = window.innerWidth
+ canvas.height = window.innerHeight
+ canvas.style.width = canvas.width + 'px'
+ canvas.style.height = canvas.height + 'px'
+
+ const ctx = canvas.getContext('2d')
+ const w = canvas.width = canvas.width * devicePixelRatio
+ const h = canvas.height = canvas.height * devicePixelRatio
+ ctx.clearRect(0,0,w,h)
+
+ const keys = Object.keys(lossReport).sort().filter(k => !!lossReport[k].length)
+ let scaleMax = 0
+ let scaleMin = Infinity
+ let epochsMax = 0
+ keys.forEach(key => {
+ const loss = lossReport[key]
+ epochsMax = Math.max(loss.length, epochsMax)
+ loss.forEach((a) => {
+ const v = parseFloat(a.training_loss)
+ if (! v) return
+ scaleMax = Math.max(v, scaleMax)
+ scaleMin = Math.min(v, scaleMin)
+ })
+ })
+ // scaleMax *= 10
+ console.log(scaleMax, scaleMin, epochsMax)
+
+ scaleMax = 3
+ scaleMin = 0
+ const margin = 0
+ const wmin = 0
+ const wmax = w
+ const hmin = 0
+ const hmax = h
+ const epochsScaleFactor = 1 // 3/2
+
+ let X, Y
+ for (var ii = 0; ii < epochsMax; ii++) {
+ X = lerp((ii)/(epochsMax/(epochsScaleFactor))*(epochsScaleFactor), wmin, wmax)
+ ctx.strokeStyle = 'rgba(0,0,0,0.3)'
+ ctx.beginPath(0, 0)
+ ctx.moveTo(X, 0)
+ ctx.lineTo(X, h)
+ ctx.lineWidth = 1
+ // ctx.stroke()
+ if ( (ii % 5) === 0 ) {
+ ctx.lineWidth = 2
+ ctx.stroke()
+ const fontSize = 12
+ ctx.font = 'italic ' + (fontSize * devicePixelRatio) + 'px "Georgia"'
+ ctx.fillStyle = 'rgba(0,12,28,0.6)'
+ ctx.fillText(ii/5*6, X + (8 * devicePixelRatio), h - ((fontSize + 4) * devicePixelRatio))
+ }
+ }
+ for (var ii = scaleMin; ii < scaleMax; ii += 1) {
+ Y = lerp(ii/scaleMax, hmin, hmax)
+ // ctx.strokeStyle = 'rgba(255,255,255,1.0)'
+ ctx.beginPath(0, 0)
+ ctx.moveTo(0, (h-Y))
+ ctx.lineTo(w, (h-Y))
+ ctx.lineWidth = 1
+ // ctx.stroke()
+ // if ( (ii % 1) < 0.1) {
+ // ctx.strokeStyle = 'rgba(255,255,255,1.0)'
+ ctx.lineWidth = 2
+ ctx.setLineDash([4, 4])
+ ctx.stroke()
+ ctx.stroke()
+ ctx.stroke()
+ ctx.setLineDash([0,0])
+ const fontSize = 12
+ ctx.font = 'italic ' + (fontSize * devicePixelRatio) + 'px "Georgia"'
+ ctx.fillStyle = 'rgba(0,12,28,0.6)'
+ ctx.fillText(ii.toFixed(1), w-50, (h-Y) + fontSize + (10 * devicePixelRatio))
+ // }
+ }
+ ctx.lineWidth = 1
+
+ keys.forEach(key => {
+ const loss = lossReport[key]
+ const vf = parseFloat(loss[loss.length-1].training_loss) || 0
+ const vg = parseFloat(loss[0].training_loss) || 5
+ // console.log(vf)
+ const vv = 1 - norm(vf, scaleMin, scaleMax/2)
+ ctx.lineWidth = (1-norm(vf, scaleMin, scaleMax)) * 5
+ ctx.strokeStyle = 'rgba(' + [randrange(30,190), randrange(30,150), randrange(60,120)].join(',') + ',' + 0.8+ ')'
+ let begun = false
+ loss.forEach((a, i) => {
+ const v = parseFloat(a.training_loss)
+ if (! v) return
+ const x = lerp((i-2)/(epochsMax/(epochsScaleFactor))*(epochsScaleFactor), wmin, wmax)
+ const y = lerp(norm(v, scaleMin, scaleMax), hmax, hmin)
+ if (i === 0) {
+ return
+ }
+ if (! begun) {
+ begun = true
+ ctx.beginPath(x,y)
+ } else {
+ ctx.lineTo(x,y)
+ // ctx.stroke()
+ }
+ })
+ ctx.stroke()
+ const i = loss.length-1
+ const v = parseFloat(loss[i].training_loss)
+ const x = lerp((i-2)/(epochsMax/(epochsScaleFactor))*(epochsScaleFactor), wmin, wmax)
+ const y = lerp(norm(v, scaleMin, scaleMax), hmax, hmin)
+ const fontSize = 9
+ ctx.font = 'italic ' + (fontSize * devicePixelRatio) + 'px "Georgia"'
+ ctx.fillText(key, x + fontSize, y + fontSize)
+ })
+ }
+}
+
+const mapStateToProps = state => ({
+ samplernn: state.module.samplernn,
+})
+
+const mapDispatchToProps = (dispatch, ownProps) => ({
+ actions: bindActionCreators(samplernnActions, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(SampleRNNGraph)
diff --git a/app/client/modules/samplernn/views/samplernn.import.js b/app/client/modules/samplernn/views/samplernn.import.js
new file mode 100644
index 0000000..6f5f78a
--- /dev/null
+++ b/app/client/modules/samplernn/views/samplernn.import.js
@@ -0,0 +1,145 @@
+import { h, Component } from 'preact'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+import * as util from '../../../util'
+
+import * as samplernnActions from '../samplernn.actions'
+
+import Select from '../../../common/select.component'
+import TextInput from '../../../common/textInput.component'
+import Button from '../../../common/button.component'
+
+import SampleRNNDatasets from '../samplernn.datasets'
+
+const samplernnModule = {
+ name: 'samplernn',
+ datatype: 'audio',
+}
+
+class SampleRNNImport extends Component {
+ constructor(){
+ super()
+ this.state = {
+ folder: 1,
+ import_action: 'Hotlink',
+ url_base: 'https://s3.amazonaws.com/i.asdf.us/bucky/data/4279/',
+ selected: {},
+ }
+ }
+ componentWillMount(){
+ const id = this.props.match.params.id || localStorage.getItem('samplernn.last_id')
+ console.log('load dataset:', id)
+ const { match, samplernn, samplernnActions } = this.props
+ if (id === 'new') return
+ if (id) {
+ if (parseInt(id)) localStorage.setItem('samplernn.last_id', id)
+ if (! samplernn.folder || samplernn.folder.id !== id) {
+ samplernnActions.load_directories(id)
+ }
+ }
+ }
+ render(){
+ let datasets = [], folder;
+ if (this.props.samplernn.data) {
+ datasets = (this.props.samplernn.data.folders || []).map(folder => {
+ return [folder.name, folder.id]
+ })
+ folder = this.props.samplernn.data.folderLookup.unsorted
+ }
+ return (
+ <div className='app top'>
+ <div class='heading'>
+ <h1>Import</h1>
+ </div>
+ <div class='params form row datasets'>
+ <div class='row dataset'>
+ <div class='col'>
+ </div>
+ <div class='col'>
+ </div>
+ <div class='col'>
+ </div>
+ <div class='col'>
+ <h2>Import to dataset</h2>
+ <Select
+ title='Destination dataset'
+ options={datasets}
+ name='folder'
+ opt={this.state}
+ onChange={(name, value) => this.setState({ folder: value })}
+ />
+ <Select
+ title='Import action'
+ options={['Hotlink', 'Upload']}
+ name='import_action'
+ opt={this.state}
+ onChange={(name, value) => this.setState({ import_action: value })}
+ />
+ <TextInput
+ title="Remote URL base"
+ value={this.state.url_base}
+ placeholder="http://"
+ onSave={(value) => this.setState({ url_base: value })}
+ />
+ <Button
+ title=""
+ onClick={() => this.doImport()}
+ >
+ Import
+ </Button>
+ </div>
+ </div>
+ </div>
+ <SampleRNNDatasets
+ id="unsorted"
+ folder={folder}
+ history={this.props.history}
+ onPickDataset={(dataset => this.toggle(dataset.name, this.state.selected[name]))}
+ beforeRow={dataset => this.beforeRow(dataset)}
+ afterRow={dataset => this.afterRow(dataset)}
+ />
+ </div>
+ )
+ }
+ toggle(name){
+ this.setState({
+ ...this.state,
+ selected: {
+ ...this.state.selected,
+ [name]: !this.state.selected[name],
+ }
+ })
+ }
+ beforeRow(dataset){
+ // console.log(dataset)
+ }
+ afterRow(dataset){
+ const name = dataset.name
+ return (
+ <div>
+ <input
+ type="checkbox"
+ value={name}
+ checked={!!this.state.selected[name]}
+ />
+ </div>
+ )
+ }
+ doImport(){
+ const { samplernn } = this.props
+ console.log(this.state)
+ this.props.actions.import_files(this.state, samplernn.data.datasetLookup, samplernn.data.fileLookup)
+ }
+}
+
+const mapStateToProps = state => ({
+ samplernn: state.module.samplernn,
+ runner: state.system.runner,
+ task: state.task,
+})
+
+const mapDispatchToProps = (dispatch, ownProps) => ({
+ actions: bindActionCreators(samplernnActions, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(SampleRNNImport)
diff --git a/app/client/modules/samplernn/views/samplernn.new.js b/app/client/modules/samplernn/views/samplernn.new.js
new file mode 100644
index 0000000..d76e6c0
--- /dev/null
+++ b/app/client/modules/samplernn/views/samplernn.new.js
@@ -0,0 +1,35 @@
+import { h, Component } from 'preact'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+import * as util from '../../../util'
+
+import * as samplernnActions from '../samplernn.actions'
+
+import DatasetForm from '../../../dataset/dataset.form'
+import NewDatasetForm from '../../../dataset/dataset.new'
+import { FileList, FileRow } from '../../../common/fileList.component'
+
+import samplernnModule from '../samplernn.module'
+
+class SampleRNNNew extends Component {
+ constructor(props){
+ super(props)
+ }
+ render(){
+ const { history } = this.props
+ return (
+ <div class='app samplernn'>
+ <NewDatasetForm module={samplernnModule} history={history} />
+ </div>
+ )
+ }
+}
+const mapStateToProps = state => ({
+ samplernn: state.module.samplernn,
+})
+
+const mapDispatchToProps = (dispatch, ownProps) => ({
+ actions: bindActionCreators(samplernnActions, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(SampleRNNNew)
diff --git a/app/client/modules/samplernn/views/samplernn.results.js b/app/client/modules/samplernn/views/samplernn.results.js
new file mode 100644
index 0000000..12367a3
--- /dev/null
+++ b/app/client/modules/samplernn/views/samplernn.results.js
@@ -0,0 +1,84 @@
+import { h, Component } from 'preact'
+import { bindActionCreators } from 'redux'
+import { Link } from 'react-router-dom';
+import { connect } from 'react-redux'
+
+import * as util from '../../../util'
+import * as samplernnActions from '../samplernn.actions'
+import * as audioPlayerActions from '../../../common/audioPlayer/audioPlayer.actions'
+
+import Loading from '../../../common/loading.component'
+import { FileList, FileRow } from '../../../common/fileList.component'
+
+class SampleRNNResults extends Component {
+ constructor(props){
+ super()
+ if (!props.samplernn.data) props.actions.load_directories()
+ }
+ render(){
+ if (this.props.samplernn.loading) return <Loading progress={this.props.samplernn.progress} />
+ const { folderLookup, fileLookup, datasetLookup } = this.props.samplernn.data
+ // const { folderLookup } = samplernn
+
+ const renders = Object.keys(folderLookup).sort(util.sort.stringSort.asc).map(key => {
+ const folder = folderLookup[key]
+ let { mapFn, sortFn } = util.sort.orderByFn('epoch desc')
+ const datasetPairs = folder.datasets.map(name => datasetLookup[name]).map(mapFn).sort(sortFn)
+ const bestRenders = datasetPairs
+ .map(pair => pair[1])
+ .filter(dataset => dataset.output.length)
+ .map(dataset => {
+ const { output } = dataset
+ return output.map(id => fileLookup[id]).map(mapFn).sort(sortFn)[0][1]
+ })
+ // console.log(bestRenders.map(r => r.epoch))
+ const path = folder.name === 'unsorted'
+ ? "/samplernn/import/"
+ : "/samplernn/datasets/" + folder.id + "/"
+ return (
+ <div className='col bestRenders'>
+ <h3><Link to={path}>{folder.name}</Link></h3>
+ <FileList
+ files={bestRenders}
+ orderBy='date desc'
+ fields={'name date epoch size'}
+ onClick={(file, e) => {
+ e.preventDefault()
+ e.stopPropagation()
+ console.log('picked a file', file)
+ this.handlePick(file)
+ }}
+ />
+ </div>
+ )
+ })
+
+ return (
+ <div className='app samplernn'>
+ <div className='heading row middle'>
+ <h1>SampleRNN Results</h1>
+ <Link to='/samplernn/new/'>new project</Link>
+ </div>
+ <div class='rows params renders'>
+ {renders}
+ </div>
+ </div>
+ )
+ }
+ handlePick(file){
+ this.props.audioPlayer.play(file)
+ }
+}
+
+const mapStateToProps = state => ({
+ samplernn: state.module.samplernn,
+ runner: state.system.runner,
+ task: state.task,
+})
+
+const mapDispatchToProps = (dispatch, ownProps) => ({
+ actions: bindActionCreators(samplernnActions, dispatch),
+ audioPlayer: bindActionCreators(audioPlayerActions, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(SampleRNNResults)
diff --git a/app/client/modules/samplernn/views/samplernn.show.js b/app/client/modules/samplernn/views/samplernn.show.js
new file mode 100644
index 0000000..8b42d44
--- /dev/null
+++ b/app/client/modules/samplernn/views/samplernn.show.js
@@ -0,0 +1,118 @@
+import { h, Component } from 'preact'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+import * as util from '../../../util'
+
+import * as samplernnActions from '../samplernn.actions'
+import * as samplernnTasks from '../samplernn.tasks'
+import * as audioPlayerActions from '../../../common/audioPlayer/audioPlayer.actions'
+
+import Loading from '../../../common/loading.component'
+import DatasetForm from '../../../dataset/dataset.form'
+import NewDatasetForm from '../../../dataset/dataset.new'
+import DatasetStatus from '../../../dataset/dataset.status'
+import { FileList, FileRow } from '../../../common/fileList.component'
+
+import SampleRNNDatasets from '../samplernn.datasets'
+
+import samplernnModule from '../samplernn.module'
+
+class SampleRNNShow extends Component {
+ constructor(props){
+ super(props)
+ this.datasetActions = this.datasetActions.bind(this)
+ }
+ componentWillMount(){
+ const id = this.props.match.params.id || localStorage.getItem('samplernn.last_id')
+ console.log('load dataset:', id)
+ const { match, samplernn, actions } = this.props
+ if (id === 'new') return
+ if (id) {
+ if (parseInt(id)) localStorage.setItem('samplernn.last_id', id)
+ if (! samplernn.folder || samplernn.folder.id !== id) {
+ actions.load_directories(id)
+ }
+ }
+ }
+ render(){
+ const { samplernn, match, history } = this.props
+ const { folderLookup } = (samplernn.data || {})
+ const folder = (folderLookup || {})[samplernn.folder_id] || {}
+ return (
+ <div className='app samplernn'>
+ <div class='heading'>
+ <div class='spaced'>
+ <h1>{folder ? folder.name : <Loading />}</h1>
+ <DatasetStatus />
+ </div>
+ </div>
+ {folder && folder.name && folder.name !== 'unsorted' &&
+ <DatasetForm
+ title='Add Files'
+ module={samplernnModule}
+ folder={folder}
+ canUpload canAddURL
+ />
+ }
+ <SampleRNNDatasets
+ id={samplernn.folder_id}
+ folder={folder}
+ history={history}
+ onPickFile={(file, e) => {
+ e.preventDefault()
+ e.stopPropagation()
+ console.log('picked a file', file)
+ this.handlePick(file)
+ }}
+ datasetActions={this.datasetActions}
+ />
+ </div>
+ )
+ }
+ datasetActions(dataset, isFetching=false, isProcessing=false){
+ const { samplernn, remote } = this.props
+ const input = samplernn.data.fileLookup[dataset.input[0]]
+ if (! input) return null
+ if (input.name && input.name.match(/(gif|jpe?g|png)$/i)) return null
+ return (
+ <div>
+ <div class={'actions'}>
+ <span class='link' onClick={() => remote.train_task(dataset, samplernn.folder_id, 1)}>train</span>
+ <span class='link' onClick={() => remote.train_task(dataset, samplernn.folder_id, 2)}>2x</span>
+ <span class='link' onClick={() => remote.train_task(dataset, samplernn.folder_id, 4)}>4x</span>
+ <span class='link' onClick={() => remote.train_task(dataset, samplernn.folder_id, 6)}>6x</span>
+ <span class='link' onClick={() => remote.train_task(dataset, samplernn.folder_id, 18)}>18x</span>
+ </div>
+ {dataset.isBuilt
+ ? <div class='subtext'>
+ {'fetched '}
+ <span class='link' onClick={() => remote.clear_cache_task(dataset)}>rm</span>
+ </div>
+ : isFetching
+ ? <div class='subtext'>
+ {'fetching'}
+ </div>
+ : <div class='subtext'>
+ <span class='link' onClick={() => remote.fetch_task(input.url, input.id, dataset.name)}>fetch</span>
+ </div>
+ }
+ </div>
+ )
+ }
+ handlePick(file){
+ this.props.audioPlayer.play(file)
+ }
+}
+
+const mapStateToProps = state => ({
+ samplernn: state.module.samplernn,
+ dataset: state.dataset,
+})
+
+const mapDispatchToProps = (dispatch, ownProps) => ({
+ actions: bindActionCreators(samplernnActions, dispatch),
+ remote: bindActionCreators(samplernnTasks, dispatch),
+ audioPlayer: bindActionCreators(audioPlayerActions, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(SampleRNNShow)