1/*****************************************************************************\
2     Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3                This file is licensed under the Snes9x License.
4   For further information, consult the LICENSE file in the root directory.
5\*****************************************************************************/
6
7/***********************************************************************************
8  SNES9X for Mac OS (c) Copyright John Stiles
9
10  Snes9x for Mac OS X
11
12  (c) Copyright 2001 - 2011  zones
13  (c) Copyright 2002 - 2005  107
14  (c) Copyright 2002         PB1400c
15  (c) Copyright 2004         Alexander and Sander
16  (c) Copyright 2004 - 2005  Steven Seeger
17  (c) Copyright 2005         Ryan Vogt
18 ***********************************************************************************/
19
20
21#import "snes9x.h"
22#import "memmap.h"
23#import "apu.h"
24#import "snapshot.h"
25#import "snes.hpp"
26
27#import <Cocoa/Cocoa.h>
28#import <sys/time.h>
29#import <pthread.h>
30
31#import "mac-prefix.h"
32#import "mac-audio.h"
33#import "mac-file.h"
34#import "mac-os.h"
35#import "mac-musicbox.h"
36
37volatile bool8			mboxPause = false;
38
39static volatile bool8	stopNow, showIndicator, headPressed;
40static int32			oldCPUCycles;
41static uint16			stereo_switch;
42static uint8			storedSoundSnapshot[SPC_SAVE_STATE_BLOCK_SIZE];
43
44static void SPCPlayExec (void);
45static void SPCPlayFreeze (void);
46static void SPCPlayDefrost (void);
47static void MusicBoxForceFreeze (void);
48static void MusicBoxForceDefrost (void);
49static void * SoundTask (void *);
50
51
52@implementation MusicBoxController
53
54- (id) init
55{
56	NSUserDefaults	*defaults;
57	NSString		*s;
58	NSRect			rect;
59	NSSize			size;
60	BOOL			apuonly, r;
61	char			drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1];
62
63	self = [super init];
64	if (!self)
65		return (self);
66
67	r = [NSBundle loadNibNamed: @"musicbox" owner: self];
68	if (!r)
69		return (self);
70
71	apuonly = (musicboxmode == kMBXSoundEmulation);
72
73	if (apuonly)
74		SPCPlayFreeze();
75	else
76		MusicBoxForceFreeze();
77
78	_splitpath(Memory.ROMFilename, drive, dir, fname, ext);
79	[gametitle setStringValue: [NSString stringWithUTF8String: fname]];
80
81	[led setImage: [NSImage imageNamed: (apuonly ? @"musicbox_ledoff.icns" : @"musicbox_ledon.icns")]];
82
83	if (!apuonly)
84	{
85		[rewind setState: NSOffState];
86		[rewind setEnabled: NO];
87	}
88
89	defaults = [NSUserDefaults standardUserDefaults];
90	s = [defaults stringForKey: @"frame_musicbox"];
91	if (s)
92	{
93		rect = NSRectFromString(s);
94		[window setFrame: rect display: NO];
95	}
96
97	if (!savewindowpos)
98		[window center];
99
100	size = [window minSize];
101	mbxClosedHeight = size.height;
102	size = [window maxSize];
103	mbxOpenedHeight = size.height;
104
105	rect = [window frame];
106	showIndicator = (rect.size.height > mbxClosedHeight) ? true : false;
107
108	[disclosure setState: (showIndicator ? NSOnState : NSOffState)];
109
110	[window makeKeyAndOrderFront: self];
111
112	mboxPause   = false;
113	headPressed = false;
114
115	stereo_switch = ~0;
116	SNES::dsp.spc_dsp.set_stereo_switch(stereo_switch);
117
118	for (int i = 0; i < MAC_MAX_PLAYERS; i++)
119		controlPad[i] = 0;
120
121	stopNow = false;
122	MacStartSound();
123	pthread_create(&mbxThread, NULL, SoundTask, NULL);
124
125	timer = [[NSTimer scheduledTimerWithTimeInterval: (2.0 / (double) Memory.ROMFramesPerSecond) target: self selector: @selector(updateIndicator:) userInfo: nil repeats: YES] retain];
126
127	return (self);
128}
129
130- (void) windowWillClose: (NSNotification *) aNotification
131{
132	NSUserDefaults	*defaults;
133	NSString		*s;
134	BOOL			r;
135
136	[timer invalidate];
137	[timer release];
138
139	showIndicator = false;
140
141	stopNow = true;
142	pthread_join(mbxThread, NULL);
143	MacStopSound();
144
145	defaults = [NSUserDefaults standardUserDefaults];
146	s = NSStringFromRect([window frame]);
147	[defaults setObject: s forKey: @"frame_musicbox"];
148	r = [defaults synchronize];
149
150	[NSApp stopModal];
151}
152
153- (void) dealloc
154{
155	stereo_switch = ~0;
156	SNES::dsp.spc_dsp.set_stereo_switch(stereo_switch);
157
158	if (musicboxmode == kMBXSoundEmulation)
159		SPCPlayDefrost();
160	else
161		MusicBoxForceDefrost();
162
163	[window release];
164
165	[super dealloc];
166}
167
168- (NSWindow *) window
169{
170	return (window);
171}
172
173- (IBAction) handlePauseButton: (id) sender
174{
175	mboxPause = !mboxPause;
176	S9xSetSoundMute(mboxPause);
177}
178
179- (IBAction) handleRewindButton: (id) sender
180{
181	headPressed = true;
182}
183
184- (IBAction) handleEffectButton: (id) sender
185{
186	[window orderOut: self];
187	showIndicator = false;
188	ConfigureSoundEffects();
189	showIndicator = true;
190	[window makeKeyAndOrderFront: self];
191}
192
193- (IBAction) handleChannelButton: (id) sender
194{
195	stereo_switch ^= (1 << [sender tag]);
196	SNES::dsp.spc_dsp.set_stereo_switch(stereo_switch);
197}
198
199- (IBAction) handleDisclosureButton: (id) sender
200{
201	NSRect	rect;
202	float	h;
203
204	showIndicator = !showIndicator;
205	rect = [window frame];
206	h = rect.size.height;
207	rect.size.height = showIndicator ? mbxOpenedHeight : mbxClosedHeight;
208	rect.origin.y += (h - rect.size.height);
209	[window setFrame: rect display: YES animate: YES];
210}
211
212- (void) updateIndicator: (NSTimer *) aTimer
213{
214	if (showIndicator)
215		[indicator setNeedsDisplay: YES];
216}
217
218@end
219
220@implementation MusicBoxIndicatorView
221
222- (id) initWithFrame: (NSRect) frame
223{
224	self = [super initWithFrame: frame];
225	if (self)
226	{
227		NSRect			rect;
228		long long		currentTime;
229		struct timeval	tv;
230
231		mbxOffsetX   =   0.0f;
232		mbxOffsetY   =   0.0f;
233		mbxBarWidth  =  12.0f;
234		mbxBarHeight = 128.0f;
235		mbxBarSpace  =   2.0f;
236		mbxLRSpace   =  20.0f;
237		mbxRightBarX = (mbxLRSpace + (mbxBarWidth * 8.0f + mbxBarSpace * 7.0f));
238		yyscale      = (float) (128.0 / sqrt(64.0));
239
240		rect = [self bounds];
241		mbxViewWidth  = rect.size.width;
242		mbxViewHeight = rect.size.height;
243		mbxMarginX = (mbxViewWidth - ((mbxBarWidth * 8.0f + mbxBarSpace * 7.0f) * 2.0f + mbxLRSpace)) / 2.0f;
244		mbxMarginY = (mbxViewHeight - mbxBarHeight) / 2.0f;
245
246		gettimeofday(&tv, NULL);
247		currentTime = tv.tv_sec * 1000000 + tv.tv_usec;
248
249		for (int i = 0; i < 8; i++)
250		{
251			prevLMax[i] = prevRMax[i] = 0;
252			prevLVol[i] = prevRVol[i] = 0;
253			barTimeL[i] = barTimeR[i] = currentTime;
254		}
255	}
256
257	return (self);
258}
259
260- (void) drawRect: (NSRect) rect
261{
262	CGContextRef	mboxctx;
263
264	mboxctx = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
265
266	// Bar
267
268	const float	length[] = { 1.0f, 1.0f };
269
270	CGContextSetLineWidth(mboxctx, mbxBarWidth);
271	CGContextSetLineDash(mboxctx, 0, length, 2);
272	CGContextSetLineJoin(mboxctx, kCGLineJoinMiter);
273
274	CGContextBeginPath(mboxctx);
275
276	float   x = mbxOffsetX + mbxMarginX + mbxBarWidth / 2.0f;
277
278	for (int h = 0; h < 8; h++)
279	{
280		// Inactive
281
282		CGContextSetRGBStrokeColor(mboxctx, (196.0f / 256.0f), (200.0f / 256.0f), (176.0f / 256.0f), 1.0f);
283
284		CGContextMoveToPoint   (mboxctx, x,                mbxOffsetY + mbxMarginY);
285		CGContextAddLineToPoint(mboxctx, x,                mbxOffsetY + mbxMarginY + mbxBarHeight);
286
287		CGContextMoveToPoint   (mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY);
288		CGContextAddLineToPoint(mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + mbxBarHeight);
289
290		CGContextStrokePath(mboxctx);
291
292		// Max
293
294		short			vl = (SNES::dsp.spc_dsp.reg_value(h, 0x00) * SNES::dsp.spc_dsp.envx_value(h)) >> 11;
295		short			vr = (SNES::dsp.spc_dsp.reg_value(h, 0x01) * SNES::dsp.spc_dsp.envx_value(h)) >> 11;
296		long long		currentTime;
297		struct timeval	tv;
298
299		if (vl <= 0) vl = 0; else if (vl > 64) vl = 64; else vl = (short) (yyscale * sqrt((double) vl)) & (~0 << 1);
300		if (vr <= 0) vr = 0; else if (vr > 64) vr = 64; else vr = (short) (yyscale * sqrt((double) vr)) & (~0 << 1);
301
302		if (vl < prevLVol[h]) vl = ((prevLVol[h] + vl) >> 1);
303		if (vr < prevRVol[h]) vr = ((prevRVol[h] + vr) >> 1);
304
305		gettimeofday(&tv, NULL);
306		currentTime = tv.tv_sec * 1000000 + tv.tv_usec;
307
308		// left
309
310		if ((vl >= prevLMax[h]) && (vl > prevLVol[h]))
311		{
312			barTimeL[h] = currentTime;
313			prevLMax[h] = vl;
314		}
315		else
316		if ((prevLMax[h] > 0) && (barTimeL[h] + 1000000 > currentTime))
317		{
318			CGContextSetRGBStrokeColor(mboxctx, (22.0f / 256.0f), (156.0f / 256.0f), (20.0f / 256.0f), (float) (barTimeL[h] + 1000000 - currentTime) / 1000000.0f);
319
320			CGContextMoveToPoint   (mboxctx, x, mbxOffsetY + mbxMarginY + (float) (prevLMax[h] - 2));
321			CGContextAddLineToPoint(mboxctx, x, mbxOffsetY + mbxMarginY + (float) (prevLMax[h]    ));
322
323			CGContextStrokePath(mboxctx);
324		}
325		else
326			prevLMax[h] = 0;
327
328		prevLVol[h] = vl;
329
330		// right
331
332		if ((vr >= prevRMax[h]) && (vr > prevRVol[h]))
333		{
334			barTimeR[h] = currentTime;
335			prevRMax[h] = vr;
336		}
337		else
338		if ((prevRMax[h] > 0) && (barTimeR[h] + 1000000 > currentTime))
339		{
340			CGContextSetRGBStrokeColor(mboxctx, (22.0f / 256.0f), (156.0f / 256.0f), (20.0f / 256.0f), (float) (barTimeR[h] + 1000000 - currentTime) / 1000000.0f);
341
342			CGContextMoveToPoint   (mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + (float) (prevRMax[h] - 2));
343			CGContextAddLineToPoint(mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + (float) (prevRMax[h]    ));
344
345			CGContextStrokePath(mboxctx);
346		}
347		else
348			prevRMax[h] = 0;
349
350		prevRVol[h] = vr;
351
352		// Active
353
354		CGContextSetRGBStrokeColor(mboxctx, (22.0f / 256.0f), (22.0f / 256.0f), (20.0f / 256.0f), 1.0f);
355
356		CGContextMoveToPoint   (mboxctx, x,                mbxOffsetY + mbxMarginY);
357		CGContextAddLineToPoint(mboxctx, x,                mbxOffsetY + mbxMarginY + (float) vl);
358		CGContextStrokePath(mboxctx);
359		CGContextMoveToPoint   (mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY);
360		CGContextAddLineToPoint(mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + (float) vr);
361		CGContextStrokePath(mboxctx);
362
363		x += (mbxBarWidth + mbxBarSpace);
364	}
365}
366
367@end
368
369static void * SoundTask (void *)
370{
371	long long		last, curr;
372	struct timeval	tv;
373
374	gettimeofday(&tv, NULL);
375	last = tv.tv_sec * 1000000 + tv.tv_usec;
376
377	while (!stopNow)
378	{
379		if (!mboxPause)
380		{
381			if (musicboxmode == kMBXSoundEmulation)
382				SPCPlayExec();
383			else
384				S9xMainLoop();
385		}
386
387		if (headPressed)
388		{
389			showIndicator = false;
390			SPCPlayDefrost();
391			showIndicator = true;
392
393			headPressed = false;
394		}
395
396		last += (1000000 / Memory.ROMFramesPerSecond);
397		gettimeofday(&tv, NULL);
398		curr = tv.tv_sec * 1000000 + tv.tv_usec;
399
400		if (last > curr)
401			usleep((useconds_t) (last - curr));
402	}
403
404	return (NULL);
405}
406
407static void SPCPlayExec (void)
408{
409	for (int v = 0; v < Timings.V_Max; v++)
410	{
411		CPU.Cycles = Timings.H_Max;
412		S9xAPUEndScanline();
413		CPU.Cycles = 0;
414		S9xAPUSetReferenceTime(0);
415	}
416}
417
418static void MusicBoxForceFreeze (void)
419{
420	char	filename[PATH_MAX + 1];
421
422	strcpy(filename, S9xGetFreezeFilename(999));
423	strcat(filename, ".tmp");
424
425	S9xFreezeGame(filename);
426}
427
428static void MusicBoxForceDefrost (void)
429{
430	char	filename[PATH_MAX + 1];
431
432	strcpy(filename, S9xGetFreezeFilename(999));
433	strcat(filename, ".tmp");
434
435	S9xUnfreezeGame(filename);
436	remove(filename);
437}
438
439static void SPCPlayFreeze (void)
440{
441	oldCPUCycles = CPU.Cycles;
442
443	S9xSetSoundMute(true);
444	S9xAPUSaveState(storedSoundSnapshot);
445	S9xSetSoundMute(false);
446}
447
448static void SPCPlayDefrost (void)
449{
450	CPU.Cycles = oldCPUCycles;
451
452	S9xSetSoundMute(true);
453	S9xAPULoadState(storedSoundSnapshot);
454	S9xSetSoundMute(false);
455}
456
457void MusicBoxDialog (void)
458{
459	MusicBoxController	*controller;
460	NSAutoreleasePool	*pool;
461
462	if (!cartOpen)
463		return;
464
465	pool = [[NSAutoreleasePool alloc] init];
466	controller = [[MusicBoxController alloc] init];
467	[pool release];
468
469	if (!controller)
470		return;
471
472	[NSApp runModalForWindow: [controller window]];
473
474	pool = [[NSAutoreleasePool alloc] init];
475	[controller release];
476	[pool release];
477}
478