Cog/Window/PitchSlider.m
Christopher Snowhill e5eeb987fa Implemented real pitch and time shifting using Rubber Band
I will implement the more complex setup of providing options for
most of the configuration that Rubber Band provides, at a later
date, when I feel like creating a complex configuration dialog
for it, and asking for help translating every option and setting.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2024-12-09 00:44:43 -08:00

187 lines
5.2 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// PitchSlider.m
// Cog
//
// Created by Christopher Snowhill on 9/20/24.
// Copyright 2024 __LoSnoCo__. All rights reserved.
//
#import "PitchSlider.h"
#import "CogAudio/Helper.h"
#import "PlaybackController.h"
static void *kPitchSliderContext = &kPitchSliderContext;
@implementation PitchSlider {
NSTimer *currentTimer;
BOOL wasInsideSnapRange;
/*BOOL observersadded;*/
}
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
return self;
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
return self;
}
- (void)awakeFromNib {
wasInsideSnapRange = NO;
textView = [[NSText alloc] init];
[textView setFrame:NSMakeRect(0, 0, 50, 20)];
textView.drawsBackground = NO;
textView.editable = NO;
textView.alignment = NSTextAlignmentCenter;
NSViewController *viewController = [[NSViewController alloc] init];
viewController.view = textView;
popover = [[NSPopover alloc] init];
popover.contentViewController = viewController;
// Don't hide the popover automatically.
popover.behavior = NSPopoverBehaviorTransient;
popover.animates = NO;
[popover setContentSize:textView.bounds.size];
/*observersadded = YES;*/
}
/*- (void)dealloc {
if(observersadded) {
}
}*/
- (void)updateToolTip {
const double value = ([self doubleValue] - [self minValue]) * 100.0 / ([self maxValue] - [self minValue]);
NSString *text;
const double adjustedValue = ((value * value) * (5.0 - 0.2) / 10000.0) + 0.2;
double pitch;
if(adjustedValue < 0.2) {
pitch = 0.2;
} else if(adjustedValue > 5.0) {
pitch = 5.0;
} else {
pitch = adjustedValue;
}
if(pitch < 1)
text = [NSString stringWithFormat:@"%0.2lf×", pitch];
else
text = [NSString stringWithFormat:@"%0.1lf×", pitch];
[textView setString:text];
}
- (void)showToolTip {
[self updateToolTip];
double progress = (self.maxValue - [self doubleValue]) / (self.maxValue - self.minValue);
CGFloat width = self.knobThickness - 1;
// Show tooltip to the left of the Slider Knob
CGFloat height = self.knobThickness / 2.f + (self.bounds.size.height - self.knobThickness) * progress - 1;
NSWindow *window = self.window;
NSPoint screenPoint = [window convertPointToScreen:NSMakePoint(width + 1, height + 1)];
if(window.screen.frame.size.width < screenPoint.x + textView.bounds.size.width + 64) // wing it
[popover showRelativeToRect:NSMakeRect(1, height, 2, 2) ofView:self preferredEdge:NSRectEdgeMinX];
else
[popover showRelativeToRect:NSMakeRect(width, height, 2, 2) ofView:self preferredEdge:NSRectEdgeMaxX];
[self.window.parentWindow makeKeyWindow];
}
- (void)showToolTipForDuration:(NSTimeInterval)duration {
[self showToolTip];
[self hideToolTipAfterDelay:duration];
}
- (void)showToolTipForView:(NSView *)view closeAfter:(NSTimeInterval)duration {
[self updateToolTip];
[popover showRelativeToRect:view.bounds ofView:view preferredEdge:NSRectEdgeMaxY];
[self hideToolTipAfterDelay:duration];
}
- (void)hideToolTip {
[popover close];
}
- (void)hideToolTipAfterDelay:(NSTimeInterval)duration {
if(currentTimer) {
[currentTimer invalidate];
currentTimer = nil;
}
if(duration > 0.0) {
currentTimer = [NSTimer scheduledTimerWithTimeInterval:duration
target:self
selector:@selector(hideToolTip)
userInfo:nil
repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:currentTimer forMode:NSRunLoopCommonModes];
}
}
/*- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if(context != kPitchSliderContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
}*/
- (BOOL)sendAction:(SEL)theAction to:(id)theTarget {
// Snap to 1.0× if value is close
double snapTarget = 1.0;
const double value = ([self doubleValue] - [self minValue]) * 100.0 / ([self maxValue] - [self minValue]);
const double adjustedValue = ((value * value) * (5.0 - 0.2) / 10000.0) + 0.2;
double snapProgress = (adjustedValue - snapTarget);
BOOL speedLock = [[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"];
if(speedLock) {
[_TempoSlider setDoubleValue:[self doubleValue]];
}
if(fabs(snapProgress) < 0.01) {
const double inverseValue = sqrtf((snapTarget - 0.2) * 10000.0 / (5.0 - 0.2));
[self setDoubleValue:inverseValue];
if(speedLock) {
[_TempoSlider setDoubleValue:inverseValue];
}
if(!wasInsideSnapRange) {
[[NSHapticFeedbackManager defaultPerformer] performFeedbackPattern:NSHapticFeedbackPatternGeneric performanceTime:NSHapticFeedbackPerformanceTimeDefault];
}
wasInsideSnapRange = YES;
} else {
wasInsideSnapRange = NO;
}
[self showToolTipForDuration:1.0];
return [super sendAction:theAction to:theTarget];
}
- (void)scrollWheel:(NSEvent *)theEvent {
double change = [theEvent deltaY];
[self setDoubleValue:[self doubleValue] + change];
[[self target] changePitch:self];
BOOL speedLock = [[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"];
if(speedLock) {
[_TempoSlider setDoubleValue:[self doubleValue]];
[[self target] changeTempo:self];
}
[self showToolTipForDuration:1.0];
}
@end