all repos — mgba @ b8ff8d618f36ee0de63f008b90be47f51ed5b1c3

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