2018-11-16 00:31:39 +01:00
|
|
|
/*****************************************************************************\
|
|
|
|
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
|
|
|
|
This file is licensed under the Snes9x License.
|
|
|
|
For further information, consult the LICENSE file in the root directory.
|
|
|
|
\*****************************************************************************/
|
2011-04-10 15:44:28 +02:00
|
|
|
|
|
|
|
/***********************************************************************************
|
|
|
|
SNES9X for Mac OS (c) Copyright John Stiles
|
|
|
|
|
|
|
|
Snes9x for Mac OS X
|
|
|
|
|
|
|
|
(c) Copyright 2001 - 2011 zones
|
|
|
|
(c) Copyright 2002 - 2005 107
|
|
|
|
(c) Copyright 2002 PB1400c
|
|
|
|
(c) Copyright 2004 Alexander and Sander
|
|
|
|
(c) Copyright 2004 - 2005 Steven Seeger
|
|
|
|
(c) Copyright 2005 Ryan Vogt
|
2019-09-02 19:20:32 +02:00
|
|
|
(c) Copyright 2019 Michael Donald Buckley
|
2011-04-10 15:44:28 +02:00
|
|
|
***********************************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
#import "snes9x.h"
|
|
|
|
#import "memmap.h"
|
|
|
|
#import "apu.h"
|
|
|
|
#import "snapshot.h"
|
2017-10-29 07:00:29 +01:00
|
|
|
#import "snes.hpp"
|
2011-04-10 15:44:28 +02:00
|
|
|
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#import <sys/time.h>
|
|
|
|
#import <pthread.h>
|
|
|
|
|
|
|
|
#import "mac-prefix.h"
|
|
|
|
#import "mac-audio.h"
|
|
|
|
#import "mac-file.h"
|
|
|
|
#import "mac-os.h"
|
|
|
|
#import "mac-musicbox.h"
|
|
|
|
|
|
|
|
volatile bool8 mboxPause = false;
|
|
|
|
|
|
|
|
static volatile bool8 stopNow, showIndicator, headPressed;
|
|
|
|
static int32 oldCPUCycles;
|
|
|
|
static uint16 stereo_switch;
|
|
|
|
static uint8 storedSoundSnapshot[SPC_SAVE_STATE_BLOCK_SIZE];
|
|
|
|
|
|
|
|
static void SPCPlayExec (void);
|
|
|
|
static void SPCPlayFreeze (void);
|
|
|
|
static void SPCPlayDefrost (void);
|
|
|
|
static void MusicBoxForceFreeze (void);
|
|
|
|
static void MusicBoxForceDefrost (void);
|
|
|
|
static void * SoundTask (void *);
|
|
|
|
|
|
|
|
|
|
|
|
@implementation MusicBoxController
|
|
|
|
|
|
|
|
- (id) init
|
|
|
|
{
|
|
|
|
NSUserDefaults *defaults;
|
|
|
|
NSString *s;
|
|
|
|
NSRect rect;
|
|
|
|
NSSize size;
|
|
|
|
BOOL apuonly, r;
|
|
|
|
|
|
|
|
self = [super init];
|
|
|
|
if (!self)
|
|
|
|
return (self);
|
|
|
|
|
2023-03-08 19:23:32 +01:00
|
|
|
r = [NSBundle loadNibNamed: @"musicbox" owner: self ];
|
2011-04-10 15:44:28 +02:00
|
|
|
if (!r)
|
|
|
|
return (self);
|
|
|
|
|
|
|
|
apuonly = (musicboxmode == kMBXSoundEmulation);
|
|
|
|
|
|
|
|
if (apuonly)
|
|
|
|
SPCPlayFreeze();
|
|
|
|
else
|
|
|
|
MusicBoxForceFreeze();
|
|
|
|
|
2023-03-08 19:23:32 +01:00
|
|
|
auto path = splitpath(Memory.ROMFilename);
|
|
|
|
[gametitle setStringValue: [NSString stringWithUTF8String: path.stem.c_str()]];
|
2011-04-10 15:44:28 +02:00
|
|
|
|
|
|
|
[led setImage: [NSImage imageNamed: (apuonly ? @"musicbox_ledoff.icns" : @"musicbox_ledon.icns")]];
|
|
|
|
|
|
|
|
if (!apuonly)
|
|
|
|
{
|
|
|
|
[rewind setState: NSOffState];
|
|
|
|
[rewind setEnabled: NO];
|
|
|
|
}
|
|
|
|
|
|
|
|
defaults = [NSUserDefaults standardUserDefaults];
|
|
|
|
s = [defaults stringForKey: @"frame_musicbox"];
|
|
|
|
if (s)
|
|
|
|
{
|
|
|
|
rect = NSRectFromString(s);
|
|
|
|
[window setFrame: rect display: NO];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!savewindowpos)
|
|
|
|
[window center];
|
|
|
|
|
|
|
|
size = [window minSize];
|
|
|
|
mbxClosedHeight = size.height;
|
|
|
|
size = [window maxSize];
|
|
|
|
mbxOpenedHeight = size.height;
|
|
|
|
|
|
|
|
rect = [window frame];
|
|
|
|
showIndicator = (rect.size.height > mbxClosedHeight) ? true : false;
|
|
|
|
|
|
|
|
[disclosure setState: (showIndicator ? NSOnState : NSOffState)];
|
|
|
|
|
|
|
|
[window makeKeyAndOrderFront: self];
|
|
|
|
|
|
|
|
mboxPause = false;
|
|
|
|
headPressed = false;
|
|
|
|
|
|
|
|
stereo_switch = ~0;
|
2017-10-29 07:00:29 +01:00
|
|
|
SNES::dsp.spc_dsp.set_stereo_switch(stereo_switch);
|
2011-04-10 15:44:28 +02:00
|
|
|
|
|
|
|
for (int i = 0; i < MAC_MAX_PLAYERS; i++)
|
|
|
|
controlPad[i] = 0;
|
|
|
|
|
|
|
|
stopNow = false;
|
|
|
|
MacStartSound();
|
|
|
|
pthread_create(&mbxThread, NULL, SoundTask, NULL);
|
|
|
|
|
2019-07-14 05:42:21 +02:00
|
|
|
timer = [NSTimer scheduledTimerWithTimeInterval: (2.0 / (double) Memory.ROMFramesPerSecond) target: self selector: @selector(updateIndicator:) userInfo: nil repeats: YES];
|
2023-03-08 19:23:32 +01:00
|
|
|
//
|
2011-04-10 15:44:28 +02:00
|
|
|
return (self);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) windowWillClose: (NSNotification *) aNotification
|
|
|
|
{
|
|
|
|
NSUserDefaults *defaults;
|
|
|
|
NSString *s;
|
|
|
|
BOOL r;
|
|
|
|
|
|
|
|
[timer invalidate];
|
2019-07-14 05:42:21 +02:00
|
|
|
timer = nil;
|
2011-04-10 15:44:28 +02:00
|
|
|
|
|
|
|
showIndicator = false;
|
|
|
|
|
|
|
|
stopNow = true;
|
|
|
|
pthread_join(mbxThread, NULL);
|
|
|
|
MacStopSound();
|
|
|
|
|
|
|
|
defaults = [NSUserDefaults standardUserDefaults];
|
|
|
|
s = NSStringFromRect([window frame]);
|
|
|
|
[defaults setObject: s forKey: @"frame_musicbox"];
|
|
|
|
r = [defaults synchronize];
|
|
|
|
|
|
|
|
[NSApp stopModal];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
{
|
|
|
|
stereo_switch = ~0;
|
2017-10-29 07:00:29 +01:00
|
|
|
SNES::dsp.spc_dsp.set_stereo_switch(stereo_switch);
|
2011-04-10 15:44:28 +02:00
|
|
|
|
|
|
|
if (musicboxmode == kMBXSoundEmulation)
|
|
|
|
SPCPlayDefrost();
|
|
|
|
else
|
|
|
|
MusicBoxForceDefrost();
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSWindow *) window
|
|
|
|
{
|
|
|
|
return (window);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction) handlePauseButton: (id) sender
|
|
|
|
{
|
|
|
|
mboxPause = !mboxPause;
|
|
|
|
S9xSetSoundMute(mboxPause);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction) handleRewindButton: (id) sender
|
|
|
|
{
|
|
|
|
headPressed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction) handleEffectButton: (id) sender
|
|
|
|
{
|
|
|
|
[window orderOut: self];
|
|
|
|
showIndicator = false;
|
|
|
|
ConfigureSoundEffects();
|
|
|
|
showIndicator = true;
|
|
|
|
[window makeKeyAndOrderFront: self];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction) handleChannelButton: (id) sender
|
|
|
|
{
|
|
|
|
stereo_switch ^= (1 << [sender tag]);
|
2017-10-29 07:00:29 +01:00
|
|
|
SNES::dsp.spc_dsp.set_stereo_switch(stereo_switch);
|
2011-04-10 15:44:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction) handleDisclosureButton: (id) sender
|
|
|
|
{
|
|
|
|
NSRect rect;
|
|
|
|
float h;
|
|
|
|
|
|
|
|
showIndicator = !showIndicator;
|
|
|
|
rect = [window frame];
|
|
|
|
h = rect.size.height;
|
|
|
|
rect.size.height = showIndicator ? mbxOpenedHeight : mbxClosedHeight;
|
|
|
|
rect.origin.y += (h - rect.size.height);
|
|
|
|
[window setFrame: rect display: YES animate: YES];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) updateIndicator: (NSTimer *) aTimer
|
|
|
|
{
|
|
|
|
if (showIndicator)
|
|
|
|
[indicator setNeedsDisplay: YES];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation MusicBoxIndicatorView
|
|
|
|
|
|
|
|
- (id) initWithFrame: (NSRect) frame
|
|
|
|
{
|
|
|
|
self = [super initWithFrame: frame];
|
|
|
|
if (self)
|
|
|
|
{
|
|
|
|
NSRect rect;
|
|
|
|
long long currentTime;
|
|
|
|
struct timeval tv;
|
|
|
|
|
|
|
|
mbxOffsetX = 0.0f;
|
|
|
|
mbxOffsetY = 0.0f;
|
|
|
|
mbxBarWidth = 12.0f;
|
|
|
|
mbxBarHeight = 128.0f;
|
|
|
|
mbxBarSpace = 2.0f;
|
|
|
|
mbxLRSpace = 20.0f;
|
|
|
|
mbxRightBarX = (mbxLRSpace + (mbxBarWidth * 8.0f + mbxBarSpace * 7.0f));
|
|
|
|
yyscale = (float) (128.0 / sqrt(64.0));
|
|
|
|
|
|
|
|
rect = [self bounds];
|
|
|
|
mbxViewWidth = rect.size.width;
|
|
|
|
mbxViewHeight = rect.size.height;
|
|
|
|
mbxMarginX = (mbxViewWidth - ((mbxBarWidth * 8.0f + mbxBarSpace * 7.0f) * 2.0f + mbxLRSpace)) / 2.0f;
|
|
|
|
mbxMarginY = (mbxViewHeight - mbxBarHeight) / 2.0f;
|
|
|
|
|
|
|
|
gettimeofday(&tv, NULL);
|
|
|
|
currentTime = tv.tv_sec * 1000000 + tv.tv_usec;
|
|
|
|
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
|
|
{
|
|
|
|
prevLMax[i] = prevRMax[i] = 0;
|
|
|
|
prevLVol[i] = prevRVol[i] = 0;
|
|
|
|
barTimeL[i] = barTimeR[i] = currentTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (self);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) drawRect: (NSRect) rect
|
|
|
|
{
|
|
|
|
CGContextRef mboxctx;
|
|
|
|
|
|
|
|
mboxctx = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
|
|
|
|
|
|
|
|
// Bar
|
|
|
|
|
|
|
|
const float length[] = { 1.0f, 1.0f };
|
|
|
|
|
|
|
|
CGContextSetLineWidth(mboxctx, mbxBarWidth);
|
2019-07-14 05:42:21 +02:00
|
|
|
//CGContextSetLineDash(mboxctx, 0, length, 2);
|
2011-04-10 15:44:28 +02:00
|
|
|
CGContextSetLineJoin(mboxctx, kCGLineJoinMiter);
|
|
|
|
|
|
|
|
CGContextBeginPath(mboxctx);
|
|
|
|
|
|
|
|
float x = mbxOffsetX + mbxMarginX + mbxBarWidth / 2.0f;
|
|
|
|
|
|
|
|
for (int h = 0; h < 8; h++)
|
|
|
|
{
|
|
|
|
// Inactive
|
|
|
|
|
|
|
|
CGContextSetRGBStrokeColor(mboxctx, (196.0f / 256.0f), (200.0f / 256.0f), (176.0f / 256.0f), 1.0f);
|
|
|
|
|
|
|
|
CGContextMoveToPoint (mboxctx, x, mbxOffsetY + mbxMarginY);
|
|
|
|
CGContextAddLineToPoint(mboxctx, x, mbxOffsetY + mbxMarginY + mbxBarHeight);
|
|
|
|
|
|
|
|
CGContextMoveToPoint (mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY);
|
|
|
|
CGContextAddLineToPoint(mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + mbxBarHeight);
|
|
|
|
|
|
|
|
CGContextStrokePath(mboxctx);
|
|
|
|
|
|
|
|
// Max
|
|
|
|
|
2017-10-29 07:00:29 +01:00
|
|
|
short vl = (SNES::dsp.spc_dsp.reg_value(h, 0x00) * SNES::dsp.spc_dsp.envx_value(h)) >> 11;
|
|
|
|
short vr = (SNES::dsp.spc_dsp.reg_value(h, 0x01) * SNES::dsp.spc_dsp.envx_value(h)) >> 11;
|
2011-04-10 15:44:28 +02:00
|
|
|
long long currentTime;
|
|
|
|
struct timeval tv;
|
|
|
|
|
|
|
|
if (vl <= 0) vl = 0; else if (vl > 64) vl = 64; else vl = (short) (yyscale * sqrt((double) vl)) & (~0 << 1);
|
|
|
|
if (vr <= 0) vr = 0; else if (vr > 64) vr = 64; else vr = (short) (yyscale * sqrt((double) vr)) & (~0 << 1);
|
|
|
|
|
|
|
|
if (vl < prevLVol[h]) vl = ((prevLVol[h] + vl) >> 1);
|
|
|
|
if (vr < prevRVol[h]) vr = ((prevRVol[h] + vr) >> 1);
|
|
|
|
|
|
|
|
gettimeofday(&tv, NULL);
|
|
|
|
currentTime = tv.tv_sec * 1000000 + tv.tv_usec;
|
|
|
|
|
|
|
|
// left
|
|
|
|
|
|
|
|
if ((vl >= prevLMax[h]) && (vl > prevLVol[h]))
|
|
|
|
{
|
|
|
|
barTimeL[h] = currentTime;
|
|
|
|
prevLMax[h] = vl;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if ((prevLMax[h] > 0) && (barTimeL[h] + 1000000 > currentTime))
|
|
|
|
{
|
|
|
|
CGContextSetRGBStrokeColor(mboxctx, (22.0f / 256.0f), (156.0f / 256.0f), (20.0f / 256.0f), (float) (barTimeL[h] + 1000000 - currentTime) / 1000000.0f);
|
|
|
|
|
|
|
|
CGContextMoveToPoint (mboxctx, x, mbxOffsetY + mbxMarginY + (float) (prevLMax[h] - 2));
|
|
|
|
CGContextAddLineToPoint(mboxctx, x, mbxOffsetY + mbxMarginY + (float) (prevLMax[h] ));
|
|
|
|
|
|
|
|
CGContextStrokePath(mboxctx);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
prevLMax[h] = 0;
|
|
|
|
|
|
|
|
prevLVol[h] = vl;
|
|
|
|
|
|
|
|
// right
|
|
|
|
|
|
|
|
if ((vr >= prevRMax[h]) && (vr > prevRVol[h]))
|
|
|
|
{
|
|
|
|
barTimeR[h] = currentTime;
|
|
|
|
prevRMax[h] = vr;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if ((prevRMax[h] > 0) && (barTimeR[h] + 1000000 > currentTime))
|
|
|
|
{
|
|
|
|
CGContextSetRGBStrokeColor(mboxctx, (22.0f / 256.0f), (156.0f / 256.0f), (20.0f / 256.0f), (float) (barTimeR[h] + 1000000 - currentTime) / 1000000.0f);
|
|
|
|
|
|
|
|
CGContextMoveToPoint (mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + (float) (prevRMax[h] - 2));
|
|
|
|
CGContextAddLineToPoint(mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + (float) (prevRMax[h] ));
|
|
|
|
|
|
|
|
CGContextStrokePath(mboxctx);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
prevRMax[h] = 0;
|
|
|
|
|
|
|
|
prevRVol[h] = vr;
|
|
|
|
|
|
|
|
// Active
|
|
|
|
|
|
|
|
CGContextSetRGBStrokeColor(mboxctx, (22.0f / 256.0f), (22.0f / 256.0f), (20.0f / 256.0f), 1.0f);
|
|
|
|
|
|
|
|
CGContextMoveToPoint (mboxctx, x, mbxOffsetY + mbxMarginY);
|
|
|
|
CGContextAddLineToPoint(mboxctx, x, mbxOffsetY + mbxMarginY + (float) vl);
|
|
|
|
CGContextStrokePath(mboxctx);
|
|
|
|
CGContextMoveToPoint (mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY);
|
|
|
|
CGContextAddLineToPoint(mboxctx, x + mbxRightBarX, mbxOffsetY + mbxMarginY + (float) vr);
|
|
|
|
CGContextStrokePath(mboxctx);
|
|
|
|
|
|
|
|
x += (mbxBarWidth + mbxBarSpace);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
static void * SoundTask (void *)
|
|
|
|
{
|
|
|
|
long long last, curr;
|
|
|
|
struct timeval tv;
|
|
|
|
|
|
|
|
gettimeofday(&tv, NULL);
|
|
|
|
last = tv.tv_sec * 1000000 + tv.tv_usec;
|
|
|
|
|
|
|
|
while (!stopNow)
|
|
|
|
{
|
|
|
|
if (!mboxPause)
|
|
|
|
{
|
|
|
|
if (musicboxmode == kMBXSoundEmulation)
|
|
|
|
SPCPlayExec();
|
|
|
|
else
|
|
|
|
S9xMainLoop();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (headPressed)
|
|
|
|
{
|
|
|
|
showIndicator = false;
|
|
|
|
SPCPlayDefrost();
|
|
|
|
showIndicator = true;
|
|
|
|
|
|
|
|
headPressed = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
last += (1000000 / Memory.ROMFramesPerSecond);
|
|
|
|
gettimeofday(&tv, NULL);
|
|
|
|
curr = tv.tv_sec * 1000000 + tv.tv_usec;
|
|
|
|
|
|
|
|
if (last > curr)
|
|
|
|
usleep((useconds_t) (last - curr));
|
|
|
|
}
|
|
|
|
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SPCPlayExec (void)
|
|
|
|
{
|
|
|
|
for (int v = 0; v < Timings.V_Max; v++)
|
|
|
|
{
|
|
|
|
CPU.Cycles = Timings.H_Max;
|
|
|
|
S9xAPUEndScanline();
|
|
|
|
CPU.Cycles = 0;
|
|
|
|
S9xAPUSetReferenceTime(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void MusicBoxForceFreeze (void)
|
|
|
|
{
|
2020-09-07 20:23:05 +02:00
|
|
|
char filename[PATH_MAX + 1];
|
2011-04-10 15:44:28 +02:00
|
|
|
|
2020-09-07 20:23:05 +02:00
|
|
|
strlcpy(filename, S9xGetFreezeFilename(999), sizeof(filename));
|
|
|
|
strlcat(filename, ".tmp", sizeof(filename));
|
2011-04-10 15:44:28 +02:00
|
|
|
|
|
|
|
S9xFreezeGame(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void MusicBoxForceDefrost (void)
|
|
|
|
{
|
2020-09-07 20:23:05 +02:00
|
|
|
char filename[PATH_MAX + 1];
|
2011-04-10 15:44:28 +02:00
|
|
|
|
2020-09-07 20:23:05 +02:00
|
|
|
strlcpy(filename, S9xGetFreezeFilename(999), sizeof(filename));
|
|
|
|
strlcat(filename, ".tmp", sizeof(filename));
|
2011-04-10 15:44:28 +02:00
|
|
|
|
|
|
|
S9xUnfreezeGame(filename);
|
|
|
|
remove(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SPCPlayFreeze (void)
|
|
|
|
{
|
|
|
|
oldCPUCycles = CPU.Cycles;
|
|
|
|
|
|
|
|
S9xSetSoundMute(true);
|
|
|
|
S9xAPUSaveState(storedSoundSnapshot);
|
|
|
|
S9xSetSoundMute(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SPCPlayDefrost (void)
|
|
|
|
{
|
|
|
|
CPU.Cycles = oldCPUCycles;
|
|
|
|
|
|
|
|
S9xSetSoundMute(true);
|
|
|
|
S9xAPULoadState(storedSoundSnapshot);
|
|
|
|
S9xSetSoundMute(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MusicBoxDialog (void)
|
|
|
|
{
|
|
|
|
MusicBoxController *controller;
|
|
|
|
|
|
|
|
if (!cartOpen)
|
|
|
|
return;
|
|
|
|
|
|
|
|
controller = [[MusicBoxController alloc] init];
|
|
|
|
|
|
|
|
if (!controller)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[NSApp runModalForWindow: [controller window]];
|
|
|
|
}
|