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 <kode54@gmail.com>
CQTexperiment
Christopher Snowhill 2022-02-13 01:32:26 -08:00
parent 417687600b
commit 992b716193
6 changed files with 164 additions and 62 deletions

View File

@ -210,7 +210,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="gpC-Oe-Rog">
<rect key="frame" x="234.5" y="3" width="149" height="18"/>
<rect key="frame" x="235" y="3" width="149" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1WK-qN-Mgj">
@ -289,7 +289,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="hhB-nv-e78">
<rect key="frame" x="540.5" y="3" width="95" height="18"/>
<rect key="frame" x="541" y="3" width="95" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tHy-sM-HDB">
@ -366,7 +366,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="rRl-p9-Awr">
<rect key="frame" x="735.5" y="3" width="144" height="18"/>
<rect key="frame" x="736" y="3" width="144" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yW6-2w-6mN">
@ -402,7 +402,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="hgh-VE-5kl">
<rect key="frame" x="882.5" y="3" width="38" height="18"/>
<rect key="frame" x="883" y="3" width="38" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yEY-MI-d3o">
@ -876,13 +876,12 @@
</connections>
</button>
</toolbarItem>
<toolbarItem implicitItemIdentifier="5E0A643B-09FC-45CF-A562-C56F1E388E62" label="Spectrum" paletteLabel="Spectrum" sizingBehavior="auto" id="gui-fC-qTP">
<toolbarItem implicitItemIdentifier="80D8CDBD-A6E9-47A6-B9C0-213C450E45FA" label="Spectrum" paletteLabel="Spectrum" tag="-1" sizingBehavior="auto" id="NtB-XF-g07" customClass="SpectrumItem">
<nil key="toolTip"/>
<imageView key="view" horizontalHuggingPriority="251" verticalHuggingPriority="251" id="Zaq-sS-aTV" customClass="SpectrumView">
<customView key="view" id="lVy-2P-05b" customClass="SpectrumView">
<rect key="frame" x="0.0" y="14" width="64" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="VDu-2p-2bJ"/>
</imageView>
</customView>
</toolbarItem>
</allowedToolbarItems>
<defaultToolbarItems>
@ -896,7 +895,7 @@
<toolbarItem reference="1630"/>
<toolbarItem reference="1629"/>
<toolbarItem reference="1529"/>
<toolbarItem reference="gui-fC-qTP"/>
<toolbarItem reference="NtB-XF-g07"/>
<toolbarItem reference="1568"/>
<toolbarItem reference="1551"/>
<toolbarItem reference="1610"/>
@ -1131,13 +1130,12 @@
</connections>
</button>
</toolbarItem>
<toolbarItem implicitItemIdentifier="1A931C99-28FC-4209-BD5C-38E5CF96F7AF" label="Spectrum" paletteLabel="Spectrum" sizingBehavior="auto" id="K64-ro-2GI">
<toolbarItem implicitItemIdentifier="09140C29-FF34-4BF2-92C8-54C7D9AA4A27" label="Spectrum" paletteLabel="Spectrum" tag="-1" sizingBehavior="auto" id="sf3-l1-fJw" customClass="SpectrumItem">
<nil key="toolTip"/>
<imageView key="view" horizontalHuggingPriority="251" verticalHuggingPriority="251" id="F4M-9A-fZv" customClass="SpectrumView">
<customView key="view" id="gyu-hn-EUs" customClass="SpectrumView">
<rect key="frame" x="0.0" y="14" width="64" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="br1-X9-N6H"/>
</imageView>
</customView>
</toolbarItem>
</allowedToolbarItems>
<defaultToolbarItems>

View File

@ -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 = "<group>"; };
8377C66227B8CF6300E8BC0F /* SpectrumView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectrumView.h; path = Visualization/SpectrumView.h; sourceTree = "<group>"; };
8377C66427B8CF7A00E8BC0F /* VisualizationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VisualizationController.h; path = Audio/Visualization/VisualizationController.h; sourceTree = "<group>"; };
8377C6B727B900F000E8BC0F /* SpectrumItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpectrumItem.h; path = Visualization/SpectrumItem.h; sourceTree = "<group>"; };
8377C6B827B900F000E8BC0F /* SpectrumItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SpectrumItem.m; path = Visualization/SpectrumItem.m; sourceTree = "<group>"; };
8384912518080F2D00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = "<group>"; };
8384913D18083E4E00E7332D /* filetype.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = filetype.icns; sourceTree = "<group>"; };
8384914318083EAB00E7332D /* infoTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = infoTemplate.pdf; path = Images/infoTemplate.pdf; sourceTree = "<group>"; };
@ -1697,6 +1700,8 @@
8377C66427B8CF7A00E8BC0F /* VisualizationController.h */,
8377C66227B8CF6300E8BC0F /* SpectrumView.h */,
8377C66127B8CF6300E8BC0F /* SpectrumView.m */,
8377C6B727B900F000E8BC0F /* SpectrumItem.h */,
8377C6B827B900F000E8BC0F /* SpectrumItem.m */,
);
name = Visualization;
sourceTree = "<group>";
@ -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 */,

View File

@ -0,0 +1,18 @@
//
// SpectrumItem.h
// Cog
//
// Created by Christopher Snowhill on 2/13/22.
//
#import <Cocoa/Cocoa.h>
#import "SpectrumView.h"
NS_ASSUME_NONNULL_BEGIN
@interface SpectrumItem : NSToolbarItem
@end
NS_ASSUME_NONNULL_END

View File

@ -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

View File

@ -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

View File

@ -7,6 +7,8 @@
#import "SpectrumView.h"
#import <Accelerate/Accelerate.h>
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