-
Notifications
You must be signed in to change notification settings - Fork 0
/
usb.c
335 lines (278 loc) · 11.9 KB
/
usb.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
#include "tusb.h"
#include "usb_report_ids.h"
#include "ffb_midi.h"
// Translate from index in USB descriptor to byte that Sidewinder MIDI expects
static const uint8_t effect_type_usb_to_midi[] =
{
MIDI_ET_NONE,
MIDI_ET_CONSTANT,
MIDI_ET_RAMP,
MIDI_ET_SQUARE,
MIDI_ET_SINE,
MIDI_ET_TRIANGLE,
MIDI_ET_SAWTOOTHUP,
MIDI_ET_SAWTOOTHDOWN,
MIDI_ET_SPRING,
MIDI_ET_DAMPER,
MIDI_ET_INERTIA,
MIDI_ET_FRICTION
};
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id,
hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
switch (report_type)
{
case HID_REPORT_TYPE_FEATURE:
switch (report_id)
{
// Block Load Request
case REPORT_ID_FEATURE_BLOCK_LOAD:
// Block Index / Assigned Effect ID
buffer[0] = ffb_midi_last_assigned_effect_id();
// Block Load Status: per our descriptor, 1 is success, 2 is full, 3 is error.
buffer[1] = ffb_midi_last_add_succeeded() ? 1 : 2;
// 16-bit RAM pool available: we'll just give the number of effects remaining
buffer[2] = ffb_midi_get_num_available_effects();
buffer[3] = 0x00;
return 4;
// Pool size, max simultaneous effects, etc.
case REPORT_ID_FEATURE_POOL_REPORT:
// 16-bit RAM pool size: we'll just give the total number of effects
buffer[0] = EFFECT_MEMORY_SIZE;
buffer[1] = 0x00;
// Num simultaneous effects
buffer[2] = MAX_SIMULTANEOUS_EFFECTS;
// Bit 7: 1 for supporting shared managed pool, 0 for not
// Bit 6: 1 for supporting shared parameter blocks, 0 for not
buffer[3] = 0xff;
return 4;
}
break;
}
return 0;
}
static inline uint16_t join16(uint8_t first, uint8_t second)
{
return ((uint16_t) first) | (((uint16_t) second) << 8);
}
#define USB_DURATION_INFINITE 0xffff
#define MIDI_DURATION_INFINITE 0
struct __attribute__((__packed__ )) t_set_envelope_report
{
uint8_t effect_id;
uint8_t attack_level;
uint8_t fade_level;
uint16_t attack_time;
uint16_t fade_time;
};
struct __attribute__((__packed__ )) t_set_condition_report
{
uint8_t effect_id;
uint8_t axis; // "block offset"
int8_t center_point_offset;
uint8_t pos_coefficient;
uint8_t neg_coefficient;
uint8_t pos_saturation;
uint8_t neg_saturation;
uint8_t dead_band;
};
struct __attribute__((__packed__ )) t_set_ramp_report
{
uint8_t effect_id;
int8_t start;
int8_t end;
};
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id,
hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
bool echo = true;
switch (report_type)
{
case HID_REPORT_TYPE_OUTPUT:
{
switch(report_id)
{
case REPORT_ID_OUTPUT_SET_EFFECT:
{
uint8_t effect_id = buffer[0];
uint8_t effect_type_usb = buffer[1];
uint16_t duration = join16(buffer[2], buffer[3]);
uint16_t trig_interval = join16(buffer[4], buffer[5]);
uint16_t sample_period = join16(buffer[6], buffer[7]);
uint8_t gain = buffer[8];
uint8_t trig_button = buffer[9];
bool ax0_enable = (buffer[10] >> 0) & 0x01;
bool ax1_enable = (buffer[10] >> 1) & 0x01;
bool dir_enable = (buffer[10] >> 2) & 0x01;
uint8_t direction_x = buffer[11];
uint8_t direction_y = buffer[12];
uint16_t start_delay = join16(buffer[13], buffer[14]);
// TODO use trigger interval
// TODO use sample period
// TODO use trigger button
// TODO use ax0 enable
// TODO use ax1 enable
uint8_t effect_type_midi = effect_type_usb_to_midi[effect_type_usb];
// MIDI duration is in "units of 2 ms".
// USB uses the max possible value for infinity. MIDI uses 0.
uint16_t duration_midi = (duration == USB_DURATION_INFINITE) ? MIDI_DURATION_INFINITE : (duration >> 1);
if (duration_midi > 0x3fff) { duration_midi = 0x3fff; } // cap long but finite effects
ffb_midi_modify(uart0, effect_id, MODIFY_DURATION, duration_midi);
switch (effect_type_midi)
{
case MIDI_ET_CONSTANT:
case MIDI_ET_SINE:
case MIDI_ET_SQUARE:
case MIDI_ET_RAMP:
case MIDI_ET_TRIANGLE:
case MIDI_ET_SAWTOOTHDOWN:
case MIDI_ET_SAWTOOTHUP:
ffb_midi_modify(uart0, effect_id, MODIFY_GAIN, gain);
// TODO axes enable
uint16_t direction_midi = 0;
if (dir_enable)
{
// map 0-180 to 0-360
direction_midi = ((uint16_t)direction_x) << 1;
ffb_midi_modify(uart0, effect_id, MODIFY_DIRECTION, direction_midi);
}
break;
}
break;
}
case REPORT_ID_OUTPUT_SET_ENVELOPE:
{
struct t_set_envelope_report *report = (struct t_set_envelope_report*)(buffer);
// map 0->0xff down to 0->0x7f
ffb_midi_modify(uart0, report->effect_id, MODIFY_ATTACK_LEVEL, report->attack_level >> 1);
ffb_midi_modify(uart0, report->effect_id, MODIFY_FADE_LEVEL, report->fade_level >> 1);
// USB times are in ms; we convert to 2ms units
ffb_midi_modify(uart0, report->effect_id, MODIFY_ATTACK_TIME, report->attack_time >> 1);
ffb_midi_modify(uart0, report->effect_id, MODIFY_FADE_TIME, report->fade_time >> 1);
break;
}
case REPORT_ID_OUTPUT_SET_CONDITION:
{
struct t_set_condition_report *report = (struct t_set_condition_report*)(buffer);
// SW FFB Pro cannot use Neg Coefficient, Pos/Neg Saturation, or Dead Band.
switch (report->axis)
{
case 0:
{
ffb_midi_modify(uart0, report->effect_id, MODIFY_OFFSET_X, report->center_point_offset);
ffb_midi_modify(uart0, report->effect_id, MODIFY_STRENGTH_X, report->pos_coefficient >> 1);
break;
}
case 1:
{
ffb_midi_modify(uart0, report->effect_id, MODIFY_OFFSET_Y, report->center_point_offset);
ffb_midi_modify(uart0, report->effect_id, MODIFY_STRENGTH_Y, report->pos_coefficient >> 1);
break;
}
}
break;
}
case REPORT_ID_OUTPUT_SET_CONSTANT:
{
uint8_t effect_id = buffer[0];
uint16_t magnitude = join16(buffer[1], buffer[2]); // 255 to -255
magnitude = (magnitude & 0x1ff) >> 1;
ffb_midi_modify(uart0, effect_id, MODIFY_AMPLITUDE, magnitude);
break;
}
case REPORT_ID_OUTPUT_SET_RAMP:
{
struct t_set_ramp_report *report = (struct t_set_ramp_report*)(buffer);
ffb_midi_modify(uart0, report->effect_id, MODIFY_AMPLITUDE, report->start);
ffb_midi_modify(uart0, report->effect_id, MODIFY_RAMP_END, report->end);
break;
}
case REPORT_ID_OUTPUT_SET_PERIODIC:
{
uint8_t effect_id = buffer[0];
uint8_t magnitude = buffer[1];
int8_t offset = (int8_t) buffer[2];
uint8_t phase = buffer[3];
uint16_t period = join16(buffer[4], buffer[5]);
// TODO: FFB Pro doesn't support offset or phase.
// adapt-ffb-joy works around this by switching between waveforms
// (apparently effect type 3 on the FFB Pro is a cosine?!)
uint16_t frequency = 1;
if (period <= 13) { frequency = 77; }
else if (period < 1000) { frequency = ((2000 / period) + 1) / 2; }
ffb_midi_modify(uart0, effect_id, MODIFY_FREQUENCY, frequency);
ffb_midi_modify(uart0, effect_id, MODIFY_SUSTAIN_LEVEL, magnitude);
break;
}
case REPORT_ID_OUTPUT_EFFECT_OPERATION:
{
uint8_t effect_id = buffer[0];
uint8_t operation = buffer[1];
uint8_t loop_count = buffer[2]; // TODO use this
switch (operation)
{
case 1: // Start
ffb_midi_play(uart0, effect_id);
break;
case 2: // Start Solo
ffb_midi_play_solo(uart0, effect_id);
break;
case 3: // Stop
ffb_midi_pause(uart0, effect_id);
break;
}
break;
}
case REPORT_ID_OUTPUT_BLOCK_FREE:
{
uint8_t effect_id = buffer[0];
ffb_midi_erase(uart0, effect_id);
break;
}
case REPORT_ID_OUTPUT_DEVICE_CONTROL:
{
// TODO
break;
}
case REPORT_ID_OUTPUT_DEVICE_GAIN:
{
uint8_t device_gain = buffer[0];
ffb_midi_modify(uart0, 0x7f, MODIFY_DEVICE_GAIN, device_gain);
}
}
break;
}
case HID_REPORT_TYPE_FEATURE:
{
switch(report_id)
{
case REPORT_ID_FEATURE_CREATE_NEW_EFFECT:
{
// Only one byte: the effect type
struct Effect newEffect = {
.type = effect_type_usb_to_midi[buffer[0]],
.duration = 0,
.button_mask = 0,
.direction = 0,
.gain = 0x7f,
.strength_x = 0,
.strength_y = 0,
.sample_rate = 100,
.attack_level = 0x7f,
.sustain_level = 0x7f,
.fade_level = 0x7f,
.attack_time = 0,
.fade_time = 0,
.frequency = 1,
.amplitude = 0x7f,
};
ffb_midi_define_effect(uart0, &newEffect);
break;
}
}
break;
}
}
// Echo back anything we received from the host
if (echo) { tud_hid_report(0, buffer, bufsize); }
}