src/platform/wii/main.c (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#define asm __asm__
7
8#include <fat.h>
9#include <gccore.h>
10#include <ogc/machine/processor.h>
11#include <malloc.h>
12#include <wiiuse/wpad.h>
13
14#include "util/common.h"
15
16#include "gba/renderers/video-software.h"
17#include "gba/context/context.h"
18#include "gba/gui/gui-runner.h"
19#include "util/gui.h"
20#include "util/gui/file-select.h"
21#include "util/gui/font.h"
22#include "util/vfs.h"
23
24#define SAMPLES 1024
25
26static void _retraceCallback(u32 count);
27
28static void _audioDMA(void);
29static void _setRumble(struct GBARumble* rumble, int enable);
30static void _sampleRotation(struct GBARotationSource* source);
31static int32_t _readTiltX(struct GBARotationSource* source);
32static int32_t _readTiltY(struct GBARotationSource* source);
33static int32_t _readGyroZ(struct GBARotationSource* source);
34
35static void _drawStart(void);
36static void _drawEnd(void);
37static uint32_t _pollInput(void);
38static enum GUICursorState _pollCursor(int* x, int* y);
39static void _guiPrepare(void);
40static void _guiFinish(void);
41
42static void _setup(struct GBAGUIRunner* runner);
43static void _gameLoaded(struct GBAGUIRunner* runner);
44static void _gameUnloaded(struct GBAGUIRunner* runner);
45static void _drawFrame(struct GBAGUIRunner* runner, bool faded);
46static uint16_t _pollGameInput(struct GBAGUIRunner* runner);
47
48static s8 WPAD_StickX(u8 chan, u8 right);
49static s8 WPAD_StickY(u8 chan, u8 right);
50
51static struct GBAVideoSoftwareRenderer renderer;
52static struct GBARumble rumble;
53static struct GBARotationSource rotation;
54static GXRModeObj* vmode;
55static Mtx model, view, modelview;
56static uint16_t* texmem;
57static GXTexObj tex;
58static int32_t tiltX;
59static int32_t tiltY;
60static int32_t gyroZ;
61static uint32_t retraceCount;
62static uint32_t referenceRetraceCount;
63
64static void* framebuffer[2] = { 0, 0 };
65static int whichFb = 0;
66
67static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32)));
68static volatile size_t audioBufferSize = 0;
69static volatile int currentAudioBuffer = 0;
70
71static struct GUIFont* font;
72
73static void reconfigureScreen(GXRModeObj* vmode) {
74 free(framebuffer[0]);
75 free(framebuffer[1]);
76
77 framebuffer[0] = SYS_AllocateFramebuffer(vmode);
78 framebuffer[1] = SYS_AllocateFramebuffer(vmode);
79
80 VIDEO_SetBlack(true);
81 VIDEO_Configure(vmode);
82 VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
83 VIDEO_SetBlack(false);
84 VIDEO_Flush();
85 VIDEO_WaitVSync();
86 if (vmode->viTVMode & VI_NON_INTERLACE) {
87 VIDEO_WaitVSync();
88 }
89 GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
90
91 f32 yscale = GX_GetYScaleFactor(vmode->efbHeight, vmode->xfbHeight);
92 u32 xfbHeight = GX_SetDispCopyYScale(yscale);
93 GX_SetScissor(0, 0, vmode->viWidth, vmode->viWidth);
94 GX_SetDispCopySrc(0, 0, vmode->fbWidth, vmode->efbHeight);
95 GX_SetDispCopyDst(vmode->fbWidth, xfbHeight);
96 GX_SetCopyFilter(vmode->aa, vmode->sample_pattern, GX_TRUE, vmode->vfilter);
97 GX_SetFieldMode(vmode->field_rendering, ((vmode->viHeight == 2 * vmode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
98};
99
100int main() {
101 VIDEO_Init();
102 PAD_Init();
103 WPAD_Init();
104 WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR);
105 AUDIO_Init(0);
106 AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
107 AUDIO_RegisterDMACallback(_audioDMA);
108
109 memset(audioBuffer, 0, sizeof(audioBuffer));
110
111#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
112#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
113#endif
114
115 vmode = VIDEO_GetPreferredMode(0);
116
117 GXColor bg = { 0, 0, 0, 0xFF };
118 void* fifo = memalign(32, 0x40000);
119 memset(fifo, 0, 0x40000);
120 GX_Init(fifo, 0x40000);
121 GX_SetCopyClear(bg, 0x00FFFFFF);
122
123 reconfigureScreen(vmode);
124
125 GX_SetCullMode(GX_CULL_NONE);
126 GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
127 GX_SetDispCopyGamma(GX_GM_1_0);
128
129 GX_ClearVtxDesc();
130 GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
131 GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
132 GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
133
134 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
135 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
136 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
137
138 GX_SetNumChans(1);
139 GX_SetNumTexGens(1);
140 GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
141 GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
142
143 GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
144 GX_InvVtxCache();
145 GX_InvalidateTexAll();
146
147 guVector cam = { 0.0f, 0.0f, 0.0f };
148 guVector up = { 0.0f, 1.0f, 0.0f };
149 guVector look = { 0.0f, 0.0f, -1.0f };
150 guLookAt(view, &cam, &up, &look);
151
152 guMtxIdentity(model);
153 guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f);
154 guMtxConcat(view, model, modelview);
155 GX_LoadPosMtxImm(modelview, GX_PNMTX0);
156
157 texmem = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
158 memset(texmem, 0, 256 * 256 * BYTES_PER_PIXEL);
159 GX_InitTexObj(&tex, texmem, 256, 256, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
160
161 VIDEO_SetPostRetraceCallback(_retraceCallback);
162
163 font = GUIFontCreate();
164
165 fatInitDefault();
166
167 rumble.setRumble = _setRumble;
168
169 rotation.sample = _sampleRotation;
170 rotation.readTiltX = _readTiltX;
171 rotation.readTiltY = _readTiltY;
172 rotation.readGyroZ = _readGyroZ;
173
174 struct GBAGUIRunner runner = {
175 .params = {
176 352, 230,
177 font, "/",
178 _drawStart, _drawEnd,
179 _pollInput, _pollCursor,
180 0,
181 _guiPrepare, _guiFinish,
182
183 GUI_PARAMS_TRAIL
184 },
185 .setup = _setup,
186 .teardown = 0,
187 .gameLoaded = _gameLoaded,
188 .gameUnloaded = _gameUnloaded,
189 .prepareForFrame = 0,
190 .drawFrame = _drawFrame,
191 .paused = _gameUnloaded,
192 .unpaused = 0,
193 .pollGameInput = _pollGameInput
194 };
195 GBAGUIInit(&runner, "wii");
196 GBAGUIRunloop(&runner);
197 GBAGUIDeinit(&runner);
198
199 free(fifo);
200
201 free(renderer.outputBuffer);
202 GUIFontDestroy(font);
203
204 free(framebuffer[0]);
205 free(framebuffer[1]);
206
207 return 0;
208}
209
210static void _audioDMA(void) {
211 if (!audioBufferSize) {
212 return;
213 }
214 DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
215 AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
216 currentAudioBuffer = (currentAudioBuffer + 1) % 3;
217 audioBufferSize = 0;
218}
219
220static void _drawStart(void) {
221 u32 level = 0;
222 _CPU_ISR_Disable(level);
223 if (referenceRetraceCount >= retraceCount) {
224 VIDEO_WaitVSync();
225 }
226 _CPU_ISR_Restore(level);
227
228 GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
229 GX_SetColorUpdate(GX_TRUE);
230
231 GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
232}
233
234static void _drawEnd(void) {
235 GX_DrawDone();
236
237 whichFb = !whichFb;
238
239 GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
240 VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
241 VIDEO_Flush();
242
243 u32 level = 0;
244 _CPU_ISR_Disable(level);
245 ++referenceRetraceCount;
246 _CPU_ISR_Restore(level);
247}
248
249static uint32_t _pollInput(void) {
250 PAD_ScanPads();
251 u16 padkeys = PAD_ButtonsHeld(0);
252
253 WPAD_ScanPads();
254 u32 wiiPad = WPAD_ButtonsHeld(0);
255 u32 ext = 0;
256 WPAD_Probe(0, &ext);
257
258 int keys = 0;
259 int x = PAD_StickX(0);
260 int y = PAD_StickY(0);
261 int w_x = WPAD_StickX(0,0);
262 int w_y = WPAD_StickY(0,0);
263 if (x < -0x40 || w_x < -0x40) {
264 keys |= 1 << GUI_INPUT_LEFT;
265 }
266 if (x > 0x40 || w_x > 0x40) {
267 keys |= 1 << GUI_INPUT_RIGHT;
268 }
269 if (y < -0x40 || w_y <- 0x40) {
270 keys |= 1 << GUI_INPUT_DOWN;
271 }
272 if (y > 0x40 || w_y > 0x40) {
273 keys |= 1 << GUI_INPUT_UP;
274 }
275 if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) ||
276 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
277 keys |= 1 << GUI_INPUT_SELECT;
278 }
279 if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) || (wiiPad & WPAD_BUTTON_B) ||
280 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
281 keys |= 1 << GUI_INPUT_BACK;
282 }
283 if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) ||
284 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_HOME)))) {
285 keys |= 1 << GUI_INPUT_CANCEL;
286 }
287 if ((padkeys & PAD_BUTTON_LEFT)|| (wiiPad & WPAD_BUTTON_UP) ||
288 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
289 keys |= 1 << GUI_INPUT_LEFT;
290 }
291 if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
292 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
293 keys |= 1 << GUI_INPUT_RIGHT;
294 }
295 if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
296 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
297 keys |= 1 << GUI_INPUT_UP;
298 }
299 if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
300 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
301 keys |= 1 << GUI_INPUT_DOWN;
302 }
303 return keys;
304}
305
306static enum GUICursorState _pollCursor(int* x, int* y) {
307 ir_t ir;
308 WPAD_IR(0, &ir);
309 if (!ir.smooth_valid) {
310 return GUI_CURSOR_NOT_PRESENT;
311 }
312 *x = ir.sx;
313 *y = ir.sy;
314 WPAD_ScanPads();
315 u32 wiiPad = WPAD_ButtonsHeld(0);
316 if (wiiPad & WPAD_BUTTON_A) {
317 return GUI_CURSOR_DOWN;
318 }
319 return GUI_CURSOR_UP;
320}
321
322void _guiPrepare(void) {
323 Mtx44 proj;
324 guOrtho(proj, -20, 240, 0, 352, 0, 300);
325 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
326}
327
328void _guiFinish(void) {
329 Mtx44 proj;
330 short top = (CONF_GetAspectRatio() == CONF_ASPECT_16_9) ? 10 : 20;
331 short bottom = VIDEO_VERTICAL_PIXELS + top;
332 guOrtho(proj, -top, bottom, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300);
333 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
334}
335
336void _setup(struct GBAGUIRunner* runner) {
337 runner->context.gba->rumble = &rumble;
338 runner->context.gba->rotationSource = &rotation;
339
340 GBAVideoSoftwareRendererCreate(&renderer);
341 renderer.outputBuffer = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
342 renderer.outputBufferStride = 256;
343 runner->context.renderer = &renderer.d;
344
345 GBAAudioResizeBuffer(&runner->context.gba->audio, SAMPLES);
346
347#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
348 double ratio = GBAAudioCalculateRatio(1, 60 / 1.001, 1);
349 blip_set_rates(runner->context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 48000 * ratio);
350 blip_set_rates(runner->context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 48000 * ratio);
351#endif
352}
353
354void _gameUnloaded(struct GBAGUIRunner* runner) {
355 UNUSED(runner);
356 AUDIO_StopDMA();
357}
358
359void _gameLoaded(struct GBAGUIRunner* runner) {
360 if (runner->context.gba->memory.hw.devices & HW_GYRO) {
361 int i;
362 for (i = 0; i < 6; ++i) {
363 u32 result = WPAD_SetMotionPlus(0, 1);
364 if (result == WPAD_ERR_NONE) {
365 break;
366 }
367 sleep(1);
368 }
369 }
370 u32 level = 0;
371 _CPU_ISR_Disable(level);
372 referenceRetraceCount = retraceCount;
373 _CPU_ISR_Restore(level);
374}
375
376void _drawFrame(struct GBAGUIRunner* runner, bool faded) {
377#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
378 int available = blip_samples_avail(runner->context.gba->audio.left);
379 if (available + audioBufferSize > SAMPLES) {
380 available = SAMPLES - audioBufferSize;
381 }
382 available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
383 if (available > 0) {
384 blip_read_samples(runner->context.gba->audio.left, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
385 blip_read_samples(runner->context.gba->audio.right, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
386 audioBufferSize += available;
387 }
388 if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
389 _audioDMA();
390 AUDIO_StartDMA();
391 }
392#endif
393
394 uint32_t color = 0xFFFFFF3F;
395 if (!faded) {
396 color |= 0xC0;
397 }
398 size_t x, y;
399 uint64_t* texdest = (uint64_t*) texmem;
400 uint64_t* texsrc = (uint64_t*) renderer.outputBuffer;
401 for (y = 0; y < VIDEO_VERTICAL_PIXELS; y += 4) {
402 for (x = 0; x < VIDEO_HORIZONTAL_PIXELS >> 2; ++x) {
403 texdest[0 + x * 4 + y * 64] = texsrc[0 + x + y * 64];
404 texdest[1 + x * 4 + y * 64] = texsrc[64 + x + y * 64];
405 texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
406 texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
407 }
408 }
409 DCFlushRange(texdest, 256 * 256 * BYTES_PER_PIXEL);
410
411 if (faded) {
412 GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP);
413 } else {
414 GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
415 }
416 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
417 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
418 GX_InvalidateTexAll();
419 GX_LoadTexObj(&tex, GX_TEXMAP0);
420
421 GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
422 GX_Position2s16(0, 256);
423 GX_Color1u32(color);
424 GX_TexCoord2s16(0, 1);
425
426 GX_Position2s16(256, 256);
427 GX_Color1u32(color);
428 GX_TexCoord2s16(1, 1);
429
430 GX_Position2s16(256, 0);
431 GX_Color1u32(color);
432 GX_TexCoord2s16(1, 0);
433
434 GX_Position2s16(0, 0);
435 GX_Color1u32(color);
436 GX_TexCoord2s16(0, 0);
437 GX_End();
438}
439
440uint16_t _pollGameInput(struct GBAGUIRunner* runner) {
441 UNUSED(runner);
442 PAD_ScanPads();
443 u16 padkeys = PAD_ButtonsHeld(0);
444 WPAD_ScanPads();
445 u32 wiiPad = WPAD_ButtonsHeld(0);
446 u32 ext = 0;
447 uint16_t keys = 0;
448 WPAD_Probe(0, &ext);
449
450 if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) ||
451 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
452 keys |= 1 << GBA_KEY_A;
453 }
454 if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) ||
455 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
456 keys |= 1 << GBA_KEY_B;
457 }
458 if ((padkeys & PAD_TRIGGER_L) || (wiiPad & WPAD_BUTTON_B) ||
459 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_L))) {
460 keys |= 1 << GBA_KEY_L;
461 }
462 if ((padkeys & PAD_TRIGGER_R) || (wiiPad & WPAD_BUTTON_A) ||
463 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_R))) {
464 keys |= 1 << GBA_KEY_R;
465 }
466 if ((padkeys & PAD_BUTTON_START) || (wiiPad & WPAD_BUTTON_PLUS) ||
467 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_PLUS))) {
468 keys |= 1 << GBA_KEY_START;
469 }
470 if ((padkeys & (PAD_BUTTON_X | PAD_BUTTON_Y)) || (wiiPad & WPAD_BUTTON_MINUS) ||
471 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_MINUS))) {
472 keys |= 1 << GBA_KEY_SELECT;
473 }
474 if ((padkeys & PAD_BUTTON_LEFT) || (wiiPad & WPAD_BUTTON_UP) ||
475 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
476 keys |= 1 << GBA_KEY_LEFT;
477 }
478 if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
479 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
480 keys |= 1 << GBA_KEY_RIGHT;
481 }
482 if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
483 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
484 keys |= 1 << GBA_KEY_UP;
485 }
486 if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
487 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
488 keys |= 1 << GBA_KEY_DOWN;
489 }
490 int x = PAD_StickX(0);
491 int y = PAD_StickY(0);
492 int w_x = WPAD_StickX(0,0);
493 int w_y = WPAD_StickY(0,0);
494 if (x < -0x40 || w_x < -0x40) {
495 keys |= 1 << GBA_KEY_LEFT;
496 }
497 if (x > 0x40 || w_x > 0x40) {
498 keys |= 1 << GBA_KEY_RIGHT;
499 }
500 if (y < -0x40 || w_y < -0x40) {
501 keys |= 1 << GBA_KEY_DOWN;
502 }
503 if (y > 0x40 || w_y > 0x40) {
504 keys |= 1 << GBA_KEY_UP;
505 }
506 return keys;
507}
508
509void _setRumble(struct GBARumble* rumble, int enable) {
510 UNUSED(rumble);
511 WPAD_Rumble(0, enable);
512 if (enable) {
513 PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
514 } else {
515 PAD_ControlMotor(0, PAD_MOTOR_STOP);
516 }
517}
518
519void _sampleRotation(struct GBARotationSource* source) {
520 UNUSED(source);
521 vec3w_t accel;
522 WPAD_Accel(0, &accel);
523 // These are swapped
524 tiltX = (accel.y - 0x1EA) << 22;
525 tiltY = (accel.x - 0x1EA) << 22;
526
527 // This doesn't seem to work at all with -TR remotes
528 struct expansion_t exp;
529 WPAD_Expansion(0, &exp);
530 if (exp.type != EXP_MOTION_PLUS) {
531 return;
532 }
533 gyroZ = exp.mp.rz - 0x1FA0;
534 gyroZ <<= 18;
535}
536
537int32_t _readTiltX(struct GBARotationSource* source) {
538 UNUSED(source);
539 return tiltX;
540}
541
542int32_t _readTiltY(struct GBARotationSource* source) {
543 UNUSED(source);
544 return tiltY;
545}
546
547int32_t _readGyroZ(struct GBARotationSource* source) {
548 UNUSED(source);
549 return gyroZ;
550}
551
552static s8 WPAD_StickX(u8 chan, u8 right) {
553 float mag = 0.0;
554 float ang = 0.0;
555 WPADData *data = WPAD_Data(chan);
556
557 switch (data->exp.type) {
558 case WPAD_EXP_NUNCHUK:
559 case WPAD_EXP_GUITARHERO3:
560 if (right == 0) {
561 mag = data->exp.nunchuk.js.mag;
562 ang = data->exp.nunchuk.js.ang;
563 }
564 break;
565 case WPAD_EXP_CLASSIC:
566 if (right == 0) {
567 mag = data->exp.classic.ljs.mag;
568 ang = data->exp.classic.ljs.ang;
569 } else {
570 mag = data->exp.classic.rjs.mag;
571 ang = data->exp.classic.rjs.ang;
572 }
573 break;
574 default:
575 break;
576 }
577
578 /* calculate X value (angle need to be converted into radian) */
579 if (mag > 1.0) {
580 mag = 1.0;
581 } else if (mag < -1.0) {
582 mag = -1.0;
583 }
584 double val = mag * sinf(M_PI * ang / 180.0f);
585
586 return (s8)(val * 128.0f);
587}
588
589
590static s8 WPAD_StickY(u8 chan, u8 right) {
591 float mag = 0.0;
592 float ang = 0.0;
593 WPADData *data = WPAD_Data(chan);
594
595 switch (data->exp.type) {
596 case WPAD_EXP_NUNCHUK:
597 case WPAD_EXP_GUITARHERO3:
598 if (right == 0) {
599 mag = data->exp.nunchuk.js.mag;
600 ang = data->exp.nunchuk.js.ang;
601 }
602 break;
603 case WPAD_EXP_CLASSIC:
604 if (right == 0) {
605 mag = data->exp.classic.ljs.mag;
606 ang = data->exp.classic.ljs.ang;
607 } else {
608 mag = data->exp.classic.rjs.mag;
609 ang = data->exp.classic.rjs.ang;
610 }
611 break;
612 default:
613 break;
614 }
615
616 /* calculate X value (angle need to be converted into radian) */
617 if (mag > 1.0) {
618 mag = 1.0;
619 } else if (mag < -1.0) {
620 mag = -1.0;
621 }
622 double val = mag * cosf(M_PI * ang / 180.0f);
623
624 return (s8)(val * 128.0f);
625}
626
627void _retraceCallback(u32 count) {
628 u32 level = 0;
629 _CPU_ISR_Disable(level);
630 retraceCount = count;
631 _CPU_ISR_Restore(level);
632}