From 992b716193cadc98c30bb6dfab55f46159e7bfb2 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sun, 13 Feb 2022 01:32:26 -0800 Subject: [PATCH] Visualization: Greatly improve spectrum design Improvement includes greatly reducing the CPU usage by not using an NSImage based painting system. Signed-off-by: Christopher Snowhill --- Base.lproj/MainMenu.xib | 24 +++--- Cog.xcodeproj/project.pbxproj | 6 ++ Visualization/SpectrumItem.h | 18 ++++ Visualization/SpectrumItem.m | 17 ++++ Visualization/SpectrumView.h | 12 ++- Visualization/SpectrumView.m | 149 +++++++++++++++++++++++----------- 6 files changed, 164 insertions(+), 62 deletions(-) create mode 100644 Visualization/SpectrumItem.h create mode 100644 Visualization/SpectrumItem.m diff --git a/Base.lproj/MainMenu.xib b/Base.lproj/MainMenu.xib index b229e08dc..8f6a42774 100644 --- a/Base.lproj/MainMenu.xib +++ b/Base.lproj/MainMenu.xib @@ -210,7 +210,7 @@ - + @@ -289,7 +289,7 @@ - + @@ -366,7 +366,7 @@ - + @@ -402,7 +402,7 @@ - + @@ -876,13 +876,12 @@ - + - + - - + @@ -896,7 +895,7 @@ - + @@ -1131,13 +1130,12 @@ - + - + - - + diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 9c24add20..7452020af 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -125,6 +125,7 @@ 8370D73D277419F700245CE0 /* SQLiteStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8370D73C277419F700245CE0 /* SQLiteStore.m */; }; 8370D73F2775AE1300245CE0 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8370D73E2775AE1300245CE0 /* libsqlite3.tbd */; }; 8377C66327B8CF6300E8BC0F /* SpectrumView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C66127B8CF6300E8BC0F /* SpectrumView.m */; }; + 8377C6B927B900F000E8BC0F /* SpectrumItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C6B827B900F000E8BC0F /* SpectrumItem.m */; }; 8384914018083E4E00E7332D /* filetype.icns in Resources */ = {isa = PBXBuildFile; fileRef = 8384913D18083E4E00E7332D /* filetype.icns */; }; 8384915918083EAB00E7332D /* infoTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 8384914318083EAB00E7332D /* infoTemplate.pdf */; }; 8384915A18083EAB00E7332D /* missingArt@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384914418083EAB00E7332D /* missingArt@2x.png */; }; @@ -946,6 +947,8 @@ 8377C66127B8CF6300E8BC0F /* SpectrumView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectrumView.m; path = Visualization/SpectrumView.m; sourceTree = ""; }; 8377C66227B8CF6300E8BC0F /* SpectrumView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectrumView.h; path = Visualization/SpectrumView.h; sourceTree = ""; }; 8377C66427B8CF7A00E8BC0F /* VisualizationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VisualizationController.h; path = Audio/Visualization/VisualizationController.h; sourceTree = ""; }; + 8377C6B727B900F000E8BC0F /* SpectrumItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpectrumItem.h; path = Visualization/SpectrumItem.h; sourceTree = ""; }; + 8377C6B827B900F000E8BC0F /* SpectrumItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SpectrumItem.m; path = Visualization/SpectrumItem.m; sourceTree = ""; }; 8384912518080F2D00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = ""; }; 8384913D18083E4E00E7332D /* filetype.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = filetype.icns; sourceTree = ""; }; 8384914318083EAB00E7332D /* infoTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = infoTemplate.pdf; path = Images/infoTemplate.pdf; sourceTree = ""; }; @@ -1697,6 +1700,8 @@ 8377C66427B8CF7A00E8BC0F /* VisualizationController.h */, 8377C66227B8CF6300E8BC0F /* SpectrumView.h */, 8377C66127B8CF6300E8BC0F /* SpectrumView.m */, + 8377C6B727B900F000E8BC0F /* SpectrumItem.h */, + 8377C6B827B900F000E8BC0F /* SpectrumItem.m */, ); name = Visualization; sourceTree = ""; @@ -2459,6 +2464,7 @@ 8E75757409F31D5A0080F1EE /* PlaylistView.m in Sources */, 8E75757509F31D5A0080F1EE /* Shuffle.m in Sources */, 8E07AB790AAC930B00A4B32F /* PreferencesController.m in Sources */, + 8377C6B927B900F000E8BC0F /* SpectrumItem.m in Sources */, 177EBFA70B8BC2A70000BC8C /* ImageTextCell.m in Sources */, 177EC0270B8BC2CF0000BC8C /* TrackingCell.m in Sources */, 177EC0290B8BC2CF0000BC8C /* TrackingSlider.m in Sources */, diff --git a/Visualization/SpectrumItem.h b/Visualization/SpectrumItem.h new file mode 100644 index 000000000..91b5420b1 --- /dev/null +++ b/Visualization/SpectrumItem.h @@ -0,0 +1,18 @@ +// +// SpectrumItem.h +// Cog +// +// Created by Christopher Snowhill on 2/13/22. +// + +#import + +#import "SpectrumView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SpectrumItem : NSToolbarItem + +@end + +NS_ASSUME_NONNULL_END diff --git a/Visualization/SpectrumItem.m b/Visualization/SpectrumItem.m new file mode 100644 index 000000000..f2114abe5 --- /dev/null +++ b/Visualization/SpectrumItem.m @@ -0,0 +1,17 @@ +// +// SpectrumItem.m +// Cog +// +// Created by Christopher Snowhill on 2/13/22. +// + +#import "SpectrumItem.h" + +@implementation SpectrumItem + +- (void)awakeFromNib { + SpectrumView *view = [[SpectrumView alloc] initWithFrame:NSMakeRect(0, 0, 64, 26)]; + [self setView:view]; +} + +@end diff --git a/Visualization/SpectrumView.h b/Visualization/SpectrumView.h index 045a32762..8394b6b99 100644 --- a/Visualization/SpectrumView.h +++ b/Visualization/SpectrumView.h @@ -11,12 +11,20 @@ NS_ASSUME_NONNULL_BEGIN -@interface SpectrumView : NSImageView { +@interface SpectrumView : NSView { VisualizationController *visController; NSTimer *timer; - NSImage *theImage; + BOOL paused; BOOL stopped; + BOOL isListening; + + float FFTMax[256]; + + NSColor *baseColor; + NSColor *peakColor; + NSColor *backgroundColor; } +@property(nonatomic) BOOL isListening; @end NS_ASSUME_NONNULL_END diff --git a/Visualization/SpectrumView.m b/Visualization/SpectrumView.m index afc6d9a96..e89b61969 100644 --- a/Visualization/SpectrumView.m +++ b/Visualization/SpectrumView.m @@ -7,6 +7,8 @@ #import "SpectrumView.h" +#import + extern NSString *CogPlaybackDidBeginNotficiation; extern NSString *CogPlaybackDidPauseNotficiation; extern NSString *CogPlaybackDidResumeNotficiation; @@ -14,21 +16,36 @@ extern NSString *CogPlaybackDidStopNotficiation; @implementation SpectrumView -- (void)awakeFromNib { +@synthesize isListening; + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if(self) { + [self setup]; + } + return self; +} + +- (void)updateVisListening { + if(self.isListening && (paused || stopped)) { + [self stopTimer]; + self.isListening = NO; + } else if(!self.isListening && (!stopped && !paused)) { + [self startTimer]; + self.isListening = YES; + } +} + +- (void)setup { visController = [NSClassFromString(@"VisualizationController") sharedController]; timer = nil; - theImage = [NSImage imageWithSize:NSMakeSize(64, 26) - flipped:NO - drawingHandler:^BOOL(NSRect dstRect) { - NSColor *backColor = [NSColor textBackgroundColor]; - [backColor drawSwatchInRect:dstRect]; - return YES; - }]; - stopped = YES; + paused = NO; + isListening = NO; - [self setImage:theImage]; - [self setImageScaling:NSImageScaleAxesIndependently]; + [self colorsDidChange:nil]; + + vDSP_vclr(&FFTMax[0], 1, 256); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(colorsDidChange:) @@ -52,43 +69,31 @@ extern NSString *CogPlaybackDidStopNotficiation; object:nil]; } +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSSystemColorsDidChangeNotification + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:CogPlaybackDidBeginNotficiation + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:CogPlaybackDidPauseNotficiation + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:CogPlaybackDidResumeNotficiation + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:CogPlaybackDidStopNotficiation + object:nil]; +} + - (void)repaint { - { - [theImage lockFocus]; - - NSColor *backColor = [NSColor textBackgroundColor]; - [backColor drawSwatchInRect:NSMakeRect(0, 0, 64, 26)]; - - NSBezierPath *bezierPath = [[NSBezierPath alloc] init]; - - float visAudio[512], visFFT[256]; - - if(!self->stopped) { - [self->visController copyVisPCM:&visAudio[0] visFFT:&visFFT[0]]; - } else { - memset(visFFT, 0, sizeof(visFFT)); - } - - for(int i = 0; i < 60; ++i) { - CGFloat y = MAX(MIN(visFFT[i], 0.25), 0.0) * 4.0 * 22.0 + 2.0; - [bezierPath moveToPoint:NSMakePoint(2 + i, 2)]; - [bezierPath lineToPoint:NSMakePoint(2 + i, y)]; - } - - NSColor *lineColor = [NSColor textColor]; - [lineColor setStroke]; - - [bezierPath stroke]; - - [theImage unlockFocus]; - } - - [self setNeedsDisplay]; + self.needsDisplay = YES; } - (void)startTimer { [self stopTimer]; - timer = [NSTimer timerWithTimeInterval:0.02 + timer = [NSTimer timerWithTimeInterval:1.0 / 60.0 target:self selector:@selector(timerRun:) userInfo:nil @@ -106,32 +111,82 @@ extern NSString *CogPlaybackDidStopNotficiation; } - (void)colorsDidChange:(NSNotification *)notification { + backgroundColor = [NSColor textBackgroundColor]; + + if(@available(macOS 10.14, *)) { + baseColor = [NSColor textColor]; + peakColor = [NSColor controlAccentColor]; + peakColor = [peakColor colorWithAlphaComponent:0.7]; + } else { + peakColor = [NSColor textColor]; + baseColor = [peakColor colorWithAlphaComponent:0.6]; + } + [self repaint]; } - (void)playbackDidBegin:(NSNotification *)notification { stopped = NO; - [self startTimer]; + paused = NO; + [self updateVisListening]; } - (void)playbackDidPause:(NSNotification *)notification { stopped = NO; - [self stopTimer]; + paused = YES; + [self updateVisListening]; } - (void)playbackDidResume:(NSNotification *)notification { stopped = NO; - [self startTimer]; + paused = NO; + [self updateVisListening]; } - (void)playbackDidStop:(NSNotification *)notification { - [self stopTimer]; stopped = YES; + paused = NO; + [self updateVisListening]; [self repaint]; } - (void)drawRect:(NSRect)dirtyRect { [super drawRect:dirtyRect]; + + [self updateVisListening]; + + [backgroundColor setFill]; + NSRectFill(dirtyRect); + + float visAudio[512], visFFT[256]; + + if(!self->stopped) { + [self->visController copyVisPCM:&visAudio[0] visFFT:&visFFT[0]]; + } else { + memset(visFFT, 0, sizeof(visFFT)); + } + + float scale = 0.95; + vDSP_vsmul(&FFTMax[0], 1, &scale, &FFTMax[0], 1, 256); + vDSP_vmax(&visFFT[0], 1, &FFTMax[0], 1, &FFTMax[0], 1, 256); + + CGContextRef context = NSGraphicsContext.currentContext.CGContext; + + for(int i = 0; i < 60; ++i) { + CGFloat y = MAX(MIN(visFFT[i], 0.25), 0.0) * 4.0 * 22.0; + CGContextMoveToPoint(context, 2.0 + i, 2.0); + CGContextAddLineToPoint(context, 2.0 + i, 2.0 + y); + } + CGContextSetStrokeColorWithColor(context, baseColor.CGColor); + CGContextStrokePath(context); + + for(int i = 0; i < 60; ++i) { + CGFloat y = MAX(MIN(FFTMax[i], 0.25), 0.0) * 4.0 * 22.0; + CGContextMoveToPoint(context, 2.0 + i, 1.5 + y); + CGContextAddLineToPoint(context, 2.0 + i, 2.5 + y); + } + CGContextSetStrokeColorWithColor(context, peakColor.CGColor); + CGContextStrokePath(context); } @end