all repos — mgba @ 2083aa921be4658815534aa2b89bab9e041df1ff

mGBA Game Boy Advance Emulator

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