├── src
├── lib
│ └── tone.js
├── assets
│ ├── fonts
│ │ └── editundo.ttf
│ └── images
│ │ └── logo.svg
├── components
│ ├── CommandType.jsx
│ ├── CommandValue.jsx
│ ├── InstrumentValue.jsx
│ ├── BPMAdjust.jsx
│ ├── ControlBar.jsx
│ ├── MeasureView.jsx
│ ├── MeasureSequence.jsx
│ ├── NoteEntry.jsx
│ ├── PitchValue.jsx
│ └── Project.jsx
├── stylesheets
│ ├── App.css
│ ├── MeasureView.css
│ ├── PitchValue.css
│ ├── ControlBar.css
│ ├── MeasureSequence.css
│ ├── BPMAdjust.css
│ └── NoteEntry.css
├── main.jsx
├── index.css
└── App.jsx
├── notes
├── jsdj-wireframe.jpg
├── JSDJ_component_diagram_progress01.png
├── JSDJ_component_diagram_progress02.png
├── index.html
├── gameboy-palette.md
├── arpeggiator.js
├── state-model.js
├── React-Select-notes.md
├── INTERACTIVE_MUSIC_WITH_TONEJS.md
└── README.md
├── vite.config.js
├── .gitignore
├── index.html
├── .eslintrc.cjs
├── package.json
├── public
└── vite.svg
├── README.md
└── yarn.lock
/src/lib/tone.js:
--------------------------------------------------------------------------------
1 | import * as Tone from "tone";
2 |
3 | export default Tone;
4 |
--------------------------------------------------------------------------------
/notes/jsdj-wireframe.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukebertram/jsdj/HEAD/notes/jsdj-wireframe.jpg
--------------------------------------------------------------------------------
/src/assets/fonts/editundo.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukebertram/jsdj/HEAD/src/assets/fonts/editundo.ttf
--------------------------------------------------------------------------------
/src/components/CommandType.jsx:
--------------------------------------------------------------------------------
1 | function CommandType() {
2 | return
-
;
3 | }
4 |
5 | export default CommandType;
6 |
--------------------------------------------------------------------------------
/src/components/CommandValue.jsx:
--------------------------------------------------------------------------------
1 | function CommandValue() {
2 | return 00
;
3 | }
4 |
5 | export default CommandValue;
6 |
--------------------------------------------------------------------------------
/src/stylesheets/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | height: 100vh;
3 | width: 100vw;
4 | background-color: #333;
5 | color: #d8d8d8;
6 | }
7 |
--------------------------------------------------------------------------------
/notes/JSDJ_component_diagram_progress01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukebertram/jsdj/HEAD/notes/JSDJ_component_diagram_progress01.png
--------------------------------------------------------------------------------
/notes/JSDJ_component_diagram_progress02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukebertram/jsdj/HEAD/notes/JSDJ_component_diagram_progress02.png
--------------------------------------------------------------------------------
/src/components/InstrumentValue.jsx:
--------------------------------------------------------------------------------
1 | function InstrumentValue() {
2 | return --
;
3 | }
4 |
5 | export default InstrumentValue;
6 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/src/stylesheets/MeasureView.css:
--------------------------------------------------------------------------------
1 | .measure-view-container {
2 | display: flex;
3 | justify-content: space-around;
4 | max-width: 950px;
5 | max-height: 950px;
6 | margin: auto;
7 | background-color: #9bbc0f;
8 | color: #0f380f;
9 | }
10 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import "./index.css";
5 |
6 | ReactDOM.createRoot(document.getElementById("root")).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/stylesheets/PitchValue.css:
--------------------------------------------------------------------------------
1 | .Select{
2 | background-color:
3 | }
4 | .Select-control{
5 | background-color: #9abd0f ;
6 | border: 2px solid #8bac0f;
7 | }
8 | .Select-menu-outer{
9 | background-color: #9abd0f;
10 | }
11 | .Select.is-Focused > Select-control{
12 | background-color: #9abd0f;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "EditUndo";
3 | src: url("./assets/fonts/editundo.ttf") format("truetype");
4 | }
5 |
6 | body {
7 | margin: 0;
8 | padding: 0;
9 | font-family: "EditUndo", Fallback, sans-serif;
10 | background-color: #000;
11 | }
12 |
13 | h3 {
14 | margin: 0;
15 | padding-top: 1em;
16 | padding-bottom: 1em;
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/stylesheets/ControlBar.css:
--------------------------------------------------------------------------------
1 | .control-bar-container{
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: space-between;
6 | max-height: 950px;
7 | max-width: 200px;
8 | border-radius: 15px;
9 | background-color: #8bac0f;
10 | color: #0f380f;
11 | margin: 75px 40px 40px;
12 | }
13 |
14 | .toggle-play-btn {
15 | background-color: #0f380f;
16 | color: #9bbc0f;
17 | border: 2px solid #306230;
18 | border-radius: 5px;
19 | font-size: 2em;
20 | font-family: EditUndo;
21 | text-align: center;
22 | padding: 5px;
23 | margin-bottom: 20px;
24 | }
25 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/notes/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ToneJS Goofin
9 |
10 |
11 | Tone.js Arpeggiator Test
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/notes/gameboy-palette.md:
--------------------------------------------------------------------------------
1 | # Original Gameboy Color Palette
2 |
3 | Original green-yellow gameboy palette from [color-hex.com](http://www.color-hex.com/color-palette/45299)
4 |
5 | |Color|Hex|RGB|
6 | |---|---|---|
7 | |lightest|#9bbc0f|(155, 188, 15)|
8 | |light|#8bac0f|(139, 172, 15)|
9 | |mid|#306230|(48, 98, 48)|
10 | |dark|#0f380f|(15, 56, 15)|
11 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import styled from "styled-components";
3 | import Tone from "./lib/tone";
4 | import StartAudioContext from "startaudiocontext";
5 | import Project from "./components/Project.jsx";
6 |
7 | const AppContainer = styled.div`
8 | height: 100%;
9 | width: 100%;
10 | background-color: dark-gray;
11 | `;
12 |
13 | function App() {
14 | useEffect(() => {
15 | document.body.addEventListener(
16 | "click",
17 | () => {
18 | // iOS Web Audio API requires this library.
19 | StartAudioContext(Tone.context);
20 | },
21 | {
22 | once: true,
23 | }
24 | );
25 | }, []);
26 |
27 | return (
28 |
29 |
30 |
31 | );
32 | }
33 |
34 | export default App;
35 |
--------------------------------------------------------------------------------
/src/components/BPMAdjust.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import "./../stylesheets/BPMAdjust.css";
4 |
5 | function BPMAdjust(props) {
6 | return (
7 |
8 |
{props.currentBPM}
9 |
10 |
14 |
18 |
19 |
20 | );
21 | }
22 |
23 | BPMAdjust.propTypes = {
24 | currentBPM: PropTypes.number,
25 | onIncrementBPM: PropTypes.func,
26 | onDecrementBPM: PropTypes.func,
27 | };
28 |
29 | export default BPMAdjust;
30 |
--------------------------------------------------------------------------------
/src/stylesheets/MeasureSequence.css:
--------------------------------------------------------------------------------
1 | .measure-sequence-container {
2 | margin-left: 40px;
3 | }
4 |
5 | .entry-table-header {
6 | width: 100%;
7 | background-color: #9bbc0f;
8 | padding: 0px;
9 | color: #0f380f;
10 | display: flex;
11 | }
12 | .entry-table-header div {
13 | padding: 2px;
14 | flex-grow: 1;
15 | text-align: center;
16 | }
17 |
18 | .entry-table-header .note-position-header {
19 | flex-grow: 0;
20 | width: 1.5em;
21 | text-align: right;
22 | }
23 | .entry-table-header .playhead-arrow-header {
24 | flex-grow: 0;
25 | width: 3em;
26 | font-size: .8em;
27 | text-align: center;
28 | }
29 |
30 | .pitch-value-header {
31 | margin-right: 15px;
32 | width: 2.5em;
33 | }
34 | .I-label {
35 | background: #8bac0f;
36 | }
37 | .instrument-value-header{
38 | margin-right: 15px;
39 | }
40 |
41 | .command-value-header {
42 | }
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-jsdj",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-select": "^5.8.0",
16 | "startaudiocontext": "^1.2.1",
17 | "styled-components": "^6.1.1",
18 | "tone": "^14.7.77"
19 | },
20 | "devDependencies": {
21 | "@types/react": "^18.2.37",
22 | "@types/react-dom": "^18.2.15",
23 | "@vitejs/plugin-react-swc": "^3.5.0",
24 | "eslint": "^8.53.0",
25 | "eslint-plugin-react": "^7.33.2",
26 | "eslint-plugin-react-hooks": "^4.6.0",
27 | "eslint-plugin-react-refresh": "^0.4.4",
28 | "vite": "^5.0.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/stylesheets/BPMAdjust.css:
--------------------------------------------------------------------------------
1 | .BPM-adjust-container {
2 | flex-grow: 0;
3 | display: flex;
4 | justify-content: space-between;
5 | height:80px;
6 | margin: 10px 0px;
7 | width: 125px;
8 | align-items: center;
9 | background-color: #9bbc0f;
10 | border: 2px solid #306230;
11 | border-radius: 5px;
12 | }
13 |
14 | .BPM-adjust-display {
15 | font-size: 2.5em;
16 | }
17 |
18 | .BPM-adjust-buttons {
19 | height: 100%;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | justify-content: space-around;
24 | width: 30px;
25 | padding: 7px 0;
26 | box-sizing: border-box;
27 | }
28 |
29 | .BPM-button {
30 | height: 27px;
31 | width: 40px;
32 | background-color: #0f380f;
33 | color: #9bbc0f;
34 | border: 2px solid #306230;
35 | border-radius: 5px;
36 | font-size: 1em;
37 | font-family: EditUndo;
38 | text-align: center;
39 | padding: 2px 0;
40 | }
41 |
--------------------------------------------------------------------------------
/notes/arpeggiator.js:
--------------------------------------------------------------------------------
1 | // declare a new SimpleSynth
2 | var synth = new Tone.Synth();
3 | var isPlaying = false;
4 |
5 | //connect synth to the master output so it can be heard
6 | synth.toMaster();
7 |
8 | //trigger a C4 note, then trigger release 0.25s later
9 | // synth.triggerAttack("C4", time);
10 | // synth.triggerRelease(time + 0.25);
11 | //do the same with a single command
12 | // synth.triggerAttackRelease('C4', 0.25);
13 |
14 | var pattern = new Tone.Pattern(
15 | function(time, note) {
16 | synth.triggerAttackRelease(note, '8n');
17 | },
18 | ['C4', 'E4', 'G4', 'A4']
19 | );
20 |
21 | pattern.start(0);
22 |
23 | function togglePlayback() {
24 | if (isPlaying) {
25 | console.log('stop playback');
26 | Tone.Transport.stop();
27 | } else {
28 | console.log('start playback');
29 | Tone.Transport.start();
30 | }
31 | isPlaying = !isPlaying;
32 | }
33 |
34 | // Tone.Transport.start();
35 | $(document).ready(function() {
36 | document.getElementById('toggle-play').onclick = togglePlayback;
37 | });
38 |
--------------------------------------------------------------------------------
/src/components/ControlBar.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import BPMAdjust from "./BPMAdjust";
3 | import "./../stylesheets/ControlBar.css";
4 |
5 | function ControlBar(props) {
6 | let togglePlayButton;
7 | if (props.isPlaying) {
8 | togglePlayButton = (
9 |
12 | );
13 | } else {
14 | togglePlayButton = (
15 |
21 | );
22 | }
23 | return (
24 |
25 |
Controls
26 |
31 | {togglePlayButton}
32 |
33 | );
34 | }
35 |
36 | ControlBar.propTypes = {
37 | onStartMeasurePlayback: PropTypes.func,
38 | onStopMeasurePlayback: PropTypes.func,
39 | isPlaying: PropTypes.bool,
40 | };
41 |
42 | export default ControlBar;
43 |
--------------------------------------------------------------------------------
/src/components/MeasureView.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import MeasureSequence from "./MeasureSequence";
3 | import ControlBar from "./ControlBar";
4 |
5 | import "./../stylesheets/MeasureView.css";
6 |
7 | function MeasureView(props) {
8 | return (
9 |
10 |
16 |
24 |
25 | );
26 | }
27 |
28 | MeasureView.propTypes = {
29 | noteArray: PropTypes.arrayOf(PropTypes.object),
30 | playheadPosition: PropTypes.string,
31 | isPlaying: PropTypes.bool,
32 | onStartMeasurePlayback: PropTypes.func,
33 | onStopMeasurePlayback: PropTypes.func,
34 | onPitchValueChange: PropTypes.func,
35 | };
36 |
37 | export default MeasureView;
38 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/stylesheets/NoteEntry.css:
--------------------------------------------------------------------------------
1 | .note-entry {
2 | width: 100%;
3 | background-color: #9bbc0f;
4 | padding: 0px;
5 | color: #0f380f;
6 | display: flex;
7 | border: 1px solid #8bac0f;
8 | }
9 | div {
10 | padding: 2px;
11 | flex-grow: 1;
12 | text-align: center;
13 | }
14 |
15 | /*.note-entry:nth-child(even) {
16 | background-color: #8bac0f;
17 | }*/
18 |
19 | .note-entry .note-position {
20 | background: #8bac0f;
21 | flex-grow: 0;
22 | width: 1.5em;
23 | text-align: right;
24 | display: flex;
25 | align-items: center;
26 | justify-content: flex-end;
27 | }
28 | .note-entry .playhead-arrow {
29 | flex-grow: 0;
30 | width: 3em;
31 | font-size: 1em;
32 | text-align: center;
33 | display: flex;
34 | align-items: center;
35 | justify-content: flex-end;
36 | }
37 |
38 | .pitch-value {
39 | padding: 0;
40 | /* margin-right: 15px; */
41 | /*width: 2.5em;*/
42 | }
43 | .I-label {
44 | background: #8bac0f;
45 | display: flex;
46 | align-items: center;
47 | justify-content: center;
48 | max-width: 30px;
49 | }
50 | .instrument-value {
51 | margin-right: 15px;
52 | display: flex;
53 | align-items: center;
54 | justify-content: center;
55 | }
56 |
57 | .command-type {
58 | background: #8bac0f;
59 | max-width: 30px;
60 | display: flex;
61 | align-items: center;
62 | justify-content: center;
63 | }
64 |
65 | .command-value {
66 | display: flex;
67 | align-items: center;
68 | justify-content: center;
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/MeasureSequence.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import NoteEntry from "./NoteEntry";
4 | import "./../stylesheets/MeasureSequence.css";
5 |
6 | function MeasureSequence(props) {
7 | return (
8 |
9 |
Measure 01
10 |
11 |
12 |
13 |
NOTE
14 |
INSTR
15 |
CMD
16 |
17 |
18 | {props.noteArray.map((note, index) => (
19 |
30 | ))}
31 |
32 |
33 | );
34 | }
35 |
36 | MeasureSequence.propTypes = {
37 | noteArray: PropTypes.arrayOf(PropTypes.object),
38 | playheadPosition: PropTypes.string,
39 | isPlaying: PropTypes.bool,
40 | onPitchValueChange: PropTypes.func,
41 | };
42 |
43 | export default MeasureSequence;
44 |
--------------------------------------------------------------------------------
/src/components/NoteEntry.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import PitchValue from "./PitchValue";
3 | import InstrumentValue from "./InstrumentValue";
4 | import CommandType from "./CommandType";
5 | import CommandValue from "./CommandValue";
6 | import "./../stylesheets/NoteEntry.css";
7 |
8 | function NoteEntry(props) {
9 | const playhead = props.playheadPosition === props.time ? `\u25B6` : ` `;
10 | return (
11 |
12 |
{props.position + 1}
13 |
{playhead}
14 |
21 |
I
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | NoteEntry.propTypes = {
36 | position: PropTypes.number,
37 | time: PropTypes.string,
38 | pitch: PropTypes.string,
39 | instrument: PropTypes.number,
40 | commandType: PropTypes.string,
41 | commandValue: PropTypes.number,
42 | onPitchValueChange: PropTypes.func,
43 | };
44 |
45 | export default NoteEntry;
46 |
--------------------------------------------------------------------------------
/notes/state-model.js:
--------------------------------------------------------------------------------
1 | const stateModel = {
2 | currentMeasureId: null,
3 |
4 | playheadPosition: 0,
5 |
6 | measure:
7 | uuid: 0,
8 | chainId: 0,
9 | timelinePosition: "0:0:0",
10 | noteArray: [
11 | {"measurePosition" : 0, "time" : "0:0:0", "pitch" : "C3", "velocity": 0.9},
12 | {"measurePosition" : 1, "time" : "0:0:1", "pitch" : "D3", "velocity": 0.9},
13 | {"measurePosition" : 2, "time" : "0:0:2", "pitch" : "E3", "velocity": 0.9},
14 | {"measurePosition" : 3, "time" : "0:0:3", "pitch" : "G3", "velocity": 0.9},
15 | {"measurePosition" : 4, "time" : "0:0:4", "pitch" : "C4", "velocity": 0.9},
16 | {"measurePosition" : 5, "time" : "0:0:5", "pitch" : "D4", "velocity": 0.9},
17 | {"measurePosition" : 6, "time" : "0:0:6", "pitch" : "E4", "velocity": 0.9},
18 | {"measurePosition" : 7, "time" : "0:0:7", "pitch" : "G4", "velocity": 0.9},
19 | {"measurePosition" : 8, "time" : "0:0:8", "pitch" : "C5", "velocity": 0.9},
20 | {"measurePosition" : 9, "time" : "0:0:9", "pitch" : "D5", "velocity": 0.9},
21 | {"measurePosition" : 10, "time" : "0:0:10", "pitch" : "E5", "velocity": 0.9},
22 | {"measurePosition" : 11, "time" : "0:0:11", "pitch" : "G5", "velocity": 0.9},
23 | {"measurePosition" : 12, "time" : "0:0:12", "pitch" : "C6", "velocity": 0.9},
24 | {"measurePosition" : 13, "time" : "0:0:13", "pitch" : "D6", "velocity": 0.9},
25 | {"measurePosition" : 14, "time" : "0:0:14", "pitch" : "E6", "velocity": 0.9},
26 | {"measurePosition" : 15, "time" : "0:0:15", "pitch" : "G6", "velocity": 0.9}
27 | ],
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/notes/React-Select-notes.md:
--------------------------------------------------------------------------------
1 | Current model for note select dropdown options:
2 |
3 |
91 |
--------------------------------------------------------------------------------
/src/components/PitchValue.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import Select from "react-select";
3 | // import "react-select/dist/react-select.css";
4 | import "./../stylesheets/PitchValue.css";
5 |
6 | function PitchValue(props) {
7 | const selectedOption = { value: props.pitch, label: props.pitch };
8 | const value = selectedOption && selectedOption.value;
9 | // const options = enumerables.map(e => ({
10 | // value: e,
11 | // label: e
12 | // }));
13 | // const pitch = props.pitch ? props.pitch : '---';
14 | // console.log(value);
15 | return (
16 |