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