forked from BRNSystems/bosca-ceoil-js
Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
@ -1,10 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.{feature,json,md,yaml,yml}]
|
|
||||||
indent_size = 2
|
|
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,9 +1,2 @@
|
|||||||
node_modules/
|
node_modules
|
||||||
out/
|
out
|
||||||
tmp/
|
|
||||||
assets/
|
|
||||||
public/*.js
|
|
||||||
audio/**/*_data/
|
|
||||||
*.aup
|
|
||||||
*.tgz
|
|
||||||
.vscode/
|
|
||||||
|
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2019 Matthew T. Kennerly (mtkennerly)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
35
README.md
35
README.md
@ -1,35 +0,0 @@
|
|||||||
# Bosca Ceoil JS
|
|
||||||
This project is an HTML/CSS/JavaScript (TypeScript) rewrite of
|
|
||||||
[Bosca Ceoil](https://github.com/TerryCavanagh/boscaceoil) using samples
|
|
||||||
of the preset instruments from [SiON](https://github.com/keim/SiON)
|
|
||||||
(rather than a port of SiON itself).
|
|
||||||
|
|
||||||
It is still a prototype, so significant functionality is missing.
|
|
||||||
|
|
||||||
## Sample creation
|
|
||||||
The SiON samples were created by running
|
|
||||||
`npm run samples -- <path_to_bosca_ceoil_clone>`.
|
|
||||||
This requires a few things:
|
|
||||||
|
|
||||||
* A clone of Bosca Ceoil with support for
|
|
||||||
[exporting via CLI](https://github.com/TerryCavanagh/boscaceoil/pull/71).
|
|
||||||
Since that functionality is not in an official release, the script will run
|
|
||||||
`adl application.xml` in that clone, so you'll need the Adobe AIR SDK.
|
|
||||||
* [SoX](http://sox.sourceforge.net) for removing silence from the end of the
|
|
||||||
recordings and converting from WAV to OGG.
|
|
||||||
|
|
||||||
The script should be run with 100% system volume.
|
|
||||||
|
|
||||||
## Development
|
|
||||||
Prerequisites:
|
|
||||||
|
|
||||||
* [Node](https://nodejs.org/en)
|
|
||||||
|
|
||||||
Initial setup:
|
|
||||||
|
|
||||||
* `npm install`
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
* `npm run dev`
|
|
||||||
* Open `http://127.0.0.1:8000/index.html` in your browser
|
|
@ -1 +0,0 @@
|
|||||||
3,0,0,0,120,16,4,1,0,0,0,128,0,256,2,0,0,0,0,1,0,16,0,0,0,0,0,0,0,0,0,16,0,16,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,-1,-1,-1,
|
|
15
index.js
Normal file
15
index.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const { app, BrowserWindow } = require('electron')
|
||||||
|
const createWindow = () => {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600
|
||||||
|
});
|
||||||
|
|
||||||
|
win.loadFile('index.html')
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
createWindow()
|
||||||
|
})
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') app.quit()
|
||||||
|
})
|
||||||
|
}
|
775
instruments.yaml
775
instruments.yaml
@ -1,775 +0,0 @@
|
|||||||
midi.piano1:
|
|
||||||
index: 0
|
|
||||||
duration: short
|
|
||||||
category: [MIDI, Piano, Grand Piano]
|
|
||||||
# midi.piano2:
|
|
||||||
# index: 1
|
|
||||||
# category: [MIDI, Piano, Bright Piano]
|
|
||||||
# midi.piano3:
|
|
||||||
# index: 2
|
|
||||||
# category: [MIDI, Piano, Electric Grand]
|
|
||||||
# midi.piano4:
|
|
||||||
# index: 3
|
|
||||||
# category: [MIDI, Piano, Honky Tonk]
|
|
||||||
# midi.piano5:
|
|
||||||
# index: 4
|
|
||||||
# category: [MIDI, Piano, Electric Piano 1]
|
|
||||||
# midi.piano6:
|
|
||||||
# index: 5
|
|
||||||
# category: [MIDI, Piano, Electric Piano 2]
|
|
||||||
# midi.piano7:
|
|
||||||
# index: 6
|
|
||||||
# category: [MIDI, Piano, Harpsichord]
|
|
||||||
# midi.piano8:
|
|
||||||
# index: 7
|
|
||||||
# category: [MIDI, Piano, Clavi]
|
|
||||||
midi.chrom1:
|
|
||||||
index: 8
|
|
||||||
duration: constant
|
|
||||||
category: [MIDI, Bells, Celesta]
|
|
||||||
# midi.chrom2:
|
|
||||||
# index: 9
|
|
||||||
# midi.chrom3:
|
|
||||||
# index: 10
|
|
||||||
# midi.chrom4:
|
|
||||||
# index: 11
|
|
||||||
# midi.chrom5:
|
|
||||||
# index: 12
|
|
||||||
# midi.chrom6:
|
|
||||||
# index: 13
|
|
||||||
# midi.chrom7:
|
|
||||||
# index: 14
|
|
||||||
# midi.chrom8:
|
|
||||||
# index: 15
|
|
||||||
midi.organ1:
|
|
||||||
index: 16
|
|
||||||
duration: instant
|
|
||||||
category: [MIDI, Organ, Drawbar Organ]
|
|
||||||
# midi.organ2:
|
|
||||||
# index: 17
|
|
||||||
# midi.organ3:
|
|
||||||
# index: 18
|
|
||||||
# midi.organ4:
|
|
||||||
# index: 19
|
|
||||||
# midi.organ5:
|
|
||||||
# index: 20
|
|
||||||
# midi.organ6:
|
|
||||||
# index: 21
|
|
||||||
# midi.organ7:
|
|
||||||
# index: 22
|
|
||||||
# midi.organ8:
|
|
||||||
# index: 23
|
|
||||||
midi.guitar1:
|
|
||||||
index: 24
|
|
||||||
duration: short
|
|
||||||
category: [MIDI, Guitar, Nylon Guitar]
|
|
||||||
# midi.guitar2:
|
|
||||||
# index: 25
|
|
||||||
# midi.guitar3:
|
|
||||||
# index: 26
|
|
||||||
# midi.guitar4:
|
|
||||||
# index: 27
|
|
||||||
# midi.guitar5:
|
|
||||||
# index: 28
|
|
||||||
# midi.guitar6:
|
|
||||||
# index: 29
|
|
||||||
# midi.guitar7:
|
|
||||||
# index: 30
|
|
||||||
# midi.guitar8:
|
|
||||||
# index: 31
|
|
||||||
midi.bass1:
|
|
||||||
index: 32
|
|
||||||
duration: instant
|
|
||||||
category: [MIDI, Bass, Acoustic Bass]
|
|
||||||
# midi.bass2:
|
|
||||||
# index: 33
|
|
||||||
# midi.bass3:
|
|
||||||
# index: 34
|
|
||||||
# midi.bass4:
|
|
||||||
# index: 35
|
|
||||||
# midi.bass5:
|
|
||||||
# index: 36
|
|
||||||
# midi.bass6:
|
|
||||||
# index: 37
|
|
||||||
# midi.bass7:
|
|
||||||
# index: 38
|
|
||||||
# midi.bass8:
|
|
||||||
# index: 39
|
|
||||||
midi.strings1:
|
|
||||||
index: 40
|
|
||||||
duration: mini
|
|
||||||
category: [MIDI, Strings, Violin]
|
|
||||||
# midi.strings2:
|
|
||||||
# index: 41
|
|
||||||
# midi.strings3:
|
|
||||||
# index: 42
|
|
||||||
# midi.strings4:
|
|
||||||
# index: 43
|
|
||||||
# midi.strings5:
|
|
||||||
# index: 44
|
|
||||||
# midi.strings6:
|
|
||||||
# index: 45
|
|
||||||
# midi.strings7:
|
|
||||||
# index: 46
|
|
||||||
# midi.strings8:
|
|
||||||
# index: 47
|
|
||||||
midi.ensemble1:
|
|
||||||
index: 48
|
|
||||||
duration: mid
|
|
||||||
category: [MIDI, Ensemble, String Ensemble 1]
|
|
||||||
# midi.ensemble2:
|
|
||||||
# index: 49
|
|
||||||
# midi.ensemble3:
|
|
||||||
# index: 50
|
|
||||||
# midi.ensemble4:
|
|
||||||
# index: 51
|
|
||||||
# midi.ensemble5:
|
|
||||||
# index: 52
|
|
||||||
# midi.ensemble6:
|
|
||||||
# index: 53
|
|
||||||
# midi.ensemble7:
|
|
||||||
# index: 54
|
|
||||||
# midi.ensemble8:
|
|
||||||
# index: 55
|
|
||||||
midi.brass1:
|
|
||||||
index: 56
|
|
||||||
duration: mini
|
|
||||||
category: [MIDI, Brass, Trumpet]
|
|
||||||
# midi.brass2:
|
|
||||||
# index: 57
|
|
||||||
# midi.brass3:
|
|
||||||
# index: 58
|
|
||||||
# midi.brass4:
|
|
||||||
# index: 59
|
|
||||||
# midi.brass5:
|
|
||||||
# index: 60
|
|
||||||
# midi.brass6:
|
|
||||||
# index: 61
|
|
||||||
# midi.brass7:
|
|
||||||
# index: 62
|
|
||||||
# midi.brass8:
|
|
||||||
# index: 63
|
|
||||||
midi.reed1:
|
|
||||||
index: 64
|
|
||||||
duration: mini
|
|
||||||
category: [MIDI, Reed, Soprano Sax]
|
|
||||||
# midi.reed2:
|
|
||||||
# index: 65
|
|
||||||
# midi.reed3:
|
|
||||||
# index: 66
|
|
||||||
# midi.reed4:
|
|
||||||
# index: 67
|
|
||||||
# midi.reed5:
|
|
||||||
# index: 68
|
|
||||||
# midi.reed6:
|
|
||||||
# index: 69
|
|
||||||
# midi.reed7:
|
|
||||||
# index: 70
|
|
||||||
# midi.reed8:
|
|
||||||
# index: 71
|
|
||||||
midi.pipe1:
|
|
||||||
index: 72
|
|
||||||
duration: mini
|
|
||||||
category: [MIDI, Pipe, Piccolo]
|
|
||||||
# midi.pipe2:
|
|
||||||
# index: 73
|
|
||||||
# midi.pipe3:
|
|
||||||
# index: 74
|
|
||||||
# midi.pipe4:
|
|
||||||
# index: 75
|
|
||||||
# midi.pipe5:
|
|
||||||
# index: 76
|
|
||||||
# midi.pipe6:
|
|
||||||
# index: 77
|
|
||||||
# midi.pipe7:
|
|
||||||
# index: 78
|
|
||||||
# midi.pipe8:
|
|
||||||
# index: 79
|
|
||||||
midi.lead1:
|
|
||||||
index: 80
|
|
||||||
duration: mini
|
|
||||||
category: [MIDI, Lead, Square Lead]
|
|
||||||
# midi.lead2:
|
|
||||||
# index: 81
|
|
||||||
# midi.lead3:
|
|
||||||
# index: 82
|
|
||||||
# midi.lead4:
|
|
||||||
# index: 83
|
|
||||||
# midi.lead5:
|
|
||||||
# index: 84
|
|
||||||
# midi.lead6:
|
|
||||||
# index: 85
|
|
||||||
# midi.lead7:
|
|
||||||
# index: 86
|
|
||||||
# midi.lead8:
|
|
||||||
# index: 87
|
|
||||||
midi.pad1:
|
|
||||||
index: 88
|
|
||||||
duration: long
|
|
||||||
category: [MIDI, Pads, New Age Pad]
|
|
||||||
# midi.pad2:
|
|
||||||
# index: 89
|
|
||||||
# midi.pad3:
|
|
||||||
# index: 90
|
|
||||||
# midi.pad4:
|
|
||||||
# index: 91
|
|
||||||
# midi.pad5:
|
|
||||||
# index: 92
|
|
||||||
# midi.pad6:
|
|
||||||
# index: 93
|
|
||||||
# midi.pad7:
|
|
||||||
# index: 94
|
|
||||||
# midi.pad8:
|
|
||||||
# index: 95
|
|
||||||
midi.fx1:
|
|
||||||
index: 96
|
|
||||||
duration: mega
|
|
||||||
category: [MIDI, Synth, Rain]
|
|
||||||
# midi.fx2:
|
|
||||||
# index: 97
|
|
||||||
# midi.fx3:
|
|
||||||
# index: 98
|
|
||||||
# midi.fx4:
|
|
||||||
# index: 99
|
|
||||||
# midi.fx5:
|
|
||||||
# index: 100
|
|
||||||
# midi.fx6:
|
|
||||||
# index: 101
|
|
||||||
# midi.fx7:
|
|
||||||
# index: 102
|
|
||||||
# midi.fx8:
|
|
||||||
# index: 103
|
|
||||||
midi.world1:
|
|
||||||
index: 104
|
|
||||||
duration: extended
|
|
||||||
category: [MIDI, World, Sitar]
|
|
||||||
# midi.world2:
|
|
||||||
# index: 105
|
|
||||||
# midi.world3:
|
|
||||||
# index: 106
|
|
||||||
# midi.world4:
|
|
||||||
# index: 107
|
|
||||||
# midi.world5:
|
|
||||||
# index: 108
|
|
||||||
# midi.world6:
|
|
||||||
# index: 109
|
|
||||||
# midi.world7:
|
|
||||||
# index: 110
|
|
||||||
# midi.world8:
|
|
||||||
# index: 111
|
|
||||||
midi.percus1:
|
|
||||||
index: 112
|
|
||||||
duration: long
|
|
||||||
category: [MIDI, Drums, Tinkle Bell]
|
|
||||||
# midi.percus2:
|
|
||||||
# index: 113
|
|
||||||
# midi.percus3:
|
|
||||||
# index: 114
|
|
||||||
# midi.percus4:
|
|
||||||
# index: 115
|
|
||||||
# midi.percus5:
|
|
||||||
# index: 116
|
|
||||||
# midi.percus6:
|
|
||||||
# index: 117
|
|
||||||
# midi.percus7:
|
|
||||||
# index: 118
|
|
||||||
# midi.percus8:
|
|
||||||
# index: 119
|
|
||||||
midi.se1:
|
|
||||||
index: 120
|
|
||||||
duration: mini
|
|
||||||
category: [MIDI, Effects, Fret Noise]
|
|
||||||
# midi.se2:
|
|
||||||
# index: 121
|
|
||||||
# midi.se3:
|
|
||||||
# index: 122
|
|
||||||
# midi.se4:
|
|
||||||
# index: 123
|
|
||||||
# midi.se5:
|
|
||||||
# index: 124
|
|
||||||
# midi.se6:
|
|
||||||
# index: 125
|
|
||||||
# midi.se7:
|
|
||||||
# index: 126
|
|
||||||
# midi.se8:
|
|
||||||
# index: 127
|
|
||||||
square:
|
|
||||||
index: 128
|
|
||||||
duration: infinite
|
|
||||||
category: [Chiptune, Square Wave]
|
|
||||||
# saw:
|
|
||||||
# index: 129
|
|
||||||
# triangle:
|
|
||||||
# index: 130
|
|
||||||
# sine:
|
|
||||||
# index: 131
|
|
||||||
# noise:
|
|
||||||
# index: 132
|
|
||||||
# dualsquare:
|
|
||||||
# index: 133
|
|
||||||
# dualsaw:
|
|
||||||
# index: 134
|
|
||||||
# triangle8:
|
|
||||||
# index: 135
|
|
||||||
# konami:
|
|
||||||
# index: 136
|
|
||||||
# ramp:
|
|
||||||
# index: 137
|
|
||||||
# beep:
|
|
||||||
# index: 138
|
|
||||||
# ma1:
|
|
||||||
# index: 139
|
|
||||||
# bassdrumm:
|
|
||||||
# index: 140
|
|
||||||
# snare:
|
|
||||||
# index: 141
|
|
||||||
# closedhh:
|
|
||||||
# index: 142
|
|
||||||
# valsound.bass1:
|
|
||||||
# index: 143
|
|
||||||
# valsound.bass2:
|
|
||||||
# index: 144
|
|
||||||
# valsound.bass3:
|
|
||||||
# index: 145
|
|
||||||
# valsound.bass4:
|
|
||||||
# index: 146
|
|
||||||
# valsound.bass5:
|
|
||||||
# index: 147
|
|
||||||
# valsound.bass6:
|
|
||||||
# index: 148
|
|
||||||
# valsound.bass7:
|
|
||||||
# index: 149
|
|
||||||
# valsound.bass8:
|
|
||||||
# index: 150
|
|
||||||
# valsound.bass9:
|
|
||||||
# index: 151
|
|
||||||
# valsound.bass10:
|
|
||||||
# index: 152
|
|
||||||
# valsound.bass11:
|
|
||||||
# index: 153
|
|
||||||
# valsound.bass12:
|
|
||||||
# index: 154
|
|
||||||
# valsound.bass13:
|
|
||||||
# index: 155
|
|
||||||
# valsound.bass14:
|
|
||||||
# index: 156
|
|
||||||
# valsound.bass15:
|
|
||||||
# index: 157
|
|
||||||
# valsound.bass16:
|
|
||||||
# index: 158
|
|
||||||
# valsound.bass17:
|
|
||||||
# index: 159
|
|
||||||
# valsound.bass18:
|
|
||||||
# index: 160
|
|
||||||
# valsound.bass19:
|
|
||||||
# index: 161
|
|
||||||
# valsound.bass20:
|
|
||||||
# index: 162
|
|
||||||
# valsound.bass21:
|
|
||||||
# index: 163
|
|
||||||
# valsound.bass22:
|
|
||||||
# index: 164
|
|
||||||
# valsound.bass23:
|
|
||||||
# index: 165
|
|
||||||
# valsound.bass24:
|
|
||||||
# index: 166
|
|
||||||
# valsound.bass25:
|
|
||||||
# index: 167
|
|
||||||
# valsound.bass26:
|
|
||||||
# index: 168
|
|
||||||
# valsound.bass27:
|
|
||||||
# index: 169
|
|
||||||
# valsound.bass28:
|
|
||||||
# index: 170
|
|
||||||
# valsound.bass29:
|
|
||||||
# index: 171
|
|
||||||
# valsound.bass30:
|
|
||||||
# index: 172
|
|
||||||
# valsound.bass31:
|
|
||||||
# index: 173
|
|
||||||
# valsound.bass32:
|
|
||||||
# index: 174
|
|
||||||
# valsound.bass33:
|
|
||||||
# index: 175
|
|
||||||
# valsound.bass34:
|
|
||||||
# index: 176
|
|
||||||
# valsound.bass35:
|
|
||||||
# index: 177
|
|
||||||
# valsound.bass36:
|
|
||||||
# index: 178
|
|
||||||
# valsound.bass37:
|
|
||||||
# index: 179
|
|
||||||
# valsound.bass38:
|
|
||||||
# index: 180
|
|
||||||
# valsound.bass39:
|
|
||||||
# index: 181
|
|
||||||
# valsound.bass40:
|
|
||||||
# index: 182
|
|
||||||
# valsound.bass41:
|
|
||||||
# index: 183
|
|
||||||
# valsound.bass42:
|
|
||||||
# index: 184
|
|
||||||
# valsound.bass43:
|
|
||||||
# index: 185
|
|
||||||
# valsound.bass44:
|
|
||||||
# index: 186
|
|
||||||
# valsound.bass45:
|
|
||||||
# index: 187
|
|
||||||
# valsound.bass46:
|
|
||||||
# index: 188
|
|
||||||
# valsound.bass47:
|
|
||||||
# index: 189
|
|
||||||
# valsound.bass48:
|
|
||||||
# index: 190
|
|
||||||
# valsound.bass49:
|
|
||||||
# index: 191
|
|
||||||
# valsound.bass50:
|
|
||||||
# index: 192
|
|
||||||
# valsound.bass51:
|
|
||||||
# index: 193
|
|
||||||
# valsound.bass52:
|
|
||||||
# index: 194
|
|
||||||
# valsound.bass53:
|
|
||||||
# index: 195
|
|
||||||
# valsound.bass54:
|
|
||||||
# index: 196
|
|
||||||
# valsound.brass1:
|
|
||||||
# index: 197
|
|
||||||
# valsound.brass2:
|
|
||||||
# index: 198
|
|
||||||
# valsound.brass3:
|
|
||||||
# index: 199
|
|
||||||
# valsound.brass4:
|
|
||||||
# index: 200
|
|
||||||
# valsound.brass5:
|
|
||||||
# index: 201
|
|
||||||
# valsound.brass6:
|
|
||||||
# index: 202
|
|
||||||
# valsound.brass7:
|
|
||||||
# index: 203
|
|
||||||
# valsound.brass8:
|
|
||||||
# index: 204
|
|
||||||
# valsound.brass9:
|
|
||||||
# index: 205
|
|
||||||
# valsound.brass10:
|
|
||||||
# index: 206
|
|
||||||
# valsound.brass11:
|
|
||||||
# index: 207
|
|
||||||
# valsound.brass12:
|
|
||||||
# index: 208
|
|
||||||
# valsound.brass13:
|
|
||||||
# index: 209
|
|
||||||
# valsound.brass14:
|
|
||||||
# index: 210
|
|
||||||
# valsound.brass15:
|
|
||||||
# index: 211
|
|
||||||
# valsound.brass16:
|
|
||||||
# index: 212
|
|
||||||
# valsound.brass17:
|
|
||||||
# index: 213
|
|
||||||
# valsound.brass18:
|
|
||||||
# index: 214
|
|
||||||
# valsound.brass19:
|
|
||||||
# index: 215
|
|
||||||
# valsound.brass20:
|
|
||||||
# index: 216
|
|
||||||
# valsound.bell1:
|
|
||||||
# index: 217
|
|
||||||
# valsound.bell2:
|
|
||||||
# index: 218
|
|
||||||
# valsound.bell3:
|
|
||||||
# index: 219
|
|
||||||
# valsound.bell4:
|
|
||||||
# index: 220
|
|
||||||
# valsound.bell5:
|
|
||||||
# index: 221
|
|
||||||
# valsound.bell6:
|
|
||||||
# index: 222
|
|
||||||
# valsound.bell7:
|
|
||||||
# index: 223
|
|
||||||
# valsound.bell8:
|
|
||||||
# index: 224
|
|
||||||
# valsound.bell9:
|
|
||||||
# index: 225
|
|
||||||
# valsound.bell10:
|
|
||||||
# index: 226
|
|
||||||
# valsound.bell11:
|
|
||||||
# index: 227
|
|
||||||
# valsound.bell12:
|
|
||||||
# index: 228
|
|
||||||
# valsound.bell13:
|
|
||||||
# index: 229
|
|
||||||
# valsound.bell14:
|
|
||||||
# index: 230
|
|
||||||
# valsound.bell15:
|
|
||||||
# index: 231
|
|
||||||
# valsound.bell16:
|
|
||||||
# index: 232
|
|
||||||
# valsound.bell17:
|
|
||||||
# index: 233
|
|
||||||
# valsound.bell18:
|
|
||||||
# index: 234
|
|
||||||
# valsound.guitar1:
|
|
||||||
# index: 235
|
|
||||||
# valsound.guitar2:
|
|
||||||
# index: 236
|
|
||||||
# valsound.guitar3:
|
|
||||||
# index: 237
|
|
||||||
# valsound.guitar4:
|
|
||||||
# index: 238
|
|
||||||
# valsound.guitar5:
|
|
||||||
# index: 239
|
|
||||||
# valsound.guitar6:
|
|
||||||
# index: 240
|
|
||||||
# valsound.guitar7:
|
|
||||||
# index: 241
|
|
||||||
# valsound.guitar8:
|
|
||||||
# index: 242
|
|
||||||
# valsound.guitar9:
|
|
||||||
# index: 243
|
|
||||||
# valsound.guitar10:
|
|
||||||
# index: 244
|
|
||||||
# valsound.guitar11:
|
|
||||||
# index: 245
|
|
||||||
# valsound.guitar12:
|
|
||||||
# index: 246
|
|
||||||
# valsound.guitar13:
|
|
||||||
# index: 247
|
|
||||||
# valsound.guitar14:
|
|
||||||
# index: 248
|
|
||||||
# valsound.guitar15:
|
|
||||||
# index: 249
|
|
||||||
# valsound.guitar16:
|
|
||||||
# index: 250
|
|
||||||
# valsound.guitar17:
|
|
||||||
# index: 251
|
|
||||||
# valsound.guitar18:
|
|
||||||
# index: 252
|
|
||||||
# valsound.lead1:
|
|
||||||
# index: 253
|
|
||||||
# valsound.lead2:
|
|
||||||
# index: 254
|
|
||||||
# valsound.lead3:
|
|
||||||
# index: 255
|
|
||||||
# valsound.lead4:
|
|
||||||
# index: 256
|
|
||||||
# valsound.lead5:
|
|
||||||
# index: 257
|
|
||||||
# valsound.lead6:
|
|
||||||
# index: 258
|
|
||||||
# valsound.lead7:
|
|
||||||
# index: 259
|
|
||||||
# valsound.lead8:
|
|
||||||
# index: 260
|
|
||||||
# valsound.lead9:
|
|
||||||
# index: 261
|
|
||||||
# valsound.lead10:
|
|
||||||
# index: 262
|
|
||||||
# valsound.lead11:
|
|
||||||
# index: 263
|
|
||||||
# valsound.lead12:
|
|
||||||
# index: 264
|
|
||||||
# valsound.lead13:
|
|
||||||
# index: 265
|
|
||||||
# valsound.lead14:
|
|
||||||
# index: 266
|
|
||||||
# valsound.lead15:
|
|
||||||
# index: 267
|
|
||||||
# valsound.lead16:
|
|
||||||
# index: 268
|
|
||||||
# valsound.lead17:
|
|
||||||
# index: 269
|
|
||||||
# valsound.lead18:
|
|
||||||
# index: 270
|
|
||||||
# valsound.lead19:
|
|
||||||
# index: 271
|
|
||||||
# valsound.lead20:
|
|
||||||
# index: 272
|
|
||||||
# valsound.lead21:
|
|
||||||
# index: 273
|
|
||||||
# valsound.lead22:
|
|
||||||
# index: 274
|
|
||||||
# valsound.lead23:
|
|
||||||
# index: 275
|
|
||||||
# valsound.lead24:
|
|
||||||
# index: 276
|
|
||||||
# valsound.lead25:
|
|
||||||
# index: 277
|
|
||||||
# valsound.lead26:
|
|
||||||
# index: 278
|
|
||||||
# valsound.lead27:
|
|
||||||
# index: 279
|
|
||||||
# valsound.lead28:
|
|
||||||
# index: 280
|
|
||||||
# valsound.lead29:
|
|
||||||
# index: 281
|
|
||||||
# valsound.lead30:
|
|
||||||
# index: 282
|
|
||||||
# valsound.lead31:
|
|
||||||
# index: 283
|
|
||||||
# valsound.lead32:
|
|
||||||
# index: 284
|
|
||||||
# valsound.lead33:
|
|
||||||
# index: 285
|
|
||||||
# valsound.lead34:
|
|
||||||
# index: 286
|
|
||||||
# valsound.lead35:
|
|
||||||
# index: 287
|
|
||||||
# valsound.lead36:
|
|
||||||
# index: 288
|
|
||||||
# valsound.lead37:
|
|
||||||
# index: 289
|
|
||||||
# valsound.lead38:
|
|
||||||
# index: 290
|
|
||||||
# valsound.lead39:
|
|
||||||
# index: 291
|
|
||||||
# valsound.lead40:
|
|
||||||
# index: 292
|
|
||||||
# valsound.lead41:
|
|
||||||
# index: 293
|
|
||||||
# valsound.lead42:
|
|
||||||
# index: 294
|
|
||||||
# valsound.piano1:
|
|
||||||
# index: 295
|
|
||||||
# valsound.piano2:
|
|
||||||
# index: 296
|
|
||||||
# valsound.piano3:
|
|
||||||
# index: 297
|
|
||||||
# valsound.piano4:
|
|
||||||
# index: 298
|
|
||||||
# valsound.piano5:
|
|
||||||
# index: 299
|
|
||||||
# valsound.piano6:
|
|
||||||
# index: 300
|
|
||||||
# valsound.piano7:
|
|
||||||
# index: 301
|
|
||||||
# valsound.piano8:
|
|
||||||
# index: 302
|
|
||||||
# valsound.piano9:
|
|
||||||
# index: 303
|
|
||||||
# valsound.piano10:
|
|
||||||
# index: 304
|
|
||||||
# valsound.piano11:
|
|
||||||
# index: 305
|
|
||||||
# valsound.piano12:
|
|
||||||
# index: 306
|
|
||||||
# valsound.piano13:
|
|
||||||
# index: 307
|
|
||||||
# valsound.piano14:
|
|
||||||
# index: 308
|
|
||||||
# valsound.piano15:
|
|
||||||
# index: 309
|
|
||||||
# valsound.piano16:
|
|
||||||
# index: 310
|
|
||||||
# valsound.piano17:
|
|
||||||
# index: 311
|
|
||||||
# valsound.piano18:
|
|
||||||
# index: 312
|
|
||||||
# valsound.piano19:
|
|
||||||
# index: 313
|
|
||||||
# valsound.piano20:
|
|
||||||
# index: 314
|
|
||||||
# valsound.se1:
|
|
||||||
# index: 315
|
|
||||||
# valsound.se2:
|
|
||||||
# index: 316
|
|
||||||
# valsound.se3:
|
|
||||||
# index: 317
|
|
||||||
# valsound.special1:
|
|
||||||
# index: 318
|
|
||||||
# valsound.special2:
|
|
||||||
# index: 319
|
|
||||||
# valsound.special3:
|
|
||||||
# index: 320
|
|
||||||
# valsound.special4:
|
|
||||||
# index: 321
|
|
||||||
# valsound.special5:
|
|
||||||
# index: 322
|
|
||||||
# valsound.strpad1:
|
|
||||||
# index: 323
|
|
||||||
# valsound.strpad2:
|
|
||||||
# index: 324
|
|
||||||
# valsound.strpad3:
|
|
||||||
# index: 325
|
|
||||||
# valsound.strpad4:
|
|
||||||
# index: 326
|
|
||||||
# valsound.strpad5:
|
|
||||||
# index: 327
|
|
||||||
# valsound.strpad6:
|
|
||||||
# index: 328
|
|
||||||
# valsound.strpad7:
|
|
||||||
# index: 329
|
|
||||||
# valsound.strpad8:
|
|
||||||
# index: 330
|
|
||||||
# valsound.strpad9:
|
|
||||||
# index: 331
|
|
||||||
# valsound.strpad10:
|
|
||||||
# index: 332
|
|
||||||
# valsound.strpad11:
|
|
||||||
# index: 333
|
|
||||||
# valsound.strpad12:
|
|
||||||
# index: 334
|
|
||||||
# valsound.strpad13:
|
|
||||||
# index: 335
|
|
||||||
# valsound.strpad14:
|
|
||||||
# index: 336
|
|
||||||
# valsound.strpad15:
|
|
||||||
# index: 337
|
|
||||||
# valsound.strpad16:
|
|
||||||
# index: 338
|
|
||||||
# valsound.strpad17:
|
|
||||||
# index: 339
|
|
||||||
# valsound.strpad18:
|
|
||||||
# index: 340
|
|
||||||
# valsound.strpad19:
|
|
||||||
# index: 341
|
|
||||||
# valsound.strpad20:
|
|
||||||
# index: 342
|
|
||||||
# valsound.strpad21:
|
|
||||||
# index: 343
|
|
||||||
# valsound.strpad22:
|
|
||||||
# index: 344
|
|
||||||
# valsound.strpad23:
|
|
||||||
# index: 345
|
|
||||||
# valsound.strpad24:
|
|
||||||
# index: 346
|
|
||||||
# valsound.strpad25:
|
|
||||||
# index: 347
|
|
||||||
# valsound.wind1:
|
|
||||||
# index: 348
|
|
||||||
# valsound.wind2:
|
|
||||||
# index: 349
|
|
||||||
# valsound.wind3:
|
|
||||||
# index: 350
|
|
||||||
# valsound.wind4:
|
|
||||||
# index: 351
|
|
||||||
# valsound.wind5:
|
|
||||||
# index: 352
|
|
||||||
# valsound.wind6:
|
|
||||||
# index: 353
|
|
||||||
# valsound.wind7:
|
|
||||||
# index: 354
|
|
||||||
# valsound.wind8:
|
|
||||||
# index: 355
|
|
||||||
# valsound.world1:
|
|
||||||
# index: 356
|
|
||||||
# valsound.world2:
|
|
||||||
# index: 357
|
|
||||||
# valsound.world3:
|
|
||||||
# index: 358
|
|
||||||
# valsound.world4:
|
|
||||||
# index: 359
|
|
||||||
# valsound.world5:
|
|
||||||
# index: 360
|
|
||||||
# valsound.world6:
|
|
||||||
# index: 361
|
|
||||||
# valsound.world7:
|
|
||||||
# index: 362
|
|
||||||
# drumkit.1:
|
|
||||||
# index: 363
|
|
||||||
# duration: short
|
|
||||||
# category: [Drumkit, Simple Drumkit]
|
|
||||||
# drumkit.2:
|
|
||||||
# index: 364
|
|
||||||
# drumkit.3:
|
|
||||||
# index: 365
|
|
17763
package-lock.json
generated
17763
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
116
package.json
116
package.json
@ -1,64 +1,62 @@
|
|||||||
{
|
{
|
||||||
"name": "bosca-ceoil-js",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "TypeScript port of SiON",
|
|
||||||
"main": "out/index.js",
|
|
||||||
"types": "out/index.d.ts",
|
|
||||||
"scripts": {
|
|
||||||
"compile": "tsc -p ./",
|
|
||||||
"deploy": "ts-node tasks/deploy.ts",
|
|
||||||
"dev": "concurrently npm:watch npm:webpack-dev npm:serve",
|
|
||||||
"samples": "ts-node tasks/samples.ts",
|
|
||||||
"serve": "ws --rewrite \"/bosca-ceoil-js/(.*) -> /$1\"",
|
|
||||||
"start": "node out/index.js",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
|
||||||
"watch": "tsc -watch -p ./",
|
|
||||||
"webpack": "webpack --mode development",
|
|
||||||
"webpack-dev": "webpack --mode development --watch"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/mtkennerly/bosca-ceoil-js.git"
|
|
||||||
},
|
|
||||||
"author": "Matthew T. Kennerly",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/mtkennerly/bosca-ceoil-js/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/mtkennerly/bosca-ceoil-js#readme",
|
|
||||||
"files": [
|
|
||||||
"/out"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material/react-button": "^0.14.1",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"@material/react-dialog": "^0.15.0",
|
"rpmbuild": "^0.0.23"
|
||||||
"@material/react-menu-surface": "^0.15.0",
|
|
||||||
"@material/react-select": "^0.14.1",
|
|
||||||
"@material/react-text-field": "^0.14.1",
|
|
||||||
"react": "^16.8.6",
|
|
||||||
"react-dom": "^16.8.6",
|
|
||||||
"react-easy-state": "^6.1.3",
|
|
||||||
"tone": "^13.4.9"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/gh-pages": "^2.0.0",
|
"@electron-forge/cli": "^6.0.0-beta.64",
|
||||||
"@types/js-yaml": "^3.12.1",
|
"@electron-forge/maker-deb": "^6.0.0-beta.64",
|
||||||
"@types/node": "^12.6.3",
|
"@electron-forge/maker-rpm": "^6.0.0-beta.64",
|
||||||
"@types/react": "^16.8.23",
|
"@electron-forge/maker-squirrel": "^6.0.0-beta.64",
|
||||||
"@types/react-dom": "^16.8.4",
|
"@electron-forge/maker-zip": "^6.0.0-beta.64",
|
||||||
"@types/tone": "git+https://github.com/Tonejs/TypeScript.git",
|
"electron": "^19.0.6"
|
||||||
"concurrently": "^4.1.1",
|
},
|
||||||
"css-loader": "^3.1.0",
|
"scripts": {
|
||||||
"gh-pages": "^2.0.1",
|
"start": "electron-forge start",
|
||||||
"js-yaml": "^3.13.1",
|
"package": "electron-forge package",
|
||||||
"js-yaml-loader": "^1.2.2",
|
"make": "electron-forge make"
|
||||||
"local-web-server": "^3.0.4",
|
},
|
||||||
"style-loader": "^0.23.1",
|
"config": {
|
||||||
"ts-loader": "^6.0.4",
|
"forge": {
|
||||||
"ts-node": "^8.3.0",
|
"packagerConfig": {},
|
||||||
"tslint": "^5.18.0",
|
"makers": [
|
||||||
"typescript": "^3.5.2",
|
{
|
||||||
"webpack": "^4.35.2",
|
"name": "@electron-forge/maker-squirrel",
|
||||||
"webpack-cli": "^3.3.5"
|
"config": {
|
||||||
}
|
"name": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-zip",
|
||||||
|
"platforms": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-deb",
|
||||||
|
"config": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-rpm",
|
||||||
|
"config": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "bosca-ceoil-electron",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "just a port to electron",
|
||||||
|
"main": "index.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.brn.systems/BRN_Systems/bosca-ceoil-electron.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"bosca",
|
||||||
|
"ceoil",
|
||||||
|
"boscaceol",
|
||||||
|
"electron"
|
||||||
|
],
|
||||||
|
"author": "BRN Systems (original:mtkennerly)",
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
28439
public/index.js
Normal file
28439
public/index.js
Normal file
File diff suppressed because one or more lines are too long
71
src/audio.ts
71
src/audio.ts
@ -1,71 +0,0 @@
|
|||||||
import tone from "tone";
|
|
||||||
import { assertNever, notes } from "./index";
|
|
||||||
import { instrumentData } from "./data";
|
|
||||||
|
|
||||||
function setSamplerRelease(sampler: tone.Sampler, instrument: string) {
|
|
||||||
const instrumentDuration = instrumentData[instrument]["duration"];
|
|
||||||
|
|
||||||
switch (instrumentDuration) {
|
|
||||||
case "instant":
|
|
||||||
sampler.release = 0.05;
|
|
||||||
break;
|
|
||||||
case "mini":
|
|
||||||
sampler.release = 0.125;
|
|
||||||
break;
|
|
||||||
case "short":
|
|
||||||
sampler.release = 0.25;
|
|
||||||
break;
|
|
||||||
case "mid":
|
|
||||||
sampler.release = 0.4;
|
|
||||||
break;
|
|
||||||
case "long":
|
|
||||||
sampler.release = 1;
|
|
||||||
break;
|
|
||||||
case "extended":
|
|
||||||
sampler.release = 1.5;
|
|
||||||
break;
|
|
||||||
case "mega":
|
|
||||||
sampler.release = 5;
|
|
||||||
break;
|
|
||||||
case "constant":
|
|
||||||
sampler.release = 30;
|
|
||||||
break;
|
|
||||||
case "infinite":
|
|
||||||
sampler.release = 0.1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assertNever(instrumentDuration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSamplerCurve(sampler: tone.Sampler) {
|
|
||||||
(sampler as any).curve = "linear";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSampler(
|
|
||||||
instrument: string,
|
|
||||||
extension: string = "ogg",
|
|
||||||
baseUrl: string = "/bosca-ceoil-js/audio/"
|
|
||||||
): tone.Sampler {
|
|
||||||
let samples: { [key: string]: string } = {};
|
|
||||||
for (const note of notes) {
|
|
||||||
samples[note] = `${instrument}/${note.toLowerCase()}.${extension}`;
|
|
||||||
}
|
|
||||||
let sampler = new tone.Sampler(samples, undefined, baseUrl);
|
|
||||||
setSamplerCurve(sampler);
|
|
||||||
setSamplerRelease(sampler, instrument);
|
|
||||||
return sampler;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function changeSampler(
|
|
||||||
sampler: tone.Sampler,
|
|
||||||
instrument: string,
|
|
||||||
extension: string = "ogg",
|
|
||||||
): tone.Sampler {
|
|
||||||
for (const note of notes) {
|
|
||||||
sampler.add(note, `${instrument}/${note.toLowerCase()}.${extension}`);
|
|
||||||
}
|
|
||||||
setSamplerCurve(sampler);
|
|
||||||
setSamplerRelease(sampler, instrument);
|
|
||||||
return sampler;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
import { InstrumentData } from "./index";
|
|
||||||
|
|
||||||
export const instrumentData: InstrumentData = require("../instruments.yaml");
|
|
22
src/index.ts
22
src/index.ts
@ -1,22 +0,0 @@
|
|||||||
export function assertNever(x: never): never {
|
|
||||||
throw new Error(`Unexhaustive condition leading to value: ${x}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Durations:
|
|
||||||
// - Constant means that the sound plays for the same amount of time
|
|
||||||
// regardless of the length of the note in Bosca Ceoil.
|
|
||||||
// - Infinite means that the sound plays for however arbitrarily long
|
|
||||||
// the Bosca Ceoil note is.
|
|
||||||
// - The others are finite times, meaning that the sound will play up to
|
|
||||||
// a max time, but after that point, it will go silent regardless of how
|
|
||||||
// long the note is in Bosca Ceoil.
|
|
||||||
export interface Instrument {
|
|
||||||
index: number;
|
|
||||||
duration: "instant" | "mini" | "short" | "mid" | "long" | "extended" | "mega" | "constant" | "infinite";
|
|
||||||
release: number;
|
|
||||||
category: Array<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type InstrumentData = { [key: string]: Instrument };
|
|
||||||
|
|
||||||
export const notes = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"];
|
|
675
src/player.tsx
675
src/player.tsx
@ -1,675 +0,0 @@
|
|||||||
require("@material/react-button/dist/button.css");
|
|
||||||
require("@material/react-select/dist/select.css");
|
|
||||||
require("@material/react-text-field/dist/text-field.css");
|
|
||||||
require("@material/react-menu-surface/dist/menu-surface.css");
|
|
||||||
require("@material/react-dialog/dist/dialog.css");
|
|
||||||
|
|
||||||
import { store, view } from "react-easy-state";
|
|
||||||
import tone from "tone";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import ReactDom from "react-dom";
|
|
||||||
import Button from "@material/react-button";
|
|
||||||
import Select, { Option } from "@material/react-select";
|
|
||||||
import TextField, { Input } from "@material/react-text-field";
|
|
||||||
import MenuSurface, { Corner } from "@material/react-menu-surface";
|
|
||||||
import Dialog, { DialogTitle, DialogContent, DialogFooter, DialogButton } from "@material/react-dialog";
|
|
||||||
|
|
||||||
import { changeSampler, getSampler } from "./audio";
|
|
||||||
import { instrumentData } from "./data";
|
|
||||||
import { assertNever } from "./index";
|
|
||||||
|
|
||||||
let playing = false;
|
|
||||||
const letters = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
|
||||||
const chords = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
||||||
|
|
||||||
function fromMaybes<T>(maybes: Array<T | null | undefined>, fallback: T) {
|
|
||||||
for (const value of maybes) {
|
|
||||||
if (value !== null && value !== undefined) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Song {
|
|
||||||
meta: SongMeta = new SongMeta();
|
|
||||||
effects: Effects = new Effects();
|
|
||||||
patterns: Array<Pattern> = [new Pattern()];
|
|
||||||
channels: Array<Channel> = [new Channel()];
|
|
||||||
playing: boolean = false;
|
|
||||||
|
|
||||||
toggleAudio(): void {
|
|
||||||
if (this.playing) {
|
|
||||||
tone.Transport.stop();
|
|
||||||
tone.Transport.position = "0";
|
|
||||||
} else {
|
|
||||||
resumeAudioContext();
|
|
||||||
tone.Transport.loopEnd = "1m";
|
|
||||||
tone.Transport.loop = true;
|
|
||||||
}
|
|
||||||
tone.Transport.toggle(0);
|
|
||||||
this.playing = !this.playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEffect(effect: keyof Effects, channel: number, pattern: number): number {
|
|
||||||
return fromMaybes(
|
|
||||||
[
|
|
||||||
this.patterns[pattern].effects[effect],
|
|
||||||
this.channels[channel].effects[effect],
|
|
||||||
],
|
|
||||||
this.effects[effect]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getEffectBy(effect: keyof Effects, level: "song" | "channel" | "pattern", index: number = 0) {
|
|
||||||
switch (level) {
|
|
||||||
case "song":
|
|
||||||
return this.effects[effect];
|
|
||||||
case "channel":
|
|
||||||
return this.channels[index].effects[effect];
|
|
||||||
case "pattern":
|
|
||||||
return this.patterns[index].effects[effect];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setEffectBy(effect: keyof Effects, value: number | undefined, level: "song" | "channel" | "pattern", index: number = 0) {
|
|
||||||
switch (level) {
|
|
||||||
case "song":
|
|
||||||
if (value === undefined) {
|
|
||||||
throw new Error("Song-level effects cannot be undefined");
|
|
||||||
}
|
|
||||||
this.effects[effect] = value;
|
|
||||||
for (let channel = 0; channel < this.channels.length; channel++) {
|
|
||||||
for (const pattern of this.channels[channel].patterns) {
|
|
||||||
if (pattern !== null) {
|
|
||||||
this.patterns[pattern].pipeline.setEffect(effect, this.getEffect(effect, channel, pattern));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "channel":
|
|
||||||
this.channels[index].effects[effect] = value;
|
|
||||||
for (const pattern of this.channels[index].patterns) {
|
|
||||||
if (pattern !== null) {
|
|
||||||
this.patterns[pattern].pipeline.setEffect(effect, this.getEffect(effect, index, pattern));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "pattern":
|
|
||||||
this.patterns[index].effects[effect] = value;
|
|
||||||
for (let channel = 0; channel < this.channels.length; channel++) {
|
|
||||||
for (const pattern of this.channels[channel].patterns) {
|
|
||||||
if (pattern === index) {
|
|
||||||
this.patterns[pattern].pipeline.setEffect(effect, this.getEffect(effect, channel, index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInstrument(value: string, pattern: number): void {
|
|
||||||
this.patterns[pattern].instrument = value;
|
|
||||||
changeSampler(this.patterns[pattern].pipeline.sampler, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
setBpm(value: number): void {
|
|
||||||
this.meta.bpm = value;
|
|
||||||
tone.Transport.bpm.value = song.meta.bpm;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSwing(value: number): void {
|
|
||||||
this.meta.swing = value;
|
|
||||||
tone.Transport.swing = song.meta.swing;
|
|
||||||
tone.Transport.swingSubdivision = "16n";
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleNote(channel: number, pattern: number, chord: number, letter: string, index: number, length: number, noteCell: NoteCell) {
|
|
||||||
this.unscheduleNote(channel, pattern, chord, letter, index);
|
|
||||||
// console.log(`Song.scheduleNote(chord=${chord}, letter=${letter}, index=${index}, length=${length})`);
|
|
||||||
this.patterns[pattern].notes[chord][letter][index].length = length;
|
|
||||||
|
|
||||||
this.patterns[pattern].notes[chord][letter][index].scheduledEvent = tone.Transport.schedule(
|
|
||||||
time => {
|
|
||||||
this.patterns[pattern].pipeline.sampler.triggerAttackRelease(
|
|
||||||
`${letter}${chord}`, tone.Time("16n") * length, time
|
|
||||||
);
|
|
||||||
tone.Draw.schedule(() => {
|
|
||||||
noteCell.setState({ playing: true });
|
|
||||||
setTimeout(() => {
|
|
||||||
noteCell.setState({ playing: false });
|
|
||||||
}, 100 * length);
|
|
||||||
}, time);
|
|
||||||
},
|
|
||||||
`0:0:${index}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
unscheduleNote(channel: number, pattern: number, chord: number, letter: string, index: number) {
|
|
||||||
// console.log(`Song.unscheduleNote(chord=${chord}, letter=${letter}, index=${index}, length=${length})`);
|
|
||||||
this.patterns[pattern].notes[chord][letter][index].length = 0;
|
|
||||||
const schedulee = this.patterns[pattern].notes[chord][letter][index].scheduledEvent;
|
|
||||||
if (schedulee !== null) {
|
|
||||||
tone.Transport.clear(schedulee);
|
|
||||||
this.patterns[pattern].notes[chord][letter][index].scheduledEvent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SongMeta {
|
|
||||||
bpm: number = 120;
|
|
||||||
swing: number = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Effects {
|
|
||||||
volume: number = 100;
|
|
||||||
resonance: number = 0;
|
|
||||||
dampening: number = 0;
|
|
||||||
delay: number = 0;
|
|
||||||
chorus: number = 0;
|
|
||||||
reverb: number = 0;
|
|
||||||
distortion: number = 0;
|
|
||||||
lowBoost: number = 0;
|
|
||||||
compressor: number = 0;
|
|
||||||
highPass: number = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pipeline {
|
|
||||||
sampler: tone.Sampler;
|
|
||||||
volume = new tone.Volume(0);
|
|
||||||
lowPass = new tone.LowpassCombFilter(0, 0);
|
|
||||||
delayEffect = new tone.FeedbackDelay(0, 0);
|
|
||||||
chorusEffect = new tone.Chorus();
|
|
||||||
reverbEffect = new tone.Freeverb(0, 3000);
|
|
||||||
distortionEffect = new tone.BitCrusher(4);
|
|
||||||
lowBoostEffect = new tone.Filter(0, "lowshelf");
|
|
||||||
compressorEffect = new tone.Compressor(0);
|
|
||||||
highPassEffect = new tone.Filter(0, "highpass");
|
|
||||||
|
|
||||||
constructor(instrument: string = "midi.piano1") {
|
|
||||||
this.sampler = getSampler(instrument);
|
|
||||||
this.chorusEffect.wet.value = 0;
|
|
||||||
this.reverbEffect.wet.value = 0;
|
|
||||||
this.distortionEffect.wet.value = 0;
|
|
||||||
|
|
||||||
this.sampler.chain(
|
|
||||||
this.volume,
|
|
||||||
this.lowPass,
|
|
||||||
this.delayEffect,
|
|
||||||
this.chorusEffect,
|
|
||||||
this.reverbEffect,
|
|
||||||
this.distortionEffect,
|
|
||||||
this.lowBoostEffect,
|
|
||||||
this.compressorEffect,
|
|
||||||
this.highPassEffect,
|
|
||||||
tone.Master,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setEffect(effect: keyof Effects, value: number): void {
|
|
||||||
switch (effect) {
|
|
||||||
case "volume":
|
|
||||||
if (value === 0) {
|
|
||||||
this.volume.mute = true;
|
|
||||||
} else {
|
|
||||||
this.volume.volume.value = (value - 100) / 5;
|
|
||||||
this.volume.mute = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "resonance":
|
|
||||||
this.lowPass.resonance.value = value;
|
|
||||||
break;
|
|
||||||
case "dampening":
|
|
||||||
this.lowPass.dampening.value = value;
|
|
||||||
break;
|
|
||||||
case "delay":
|
|
||||||
this.delayEffect.delayTime.value = value === 0 ? 0 : tone.Time("8n") * 2 * value;
|
|
||||||
this.delayEffect.feedback.value = 0.15 * 2 * value;
|
|
||||||
break;
|
|
||||||
case "chorus":
|
|
||||||
this.chorusEffect.wet.value = value;
|
|
||||||
break;
|
|
||||||
case "reverb":
|
|
||||||
this.reverbEffect.roomSize.value = value * 0.9;
|
|
||||||
this.reverbEffect.wet.value = value;
|
|
||||||
break;
|
|
||||||
case "distortion":
|
|
||||||
this.distortionEffect.wet.value = value === 0 ? 0 : 1;
|
|
||||||
this.distortionEffect.bits = value;
|
|
||||||
break;
|
|
||||||
case "lowBoost":
|
|
||||||
this.lowBoostEffect.frequency.value = value;
|
|
||||||
this.lowBoostEffect.gain.value = value === 0 ? 0 : 20;
|
|
||||||
break;
|
|
||||||
case "compressor":
|
|
||||||
this.compressorEffect.threshold.value = value;
|
|
||||||
break;
|
|
||||||
case "highPass":
|
|
||||||
this.highPassEffect.frequency.value = value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assertNever(effect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pattern {
|
|
||||||
effects: Partial<Effects> = {};
|
|
||||||
notes: { [key: number]: { [key: string]: Array<Note> } } = {};
|
|
||||||
pipeline: Pipeline;
|
|
||||||
|
|
||||||
constructor(public instrument: string = "midi.piano1") {
|
|
||||||
this.pipeline = new Pipeline(this.instrument);
|
|
||||||
for (const chord of chords) {
|
|
||||||
this.notes[chord] = {};
|
|
||||||
for (const letter of letters) {
|
|
||||||
this.notes[chord][letter] = Array.from({ length: 16 }, () => ({ length: 0, scheduledEvent: null }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Channel {
|
|
||||||
effects: Partial<Effects> = {};
|
|
||||||
patterns: Array<number | null>;
|
|
||||||
|
|
||||||
constructor(patterns: Array<number | null> = [0]) {
|
|
||||||
this.patterns = patterns;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const song: Song = store(new Song());
|
|
||||||
|
|
||||||
@view
|
|
||||||
class EffectChoice extends React.Component<{
|
|
||||||
effect: keyof Effects,
|
|
||||||
mode: "song" | "channel" | "pattern",
|
|
||||||
modeIndex: number,
|
|
||||||
min: number,
|
|
||||||
max: number,
|
|
||||||
step: number,
|
|
||||||
}> {
|
|
||||||
getLabel(): string {
|
|
||||||
switch (this.props.effect) {
|
|
||||||
case "chorus":
|
|
||||||
return "Chorus";
|
|
||||||
case "compressor":
|
|
||||||
return "Compressor";
|
|
||||||
case "dampening":
|
|
||||||
return "Dampening";
|
|
||||||
case "delay":
|
|
||||||
return "Delay";
|
|
||||||
case "distortion":
|
|
||||||
return "Distortion";
|
|
||||||
case "highPass":
|
|
||||||
return "High pass";
|
|
||||||
case "lowBoost":
|
|
||||||
return "Low boost";
|
|
||||||
case "resonance":
|
|
||||||
return "Resonance";
|
|
||||||
case "reverb":
|
|
||||||
return "Reverb";
|
|
||||||
case "volume":
|
|
||||||
return "Volume";
|
|
||||||
default:
|
|
||||||
return assertNever(this.props.effect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render(): React.ReactNode {
|
|
||||||
return <TextField label={this.getLabel()}>
|
|
||||||
<Input
|
|
||||||
required={this.props.mode === "song"}
|
|
||||||
type="number"
|
|
||||||
min={this.props.min}
|
|
||||||
max={this.props.max}
|
|
||||||
step={this.props.step}
|
|
||||||
value={song.getEffectBy(this.props.effect, this.props.mode, this.props.modeIndex)}
|
|
||||||
// @ts-ignore
|
|
||||||
onChange={e => {
|
|
||||||
if (this.props.mode !== "song" && e.currentTarget.value === "") {
|
|
||||||
song.setEffectBy(this.props.effect, undefined, this.props.mode, this.props.modeIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newValue = parseFloat(e.currentTarget.value);
|
|
||||||
if (!isNaN(newValue)) {
|
|
||||||
song.setEffectBy(this.props.effect, newValue, this.props.mode, this.props.modeIndex);
|
|
||||||
} else if (e.currentTarget.value === undefined && this.props.mode !== "song") {
|
|
||||||
song.setEffectBy(this.props.effect, undefined, this.props.mode, this.props.modeIndex);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TextField>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@view
|
|
||||||
class EffectPanel extends React.Component<{
|
|
||||||
mode: "song" | "channel" | "pattern",
|
|
||||||
modeIndex: number,
|
|
||||||
}> {
|
|
||||||
state = {
|
|
||||||
open: false,
|
|
||||||
anchorElement: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
getButtonText(): string {
|
|
||||||
switch (this.props.mode) {
|
|
||||||
case "song":
|
|
||||||
return "Song";
|
|
||||||
case "channel":
|
|
||||||
return "Channel";
|
|
||||||
case "pattern":
|
|
||||||
return "Pattern"
|
|
||||||
default:
|
|
||||||
return assertNever(this.props.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setAnchorElement = (element: HTMLDivElement) => {
|
|
||||||
if (this.state.anchorElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({ anchorElement: element });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="mdc-menu-surface--anchor effect-panel"
|
|
||||||
ref={this.setAnchorElement}
|
|
||||||
>
|
|
||||||
<Button raised onClick={() => this.setState({ open: true })}>{this.getButtonText()}</Button>
|
|
||||||
|
|
||||||
<MenuSurface
|
|
||||||
open={this.state.open}
|
|
||||||
anchorCorner={Corner.BOTTOM_LEFT}
|
|
||||||
onClose={() => this.setState({ open: false })}
|
|
||||||
anchorElement={this.state.anchorElement}
|
|
||||||
>
|
|
||||||
<div className="effect-panel-popup">
|
|
||||||
<EffectChoice effect="volume" min={0} max={200} step={5} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
<EffectChoice effect="resonance" min={0} max={1} step={0.05} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
<EffectChoice effect="dampening" min={0} max={3000} step={100} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
<EffectChoice effect="delay" min={0} max={1} step={0.05} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
<EffectChoice effect="chorus" min={0} max={1} step={0.05} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
<EffectChoice effect="reverb" min={0} max={1} step={0.05} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
<EffectChoice effect="distortion" min={0} max={1} step={0.05} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
<EffectChoice effect="lowBoost" min={0} max={1} step={0.05} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
<EffectChoice effect="compressor" min={0} max={1} step={0.05} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
<EffectChoice effect="highPass" min={0} max={1} step={0.05} mode={this.props.mode} modeIndex={this.props.modeIndex} />
|
|
||||||
</div>
|
|
||||||
</MenuSurface>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@view
|
|
||||||
class PlayButton extends React.Component {
|
|
||||||
onClick() {
|
|
||||||
song.toggleAudio();
|
|
||||||
}
|
|
||||||
render(): React.ReactNode {
|
|
||||||
return <Button id="playButton" onClick={() => this.onClick()} raised>
|
|
||||||
{song.playing ? "Stop" : "Play"}
|
|
||||||
</Button>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@view
|
|
||||||
class Help extends React.Component {
|
|
||||||
state = {
|
|
||||||
displayed: false,
|
|
||||||
};
|
|
||||||
render(): React.ReactNode {
|
|
||||||
return <a>
|
|
||||||
<Button id="helpButton" onClick={() => this.setState({displayed: true})} raised>Help</Button>
|
|
||||||
<Dialog open={this.state.displayed} onClose={() => this.setState({displayed: false})}>
|
|
||||||
<DialogTitle>Help</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
<li>Click in a box to add or remove a note.</li>
|
|
||||||
<li>Shift click to extend a note, and ctrl-shift click to shorten it.</li>
|
|
||||||
<li>Press the space bar to play or stop the song.</li>
|
|
||||||
<li>After clicking in an entry field, use the arrow keys to quickly change the value.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogFooter>
|
|
||||||
<DialogButton action="close" isDefault>Close</DialogButton>
|
|
||||||
</DialogFooter>
|
|
||||||
</Dialog>
|
|
||||||
</a>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@view
|
|
||||||
class MetaWorkspace extends React.Component<{ activeChannel: number, activePattern: number }> {
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const instruments = [];
|
|
||||||
for (const [instrument, { category }] of Object.entries(instrumentData)) {
|
|
||||||
instruments.push(
|
|
||||||
<Option key={instrument} value={instrument}>{category.join(" > ")}</Option>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div id="metaWorkspace">
|
|
||||||
<PlayButton />
|
|
||||||
<Select
|
|
||||||
label="Pattern instrument"
|
|
||||||
value={song.patterns[this.props.activePattern].instrument}
|
|
||||||
onChange={e => {
|
|
||||||
song.setInstrument(e.currentTarget.value, this.props.activeChannel);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{instruments}
|
|
||||||
</Select>
|
|
||||||
<TextField label="BPM">
|
|
||||||
<Input
|
|
||||||
required
|
|
||||||
type="number"
|
|
||||||
min={10}
|
|
||||||
max={220}
|
|
||||||
step={5}
|
|
||||||
value={song.meta.bpm}
|
|
||||||
// @ts-ignore
|
|
||||||
onChange={e => {
|
|
||||||
const value = parseInt(e.currentTarget.value);
|
|
||||||
if (isNaN(value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
song.setBpm(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TextField>
|
|
||||||
<TextField label="Swing">
|
|
||||||
<Input
|
|
||||||
required
|
|
||||||
type="number"
|
|
||||||
min={0}
|
|
||||||
max={1}
|
|
||||||
step={0.05}
|
|
||||||
value={song.meta.swing}
|
|
||||||
// @ts-ignore
|
|
||||||
onChange={e => {
|
|
||||||
const value = parseFloat(e.currentTarget.value);
|
|
||||||
if (isNaN(value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
song.setSwing(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TextField>
|
|
||||||
<EffectPanel mode="song" modeIndex={0} />
|
|
||||||
<EffectPanel mode="channel" modeIndex={0} />
|
|
||||||
<EffectPanel mode="pattern" modeIndex={0} />
|
|
||||||
<Help />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@view
|
|
||||||
class NoteCell extends React.Component<{ channel: number, pattern: number, chord: number, letter: string, index: number }> {
|
|
||||||
state = {
|
|
||||||
chord: this.props.chord,
|
|
||||||
letter: this.props.letter,
|
|
||||||
index: this.props.index,
|
|
||||||
length: 0,
|
|
||||||
playing: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
onClick(chord: number, letter: string, index: number, withShift: boolean, withCtrl: boolean) {
|
|
||||||
let length = this.state.length;
|
|
||||||
|
|
||||||
if (withShift && withCtrl) {
|
|
||||||
length = Math.max(length - 1, 0);
|
|
||||||
} else if (withShift) {
|
|
||||||
length = Math.max(Math.min(length + 1, 16), 2);
|
|
||||||
} else if (length > 0) {
|
|
||||||
length = 0;
|
|
||||||
} else {
|
|
||||||
length = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length > 0 && length !== this.state.length) {
|
|
||||||
song.scheduleNote(this.props.channel, this.props.pattern, chord, letter, index, length, this);
|
|
||||||
} else if (length === 0) {
|
|
||||||
song.unscheduleNote(this.props.channel, this.props.pattern, chord, letter, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ length: length });
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const classes = [`note-${this.state.letter}`];
|
|
||||||
if (this.state.length > 0) {
|
|
||||||
classes.push("active");
|
|
||||||
}
|
|
||||||
if (this.state.length > 1) {
|
|
||||||
classes.push("noteLong");
|
|
||||||
}
|
|
||||||
if (this.state.playing) {
|
|
||||||
if (this.state.length > 1) {
|
|
||||||
classes.push("playingLong");
|
|
||||||
} else {
|
|
||||||
classes.push("playing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <td
|
|
||||||
id={`${this.state.letter}-${this.state.chord}-${this.state.index}`}
|
|
||||||
className={classes.join(" ")}
|
|
||||||
onClick={event => {
|
|
||||||
this.onClick(this.state.chord, this.state.letter, this.state.index, event.shiftKey, event.ctrlKey);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.state.length === 0 ? "" : this.state.length}
|
|
||||||
</td>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@view
|
|
||||||
class PatternWorkspace extends React.Component<{ channel: number, pattern: number }> {
|
|
||||||
render(): React.ReactNode {
|
|
||||||
let rows = [];
|
|
||||||
for (const chord of chords.slice().reverse()) {
|
|
||||||
let first = true;
|
|
||||||
for (const letter of letters.slice().reverse()) {
|
|
||||||
let items = [];
|
|
||||||
if (first) {
|
|
||||||
first = false;
|
|
||||||
items.push(<th key={`chordHeader-${chord}`} rowSpan={letters.length}>{chord}</th>);
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push(<th key={`letterHeader-${chord}-${letter}`}>{letter}</th>);
|
|
||||||
|
|
||||||
for (const i of Array(16).keys()) {
|
|
||||||
items.push(
|
|
||||||
<NoteCell
|
|
||||||
key={`cell-${chord}-${letter}-${i}`}
|
|
||||||
channel={this.props.channel}
|
|
||||||
pattern={this.props.pattern}
|
|
||||||
chord={chord}
|
|
||||||
letter={letter}
|
|
||||||
index={i}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
rows.push(<tr key={`row-${chord}-${letter}`}>{items}</tr>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div id="patternWorkspace" className="no-select">
|
|
||||||
<table id="pattern"><tbody>{rows}</tbody></table>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@view
|
|
||||||
class App extends React.Component {
|
|
||||||
state = {
|
|
||||||
activeChannel: 0,
|
|
||||||
activePattern: 0,
|
|
||||||
};
|
|
||||||
samplers: { [key: number]: tone.Sampler } = {};
|
|
||||||
render(): React.ReactNode {
|
|
||||||
return <div id="app">
|
|
||||||
<MetaWorkspace activeChannel={this.state.activeChannel} activePattern={this.state.activePattern} />
|
|
||||||
<PatternWorkspace channel={this.state.activeChannel} pattern={this.state.activePattern} />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Note {
|
|
||||||
length: number;
|
|
||||||
scheduledEvent: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let patterns: { [key: number]: { [key: string]: Array<Note> } } = {};
|
|
||||||
for (const chord of chords) {
|
|
||||||
patterns[chord] = {};
|
|
||||||
for (const letter of letters) {
|
|
||||||
patterns[chord][letter] = Array.from({ length: 16 }, () => { return { length: 0, scheduledEvent: null }; });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleAudio() {
|
|
||||||
if (playing) {
|
|
||||||
tone.Transport.stop();
|
|
||||||
tone.Transport.position = "0";
|
|
||||||
} else {
|
|
||||||
resumeAudioContext();
|
|
||||||
tone.Transport.loopEnd = "1m";
|
|
||||||
tone.Transport.loop = true;
|
|
||||||
}
|
|
||||||
tone.Transport.toggle(0);
|
|
||||||
playing = !playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resumeAudioContext() {
|
|
||||||
let ac = (tone as any).context;
|
|
||||||
if (ac.state !== "running") {
|
|
||||||
ac.resume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onLoad() {
|
|
||||||
ReactDom.render(<App />, document.querySelector("#root"));
|
|
||||||
|
|
||||||
document.onkeypress = event => {
|
|
||||||
if (event.keyCode === 32) {
|
|
||||||
song.toggleAudio();
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const centralRow = document.getElementById("F-5-0");
|
|
||||||
if (centralRow !== null) {
|
|
||||||
centralRow.scrollIntoView({ "behavior": "smooth", "block": "center" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = onLoad;
|
|
@ -1,21 +0,0 @@
|
|||||||
import ghPages from "gh-pages";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const root = `${path.dirname(process.argv[1])}/..`;
|
|
||||||
|
|
||||||
ghPages.publish(
|
|
||||||
root,
|
|
||||||
{
|
|
||||||
"src": [
|
|
||||||
"index.html",
|
|
||||||
"public/*.css",
|
|
||||||
"public/*.js",
|
|
||||||
"audio/**/*.ogg",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
err => {
|
|
||||||
if (err !== undefined) {
|
|
||||||
console.log(`Failure: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,59 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import process from "process";
|
|
||||||
import child_process from "child_process";
|
|
||||||
import yaml from "js-yaml";
|
|
||||||
import { InstrumentData, notes } from "../src/index";
|
|
||||||
|
|
||||||
if (process.argv.length < 3) {
|
|
||||||
console.error("Must specify location of Bosca Ceoil clone with CLI export functionality.");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = `${path.dirname(process.argv[1])}/..`;
|
|
||||||
const tmp = `${root}/tmp`;
|
|
||||||
const boscaCeoilDir = process.argv[2];
|
|
||||||
const instrumentData: InstrumentData = yaml.safeLoad(fs.readFileSync(`${root}/instruments.yaml`).toString());
|
|
||||||
const song = fs.readFileSync(`${root}/audio/main.ceol`).toString().split(",");
|
|
||||||
|
|
||||||
if (!fs.existsSync(tmp)) {
|
|
||||||
fs.mkdirSync(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [instrument, { index }] of Object.entries(instrumentData)) {
|
|
||||||
const instrumentDir = `${root}/audio/${instrument}`;
|
|
||||||
|
|
||||||
if (!fs.existsSync(instrumentDir)) {
|
|
||||||
fs.mkdirSync(instrumentDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const note of notes) {
|
|
||||||
if (fs.existsSync(`${instrumentDir}/${note.toLowerCase()}.ogg`)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(`${tmp}/${instrument}-${note.toLowerCase()}.wav`)) {
|
|
||||||
song[8] = index.toString();
|
|
||||||
song[20] = (0 + 12 * notes.indexOf(note)).toString();
|
|
||||||
fs.writeFileSync(`${tmp}/${instrument}.ceol`, song.join(","));
|
|
||||||
|
|
||||||
console.log(`${instrument}: ${note}`);
|
|
||||||
|
|
||||||
child_process.execSync(
|
|
||||||
`adl application.xml -- ${tmp}/main.ceol --export ${tmp}/${instrument}-${note.toLowerCase()}.wav`,
|
|
||||||
{ cwd: boscaCeoilDir },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
child_process.execSync(
|
|
||||||
`sox ${tmp}/${instrument}-${note.toLowerCase()}.wav -C 5 ${instrumentDir}/${note.toLowerCase()}.ogg silence 0 1 0.1 0.1%`,
|
|
||||||
{ cwd: root },
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.unlinkSync(`${tmp}/${instrument}-${note.toLowerCase()}.wav`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(`${tmp}/main.ceol`)) {
|
|
||||||
fs.unlinkSync(`${tmp}/main.ceol`);
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es6",
|
|
||||||
"outDir": "out",
|
|
||||||
"lib": [
|
|
||||||
"es6",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
|
||||||
"rootDir": "src",
|
|
||||||
"strict": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"jsx": "react",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"tasks",
|
|
||||||
"tmp"
|
|
||||||
],
|
|
||||||
}
|
|
15
tslint.json
15
tslint.json
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"rules": {
|
|
||||||
"no-string-throw": true,
|
|
||||||
"no-unused-expression": true,
|
|
||||||
"no-duplicate-variable": true,
|
|
||||||
"curly": true,
|
|
||||||
"class-name": true,
|
|
||||||
"semicolon": [
|
|
||||||
true,
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"triple-equals": true
|
|
||||||
},
|
|
||||||
"defaultSeverity": "warning"
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
path = require("path");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: {
|
|
||||||
index: './src/player.tsx'
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.tsx?$/,
|
|
||||||
use: 'ts-loader',
|
|
||||||
exclude: /node_modules/
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
use: ["style-loader", "css-loader"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.yaml$/,
|
|
||||||
use: 'js-yaml-loader',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.tsx', '.ts', '.js']
|
|
||||||
},
|
|
||||||
devtool: 'inline-source-map',
|
|
||||||
output: {
|
|
||||||
filename: '[name].js',
|
|
||||||
path: path.resolve(__dirname, 'public')
|
|
||||||
}
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user