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