├── .gitignore
├── LAYOUT.md
├── LAYOUT.svg
├── LICENSE.md
├── PREVIEW.jpg
├── README.md
└── desktop
├── icon.icns
├── icon.ico
├── main.js
├── package.json
└── sources
├── index.html
├── links
├── fonts.css
├── main.css
└── reset.css
├── media
└── fonts
│ ├── input_mono_medium.ttf
│ └── input_mono_regular.ttf
└── scripts
├── arp.js
├── cheatcode.js
├── core
├── jammer.js
├── oscillator.js
├── player-worker.js
├── player.js
└── signal.js
├── editor.js
├── follower.js
├── instrument.js
├── lib
├── controller.js
├── history.js
└── theme.js
├── loop.js
├── marabu.js
├── sequencer.js
├── song.js
├── track.js
└── ui
├── choice.js
├── slider.js
└── uv.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | builds/
3 | *.wav
4 | .vscode/
--------------------------------------------------------------------------------
/LAYOUT.md:
--------------------------------------------------------------------------------
1 | ## default Mode
2 |
3 | ### File
4 | - New: `CmdOrCtrl+N`
5 | - Open: `CmdOrCtrl+O`
6 | - Save: `CmdOrCtrl+S`
7 | - Save As: `CmdOrCtrl+Shift+S`
8 | - Render: `CmdOrCtrl+R`
9 | - Export Ins: `CmdOrCtrl+I`
10 |
11 | ### Edit
12 | - Inc BPM: `>`
13 | - Dec BPM: `<`
14 | - Delete: `Backspace`
15 | - Undo: `CmdOrCtrl+Z`
16 | - Redo: `CmdOrCtrl+Shift+Z`
17 |
18 | ### Select
19 | - 1st Row: `1`
20 | - 4th Row: `2`
21 | - 8th Row: `3`
22 | - 12th Row: `4`
23 | - 16th Row: `5`
24 | - 20th Row: `6`
25 | - 24th Row: `7`
26 | - 28th Row: `8`
27 |
28 | ### Track
29 | - Next Inst: `Right`
30 | - Prev Inst: `Left`
31 | - Next Row: `Down`
32 | - Prev Row: `Up`
33 | - Next Track: `CmdOrCtrl+Down`
34 | - Prev Track: `CmdOrCtrl+Up`
35 | - Next Pattern: `CmdOrCtrl+Right`
36 | - Prev Pattern: `CmdOrCtrl+Left`
37 |
38 | ### Play
39 | - Track: `Space`
40 | - Range: `Enter`
41 | - Stop: `Esc`
42 |
43 | ### Mode
44 | - Cheatcode: `CmdOrCtrl+K`
45 | - Loop: `CmdOrCtrl+L`
46 | - Arp: `CmdOrCtrl+M`
47 | - Composer: `M`
48 |
49 | ### Keyboard
50 | - Inc Octave: `X`
51 | - Dec Octave: `Z`
52 | - C: `A`
53 | - C#: `W`
54 | - D: `S`
55 | - D#: `E`
56 | - E: `D`
57 | - F: `F`
58 | - F#: `T`
59 | - G: `G`
60 | - G#: `Y`
61 | - A: `H`
62 | - A#: `U`
63 | - B: `J`
64 | - (Right)C: `Shift+A`
65 | - (Right)C#: `Shift+W`
66 | - (Right)D: `Shift+S`
67 | - (Right)D#: `Shift+E`
68 | - (Right)E: `Shift+D`
69 | - (Right)F: `Shift+F`
70 | - (Right)F#: `Shift+T`
71 | - (Right)G: `Shift+G`
72 | - (Right)G#: `Shift+Y`
73 | - (Right)A: `Shift+H`
74 | - (Right)A#: `Shift+U`
75 | - (Right)B: `Shift+J`
76 |
77 | ### Instrument
78 | - Next Control: `Shift+Up`
79 | - Prev Control: `Shift+Down`
80 | - Inc Control +10: `Shift+Right`
81 | - Dec Control -10: `Shift+Left`
82 | - Inc Control 1: `}`
83 | - Dec Control -1: `{`
84 | - Inc Control 10(alt): `]`
85 | - Dec Control -10(alt): `[`
86 | - Min: `9`
87 | - Max: `0`
88 | - Keyframe: `/`
89 |
90 |
91 | ## cheatcode Mode
92 |
93 | ### Mode
94 | - Stop: `Esc`
95 | - Copy: `C`
96 | - Paste: `V`
97 | - Erase: `Backspace`
98 |
99 | ### Effect
100 | - Inc Note +12: `]`
101 | - Dec Note -12: `[`
102 | - Inc Note +1: `}`
103 | - Dec Note -1: `{`
104 |
105 | ### Selection
106 | - All: `1`
107 | - 2nd: `2`
108 | - 3rd: `3`
109 | - 4th: `4`
110 | - 5th: `5`
111 | - 6th: `6`
112 | - 7th: `7`
113 | - 8th: `8`
114 | - Offset +1: `Right`
115 | - Offset -1: `Left`
116 | - Length +1: `Down`
117 | - Length -1: `Up`
118 |
119 | ### Keyboard
120 | - C: `A`
121 | - C#: `W`
122 | - D: `S`
123 | - D#: `E`
124 | - E: `D`
125 | - F: `F`
126 | - F#: `T`
127 | - G: `G`
128 | - G#: `Y`
129 | - A: `H`
130 | - A#: `U`
131 | - B: `J`
132 |
133 |
134 | ## loop Mode
135 |
136 | ### Edit
137 | - Clear: `X`
138 | - Copy: `C`
139 | - Paste: `V`
140 | - Delete: `Backspace`
141 |
142 | ### Mode
143 | - Play: `Enter`
144 | - Stop: `Esc`
145 | - render: `CmdOrCtrl+R`
146 |
147 | ### Selection
148 | - Solo: `/`
149 | - 1 Row: `1`
150 | - 2 Rows: `2`
151 | - 3 Rows: `3`
152 | - 4 Rows: `4`
153 | - 5 Rows: `5`
154 | - 6 Rows: `6`
155 | - 7 Rows: `7`
156 | - 8 Rows: `8`
157 |
158 |
159 | ## arp Mode
160 |
161 | ### Mode
162 | - Pause/Stop: `Esc`
163 |
164 | ### Keyboard
165 | - C: `A`
166 | - C#: `W`
167 | - D: `S`
168 | - D#: `E`
169 | - E: `D`
170 | - F: `F`
171 | - F#: `T`
172 | - G: `G`
173 | - G#: `Y`
174 | - A: `H`
175 | - A#: `U`
176 | - B: `J`
177 | - Skip: `Space`
178 |
179 |
180 |
--------------------------------------------------------------------------------
/LAYOUT.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Hundredrabbits
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PREVIEW.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hundredrabbits/Marabu/4e358119fe3792eec37aa478f59728404a1440b4/PREVIEW.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Marabu
2 |
3 | Marabu is a free and open-source music tracker built from [Soundbox](https://github.com/mbitsnbites/soundbox).
4 |
5 |
6 |
7 | ## Guide
8 |
9 | If this is your first time using a tracker, don't worry this quick introduction will cover the basics of writing a little track, and exporting it to an audio file.
10 |
11 | The interface is divided into 3 columns, the *sequencer*, the *pattern editor* and the *instrument*. By default, the application launches with an active pattern, in the first instrument. There is a maximum of 16 instruments that can play at the same time.
12 |
13 | To move the **pattern cursor**, use the `arrowUp`, and `arrowDown`, keys. Pressing the keyboard keys a,s,d,f,g,h & j will record a note in the first row of the selected row. Pressing the `ArrowDown` and `ArrowUp` keys, will move the *cursor* up/down in the sequencer. Allowing you to fill `pattern #1` with notes. Pressing `space` will play the pattern, pressing `esc` will stop and pressing `del`/`backspace` will erase a note. By default, a new Marabu track has the pattern #1 loaded into the first instrument.
14 |
15 | To change the **sequencer patterns**, use the arrow keys while `holding alt`. To add notes to a second instrument, move to the second column and press `alt ArrowRight`, this will set the first row of the second instrument to 1, and allow you to record notes. Press `alt ArrowDown` to move to the second row, and press `alt ArrowRight` again twice, to extend the track to 2 rows, and begin adding notes to the second row of the second instrument.
16 |
17 | To change the **instrument controls**, use the arrow keys while `holding shift`. To save your song, press `ctrl s`, to render an audio file(.wav) press `ctrl r`.
18 |
19 | ## Controls
20 |
21 | ## default Mode
22 |
23 | ### File
24 | - New: `CmdOrCtrl+N`
25 | - Open: `CmdOrCtrl+O`
26 | - Save: `CmdOrCtrl+S`
27 | - Save As: `CmdOrCtrl+Shift+S`
28 | - Render: `CmdOrCtrl+R`
29 | - Export Ins: `CmdOrCtrl+I`
30 |
31 | ### Edit
32 | - Inc BPM: `>`
33 | - Dec BPM: `<`
34 | - Delete: `Backspace`
35 | - Undo: `CmdOrCtrl+Z`
36 | - Redo: `CmdOrCtrl+Shift+Z`
37 |
38 | ### Select
39 | - 1st Row: `1`
40 | - 4th Row: `2`
41 | - 8th Row: `3`
42 | - 12th Row: `4`
43 | - 16th Row: `5`
44 | - 20th Row: `6`
45 | - 24th Row: `7`
46 | - 28th Row: `8`
47 |
48 | ### Track
49 | - Next Inst: `Right`
50 | - Prev Inst: `Left`
51 | - Next Row: `Down`
52 | - Prev Row: `Up`
53 | - Next Track: `CmdOrCtrl+Down`
54 | - Prev Track: `CmdOrCtrl+Up`
55 | - Next Pattern: `CmdOrCtrl+Right`
56 | - Prev Pattern: `CmdOrCtrl+Left`
57 |
58 | ### Play
59 | - Track: `Space`
60 | - Range: `Enter`
61 | - Stop: `Esc`
62 |
63 | ### Mode
64 | - Cheatcode: `CmdOrCtrl+K`
65 | - Loop: `CmdOrCtrl+L`
66 | - Arp: `CmdOrCtrl+M`
67 | - Composer: `M`
68 |
69 | ### Keyboard
70 | - Inc Octave: `X`
71 | - Dec Octave: `Z`
72 | - C: `A`
73 | - C#: `W`
74 | - D: `S`
75 | - D#: `E`
76 | - E: `D`
77 | - F: `F`
78 | - F#: `T`
79 | - G: `G`
80 | - G#: `Y`
81 | - A: `H`
82 | - A#: `U`
83 | - B: `J`
84 | - (Right)C: `Shift+A`
85 | - (Right)C#: `Shift+W`
86 | - (Right)D: `Shift+S`
87 | - (Right)D#: `Shift+E`
88 | - (Right)E: `Shift+D`
89 | - (Right)F: `Shift+F`
90 | - (Right)F#: `Shift+T`
91 | - (Right)G: `Shift+G`
92 | - (Right)G#: `Shift+Y`
93 | - (Right)A: `Shift+H`
94 | - (Right)A#: `Shift+U`
95 | - (Right)B: `Shift+J`
96 |
97 | ### Instrument
98 | - Next Control: `Shift+Up`
99 | - Prev Control: `Shift+Down`
100 | - Inc Control +10: `Shift+Right`
101 | - Dec Control -10: `Shift+Left`
102 | - Inc Control 1: `}`
103 | - Dec Control -1: `{`
104 | - Inc Control 10(alt): `]`
105 | - Dec Control -10(alt): `[`
106 | - Min: `9`
107 | - Max: `0`
108 | - Keyframe: `/`
109 |
110 | ## cheatcode Mode
111 |
112 | ### Mode
113 | - Stop: `Esc`
114 | - Copy: `C`
115 | - Paste: `V`
116 | - Erase: `Backspace`
117 |
118 | ### Effect
119 | - Inc Note +12: `]`
120 | - Dec Note -12: `[`
121 | - Inc Note +1: `}`
122 | - Dec Note -1: `{`
123 |
124 | ### Selection
125 | - All: `1`
126 | - 2nd: `2`
127 | - 3rd: `3`
128 | - 4th: `4`
129 | - 5th: `5`
130 | - 6th: `6`
131 | - 7th: `7`
132 | - 8th: `8`
133 | - Offset +1: `Right`
134 | - Offset -1: `Left`
135 | - Length +1: `Down`
136 | - Length -1: `Up`
137 |
138 | ### Keyboard
139 | - C: `A`
140 | - C#: `W`
141 | - D: `S`
142 | - D#: `E`
143 | - E: `D`
144 | - F: `F`
145 | - F#: `T`
146 | - G: `G`
147 | - G#: `Y`
148 | - A: `H`
149 | - A#: `U`
150 | - B: `J`
151 |
152 | ## loop Mode
153 |
154 | ### Edit
155 | - Clear: `X`
156 | - Copy: `C`
157 | - Paste: `V`
158 | - Delete: `Backspace`
159 |
160 | ### Mode
161 | - Play: `Enter`
162 | - Stop: `Esc`
163 | - render: `CmdOrCtrl+R`
164 |
165 | ### Selection
166 | - Solo: `/`
167 | - 1 Row: `1`
168 | - 2 Rows: `2`
169 | - 3 Rows: `3`
170 | - 4 Rows: `4`
171 | - 5 Rows: `5`
172 | - 6 Rows: `6`
173 | - 7 Rows: `7`
174 | - 8 Rows: `8`
175 |
176 | ## arp Mode
177 |
178 | ### Mode
179 | - Pause/Stop: `Esc`
180 |
181 | ### Keyboard
182 | - C: `A`
183 | - C#: `W`
184 | - D: `S`
185 | - D#: `E`
186 | - E: `D`
187 | - F: `F`
188 | - F#: `T`
189 | - G: `G`
190 | - G#: `Y`
191 | - A: `H`
192 | - A#: `U`
193 | - B: `J`
194 | - Skip: `Space`
195 |
196 |
197 |
198 | ## Extras
199 |
200 | - Download additional [themes](https://github.com/hundredrabbits/Themes).
201 | - Support this project through [Patreon](https://patreon.com/hundredrabbits).
202 | - See the [License](LICENSE.md) file for license rights and limitations (MIT).
203 |
--------------------------------------------------------------------------------
/desktop/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hundredrabbits/Marabu/4e358119fe3792eec37aa478f59728404a1440b4/desktop/icon.icns
--------------------------------------------------------------------------------
/desktop/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hundredrabbits/Marabu/4e358119fe3792eec37aa478f59728404a1440b4/desktop/icon.ico
--------------------------------------------------------------------------------
/desktop/main.js:
--------------------------------------------------------------------------------
1 | const {app, BrowserWindow, webFrame, Menu} = require('electron')
2 | const path = require('path')
3 | const url = require('url')
4 | const shell = require('electron').shell;
5 |
6 | let is_shown = true;
7 |
8 | app.inspect = function()
9 | {
10 | app.win.toggleDevTools();
11 | }
12 |
13 | app.toggle_fullscreen = function()
14 | {
15 | app.win.setFullScreen(app.win.isFullScreen() ? false : true);
16 | }
17 |
18 | app.toggle_visible = function()
19 | {
20 | if(is_shown){ app.win.hide(); } else{ app.win.show(); }
21 | }
22 |
23 | app.inject_menu = function(m)
24 | {
25 | Menu.setApplicationMenu(Menu.buildFromTemplate(m));
26 | }
27 |
28 | app.win = null;
29 |
30 | app.on('ready', () =>
31 | {
32 | app.win = new BrowserWindow({width: 890, height: 540, backgroundColor:"#000", minWidth: 595, minHeight: 540, frame:false, autoHideMenuBar: true, icon: __dirname + '/icon.ico'})
33 |
34 | app.win.loadURL(`file://${__dirname}/sources/index.html`)
35 |
36 | app.win.on('closed', () => {
37 | win = null
38 | app.quit()
39 | })
40 |
41 | app.win.on('hide',function() {
42 | is_shown = false;
43 | })
44 |
45 | app.win.on('show',function() {
46 | is_shown = true;
47 | })
48 | })
49 |
50 | app.on('window-all-closed', () =>
51 | {
52 | app.quit()
53 | })
54 |
55 | app.on('activate', () => {
56 | if (app.win === null) {
57 | createWindow()
58 | }
59 | })
--------------------------------------------------------------------------------
/desktop/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Marabu",
3 | "version": "0.1.0",
4 | "main": "main.js",
5 | "scripts": {
6 | "start": "electron .",
7 | "clean" : "rm -r ~/Desktop/Marabu-darwin-x64/ ; rm -r ~/Desktop/Marabu-linux-x64/ ; rm -r ~/Desktop/Marabu-win32-x64/ ; echo 'cleaned build location'",
8 | "build_osx" : "electron-packager . Marabu --platform=darwin --arch=x64 --out ~/Desktop/ --overwrite --icon=icon.icns ; echo 'Built for OSX'",
9 | "build_linux" : "electron-packager . Marabu --platform=linux --arch=x64 --out ~/Desktop/ --overwrite --icon=icon.ico ; echo 'Built for LINUX'",
10 | "build_win" : "electron-packager . Marabu --platform=win32 --arch=x64 --out ~/Desktop/ --overwrite --icon=icon.ico ; echo 'Built for WIN'",
11 | "build" : "npm run clean ; npm run build_osx ; npm run build_linux ; npm run build_win",
12 | "push_osx" : "~/butler push ~/Desktop/Marabu-darwin-x64/ hundredrabbits/marabu:osx-64",
13 | "push_linux" : "~/butler push ~/Desktop/Marabu-linux-x64/ hundredrabbits/marabu:linux-64",
14 | "push_win" : "~/butler push ~/Desktop/Marabu-win32-x64/ hundredrabbits/marabu:windows-64",
15 | "push_theme" : "~/butler push ~/Github/HundredRabbits/Themes/themes/ hundredrabbits/marabu:themes",
16 | "push_status" : "~/butler status hundredrabbits/marabu",
17 | "push" : "npm run build ; npm run push_theme ; npm run push_osx ; npm run push_linux ; npm run push_win ; npm run clean ; npm run push_status"
18 | },
19 | "devDependencies": {
20 | "electron": "^1.8.1"
21 | },
22 | "dependencies": {
23 | "electron-packager": "^8.4.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/desktop/sources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Marabu
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/desktop/sources/links/fonts.css:
--------------------------------------------------------------------------------
1 | /* Input */
2 |
3 | @font-face {
4 | font-family: 'input_mono_regular';
5 | src: url('../media/fonts/input_mono_regular.ttf') format('truetype');
6 | font-weight: normal;
7 | font-style: normal;
8 | }
9 |
10 | @font-face {
11 | font-family: 'input_mono_medium';
12 | src: url('../media/fonts/input_mono_medium.ttf') format('truetype');
13 | font-weight: normal;
14 | font-style: normal;
15 | }
--------------------------------------------------------------------------------
/desktop/sources/links/main.css:
--------------------------------------------------------------------------------
1 | body { background:#000; -webkit-user-select: none;-webkit-app-region: drag; overflow: hidden; transition: background-color 500ms }
2 | yu { display: block; font-family: 'input_mono_regular'; font-size:11px; line-height: 30px; vertical-align: top; -webkit-app-region: no-drag; }
3 | yu:after { display: table; content: ""; clear: both; }
4 | ln { display: block;}
5 | ln i { color:#999; }
6 | hl { line-height: 29px; font-family: 'input_mono_medium'; display: block; border-bottom:1px solid black; }
7 | t { vertical-align: top; display: inline-block; }
8 | svg { display: inline-block; vertical-align: top }
9 | textarea { background:none; padding:px 0px; line-height: 15px; overflow: hidden}
10 | b { font-weight: normal; font-family: 'input_mono_medium'}
11 |
12 | app { display: block; width:30px; height:30px; padding:30px; color:black; overflow: hidden; z-index:20; margin: calc(50vh - 270px) auto; transition: background-color 150ms, opacity 350ms; max-width: 830px; height: 480px; position: relative; opacity:0; width:calc(100% - 60px); }
13 |
14 | .tracks tr td { padding: 0 2.5px; color:#555}
15 | #instrument { width:130px; display:block; vertical-align:top; margin-left:15px; line-height:15px; overflow: hidden; position: absolute; right:25px;}
16 | #instrument > div > * { width:100%; cursor:pointer; }
17 | #instrument .family { margin-bottom:15px; }
18 | #sequencer { display:block; vertical-align:top; float:left; max-height: 465px; overflow:hidden; position: relative;}
19 | #sequencer tr td { padding:0px;}
20 | #sequencer tr td:first-child { padding-left:2.5px;}
21 | #sequencer tr td:last-child { padding-right:2.5px;}
22 | #scrollbar { background: #333;width: 1px;height: 0px;position: absolute;left: 161px;top: 30px; display: none }
23 | #editor { display:block; vertical-align:top; margin-left:15px; float:left; max-width: 571px; overflow: hidden; height:480px; max-width:calc(100% - 270px);}
24 | #editor #composer-table { width:550px; }
25 | #editor #pattern-table { width:550px; }
26 | .tracks * { line-height: 15px }
27 | .tracks tr:hover { color:#999}
28 | .tracks tr td { position:relative}
29 | .tracks td:hover { cursor:pointer}
30 | .tracks tr td:hover { cursor:pointer; }
31 | .tracks tr th { color:#555; font-family: 'input_mono_regular'; padding: 0 2.5px;}
32 | .tracks tr th:hover { cursor:pointer; color:#999}
33 |
34 | #editor.composer #composer-table { display: table; }
35 | #composer-table { width:100%; margin-top: -240px; transition: all 150ms; margin-bottom: 15px }
36 | #composer-table tr td, #composer-table tr th { line-height: 15px; padding:0px; text-align: center }
37 | #editor.composer #composer-table { margin-top:0px; }
38 | #position { position: absolute;bottom: 30px;line-height: 15px;padding: 0px 2.5px;width: 110px;}
39 |
40 | .right { float:right; }
41 | .left { float:left; }
42 | .hide { display: none }
43 | .pointer { cursor:pointer; }
44 |
45 | .control { height: 15px; overflow: hidden; }
46 | .control t.name { width:25px; display:inline-block; padding:0px 2.5px;text-align: center }
47 | .control t.value { display:inline-block; padding:0px 2.5px; }
48 | .control.slider { cursor: pointer; display: block; position:relative; }
49 | .control.slider.selected { position: relative; }
50 | .control.slider .slide { width:60px; height:15px; display: inline-block; position:relative; padding:0px 2.5px;}
51 | .control.uv { height:60px; }
52 |
53 | /* Theme Defaults */
54 |
55 | :root { --background: "#222"; --f_high: "#fff";--f_med: "#777";--f_low: "#444";--f_inv: "#000";--b_high: "#000";--b_med: "#affec7";--b_low: "#000";--b_inv: "#affec7"; }
56 |
57 | body { background:var(--background) !important }
58 | .fh { color:var(--f_high) !important; stroke:var(--f_high) !important }
59 | .fm { color:var(--f_med) !important ; stroke:var(--f_med) !important }
60 | .fl { color:var(--f_low) !important ; stroke:var(--f_low) !important }
61 | .f_inv { color:var(--f_inv) !important ; stroke:var(--f_inv) !important }
62 | .f_inv { color:var(--f_inv) !important ; stroke:var(--f_inv) !important }
63 | .bh { background:var(--b_high) !important; fill:var(--b_high) !important }
64 | .bm { background:var(--b_med) !important ; fill:var(--b_med) !important }
65 | .bl { background:var(--b_low) !important ; fill:var(--b_low) !important }
66 | .b_inv { background:var(--b_inv) !important ; fill:var(--b_inv) !important }
67 | .b { background:var(--background) !important }
68 |
69 | #editor table tr.selected { background:var(--b_low) !important ; }
70 | #editor table tr.selected td { color:var(--f_med) !important; }
71 | #editor table tr.selected td.selected { color:var(--f_high) !important ; }
72 | #editor table td.beat { color:var(--f_med) !important; }
73 |
74 | .control .name { color:var(--f_med) !important; }
75 | .control .value { color:var(--f_med) !important ; }
76 | .control.slider.min .value { color:var(--f_low) !important ; }
77 | .control.slider.max .value { color:var(--f_high) !important ; }
78 | .control.slider.keyframed .name { background: var(--b_inv) !important; color: var(--f_inv) !important; }
79 | .control.slider .slide { color:var(--f_low) !important; }
80 | .control.slider .slide .fg { color:var(--f_high) !important }
81 | .control.slider.selected { background:var(--b_low) !important; }
82 |
--------------------------------------------------------------------------------
/desktop/sources/links/reset.css:
--------------------------------------------------------------------------------
1 | /* Reset */
2 |
3 | * { margin:0;padding:0;border:0;outline:0;text-decoration:none;font-weight:inherit;font-style:inherit;color:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;list-style:none;border-collapse:separate;border-spacing:0; -webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}
4 | *:focus { outline: none}
5 | ::selection { background: #eee; opacity:1.0; color:#000; padding:10px; /* Safari */ }
--------------------------------------------------------------------------------
/desktop/sources/media/fonts/input_mono_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hundredrabbits/Marabu/4e358119fe3792eec37aa478f59728404a1440b4/desktop/sources/media/fonts/input_mono_medium.ttf
--------------------------------------------------------------------------------
/desktop/sources/media/fonts/input_mono_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hundredrabbits/Marabu/4e358119fe3792eec37aa478f59728404a1440b4/desktop/sources/media/fonts/input_mono_regular.ttf
--------------------------------------------------------------------------------
/desktop/sources/scripts/arp.js:
--------------------------------------------------------------------------------
1 | function Arp()
2 | {
3 | this.is_active = false;
4 | this.is_recording = false;
5 | this.memory = [];
6 |
7 | this.start = function()
8 | {
9 | console.log("arp","start")
10 | this.is_active = true;
11 | this.is_recording = true;
12 | this.memory = [];
13 | marabu.controller.set("arp");
14 | marabu.update();
15 | }
16 |
17 | this.stop = function()
18 | {
19 | if(this.is_recording){ this.is_recording = false; marabu.update(); return; }
20 |
21 | this.is_active = false;
22 | marabu.controller.set("default");
23 | marabu.update();
24 | }
25 |
26 | this.ins = function(mod)
27 | {
28 | if(mod == -99){
29 | this.record(null);
30 | return
31 | }
32 |
33 | var note = (marabu.selection.octave * 12)+mod;
34 |
35 | if(this.is_recording && mod != null){
36 | this.record(note);
37 | return;
38 | }
39 | else{
40 | this.play(note);
41 | }
42 | }
43 |
44 | this.play = function(note)
45 | {
46 | var key = this.memory[0];
47 | for(id in this.memory){
48 | if(marabu.selection.row+parseInt(id) >= 32){ break; }
49 | var n = this.memory[id];
50 | var offset = n.distance - key.distance;
51 | var row = marabu.selection.row+parseInt(id);
52 | marabu.song.inject_note_at(marabu.selection.instrument,marabu.selection.track,row,note ? this.offset_note(note,offset) : 0);
53 | }
54 | marabu.update();
55 | }
56 |
57 | this.record = function(note)
58 | {
59 | this.memory.push(note ? parse_note(note) : 0);
60 | console.log("record",this.memory)
61 | marabu.update();
62 | }
63 |
64 | this.offset_note = function(note,offset)
65 | {
66 | var dict = this.notes_dict()[parse_note(note).sharp ? "sharp" : "normal"];
67 | var index = dict.indexOf(note);
68 |
69 | return dict[index+offset];
70 | }
71 |
72 | this.notes_dict = function()
73 | {
74 | var h = {normal:[],sharp:[]}
75 | var n = 12 * 3;
76 | while(n < 87){
77 | var note = parse_note(n);
78 | h[note.sharp ? "sharp" : "normal"].push(n)
79 | n += 1
80 | }
81 | return h;
82 | }
83 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/cheatcode.js:
--------------------------------------------------------------------------------
1 | function Cheatcode()
2 | {
3 | this.is_active = false;
4 | this.val = "";
5 |
6 | this.rate = 1;
7 | this.offset = 0;
8 | this.length = 0;
9 |
10 | this.selection = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
11 |
12 | this.start = function()
13 | {
14 | console.log("cheatcode","start")
15 | marabu.loop.stop();
16 |
17 | var active_pattern = marabu.song.pattern_at(marabu.selection.instrument,marabu.selection.track);
18 |
19 | if(active_pattern == 0){ return; }
20 |
21 | this.is_active = true;
22 | this.select();
23 | marabu.selection.row = 0;
24 | marabu.update();
25 | marabu.controller.set("cheatcode");
26 | }
27 |
28 | this.stop = function()
29 | {
30 | console.log("cheatcode","stop")
31 | this.is_active = false;
32 | this.rate = 1;
33 | this.offset = 0;
34 | this.length = 0;
35 |
36 | this.val = "";
37 | marabu.update();
38 | marabu.controller.set("default");
39 | }
40 |
41 | this.set_rate = function(mod)
42 | {
43 | this.rate = mod;
44 | this.select();
45 | marabu.update();
46 | }
47 |
48 | this.set_offset = function(mod)
49 | {
50 | this.offset += mod;
51 | this.select();
52 | marabu.update();
53 | }
54 |
55 | this.set_length = function(mod)
56 | {
57 | this.length = this.length == 0 ? 1 : this.length;
58 | this.length += mod;
59 | this.select();
60 | marabu.update();
61 | }
62 |
63 | this.selection_count = function()
64 | {
65 | var count = 0;
66 | for(var row = 0; row < 32; row++){
67 | count += this.selection[row];
68 | }
69 | return count;
70 | }
71 |
72 | this.select = function()
73 | {
74 | this.selection = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
75 |
76 | this.offset = this.offset == 15 ? 16 : this.offset;
77 | this.length = this.length == 15 ? 16 : this.length;
78 | for(var row = 0; row < 32; row++){
79 | var key = (row-this.offset) % this.rate;
80 | if(key < 0){ key = this.rate + key; }
81 | if(key == 0 || key < this.length){ this.selection[row] = key+1; }
82 | }
83 | if(this.offset > 0){
84 | marabu.selection.row = (this.offset)%32;
85 | }
86 | }
87 |
88 | this.ins = function(mod)
89 | {
90 | console.log("cheatcode","ins")
91 | for(var row = 0; row < 32; row++){
92 | if(!this.selection[row]){ continue; }
93 | var note = (marabu.selection.octave * 12)+mod;
94 | marabu.song.inject_note_at(marabu.selection.instrument,marabu.selection.track,row,note);
95 | this.selection[row] = 0;
96 | marabu.selection.row = (row+1)%32;
97 | marabu.update();
98 | if(this.selection_count() == 0){ this.stop(); }
99 | return;
100 | }
101 | marabu.history.push(marabu.song.song());
102 | }
103 |
104 | this.del = function()
105 | {
106 | console.log("cheatcode","del")
107 | for(var row = 0; row < 32; row++){
108 | if(!this.selection[row]){ continue;}
109 | marabu.song.inject_note_at(marabu.selection.instrument,marabu.selection.track,row,-87);
110 | marabu.song.inject_note_at(marabu.selection.instrument,marabu.selection.track,row+32,-87);
111 | }
112 | marabu.history.push(marabu.song.song());
113 | this.stop();
114 | }
115 |
116 | this.mod = function(mod)
117 | {
118 | console.log("cheatcode","mod")
119 | for(var row = 0; row < 32; row++){
120 | if(!this.selection[row]){ continue;}
121 | var note = marabu.song.note_at(marabu.selection.instrument,marabu.selection.track,row);
122 | if(note == 0){ continue; }
123 | note += mod;
124 | marabu.selection.row = row;
125 | marabu.song.inject_note_at(marabu.selection.instrument,marabu.selection.track,row,note-87);
126 | }
127 | marabu.history.push(marabu.song.song());
128 | marabu.update();
129 | }
130 |
131 | this.stash = null;
132 |
133 | this.copy = function()
134 | {
135 | console.log("cheatcode","copy")
136 | var new_stash = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
137 |
138 | for(var row = 0; row < 32; row++){
139 | if(!this.selection[row]){ continue;}
140 | var note = marabu.song.note_at(marabu.selection.instrument,marabu.selection.track,row);
141 | new_stash[row] = note;
142 | }
143 | this.stash = new_stash;
144 | this.stop();
145 | }
146 |
147 | this.paste = function()
148 | {
149 | console.log("cheatcode","paste")
150 | for(var row = 0; row < 32; row++){
151 | var target_row = (row + marabu.selection.row) % 32;
152 | if(this.stash[row] == 0){ continue; }
153 | marabu.song.inject_note_at(marabu.selection.instrument,marabu.selection.track,target_row,this.stash[row]-87);
154 | }
155 | marabu.history.push(marabu.song.song());
156 | this.stop();
157 | }
158 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/core/jammer.js:
--------------------------------------------------------------------------------
1 | /* -*- mode: javascript; tab-width: 2; indent-tabs-mode: nil; -*-
2 | *
3 | * Copyright (c) 2011-2013 Marcus Geelnard
4 | *
5 | * This file is part of SoundBox.
6 | *
7 | * SoundBox is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * SoundBox is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * along with SoundBox. If not, see .
19 | *
20 | */
21 |
22 | "use strict";
23 |
24 | var CJammer = function ()
25 | {
26 | var signal_processor = new Signal_Processor();
27 |
28 | // Currently playing notes.
29 | var MAX_POLYPHONY = 2;
30 | var mPlayingNotes = [];
31 |
32 | // Current instrument.
33 | var mInstr;
34 |
35 | // Current row length (i.e. BPM).
36 | var mRowLen;
37 |
38 | // Effect state.
39 | var mFXState;
40 |
41 | // Delay buffers.
42 | var MAX_DELAY = 131072; // Must be a power of 2.
43 | var mDlyLeft, mDlyRight;
44 |
45 | // Web Audio context.
46 | var mAudioContext;
47 | var mScriptNode;
48 | var mSampleRate;
49 | var mRateScale;
50 |
51 |
52 | //--------------------------------------------------------------------------
53 | // Sound synthesis engine.
54 | //--------------------------------------------------------------------------
55 |
56 | var getnotefreq = function (n)
57 | {
58 | return (174.614115728 / mSampleRate) * Math.pow(2, (n-128)/12);
59 | };
60 |
61 | // Array of oscillator functions.
62 | var mOscillators =
63 | [
64 | new Oscillator().sin,
65 | new Oscillator().square,
66 | new Oscillator().saw,
67 | new Oscillator().tri,
68 | new Oscillator().noise,
69 | new Oscillator().rev,
70 | new Oscillator().pulse
71 | ];
72 |
73 | // Fill the buffer with more audio, and advance state accordingly.
74 | var generateTimeSlice = function (leftBuf, rightBuf)
75 | {
76 | var numSamples = rightBuf.length;
77 |
78 | // Local variables
79 | var i, j, k, b, p, row, col, n, cp,
80 | t, lfor, e, x, rsample, rowStartSample, f, da;
81 |
82 | // Clear buffers
83 | for (k = 0; k < numSamples; ++k) {
84 | leftBuf[k] = 0;
85 | rightBuf[k] = 0;
86 | }
87 |
88 | // Generate active notes.
89 | for (i = 0; i < MAX_POLYPHONY; ++i) {
90 | var note = mPlayingNotes[i];
91 | if (note != undefined) {
92 |
93 | var envelope_shape = note.instr[3];
94 | var envelope_curve = mInstr[18] / 255.0
95 |
96 | var osc1 = mOscillators[signal_processor.osc_to_waveform(note.instr[0])[0]],
97 | o1vol = 255 - note.instr[1],
98 | o1xenv = note.instr[3],
99 | osc2 = mOscillators[signal_processor.osc_to_waveform(note.instr[0])[1]],
100 | o2vol = 255 - (255 - note.instr[1]),
101 | o2xenv = note.instr[3],
102 | noiseVol = note.instr[13],
103 | attack = Math.round(note.instr[10] * note.instr[10] * 4 * mRateScale),
104 | sustain = Math.round(note.instr[11] * note.instr[11] * 4 * mRateScale),
105 | release = Math.round(note.instr[12] * note.instr[12] * 4 * mRateScale),
106 | releaseInv = 1 / release,
107 | arpInterval = mRowLen * Math.pow(2, 2 - 0);
108 |
109 | // Note frequencies (defined later) and arpeggio
110 | var o1f, o2f;
111 | var arp = note.arp, arpSamples = note.arpSamples;
112 |
113 | // Current oscillator state.
114 | var o1t = note.o1t, o2t = note.o2t;
115 |
116 | // Generate note.
117 | var samplesLeft = attack + sustain + release - note.env;
118 | if (samplesLeft <= numSamples) {
119 | // End of note.
120 | mPlayingNotes[i] = undefined;
121 | } else {
122 | samplesLeft = numSamples;
123 | }
124 | for (j = note.env, k = 0; k < samplesLeft; j++, k++) {
125 | if (arpSamples >= 0 || k == 0) {
126 | if (arpSamples >= 0) {
127 | // Switch arpeggio note
128 | arp = (arp >> 8) | ((arp & 255) << 4);
129 | arpSamples -= arpInterval;
130 | }
131 |
132 | // Calculate note frequencies for the oscillators
133 | o1f = getnotefreq(note.n + (arp & 15) + note.instr[2] - 128);
134 | o2f = getnotefreq(note.n + (arp & 15) + note.instr[2] - 128) * (1 + 0.0008 * note.instr[7]);
135 | }
136 | arpSamples++;
137 |
138 | // Envelope
139 | e = 1;
140 | if (j < attack) {
141 | e = j / attack;
142 | var attack_t = j/attack;
143 | var attack_force = attack_t;
144 | e = e * Math.pow(attack_force,10 * envelope_curve)
145 | } else if (j >= attack + sustain) {
146 | var release_t = (j - attack - sustain)
147 | var release_force = (1 - (release_t/release));
148 | e = e * Math.pow(release_force,10 * envelope_curve);
149 | }
150 |
151 | // Oscillator 1
152 | t = o1f;
153 |
154 | if(envelope_shape == 1){ t *= e * e; }
155 | else if(envelope_shape == 2){ t *= e * e * e; }
156 | else if(envelope_shape == 3){ t *= e * e * e * e; }
157 |
158 | o1t += t;
159 | rsample = osc1(o1t) * o1vol;
160 |
161 | // Oscillator 2
162 | t = o2f;
163 |
164 | if(envelope_shape == 1){ t *= e * e; }
165 | else if(envelope_shape == 2){ t *= e * e * e; }
166 | else if(envelope_shape == 3){ t *= e * e * e * e; }
167 |
168 | o2t += t;
169 | rsample += osc2(o2t) * o2vol;
170 |
171 | // Noise oscillator
172 | if (noiseVol) {
173 | rsample += (2 * Math.random() - 1) * noiseVol;
174 | }
175 |
176 | // Add to (mono) channel buffer
177 | rightBuf[k] += 0.002441481 * rsample * e;
178 | }
179 |
180 | // Save state.
181 | note.env = j;
182 | note.arp = arp;
183 | note.arpSamples = arpSamples;
184 | note.o1t = o1t;
185 | note.o2t = o2t;
186 | }
187 | }
188 |
189 | // And the effects...
190 |
191 | var pos = mFXState.pos,
192 | low = mFXState.low,
193 | band = mFXState.band,
194 | filterActive = mFXState.filterActive,
195 | dlyPos = mFXState.dlyPos;
196 |
197 | var lsample, high, dlyRead, dlyMask = MAX_DELAY - 1;
198 |
199 | // Put performance critical instrument properties in local variables
200 | var oscLFO = mOscillators[mInstr[15]],
201 | lfoAmt = mInstr[16] / 512,
202 | lfoFreq = Math.pow(2, mInstr[17] - 9) / mRowLen,
203 | fxFilter = mInstr[19],
204 | fxFreq = mInstr[20] * 43.23529 * 3.141592 / mSampleRate,
205 | q = 1 - mInstr[21] / 255,
206 | dlyAmt = mInstr[27] == 0 ? 0 : mInstr[26] / 255,
207 | dly = (signal_processor.delay_conv(mInstr[27]) * mRowLen) >> 1;
208 |
209 | signal_processor.knobs.distortion = mInstr[22] * 1e-5 * 32767;
210 | signal_processor.knobs.pinking = mInstr[28] / 255.0;
211 | signal_processor.knobs.compressor = mInstr[14] / 255.0;
212 | signal_processor.knobs.drive = mInstr[23] / 32.0;
213 | signal_processor.knobs.bit_phaser = 0.5 - (0.49 * (mInstr[9]/255.0));
214 | signal_processor.knobs.bit_step = 16 - (14 * (mInstr[9]/255.0));
215 | signal_processor.knobs.pan = mInstr[24] / 255.0;
216 | signal_processor.knobs.delay = mInstr[27] / 255.0;
217 |
218 | // Limit the delay to the delay buffer size.
219 | if (dly >= MAX_DELAY) {
220 | dly = MAX_DELAY - 1;
221 | }
222 |
223 | // Perform effects for this time slice
224 | for (j = 0; j < numSamples; j++) {
225 | k = (pos + j) * 2;
226 |
227 | // Dry mono-sample.
228 | rsample = rightBuf[j];
229 |
230 | // We only do effects if we have some sound input.
231 | if (rsample || filterActive) {
232 |
233 | // State variable filter.
234 | f = fxFreq;
235 | f *= oscLFO(lfoFreq * k) * lfoAmt + 0.5;
236 | f = 1.5 * Math.sin(f);
237 |
238 | low += f * band;
239 | high = q * (rsample - band) - low;
240 | band += f * high;
241 | rsample = fxFilter == 3 ? band : fxFilter == 1 ? high : low;
242 |
243 | var signal = signal_processor.operate(rsample);
244 | rsample = signal.right;
245 | lsample = signal.left;
246 |
247 | // Is the filter active (i.e. still audiable)?
248 | filterActive = rsample * rsample > 1e-5;
249 | } else {
250 | lsample = 0;
251 | }
252 |
253 | // Delay is always done, since it does not need sound input.
254 | dlyRead = (dlyPos - dly) & dlyMask;
255 | lsample += mDlyRight[dlyRead] * dlyAmt;
256 | rsample += mDlyLeft[dlyRead] * dlyAmt;
257 | mDlyLeft[dlyPos] = lsample;
258 | mDlyRight[dlyPos] = rsample;
259 | dlyPos = (dlyPos + 1) & dlyMask;
260 |
261 | // Store wet stereo sample.
262 | leftBuf[j] = lsample;
263 | rightBuf[j] = rsample;
264 | }
265 |
266 | // Update effect sample position.
267 | pos += numSamples;
268 |
269 | // Prevent rounding problems...
270 | while (pos > mRowLen * 2048) {
271 | pos -= mRowLen * 2048;
272 | }
273 |
274 | // Store filter state.
275 | mFXState.pos = pos;
276 | mFXState.low = low;
277 | mFXState.band = band;
278 | mFXState.filterActive = filterActive;
279 | mFXState.dlyPos = dlyPos;
280 | };
281 |
282 | //--------------------------------------------------------------------------
283 | // Public interface.
284 | //--------------------------------------------------------------------------
285 |
286 | this.start = function ()
287 | {
288 | // Create an audio context.
289 | if (window.AudioContext) {
290 | mAudioContext = new AudioContext();
291 | } else if (window.webkitAudioContext) {
292 | mAudioContext = new webkitAudioContext();
293 | } else {
294 | mAudioContext = undefined;
295 | return;
296 | }
297 |
298 | // Get actual sample rate (SoundBox is hard-coded to 44100 samples/s).
299 | mSampleRate = mAudioContext.sampleRate;
300 | mRateScale = mSampleRate / 44100;
301 |
302 | // Clear state.
303 | mFXState = {
304 | pos: 0,
305 | low: 0,
306 | band: 0,
307 | filterActive: false,
308 | dlyPos: 0
309 | };
310 |
311 | // Create delay buffers (lengths must be equal and a power of 2).
312 | mDlyLeft = new Float32Array(MAX_DELAY);
313 | mDlyRight = new Float32Array(MAX_DELAY);
314 |
315 | // Create a script processor node with no inputs and one stereo output.
316 | mScriptNode = mAudioContext.createScriptProcessor(2048, 0, 2);
317 |
318 | mScriptNode.onaudioprocess = function (event){
319 | var leftBuf = event.outputBuffer.getChannelData(0);
320 | var rightBuf = event.outputBuffer.getChannelData(1);
321 | generateTimeSlice(leftBuf, rightBuf);
322 | };
323 |
324 | mScriptNode.connect(mAudioContext.destination);
325 | };
326 |
327 | this.stop = function ()
328 | {
329 |
330 | };
331 |
332 | this.updateInstr = function (instr)
333 | {
334 | mInstr = [];
335 | for (var i = 0; i < instr.length; ++i) {
336 | mInstr.push(instr[i]);
337 | }
338 | };
339 |
340 | this.updateRowLen = function (rowLen)
341 | {
342 | mRowLen = Math.round(rowLen * mRateScale);
343 | };
344 |
345 | this.addNote = function (n)
346 | {
347 | var t = (new Date()).getTime();
348 |
349 | // Create a new note object.
350 | var note = {
351 | startT: t,
352 | env: 0,
353 | arp: 0,
354 | arpSamples: 0,
355 | o1t: 0,
356 | o2t: 0,
357 | n: n,
358 | instr: new Array(15)
359 | };
360 |
361 | // Copy (snapshot) the oscillator/env/arp part of the current instrument.
362 | for (var i = 0; i < 15; ++i) {
363 | note.instr[i] = mInstr[i];
364 | }
365 |
366 | // Find an empty channel, or replace the oldest note.
367 | var oldestIdx = 0;
368 | var oldestDt = -100;
369 | for (var i = 0; i < MAX_POLYPHONY; ++i) {
370 | // If the channel is currently free - use it.
371 | if (mPlayingNotes[i] == undefined) {
372 | mPlayingNotes[i] = note;
373 | return;
374 | }
375 |
376 | // Check if this channel has the oldest playing note.
377 | var dt = t - mPlayingNotes[i].startT;
378 | if (dt > oldestDt) {
379 | oldestIdx = i;
380 | oldestDt = dt;
381 | }
382 | }
383 |
384 | // All channels are playing - replace the oldest one.
385 | mPlayingNotes[oldestIdx] = note;
386 | };
387 |
388 | };
389 |
--------------------------------------------------------------------------------
/desktop/sources/scripts/core/oscillator.js:
--------------------------------------------------------------------------------
1 | var Oscillator = function()
2 | {
3 | function clamp(v, min, max) { return v < min ? min : v > max ? max : v; }
4 |
5 | this.sin = function(value)
6 | {
7 | return Math.sin(value * 2 * Math.PI);
8 | };
9 |
10 | this.abs = function(value)
11 | {
12 | return 0.5 + Math.sin(value * 2 * Math.PI);
13 | }
14 |
15 | this.pulse = function (value)
16 | {
17 | return clamp(0.05/(Math.sin(2 * Math.PI * value) * Math.tan(2 * Math.PI * value)),-1.0,1.0);
18 | }
19 |
20 | this.saw = function(value)
21 | {
22 | return 2 * (value % 1) - 1;
23 | }
24 |
25 | this.rev = function(value)
26 | {
27 | return 1 - (2 * (value % 1));
28 | }
29 |
30 | this.square = function(value)
31 | {
32 | return (value % 1) < 0.5 ? 1 : -1;
33 | }
34 |
35 | this.noise = function(value)
36 | {
37 | return (2 * Math.random() - 1);
38 | }
39 |
40 | this.tri = function(value)
41 | {
42 | var v2 = (value % 1) * 4;
43 | return v2 < 2 ? v2 - 1 : 3 - v2;
44 | }
45 |
46 | // generates waveform from custom text
47 | // e.g.: 'sin(x)'
48 | this.custom = function(value, expression)
49 | {
50 | var ret = eval('var x=value;' + expression);
51 | ret = ret > 1.0 ? 1.0 : ret;
52 | ret = ret < -1.0 ? -1.0 : ret;
53 | return ret;
54 | }
55 |
56 | // TODO: complete sin2 - clear idea what I want, but... maths.
57 | // this.osc_sin2 = function (value) {
58 | // return Math.sin(2 * Math.PI * value) * Math.sin(4 * Math.PI * value);
59 | // }
60 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/core/player-worker.js:
--------------------------------------------------------------------------------
1 | /* -*- mode: javascript; tab-width: 2; indent-tabs-mode: nil; -*-
2 | *
3 | * Copyright (c) 2011-2013 Marcus Geelnard
4 | *
5 | * This file is part of SoundBox.
6 | *
7 | * SoundBox is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * SoundBox is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * along with SoundBox. If not, see .
19 | *
20 | */
21 |
22 | self.importScripts('signal.js');
23 | self.importScripts('oscillator.js');
24 |
25 |
26 | var CPlayerWorker = function()
27 | {
28 | var signal_processor = new Signal_Processor();
29 |
30 | var getnotefreq = function (n)
31 | {
32 | // 174.61.. / 44100 = 0.003959503758 (F)
33 | return 0.003959503758 * Math.pow(2, (n-128)/12);
34 | };
35 |
36 | var createNote = function (instr, n, rowLen)
37 | {
38 | var envelope_shape = instr.i[3];
39 | var envelope_curve = instr.i[18] / 255.0;
40 |
41 | var osc1 = mOscillators[signal_processor.osc_to_waveform(instr.i[0])[0]],
42 | o1vol = 255 - instr.i[1],
43 | o1xenv = instr.i[3],
44 | osc2 = mOscillators[signal_processor.osc_to_waveform(instr.i[0])[1]],
45 | o2vol = 255 - (255 - instr.i[1]),
46 | o2xenv = instr.i[3],
47 | noiseVol = instr.i[13],
48 | attack = instr.i[10] * instr.i[10] * 4,
49 | sustain = instr.i[11] * instr.i[11] * 4,
50 | release = instr.i[12] * instr.i[12] * 4,
51 | releaseInv = 1 / release,
52 | arp = 0,
53 | arpInterval = rowLen * Math.pow(2, 2 - 0);
54 |
55 | var noteBuf = new Int32Array(attack + sustain + release);
56 |
57 | // Re-trig oscillators
58 | var c1 = 0, c2 = 0;
59 |
60 | // Local variables.
61 | var j, j2, e, t, rsample, o1t, o2t;
62 |
63 | // Generate one note (attack + sustain + release)
64 | for (j = 0, j2 = 0; j < attack + sustain + release; j++, j2++) {
65 | if (j2 >= 0) {
66 | // Switch arpeggio note.
67 | arp = (arp >> 8) | ((arp & 255) << 4);
68 | j2 -= arpInterval;
69 |
70 | // Calculate note frequencies for the oscillators
71 | o1t = getnotefreq(n + (arp & 15) + instr.i[2] - 128);
72 | o2t = getnotefreq(n + (arp & 15) + instr.i[2] - 128) * (1 + 0.0008 * instr.i[7]);
73 | }
74 |
75 | // Envelope
76 | e = 1;
77 | if (j < attack) {
78 | e = j / attack;
79 | var attack_t = j/attack;
80 | var attack_force = attack_t;
81 | e = e * Math.pow(attack_force,10 * envelope_curve)
82 | } else if (j >= attack + sustain) {
83 | var release_t = (j - attack - sustain)
84 | var release_force = (1 - (release_t/release));
85 | e = e * Math.pow(release_force,10 * envelope_curve);
86 | }
87 |
88 | // Oscillator 1
89 | t = o1t;
90 | if(envelope_shape == 1){ t *= e * e; }
91 | else if(envelope_shape == 2){ t *= e * e * e; }
92 | else if(envelope_shape == 3){ t *= e * e * e * e; }
93 | c1 += t;
94 | rsample = osc1(c1) * o1vol;
95 |
96 | // Oscillator 2
97 | t = o2t;
98 | if(envelope_shape == 1){ t *= e * e; }
99 | else if(envelope_shape == 2){ t *= e * e * e; }
100 | else if(envelope_shape == 3){ t *= e * e * e * e; }
101 | c2 += t;
102 | rsample += osc2(c2) * o2vol;
103 |
104 | // Noise oscillator
105 | if(noiseVol){
106 | rsample += (2 * Math.random() - 1) * noiseVol;
107 | }
108 |
109 | // Add to (mono) channel buffer
110 | noteBuf[j] = (80 * rsample * e) | 0;
111 | }
112 |
113 | return noteBuf;
114 | };
115 |
116 |
117 | //--------------------------------------------------------------------------
118 | // Private members
119 | //--------------------------------------------------------------------------
120 |
121 | // Array of oscillator functions
122 | var mOscillators =
123 | [
124 | new Oscillator().sin,
125 | new Oscillator().square,
126 | new Oscillator().saw,
127 | new Oscillator().tri,
128 | new Oscillator().noise,
129 | new Oscillator().rev,
130 | new Oscillator().pulse
131 | ];
132 |
133 | //----------------------------------------------------------------------------
134 | // Public methods
135 | //----------------------------------------------------------------------------
136 |
137 | // Initialize buffers etc.
138 | this.init = function (song, opts)
139 | {
140 | // Handle optional arguments
141 | this.firstRow = 0;
142 | this.lastRow = song.endPattern - 1;
143 | this.firstCol = 0;
144 | this.lastCol = 15;
145 |
146 | if(opts) {
147 | this.firstRow = opts.firstRow;
148 | this.lastRow = opts.lastRow;
149 | this.firstCol = opts.firstCol;
150 | this.lastCol = opts.lastCol;
151 | }
152 |
153 | // Prepare song info
154 | this.song = song;
155 | this.numSamples = song.rowLen * song.patternLen * (this.lastRow - this.firstRow + 1);
156 | this.numWords = this.numSamples * 2;
157 |
158 | // Create work buffers (initially cleared)
159 | this.mixBufWork = new Int32Array(this.numWords);
160 | };
161 |
162 | // Generate audio data for a single track
163 | this.generate = function ()
164 | {
165 |
166 | // Local variables
167 | var i, j, b, p, row, col, currentCol, n, cp,
168 | k, t, lfor, e, x, rsample, rowStartSample, f, da;
169 |
170 | for (currentCol = this.firstCol; currentCol <= this.lastCol; currentCol++) {
171 | // Put performance critical items in local variables
172 | var chnBuf = new Int32Array(this.numWords),
173 | mixBuf = this.mixBufWork,
174 | waveSamples = this.numSamples,
175 | waveWords = this.numWords,
176 | instr = this.song.songData[currentCol],
177 | rowLen = this.song.rowLen,
178 | patternLen = this.song.patternLen;
179 |
180 | // Clear effect state
181 | var low = 0, band = 0, high;
182 | var lsample, filterActive = false;
183 |
184 | var mFXState = {
185 | bit_last: 0,
186 | bit_phaser: 0
187 | };
188 |
189 | // Clear note cache.
190 | var noteCache = [];
191 |
192 | // Patterns
193 | for (p = this.firstRow; p <= this.lastRow; ++p) {
194 | cp = instr.p[p];
195 |
196 | // Pattern rows
197 | for (row = 0; row < patternLen; ++row) {
198 | // Execute effect command.
199 | var cmdNo = cp ? instr.c[cp - 1].f[row] : 0;
200 | if (cmdNo) {
201 | instr.i[cmdNo - 1] = instr.c[cp - 1].f[row + patternLen] || 0;
202 |
203 | // Clear the note cache since the instrument has changed.
204 | if (cmdNo < 15) {
205 | noteCache = [];
206 | }
207 | }
208 |
209 | // Put performance critical instrument properties in local variables
210 | var oscLFO = mOscillators[instr.i[15]],
211 | lfoAmt = instr.i[16] / 512,
212 | lfoFreq = Math.pow(2, instr.i[17] - 9) / rowLen,
213 | fxFilter = instr.i[19],
214 | fxFreq = instr.i[20] * 43.23529 * 3.141592 / 44100,
215 | q = 1 - instr.i[21] / 255,
216 | dlyAmt = instr.i[27] == 0 ? 0 : instr.i[26] / 255,
217 | dly = signal_processor.delay_conv(instr.i[27]) * rowLen;
218 |
219 | signal_processor.knobs.distortion = instr.i[22] * 1e-5;
220 | signal_processor.knobs.pinking = instr.i[28] / 255.0;
221 | signal_processor.knobs.compressor = instr.i[14] / 255.0;
222 | signal_processor.knobs.drive = instr.i[23] / 32.0;
223 | signal_processor.knobs.bit_phaser = 0.5 - (0.49 * (instr.i[9]/255.0));
224 | signal_processor.knobs.bit_step = 16 - (14 * (instr.i[9]/255.0));
225 | signal_processor.knobs.pan = instr.i[24] / 255.0;
226 |
227 | // Calculate start sample number for this row in the pattern
228 | rowStartSample = ((p - this.firstRow) * patternLen + row) * rowLen;
229 |
230 | // Generate notes for this pattern row
231 | for (col = 0; col < 4; ++col) {
232 | n = cp ? instr.c[cp - 1].n[row + col * patternLen] : 0;
233 | if (n) {
234 | if (!noteCache[n]) {
235 | noteCache[n] = createNote(instr, n, rowLen);
236 | }
237 |
238 | // Copy note from the note cache
239 | var noteBuf = noteCache[n];
240 | for (j = 0, i = rowStartSample * 2; j < noteBuf.length; j++, i += 2) {
241 | chnBuf[i] += noteBuf[j];
242 | }
243 | }
244 | }
245 |
246 | // Perform effects for this pattern row
247 | for (j = 0; j < rowLen; j++) {
248 | // Dry mono-sample
249 | k = (rowStartSample + j) * 2;
250 | rsample = chnBuf[k];
251 |
252 | // We only do effects if we have some sound input
253 | if (rsample || filterActive) {
254 |
255 | // State variable filter
256 | f = fxFreq;
257 | f *= oscLFO(lfoFreq * k) * lfoAmt + 0.5;
258 | f = 1.5 * Math.sin(f);
259 | low += f * band;
260 | high = q * (rsample - band) - low;
261 | band += f * high;
262 | rsample = fxFilter == 3 ? band : fxFilter == 1 ? high : low;
263 |
264 | var signal = signal_processor.operate(rsample,true);
265 | rsample = signal.right;
266 | lsample = signal.left;
267 |
268 | // Is the filter active (i.e. still audiable)?
269 | filterActive = rsample * rsample > 1e-5;
270 | } else {
271 | lsample = 0;
272 | }
273 |
274 | // Delay is always done, since it does not need sound input
275 | if (k >= dly) {
276 | lsample += chnBuf[k-dly+1] * dlyAmt;
277 | rsample += chnBuf[k-dly] * dlyAmt;
278 | }
279 |
280 | // Store in stereo channel buffer (needed for the delay effect)
281 | chnBuf[k] = lsample | 0;
282 | chnBuf[k+1] = rsample | 0;
283 |
284 | // ...and add to stereo mix buffer
285 | mixBuf[k] += lsample | 0;
286 | mixBuf[k+1] += rsample | 0;
287 | }
288 | }
289 |
290 | // Post progress to the main thread...
291 | var progress = (currentCol - this.firstCol + (p - this.firstRow) /
292 | (this.lastRow - this.firstRow + 1)) /
293 | (this.lastCol - this.firstCol + 1);
294 | postMessage({
295 | cmd: "progress",
296 | progress: progress,
297 | buffer: null
298 | });
299 | }
300 | }
301 | };
302 |
303 | // Get the final buffer (as generated by the generate() method).
304 | this.getBuf = function () {
305 | // We no longer need the channel working buffer
306 | this.chnBufWork = null;
307 |
308 | return this.mixBufWork;
309 | };
310 | };
311 |
312 | var gPlayerWorker = new CPlayerWorker();
313 |
314 | onmessage = function (event) {
315 | if (event.data.cmd === "generate") {
316 | // Generate the sound data.
317 | gPlayerWorker.init(event.data.song, event.data.opts);
318 | gPlayerWorker.generate();
319 |
320 | // Signal that we are done, and send the resulting buffer over to the main
321 | // thread.
322 | postMessage({
323 | cmd: "progress",
324 | progress: 1,
325 | buffer: gPlayerWorker.getBuf()
326 | });
327 | }
328 | };
--------------------------------------------------------------------------------
/desktop/sources/scripts/core/player.js:
--------------------------------------------------------------------------------
1 | /* -*- mode: javascript; tab-width: 2; indent-tabs-mode: nil; -*-
2 | *
3 | * Copyright (c) 2011-2013 Marcus Geelnard
4 | *
5 | * This file is part of SoundBox.
6 | *
7 | * SoundBox is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * SoundBox is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * along with SoundBox. If not, see .
19 | *
20 | */
21 |
22 | "use strict";
23 |
24 | var CPlayer = function ()
25 | {
26 | var mProgressCallback;
27 |
28 | var mGeneratedBuffer;
29 |
30 | var mWorker = new Worker("scripts/core/player-worker.js");
31 |
32 | mWorker.onmessage = function (event) {
33 | if (event.data.cmd === "progress") {
34 | mGeneratedBuffer = event.data.buffer;
35 | if (mProgressCallback) {
36 | mProgressCallback(event.data.progress);
37 | }
38 | }
39 | };
40 |
41 |
42 | //--------------------------------------------------------------------------
43 | // Public methods
44 | //--------------------------------------------------------------------------
45 |
46 | // Generate the audio data (done in worker).
47 | this.generate = function(song, opts, progressCallback)
48 | {
49 | mProgressCallback = progressCallback;
50 | mWorker.postMessage({
51 | cmd: "generate",
52 | song: song,
53 | opts: opts
54 | });
55 | };
56 |
57 | // Create a WAVE formatted Uint8Array from the generated audio data.
58 | this.createWave = function()
59 | {
60 | // Turn critical object properties into local variables (performance)
61 | var mixBuf = mGeneratedBuffer,
62 | waveWords = mixBuf.length;
63 |
64 | // Create WAVE header
65 | var headerLen = 44;
66 | var l1 = headerLen + waveWords * 2 - 8;
67 | var l2 = l1 - 36;
68 | var wave = new Uint8Array(headerLen + waveWords * 2);
69 | wave.set(
70 | [82,73,70,70,
71 | l1 & 255,(l1 >> 8) & 255,(l1 >> 16) & 255,(l1 >> 24) & 255,
72 | 87,65,86,69,102,109,116,32,16,0,0,0,1,0,2,0,
73 | 68,172,0,0,16,177,2,0,4,0,16,0,100,97,116,97,
74 | l2 & 255,(l2 >> 8) & 255,(l2 >> 16) & 255,(l2 >> 24) & 255]
75 | );
76 |
77 | // Append actual wave data
78 | for(var i = 0, idx = headerLen; i < waveWords; ++i){
79 | // Note: We clamp here
80 | var y = mixBuf[i];
81 | y = y < -32767 ? -32767 : (y > 32767 ? 32767 : y);
82 | wave[idx++] = y & 255;
83 | wave[idx++] = (y >> 8) & 255;
84 | }
85 |
86 | // Return the WAVE formatted typed array
87 | return wave;
88 | };
89 |
90 | // Get n samples of wave data at time t [s]. Wave data in range [-2,2].
91 | this.getData = function(t, n)
92 | {
93 | var i = 2 * Math.floor(t * 44100);
94 | var d = new Array(n);
95 | var b = mGeneratedBuffer;
96 | for (var j = 0; j < 2*n; j += 1) {
97 | var k = i + j;
98 | d[j] = t > 0 && k < b.length ? b[k] / 32768 : 0;
99 | }
100 | return d;
101 | };
102 | };
103 |
--------------------------------------------------------------------------------
/desktop/sources/scripts/core/signal.js:
--------------------------------------------------------------------------------
1 | function Signal_Processor()
2 | {
3 | this.knobs = {distortion:null,pinking:null,compressor:null,drive:null,bit_phaser:null,bit_step:null,pan:null};
4 |
5 | this.step_last = 0;
6 | this.phase = 0;
7 | this.is_export = false;
8 |
9 | this.operate = function(input,is_export = false)
10 | {
11 | var output = input;
12 |
13 | this.is_export = is_export;
14 |
15 | output = this.effect_bitcrusher(output);
16 |
17 | output = this.effect_distortion(output,this.knobs.distortion);
18 | output = this.effect_pinking(output,this.knobs.pinking);
19 | output = this.effect_compressor(output,this.knobs.compressor);
20 | output = this.effect_drive(output,this.knobs.drive);
21 |
22 | // Pan
23 | var left = output * (1 - this.knobs.pan);
24 | var right = output * (this.knobs.pan);
25 |
26 | return {left:left,right:right};
27 | }
28 |
29 | this.effect_bitcrusher = function(input)
30 | {
31 | var output = input;
32 |
33 | this.phase += this.knobs.bit_phaser; // Between 0.1 and 1
34 | var step = Math.pow(1/2, this.knobs.bit_step); // between 1 and 16
35 |
36 | if(this.phase >= 1.0) {
37 | this.phase -= 1.0;
38 | this.step_last = step * Math.floor(output / step + 0.5);
39 | }
40 |
41 | output = this.knobs.bit_step < 16 ? this.step_last : output;
42 |
43 | return output;
44 | }
45 |
46 | var b0, b1, b2, b3, b4, b5, b6; b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0;
47 |
48 | this.effect_pinking = function(input,val)
49 | {
50 | b0 = 0.99886 * b0 + input * 0.0555179;
51 | b1 = 0.99332 * b1 + input * 0.0750759;
52 | b2 = 0.96900 * b2 + input * 0.1538520;
53 | b3 = 0.86650 * b3 + input * 0.3104856;
54 | b4 = 0.55000 * b4 + input * 0.5329522;
55 | b5 = -0.7616 * b5 - input * 0.0168980;
56 | var output = (b0 + b1 + b2 + b3 + b4 + b5 + b6 + input * 0.5362) * 0.1;
57 | b6 = input * 0.115926;
58 |
59 | return (output * val) + (input * (1 - val));
60 | }
61 |
62 | this.effect_compressor = function(input,val)
63 | {
64 | var output = this.is_export ? input/44100 : input;
65 | var offset = 1 - Math.abs(output);
66 |
67 | output = output * offset * offset;
68 |
69 | if(this.is_export){
70 | output *= 44100
71 | }
72 |
73 | return (output * val) + (input * (1 - val));
74 | }
75 |
76 | this.effect_distortion = function(input,val)
77 | {
78 | if(!val){ return input; }
79 |
80 | var output = input;
81 | output *= val;
82 | output = output < 1 ? output > -1 ? new Oscillator().sin(output*.25) : -1 : 1;
83 | output /= val;
84 | return output;
85 | }
86 |
87 | this.effect_drive = function(input,val)
88 | {
89 | var output = input;
90 | return output * val;
91 | }
92 |
93 | // Tools
94 |
95 | this.delay_conv = function(val)
96 | {
97 | return [0,32,24,16,12,8,6,4][val];
98 | }
99 |
100 | this.osc_to_waveform = function(index)
101 | {
102 | if(index == 0 ){ return [0,0]; } // SIN
103 | if(index == 1 ){ return [0,1]; } // SINSQR
104 | if(index == 2 ){ return [0,2]; } // SINSAW
105 | if(index == 3 ){ return [0,3]; } // SINTRI
106 | if(index == 4 ){ return [1,1]; } // SQR
107 | if(index == 5 ){ return [1,0]; } // SQRSIN
108 | if(index == 6 ){ return [1,2]; } // SQRSAW
109 | if(index == 7 ){ return [1,3]; } // SQRTRI
110 | if(index == 8 ){ return [2,2]; } // SAW
111 | if(index == 9 ){ return [2,0]; } // SAWSIN
112 | if(index == 10){ return [2,1]; } // SAWSQR
113 | if(index == 11){ return [2,3]; } // SAWTRI
114 | if(index == 12){ return [3,3]; } // TRI
115 | if(index == 13){ return [3,0]; } // TRISIN
116 | if(index == 14){ return [3,1]; } // TRISQR
117 | if(index == 15){ return [3,2]; } // TRISAW
118 | if(index == 16){ return [4,4]; } // NOISE
119 | if(index == 17){ return [6,0]; } // PULSE
120 | if(index == 18){ return [6,2]; } // REVSAW
121 | }
122 |
123 | function clamp(v, min, max) { return v < min ? min : v > max ? max : v; }
124 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/editor.js:
--------------------------------------------------------------------------------
1 | function Editor(t,b)
2 | {
3 | var target = this;
4 |
5 | this.edit_mode = false;
6 | this.pattern = {id:0,beat:4,length:(t*b),signature:[t,b],effect:-1};
7 | this.composer = false;
8 |
9 | this.start = function()
10 | {
11 | console.log("Started Editor");
12 |
13 | // Table
14 | var table = document.getElementById("pattern-table");
15 | var tr, td;
16 | for(var r = 0; r < 32; r++) {
17 | tr = document.createElement("tr");
18 | tr.id = "ppr"+r;
19 | // Notes
20 | for (i = 0; i < marabu.channels; i++) {
21 | td = document.createElement("td");
22 | td.id = "i"+(i < 10 ? "0"+i : i)+"r"+r;
23 | td.textContent = "----";
24 | td.addEventListener("mousedown", this.pattern_mouse_down, false);
25 | tr.appendChild(td);
26 | }
27 | // Effects
28 | var th = document.createElement("th");
29 | th.id = "fxr" + r;
30 | th.textContent = "0000";
31 | th.addEventListener("mousedown", this.effect_mouse_down, false);
32 | tr.appendChild(th);
33 | // End
34 | table.appendChild(tr);
35 | }
36 |
37 | // Composer
38 |
39 | var notes = ['C', 'c', 'D', 'd', 'E', 'F', 'f', 'G', 'g', 'A', 'a', 'B'];
40 |
41 | var table = document.getElementById("composer-table");
42 | var tr, td, th;
43 | for(var r = 0; r < 15; r++) {
44 | tr = document.createElement("tr");
45 | // Notes
46 | for (i = 0; i < 32; i++) {
47 | td = document.createElement("td");
48 | td.textContent = i % 4 == 0 ? ">-" : "--"
49 | td.className = "fl"
50 | td.id = "cpm_"+i+"_"+r;
51 | td.addEventListener("mousedown", this.composer_mouse_down, false);
52 | tr.appendChild(td);
53 | if(i == 31){
54 | td.textContent = notes[r % notes.length]+""+(parseInt(r /12) + 4);
55 | }
56 | }
57 | // End
58 | table.appendChild(tr);
59 | }
60 | }
61 |
62 | this.build = function()
63 | {
64 | return "";
65 | }
66 |
67 | this.pattern_mouse_down = function(e)
68 | {
69 | var i = parseInt(e.target.id.slice(1,3));
70 | var r = parseInt(e.target.id.slice(4));
71 |
72 | marabu.selection.instrument = i;
73 | marabu.selection.row = r;
74 | marabu.sequencer.follower.stop();
75 | marabu.update();
76 | }
77 |
78 | this.effect_mouse_down = function(e)
79 | {
80 | var row = parseInt(e.target.id.slice(3,5));
81 | marabu.selection.row = row;
82 | marabu.sequencer.follower.stop();
83 | marabu.update();
84 | }
85 |
86 | this.composer_mouse_down = function(e)
87 | {
88 | var row = parseInt(e.target.id.split("_")[1]);
89 | var note = parseInt(e.target.id.split("_")[2]);
90 | var note_val = 24 - note - 11;
91 |
92 | marabu.selection.row = row;
93 | marabu.sequencer.follower.stop();
94 | marabu.play_note(note_val,e.shiftKey);
95 | marabu.update();
96 | }
97 |
98 | this.toggle_composer = function()
99 | {
100 | marabu.editor.composer = marabu.editor.composer ? false : true;
101 |
102 | this.editor_el = document.getElementById("editor");
103 | this.editor_el.className = marabu.editor.composer ? "composer" : "";
104 | }
105 |
106 | // Parser
107 |
108 | this.cell_data = function(i,t,r,pattern = null, note = null)
109 | {
110 | // Basics
111 | var left_val = marabu.song.note_at(i,t,r);
112 | var right_val = marabu.song.note_at(i,t,r+32);
113 | var values = {left:left_val,right:right_val};
114 |
115 | var left_note = left_val > 0 ? parse_note(left_val) : null;
116 | var right_note = right_val > 0 ? parse_note(right_val) : null;
117 | var notes = {left:left_note,right:right_note};
118 |
119 | var cmd = marabu.song.effect_at(i,t,r);
120 | var val = marabu.song.effect_at(i,t,r+32);
121 | var effect = {cmd:cmd,val:val}
122 |
123 | // Strings
124 | var base_str = r == 1 && marabu.song.instrument(i).name ? marabu.song.instrument(i).name : "----"
125 | var left_str = effect.cmd ? to_hex(effect.cmd,2) : (r % 4 == 0 ? ">-" : base_str.substr(0,2));
126 | var right_str = effect.cmd ? to_hex(effect.val,2) : base_str.substr(2,2);
127 | left_str = notes.left ? ((notes.left.sharp ? notes.left.note.toLowerCase() : notes.left.note)+""+notes.left.octave) : left_str;
128 | right_str = notes.right ? ((notes.right.sharp ? notes.right.note.toLowerCase() : notes.right.note)+""+notes.right.octave) : right_str;
129 | var strings = {left:left_str,right:right_str,any:left_str};
130 |
131 | // Class
132 | var classes = {fg:"fl",bg:""};
133 | if(marabu.cheatcode.is_active && i == marabu.selection.instrument && marabu.cheatcode.selection[r] || effect.cmd){ classes.bg = "b_inv f_inv"; }
134 | else if(marabu.arp.is_active && marabu.arp.is_recording && i == marabu.selection.instrument && r >= marabu.selection.row && r <= marabu.selection.row+marabu.arp.memory.length){ classes.bg = "b_inv"; }
135 | else if(marabu.arp.is_active && !marabu.arp.is_recording && i == marabu.selection.instrument && r == marabu.selection.row){ classes.bg = "b_inv f_inv"; }
136 |
137 | if(marabu.cheatcode.is_active && i == marabu.selection.instrument && marabu.cheatcode.selection[r] || effect.cmd){ classes.fg = "f_inv"; }
138 | else if(marabu.arp.is_active && marabu.arp.is_recording && i == marabu.selection.instrument && r >= marabu.selection.row && r <= marabu.selection.row+marabu.arp.memory.length){ classes.fg = "f_inv"; }
139 | else if(marabu.arp.is_active && !marabu.arp.is_recording && i == marabu.selection.instrument && r == marabu.selection.row){ classes.fg = "b_inv f_inv"; }
140 | else if(values.left || values.right){ classes.fg = "fm"; }
141 | else if(pattern > 0 && r % 4 == 0){ classes.fg = "fm";}
142 |
143 | // Highlights
144 | if(r == marabu.selection.row && i == marabu.selection.instrument){ classes.fg = "selected";}
145 |
146 | // Compositor
147 | if(note){
148 | if(values.left == note || values.right == note){
149 | strings.any = values.left == note ? strings.left : strings.right
150 | classes.fg = r == marabu.selection.row ? "fh" : "fm"
151 | }
152 | else{
153 | strings.any = r % 4 == 0 ? ">-" : "--";
154 | classes.fg = r % 4 == 0 ? "beat" : ""
155 | }
156 | }
157 |
158 | return {notes:notes,effect:effect,values:values,strings:strings,classes:classes};
159 | }
160 |
161 | // Update
162 |
163 | this.update = function()
164 | {
165 | // Editor
166 | for(var r = 0; r < 32; r++){
167 | var row = document.getElementById("ppr"+r);
168 | row.className = marabu.selection.row == r ? "selected" : ""
169 | }
170 | for(var i = 0; i < marabu.channels; i++){
171 | var pattern = marabu.song.pattern_at(i,marabu.selection.track);
172 | for(var r = 0; r < 32; r++){
173 | var cell = document.getElementById("i"+(i < 10 ? '0'+i : i)+"r"+r);
174 | var c = marabu.editor.cell_data(i,marabu.selection.track,r,pattern);
175 | cell.className = c.classes.fg+" "+c.classes.bg;
176 | cell.textContent = c.strings.left+c.strings.right;
177 | }
178 | }
179 |
180 | // Composer
181 | for(var r = 0; r < 32; r++){
182 | for(var n = 0; n < 15; n++){
183 | var cell = document.getElementById("cpm_"+r+"_"+n);
184 | var offset = (marabu.selection.octave * 12);
185 | var note = -(n - offset - 87) + 13;
186 | var c = marabu.editor.cell_data(marabu.selection.instrument,marabu.selection.track,r,pattern,note);
187 | cell.textContent = c.strings.any;
188 | cell.className = c.classes.fg+" "+c.classes.bg;
189 | }
190 | }
191 |
192 | // Effects
193 | for(var r = 0; r < 32; r++){
194 | var cell = document.getElementById("fxr"+r);
195 | var effect_cmd = marabu.song.effect_at(marabu.selection.instrument,marabu.selection.track,r);
196 | var effect_val = marabu.song.effect_at(marabu.selection.instrument,marabu.selection.track,r+32)
197 | cell.textContent = effect_cmd > 0 ? (to_hex(effect_cmd,2) + "" + to_hex(effect_val,2)) : "0000";
198 | cell.className = effect_cmd > 0 ? "fh" : (r % 4 == 0 ? "fm" : "fl");
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/desktop/sources/scripts/follower.js:
--------------------------------------------------------------------------------
1 | function Follower()
2 | {
3 | this.timer = -1;
4 | this.prev = -1;
5 | this.offset = 0;
6 |
7 | this.start = function(offset = 0)
8 | {
9 | this.timer = setInterval(this.update, 16);
10 | this.offset = offset;
11 | console.log("follower","start -> "+this.offset);
12 | }
13 |
14 | this.update = function()
15 | {
16 | var t = marabu.song.mAudio_timer().currentTime();
17 |
18 | if (marabu.song.mAudio().ended || (marabu.song.mAudio().duration && ((marabu.song.mAudio().duration - t) < 0.001))) {
19 | clearInterval(this.timer);
20 | this.timer = -1;
21 | marabu.instrument.controls.uv.monitor.draw(-1);
22 | return;
23 | }
24 |
25 | marabu.instrument.controls.uv.monitor.draw(t);
26 |
27 | var n = Math.floor(t * 44100 / marabu.song.song().rowLen);
28 | var r = n % 32;
29 |
30 | if(n != this.prev){
31 | marabu.selection.row = r;
32 | marabu.selection.track = parseInt(n/32) + marabu.sequencer.follower.offset;
33 | marabu.update();
34 | this.prev = n;
35 | }
36 | }
37 |
38 | this.stop = function()
39 | {
40 | if(this.timer == -1){ return; }
41 | console.log("follower","stop");
42 | clearInterval(this.timer);
43 | this.timer = -1;
44 | marabu.update();
45 | marabu.instrument.controls.uv.monitor.draw(-1);
46 | }
47 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/instrument.js:
--------------------------------------------------------------------------------
1 | function Instrument()
2 | {
3 | var target = this;
4 |
5 | this.el = null;
6 |
7 | this.start = function()
8 | {
9 | this.el = document.getElementById("instrument");
10 |
11 | this.controls = {
12 | envelope : {
13 | type : new UI_Choice({name: "ENV", choices: ["NONE","WEAK","AVRG","HARD"] }),
14 | attack : new UI_Slider({name: "ATK", min: 0, max: 255 }),
15 | sustain : new UI_Slider({name: "SUS", min: 0, max: 255 }),
16 | release : new UI_Slider({name: "REL", min: 0, max: 255 }),
17 | curve : new UI_Slider({name: "POW", min: 12, max: 255 })
18 | },
19 | osc: {
20 | shape : new UI_Choice({name: "OSC", choices: ["SIN","SINSQR","SINSAW","SINTRI","SQR","SQRSIN","SQRSAW","SQRTRI","SAW","SAWSIN","SAWSQR","SAWTRI","TRI","TRISIN","TRISQR","TRISAW","NOISE","PULSE","REVSAW"]}),
21 | frequency : new UI_Slider({name: "FRQ", min: 92, max: 164 }),
22 | mix : new UI_Slider({name: "MIX", min: 0, max: 255, center:true }),
23 | detune : new UI_Slider({name: "DET", min: 0, max: 255 })
24 | },
25 | lfo : {
26 | shape : new UI_Choice({name: "LFO", choices: ["SIN","SQR","SAW","TRI","NOISE","REVSAW","PULSE"] }),
27 | frequency : new UI_Slider({name: "FRQ", min: 2, max: 12 }),
28 | amount : new UI_Slider({name: "AMT", min: 0, max: 255 })
29 | },
30 | filter : {
31 | shape : new UI_Choice({name: "FLT", choices: ["LP","HP","LP","BP"] }),
32 | frequency : new UI_Slider({name: "FRQ", min: 0, max: 255 }),
33 | resonance : new UI_Slider({name: "RES", min: 0, max: 254 })
34 | },
35 | delay : {
36 | rate : new UI_Choice({name: "DLY", choices: ["OFF","1/2","1/3","1/4","1/6","1/8","1/12","1/16"] }),
37 | amount : new UI_Slider({name: "AMT", min: 0, max: 255 })
38 | },
39 | effect : {
40 | noise : new UI_Slider({name: "NOI", min: 0, max: 255 }),
41 | bit : new UI_Slider({name: "BIT", min: 0, max: 255 }),
42 | distortion : new UI_Slider({name: "DIS", min: 0, max: 64 }),
43 | pinking : new UI_Slider({name: "PIN", min: 0, max: 255 }),
44 | compressor : new UI_Slider({name: "CMP", min: 0, max: 255 }),
45 | drive : new UI_Slider({name: "DRV", min: 0, max: 255 }),
46 | pan : new UI_Slider({name: "PAN", min: 0, max: 255, center:true })
47 | },
48 | uv : {
49 | monitor : new UI_Uv()
50 | }
51 | };
52 |
53 | this.install();
54 | }
55 |
56 | this.install = function()
57 | {
58 | var selection_id = 0;
59 | for(family_id in this.controls){
60 | var family_el = document.createElement("div");
61 | family_el.id = family_id;
62 | family_el.className = "family";
63 | for(control_id in this.controls[family_id]){
64 | var ctrl = this.controls[family_id][control_id];
65 | ctrl.family = family_id;
66 | ctrl.id = control_id;
67 | ctrl.control = selection_id;
68 | ctrl.install(family_el);
69 | selection_id += 1;
70 | }
71 | this.el.appendChild(family_el);
72 | }
73 | }
74 |
75 | this.control_target = function(target_control)
76 | {
77 | for(family_id in this.controls){
78 | for(control_id in this.controls[family_id]){
79 | if(this.controls[family_id][control_id].control == target_control){ return this.controls[family_id][control_id]; }
80 | }
81 | }
82 | }
83 |
84 | this.update = function()
85 | {
86 | for(family_id in this.controls){
87 | for(control_id in this.controls[family_id]){
88 | var ctrl = this.controls[family_id][control_id];
89 | var ctrl_storage = this.get_storage(family_id+"_"+control_id);
90 | var value = marabu.song.control_at(marabu.selection.instrument,ctrl_storage);
91 | ctrl.override(value);
92 | }
93 | }
94 | marabu.song.mJammer_update();
95 | }
96 |
97 | this.get_storage = function(id)
98 | {
99 | // Env
100 | switch (id){
101 | case 'envelope_type' : return 3
102 | case 'envelope_attack' : return 10
103 | case 'envelope_sustain' : return 11
104 | case 'envelope_release' : return 12
105 | case 'envelope_curve' : return 18
106 |
107 | case 'osc_shape' : return 0
108 | case 'osc_frequency' : return 2
109 | case 'osc_mix' : return 1
110 | case 'osc_detune' : return 7
111 |
112 | case 'lfo_shape' : return 15
113 | case 'lfo_frequency' : return 17
114 | case 'lfo_amount' : return 16
115 |
116 | case 'filter_shape' : return 19
117 | case 'filter_frequency' : return 20
118 | case 'filter_resonance' : return 21
119 |
120 | case 'delay_rate' : return 27
121 | case 'delay_amount' : return 26
122 |
123 | case 'effect_noise' : return 13
124 | case 'effect_bit' : return 9
125 | case 'effect_distortion' : return 22
126 | case 'effect_pinking' : return 28
127 | case 'effect_compressor' : return 14
128 | case 'effect_drive' : return 23
129 | case 'effect_pan' : return 24
130 |
131 | case 'uv_monitor' : return null
132 | }
133 |
134 | console.log("Unknown",id);
135 | return -1;
136 | }
137 |
138 | this.build = function()
139 | {
140 | return "";
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/desktop/sources/scripts/lib/controller.js:
--------------------------------------------------------------------------------
1 | function Controller()
2 | {
3 | this.menu = {default:{}};
4 | this.mode = "default";
5 |
6 | this.app = require('electron').remote.app;
7 |
8 | this.start = function()
9 | {
10 | }
11 |
12 | this.add = function(mode,cat,label,fn,accelerator)
13 | {
14 | if(!this.menu[mode]){ this.menu[mode] = {}; }
15 | if(!this.menu[mode][cat]){ this.menu[mode][cat] = {}; }
16 | this.menu[mode][cat][label] = {fn:fn,accelerator:accelerator};
17 | console.log(`${mode}/${cat}/${label} <${accelerator}>`);
18 | }
19 |
20 | this.add_role = function(mode,cat,label)
21 | {
22 | if(!this.menu[mode]){ this.menu[mode] = {}; }
23 | if(!this.menu[mode][cat]){ this.menu[mode][cat] = {}; }
24 | this.menu[mode][cat][label] = {role:label};
25 | }
26 |
27 | this.set = function(mode = "default")
28 | {
29 | this.mode = mode;
30 | this.commit();
31 | }
32 |
33 | this.format = function()
34 | {
35 | var f = [];
36 | var m = this.menu[this.mode];
37 | for(cat in m){
38 | var submenu = [];
39 | for(name in m[cat]){
40 | var option = m[cat][name];
41 | if(option.role){
42 | submenu.push({role:option.role})
43 | }
44 | else{
45 | submenu.push({label:name,accelerator:option.accelerator,click:option.fn})
46 | }
47 | }
48 | f.push({label:cat,submenu:submenu});
49 | }
50 | return f;
51 | }
52 |
53 | this.commit = function()
54 | {
55 | this.app.inject_menu(this.format());
56 | }
57 |
58 | this.docs = function()
59 | {
60 | console.log("Generating docs..");
61 | var svg = this.generate_svg(this.format())
62 | var txt = this.documentation(this.format());
63 | dialog.showSaveDialog((fileName) => {
64 | if (fileName === undefined){ return; }
65 | fileName = fileName.substr(-4,4) != ".svg" ? fileName+".svg" : fileName;
66 | fs.writeFile(fileName,svg);
67 | fs.writeFile(fileName.replace(".svg",".md"),txt);
68 | });
69 | }
70 |
71 | this.generate_svg = function(m)
72 | {
73 | var svg_html = "";
74 |
75 | for(id in this.layout){
76 | var key = this.layout[id];
77 | var acc = this.accelerator_for_key(key.name,m);
78 | svg_html += ``;
79 | svg_html += ``;
80 | svg_html += `${key.name.toUpperCase()}`;
81 | svg_html += acc && acc.basic ? `${acc.basic}` : '';
82 | svg_html += acc && acc.ctrl ? `${acc.ctrl}` : '';
83 | }
84 | return ``;
85 | }
86 |
87 | this.documentation = function()
88 | {
89 | var txt = "";
90 |
91 | txt += this.documentation_for_mode("default",this.menu.default);
92 |
93 | for(name in this.menu){
94 | if(name == "default"){ continue; }
95 | txt += this.documentation_for_mode(name,this.menu[name]);
96 | }
97 | return txt;
98 | }
99 |
100 | this.documentation_for_mode = function(name,mode)
101 | {
102 | var txt = `## ${name} Mode\n\n`;
103 |
104 | for(id in mode){
105 | if(id == "*"){ continue; }
106 | txt += `### ${id}\n`;
107 | for(name in mode[id]){
108 | var option = mode[id][name];
109 | txt += `- ${name}: \`${option.accelerator}\`\n`;
110 | }
111 | txt += "\n"
112 | }
113 |
114 | return txt+"\n";
115 | }
116 |
117 | this.accelerator_for_key = function(key,menu)
118 | {
119 | var acc = {basic:null,ctrl:null}
120 | for(cat in menu){
121 | var options = menu[cat];
122 | for(id in options.submenu){
123 | var option = options.submenu[id]; if(option.role){ continue; }
124 | acc.basic = (option.accelerator.toLowerCase() == key.toLowerCase()) ? option.label.toUpperCase().replace("TOGGLE ","").substr(0,8).trim() : acc.basic;
125 | acc.ctrl = (option.accelerator.toLowerCase() == ("CmdOrCtrl+"+key).toLowerCase()) ? option.label.toUpperCase().replace("TOGGLE ","").substr(0,8).trim() : acc.ctrl;
126 | }
127 | }
128 | return acc;
129 | }
130 |
131 | this.layout = [
132 | {x:0, y:0, width:60, height:60, name:"esc"},
133 | {x:60, y:0, width:60, height:60, name:"1"},
134 | {x:120, y:0, width:60, height:60, name:"2"},
135 | {x:180, y:0, width:60, height:60, name:"3"},
136 | {x:240, y:0, width:60, height:60, name:"4"},
137 | {x:300, y:0, width:60, height:60, name:"5"},
138 | {x:360, y:0, width:60, height:60, name:"6"},
139 | {x:420, y:0, width:60, height:60, name:"7"},
140 | {x:480, y:0, width:60, height:60, name:"8"},
141 | {x:540, y:0, width:60, height:60, name:"9"},
142 | {x:600, y:0, width:60, height:60, name:"0"},
143 | {x:660, y:0, width:60, height:60, name:"-"},
144 | {x:720, y:0, width:60, height:60, name:"plus"},
145 | {x:780, y:0, width:120, height:60, name:"backspace"},
146 | {x:0, y:60, width:90, height:60, name:"tab"},
147 | {x:90, y:60, width:60, height:60, name:"q"},
148 | {x:150, y:60, width:60, height:60, name:"w"},
149 | {x:210, y:60, width:60, height:60, name:"e"},
150 | {x:270, y:60, width:60, height:60, name:"r"},
151 | {x:330, y:60, width:60, height:60, name:"t"},
152 | {x:390, y:60, width:60, height:60, name:"y"},
153 | {x:450, y:60, width:60, height:60, name:"u"},
154 | {x:510, y:60, width:60, height:60, name:"i"},
155 | {x:570, y:60, width:60, height:60, name:"o"},
156 | {x:630, y:60, width:60, height:60, name:"p"},
157 | {x:690, y:60, width:60, height:60, name:"["},
158 | {x:750, y:60, width:60, height:60, name:"]"},
159 | {x:810, y:60, width:90, height:60, name:"|"},
160 | {x:0, y:120, width:105, height:60, name:"caps"},
161 | {x:105, y:120, width:60, height:60, name:"a"},
162 | {x:165, y:120, width:60, height:60, name:"s"},
163 | {x:225, y:120, width:60, height:60, name:"d"},
164 | {x:285, y:120, width:60, height:60, name:"f"},
165 | {x:345, y:120, width:60, height:60, name:"g"},
166 | {x:405, y:120, width:60, height:60, name:"h"},
167 | {x:465, y:120, width:60, height:60, name:"j"},
168 | {x:525, y:120, width:60, height:60, name:"k"},
169 | {x:585, y:120, width:60, height:60, name:"l"},
170 | {x:645, y:120, width:60, height:60, name:";"},
171 | {x:705, y:120, width:60, height:60, name:"'"},
172 | {x:765, y:120, width:135, height:60, name:"enter"},
173 | {x:0, y:180, width:135, height:60, name:"shift"},
174 | {x:135, y:180, width:60, height:60, name:"z"},
175 | {x:195, y:180, width:60, height:60, name:"x"},
176 | {x:255, y:180, width:60, height:60, name:"c"},
177 | {x:315, y:180, width:60, height:60, name:"v"},
178 | {x:375, y:180, width:60, height:60, name:"b"},
179 | {x:435, y:180, width:60, height:60, name:"n"},
180 | {x:495, y:180, width:60, height:60, name:"m"},
181 | {x:555, y:180, width:60, height:60, name:","},
182 | {x:615, y:180, width:60, height:60, name:"."},
183 | {x:675, y:180, width:60, height:60, name:"/"},
184 | {x:735, y:180, width:165, height:60, name:"capslock"},
185 | {x:0, y:240, width:90, height:60, name:"ctrl"},
186 | {x:90, y:240, width:90, height:60, name:"cmd"},
187 | {x:180, y:240, width:90, height:60, name:"alt"},
188 | {x:270, y:240, width:270, height:60, name:"space"},
189 | {x:810, y:240, width:90, height:60, name:"ctrl"},
190 | {x:720, y:240, width:90, height:60, name:"pn"},
191 | {x:630, y:240, width:90, height:60, name:"fn"},
192 | {x:540, y:240, width:90, height:60, name:"alt"}
193 | ];
194 | }
195 |
196 | module.exports = new Controller();
--------------------------------------------------------------------------------
/desktop/sources/scripts/lib/history.js:
--------------------------------------------------------------------------------
1 | function History()
2 | {
3 | this.index = 0;
4 | this.a = [];
5 |
6 | this.clear = function()
7 | {
8 | this.a = [];
9 | this.index = 0;
10 | }
11 |
12 | this.push = function(data)
13 | {
14 | if(this.index < this.a.length-1){
15 | this.fork();
16 | }
17 | this.index = this.a.length;
18 | this.a = this.a.slice(0,this.index);
19 | this.a.push(copy(data));
20 |
21 | if(this.a.length > 20){
22 | this.a.shift();
23 | }
24 | }
25 |
26 | this.fork = function()
27 | {
28 | this.a = this.a.slice(0,this.index+1);
29 | }
30 |
31 | this.pop = function()
32 | {
33 | return this.a.pop();
34 | }
35 |
36 | this.prev = function()
37 | {
38 | this.index = clamp(this.index-1,0,this.a.length-1);
39 | return copy(this.a[this.index]);
40 | }
41 |
42 | this.next = function()
43 | {
44 | this.index = clamp(this.index+1,0,this.a.length-1);
45 | return copy(this.a[this.index]);
46 | }
47 |
48 | function copy(data){ return data ? JSON.parse(JSON.stringify(data)) : []; }
49 | function clamp(v, min, max) { return v < min ? min : v > max ? max : v; }
50 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/lib/theme.js:
--------------------------------------------------------------------------------
1 | function Theme()
2 | {
3 | var app = this;
4 |
5 | this.el = document.createElement("style");
6 | this.el.type = 'text/css';
7 | this.default = {meta:{}, data: { background: "#222", f_high: "#fff", f_med: "#777", f_low: "#444", f_inv: "#000", b_high: "#000", b_med: "#affec7", b_low: "#000", b_inv: "#affec7" }}
8 | this.active = this.default;
9 |
10 | this.start = function()
11 | {
12 | this.load(localStorage.theme ? localStorage.theme : this.default, this.default);
13 | window.addEventListener('dragover',this.drag_enter);
14 | window.addEventListener('drop', this.drag);
15 | document.head.appendChild(this.el)
16 | }
17 |
18 | this.load = function(t, fall_back)
19 | {
20 | var theme = is_json(t) ? JSON.parse(t).data : t.data;
21 |
22 | if(!theme || !theme.background){
23 | if(fall_back) {
24 | theme = fall_back.data;
25 | } else {
26 | return;
27 | }
28 | }
29 |
30 | var css = `
31 | :root {
32 | --background: ${theme.background};
33 | --f_high: ${theme.f_high};
34 | --f_med: ${theme.f_med};
35 | --f_low: ${theme.f_low};
36 | --f_inv: ${theme.f_inv};
37 | --b_high: ${theme.b_high};
38 | --b_med: ${theme.b_med};
39 | --b_low: ${theme.b_low};
40 | --b_inv: ${theme.b_inv};
41 | }`;
42 |
43 | this.active = theme;
44 | this.el.textContent = css;
45 | localStorage.setItem("theme", JSON.stringify({data: theme}));
46 | }
47 |
48 | this.reset = function()
49 | {
50 | this.load(this.default);
51 | }
52 |
53 | this.drag_enter = function(e)
54 | {
55 | e.stopPropagation();
56 | e.preventDefault();
57 | e.dataTransfer.dropEffect = 'copy';
58 | }
59 |
60 | this.drag = function(e)
61 | {
62 | e.preventDefault();
63 | e.stopPropagation();
64 |
65 | var file = e.dataTransfer.files[0];
66 |
67 | if(!file.name || !file.name.indexOf(".thm") < 0){ console.log("Theme","Not a theme"); return; }
68 |
69 | var reader = new FileReader();
70 | reader.onload = function(e){
71 | app.load(e.target.result);
72 | };
73 | reader.readAsText(file);
74 | }
75 |
76 | function is_json(text)
77 | {
78 | try{
79 | JSON.parse(text);
80 | return true;
81 | }
82 | catch (error){
83 | return false;
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/loop.js:
--------------------------------------------------------------------------------
1 | function Loop()
2 | {
3 | this.is_active = false;
4 |
5 | this.x = 0;
6 | this.width = 0;
7 | this.y = 0;
8 | this.height = 0;
9 |
10 | this.reset = function()
11 | {
12 | this.x = 0;
13 | this.width = 15;
14 | this.y = marabu.selection.track;
15 | this.height = 0;
16 | }
17 |
18 | this.start = function()
19 | {
20 | marabu.cheatcode.stop();
21 | this.is_active = true;
22 | this.reset();
23 | marabu.update();
24 | marabu.controller.set("loop");
25 | }
26 |
27 | this.stop = function()
28 | {
29 | this.is_active = false;
30 | marabu.stop();
31 | this.reset();
32 | marabu.update();
33 | marabu.controller.set("default");
34 | }
35 |
36 | this.mod = function(m)
37 | {
38 | this.height = clamp(this.height+m,0,(marabu.song.length-this.y)+3);
39 | marabu.update();
40 | }
41 |
42 | this.buffer = [];
43 |
44 | this.copy = function()
45 | {
46 | this.buffer = [];
47 | for(var i = 0; i < 16; i++){
48 | this.buffer[i] = marabu.song.song().songData[i].p.slice(this.y,this.y+this.height+1);
49 | }
50 | this.stop();
51 | }
52 |
53 | this.paste = function()
54 | {
55 | for(var i = 0; i < 16; i++){
56 | marabu.song.song().songData[i].p.splice(this.y, 0, ...this.buffer[i]);
57 | }
58 | marabu.history.push(marabu.song.song());
59 | this.stop();
60 | }
61 |
62 | this.cut = function()
63 | {
64 | // Copy
65 | this.buffer = [];
66 | for(var i = 0; i < 16; i++){
67 | this.buffer[i] = marabu.song.song().songData[i].p.slice(this.y,this.y+this.height+1);
68 | }
69 | // Erase
70 | for(var y = this.y; y < this.y+(this.height+1); y++){
71 | for(var i = 0; i < 16; i++){
72 | marabu.song.song().songData[i].p[y] = 0;
73 | }
74 | }
75 | marabu.history.push(marabu.song.song());
76 | this.stop();
77 | }
78 |
79 | this.erase = function()
80 | {
81 | for(var i = 0; i < 16; i++){
82 | marabu.song.song().songData[i].p.splice(this.y,this.height+1);
83 | }
84 | marabu.history.push(marabu.song.song());
85 | this.stop();
86 | }
87 |
88 | this.solo = function()
89 | {
90 | this.x = marabu.selection.instrument;
91 | this.width = 0;
92 | this.y = marabu.selection.track;
93 | this.height = 0;
94 | marabu.update();
95 | }
96 |
97 | this.play = function()
98 | {
99 | this.is_active = false;
100 |
101 | marabu.song.play_loop(this.range());
102 | }
103 |
104 | this.range = function()
105 | {
106 | return {
107 | firstCol: this.x,
108 | lastCol: this.x + this.width,
109 | firstRow: this.y,
110 | lastRow: this.y + this.height
111 | };
112 | }
113 |
114 | this.render = function()
115 | {
116 | this.stop();
117 | marabu.song.export_wav(this.range());
118 | }
119 |
120 | this.set_height = function(mod)
121 | {
122 | this.height = mod;
123 | marabu.update();
124 | }
125 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/marabu.js:
--------------------------------------------------------------------------------
1 | function Marabu()
2 | {
3 | this.history = new History();
4 | this.theme = new Theme();
5 | this.controller = new Controller();
6 |
7 | this.el = document.createElement("app");
8 | this.el.style.opacity = 0;
9 | this.el.id = "marabu";
10 |
11 | this.wrapper_el = document.createElement("yu");
12 | this.wrapper_el.className = "wrapper";
13 |
14 | this.el.appendChild(this.wrapper_el);
15 |
16 | document.body.appendChild(this.el);
17 |
18 | this.selection = {instrument:0,track:0,row:0,octave:5,control:0};
19 | this.channels = 16;
20 |
21 | this.song = new Song();
22 | this.sequencer = new Sequencer();
23 | this.editor = new Editor(8,4);
24 | this.instrument = new Instrument();
25 |
26 | this.cheatcode = new Cheatcode();
27 | this.loop = new Loop();
28 | this.arp = new Arp();
29 |
30 | this.start = function()
31 | {
32 | this.wrapper_el.innerHTML += "";
33 | this.wrapper_el.innerHTML += this.editor.build();
34 | this.wrapper_el.innerHTML += this.instrument.build();
35 |
36 | this.song.init();
37 | this.theme.start();
38 |
39 | this.sequencer.start();
40 | this.editor.start();
41 | this.instrument.start();
42 |
43 | this.song.update();
44 | this.sequencer.update();
45 | this.editor.update();
46 | this.instrument.update();
47 |
48 | this.controller.add("default","*","About",() => { require('electron').shell.openExternal('https://github.com/hundredrabbits/Marabu'); },"CmdOrCtrl+,");
49 | this.controller.add("default","*","Fullscreen",() => { app.toggle_fullscreen(); },"CmdOrCtrl+Enter");
50 | this.controller.add("default","*","Hide",() => { app.toggle_visible(); },"CmdOrCtrl+H");
51 | this.controller.add("default","*","Inspect",() => { app.inspect(); },"CmdOrCtrl+.");
52 | this.controller.add("default","*","Documentation",() => { marabu.controller.docs(); },"CmdOrCtrl+Esc");
53 | this.controller.add("default","*","Reset",() => { marabu.theme.reset(); },"CmdOrCtrl+Backspace");
54 | this.controller.add("default","*","Quit",() => { app.exit(); },"CmdOrCtrl+Q");
55 | this.controller.add("default","File","New",() => { marabu.new(); },"CmdOrCtrl+N");
56 | this.controller.add("default","File","Open",() => { marabu.open(); },"CmdOrCtrl+O");
57 | this.controller.add("default","File","Save",() => { marabu.save(); },"CmdOrCtrl+S");
58 | this.controller.add("default","File","Save As",() => { marabu.export(); },"CmdOrCtrl+Shift+S");
59 | this.controller.add("default","File","Render",() => { marabu.render(); },"CmdOrCtrl+R");
60 | this.controller.add("default","File","Export Ins",() => { marabu.export_instrument(); },"CmdOrCtrl+I");
61 | this.controller.add("default","Edit","Inc BPM",() => { marabu.move_bpm(5) },">");
62 | this.controller.add("default","Edit","Dec BPM",() => { marabu.move_bpm(-5) },"<");
63 | this.controller.add("default","Edit","Delete",() => { marabu.set_note(0); marabu.remove_control_value(0); },"Backspace");
64 | this.controller.add("default","Edit","Undo",() => { marabu.undo(); },"CmdOrCtrl+Z");
65 | this.controller.add("default","Edit","Redo",() => { marabu.redo(); },"CmdOrCtrl+Shift+Z");
66 | this.controller.add("default","Select","1st Row",() => { marabu.select_row(0); },"1");
67 | this.controller.add("default","Select","4th Row",() => { marabu.select_row(4); },"2");
68 | this.controller.add("default","Select","8th Row",() => { marabu.select_row(8); },"3");
69 | this.controller.add("default","Select","12th Row",() => { marabu.select_row(12); },"4");
70 | this.controller.add("default","Select","16th Row",() => { marabu.select_row(16); },"5");
71 | this.controller.add("default","Select","20th Row",() => { marabu.select_row(20); },"6");
72 | this.controller.add("default","Select","24th Row",() => { marabu.select_row(24); },"7");
73 | this.controller.add("default","Select","28th Row",() => { marabu.select_row(28); },"8");
74 | this.controller.add("default","Track","Next Inst",() => { marabu.move_inst(1); },"Right");
75 | this.controller.add("default","Track","Prev Inst",() => { marabu.move_inst(-1) },"Left");
76 | this.controller.add("default","Track","Next Row",() => { marabu.move_row(1); },"Down");
77 | this.controller.add("default","Track","Prev Row",() => { marabu.move_row(-1) },"Up");
78 | this.controller.add("default","Track","Next Track",() => { marabu.move_track(1); },"CmdOrCtrl+Down");
79 | this.controller.add("default","Track","Prev Track",() => { marabu.move_track(-1); },"CmdOrCtrl+Up");
80 | this.controller.add("default","Track","Next Pattern",() => { marabu.move_pattern(1); },"CmdOrCtrl+Right");
81 | this.controller.add("default","Track","Prev Pattern",() => { marabu.move_pattern(-1); },"CmdOrCtrl+Left");
82 | this.controller.add("default","Play","Track",() => { marabu.play(); },"Space");
83 | this.controller.add("default","Play","Range",() => { marabu.loop.start(); },"Enter");
84 | this.controller.add("default","Play","Stop",() => { marabu.stop(); },"Esc");
85 | this.controller.add("default","Mode","Cheatcode",() => { marabu.cheatcode.start(); },"CmdOrCtrl+K");
86 | this.controller.add("default","Mode","Loop",() => { marabu.loop.start(); },"CmdOrCtrl+L");
87 | this.controller.add("default","Mode","Arp",() => { marabu.arp.start(); },"CmdOrCtrl+M");
88 | this.controller.add("default","Mode","Composer",() => { marabu.editor.toggle_composer(); },"M");
89 | this.controller.add("default","Keyboard","Inc Octave",() => { marabu.move_octave(1); },"X");
90 | this.controller.add("default","Keyboard","Dec Octave",() => { marabu.move_octave(-1); },"Z");
91 | this.controller.add("default","Keyboard","C",() => { marabu.play_note(0,true); },"A");
92 | this.controller.add("default","Keyboard","C#",() => { marabu.play_note(1,true); },"W");
93 | this.controller.add("default","Keyboard","D",() => { marabu.play_note(2,true); },"S");
94 | this.controller.add("default","Keyboard","D#",() => { marabu.play_note(3,true); },"E");
95 | this.controller.add("default","Keyboard","E",() => { marabu.play_note(4,true); },"D");
96 | this.controller.add("default","Keyboard","F",() => { marabu.play_note(5,true); },"F");
97 | this.controller.add("default","Keyboard","F#",() => { marabu.play_note(6,true); },"T");
98 | this.controller.add("default","Keyboard","G",() => { marabu.play_note(7,true); },"G");
99 | this.controller.add("default","Keyboard","G#",() => { marabu.play_note(8,true); },"Y");
100 | this.controller.add("default","Keyboard","A",() => { marabu.play_note(9,true); },"H");
101 | this.controller.add("default","Keyboard","A#",() => { marabu.play_note(10,true); },"U");
102 | this.controller.add("default","Keyboard","B",() => { marabu.play_note(11,true); },"J");
103 | this.controller.add("default","Keyboard","(Right)C",() => { marabu.play_note(0,false); },"Shift+A");
104 | this.controller.add("default","Keyboard","(Right)C#",() => { marabu.play_note(1,false); },"Shift+W");
105 | this.controller.add("default","Keyboard","(Right)D",() => { marabu.play_note(2,false); },"Shift+S");
106 | this.controller.add("default","Keyboard","(Right)D#",() => { marabu.play_note(3,false); },"Shift+E");
107 | this.controller.add("default","Keyboard","(Right)E",() => { marabu.play_note(4,false); },"Shift+D");
108 | this.controller.add("default","Keyboard","(Right)F",() => { marabu.play_note(5,false); },"Shift+F");
109 | this.controller.add("default","Keyboard","(Right)F#",() => { marabu.play_note(6,false); },"Shift+T");
110 | this.controller.add("default","Keyboard","(Right)G",() => { marabu.play_note(7,false); },"Shift+G");
111 | this.controller.add("default","Keyboard","(Right)G#",() => { marabu.play_note(8,false); },"Shift+Y");
112 | this.controller.add("default","Keyboard","(Right)A",() => { marabu.play_note(9,false); },"Shift+H");
113 | this.controller.add("default","Keyboard","(Right)A#",() => { marabu.play_note(10,false); },"Shift+U");
114 | this.controller.add("default","Keyboard","(Right)B",() => { marabu.play_note(11,false); },"Shift+J");
115 | this.controller.add("default","Instrument","Next Control",() => { marabu.move_control(1); },"Shift+Up");
116 | this.controller.add("default","Instrument","Prev Control",() => { marabu.move_control(-1); },"Shift+Down");
117 | this.controller.add("default","Instrument","Inc Control +10",() => { marabu.move_control_value(10); },"Shift+Right");
118 | this.controller.add("default","Instrument","Dec Control -10",() => { marabu.move_control_value(-10); },"Shift+Left");
119 | this.controller.add("default","Instrument","Inc Control 1",() => { marabu.move_control_value(1); },"}");
120 | this.controller.add("default","Instrument","Dec Control -1",() => { marabu.move_control_value(-1); },"{");
121 | this.controller.add("default","Instrument","Inc Control 10(alt)",() => { marabu.move_control_value(10); },"]");
122 | this.controller.add("default","Instrument","Dec Control -10(alt)",() => { marabu.move_control_value(-10); },"[");
123 | this.controller.add("default","Instrument","Min",() => { marabu.move_control_min(); },"9");
124 | this.controller.add("default","Instrument","Max",() => { marabu.move_control_max(); },"0");
125 | this.controller.add("default","Instrument","Keyframe",() => { marabu.add_control_value(); },"/");
126 |
127 | this.controller.add("cheatcode","*","Quit",() => { app.exit(); },"CmdOrCtrl+Q");
128 | this.controller.add("cheatcode","Mode","Stop",() => { marabu.cheatcode.stop(); },"Esc");
129 | this.controller.add("cheatcode","Mode","Copy",() => { marabu.cheatcode.copy(); },"C");
130 | this.controller.add("cheatcode","Mode","Paste",() => { marabu.cheatcode.paste(); },"V");
131 | this.controller.add("cheatcode","Mode","Erase",() => { marabu.cheatcode.del(); },"Backspace");
132 | this.controller.add("cheatcode","Effect","Inc Note +12",() => { marabu.cheatcode.mod(12); },"]");
133 | this.controller.add("cheatcode","Effect","Dec Note -12",() => { marabu.cheatcode.mod(-12); },"[");
134 | this.controller.add("cheatcode","Effect","Inc Note +1",() => { marabu.cheatcode.mod(1); },"}");
135 | this.controller.add("cheatcode","Effect","Dec Note -1",() => { marabu.cheatcode.mod(-1); },"{");
136 | this.controller.add("cheatcode","Selection","All",() => { marabu.cheatcode.set_rate(1); },"1");
137 | this.controller.add("cheatcode","Selection","2nd",() => { marabu.cheatcode.set_rate(2); },"2");
138 | this.controller.add("cheatcode","Selection","3rd",() => { marabu.cheatcode.set_rate(3); },"3");
139 | this.controller.add("cheatcode","Selection","4th",() => { marabu.cheatcode.set_rate(4); },"4");
140 | this.controller.add("cheatcode","Selection","5th",() => { marabu.cheatcode.set_rate(5); },"5");
141 | this.controller.add("cheatcode","Selection","6th",() => { marabu.cheatcode.set_rate(6); },"6");
142 | this.controller.add("cheatcode","Selection","7th",() => { marabu.cheatcode.set_rate(7); },"7");
143 | this.controller.add("cheatcode","Selection","8th",() => { marabu.cheatcode.set_rate(8); },"8");
144 | this.controller.add("cheatcode","Selection","Offset +1",() => { marabu.cheatcode.set_offset(1); },"Right");
145 | this.controller.add("cheatcode","Selection","Offset -1",() => { marabu.cheatcode.set_offset(-1) },"Left");
146 | this.controller.add("cheatcode","Selection","Length +1",() => { marabu.cheatcode.set_length(1); },"Down");
147 | this.controller.add("cheatcode","Selection","Length -1",() => { marabu.cheatcode.set_length(-1) },"Up");
148 | this.controller.add("cheatcode","Keyboard","C",() => { marabu.cheatcode.ins(0); },"A");
149 | this.controller.add("cheatcode","Keyboard","C#",() => { marabu.cheatcode.ins(1); },"W");
150 | this.controller.add("cheatcode","Keyboard","D",() => { marabu.cheatcode.ins(2); },"S");
151 | this.controller.add("cheatcode","Keyboard","D#",() => { marabu.cheatcode.ins(3); },"E");
152 | this.controller.add("cheatcode","Keyboard","E",() => { marabu.cheatcode.ins(4); },"D");
153 | this.controller.add("cheatcode","Keyboard","F",() => { marabu.cheatcode.ins(5); },"F");
154 | this.controller.add("cheatcode","Keyboard","F#",() => { marabu.cheatcode.ins(6); },"T");
155 | this.controller.add("cheatcode","Keyboard","G",() => { marabu.cheatcode.ins(7); },"G");
156 | this.controller.add("cheatcode","Keyboard","G#",() => { marabu.cheatcode.ins(8); },"Y");
157 | this.controller.add("cheatcode","Keyboard","A",() => { marabu.cheatcode.ins(9); },"H");
158 | this.controller.add("cheatcode","Keyboard","A#",() => { marabu.cheatcode.ins(10); },"U");
159 | this.controller.add("cheatcode","Keyboard","B",() => { marabu.cheatcode.ins(11); },"J");
160 |
161 | this.controller.add("loop","*","Quit",() => { app.exit(); },"CmdOrCtrl+Q");
162 | this.controller.add("loop","Edit","Clear",() => { marabu.loop.cut(); },"X");
163 | this.controller.add("loop","Edit","Copy",() => { marabu.loop.copy(); },"C");
164 | this.controller.add("loop","Edit","Paste",() => { marabu.loop.paste(); },"V");
165 | this.controller.add("loop","Edit","Delete",() => { marabu.loop.erase(); },"Backspace");
166 | this.controller.add("loop","Select","Solo",() => { marabu.loop.solo(); },"/");
167 | this.controller.add("loop","Select","1 Row",() => { marabu.loop.set_height(0); },"1");
168 | this.controller.add("loop","Select","2 Rows",() => { marabu.loop.set_height(1); },"2");
169 | this.controller.add("loop","Select","3 Rows",() => { marabu.loop.set_height(2); },"3");
170 | this.controller.add("loop","Select","4 Rows",() => { marabu.loop.set_height(3); },"4");
171 | this.controller.add("loop","Select","5 Rows",() => { marabu.loop.set_height(4); },"5");
172 | this.controller.add("loop","Select","6 Rows",() => { marabu.loop.set_height(5); },"6");
173 | this.controller.add("loop","Select","7 Rows",() => { marabu.loop.set_height(6); },"7");
174 | this.controller.add("loop","Select","8 Rows",() => { marabu.loop.set_height(7); },"8");
175 | this.controller.add("loop","Select","Inc",() => { marabu.loop.mod(1); },"Down");
176 | this.controller.add("loop","Select","Dec",() => { marabu.loop.mod(-1); },"Up");
177 | this.controller.add("loop","Mode","Play",() => { marabu.loop.play(); },"Enter");
178 | this.controller.add("loop","Mode","Stop",() => { marabu.loop.stop(); },"Esc");
179 | this.controller.add("loop","Mode","render",() => { marabu.loop.render(); },"CmdOrCtrl+R");
180 |
181 |
182 | this.controller.add("arp","*","Quit",() => { app.exit(); },"CmdOrCtrl+Q");
183 | this.controller.add("arp","Mode","Pause/Stop",() => { marabu.arp.stop(); },"Esc");
184 | this.controller.add("arp","Keyboard","C",() => { marabu.arp.ins(0); },"A");
185 | this.controller.add("arp","Keyboard","C#",() => { marabu.arp.ins(1); },"W");
186 | this.controller.add("arp","Keyboard","D",() => { marabu.arp.ins(2); },"S");
187 | this.controller.add("arp","Keyboard","D#",() => { marabu.arp.ins(3); },"E");
188 | this.controller.add("arp","Keyboard","E",() => { marabu.arp.ins(4); },"D");
189 | this.controller.add("arp","Keyboard","F",() => { marabu.arp.ins(5); },"F");
190 | this.controller.add("arp","Keyboard","F#",() => { marabu.arp.ins(6); },"T");
191 | this.controller.add("arp","Keyboard","G",() => { marabu.arp.ins(7); },"G");
192 | this.controller.add("arp","Keyboard","G#",() => { marabu.arp.ins(8); },"Y");
193 | this.controller.add("arp","Keyboard","A",() => { marabu.arp.ins(9); },"H");
194 | this.controller.add("arp","Keyboard","A#",() => { marabu.arp.ins(10); },"U");
195 | this.controller.add("arp","Keyboard","B",() => { marabu.arp.ins(11); },"J");
196 | this.controller.add("arp","Keyboard","Skip",() => { marabu.arp.ins(-99); },"Space");
197 |
198 | this.controller.commit();
199 |
200 | setTimeout(marabu.show,250)
201 | }
202 |
203 | this.update = function()
204 | {
205 | this.selection.instrument = clamp(this.selection.instrument,0,this.channels-1);
206 | this.selection.track = clamp(this.selection.track,0,this.sequencer.length-1);
207 | this.selection.row = clamp(this.selection.row,0,31);
208 | this.selection.octave = clamp(this.selection.octave,3,8);
209 | this.selection.control = clamp(this.selection.control,0,23);
210 |
211 | this.song.update();
212 | this.sequencer.update();
213 | this.editor.update();
214 | this.instrument.update();
215 | }
216 |
217 | this.undo = function()
218 | {
219 | this.song.replace_song(this.history.prev());
220 | this.update();
221 | }
222 |
223 | this.redo = function()
224 | {
225 | this.song.replace_song(this.history.next());
226 | this.update();
227 | }
228 |
229 | // Controls
230 |
231 | this.move_inst = function(mod)
232 | {
233 | this.selection.instrument += mod;
234 | this.update();
235 | }
236 |
237 | this.move_pattern = function(mod)
238 | {
239 | var p = this.song.pattern_at(this.selection.instrument,this.selection.track) + mod;
240 | p = clamp(p,0,15);
241 | this.song.inject_pattern_at(this.selection.instrument,this.selection.track,p);
242 | this.update();
243 | }
244 |
245 | this.select_row = function(row)
246 | {
247 | this.selection.row = row;
248 | this.update();
249 | }
250 |
251 | this.move_row = function(mod)
252 | {
253 | this.selection.row += mod;
254 | this.update();
255 | }
256 |
257 | this.move_track = function(mod)
258 | {
259 | this.selection.track += mod;
260 | this.update();
261 | }
262 |
263 | this.move_octave = function(mod)
264 | {
265 | this.selection.octave += mod;
266 | this.update();
267 | }
268 |
269 | this.move_control = function(mod)
270 | {
271 | this.selection.control += mod;
272 | this.update();
273 | }
274 |
275 | this.move_bpm = function(mod)
276 | {
277 | this.song.song().bpm = this.song.get_bpm() + mod;
278 | this.song.update_bpm(this.song.get_bpm() + mod);
279 | this.history.push(this.song.song());
280 | this.update();
281 | }
282 |
283 | this.move_control_value = function(mod,relative)
284 | {
285 | var control = this.instrument.control_target(this.selection.control);
286 | control.mod(mod,relative);
287 | this.history.push(this.song.song());
288 | control.save();
289 | }
290 |
291 | this.move_control_min = function()
292 | {
293 | var control = this.instrument.control_target(this.selection.control);
294 | control.value = control.min;
295 | this.history.push(this.song.song());
296 | control.save();
297 | control.update();
298 | }
299 |
300 | this.move_control_max = function()
301 | {
302 | var control = this.instrument.control_target(this.selection.control);
303 | control.value = control.max;
304 | this.history.push(this.song.song());
305 | control.save();
306 | control.update();
307 | }
308 |
309 | this.add_control_value = function()
310 | {
311 | var control = this.instrument.control_target(this.selection.control);
312 | var control_storage = this.instrument.get_storage(control.family+"_"+control.id);
313 | var control_value = control.value;
314 |
315 | this.song.inject_effect_at(this.selection.instrument,this.selection.track,this.selection.row,control_storage+1,control_value);
316 | this.history.push(this.song.song());
317 | this.update();
318 | }
319 |
320 | this.remove_control_value = function()
321 | {
322 | var control = this.instrument.control_target(this.selection.control);
323 | var control_storage = this.instrument.get_storage(control.family+"_"+control.id);
324 | var control_value = control.value;
325 |
326 | this.song.erase_effect_at(this.selection.instrument,this.selection.track,this.selection.row);
327 | this.history.push(this.song.song());
328 | this.update();
329 | }
330 |
331 | this.set_note = function(val)
332 | {
333 | this.song.inject_note_at(this.selection.instrument,this.selection.track,this.selection.row,val-87);
334 |
335 | if(val == 0){
336 | this.song.inject_note_at(this.selection.instrument,this.selection.track,this.selection.row+32,val-87);
337 | }
338 | this.history.push(this.song.song());
339 | this.update();
340 | }
341 |
342 | this.move_note_value = function(mod)
343 | {
344 | var note = marabu.song.note_at(this.selection.instrument,this.selection.track,this.selection.row);
345 |
346 | this.song.inject_note_at(this.selection.instrument,this.selection.track,this.selection.row,note+mod-87);
347 | this.history.push(this.song.song());
348 | this.update();
349 | }
350 |
351 | this.play_note = function(note,right_hand = true)
352 | {
353 | var note_value = note + (this.selection.octave * 12);
354 | this.song.play_note(note_value);
355 | this.song.inject_note_at(this.selection.instrument,this.selection.track,this.selection.row+(right_hand ? 0 : 32),note_value);
356 | this.history.push(this.song.song());
357 | this.update();
358 | }
359 |
360 | // Methods
361 |
362 | this.is_playing = false;
363 |
364 | this.play = function()
365 | {
366 | if(this.selection.row > 0 || this.is_playing){ this.stop(); return; }
367 | console.log("Play!");
368 | this.song.play_song();
369 | this.is_playing = true;
370 | }
371 |
372 | this.stop = function()
373 | {
374 | console.log("Stop!");
375 | this.song.stop_song();
376 | this.instrument.controls.uv.monitor.clear();
377 | this.is_playing = false;
378 | this.selection.row = 0;
379 | this.update();
380 | }
381 |
382 | this.new = function()
383 | {
384 | this.history.clear();
385 | this.path = null;
386 | this.song = new Song();
387 | this.song.init();
388 | this.update();
389 | }
390 |
391 | this.path = null;
392 |
393 | this.open = function()
394 | {
395 | var filepath = dialog.showOpenDialog({filters: [{name: 'Marabu Files', extensions: ['mar', 'ins']}], properties: ['openFile']});
396 |
397 | if(!filepath){ console.log("Nothing to load"); return; }
398 |
399 | fs.readFile(filepath[0], 'utf-8', (err, data) => {
400 | if(err){ alert("An error ocurred reading the file :" + err.message); return; }
401 |
402 | marabu.load(data,filepath[0]);
403 | });
404 | this.history.clear();
405 | }
406 |
407 | this.load = function(data,path = "")
408 | {
409 | console.log("loading",path);
410 |
411 | var file_type = path.split(".")[path.split(".").length-1];
412 |
413 | if(file_type == "mar"){
414 | var o = JSON.parse(data);
415 | marabu.load_file(o);
416 | marabu.path = path;
417 | }
418 | else if(file_type == "ins"){
419 | var o = JSON.parse(data);
420 | marabu.load_instrument(o);
421 | }
422 | this.history.clear();
423 | }
424 |
425 | this.save = function()
426 | {
427 | if(!marabu.path){ marabu.export(); return; }
428 |
429 | fs.writeFile(marabu.path, marabu.song.to_string(), (err) => {
430 | if(err) { alert("An error ocurred updating the file" + err.message); console.log(err); return; }
431 | console.log("saved",marabu.path);
432 | var el = document.getElementById("fxr31");
433 | if(el){ el.className = "b_inv f_inv"; el.innerHTML = "--OK"; }
434 | });
435 | }
436 |
437 | this.export = function()
438 | {
439 | this.song.update_ranges();
440 | var str = this.song.to_string();
441 |
442 | dialog.showSaveDialog({filters:[{name:'Marabu',extensions:['mar']}]},(fileName) => {
443 | if (fileName === undefined){ return; }
444 | fs.writeFile(`${fileName.substr(-4,4) != ".mar" ? fileName+".mar" : fileName}`, str, (err) => {
445 | if(err){ alert("An error ocurred creating the file "+ err.message); return; }
446 | marabu.path = fileName;
447 | var el = document.getElementById("fxr31");
448 | if(el){ el.className = "b_inv f_inv"; el.innerHTML = "--OK"; }
449 | });
450 | });
451 | }
452 |
453 | this.load_file = function(track)
454 | {
455 | marabu.song.replace_song(track);
456 | this.history.clear();
457 | marabu.update();
458 | }
459 |
460 | this.export_instrument = function()
461 | {
462 | var instr = this.song.instrument();
463 | var instr_obj = {};
464 | instr_obj.name = instr.name;
465 | instr_obj.i = instr.i;
466 | var str = JSON.stringify(instr_obj);
467 |
468 | dialog.showSaveDialog({filters:[{name:"Instrument",extensions:['ins']}]},(fileName) => {
469 | if (fileName === undefined){ return; }
470 | fs.writeFile(`${fileName.substr(-4,4) != ".ins" ? fileName+".ins" : fileName}`, str, (err) => {
471 | if(err){ alert("An error ocurred creating the file "+ err.message); return; }
472 | });
473 | });
474 | }
475 |
476 | this.load_instrument = function(instr)
477 | {
478 | this.song.song().songData[this.selection.instrument].name = instr.name;
479 | this.song.song().songData[this.selection.instrument].i = instr.i;
480 | this.history.push(this.song.song());
481 | this.update();
482 | }
483 |
484 | this.render = function(val, is_passive = false)
485 | {
486 | this.song.export_wav();
487 | }
488 |
489 | this.reset = function()
490 | {
491 | this.history.clear();
492 | this.path = null;
493 | this.song = new Song();
494 | this.theme.reset();
495 | this.song.init();
496 | this.update();
497 | }
498 |
499 | this.show = function()
500 | {
501 | marabu.el.style.opacity = 1;
502 | }
503 |
504 | this.when_key = function(e)
505 | {
506 | var key = e.key;
507 |
508 | // These shortcuts are faster in repetition than the Electron managers.
509 |
510 | if(marabu.cheatcode.is_active == true){ return; }
511 | if(marabu.loop.is_active == true){ return; }
512 |
513 | // Arrows
514 | if(e.shiftKey){ // Instrument
515 | if(key == "ArrowDown") { marabu.move_control(1); e.preventDefault(); return; }
516 | if(key == "ArrowUp") { marabu.move_control(-1); e.preventDefault();return; }
517 | if(key == "ArrowRight"){ marabu.move_control_value(1,true); e.preventDefault(); return; }
518 | if(key == "ArrowLeft") { marabu.move_control_value(-1,true); e.preventDefault();return; }
519 | }
520 | else if(e.altKey || e.metaKey){
521 | if(key == "ArrowDown") { marabu.move_track(1); e.preventDefault(); return; }
522 | if(key == "ArrowUp") { marabu.move_track(-1); e.preventDefault();return; }
523 | if(key == "ArrowRight"){ marabu.move_pattern(1); e.preventDefault(); return; }
524 | if(key == "ArrowLeft") { marabu.move_pattern(-1); e.preventDefault();return; }
525 | }
526 | else{
527 | if(key == "ArrowRight"){ marabu.move_inst(1); e.preventDefault(); return; }
528 | if(key == "ArrowLeft") { marabu.move_inst(-1); e.preventDefault(); return; }
529 | if(key == "ArrowDown") { marabu.move_row(1); e.preventDefault(); return; }
530 | if(key == "ArrowUp") { marabu.move_row(-1); e.preventDefault(); return; }
531 | }
532 | if(key == "]") { marabu.move_control_value(1); e.preventDefault(); return; }
533 | if(key == "[") { marabu.move_control_value(-1); e.preventDefault(); return; }
534 | if(key == "}") { marabu.move_control_value(10); e.preventDefault(); return; }
535 | if(key == "{") { marabu.move_control_value(-10); e.preventDefault(); return; }
536 | }
537 | window.addEventListener("keydown", this.when_key, false);
538 | }
539 |
540 | window.addEventListener('dragover',function(e)
541 | {
542 | e.preventDefault();
543 | e.stopPropagation();
544 | e.dataTransfer.dropEffect = 'copy';
545 | });
546 |
547 | window.addEventListener('drop', function(e)
548 | {
549 | e.preventDefault();
550 | e.stopPropagation();
551 |
552 | var files = e.dataTransfer.files;
553 |
554 | for(file_id in files){
555 | var file = files[file_id];
556 | if(!file || !file.name || file.name.indexOf(".mar") == -1 && file.name.indexOf(".ins") == -1 && file.name.indexOf(".thm") == -1){ console.log("skipped",file); continue; }
557 |
558 | var path = file.path;
559 | var reader = new FileReader();
560 | reader.onload = function(e){
561 | var o = e.target.result;
562 | marabu.load(o,path);
563 | };
564 | reader.readAsText(file);
565 | return;
566 | }
567 | });
568 |
569 | window.onbeforeunload = function(e)
570 | {
571 |
572 | };
573 |
574 | // Tools
575 |
576 | var parse_note = function(val)
577 | {
578 | val -= 87;
579 | if(val < 0){ val += 87; }
580 | var keyboard = ['C','D','E','F','G','A','B'];
581 | var notes = ['C-', 'C#', 'D-', 'D#', 'E-', 'F-', 'F#', 'G-', 'G#', 'A-', 'A#', 'B-'];
582 | var octave = Math.floor((val)/12);
583 | var key = notes[(val) % 12];
584 | var key_sharp = key.substr(1,1) == "#" ? true : false;
585 | var key_note = key.substr(0,1);
586 | var offset = keyboard.indexOf(key_note);
587 | var distance = (keyboard.length*octave) + offset;
588 | return {id:val,octave:octave,sharp:key_sharp,note:key_note,offset:offset,distance:distance};
589 | }
590 |
591 | var hex_to_int = function(hex)
592 | {
593 | var hex = hex.toLowerCase();
594 | if(parseInt(hex) > 0){ return parseInt(hex); }
595 | if(hex == "a"){ return 10; }
596 | if(hex == "b"){ return 11; }
597 | if(hex == "c"){ return 12; }
598 | if(hex == "d"){ return 13; }
599 | if(hex == "e"){ return 14; }
600 | if(hex == "f"){ return 15; }
601 | return 0;
602 | }
603 |
604 | var prepend_to_length = function(str,length = 4,fill = "0")
605 | {
606 | var str = str+"";
607 |
608 | var offset = length - str.length;
609 |
610 | if(offset == 1){ return fill+str; }
611 | else if(offset == 2){ return fill+fill+str; }
612 | else if(offset == 3){ return fill+fill+fill+str; }
613 | else if(offset == 4){ return fill+fill+fill+fill+str; }
614 |
615 | return str
616 | }
617 |
618 | var to_hex_val = function(num)
619 | {
620 | if(num < 10){ return ""+num; }
621 | var l = ["a","b","c","d","e","f"];
622 | return l[(num-10) % l.length];
623 | }
624 |
625 | var to_hex = function(num, count = 1)
626 | {
627 | var s = num.toString(16).toUpperCase();
628 | for (var i = 0; i < (count - s.length); ++i){
629 | s = "0" + s;
630 | }
631 | return s;
632 | };
633 |
634 | var clamp = function(val,min,max)
635 | {
636 | val = val < min ? min : val;
637 | val = val > max ? max : val;
638 | return val;
639 | }
640 |
641 | var calcSamplesPerRow = function(bpm)
642 | {
643 | return Math.round((60 * 44100 / 4) / bpm);
644 | };
--------------------------------------------------------------------------------
/desktop/sources/scripts/sequencer.js:
--------------------------------------------------------------------------------
1 | function Sequencer()
2 | {
3 | var MIN_LENGTH = 30;
4 |
5 | var target = this;
6 |
7 | this.el = null;
8 | this.scrollbar_el = null;
9 | this.position_el = null;
10 | this.follower = new Follower();
11 | this.length = 0;
12 |
13 | this.start = function()
14 | {
15 | console.log("Started Sequencer");
16 |
17 | this.el = document.getElementById("sequencer");
18 | this.scrollbar_el = document.getElementById("scrollbar");
19 | this.position_el = document.getElementById("position");
20 | this.position_el.innerHTML = ""; this.position_el.className = "fl"
21 |
22 | this.build(MIN_LENGTH);
23 |
24 | this.el.addEventListener('wheel', function(e)
25 | {
26 | e.preventDefault();
27 | marabu.sequencer.el.scrollTop += e.wheelDeltaY * -0.25;
28 | marabu.sequencer.scrollbar_el.style.height = 480 * (marabu.sequencer.el.scrollTop/(marabu.sequencer.el.scrollHeight * 0.75))+"px";
29 | }, false);
30 | }
31 |
32 | this.build = function(target_length)
33 | {
34 | this.length = clamp(target_length,MIN_LENGTH,256);
35 | console.log("sequencer","build "+this.length)
36 |
37 | var table = document.getElementById("sequencer-table");
38 | table.innerHTML = "";
39 | var tr = document.createElement("tr");
40 | for (var t = 0; t < this.length; t++) {
41 | var tr = document.createElement("tr");
42 | tr.id = "spr"+t;
43 | for (var i = 0; i < 16; i++) {
44 | var td = document.createElement("td");
45 | td.id = "sc" + i + "t" + t;
46 | td.textContent = "-";
47 | td.addEventListener("mousedown", this.sequence_mouse_down, false);
48 | tr.appendChild(td);
49 | }
50 | table.appendChild(tr);
51 | }
52 | }
53 |
54 | this.sequence_mouse_down = function(e)
55 | {
56 | var c = e.target.id.substr(2)
57 |
58 | var i = parseInt(c.split("t")[0]);
59 | var r = parseInt(c.split("t")[1]);
60 |
61 | marabu.selection.instrument = i;
62 | marabu.selection.track = r;
63 | marabu.sequencer.follower.stop();
64 | marabu.update();
65 | }
66 |
67 | this.update = function()
68 | {
69 | var length = clamp(marabu.song.length,MIN_LENGTH,256) + 3;
70 | var active_pat = marabu.song.pattern_at(marabu.selection.instrument,marabu.selection.track);
71 |
72 | if(length != this.length){
73 | this.build(length);
74 | }
75 |
76 | for (var t = 0; t < this.length; ++t)
77 | {
78 | var tr = document.getElementById("spr" + t);
79 | tr.className = t == marabu.selection.track ? "bl" : "";
80 |
81 | for (var i = 0; i < 16; ++i)
82 | {
83 | var o = document.getElementById("sc" + i + "t" + t);
84 | var pat = marabu.song.pattern_at(i,t);
85 | // Default
86 | o.className = i == marabu.selection.instrument ? (pat && active_pat == pat ? "fh" : "fm") : (pat ? "fm" : "fl");
87 | o.textContent = pat ? to_hex(pat) : (t % 8 == 0 && i == 0 ? ">" : "-");
88 | // Selection
89 | if(marabu.loop.is_active && i >= marabu.loop.x && i < marabu.loop.x + marabu.loop.width+1 && t >= marabu.loop.y && t < marabu.loop.y + (marabu.loop.height+1)){ o.className = "b_inv f_inv"; }
90 | else if(t == marabu.selection.track && i == marabu.selection.instrument){ o.className = "fh"; }
91 | }
92 | }
93 |
94 | // Position
95 | var track_length = 100;
96 | var track_position = (marabu.selection.track * 32)+parseInt(marabu.selection.row);
97 | var track_time = parseInt(marabu.song.calculate_time(track_position/4));
98 | var track_min = parseInt(track_time/60);
99 | var track_sec = track_time % 60;
100 | this.position_el.innerHTML = `${track_position}~${marabu.song.get_bpm()}+${marabu.selection.octave}${prepend(track_min,2)}:${prepend(track_sec,2)}`;
101 | }
102 |
103 | function prepend(str,length,char = "0")
104 | {
105 | var fill = "";
106 | var i = 0;
107 | while(i < length - `${str}`.length){
108 | fill += char;
109 | i += 1
110 | }
111 | return `${fill}${str}`;
112 | }
113 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/song.js:
--------------------------------------------------------------------------------
1 | var Song = function()
2 | {
3 | var MAX_SONG_ROWS = 32,
4 | MAX_PATTERNS = 32;
5 |
6 | // Resources
7 | var mSong = {};
8 | var mAudio = null;
9 | var mAudioTimer = new CAudioTimer();
10 | var mPlayer = new CPlayer();
11 | var mJammer = new CJammer();
12 |
13 | this.mAudio = function(){ return mAudio; }
14 | this.mAudio_timer = function(){ return mAudioTimer; }
15 |
16 | this.mJammer = mJammer;
17 |
18 | var mPreload = [];
19 |
20 | //--------------------------------------------------------------------------
21 | // Song import/export functions
22 | //--------------------------------------------------------------------------
23 |
24 | this.player = function()
25 | {
26 | return mPlayer;
27 | }
28 |
29 | this.get_bpm = function()
30 | {
31 | return Math.round((60 * 44100 / 4) / mSong.rowLen);
32 | };
33 |
34 | this.update_bpm = function(bpm)
35 | {
36 | console.log(bpm)
37 | mSong.rowLen = calcSamplesPerRow(bpm);
38 | mJammer.updateRowLen(mSong.rowLen);
39 | }
40 |
41 | this.update_rpp = function(rpp)
42 | {
43 | setPatternLength(rpp);
44 | marabu.update();
45 | }
46 |
47 | this.play_note = function(note)
48 | {
49 | mJammer.addNote(note+87);
50 | }
51 |
52 | this.song = function()
53 | {
54 | return mSong;
55 | }
56 |
57 | this.to_string = function()
58 | {
59 | return JSON.stringify(this.song());
60 | }
61 |
62 | this.replace_song = function(new_song)
63 | {
64 | if(!new_song.name){ return; }
65 | stopAudio();
66 |
67 | mSong = new_song;
68 |
69 | this.update_bpm(mSong.bpm ? mSong.bpm : 120);
70 | this.update_rpp(32);
71 |
72 | // Inject names
73 | for(id in this.song().songData){
74 | var ins = this.song().songData[id];
75 | ins.name = ins.name && ins.name.length > 3 ? ins.name.substr(0,4) : `INS${to_hex_val(id).toUpperCase()}`
76 | }
77 |
78 | updateSongRanges();
79 | }
80 |
81 | this.mJammer_update = function()
82 | {
83 | return mJammer.updateInstr(this.instrument().i);
84 | }
85 |
86 | this.instrument = function(id = marabu.selection.instrument)
87 | {
88 | return this.song().songData[id];
89 | }
90 |
91 | //
92 |
93 | this.pattern_at = function(i,t)
94 | {
95 | return this.song().songData[i].p[t];
96 | }
97 |
98 | this.inject_pattern_at = function(i,t,v)
99 | {
100 | this.song().songData[i].p[t] = v;
101 | this.update_ranges();
102 | marabu.update();
103 | }
104 |
105 | this.note_at = function(i,t,n)
106 | {
107 | var c = this.pattern_at(i,t)-1; if(c == -1){ return; }
108 | return this.song().songData[i].c[c].n[n];
109 | }
110 |
111 | this.inject_note_at = function(i,t,n,v)
112 | {
113 | var c = this.pattern_at(i,t)-1; if(c == -1){ return; }
114 | this.song().songData[i].c[c].n[n] = (v == -87 ? 0 : clamp(v,36,107)+87);
115 | }
116 |
117 | this.effect_at = function(i,t,f)
118 | {
119 | var c = this.pattern_at(i,t)-1; if(c == -1){ return; }
120 | return this.song().songData[i].c[c].f[f];
121 | }
122 |
123 | this.effect_value_at = function(i,t,f)
124 | {
125 | var c = this.pattern_at(i,t)-1; if(c == -1){ return; }
126 | return this.song().songData[i].c[c].f[f+32];
127 | }
128 |
129 | this.inject_effect_at = function(i,t,f,cmd,val)
130 | {
131 | if(!cmd || val === undefined){ return; }
132 | var c = this.pattern_at(i,t)-1; if(c == -1){ return; }
133 | this.song().songData[i].c[c].f[f] = cmd;
134 | this.song().songData[i].c[c].f[f+32] = val;
135 | }
136 |
137 | this.erase_effect_at = function(i,t,f)
138 | {
139 | var c = this.pattern_at(i,t)-1; if(c == -1){ return; }
140 | this.song().songData[i].c[c].f[f] = 0;
141 | this.song().songData[i].c[c].f[f+32] = 0;
142 | }
143 |
144 | this.control_at = function(i,s)
145 | {
146 | return this.song().songData[i].i[s];
147 | }
148 |
149 | this.inject_control = function(i,s,v)
150 | {
151 | this.song().songData[i].i[s] = v;
152 | this.mJammer_update();
153 | }
154 |
155 | var setPatternLength = function (length)
156 | {
157 | if (mSong.patternLen === length)
158 | return;
159 |
160 | stopAudio();
161 |
162 | var i, j, k, col, notes, fx;
163 | for (i = 0; i < 8; i++) {
164 | for (j = 0; j < MAX_PATTERNS; j++) {
165 | col = mSong.songData[i].c[j];
166 | notes = [];
167 | fx = [];
168 | for (k = 0; k < 4 * length; k++)
169 | notes[k] = 0;
170 | for (k = 0; k < 2 * length; k++)
171 | fx[k] = 0;
172 | for (k = 0; k < Math.min(mSong.patternLen, length); k++) {
173 | notes[k] = col.n[k];
174 | notes[k + length] = col.n[k + mSong.patternLen];
175 | notes[k + 2 * length] = col.n[k + 2 * mSong.patternLen];
176 | notes[k + 3 * length] = col.n[k + 3 * mSong.patternLen];
177 | fx[k] = col.f[k];
178 | fx[k + length] = col.f[k + mSong.patternLen];
179 | }
180 | col.n = notes;
181 | col.f = fx;
182 | }
183 | }
184 |
185 | // Update pattern length
186 | mSong.patternLen = length;
187 | };
188 |
189 | this.update_ranges = function()
190 | {
191 | updateSongRanges();
192 | }
193 |
194 | var updateSongRanges = function ()
195 | {
196 | var i, j, emptyRow;
197 |
198 | // Determine the last song pattern
199 | mSong.endPattern = marabu.sequencer.length + 1;
200 | for (i = marabu.sequencer.length; i >= 0; --i) {
201 | emptyRow = true;
202 | for (j = 0; j < 16; ++j) {
203 | if (mSong.songData[j].p[i] > 0) {
204 | emptyRow = false;
205 | break;
206 | }
207 | }
208 | if (!emptyRow) break;
209 | mSong.endPattern--;
210 | }
211 | };
212 |
213 | this.export_wav = function(opts = null)
214 | {
215 | updateSongRanges();
216 |
217 | var doneFun = function(wave)
218 | {
219 | dialog.showSaveDialog({filters:[{name:'Audio File',extensions:['wav']}]},(fileName) => {
220 | if (fileName === undefined){ return; }
221 | fs.writeFile(`${fileName.substr(-4,4) != ".wav" ? fileName+".wav" : fileName}`, new Buffer(wave), (err) => {
222 | if(err){ alert("An error ocurred creating the file "+ err.message); return; }
223 | });
224 | });
225 | };
226 | generateAudio(doneFun,opts);
227 | };
228 |
229 | this.calculate_time = function(pos = (8 * (marabu.song.length+1)))
230 | {
231 | var bpm = parseFloat(marabu.song.song().bpm);
232 | var beats = pos;
233 | var minutes = beats/bpm;
234 | var seconds = minutes * 60;
235 |
236 | return seconds;
237 | }
238 |
239 | var generateAudio = function(doneFun, opts, override_song = null)
240 | {
241 | var display_progress_el = document.getElementById("fxr31");
242 | var song = mSong;
243 | var render_time = marabu.song.calculate_time();
244 | var minutes = Math.floor(render_time/60.0);
245 | var seconds = Math.floor(render_time % 60);
246 |
247 | var d1 = new Date();
248 | mPlayer = new CPlayer();
249 | mPlayer.generate(song, opts, function(progress){
250 | if(progress >= 1){
251 | var wave = mPlayer.createWave();
252 | var d2 = new Date();
253 | doneFun(wave);
254 | display_progress_el.className = "fl";
255 | }
256 | else{
257 | display_progress_el.className = "b_inv f_inv";
258 | display_progress_el.textContent = prepend_to_length(parseInt(progress * 100),4,"0");
259 | }
260 | });
261 | };
262 |
263 | var stopAudio = function ()
264 | {
265 | marabu.sequencer.follower.stop();
266 | if(mAudio) {
267 | mAudio.pause();
268 | mAudioTimer.reset();
269 | }
270 | };
271 |
272 | this.stop_song = function()
273 | {
274 | stopAudio();
275 | marabu.selection.row = 0;
276 | marabu.update();
277 | }
278 |
279 | this.start_over = function()
280 | {
281 | this.currentTime = 0;
282 | this.play();
283 | }
284 |
285 | this.play_song = function()
286 | {
287 | mAudio.removeEventListener('ended', marabu.song.start_over, false);
288 |
289 | this.update();
290 | this.update_bpm(this.song().bpm);
291 | this.update_rpp(32);
292 |
293 | stopAudio();
294 | updateSongRanges();
295 |
296 | var doneFun = function(wave)
297 | {
298 | console.log("playing..")
299 | marabu.sequencer.follower.start();
300 | mAudio.src = URL.createObjectURL(new Blob([wave], {type: "audio/wav"}));
301 | mAudioTimer.reset();
302 | mAudio.play();
303 | };
304 | generateAudio(doneFun);
305 | }
306 |
307 | this.play_loop = function(opts,looped_song)
308 | {
309 | mAudio.addEventListener('ended', marabu.song.start_over, false);
310 |
311 | this.update_bpm(this.song().bpm);
312 | this.update_rpp(32);
313 |
314 | this.stop_song();
315 | updateSongRanges();
316 |
317 | var offset = opts.firstRow;
318 |
319 | var doneFun = function(wave)
320 | {
321 | console.log("playing..",offset)
322 | marabu.sequencer.follower.start(offset);
323 | mAudio.src = URL.createObjectURL(new Blob([wave], {type: "audio/wav"}));
324 | mAudioTimer.reset();
325 | mAudio.play();
326 | };
327 | generateAudio(doneFun,opts,looped_song);
328 | }
329 |
330 | this.length = 0;
331 | this.is_looping = false;
332 |
333 | this.update = function()
334 | {
335 | this.validate();
336 | this.update_length();
337 | }
338 |
339 | this.validate = function()
340 | {
341 | for(var i = 0; i < 16; ++i) {
342 | var offset = (this.length+34) - mSong.songData[i].p.length;
343 | if(offset < 0){ continue; }
344 | // Fill
345 | for(var fill = 0; fill < offset; ++fill){
346 | mSong.songData[i].p.push(0);
347 | }
348 | }
349 | }
350 |
351 | this.update_length = function()
352 | {
353 | var l = 0;
354 | for(var i = 0; i < 16; ++i) {
355 | for(var p = 0; p < mSong.songData[i].p.length; ++p){
356 | if(mSong.songData[i].p[p] > 0 && p > l){ l = p; }
357 | }
358 | }
359 | this.length = l;
360 | }
361 |
362 | //--------------------------------------------------------------------------
363 | // Initialization
364 | //--------------------------------------------------------------------------
365 |
366 | this.init = function ()
367 | {
368 | var i, j, o;
369 |
370 | // Create audio element, and always play the audio as soon as it's ready
371 | mAudio = new Audio();
372 | mAudioTimer.setAudioElement(mAudio);
373 | mAudio.addEventListener("canplay", function (){ this.play(); }, true);
374 |
375 | mSong = new Track();
376 |
377 | mJammer.start();
378 | mJammer.updateRowLen(mSong.rowLen);
379 | };
380 | };
381 |
382 | var CAudioTimer = function ()
383 | {
384 | var mAudioElement = null;
385 | var mStartT = 0;
386 | var mErrHist = [0, 0, 0, 0, 0, 0];
387 | var mErrHistPos = 0;
388 |
389 | this.setAudioElement = function (audioElement)
390 | {
391 | mAudioElement = audioElement;
392 | }
393 |
394 | this.currentTime = function ()
395 | {
396 | if (!mAudioElement)
397 | return 0;
398 |
399 | // Calculate current time according to Date()
400 | var t = (new Date()).getTime() * 0.001;
401 | var currentTime = t - mStartT;
402 |
403 | // Get current time according to the audio element
404 | var audioCurrentTime = mAudioElement.currentTime;
405 |
406 | // Check if we are off by too much - in which case we will use the time
407 | // from the audio element
408 | var err = audioCurrentTime - currentTime;
409 | if (audioCurrentTime < 0.01 || err > 0.2 || err < -0.2) {
410 | currentTime = audioCurrentTime;
411 | mStartT = t - currentTime;
412 | for (var i = 0; i < mErrHist.length; i++)
413 | mErrHist[i] = 0;
414 | }
415 |
416 | // Error compensation (this should fix the problem when we're constantly
417 | // slightly off)
418 | var comp = 0;
419 | for (var i = 0; i < mErrHist.length; i++)
420 | comp += mErrHist[i];
421 | comp /= mErrHist.length;
422 | mErrHist[mErrHistPos] = err;
423 | mErrHistPos = (mErrHistPos + 1) % mErrHist.length;
424 |
425 | return currentTime + comp;
426 | };
427 |
428 | this.reset = function ()
429 | {
430 | mStartT = (new Date()).getTime() * 0.001;
431 | for (var i = 0; i < mErrHist.length; i++){
432 | mErrHist[i] = 0;
433 | }
434 | };
435 | };
--------------------------------------------------------------------------------
/desktop/sources/scripts/track.js:
--------------------------------------------------------------------------------
1 | function Track()
2 | {
3 | var MAX_SONG_ROWS = 32, MAX_PATTERNS = 32, MAX_INSTRUMENTS = 16;
4 |
5 | var song = {}, i, j, k, instr, col;
6 |
7 | // Settings
8 | song.artist = "Unknown";
9 | song.name = "Untitled";
10 | song.bpm = 120;
11 |
12 | song.theme = new Theme().default;
13 |
14 | // Automated
15 | song.rowLen = calcSamplesPerRow(song.bpm);
16 | song.endPattern = 2;
17 | song.patternLen = 32;
18 |
19 | // All 8 instruments
20 | song.songData = [];
21 | for (i = 0; i < MAX_INSTRUMENTS; i++){
22 | instr = {};
23 | instr.i = [15,67,111,0,0,100,111,6,0,0,0,0,153,0,20,0,0,8,64,2,205,90,0,63,127,0,0,0,190,0,0];
24 |
25 | // Sequence
26 | instr.p = [];
27 | for (j = 0; j < MAX_SONG_ROWS; j++)
28 | instr.p[j] = 0;
29 |
30 | // Patterns
31 | instr.c = [];
32 | for (j = 0; j < MAX_PATTERNS; j++)
33 | {
34 | col = {};
35 | col.n = [];
36 | for (k = 0; k < song.patternLen * 2; k++)
37 | col.n[k] = 0;
38 | col.f = [];
39 | for (k = 0; k < song.patternLen * 2; k++)
40 | col.f[k] = 0;
41 | instr.c[j] = col;
42 | }
43 | song.songData[i] = instr;
44 | }
45 |
46 | // Make a first empty pattern
47 | song.songData[0].p[0] = 1;
48 |
49 | song.songData[0].name = "SYN1"
50 | song.songData[1].name = "SYN2"
51 | song.songData[2].name = "PAD1"
52 | song.songData[3].name = "PAD2"
53 |
54 | song.songData[4].name = "IDM1"
55 | song.songData[5].name = "IDM2"
56 | song.songData[6].name = "TXT1"
57 | song.songData[7].name = "TXT2"
58 |
59 | song.songData[8].name = "SNAR"
60 | song.songData[9].name = "CLAP"
61 | song.songData[10].name = "BASS"
62 | song.songData[11].name = "KICK"
63 |
64 | song.songData[12].name = "INST"
65 | song.songData[13].name = "INST"
66 | song.songData[14].name = "INST"
67 | song.songData[15].name = "INST"
68 |
69 | return song;
70 | };
--------------------------------------------------------------------------------
/desktop/sources/scripts/ui/choice.js:
--------------------------------------------------------------------------------
1 | function UI_Choice(data)
2 | {
3 | this.family = null;
4 | this.id = data.id;
5 | this.name = data.name;
6 | this.choices = data.choices;
7 |
8 | this.control = 0;
9 |
10 | this.el = document.createElement("div");
11 | this.name_el = document.createElement("t");
12 | this.value_el = document.createElement("t");
13 |
14 | this.value = 0;
15 |
16 | var target = this;
17 |
18 | this.install = function(parent)
19 | {
20 | this.el.className = "control choice";
21 | // Name Span
22 | this.name_el.className = "name";
23 | this.name_el.innerHTML = this.name;
24 |
25 | this.value_el.textContent = this.min+"/"+this.max;
26 | this.value_el.className = "value";
27 |
28 | this.el.appendChild(this.name_el);
29 | this.el.appendChild(this.value_el);
30 |
31 | this.el.addEventListener("mousedown", this.mouse_down, false);
32 |
33 | parent.appendChild(this.el);
34 | this.storage = marabu.instrument.get_storage(this.family+"_"+this.id);
35 | }
36 |
37 | this.mod = function(v)
38 | {
39 | this.value += v > 0 ? 1 : -1;
40 | this.value = this.value % this.choices.length;
41 | this.value = this.value < 0 ? this.choices.length-1 : this.value;
42 | this.update();
43 | }
44 |
45 | this.override = function(v)
46 | {
47 | if(v == null){ console.log("Missing control value",this.family+"."+this.id); return;}
48 |
49 | var v = v % this.choices.length;
50 | this.value = v;
51 | this.update();
52 | }
53 |
54 | this.save = function()
55 | {
56 | var storage_id = marabu.instrument.get_storage(this.family+"_"+this.id);
57 | marabu.song.inject_control(marabu.selection.instrument,storage_id,this.value % this.choices.length);
58 | }
59 |
60 | this.update = function()
61 | {
62 | var target = this.choices[this.value % this.choices.length];
63 | this.value_el.textContent = target;
64 |
65 | this.el.className = marabu.selection.control == this.control ? "control choice bl" : "control choice ";
66 | this.name_el.className = marabu.selection.control == this.control ? "name fh" : "name fm";
67 |
68 | // Keyframes
69 | if(this.has_keyframes()){
70 | this.name_el.className = "name b_inv f_inv";
71 | }
72 | }
73 |
74 | this.has_keyframes = function()
75 | {
76 | var i = marabu.selection.instrument;
77 | var t = 0;
78 | var f = this.storage;
79 | while(t <= marabu.selection.track){
80 | var r = 0;
81 | while(r < 32){
82 | var cmd = marabu.song.effect_at(i,t,r)
83 | if(cmd == f+1){
84 | return marabu.song.effect_value_at(i,t,r);
85 | }
86 | r += 1;
87 | }
88 | t += 1;
89 | }
90 | return null;
91 | }
92 |
93 | this.mouse_down = function(e)
94 | {
95 | marabu.selection.control = target.control;
96 | marabu.update();
97 | }
98 | }
--------------------------------------------------------------------------------
/desktop/sources/scripts/ui/slider.js:
--------------------------------------------------------------------------------
1 | function UI_Slider(data)
2 | {
3 | var app = marabu;
4 | var self = this;
5 |
6 | this.family = null;
7 | this.id = data.id;
8 | this.storage = 0;
9 |
10 | this.name = data.name;
11 | this.min = data.min;
12 | this.max = data.max;
13 |
14 | this.control = 0;
15 | this.center = data.center;
16 | this.percent = data.percent;
17 |
18 | this.value = this.min;
19 |
20 | this.el = document.createElement("div");
21 | this.name_el = document.createElement("t");
22 | this.value_el = document.createElement("t");
23 | this.slide_el = document.createElement("div"); this.slide_el.className = "slide";
24 |
25 | this.install = function(parent)
26 | {
27 | this.el.className = `control slider ${this.center ? 'center' : ''}`;
28 |
29 | // Name Span
30 | this.name_el.className = "name";
31 | this.name_el.innerHTML = this.name;
32 |
33 | // Value Input
34 | this.value_el.className = "value";
35 | this.value_el.textContent = "--";
36 |
37 | this.el.appendChild(this.name_el);
38 | this.el.appendChild(this.slide_el);
39 | this.el.appendChild(this.value_el);
40 |
41 | this.el.addEventListener("mousedown", this.mouse_down, false);
42 |
43 | parent.appendChild(this.el);
44 | this.storage = marabu.instrument.get_storage(this.family+"_"+this.id);
45 | }
46 |
47 | this.mod = function(v,relative = false)
48 | {
49 | if(relative && this.max > 128){ v *= 10; }
50 | if(this.max <= 64 && (v > 1 || v < 1) && Math.abs(v) != 1){ v = v/10;}
51 | this.value += parseInt(v);
52 | this.value = clamp(this.value,this.min,this.max);
53 | this.update();
54 | }
55 |
56 | this.override = function(v)
57 | {
58 | if(v == null){ console.log("Missing control value",this.family+"."+this.id); return;}
59 |
60 | this.value = parseInt(v);
61 | this.value = clamp(this.value,this.min,this.max);
62 | this.update();
63 | }
64 |
65 | this.save = function()
66 | {
67 | marabu.song.inject_control(marabu.selection.instrument,this.storage,this.value);
68 | }
69 |
70 | this.update = function()
71 | {
72 | var val = parseInt(this.value) - parseInt(this.min);
73 | var over = parseFloat(this.max) - parseInt(this.min);
74 | var perc = val/parseFloat(over);
75 | var val_mod = this.center ? this.value - Math.floor(this.max/2) : this.value
76 | var keyframe = this.last_keyframe();
77 |
78 | var c = ""
79 | c = 'slider control '
80 | c += app.selection.control == this.control ? 'selected ' : '';
81 | c += keyframe ? 'keyframed ' : '';
82 | c += this.value == this.min ? 'min ' : ''
83 | c += this.value == this.max ? 'max ' : ''
84 |
85 | this.el.className = c;
86 | this.value_el.textContent = app.selection.control != this.control && keyframe ? '$'+keyframe : (keyframe ? '$' : '')+val_mod;
87 | this.update_display(perc);
88 | }
89 |
90 | this.last_keyframe = function()
91 | {
92 | var i = app.selection.instrument;
93 | var t = app.selection.track;
94 | var f = this.storage;
95 | while(t >= 0){
96 | var r = app.selection.track == t ? app.selection.row : 32;
97 | while(r >= 0){
98 | var cmd = app.song.effect_at(i,t,r)
99 | if(cmd == f+1){
100 | return app.song.effect_value_at(i,t,r);
101 | }
102 | r -= 1;
103 | }
104 | t -= 1;
105 | }
106 | return null;
107 | }
108 |
109 | this.update_display = function(perc)
110 | {
111 | var html = ""
112 | var c = 0;
113 | while(c < perc*80){
114 | html += "-"
115 | c += 10;
116 | }
117 | html = `${html}`
118 | while(c < 80){
119 | html += "-"
120 | c += 10
121 | }
122 | this.slide_el.innerHTML = `${html}`
123 | }
124 |
125 | this.mouse_down = function(e)
126 | {
127 | app.selection.control = self.control;
128 | app.update();
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/desktop/sources/scripts/ui/uv.js:
--------------------------------------------------------------------------------
1 | function UI_Uv()
2 | {
3 | this.el = document.createElement("div");
4 |
5 | this.vol_el = document.createElement("div");
6 | this.vol_name_el = document.createElement("t");
7 | this.vol_canvas = document.createElement("canvas");
8 | this.env_el = document.createElement("div");
9 | this.env_name_el = document.createElement("t");
10 | this.env_canvas = document.createElement("canvas");
11 | this.wav_el = document.createElement("div");
12 | this.wav_name_el = document.createElement("t");
13 | this.wav_canvas = document.createElement("canvas");
14 |
15 | this.size = {width:45,height:15};
16 |
17 | var mFollowerLastVULeft = 0;
18 | var mFollowerLastVURight = 0;
19 |
20 | this.install = function(parent)
21 | {
22 | this.el.className = "control uv";
23 |
24 | this.el.appendChild(this.env_el);
25 | this.el.appendChild(this.vol_el);
26 | this.el.appendChild(this.wav_el);
27 |
28 | this.env_el.appendChild(this.env_name_el);
29 | this.env_el.appendChild(this.env_canvas);
30 |
31 | this.vol_el.appendChild(this.vol_name_el);
32 | this.vol_el.appendChild(this.vol_canvas);
33 |
34 | this.wav_el.appendChild(this.wav_name_el);
35 | this.wav_el.appendChild(this.wav_canvas);
36 |
37 | this.vol_el.style.height = "15px";
38 | this.env_el.style.height = "15px";
39 | this.wav_el.style.height = "15px";
40 |
41 | this.vol_el.style.marginTop = "-15px";
42 |
43 | this.vol_name_el.className = "name fl";
44 | this.vol_name_el.innerHTML = "VOL";
45 |
46 | this.vol_canvas.style.width = this.size.width+"px";
47 | this.vol_canvas.style.height = this.size.height+"px";
48 | this.vol_canvas.width = this.size.width * 2;
49 | this.vol_canvas.height = this.size.height * 2;
50 |
51 | this.env_name_el.className = "name fl";
52 | this.env_name_el.innerHTML = "";
53 |
54 | this.env_canvas.style.width = this.size.width+"px";
55 | this.env_canvas.style.height = this.size.height+"px";
56 | this.env_canvas.width = this.size.width * 2;
57 | this.env_canvas.height = this.size.height * 2;
58 |
59 | this.wav_name_el.className = "name fl lh15";
60 | this.wav_name_el.innerHTML = "WAV";
61 |
62 | this.wav_canvas.style.width = this.size.width+"px";
63 | this.wav_canvas.style.height = this.size.height+"px";
64 | this.wav_canvas.width = this.size.width * 2;
65 | this.wav_canvas.height = this.size.height * 2;
66 |
67 | this.el.style.width = "120px";
68 |
69 | var vol_ctx = this.vol_canvas.getContext("2d");
70 | var env_ctx = this.env_canvas.getContext("2d");
71 | var wav_ctx = this.env_canvas.getContext("2d");
72 |
73 | vol_ctx.setLineDash([2, 2]);
74 | vol_ctx.lineWidth = 2;
75 | env_ctx.setLineDash([2, 2]);
76 | env_ctx.lineWidth = 2;
77 | wav_ctx.setLineDash([2, 2]);
78 | wav_ctx.lineWidth = 2;
79 |
80 | this.clear();
81 |
82 | parent.appendChild(this.el);
83 | }
84 |
85 | var getSamplesSinceNote = function (t, chan)
86 | {
87 | var mSong = marabu.song.song();
88 | var nFloat = t * 44100 / mSong.rowLen;
89 | var n = Math.floor(nFloat);
90 | var seqPos0 = Math.floor(n / mSong.patternLen);
91 | var patPos0 = n % mSong.patternLen;
92 | for (var k = 0; k < mSong.patternLen; ++k) {
93 | var seqPos = seqPos0;
94 | var patPos = patPos0 - k;
95 | while (patPos < 0) {
96 | --seqPos;
97 | patPos += mSong.patternLen;
98 | }
99 | var pat = mSong.songData[chan].p[seqPos] - 1;
100 | for (var patCol = 0; patCol < 4; patCol++) {
101 | if (pat >= 0 && mSong.songData[chan].c[pat].n[patPos+patCol*mSong.patternLen] > 0)
102 | return (k + (nFloat - n)) * mSong.rowLen;
103 | }
104 | }
105 | return -1;
106 | };
107 |
108 | this.clear = function()
109 | {
110 | var vol_ctx = this.vol_canvas.getContext("2d");
111 | var env_ctx = this.env_canvas.getContext("2d");
112 | var wav_ctx = this.env_canvas.getContext("2d");
113 |
114 | vol_ctx.clearRect(0, 0, this.size.width * 2, this.size.height * 2);
115 | env_ctx.clearRect(0, 0, this.size.width * 2, this.size.height * 2);
116 | wav_ctx.clearRect(0, 0, this.size.width * 2, this.size.height * 2);
117 |
118 | env_ctx.strokeStyle = marabu.theme.active.f_low;
119 | env_ctx.beginPath();
120 | env_ctx.moveTo(0,15);
121 | env_ctx.lineTo(this.size.width * 2,15);
122 | env_ctx.stroke();
123 | }
124 |
125 | this.override = function()
126 | {
127 |
128 | }
129 |
130 | this.update = function()
131 | {
132 | this.draw(-1);
133 | }
134 |
135 | this.draw = function(t)
136 | {
137 | if(t <= 0){ return; }
138 |
139 | this.clear();
140 |
141 | var pl = 0, pr = 0;
142 | var vol_ctx = this.vol_canvas.getContext("2d");
143 | var env_ctx = this.env_canvas.getContext("2d");
144 | var wav_ctx = this.wav_canvas.getContext("2d");
145 |
146 | // Get the waveform
147 | var wave = marabu.song.player().getData(t, 1000);
148 |
149 | wav_ctx.clearRect(0, 0, this.size.width * 2, this.size.height * 2);
150 | wav_ctx.strokeStyle = marabu.theme.active.f_high;
151 | wav_ctx.beginPath();
152 | wav_ctx.setLineDash([2, 2]);
153 | wav_ctx.lineWidth = 2;
154 | wav_ctx.moveTo(0,this.size.height);
155 |
156 | // Calculate volume
157 | var i, l, r;
158 | var sl = 0, sr = 0, l_old = 0, r_old = 0;
159 | for (i = 1; i < wave.length; i += 2)
160 | {
161 | this.clear();
162 |
163 | l = wave[i-1];
164 | r = wave[i];
165 |
166 | // Band-pass filter (low-pass + high-pass)
167 | sl = 0.8 * l + 0.1 * sl - 0.3 * l_old;
168 | sr = 0.8 * r + 0.1 * sr - 0.3 * r_old;
169 | l_old = l;
170 | r_old = r;
171 |
172 | // Sum of squares
173 | pl += sl * sl;
174 | pr += sr * sr;
175 |
176 | var last_x = 0;
177 | var x = parseInt(i/20);
178 |
179 | if(parseInt(x) != parseInt(last_x) && x < this.size.width * 2){
180 | var mod = ((l+r)/2.0) * 20;
181 | var y = this.size.height + mod;
182 | y = clamp(y,0,this.size.height*2);
183 | wav_ctx.lineTo(x, y);
184 | last_x = x;
185 | }
186 | }
187 | wav_ctx.stroke();
188 |
189 | // Low-pass filtered mean power (RMS)
190 | pl = Math.sqrt(pl / wave.length) * 0.2 + mFollowerLastVULeft * 0.8;
191 | pr = Math.sqrt(pr / wave.length) * 0.2 + mFollowerLastVURight * 0.8;
192 | mFollowerLastVULeft = pl;
193 | mFollowerLastVURight = pr;
194 |
195 | var index = ((pl+pr)/2) * 4.0;
196 |
197 | vol_ctx.strokeStyle = marabu.theme.active.f_high;
198 | vol_ctx.beginPath();
199 | vol_ctx.moveTo(0,15);
200 | vol_ctx.lineTo((this.size.width * 2) * index,15);
201 | vol_ctx.stroke();
202 |
203 | for (i = 0; i < 16; ++i)
204 | {
205 | var env_a = marabu.song.song().songData[i].i[10],
206 | env_s = marabu.song.song().songData[i].i[11],
207 | env_r = marabu.song.song().songData[i].i[12];
208 | env_a = env_a * env_a * 4;
209 | env_r = env_s * env_s * 4 + env_r * env_r * 4;
210 | var env_tot = env_a + env_r;
211 | if (env_tot < 10000)
212 | {
213 | env_tot = 10000;
214 | env_r = env_tot - env_a;
215 | }
216 |
217 | var numSamp = getSamplesSinceNote(t, i);
218 | if (numSamp >= 0 && numSamp < env_tot)
219 | {
220 | var alpha = (numSamp < env_a) ? alpha = numSamp / env_a : 1 - (numSamp - env_a) / env_r;
221 | env_ctx.strokeStyle = marabu.theme.active.f_med;
222 | env_ctx.beginPath();
223 | env_ctx.moveTo(0,15);
224 | env_ctx.lineTo((this.size.width * 2) * alpha,15);
225 | env_ctx.stroke();
226 | }
227 | }
228 |
229 | }
230 |
231 | function clamp(v, min, max) { return v < min ? min : v > max ? max : v; }
232 | }
--------------------------------------------------------------------------------