Reduce the virtual resolution to match the actual resolution, and set the analyzere frequency to Nyquist of the audio input, as it seems to behave as if the entire range of the input FFT bands are up to the specified frequency, rather than half of it. Otherwise, we lose half of the frequency range provided by the input data. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
257 lines
7.9 KiB
Objective-C
257 lines
7.9 KiB
Objective-C
//
|
|
// SpectrumView.m
|
|
// Cog
|
|
//
|
|
// Created by Christopher Snowhill on 2/12/22.
|
|
//
|
|
|
|
#import "SpectrumView.h"
|
|
|
|
#import "analyzer.h"
|
|
|
|
#define LOWER_BOUND -80
|
|
|
|
extern NSString *CogPlaybackDidBeginNotficiation;
|
|
extern NSString *CogPlaybackDidPauseNotficiation;
|
|
extern NSString *CogPlaybackDidResumeNotficiation;
|
|
extern NSString *CogPlaybackDidStopNotficiation;
|
|
|
|
@interface SpectrumView () {
|
|
VisualizationController *visController;
|
|
NSTimer *timer;
|
|
BOOL paused;
|
|
BOOL stopped;
|
|
BOOL isListening;
|
|
|
|
NSColor *baseColor;
|
|
NSColor *peakColor;
|
|
NSColor *backgroundColor;
|
|
NSColor *borderColor;
|
|
ddb_analyzer_t _analyzer;
|
|
ddb_analyzer_draw_data_t _draw_data;
|
|
}
|
|
@end
|
|
|
|
@implementation SpectrumView
|
|
|
|
@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;
|
|
stopped = YES;
|
|
paused = NO;
|
|
isListening = NO;
|
|
|
|
[self colorsDidChange:nil];
|
|
|
|
ddb_analyzer_init(&_analyzer);
|
|
_analyzer.db_lower_bound = LOWER_BOUND;
|
|
_analyzer.peak_hold = 10;
|
|
_analyzer.view_width = 64;
|
|
_analyzer.fractional_bars = 1;
|
|
_analyzer.octave_bars_step = 2;
|
|
_analyzer.max_of_stereo_data = 1;
|
|
_analyzer.mode = DDB_ANALYZER_MODE_OCTAVE_NOTE_BANDS;
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(colorsDidChange:)
|
|
name:NSSystemColorsDidChangeNotification
|
|
object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(playbackDidBegin:)
|
|
name:CogPlaybackDidBeginNotficiation
|
|
object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(playbackDidPause:)
|
|
name:CogPlaybackDidPauseNotficiation
|
|
object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(playbackDidResume:)
|
|
name:CogPlaybackDidResumeNotficiation
|
|
object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(playbackDidStop:)
|
|
name:CogPlaybackDidStopNotficiation
|
|
object:nil];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
ddb_analyzer_dealloc(&_analyzer);
|
|
ddb_analyzer_draw_data_dealloc(&_draw_data);
|
|
|
|
[[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 {
|
|
self.needsDisplay = YES;
|
|
}
|
|
|
|
- (void)startTimer {
|
|
[self stopTimer];
|
|
timer = [NSTimer timerWithTimeInterval:1.0 / 60.0
|
|
target:self
|
|
selector:@selector(timerRun:)
|
|
userInfo:nil
|
|
repeats:YES];
|
|
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
|
}
|
|
|
|
- (void)stopTimer {
|
|
[timer invalidate];
|
|
timer = nil;
|
|
}
|
|
|
|
- (void)timerRun:(NSTimer *)timer {
|
|
[self repaint];
|
|
}
|
|
|
|
- (void)colorsDidChange:(NSNotification *)notification {
|
|
backgroundColor = [NSColor textBackgroundColor];
|
|
backgroundColor = [backgroundColor colorWithAlphaComponent:0.0];
|
|
borderColor = [NSColor systemGrayColor];
|
|
|
|
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;
|
|
paused = NO;
|
|
[self updateVisListening];
|
|
}
|
|
|
|
- (void)playbackDidPause:(NSNotification *)notification {
|
|
stopped = NO;
|
|
paused = YES;
|
|
[self updateVisListening];
|
|
}
|
|
|
|
- (void)playbackDidResume:(NSNotification *)notification {
|
|
stopped = NO;
|
|
paused = NO;
|
|
[self updateVisListening];
|
|
}
|
|
|
|
- (void)playbackDidStop:(NSNotification *)notification {
|
|
stopped = YES;
|
|
paused = NO;
|
|
[self updateVisListening];
|
|
[self repaint];
|
|
}
|
|
|
|
- (void)drawAnalyzerDescreteFrequencies {
|
|
CGContextRef context = NSGraphicsContext.currentContext.CGContext;
|
|
ddb_analyzer_draw_bar_t *bar = _draw_data.bars;
|
|
for(int i = 0; i < _draw_data.bar_count; i++, bar++) {
|
|
CGContextMoveToPoint(context, bar->xpos, 0);
|
|
CGContextAddLineToPoint(context, bar->xpos, bar->bar_height);
|
|
}
|
|
CGContextSetStrokeColorWithColor(context, baseColor.CGColor);
|
|
CGContextStrokePath(context);
|
|
|
|
bar = _draw_data.bars;
|
|
for(int i = 0; i < _draw_data.bar_count; i++, bar++) {
|
|
CGContextMoveToPoint(context, bar->xpos - 0.5, bar->peak_ypos);
|
|
CGContextAddLineToPoint(context, bar->xpos + 0.5, bar->peak_ypos);
|
|
}
|
|
CGContextSetStrokeColorWithColor(context, peakColor.CGColor);
|
|
CGContextStrokePath(context);
|
|
}
|
|
|
|
- (void)drawAnalyzerOctaveBands {
|
|
CGContextRef context = NSGraphicsContext.currentContext.CGContext;
|
|
ddb_analyzer_draw_bar_t *bar = _draw_data.bars;
|
|
for(int i = 0; i < _draw_data.bar_count; i++, bar++) {
|
|
CGContextAddRect(context, CGRectMake(bar->xpos, 0, _draw_data.bar_width, bar->bar_height));
|
|
}
|
|
CGContextSetFillColorWithColor(context, baseColor.CGColor);
|
|
CGContextFillPath(context);
|
|
|
|
bar = _draw_data.bars;
|
|
for(int i = 0; i < _draw_data.bar_count; i++, bar++) {
|
|
CGContextAddRect(context, CGRectMake(bar->xpos, bar->peak_ypos, _draw_data.bar_width, 1.0));
|
|
}
|
|
CGContextSetFillColorWithColor(context, peakColor.CGColor);
|
|
CGContextFillPath(context);
|
|
}
|
|
|
|
- (void)drawAnalyzer {
|
|
if(_analyzer.mode == DDB_ANALYZER_MODE_FREQUENCIES) {
|
|
[self drawAnalyzerDescreteFrequencies];
|
|
} else {
|
|
[self drawAnalyzerOctaveBands];
|
|
}
|
|
}
|
|
|
|
- (void)drawRect:(NSRect)dirtyRect {
|
|
[super drawRect:dirtyRect];
|
|
|
|
[self updateVisListening];
|
|
|
|
[backgroundColor setFill];
|
|
NSRectFill(dirtyRect);
|
|
|
|
CGContextRef context = NSGraphicsContext.currentContext.CGContext;
|
|
CGContextMoveToPoint(context, 0.0, 0.0);
|
|
CGContextAddLineToPoint(context, 64.0, 0.0);
|
|
CGContextAddLineToPoint(context, 64.0, 26.0);
|
|
CGContextAddLineToPoint(context, 0.0, 26.0);
|
|
CGContextAddLineToPoint(context, 0.0, 0.0);
|
|
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
|
|
CGContextStrokePath(context);
|
|
|
|
if(stopped) return;
|
|
|
|
float visAudio[8192], visFFT[4096];
|
|
|
|
[self->visController copyVisPCM:&visAudio[0] visFFT:&visFFT[0]];
|
|
|
|
ddb_analyzer_process(&_analyzer, [self->visController readSampleRate] / 2.0, 1, visFFT, 4096);
|
|
ddb_analyzer_tick(&_analyzer);
|
|
ddb_analyzer_get_draw_data(&_analyzer, self.bounds.size.width, self.bounds.size.height, &_draw_data);
|
|
|
|
[self drawAnalyzer];
|
|
}
|
|
|
|
@end
|