diff --git a/Cocoa/Document.h b/Cocoa/Document.h
index e0a3aa3a..297f5085 100644
--- a/Cocoa/Document.h
+++ b/Cocoa/Document.h
@@ -2,6 +2,7 @@
#include "GBView.h"
#include "GBImageView.h"
#include "GBSplitView.h"
+#include "GBVisualizerView.h"
@class GBCheatWindowController;
@@ -47,6 +48,7 @@
@property (strong) IBOutlet NSButton *gbsPlayPauseButton;
@property (strong) IBOutlet NSButton *gbsRewindButton;
@property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton;
+@property (strong) IBOutlet GBVisualizerView *gbsVisualizer;
-(uint8_t) readMemory:(uint16_t) addr;
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
diff --git a/Cocoa/Document.m b/Cocoa/Document.m
index 9d2d7f79..0e7009ac 100644
--- a/Cocoa/Document.m
+++ b/Cocoa/Document.m
@@ -318,6 +318,11 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on)
- (void) vblank
{
+ if (_gbsVisualizer) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [_gbsVisualizer setNeedsDisplay:YES];
+ });
+ }
[self.view flip];
if (borderModeChanged) {
dispatch_sync(dispatch_get_main_queue(), ^{
@@ -344,6 +349,9 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on)
- (void)gotNewSample:(GB_sample_t *)sample
{
+ if (_gbsVisualizer) {
+ [_gbsVisualizer addSample:sample];
+ }
[audioLock lock];
if (self.audioClient.isPlaying) {
if (audioBufferPosition == audioBufferSize) {
@@ -837,6 +845,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (IBAction)changeGBSTrack:(id)sender
{
+ if (!running) {
+ [self start];
+ }
[self performAtomicBlock:^{
GB_gbs_switch_track(&gb, self.gbsTracks.indexOfSelectedItem);
}];
@@ -878,7 +889,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
[_mainWindow standardWindowButton:NSWindowZoomButton].enabled = false;
});
[_mainWindow.contentView addSubview:self.gbsPlayerView];
-
+ _mainWindow.movableByWindowBackground = true;
+ [_mainWindow setContentBorderThickness:24 forEdge:NSRectEdgeMinY];
+
self.gbsTitle.stringValue = [NSString stringWithCString:info->title encoding:NSISOLatin1StringEncoding] ?: @"GBS Player";
self.gbsAuthor.stringValue = [NSString stringWithCString:info->author encoding:NSISOLatin1StringEncoding] ?: @"Unknown Composer";
NSString *copyright = [NSString stringWithCString:info->copyright encoding:NSISOLatin1StringEncoding];
diff --git a/Cocoa/GBS.xib b/Cocoa/GBS.xib
index 6d5ba017..534ff559 100644
--- a/Cocoa/GBS.xib
+++ b/Cocoa/GBS.xib
@@ -16,44 +16,36 @@
+
-
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
@@ -86,8 +78,8 @@
-
-
+
+
@@ -99,8 +91,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m
index 9a1c78b6..580db2ca 100644
--- a/Cocoa/GBViewMetal.m
+++ b/Cocoa/GBViewMetal.m
@@ -123,7 +123,7 @@ static const vector_float2 rect[] =
command_queue = [device newCommandQueue];
}
-- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
+- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
{
output_resolution = (vector_float2){size.width, size.height};
dispatch_async(dispatch_get_main_queue(), ^{
@@ -131,7 +131,7 @@ static const vector_float2 rect[] =
});
}
-- (void)drawInMTKView:(nonnull MTKView *)view
+- (void)drawInMTKView:(MTKView *)view
{
if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return;
if (!self.gb) return;
diff --git a/Cocoa/GBVisualizerView.h b/Cocoa/GBVisualizerView.h
new file mode 100644
index 00000000..43cda4bf
--- /dev/null
+++ b/Cocoa/GBVisualizerView.h
@@ -0,0 +1,14 @@
+//
+// GBVisualizerView.h
+// SameBoySDL
+//
+// Created by Lior Halphon on 7/4/21.
+// Copyright © 2021 Lior Halphon. All rights reserved.
+//
+
+#import
+#include
+
+@interface GBVisualizerView : NSView
+- (void)addSample:(GB_sample_t *)sample;
+@end
diff --git a/Cocoa/GBVisualizerView.m b/Cocoa/GBVisualizerView.m
new file mode 100644
index 00000000..c09cfe1e
--- /dev/null
+++ b/Cocoa/GBVisualizerView.m
@@ -0,0 +1,95 @@
+//
+// GBVisualizerView.m
+// SameBoySDL
+//
+// Created by Lior Halphon on 7/4/21.
+// Copyright © 2021 Lior Halphon. All rights reserved.
+//
+
+#import "GBVisualizerView.h"
+#include
+
+#define SAMPLE_COUNT 1024
+
+static NSColor *color_to_effect_color(typeof(GB_PALETTE_DMG.colors[0]) color)
+{
+ if (@available(macOS 10.10, *)) {
+ double tint = MAX(color.r, MAX(color.g, color.b)) + 64;
+
+ return [NSColor colorWithRed:color.r / tint
+ green:color.g / tint
+ blue:color.b / tint
+ alpha:tint/(255 + 64)];
+
+ }
+ return [NSColor colorWithRed:color.r / 255.0
+ green:color.g / 255.0
+ blue:color.b / 255.0
+ alpha:1.0];
+}
+
+@implementation GBVisualizerView
+{
+ GB_sample_t _samples[SAMPLE_COUNT];
+ size_t _position;
+}
+
+- (void)drawRect:(NSRect)dirtyRect
+{
+ const GB_palette_t *palette;
+ switch ([[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]) {
+ case 1:
+ palette = &GB_PALETTE_DMG;
+ break;
+
+ case 2:
+ palette = &GB_PALETTE_MGB;
+ break;
+
+ case 3:
+ palette = &GB_PALETTE_GBL;
+ break;
+
+ default:
+ palette = &GB_PALETTE_GREY;
+ break;
+ }
+ NSSize size = self.bounds.size;
+
+ [color_to_effect_color(palette->colors[0]) setFill];
+ NSRectFill(self.bounds);
+
+ NSBezierPath *line = [NSBezierPath bezierPath];
+ [line moveToPoint:NSMakePoint(0, size.height / 2)];
+
+ for (unsigned i = 0; i < SAMPLE_COUNT; i++) {
+ GB_sample_t *sample = _samples + ((i + _position) % SAMPLE_COUNT);
+ double volume = ((signed)sample->left + (signed)sample->right) / 32768.0;
+ [line lineToPoint:NSMakePoint(size.width * (i + 0.5) / SAMPLE_COUNT,
+ (volume + 1) * size.height / 2)];
+ }
+
+ [line lineToPoint:NSMakePoint(size.width, size.height / 2)];
+ [line setLineWidth:1.0];
+
+ [color_to_effect_color(palette->colors[2]) setFill];
+ [line fill];
+
+ [color_to_effect_color(palette->colors[1]) setFill];
+ NSRectFill(NSMakeRect(0, size.height / 2 - 0.5, size.width, 1));
+
+ [color_to_effect_color(palette->colors[3]) setStroke];
+ [line stroke];
+
+ [super drawRect:dirtyRect];
+}
+
+- (void)addSample:(GB_sample_t *)sample
+{
+ _samples[_position++] = *sample;
+ if (_position == SAMPLE_COUNT) {
+ _position = 0;
+ }
+}
+
+@end