midiwire 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +845 -0
- package/dist/midiwire.es.js +1987 -0
- package/dist/midiwire.umd.js +1 -0
- package/package.json +58 -0
- package/src/bindings/DataAttributeBinder.js +198 -0
- package/src/bindings/DataAttributeBinder.test.js +825 -0
- package/src/core/EventEmitter.js +93 -0
- package/src/core/EventEmitter.test.js +357 -0
- package/src/core/MIDIConnection.js +364 -0
- package/src/core/MIDIConnection.test.js +783 -0
- package/src/core/MIDIController.js +756 -0
- package/src/core/MIDIController.test.js +1958 -0
- package/src/core/MIDIDeviceManager.js +204 -0
- package/src/core/MIDIDeviceManager.test.js +638 -0
- package/src/core/errors.js +99 -0
- package/src/index.js +181 -0
- package/src/utils/dx7.js +1294 -0
- package/src/utils/dx7.test.js +1208 -0
- package/src/utils/midi.js +244 -0
- package/src/utils/midi.test.js +260 -0
- package/src/utils/sysex.js +98 -0
- package/src/utils/sysex.test.js +222 -0
- package/src/utils/validators.js +88 -0
- package/src/utils/validators.test.js +300 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { createSysEx, decode7Bit, encode7Bit, isSysEx, parseSysEx } from "./sysex.js"
|
|
3
|
+
|
|
4
|
+
describe("SysEx Utils", () => {
|
|
5
|
+
describe("parseSysEx", () => {
|
|
6
|
+
it("should parse valid SysEx messages", () => {
|
|
7
|
+
const data = [0xf0, 0x41, 0x10, 0x20, 0x30, 0xf7]
|
|
8
|
+
const result = parseSysEx(data)
|
|
9
|
+
expect(result).toEqual({
|
|
10
|
+
manufacturerId: 0x41,
|
|
11
|
+
payload: [0x10, 0x20, 0x30],
|
|
12
|
+
raw: data,
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it("should return null for non-SysEx messages", () => {
|
|
17
|
+
expect(parseSysEx([0x90, 0x40, 0x7f])).toBeNull() // Note on
|
|
18
|
+
expect(parseSysEx([0xb0, 0x01, 0x40])).toBeNull() // CC
|
|
19
|
+
expect(parseSysEx([0xf0, 0x41, 0x10])).toBeNull() // Missing F7
|
|
20
|
+
expect(parseSysEx([0x41, 0x10, 0xf7])).toBeNull() // Missing F0
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("should handle empty payload", () => {
|
|
24
|
+
const data = [0xf0, 0xf7]
|
|
25
|
+
expect(parseSysEx(data)).toEqual({
|
|
26
|
+
manufacturerId: 0xf7,
|
|
27
|
+
payload: [],
|
|
28
|
+
raw: data,
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("should handle long SysEx messages", () => {
|
|
33
|
+
const payload = new Array(100).fill(0x40)
|
|
34
|
+
const data = [0xf0, 0x41, ...payload, 0xf7]
|
|
35
|
+
const result = parseSysEx(data)
|
|
36
|
+
expect(result.payload.length).toBe(100)
|
|
37
|
+
expect(result.payload.every((byte) => byte === 0x40)).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe("createSysEx", () => {
|
|
42
|
+
it("should create valid SysEx messages", () => {
|
|
43
|
+
const result = createSysEx(0x41, [0x10, 0x20, 0x30])
|
|
44
|
+
expect(result).toEqual([0xf0, 0x41, 0x10, 0x20, 0x30, 0xf7])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("should handle empty payload", () => {
|
|
48
|
+
expect(createSysEx(0x41, [])).toEqual([0xf0, 0x41, 0xf7])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("should handle single byte payload", () => {
|
|
52
|
+
expect(createSysEx(0x41, [0x7f])).toEqual([0xf0, 0x41, 0x7f, 0xf7])
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe("isSysEx", () => {
|
|
57
|
+
it("should identify SysEx messages", () => {
|
|
58
|
+
expect(isSysEx([0xf0, 0x41, 0x10, 0xf7])).toBe(true)
|
|
59
|
+
expect(isSysEx([0xf0, 0xf7])).toBe(true)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it("should reject non-SysEx messages", () => {
|
|
63
|
+
expect(isSysEx([0x90, 0x40, 0x7f])).toBe(false) // Note on
|
|
64
|
+
expect(isSysEx([0xb0, 0x01, 0x40])).toBe(false) // CC
|
|
65
|
+
expect(isSysEx([0xf0, 0x41, 0x10])).toBe(false) // Missing F7
|
|
66
|
+
expect(isSysEx([0x41, 0x10, 0xf7])).toBe(false) // Missing F0
|
|
67
|
+
expect(isSysEx([])).toBe(false) // Empty
|
|
68
|
+
expect(isSysEx([0xf0])).toBe(false) // Too short
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
describe("encode7Bit", () => {
|
|
73
|
+
it("should encode 8-bit data to 7-bit MIDI format", () => {
|
|
74
|
+
// 7 bytes of 8-bit data should encode to 8 bytes of 7-bit data
|
|
75
|
+
const data = [0x80, 0x40, 0x20, 0x7f, 0xff, 0x00, 0x01]
|
|
76
|
+
const result = encode7Bit(data)
|
|
77
|
+
|
|
78
|
+
// Should be exactly 8 bytes (1 header + 7 data)
|
|
79
|
+
expect(result.length).toBe(8)
|
|
80
|
+
|
|
81
|
+
// All bytes should be 7-bit values (<= 0x7f)
|
|
82
|
+
expect(result.every((byte) => byte <= 0x7f)).toBe(true)
|
|
83
|
+
|
|
84
|
+
// First byte is header with packed MSBs: bytes 0 and 4 have MSB=1
|
|
85
|
+
// Bits: 0b0010001 = 0x11 (bits 0 and 4 set)
|
|
86
|
+
expect(result[0]).toBe(0x11)
|
|
87
|
+
|
|
88
|
+
// Remaining bytes are lower 7 bits of each input byte
|
|
89
|
+
expect(result.slice(1)).toEqual([0x00, 0x40, 0x20, 0x7f, 0x7f, 0x00, 0x01])
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it("should handle single byte encoding", () => {
|
|
93
|
+
// Single byte should still work but creates a group
|
|
94
|
+
expect(encode7Bit([0x80])).toEqual([0x01, 0x00])
|
|
95
|
+
expect(encode7Bit([0x40])).toEqual([0x00, 0x40])
|
|
96
|
+
expect(encode7Bit([0x7f])).toEqual([0x00, 0x7f])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("should handle empty data", () => {
|
|
100
|
+
expect(encode7Bit([])).toEqual([])
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("should handle multiple groups", () => {
|
|
104
|
+
// 14 bytes should encode to 16 bytes (2 groups of 8)
|
|
105
|
+
const data = [
|
|
106
|
+
0x80, 0x40, 0x20, 0x7f, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
107
|
+
]
|
|
108
|
+
const result = encode7Bit(data)
|
|
109
|
+
|
|
110
|
+
expect(result.length).toBe(16)
|
|
111
|
+
expect(result.every((byte) => byte <= 0x7f)).toBe(true)
|
|
112
|
+
|
|
113
|
+
// First group header (bytes 0 and 4 have MSB set)
|
|
114
|
+
expect(result[0]).toBe(0x11)
|
|
115
|
+
// First group data bytes
|
|
116
|
+
expect(result.slice(1, 8)).toEqual([0x00, 0x40, 0x20, 0x7f, 0x7f, 0x00, 0x01])
|
|
117
|
+
|
|
118
|
+
// Second group header (no bytes have MSB set)
|
|
119
|
+
expect(result[8]).toBe(0x00)
|
|
120
|
+
// Second group data bytes
|
|
121
|
+
expect(result.slice(9, 16)).toEqual([0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("should handle partial last group", () => {
|
|
125
|
+
// 5 bytes should encode to 6 bytes (header + 5 data)
|
|
126
|
+
const data = [0x80, 0x40, 0x20, 0x7f, 0xff]
|
|
127
|
+
const result = encode7Bit(data)
|
|
128
|
+
|
|
129
|
+
expect(result.length).toBe(6)
|
|
130
|
+
expect(result.every((byte) => byte <= 0x7f)).toBe(true)
|
|
131
|
+
expect(result[0]).toBe(0x11) // Header: bytes 0 and 3 have MSB set
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it("should handle all values from 0x00 to 0xff", () => {
|
|
135
|
+
// Test with values that have various bit patterns
|
|
136
|
+
const data = [0x00, 0x01, 0x7f, 0x80, 0xff]
|
|
137
|
+
const result = encode7Bit(data)
|
|
138
|
+
|
|
139
|
+
expect(result.length).toBe(6)
|
|
140
|
+
expect(result.every((byte) => byte <= 0x7f)).toBe(true)
|
|
141
|
+
|
|
142
|
+
// Check specific values
|
|
143
|
+
expect(result[0]).toBe(0x18) // Header: 0x80 and 0xff have MSB set
|
|
144
|
+
expect(result[1]).toBe(0x00) // 0x00 & 0x7f
|
|
145
|
+
expect(result[2]).toBe(0x01) // 0x01 & 0x7f
|
|
146
|
+
expect(result[3]).toBe(0x7f) // 0x7f & 0x7f
|
|
147
|
+
expect(result[4]).toBe(0x00) // 0x80 & 0x7f
|
|
148
|
+
expect(result[5]).toBe(0x7f) // 0xff & 0x7f
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe("decode7Bit", () => {
|
|
153
|
+
it("should decode 7-bit format to 8-bit data", () => {
|
|
154
|
+
// Decode the 8-byte encoded data back to original 7 bytes
|
|
155
|
+
const encoded = [0x11, 0x00, 0x40, 0x20, 0x7f, 0x7f, 0x00, 0x01]
|
|
156
|
+
const decoded = decode7Bit(encoded)
|
|
157
|
+
|
|
158
|
+
// Should reconstruct the original 7 bytes
|
|
159
|
+
expect(decoded).toEqual([0x80, 0x40, 0x20, 0x7f, 0xff, 0x00, 0x01])
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it("should handle single byte decoding", () => {
|
|
163
|
+
// Single byte encoded
|
|
164
|
+
const result = decode7Bit([0x01, 0x00])
|
|
165
|
+
expect(result).toEqual([0x80])
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it("should handle empty data", () => {
|
|
169
|
+
expect(decode7Bit([])).toEqual([])
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it("should round-trip correctly", () => {
|
|
173
|
+
const testData = [
|
|
174
|
+
[0x00],
|
|
175
|
+
[0x00, 0x01, 0x7f],
|
|
176
|
+
[0x80, 0x40, 0x20, 0x7f, 0xff, 0x00, 0x01],
|
|
177
|
+
[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
|
|
178
|
+
new Array(14).fill(0x42),
|
|
179
|
+
[0x00, 0x01, 0x02, 0x03, 0x04, 0x05],
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
testData.forEach((original) => {
|
|
183
|
+
const encoded = encode7Bit(original)
|
|
184
|
+
const decoded = decode7Bit(encoded)
|
|
185
|
+
expect(decoded).toEqual(original)
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it("should handle multiple groups", () => {
|
|
190
|
+
// Two groups of 8 bytes each
|
|
191
|
+
const encoded = [
|
|
192
|
+
0x11,
|
|
193
|
+
0x00,
|
|
194
|
+
0x40,
|
|
195
|
+
0x20,
|
|
196
|
+
0x7f,
|
|
197
|
+
0x7f,
|
|
198
|
+
0x00,
|
|
199
|
+
0x01, // First group
|
|
200
|
+
0x00,
|
|
201
|
+
0x02,
|
|
202
|
+
0x03,
|
|
203
|
+
0x04,
|
|
204
|
+
0x05,
|
|
205
|
+
0x06,
|
|
206
|
+
0x07,
|
|
207
|
+
0x08, // Second group
|
|
208
|
+
]
|
|
209
|
+
const decoded = decode7Bit(encoded)
|
|
210
|
+
expect(decoded.length).toBe(14)
|
|
211
|
+
expect(decoded.slice(0, 7)).toEqual([0x80, 0x40, 0x20, 0x7f, 0xff, 0x00, 0x01])
|
|
212
|
+
expect(decoded.slice(7)).toEqual([0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it("should handle partial last group", () => {
|
|
216
|
+
// Partial group with 5 data bytes
|
|
217
|
+
const encoded = [0x11, 0x00, 0x40, 0x20, 0x7f, 0x7f]
|
|
218
|
+
const decoded = decode7Bit(encoded)
|
|
219
|
+
expect(decoded).toEqual([0x80, 0x40, 0x20, 0x7f, 0xff])
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate MIDI channel number
|
|
3
|
+
* @param {number} channel - Channel number
|
|
4
|
+
* @returns {boolean}
|
|
5
|
+
*/
|
|
6
|
+
export function isValidChannel(channel) {
|
|
7
|
+
return Number.isInteger(channel) && channel >= 1 && channel <= 16
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validate MIDI CC number
|
|
12
|
+
* @param {number} cc - CC number
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
export function isValidCC(cc) {
|
|
16
|
+
return Number.isInteger(cc) && cc >= 0 && cc <= 127
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate 14-bit MIDI CC number (0-31 for MSB)
|
|
21
|
+
* @param {number} cc - CC number
|
|
22
|
+
* @returns {boolean}
|
|
23
|
+
*/
|
|
24
|
+
export function isValid14BitCC(cc) {
|
|
25
|
+
return Number.isInteger(cc) && cc >= 0 && cc <= 31
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validate MIDI value (0-127)
|
|
30
|
+
* @param {number} value - MIDI value
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
export function isValidMIDIValue(value) {
|
|
34
|
+
return Number.isInteger(value) && value >= 0 && value <= 127
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate MIDI note number
|
|
39
|
+
* @param {number} note - Note number
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function isValidNote(note) {
|
|
43
|
+
return Number.isInteger(note) && note >= 0 && note <= 127
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate note velocity
|
|
48
|
+
* @param {number} velocity - Velocity value
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
export function isValidVelocity(velocity) {
|
|
52
|
+
return Number.isInteger(velocity) && velocity >= 0 && velocity <= 127
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate MIDI program change number
|
|
57
|
+
* @param {number} program - Program number
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
export function isValidProgramChange(program) {
|
|
61
|
+
return Number.isInteger(program) && program >= 0 && program <= 127
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validate pitch bend value (14-bit)
|
|
66
|
+
* @param {number} value - Pitch bend value (0-16383, where 8192 is center)
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
export function isValidPitchBend(value) {
|
|
70
|
+
return Number.isInteger(value) && value >= 0 && value <= 16383
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validate pitch bend MSB and LSB separately
|
|
75
|
+
* @param {number} msb - Most significant byte (0-127)
|
|
76
|
+
* @param {number} lsb - Least significant byte (0-127)
|
|
77
|
+
* @returns {boolean}
|
|
78
|
+
*/
|
|
79
|
+
export function isValidPitchBendBytes(msb, lsb) {
|
|
80
|
+
return (
|
|
81
|
+
Number.isInteger(msb) &&
|
|
82
|
+
msb >= 0 &&
|
|
83
|
+
msb <= 127 &&
|
|
84
|
+
Number.isInteger(lsb) &&
|
|
85
|
+
lsb >= 0 &&
|
|
86
|
+
lsb <= 127
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
isValid14BitCC,
|
|
4
|
+
isValidCC,
|
|
5
|
+
isValidChannel,
|
|
6
|
+
isValidMIDIValue,
|
|
7
|
+
isValidNote,
|
|
8
|
+
isValidPitchBend,
|
|
9
|
+
isValidPitchBendBytes,
|
|
10
|
+
isValidProgramChange,
|
|
11
|
+
isValidVelocity,
|
|
12
|
+
} from "./validators.js"
|
|
13
|
+
|
|
14
|
+
describe("Validators - Complete Tests", () => {
|
|
15
|
+
describe("isValidChannel", () => {
|
|
16
|
+
const validChannels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
|
17
|
+
const invalidChannels = [
|
|
18
|
+
0,
|
|
19
|
+
17,
|
|
20
|
+
-1,
|
|
21
|
+
100,
|
|
22
|
+
1.5,
|
|
23
|
+
0.5,
|
|
24
|
+
NaN,
|
|
25
|
+
Infinity,
|
|
26
|
+
-Infinity,
|
|
27
|
+
null,
|
|
28
|
+
"",
|
|
29
|
+
"1",
|
|
30
|
+
undefined,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
it("should validate all valid channels 1-16", () => {
|
|
34
|
+
validChannels.forEach((channel) => {
|
|
35
|
+
expect(isValidChannel(channel)).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("should reject invalid channels", () => {
|
|
40
|
+
invalidChannels.forEach((channel) => {
|
|
41
|
+
expect(isValidChannel(channel)).toBe(false)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("should only accept integers", () => {
|
|
46
|
+
expect(isValidChannel(1)).toBe(true)
|
|
47
|
+
expect(isValidChannel(1.0)).toBe(true)
|
|
48
|
+
expect(isValidChannel(1.5)).toBe(false)
|
|
49
|
+
expect(isValidChannel(Number.EPSILON)).toBe(false)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe("isValidCC", () => {
|
|
54
|
+
it("should validate all valid CC numbers 0-127", () => {
|
|
55
|
+
for (let cc = 0; cc <= 127; cc++) {
|
|
56
|
+
expect(isValidCC(cc)).toBe(true)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("should reject invalid CC numbers", () => {
|
|
61
|
+
const invalid = [-1, 128, 200, 1.5, 63.7, NaN, Infinity, -Infinity, null, "", "1", undefined]
|
|
62
|
+
invalid.forEach((cc) => {
|
|
63
|
+
expect(isValidCC(cc)).toBe(false)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("should only accept integers", () => {
|
|
68
|
+
expect(isValidCC(64)).toBe(true)
|
|
69
|
+
expect(isValidCC(64.0)).toBe(true)
|
|
70
|
+
expect(isValidCC(64.1)).toBe(false)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe("isValidMIDIValue", () => {
|
|
75
|
+
it("should validate all valid MIDI values 0-127", () => {
|
|
76
|
+
for (let value = 0; value <= 127; value++) {
|
|
77
|
+
expect(isValidMIDIValue(value)).toBe(true)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it("should reject invalid values", () => {
|
|
82
|
+
const invalid = [-1, 128, 200, 1.5, NaN, Infinity, -Infinity, null, "", "1", undefined]
|
|
83
|
+
invalid.forEach((value) => {
|
|
84
|
+
expect(isValidMIDIValue(value)).toBe(false)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it("should only accept integers", () => {
|
|
89
|
+
expect(isValidMIDIValue(100)).toBe(true)
|
|
90
|
+
expect(isValidMIDIValue(100.0)).toBe(true)
|
|
91
|
+
expect(isValidMIDIValue(100.5)).toBe(false)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe("isValidNote", () => {
|
|
96
|
+
it("should validate all valid note numbers 0-127", () => {
|
|
97
|
+
for (let note = 0; note <= 127; note++) {
|
|
98
|
+
expect(isValidNote(note)).toBe(true)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it("should reject invalid note numbers", () => {
|
|
103
|
+
const invalid = [-1, 128, 200, 1.5, 60.5, NaN, Infinity, -Infinity, null, "", "60", undefined]
|
|
104
|
+
invalid.forEach((note) => {
|
|
105
|
+
expect(isValidNote(note)).toBe(false)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it("should only accept integers", () => {
|
|
110
|
+
expect(isValidNote(60)).toBe(true)
|
|
111
|
+
expect(isValidNote(60.0)).toBe(true)
|
|
112
|
+
expect(isValidNote(60.5)).toBe(false)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe("isValidVelocity", () => {
|
|
117
|
+
it("should validate all valid velocities 0-127", () => {
|
|
118
|
+
for (let velocity = 0; velocity <= 127; velocity++) {
|
|
119
|
+
expect(isValidVelocity(velocity)).toBe(true)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it("should reject invalid velocities", () => {
|
|
124
|
+
const invalid = [-1, 128, 200, 1.5, 64.7, NaN, Infinity, -Infinity, null, "", "64", undefined]
|
|
125
|
+
invalid.forEach((velocity) => {
|
|
126
|
+
expect(isValidVelocity(velocity)).toBe(false)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it("should only accept integers", () => {
|
|
131
|
+
expect(isValidVelocity(100)).toBe(true)
|
|
132
|
+
expect(isValidVelocity(100.0)).toBe(true)
|
|
133
|
+
expect(isValidVelocity(100.5)).toBe(false)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("should validate common velocity values", () => {
|
|
137
|
+
expect(isValidVelocity(0)).toBe(true) // Note off
|
|
138
|
+
expect(isValidVelocity(64)).toBe(true) // Medium
|
|
139
|
+
expect(isValidVelocity(100)).toBe(true) // Strong
|
|
140
|
+
expect(isValidVelocity(127)).toBe(true) // Maximum
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
describe("isValid14BitCC", () => {
|
|
145
|
+
it("should validate all valid 14-bit CC numbers 0-31", () => {
|
|
146
|
+
for (let cc = 0; cc <= 31; cc++) {
|
|
147
|
+
expect(isValid14BitCC(cc)).toBe(true)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it("should reject invalid 14-bit CC numbers", () => {
|
|
152
|
+
const invalid = [
|
|
153
|
+
-1,
|
|
154
|
+
32,
|
|
155
|
+
100,
|
|
156
|
+
127,
|
|
157
|
+
128,
|
|
158
|
+
1.5,
|
|
159
|
+
NaN,
|
|
160
|
+
Infinity,
|
|
161
|
+
-Infinity,
|
|
162
|
+
null,
|
|
163
|
+
"",
|
|
164
|
+
"1",
|
|
165
|
+
undefined,
|
|
166
|
+
]
|
|
167
|
+
invalid.forEach((cc) => {
|
|
168
|
+
expect(isValid14BitCC(cc)).toBe(false)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it("should only accept integers", () => {
|
|
173
|
+
expect(isValid14BitCC(15)).toBe(true)
|
|
174
|
+
expect(isValid14BitCC(15.0)).toBe(true)
|
|
175
|
+
expect(isValid14BitCC(15.5)).toBe(false)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it("should validate common 14-bit CC values", () => {
|
|
179
|
+
expect(isValid14BitCC(0)).toBe(true) // CC 0 (MSB for bank select)
|
|
180
|
+
expect(isValid14BitCC(6)).toBe(true) // Data entry MSB
|
|
181
|
+
expect(isValid14BitCC(16)).toBe(true) // General purpose 1 MSB
|
|
182
|
+
expect(isValid14BitCC(31)).toBe(true) // Maximum valid 14-bit CC
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe("isValidProgramChange", () => {
|
|
187
|
+
it("should validate all valid program numbers 0-127", () => {
|
|
188
|
+
for (let program = 0; program <= 127; program++) {
|
|
189
|
+
expect(isValidProgramChange(program)).toBe(true)
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it("should reject invalid program numbers", () => {
|
|
194
|
+
const invalid = [-1, 128, 200, 1.5, NaN, Infinity, null, "", "64", undefined]
|
|
195
|
+
invalid.forEach((program) => {
|
|
196
|
+
expect(isValidProgramChange(program)).toBe(false)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it("should only accept integers", () => {
|
|
201
|
+
expect(isValidProgramChange(64)).toBe(true)
|
|
202
|
+
expect(isValidProgramChange(64.0)).toBe(true)
|
|
203
|
+
expect(isValidProgramChange(64.5)).toBe(false)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe("isValidPitchBend", () => {
|
|
208
|
+
it("should validate 14-bit pitch bend range 0-16383", () => {
|
|
209
|
+
expect(isValidPitchBend(0)).toBe(true) // Max down
|
|
210
|
+
expect(isValidPitchBend(8192)).toBe(true) // Center
|
|
211
|
+
expect(isValidPitchBend(16383)).toBe(true) // Max up
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it("should reject out-of-range values", () => {
|
|
215
|
+
expect(isValidPitchBend(-1)).toBe(false)
|
|
216
|
+
expect(isValidPitchBend(16384)).toBe(false)
|
|
217
|
+
expect(isValidPitchBend(20000)).toBe(false)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it("should reject non-integers", () => {
|
|
221
|
+
expect(isValidPitchBend(8192.5)).toBe(false)
|
|
222
|
+
expect(isValidPitchBend(NaN)).toBe(false)
|
|
223
|
+
expect(isValidPitchBend(Infinity)).toBe(false)
|
|
224
|
+
expect(isValidPitchBend("8192")).toBe(false)
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
describe("isValidPitchBendBytes", () => {
|
|
229
|
+
it("should validate valid MSB/LSB combinations", () => {
|
|
230
|
+
expect(isValidPitchBendBytes(0, 0)).toBe(true) // 0
|
|
231
|
+
expect(isValidPitchBendBytes(64, 0)).toBe(true) // 8192 (center)
|
|
232
|
+
expect(isValidPitchBendBytes(127, 127)).toBe(true) // 16383
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it("should reject invalid MSB or LSB", () => {
|
|
236
|
+
expect(isValidPitchBendBytes(-1, 0)).toBe(false)
|
|
237
|
+
expect(isValidPitchBendBytes(0, 128)).toBe(false)
|
|
238
|
+
expect(isValidPitchBendBytes(128, 0)).toBe(false)
|
|
239
|
+
expect(isValidPitchBendBytes(64.5, 0)).toBe(false)
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
describe("validator edge cases", () => {
|
|
244
|
+
it("should handle boundary values correctly", () => {
|
|
245
|
+
// Valid boundaries
|
|
246
|
+
expect(isValidChannel(1)).toBe(true)
|
|
247
|
+
expect(isValidChannel(16)).toBe(true)
|
|
248
|
+
expect(isValidCC(0)).toBe(true)
|
|
249
|
+
expect(isValidCC(127)).toBe(true)
|
|
250
|
+
expect(isValidMIDIValue(0)).toBe(true)
|
|
251
|
+
expect(isValidMIDIValue(127)).toBe(true)
|
|
252
|
+
expect(isValidNote(0)).toBe(true)
|
|
253
|
+
expect(isValidNote(127)).toBe(true)
|
|
254
|
+
expect(isValidVelocity(0)).toBe(true)
|
|
255
|
+
expect(isValidVelocity(127)).toBe(true)
|
|
256
|
+
expect(isValid14BitCC(0)).toBe(true)
|
|
257
|
+
expect(isValid14BitCC(31)).toBe(true)
|
|
258
|
+
|
|
259
|
+
// Invalid boundaries
|
|
260
|
+
expect(isValidChannel(0)).toBe(false)
|
|
261
|
+
expect(isValidChannel(17)).toBe(false)
|
|
262
|
+
expect(isValidCC(-1)).toBe(false)
|
|
263
|
+
expect(isValidCC(128)).toBe(false)
|
|
264
|
+
expect(isValidMIDIValue(-1)).toBe(false)
|
|
265
|
+
expect(isValidMIDIValue(128)).toBe(false)
|
|
266
|
+
expect(isValidNote(-1)).toBe(false)
|
|
267
|
+
expect(isValidNote(128)).toBe(false)
|
|
268
|
+
expect(isValidVelocity(-1)).toBe(false)
|
|
269
|
+
expect(isValidVelocity(128)).toBe(false)
|
|
270
|
+
expect(isValid14BitCC(-1)).toBe(false)
|
|
271
|
+
expect(isValid14BitCC(32)).toBe(false)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it("should handle all zero edge cases", () => {
|
|
275
|
+
expect(isValidChannel(0)).toBe(false) // Channel 0 is not valid
|
|
276
|
+
expect(isValidCC(0)).toBe(true) // CC 0 is valid
|
|
277
|
+
expect(isValidMIDIValue(0)).toBe(true) // Value 0 is valid
|
|
278
|
+
expect(isValidNote(0)).toBe(true) // Note 0 is valid
|
|
279
|
+
expect(isValidVelocity(0)).toBe(true) // Velocity 0 is valid
|
|
280
|
+
expect(isValid14BitCC(0)).toBe(true) // 14-bit CC 0 is valid
|
|
281
|
+
expect(isValidProgramChange(0)).toBe(true) // Program 0 is valid
|
|
282
|
+
expect(isValidPitchBend(0)).toBe(true) // Pitch bend 0 is valid (max down)
|
|
283
|
+
expect(isValidPitchBendBytes(0, 0)).toBe(true) // Pitch bend bytes 0, 0 is valid
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it("should handle non-numeric inputs", () => {
|
|
287
|
+
const nonNumeric = ["1", "", null, undefined, {}, []]
|
|
288
|
+
nonNumeric.forEach((value) => {
|
|
289
|
+
expect(isValidChannel(value)).toBe(false)
|
|
290
|
+
expect(isValidCC(value)).toBe(false)
|
|
291
|
+
expect(isValidMIDIValue(value)).toBe(false)
|
|
292
|
+
expect(isValidNote(value)).toBe(false)
|
|
293
|
+
expect(isValidVelocity(value)).toBe(false)
|
|
294
|
+
expect(isValid14BitCC(value)).toBe(false)
|
|
295
|
+
expect(isValidProgramChange(value)).toBe(false)
|
|
296
|
+
expect(isValidPitchBend(value)).toBe(false)
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
})
|
|
300
|
+
})
|