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