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