change menu and start saving process

convert file to state wip

finish file to state conversion

load in file to state (needs to be fixed in order to properly load some things in)

formatting

Add microkorg sysex decoding

Correctly parse bit array sections

Add sysex encoding and header creation

remove log

Finish format file v1

Fix a definitions

Move stuff around

add file to store functions

Finish prepare_timbre

begin to Setup menu

Gut a lot of stuff, and implement the menu

Begin overhaul of fileupload process

Finish hooking up the menu

clean up

start saving functions

add unused state, mostly finish saveStore

add self reminder

complete store saving process 1

store to simplified store is complete
This commit is contained in:
[poww10s]
2021-04-01 03:54:43 -04:00
committed by Aoife
parent aa429428d5
commit 8a35046237
37 changed files with 3113 additions and 1764 deletions
+8 -3
View File
@@ -3765,13 +3765,18 @@
"optional": true "optional": true
}, },
"binary-parser-encoder": { "binary-parser-encoder": {
"version": "1.5.2", "version": "1.5.3",
"resolved": "https://registry.npmjs.org/binary-parser-encoder/-/binary-parser-encoder-1.5.2.tgz", "resolved": "https://registry.npmjs.org/binary-parser-encoder/-/binary-parser-encoder-1.5.3.tgz",
"integrity": "sha512-bZKR5Qo+pujq+Pvye/ZR94aRbYJx+7WijKAD7RcFi6qgtNJ4kHqAbDaH588VM2N4HylWp08Pm2l0buXqh2t5kw==", "integrity": "sha512-yu3tdLBYqPIwGRaXyswLoLrhaffkuZkNuXveq/jYoyBHQbFMjamHCWPFOmI2Qz+Go0Rh6wE9f6tt0EAvsgDD0g==",
"requires": { "requires": {
"smart-buffer": "^4.1.0" "smart-buffer": "^4.1.0"
} }
}, },
"bitset": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bitset/-/bitset-5.1.1.tgz",
"integrity": "sha512-oKaRp6mzXedJ1Npo86PKhWfDelI6HxxJo+it9nAcBB0HLVvYVp+5i6yj6DT5hfFgo+TS5T57MRWtw8zhwdTs3g=="
},
"bluebird": { "bluebird": {
"version": "3.7.2", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+2 -1
View File
@@ -7,7 +7,8 @@
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0", "@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1", "@testing-library/user-event": "^7.2.1",
"binary-parser-encoder": "^1.5.2", "binary-parser-encoder": "^1.5.3",
"bitset": "^5.1.1",
"rc-slider": "^9.6.3", "rc-slider": "^9.6.3",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
+13
View File
@@ -0,0 +1,13 @@
diff --git a/node_modules/binary-parser-encoder/dist/binary_parser.js b/node_modules/binary-parser-encoder/dist/binary_parser.js
index ee49a70..ef0ac58 100644
--- a/node_modules/binary-parser-encoder/dist/binary_parser.js
+++ b/node_modules/binary-parser-encoder/dist/binary_parser.js
@@ -551,7 +551,7 @@ var Parser = /** @class */ (function () {
Parser.prototype.compile = function () {
var importPath = 'imports';
var ctx = this.getContext(importPath);
- this.compiled = new Function(importPath, 'TextDecoder', "return function (buffer, constructorFn) { " + ctx.code + " };")(ctx.imports, typeof TextDecoder === 'undefined'
+ this.compiled = new Function(importPath, 'TextDecoder', "return function (buffer, constructorFn) { ' + ctx.code + ' };")(ctx.imports, typeof TextDecoder === 'undefined'
? require('util').TextDecoder
: TextDecoder);
};
+5
View File
@@ -10,6 +10,11 @@
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;700&family=Roboto:wght@100;300;400;500;700&display=swap"
rel="stylesheet"
/>
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373D" d="M2 36s-2 0-2-2V2s0-2 2-2h32.031C36 0 36 2 36 2v32s0 2-2 2H2z"/><path d="M19 33s0 1 1 1h5c1 0 1-1 1-1V5h-7v28zm9-28v28s0 1 1 1h4c1 0 1-1 1-1V5h-6zM10 33s0 1 1 1h5c1 0 1-1 1-1V5h-7v28zm-8 0s0 1 1 1h4c1 0 1-1 1-1V5H2v28z" fill="#E1E8ED"/><path fill="#31373D" d="M30 23s0 1-1 1h-4c-1 0-1-1-1-1V3h6v20zm-9 0s0 1-1 1h-4c-1 0-1-1-1-1V3h6v20zm-9 0s0 1-1 1H7c-1 0-1-1-1-1V3h6v20z"/></svg>

Before

Width:  |  Height:  |  Size: 463 B

+6 -1
View File
@@ -16,7 +16,7 @@
.App-header { .App-header {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: calc(10px + 2vmin); font-size: calc(10px + 2vmin);
@@ -25,6 +25,11 @@
.App-link { .App-link {
color: rgb(112, 76, 182); color: rgb(112, 76, 182);
} }
.App-spacer {
min-height: 100vh;
min-width: 15vw;
border-right: 1px solid black;
}
@keyframes App-logo-float { @keyframes App-logo-float {
0% { 0% {
+4 -2
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import MainMenu from './features/menu/Menu'; import Menu from './features/menu';
import TimbreSwitcher from './features/timbre/timbreSwitcher'; import TimbreSwitcher from './features/timbre/timbreSwitcher';
import './App.css'; import './App.css';
@@ -7,7 +7,9 @@ function App() {
return ( return (
<div className='App'> <div className='App'>
<header className='App-header'> <header className='App-header'>
<MainMenu /> <div className='App-spacer'>
<Menu />
</div>
<TimbreSwitcher /> <TimbreSwitcher />
</header> </header>
</div> </div>
-60
View File
@@ -1,60 +0,0 @@
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
selectCount,
} from './counterSlice';
import styles from './Counter.module.css';
export function Counter() {
const count = useSelector(selectCount);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');
return (
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
<span className={styles.value}>{count}</span>
<button
className={styles.button}
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
</div>
<div className={styles.row}>
<input
className={styles.textbox}
aria-label="Set increment amount"
value={incrementAmount}
onChange={e => setIncrementAmount(e.target.value)}
/>
<button
className={styles.button}
onClick={() =>
dispatch(incrementByAmount(Number(incrementAmount) || 0))
}
>
Add Amount
</button>
<button
className={styles.asyncButton}
onClick={() => dispatch(incrementAsync(Number(incrementAmount) || 0))}
>
Add Async
</button>
</div>
</div>
);
}
-74
View File
@@ -1,74 +0,0 @@
.row {
display: flex;
align-items: center;
justify-content: center;
}
.row:not(:last-child) {
margin-bottom: 16px;
}
.value {
font-size: 78px;
padding-left: 16px;
padding-right: 16px;
margin-top: 2px;
font-family: 'Courier New', Courier, monospace;
}
.button {
appearance: none;
background: none;
font-size: 32px;
padding-left: 12px;
padding-right: 12px;
outline: none;
border: 2px solid transparent;
color: rgb(112, 76, 182);
padding-bottom: 4px;
cursor: pointer;
background-color: rgba(112, 76, 182, 0.1);
border-radius: 2px;
transition: all 0.15s;
}
.textbox {
font-size: 32px;
padding: 2px;
width: 64px;
text-align: center;
margin-right: 8px;
}
.button:hover, .button:focus {
border: 2px solid rgba(112, 76, 182, 0.4);
}
.button:active {
background-color: rgba(112, 76, 182, 0.2);
}
.asyncButton {
composes: button;
position: relative;
margin-left: 8px;
}
.asyncButton:after {
content: "";
background-color: rgba(112, 76, 182, 0.15);
display: block;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
opacity: 0;
transition: width 1s linear, opacity 0.5s ease 1s;
}
.asyncButton:active:after {
width: 0%;
opacity: 1;
transition: 0s
}
-42
View File
@@ -1,42 +0,0 @@
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: state => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1;
},
decrement: state => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched
export const incrementAsync = amount => dispatch => {
setTimeout(() => {
dispatch(incrementByAmount(amount));
}, 1000);
};
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state) => state.counter.value)`
export const selectCount = state => state.counter.value;
export default counterSlice.reducer;
+9 -5
View File
@@ -6,9 +6,9 @@ import './rcSelectTheme.css';
const SliderWithToolTip = createSliderWithTooltip(RCSlider); const SliderWithToolTip = createSliderWithTooltip(RCSlider);
const SelectStyle = { const SelectStyle = {
control: (provided) => ({ control: (provided, state) => ({
...provided, ...provided,
width: '100%', width: state.selectProps.width,
}), }),
}; };
@@ -24,9 +24,12 @@ const Slider = (props) => {
); );
}; };
const SelectList = (props) => { const SelectList = (props = { width: '100%' }) => {
return ( return (
<Select <Select
className='react-select-container'
classNamePrefix='react-select'
menuPlacement='auto'
value={{ value: props.value }} value={{ value: props.value }}
options={props.list} options={props.list}
getOptionLabel={ getOptionLabel={
@@ -36,6 +39,7 @@ const SelectList = (props) => {
} }
onChange={(e) => props.onChange(e.value)} onChange={(e) => props.onChange(e.value)}
styles={SelectStyle} styles={SelectStyle}
width={props.width}
/> />
); );
}; };
@@ -43,9 +47,9 @@ const SelectList = (props) => {
const Checkbox = (props) => { const Checkbox = (props) => {
return ( return (
<div> <div>
<span>{`${props.name}:`}</span> <span>{props.name ? `${props.name}:` : ''}</span>
<input <input
type="checkbox" type='checkbox'
value={props.parameter} value={props.parameter}
defaultChecked={props.parameter} defaultChecked={props.parameter}
onChange={(e) => props.onChange(e.target.checked)} onChange={(e) => props.onChange(e.target.checked)}
-16
View File
@@ -1,16 +0,0 @@
.dropzone-container {
background: white;
padding: 0.95em;
position: fixed;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: calc(7.5em);
box-sizing: border-box;
width: 14em;
z-index: 100;
border: 0.09em solid slategray;
border-radius: 6px;
}
-94
View File
@@ -1,94 +0,0 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useDropzone } from 'react-dropzone';
import { Parse } from './ioParser';
import './IO.css';
const baseStyle = {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
height: '4.25em',
padding: '0.5em',
borderWidth: 2,
borderRadius: 2,
borderColor: 'slategrey',
borderStyle: 'dashed',
backgroundColor: 'ghostwhite',
color: 'slategray',
outline: 'none',
transition: 'border .24s ease-in-out',
};
const activeStyle = {
borderColor: 'slateblue',
};
const acceptStyle = {
borderColor: 'mediumseagreen',
};
const rejectStyle = {
borderColor: 'crimson',
};
const Dropzone = ({ setFile }) => {
const {
getRootProps,
getInputProps,
isDragAccept,
isDragActive,
isDragReject,
} = useDropzone({
multiple: false,
maxFiles: 1,
noKeyboard: true,
onDrop: (files) => setFile(files[0]),
});
const style = useMemo(
() => ({
...baseStyle,
...(isDragActive ? activeStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {}),
}),
[isDragActive, isDragReject, isDragAccept]
);
return (
<div className='dropzone-container'>
<div {...getRootProps({ style })}>
<input {...getInputProps()} />
<p>Drag and drop file here, or click to select</p>
</div>
</div>
);
};
export default function IO() {
const [file, setFile] = useState(null);
const reader = new FileReader();
// turn file into array buffer for parsing
const fileToArrayBuffer = (f) => {
reader.onload = (e) => {
setFile(new Uint8Array(e.target.result));
};
reader.readAsArrayBuffer(f);
};
useEffect(() => {
if (file) {
console.log(Parse(file));
}
});
return (
<div className='dropzone-container'>
<Dropzone className='dropzone-modal' setFile={fileToArrayBuffer} />
</div>
);
}
/* TODO -- possibly add clicking outside the modal to close it*/
+793
View File
@@ -0,0 +1,793 @@
import { returnFromList } from '../utils/functions';
export default function build_store(patch, timbre1, timbre2, vocoder) {
return {
...prepare_patch(patch),
...prepare_timbre(timbre1),
...prepare_timbre(timbre2, true),
...prepare_vocoder(vocoder),
};
}
function prepare_patch(object) {
console.log(object.voice_mode);
return {
unusedPatch: {
id: 'unusedPatch',
scaleKey: object.scale_key,
scaleType: object.scale_type,
keyboardOctave: object.keyboard_octave,
arpSwitch: object.arp_on_off,
},
patch: {
id: 'patch',
mode: returnFromList(
object.voice_mode,
'Single',
null,
'Multiple',
'Vocoder'
),
name: object.program_name,
},
delay: {
id: 'delay',
type: returnFromList(
object.delay_type,
'Stereo Delay',
'Cross Delay',
'L/R Delay'
),
tempoSync: object.delay_sync,
syncNote: returnFromList(
object.delay_timebase,
'1/32',
'1/24',
'1/16',
'1/12',
'3/32',
'1/8',
'1/6',
'3/16',
'1/4',
'1/3',
'3/8',
'1/2',
'2/3',
'3/4',
'1/1'
),
delayTime: object.delay_time,
delayDepth: object.delay_depth,
},
modfx: {
id: 'modfx',
type: returnFromList(
object.mod_type,
'Chorus/Flanger',
'Ensemble',
'Phaser'
),
lfoSpeed: object.mod_lfo_speed,
effectDepth: object.mod_depth,
},
eq: {
id: 'eq',
lowFrequency: returnFromList(
object.eq_low_freq,
40,
50,
60,
80,
100,
120,
140,
160,
180,
200,
220,
240,
260,
280,
300,
320,
340,
360,
380,
400,
420,
440,
460,
480,
500,
600,
700,
800,
900,
1000
),
lowGain: object.eq_low_gain - 64,
highFrequency: returnFromList(
object.eq_hi_freq,
1.0,
1.25,
1.5,
1.75,
2.0,
2.25,
2.5,
2.75,
3.0,
3.25,
3.5,
3.75,
4.0,
4.25,
4.5,
4.75,
5.0,
5.25,
5.5,
5.75,
6.0,
7.0,
8.0,
9.0,
10.0,
11.0,
12.0,
14.0,
16.0,
18.0
),
highGain: object.eq_hi_gain - 64,
},
arpeggA: {
id: 'arpeggA',
tempo: object.arp_tempo,
resolution: returnFromList(
object.arp_resolution,
'1/24',
'1/16',
'1/12',
'1/8',
'1/6',
'1/4'
),
gate: object.arp_gate_time,
type: returnFromList(
object.arp_type,
'Up',
'Down',
'Alternate 1',
'Alternate 2',
'Random',
'Trigger'
),
range: object.arp_range,
},
arpeggB: {
id: 'arpeggB',
latch: object.arp_latch,
swing: object.arp_swing,
keySync: object.arp_key_sync,
lastStep: object.arp_trigger_length,
target: object.arp_target,
pattern: object.arp_trigger_pattern,
},
};
}
function prepare_timbre(object, timbre2 = false) {
const num = timbre2 ? 2 : 1;
if (!object) return null;
return {
[`unusedTimbre${num}`]: {
id: `unusedTimbre${num}`,
midiChannel: object[`t${num}_midi_channel`],
keyPriority: object[`t${num}_key_priority`],
filterVelSense: object[`t${num}_filter_vel_sense`],
ampSwitch: object[`t${num}_amp_switch`],
ampVelSense: object[`t${num}_amp_vel_sense`],
},
[`vco${timbre2 ? num + 1 : num}`]: {
id: `vco${timbre2 ? num + 1 : num}`,
waveform: returnFromList(
object[`t${num}_osc_1_wave`],
'Saw',
'Square',
'Triangle',
'Sine',
'Vox Wave',
'DWGS',
'Noise',
'Audio In'
),
dwgsOrModType: returnFromList(
object[`t${num}_osc_1_dgws`],
'SynSine1',
'SynSine2',
'SynSine3',
'SynSine4',
'SynSine5',
'SynSine6',
'SynSine7',
'SynBass1',
'SynBass2',
'SynBass3',
'SynBass4',
'SynBass5',
'SynBass6',
'SynBass7',
'SynWave1',
'SynWave2',
'SynWave3',
'SynWave4',
'SynWave5',
'SynWave6',
'SynWave7',
'SynWave8',
'SynWave9',
'5thWave1',
'5thWave2',
'5thWave3',
'Digi1',
'Digi2',
'Digi3',
'Digi4',
'Digi5',
'Digi6',
'Digi7',
'Digi8',
'Endless',
'E.Piano1',
'E.Piano2',
'E.Piano3',
'E.Piano4',
'Organ1',
'Organ2',
'Organ3',
'Organ4',
'Organ5',
'Organ6',
'Organ7',
'Clav1',
'Clav2',
'Guitar1',
'Guitar2',
'Guitar3',
'Bass1',
'Bass2',
'Bass3',
'Bass4',
'Bass5',
'Bell1',
'Bell2',
'Bell3',
'Bell4',
'Voice1',
'Voice2',
'Voice3',
'Voice4'
),
waveMod: object[`t${num}_osc_1_ctrl_1`],
lfoMod: object[`t${num}_osc_1_ctrl_2`],
},
[`vco${num * 2}`]: {
id: `vco${num * 2}`,
waveform: returnFromList(
object[`t${num}_osc_2_wave`],
'Saw',
'Square',
'Triangle'
),
dwgsOrModType: returnFromList(
object[`t${num}_osc_2_mod_select`],
'Off',
'Ring',
'Sync',
'RingSync'
),
waveMod: object[`t${num}_osc_2_semitone`],
lfoMod: object[`t${num}_osc_2_tune`],
},
[`mixer${num}`]: {
id: `mixer${num}`,
vol1: object[`t${num}_osc_1_level`],
vol2: object[`t${num}_osc_2_level`],
vol3: object[`t${num}_noise_level`],
},
[`pitch${num}`]: {
id: `pitch${num}`,
voiceAssign: returnFromList(
object[`t${num}_assign_mode`],
'Mono',
'Poly',
'Unison'
),
triggerMode: returnFromList(
object[`t${num}_trigger_mode`],
'Single',
'Multiple'
),
transpose: object[`t${num}_transpose`] - 64,
tune: object[`t${num}_tune`] - 64,
unisonDetune: object[`t${num}_unison_detune`],
portamento: object[`t${num}_portamento`],
bendRange: object[`t${num}_bend_range`] - 64,
vibratoIntensity: object[`t${num}_vibrato_int`] - 64,
},
[`filter${num}`]: {
id: `filter${num}`,
filterType: returnFromList(
object[`t${num}_filter_type`],
'-24db Low Pass',
'-12db Low Pass',
'Band Pass',
'High Pass'
),
cutoff: object[`t${num}_filter_cutoff`],
resonance: object[`t${num}_filter_resonance`],
egIntensity: object[`t${num}_filter_eg_intensity`] - 64,
keyboardTrack: object[`t${num}_filter_key_track`] - 64,
},
[`amp${num}`]: {
id: `amp${num}`,
ampLevel: object[`t${num}_amp_level`],
panpot: object[`t${num}_amp_panpot`],
ampTrack: object[`t${num}_amp_key_track`] - 64,
distortion: object[`t${num}_amp_distortion`],
},
[`eg${timbre2 ? num + 1 : num}`]: {
id: `eg${timbre2 ? num + 1 : num}`,
attack: object[`t${num}_eg_1_attack`],
decay: object[`t${num}_eg_1_decay`],
sustain: object[`t${num}_eg_1_sustain`],
release: object[`t${num}_eg_1_release`],
egReset: object[`t${num}_eg_1_reset`],
},
[`eg${num * 2}`]: {
id: `eg${num * 2}`,
attack: object[`t${num}_eg_2_attack`],
decay: object[`t${num}_eg_2_decay`],
sustain: object[`t${num}_eg_2_sustain`],
release: object[`t${num}_eg_2_release`],
egReset: object[`t${num}_eg_2_reset`],
},
[`lfo${timbre2 ? num + 1 : num}`]: {
id: `lfo${timbre2 ? num + 1 : num}`,
waveform: returnFromList(
object[`t${num}_lfo_1_wave`],
'Saw',
'Square 1',
'Triangle',
'Sample & Hold'
),
keySync: returnFromList(
object[`t${num}_lfo_1_key_sync`],
'Off',
'Timbre',
'Voice'
),
tempoSync: object[`t${num}_lfo_1_tempo_sync`],
frequency: object[`t${num}_lfo_1_frequency`],
syncNote: returnFromList(
object[`t${num}_lfo_1_sync_note`],
'1/1',
'3/4',
'2/3',
'1/2',
'3/8',
'1/3',
'1/4',
'3/16',
'1/6',
'1/8',
'3/32',
'1/12',
'1/16',
'1/24',
'1/32'
),
},
[`lfo${num * 2}`]: {
id: `lfo${num * 2}`,
waveform: returnFromList(
object[`t${num}_lfo_2_wave`],
'Saw',
'Square 2',
'Sine',
'Sample & Hold'
),
keySync: returnFromList(
object[`t${num}_lfo_2_key_sync`],
'Off',
'Timbre',
'Voice'
),
tempoSync: object[`t${num}_lfo_2_tempo_sync`],
frequency: object[`t${num}_lfo_2_frequency`],
syncNote: returnFromList(
object[`t${num}_lfo_2_sync_note`],
'1/1',
'3/4',
'2/3',
'1/2',
'3/8',
'1/3',
'1/4',
'3/16',
'1/6',
'1/8',
'3/32',
'1/12',
'1/16',
'1/24',
'1/32'
),
},
[`patches${num}`]: {
id: `patches${num}`,
patch1: {
source: returnFromList(
object[`t${num}_patch_1_src`],
'Filter EG',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel'
),
destination: returnFromList(
object[`t${num}_patch_1_dest`],
'Pitch',
'OSC 2 Tune',
'OSC 1 Control 1',
'Noise Level',
'Cutoff',
'Amp',
'Pan',
'LFO 2 Frequency'
),
modIntensity: object[`t${num}_patch_1_intensity`],
},
patch2: {
source: returnFromList(
object[`t${num}_patch_2_src`],
'Filter EG',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel'
),
destination: returnFromList(
object[`t${num}_patch_2_dest`],
'Pitch',
'OSC 2 Tune',
'OSC 1 Control 1',
'Noise Level',
'Cutoff',
'Amp',
'Pan',
'LFO 2 Frequency'
),
modIntensity: object[`t${num}_patch_2_intensity`],
},
patch3: {
source: returnFromList(
object[`t${num}_patch_3_src`],
'Filter EG',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel'
),
destination: returnFromList(
object[`t${num}_patch_3_dest`],
'Pitch',
'OSC 2 Tune',
'OSC 1 Control 1',
'Noise Level',
'Cutoff',
'Amp',
'Pan',
'LFO 2 Frequency'
),
modIntensity: object[`t${num}_patch_3_intensity`],
},
patch4: {
source: returnFromList(
object[`t${num}_patch_4_src`],
'Filter EG',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel'
),
destination: returnFromList(
object[`t${num}_patch_4_dest`],
'Pitch',
'OSC 2 Tune',
'OSC 1 Control 1',
'Noise Level',
'Cutoff',
'Amp',
'Pan',
'LFO 2 Frequency'
),
modIntensity: object[`t${num}_patch_4_intensity`],
},
},
};
}
function prepare_vocoder(object) {
if (!object) return null;
return {
unusedVocoder: {
midiChannel: object.v_midi_channel,
egReset2: object.v_eg_2_reset,
keyPriority: object.v_key_priority,
filterEFSense: object.v_filter_e_f_sense,
ampVelSense: object.v_amp_vel_sense,
eg2attack: object.v_eg_2_attack,
eg2decay: object.v_eg_2_decay,
eg2sustain: object.v_eg_2_sustain,
eg2release: object.v_eg_2_release,
},
vco5: {
id: 'vco5',
waveform: returnFromList(
object.v_osc_wave,
'Saw',
'Square',
'Triangle',
'Sine',
'Vox Wave',
'DWGS',
'Noise',
'Audio In'
),
dwgsOrModType: returnFromList(
object.v_osc_dgws,
'SynSine1',
'SynSine2',
'SynSine3',
'SynSine4',
'SynSine5',
'SynSine6',
'SynSine7',
'SynBass1',
'SynBass2',
'SynBass3',
'SynBass4',
'SynBass5',
'SynBass6',
'SynBass7',
'SynWave1',
'SynWave2',
'SynWave3',
'SynWave4',
'SynWave5',
'SynWave6',
'SynWave7',
'SynWave8',
'SynWave9',
'5thWave1',
'5thWave2',
'5thWave3',
'Digi1',
'Digi2',
'Digi3',
'Digi4',
'Digi5',
'Digi6',
'Digi7',
'Digi8',
'Endless',
'E.Piano1',
'E.Piano2',
'E.Piano3',
'E.Piano4',
'Organ1',
'Organ2',
'Organ3',
'Organ4',
'Organ5',
'Organ6',
'Organ7',
'Clav1',
'Clav2',
'Guitar1',
'Guitar2',
'Guitar3',
'Bass1',
'Bass2',
'Bass3',
'Bass4',
'Bass5',
'Bell1',
'Bell2',
'Bell3',
'Bell4',
'Voice1',
'Voice2',
'Voice3',
'Voice4'
),
waveMod: object.v_osc_ctrl_1,
lfoMod: object.v_osc_ctrl_1,
},
audioIn: {
id: 'audioIn',
gate: object.v_audio_in_gate_sense,
threshold: object.v_audio_in_threshold,
hpfLevel: object.v_audio_in_hpf_level,
hpfGate: object.v_audio_in_hpf_gate,
},
mixer3: {
id: 'mixer3',
vol1: object.v_osc_level,
vol2: object.v_ext_level,
vol3: object.v_noise_level,
},
pitch3: {
id: 'pitch3',
voiceAssign: returnFromList(
object.v_assign_mode,
'Mono',
'Poly',
'Unison'
),
triggerMode: returnFromList(object.v_trigger_mode, 'Single', 'Multiple'),
transpose: object.v_transpose - 64,
tune: object.v_tune - 64,
unisonDetune: object.v_unison_detune,
portamento: object.v_portamento_time,
bendRange: object.v_bend_range - 64,
vibratoIntensity: object.v_vibrato_int - 64,
},
filter3: {
id: 'filter',
shift: returnFromList(object.v_filter_shift, 0, 1, 2, -1, -2),
cutoff: object.v_filter_cutoff,
resonance: object.v_filter_resonance,
efSense: object.v_filter_e_f_sense,
},
fcMod: {
id: 'fcMod',
source: returnFromList(
object.v_filter_mod_source,
'---',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel'
),
intensity: object.v_filter_intensity,
},
eg5: {
id: 'eg5',
attack: object.v_eg_1_attack,
decay: object.v_eg_1_decay,
sustain: object.v_eg_1_sustain,
release: object.v_eg_1_release,
egReset: object.v_eg_1_reset,
},
amp3: {
id: 'amp3',
ampLevel: object.v_amp_level,
directLevel: object.v_amp_direct_level,
ampTrack: object.v_amp_key_track - 64,
distortion: object.v_amp_direction,
},
lfo5: {
id: 'lfo5',
waveform: returnFromList(
object.v_lfo_1_wave,
'Saw',
'Square 1',
'Triangle',
'Sample & Hold'
),
keySync: returnFromList(
object.v_lfo_1_key_sync,
'Off',
'Timbre',
'Voice'
),
tempoSync: object.v_lfo_1_tempo_sync,
frequency: object.v_lfo_1_frequency,
syncNote: returnFromList(
object.v_lfo_1_sync_note,
'1/1',
'3/4',
'2/3',
'1/2',
'3/8',
'1/3',
'1/4',
'3/16',
'1/6',
'1/8',
'3/32',
'1/12',
'1/16',
'1/24',
'1/32'
),
},
lfo6: {
id: 'lfo6',
waveform: returnFromList(
object.v_lfo_2_wave,
'Saw',
'Square 2',
'Sine',
'Sample & Hold'
),
keySync: returnFromList(
object.v_lfo_2_key_sync,
'Off',
'Timbre',
'Voice'
),
tempoSync: object.v_lfo_2_tempo_sync,
frequency: object.v_lfo_2_frequency,
syncNote: returnFromList(
object.v_lfo_2_sync_note,
'1/1',
'3/4',
'2/3',
'1/2',
'3/8',
'1/3',
'1/4',
'3/16',
'1/6',
'1/8',
'3/32',
'1/12',
'1/16',
'1/24',
'1/32'
),
},
levels: {
id: 'levels',
level1: object.v_level_1,
level2: object.v_level_2,
level3: object.v_level_3,
level4: object.v_level_4,
level5: object.v_level_5,
level6: object.v_level_6,
level7: object.v_level_7,
level8: object.v_level_8,
},
pans: {
id: 'pans',
pan1: object.v_pan_1,
pan2: object.v_pan_2,
pan3: object.v_pan_3,
pan4: object.v_pan_4,
pan5: object.v_pan_5,
pan6: object.v_pan_6,
pan7: object.v_pan_7,
pan8: object.v_pan_8,
},
};
}
+213
View File
@@ -0,0 +1,213 @@
import { getBits, toBitArray } from '../utils/functions';
export default function parse(sysex) {
const patch = build_patch(sysex.slice(0, 38));
switch (patch.voice_mode) {
default:
case 0:
return [
{ ...patch },
{ ...build_timbre(1, sysex.slice(38, 146)) },
null,
null,
];
case 2:
return [
{ ...patch },
{ ...build_timbre(1, sysex.slice(38, 146)) },
{ ...build_timbre(2, sysex.slice(146, 254)) },
null,
];
case 3:
return [
{ ...patch },
null,
null,
{ ...build_vocoder(sysex.slice(38, 142)) },
];
}
}
function build_patch(patch) {
return {
/* BYTES 0, 11 NOT 0, 12 */
program_name: String.fromCharCode(...patch.slice(0, 12)),
/* BYTES 13 / 14 SKIPPED */
arp_trigger_length: patch[14],
arp_trigger_pattern: toBitArray(patch[15]),
voice_mode: getBits(patch[16], 4, 5),
scale_key: getBits(patch[17], 4, 7),
scale_type: getBits(patch[17], 0, 3),
/* BYTE 18 SKIPPED */
delay_sync: patch[19] & 0x80,
delay_timebase: getBits(patch[19], 0, 3),
delay_time: patch[20],
delay_depth: patch[21],
delay_type: patch[22],
mod_lfo_speed: patch[23],
mod_depth: patch[24],
mod_type: patch[25],
eq_hi_freq: patch[26],
eq_hi_gain: patch[27],
eq_low_freq: patch[28],
eq_low_gain: patch[29],
arp_tempo: patch.slice(30, 32).reduce((acc, curr) => acc + curr),
arp_on_off: patch[32] & 0x80,
arp_latch: patch[32] & 0x40,
arp_target: getBits(patch[32], 4, 5),
arp_key_sync: patch[32] & 0x01,
arp_type: getBits(patch[33], 0, 3),
arp_range: getBits(patch[33], 4, 7),
arp_gate_time: patch[34],
arp_resolution: patch[35],
arp_swing: patch[36],
keyboard_octave: patch[37],
};
}
function build_timbre(num, timbre) {
return {
[`t${num}_midi_channel`]: timbre[0],
[`t${num}_assign_mode`]: getBits(timbre[1], 6, 7),
[`t${num}_eg_2_reset`]: timbre[1] & 0x20,
[`t${num}_eg_1_reset`]: timbre[1] & 0x10,
[`t${num}_trigger_mode`]: timbre[1] & 0x08,
[`t${num}_key_priority`]: getBits(timbre[1], 0, 1),
[`t${num}_unison_detune`]: timbre[2],
[`t${num}_tune`]: timbre[3],
[`t${num}_bend_range`]: timbre[4],
[`t${num}_transpose`]: timbre[5],
[`t${num}_vibrato_int`]: timbre[6],
[`t${num}_osc_1_wave`]: timbre[7],
[`t${num}_osc_1_ctrl_1`]: timbre[8],
[`t${num}_osc_1_ctrl_2`]: timbre[9],
[`t${num}_osc_1_dgws`]: timbre[10],
/* SKIP BYTE 11 */
[`t${num}_osc_2_mod_select`]: getBits(timbre[12], 4, 5),
[`t${num}_osc_2_wave`]: getBits(timbre[12], 0, 1),
[`t${num}_osc_2_semitone`]: timbre[13],
[`t${num}_osc_2_tune`]: timbre[14],
[`t${num}_portamento`]: getBits(timbre[15], 0, 6),
[`t${num}_osc_1_level`]: timbre[16],
[`t${num}_osc_2_level`]: timbre[17],
[`t${num}_noise_level`]: timbre[18],
[`t${num}_filter_type`]: timbre[19],
[`t${num}_filter_cutoff`]: timbre[20],
[`t${num}_filter_resonance`]: timbre[21],
[`t${num}_filter_eg_intensity`]: timbre[22],
[`t${num}_filter_vel_sense`]: timbre[23],
[`t${num}_filter_key_track`]: timbre[24],
[`t${num}_amp_level`]: timbre[25],
[`t${num}_amp_panpot`]: timbre[26],
[`t${num}_amp_switch`]: timbre[27] & 0x40,
[`t${num}_amp_distortion`]: timbre[27] & 0x01,
[`t${num}_amp_vel_sense`]: timbre[28],
[`t${num}_amp_key_track`]: timbre[29],
[`t${num}_eg_1_attack`]: timbre[30],
[`t${num}_eg_1_decay`]: timbre[31],
[`t${num}_eg_1_sustain`]: timbre[32],
[`t${num}_eg_1_release`]: timbre[33],
[`t${num}_eg_2_attack`]: timbre[34],
[`t${num}_eg_2_decay`]: timbre[35],
[`t${num}_eg_2_sustain`]: timbre[36],
[`t${num}_eg_2_release`]: timbre[37],
[`t${num}_lfo_1_key_sync`]: getBits(timbre[38], 4, 5),
[`t${num}_lfo_1_wave`]: getBits(timbre[38], 0, 1),
[`t${num}_lfo_1_frequency`]: timbre[39],
[`t${num}_lfo_1_tempo_sync`]: timbre[40] & 0x80,
[`t${num}_lfo_1_sync_note`]: getBits(timbre[40], 0, 4),
[`t${num}_lfo_2_key_sync`]: getBits(timbre[41], 4, 5),
[`t${num}_lfo_2_wave`]: getBits(timbre[41], 0, 1),
[`t${num}_lfo_2_frequency`]: timbre[42],
[`t${num}_lfo_2_tempo_sync`]: timbre[43] & 0x80,
[`t${num}_lfo_2_sync_note`]: getBits(timbre[43], 0, 4),
[`t${num}_patch_1_dest`]: getBits(timbre[44], 4, 7),
[`t${num}_patch_1_src`]: getBits(timbre[44], 0, 3),
[`t${num}_patch_1_intensity`]: timbre[45],
[`t${num}_patch_2_dest`]: getBits(timbre[46], 4, 7),
[`t${num}_patch_2_src`]: getBits(timbre[46], 0, 3),
[`t${num}_patch_2_intensity`]: timbre[47],
[`t${num}_patch_3_dest`]: getBits(timbre[48], 4, 7),
[`t${num}_patch_3_src`]: getBits(timbre[48], 0, 3),
[`t${num}_patch_3_intensity`]: timbre[49],
[`t${num}_patch_4_dest`]: getBits(timbre[50], 4, 7),
[`t${num}_patch_4_src`]: getBits(timbre[50], 0, 3),
[`t${num}_patch_4_intensity`]: timbre[51],
};
}
function build_vocoder(vocoder) {
return {
v_midi_channel: vocoder[0],
v_assign_mode: getBits(vocoder[1], 6, 7),
v_eg_2_reset: vocoder[1] & 0x20,
v_eg_1_reset: vocoder[1] & 0x10,
v_trigger_mode: vocoder[1] & 0x08,
v_key_priority: getBits(vocoder[1], 0, 1),
v_unison_detune: vocoder[2],
v_tune: vocoder[3],
v_bend_range: vocoder[4],
v_transpose: vocoder[5],
v_vibrato_int: vocoder[6],
v_osc_wave: vocoder[7],
v_osc_ctrl_1: vocoder[8],
v_osc_ctrl_2: vocoder[9],
v_osc_dgws: vocoder[10],
/* BYTE 11 SKIPPED */
v_audio_in_hpf_gate: vocoder[12] & 0x01,
/* BYTE 13 SKIPPED */
v_portamento_time: getBits(vocoder[14], 0, 6),
v_osc_level: vocoder[15],
v_ext_level: vocoder[16],
v_noise_level: vocoder[17],
v_audio_in_hpf_level: vocoder[18],
v_audio_in_gate_sense: vocoder[19],
v_audio_in_threshold: vocoder[20],
v_filter_shift: vocoder[21],
v_filter_cutoff: vocoder[22],
v_filter_resonance: vocoder[23],
v_filter_mod_source: vocoder[24],
v_filter_intensity: vocoder[25],
v_filter_e_f_sense: vocoder[26],
v_amp_level: vocoder[27],
v_amp_direct_level: vocoder[28],
v_amp_distortion: vocoder[29] & 0x01,
v_amp_vel_sense: vocoder[30],
v_amp_key_track: vocoder[31],
v_eg_1_attack: vocoder[32],
v_eg_1_decay: vocoder[33],
v_eg_1_sustain: vocoder[34],
v_eg_1_release: vocoder[35],
v_eg_2_attack: vocoder[36],
v_eg_2_decay: vocoder[37],
v_eg_2_sustain: vocoder[38],
v_eg_2_release: vocoder[39],
v_lfo_1_key_sync: getBits(vocoder[40], 4, 5),
v_lfo_1_wave: getBits(vocoder[40], 0, 1),
v_lfo_1_frequency: vocoder[41],
v_lfo_1_tempo_sync: vocoder[42] & 0x80,
v_lfo_1_sync_note: getBits(vocoder[42], 0, 4),
v_lfo_2_key_sync: getBits(vocoder[43], 4, 5),
v_lfo_2_wave: getBits(vocoder[43], 0, 1),
v_lfo_2_frequency: vocoder[44],
v_lfo_2_tempo_sync: vocoder[45] & 0x80,
v_lfo_2_sync_note: getBits(vocoder[45], 0, 4),
v_level_1: vocoder[46],
v_level_2: vocoder[47],
v_level_3: vocoder[48],
v_level_4: vocoder[49],
v_level_5: vocoder[50],
v_level_6: vocoder[51],
v_level_7: vocoder[52],
v_level_8: vocoder[53],
v_pan_1: vocoder[62],
v_pan_2: vocoder[63],
v_pan_3: vocoder[64],
v_pan_4: vocoder[65],
v_pan_5: vocoder[66],
v_pan_6: vocoder[67],
v_pan_7: vocoder[68],
v_pan_8: vocoder[69],
};
}
+732
View File
@@ -0,0 +1,732 @@
import { returnValue } from '../utils/functions';
export default function saveStoreState(state) {
switch (state.patch.mode) {
default:
case 'Single':
return [
{
...state_patch({
unused: { ...state.unusedPatch },
patch: { ...state.patch },
arpeggA: { ...state.arpeggA },
arpeggB: { ...state.arpeggB },
delay: { ...state.delay },
modfx: { ...state.modfx },
eq: { ...state.eq },
}),
},
{
...state_timbre({
unused: { ...state.unusedTimbre1 },
vco1: { ...state.vco1 },
vco2: { ...state.vco2 },
mixer: { ...state.mixer1 },
pitch: { ...state.pitch1 },
filter: { ...state.filter1 },
amp: { ...state.amp1 },
eg1: { ...state.eg1 },
eg2: { ...state.eg2 },
lfo1: { ...state.lfo1 },
lfo2: { ...state.lfo2 },
patches: { ...state.patches1 },
}),
},
null,
null,
];
case 'Multiple':
return [
{
...state_patch({
unused: { ...state.unusedPatch },
patch: { ...state.Patch },
arpeggA: { ...state.arpeggA },
arpeggB: { ...state.arpeggB },
delay: { ...state.delay },
modfx: { ...state.modfx },
eq: { ...state.eq },
}),
},
{
...state_timbre({
unused: { ...state.unusedTimbre1 },
vco1: { ...state.vco1 },
vco2: { ...state.vco2 },
mixer: { ...state.mixer1 },
pitch: { ...state.pitch1 },
filter: { ...state.filter1 },
amp: { ...state.amp1 },
eg1: { ...state.eg1 },
eg2: { ...state.eg2 },
lfo1: { ...state.lfo1 },
lfo2: { ...state.lfo2 },
patches: { ...state.patches1 },
}),
},
{
...state_timbre(
{
unused: { ...state.unusedTimbre2 },
vco1: { ...state.vco3 },
vco2: { ...state.vco4 },
mixer: { ...state.mixer2 },
pitch: { ...state.pitch2 },
filter: { ...state.filter2 },
amp: { ...state.amp2 },
eg1: { ...state.eg3 },
eg2: { ...state.eg4 },
lfo1: { ...state.lfo3 },
lfo2: { ...state.lfo4 },
patches: { ...state.patches2 },
},
true
),
},
null,
];
case 'Vocoder':
return [
{
...state_patch({
unused: { ...state.unusedPatch },
patch: { ...state.patch },
arpeggA: { ...state.arpeggA },
arpeggB: { ...state.arpeggB },
delay: { ...state.delay },
modfx: { ...state.modfx },
eq: { ...state.eq },
}),
},
null,
null,
{
...state_vocoder({
unused: { ...state.unusedVocoder },
vco1: { ...state.vco5 },
audioIn: { ...state.audioIn },
mixer: { ...state.mixer3 },
pitch: { ...state.pitch3 },
filter: { ...state.filter3 },
fcMod: { ...state.fcMod },
amp: { ...state.amp3 },
eg1: { ...state.eg5 },
lfo1: { ...state.lfo5 },
lfo2: { ...state.lfo6 },
levels: { ...state.levels },
pans: { ...state.pans },
}),
},
];
}
}
function state_patch(state) {
return {
program_name: state.patch.name,
arp_trigger_length: state.arpeggB.lastStep,
arp_trigger_pattern: state.arpeggB.pattern,
voice_mode: returnValue(state.patch.mode, [
'Single',
null,
'Multiple',
'Vocoder',
]),
scale_key: state.unused.scaleKey,
scale_type: state.unused.scaleType,
arp_tempo: state.arpeggA.tempo,
arp_on_off: state.unused.arpSwitch,
arp_latch: state.arpeggB.latch,
arp_target: state.arpeggB.target,
arp_key_sync: state.arpeggB.keySync,
arp_type: returnValue(state.arpeggA.type, [
'Up',
'Down',
'Alternate 1',
'Alternate 2',
'Random',
'Trigger',
]),
arp_range: state.arpeggA.range,
arp_gate_time: state.arpeggA.gate,
arp_resolution: returnValue(state.arpeggA.resolution, [
'1/24',
'1/16',
'1/12',
'1/8',
'1/6',
'1/4',
]),
arp_swing: state.arpeggB.swing,
delay_sync: state.delay.tempoSync,
delay_timebase: returnValue(state.delay.syncNote, [
'1/32',
'1/24',
'1/16',
'1/12',
'3/32',
'1/8',
'1/6',
'3/16',
'1/4',
'1/3',
'3/8',
'1/2',
'2/3',
'3/4',
'1/1',
]),
delay_time: state.delay.delayTime,
delay_depth: state.delay.delayDepth,
delay_type: returnValue(state.delay.type, [
'Stereo Delay',
'Cross Delay',
'L/R Delay',
]),
mod_lfo_speed: state.modfx.lfoSpeed,
mod_depth: state.modfx.effectDepth,
mod_type: returnValue(state.modfx.type, [
'Chorus/Flanger',
'Ensemble',
'Phaser',
]),
eq_hi_freq: returnValue(state.eq.highFrequency, [
1.0,
1.25,
1.5,
1.75,
2.0,
2.25,
2.5,
2.75,
3.0,
3.25,
3.5,
3.75,
4.0,
4.25,
4.5,
4.75,
5.0,
5.25,
5.5,
5.75,
6.0,
7.0,
8.0,
9.0,
10.0,
11.0,
12.0,
14.0,
16.0,
18.0,
]),
eq_hi_gain: state.eq.highGain + 64,
eq_low_freq: returnValue(state.eq.lowFrequency, [
40,
50,
60,
80,
100,
120,
140,
160,
180,
200,
220,
240,
260,
280,
300,
320,
340,
360,
380,
400,
420,
440,
460,
480,
500,
600,
700,
800,
900,
1000,
]),
eq_low_gain: state.eq.lowGain + 64,
keyboard_octave: state.unused.keyboardOctave,
};
}
function state_timbre(state, timbre2 = false) {
const num = timbre2 ? 2 : 1;
return {
[`t${num}_midi_channel`]: state.unused.midiChannel,
[`t${num}_assign_mode`]: returnValue(state.pitch.voiceAssign, [
'Mono',
'Poly',
'Unison',
]),
[`t${num}_eg_2_reset`]: state.eg2.egReset,
[`t${num}_eg_1_reset`]: state.eg1.egReset,
[`t${num}_trigger_mode`]: returnValue(state.pitch.triggerMode, [
'Single',
'Multiple',
]),
[`t${num}_key_priority`]: state.unused.keyPriority,
[`t${num}_unison_detune`]: state.pitch.unisonDetune,
[`t${num}_tune`]: state.pitch.tune + 64,
[`t${num}_bend_range`]: state.pitch.bendRange + 64,
[`t${num}_transpose`]: state.pitch.transpose + 64,
[`t${num}_vibrato_int`]: state.pitch.vibratoIntensity + 64,
[`t${num}_osc_1_wave`]: returnValue(state.vco1.waveform, [
'Saw',
'Square',
'Triangle',
'Sine',
'Vox Wave',
'DWGS',
'Noise',
'Audio In',
]),
[`t${num}_osc_1_ctrl_1`]: state.vco1.waveMod,
[`t${num}_osc_1_ctrl_2`]: state.vco1.lfoMod,
[`t${num}_osc_1_dgws`]: returnValue(state.vco1.dwgsOrModType, [
'SynSine1',
'SynSine2',
'SynSine3',
'SynSine4',
'SynSine5',
'SynSine6',
'SynSine7',
'SynBass1',
'SynBass2',
'SynBass3',
'SynBass4',
'SynBass5',
'SynBass6',
'SynBass7',
'SynWave1',
'SynWave2',
'SynWave3',
'SynWave4',
'SynWave5',
'SynWave6',
'SynWave7',
'SynWave8',
'SynWave9',
'5thWave1',
'5thWave2',
'5thWave3',
'Digi1',
'Digi2',
'Digi3',
'Digi4',
'Digi5',
'Digi6',
'Digi7',
'Digi8',
'Endless',
'E.Piano1',
'E.Piano2',
'E.Piano3',
'E.Piano4',
'Organ1',
'Organ2',
'Organ3',
'Organ4',
'Organ5',
'Organ6',
'Organ7',
'Clav1',
'Clav2',
'Guitar1',
'Guitar2',
'Guitar3',
'Bass1',
'Bass2',
'Bass3',
'Bass4',
'Bass5',
'Bell1',
'Bell2',
'Bell3',
'Bell4',
'Voice1',
'Voice2',
'Voice3',
'Voice4',
]),
[`t${num}_osc_2_mod_select`]: returnValue(state.vco2.dwgsOrModType, [
'Off',
'Ring',
'Sync',
'RingSync',
]),
[`t${num}_osc_2_wave`]: returnValue(state.vco2.waveform, [
'Saw',
'Square',
'Triangle',
]),
[`t${num}_osc_2_semitone`]: state.vco2.waveMod,
[`t${num}_osc_2_tune`]: state.vco2.lfoMod,
[`t${num}_portamento`]: state.pitch.portamento,
[`t${num}_osc_1_level`]: state.mixer.vol1,
[`t${num}_osc_2_level`]: state.mixer.vol2,
[`t${num}_noise_level`]: state.mixer.vol3,
[`t${num}_filter_type`]: returnValue(state.filter.filterType, [
'-24db Low Pass',
'-12db Low Pass',
'Band Pass',
'High Pass',
]),
[`t${num}_filter_cutoff`]: state.filter.cutoff,
[`t${num}_filter_resonance`]: state.filter.resonance,
[`t${num}_filter_eg_intensity`]: state.filter.egIntensity + 64,
[`t${num}_filter_vel_sense`]: state.unused.filterVelSense,
[`t${num}_filter_key_track`]: state.filter.keyboardTrack + 64,
[`t${num}_amp_level`]: state.amp.ampLevel,
[`t${num}_amp_panpot`]: state.amp.panpot,
[`t${num}_amp_switch`]: state.unused.ampSwitch,
[`t${num}_amp_distortion`]: state.amp.distortion,
[`t${num}_amp_vel_sense`]: state.unused.ampVelSense,
[`t${num}_amp_key_track`]: state.amp.ampTrack + 64,
[`t${num}_eg_1_attack`]: state.eg1.attack,
[`t${num}_eg_1_decay`]: state.eg1.decay,
[`t${num}_eg_1_sustain`]: state.eg1.sustain,
[`t${num}_eg_1_release`]: state.eg1.release,
[`t${num}_eg_2_attack`]: state.eg2.attack,
[`t${num}_eg_2_decay`]: state.eg2.decay,
[`t${num}_eg_2_sustain`]: state.eg2.sustain,
[`t${num}_eg_2_release`]: state.eg2.release,
[`t${num}_lfo_1_key_sync`]: returnValue(state.lfo1.keySync, [
'Off',
'Timbre',
'Voice',
]),
[`t${num}_lfo_1_wave`]: returnValue(state.lfo1.waveform, [
'Saw',
'Square 1',
'Triangle',
'Sample & Hold',
]),
[`t${num}_lfo_1_frequency`]: state.lfo1.frequency,
[`t${num}_lfo_1_tempo_sync`]: state.lfo1.tempoSync,
[`t${num}_lfo_1_sync_note`]: returnValue(state.lfo1.syncNote, [
'1/1',
'3/4',
'2/3',
'1/2',
'3/8',
'1/3',
'1/4',
'3/16',
'1/6',
'1/8',
'3/32',
'1/12',
'1/16',
'1/24',
'1/32',
]),
[`t${num}_lfo_2_key_sync`]: returnValue(state.lfo2.keySync, [
'Off',
'Timbre',
'Voice',
]),
[`t${num}_lfo_2_wave`]: returnValue(state.lfo2.waveform, [
'Saw',
'Square 2',
'Sine',
'Sample & Hold',
]),
[`t${num}_lfo_2_frequency`]: state.lfo2.frequency,
[`t${num}_lfo_2_tempo_sync`]: state.lfo2.tempoSync,
[`t${num}_lfo_2_sync_note`]: returnValue(state.lfo2.syncNote, [
'1/1',
'3/4',
'2/3',
'1/2',
'3/8',
'1/3',
'1/4',
'3/16',
'1/6',
'1/8',
'3/32',
'1/12',
'1/16',
'1/24',
'1/32',
]),
[`t${num}_patch_1_dest`]: returnValue(state.patches.patch1.destination, [
'Pitch',
'OSC 2 Tune',
'OSC 1 Control 1',
'Noise Level',
'Cutoff',
'Amp',
'Pan',
'LFO 2 Frequency',
]),
[`t${num}_patch_1_src`]: returnValue(state.patches.patch1.source, [
'Filter EG',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel',
]),
[`t${num}_patch_1_intensity`]: state.patches.patch1.modIntensity,
[`t${num}_patch_2_dest`]: returnValue(state.patches.patch2.destination, [
'Pitch',
'OSC 2 Tune',
'OSC 1 Control 1',
'Noise Level',
'Cutoff',
'Amp',
'Pan',
'LFO 2 Frequency',
]),
[`t${num}_patch_2_src`]: returnValue(state.patches.patch2.source, [
'Filter EG',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel',
]),
[`t${num}_patch_2_intensity`]: state.patches.patch2.modIntensity,
[`t${num}_patch_3_dest`]: returnValue(state.patches.patch3.destination, [
'Pitch',
'OSC 2 Tune',
'OSC 1 Control 1',
'Noise Level',
'Cutoff',
'Amp',
'Pan',
'LFO 2 Frequency',
]),
[`t${num}_patch_3_src`]: returnValue(state.patches.patch3.source, [
'Filter EG',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel',
]),
[`t${num}_patch_3_intensity`]: state.patches.patch3.modIntensity,
[`t${num}_patch_4_dest`]: returnValue(state.patches.patch4.destination, [
'Pitch',
'OSC 2 Tune',
'OSC 1 Control 1',
'Noise Level',
'Cutoff',
'Amp',
'Pan',
'LFO 2 Frequency',
]),
[`t${num}_patch_4_src`]: returnValue(state.patches.patch4.source, [
'Filter EG',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel',
]),
[`t${num}_patch_4_intensity`]: state.patches.patch4.modIntensity,
};
}
function state_vocoder(state) {
return {
v_midi_channel: state.unused.midiChannel,
v_assign_mode: returnValue(state.pitch.assignMode, [
'Mono',
'Poly',
'Unison',
]),
v_eg_2_reset: state.unused.egReset2,
v_eg_1_reset: state.eg1.egReset,
v_trigger_mode: returnValue(state.pitch.triggerMode, [
'Single',
'Multiple',
]),
v_key_priority: state.unused.keyPriority,
v_unison_detune: state.pitch.unisonDetune,
v_tune: state.pitch.tune + 64,
v_bend_range: state.pitch.bendRange,
v_transpose: state.pitch.transpose + 64,
v_vibrato_int: state.pitch.vibratoIntensity,
v_osc_wave: returnValue(
state.vco1.waveform,
'Saw',
'Square',
'Triangle',
'Sine',
'Vox Wave',
'DWGS',
'Noise',
'Audio In'
),
v_osc_ctrl_1: state.vco1.waveMod,
v_osc_ctrl_2: state.vco1.lfoMod,
v_osc_dgws: returnValue(
state.vco1.dwgsOrModType,
'SynSine1',
'SynSine2',
'SynSine3',
'SynSine4',
'SynSine5',
'SynSine6',
'SynSine7',
'SynBass1',
'SynBass2',
'SynBass3',
'SynBass4',
'SynBass5',
'SynBass6',
'SynBass7',
'SynWave1',
'SynWave2',
'SynWave3',
'SynWave4',
'SynWave5',
'SynWave6',
'SynWave7',
'SynWave8',
'SynWave9',
'5thWave1',
'5thWave2',
'5thWave3',
'Digi1',
'Digi2',
'Digi3',
'Digi4',
'Digi5',
'Digi6',
'Digi7',
'Digi8',
'Endless',
'E.Piano1',
'E.Piano2',
'E.Piano3',
'E.Piano4',
'Organ1',
'Organ2',
'Organ3',
'Organ4',
'Organ5',
'Organ6',
'Organ7',
'Clav1',
'Clav2',
'Guitar1',
'Guitar2',
'Guitar3',
'Bass1',
'Bass2',
'Bass3',
'Bass4',
'Bass5',
'Bell1',
'Bell2',
'Bell3',
'Bell4',
'Voice1',
'Voice2',
'Voice3',
'Voice4'
),
v_audio_in_hpf_gate: state.audioIn.hpfGate,
v_portamento_time: state.pitch.portamento,
v_osc_level: state.mixer.vol1,
v_ext_level: state.mixer.vol2,
v_noise_level: state.mixer.vol3,
v_audio_in_hpf_level: state.audioIn.hpfLevel,
v_audio_in_gate_sense: state.audioIn.gate,
v_audio_in_threshold: state.audioIn.threshold,
v_filter_shift: returnValue(state.filter.shift, [0, 1, 2, -1, -2]),
v_filter_cutoff: state.filter.cutoff,
v_filter_resonance: state.filter.resonance,
v_filter_mod_source: returnValue(state.fcMod.source, [
'---',
'Amp EG',
'LFO 1',
'LFO 2',
'Velocity',
'Keyboard Track',
'Pitch Bend',
'Mod Wheel',
]),
v_filter_intensity: state.fcMod.intensity,
v_filter_e_f_sense: state.filter.efSense,
v_amp_level: state.amp.ampLevel,
v_amp_direct_level: state.amp.directLevel,
v_amp_distortion: state.amp.distortion,
v_amp_vel_sense: state.unused.ampVelSense,
v_amp_key_track: state.amp.keyTrack,
v_eg_1_attack: state.eg1.attack,
v_eg_1_decay: state.eg1.decay,
v_eg_1_sustain: state.eg1.sustain,
v_eg_1_release: state.eg1.release,
v_eg_2_attack: state.unused.eg2attack,
v_eg_2_decay: state.unused.eg2decay,
v_eg_2_sustain: state.unused.eg2sustain,
v_eg_2_release: state.unused.eg2release,
v_lfo_1_key_sync: returnValue(state.lfo1.keySync, [
'Off',
'Timbre',
'Voice',
]),
v_lfo_1_wave: returnValue(state.lfo1.waveform, [
'Saw',
'Square 1',
'Triangle',
'Sample & Hold',
]),
v_lfo_1_frequency: state.lfo1.frequency,
v_lfo_1_tempo_sync: state.lfo1.tempoSync,
v_lfo_1_sync_note: state.lfo1.syncNote,
v_lfo_2_key_sync: returnValue(state.lfo2.keySync, [
'Off',
'Timbre',
'Voice',
]),
v_lfo_2_wave: returnValue(state.lfo2.waveform, [
'Saw',
'Square 2',
'Sine',
'Sample & Hold',
]),
v_lfo_2_frequency: state.lfo2.frequency,
v_lfo_2_tempo_sync: state.lfo2.tempoSync,
v_lfo_2_sync_note: state.lfo2.syncNote,
v_level_1: state.levels.level1,
v_level_2: state.levels.level2,
v_level_3: state.levels.level3,
v_level_4: state.levels.level4,
v_level_5: state.levels.level5,
v_level_6: state.levels.level6,
v_level_7: state.levels.level7,
v_level_8: state.levels.level8,
v_pan_1: state.pans.pan1,
v_pan_2: state.pans.pan2,
v_pan_3: state.pans.pan3,
v_pan_4: state.pans.pan4,
v_pan_5: state.pans.pan5,
v_pan_6: state.pans.pan6,
v_pan_7: state.pans.pan7,
v_pan_8: state.pans.pan8,
};
}
+5
View File
@@ -0,0 +1,5 @@
export default function serialise(patch) {
serialise_patch(patch);
}
function serialise_patch(patch) {}
+18
View File
@@ -0,0 +1,18 @@
import { sysex_decode, sysex_check } from './utils/sysex';
import parse from './convert/parse';
import serialise from './convert/serialise';
import build_store from './convert/loadStore';
import saveStoreState from './convert/saveStore';
function return_store_from_file(file) {
sysex_check(file);
const parsed = parse(sysex_decode(file));
return build_store(...parsed);
}
function return_file_from_store(state) {
const simple_state = saveStoreState(state);
console.log(serialise(simple_state));
}
export { return_store_from_file, return_file_from_store };
-257
View File
@@ -1,257 +0,0 @@
import { Parser } from 'binary-parser-encoder';
const timbre = new Parser()
.int8('midiChannel') // in 7 bits
.bit2('assignMode')
.bit1('eg2Reset')
.bit1('eg1Reset')
.bit1('triggerMode')
.bit2('keyPriority')
.uint8('unisonDetune')
.uint8('pitchTune')
.uint8('pitchBendRange')
.uint8('pitchTranspose')
.uint8('pitchVibratoInt')
.uint8('osc1Wave')
.uint8('osc1WaveCtrl1')
.uint8('osc1WaveCtrl2')
.uint8('osc1DWGSWave')
.skip(1)
.bit2('not-use1')
.bit2('osc2ModSelect')
.bit2('not-use2')
.bit2('osc2Wave')
.uint8('osc2Semitone')
.uint8('osc2Tune')
.bit1('not-use3')
.bit7('portamentoTime')
.uint8('osc1Level')
.uint8('osc2Level')
.uint8('noiseLevel')
.uint8('filterType')
.uint8('filterCutoff')
.uint8('filterResonance')
.uint8('filterEG1Intensity')
.uint8('filterVelocitySense')
.uint8('filterKeyboardTrack')
.uint8('ampLevel')
.uint8('ampPanpot')
.bit1('not-use4')
.bit1('ampSW')
.bit5('not-use5')
.bit1('ampDistortion')
.uint8('ampVelocitySense')
.uint8('ampKeyboardTrack')
.uint8('eg1Attack')
.uint8('eg1Decay')
.uint8('eg1Sustain')
.uint8('eg1Release')
.uint8('eg2Attack')
.uint8('eg2Decay')
.uint8('eg2Sustain')
.uint8('eg2Release')
.bit2('not-use6')
.bit2('lfo1KeySync')
.bit2('not-use7')
.bit2('lfo1Wave')
.uint8('lfo1Freq')
.bit1('lfo1TempoSync')
.bit2('not-use8')
.bit5('lfo1SyncNote')
.bit2('not-use9')
.bit2('lfo2KeySync')
.bit2('not-use10')
.bit2('lfo2Wave')
.uint8('lfo2Freq')
.bit1('lfo2TempoSync')
.bit2('not-use11')
.bit5('lfo2SyncNote')
.bit4('patch1Dest')
.bit4('patch1Src')
.uint8('patch1Intensity')
.bit4('patch2Dest')
.bit4('patch2Src')
.uint8('patch2Intensity')
.bit4('patch3Dest')
.bit4('patch3Src')
.uint8('patch3Intensity')
.bit4('patch4Dest')
.bit4('patch4Src')
.uint8('patch4Intensity');
const vocoder = new Parser()
.int8('midiChannel') // in 7 bits
.bit2('assignMode')
.bit1('eg2Reset')
.bit1('eg1Reset')
.bit1('triggerMode')
.bit2('keyPriority')
.uint8('unisonDetune')
.uint8('pitchTune')
.uint8('pitchBendRange')
.uint8('pitchTranspose')
.uint8('pitchVibratoInt')
.uint8('osc1Wave')
.uint8('osc1WaveCtrl1')
.uint8('osc1WaveCtrl2')
.uint8('osc1DWGSWave')
.skip(1) // dummy byte
.bit7('not-use17')
.bit1('HPF Gate')
.skip(1)
.bit1('not-use18')
.bit7('portamentoTime')
.uint8('osc1Level')
.uint8('ext1Level')
.uint8('noiseLevel')
.uint8('hpfLevel')
.uint8('gateSense')
.uint8('threshold')
.uint8('shift')
.uint8('cutoff')
.uint8('resonance')
.uint8('modSource')
.uint8('intensity')
.uint8('efSense')
.uint8('ampLevel')
.uint8('ampDirectLevel')
.bit7('not-use19')
.bit1('ampDistortion')
.uint8('velocitySense')
.uint8('keyTrack')
// always 0
.uint8('eg1Attack')
// always 0
.uint8('eg1Decay')
// always 127
.uint8('eg1Sustain')
// always 0
.uint8('eg1Release')
.uint8('eg2Attack')
.uint8('eg2Decay')
.uint8('eg2Sustain')
.uint8('eg2Release')
.bit2('not-use20')
.bit2('lfo1KeySync')
.bit2('not-use21')
.bit2('lfo1Wave')
.uint8('lfo1Freq')
.bit1('lfo1TempoSync')
.bit2('not-use22')
.bit5('lfo1SyncNote')
.bit2('not-use23')
.bit2('lfo2KeySync')
.bit2('not-use24')
.bit2('lfo2Wave')
.uint8('lfo2Freq')
.bit1('lfo2TempoSync')
.bit2('not-use25')
.bit5('lfo2SyncNote')
.array('levels', {
type: 'uint8',
length: 16,
})
.array('pans', {
type: 'uint8',
length: 16,
})
.array('efSenseLevels', {
type: 'uint8',
length: 16,
});
const parser = new Parser()
.endianess('big')
.string('name', { encoding: 'ASCII', length: 12 })
.skip(2)
.bit5('not-use12')
.bit3('arpTriggerLength')
.bit8('arpTriggerPattern')
.bit2('not-use13')
.bit2('voiceMode')
.bit4('not-use14')
.bit4('scaleKey')
.bit4('scaleType')
.skip(1)
.bit1('delayFxSync')
.bit3('not-use15')
.bit4('delayFXTimeBase')
.uint8('delayFXDelayTime')
.uint8('delayFXDepth')
.uint8('delayFXType')
.uint8('modFXLFOSpeed')
.uint8('modFXDepth')
.uint8('modFXType')
.uint8('eqHiFreq')
.uint8('eqHiGain')
.uint8('eqLowFreq')
.uint8('eqLowGain')
.uint8('arpTempo')
.uint8('arpSeqTempo')
.bit1('arpOnOff')
.bit1('arpLatchOnOff')
.bit2('arpTarget')
.bit1('not-use16')
.bit1('arpKeySync')
.bit4('arpRange')
.bit4('arpType')
.uint8('arpGateTime')
.uint8('arpResolution')
.uint8('arpSwing')
.uint8('kbdOctave')
.nest('timbre1', { type: timbre })
// dummy bytes *see documentation
.skip(56)
.nest('timbre2', { type: timbre });
// dummy bytes *see documentation
//.skip(56)
//.nest('vocoder', { type: vocoder })
// dummy bytes *see documentation
// .skip(111);
function parse(parser, binary) {
// check if the binary is a valid KORG-produced SYSEX file
if (binary.length !== 297) {
throw new Error('incorrect file length');
}
if (binary[0] !== 0xf0) {
throw new Error('first byte is not SYSEX byte');
}
if (binary[1] !== 0x42) {
throw new Error('manufacturer code is not KORG');
}
if (binary[4] !== 0x40) {
throw new Error('not a program dump');
}
if (binary[5] !== 0x00) {
// in all files found in the wild it's always zero
throw new Error('sixth byte is not zero');
}
if (binary[binary.length - 1] !== 0xf7) {
throw new Error('last byte is does not end SYSEX message');
}
const trimmedBinary = binary.slice(5, -1);
// every eigth byte is padding, so we remove it
const unpaddedBinary = trimmedBinary.filter((_, idx) => idx % 8);
const buffer = Buffer.from(unpaddedBinary).toString('hex');
console.log(buffer);
console.log(parser.parse(unpaddedBinary));
// do stuff with the parsed object
}
// converts patch object to dump format the system uses
function serialise(parser, object) {
// output object
console.log(parser.encode(object));
}
function Parse(object) {
return parse(parser, object);
}
export { Parse, serialise };
+32
View File
@@ -0,0 +1,32 @@
const toBitArray = (n, b = 8) => [...Array(b)].map((x, i) => (n >> i) & 1);
const getBits = (byte, indexStart, indexEnd = 7) =>
parseInt(
toBitArray(byte)
.slice(indexStart, indexEnd + 1)
.reverse()
.join(''),
2
);
const signedNumber = (byte) => (byte > 63 ? (byte ^ 64) - 64 : byte);
console.assert(1 === signedNumber(1));
console.assert(-1 === signedNumber(127));
// first argument must be type from file, and the rest the possible types in correct order (midi implementation)
function returnFromList(...args) {
for (var i = 1; i < arguments.length; i++) {
if (i - 1 === arguments[0]) {
return arguments[i];
}
}
}
function returnValue(setting, list) {
for (var i = 0; i < list.length; i++) {
if (list[i] === setting) return i;
}
}
export { toBitArray, getBits, signedNumber, returnFromList, returnValue };
+126
View File
@@ -0,0 +1,126 @@
// Copyright 2021 Aoife Bradley
//
// Author: Aoife Bradley (aoife@spinlock.icu)
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/*
SYSEX Midi Functions for the microKORG
As per the microKORG midi specification found here: http://i.korg.com/uploads/Support/MK1_633652915168960000.pdf
How does this work?
The data handled by the microKORG is in 'sets' with each 'set' composed of 7 Bytes with 8 Bits as such:
0b0000 0000
0b1111 1111
0bnnnn nnnn
0b6666 6666
this gets turned into 'sets' of 8 bytes with 7 Bits:
0b0654 3210 -- this stores the MSB of the following 7 Bytes
0b0000 0000
0b0111 1111
0b0nnn nnnn
0b0666 6666
In addition, the data is stored with an extra 2 Bytes, which gets converted into 3 bytes in the SYSEX format.
Handily, they displayed this as:
CURRENT PROGRAM DATA (IN CURRENT BUFFER) DUMP FORMAT
254bytes = 7*36+2 -> 8*36+(1+2) => 291Bytes
It probably has something to do with dumping multiple programs as well as global settings, but I'm not handling those here.
*/
const SYSEX_LENGTH = 36 * 8 + (1 + 2);
const DATA_LENGTH = 36 * 7 + 2;
export function sysex_encode(data) {
let sysex = new Uint8Array(SYSEX_LENGTH);
let data_index = 0,
syx_index = 0;
while (data_index < DATA_LENGTH) {
if (data_index + 7 <= DATA_LENGTH) {
sysex[syx_index] =
((data[data_index] & 0x80) >> 7) |
((data[data_index + 1] & 0x80) >> 6) |
((data[data_index + 2] & 0x80) >> 5) |
((data[data_index + 3] & 0x80) >> 4) |
((data[data_index + 4] & 0x80) >> 3) |
((data[data_index + 5] & 0x80) >> 2) |
((data[data_index + 6] & 0x80) >> 1);
sysex[syx_index + 1] = data[data_index] & 0x7f;
sysex[syx_index + 2] = data[data_index + 1] & 0x7f;
sysex[syx_index + 3] = data[data_index + 2] & 0x7f;
sysex[syx_index + 4] = data[data_index + 3] & 0x7f;
sysex[syx_index + 5] = data[data_index + 4] & 0x7f;
sysex[syx_index + 6] = data[data_index + 5] & 0x7f;
sysex[syx_index + 7] = data[data_index + 6] & 0x7f;
}
data_index += 7;
syx_index += 8;
}
return sysex;
}
export function sysex_write_header(data, channel) {
const ch = 0x30 + channel;
return new Uint8Array(5 + SYSEX_LENGTH + 1)
.set([0xf0, 0x42, ch, 0x58, 0x40], 0)
.set(data, 5)
.set(0xf7, -1);
}
export function sysex_decode(syx) {
const trimmed_data = syx.slice(5, -4);
let data = new Uint8Array(DATA_LENGTH);
let syx_index = 0,
data_index = 0;
while (syx_index < trimmed_data.length) {
data[data_index] =
((trimmed_data[syx_index] & 0x1) << 7) | trimmed_data[syx_index + 1];
data[data_index + 1] =
((trimmed_data[syx_index] & 0x2) << 6) | trimmed_data[syx_index + 2];
data[data_index + 2] =
((trimmed_data[syx_index] & 0x4) << 5) | trimmed_data[syx_index + 3];
data[data_index + 3] =
((trimmed_data[syx_index] & 0x8) << 4) | trimmed_data[syx_index + 4];
data[data_index + 4] =
((trimmed_data[syx_index] & 0x10) << 3) | trimmed_data[syx_index + 5];
data[data_index + 5] =
((trimmed_data[syx_index] & 0x20) << 2) | trimmed_data[syx_index + 6];
data[data_index + 6] =
((trimmed_data[syx_index] & 0x40) << 1) | trimmed_data[syx_index + 7];
data_index += 7;
syx_index += 8;
}
return data;
}
export function sysex_check(sysex) {
if (sysex.length !== 297) {
throw new Error('incorrect file length');
}
if (sysex[0] !== 0xf0) {
throw new Error('first byte is not SYSEX byte');
}
if (sysex[1] !== 0x42) {
throw new Error('manufacturer code is not KORG');
}
if (sysex[4] !== 0x40) {
throw new Error('not a program dump');
}
if (sysex[sysex.length - 1] !== 0xf7) {
throw new Error('last byte is does not end SYSEX message');
}
}
-247
View File
@@ -1,247 +0,0 @@
.menu,
.menu-true,
.menu-false {
position: fixed;
background: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 99;
top: 0;
left: 0;
bottom: 0;
right: 0;
min-height: 100vh;
min-width: 100vw;
}
.menu-false {
display: none;
}
.menu-true-animation {
-webkit-animation: scale-in-out-tl 0.3s
cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
animation: scale-in-out-tl 0.3s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
}
.menu-false-animation {
-webkit-animation: scale-in-out-tl 0.3s
cubic-bezier(0.55, 0.085, 0.68, 0.53) reverse both;
animation: scale-in-out-tl 0.3s cubic-bezier(0.55, 0.085, 0.68, 0.53)
reverse both;
}
/* ----------------------------------------------
* Generated by Animista on 2020-11-21 14:21:54
* Licensed under FreeBSD License.
* See http://animista.net/license for more info.
* w: http://animista.net, t: @cssanimista
* ---------------------------------------------- */
/**
* ----------------------------------------
* animation scale-out-tl
* ----------------------------------------
*/
@-webkit-keyframes scale-in-out-tl {
0% {
-webkit-transform: scale(1);
transform: scale(1);
clip-path: circle(100%);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
opacity: 1;
}
100% {
-webkit-transform: scale(0);
transform: scale(0);
clip-path: circle(0%);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
opacity: 0;
}
}
@keyframes scale-in-out-tl {
0% {
-webkit-transform: scale(1);
transform: scale(1);
-webkit-transform-origin: 0 0;
clip-path: circle(100%);
transform-origin: 0 0;
opacity: 1;
}
100% {
-webkit-transform: scale(0);
transform: scale(0);
clip-path: circle(0%);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
opacity: 0;
}
}
.menu-logo {
height: 40vmin;
pointer-events: none;
padding: 0.5em;
-webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}
.menu-title {
color: #1c1d21;
font-family: 'Lato', sans-serif;
line-height: 1em;
margin: 0 0 0.5em 0;
}
h1 {
margin: 0.25em;
font-weight: 700;
line-height: 0.85em;
font-size: 2.5em;
}
h2 {
margin: 0.25em;
font-weight: 500;
line-height: 1em;
font-size: 1em;
}
.menu-button,
.menu-open-button {
background: transparent;
display: inline-block;
margin: 1em;
padding: 0.35em 1em;
border: 0.1em solid slateblue;
border-radius: 0.12em;
color: slateblue;
font-family: 'Roboto', sans-serif;
font-size: 1em;
font-weight: 300;
text-align: center;
outline: none;
transition: all 0.2s ease-in-out;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
}
.menu-button:hover {
color: white;
background: slateblue;
}
.menu-open-button:hover {
color: white;
background: #ef476f;
}
.menu-open-button {
z-index: 100;
height: 1.495em;
line-height: 0.9em;
position: absolute;
padding: 0em 0.3em;
color: #ef476f;
border: 0.09em solid #ef476f;
margin: 0;
top: 0.55em;
left: 0.5em;
}
/* change how the button is handled when screen is smaller then 880px*/
.menu-open-button::before {
content: '\2630';
}
.modal {
margin: auto;
height: 8em;
width: 14em;
position: fixed;
z-index: 100;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.modal-hide {
display: none;
}
.modal-display {
display: block;
}
.modal-main {
background: white;
padding: 0.75em 0 1em;
border: solid 0.09em slategrey;
border-radius: 6px;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.modal-header {
font-size: 0.75em;
line-height: 1.25em;
text-align: left;
margin: 0em 9em 0 0;
}
.modal-text {
flex-grow: 3;
margin: 0.5em 0em 1em 1.85em;
text-align: left;
line-height: 1.5em;
font-size: 0.65em;
font-weight: 300;
}
.button-save,
.button-no-save,
.button-cancel {
width: 25%;
font-size: 0.5em;
margin: 0 0.5em;
border-radius: 0.2em;
padding: 0.35em 0;
}
.button-save {
color: mediumseagreen;
border-color: mediumseagreen;
}
.button-save:hover {
background: mediumseagreen;
}
.button-no-save {
color: crimson;
border-color: crimson;
}
.button-no-save:hover {
background: crimson;
}
.button-cancel {
color: slategray;
border-color: grey;
}
.button-cancel:hover {
background: slategray;
}
-146
View File
@@ -1,146 +0,0 @@
import React, { useState, useRef, useEffect } from 'react';
import IO from '../io/IO';
import { useDispatch, useSelector } from 'react-redux'; // probably temporarily here
import { parameterRefreshAll } from '../timbre/parameters/parameterSlice'; // probably temporarily here
import logo from '../../1f3b9.svg';
import './Menu.css';
// for things such as (do you want to save? or do you want to reset things?)
const Modal = ({ children, show, setShow }) => {
const node = useRef();
const modalDisplay = show ? 'modal modal-display' : 'modal modal-hide';
useEffect(() => {
const current = node.current;
const handleOutsideClick = (event) => {
console.log('clicking anywhere');
if (current.contains(event.target)) {
return;
} else setShow();
};
if (show) {
document.addEventListener('mousedown', handleOutsideClick);
} else {
document.removeEventListener('mousedown', handleOutsideClick);
}
return () => {
document.removeEventListener('mousedown', handleOutsideClick);
};
}, [show, setShow]);
return (
<div ref={node} className={modalDisplay}>
{children}
</div>
);
};
export default function MainMenu() {
const unsaved = useSelector((state) => state.parameters.unsaved);
const [open, setOpen] = useState({
className: '',
openState: true,
});
const [showModal, setShowModal] = useState({
saveCheck: false,
ioModal: false,
});
const dispatch = useDispatch();
const HandleClick = () => {
setOpen({
className: open.openState
? 'menu-true-animation'
: 'menu-false-animation',
openState: !open.openState,
});
};
const AnimationEnd = () => {
setOpen({
...open,
className: `menu-${open.openState}`,
});
};
const showSaveCheck = () => {
setShowModal({ ...showModal, saveCheck: !showModal.saveCheck });
};
const showIOModal = () => {
setShowModal({ ...showModal, ioModal: !showModal.ioModal });
};
return (
<div>
<Modal show={showModal.saveCheck} setShow={showSaveCheck}>
<div className="modal-main">
<span className="modal-header">Save Changes</span>
<span className="modal-text">
Do you want to save your changes?
</span>
<button
className="menu-button button-save"
onClick={() => {
showSaveCheck();
HandleClick();
}} // temporary while I implement the binary parsing stuff
>
Save Changes
</button>
<button
className="menu-button button-no-save"
onClick={() => {
dispatch(parameterRefreshAll());
showSaveCheck();
HandleClick();
}}>
Don't Save
</button>
<button
className="menu-button button-cancel"
onClick={showSaveCheck}>
Cancel
</button>
</div>
</Modal>
<div
className={`menu ${open.className}`}
onAnimationEnd={AnimationEnd}>
<img
src={logo}
className="menu-logo"
alt="logo"
draggable={false}
/>
<header className="menu-title">
<h1>Alapatch</h1>
<h2>A microKORG Patch Editor</h2>
</header>
<div className="menu-button-container">
<button
className="menu-button"
onClick={() => {
!unsaved ? HandleClick() : showSaveCheck();
}}>
New Patch
</button>
<button
className="menu-button"
onClick={() => {
!unsaved ? showIOModal() : showSaveCheck();
}}>
Load Patch
</button>
</div>
</div>
<Modal show={showModal.ioModal} setShow={showIOModal}>
<IO />
</Modal>
<button className="menu-open-button" onClick={HandleClick} />
</div>
);
}
+148
View File
@@ -0,0 +1,148 @@
import { useRef, useState, useEffect } from 'react';
import './menu.css';
import { SelectList } from '../helpers/Helpers';
import { useDispatch, useSelector } from 'react-redux';
import {
parameterUpdateIndex,
parameterUpdated,
parameterFromFile,
parameterRefreshAll,
selectEntities,
} from '../timbre/parameters/parameterSlice'; // probably temporarily here
import { return_store_from_file, return_file_from_store } from '../io';
export default function Menu() {
const [file, setFile] = useState(null);
const fileRef = useRef(null);
const state = useSelector((state) => selectEntities(state));
const dispatch = useDispatch();
const reader = new FileReader();
const loadFile = (f) => {
reader.onloadend = (e) => {
setFile(new Uint8Array(e.target.result));
};
reader.readAsArrayBuffer(f);
};
useEffect(() => {
if (file) {
dispatch(parameterFromFile(return_store_from_file(file)));
}
}, [file, dispatch]);
return (
<div className='menu'>
<div className='menu-patch-container'>
<div className='patch-name'>
<input
placeholder='Patch Name'
type='text'
maxLength='12'
value={state.patch.name}
onChange={(e) => {
dispatch(
parameterUpdated({
id: 'Patch',
changes: { name: e.target.value },
})
);
}}
/>
</div>
<div className='patch-load'>
<button
className='menu-button'
onClick={() => {
dispatch(parameterRefreshAll());
}}>
New Patch
</button>
<button
className='menu-button'
onClick={() => {
fileRef.current.click();
}}>
Load Patch
</button>
<button
className='menu-button'
onClick={() => {
return_file_from_store(state);
}}>
Save Patch
</button>
<input
type='file'
accept='.syx'
className='menu-file-input'
ref={fileRef}
onChange={(e) => {
loadFile(e.target.files[0]);
}}
/>
</div>
<div className='patch-settings'>
<SelectList
value={state.patch.mode}
list={[
{ value: 'Single' },
{ value: 'Multiple' },
{ value: 'Vocoder' },
]}
onChange={(value) => {
dispatch(
parameterUpdated({
id: 'patch',
changes: { mode: value },
})
);
}}
/>
</div>
</div>
<div className='menu-timbre-select'>
<button
disabled={state.patch.mode === 'Vocoder'}
className='menu-button'
onClick={() => {
dispatch(parameterUpdateIndex(0));
}}>
Timbre 1
</button>
<button
disabled={state.patch.mode !== 'Multiple'}
className='menu-button'
onClick={() => {
dispatch(parameterUpdateIndex(1));
}}>
Timbre 2
</button>
<button
disabled={state.patch.mode !== 'Vocoder'}
className='menu-button'
onClick={() => {
dispatch(parameterUpdateIndex(2));
}}>
Vocoder
</button>
<button
className='menu-button'
onClick={() => {
dispatch(parameterUpdateIndex(3));
}}>
Effects
</button>
<button
className='menu-button'
onClick={() => {
dispatch(parameterUpdateIndex(4));
}}>
Arpeggio
</button>
</div>
<div className='menu-title'>
Alapatch <br /> A microKORG Patch Editor
</div>
</div>
);
}
+54
View File
@@ -0,0 +1,54 @@
.menu {
height: 100vh;
min-width: 15vw;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: space-around;
align-items: stretch;
}
.menu-patch-container > * {
margin-top: 10pt;
}
input[type='text'] {
font-family: 'Roboto', sans-serif;
text-indent: 8pt;
width: 70%;
padding: 6pt 0;
outline: none;
border-radius: 4pt;
border: 1px solid gainsboro;
}
input[type='text']:hover {
border-color: silver;
}
input[type='text']:focus {
border: 2px solid dodgerblue;
}
.menu-button {
width: 65%;
display: block;
padding: 4pt;
margin: 8px auto;
outline: none;
}
h2 {
text-align: left;
font-size: x-large;
font-weight: 400;
}
.menu-file-input {
display: none;
}
.patch-settings {
font-size: small;
width: 75%;
margin: 10pt auto;
line-height: 1.5em;
}
+35
View File
@@ -0,0 +1,35 @@
import { useRef, useEffect } from 'react';
const Modal = ({ children, show, setShow }) => {
const node = useRef();
const modalDisplay = show ? 'modal modal-display' : 'modal modal-hide';
useEffect(() => {
const current = node.current;
const handleOutsideClick = (event) => {
console.log('clicking anywhere');
if (current.contains(event.target)) {
return;
} else setShow();
};
if (show) {
document.addEventListener('mousedown', handleOutsideClick);
} else {
document.removeEventListener('mousedown', handleOutsideClick);
}
return () => {
document.removeEventListener('mousedown', handleOutsideClick);
};
}, [show, setShow]);
return (
<div ref={node} className={modalDisplay}>
{children}
</div>
);
};
export default Modal;
+14 -14
View File
@@ -16,7 +16,7 @@ export default function Timbre(props) {
return ( return (
<div className={styles.timbreStyle}> <div className={styles.timbreStyle}>
<VCO <VCO
name="VCO 1" name='VCO 1'
id={ids.vco1} id={ids.vco1}
waveforms={[ waveforms={[
{ value: 'Saw' }, { value: 'Saw' },
@@ -28,7 +28,7 @@ export default function Timbre(props) {
{ value: 'Noise' }, { value: 'Noise' },
{ value: 'Audio In' }, { value: 'Audio In' },
]} ]}
dwgsOrModTypeName="DWGS" dwgsOrModTypeName='DWGS'
dwgsOrModType={[ dwgsOrModType={[
{ value: 'SynSine1' }, { value: 'SynSine1' },
{ value: 'SynSine2' }, { value: 'SynSine2' },
@@ -97,30 +97,30 @@ export default function Timbre(props) {
]} ]}
/> />
<VCO <VCO
name="VCO 2" name='VCO 2'
id={ids.vco2} id={ids.vco2}
waveforms={[ waveforms={[
{ value: 'Saw' }, { value: 'Saw' },
{ value: 'Square' }, { value: 'Square' },
{ value: 'Triangle' }, { value: 'Triangle' },
]} ]}
dwgsOrModTypeName="Mod Type" dwgsOrModTypeName='Mod Type'
dwgsOrModType={[ dwgsOrModType={[
{ value: 'Off' }, { value: 'Off' },
{ value: 'Ring' }, { value: 'Ring' },
{ value: 'Sync' }, { value: 'Sync' },
{ value: 'RingSync' }, { value: 'Ring Sync' },
]} ]}
/> />
<Mixer name="Mixer" id={ids.mixer} /> <Mixer name='Mixer' id={ids.mixer} />
<Pitch name="Pitch" id={ids.pitch} /> <Pitch name='Pitch' id={ids.pitch} />
<Filter name="Filter" id={ids.filter} /> <Filter name='Filter' id={ids.filter} />
<Amp name="Amp" id={ids.amp} /> <Amp name='Amp' id={ids.amp} />
<EG name="EG 1" id={ids.eg1} /> <EG name='EG 1' id={ids.eg1} />
<EG name="EG 2" id={ids.eg2} /> <EG name='EG 2' id={ids.eg2} />
<div className={styles.lineBreak} /> <div className={styles.lineBreak} />
<LFO <LFO
name="LFO 1" name='LFO 1'
id={ids.lfo1} id={ids.lfo1}
waveforms={[ waveforms={[
{ value: 'Saw' }, { value: 'Saw' },
@@ -130,7 +130,7 @@ export default function Timbre(props) {
]} ]}
/> />
<LFO <LFO
name="LFO 2" name='LFO 2'
id={ids.lfo2} id={ids.lfo2}
waveforms={[ waveforms={[
{ value: 'Saw' }, { value: 'Saw' },
@@ -139,7 +139,7 @@ export default function Timbre(props) {
{ value: 'Sample & Hold' }, { value: 'Sample & Hold' },
]} ]}
/> />
<Patches name="Patches" id={ids.patches} /> <Patches name='Patches' id={ids.patches} />
</div> </div>
); );
} }
+4 -8
View File
@@ -58,15 +58,14 @@
.tabList { .tabList {
border-bottom: 1px solid black; border-bottom: 1px solid black;
display: inline-flex; display: none;
flex-direction: row; flex-direction: row;
list-style-type: none; list-style-type: none;
justify-content: center; justify-content: center;
top: 0; top: 0;
padding: 0; padding: 0;
margin: 0; margin: 0;
width: 100vw; width: 85vw;
background: white; background: white;
} }
.tab { .tab {
@@ -117,18 +116,15 @@
overflow-y: scroll; overflow-y: scroll;
} }
.tabPanel::-webkit-scrollbar { .tabPanel::-webkit-scrollbar {
width: 0.25em; width: 0.3em;
} }
.tabPanel::-webkit-scrollbar-track, .tabPanel::-webkit-scrollbar-track,
.tabPanel::-webkit-scrollbar-thumb { .tabPanel::-webkit-scrollbar-thumb {
background-color: transparent; background-color: transparent;
} }
.tabPanel:hover::-webkit-scrollbar-track {
background-color: lightgray;
}
.tabPanel:hover::-webkit-scrollbar-thumb { .tabPanel:hover::-webkit-scrollbar-thumb {
background-color: slateblue; background-color: lightgray;
border-radius: 6px; border-radius: 6px;
} }
+33 -3
View File
@@ -9,7 +9,7 @@ export default function LFO(props) {
const dispatch = useDispatch(); const dispatch = useDispatch();
return ( return (
<div className={styles.container}> <div className={styles.lfo}>
<span className={styles.header}>{`${props.name}`}</span> <span className={styles.header}>{`${props.name}`}</span>
LFO Waveform LFO Waveform
<SelectList <SelectList
@@ -27,7 +27,7 @@ export default function LFO(props) {
Key Sync Key Sync
<SelectList <SelectList
value={parameters.keySync} value={parameters.keySync}
list={['Off', 'Timbre', 'Voice']} list={[{ value: 'Off' }, { value: 'Timbre' }, { value: 'Voice' }]}
onChange={(value) => onChange={(value) =>
dispatch( dispatch(
parameterUpdated({ parameterUpdated({
@@ -37,8 +37,9 @@ export default function LFO(props) {
) )
} }
/> />
<div className='check-list'>
<Checkbox <Checkbox
name="Tempo Sync" name='Tempo Sync'
parameter={parameters.tempoSync} parameter={parameters.tempoSync}
onChange={(value) => onChange={(value) =>
dispatch( dispatch(
@@ -49,6 +50,35 @@ export default function LFO(props) {
) )
} }
/> />
<SelectList
value={parameters.syncNote}
list={[
{ value: '1/1' },
{ value: '3/4' },
{ value: '2/3' },
{ value: '1/2' },
{ value: '3/8' },
{ value: '1/3' },
{ value: '1/4' },
{ value: '3/16' },
{ value: '1/6' },
{ value: '1/8' },
{ value: '3/32' },
{ value: '1/12' },
{ value: '1/16' },
{ value: '1/24' },
{ value: '1/32' },
]}
onChange={(value) =>
dispatch(
parameterUpdated({
id: props.id,
changes: { syncNote: value },
})
)
}
/>
</div>
Frequency Frequency
<Slider <Slider
parameter={parameters.frequency} parameter={parameters.frequency}
@@ -1,11 +1,13 @@
.container, .container,
.lfo,
.horizontalContainer, .horizontalContainer,
.patchesContainer { .patchesContainer {
border: solid 1pt slategrey; border: solid 1pt slategrey;
border-radius: 6px; border-radius: 6px;
} }
.container { .container,
.lfo {
position: relative; position: relative;
font-size: small; font-size: small;
display: inline-flex; display: inline-flex;
@@ -17,6 +19,12 @@
height: 214px; height: 214px;
line-height: 1.5em; line-height: 1.5em;
} }
.lfo {
height: 250px;
}
.lfo > *,
.container > * { .container > * {
margin: 3pt 0; margin: 3pt 0;
} }
@@ -31,6 +39,7 @@
border-right: 1px solid slategrey; border-right: 1px solid slategrey;
box-sizing: border-box; box-sizing: border-box;
} }
.lfo > .header,
.container > .header { .container > .header {
position: absolute; position: absolute;
top: -7.5%; top: -7.5%;
@@ -66,7 +75,7 @@
padding: 16pt 8pt 16pt 8pt; padding: 16pt 8pt 16pt 8pt;
margin: 10pt; margin: 10pt;
width: 680px; width: 680px;
height: 214px; height: 250px;
} }
.horizontalContainer > .container, .horizontalContainer > .container,
@@ -41,6 +41,11 @@ const defaultState = {
'eq', 'eq',
'arpeggA', 'arpeggA',
'arpeggB', 'arpeggB',
'patch',
'unusedPatch',
'unusedTimbre1',
'unusedTimbre2',
'unusedVocoder',
], ],
entities: { entities: {
vco1: { vco1: {
@@ -154,6 +159,7 @@ const defaultState = {
}, },
filter3: { filter3: {
id: 'filter3', id: 'filter3',
shift: 2,
cutoff: 0, cutoff: 0,
resonance: 0, resonance: 0,
efSense: 0, efSense: 0,
@@ -230,6 +236,7 @@ const defaultState = {
keySync: 'Off', keySync: 'Off',
tempoSync: false, tempoSync: false,
frequency: 0, frequency: 0,
syncNote: '1/1',
}, },
lfo2: { lfo2: {
id: 'lfo2', id: 'lfo2',
@@ -237,6 +244,7 @@ const defaultState = {
keySync: 'Off', keySync: 'Off',
tempoSync: false, tempoSync: false,
frequency: 0, frequency: 0,
syncNote: '1/1',
}, },
lfo3: { lfo3: {
id: 'lfo3', id: 'lfo3',
@@ -244,6 +252,7 @@ const defaultState = {
keySync: 'Off', keySync: 'Off',
tempoSync: false, tempoSync: false,
frequency: 0, frequency: 0,
syncNote: '1/1',
}, },
lfo4: { lfo4: {
id: 'lfo4', id: 'lfo4',
@@ -251,6 +260,7 @@ const defaultState = {
keySync: 'Off', keySync: 'Off',
tempoSync: false, tempoSync: false,
frequency: 0, frequency: 0,
syncNote: '1/1',
}, },
lfo5: { lfo5: {
id: 'lfo5', id: 'lfo5',
@@ -258,13 +268,14 @@ const defaultState = {
keySync: 'Off', keySync: 'Off',
tempoSync: false, tempoSync: false,
frequency: 0, frequency: 0,
syncNote: '1/1',
}, },
lfo6: { lfo6: {
id: 'lfo6', id: 'lfo6',
waveform: 'Saw', waveform: 'Saw',
keySync: 'Off', keySync: 'Off',
tempoSync: false, tempoSync: false,
frequency: 0, frequency: '1/1',
}, },
patches1: { patches1: {
id: 'patches1', id: 'patches1',
@@ -370,9 +381,51 @@ const defaultState = {
keySync: false, keySync: false,
lastStep: 1, lastStep: 1,
target: 'Both', target: 'Both',
pattern: [0, 0, 0, 0, 0, 0, 0, 0],
},
patch: {
id: 'patch',
name: 'Default',
mode: 'Single',
},
unusedPatch: {
id: 'unusedPatch',
scaleKey: 0,
scaleType: 0,
keyboardOctave: 0,
arpSwitch: 0,
},
unusedTimbre1: {
id: 'unusedTimbre1',
midiChannel: 0,
keyPriority: 0,
filterVelSense: 0,
ampSwitch: 0,
ampVelSense: 0,
},
unusedTimbre2: {
id: 'unusedTimbre2',
midiChannel: 0,
keyPriority: 0,
filterVelSense: 0,
ampSwitch: 0,
ampVelSense: 0,
},
unusedVocoder: {
id: 'unusedVocoder',
egReset2: 0,
midiChannel: 0,
keyPriority: 0,
filterEFSense: 0,
ampVelSense: 0,
eg2attack: 0,
eg2decay: 0,
eg2sustain: 0,
eg2release: 0,
}, },
}, },
unsaved: false, unsaved: false,
tabIndex: 0,
}; };
const parametersAdapter = createEntityAdapter({}); const parametersAdapter = createEntityAdapter({});
@@ -389,6 +442,12 @@ const parametersSlice = createSlice({
parameterFlipUnsaved(state) { parameterFlipUnsaved(state) {
state.unsaved = !state.unsaved; state.unsaved = !state.unsaved;
}, },
parameterUpdateIndex(state, action) {
state.tabIndex = action.payload;
},
parameterFromFile(state, action) {
parametersAdapter.upsertMany(state, action.payload);
},
}, },
}); });
@@ -404,6 +463,8 @@ export const {
parameterUpdated, parameterUpdated,
parameterRefreshAll, parameterRefreshAll,
parameterFlipUnsaved, parameterFlipUnsaved,
parameterFromFile,
parameterUpdateIndex,
} = parametersSlice.actions; } = parametersSlice.actions;
export default parametersSlice.reducer; export default parametersSlice.reducer;
+10 -7
View File
@@ -4,14 +4,17 @@ import Vocoder from './Vocoder';
import Effects from './Effects'; import Effects from './Effects';
import Arpeggio from './Arpeggio'; import Arpeggio from './Arpeggio';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import { useSelector } from 'react-redux';
import styles from './Timbre.module.css'; import styles from './Timbre.module.css';
export default function TimbreSwitcher(props) { export default function TimbreSwitcher() {
const index = useSelector((state) => state.parameters.tabIndex);
return ( return (
<div className={styles.tabs}> <div className={styles.tabs}>
<Tabs <Tabs
className={styles.tabs} className={styles.tabs}
defaultIndex={0} selectedIndex={index}
onSelect={() => {}}
selectedTabClassName={styles.selected} selectedTabClassName={styles.selected}
selectedTabPanelClassName={styles.tabPanel}> selectedTabPanelClassName={styles.tabPanel}>
<TabList className={styles.tabList}> <TabList className={styles.tabList}>
@@ -23,19 +26,19 @@ export default function TimbreSwitcher(props) {
</TabList> </TabList>
<TabPanel> <TabPanel>
<Timbre id="timbre1" /> <Timbre id='timbre1' />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<Timbre id="timbre2" /> <Timbre id='timbre2' />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<Vocoder id="vocoder" /> <Vocoder id='vocoder' />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<Effects id="effects" /> <Effects id='effects' />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<Arpeggio id="arpeggio" /> <Arpeggio id='arpeggio' />
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</div> </div>
-2
View File
@@ -1,2 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&family=Noto+Sans+HK&family=Roboto:wght@100;300;400&display=swap');
/* import Lato and Roboto and Noto Fonts*/
+2 -3
View File
@@ -1,8 +1,7 @@
@import url(./fonts.css);
body { body {
margin: 0; margin: 0;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Oxygen',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><g fill="#764ABC"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB