Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
crucialfelix committed Aug 19, 2016
2 parents 3571518 + 8544742 commit 517c35f
Show file tree
Hide file tree
Showing 83 changed files with 1,351 additions and 774 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
/app/vendor/supercollider
.tags
.tags1
main.js
main.js.map
/main.js
/main.js.map
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) crucialfelix <crucialfelix@gmail.com> (http://ixdm.ch/critical-media-lab)
Copyright (c) Chris Sattinger <crucialfelix@gmail.com> (http://ixdm.ch/critical-media-lab)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
142 changes: 133 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,153 @@
# PlaySPLOM

status: ALPHA
status: ALPHA / Work in Progress

## Install
This application explores multi-variate datasets through scatter plot matrices and multi-parameter sonification. It is a downloadable, cross-platform application for visualization and sonification.

### Node
Scatterplot matrices are a way to roughly determine if you have a linear correlation between multiple variables.

You should use `nodejs` >= `4.2.0`
Sound events are spawned when you brush over plotted data points. Many sounds are included and the and sonification mapping controls are all

### Features

- Load CSV datasets
- Plot scatterplots for each combination of data features
- Automatically parses well-formed datetimes
- Sonifies datapoints as you brush over them
- Includes embedded SuperCollider synthesis engine
- Many sounds included (more to come)
- Selectable sounds and adjustable sound parameters
- Adjustable data -> sound parameter mapping
- Play sounds in loop mode: the dataset is treated as a sequence

Some background to the project is described here:

https://medium.com/@crucialfelix/experimental-data-aesthetics-4563d01a5ebb#.3oirsvtq1

### Try it out

Easiest is to download the [latest release from github](https://github.com/experimentalDataAesthetics/play-splom/releases)

- Click and drag on any plot to create a brush and move it around.
- command-click on a plot to start or change looping.

## Architecture

It is built on [Electron](http://electron.atom.io/), so it is a desktop app for OS X / Windows / Linux written in [Node.js](https://nodejs.org/)

The interface and plots are written in [React.js](https://facebook.github.io/react/) with [Redux](http://redux.js.org/) and [Reselect](https://github.com/reactjs/reselect).

[D3.js](https://d3js.org/) is used only for some scaling functions - the SVG nodes are rendered with React. There were some tradeoffs here between ease of data flow (with React and Redux) and just grabbing a D3 demo and hooking in some handlers to it. I opted to have full application control using React. D3 is nice for simple examples but it gets messy when you want to scale up application complexity.

The sound engine is [SuperCollider](http://supercollider.github.io/) controlled by [supercollider.js](https://github.com/crucialfelix/supercolliderjs)

All groups, synths, synthdefs and sequencing are managed by the new work-in-progress [Dryadic](https://github.com/crucialfelix/dryadic) library. This is a way to write declarative documents in JSON (from any language) that specify all the synths, connections, resources that you want playing and then it plays it. If you update the document then the server updates accordingly.

This application is the first real use of that library and is an exploration and demonstration of how supercollider.js apps can be written with Electron.

That said if you are not knee deep in contemporary (mid-August 2016) ES6/ES7/JSX JavaScript erm ... ECMAScript, webpacking, jsx, juggling transpiling wonder-of-the-week technology then this isn't the simplest example to just show supercollider.js running in Electron. There is a lot of stuff here to wonder and learn about. By mid-september some of it will be no longer current.

The supercollider.js / dryadic part will become even simpler once updating/streams and remote clients are implemented.

## Installation

To just play with it, download the most recent release.

These instructions are for those who want to hack or explore the code.

### Node JS

You should use `nodejs` >= `4.2.1`

Probably the lastest in the 4 series (LTM) but the latest 6 should work as well. I developed using 4.2.1

The code is all transpiled anyway, so the latest language features aren't needed.

The best way to install `node` is to use `nvm`:

https://github.com/creationix/nvm

This allows you to install different node versions and easily to switch between them.

nvm install 4.2.1
nvm install 4.5.0

Switch to it:

nvm use 4.2.1
nvm use 4.5.0

Now when you run `node` it is the `4.2.1` version
Now when you run `node` it is the `4.5.0` version

### PlaySPLOM
### play-splom

Install this app:

npm install

[SuperCollider](https://supercollider.github.io) is included by manually putting a prebuilt version into `app/vendor/supercollider`
Later on this will be installable automatically by npm (node package manager), but for now use the google drive version.

Later on a prebuilt version will be installable automatically by npm (node package manager), but for now it has to be put in there.

```sh
> app/vendor
└── supercollider
├── COPYING
├── README.md
└── osx
├── MacOS
│   ├── libFLAC.8.dylib
│   ├── libogg.0.dylib
│   ├── libreadline.6.dylib
│   ├── libsndfile.1.dylib
│   ├── libvorbis.0.dylib
│   └── libvorbisenc.2.dylib
├── bin
│   ├── plugins
│   │   ├── BinaryOpUGens.scx
│   │   ├── ChaosUGens.scx
│   │   ├── DelayUGens.scx
│   │   ├── DemandUGens.scx
│   │   ├── DiskIO_UGens.scx
│   │   ├── DynNoiseUGens.scx
│   │   ├── FFT_UGens.scx
│   │   ├── FilterUGens.scx
│   │   ├── GendynUGens.scx
│   │   ├── GrainUGens.scx
│   │   ├── IOUGens.scx
│   │   ├── LFUGens.scx
│   │   ├── ML_UGens.scx
│   │   ├── MulAddUGens.scx
│   │   ├── NoiseUGens.scx
│   │   ├── OscUGens.scx
│   │   ├── PV_ThirdParty.scx
│   │   ├── PanUGens.scx
│   │   ├── PhysicalModelingUGens.scx
│   │   ├── ReverbUGens.scx
│   │   ├── TestUGens.scx
│   │   ├── TriggerUGens.scx
│   │   ├── UIUGens.scx
│   │   ├── UnaryOpUGens.scx
│   │   └── UnpackFFTUGens.scx
│   ├── scsynth
│   └── synthdefs
└── scsynth
```

Where the outer `scsynth` is a script that launches the actual `scsynth` binary:

```
#!/bin/bash
DIR="${BASH_SOURCE%/*}/bin";
if [[ -z "$@" ]]; then
ARGS="-u 57110";
else
ARGS="$@";
fi
if [[ -z "$SC_SYNTHDEF_PATH" ]]; then
export SC_SYNTHDEF_PATH="$DIR/synthdefs/"
fi
export SC_PLUGIN_PATH="$DIR/plugins/";
exec "$DIR/scsynth" $ARGS;
```

## Run

Expand All @@ -53,3 +171,9 @@ You only have to do this once; it should be there each time you open up this cop
## Build

npm run build

## Architecture

A more technical overview of the architecture will follow.

The frontend is fairly standard React, Redux, Reselect
5 changes: 3 additions & 2 deletions app/actionTypes.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

var keyMirror = require('keyMirror');
import keyMirror from 'keymirror';

module.exports = keyMirror({
SELECT_DATASET: null,
Expand Down Expand Up @@ -31,7 +31,8 @@ module.exports = keyMirror({
SET_HOVERING: null,
SET_POINTS_UNDER_BRUSH: null,
TOGGLE_LOOP_MODE: null,
SET_LOOPING: null,
SET_LOOP_TIME: null,
SET_LOOP_BOX: null,

// ui
FOCUS_SCATTERPLOT: null,
Expand Down
60 changes: 43 additions & 17 deletions app/actions/interaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
SHOW_BRUSH,
SET_POINTS_UNDER_BRUSH,
TOGGLE_LOOP_MODE,
SET_LOOPING
SET_LOOP_BOX,
SET_LOOP_TIME
} from '../actionTypes';

export function showBrush(show, x, y) {
Expand All @@ -17,6 +18,18 @@ export function showBrush(show, x, y) {
};
}


/**
* setPointsUnderBrush - Called when moving the brush over points.
*
* Points under brush is further processed by the reducers into points entering.
* The sound app responds to changes in point.
*
* @param {number} m box coordinate
* @param {number} n box coordinate
* @param {Array} indices list of point indices
* @return {Object} action
*/
export function setPointsUnderBrush(m, n, indices) {
return (dispatch, getState) => {
const s = getState().interaction;
Expand All @@ -34,33 +47,46 @@ export function setPointsUnderBrush(m, n, indices) {
};
}

export function toggleLoopMode(m, n) {

/**
* setLoopBox - Start loop at box, or change loop to box, toggle loop off if already playing.
*
* @param {number} m box coordinate
* @param {number} n box coordinate
* @return {Object} action
*/
export function setLoopBox(m, n) {
return {
type: TOGGLE_LOOP_MODE,
type: SET_LOOP_BOX,
payload: {
m,
n
}
};
}


/**
* @param {Object} loopingState
*
* May contain these:
* toggleLoopMode - Turn looping on or off
*/
export function toggleLoopMode() {
return {
type: TOGGLE_LOOP_MODE
};
}


/**
* setLoopTime - Set time of loop in seconds.
*
* nowPlaying: {m n}
* pending: {m n}
* @param {number} loopTime
* @return {Object} action
*/
export function setLooping(loopingState) {
return (dispatch, getState) => {
const state = getState().interaction.loopMode;
const comp = {nowPlaying: state.nowPlaying, pending: state.pending};
if (!_.isEqual(comp, loopingState)) {
dispatch({
type: SET_LOOPING,
payload: loopingState
});
export function setLoopTime(loopTime) {
return {
type: SET_LOOP_TIME,
payload: {
loopTime
}
};
}
6 changes: 5 additions & 1 deletion app/actions/sounds.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ const path = require('path');
const jetpack = require('fs-jetpack');

import {
SET_SOUNDS, SELECT_SOUND, SPAWN_SYNTH, SET_MASTER_CONTROLS
SET_SOUNDS,
SELECT_SOUND,
SPAWN_SYNTH,
SET_MASTER_CONTROLS
} from '../actionTypes';

import callActionOnMain from '../ipc/callActionOnMain';

/**
Expand Down
46 changes: 46 additions & 0 deletions app/app.global.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

/**
* TODO keep this locally
*/
@import url(https://fonts.googleapis.com/css?family=Open+Sans);

html,
Expand All @@ -24,3 +27,46 @@ body {
.MainLayout {
height: 100vh;
}

/* sidebar heights that can't be done with css modules */
.selectable-list {
overflow-y: auto;
margin-bottom: 3px;
}

.sound-selector .selectable-list {
height: 150px;
}

/*.dataset-selector {
height: 150px;
}*/

/*.dataset-selector .selectable-list {
height: 73px;
}*/

/* notifications */
.notification--outer {
width: 100%;
height: auto;
bottom: 0px;
top: 0px;
left: 0;
position: absolute;
background-color: rgba(0, 0, 0, 0.4);
z-index: 1000;
}

.notification--inner {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}

.inform {
color: #bbb;
font-size: 3em;
text-align: center;
}
5 changes: 5 additions & 0 deletions app/components/Axis.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import h from 'react-hyperscript';
import XAxis from './XAxis';
import YAxis from './YAxis';

/**
* Standard pair of X and Y axis with ticks and labelling.
*
* This combines the X and Y into one component.
*/
export default class Axis extends React.Component {

static propTypes = {
Expand Down
7 changes: 3 additions & 4 deletions app/components/AxisLine.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React from 'react';

/**
* Draws a line along an axis - used by XAxis and YAxis
*
* https://github.com/esbullington/react-d3
*/

import React from 'react';
// var d3 = require('d3');

export default React.createClass({

displayName: 'AxisLine',
Expand Down
Loading

0 comments on commit 517c35f

Please sign in to comment.