├── client
├── main.css
├── main.html
└── main.jsx
├── .gitignore
├── .meteor
├── .gitignore
├── release
├── platforms
├── .id
├── .finished-upgraders
├── packages
└── versions
├── _config.yml
├── public
├── clap1.mp3
├── clap1.wav
└── test.png
├── fonts
├── FontAwesome.otf
├── fontawesome-webfont.eot
├── fontawesome-webfont.ttf
├── fontawesome-webfont.woff
└── fontawesome-webfont.woff2
├── imports
├── client
│ ├── components
│ │ ├── editor
│ │ │ ├── editor.sass
│ │ │ └── Editor.jsx
│ │ ├── mixer
│ │ │ ├── knob
│ │ │ │ ├── assets
│ │ │ │ │ ├── top.png
│ │ │ │ │ ├── back.png
│ │ │ │ │ └── knob.png
│ │ │ │ ├── knob.sass
│ │ │ │ └── Knob.jsx
│ │ │ └── MixTable.jsx
│ │ ├── utils
│ │ │ ├── Icon.jsx
│ │ │ └── currys.js
│ │ ├── AddSong.jsx
│ │ ├── screens
│ │ │ ├── Index.jsx
│ │ │ ├── Mixer.jsx
│ │ │ ├── Mixer2.jsx
│ │ │ └── Main.jsx
│ │ ├── Bpm.jsx
│ │ ├── css
│ │ │ ├── common.sass
│ │ │ └── styles.js
│ │ ├── code
│ │ │ ├── prepareCode.js
│ │ │ ├── evalCode.js
│ │ │ └── updateParamsInCode.js
│ │ ├── sound
│ │ │ ├── AddSound.jsx
│ │ │ ├── Sound.jsx
│ │ │ └── SoundPush.jsx
│ │ ├── Song.jsx
│ │ ├── explorer
│ │ │ ├── Explorer.jsx
│ │ │ └── Explorer2.jsx
│ │ └── player
│ │ │ ├── soundsWatcher.js
│ │ │ ├── sounds.js
│ │ │ ├── Player.jsx
│ │ │ └── tones.js
│ └── router
│ │ └── Router.jsx
└── api
│ ├── tones.js
│ ├── songs.js
│ └── sounds.js
├── static-assets.json
├── server
└── main.js
├── README.md
└── package.json
/client/main.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.6
2 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/public/clap1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/public/clap1.mp3
--------------------------------------------------------------------------------
/public/clap1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/public/clap1.wav
--------------------------------------------------------------------------------
/public/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/public/test.png
--------------------------------------------------------------------------------
/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/imports/client/components/editor/editor.sass:
--------------------------------------------------------------------------------
1 | .component-editor
2 | .CodeMirror
3 | height: 100%
4 |
--------------------------------------------------------------------------------
/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/imports/client/components/mixer/knob/assets/top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/imports/client/components/mixer/knob/assets/top.png
--------------------------------------------------------------------------------
/static-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "map": {
3 | "imports/client/components/mixer/knob/assets/": "knob/"
4 | },
5 | "verbose": true
6 | }
7 |
--------------------------------------------------------------------------------
/imports/client/components/mixer/knob/assets/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/imports/client/components/mixer/knob/assets/back.png
--------------------------------------------------------------------------------
/imports/client/components/mixer/knob/assets/knob.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotgreg/XinDaw/HEAD/imports/client/components/mixer/knob/assets/knob.png
--------------------------------------------------------------------------------
/client/main.html:
--------------------------------------------------------------------------------
1 |
2 | XinDaw
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/server/main.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor';
2 | import '/imports/api/sounds.js';
3 | import '/imports/api/songs.js';
4 | import '/imports/api/tones.js';
5 |
6 | Meteor.startup(() => {
7 | // code to run on server at startup
8 | });
9 |
--------------------------------------------------------------------------------
/imports/client/components/utils/Icon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Icon extends React.Component {
4 |
5 | constructor(props){
6 | super(props)
7 | }
8 |
9 | render() {
10 | return (
11 |
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 9aj2n61gb8uxx103be7u
8 |
--------------------------------------------------------------------------------
/client/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Meteor } from 'meteor/meteor';
3 | import { render } from 'react-dom';
4 |
5 | import { BrowserRouter } from 'react-router-dom'
6 |
7 | import RouterWrapper from '/imports/client/router/Router.jsx';
8 |
9 | const AppWrapper = () => (
10 |
11 |
12 |
13 | )
14 |
15 | Meteor.startup(() => {
16 | render(, document.getElementById('render-target'));
17 | });
18 |
--------------------------------------------------------------------------------
/imports/client/components/mixer/knob/knob.sass:
--------------------------------------------------------------------------------
1 | @import "../../css/common.sass"
2 |
3 | .component-knob
4 | width: 50px
5 | float: left
6 | padding: 5px
7 | font-size: $font-size-s
8 | .name
9 | text-align: center
10 | margin-bottom: 3px
11 | .knob
12 | +icon('knob/knob.png', 30px)
13 | margin: 0 auto 3px auto
14 | input.number
15 | border: none
16 | background: none
17 | text-align: center
18 | width: 60px
19 | font-size: $font-size-s
20 |
--------------------------------------------------------------------------------
/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 | 1.4.0-remove-old-dev-bundle-link
15 | 1.4.1-add-shell-server-package
16 | 1.4.3-split-account-service-packages
17 | 1.5-add-dynamic-import-package
18 |
--------------------------------------------------------------------------------
/imports/client/components/AddSong.jsx:
--------------------------------------------------------------------------------
1 | import {random} from 'lodash';
2 | import React from 'react';
3 | import dedent from 'dedent-js';
4 |
5 | import { Meteor } from 'meteor/meteor'
6 |
7 | import * as css from '/imports/client/components/css/styles.js'
8 |
9 | export default class AddSong extends React.Component {
10 |
11 | constructor(props){
12 | super(props)
13 | }
14 |
15 | createSong = () => {
16 | Meteor.call('songs.insert');
17 | }
18 |
19 | render() {
20 | return (
21 |
22 | addSong
23 |
24 | )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/imports/api/tones.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor';
2 | import { Mongo } from 'meteor/mongo';
3 |
4 | import {each} from 'lodash';
5 |
6 | export const Tones = new Mongo.Collection('tones');
7 |
8 | if (!Meteor.isServer) return false
9 |
10 | Meteor.publish('tones', () => Tones.find())
11 |
12 | Meteor.methods({
13 | 'tones.insert'(tone) {
14 | Tones.insert(tone);
15 | },
16 |
17 | 'tones.removeFromSoundId'(soundId) {
18 | Tones.remove({soundId: soundId});
19 | },
20 |
21 | 'tones.removeAll'() {
22 | Tones.remove({});
23 | },
24 |
25 | 'tones.update'(tone) {
26 | Tones.update(tone._id, { $set: tone })
27 | }
28 | });
29 |
--------------------------------------------------------------------------------
/imports/client/components/screens/Index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Router, Route, IndexRoute, Link, hashHistory } from 'react-router-dom'
4 |
5 | export default class IndexScreen extends React.Component {
6 | constructor(props){
7 | super(props)
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
Xindaw
14 |
15 | - Main Screen
16 | - Mixer Screen
17 | - Mixer2 Screen
18 |
19 |
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/imports/client/components/screens/Mixer.jsx:
--------------------------------------------------------------------------------
1 | import { withTracker } from 'meteor/react-meteor-data';
2 | import { Tones } from '/imports/api/tones.js';
3 |
4 | import Tone from 'tone';
5 | import React from 'react';
6 |
7 | import {each} from 'lodash';
8 | import styled from 'styled-components';
9 |
10 | import MixTable from '../mixer/MixTable';
11 |
12 |
13 | class MixerScreen extends React.Component {
14 | constructor(props){
15 | super(props)
16 | }
17 |
18 | render() {
19 | return (
20 |
21 | )
22 | }
23 | }
24 |
25 |
26 | //
27 | // DATA CONTAINER (METEOR)
28 | //
29 |
30 | export default withTracker(props => {
31 | Meteor.subscribe('tones');
32 | return {
33 | tones: Tones.find({}, { sort: { createdAt: -1 } }).fetch(),
34 | };
35 | })(MixerScreen);
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # XinDaw
2 |
3 | 
4 |
5 |
6 | ## Introduction
7 | * A Live Coding environment & Daw experimentation leveraging websockets (ToneJS/React/Meteor)
8 | * A surely far too ambitious project
9 |
10 | ## Why
11 | * Its like a Daw using live coding as its base
12 | * Multiscreens using websockets. For the initial design I plan to have
13 | * a main screen (live coding and sounds explorer, on desktop)
14 | * a mixer screen (all the knobs and other sounds modifiers, controlled on an ipad)
15 | * a video screen (a canvas which will evolve according to some musical parameter, projected or on TV screens)
16 |
17 | ## Current State
18 | Under intensive care right now, should reach a first usable state + some docs soonish
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/imports/client/router/Router.jsx:
--------------------------------------------------------------------------------
1 | import {Route, Switch, IndexRoute } from 'react-router-dom'
2 | import React from 'react';
3 |
4 | import MainScreen from '/imports/client/components/screens/Main';
5 | import MixerScreen from '/imports/client/components/screens/Mixer';
6 | import MixerScreen2 from '/imports/client/components/screens/Mixer2';
7 | import IndexScreen from '/imports/client/components/screens/Index';
8 |
9 | export default class RouterWrapper extends React.Component {
10 |
11 | constructor(props){
12 | super(props)
13 | }
14 |
15 | render() {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/imports/client/components/Bpm.jsx:
--------------------------------------------------------------------------------
1 | import Tone from 'tone';
2 | import React from 'react';
3 |
4 | import * as css from '/imports/client/components/css/styles.js'
5 |
6 | export default class Bpm extends React.Component {
7 |
8 | constructor(props){
9 | super(props)
10 | this.state = {
11 | bpm: '120'
12 | }
13 | }
14 |
15 | componentDidMount () {
16 | Tone.Transport.bpm.value = this.state.bpm
17 | }
18 |
19 | updateBpm = () => {
20 | setTimeout(() => Tone.Transport.bpm.value = this.state.bpm, 100)
21 | this.setState({bpm: this.refs.bpm.value})
22 | }
23 |
24 | render() {
25 | return (
26 |
27 |
32 | Change BPM
33 |
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/imports/client/components/css/common.sass:
--------------------------------------------------------------------------------
1 | // =icon($url, $size)
2 | // width: $size
3 | // height: $size
4 | // background-image: url($url)
5 | // background-size: contain
6 | // background-repeat: none
7 |
8 | // ****************************
9 | // VARIABLES
10 | // ****************************
11 |
12 | $font: monospace;
13 | $font-size-s: 9px;
14 |
15 | // ****************************
16 | // BASE
17 | // ****************************
18 | body {
19 | font-family: monospace;
20 | margin: 0;
21 | padding: 0;
22 | }
23 |
24 | // ****************************
25 | // MIXINS
26 | // ****************************
27 |
28 | @mixin icon($url, $size) {
29 | width: $size;
30 | height: $size;
31 | background-image: url($url);
32 | background-size: contain;
33 | background-repeat: none;
34 | }
35 |
36 | // ****************************
37 | // FONT
38 | // ****************************
39 |
40 | .input.text {
41 | border: none;
42 | padding: 10px;
43 | background: rgba(0, 0, 0, 0.05);
44 | }
45 |
--------------------------------------------------------------------------------
/imports/client/components/code/prepareCode.js:
--------------------------------------------------------------------------------
1 | import esprima from 'esprima'
2 | import { intersection, filter } from 'lodash'
3 |
4 | export let prepareCode = (code) => {
5 | try {
6 | result = esprima.parseModule(code)
7 |
8 | // get all the vars declarations
9 | let variables = filter(result.body, e => e.type === "VariableDeclaration")
10 |
11 | // build the object returned according to what is given
12 | let toneVar = filter(variables, v => ['t', 'tone', 'c'].includes(v.declarations[0].id.name))[0] || false
13 | if (toneVar) toneVar = toneVar.declarations[0].id.name
14 |
15 | let optionsVar = filter(variables, v => ['o', 'options'].includes(v.declarations[0].id.name))[0] || false
16 | if (optionsVar) optionsVar = optionsVar.declarations[0].id.name
17 |
18 | let allElements = variables.map(v => v.declarations[0].id.name)
19 |
20 | let returnStatement = `return {c: ${toneVar}, o: ${optionsVar}, e: [${allElements}]}`
21 |
22 | return code + '\n\r' + returnStatement
23 |
24 | } catch (e) {
25 | console.warn('PrepareCode Error :', e)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/imports/client/components/code/evalCode.js:
--------------------------------------------------------------------------------
1 | import { each, filter } from 'lodash'
2 | import { prepareCode } from './prepareCode'
3 |
4 | import Tone from 'tone';
5 | import teoria from 'teoria'
6 |
7 | export let evalCode = (code) => {
8 |
9 | code = prepareCode(code)
10 |
11 | try {
12 | let result = eval(`(function self(){${code}}())`);
13 | let error = `EDITOR RESULT ERROR => the code should contain one c(code) var`
14 |
15 | if(!result || typeof result !== 'object') return {status: 'err', body: error}
16 | if(!result.c) return {status: 'err', body: error}
17 | if(!result.e) return {status: 'err', body: error}
18 | if(result.o && result.o.vars) {
19 | let error = {status: 'err', body: 'EDITOR RESULT ERROR => each result.o.vars structure should be [NAMEVAR, VAR, MINVAL, MAXVAL]'}
20 | if (filter(result.o.vars, v => v instanceof Array).length !== result.o.vars.length) return error
21 | }
22 |
23 | return {status: 'ok', body: result}
24 |
25 | } catch (e) {
26 | let error = `EDITOR SYNTAX ERROR => ${e.message}`
27 | return {status: 'err', body: error}
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/imports/client/components/utils/currys.js:
--------------------------------------------------------------------------------
1 | import _ from 'ramda';
2 | import R from 'ramda';
3 |
4 | //// NUMBERS
5 | export const intervalWithIntVariation = (callback, min, max, step, intTime) => {
6 | step = step || 1
7 | intTime = intTime || 100
8 | let nb = min
9 | let up = true
10 |
11 | return setInterval(() => {
12 | nb = up ? nb + step : nb - step
13 | if (nb === max) up = false
14 | if (nb === min) up = true
15 | callback(nb)
16 | }, intTime)
17 | }
18 |
19 |
20 |
21 | //// ARRAY MANIP
22 | // +
23 | // export const arrayWithThisObj = (obj, array) => [...array, obj]
24 |
25 | // -
26 | export const arrayWithoutObjFrom = R.curry((prop, val, array) => R.filter(item => item[prop] !== val, array))
27 |
28 | // get
29 | export const objInArrayFrom = R.curry((idProp, idVal, array) => R.find(R.propEq(idProp, idVal), array))
30 | export const indexObjInArrayFrom = R.curry((idProp, idVal, array) => R.findIndex(R.propEq(idProp, idVal), array))
31 |
32 | // update
33 | export const arrayWithUpdatedObjFrom = R.curry((idProp, idVal, update, array) => R.update(indexObjInArrayFrom(idProp, idVal, array), update, array))
34 |
--------------------------------------------------------------------------------
/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | # Check this file (and the other files in this directory) into your repository.
3 | #
4 | # 'meteor add' and 'meteor remove' will edit this file for you,
5 | # but you can also edit it by hand.
6 |
7 | meteor-base # Packages every Meteor app needs to have
8 | mobile-experience # Packages for a great mobile UX
9 | mongo # The database Meteor supports right now
10 | blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views
11 | reactive-var # Reactive variable for tracker
12 | tracker # Meteor's client-side reactive programming library
13 |
14 | standard-minifier-css # CSS minifier run for production mode
15 | standard-minifier-js # JS minifier run for production mode
16 | es5-shim # ECMAScript 5 compatibility for older browsers
17 | ecmascript # Enable ECMAScript2015+ syntax in app code
18 | shell-server # Server-side component of the `meteor shell` command
19 |
20 | react-meteor-data
21 | fortawesome:fontawesome
22 | fourseven:scss
23 | runisland:static-assets
24 |
--------------------------------------------------------------------------------
/imports/client/components/sound/AddSound.jsx:
--------------------------------------------------------------------------------
1 | import {random} from 'lodash';
2 | import React from 'react';
3 | import dedent from 'dedent-js';
4 |
5 | import { Meteor } from 'meteor/meteor'
6 |
7 | let notes = ['A2','B2','C2','D2','E2','F2','G2']
8 | let timeNotes = ['2n','4n','8n','16n']
9 | let times = ['0.5','1','1.5','2','2.5']
10 |
11 | import * as css from '/imports/client/components/css/styles.js'
12 |
13 | export default class AddSound extends React.Component {
14 |
15 | constructor(props){
16 | super(props)
17 | }
18 |
19 | createSound = () => {
20 | let codeDefault = dedent(`
21 | let freeverb = new Tone.Freeverb().toMaster();
22 | freeverb.dampening.value = ${random(1000,5000)};
23 | let synth = new Tone.AMSynth().connect(freeverb);
24 | Tone.Transport.schedule(time => {
25 | synth.triggerAttackRelease('${notes[random(notes.length - 1)]}', '${timeNotes[random(timeNotes.length - 1)]}', time)
26 | }, ${times[random(times.length - 1)]})`)
27 |
28 | Meteor.call('sounds.insert', codeDefault);
29 | }
30 |
31 | render() {
32 | return (
33 |
34 | addSound
35 |
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-todos",
3 | "private": true,
4 | "scripts": {
5 | "start": "meteor run"
6 | },
7 | "dependencies": {
8 | "babel-runtime": "^6.20.0",
9 | "dedent-js": "^1.0.1",
10 | "esprima": "^4.0.0",
11 | "font-awesome": "^4.7.0",
12 | "lodash": "^4.17.4",
13 | "meteor-node-stubs": "~0.2.4",
14 | "ramda": "^0.25.0",
15 | "react": "^16.0.0",
16 | "react-addons-pure-render-mixin": "^15.6.2",
17 | "react-codemirror": "^1.0.0",
18 | "react-dom": "^16.0.0",
19 | "react-hammerjs": "^1.0.1",
20 | "react-meteor-data": "^0.2.10",
21 | "react-router-dom": "^4.2.2",
22 | "react-sortable-tree": "^1.5.2",
23 | "react-sortable-tree-theme-file-explorer": "^1.1.2",
24 | "styled-components": "^2.2.3",
25 | "teoria": "^2.5.0",
26 | "tone": "^0.11.11"
27 | },
28 | "devDependencies": {
29 | "@meteorjs/eslint-config-meteor": "^1.0.5",
30 | "babel-eslint": "^8.0.1",
31 | "eslint": "^4.10.0",
32 | "eslint-config-airbnb": "^16.1.0",
33 | "eslint-import-resolver-meteor": "^0.4.0",
34 | "eslint-plugin-import": "^2.8.0",
35 | "eslint-plugin-jsx-a11y": "^6.0.2",
36 | "eslint-plugin-meteor": "^4.1.4",
37 | "eslint-plugin-react": "^7.4.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/imports/client/components/css/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | //
4 | // VARS
5 | //
6 | export const vars = {
7 | colors: {
8 | orange: '#f8981d',
9 | grey3: '#ababab'
10 | }
11 | }
12 |
13 | //
14 | // LAYOUT
15 | //
16 |
17 | export const Panel = styled.div`
18 | width: ${props => props.w}%;
19 | height: 99vh;
20 | overflow-y: auto;
21 | float: left;
22 | padding: 1%
23 | `;
24 |
25 | export const Inline = styled.div`
26 | display: flex;
27 | flex-direction: row;
28 | `;
29 |
30 |
31 | //
32 | // INTERFACE
33 | //
34 |
35 | // FORMS
36 | export const FieldWrapper = styled.div`
37 | padding: 10px;
38 | `;
39 |
40 | export const Label = styled.div`
41 | display: inline-block;
42 | `;
43 |
44 | export const Input = styled.input`
45 | border: none;
46 | padding: 10px;
47 | background: rgba(0, 0, 0, 0.05);
48 | `;
49 |
50 | export const Number = Input.extend`
51 | width: ${props => props.width};
52 | `;
53 |
54 | export const Button = styled.button`
55 | border: none;
56 | padding: 10px;
57 | margin: 5px;
58 | background: none;
59 | border: 2px inset black;
60 | color: black;
61 | pointer: cursor;
62 | transition: all .3s cubic-bezier(.55,0,.1,1);
63 | &:hover {
64 | background: black;
65 | color: white;
66 | }
67 | `;
68 |
--------------------------------------------------------------------------------
/imports/client/components/Song.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Meteor } from 'meteor/meteor'
3 |
4 | import Icon from './utils/Icon'
5 |
6 | import styled from 'styled-components';
7 |
8 | export default class Song extends React.Component {
9 |
10 | constructor(props){
11 | super(props)
12 | }
13 |
14 | removeThatSong = () => Meteor.call('songs.remove', this.props.song._id);
15 | selectThatSong = () => Meteor.call('songs.select', this.props.song._id);
16 |
17 | render() {
18 | return (
19 |
21 | {this.props.song.name} ({this.props.song.sounds.length})
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
29 | }
30 |
31 | const OneSong = styled.li`
32 | position: relative;
33 | min-height: 25px;
34 | `;
35 |
36 | const Name = styled.p`
37 | display: inline-block;
38 | height: 0px;
39 | margin: 0px;
40 | color: ${props => props.selected && 'blue'};
41 | `;
42 |
43 | const Buttons = styled.p`
44 | position: absolute;
45 | top: 0px;
46 | margin: 0px;
47 | right: 0px;
48 | `;
49 |
--------------------------------------------------------------------------------
/imports/client/components/code/updateParamsInCode.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor'
2 | import { intersection, filter, each, isNumber } from 'lodash'
3 |
4 | import { Tones } from '/imports/api/tones';
5 | Meteor.subscribe('tones');
6 |
7 | import { Sounds } from '/imports/api/sounds';
8 | Meteor.subscribe('sounds');
9 |
10 | // Function that take the code that generate a tone and inject into the o/options array
11 | // the current tone value of the parameter
12 | export let updateParamsInCode = (soundId, toneId) => {
13 | console.log(soundId, toneId)
14 |
15 | let sound = Sounds.findOne(soundId)
16 | let tone = Tones.findOne(toneId)
17 |
18 | let values = tone.options.vars.map(v => `${v[0]} - ${v[1][v[0]]} - ${ v[1].persistedValue ? v[1].persistedValue : ''}`)
19 | console.log(values)
20 |
21 | let newCode = sound.code
22 | each(tone.options.vars, v => {
23 | let regString = `\\[(\\'|\\")${v[0]}(\\'|\\")\\,(.+)\\,(.+)\\,(.+)\\,(.+)(\\,(.+))*\\]`
24 | let regex = new RegExp(regString,"gi")
25 |
26 | let nameProp = v[0]
27 | let newValue = isNumber(v[1].persistedValue) ? v[1].persistedValue : v[1][nameProp]
28 |
29 | // newCode = newCode.replace(regex, `['${v[0]}'$3, ${newValue}]`)
30 | newCode = newCode.replace(regex, `['${v[0]}',$3,$4,$5,${newValue}]`)
31 | })
32 | Meteor.call('sounds.updateCode', soundId, newCode)
33 | }
34 |
--------------------------------------------------------------------------------
/imports/client/components/explorer/Explorer.jsx:
--------------------------------------------------------------------------------
1 | import { withTracker } from 'meteor/react-meteor-data';
2 | import { Meteor } from 'meteor/meteor'
3 | import { Sounds } from '/imports/api/sounds.js';
4 | import React from 'react';
5 | import Sound from '../sound/Sound.jsx';
6 |
7 | import * as css from '/imports/client/components/css/styles.js'
8 |
9 | class FileExplorer extends React.Component {
10 |
11 | constructor(props){
12 | super(props)
13 | this.state = {
14 | searchedTerm: ''
15 | }
16 | }
17 |
18 |
19 | updateSearchedTerm = e => this.setState({searchedTerm: e.target.value})
20 |
21 | render() {
22 | return (
23 |
24 |
25 | search:
26 |
30 |
31 |
32 | {this.props.soundsFiltered(new RegExp(this.state.searchedTerm)).map(sound =>
33 |
34 | )}
35 |
36 |
37 | )
38 | }
39 | }
40 |
41 | export default withTracker(props => {
42 | return {
43 | sounds: Sounds.find().fetch(),
44 | soundsFiltered: search => Sounds.find( { $or : [ { name : search }, { tags : search } ] }, { sort: { name: -1 } }).fetch()
45 | // soundsFiltered: search => Sounds.find({"name": search}, { sort: { name: -1 } }).fetch()
46 | };
47 | })(FileExplorer);
48 |
--------------------------------------------------------------------------------
/imports/client/components/player/soundsWatcher.js:
--------------------------------------------------------------------------------
1 | import {filter, reduce, intersection, indexOf, find, isEqual, each} from 'lodash';
2 |
3 | export let soundsWatcher = (props) => {
4 | // 1 DETECT SOUNDS ADDED
5 | each(props.array1, sound => {
6 | if (!sound) return false
7 |
8 | let oldSound = find(props.array2, {'_id': sound._id})
9 | if (!oldSound) {
10 | if (props.added) {
11 | // console.log(`${sound.name} added`)
12 | return props.added(sound)
13 | }
14 | }
15 | })
16 |
17 | // 2 DETECT SOUNDS DELETED
18 | each(props.array2, sound => {
19 | if (!sound) return false
20 |
21 | let newSound = find(props.array1, {'_id': sound._id})
22 | if (!newSound) {
23 | if (props.deleted) {
24 | // console.log(`${sound.name} deleted`)
25 | return props.deleted(sound)
26 | }
27 | }
28 |
29 | // 3 DETECT SOUNDS UPDATED
30 | let res = reduce(sound, (result, value, key) => isEqual(value, newSound[key]) ? result : result.concat(key), [])
31 |
32 | if (res.length === 0) return
33 |
34 | if (intersection(['code', 'muted'], res).length > 0) {
35 | if (props.updated) {
36 | console.log(`${newSound.name} updated, prop ${res[0]} changed`)
37 | return props.updated(newSound, res[0])
38 | }
39 | } else {
40 | if (props.nothing) {
41 | // console.log(`${newSound.name} NOT updated, prop ${res[0]} changed`)
42 | return props.nothing(newSound, res[0])
43 | }
44 | }
45 | })
46 | }
47 |
--------------------------------------------------------------------------------
/.meteor/versions:
--------------------------------------------------------------------------------
1 | allow-deny@1.1.0
2 | autoupdate@1.3.12
3 | babel-compiler@6.24.7
4 | babel-runtime@1.1.1
5 | base64@1.0.10
6 | binary-heap@1.0.10
7 | blaze@2.3.2
8 | blaze-html-templates@1.1.2
9 | blaze-tools@1.0.10
10 | boilerplate-generator@1.3.0
11 | caching-compiler@1.1.9
12 | caching-html-compiler@1.1.2
13 | callback-hook@1.0.10
14 | check@1.2.5
15 | ddp@1.4.0
16 | ddp-client@2.2.0
17 | ddp-common@1.3.0
18 | ddp-server@2.1.0
19 | deps@1.0.12
20 | diff-sequence@1.0.7
21 | dynamic-import@0.2.0
22 | ecmascript@0.9.0
23 | ecmascript-runtime@0.5.0
24 | ecmascript-runtime-client@0.5.0
25 | ecmascript-runtime-server@0.5.0
26 | ejson@1.1.0
27 | es5-shim@4.6.15
28 | fortawesome:fontawesome@4.7.0
29 | fourseven:scss@4.5.4
30 | geojson-utils@1.0.10
31 | hot-code-push@1.0.4
32 | html-tools@1.0.11
33 | htmljs@1.0.11
34 | http@1.3.0
35 | id-map@1.0.9
36 | jquery@1.11.10
37 | launch-screen@1.1.1
38 | livedata@1.0.18
39 | logging@1.1.19
40 | meteor@1.8.0
41 | meteor-base@1.2.0
42 | minifier-css@1.2.16
43 | minifier-js@2.2.0
44 | minimongo@1.4.0
45 | mobile-experience@1.0.5
46 | mobile-status-bar@1.0.14
47 | modules@0.11.0
48 | modules-runtime@0.9.0
49 | mongo@1.3.0
50 | mongo-dev-server@1.1.0
51 | mongo-id@1.0.6
52 | npm-mongo@2.2.33
53 | observe-sequence@1.0.16
54 | ordered-dict@1.0.9
55 | promise@0.10.0
56 | random@1.0.10
57 | react-meteor-data@0.2.14
58 | reactive-var@1.0.11
59 | reload@1.1.11
60 | retry@1.0.9
61 | routepolicy@1.0.12
62 | runisland:static-assets@0.1.5
63 | shell-server@0.3.0
64 | spacebars@1.0.15
65 | spacebars-compiler@1.1.3
66 | standard-minifier-css@1.3.5
67 | standard-minifier-js@2.2.0
68 | templating@1.3.2
69 | templating-compiler@1.3.3
70 | templating-runtime@1.3.2
71 | templating-tools@1.1.2
72 | tmeasday:check-npm-versions@0.2.0
73 | tracker@1.1.3
74 | ui@1.0.13
75 | underscore@1.0.10
76 | url@1.1.0
77 | webapp@1.4.0
78 | webapp-hashing@1.0.9
79 |
--------------------------------------------------------------------------------
/imports/client/components/player/sounds.js:
--------------------------------------------------------------------------------
1 | import { evalCode } from '../code/evalCode';
2 | import { stopTone, startTone, persistTone, initTonesModifiers } from './tones';
3 |
4 | import {filter, isFunction, reduce, intersection, indexOf, find, isEqual, each, get} from 'lodash';
5 |
6 | tones = []
7 | window.tones = tones
8 |
9 | // when sound is created or updated, call that function to generate its tome
10 | export let updateSound = (sound, props) => {
11 |
12 | console.log('updateSound')
13 | removeSound(sound, true)
14 | console.log('updateSound removeSound')
15 |
16 | if (sound.muted) return {status:'ok'}
17 |
18 | let result = evalCode(sound.code)
19 |
20 | if (result.status === 'err') return result
21 |
22 | result = result.body
23 | result = {soundId: sound._id, name: sound.name, tone: result.c, elementsToDispose: result.e, options: result.o}
24 |
25 | startTone(result.tone)
26 | console.log('updateSound starttone')
27 |
28 | window.tones.push(result)
29 |
30 | initTonesModifiers(result.options.vars)
31 |
32 | persistTone(result)
33 |
34 | return {status:'ok'}
35 | }
36 |
37 | // when tone get removed
38 | export let removeSound = (sound) => {
39 | let old = find(window.tones, {'soundId': sound._id})
40 |
41 | old && stopTone(old.tone)
42 |
43 | if (get(old, 'elementsToDispose')) Tone.Transport.scheduleOnce(t => {
44 | each(old.elementsToDispose, e => {
45 | // if we have elementsToDispose, try to dispose them (tonejs)
46 | if (isFunction(e.dispose)) try { e.dispose() } catch (e) { console.log('error when dispose() :'+ e.message) }
47 |
48 | // then put them all equal to null to force the garbage collection process
49 | e = null
50 | })
51 | }, 1)
52 |
53 | window.tones = filter(window.tones, t => t.soundId !== sound._id)
54 | Meteor.call('tones.removeFromSoundId', sound._id)
55 | }
56 |
--------------------------------------------------------------------------------
/imports/api/songs.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor';
2 | import { Mongo } from 'meteor/mongo';
3 | import { check } from 'meteor/check';
4 | import { random, filter, uniq, each } from 'lodash';
5 |
6 | import {Sounds} from './sounds';
7 |
8 | export const Songs = new Mongo.Collection('songs');
9 |
10 | if (!Meteor.isServer) return false
11 |
12 | Meteor.publish('songs', () => Songs.find())
13 |
14 | Meteor.methods({
15 | 'helloworld' () {
16 | return `helloworld-${random(10000)}`
17 | },
18 | 'songs.insert'() {
19 | Songs.insert({
20 | 'name': `song-${random(10000)}`,
21 | 'selected': false,
22 | 'selectedSound': false,
23 | 'sounds': [],
24 | 'createdAt': new Date()
25 | });
26 | },
27 |
28 | 'songs.remove'(songId) {
29 | Songs.remove(songId);
30 | },
31 |
32 | 'songs.select'(songId) {
33 | Songs.update({ selected: true }, { $set: { selected: false } })
34 | Songs.update(songId, { $set: { selected: true } })
35 | },
36 |
37 | 'songs.addSound'(soundId) {
38 | let song = Songs.findOne({selected: true})
39 | Songs.update(song._id, { $set: { sounds: uniq([...song.sounds, soundId]) } })
40 | },
41 |
42 | 'songs.removeSound'(soundId) {
43 | let song = Songs.findOne({selected: true})
44 | Songs.update(song._id, { $set: { sounds: filter(song.sounds, s => s !== soundId) } })
45 | },
46 |
47 | 'songs.selectSound'(soundId) {
48 | let song = Songs.findOne({selected: true})
49 | Songs.update(song._id, { $set: { selectedSound: soundId } })
50 | },
51 |
52 | // clean all songs from possible orphans id
53 | 'songs.cleanSoundsSongs' () {
54 | let songs = Songs.find().fetch()
55 |
56 | each(songs, song => {
57 | Songs.update(song._id, { $set: { sounds: filter(song.sounds, sid => {
58 | // if the sound still exist keep it, otherwise remove it
59 | return Sounds.find(sid).fetch().length > 0
60 | })
61 | }})
62 | })
63 | }
64 | });
65 |
--------------------------------------------------------------------------------
/imports/client/components/player/Player.jsx:
--------------------------------------------------------------------------------
1 | import { withTracker } from 'meteor/react-meteor-data';
2 | import { Meteor } from 'meteor/meteor'
3 |
4 | import React from 'react';
5 |
6 | import Tone from 'tone';
7 | import teoria from 'teoria'
8 | import _ from 'lodash'
9 |
10 | import styled from 'styled-components';
11 |
12 | import { updateSound, removeSound } from './sounds';
13 | import { soundsWatcher } from './soundsWatcher';
14 | import { observeTones } from './tones';
15 |
16 | export default class Player extends React.Component {
17 |
18 | constructor(props){
19 | super(props)
20 | this.state = {
21 | codeErrors: false
22 | }
23 | }
24 |
25 | componentDidMount () {
26 | // clean all the meteor collection replication of the window.tones object
27 | Meteor.call('tones.removeAll')
28 |
29 | Tone.Transport.start('+0.1')
30 | Tone.Transport.loopEnd = '2m'
31 | Tone.Transport.loop = true
32 | window.Tone = Tone
33 |
34 | Tone.context.latencyHint = "interactive"
35 |
36 | observeTones()
37 | }
38 |
39 | // initializeSounds
40 | componentWillReceiveProps(nextProps) {
41 | let a =[this.props.sounds, nextProps.sounds]
42 |
43 | soundsWatcher({
44 | array1: nextProps.sounds,
45 | array2: this.props.sounds,
46 | added: s => {
47 | updateSound(s)
48 | },
49 | updated: (s, p) => {
50 | let result = updateSound(s)
51 | result.status === 'err' ? this.setState({codeErrors: result.body}) : this.setState({codeErrors: false})
52 | },
53 | deleted: s => {
54 | removeSound(s)
55 | },
56 | nothing: (s, p) => console.log('nothing', p)
57 | })
58 | }
59 |
60 | componentWillUnmount () {
61 | Tone.Transport.stop()
62 | }
63 |
64 | render() {
65 | let errors = this.state.codeErrors ? this.state.codeErrors : 'no synthax error'
66 |
67 | return (
68 |
69 | {errors}
70 |
71 |
72 | )
73 | }
74 | }
75 |
76 | const Result = styled.p`
77 | color: ${props => props.error ? 'red' : 'green'};
78 | `;
79 |
--------------------------------------------------------------------------------
/imports/api/sounds.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor';
2 | import { Mongo } from 'meteor/mongo';
3 | import { check } from 'meteor/check';
4 | import {random, each, filter} from 'lodash';
5 |
6 | import {Songs} from './songs';
7 |
8 | export const Sounds = new Mongo.Collection('sounds');
9 |
10 | if (!Meteor.isServer) return false
11 |
12 | Meteor.publish('sounds', () => Sounds.find())
13 |
14 | Meteor.methods({
15 | 'sounds.insert'(code) {
16 |
17 | check(code, String);
18 |
19 | Sounds.insert({
20 | 'name': `sound-${random(10000)}`,
21 | 'tags': ``,
22 | 'selected': false,
23 | 'muted': false,
24 | 'code': code,
25 | 'createdAt': new Date()
26 | });
27 | },
28 | 'sounds.remove'(soundId) {
29 | check(soundId, String);
30 |
31 | Sounds.remove(soundId);
32 |
33 | // Clean orphan sounds id in songs
34 | Meteor.call('songs.cleanSoundsSongs')
35 | },
36 | 'sounds.clone'(soundId) {
37 | check(soundId, String);
38 | let sound = Sounds.findOne(soundId)
39 | Sounds.insert({
40 | 'name': `${sound.name}-clone`,
41 | 'tags': sound.tags,
42 | 'selected': sound.selected,
43 | 'muted': sound.muted,
44 | 'code': sound.code,
45 | 'createdAt': new Date()
46 | });
47 | },
48 | //
49 | // UPDATERS
50 | //
51 | 'sounds.select'(soundId) {
52 | check(soundId, String);
53 |
54 | Sounds.update({ selected: true }, { $set: { selected: false } })
55 | Sounds.update(soundId, { $set: { selected: true } })
56 | },
57 | 'sounds.toggleMute'(soundId) {
58 | check(soundId, String);
59 | let sound = Sounds.findOne(soundId)
60 | Sounds.update(soundId, { $set: { muted: !sound.muted } })
61 | },
62 | 'sounds.updateCode'(soundId, code) {
63 | check(soundId, String);
64 | Sounds.update(soundId, { $set: { code: code } })
65 | },
66 | 'sounds.updateName'(soundId, name) {
67 | check(soundId, String);
68 | Sounds.update(soundId, { $set: { name: name } })
69 | },
70 | 'sounds.updateTags'(soundId, tags) {
71 | check(soundId, String);
72 | Sounds.update(soundId, { $set: { tags: tags } })
73 | }
74 | });
75 |
--------------------------------------------------------------------------------
/imports/client/components/screens/Mixer2.jsx:
--------------------------------------------------------------------------------
1 | import { withTracker } from 'meteor/react-meteor-data';
2 |
3 | import { Meteor } from 'meteor/meteor'
4 |
5 | import { Tones } from '/imports/api/tones.js';
6 | import { Sounds } from '/imports/api/sounds.js';
7 | import { Songs } from '/imports/api/songs.js';
8 |
9 | import { find, times, each } from 'lodash';
10 |
11 | import React from 'react';
12 | import styled from 'styled-components';
13 |
14 | import MixTable from '../mixer/MixTable';
15 | import SoundPush from '../sound/SoundPush.jsx';
16 |
17 | class MixerScreen2 extends React.Component {
18 | constructor(props){
19 | super(props)
20 | this.state = {
21 | songSounds: [],
22 | selectedSong: false
23 | }
24 | }
25 |
26 | componentWillUpdate(nextProps, nextState) {
27 | // get the selected song and its related sounds
28 | let selectedSong = find(nextProps.songs, {selected: true})
29 |
30 | if (!selectedSong) return false
31 | let songSounds = times(selectedSong.sounds.length, id => Sounds.findOne(selectedSong.sounds[id]))
32 |
33 | nextState.songSounds = songSounds
34 | nextState.selectedSong = selectedSong
35 |
36 | if (!nextProps.tones[0]) return false
37 | // let values = nextProps.tones[0]. options.vars.map(v => `${v[0]} - ${v[1][v[0]]}`)
38 | // console.log(values)
39 | }
40 |
41 | render() {
42 | return (
43 |
44 |
45 | {this.state.songSounds.map(sound =>
46 |
47 | )}
48 |
49 |
50 |
51 | )
52 | }
53 | }
54 |
55 | // CSS
56 |
57 | const SoundsPushs = styled.div`
58 | display: flex;
59 | flex-wrap: wrap;
60 | // justify-content: space-between;
61 | flex-direction: row;
62 | `;
63 |
64 |
65 | //
66 | // DATA CONTAINER (METEOR)
67 | //
68 |
69 | export default withTracker(props => {
70 | Meteor.subscribe('sounds');
71 | Meteor.subscribe('songs');
72 | Meteor.subscribe('tones');
73 | return {
74 | songs: Songs.find({}, { sort: { createdAt: -1 } }).fetch(),
75 | sounds: Sounds.find({}, { sort: { createdAt: -1 } }).fetch(),
76 | tones: Tones.find({}, { sort: { createdAt: -1 } }).fetch()
77 | };
78 | })(MixerScreen2);
79 |
--------------------------------------------------------------------------------
/imports/client/components/sound/Sound.jsx:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor'
2 | import Songs from '/imports/api/songs'
3 |
4 | import React from 'react';
5 | import styled from 'styled-components';
6 |
7 | import Icon from '../utils/Icon'
8 |
9 | import {filter} from 'lodash';
10 |
11 | export default class Sound extends React.Component {
12 |
13 | constructor(props){
14 | super(props)
15 | }
16 |
17 | removeThatSound = () => Meteor.call('sounds.remove', this.props.sound._id);
18 | selectThatSound = () => Meteor.call('sounds.select', this.props.sound._id);
19 | toggleThatSound = () => Meteor.call('sounds.toggleMute', this.props.sound._id);
20 | cloneThatSound = () => Meteor.call('sounds.clone', this.props.sound._id);
21 |
22 | addToSong = () => Meteor.call('songs.addSound', this.props.sound._id);
23 | removeToSong = () => Meteor.call('songs.removeSound', this.props.sound._id);
24 |
25 | render() {
26 | let buttons = null
27 | if (this.props.type === 'songSound') {
28 | buttons = ([
29 | ,
32 |
35 | ])
36 | } else {
37 | buttons = ([
38 | ,
41 |
44 | ])
45 | }
46 |
47 | return (
48 |
49 | {this.props.sound.name}
50 |
51 |
54 | {buttons}
55 |
56 |
57 | )
58 | }
59 | }
60 |
61 | const OneSound = styled.li`
62 | position: relative;
63 | min-height: 25px;
64 | `;
65 |
66 | const Name = styled.p`
67 | display: inline-block;
68 | height: 0px;
69 | margin: 0px;
70 | color: ${props => props.selected && 'blue'};
71 | `;
72 |
73 | const Buttons = styled.p`
74 | position: absolute;
75 | top: 0px;
76 | margin: 0px;
77 | right: 0px;
78 | `;
79 |
--------------------------------------------------------------------------------
/imports/client/components/sound/SoundPush.jsx:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor'
2 | import Songs from '/imports/api/songs'
3 |
4 | import React from 'react';
5 | import styled from 'styled-components';
6 |
7 | import Icon from '../utils/Icon'
8 |
9 | import {filter} from 'lodash';
10 | import * as css from '/imports/client/components/css/styles.js'
11 |
12 | export default class SoundPush extends React.Component {
13 |
14 | constructor(props){
15 | super(props)
16 | }
17 |
18 | selectThatSound = () => Meteor.call('sounds.select', this.props.sound._id);
19 | toggleThatSound = () => Meteor.call('sounds.toggleMute', this.props.sound._id);
20 |
21 | removeToSong = () => Meteor.call('songs.removeSound', this.props.sound._id);
22 |
23 | toggleAndSelectThatSound = () => { this.selectThatSound(); this.toggleThatSound() }
24 |
25 | render() {
26 | let buttons = null
27 | if (this.props.type === 'songSound') {
28 | buttons = ([
29 |
30 |
31 | ,
32 |
33 |
34 |
35 | ])
36 | }
37 |
38 | return (
39 |
40 | {this.props.sound.name}
41 |
42 | {buttons}
43 |
44 |
45 | )
46 | }
47 | }
48 |
49 | const OneSound = styled.li`
50 | position: relative;
51 | width: 80px;
52 | height: 80px;
53 | flex-shrink: 0;
54 | margin: 10px;
55 | padding: 10px;
56 | border: 2px black solid;
57 | min-height: 25px;
58 | list-style: none;
59 | font-size: 10px;
60 | background: ${props => props.muted && 'rgba(255, 233, 77, 0.5)'};
61 | `;
62 |
63 | const Name = styled.p`
64 | display: block;
65 | height: 0px;
66 | margin: 0px;
67 | color: ${props => props.selected && 'blue'};
68 | `;
69 |
70 | const Buttons = styled.p`
71 | position: absolute;
72 | bottom: 0px;
73 | left: 0px;
74 | display: flex;
75 | width: 100%;
76 | margin: 0px
77 | `;
78 |
79 | const SoundPushButton = css.Button.extend`
80 | flex-grow: 1;
81 | margin: 0px;
82 | border-right: 0px solid black;
83 | border-left: 0px solid black;
84 | border-bottom: 0px solid black;
85 | `;
86 |
--------------------------------------------------------------------------------
/imports/client/components/player/tones.js:
--------------------------------------------------------------------------------
1 | import Tone from 'tone';
2 | import { Meteor } from 'meteor/meteor'
3 |
4 | import { Tones } from '/imports/api/tones';
5 | import { each, find, get, set, isNumber, round } from 'lodash';
6 |
7 | Meteor.subscribe('tones');
8 |
9 | export let getToneType = tone => {
10 | if (typeof tone === 'number') return 'transport-event'
11 | else if (typeof tone === 'object') return 'loop'
12 |
13 | return false
14 | }
15 |
16 | export let stopTone = tone => {
17 | console.log('stopTone')
18 | let type = getToneType(tone)
19 | type === 'loop' && Tone.Transport.scheduleOnce(t => tone.stop().dispose(), 1)
20 | type === 'transport-event' && Tone.Transport.clear(tone)
21 | type === 'transport-event' && console.log('transport event' + tone)
22 | }
23 |
24 | export let startTone = tone => {
25 | let type = getToneType(tone)
26 | type === 'loop' && Tone.Transport.scheduleOnce(t => tone.start(0), 1)
27 | }
28 |
29 | // for each option var defined in the o array, apply the init value
30 | export let initTonesModifiers = (vars) => {
31 | each(vars, v => {
32 | // ['pitchDecay', synth, 0, 0.5, 0.001 ,->0] which is init value of param
33 | if (isNumber(v[5])) {
34 | isNumber(v[1][v[0]]) ? v[1][v[0]] = round(v[5],5) : v[1].value = v[5]
35 | }
36 | })
37 | }
38 |
39 |
40 | // persist the window.tones object to a meteor collection
41 | export let persistTone = tone => {
42 | if (!get(tone, 'options.vars')) return false
43 | each(tone.options.vars, (v,i) => {
44 | if (!v[1]) {
45 | console.warn(`the options ${v[0]} is not interpreted as its value is null`)
46 | tone.options.vars.splice(i, 1)
47 | return false
48 | }
49 | if (v[1].value) v[1].persistedValue = v[1].value
50 | })
51 | Meteor.call('tones.insert', tone)
52 | }
53 |
54 | // if we have some vars inside the options that changed
55 | // spread the change to value inside window.tones to modify sound
56 | export let observeTones = () => {
57 | var handle = Tones.find().observeChanges({
58 | changed: function(id, field) {
59 | let soundId = Tones.findOne(id).soundId
60 | let tone = find(window.tones, {soundId: soundId})
61 | let vars = field['options']['vars']
62 |
63 | each(vars, (v,i) => {
64 | // if the options.vars.variable is a Tone.Param (obj), update its .value prop
65 | // else if a number, update the number directly
66 | let nameProp = tone['options']['vars'][i][0]
67 | isNumber(v[1].persistedValue) ? tone['options']['vars'][i][1].value = v[1].persistedValue : tone['options']['vars'][i][1][nameProp] = v[1][nameProp]
68 | })
69 | }
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/imports/client/components/mixer/MixTable.jsx:
--------------------------------------------------------------------------------
1 | import Tone from 'tone';
2 | import React from 'react';
3 |
4 | import { each, isNumber, find} from 'lodash';
5 | import styled from 'styled-components';
6 |
7 | import { Meteor } from 'meteor/meteor'
8 | import esprima from 'esprima'
9 |
10 | import Knob from './knob/Knob';
11 | import { updateParamsInCode } from '../code/updateParamsInCode';
12 |
13 | export default class MixTable extends React.Component {
14 | constructor(props){
15 | super(props)
16 | }
17 |
18 | changeValue = (tone, i, value) => {
19 |
20 | let nameProp = tone.options.vars[i][0]
21 | if (isNumber(tone.options.vars[i][1].persistedValue)) tone.options.vars[i][1].persistedValue = value
22 | else tone.options.vars[i][1][nameProp] = value
23 |
24 | if (!tone.options.vars[i][1].amithesame) tone.options.vars[i][1].amithesame = 0
25 | tone.options.vars[i][1].amithesame = tone.options.vars[i][1].amithesame + 1
26 |
27 | Meteor.call('tones.update', tone)
28 | }
29 |
30 | componentWillUpdate (nextProps, nextState) {
31 | // console.log('MIXTABLE componentWillUpdate', nextProps, nextState)
32 | // nextState.variable = nextProps.variable
33 | }
34 |
35 | saveParamsTone = tone => {
36 | updateParamsInCode(tone.soundId, tone._id)
37 | }
38 |
39 | render() {
40 | return (
41 |
42 | {this.props.tones.map(tone => (
43 |
44 | {tone.name}
45 |
46 | {(tone.options && tone.options.vars) && tone.options.vars.map((v,i) =>
47 |
56 | )}
57 |
58 | save
59 |
60 | )
61 | )}
62 |
63 |
64 | )
65 | }
66 | }
67 |
68 | //
69 | // CSS
70 | //
71 |
72 | const Table = styled.div`
73 |
74 | `;
75 |
76 | const Clear = styled.div`
77 | clear: both;
78 | `;
79 |
80 | const SoundName = styled.div`
81 | padding: 10px;
82 | background: #dfdfdf;
83 | text-align: center;
84 | `;
85 |
86 | const SoundMixer = styled.div`
87 | background: #f3f3f3;
88 | max-width: 50%;
89 | margin: 5px;
90 | float: left;
91 | `;
92 |
--------------------------------------------------------------------------------
/imports/client/components/editor/Editor.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CodeMirror from 'react-codemirror';
3 |
4 | import { Meteor } from 'meteor/meteor'
5 |
6 | import * as css from '/imports/client/components/css/styles.js'
7 |
8 | // require('./editor.css');
9 | require('./editor.sass');
10 |
11 | require('codemirror/lib/codemirror.css');
12 | require('codemirror/theme/monokai.css');
13 | require('codemirror/keymap/sublime.js');
14 | require('codemirror/mode/javascript/javascript.js');
15 |
16 | export default class Editor extends React.Component {
17 |
18 | constructor(props){
19 | super(props)
20 | }
21 |
22 | componentDidUpdate(prevProps, prevState) {
23 | if (!this.props.sound) return false
24 |
25 | const codeMirror = this.refs['editor'].getCodeMirrorInstance();
26 |
27 | this.refs.editor.getCodeMirror().setValue(this.props.sound.code);
28 | }
29 |
30 | saveCode = () => Meteor.call('sounds.updateCode', this.props.sound._id, this.refs.editor.getCodeMirror().getValue())
31 | saveName = () => Meteor.call('sounds.updateName', this.props.sound._id, this.refs.name.value)
32 | saveTags = () => Meteor.call('sounds.updateTags', this.props.sound._id, this.refs.tags.value)
33 | save = () => {
34 | this.saveCode()
35 | // this.saveTitle()
36 | }
37 |
38 | render() {
39 | if (!this.props.sound) return false
40 |
41 | let options = {
42 | keyMap: "sublime",
43 | mode: "javascript",
44 | theme: "monokai",
45 | disableKeywords: true,
46 | completeSingle: false,
47 | lineNumbers: true,
48 | extraKeys: {
49 | "Ctrl-S": () => {
50 | this.saveCode()
51 | }
52 | },
53 | completeOnSingleClick: false
54 | }
55 |
56 | return (
57 |
58 |
59 |
60 | title:
61 |
67 |
68 |
69 | tags:
70 |
76 |
77 |
78 |
82 | Save Code
83 |
84 | )
85 | }
86 | }
87 |
88 | // React.render(, document.getElementById('app'));
89 |
--------------------------------------------------------------------------------
/imports/client/components/screens/Main.jsx:
--------------------------------------------------------------------------------
1 | import { withTracker } from 'meteor/react-meteor-data';
2 | import { Meteor } from 'meteor/meteor'
3 | import { Sounds } from '/imports/api/sounds.js';
4 | import { Songs } from '/imports/api/songs.js';
5 |
6 | import React from 'react';
7 | import { find, times, random } from 'lodash';
8 |
9 | import AddSound from '../sound/AddSound.jsx';
10 | import Sound from '../sound/Sound.jsx';
11 |
12 | import AddSong from '../AddSong.jsx';
13 | import Song from '../Song.jsx';
14 |
15 | import Bpm from '../Bpm.jsx';
16 | import Editor from '../editor/Editor.jsx';
17 | import Player from '../player/Player.jsx';
18 | import Explorer from '../explorer/Explorer.jsx';
19 | // import Explorer from '../explorer/Explorer2.jsx';
20 |
21 | // import styled from 'styled-components';
22 | import * as css from '/imports/client/components/css/styles.js'
23 |
24 | export class MainScreen extends React.Component {
25 |
26 | constructor(props){
27 | super(props)
28 | this.state = {
29 | songSounds: [],
30 | selectedSong: false,
31 | tones: []
32 | }
33 | }
34 |
35 | componentWillUpdate(nextProps, nextState) {
36 | // get the selected song and its related sounds
37 | let selectedSong = find(nextProps.songs, {selected: true})
38 | let songSounds = times(selectedSong.sounds.length, id => Sounds.findOne(selectedSong.sounds[id]))
39 |
40 | nextState.songSounds = songSounds
41 | nextState.selectedSong = selectedSong
42 | }
43 |
44 | render() {
45 | return (
46 |
47 |
48 |
49 | SOUNDS SONG
50 |
51 | {this.state.songSounds.map(sound => {
52 | return
53 | }
54 | )}
55 |
56 |
57 | SONGS
58 |
59 | {this.props.songs.map(song =>
60 |
61 | )}
62 |
63 |
64 |
65 |
66 |
67 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | )
79 | }
80 | }
81 |
82 | //
83 | // DATA CONTAINER (METEOR)
84 | //
85 |
86 | export default withTracker(props => {
87 | Meteor.subscribe('sounds');
88 | Meteor.subscribe('songs');
89 | return {
90 | sounds: Sounds.find({}, { sort: { createdAt: -1 } }).fetch(),
91 | songs: Songs.find({}, { sort: { createdAt: -1 } }).fetch(),
92 | selectedSound: Sounds.findOne({selected: true})
93 | };
94 | })(MainScreen);
95 |
--------------------------------------------------------------------------------
/imports/client/components/mixer/knob/Knob.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Hammer from 'react-hammerjs';
4 | import { clamp, round, throttle, debounce } from 'lodash';
5 |
6 | require('./knob.sass')
7 |
8 | export default class Knob extends React.Component {
9 |
10 | constructor(props){
11 | super(props)
12 |
13 | let decimals = (this.props.step + "").split(".")[1]
14 | let precision = decimals ? decimals.length : 0
15 |
16 | // console.log(props.initVal, )
17 | let val = props.initVal ? props.initVal : props.val
18 |
19 | this.state = {
20 | startingVal: 0,
21 | val: val,
22 | currentPercentage: 0,
23 | precision: precision
24 | }
25 |
26 | // console.log(this.props.name, this.state.val, props.initVal, props.val)
27 | }
28 |
29 | componentDidMount () {
30 | this.updateCurrentPercentage()
31 | }
32 |
33 |
34 | componentWillUpdate (nextProps, nextState) {
35 | // console.log('componentWillUpdate', nextProps, nextState)
36 | nextState.variable = nextProps.variable
37 | }
38 |
39 | // shouldComponentUpdate(nextProps, nextState){
40 | // // return false;
41 | // }
42 |
43 | //
44 | // UX : touch management
45 | //
46 |
47 | throttleHandlePan = throttle(e => { this.handlePan(e) }, 200)
48 |
49 | handlePan = e => {
50 | e.preventDefault()
51 | let variator = (this.props.max / 200) * e.deltaY
52 |
53 | this.state.val = clamp(parseFloat(round(this.state.startingVal + variator, this.state.precision)), this.props.min, this.props.max)
54 |
55 | this.refs.input.value = round(this.state.val, this.state.precision)
56 |
57 | this.changeValue()
58 | }
59 |
60 | handlePanStart = e => {
61 | console.log('handlePanStart')
62 | e.preventDefault()
63 | this.setState({startingVal: this.state.val})
64 | }
65 |
66 | modifyValueByInput = e => {
67 | this.state.val = parseFloat(e.target.value)
68 |
69 | this.changeValue()
70 | }
71 |
72 | // UI
73 |
74 | updateCurrentPercentage = () => {
75 | let aPercent = ( this.props.max - this.props.min ) / 100
76 | let currentRelativePosition = this.state.val - this.props.min
77 | let currentPercentage = currentRelativePosition / aPercent
78 |
79 | this.setState({currentPercentage: currentPercentage})
80 | }
81 |
82 | //
83 | // DATA FLOW
84 | //
85 |
86 | changeValue = () => {
87 | this.updateCurrentPercentage()
88 | this.props.onValueChange(this.state.val)
89 | }
90 |
91 | // throttleChangeValue = throttle(() => {
92 | // this.updateCurrentPercentage()
93 | // this.props.onValueChange(this.state.val)
94 | // }, 50)
95 |
96 | render() {
97 | return (
98 |
99 |
104 |
105 |
{this.props.name}
106 |
107 |
108 |
117 |
118 |
119 |
120 | )
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/imports/client/components/explorer/Explorer2.jsx:
--------------------------------------------------------------------------------
1 | import { withTracker } from 'meteor/react-meteor-data';
2 | import { Meteor } from 'meteor/meteor'
3 | import { Sounds } from '/imports/api/sounds.js';
4 | import React from 'react';
5 | import Sound from '../sound/Sound.jsx';
6 |
7 | import SortableTree, { addNodeUnderParent, removeNodeAtPath, getFlatDataFromTree, getTreeFromFlatData } from 'react-sortable-tree';
8 | import FileExplorerTheme from 'react-sortable-tree-theme-file-explorer';
9 |
10 | import styled from 'styled-components';
11 |
12 | class FileExplorer2 extends React.Component {
13 |
14 | constructor(props){
15 | super(props)
16 | this.state = {
17 | searchedTerm: '',
18 | treeData: [{ title: 'Chicken', children: [ { title: 'Egg', otherprop: 'lolcats2' } ], otherprop: 'lolcats' }]
19 | }
20 |
21 | setTimeout(() => {
22 | let newTreeData = [ ...this.state.treeData, { title: 'woooopreactivity', children: [ { title: 'Egg', otherprop: 'lolcats2' } ], otherprop: 'lolcats' }]
23 | this.setState({treeData: newTreeData})
24 | }, 2000)
25 | }
26 |
27 |
28 | updateSearchedTerm = e => this.setState({searchedTerm: e.target.value})
29 |
30 | updateTreeData = treeData => {
31 |
32 | let flatmap = getFlatDataFromTree({treeData: treeData, getNodeKey: this.getNodeKey, ignoreCollapsed: false})
33 | flatmap = flatmap.map(({ node, path }) => {
34 | return {
35 | id: node.id,
36 | name: node.name,
37 | // The last entry in the path is this node's key
38 | // The second to last entry (accessed here) is the parent node's key
39 | parent: path.length > 1 ? path[path.length - 2] : null,
40 | }
41 | })
42 |
43 | this.setState({ treeData })
44 | }
45 |
46 | createNode = (node) => {
47 | return {
48 | title: `helloworld ${
49 | node.title.split(' ')[0]
50 | }sson`,
51 | }
52 | }
53 |
54 | generateNodePropsFunc = ({ node, path }) => {
55 | let buttons = [
56 |
57 | ]
58 | // if (!node.children) buttons.push()
59 | return {buttons: buttons}
60 | }
61 |
62 | getNodeKey = ({ treeIndex }) => {
63 | return treeIndex
64 | };
65 |
66 | // addNode = ({ node, path }) => {
67 | addNode = (node, path) => {
68 | // return
69 | return this.setState(state => ({
70 | treeData: addNodeUnderParent({
71 | treeData: state.treeData,
72 | parentKey: path[path.length - 1],
73 | expandParent: true,
74 | getNodeKey: this.getNodeKey,
75 | newNode: this.createNode(node),
76 | }).treeData,
77 | }))
78 | }
79 |
80 | removeNode = (node, path) => {
81 | return this.setState(state => ({
82 | treeData: removeNodeAtPath({
83 | treeData: state.treeData,
84 | path: path,
85 | getNodeKey: this.getNodeKey,
86 | }),
87 | }))
88 | }
89 |
90 | render() {
91 | return (
92 |
93 |
94 |
100 |
101 | Explorer
102 |
106 |
107 | {this.props.soundsFiltered(new RegExp(this.state.searchedTerm)).map(sound =>
108 |
109 | )}
110 |
111 |
112 | )
113 | }
114 | }
115 |
116 | export default withTracker(props => {
117 | return {
118 | sounds: Sounds.find().fetch(),
119 | soundsFiltered: search => Sounds.find({"name": search}, { sort: { name: -1 } }).fetch()
120 | };
121 | })(FileExplorer2);
122 |
--------------------------------------------------------------------------------