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.
@@ -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
+ })