1/***********************************************************************************
2  Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3
4  (c) Copyright 1996 - 2002  Gary Henderson (gary.henderson@ntlworld.com),
5                             Jerremy Koot (jkoot@snes9x.com)
6
7  (c) Copyright 2002 - 2004  Matthew Kendora
8
9  (c) Copyright 2002 - 2005  Peter Bortas (peter@bortas.org)
10
11  (c) Copyright 2004 - 2005  Joel Yliluoma (http://iki.fi/bisqwit/)
12
13  (c) Copyright 2001 - 2006  John Weidman (jweidman@slip.net)
14
15  (c) Copyright 2002 - 2006  funkyass (funkyass@spam.shaw.ca),
16                             Kris Bleakley (codeviolation@hotmail.com)
17
18  (c) Copyright 2002 - 2010  Brad Jorsch (anomie@users.sourceforge.net),
19                             Nach (n-a-c-h@users.sourceforge.net),
20
21  (c) Copyright 2002 - 2011  zones (kasumitokoduck@yahoo.com)
22
23  (c) Copyright 2006 - 2007  nitsuja
24
25  (c) Copyright 2009 - 2016  BearOso,
26                             OV2
27
28
29  BS-X C emulator code
30  (c) Copyright 2005 - 2006  Dreamer Nom,
31                             zones
32
33  C4 x86 assembler and some C emulation code
34  (c) Copyright 2000 - 2003  _Demo_ (_demo_@zsnes.com),
35                             Nach,
36                             zsKnight (zsknight@zsnes.com)
37
38  C4 C++ code
39  (c) Copyright 2003 - 2006  Brad Jorsch,
40                             Nach
41
42  DSP-1 emulator code
43  (c) Copyright 1998 - 2006  _Demo_,
44                             Andreas Naive (andreasnaive@gmail.com),
45                             Gary Henderson,
46                             Ivar (ivar@snes9x.com),
47                             John Weidman,
48                             Kris Bleakley,
49                             Matthew Kendora,
50                             Nach,
51                             neviksti (neviksti@hotmail.com)
52
53  DSP-2 emulator code
54  (c) Copyright 2003         John Weidman,
55                             Kris Bleakley,
56                             Lord Nightmare (lord_nightmare@users.sourceforge.net),
57                             Matthew Kendora,
58                             neviksti
59
60  DSP-3 emulator code
61  (c) Copyright 2003 - 2006  John Weidman,
62                             Kris Bleakley,
63                             Lancer,
64                             z80 gaiden
65
66  DSP-4 emulator code
67  (c) Copyright 2004 - 2006  Dreamer Nom,
68                             John Weidman,
69                             Kris Bleakley,
70                             Nach,
71                             z80 gaiden
72
73  OBC1 emulator code
74  (c) Copyright 2001 - 2004  zsKnight,
75                             pagefault (pagefault@zsnes.com),
76                             Kris Bleakley
77                             Ported from x86 assembler to C by sanmaiwashi
78
79  SPC7110 and RTC C++ emulator code used in 1.39-1.51
80  (c) Copyright 2002         Matthew Kendora with research by
81                             zsKnight,
82                             John Weidman,
83                             Dark Force
84
85  SPC7110 and RTC C++ emulator code used in 1.52+
86  (c) Copyright 2009         byuu,
87                             neviksti
88
89  S-DD1 C emulator code
90  (c) Copyright 2003         Brad Jorsch with research by
91                             Andreas Naive,
92                             John Weidman
93
94  S-RTC C emulator code
95  (c) Copyright 2001 - 2006  byuu,
96                             John Weidman
97
98  ST010 C++ emulator code
99  (c) Copyright 2003         Feather,
100                             John Weidman,
101                             Kris Bleakley,
102                             Matthew Kendora
103
104  Super FX x86 assembler emulator code
105  (c) Copyright 1998 - 2003  _Demo_,
106                             pagefault,
107                             zsKnight
108
109  Super FX C emulator code
110  (c) Copyright 1997 - 1999  Ivar,
111                             Gary Henderson,
112                             John Weidman
113
114  Sound emulator code used in 1.5-1.51
115  (c) Copyright 1998 - 2003  Brad Martin
116  (c) Copyright 1998 - 2006  Charles Bilyue'
117
118  Sound emulator code used in 1.52+
119  (c) Copyright 2004 - 2007  Shay Green (gblargg@gmail.com)
120
121  S-SMP emulator code used in 1.54+
122  (c) Copyright 2016         byuu
123
124  SH assembler code partly based on x86 assembler code
125  (c) Copyright 2002 - 2004  Marcus Comstedt (marcus@mc.pp.se)
126
127  2xSaI filter
128  (c) Copyright 1999 - 2001  Derek Liauw Kie Fa
129
130  HQ2x, HQ3x, HQ4x filters
131  (c) Copyright 2003         Maxim Stepin (maxim@hiend3d.com)
132
133  NTSC filter
134  (c) Copyright 2006 - 2007  Shay Green
135
136  GTK+ GUI code
137  (c) Copyright 2004 - 2016  BearOso
138
139  Win32 GUI code
140  (c) Copyright 2003 - 2006  blip,
141                             funkyass,
142                             Matthew Kendora,
143                             Nach,
144                             nitsuja
145  (c) Copyright 2009 - 2016  OV2
146
147  Mac OS GUI code
148  (c) Copyright 1998 - 2001  John Stiles
149  (c) Copyright 2001 - 2011  zones
150
151
152  Specific ports contains the works of other authors. See headers in
153  individual files.
154
155
156  Snes9x homepage: http://www.snes9x.com/
157
158  Permission to use, copy, modify and/or distribute Snes9x in both binary
159  and source form, for non-commercial purposes, is hereby granted without
160  fee, providing that this license information and copyright notice appear
161  with all copies and any derived work.
162
163  This software is provided 'as-is', without any express or implied
164  warranty. In no event shall the authors be held liable for any damages
165  arising from the use of this software or it's derivatives.
166
167  Snes9x is freeware for PERSONAL USE only. Commercial users should
168  seek permission of the copyright holders first. Commercial use includes,
169  but is not limited to, charging money for Snes9x or software derived from
170  Snes9x, including Snes9x or derivatives in commercial game bundles, and/or
171  using Snes9x as a promotion for your commercial product.
172
173  The copyright holders request that bug fixes and improvements to the code
174  should be forwarded to them so everyone can benefit from the modifications
175  in future versions.
176
177  Super NES and Super Nintendo Entertainment System are trademarks of
178  Nintendo Co., Limited and its subsidiary companies.
179 ***********************************************************************************/
180
181/***********************************************************************************
182  SNES9X for Mac OS (c) Copyright John Stiles
183
184  Snes9x for Mac OS X
185
186  (c) Copyright 2001 - 2011  zones
187  (c) Copyright 2002 - 2005  107
188  (c) Copyright 2002         PB1400c
189  (c) Copyright 2004         Alexander and Sander
190  (c) Copyright 2004 - 2005  Steven Seeger
191  (c) Copyright 2005         Ryan Vogt
192 ***********************************************************************************/
193
194
195#import "snes9x.h"
196#import "memmap.h"
197#import "apu.h"
198#import "snapshot.h"
199
200#import <Cocoa/Cocoa.h>
201#import <sys/time.h>
202#import <pthread.h>
203
204#import "mac-prefix.h"
205#import "mac-audio.h"
206#import "mac-file.h"
207#import "mac-os.h"
208#import "mac-musicbox.h"
209
210volatile bool8			mboxPause = false;
211
212static volatile bool8	stopNow, showIndicator, headPressed;
213static int32			oldCPUCycles;
214static uint16			stereo_switch;
215static uint8			storedSoundSnapshot[SPC_SAVE_STATE_BLOCK_SIZE];
216
217static void SPCPlayExec (void);
218static void SPCPlayFreeze (void);
219static void SPCPlayDefrost (void);
220static void MusicBoxForceFreeze (void);
221static void MusicBoxForceDefrost (void);
222static void * SoundTask (void *);
223
224
225@implementation MusicBoxController
226
227- (id) init
228{
229	NSUserDefaults	*defaults;
230	NSString		*s;
231	NSRect			rect;
232	NSSize			size;
233	BOOL			apuonly, r;
234	char			drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1];
235
236	self = [super init];
237	if (!self)
238		return (self);
239
240	r = [NSBundle loadNibNamed: @"musicbox" owner: self];
241	if (!r)
242		return (self);
243
244	apuonly = (musicboxmode == kMBXSoundEmulation);
245
246	if (apuonly)
247		SPCPlayFreeze();
248	else
249		MusicBoxForceFreeze();
250
251	_splitpath(Memory.ROMFilename, drive, dir, fname, ext);
252	[gametitle setStringValue: [NSString stringWithUTF8String: fname]];
253
254	[led setImage: [NSImage imageNamed: (apuonly ? @"musicbox_ledoff.icns" : @"musicbox_ledon.icns")]];
255
256	if (!apuonly)
257	{
258		[rewind setState: NSOffState];
259		[rewind setEnabled: NO];
260	}
261
262	defaults = [NSUserDefaults standardUserDefaults];
263	s = [defaults stringForKey: @"frame_musicbox"];
264	if (s)
265	{
266		rect = NSRectFromString(s);
267		[window setFrame: rect display: NO];
268	}
269
270	if (!savewindowpos)
271		[window center];
272
273	size = [window minSize];
274	mbxClosedHeight = size.height;
275	size = [window maxSize];
276	mbxOpenedHeight = size.height;
277
278	rect = [window frame];
279	showIndicator = (rect.size.height > mbxClosedHeight) ? true : false;
280
281	[disclosure setState: (showIndicator ? NSOnState : NSOffState)];
282
283	[window makeKeyAndOrderFront: self];
284
285	mboxPause   = false;
286	headPressed = false;
287
288	stereo_switch = ~0;
289	spc_core->dsp_set_stereo_switch(stereo_switch);
290
291	for (int i = 0; i < MAC_MAX_PLAYERS; i++)
292		controlPad[i] = 0;
293
294	stopNow = false;
295	MacStartSound();
296	pthread_create(&mbxThread, NULL, SoundTask, NULL);
297
298	timer = [[NSTimer scheduledTimerWithTimeInterval: (2.0 / (double) Memory.ROMFramesPerSecond) target: self selector: @selector(updateIndicator:) userInfo: nil repeats: YES] retain];
299
300	return (self);
301}
302
303- (void) windowWillClose: (NSNotification *) aNotification
304{
305	NSUserDefaults	*defaults;
306	NSString		*s;
307	BOOL			r;
308
309	[timer invalidate];
310	[timer release];
311
312	showIndicator = false;
313
314	stopNow = true;
315	pthread_join(mbxThread, NULL);
316	MacStopSound();
317
318	defaults = [NSUserDefaults standardUserDefaults];
319	s = NSStringFromRect([window frame]);
320	[defaults setObject: s forKey: @"frame_musicbox"];
321	r = [defaults synchronize];
322
323	[NSApp stopModal];
324}
325
326- (void) dealloc
327{
328	stereo_switch = ~0;
329	spc_core->dsp_set_stereo_switch(stereo_switch);
330
331	if (musicboxmode == kMBXSoundEmulation)
332		SPCPlayDefrost();
333	else
334		MusicBoxForceDefrost();
335
336	[window release];
337
338	[super dealloc];
339}
340
341- (NSWindow *) window
342{
343	return (window);
344}
345
346- (IBAction) handlePauseButton: (id) sender
347{
348	mboxPause = !mboxPause;
349	S9xSetSoundMute(mboxPause);
350}
351
352- (IBAction) handleRewindButton: (id) sender
353{
354	headPressed = true;
355}
356
357- (IBAction) handleEffectButton: (id) sender
358{
359	[window orderOut: self];
360	showIndicator = false;
361	ConfigureSoundEffects();
362	showIndicator = true;
363	[window makeKeyAndOrderFront: self];
364}
365
366- (IBAction) handleChannelButton: (id) sender
367{
368	stereo_switch ^= (1 << [sender tag]);
369	spc_core->dsp_set_stereo_switch(stereo_switch);
370}
371
372- (IBAction) handleDisclosureButton: (id) sender
373{
374	NSRect	rect;
375	float	h;
376
377	showIndicator = !showIndicator;
378	rect = [window frame];
379	h = rect.size.height;
380	rect.size.height = showIndicator ? mbxOpenedHeight : mbxClosedHeight;
381	rect.origin.y += (h - rect.size.height);
382	[window setFrame: rect display: YES animate: YES];
383}
384
385- (void) updateIndicator: (NSTimer *) aTimer
386{
387	if (showIndicator)
388		[indicator setNeedsDisplay: YES];
389}
390
391@end
392
393@implementation MusicBoxIndicatorView
394
395- (id) initWithFrame: (NSRect) frame
396{
397	self = [super initWithFrame: frame];
398	if (self)
399	{
400		NSRect			rect;
401		long long		currentTime;
402		struct timeval	tv;
403
404		mbxOffsetX   =   0.0f;
405		mbxOffsetY   =   0.0f;
406		mbxBarWidth  =  12.0f;
407		mbxBarHeight = 128.0f;
408		mbxBarSpace  =   2.0f;
409		mbxLRSpace   =  20.0f;
410		mbxRightBarX = (mbxLRSpace + (mbxBarWidth * 8.0f + mbxBarSpace * 7.0f));
411		yyscale      = (float) (128.0 / sqrt(64.0));
412
413		rect = [self bounds];
414		mbxViewWidth  = rect.size.width;
415		mbxViewHeight = rect.size.height;
416		mbxMarginX = (mbxViewWidth - ((mbxBarWidth * 8.0f + mbxBarSpace * 7.0f) * 2.0f + mbxLRSpace)) / 2.0f;
417		mbxMarginY = (mbxViewHeight - mbxBarHeight) / 2.0f;
418
419		gettimeofday(&tv, NULL);
420		currentTime = tv.tv_sec * 1000000 + tv.tv_usec;
421
422		for (int i = 0; i < 8; i++)
423		{
424			prevLMax[i] = prevRMax[i] = 0;
425			prevLVol[i] = prevRVol[i] = 0;
426			barTimeL[i] = barTimeR[i] = currentTime;
427		}
428	}
429
430	return (self);
431}
432
433- (void) drawRect: (NSRect) rect
434{
435	CGContextRef	mboxctx;
436
437	mboxctx = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
438
439	// Bar
440
441	const float	length[] = { 1.0f, 1.0f };
442
443	CGContextSetLineWidth(mboxctx, mbxBarWidth);
444	CGContextSetLineDash(mboxctx, 0, length, 2);
445	CGContextSetLineJoin(mboxctx, kCGLineJoinMiter);
446
447	CGContextBeginPath(mboxctx);
448
449	float   x = mbxOffsetX + mbxMarginX + mbxBarWidth / 2.0f;
450
451	for (int h = 0; h < 8; h++)
452	{
453		// Inactive
454
455		CGContextSetRGBStrokeColor(mboxctx, (196.0f / 256.0f), (200.0f / 256.0f), (176.0f / 256.0f), 1.0f);
456
457		CGContextMoveToPoint   (mboxctx, x,                mbxOffsetY + mbxMarginY);
458		CGContextAddLineToPoint(mboxctx, x,                mbxOffsetY + mbxMarginY + mbxBarHeight);
459
460		CGContextMoveToPoint   (mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY);
461		CGContextAddLineToPoint(mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + mbxBarHeight);
462
463		CGContextStrokePath(mboxctx);
464
465		// Max
466
467		short			vl = (spc_core->dsp_reg_value(h, 0x00) * spc_core->dsp_envx_value(h)) >> 11;
468		short			vr = (spc_core->dsp_reg_value(h, 0x01) * spc_core->dsp_envx_value(h)) >> 11;
469		long long		currentTime;
470		struct timeval	tv;
471
472		if (vl <= 0) vl = 0; else if (vl > 64) vl = 64; else vl = (short) (yyscale * sqrt((double) vl)) & (~0 << 1);
473		if (vr <= 0) vr = 0; else if (vr > 64) vr = 64; else vr = (short) (yyscale * sqrt((double) vr)) & (~0 << 1);
474
475		if (vl < prevLVol[h]) vl = ((prevLVol[h] + vl) >> 1);
476		if (vr < prevRVol[h]) vr = ((prevRVol[h] + vr) >> 1);
477
478		gettimeofday(&tv, NULL);
479		currentTime = tv.tv_sec * 1000000 + tv.tv_usec;
480
481		// left
482
483		if ((vl >= prevLMax[h]) && (vl > prevLVol[h]))
484		{
485			barTimeL[h] = currentTime;
486			prevLMax[h] = vl;
487		}
488		else
489		if ((prevLMax[h] > 0) && (barTimeL[h] + 1000000 > currentTime))
490		{
491			CGContextSetRGBStrokeColor(mboxctx, (22.0f / 256.0f), (156.0f / 256.0f), (20.0f / 256.0f), (float) (barTimeL[h] + 1000000 - currentTime) / 1000000.0f);
492
493			CGContextMoveToPoint   (mboxctx, x, mbxOffsetY + mbxMarginY + (float) (prevLMax[h] - 2));
494			CGContextAddLineToPoint(mboxctx, x, mbxOffsetY + mbxMarginY + (float) (prevLMax[h]    ));
495
496			CGContextStrokePath(mboxctx);
497		}
498		else
499			prevLMax[h] = 0;
500
501		prevLVol[h] = vl;
502
503		// right
504
505		if ((vr >= prevRMax[h]) && (vr > prevRVol[h]))
506		{
507			barTimeR[h] = currentTime;
508			prevRMax[h] = vr;
509		}
510		else
511		if ((prevRMax[h] > 0) && (barTimeR[h] + 1000000 > currentTime))
512		{
513			CGContextSetRGBStrokeColor(mboxctx, (22.0f / 256.0f), (156.0f / 256.0f), (20.0f / 256.0f), (float) (barTimeR[h] + 1000000 - currentTime) / 1000000.0f);
514
515			CGContextMoveToPoint   (mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + (float) (prevRMax[h] - 2));
516			CGContextAddLineToPoint(mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + (float) (prevRMax[h]    ));
517
518			CGContextStrokePath(mboxctx);
519		}
520		else
521			prevRMax[h] = 0;
522
523		prevRVol[h] = vr;
524
525		// Active
526
527		CGContextSetRGBStrokeColor(mboxctx, (22.0f / 256.0f), (22.0f / 256.0f), (20.0f / 256.0f), 1.0f);
528
529		CGContextMoveToPoint   (mboxctx, x,                mbxOffsetY + mbxMarginY);
530		CGContextAddLineToPoint(mboxctx, x,                mbxOffsetY + mbxMarginY + (float) vl);
531		CGContextStrokePath(mboxctx);
532		CGContextMoveToPoint   (mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY);
533		CGContextAddLineToPoint(mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + (float) vr);
534		CGContextStrokePath(mboxctx);
535
536		x += (mbxBarWidth + mbxBarSpace);
537	}
538}
539
540@end
541
542static void * SoundTask (void *)
543{
544	long long		last, curr;
545	struct timeval	tv;
546
547	gettimeofday(&tv, NULL);
548	last = tv.tv_sec * 1000000 + tv.tv_usec;
549
550	while (!stopNow)
551	{
552		if (!mboxPause)
553		{
554			if (musicboxmode == kMBXSoundEmulation)
555				SPCPlayExec();
556			else
557				S9xMainLoop();
558		}
559
560		if (headPressed)
561		{
562			showIndicator = false;
563			SPCPlayDefrost();
564			showIndicator = true;
565
566			headPressed = false;
567		}
568
569		last += (1000000 / Memory.ROMFramesPerSecond);
570		gettimeofday(&tv, NULL);
571		curr = tv.tv_sec * 1000000 + tv.tv_usec;
572
573		if (last > curr)
574			usleep((useconds_t) (last - curr));
575	}
576
577	return (NULL);
578}
579
580static void SPCPlayExec (void)
581{
582	for (int v = 0; v < Timings.V_Max; v++)
583	{
584		CPU.Cycles = Timings.H_Max;
585		S9xAPUEndScanline();
586		CPU.Cycles = 0;
587		S9xAPUSetReferenceTime(0);
588	}
589}
590
591static void MusicBoxForceFreeze (void)
592{
593	char	filename[PATH_MAX + 1];
594
595	strcpy(filename, S9xGetFreezeFilename(999));
596	strcat(filename, ".tmp");
597
598	S9xFreezeGame(filename);
599}
600
601static void MusicBoxForceDefrost (void)
602{
603	char	filename[PATH_MAX + 1];
604
605	strcpy(filename, S9xGetFreezeFilename(999));
606	strcat(filename, ".tmp");
607
608	S9xUnfreezeGame(filename);
609	remove(filename);
610}
611
612static void SPCPlayFreeze (void)
613{
614	oldCPUCycles = CPU.Cycles;
615
616	S9xSetSoundMute(true);
617	S9xAPUSaveState(storedSoundSnapshot);
618	S9xSetSoundMute(false);
619}
620
621static void SPCPlayDefrost (void)
622{
623	CPU.Cycles = oldCPUCycles;
624
625	S9xSetSoundMute(true);
626	S9xAPULoadState(storedSoundSnapshot);
627	S9xSetSoundMute(false);
628}
629
630void MusicBoxDialog (void)
631{
632	MusicBoxController	*controller;
633	NSAutoreleasePool	*pool;
634
635	if (!cartOpen)
636		return;
637
638	pool = [[NSAutoreleasePool alloc] init];
639	controller = [[MusicBoxController alloc] init];
640	[pool release];
641
642	if (!controller)
643		return;
644
645	[NSApp runModalForWindow: [controller window]];
646
647	pool = [[NSAutoreleasePool alloc] init];
648	[controller release];
649	[pool release];
650}
651