Reformat my own source code with clang-format
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
parent
361f61618a
commit
85c7073649
397 changed files with 2238946 additions and 210159 deletions
176
.clang-format
Normal file
176
.clang-format
Normal file
|
@ -0,0 +1,176 @@
|
|||
# The style used for all options not specifically set in the configuration.
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
# The extra indent or outdent of access modifiers, e.g. public:.
|
||||
AccessModifierOffset: 0
|
||||
|
||||
# If true, aligns escaped newlines as far left as possible. Otherwise puts them into the right-most column.
|
||||
AlignEscapedNewlinesLeft: false
|
||||
|
||||
# If true, aligns trailing comments.
|
||||
AlignTrailingComments: false
|
||||
|
||||
# Allow putting all parameters of a function declaration onto the next line even if BinPackParameters is false.
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
|
||||
# Allows contracting simple braced statements to a single line.
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
|
||||
# If true, short case labels will be contracted to a single line.
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
|
||||
# Dependent on the value, int f() { return 0; } can be put on a single line. Possible values: None, Inline, All.
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
|
||||
# If true, if (a) return; can be put on a single line.
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
|
||||
# If true, while (true) continue; can be put on a single line.
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
|
||||
# If true, always break after function definition return types. More truthfully called ‘break before the identifier following the type in a function definition’.
|
||||
AlwaysBreakAfterDefinitionReturnType: false
|
||||
|
||||
# If true, always break before multiline string literals.
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
|
||||
# If true, always break after the template<...> of a template declaration.
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
|
||||
# If false, a function call’s arguments will either be all on the same line or will have one line each.
|
||||
#BinPackArguments: true
|
||||
|
||||
# If false, a function declaration’s or function definition’s parameters will either all be on the same line or will have one line each.
|
||||
BinPackParameters: true
|
||||
|
||||
# The way to wrap binary operators. Possible values: None, NonAssignment, All.
|
||||
BreakBeforeBinaryOperators: None
|
||||
|
||||
# The brace breaking style to use. Possible values: Attach, Linux, Stroustrup, Allman, GNU.
|
||||
BreakBeforeBraces: Attach
|
||||
|
||||
# If true, ternary operators will be placed after line breaks.
|
||||
BreakBeforeTernaryOperators: false
|
||||
|
||||
# Always break constructor initializers before commas and align the commas with the colon.
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
|
||||
# The column limit. A column limit of 0 means that there is no column limit.
|
||||
ColumnLimit: 0
|
||||
|
||||
# A regular expression that describes comments with special meaning, which should not be split into lines or otherwise changed.
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
|
||||
# If the constructor initializers don’t fit on a line, put each initializer on its own line.
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
|
||||
# The number of characters to use for indentation of constructor initializer lists.
|
||||
ConstructorInitializerIndentWidth: 0
|
||||
|
||||
# Indent width for line continuations.
|
||||
ContinuationIndentWidth: 0
|
||||
|
||||
# If true, format braced lists as best suited for C++11 braced lists.
|
||||
Cpp11BracedListStyle: false
|
||||
|
||||
# If true, analyze the formatted file for the most common alignment of & and *. PointerAlignment is then used only as fallback.
|
||||
DerivePointerAlignment: true
|
||||
|
||||
# Disables formatting at all.
|
||||
DisableFormat: false
|
||||
|
||||
# If true, clang-format detects whether function calls and definitions are formatted with one parameter per line.
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
|
||||
# A vector of macros that should be interpreted as foreach loops instead of as function calls.
|
||||
#ForEachMacros: ''
|
||||
|
||||
# Indent case labels one level from the switch statement. When false, use the same indentation level as for the switch statement. Switch statement body is always indented one level more than case labels.
|
||||
IndentCaseLabels: true
|
||||
|
||||
# The number of columns to use for indentation.
|
||||
IndentWidth: 4
|
||||
|
||||
# Indent if a function definition or declaration is wrapped after the type.
|
||||
IndentWrappedFunctionNames: false
|
||||
|
||||
# If true, empty lines at the start of blocks are kept.
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
|
||||
# Language, this format style is targeted at. Possible values: None, Cpp, Java, JavaScript, Proto.
|
||||
# Language: None
|
||||
|
||||
# The maximum number of consecutive empty lines to keep.
|
||||
MaxEmptyLinesToKeep: 1
|
||||
|
||||
# The indentation used for namespaces. Possible values: None, Inner, All.
|
||||
NamespaceIndentation: All
|
||||
|
||||
# The number of characters to use for indentation of ObjC blocks.
|
||||
ObjCBlockIndentWidth: 4
|
||||
|
||||
# Add a space after @property in Objective-C, i.e. use \@property (readonly) instead of \@property(readonly).
|
||||
ObjCSpaceAfterProperty: false
|
||||
|
||||
# Add a space in front of an Objective-C protocol list, i.e. use Foo <Protocol> instead of Foo<Protocol>.
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
# The penalty for breaking a function call after “call(”.
|
||||
PenaltyBreakBeforeFirstCallParameter: 1000
|
||||
|
||||
# The penalty for each line break introduced inside a comment.
|
||||
PenaltyBreakComment: 1000
|
||||
|
||||
# The penalty for breaking before the first <<.
|
||||
PenaltyBreakFirstLessLess: 1000
|
||||
|
||||
# The penalty for each line break introduced inside a string literal.
|
||||
PenaltyBreakString: 1000
|
||||
|
||||
# The penalty for each character outside of the column limit.
|
||||
PenaltyExcessCharacter: 1000
|
||||
|
||||
# Penalty for putting the return type of a function onto its own line.
|
||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||
|
||||
# Pointer and reference alignment style. Possible values: Left, Right, Middle.
|
||||
PointerAlignment: Left
|
||||
|
||||
# If true, a space may be inserted after C style casts.
|
||||
SpaceAfterCStyleCast: false
|
||||
|
||||
# If false, spaces will be removed before assignment operators.
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
|
||||
# Defines in which cases to put a space before opening parentheses. Possible values: Never, ControlStatements, Always.
|
||||
SpaceBeforeParens: Never
|
||||
|
||||
# If true, spaces may be inserted into ‘()’.
|
||||
SpaceInEmptyParentheses: false
|
||||
|
||||
# The number of spaces before trailing line comments (// - comments).
|
||||
SpacesBeforeTrailingComments: 1
|
||||
|
||||
# If true, spaces will be inserted after ‘<’ and before ‘>’ in template argument lists.
|
||||
SpacesInAngles: false
|
||||
|
||||
# If true, spaces may be inserted into C style casts.
|
||||
SpacesInCStyleCastParentheses: false
|
||||
|
||||
# If true, spaces are inserted inside container literals (e.g. ObjC and Javascript array and dict literals).
|
||||
SpacesInContainerLiterals: false
|
||||
|
||||
# If true, spaces will be inserted after ‘(‘ and before ‘)’.
|
||||
SpacesInParentheses: false
|
||||
|
||||
# If true, spaces will be inserted after ‘[‘ and before ‘]’.
|
||||
SpacesInSquareBrackets: false
|
||||
|
||||
# Format compatible with this standard, e.g. use A<A<int> > instead of A<A<int>> for LS_Cpp03. Possible values: Cpp03, Cpp11, Auto.
|
||||
Standard: Auto
|
||||
|
||||
# The number of columns used for tab stops.
|
||||
TabWidth: 4
|
||||
|
||||
# The way to use tab characters in the resulting file. Possible values: Never, ForIndentation, Always.
|
||||
UseTab: ForIndentation
|
|
@ -9,59 +9,56 @@
|
|||
@class PlaylistLoader;
|
||||
@class SUUpdater;
|
||||
|
||||
@interface AppController : NSObject
|
||||
{
|
||||
IBOutlet NSObjectController *currentEntryController;
|
||||
|
||||
@interface AppController : NSObject {
|
||||
IBOutlet NSObjectController *currentEntryController;
|
||||
|
||||
IBOutlet PlaybackController *playbackController;
|
||||
|
||||
IBOutlet PlaylistController *playlistController;
|
||||
IBOutlet PlaylistController *playlistController;
|
||||
IBOutlet PlaylistLoader *playlistLoader;
|
||||
|
||||
|
||||
IBOutlet NSWindow *mainWindow;
|
||||
IBOutlet NSWindow *miniWindow;
|
||||
IBOutlet NSSplitView *mainView;
|
||||
|
||||
IBOutlet NSWindow *miniWindow;
|
||||
IBOutlet NSSplitView *mainView;
|
||||
|
||||
IBOutlet NSSegmentedControl *playbackButtons;
|
||||
IBOutlet NSButton *fileButton;
|
||||
IBOutlet NSButton *shuffleButton;
|
||||
IBOutlet NSButton *repeatButton;
|
||||
IBOutlet NSButton *randomizeButton;
|
||||
IBOutlet NSButton *randomizeButton;
|
||||
|
||||
IBOutlet NSTextField *totalTimeField;
|
||||
|
||||
IBOutlet PlaylistView *playlistView;
|
||||
|
||||
|
||||
IBOutlet NSMenuItem *showIndexColumn;
|
||||
IBOutlet NSMenuItem *showTitleColumn;
|
||||
IBOutlet NSMenuItem *showAlbumArtistColumn;
|
||||
IBOutlet NSMenuItem *showAlbumArtistColumn;
|
||||
IBOutlet NSMenuItem *showArtistColumn;
|
||||
IBOutlet NSMenuItem *showAlbumColumn;
|
||||
IBOutlet NSMenuItem *showGenreColumn;
|
||||
IBOutlet NSMenuItem *showLengthColumn;
|
||||
IBOutlet NSMenuItem *showTrackColumn;
|
||||
IBOutlet NSMenuItem *showYearColumn;
|
||||
|
||||
IBOutlet NSMenu *dockMenu;
|
||||
IBOutlet NSMenuItem *currentArtistItem;
|
||||
|
||||
IBOutlet NSWindowController *spotlightWindowController;
|
||||
|
||||
IBOutlet FileTreeViewController *fileTreeViewController;
|
||||
|
||||
IBOutlet SUUpdater *updater;
|
||||
|
||||
NSOperationQueue *queue; // Since we are the app delegate, we take care of the op queue
|
||||
|
||||
NSMutableSet* expandedNodes;
|
||||
|
||||
BOOL miniMode;
|
||||
|
||||
|
||||
IBOutlet NSMenu *dockMenu;
|
||||
IBOutlet NSMenuItem *currentArtistItem;
|
||||
|
||||
IBOutlet NSWindowController *spotlightWindowController;
|
||||
|
||||
IBOutlet FileTreeViewController *fileTreeViewController;
|
||||
|
||||
IBOutlet SUUpdater *updater;
|
||||
|
||||
NSOperationQueue *queue; // Since we are the app delegate, we take care of the op queue
|
||||
|
||||
NSMutableSet *expandedNodes;
|
||||
|
||||
BOOL miniMode;
|
||||
}
|
||||
|
||||
@property (strong) IBOutlet NSButton *infoButton;
|
||||
@property (strong) IBOutlet NSButton *infoButtonMini;
|
||||
@property(strong) IBOutlet NSButton *infoButton;
|
||||
@property(strong) IBOutlet NSButton *infoButtonMini;
|
||||
|
||||
- (IBAction)openURL:(id)sender;
|
||||
|
||||
|
@ -78,7 +75,7 @@
|
|||
|
||||
- (void)initDefaults;
|
||||
|
||||
//Fun stuff
|
||||
// Fun stuff
|
||||
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag;
|
||||
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
|
||||
- (void)application:(NSApplication *)theApplication openFiles:(NSArray *)filenames;
|
||||
|
@ -91,23 +88,23 @@
|
|||
- (void)clickPrev;
|
||||
- (void)clickNext;
|
||||
- (void)clickSpam;
|
||||
- (void)clickSeek: (NSTimeInterval)position;
|
||||
- (void)clickSeek:(NSTimeInterval)position;
|
||||
|
||||
- (IBAction)increaseFontSize:(id)sender;
|
||||
- (IBAction)decreaseFontSize:(id)sender;
|
||||
- (void)changeFontSize:(float)size;
|
||||
|
||||
- (void)nodeExpanded:(NSNotification*)notification;
|
||||
- (void)nodeCollapsed:(NSNotification*)notification;
|
||||
- (void)nodeExpanded:(NSNotification *)notification;
|
||||
- (void)nodeCollapsed:(NSNotification *)notification;
|
||||
|
||||
- (IBAction)toggleMiniMode:(id)sender;
|
||||
- (IBAction)toggleToolbarStyle:(id)sender;
|
||||
|
||||
@property NSWindow * mainWindow;
|
||||
@property NSWindow * miniWindow;
|
||||
@property NSWindow *mainWindow;
|
||||
@property NSWindow *miniWindow;
|
||||
|
||||
@property BOOL miniMode;
|
||||
|
||||
@property (nonatomic) BOOL floatingMiniWindow;
|
||||
@property(nonatomic) BOOL floatingMiniWindow;
|
||||
|
||||
@end
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,15 +12,15 @@
|
|||
|
||||
@interface DockIconController : NSObject {
|
||||
NSImage *dockImage;
|
||||
|
||||
|
||||
IBOutlet PlaybackController *playbackController;
|
||||
|
||||
NSInteger lastPlaybackStatus;
|
||||
NSInteger lastColorfulStatus;
|
||||
NSNumber *lastProgressStatus;
|
||||
|
||||
NSImageView *imageView;
|
||||
NSProgressIndicator *progressIndicator;
|
||||
|
||||
NSInteger lastPlaybackStatus;
|
||||
NSInteger lastColorfulStatus;
|
||||
NSNumber *lastProgressStatus;
|
||||
|
||||
NSImageView *imageView;
|
||||
NSProgressIndicator *progressIndicator;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -7,197 +7,172 @@
|
|||
//
|
||||
|
||||
#import "DockIconController.h"
|
||||
#import <CogAudio/Status.h>
|
||||
#import "PlaybackController.h"
|
||||
#import <CogAudio/Status.h>
|
||||
|
||||
@implementation DockIconController
|
||||
|
||||
static NSString *DockIconPlaybackStatusObservationContext = @"DockIconPlaybackStatusObservationContext";
|
||||
|
||||
- (void)startObserving
|
||||
{
|
||||
[playbackController addObserver:self forKeyPath:@"playbackStatus" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:(__bridge void * _Nullable)(DockIconPlaybackStatusObservationContext)];
|
||||
[playbackController addObserver:self forKeyPath:@"progressBarStatus" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:(__bridge void * _Nullable)(DockIconPlaybackStatusObservationContext)];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.colorfulDockIcons" options:0 context:(__bridge void * _Nullable)(DockIconPlaybackStatusObservationContext)];
|
||||
- (void)startObserving {
|
||||
[playbackController addObserver:self forKeyPath:@"playbackStatus" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
|
||||
[playbackController addObserver:self forKeyPath:@"progressBarStatus" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.colorfulDockIcons" options:0 context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
|
||||
}
|
||||
|
||||
- (void)stopObserving
|
||||
{
|
||||
- (void)stopObserving {
|
||||
[playbackController removeObserver:self forKeyPath:@"playbackStatus"];
|
||||
[playbackController removeObserver:self forKeyPath:@"progressBarStatus"];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.colorfulDockIcons"];
|
||||
[playbackController removeObserver:self forKeyPath:@"progressBarStatus"];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.colorfulDockIcons"];
|
||||
}
|
||||
|
||||
static NSString *getBadgeName(NSString *baseName, BOOL colorfulIcons)
|
||||
{
|
||||
if (colorfulIcons)
|
||||
{
|
||||
return [baseName stringByAppendingString:@"Colorful"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return baseName;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshDockIcon:(NSInteger)playbackStatus withProgress:(double)progressStatus
|
||||
{
|
||||
BOOL displayChanged = NO;
|
||||
BOOL drawIcon = NO;
|
||||
BOOL removeProgress = NO;
|
||||
|
||||
if ( playbackStatus < 0 )
|
||||
playbackStatus = lastPlaybackStatus;
|
||||
else
|
||||
{
|
||||
lastPlaybackStatus = playbackStatus;
|
||||
drawIcon = YES;
|
||||
}
|
||||
|
||||
if ( progressStatus < -2 )
|
||||
progressStatus = [lastProgressStatus doubleValue];
|
||||
else
|
||||
{
|
||||
if (progressStatus < 0 && [lastProgressStatus doubleValue] >= 0)
|
||||
removeProgress = YES;
|
||||
lastProgressStatus = [NSNumber numberWithDouble:progressStatus];
|
||||
}
|
||||
|
||||
BOOL displayProgress = (progressStatus >= 0.0);
|
||||
|
||||
NSImage *badgeImage = nil;
|
||||
|
||||
BOOL colorfulIcons = [[NSUserDefaults standardUserDefaults] boolForKey:@"colorfulDockIcons"];
|
||||
|
||||
if ((colorfulIcons && lastColorfulStatus < 1) ||
|
||||
(!colorfulIcons && lastColorfulStatus != 0))
|
||||
{
|
||||
lastColorfulStatus = colorfulIcons ? 1 : 0;
|
||||
drawIcon = YES;
|
||||
}
|
||||
|
||||
NSDockTile *dockTile = [NSApp dockTile];
|
||||
|
||||
if (drawIcon)
|
||||
{
|
||||
switch (playbackStatus) {
|
||||
case CogStatusPlaying:
|
||||
badgeImage = [NSImage imageNamed:getBadgeName(@"playDockBadge", colorfulIcons)];
|
||||
break;
|
||||
case CogStatusPaused:
|
||||
badgeImage = [NSImage imageNamed:getBadgeName(@"pauseDockBadge", colorfulIcons)];
|
||||
break;
|
||||
|
||||
default:
|
||||
badgeImage = [NSImage imageNamed:getBadgeName(@"stopDockBadge", colorfulIcons)];
|
||||
break;
|
||||
}
|
||||
|
||||
NSSize badgeSize = [badgeImage size];
|
||||
|
||||
NSImage *newDockImage = [dockImage copy];
|
||||
[newDockImage lockFocus];
|
||||
|
||||
[badgeImage drawInRect:NSMakeRect(0, 0, 128, 128)
|
||||
fromRect:NSMakeRect(0, 0, badgeSize.width, badgeSize.height)
|
||||
operation:NSCompositingOperationSourceOver fraction:1.0];
|
||||
|
||||
[newDockImage unlockFocus];
|
||||
|
||||
imageView = [[NSImageView alloc] init];
|
||||
[imageView setImage:newDockImage];
|
||||
[dockTile setContentView:imageView];
|
||||
|
||||
progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0.0, 0.0, dockTile.size.width, 10.0)];
|
||||
[progressIndicator setStyle:NSProgressIndicatorBarStyle];
|
||||
[progressIndicator setIndeterminate:NO];
|
||||
[progressIndicator setBezeled:YES];
|
||||
[progressIndicator setMinValue:0];
|
||||
[progressIndicator setMaxValue:100];
|
||||
[progressIndicator setHidden:YES];
|
||||
|
||||
[imageView addSubview:progressIndicator];
|
||||
|
||||
displayChanged = YES;
|
||||
}
|
||||
|
||||
if (displayProgress)
|
||||
{
|
||||
if (!imageView)
|
||||
{
|
||||
imageView = [[NSImageView alloc] init];
|
||||
[imageView setImage:[NSApp applicationIconImage]];
|
||||
[dockTile setContentView:imageView];
|
||||
}
|
||||
|
||||
if (!progressIndicator)
|
||||
{
|
||||
progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0.0, 0.0, dockTile.size.width, 10.0)];
|
||||
[progressIndicator setIndeterminate:NO];
|
||||
[progressIndicator setBezeled:YES];
|
||||
[progressIndicator setMinValue:0];
|
||||
[progressIndicator setMaxValue:100];
|
||||
|
||||
[imageView addSubview:progressIndicator];
|
||||
}
|
||||
|
||||
[progressIndicator setDoubleValue:progressStatus];
|
||||
[progressIndicator setHidden:NO];
|
||||
|
||||
displayChanged = YES;
|
||||
}
|
||||
|
||||
if (removeProgress)
|
||||
{
|
||||
if (progressIndicator)
|
||||
[progressIndicator setHidden:YES];
|
||||
|
||||
displayChanged = YES;
|
||||
}
|
||||
|
||||
if (displayChanged)
|
||||
[dockTile display];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
if ([DockIconPlaybackStatusObservationContext isEqual:(__bridge id)(context)])
|
||||
{
|
||||
if ([keyPath isEqualToString:@"playbackStatus"])
|
||||
{
|
||||
NSInteger playbackStatus = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
|
||||
|
||||
[self refreshDockIcon:playbackStatus withProgress:-10];
|
||||
}
|
||||
else if ([keyPath isEqualToString:@"progressBarStatus"])
|
||||
{
|
||||
double progressStatus = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue];
|
||||
|
||||
[self refreshDockIcon:-1 withProgress:progressStatus];
|
||||
}
|
||||
else if ([keyPath isEqualToString:@"values.colorfulDockIcons"])
|
||||
{
|
||||
[self refreshDockIcon:-1 withProgress:-10];
|
||||
}
|
||||
static NSString *getBadgeName(NSString *baseName, BOOL colorfulIcons) {
|
||||
if(colorfulIcons) {
|
||||
return [baseName stringByAppendingString:@"Colorful"];
|
||||
} else {
|
||||
return baseName;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
- (void)refreshDockIcon:(NSInteger)playbackStatus withProgress:(double)progressStatus {
|
||||
BOOL displayChanged = NO;
|
||||
BOOL drawIcon = NO;
|
||||
BOOL removeProgress = NO;
|
||||
|
||||
if(playbackStatus < 0)
|
||||
playbackStatus = lastPlaybackStatus;
|
||||
else {
|
||||
lastPlaybackStatus = playbackStatus;
|
||||
drawIcon = YES;
|
||||
}
|
||||
|
||||
if(progressStatus < -2)
|
||||
progressStatus = [lastProgressStatus doubleValue];
|
||||
else {
|
||||
if(progressStatus < 0 && [lastProgressStatus doubleValue] >= 0)
|
||||
removeProgress = YES;
|
||||
lastProgressStatus = [NSNumber numberWithDouble:progressStatus];
|
||||
}
|
||||
|
||||
BOOL displayProgress = (progressStatus >= 0.0);
|
||||
|
||||
NSImage *badgeImage = nil;
|
||||
|
||||
BOOL colorfulIcons = [[NSUserDefaults standardUserDefaults] boolForKey:@"colorfulDockIcons"];
|
||||
|
||||
if((colorfulIcons && lastColorfulStatus < 1) ||
|
||||
(!colorfulIcons && lastColorfulStatus != 0)) {
|
||||
lastColorfulStatus = colorfulIcons ? 1 : 0;
|
||||
drawIcon = YES;
|
||||
}
|
||||
|
||||
NSDockTile *dockTile = [NSApp dockTile];
|
||||
|
||||
if(drawIcon) {
|
||||
switch(playbackStatus) {
|
||||
case CogStatusPlaying:
|
||||
badgeImage = [NSImage imageNamed:getBadgeName(@"playDockBadge", colorfulIcons)];
|
||||
break;
|
||||
case CogStatusPaused:
|
||||
badgeImage = [NSImage imageNamed:getBadgeName(@"pauseDockBadge", colorfulIcons)];
|
||||
break;
|
||||
|
||||
default:
|
||||
badgeImage = [NSImage imageNamed:getBadgeName(@"stopDockBadge", colorfulIcons)];
|
||||
break;
|
||||
}
|
||||
|
||||
NSSize badgeSize = [badgeImage size];
|
||||
|
||||
NSImage *newDockImage = [dockImage copy];
|
||||
[newDockImage lockFocus];
|
||||
|
||||
[badgeImage drawInRect:NSMakeRect(0, 0, 128, 128)
|
||||
fromRect:NSMakeRect(0, 0, badgeSize.width, badgeSize.height)
|
||||
operation:NSCompositingOperationSourceOver
|
||||
fraction:1.0];
|
||||
|
||||
[newDockImage unlockFocus];
|
||||
|
||||
imageView = [[NSImageView alloc] init];
|
||||
[imageView setImage:newDockImage];
|
||||
[dockTile setContentView:imageView];
|
||||
|
||||
progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0.0, 0.0, dockTile.size.width, 10.0)];
|
||||
[progressIndicator setStyle:NSProgressIndicatorBarStyle];
|
||||
[progressIndicator setIndeterminate:NO];
|
||||
[progressIndicator setBezeled:YES];
|
||||
[progressIndicator setMinValue:0];
|
||||
[progressIndicator setMaxValue:100];
|
||||
[progressIndicator setHidden:YES];
|
||||
|
||||
[imageView addSubview:progressIndicator];
|
||||
|
||||
displayChanged = YES;
|
||||
}
|
||||
|
||||
if(displayProgress) {
|
||||
if(!imageView) {
|
||||
imageView = [[NSImageView alloc] init];
|
||||
[imageView setImage:[NSApp applicationIconImage]];
|
||||
[dockTile setContentView:imageView];
|
||||
}
|
||||
|
||||
if(!progressIndicator) {
|
||||
progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0.0, 0.0, dockTile.size.width, 10.0)];
|
||||
[progressIndicator setIndeterminate:NO];
|
||||
[progressIndicator setBezeled:YES];
|
||||
[progressIndicator setMinValue:0];
|
||||
[progressIndicator setMaxValue:100];
|
||||
|
||||
[imageView addSubview:progressIndicator];
|
||||
}
|
||||
|
||||
[progressIndicator setDoubleValue:progressStatus];
|
||||
[progressIndicator setHidden:NO];
|
||||
|
||||
displayChanged = YES;
|
||||
}
|
||||
|
||||
if(removeProgress) {
|
||||
if(progressIndicator)
|
||||
[progressIndicator setHidden:YES];
|
||||
|
||||
displayChanged = YES;
|
||||
}
|
||||
|
||||
if(displayChanged)
|
||||
[dockTile display];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
if([DockIconPlaybackStatusObservationContext isEqual:(__bridge id)(context)]) {
|
||||
if([keyPath isEqualToString:@"playbackStatus"]) {
|
||||
NSInteger playbackStatus = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
|
||||
|
||||
[self refreshDockIcon:playbackStatus withProgress:-10];
|
||||
} else if([keyPath isEqualToString:@"progressBarStatus"]) {
|
||||
double progressStatus = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue];
|
||||
|
||||
[self refreshDockIcon:-1 withProgress:progressStatus];
|
||||
} else if([keyPath isEqualToString:@"values.colorfulDockIcons"]) {
|
||||
[self refreshDockIcon:-1 withProgress:-10];
|
||||
}
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
- (void)awakeFromNib {
|
||||
dockImage = [[NSImage imageNamed:@"icon_blank"] copy];
|
||||
lastColorfulStatus = -1;
|
||||
lastProgressStatus = [NSNumber numberWithDouble:-1];
|
||||
imageView = nil;
|
||||
progressIndicator = nil;
|
||||
lastColorfulStatus = -1;
|
||||
lastProgressStatus = [NSNumber numberWithDouble:-1];
|
||||
imageView = nil;
|
||||
progressIndicator = nil;
|
||||
[self startObserving];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
[self stopObserving];
|
||||
}
|
||||
|
||||
|
|
|
@ -11,4 +11,3 @@
|
|||
@interface MediaKeysApplication : NSApplication
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -10,68 +10,67 @@
|
|||
#import "AppController.h"
|
||||
#import "Logging.h"
|
||||
|
||||
#import <MediaPlayer/MPNowPlayingInfoCenter.h>
|
||||
#import <MediaPlayer/MPRemoteCommandCenter.h>
|
||||
#import <MediaPlayer/MPRemoteCommand.h>
|
||||
#import <MediaPlayer/MPMediaItem.h>
|
||||
#import <MediaPlayer/MPNowPlayingInfoCenter.h>
|
||||
#import <MediaPlayer/MPRemoteCommand.h>
|
||||
#import <MediaPlayer/MPRemoteCommandCenter.h>
|
||||
#import <MediaPlayer/MPRemoteCommandEvent.h>
|
||||
|
||||
@implementation MediaKeysApplication {
|
||||
AppController *_appController;
|
||||
AppController *_appController;
|
||||
}
|
||||
|
||||
- (void)finishLaunching {
|
||||
[super finishLaunching];
|
||||
_appController = (AppController *)[self delegate];
|
||||
[super finishLaunching];
|
||||
_appController = (AppController *)[self delegate];
|
||||
|
||||
MPRemoteCommandCenter *remoteCommandCenter = [MPRemoteCommandCenter sharedCommandCenter];
|
||||
MPRemoteCommandCenter *remoteCommandCenter = [MPRemoteCommandCenter sharedCommandCenter];
|
||||
|
||||
[remoteCommandCenter.playCommand setEnabled:YES];
|
||||
[remoteCommandCenter.pauseCommand setEnabled:YES];
|
||||
[remoteCommandCenter.togglePlayPauseCommand setEnabled:YES];
|
||||
[remoteCommandCenter.stopCommand setEnabled:YES];
|
||||
[remoteCommandCenter.changePlaybackPositionCommand setEnabled:YES];
|
||||
[remoteCommandCenter.nextTrackCommand setEnabled:YES];
|
||||
[remoteCommandCenter.previousTrackCommand setEnabled:YES];
|
||||
[remoteCommandCenter.playCommand setEnabled:YES];
|
||||
[remoteCommandCenter.pauseCommand setEnabled:YES];
|
||||
[remoteCommandCenter.togglePlayPauseCommand setEnabled:YES];
|
||||
[remoteCommandCenter.stopCommand setEnabled:YES];
|
||||
[remoteCommandCenter.changePlaybackPositionCommand setEnabled:YES];
|
||||
[remoteCommandCenter.nextTrackCommand setEnabled:YES];
|
||||
[remoteCommandCenter.previousTrackCommand setEnabled:YES];
|
||||
|
||||
[[remoteCommandCenter playCommand] addTarget:self action:@selector(clickPlay)];
|
||||
[[remoteCommandCenter pauseCommand] addTarget:self action:@selector(clickPause)];
|
||||
[[remoteCommandCenter togglePlayPauseCommand] addTarget:self action:@selector(clickPlay)];
|
||||
[[remoteCommandCenter stopCommand] addTarget:self action:@selector(clickStop)];
|
||||
[[remoteCommandCenter changePlaybackPositionCommand] addTarget:self action:@selector(clickSeek:)];
|
||||
[[remoteCommandCenter nextTrackCommand] addTarget:self action:@selector(clickNext)];
|
||||
[[remoteCommandCenter previousTrackCommand] addTarget:self action:@selector(clickPrev)];
|
||||
[[remoteCommandCenter playCommand] addTarget:self action:@selector(clickPlay)];
|
||||
[[remoteCommandCenter pauseCommand] addTarget:self action:@selector(clickPause)];
|
||||
[[remoteCommandCenter togglePlayPauseCommand] addTarget:self action:@selector(clickPlay)];
|
||||
[[remoteCommandCenter stopCommand] addTarget:self action:@selector(clickStop)];
|
||||
[[remoteCommandCenter changePlaybackPositionCommand] addTarget:self action:@selector(clickSeek:)];
|
||||
[[remoteCommandCenter nextTrackCommand] addTarget:self action:@selector(clickNext)];
|
||||
[[remoteCommandCenter previousTrackCommand] addTarget:self action:@selector(clickPrev)];
|
||||
}
|
||||
|
||||
- (MPRemoteCommandHandlerStatus)clickPlay {
|
||||
[_appController clickPlay];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
[_appController clickPlay];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}
|
||||
|
||||
- (MPRemoteCommandHandlerStatus)clickPause {
|
||||
[_appController clickPause];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
[_appController clickPause];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}
|
||||
|
||||
- (MPRemoteCommandHandlerStatus)clickStop {
|
||||
[_appController clickStop];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
[_appController clickStop];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}
|
||||
|
||||
- (MPRemoteCommandHandlerStatus)clickNext {
|
||||
[_appController clickNext];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
[_appController clickNext];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}
|
||||
|
||||
- (MPRemoteCommandHandlerStatus)clickPrev {
|
||||
[_appController clickPrev];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
[_appController clickPrev];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}
|
||||
|
||||
- (MPRemoteCommandHandlerStatus)clickSeek: (MPChangePlaybackPositionCommandEvent*)event {
|
||||
[_appController clickSeek:event.positionTime];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
- (MPRemoteCommandHandlerStatus)clickSeek:(MPChangePlaybackPositionCommandEvent *)event {
|
||||
[_appController clickSeek:event.positionTime];
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "AppController.h"
|
||||
#import "AudioScrobbler.h"
|
||||
#import "CogAudio/AudioPlayer.h"
|
||||
#import "CogAudio/Status.h"
|
||||
#import "TrackingSlider.h"
|
||||
#import "AudioScrobbler.h"
|
||||
#import "AppController.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreAudio/CoreAudioTypes.h>
|
||||
|
||||
#import "AUPlayerView.h"
|
||||
|
@ -23,42 +23,41 @@ extern NSString *CogPlaybackDidPauseNotficiation;
|
|||
extern NSString *CogPlaybackDidResumeNotficiation;
|
||||
extern NSString *CogPlaybackDidStopNotficiation;
|
||||
|
||||
extern NSDictionary * makeRGInfo(PlaylistEntry *pe);
|
||||
extern NSDictionary *makeRGInfo(PlaylistEntry *pe);
|
||||
|
||||
@class PlaylistController;
|
||||
@class PlaylistView;
|
||||
@class PlaylistLoader;
|
||||
|
||||
@interface PlaybackController : NSObject
|
||||
{
|
||||
IBOutlet AppController *appController;
|
||||
|
||||
IBOutlet PlaylistController *playlistController;
|
||||
@interface PlaybackController : NSObject {
|
||||
IBOutlet AppController *appController;
|
||||
|
||||
IBOutlet PlaylistController *playlistController;
|
||||
IBOutlet PlaylistView *playlistView;
|
||||
IBOutlet PlaylistLoader *playlistLoader;
|
||||
|
||||
|
||||
IBOutlet NSSlider *volumeSlider;
|
||||
|
||||
|
||||
IBOutlet NSArrayController *outputDevices;
|
||||
|
||||
|
||||
NSTimer *positionTimer;
|
||||
|
||||
|
||||
AudioPlayer *audioPlayer;
|
||||
|
||||
|
||||
CogStatus playbackStatus;
|
||||
double position;
|
||||
double lastPosition;
|
||||
double lastPosition;
|
||||
BOOL seekable;
|
||||
BOOL fading;
|
||||
|
||||
// progress bar display
|
||||
double progressBarStatus;
|
||||
|
||||
BOOL _eqWasOpen;
|
||||
BOOL _eqStubbed;
|
||||
AudioUnit _eq;
|
||||
AUPluginUI *_equi;
|
||||
}
|
||||
|
||||
// progress bar display
|
||||
double progressBarStatus;
|
||||
|
||||
BOOL _eqWasOpen;
|
||||
BOOL _eqStubbed;
|
||||
AudioUnit _eq;
|
||||
AUPluginUI *_equi;
|
||||
}
|
||||
|
||||
@property CogStatus playbackStatus;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,11 +15,11 @@
|
|||
@class AudioScrobbler;
|
||||
|
||||
@interface PlaybackEventController
|
||||
: NSObject <NSUserNotificationCenterDelegate, UNUserNotificationCenterDelegate> {
|
||||
IBOutlet PlaybackController *playbackController;
|
||||
: NSObject <NSUserNotificationCenterDelegate, UNUserNotificationCenterDelegate> {
|
||||
IBOutlet PlaybackController *playbackController;
|
||||
|
||||
IBOutlet NSWindow *mainWindow;
|
||||
IBOutlet NSWindow *miniWindow;
|
||||
IBOutlet NSWindow *mainWindow;
|
||||
IBOutlet NSWindow *miniWindow;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -20,372 +20,372 @@ NSString *TrackLength = @"Total Time";
|
|||
NSString *TrackPath = @"Location";
|
||||
NSString *TrackState = @"Player State";
|
||||
|
||||
typedef NS_ENUM(NSInteger, TrackStatus) { TrackPlaying, TrackPaused, TrackStopped };
|
||||
typedef NS_ENUM(NSInteger, TrackStatus) { TrackPlaying,
|
||||
TrackPaused,
|
||||
TrackStopped };
|
||||
|
||||
@implementation PlaybackEventController {
|
||||
AudioScrobbler *scrobbler;
|
||||
AudioScrobbler *scrobbler;
|
||||
|
||||
NSOperationQueue *queue;
|
||||
NSOperationQueue *queue;
|
||||
|
||||
PlaylistEntry *entry;
|
||||
PlaylistEntry *entry;
|
||||
|
||||
Boolean didGainUN API_AVAILABLE(macosx(10.14));
|
||||
Boolean didGainUN API_AVAILABLE(macosx(10.14));
|
||||
}
|
||||
|
||||
- (void)initDefaults {
|
||||
NSDictionary *defaultsDictionary = @{
|
||||
@"enableAudioScrobbler" : @YES,
|
||||
@"automaticallyLaunchLastFM" : @NO,
|
||||
@"notifications.enable" : @YES,
|
||||
@"notifications.itunes-style" : @YES,
|
||||
@"notifications.show-album-art" : @YES
|
||||
};
|
||||
NSDictionary *defaultsDictionary = @{
|
||||
@"enableAudioScrobbler": @YES,
|
||||
@"automaticallyLaunchLastFM": @NO,
|
||||
@"notifications.enable": @YES,
|
||||
@"notifications.itunes-style": @YES,
|
||||
@"notifications.show-album-art": @YES
|
||||
};
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary];
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary];
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initDefaults];
|
||||
self = [super init];
|
||||
if(self) {
|
||||
[self initDefaults];
|
||||
|
||||
didGainUN = NO;
|
||||
didGainUN = NO;
|
||||
|
||||
if (@available(macOS 10.14, *)) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center
|
||||
requestAuthorizationWithOptions:UNAuthorizationOptionAlert
|
||||
completionHandler:^(BOOL granted, NSError *_Nullable error) {
|
||||
self->didGainUN = granted;
|
||||
if(@available(macOS 10.14, *)) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center
|
||||
requestAuthorizationWithOptions:UNAuthorizationOptionAlert
|
||||
completionHandler:^(BOOL granted, NSError *_Nullable error) {
|
||||
self->didGainUN = granted;
|
||||
|
||||
if (granted) {
|
||||
UNNotificationAction *skipAction = [UNNotificationAction
|
||||
actionWithIdentifier:@"skip"
|
||||
title:@"Skip"
|
||||
options:UNNotificationActionOptionNone];
|
||||
if(granted) {
|
||||
UNNotificationAction *skipAction = [UNNotificationAction
|
||||
actionWithIdentifier:@"skip"
|
||||
title:@"Skip"
|
||||
options:UNNotificationActionOptionNone];
|
||||
|
||||
UNNotificationCategory *playCategory = [UNNotificationCategory
|
||||
categoryWithIdentifier:@"play"
|
||||
actions:@[ skipAction ]
|
||||
intentIdentifiers:@[]
|
||||
options:UNNotificationCategoryOptionNone];
|
||||
UNNotificationCategory *playCategory = [UNNotificationCategory
|
||||
categoryWithIdentifier:@"play"
|
||||
actions:@[skipAction]
|
||||
intentIdentifiers:@[]
|
||||
options:UNNotificationCategoryOptionNone];
|
||||
|
||||
[center setNotificationCategories:
|
||||
[NSSet setWithObject:playCategory]];
|
||||
}
|
||||
}];
|
||||
[center setNotificationCategories:
|
||||
[NSSet setWithObject:playCategory]];
|
||||
}
|
||||
}];
|
||||
|
||||
[center setDelegate:self];
|
||||
}
|
||||
[center setDelegate:self];
|
||||
}
|
||||
|
||||
queue = [[NSOperationQueue alloc] init];
|
||||
[queue setMaxConcurrentOperationCount:1];
|
||||
queue = [[NSOperationQueue alloc] init];
|
||||
[queue setMaxConcurrentOperationCount:1];
|
||||
|
||||
scrobbler = [[AudioScrobbler alloc] init];
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
|
||||
scrobbler = [[AudioScrobbler alloc] init];
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
|
||||
|
||||
entry = nil;
|
||||
}
|
||||
entry = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
||||
willPresentNotification:(UNNotification *)notification
|
||||
withCompletionHandler:
|
||||
(void (^)(UNNotificationPresentationOptions options))completionHandler
|
||||
API_AVAILABLE(macos(10.14)) {
|
||||
UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionAlert;
|
||||
(void (^)(UNNotificationPresentationOptions options))completionHandler
|
||||
API_AVAILABLE(macos(10.14)) {
|
||||
UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionAlert;
|
||||
|
||||
completionHandler(presentationOptions);
|
||||
completionHandler(presentationOptions);
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
||||
didReceiveNotificationResponse:(UNNotificationResponse *)response
|
||||
withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(macos(10.14)) {
|
||||
if ([[response actionIdentifier] isEqualToString:@"skip"]) {
|
||||
[playbackController next:self];
|
||||
}
|
||||
didReceiveNotificationResponse:(UNNotificationResponse *)response
|
||||
withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(macos(10.14)) {
|
||||
if([[response actionIdentifier] isEqualToString:@"skip"]) {
|
||||
[playbackController next:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)fillNotificationDictionary:(PlaylistEntry *)pe status:(TrackStatus)status {
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
if (pe == nil) return dict;
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
if(pe == nil) return dict;
|
||||
|
||||
[dict setObject:[[pe URL] absoluteString] forKey:TrackPath];
|
||||
if ([pe title]) [dict setObject:[pe title] forKey:TrackTitle];
|
||||
if ([pe artist]) [dict setObject:[pe artist] forKey:TrackArtist];
|
||||
if ([pe album]) [dict setObject:[pe album] forKey:TrackAlbum];
|
||||
if ([pe genre]) [dict setObject:[pe genre] forKey:TrackGenre];
|
||||
if ([pe track])
|
||||
[dict setObject:[pe trackText] forKey:TrackNumber];
|
||||
if ([pe length])
|
||||
[dict setObject:[NSNumber numberWithInteger:(NSInteger)([[pe length] doubleValue] * 1000.0)]
|
||||
forKey:TrackLength];
|
||||
[dict setObject:[[pe URL] absoluteString] forKey:TrackPath];
|
||||
if([pe title]) [dict setObject:[pe title] forKey:TrackTitle];
|
||||
if([pe artist]) [dict setObject:[pe artist] forKey:TrackArtist];
|
||||
if([pe album]) [dict setObject:[pe album] forKey:TrackAlbum];
|
||||
if([pe genre]) [dict setObject:[pe genre] forKey:TrackGenre];
|
||||
if([pe track])
|
||||
[dict setObject:[pe trackText] forKey:TrackNumber];
|
||||
if([pe length])
|
||||
[dict setObject:[NSNumber numberWithInteger:(NSInteger)([[pe length] doubleValue] * 1000.0)]
|
||||
forKey:TrackLength];
|
||||
|
||||
NSString *state = nil;
|
||||
NSString *state = nil;
|
||||
|
||||
switch (status) {
|
||||
case TrackPlaying:
|
||||
state = @"Playing";
|
||||
break;
|
||||
case TrackPaused:
|
||||
state = @"Paused";
|
||||
break;
|
||||
case TrackStopped:
|
||||
state = @"Stopped";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch(status) {
|
||||
case TrackPlaying:
|
||||
state = @"Playing";
|
||||
break;
|
||||
case TrackPaused:
|
||||
state = @"Paused";
|
||||
break;
|
||||
case TrackStopped:
|
||||
state = @"Stopped";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
[dict setObject:state forKey:TrackState];
|
||||
[dict setObject:state forKey:TrackState];
|
||||
|
||||
return dict;
|
||||
return dict;
|
||||
}
|
||||
|
||||
- (void)performPlaybackDidBeginActions:(PlaylistEntry *)pe {
|
||||
if (NO == [pe error]) {
|
||||
entry = pe;
|
||||
if(NO == [pe error]) {
|
||||
entry = pe;
|
||||
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:TrackNotification
|
||||
object:nil
|
||||
userInfo:[self fillNotificationDictionary:pe status:TrackPlaying]
|
||||
deliverImmediately:YES];
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:TrackNotification
|
||||
object:nil
|
||||
userInfo:[self fillNotificationDictionary:pe status:TrackPlaying]
|
||||
deliverImmediately:YES];
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
if ([defaults boolForKey:@"notifications.enable"]) {
|
||||
if ([defaults boolForKey:@"enableAudioScrobbler"]) {
|
||||
[scrobbler start:pe];
|
||||
if ([AudioScrobbler isRunning]) return;
|
||||
}
|
||||
if([defaults boolForKey:@"notifications.enable"]) {
|
||||
if([defaults boolForKey:@"enableAudioScrobbler"]) {
|
||||
[scrobbler start:pe];
|
||||
if([AudioScrobbler isRunning]) return;
|
||||
}
|
||||
|
||||
if (@available(macOS 10.14, *)) {
|
||||
if (didGainUN) {
|
||||
UNUserNotificationCenter *center =
|
||||
[UNUserNotificationCenter currentNotificationCenter];
|
||||
if(@available(macOS 10.14, *)) {
|
||||
if(didGainUN) {
|
||||
UNUserNotificationCenter *center =
|
||||
[UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
UNMutableNotificationContent *content =
|
||||
[[UNMutableNotificationContent alloc] init];
|
||||
UNMutableNotificationContent *content =
|
||||
[[UNMutableNotificationContent alloc] init];
|
||||
|
||||
content.title = @"Now Playing";
|
||||
content.title = @"Now Playing";
|
||||
|
||||
NSString *subtitle;
|
||||
if ([pe artist] && [pe album]) {
|
||||
subtitle = [NSString stringWithFormat:@"%@ - %@", [pe artist], [pe album]];
|
||||
} else if ([pe artist]) {
|
||||
subtitle = [pe artist];
|
||||
} else if ([pe album]) {
|
||||
subtitle = [pe album];
|
||||
} else {
|
||||
subtitle = @"";
|
||||
}
|
||||
NSString *subtitle;
|
||||
if([pe artist] && [pe album]) {
|
||||
subtitle = [NSString stringWithFormat:@"%@ - %@", [pe artist], [pe album]];
|
||||
} else if([pe artist]) {
|
||||
subtitle = [pe artist];
|
||||
} else if([pe album]) {
|
||||
subtitle = [pe album];
|
||||
} else {
|
||||
subtitle = @"";
|
||||
}
|
||||
|
||||
NSString *body = [NSString stringWithFormat:@"%@\n%@", [pe title], subtitle];
|
||||
content.body = body;
|
||||
content.sound = nil;
|
||||
content.categoryIdentifier = @"play";
|
||||
NSString *body = [NSString stringWithFormat:@"%@\n%@", [pe title], subtitle];
|
||||
content.body = body;
|
||||
content.sound = nil;
|
||||
content.categoryIdentifier = @"play";
|
||||
|
||||
if ([defaults boolForKey:@"notifications.show-album-art"] &&
|
||||
[pe albumArt]) {
|
||||
NSError *error = nil;
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *tmpSubFolderURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()]
|
||||
URLByAppendingPathComponent:@"cog-artworks-cache"
|
||||
isDirectory:true];
|
||||
if ([fileManager createDirectoryAtPath:[tmpSubFolderURL path]
|
||||
withIntermediateDirectories:true
|
||||
attributes:nil
|
||||
error:&error]) {
|
||||
NSString *tmpFileName =
|
||||
[[NSProcessInfo.processInfo globallyUniqueString]
|
||||
stringByAppendingString:@".jpg"];
|
||||
NSURL *fileURL =
|
||||
[tmpSubFolderURL URLByAppendingPathComponent:tmpFileName];
|
||||
NSImage *image = [pe albumArt];
|
||||
CGImageRef cgRef = [image CGImageForProposedRect:NULL
|
||||
context:nil
|
||||
hints:nil];
|
||||
|
||||
if (cgRef) {
|
||||
NSBitmapImageRep *newRep =
|
||||
[[NSBitmapImageRep alloc] initWithCGImage:cgRef];
|
||||
NSData *jpgData = [newRep
|
||||
representationUsingType:NSBitmapImageFileTypeJPEG
|
||||
properties:@{NSImageCompressionFactor : @0.5f}];
|
||||
[jpgData writeToURL:fileURL atomically:YES];
|
||||
if([defaults boolForKey:@"notifications.show-album-art"] &&
|
||||
[pe albumArt]) {
|
||||
NSError *error = nil;
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *tmpSubFolderURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()]
|
||||
URLByAppendingPathComponent:@"cog-artworks-cache"
|
||||
isDirectory:true];
|
||||
if([fileManager createDirectoryAtPath:[tmpSubFolderURL path]
|
||||
withIntermediateDirectories:true
|
||||
attributes:nil
|
||||
error:&error]) {
|
||||
NSString *tmpFileName =
|
||||
[[NSProcessInfo.processInfo globallyUniqueString]
|
||||
stringByAppendingString:@".jpg"];
|
||||
NSURL *fileURL =
|
||||
[tmpSubFolderURL URLByAppendingPathComponent:tmpFileName];
|
||||
NSImage *image = [pe albumArt];
|
||||
CGImageRef cgRef = [image CGImageForProposedRect:NULL
|
||||
context:nil
|
||||
hints:nil];
|
||||
|
||||
UNNotificationAttachment *icon =
|
||||
[UNNotificationAttachment attachmentWithIdentifier:@"art"
|
||||
URL:fileURL
|
||||
options:nil
|
||||
error:&error];
|
||||
if (error) {
|
||||
// We have size limit of 10MB per image attachment.
|
||||
NSLog(@"%@", error.localizedDescription);
|
||||
} else {
|
||||
content.attachments = @[ icon ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(cgRef) {
|
||||
NSBitmapImageRep *newRep =
|
||||
[[NSBitmapImageRep alloc] initWithCGImage:cgRef];
|
||||
NSData *jpgData = [newRep
|
||||
representationUsingType:NSBitmapImageFileTypeJPEG
|
||||
properties:@{ NSImageCompressionFactor: @0.5f }];
|
||||
[jpgData writeToURL:fileURL atomically:YES];
|
||||
|
||||
UNNotificationRequest *request =
|
||||
[UNNotificationRequest requestWithIdentifier:@"PlayTrack"
|
||||
content:content
|
||||
trigger:nil];
|
||||
UNNotificationAttachment *icon =
|
||||
[UNNotificationAttachment attachmentWithIdentifier:@"art"
|
||||
URL:fileURL
|
||||
options:nil
|
||||
error:&error];
|
||||
if(error) {
|
||||
// We have size limit of 10MB per image attachment.
|
||||
NSLog(@"%@", error.localizedDescription);
|
||||
} else {
|
||||
content.attachments = @[icon];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[center addNotificationRequest:request
|
||||
withCompletionHandler:^(NSError *_Nullable error) {
|
||||
NSLog(@"%@", error.localizedDescription);
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
NSUserNotification *notif = [[NSUserNotification alloc] init];
|
||||
notif.title = [pe title];
|
||||
UNNotificationRequest *request =
|
||||
[UNNotificationRequest requestWithIdentifier:@"PlayTrack"
|
||||
content:content
|
||||
trigger:nil];
|
||||
|
||||
NSString *subtitle;
|
||||
if ([pe artist] && [pe album]) {
|
||||
subtitle = [NSString stringWithFormat:@"%@ - %@", [pe artist], [pe album]];
|
||||
} else if ([pe artist]) {
|
||||
subtitle = [pe artist];
|
||||
} else if ([pe album]) {
|
||||
subtitle = [pe album];
|
||||
} else {
|
||||
subtitle = @"";
|
||||
}
|
||||
[center addNotificationRequest:request
|
||||
withCompletionHandler:^(NSError *_Nullable error) {
|
||||
NSLog(@"%@", error.localizedDescription);
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
NSUserNotification *notif = [[NSUserNotification alloc] init];
|
||||
notif.title = [pe title];
|
||||
|
||||
if ([defaults boolForKey:@"notifications.itunes-style"]) {
|
||||
notif.subtitle = subtitle;
|
||||
[notif setValue:@YES forKey:@"_showsButtons"];
|
||||
} else {
|
||||
notif.informativeText = subtitle;
|
||||
}
|
||||
NSString *subtitle;
|
||||
if([pe artist] && [pe album]) {
|
||||
subtitle = [NSString stringWithFormat:@"%@ - %@", [pe artist], [pe album]];
|
||||
} else if([pe artist]) {
|
||||
subtitle = [pe artist];
|
||||
} else if([pe album]) {
|
||||
subtitle = [pe album];
|
||||
} else {
|
||||
subtitle = @"";
|
||||
}
|
||||
|
||||
if ([notif respondsToSelector:@selector(setContentImage:)]) {
|
||||
if ([defaults boolForKey:@"notifications.show-album-art"] &&
|
||||
[pe albumArtInternal]) {
|
||||
NSImage *image = [pe albumArt];
|
||||
if([defaults boolForKey:@"notifications.itunes-style"]) {
|
||||
notif.subtitle = subtitle;
|
||||
[notif setValue:@YES forKey:@"_showsButtons"];
|
||||
} else {
|
||||
notif.informativeText = subtitle;
|
||||
}
|
||||
|
||||
if ([defaults boolForKey:@"notifications.itunes-style"]) {
|
||||
[notif setValue:image forKey:@"_identityImage"];
|
||||
} else {
|
||||
notif.contentImage = image;
|
||||
}
|
||||
}
|
||||
}
|
||||
if([notif respondsToSelector:@selector(setContentImage:)]) {
|
||||
if([defaults boolForKey:@"notifications.show-album-art"] &&
|
||||
[pe albumArtInternal]) {
|
||||
NSImage *image = [pe albumArt];
|
||||
|
||||
notif.actionButtonTitle = @"Skip";
|
||||
if([defaults boolForKey:@"notifications.itunes-style"]) {
|
||||
[notif setValue:image forKey:@"_identityImage"];
|
||||
} else {
|
||||
notif.contentImage = image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter]
|
||||
scheduleNotification:notif];
|
||||
}
|
||||
}
|
||||
}
|
||||
notif.actionButtonTitle = @"Skip";
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter]
|
||||
scheduleNotification:notif];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)performPlaybackDidPauseActions {
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:TrackNotification
|
||||
object:nil
|
||||
userInfo:[self fillNotificationDictionary:entry status:TrackPaused]
|
||||
deliverImmediately:YES];
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||
[scrobbler pause];
|
||||
}
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:TrackNotification
|
||||
object:nil
|
||||
userInfo:[self fillNotificationDictionary:entry status:TrackPaused]
|
||||
deliverImmediately:YES];
|
||||
if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||
[scrobbler pause];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)performPlaybackDidResumeActions {
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:TrackNotification
|
||||
object:nil
|
||||
userInfo:[self fillNotificationDictionary:entry status:TrackPlaying]
|
||||
deliverImmediately:YES];
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||
[scrobbler resume];
|
||||
}
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:TrackNotification
|
||||
object:nil
|
||||
userInfo:[self fillNotificationDictionary:entry status:TrackPlaying]
|
||||
deliverImmediately:YES];
|
||||
if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||
[scrobbler resume];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)performPlaybackDidStopActions {
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:TrackNotification
|
||||
object:nil
|
||||
userInfo:[self fillNotificationDictionary:entry status:TrackStopped]
|
||||
deliverImmediately:YES];
|
||||
entry = nil;
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||
[scrobbler stop];
|
||||
}
|
||||
[[NSDistributedNotificationCenter defaultCenter]
|
||||
postNotificationName:TrackNotification
|
||||
object:nil
|
||||
userInfo:[self fillNotificationDictionary:entry status:TrackStopped]
|
||||
deliverImmediately:YES];
|
||||
entry = nil;
|
||||
if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) {
|
||||
[scrobbler stop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)awakeFromNib {
|
||||
[[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];
|
||||
[[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)playbackDidBegin:(NSNotification *)notification {
|
||||
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
[self performPlaybackDidBeginActions:(PlaylistEntry *)[notification object]];
|
||||
}];
|
||||
[queue addOperation:op];
|
||||
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
[self performPlaybackDidBeginActions:(PlaylistEntry *)[notification object]];
|
||||
}];
|
||||
[queue addOperation:op];
|
||||
}
|
||||
|
||||
- (void)playbackDidPause:(NSNotification *)notification {
|
||||
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
[self performPlaybackDidPauseActions];
|
||||
}];
|
||||
[queue addOperation:op];
|
||||
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
[self performPlaybackDidPauseActions];
|
||||
}];
|
||||
[queue addOperation:op];
|
||||
}
|
||||
|
||||
- (void)playbackDidResume:(NSNotification *)notification {
|
||||
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
[self performPlaybackDidResumeActions];
|
||||
}];
|
||||
[queue addOperation:op];
|
||||
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
[self performPlaybackDidResumeActions];
|
||||
}];
|
||||
[queue addOperation:op];
|
||||
}
|
||||
|
||||
- (void)playbackDidStop:(NSNotification *)notification {
|
||||
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
[self performPlaybackDidStopActions];
|
||||
}];
|
||||
[queue addOperation:op];
|
||||
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
[self performPlaybackDidStopActions];
|
||||
}];
|
||||
[queue addOperation:op];
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(NSUserNotificationCenter *)center
|
||||
didActivateNotification:(NSUserNotification *)notification {
|
||||
switch (notification.activationType) {
|
||||
case NSUserNotificationActivationTypeActionButtonClicked:
|
||||
[playbackController next:self];
|
||||
break;
|
||||
switch(notification.activationType) {
|
||||
case NSUserNotificationActivationTypeActionButtonClicked:
|
||||
[playbackController next:self];
|
||||
break;
|
||||
|
||||
case NSUserNotificationActivationTypeContentsClicked: {
|
||||
NSWindow *window = [[NSUserDefaults standardUserDefaults] boolForKey:@"miniMode"]
|
||||
? miniWindow
|
||||
: mainWindow;
|
||||
case NSUserNotificationActivationTypeContentsClicked: {
|
||||
NSWindow *window = [[NSUserDefaults standardUserDefaults] boolForKey:@"miniMode"] ? miniWindow : mainWindow;
|
||||
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
[window makeKeyAndOrderFront:self];
|
||||
}; break;
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
[window makeKeyAndOrderFront:self];
|
||||
}; break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -10,9 +10,8 @@
|
|||
#import <CogAudio/Plugin.h>
|
||||
|
||||
@interface AudioContainer : NSObject {
|
||||
|
||||
}
|
||||
|
||||
+ (NSArray *) urlsForContainerURL:(NSURL *)url;
|
||||
+ (NSArray *)urlsForContainerURL:(NSURL *)url;
|
||||
|
||||
@end
|
||||
|
|
|
@ -12,11 +12,10 @@
|
|||
|
||||
@implementation AudioContainer
|
||||
|
||||
+ (NSArray *) urlsForContainerURL:(NSURL *)url
|
||||
{
|
||||
@autoreleasepool {
|
||||
return [[PluginController sharedPluginController] urlsForContainerURL:url];
|
||||
}
|
||||
+ (NSArray *)urlsForContainerURL:(NSURL *)url {
|
||||
@autoreleasepool {
|
||||
return [[PluginController sharedPluginController] urlsForContainerURL:url];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
@interface AudioDecoder : NSObject {
|
||||
}
|
||||
|
||||
+ (id<CogDecoder>)audioDecoderForSource:(id <CogSource>)source;
|
||||
+ (id<CogDecoder>)audioDecoderForSource:(id <CogSource>)source skipCue:(BOOL)skip;
|
||||
+ (id<CogDecoder>)audioDecoderForSource:(id<CogSource>)source;
|
||||
+ (id<CogDecoder>)audioDecoderForSource:(id<CogSource>)source skipCue:(BOOL)skip;
|
||||
|
||||
@end
|
||||
|
|
|
@ -12,14 +12,12 @@
|
|||
|
||||
@implementation AudioDecoder
|
||||
|
||||
+ (id<CogDecoder>) audioDecoderForSource:(id <CogSource>)source
|
||||
{
|
||||
return [[PluginController sharedPluginController] audioDecoderForSource:source skipCue:NO];
|
||||
+ (id<CogDecoder>)audioDecoderForSource:(id<CogSource>)source {
|
||||
return [[PluginController sharedPluginController] audioDecoderForSource:source skipCue:NO];
|
||||
}
|
||||
|
||||
+ (id<CogDecoder>) audioDecoderForSource:(id <CogSource>)source skipCue:(BOOL)skip
|
||||
{
|
||||
return [[PluginController sharedPluginController] audioDecoderForSource:source skipCue:skip];
|
||||
+ (id<CogDecoder>)audioDecoderForSource:(id<CogSource>)source skipCue:(BOOL)skip {
|
||||
return [[PluginController sharedPluginController] audioDecoderForSource:source skipCue:skip];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,9 +8,7 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
@interface AudioMetadataReader : NSObject {
|
||||
|
||||
}
|
||||
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url;
|
||||
|
|
|
@ -11,18 +11,16 @@
|
|||
|
||||
@implementation AudioMetadataReader
|
||||
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url
|
||||
{
|
||||
@autoreleasepool {
|
||||
return [[PluginController sharedPluginController] metadataForURL:url skipCue:NO];
|
||||
}
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url {
|
||||
@autoreleasepool {
|
||||
return [[PluginController sharedPluginController] metadataForURL:url skipCue:NO];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip
|
||||
{
|
||||
@autoreleasepool {
|
||||
return [[PluginController sharedPluginController] metadataForURL:url skipCue:skip];
|
||||
}
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip {
|
||||
@autoreleasepool {
|
||||
return [[PluginController sharedPluginController] metadataForURL:url skipCue:skip];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,9 +8,7 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
@interface AudioMetadataWriter : NSObject {
|
||||
|
||||
}
|
||||
|
||||
+ (int)putMetadataInURL:(NSURL *)url;
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
#import "PluginController.h"
|
||||
|
||||
@implementation AudioMetadataWriter
|
||||
+ (int)putMetadataInURL:(NSURL *)url
|
||||
{
|
||||
+ (int)putMetadataInURL:(NSURL *)url {
|
||||
return [[PluginController sharedPluginController] putMetadataInURL:url];
|
||||
}
|
||||
@end
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
|
||||
#import <CogAudio/Semaphore.h>
|
||||
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <CoreAudio/CoreAudioTypes.h>
|
||||
|
||||
#import <stdatomic.h>
|
||||
|
@ -21,34 +21,33 @@
|
|||
@class BufferChain;
|
||||
@class OutputNode;
|
||||
|
||||
@interface AudioPlayer : NSObject
|
||||
{
|
||||
@interface AudioPlayer : NSObject {
|
||||
BufferChain *bufferChain;
|
||||
OutputNode *output;
|
||||
|
||||
|
||||
double volume;
|
||||
|
||||
NSMutableArray *chainQueue;
|
||||
|
||||
|
||||
NSURL *nextStream;
|
||||
id nextStreamUserInfo;
|
||||
NSDictionary *nextStreamRGInfo;
|
||||
|
||||
NSDictionary *nextStreamRGInfo;
|
||||
|
||||
id delegate;
|
||||
|
||||
|
||||
BOOL outputLaunched;
|
||||
BOOL endOfInputReached;
|
||||
BOOL startedPaused;
|
||||
BOOL initialBufferFilled;
|
||||
|
||||
Semaphore *semaphore;
|
||||
|
||||
atomic_bool resettingNow;
|
||||
atomic_int refCount;
|
||||
|
||||
int currentPlaybackStatus;
|
||||
|
||||
BOOL shouldContinue;
|
||||
BOOL startedPaused;
|
||||
BOOL initialBufferFilled;
|
||||
|
||||
Semaphore *semaphore;
|
||||
|
||||
atomic_bool resettingNow;
|
||||
atomic_int refCount;
|
||||
|
||||
int currentPlaybackStatus;
|
||||
|
||||
BOOL shouldContinue;
|
||||
}
|
||||
|
||||
- (id)init;
|
||||
|
@ -57,9 +56,9 @@
|
|||
- (id)delegate;
|
||||
|
||||
- (void)play:(NSURL *)url;
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary*)rgi;
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary*)rgi startPaused:(BOOL)paused;
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary*)rgi startPaused:(BOOL)paused andSeekTo:(double)time;
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi;
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(BOOL)paused;
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(BOOL)paused andSeekTo:(double)time;
|
||||
|
||||
- (void)stop;
|
||||
- (void)pause;
|
||||
|
@ -74,7 +73,7 @@
|
|||
- (double)amountPlayed;
|
||||
|
||||
- (void)setNextStream:(NSURL *)url;
|
||||
- (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary*)rgi;
|
||||
- (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi;
|
||||
- (void)resetNextStreams;
|
||||
|
||||
+ (NSArray *)fileTypes;
|
||||
|
@ -83,10 +82,10 @@
|
|||
|
||||
@end
|
||||
|
||||
@interface AudioPlayer (Private) //Dont use this stuff!
|
||||
@interface AudioPlayer (Private) // Dont use this stuff!
|
||||
|
||||
- (OutputNode *) output;
|
||||
- (BufferChain *) bufferChain;
|
||||
- (OutputNode *)output;
|
||||
- (BufferChain *)bufferChain;
|
||||
- (id)initWithDelegate:(id)d;
|
||||
|
||||
- (void)setPlaybackStatus:(int)status waitUntilDone:(BOOL)wait;
|
||||
|
@ -107,7 +106,7 @@
|
|||
//- (BufferChain *)bufferChain;
|
||||
- (void)launchOutputThread;
|
||||
- (void)endOfInputPlayed;
|
||||
- (void)sendDelegateMethod:(SEL)selector withVoid:(void*)obj waitUntilDone:(BOOL)wait;
|
||||
- (void)sendDelegateMethod:(SEL)selector withVoid:(void *)obj waitUntilDone:(BOOL)wait;
|
||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait;
|
||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj withObject:(id)obj2 waitUntilDone:(BOOL)wait;
|
||||
|
||||
|
@ -115,7 +114,7 @@
|
|||
@end
|
||||
|
||||
@protocol AudioPlayerDelegate
|
||||
- (void)audioPlayer:(AudioPlayer *)player willEndStream:(id)userInfo; //You must use setNextStream in this method
|
||||
- (void)audioPlayer:(AudioPlayer *)player willEndStream:(id)userInfo; // You must use setNextStream in this method
|
||||
- (void)audioPlayer:(AudioPlayer *)player didBeginStream:(id)userInfo;
|
||||
- (void)audioPlayer:(AudioPlayer *)player didChangeStatus:(id)status userInfo:(id)userInfo;
|
||||
- (void)audioPlayer:(AudioPlayer *)player displayEqualizer:(AudioUnit)eq;
|
||||
|
@ -124,4 +123,3 @@
|
|||
- (void)audioPlayer:(AudioPlayer *)player sustainHDCD:(id)userInfo;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -8,39 +8,35 @@
|
|||
|
||||
#import "AudioPlayer.h"
|
||||
#import "BufferChain.h"
|
||||
#import "OutputNode.h"
|
||||
#import "Status.h"
|
||||
#import "Helper.h"
|
||||
#import "OutputNode.h"
|
||||
#import "PluginController.h"
|
||||
|
||||
#import "Status.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
@implementation AudioPlayer
|
||||
|
||||
- (id)init
|
||||
{
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
if(self) {
|
||||
output = NULL;
|
||||
bufferChain = nil;
|
||||
outputLaunched = NO;
|
||||
endOfInputReached = NO;
|
||||
|
||||
chainQueue = [[NSMutableArray alloc] init];
|
||||
|
||||
semaphore = [[Semaphore alloc] init];
|
||||
|
||||
atomic_init(&resettingNow, false);
|
||||
atomic_init(&refCount, 0);
|
||||
chainQueue = [[NSMutableArray alloc] init];
|
||||
|
||||
semaphore = [[Semaphore alloc] init];
|
||||
|
||||
atomic_init(&resettingNow, false);
|
||||
atomic_init(&refCount, 0);
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id)d
|
||||
{
|
||||
- (void)setDelegate:(id)d {
|
||||
delegate = d;
|
||||
}
|
||||
|
||||
|
@ -48,597 +44,531 @@
|
|||
return delegate;
|
||||
}
|
||||
|
||||
- (void)play:(NSURL *)url
|
||||
{
|
||||
- (void)play:(NSURL *)url {
|
||||
[self play:url withUserInfo:nil withRGInfo:nil startPaused:NO andSeekTo:0.0];
|
||||
}
|
||||
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi
|
||||
{
|
||||
[self play:url withUserInfo:userInfo withRGInfo:rgi startPaused:NO andSeekTo:0.0];
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi {
|
||||
[self play:url withUserInfo:userInfo withRGInfo:rgi startPaused:NO andSeekTo:0.0];
|
||||
}
|
||||
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(BOOL)paused
|
||||
{
|
||||
[self play:url withUserInfo:userInfo withRGInfo:rgi startPaused:paused andSeekTo:0.0];
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(BOOL)paused {
|
||||
[self play:url withUserInfo:userInfo withRGInfo:rgi startPaused:paused andSeekTo:0.0];
|
||||
}
|
||||
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(BOOL)paused andSeekTo:(double)time
|
||||
{
|
||||
ALog(@"Opening file for playback: %@ at seek offset %f%@", url, time, (paused) ? @", starting paused" : @"");
|
||||
|
||||
[self waitUntilCallbacksExit];
|
||||
if (output) {
|
||||
[output setShouldContinue:NO];
|
||||
output = nil;
|
||||
}
|
||||
output = [[OutputNode alloc] initWithController:self previous:nil];
|
||||
[output setup];
|
||||
[output setVolume: volume];
|
||||
@synchronized(chainQueue) {
|
||||
for (id anObject in chainQueue)
|
||||
{
|
||||
- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(BOOL)paused andSeekTo:(double)time {
|
||||
ALog(@"Opening file for playback: %@ at seek offset %f%@", url, time, (paused) ? @", starting paused" : @"");
|
||||
|
||||
[self waitUntilCallbacksExit];
|
||||
if(output) {
|
||||
[output setShouldContinue:NO];
|
||||
output = nil;
|
||||
}
|
||||
output = [[OutputNode alloc] initWithController:self previous:nil];
|
||||
[output setup];
|
||||
[output setVolume:volume];
|
||||
@synchronized(chainQueue) {
|
||||
for(id anObject in chainQueue) {
|
||||
[anObject setShouldContinue:NO];
|
||||
}
|
||||
[chainQueue removeAllObjects];
|
||||
endOfInputReached = NO;
|
||||
if (bufferChain)
|
||||
{
|
||||
if(bufferChain) {
|
||||
[bufferChain setShouldContinue:NO];
|
||||
|
||||
bufferChain = nil;
|
||||
bufferChain = nil;
|
||||
}
|
||||
}
|
||||
|
||||
bufferChain = [[BufferChain alloc] initWithController:self];
|
||||
[self notifyStreamChanged:userInfo];
|
||||
|
||||
while (![bufferChain open:url withOutputFormat:[output format] withRGInfo:rgi])
|
||||
{
|
||||
|
||||
while(![bufferChain open:url withOutputFormat:[output format] withRGInfo:rgi]) {
|
||||
bufferChain = nil;
|
||||
|
||||
[self requestNextStream: userInfo];
|
||||
|
||||
[self requestNextStream:userInfo];
|
||||
|
||||
url = nextStream;
|
||||
if (url == nil)
|
||||
{
|
||||
if(url == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
userInfo = nextStreamUserInfo;
|
||||
rgi = nextStreamRGInfo;
|
||||
|
||||
rgi = nextStreamRGInfo;
|
||||
|
||||
[self notifyStreamChanged:userInfo];
|
||||
|
||||
|
||||
bufferChain = [[BufferChain alloc] initWithController:self];
|
||||
}
|
||||
|
||||
[bufferChain setUserInfo:userInfo];
|
||||
|
||||
if (time > 0.0)
|
||||
{
|
||||
[output seek:time];
|
||||
[bufferChain seek:time];
|
||||
}
|
||||
|
||||
if(time > 0.0) {
|
||||
[output seek:time];
|
||||
[bufferChain seek:time];
|
||||
}
|
||||
|
||||
[self setShouldContinue:YES];
|
||||
|
||||
|
||||
outputLaunched = NO;
|
||||
startedPaused = paused;
|
||||
initialBufferFilled = NO;
|
||||
startedPaused = paused;
|
||||
initialBufferFilled = NO;
|
||||
|
||||
[bufferChain launchThreads];
|
||||
|
||||
if (paused)
|
||||
[self setPlaybackStatus:CogStatusPaused waitUntilDone:YES];
|
||||
|
||||
if(paused)
|
||||
[self setPlaybackStatus:CogStatusPaused waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
//Set shouldoContinue to NO on all things
|
||||
- (void)stop {
|
||||
// Set shouldoContinue to NO on all things
|
||||
[self setShouldContinue:NO];
|
||||
[self setPlaybackStatus:CogStatusStopped waitUntilDone:YES];
|
||||
|
||||
@synchronized(chainQueue) {
|
||||
for (id anObject in chainQueue)
|
||||
{
|
||||
[anObject setShouldContinue:NO];
|
||||
}
|
||||
[chainQueue removeAllObjects];
|
||||
endOfInputReached = NO;
|
||||
if (bufferChain)
|
||||
{
|
||||
bufferChain = nil;
|
||||
}
|
||||
}
|
||||
output = nil;
|
||||
@synchronized(chainQueue) {
|
||||
for(id anObject in chainQueue) {
|
||||
[anObject setShouldContinue:NO];
|
||||
}
|
||||
[chainQueue removeAllObjects];
|
||||
endOfInputReached = NO;
|
||||
if(bufferChain) {
|
||||
bufferChain = nil;
|
||||
}
|
||||
}
|
||||
output = nil;
|
||||
}
|
||||
|
||||
- (void)pause
|
||||
{
|
||||
- (void)pause {
|
||||
[output pause];
|
||||
|
||||
[self setPlaybackStatus:CogStatusPaused waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)resume
|
||||
{
|
||||
if (startedPaused)
|
||||
{
|
||||
startedPaused = NO;
|
||||
if (initialBufferFilled)
|
||||
[self launchOutputThread];
|
||||
}
|
||||
|
||||
- (void)resume {
|
||||
if(startedPaused) {
|
||||
startedPaused = NO;
|
||||
if(initialBufferFilled)
|
||||
[self launchOutputThread];
|
||||
}
|
||||
|
||||
[output resume];
|
||||
|
||||
[self setPlaybackStatus:CogStatusPlaying waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)seekToTime:(double)time
|
||||
{
|
||||
if (endOfInputReached)
|
||||
{
|
||||
// This is a dirty hack in case the playback has finished with the track
|
||||
// that the user thinks they're seeking into
|
||||
CogStatus status = (CogStatus) currentPlaybackStatus;
|
||||
NSURL *url;
|
||||
id userInfo;
|
||||
NSDictionary *rgi;
|
||||
|
||||
@synchronized (chainQueue) {
|
||||
url = [bufferChain streamURL];
|
||||
userInfo = [bufferChain userInfo];
|
||||
rgi = [bufferChain rgInfo];
|
||||
}
|
||||
|
||||
[self stop];
|
||||
|
||||
[self play:url withUserInfo:userInfo withRGInfo:rgi startPaused:(status == CogStatusPaused) andSeekTo:time];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Still decoding the current file, safe to seek within it
|
||||
[output seek:time];
|
||||
[bufferChain seek:time];
|
||||
}
|
||||
- (void)seekToTime:(double)time {
|
||||
if(endOfInputReached) {
|
||||
// This is a dirty hack in case the playback has finished with the track
|
||||
// that the user thinks they're seeking into
|
||||
CogStatus status = (CogStatus)currentPlaybackStatus;
|
||||
NSURL *url;
|
||||
id userInfo;
|
||||
NSDictionary *rgi;
|
||||
|
||||
@synchronized(chainQueue) {
|
||||
url = [bufferChain streamURL];
|
||||
userInfo = [bufferChain userInfo];
|
||||
rgi = [bufferChain rgInfo];
|
||||
}
|
||||
|
||||
[self stop];
|
||||
|
||||
[self play:url withUserInfo:userInfo withRGInfo:rgi startPaused:(status == CogStatusPaused) andSeekTo:time];
|
||||
} else {
|
||||
// Still decoding the current file, safe to seek within it
|
||||
[output seek:time];
|
||||
[bufferChain seek:time];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setVolume:(double)v
|
||||
{
|
||||
- (void)setVolume:(double)v {
|
||||
volume = v;
|
||||
|
||||
|
||||
[output setVolume:v];
|
||||
}
|
||||
|
||||
- (double)volume
|
||||
{
|
||||
- (double)volume {
|
||||
return volume;
|
||||
}
|
||||
|
||||
|
||||
//This is called by the delegate DURING a requestNextStream request.
|
||||
- (void)setNextStream:(NSURL *)url
|
||||
{
|
||||
// This is called by the delegate DURING a requestNextStream request.
|
||||
- (void)setNextStream:(NSURL *)url {
|
||||
[self setNextStream:url withUserInfo:nil withRGInfo:nil];
|
||||
}
|
||||
|
||||
- (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi
|
||||
{
|
||||
- (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi {
|
||||
nextStream = url;
|
||||
|
||||
|
||||
nextStreamUserInfo = userInfo;
|
||||
|
||||
nextStreamRGInfo = rgi;
|
||||
nextStreamRGInfo = rgi;
|
||||
}
|
||||
|
||||
// Called when the playlist changed before we actually started playing a requested stream. We will re-request.
|
||||
- (void)resetNextStreams
|
||||
{
|
||||
[self waitUntilCallbacksExit];
|
||||
- (void)resetNextStreams {
|
||||
[self waitUntilCallbacksExit];
|
||||
|
||||
@synchronized (chainQueue) {
|
||||
for (id anObject in chainQueue) {
|
||||
@synchronized(chainQueue) {
|
||||
for(id anObject in chainQueue) {
|
||||
[anObject setShouldContinue:NO];
|
||||
}
|
||||
[chainQueue removeAllObjects];
|
||||
|
||||
if (endOfInputReached) {
|
||||
if(endOfInputReached) {
|
||||
[self endOfInputReached:bufferChain];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShouldContinue:(BOOL)s
|
||||
{
|
||||
shouldContinue = s;
|
||||
|
||||
if (bufferChain)
|
||||
- (void)setShouldContinue:(BOOL)s {
|
||||
shouldContinue = s;
|
||||
|
||||
if(bufferChain)
|
||||
[bufferChain setShouldContinue:s];
|
||||
|
||||
if (output)
|
||||
|
||||
if(output)
|
||||
[output setShouldContinue:s];
|
||||
}
|
||||
|
||||
- (double)amountPlayed
|
||||
{
|
||||
- (double)amountPlayed {
|
||||
return [output amountPlayed];
|
||||
}
|
||||
|
||||
- (void)launchOutputThread
|
||||
{
|
||||
initialBufferFilled = YES;
|
||||
if (outputLaunched == NO && startedPaused == NO) {
|
||||
[self setPlaybackStatus:CogStatusPlaying];
|
||||
- (void)launchOutputThread {
|
||||
initialBufferFilled = YES;
|
||||
if(outputLaunched == NO && startedPaused == NO) {
|
||||
[self setPlaybackStatus:CogStatusPlaying];
|
||||
[output launchThread];
|
||||
outputLaunched = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestNextStream:(id)userInfo
|
||||
{
|
||||
- (void)requestNextStream:(id)userInfo {
|
||||
[self sendDelegateMethod:@selector(audioPlayer:willEndStream:) withObject:userInfo waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)notifyStreamChanged:(id)userInfo
|
||||
{
|
||||
- (void)notifyStreamChanged:(id)userInfo {
|
||||
[self sendDelegateMethod:@selector(audioPlayer:didBeginStream:) withObject:userInfo waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)beginEqualizer:(AudioUnit)eq
|
||||
{
|
||||
[self sendDelegateMethod:@selector(audioPlayer:displayEqualizer:) withVoid:eq waitUntilDone:YES];
|
||||
- (void)beginEqualizer:(AudioUnit)eq {
|
||||
[self sendDelegateMethod:@selector(audioPlayer:displayEqualizer:) withVoid:eq waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)refreshEqualizer:(AudioUnit)eq
|
||||
{
|
||||
[self sendDelegateMethod:@selector(audioPlayer:refreshEqualizer:) withVoid:eq waitUntilDone:YES];
|
||||
- (void)refreshEqualizer:(AudioUnit)eq {
|
||||
[self sendDelegateMethod:@selector(audioPlayer:refreshEqualizer:) withVoid:eq waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)endEqualizer:(AudioUnit)eq
|
||||
{
|
||||
[self sendDelegateMethod:@selector(audioPlayer:removeEqualizer:) withVoid:eq waitUntilDone:YES];
|
||||
- (void)endEqualizer:(AudioUnit)eq {
|
||||
[self sendDelegateMethod:@selector(audioPlayer:removeEqualizer:) withVoid:eq waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)addChainToQueue:(BufferChain *)newChain
|
||||
{
|
||||
[newChain setUserInfo: nextStreamUserInfo];
|
||||
|
||||
- (void)addChainToQueue:(BufferChain *)newChain {
|
||||
[newChain setUserInfo:nextStreamUserInfo];
|
||||
|
||||
[newChain setShouldContinue:YES];
|
||||
[newChain launchThreads];
|
||||
|
||||
[chainQueue insertObject:newChain atIndex:[chainQueue count]];
|
||||
[chainQueue insertObject:newChain atIndex:[chainQueue count]];
|
||||
}
|
||||
|
||||
- (BOOL)endOfInputReached:(BufferChain *)sender //Sender is a BufferChain
|
||||
- (BOOL)endOfInputReached:(BufferChain *)sender // Sender is a BufferChain
|
||||
{
|
||||
BufferChain *newChain = nil;
|
||||
|
||||
if (atomic_load_explicit(&resettingNow, memory_order_relaxed))
|
||||
return YES;
|
||||
|
||||
atomic_fetch_add(&refCount, 1);
|
||||
BufferChain *newChain = nil;
|
||||
|
||||
@synchronized (chainQueue) {
|
||||
// No point in constructing new chain for the next playlist entry
|
||||
// if there's already one at the head of chainQueue... r-r-right?
|
||||
for (BufferChain *chain in chainQueue)
|
||||
{
|
||||
if ([chain isRunning])
|
||||
{
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
if(atomic_load_explicit(&resettingNow, memory_order_relaxed))
|
||||
return YES;
|
||||
|
||||
// We don't want to do this, it may happen with a lot of short files
|
||||
//if ([chainQueue count] >= 5)
|
||||
//{
|
||||
// return YES;
|
||||
//}
|
||||
}
|
||||
atomic_fetch_add(&refCount, 1);
|
||||
|
||||
double duration = 0.0;
|
||||
@synchronized(chainQueue) {
|
||||
// No point in constructing new chain for the next playlist entry
|
||||
// if there's already one at the head of chainQueue... r-r-right?
|
||||
for(BufferChain *chain in chainQueue) {
|
||||
if([chain isRunning]) {
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
@synchronized (chainQueue) {
|
||||
for (BufferChain *chain in chainQueue) {
|
||||
duration += [chain secondsBuffered];
|
||||
}
|
||||
}
|
||||
// We don't want to do this, it may happen with a lot of short files
|
||||
// if ([chainQueue count] >= 5)
|
||||
//{
|
||||
// return YES;
|
||||
//}
|
||||
}
|
||||
|
||||
while (duration >= 30.0 && shouldContinue)
|
||||
{
|
||||
[semaphore wait];
|
||||
if (atomic_load_explicit(&resettingNow, memory_order_relaxed)) {
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
@synchronized (chainQueue) {
|
||||
duration = 0.0;
|
||||
for (BufferChain *chain in chainQueue) {
|
||||
duration += [chain secondsBuffered];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextStreamUserInfo = [sender userInfo];
|
||||
|
||||
nextStreamRGInfo = [sender rgInfo];
|
||||
|
||||
// This call can sometimes lead to invoking a chainQueue block on another thread
|
||||
[self requestNextStream: nextStreamUserInfo];
|
||||
|
||||
if (!nextStream) {
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
double duration = 0.0;
|
||||
|
||||
@synchronized (chainQueue) {
|
||||
@synchronized(chainQueue) {
|
||||
for(BufferChain *chain in chainQueue) {
|
||||
duration += [chain secondsBuffered];
|
||||
}
|
||||
}
|
||||
|
||||
while(duration >= 30.0 && shouldContinue) {
|
||||
[semaphore wait];
|
||||
if(atomic_load_explicit(&resettingNow, memory_order_relaxed)) {
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
@synchronized(chainQueue) {
|
||||
duration = 0.0;
|
||||
for(BufferChain *chain in chainQueue) {
|
||||
duration += [chain secondsBuffered];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextStreamUserInfo = [sender userInfo];
|
||||
|
||||
nextStreamRGInfo = [sender rgInfo];
|
||||
|
||||
// This call can sometimes lead to invoking a chainQueue block on another thread
|
||||
[self requestNextStream:nextStreamUserInfo];
|
||||
|
||||
if(!nextStream) {
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@synchronized(chainQueue) {
|
||||
newChain = [[BufferChain alloc] initWithController:self];
|
||||
|
||||
|
||||
endOfInputReached = YES;
|
||||
|
||||
|
||||
BufferChain *lastChain = [chainQueue lastObject];
|
||||
if (lastChain == nil) {
|
||||
if(lastChain == nil) {
|
||||
lastChain = bufferChain;
|
||||
}
|
||||
|
||||
BOOL pathsEqual = NO;
|
||||
|
||||
if ([nextStream isFileURL] && [[lastChain streamURL] isFileURL])
|
||||
{
|
||||
NSMutableString *unixPathNext = [[nextStream path] mutableCopy];
|
||||
NSMutableString *unixPathPrev = [[[lastChain streamURL] path] mutableCopy];
|
||||
|
||||
if ([unixPathNext isEqualToString:unixPathPrev])
|
||||
pathsEqual = YES;
|
||||
}
|
||||
|
||||
if (pathsEqual || ([[nextStream scheme] isEqualToString:[[lastChain streamURL] scheme]]
|
||||
&& (([nextStream host] == nil &&
|
||||
[[lastChain streamURL] host] == nil)
|
||||
|| [[nextStream host] isEqualToString:[[lastChain streamURL] host]])
|
||||
&& [[nextStream path] isEqualToString:[[lastChain streamURL] path]]))
|
||||
{
|
||||
if ([lastChain setTrack:nextStream]
|
||||
&& [newChain openWithInput:[lastChain inputNode] withOutputFormat:[output format] withRGInfo:nextStreamRGInfo])
|
||||
{
|
||||
BOOL pathsEqual = NO;
|
||||
|
||||
if([nextStream isFileURL] && [[lastChain streamURL] isFileURL]) {
|
||||
NSMutableString *unixPathNext = [[nextStream path] mutableCopy];
|
||||
NSMutableString *unixPathPrev = [[[lastChain streamURL] path] mutableCopy];
|
||||
|
||||
if([unixPathNext isEqualToString:unixPathPrev])
|
||||
pathsEqual = YES;
|
||||
}
|
||||
|
||||
if(pathsEqual || ([[nextStream scheme] isEqualToString:[[lastChain streamURL] scheme]] && (([nextStream host] == nil && [[lastChain streamURL] host] == nil) || [[nextStream host] isEqualToString:[[lastChain streamURL] host]]) && [[nextStream path] isEqualToString:[[lastChain streamURL] path]])) {
|
||||
if([lastChain setTrack:nextStream] && [newChain openWithInput:[lastChain inputNode] withOutputFormat:[output format] withRGInfo:nextStreamRGInfo]) {
|
||||
[newChain setStreamURL:nextStream];
|
||||
[newChain setUserInfo:nextStreamUserInfo];
|
||||
|
||||
[self addChainToQueue:newChain];
|
||||
DLog(@"TRACK SET!!! %@", newChain);
|
||||
//Keep on-playin
|
||||
newChain = nil;
|
||||
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
// Keep on-playin
|
||||
newChain = nil;
|
||||
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
lastChain = nil;
|
||||
|
||||
while (shouldContinue && ![newChain open:nextStream withOutputFormat:[output format] withRGInfo:nextStreamRGInfo])
|
||||
{
|
||||
if (nextStream == nil)
|
||||
{
|
||||
newChain = nil;
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
|
||||
lastChain = nil;
|
||||
|
||||
while(shouldContinue && ![newChain open:nextStream withOutputFormat:[output format] withRGInfo:nextStreamRGInfo]) {
|
||||
if(nextStream == nil) {
|
||||
newChain = nil;
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
||||
newChain = nil;
|
||||
[self requestNextStream: nextStreamUserInfo];
|
||||
|
||||
newChain = nil;
|
||||
[self requestNextStream:nextStreamUserInfo];
|
||||
|
||||
newChain = [[BufferChain alloc] initWithController:self];
|
||||
}
|
||||
|
||||
[self addChainToQueue:newChain];
|
||||
|
||||
newChain = nil;
|
||||
|
||||
// I'm stupid and can't hold too much stuff in my head all at once, so writing it here.
|
||||
//
|
||||
// Once we get here:
|
||||
// - buffer chain for previous stream finished reading
|
||||
// - there are (probably) some bytes of the previous stream in the output buffer which haven't been played
|
||||
// (by output node) yet
|
||||
// - self.bufferChain == previous playlist entry's buffer chain
|
||||
// - self.nextStream == next playlist entry's URL
|
||||
// - self.nextStreamUserInfo == next playlist entry
|
||||
// - head of chainQueue is the buffer chain for the next entry (which has launched its threads already)
|
||||
[self addChainToQueue:newChain];
|
||||
|
||||
newChain = nil;
|
||||
|
||||
// I'm stupid and can't hold too much stuff in my head all at once, so writing it here.
|
||||
//
|
||||
// Once we get here:
|
||||
// - buffer chain for previous stream finished reading
|
||||
// - there are (probably) some bytes of the previous stream in the output buffer which haven't been played
|
||||
// (by output node) yet
|
||||
// - self.bufferChain == previous playlist entry's buffer chain
|
||||
// - self.nextStream == next playlist entry's URL
|
||||
// - self.nextStreamUserInfo == next playlist entry
|
||||
// - head of chainQueue is the buffer chain for the next entry (which has launched its threads already)
|
||||
}
|
||||
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
|
||||
atomic_fetch_sub(&refCount, 1);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)endOfInputPlayed
|
||||
{
|
||||
// Once we get here:
|
||||
// - the buffer chain for the next playlist entry (started in endOfInputReached) have been working for some time
|
||||
// already, so that there is some decoded and converted data to play
|
||||
// - the buffer chain for the next entry is the first item in chainQueue
|
||||
- (void)endOfInputPlayed {
|
||||
// Once we get here:
|
||||
// - the buffer chain for the next playlist entry (started in endOfInputReached) have been working for some time
|
||||
// already, so that there is some decoded and converted data to play
|
||||
// - the buffer chain for the next entry is the first item in chainQueue
|
||||
|
||||
@synchronized(chainQueue) {
|
||||
endOfInputReached = NO;
|
||||
|
||||
if ([chainQueue count] <= 0)
|
||||
{
|
||||
//End of playlist
|
||||
|
||||
if([chainQueue count] <= 0) {
|
||||
// End of playlist
|
||||
[self stop];
|
||||
|
||||
|
||||
bufferChain = nil;
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bufferChain = nil;
|
||||
bufferChain = [chainQueue objectAtIndex:0];
|
||||
|
||||
[chainQueue removeObjectAtIndex:0];
|
||||
bufferChain = nil;
|
||||
bufferChain = [chainQueue objectAtIndex:0];
|
||||
|
||||
[chainQueue removeObjectAtIndex:0];
|
||||
DLog(@"New!!! %@ %@", bufferChain, [[bufferChain inputNode] decoder]);
|
||||
|
||||
[semaphore signal];
|
||||
|
||||
[semaphore signal];
|
||||
}
|
||||
|
||||
[self notifyStreamChanged:[bufferChain userInfo]];
|
||||
|
||||
[self notifyStreamChanged:[bufferChain userInfo]];
|
||||
[output setEndOfStream:NO];
|
||||
}
|
||||
|
||||
- (BOOL)chainQueueHasTracks
|
||||
{
|
||||
@synchronized (chainQueue) {
|
||||
return [chainQueue count] > 0;
|
||||
}
|
||||
return NO;
|
||||
- (BOOL)chainQueueHasTracks {
|
||||
@synchronized(chainQueue) {
|
||||
return [chainQueue count] > 0;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)sendDelegateMethod:(SEL)selector withVoid:(void*)obj waitUntilDone:(BOOL)wait
|
||||
{
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:selector]];
|
||||
[invocation setTarget:delegate];
|
||||
[invocation setSelector:selector];
|
||||
[invocation setArgument:(void*)&self atIndex:2];
|
||||
[invocation setArgument:&obj atIndex:3];
|
||||
[invocation retainArguments];
|
||||
|
||||
[invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:wait];
|
||||
}
|
||||
|
||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait
|
||||
{
|
||||
- (void)sendDelegateMethod:(SEL)selector withVoid:(void *)obj waitUntilDone:(BOOL)wait {
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:selector]];
|
||||
[invocation setTarget:delegate];
|
||||
[invocation setTarget:delegate];
|
||||
[invocation setSelector:selector];
|
||||
[invocation setArgument:(void*)&self atIndex:2];
|
||||
[invocation setArgument:&obj atIndex:3];
|
||||
[invocation retainArguments];
|
||||
|
||||
[invocation setArgument:(void *)&self atIndex:2];
|
||||
[invocation setArgument:&obj atIndex:3];
|
||||
[invocation retainArguments];
|
||||
|
||||
[invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:wait];
|
||||
}
|
||||
|
||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj withObject:(id)obj2 waitUntilDone:(BOOL)wait
|
||||
{
|
||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj waitUntilDone:(BOOL)wait {
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:selector]];
|
||||
[invocation setTarget:delegate];
|
||||
[invocation setSelector:selector];
|
||||
[invocation setArgument:(void*)&self atIndex:2];
|
||||
[invocation setArgument:&obj atIndex:3];
|
||||
[invocation setArgument:&obj2 atIndex:4];
|
||||
[invocation retainArguments];
|
||||
|
||||
[invocation setTarget:delegate];
|
||||
[invocation setSelector:selector];
|
||||
[invocation setArgument:(void *)&self atIndex:2];
|
||||
[invocation setArgument:&obj atIndex:3];
|
||||
[invocation retainArguments];
|
||||
|
||||
[invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:wait];
|
||||
}
|
||||
|
||||
- (void)sendDelegateMethod:(SEL)selector withObject:(id)obj withObject:(id)obj2 waitUntilDone:(BOOL)wait {
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:selector]];
|
||||
[invocation setTarget:delegate];
|
||||
[invocation setSelector:selector];
|
||||
[invocation setArgument:(void *)&self atIndex:2];
|
||||
[invocation setArgument:&obj atIndex:3];
|
||||
[invocation setArgument:&obj2 atIndex:4];
|
||||
[invocation retainArguments];
|
||||
|
||||
[invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:wait];
|
||||
}
|
||||
|
||||
- (void)setPlaybackStatus:(int)status waitUntilDone:(BOOL)wait {
|
||||
currentPlaybackStatus = status;
|
||||
|
||||
- (void)setPlaybackStatus:(int)status waitUntilDone:(BOOL)wait
|
||||
{
|
||||
currentPlaybackStatus = status;
|
||||
|
||||
[self sendDelegateMethod:@selector(audioPlayer:didChangeStatus:userInfo:) withObject:[NSNumber numberWithInt:status] withObject:[bufferChain userInfo] waitUntilDone:wait];
|
||||
}
|
||||
|
||||
- (void)sustainHDCD
|
||||
{
|
||||
[self sendDelegateMethod:@selector(audioPlayer:sustainHDCD:) withObject:[bufferChain userInfo] waitUntilDone:NO];
|
||||
- (void)sustainHDCD {
|
||||
[self sendDelegateMethod:@selector(audioPlayer:sustainHDCD:) withObject:[bufferChain userInfo] waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (void)setPlaybackStatus:(int)status
|
||||
{
|
||||
- (void)setPlaybackStatus:(int)status {
|
||||
[self setPlaybackStatus:status waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (BufferChain *)bufferChain
|
||||
{
|
||||
- (BufferChain *)bufferChain {
|
||||
return bufferChain;
|
||||
}
|
||||
|
||||
- (OutputNode *) output
|
||||
{
|
||||
- (OutputNode *)output {
|
||||
return output;
|
||||
}
|
||||
|
||||
+ (NSArray *)containerTypes
|
||||
{
|
||||
+ (NSArray *)containerTypes {
|
||||
return [[[PluginController sharedPluginController] containers] allKeys];
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes
|
||||
{
|
||||
+ (NSArray *)fileTypes {
|
||||
PluginController *pluginController = [PluginController sharedPluginController];
|
||||
|
||||
|
||||
NSArray *containerTypes = [[pluginController containers] allKeys];
|
||||
NSArray *decoderTypes = [[pluginController decodersByExtension] allKeys];
|
||||
NSArray *metdataReaderTypes = [[pluginController metadataReaders] allKeys];
|
||||
NSArray *propertiesReaderTypes = [[pluginController propertiesReadersByExtension] allKeys];
|
||||
|
||||
|
||||
NSMutableSet *types = [NSMutableSet set];
|
||||
|
||||
|
||||
[types addObjectsFromArray:containerTypes];
|
||||
[types addObjectsFromArray:decoderTypes];
|
||||
[types addObjectsFromArray:metdataReaderTypes];
|
||||
[types addObjectsFromArray:propertiesReaderTypes];
|
||||
|
||||
|
||||
return [types allObjects];
|
||||
}
|
||||
|
||||
+ (NSArray *)schemes
|
||||
{
|
||||
+ (NSArray *)schemes {
|
||||
PluginController *pluginController = [PluginController sharedPluginController];
|
||||
|
||||
|
||||
return [[pluginController sources] allKeys];
|
||||
}
|
||||
|
||||
- (double)volumeUp:(double)amount
|
||||
{
|
||||
BOOL volumeLimit = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"volumeLimit"];
|
||||
const double MAX_VOLUME = (volumeLimit) ? 100.0 : 800.0;
|
||||
|
||||
- (double)volumeUp:(double)amount {
|
||||
BOOL volumeLimit = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"volumeLimit"];
|
||||
const double MAX_VOLUME = (volumeLimit) ? 100.0 : 800.0;
|
||||
|
||||
double newVolume = linearToLogarithmic(logarithmicToLinear(volume + amount, MAX_VOLUME), MAX_VOLUME);
|
||||
if (newVolume > MAX_VOLUME)
|
||||
if(newVolume > MAX_VOLUME)
|
||||
newVolume = MAX_VOLUME;
|
||||
|
||||
|
||||
[self setVolume:newVolume];
|
||||
|
||||
|
||||
// the playbackController needs to know the new volume, so it can update the
|
||||
// volumeSlider accordingly.
|
||||
return newVolume;
|
||||
}
|
||||
|
||||
- (double)volumeDown:(double)amount
|
||||
{
|
||||
BOOL volumeLimit = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"volumeLimit"];
|
||||
const double MAX_VOLUME = (volumeLimit) ? 100.0 : 800.0;
|
||||
- (double)volumeDown:(double)amount {
|
||||
BOOL volumeLimit = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"volumeLimit"];
|
||||
const double MAX_VOLUME = (volumeLimit) ? 100.0 : 800.0;
|
||||
|
||||
double newVolume;
|
||||
if (amount > volume)
|
||||
double newVolume;
|
||||
if(amount > volume)
|
||||
newVolume = 0.0;
|
||||
else
|
||||
newVolume = linearToLogarithmic(logarithmicToLinear(volume - amount, MAX_VOLUME), MAX_VOLUME);
|
||||
|
||||
|
||||
[self setVolume:newVolume];
|
||||
return newVolume;
|
||||
}
|
||||
|
||||
- (void)waitUntilCallbacksExit
|
||||
{
|
||||
// This sucks! And since the thread that's inside the function can be calling
|
||||
// event dispatches, we have to pump the message queue if we're on the main
|
||||
// thread. Damn.
|
||||
if (atomic_load_explicit(&refCount, memory_order_relaxed) != 0) {
|
||||
BOOL mainThread = (dispatch_queue_get_label(dispatch_get_main_queue()) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
|
||||
atomic_store(&resettingNow, true);
|
||||
while (atomic_load_explicit(&refCount, memory_order_relaxed) != 0) {
|
||||
[semaphore signal]; // Gotta poke this periodically
|
||||
if (mainThread)
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]];
|
||||
else
|
||||
usleep(500);
|
||||
}
|
||||
atomic_store(&resettingNow, false);
|
||||
}
|
||||
- (void)waitUntilCallbacksExit {
|
||||
// This sucks! And since the thread that's inside the function can be calling
|
||||
// event dispatches, we have to pump the message queue if we're on the main
|
||||
// thread. Damn.
|
||||
if(atomic_load_explicit(&refCount, memory_order_relaxed) != 0) {
|
||||
BOOL mainThread = (dispatch_queue_get_label(dispatch_get_main_queue()) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
|
||||
atomic_store(&resettingNow, true);
|
||||
while(atomic_load_explicit(&refCount, memory_order_relaxed) != 0) {
|
||||
[semaphore signal]; // Gotta poke this periodically
|
||||
if(mainThread)
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]];
|
||||
else
|
||||
usleep(500);
|
||||
}
|
||||
atomic_store(&resettingNow, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,9 +8,7 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
@interface AudioPropertiesReader : NSObject {
|
||||
|
||||
}
|
||||
|
||||
+ (NSDictionary *)propertiesForURL:(NSURL *)url;
|
||||
|
|
|
@ -13,11 +13,10 @@
|
|||
|
||||
@implementation AudioPropertiesReader
|
||||
|
||||
+ (NSDictionary *)propertiesForURL:(NSURL *)url
|
||||
{
|
||||
@autoreleasepool {
|
||||
return [[PluginController sharedPluginController] propertiesForURL:url];
|
||||
}
|
||||
+ (NSDictionary *)propertiesForURL:(NSURL *)url {
|
||||
@autoreleasepool {
|
||||
return [[PluginController sharedPluginController] propertiesForURL:url];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
@interface AudioSource : NSObject {
|
||||
}
|
||||
|
||||
+ (id<CogSource>) audioSourceForURL:(NSURL *)url;
|
||||
+ (id<CogSource>)audioSourceForURL:(NSURL *)url;
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,11 +8,9 @@
|
|||
|
||||
#import "AudioSource.h"
|
||||
|
||||
|
||||
@implementation AudioSource
|
||||
|
||||
+ (id<CogSource>) audioSourceForURL:(NSURL *)url
|
||||
{
|
||||
+ (id<CogSource>)audioSourceForURL:(NSURL *)url {
|
||||
return [[PluginController sharedPluginController] audioSourceForURL:url];
|
||||
}
|
||||
|
||||
|
|
|
@ -8,32 +8,32 @@
|
|||
#ifndef AudioChunk_h
|
||||
#define AudioChunk_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AudioChunk : NSObject {
|
||||
AudioStreamBasicDescription format;
|
||||
NSMutableData * chunkData;
|
||||
BOOL formatAssigned;
|
||||
BOOL lossless;
|
||||
AudioStreamBasicDescription format;
|
||||
NSMutableData *chunkData;
|
||||
BOOL formatAssigned;
|
||||
BOOL lossless;
|
||||
}
|
||||
|
||||
@property AudioStreamBasicDescription format;
|
||||
@property BOOL lossless;
|
||||
|
||||
- (id) init;
|
||||
- (id)init;
|
||||
|
||||
- (void) assignSamples:(const void *)data frameCount:(size_t)count;
|
||||
- (void)assignSamples:(const void *)data frameCount:(size_t)count;
|
||||
|
||||
- (NSData *) removeSamples:(size_t)frameCount;
|
||||
- (NSData *)removeSamples:(size_t)frameCount;
|
||||
|
||||
- (BOOL) isEmpty;
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
- (size_t) frameCount;
|
||||
- (size_t)frameCount;
|
||||
|
||||
- (double) duration;
|
||||
- (double)duration;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -9,66 +9,66 @@
|
|||
|
||||
@implementation AudioChunk
|
||||
|
||||
- (id) init {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
chunkData = [[NSMutableData alloc] init];
|
||||
formatAssigned = NO;
|
||||
lossless = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
|
||||
if(self) {
|
||||
chunkData = [[NSMutableData alloc] init];
|
||||
formatAssigned = NO;
|
||||
lossless = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@synthesize lossless;
|
||||
|
||||
- (AudioStreamBasicDescription) format {
|
||||
return format;
|
||||
- (AudioStreamBasicDescription)format {
|
||||
return format;
|
||||
}
|
||||
|
||||
- (void) setFormat:(AudioStreamBasicDescription)informat {
|
||||
formatAssigned = YES;
|
||||
format = informat;
|
||||
- (void)setFormat:(AudioStreamBasicDescription)informat {
|
||||
formatAssigned = YES;
|
||||
format = informat;
|
||||
}
|
||||
|
||||
- (void) assignSamples:(const void *)data frameCount:(size_t)count {
|
||||
if (formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
[chunkData appendBytes:data length:bytesPerPacket * count];
|
||||
}
|
||||
- (void)assignSamples:(const void *)data frameCount:(size_t)count {
|
||||
if(formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
[chunkData appendBytes:data length:bytesPerPacket * count];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *) removeSamples:(size_t)frameCount {
|
||||
if (formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
const size_t byteCount = bytesPerPacket * frameCount;
|
||||
NSData * ret = [chunkData subdataWithRange:NSMakeRange(0, byteCount)];
|
||||
[chunkData replaceBytesInRange:NSMakeRange(0, byteCount) withBytes:NULL length:0];
|
||||
return ret;
|
||||
}
|
||||
return [NSData data];
|
||||
- (NSData *)removeSamples:(size_t)frameCount {
|
||||
if(formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
const size_t byteCount = bytesPerPacket * frameCount;
|
||||
NSData *ret = [chunkData subdataWithRange:NSMakeRange(0, byteCount)];
|
||||
[chunkData replaceBytesInRange:NSMakeRange(0, byteCount) withBytes:NULL length:0];
|
||||
return ret;
|
||||
}
|
||||
return [NSData data];
|
||||
}
|
||||
|
||||
- (BOOL) isEmpty {
|
||||
return [chunkData length] == 0;
|
||||
- (BOOL)isEmpty {
|
||||
return [chunkData length] == 0;
|
||||
}
|
||||
|
||||
- (size_t) frameCount {
|
||||
if (formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
return [chunkData length] / bytesPerPacket;
|
||||
}
|
||||
return 0;
|
||||
- (size_t)frameCount {
|
||||
if(formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
return [chunkData length] / bytesPerPacket;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (double) duration {
|
||||
if (formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
const double sampleRate = format.mSampleRate;
|
||||
return (double)([chunkData length] / bytesPerPacket) / sampleRate;
|
||||
}
|
||||
return 0.0;
|
||||
- (double)duration {
|
||||
if(formatAssigned) {
|
||||
const size_t bytesPerPacket = format.mBytesPerPacket;
|
||||
const double sampleRate = format.mSampleRate;
|
||||
return (double)([chunkData length] / bytesPerPacket) / sampleRate;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,37 +8,37 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "InputNode.h"
|
||||
#import "ConverterNode.h"
|
||||
#import "AudioPlayer.h"
|
||||
#import "ConverterNode.h"
|
||||
#import "InputNode.h"
|
||||
|
||||
@interface BufferChain : NSObject {
|
||||
InputNode *inputNode;
|
||||
ConverterNode *converterNode;
|
||||
|
||||
AudioStreamBasicDescription inputFormat;
|
||||
|
||||
|
||||
AudioStreamBasicDescription inputFormat;
|
||||
|
||||
NSURL *streamURL;
|
||||
id userInfo;
|
||||
NSDictionary *rgInfo;
|
||||
|
||||
id finalNode; //Final buffer in the chain.
|
||||
|
||||
NSDictionary *rgInfo;
|
||||
|
||||
id finalNode; // Final buffer in the chain.
|
||||
|
||||
id controller;
|
||||
}
|
||||
|
||||
- (id)initWithController:(id)c;
|
||||
- (void)buildChain;
|
||||
|
||||
- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary*)rgi;
|
||||
- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi;
|
||||
|
||||
//Used when changing tracks to reuse the same decoder
|
||||
- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary*)rgi;
|
||||
// Used when changing tracks to reuse the same decoder
|
||||
- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi;
|
||||
|
||||
//Used when resetting the decoder on seek
|
||||
// Used when resetting the decoder on seek
|
||||
- (BOOL)openWithDecoder:(id<CogDecoder>)decoder
|
||||
withOutputFormat:(AudioStreamBasicDescription)outputFormat
|
||||
withRGInfo:(NSDictionary*)rgi;
|
||||
withOutputFormat:(AudioStreamBasicDescription)outputFormat
|
||||
withRGInfo:(NSDictionary *)rgi;
|
||||
|
||||
- (void)seek:(double)time;
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
|||
- (id)userInfo;
|
||||
- (void)setUserInfo:(id)i;
|
||||
|
||||
- (NSDictionary*)rgInfo;
|
||||
- (NSDictionary *)rgInfo;
|
||||
- (void)setRGInfo:(NSDictionary *)rgi;
|
||||
|
||||
- (NSURL *)streamURL;
|
||||
|
|
|
@ -7,271 +7,241 @@
|
|||
//
|
||||
|
||||
#import "BufferChain.h"
|
||||
#import "OutputNode.h"
|
||||
#import "AudioSource.h"
|
||||
#import "CoreAudioUtils.h"
|
||||
#import "OutputNode.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
@implementation BufferChain
|
||||
|
||||
- (id)initWithController:(id)c
|
||||
{
|
||||
- (id)initWithController:(id)c {
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
if(self) {
|
||||
controller = c;
|
||||
streamURL = nil;
|
||||
userInfo = nil;
|
||||
rgInfo = nil;
|
||||
rgInfo = nil;
|
||||
|
||||
inputNode = nil;
|
||||
converterNode = nil;
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)buildChain
|
||||
{
|
||||
inputNode = nil;
|
||||
converterNode = nil;
|
||||
|
||||
- (void)buildChain {
|
||||
inputNode = nil;
|
||||
converterNode = nil;
|
||||
|
||||
inputNode = [[InputNode alloc] initWithController:self previous:nil];
|
||||
converterNode = [[ConverterNode alloc] initWithController:self previous:inputNode];
|
||||
|
||||
|
||||
finalNode = converterNode;
|
||||
}
|
||||
|
||||
- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi
|
||||
{
|
||||
- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi {
|
||||
[self setStreamURL:url];
|
||||
|
||||
[self buildChain];
|
||||
|
||||
|
||||
id<CogSource> source = [AudioSource audioSourceForURL:url];
|
||||
DLog(@"Opening: %@", url);
|
||||
if (!source || ![source open:url])
|
||||
{
|
||||
if(!source || ![source open:url]) {
|
||||
DLog(@"Couldn't open source...");
|
||||
url = [NSURL URLWithString:@"silence://10"];
|
||||
source = [AudioSource audioSourceForURL:url];
|
||||
if (![source open:url])
|
||||
return NO;
|
||||
url = [NSURL URLWithString:@"silence://10"];
|
||||
source = [AudioSource audioSourceForURL:url];
|
||||
if(![source open:url])
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![inputNode openWithSource:source])
|
||||
return NO;
|
||||
|
||||
NSDictionary * properties = [inputNode properties];
|
||||
|
||||
inputFormat = [inputNode nodeFormat];
|
||||
|
||||
outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
|
||||
|
||||
if (![converterNode setupWithInputFormat:inputFormat outputFormat:outputFormat isLossless:[[properties valueForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
if(![inputNode openWithSource:source])
|
||||
return NO;
|
||||
|
||||
[self setRGInfo:rgi];
|
||||
NSDictionary *properties = [inputNode properties];
|
||||
|
||||
// return NO;
|
||||
inputFormat = [inputNode nodeFormat];
|
||||
|
||||
outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
|
||||
|
||||
if(![converterNode setupWithInputFormat:inputFormat outputFormat:outputFormat isLossless:[[properties valueForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
return NO;
|
||||
|
||||
[self setRGInfo:rgi];
|
||||
|
||||
// return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi
|
||||
{
|
||||
- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat withRGInfo:(NSDictionary *)rgi {
|
||||
DLog(@"New buffer chain!");
|
||||
[self buildChain];
|
||||
|
||||
if (![inputNode openWithDecoder:[i decoder]])
|
||||
return NO;
|
||||
|
||||
NSDictionary * properties = [inputNode properties];
|
||||
|
||||
inputFormat = [inputNode nodeFormat];
|
||||
|
||||
outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
|
||||
|
||||
DLog(@"Input Properties: %@", properties);
|
||||
if (![converterNode setupWithInputFormat:inputFormat outputFormat:outputFormat isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
if(![inputNode openWithDecoder:[i decoder]])
|
||||
return NO;
|
||||
|
||||
[self setRGInfo:rgi];
|
||||
|
||||
NSDictionary *properties = [inputNode properties];
|
||||
|
||||
inputFormat = [inputNode nodeFormat];
|
||||
|
||||
outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
|
||||
|
||||
DLog(@"Input Properties: %@", properties);
|
||||
if(![converterNode setupWithInputFormat:inputFormat outputFormat:outputFormat isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
return NO;
|
||||
|
||||
[self setRGInfo:rgi];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)openWithDecoder:(id<CogDecoder>)decoder
|
||||
withOutputFormat:(AudioStreamBasicDescription)outputFormat
|
||||
withRGInfo:(NSDictionary*)rgi;
|
||||
withOutputFormat:(AudioStreamBasicDescription)outputFormat
|
||||
withRGInfo:(NSDictionary *)rgi;
|
||||
{
|
||||
DLog(@"New buffer chain!");
|
||||
[self buildChain];
|
||||
|
||||
if (![inputNode openWithDecoder:decoder])
|
||||
return NO;
|
||||
|
||||
NSDictionary * properties = [inputNode properties];
|
||||
|
||||
DLog(@"Input Properties: %@", properties);
|
||||
|
||||
inputFormat = [inputNode nodeFormat];
|
||||
DLog(@"New buffer chain!");
|
||||
[self buildChain];
|
||||
|
||||
outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
|
||||
if(![inputNode openWithDecoder:decoder])
|
||||
return NO;
|
||||
|
||||
if (![converterNode setupWithInputFormat:inputFormat outputFormat:outputFormat isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
return NO;
|
||||
|
||||
[self setRGInfo:rgi];
|
||||
|
||||
return YES;
|
||||
NSDictionary *properties = [inputNode properties];
|
||||
|
||||
DLog(@"Input Properties: %@", properties);
|
||||
|
||||
inputFormat = [inputNode nodeFormat];
|
||||
|
||||
outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerFrame = ((outputFormat.mBitsPerChannel + 7) / 8) * outputFormat.mChannelsPerFrame;
|
||||
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
|
||||
|
||||
if(![converterNode setupWithInputFormat:inputFormat outputFormat:outputFormat isLossless:[[properties objectForKey:@"encoding"] isEqualToString:@"lossless"]])
|
||||
return NO;
|
||||
|
||||
[self setRGInfo:rgi];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)launchThreads
|
||||
{
|
||||
- (void)launchThreads {
|
||||
DLog(@"Properties: %@", [inputNode properties]);
|
||||
|
||||
[inputNode launchThread];
|
||||
[converterNode launchThread];
|
||||
}
|
||||
|
||||
- (void)setUserInfo:(id)i
|
||||
{
|
||||
- (void)setUserInfo:(id)i {
|
||||
userInfo = i;
|
||||
}
|
||||
|
||||
- (id)userInfo
|
||||
{
|
||||
- (id)userInfo {
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
- (void)setRGInfo:(NSDictionary *)rgi
|
||||
{
|
||||
rgInfo = rgi;
|
||||
[converterNode setRGInfo:rgi];
|
||||
- (void)setRGInfo:(NSDictionary *)rgi {
|
||||
rgInfo = rgi;
|
||||
[converterNode setRGInfo:rgi];
|
||||
}
|
||||
|
||||
- (NSDictionary *)rgInfo
|
||||
{
|
||||
return rgInfo;
|
||||
- (NSDictionary *)rgInfo {
|
||||
return rgInfo;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[inputNode setShouldContinue:NO];
|
||||
[[inputNode exitAtTheEndOfTheStream] signal];
|
||||
[[inputNode semaphore] signal];
|
||||
[[inputNode exitAtTheEndOfTheStream] wait]; // wait for decoder to be closed (see InputNode's -(void)process )
|
||||
- (void)dealloc {
|
||||
[inputNode setShouldContinue:NO];
|
||||
[[inputNode exitAtTheEndOfTheStream] signal];
|
||||
[[inputNode semaphore] signal];
|
||||
[[inputNode exitAtTheEndOfTheStream] wait]; // wait for decoder to be closed (see InputNode's -(void)process )
|
||||
|
||||
DLog(@"Bufferchain dealloc");
|
||||
}
|
||||
|
||||
- (void)seek:(double)time
|
||||
{
|
||||
long frame = (long) round(time * [[[inputNode properties] objectForKey:@"sampleRate"] floatValue]);
|
||||
- (void)seek:(double)time {
|
||||
long frame = (long)round(time * [[[inputNode properties] objectForKey:@"sampleRate"] floatValue]);
|
||||
|
||||
[inputNode seek:frame];
|
||||
}
|
||||
|
||||
- (BOOL)endOfInputReached
|
||||
{
|
||||
- (BOOL)endOfInputReached {
|
||||
return [controller endOfInputReached:self];
|
||||
}
|
||||
|
||||
- (BOOL)setTrack: (NSURL *)track
|
||||
{
|
||||
- (BOOL)setTrack:(NSURL *)track {
|
||||
return [inputNode setTrack:track];
|
||||
}
|
||||
|
||||
- (void)initialBufferFilled:(id)sender
|
||||
{
|
||||
- (void)initialBufferFilled:(id)sender {
|
||||
DLog(@"INITIAL BUFFER FILLED");
|
||||
[controller launchOutputThread];
|
||||
}
|
||||
|
||||
- (void)inputFormatDidChange:(AudioStreamBasicDescription)format
|
||||
{
|
||||
- (void)inputFormatDidChange:(AudioStreamBasicDescription)format {
|
||||
DLog(@"FORMAT DID CHANGE!");
|
||||
}
|
||||
|
||||
|
||||
- (InputNode *)inputNode
|
||||
{
|
||||
- (InputNode *)inputNode {
|
||||
return inputNode;
|
||||
}
|
||||
|
||||
- (id)finalNode
|
||||
{
|
||||
- (id)finalNode {
|
||||
return finalNode;
|
||||
}
|
||||
|
||||
- (NSURL *)streamURL
|
||||
{
|
||||
- (NSURL *)streamURL {
|
||||
return streamURL;
|
||||
}
|
||||
|
||||
- (void)setStreamURL:(NSURL *)url
|
||||
{
|
||||
- (void)setStreamURL:(NSURL *)url {
|
||||
streamURL = url;
|
||||
}
|
||||
|
||||
- (void)setShouldContinue:(BOOL)s
|
||||
{
|
||||
- (void)setShouldContinue:(BOOL)s {
|
||||
[inputNode setShouldContinue:s];
|
||||
[converterNode setShouldContinue:s];
|
||||
}
|
||||
|
||||
- (BOOL)isRunning
|
||||
{
|
||||
InputNode *node = [self inputNode];
|
||||
if (nil != node && [node shouldContinue] && ![node endOfStream])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
- (BOOL)isRunning {
|
||||
InputNode *node = [self inputNode];
|
||||
if(nil != node && [node shouldContinue] && ![node endOfStream]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (id)controller
|
||||
{
|
||||
return controller;
|
||||
- (id)controller {
|
||||
return controller;
|
||||
}
|
||||
|
||||
- (ConverterNode *)converter
|
||||
{
|
||||
return converterNode;
|
||||
- (ConverterNode *)converter {
|
||||
return converterNode;
|
||||
}
|
||||
|
||||
- (AudioStreamBasicDescription)inputFormat
|
||||
{
|
||||
return inputFormat;
|
||||
- (AudioStreamBasicDescription)inputFormat {
|
||||
return inputFormat;
|
||||
}
|
||||
|
||||
- (double)secondsBuffered
|
||||
{
|
||||
double duration = 0.0;
|
||||
OutputNode * outputNode = (OutputNode *) [controller output];
|
||||
duration += [outputNode secondsBuffered];
|
||||
|
||||
Node * node = [self finalNode];
|
||||
while (node) {
|
||||
duration += [node secondsBuffered];
|
||||
node = [node previousNode];
|
||||
}
|
||||
return duration;
|
||||
- (double)secondsBuffered {
|
||||
double duration = 0.0;
|
||||
OutputNode *outputNode = (OutputNode *)[controller output];
|
||||
duration += [outputNode secondsBuffered];
|
||||
|
||||
Node *node = [self finalNode];
|
||||
while(node) {
|
||||
duration += [node secondsBuffered];
|
||||
node = [node previousNode];
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
- (void)sustainHDCD
|
||||
{
|
||||
OutputNode * outputNode = (OutputNode *) [controller output];
|
||||
[outputNode sustainHDCD];
|
||||
[controller sustainHDCD];
|
||||
- (void)sustainHDCD {
|
||||
OutputNode *outputNode = (OutputNode *)[controller output];
|
||||
[outputNode sustainHDCD];
|
||||
[controller sustainHDCD];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
// Created by Christopher Snowhill on 2/5/22.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "AudioChunk.h"
|
||||
|
||||
|
@ -15,27 +15,27 @@
|
|||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ChunkList : NSObject {
|
||||
NSMutableArray<AudioChunk *> * chunkList;
|
||||
double listDuration;
|
||||
double maxDuration;
|
||||
|
||||
BOOL inAdder;
|
||||
BOOL inRemover;
|
||||
BOOL stopping;
|
||||
NSMutableArray<AudioChunk *> *chunkList;
|
||||
double listDuration;
|
||||
double maxDuration;
|
||||
|
||||
BOOL inAdder;
|
||||
BOOL inRemover;
|
||||
BOOL stopping;
|
||||
}
|
||||
|
||||
@property (readonly) double listDuration;
|
||||
@property (readonly) double maxDuration;
|
||||
@property(readonly) double listDuration;
|
||||
@property(readonly) double maxDuration;
|
||||
|
||||
- (id) initWithMaximumDuration:(double)duration;
|
||||
- (id)initWithMaximumDuration:(double)duration;
|
||||
|
||||
- (void) reset;
|
||||
- (void)reset;
|
||||
|
||||
- (BOOL) isEmpty;
|
||||
- (BOOL) isFull;
|
||||
- (BOOL)isEmpty;
|
||||
- (BOOL)isFull;
|
||||
|
||||
- (void) addChunk:(AudioChunk *)chunk;
|
||||
- (AudioChunk *) removeSamples:(size_t)maxFrameCount;
|
||||
- (void)addChunk:(AudioChunk *)chunk;
|
||||
- (AudioChunk *)removeSamples:(size_t)maxFrameCount;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -12,85 +12,85 @@
|
|||
@synthesize listDuration;
|
||||
@synthesize maxDuration;
|
||||
|
||||
- (id) initWithMaximumDuration:(double)duration {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
chunkList = [[NSMutableArray alloc] init];
|
||||
listDuration = 0.0;
|
||||
maxDuration = duration;
|
||||
|
||||
inAdder = NO;
|
||||
inRemover = NO;
|
||||
stopping = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
- (id)initWithMaximumDuration:(double)duration {
|
||||
self = [super init];
|
||||
|
||||
if(self) {
|
||||
chunkList = [[NSMutableArray alloc] init];
|
||||
listDuration = 0.0;
|
||||
maxDuration = duration;
|
||||
|
||||
inAdder = NO;
|
||||
inRemover = NO;
|
||||
stopping = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
stopping = YES;
|
||||
while (inAdder || inRemover) {
|
||||
usleep(500);
|
||||
}
|
||||
- (void)dealloc {
|
||||
stopping = YES;
|
||||
while(inAdder || inRemover) {
|
||||
usleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) reset {
|
||||
@synchronized (chunkList) {
|
||||
[chunkList removeAllObjects];
|
||||
listDuration = 0.0;
|
||||
}
|
||||
- (void)reset {
|
||||
@synchronized(chunkList) {
|
||||
[chunkList removeAllObjects];
|
||||
listDuration = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) isEmpty {
|
||||
@synchronized (chunkList) {
|
||||
return [chunkList count] == 0;
|
||||
}
|
||||
- (BOOL)isEmpty {
|
||||
@synchronized(chunkList) {
|
||||
return [chunkList count] == 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) isFull {
|
||||
return listDuration >= maxDuration;
|
||||
- (BOOL)isFull {
|
||||
return listDuration >= maxDuration;
|
||||
}
|
||||
|
||||
- (void) addChunk:(AudioChunk *)chunk {
|
||||
if (stopping) return;
|
||||
|
||||
inAdder = YES;
|
||||
|
||||
const double chunkDuration = [chunk duration];
|
||||
|
||||
@synchronized(chunkList) {
|
||||
[chunkList addObject:chunk];
|
||||
listDuration += chunkDuration;
|
||||
}
|
||||
|
||||
inAdder = NO;
|
||||
- (void)addChunk:(AudioChunk *)chunk {
|
||||
if(stopping) return;
|
||||
|
||||
inAdder = YES;
|
||||
|
||||
const double chunkDuration = [chunk duration];
|
||||
|
||||
@synchronized(chunkList) {
|
||||
[chunkList addObject:chunk];
|
||||
listDuration += chunkDuration;
|
||||
}
|
||||
|
||||
inAdder = NO;
|
||||
}
|
||||
|
||||
- (AudioChunk *) removeSamples:(size_t)maxFrameCount {
|
||||
if (stopping) {
|
||||
return [[AudioChunk alloc] init];
|
||||
}
|
||||
|
||||
@synchronized (chunkList) {
|
||||
inRemover = YES;
|
||||
if (![chunkList count])
|
||||
return [[AudioChunk alloc] init];
|
||||
AudioChunk * chunk = [chunkList objectAtIndex:0];
|
||||
if ([chunk frameCount] <= maxFrameCount) {
|
||||
[chunkList removeObjectAtIndex:0];
|
||||
listDuration -= [chunk duration];
|
||||
inRemover = NO;
|
||||
return chunk;
|
||||
}
|
||||
NSData * removedData = [chunk removeSamples:maxFrameCount];
|
||||
AudioChunk * ret = [[AudioChunk alloc] init];
|
||||
[ret setFormat:[chunk format]];
|
||||
[ret assignSamples:[removedData bytes] frameCount:maxFrameCount];
|
||||
listDuration -= [ret duration];
|
||||
inRemover = NO;
|
||||
return ret;
|
||||
}
|
||||
- (AudioChunk *)removeSamples:(size_t)maxFrameCount {
|
||||
if(stopping) {
|
||||
return [[AudioChunk alloc] init];
|
||||
}
|
||||
|
||||
@synchronized(chunkList) {
|
||||
inRemover = YES;
|
||||
if(![chunkList count])
|
||||
return [[AudioChunk alloc] init];
|
||||
AudioChunk *chunk = [chunkList objectAtIndex:0];
|
||||
if([chunk frameCount] <= maxFrameCount) {
|
||||
[chunkList removeObjectAtIndex:0];
|
||||
listDuration -= [chunk duration];
|
||||
inRemover = NO;
|
||||
return chunk;
|
||||
}
|
||||
NSData *removedData = [chunk removeSamples:maxFrameCount];
|
||||
AudioChunk *ret = [[AudioChunk alloc] init];
|
||||
[ret setFormat:[chunk format]];
|
||||
[ret assignSamples:[removedData bytes] frameCount:maxFrameCount];
|
||||
listDuration -= [ret duration];
|
||||
inRemover = NO;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
|
||||
#import <soxr.h>
|
||||
|
||||
|
@ -20,62 +20,62 @@
|
|||
#import "HeadphoneFilter.h"
|
||||
|
||||
@interface ConverterNode : Node {
|
||||
NSDictionary * rgInfo;
|
||||
|
||||
soxr_t soxr;
|
||||
NSDictionary *rgInfo;
|
||||
|
||||
void *inputBuffer;
|
||||
size_t inputBufferSize;
|
||||
size_t inpSize, inpOffset;
|
||||
|
||||
BOOL stopping;
|
||||
BOOL convertEntered;
|
||||
BOOL paused;
|
||||
BOOL outputFormatChanged;
|
||||
|
||||
BOOL skipResampler;
|
||||
|
||||
unsigned int PRIME_LEN_;
|
||||
unsigned int N_samples_to_add_;
|
||||
unsigned int N_samples_to_drop_;
|
||||
|
||||
unsigned int is_preextrapolated_;
|
||||
unsigned int is_postextrapolated_;
|
||||
soxr_t soxr;
|
||||
|
||||
void *inputBuffer;
|
||||
size_t inputBufferSize;
|
||||
size_t inpSize, inpOffset;
|
||||
|
||||
BOOL stopping;
|
||||
BOOL convertEntered;
|
||||
BOOL paused;
|
||||
BOOL outputFormatChanged;
|
||||
|
||||
BOOL skipResampler;
|
||||
|
||||
unsigned int PRIME_LEN_;
|
||||
unsigned int N_samples_to_add_;
|
||||
unsigned int N_samples_to_drop_;
|
||||
|
||||
unsigned int is_preextrapolated_;
|
||||
unsigned int is_postextrapolated_;
|
||||
|
||||
int latencyEaten;
|
||||
int latencyEatenPost;
|
||||
|
||||
double sampleRatio;
|
||||
|
||||
float volumeScale;
|
||||
|
||||
void *floatBuffer;
|
||||
size_t floatBufferSize;
|
||||
size_t floatSize, floatOffset;
|
||||
|
||||
void *extrapolateBuffer;
|
||||
size_t extrapolateBufferSize;
|
||||
|
||||
void **dsd2pcm;
|
||||
size_t dsd2pcmCount;
|
||||
int dsd2pcmLatency;
|
||||
int dsdLatencyEaten;
|
||||
|
||||
BOOL rememberedLossless;
|
||||
|
||||
int latencyEaten;
|
||||
int latencyEatenPost;
|
||||
|
||||
double sampleRatio;
|
||||
|
||||
float volumeScale;
|
||||
|
||||
void *floatBuffer;
|
||||
size_t floatBufferSize;
|
||||
size_t floatSize, floatOffset;
|
||||
|
||||
void *extrapolateBuffer;
|
||||
size_t extrapolateBufferSize;
|
||||
|
||||
void **dsd2pcm;
|
||||
size_t dsd2pcmCount;
|
||||
int dsd2pcmLatency;
|
||||
int dsdLatencyEaten;
|
||||
|
||||
BOOL rememberedLossless;
|
||||
|
||||
AudioStreamBasicDescription inputFormat;
|
||||
AudioStreamBasicDescription floatFormat;
|
||||
AudioStreamBasicDescription dmFloatFormat; // downmixed/upmixed float format
|
||||
AudioStreamBasicDescription floatFormat;
|
||||
AudioStreamBasicDescription dmFloatFormat; // downmixed/upmixed float format
|
||||
AudioStreamBasicDescription outputFormat;
|
||||
|
||||
AudioStreamBasicDescription previousOutputFormat;
|
||||
AudioStreamBasicDescription rememberedInputFormat;
|
||||
RefillNode *refillNode;
|
||||
id __weak originalPreviousNode;
|
||||
|
||||
void *hdcd_decoder;
|
||||
|
||||
HeadphoneFilter *hFilter;
|
||||
|
||||
AudioStreamBasicDescription previousOutputFormat;
|
||||
AudioStreamBasicDescription rememberedInputFormat;
|
||||
RefillNode *refillNode;
|
||||
id __weak originalPreviousNode;
|
||||
|
||||
void *hdcd_decoder;
|
||||
|
||||
HeadphoneFilter *hFilter;
|
||||
}
|
||||
|
||||
@property AudioStreamBasicDescription inputFormat;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,21 +6,20 @@
|
|||
// Copyright 2022 __LoSnoCo__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "HeadphoneFilter.h"
|
||||
|
||||
@interface DownmixProcessor : NSObject {
|
||||
HeadphoneFilter *hFilter;
|
||||
|
||||
AudioStreamBasicDescription inputFormat;
|
||||
AudioStreamBasicDescription outputFormat;
|
||||
HeadphoneFilter *hFilter;
|
||||
|
||||
AudioStreamBasicDescription inputFormat;
|
||||
AudioStreamBasicDescription outputFormat;
|
||||
}
|
||||
|
||||
- (id) initWithInputFormat:(AudioStreamBasicDescription)inf andOutputFormat:(AudioStreamBasicDescription)outf;
|
||||
- (id)initWithInputFormat:(AudioStreamBasicDescription)inf andOutputFormat:(AudioStreamBasicDescription)outf;
|
||||
|
||||
- (void) process:(const void*)inBuffer frameCount:(size_t)frames output:(void *)outBuffer;
|
||||
- (void)process:(const void *)inBuffer frameCount:(size_t)frames output:(void *)outBuffer;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -10,294 +10,272 @@
|
|||
|
||||
#import "Logging.h"
|
||||
|
||||
static const float STEREO_DOWNMIX[8-2][8][2]={
|
||||
/*3.0*/
|
||||
{
|
||||
{0.5858F,0.0F},{0.0F,0.5858F},{0.4142F,0.4142F}
|
||||
},
|
||||
/*quadrophonic*/
|
||||
{
|
||||
{0.4226F,0.0F},{0.0F,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F}
|
||||
},
|
||||
/*5.0*/
|
||||
{
|
||||
{0.651F,0.0F},{0.0F,0.651F},{0.46F,0.46F},{0.5636F,0.3254F},
|
||||
{0.3254F,0.5636F}
|
||||
},
|
||||
/*5.1*/
|
||||
{
|
||||
{0.529F,0.0F},{0.0F,0.529F},{0.3741F,0.3741F},{0.3741F,0.3741F},{0.4582F,0.2645F},
|
||||
{0.2645F,0.4582F}
|
||||
},
|
||||
/*6.1*/
|
||||
{
|
||||
{0.4553F,0.0F},{0.0F,0.4553F},{0.322F,0.322F},{0.322F,0.322F},{0.2788F,0.2788F},
|
||||
{0.3943F,0.2277F},{0.2277F,0.3943F}
|
||||
},
|
||||
/*7.1*/
|
||||
{
|
||||
{0.3886F,0.0F},{0.0F,0.3886F},{0.2748F,0.2748F},{0.2748F,0.2748F},{0.3366F,0.1943F},
|
||||
{0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F}
|
||||
}
|
||||
static const float STEREO_DOWNMIX[8 - 2][8][2] = {
|
||||
/*3.0*/
|
||||
{
|
||||
{ 0.5858F, 0.0F },
|
||||
{ 0.0F, 0.5858F },
|
||||
{ 0.4142F, 0.4142F } },
|
||||
/*quadrophonic*/
|
||||
{
|
||||
{ 0.4226F, 0.0F },
|
||||
{ 0.0F, 0.4226F },
|
||||
{ 0.366F, 0.2114F },
|
||||
{ 0.2114F, 0.336F } },
|
||||
/*5.0*/
|
||||
{
|
||||
{ 0.651F, 0.0F },
|
||||
{ 0.0F, 0.651F },
|
||||
{ 0.46F, 0.46F },
|
||||
{ 0.5636F, 0.3254F },
|
||||
{ 0.3254F, 0.5636F } },
|
||||
/*5.1*/
|
||||
{
|
||||
{ 0.529F, 0.0F },
|
||||
{ 0.0F, 0.529F },
|
||||
{ 0.3741F, 0.3741F },
|
||||
{ 0.3741F, 0.3741F },
|
||||
{ 0.4582F, 0.2645F },
|
||||
{ 0.2645F, 0.4582F } },
|
||||
/*6.1*/
|
||||
{
|
||||
{ 0.4553F, 0.0F },
|
||||
{ 0.0F, 0.4553F },
|
||||
{ 0.322F, 0.322F },
|
||||
{ 0.322F, 0.322F },
|
||||
{ 0.2788F, 0.2788F },
|
||||
{ 0.3943F, 0.2277F },
|
||||
{ 0.2277F, 0.3943F } },
|
||||
/*7.1*/
|
||||
{
|
||||
{ 0.3886F, 0.0F },
|
||||
{ 0.0F, 0.3886F },
|
||||
{ 0.2748F, 0.2748F },
|
||||
{ 0.2748F, 0.2748F },
|
||||
{ 0.3366F, 0.1943F },
|
||||
{ 0.1943F, 0.3366F },
|
||||
{ 0.3366F, 0.1943F },
|
||||
{ 0.1943F, 0.3366F } }
|
||||
};
|
||||
|
||||
static void downmix_to_stereo(const float * inBuffer, int channels, float * outBuffer, size_t count)
|
||||
{
|
||||
if (channels >= 3 && channels <= 8)
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
float left = 0, right = 0;
|
||||
for (int j = 0; j < channels; ++j)
|
||||
{
|
||||
left += inBuffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][0];
|
||||
right += inBuffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][1];
|
||||
}
|
||||
outBuffer[i * 2 + 0] = left;
|
||||
outBuffer[i * 2 + 1] = right;
|
||||
}
|
||||
static void downmix_to_stereo(const float *inBuffer, int channels, float *outBuffer, size_t count) {
|
||||
if(channels >= 3 && channels <= 8)
|
||||
for(size_t i = 0; i < count; ++i) {
|
||||
float left = 0, right = 0;
|
||||
for(int j = 0; j < channels; ++j) {
|
||||
left += inBuffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][0];
|
||||
right += inBuffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][1];
|
||||
}
|
||||
outBuffer[i * 2 + 0] = left;
|
||||
outBuffer[i * 2 + 1] = right;
|
||||
}
|
||||
}
|
||||
|
||||
static void downmix_to_mono(const float * inBuffer, int channels, float * outBuffer, size_t count)
|
||||
{
|
||||
float tempBuffer[count * 2];
|
||||
if (channels >= 3 && channels <= 8)
|
||||
{
|
||||
downmix_to_stereo(inBuffer, channels, tempBuffer, count);
|
||||
inBuffer = tempBuffer;
|
||||
channels = 2;
|
||||
}
|
||||
float invchannels = 1.0 / (float)channels;
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
float sample = 0;
|
||||
for (int j = 0; j < channels; ++j)
|
||||
{
|
||||
sample += inBuffer[i * channels + j];
|
||||
}
|
||||
outBuffer[i] = sample * invchannels;
|
||||
}
|
||||
static void downmix_to_mono(const float *inBuffer, int channels, float *outBuffer, size_t count) {
|
||||
float tempBuffer[count * 2];
|
||||
if(channels >= 3 && channels <= 8) {
|
||||
downmix_to_stereo(inBuffer, channels, tempBuffer, count);
|
||||
inBuffer = tempBuffer;
|
||||
channels = 2;
|
||||
}
|
||||
float invchannels = 1.0 / (float)channels;
|
||||
for(size_t i = 0; i < count; ++i) {
|
||||
float sample = 0;
|
||||
for(int j = 0; j < channels; ++j) {
|
||||
sample += inBuffer[i * channels + j];
|
||||
}
|
||||
outBuffer[i] = sample * invchannels;
|
||||
}
|
||||
}
|
||||
|
||||
static void upmix(const float * inBuffer, int inchannels, float * outBuffer, int outchannels, size_t count)
|
||||
{
|
||||
for (ssize_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (inchannels == 1 && outchannels == 2)
|
||||
{
|
||||
// upmix mono to stereo
|
||||
float sample = inBuffer[i];
|
||||
outBuffer[i * 2 + 0] = sample;
|
||||
outBuffer[i * 2 + 1] = sample;
|
||||
}
|
||||
else if (inchannels == 1 && outchannels == 4)
|
||||
{
|
||||
// upmix mono to quad
|
||||
float sample = inBuffer[i];
|
||||
outBuffer[i * 4 + 0] = sample;
|
||||
outBuffer[i * 4 + 1] = sample;
|
||||
outBuffer[i * 4 + 2] = 0;
|
||||
outBuffer[i * 4 + 3] = 0;
|
||||
}
|
||||
else if (inchannels == 1 && (outchannels == 3 || outchannels >= 5))
|
||||
{
|
||||
// upmix mono to center channel
|
||||
float sample = inBuffer[i];
|
||||
outBuffer[i * outchannels + 2] = sample;
|
||||
for (int j = 0; j < 2; ++j)
|
||||
{
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
for (int j = 3; j < outchannels; ++j)
|
||||
{
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
}
|
||||
else if (inchannels == 4 && outchannels >= 5)
|
||||
{
|
||||
float fl = inBuffer[i * 4 + 0];
|
||||
float fr = inBuffer[i * 4 + 1];
|
||||
float bl = inBuffer[i * 4 + 2];
|
||||
float br = inBuffer[i * 4 + 3];
|
||||
const int skipclfe = (outchannels == 5) ? 1 : 2;
|
||||
outBuffer[i * outchannels + 0] = fl;
|
||||
outBuffer[i * outchannels + 1] = fr;
|
||||
outBuffer[i * outchannels + skipclfe + 2] = bl;
|
||||
outBuffer[i * outchannels + skipclfe + 3] = br;
|
||||
for (int j = 0; j < skipclfe; ++j)
|
||||
{
|
||||
outBuffer[i * outchannels + 2 + j] = 0;
|
||||
}
|
||||
for (int j = 4 + skipclfe; j < outchannels; ++j)
|
||||
{
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
}
|
||||
else if (inchannels == 5 && outchannels >= 6)
|
||||
{
|
||||
float fl = inBuffer[i * 5 + 0];
|
||||
float fr = inBuffer[i * 5 + 1];
|
||||
float c = inBuffer[i * 5 + 2];
|
||||
float bl = inBuffer[i * 5 + 3];
|
||||
float br = inBuffer[i * 5 + 4];
|
||||
outBuffer[i * outchannels + 0] = fl;
|
||||
outBuffer[i * outchannels + 1] = fr;
|
||||
outBuffer[i * outchannels + 2] = c;
|
||||
outBuffer[i * outchannels + 3] = 0;
|
||||
outBuffer[i * outchannels + 4] = bl;
|
||||
outBuffer[i * outchannels + 5] = br;
|
||||
for (int j = 6; j < outchannels; ++j)
|
||||
{
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
}
|
||||
else if (inchannels == 7 && outchannels == 8)
|
||||
{
|
||||
float fl = inBuffer[i * 7 + 0];
|
||||
float fr = inBuffer[i * 7 + 1];
|
||||
float c = inBuffer[i * 7 + 2];
|
||||
float lfe = inBuffer[i * 7 + 3];
|
||||
float sl = inBuffer[i * 7 + 4];
|
||||
float sr = inBuffer[i * 7 + 5];
|
||||
float bc = inBuffer[i * 7 + 6];
|
||||
outBuffer[i * 8 + 0] = fl;
|
||||
outBuffer[i * 8 + 1] = fr;
|
||||
outBuffer[i * 8 + 2] = c;
|
||||
outBuffer[i * 8 + 3] = lfe;
|
||||
outBuffer[i * 8 + 4] = bc;
|
||||
outBuffer[i * 8 + 5] = bc;
|
||||
outBuffer[i * 8 + 6] = sl;
|
||||
outBuffer[i * 8 + 7] = sr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// upmix N channels to N channels plus silence the empty channels
|
||||
float samples[inchannels];
|
||||
for (int j = 0; j < inchannels; ++j)
|
||||
{
|
||||
samples[j] = inBuffer[i * inchannels + j];
|
||||
}
|
||||
for (int j = 0; j < inchannels; ++j)
|
||||
{
|
||||
outBuffer[i * outchannels + j] = samples[j];
|
||||
}
|
||||
for (int j = inchannels; j < outchannels; ++j)
|
||||
{
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
static void upmix(const float *inBuffer, int inchannels, float *outBuffer, int outchannels, size_t count) {
|
||||
for(ssize_t i = 0; i < count; ++i) {
|
||||
if(inchannels == 1 && outchannels == 2) {
|
||||
// upmix mono to stereo
|
||||
float sample = inBuffer[i];
|
||||
outBuffer[i * 2 + 0] = sample;
|
||||
outBuffer[i * 2 + 1] = sample;
|
||||
} else if(inchannels == 1 && outchannels == 4) {
|
||||
// upmix mono to quad
|
||||
float sample = inBuffer[i];
|
||||
outBuffer[i * 4 + 0] = sample;
|
||||
outBuffer[i * 4 + 1] = sample;
|
||||
outBuffer[i * 4 + 2] = 0;
|
||||
outBuffer[i * 4 + 3] = 0;
|
||||
} else if(inchannels == 1 && (outchannels == 3 || outchannels >= 5)) {
|
||||
// upmix mono to center channel
|
||||
float sample = inBuffer[i];
|
||||
outBuffer[i * outchannels + 2] = sample;
|
||||
for(int j = 0; j < 2; ++j) {
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
for(int j = 3; j < outchannels; ++j) {
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
} else if(inchannels == 4 && outchannels >= 5) {
|
||||
float fl = inBuffer[i * 4 + 0];
|
||||
float fr = inBuffer[i * 4 + 1];
|
||||
float bl = inBuffer[i * 4 + 2];
|
||||
float br = inBuffer[i * 4 + 3];
|
||||
const int skipclfe = (outchannels == 5) ? 1 : 2;
|
||||
outBuffer[i * outchannels + 0] = fl;
|
||||
outBuffer[i * outchannels + 1] = fr;
|
||||
outBuffer[i * outchannels + skipclfe + 2] = bl;
|
||||
outBuffer[i * outchannels + skipclfe + 3] = br;
|
||||
for(int j = 0; j < skipclfe; ++j) {
|
||||
outBuffer[i * outchannels + 2 + j] = 0;
|
||||
}
|
||||
for(int j = 4 + skipclfe; j < outchannels; ++j) {
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
} else if(inchannels == 5 && outchannels >= 6) {
|
||||
float fl = inBuffer[i * 5 + 0];
|
||||
float fr = inBuffer[i * 5 + 1];
|
||||
float c = inBuffer[i * 5 + 2];
|
||||
float bl = inBuffer[i * 5 + 3];
|
||||
float br = inBuffer[i * 5 + 4];
|
||||
outBuffer[i * outchannels + 0] = fl;
|
||||
outBuffer[i * outchannels + 1] = fr;
|
||||
outBuffer[i * outchannels + 2] = c;
|
||||
outBuffer[i * outchannels + 3] = 0;
|
||||
outBuffer[i * outchannels + 4] = bl;
|
||||
outBuffer[i * outchannels + 5] = br;
|
||||
for(int j = 6; j < outchannels; ++j) {
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
} else if(inchannels == 7 && outchannels == 8) {
|
||||
float fl = inBuffer[i * 7 + 0];
|
||||
float fr = inBuffer[i * 7 + 1];
|
||||
float c = inBuffer[i * 7 + 2];
|
||||
float lfe = inBuffer[i * 7 + 3];
|
||||
float sl = inBuffer[i * 7 + 4];
|
||||
float sr = inBuffer[i * 7 + 5];
|
||||
float bc = inBuffer[i * 7 + 6];
|
||||
outBuffer[i * 8 + 0] = fl;
|
||||
outBuffer[i * 8 + 1] = fr;
|
||||
outBuffer[i * 8 + 2] = c;
|
||||
outBuffer[i * 8 + 3] = lfe;
|
||||
outBuffer[i * 8 + 4] = bc;
|
||||
outBuffer[i * 8 + 5] = bc;
|
||||
outBuffer[i * 8 + 6] = sl;
|
||||
outBuffer[i * 8 + 7] = sr;
|
||||
} else {
|
||||
// upmix N channels to N channels plus silence the empty channels
|
||||
float samples[inchannels];
|
||||
for(int j = 0; j < inchannels; ++j) {
|
||||
samples[j] = inBuffer[i * inchannels + j];
|
||||
}
|
||||
for(int j = 0; j < inchannels; ++j) {
|
||||
outBuffer[i * outchannels + j] = samples[j];
|
||||
}
|
||||
for(int j = inchannels; j < outchannels; ++j) {
|
||||
outBuffer[i * outchannels + j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@implementation DownmixProcessor
|
||||
|
||||
- (id) initWithInputFormat:(AudioStreamBasicDescription)inf andOutputFormat:(AudioStreamBasicDescription)outf {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
if (inf.mFormatID != kAudioFormatLinearPCM ||
|
||||
(inf.mFormatFlags & kAudioFormatFlagsNativeFloatPacked) != kAudioFormatFlagsNativeFloatPacked ||
|
||||
inf.mBitsPerChannel != 32 ||
|
||||
inf.mBytesPerFrame != (4 * inf.mChannelsPerFrame) ||
|
||||
inf.mBytesPerPacket != inf.mFramesPerPacket * inf.mBytesPerFrame)
|
||||
return nil;
|
||||
|
||||
if (outf.mFormatID != kAudioFormatLinearPCM ||
|
||||
(outf.mFormatFlags & kAudioFormatFlagsNativeFloatPacked) != kAudioFormatFlagsNativeFloatPacked ||
|
||||
outf.mBitsPerChannel != 32 ||
|
||||
outf.mBytesPerFrame != (4 * outf.mChannelsPerFrame) ||
|
||||
outf.mBytesPerPacket != outf.mFramesPerPacket * outf.mBytesPerFrame)
|
||||
return nil;
|
||||
|
||||
inputFormat = inf;
|
||||
outputFormat = outf;
|
||||
- (id)initWithInputFormat:(AudioStreamBasicDescription)inf andOutputFormat:(AudioStreamBasicDescription)outf {
|
||||
self = [super init];
|
||||
|
||||
[self setupVirt];
|
||||
if(self) {
|
||||
if(inf.mFormatID != kAudioFormatLinearPCM ||
|
||||
(inf.mFormatFlags & kAudioFormatFlagsNativeFloatPacked) != kAudioFormatFlagsNativeFloatPacked ||
|
||||
inf.mBitsPerChannel != 32 ||
|
||||
inf.mBytesPerFrame != (4 * inf.mChannelsPerFrame) ||
|
||||
inf.mBytesPerPacket != inf.mFramesPerPacket * inf.mBytesPerFrame)
|
||||
return nil;
|
||||
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.headphoneVirtualization" options:0 context:nil];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hrirPath" options:0 context:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
if(outf.mFormatID != kAudioFormatLinearPCM ||
|
||||
(outf.mFormatFlags & kAudioFormatFlagsNativeFloatPacked) != kAudioFormatFlagsNativeFloatPacked ||
|
||||
outf.mBitsPerChannel != 32 ||
|
||||
outf.mBytesPerFrame != (4 * outf.mChannelsPerFrame) ||
|
||||
outf.mBytesPerPacket != outf.mFramesPerPacket * outf.mBytesPerFrame)
|
||||
return nil;
|
||||
|
||||
inputFormat = inf;
|
||||
outputFormat = outf;
|
||||
|
||||
[self setupVirt];
|
||||
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.headphoneVirtualization" options:0 context:nil];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hrirPath" options:0 context:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.headphoneVirtualization"];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.hrirPath"];
|
||||
- (void)dealloc {
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.headphoneVirtualization"];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.hrirPath"];
|
||||
}
|
||||
|
||||
- (void) setupVirt {
|
||||
@synchronized(hFilter) {
|
||||
hFilter = nil;
|
||||
}
|
||||
|
||||
BOOL hVirt = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"headphoneVirtualization"];
|
||||
|
||||
if (hVirt &&
|
||||
outputFormat.mChannelsPerFrame == 2 &&
|
||||
inputFormat.mChannelsPerFrame >= 1 &&
|
||||
inputFormat.mChannelsPerFrame <= 8) {
|
||||
NSString * userPreset = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] stringForKey:@"hrirPath"];
|
||||
|
||||
NSURL * presetUrl = nil;
|
||||
|
||||
if (userPreset && ![userPreset isEqualToString:@""]) {
|
||||
presetUrl = [NSURL fileURLWithPath:userPreset];
|
||||
if (![HeadphoneFilter validateImpulseFile:presetUrl])
|
||||
presetUrl = nil;
|
||||
}
|
||||
|
||||
if (!presetUrl) {
|
||||
presetUrl = [[NSBundle mainBundle] URLForResource:@"gsx" withExtension:@"wv"];
|
||||
if (![HeadphoneFilter validateImpulseFile:presetUrl])
|
||||
presetUrl = nil;
|
||||
}
|
||||
- (void)setupVirt {
|
||||
@synchronized(hFilter) {
|
||||
hFilter = nil;
|
||||
}
|
||||
|
||||
if (presetUrl) {
|
||||
@synchronized(hFilter) {
|
||||
hFilter = [[HeadphoneFilter alloc] initWithImpulseFile:presetUrl forSampleRate:outputFormat.mSampleRate withInputChannels:inputFormat.mChannelsPerFrame];
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOL hVirt = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"headphoneVirtualization"];
|
||||
|
||||
if(hVirt &&
|
||||
outputFormat.mChannelsPerFrame == 2 &&
|
||||
inputFormat.mChannelsPerFrame >= 1 &&
|
||||
inputFormat.mChannelsPerFrame <= 8) {
|
||||
NSString *userPreset = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] stringForKey:@"hrirPath"];
|
||||
|
||||
NSURL *presetUrl = nil;
|
||||
|
||||
if(userPreset && ![userPreset isEqualToString:@""]) {
|
||||
presetUrl = [NSURL fileURLWithPath:userPreset];
|
||||
if(![HeadphoneFilter validateImpulseFile:presetUrl])
|
||||
presetUrl = nil;
|
||||
}
|
||||
|
||||
if(!presetUrl) {
|
||||
presetUrl = [[NSBundle mainBundle] URLForResource:@"gsx" withExtension:@"wv"];
|
||||
if(![HeadphoneFilter validateImpulseFile:presetUrl])
|
||||
presetUrl = nil;
|
||||
}
|
||||
|
||||
if(presetUrl) {
|
||||
@synchronized(hFilter) {
|
||||
hFilter = [[HeadphoneFilter alloc] initWithImpulseFile:presetUrl forSampleRate:outputFormat.mSampleRate withInputChannels:inputFormat.mChannelsPerFrame];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
{
|
||||
DLog(@"SOMETHING CHANGED!");
|
||||
if ([keyPath isEqualToString:@"values.headphoneVirtualization"] ||
|
||||
[keyPath isEqualToString:@"values.hrirPath"]) {
|
||||
// Reset the converter, without rebuffering
|
||||
[self setupVirt];
|
||||
}
|
||||
context:(void *)context {
|
||||
DLog(@"SOMETHING CHANGED!");
|
||||
if([keyPath isEqualToString:@"values.headphoneVirtualization"] ||
|
||||
[keyPath isEqualToString:@"values.hrirPath"]) {
|
||||
// Reset the converter, without rebuffering
|
||||
[self setupVirt];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) process:(const void *)inBuffer frameCount:(size_t)frames output:(void *)outBuffer {
|
||||
@synchronized (hFilter) {
|
||||
if ( hFilter ) {
|
||||
[hFilter process:(const float *) inBuffer sampleCount:frames toBuffer:(float *) outBuffer];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2 )
|
||||
{
|
||||
downmix_to_stereo( (const float*) inBuffer, inputFormat.mChannelsPerFrame, (float*) outBuffer, frames );
|
||||
}
|
||||
else if ( inputFormat.mChannelsPerFrame > 1 && outputFormat.mChannelsPerFrame == 1 )
|
||||
{
|
||||
downmix_to_mono( (const float*) inBuffer, inputFormat.mChannelsPerFrame, (float*) outBuffer, frames );
|
||||
}
|
||||
else if ( inputFormat.mChannelsPerFrame < outputFormat.mChannelsPerFrame )
|
||||
{
|
||||
upmix( (const float*) inBuffer, inputFormat.mChannelsPerFrame, (float*) outBuffer, outputFormat.mChannelsPerFrame, frames );
|
||||
}
|
||||
else if ( inputFormat.mChannelsPerFrame == outputFormat.mChannelsPerFrame )
|
||||
{
|
||||
memcpy(outBuffer, inBuffer, frames * outputFormat.mBytesPerPacket);
|
||||
}
|
||||
- (void)process:(const void *)inBuffer frameCount:(size_t)frames output:(void *)outBuffer {
|
||||
@synchronized(hFilter) {
|
||||
if(hFilter) {
|
||||
[hFilter process:(const float *)inBuffer sampleCount:frames toBuffer:(float *)outBuffer];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2) {
|
||||
downmix_to_stereo((const float *)inBuffer, inputFormat.mChannelsPerFrame, (float *)outBuffer, frames);
|
||||
} else if(inputFormat.mChannelsPerFrame > 1 && outputFormat.mChannelsPerFrame == 1) {
|
||||
downmix_to_mono((const float *)inBuffer, inputFormat.mChannelsPerFrame, (float *)outBuffer, frames);
|
||||
} else if(inputFormat.mChannelsPerFrame < outputFormat.mChannelsPerFrame) {
|
||||
upmix((const float *)inBuffer, inputFormat.mChannelsPerFrame, (float *)outBuffer, outputFormat.mChannelsPerFrame, frames);
|
||||
} else if(inputFormat.mChannelsPerFrame == outputFormat.mChannelsPerFrame) {
|
||||
memcpy(outBuffer, inBuffer, frames * outputFormat.mBytesPerPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -8,43 +8,43 @@
|
|||
#ifndef HeadphoneFilter_h
|
||||
#define HeadphoneFilter_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Accelerate/Accelerate.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface HeadphoneFilter : NSObject {
|
||||
FFTSetup fftSetup;
|
||||
|
||||
size_t fftSize;
|
||||
size_t fftSizeOver2;
|
||||
size_t log2n;
|
||||
size_t log2nhalf;
|
||||
size_t bufferSize;
|
||||
size_t paddedBufferSize;
|
||||
size_t channelCount;
|
||||
|
||||
COMPLEX_SPLIT signal_fft;
|
||||
COMPLEX_SPLIT input_filtered_signal_per_channel[2];
|
||||
COMPLEX_SPLIT * impulse_responses;
|
||||
|
||||
float * left_result;
|
||||
float * right_result;
|
||||
|
||||
float * left_mix_result;
|
||||
float * right_mix_result;
|
||||
|
||||
float * paddedSignal;
|
||||
|
||||
float * prevOverlapLeft;
|
||||
float * prevOverlapRight;
|
||||
|
||||
int prevOverlapLength;
|
||||
FFTSetup fftSetup;
|
||||
|
||||
size_t fftSize;
|
||||
size_t fftSizeOver2;
|
||||
size_t log2n;
|
||||
size_t log2nhalf;
|
||||
size_t bufferSize;
|
||||
size_t paddedBufferSize;
|
||||
size_t channelCount;
|
||||
|
||||
COMPLEX_SPLIT signal_fft;
|
||||
COMPLEX_SPLIT input_filtered_signal_per_channel[2];
|
||||
COMPLEX_SPLIT *impulse_responses;
|
||||
|
||||
float *left_result;
|
||||
float *right_result;
|
||||
|
||||
float *left_mix_result;
|
||||
float *right_mix_result;
|
||||
|
||||
float *paddedSignal;
|
||||
|
||||
float *prevOverlapLeft;
|
||||
float *prevOverlapRight;
|
||||
|
||||
int prevOverlapLength;
|
||||
}
|
||||
|
||||
+ (BOOL)validateImpulseFile:(NSURL *)url;
|
||||
|
||||
- (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(size_t)channels;
|
||||
|
||||
- (void)process:(const float*)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer;
|
||||
- (void)process:(const float *)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer;
|
||||
|
||||
- (void)reset;
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
//
|
||||
|
||||
#import "HeadphoneFilter.h"
|
||||
#import "AudioSource.h"
|
||||
#import "AudioDecoder.h"
|
||||
#import "AudioSource.h"
|
||||
|
||||
#import <soxr.h>
|
||||
#import <mm_malloc.h>
|
||||
#import <soxr.h>
|
||||
|
||||
#import "lpc.h"
|
||||
#import "util.h"
|
||||
|
@ -19,486 +19,484 @@
|
|||
|
||||
// Symmetrical / no-reverb sets
|
||||
static const int8_t speakers_to_hesuvi_7[8][2][8] = {
|
||||
// mono/center
|
||||
{ { 6 }, { 6 } },
|
||||
// left/right
|
||||
{ { 0, 1 }, { 1, 0 } },
|
||||
// left/right/center
|
||||
{ { 0, 1, 6 }, { 1, 0, 6 } },
|
||||
// left/right/back lef/back right
|
||||
{ { 0, 1, 4, 5 }, { 1, 0, 5, 4 } },
|
||||
// left/right/center/back left/back right
|
||||
{ { 0, 1, 6, 4, 5 }, { 1, 0, 6, 5, 4 } },
|
||||
// left/right/center/lfe(center)/back left/back right
|
||||
{ { 0, 1, 6, 6, 4, 5 }, { 1, 0, 6, 6, 5, 4 } },
|
||||
// left/right/center/lfe(center)/back center(special)/side left/side right
|
||||
{ { 0, 1, 6, 6, -1, 2, 3 }, { 1, 0, 6, 6, -1, 3, 2 } },
|
||||
// left/right/center/lfe(center)/back left/back right/side left/side right
|
||||
{ { 0, 1, 6, 6, 4, 5, 2, 3 }, { 1, 0, 6, 6, 5, 4, 3, 2 } }
|
||||
// mono/center
|
||||
{ { 6 }, { 6 } },
|
||||
// left/right
|
||||
{ { 0, 1 }, { 1, 0 } },
|
||||
// left/right/center
|
||||
{ { 0, 1, 6 }, { 1, 0, 6 } },
|
||||
// left/right/back lef/back right
|
||||
{ { 0, 1, 4, 5 }, { 1, 0, 5, 4 } },
|
||||
// left/right/center/back left/back right
|
||||
{ { 0, 1, 6, 4, 5 }, { 1, 0, 6, 5, 4 } },
|
||||
// left/right/center/lfe(center)/back left/back right
|
||||
{ { 0, 1, 6, 6, 4, 5 }, { 1, 0, 6, 6, 5, 4 } },
|
||||
// left/right/center/lfe(center)/back center(special)/side left/side right
|
||||
{ { 0, 1, 6, 6, -1, 2, 3 }, { 1, 0, 6, 6, -1, 3, 2 } },
|
||||
// left/right/center/lfe(center)/back left/back right/side left/side right
|
||||
{ { 0, 1, 6, 6, 4, 5, 2, 3 }, { 1, 0, 6, 6, 5, 4, 3, 2 } }
|
||||
};
|
||||
|
||||
// Asymmetrical / reverb sets
|
||||
static const int8_t speakers_to_hesuvi_14[8][2][8] = {
|
||||
// mono/center
|
||||
{ { 6 }, { 13 } },
|
||||
// left/right
|
||||
{ { 0, 8 }, { 1, 7 } },
|
||||
// left/right/center
|
||||
{ { 0, 8, 6 }, { 1, 7, 13 } },
|
||||
// left/right/back left/back right
|
||||
{ { 0, 8, 4, 12 }, { 1, 7, 5, 11 } },
|
||||
// left/right/center/back left/back right
|
||||
{ { 0, 8, 6, 4, 12 }, { 1, 7, 13, 5, 11 } },
|
||||
// left/right/center/lfe(center)/back left/back right
|
||||
{ { 0, 8, 6, 6, 4, 12 }, { 1, 7, 13, 13, 5, 11 } },
|
||||
// left/right/center/lfe(center)/back center(special)/side left/side right
|
||||
{ { 0, 8, 6, 6, -1, 2, 10 }, { 1, 7, 13, 13, -1, 3, 9 } },
|
||||
// left/right/center/lfe(center)/back left/back right/side left/side right
|
||||
{ { 0, 8, 6, 6, 4, 12, 2, 10 }, { 1, 7, 13, 13, 5, 11, 3, 9 } }
|
||||
// mono/center
|
||||
{ { 6 }, { 13 } },
|
||||
// left/right
|
||||
{ { 0, 8 }, { 1, 7 } },
|
||||
// left/right/center
|
||||
{ { 0, 8, 6 }, { 1, 7, 13 } },
|
||||
// left/right/back left/back right
|
||||
{ { 0, 8, 4, 12 }, { 1, 7, 5, 11 } },
|
||||
// left/right/center/back left/back right
|
||||
{ { 0, 8, 6, 4, 12 }, { 1, 7, 13, 5, 11 } },
|
||||
// left/right/center/lfe(center)/back left/back right
|
||||
{ { 0, 8, 6, 6, 4, 12 }, { 1, 7, 13, 13, 5, 11 } },
|
||||
// left/right/center/lfe(center)/back center(special)/side left/side right
|
||||
{ { 0, 8, 6, 6, -1, 2, 10 }, { 1, 7, 13, 13, -1, 3, 9 } },
|
||||
// left/right/center/lfe(center)/back left/back right/side left/side right
|
||||
{ { 0, 8, 6, 6, 4, 12, 2, 10 }, { 1, 7, 13, 13, 5, 11, 3, 9 } }
|
||||
};
|
||||
|
||||
+ (BOOL)validateImpulseFile:(NSURL *)url {
|
||||
id<CogSource> source = [AudioSource audioSourceForURL:url];
|
||||
if (!source)
|
||||
return NO;
|
||||
id<CogSource> source = [AudioSource audioSourceForURL:url];
|
||||
if(!source)
|
||||
return NO;
|
||||
|
||||
if (![source open:url])
|
||||
return NO;
|
||||
|
||||
id<CogDecoder> decoder = [AudioDecoder audioDecoderForSource:source];
|
||||
if(![source open:url])
|
||||
return NO;
|
||||
|
||||
if (decoder == nil) {
|
||||
[source close];
|
||||
source = nil;
|
||||
return NO;
|
||||
}
|
||||
id<CogDecoder> decoder = [AudioDecoder audioDecoderForSource:source];
|
||||
|
||||
if (![decoder open:source])
|
||||
{
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
|
||||
int impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
if ([[properties objectForKey:@"floatingPoint"] boolValue] != YES ||
|
||||
[[properties objectForKey:@"bitsPerSample"] intValue] != 32 ||
|
||||
!([[properties objectForKey:@"endian"] isEqualToString:@"host"] ||
|
||||
[[properties objectForKey:@"endian"] isEqualToString:@"little"]) ||
|
||||
(impulseChannels != 14 && impulseChannels != 7))
|
||||
return NO;
|
||||
if(decoder == nil) {
|
||||
[source close];
|
||||
source = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
if(![decoder open:source]) {
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
|
||||
int impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
if([[properties objectForKey:@"floatingPoint"] boolValue] != YES ||
|
||||
[[properties objectForKey:@"bitsPerSample"] intValue] != 32 ||
|
||||
!([[properties objectForKey:@"endian"] isEqualToString:@"host"] ||
|
||||
[[properties objectForKey:@"endian"] isEqualToString:@"little"]) ||
|
||||
(impulseChannels != 14 && impulseChannels != 7))
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(size_t)channels {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
id<CogSource> source = [AudioSource audioSourceForURL:url];
|
||||
if (!source)
|
||||
return nil;
|
||||
self = [super init];
|
||||
|
||||
if (![source open:url])
|
||||
return nil;
|
||||
|
||||
id<CogDecoder> decoder = [AudioDecoder audioDecoderForSource:source];
|
||||
if(self) {
|
||||
id<CogSource> source = [AudioSource audioSourceForURL:url];
|
||||
if(!source)
|
||||
return nil;
|
||||
|
||||
if (decoder == nil) {
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
if(![source open:url])
|
||||
return nil;
|
||||
|
||||
if (![decoder open:source])
|
||||
{
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
|
||||
double sampleRateOfSource = [[properties objectForKey:@"sampleRate"] floatValue];
|
||||
|
||||
int sampleCount = [[properties objectForKey:@"totalFrames"] intValue];
|
||||
int impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
if ([[properties objectForKey:@"floatingPoint"] boolValue] != YES ||
|
||||
[[properties objectForKey:@"bitsPerSample"] intValue] != 32 ||
|
||||
!([[properties objectForKey:@"endian"] isEqualToString:@"host"] ||
|
||||
[[properties objectForKey:@"endian"] isEqualToString:@"little"]) ||
|
||||
(impulseChannels != 14 && impulseChannels != 7)) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
id<CogDecoder> decoder = [AudioDecoder audioDecoderForSource:source];
|
||||
|
||||
float * impulseBuffer = (float *) malloc(sampleCount * sizeof(float) * impulseChannels);
|
||||
if (!impulseBuffer) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([decoder readAudio:impulseBuffer frames:sampleCount] != sampleCount) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
if(decoder == nil) {
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
|
||||
if (sampleRateOfSource != sampleRate) {
|
||||
double sampleRatio = sampleRate / sampleRateOfSource;
|
||||
int resampledCount = (int)ceil((double)sampleCount * sampleRatio);
|
||||
if(![decoder open:source]) {
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_HQ, 0);
|
||||
soxr_io_spec_t io_spec = soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I);
|
||||
soxr_runtime_spec_t runtime_spec = soxr_runtime_spec(0);
|
||||
|
||||
soxr_error_t error;
|
||||
|
||||
unsigned long PRIME_LEN_ = MAX(sampleRateOfSource/20, 1024u);
|
||||
PRIME_LEN_ = MIN(PRIME_LEN_, 16384u);
|
||||
PRIME_LEN_ = MAX(PRIME_LEN_, 2*LPC_ORDER + 1);
|
||||
|
||||
unsigned int N_samples_to_add_ = sampleRateOfSource;
|
||||
unsigned int N_samples_to_drop_ = sampleRate;
|
||||
|
||||
samples_len(&N_samples_to_add_, &N_samples_to_drop_, 20, 8192u);
|
||||
NSDictionary *properties = [decoder properties];
|
||||
|
||||
int resamplerLatencyIn = (int) N_samples_to_add_;
|
||||
int resamplerLatencyOut = (int) N_samples_to_drop_;
|
||||
|
||||
float * tempImpulse = (float *) realloc(impulseBuffer, (sampleCount + resamplerLatencyIn * 2 + 1024) * sizeof(float) * impulseChannels);
|
||||
if (!tempImpulse) {
|
||||
free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
impulseBuffer = tempImpulse;
|
||||
|
||||
resampledCount += resamplerLatencyOut * 2 + 1024;
|
||||
|
||||
float * resampledImpulse = (float *) malloc(resampledCount * sizeof(float) * impulseChannels);
|
||||
if (!resampledImpulse) {
|
||||
free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
size_t prime = MIN(sampleCount, PRIME_LEN_);
|
||||
|
||||
void * extrapolate_buffer = NULL;
|
||||
size_t extrapolate_buffer_size = 0;
|
||||
double sampleRateOfSource = [[properties objectForKey:@"sampleRate"] floatValue];
|
||||
|
||||
memmove(impulseBuffer + resamplerLatencyIn * impulseChannels, impulseBuffer, sampleCount * sizeof(float) * impulseChannels);
|
||||
lpc_extrapolate_bkwd(impulseBuffer + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||
lpc_extrapolate_fwd(impulseBuffer + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||
free(extrapolate_buffer);
|
||||
int sampleCount = [[properties objectForKey:@"totalFrames"] intValue];
|
||||
int impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
size_t inputDone = 0;
|
||||
size_t outputDone = 0;
|
||||
if([[properties objectForKey:@"floatingPoint"] boolValue] != YES ||
|
||||
[[properties objectForKey:@"bitsPerSample"] intValue] != 32 ||
|
||||
!([[properties objectForKey:@"endian"] isEqualToString:@"host"] ||
|
||||
[[properties objectForKey:@"endian"] isEqualToString:@"little"]) ||
|
||||
(impulseChannels != 14 && impulseChannels != 7)) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
error = soxr_oneshot(sampleRateOfSource, sampleRate, impulseChannels, impulseBuffer, sampleCount + N_samples_to_add_ * 2, &inputDone, resampledImpulse, resampledCount, &outputDone, &io_spec, &q_spec, &runtime_spec);
|
||||
|
||||
if (error) {
|
||||
free(resampledImpulse);
|
||||
free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
float *impulseBuffer = (float *)malloc(sampleCount * sizeof(float) * impulseChannels);
|
||||
if(!impulseBuffer) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
outputDone -= N_samples_to_drop_ * 2;
|
||||
if([decoder readAudio:impulseBuffer frames:sampleCount] != sampleCount) {
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
memmove(resampledImpulse, resampledImpulse + N_samples_to_drop_ * impulseChannels, outputDone * sizeof(float) * impulseChannels);
|
||||
[decoder close];
|
||||
decoder = nil;
|
||||
[source close];
|
||||
source = nil;
|
||||
|
||||
free(impulseBuffer);
|
||||
impulseBuffer = resampledImpulse;
|
||||
sampleCount = (int) outputDone;
|
||||
}
|
||||
if(sampleRateOfSource != sampleRate) {
|
||||
double sampleRatio = sampleRate / sampleRateOfSource;
|
||||
int resampledCount = (int)ceil((double)sampleCount * sampleRatio);
|
||||
|
||||
channelCount = channels;
|
||||
soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_HQ, 0);
|
||||
soxr_io_spec_t io_spec = soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I);
|
||||
soxr_runtime_spec_t runtime_spec = soxr_runtime_spec(0);
|
||||
|
||||
bufferSize = 512;
|
||||
fftSize = sampleCount + bufferSize;
|
||||
soxr_error_t error;
|
||||
|
||||
int pow = 1;
|
||||
while (fftSize > 2) { pow++; fftSize /= 2; }
|
||||
fftSize = 2 << pow;
|
||||
unsigned long PRIME_LEN_ = MAX(sampleRateOfSource / 20, 1024u);
|
||||
PRIME_LEN_ = MIN(PRIME_LEN_, 16384u);
|
||||
PRIME_LEN_ = MAX(PRIME_LEN_, 2 * LPC_ORDER + 1);
|
||||
|
||||
float * deinterleavedImpulseBuffer = (float *) _mm_malloc(fftSize * sizeof(float) * impulseChannels, 16);
|
||||
if (!deinterleavedImpulseBuffer) {
|
||||
free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < impulseChannels; ++i) {
|
||||
cblas_scopy(sampleCount, impulseBuffer + i, impulseChannels, deinterleavedImpulseBuffer + i * fftSize, 1);
|
||||
vDSP_vclr(deinterleavedImpulseBuffer + i * fftSize + sampleCount, 1, fftSize - sampleCount);
|
||||
}
|
||||
unsigned int N_samples_to_add_ = sampleRateOfSource;
|
||||
unsigned int N_samples_to_drop_ = sampleRate;
|
||||
|
||||
free(impulseBuffer);
|
||||
samples_len(&N_samples_to_add_, &N_samples_to_drop_, 20, 8192u);
|
||||
|
||||
paddedBufferSize = fftSize;
|
||||
fftSizeOver2 = (fftSize + 1) / 2;
|
||||
log2n = log2f(fftSize);
|
||||
log2nhalf = log2n / 2;
|
||||
int resamplerLatencyIn = (int)N_samples_to_add_;
|
||||
int resamplerLatencyOut = (int)N_samples_to_drop_;
|
||||
|
||||
fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
|
||||
if (!fftSetup) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
float *tempImpulse = (float *)realloc(impulseBuffer, (sampleCount + resamplerLatencyIn * 2 + 1024) * sizeof(float) * impulseChannels);
|
||||
if(!tempImpulse) {
|
||||
free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
paddedSignal = (float *) _mm_malloc(sizeof(float) * paddedBufferSize, 16);
|
||||
if (!paddedSignal) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
signal_fft.realp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
signal_fft.imagp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
if (!signal_fft.realp || !signal_fft.imagp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
input_filtered_signal_per_channel[0].realp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
input_filtered_signal_per_channel[0].imagp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
if (!input_filtered_signal_per_channel[0].realp ||
|
||||
!input_filtered_signal_per_channel[0].imagp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
input_filtered_signal_per_channel[1].realp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
input_filtered_signal_per_channel[1].imagp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
if (!input_filtered_signal_per_channel[1].realp ||
|
||||
!input_filtered_signal_per_channel[1].imagp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
impulseBuffer = tempImpulse;
|
||||
|
||||
impulse_responses = (COMPLEX_SPLIT *) calloc(sizeof(COMPLEX_SPLIT), channels * 2);
|
||||
if (!impulse_responses) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
resampledCount += resamplerLatencyOut * 2 + 1024;
|
||||
|
||||
for (size_t i = 0; i < channels; ++i) {
|
||||
impulse_responses[i * 2 + 0].realp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
impulse_responses[i * 2 + 0].imagp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
impulse_responses[i * 2 + 1].realp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
impulse_responses[i * 2 + 1].imagp = (float *) _mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
|
||||
if (!impulse_responses[i * 2 + 0].realp || !impulse_responses[i * 2 + 0].imagp ||
|
||||
!impulse_responses[i * 2 + 1].realp || !impulse_responses[i * 2 + 1].imagp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
int leftInChannel;
|
||||
int rightInChannel;
|
||||
|
||||
if (impulseChannels == 7) {
|
||||
leftInChannel = speakers_to_hesuvi_7[channels-1][0][i];
|
||||
rightInChannel = speakers_to_hesuvi_7[channels-1][1][i];
|
||||
}
|
||||
else {
|
||||
leftInChannel = speakers_to_hesuvi_14[channels-1][0][i];
|
||||
rightInChannel = speakers_to_hesuvi_14[channels-1][1][i];
|
||||
}
|
||||
|
||||
if (leftInChannel == -1 || rightInChannel == -1) {
|
||||
float * temp;
|
||||
if (impulseChannels == 7) {
|
||||
temp = (float *) malloc(sizeof(float) * fftSize);
|
||||
if (!temp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
float *resampledImpulse = (float *)malloc(resampledCount * sizeof(float) * impulseChannels);
|
||||
if(!resampledImpulse) {
|
||||
free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, temp, 1);
|
||||
vDSP_vadd(temp, 1, deinterleavedImpulseBuffer + 5 * fftSize, 1, temp, 1, fftSize);
|
||||
|
||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
}
|
||||
else {
|
||||
temp = (float *) malloc(sizeof(float) * fftSize * 2);
|
||||
if (!temp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
size_t prime = MIN(sampleCount, PRIME_LEN_);
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, temp, 1);
|
||||
vDSP_vadd(temp, 1, deinterleavedImpulseBuffer + 12 * fftSize, 1, temp, 1, fftSize);
|
||||
void *extrapolate_buffer = NULL;
|
||||
size_t extrapolate_buffer_size = 0;
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 5 * fftSize, 1, temp + fftSize, 1);
|
||||
vDSP_vadd(temp + fftSize, 1, deinterleavedImpulseBuffer + 11 * fftSize, 1, temp + fftSize, 1, fftSize);
|
||||
memmove(impulseBuffer + resamplerLatencyIn * impulseChannels, impulseBuffer, sampleCount * sizeof(float) * impulseChannels);
|
||||
lpc_extrapolate_bkwd(impulseBuffer + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||
lpc_extrapolate_fwd(impulseBuffer + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||
free(extrapolate_buffer);
|
||||
|
||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)(temp + fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
}
|
||||
size_t inputDone = 0;
|
||||
size_t outputDone = 0;
|
||||
|
||||
free(temp);
|
||||
}
|
||||
else {
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + leftInChannel * fftSize), 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + rightInChannel * fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
}
|
||||
|
||||
vDSP_fft_zrip(fftSetup, &impulse_responses[i * 2 + 0], 1, log2n, FFT_FORWARD);
|
||||
vDSP_fft_zrip(fftSetup, &impulse_responses[i * 2 + 1], 1, log2n, FFT_FORWARD);
|
||||
}
|
||||
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
|
||||
left_result = (float *) _mm_malloc(sizeof(float) * fftSize, 16);
|
||||
right_result = (float *) _mm_malloc(sizeof(float) * fftSize, 16);
|
||||
if (!left_result || !right_result)
|
||||
return nil;
|
||||
|
||||
prevOverlapLeft = (float *) _mm_malloc(sizeof(float) * fftSize, 16);
|
||||
prevOverlapRight = (float *) _mm_malloc(sizeof(float) * fftSize, 16);
|
||||
if (!prevOverlapLeft || !prevOverlapRight)
|
||||
return nil;
|
||||
error = soxr_oneshot(sampleRateOfSource, sampleRate, impulseChannels, impulseBuffer, sampleCount + N_samples_to_add_ * 2, &inputDone, resampledImpulse, resampledCount, &outputDone, &io_spec, &q_spec, &runtime_spec);
|
||||
|
||||
left_mix_result = (float *) _mm_malloc(sizeof(float) * fftSize, 16);
|
||||
right_mix_result = (float *) _mm_malloc(sizeof(float) * fftSize, 16);
|
||||
if (!left_mix_result || !right_mix_result)
|
||||
return nil;
|
||||
|
||||
prevOverlapLength = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
if(error) {
|
||||
free(resampledImpulse);
|
||||
free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
outputDone -= N_samples_to_drop_ * 2;
|
||||
|
||||
memmove(resampledImpulse, resampledImpulse + N_samples_to_drop_ * impulseChannels, outputDone * sizeof(float) * impulseChannels);
|
||||
|
||||
free(impulseBuffer);
|
||||
impulseBuffer = resampledImpulse;
|
||||
sampleCount = (int)outputDone;
|
||||
}
|
||||
|
||||
channelCount = channels;
|
||||
|
||||
bufferSize = 512;
|
||||
fftSize = sampleCount + bufferSize;
|
||||
|
||||
int pow = 1;
|
||||
while(fftSize > 2) {
|
||||
pow++;
|
||||
fftSize /= 2;
|
||||
}
|
||||
fftSize = 2 << pow;
|
||||
|
||||
float *deinterleavedImpulseBuffer = (float *)_mm_malloc(fftSize * sizeof(float) * impulseChannels, 16);
|
||||
if(!deinterleavedImpulseBuffer) {
|
||||
free(impulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < impulseChannels; ++i) {
|
||||
cblas_scopy(sampleCount, impulseBuffer + i, impulseChannels, deinterleavedImpulseBuffer + i * fftSize, 1);
|
||||
vDSP_vclr(deinterleavedImpulseBuffer + i * fftSize + sampleCount, 1, fftSize - sampleCount);
|
||||
}
|
||||
|
||||
free(impulseBuffer);
|
||||
|
||||
paddedBufferSize = fftSize;
|
||||
fftSizeOver2 = (fftSize + 1) / 2;
|
||||
log2n = log2f(fftSize);
|
||||
log2nhalf = log2n / 2;
|
||||
|
||||
fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
|
||||
if(!fftSetup) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
paddedSignal = (float *)_mm_malloc(sizeof(float) * paddedBufferSize, 16);
|
||||
if(!paddedSignal) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
signal_fft.realp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
signal_fft.imagp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
if(!signal_fft.realp || !signal_fft.imagp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
input_filtered_signal_per_channel[0].realp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
input_filtered_signal_per_channel[0].imagp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
if(!input_filtered_signal_per_channel[0].realp ||
|
||||
!input_filtered_signal_per_channel[0].imagp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
input_filtered_signal_per_channel[1].realp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
input_filtered_signal_per_channel[1].imagp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
if(!input_filtered_signal_per_channel[1].realp ||
|
||||
!input_filtered_signal_per_channel[1].imagp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
impulse_responses = (COMPLEX_SPLIT *)calloc(sizeof(COMPLEX_SPLIT), channels * 2);
|
||||
if(!impulse_responses) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < channels; ++i) {
|
||||
impulse_responses[i * 2 + 0].realp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
impulse_responses[i * 2 + 0].imagp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
impulse_responses[i * 2 + 1].realp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
impulse_responses[i * 2 + 1].imagp = (float *)_mm_malloc(sizeof(float) * fftSizeOver2, 16);
|
||||
|
||||
if(!impulse_responses[i * 2 + 0].realp || !impulse_responses[i * 2 + 0].imagp ||
|
||||
!impulse_responses[i * 2 + 1].realp || !impulse_responses[i * 2 + 1].imagp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
int leftInChannel;
|
||||
int rightInChannel;
|
||||
|
||||
if(impulseChannels == 7) {
|
||||
leftInChannel = speakers_to_hesuvi_7[channels - 1][0][i];
|
||||
rightInChannel = speakers_to_hesuvi_7[channels - 1][1][i];
|
||||
} else {
|
||||
leftInChannel = speakers_to_hesuvi_14[channels - 1][0][i];
|
||||
rightInChannel = speakers_to_hesuvi_14[channels - 1][1][i];
|
||||
}
|
||||
|
||||
if(leftInChannel == -1 || rightInChannel == -1) {
|
||||
float *temp;
|
||||
if(impulseChannels == 7) {
|
||||
temp = (float *)malloc(sizeof(float) * fftSize);
|
||||
if(!temp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, temp, 1);
|
||||
vDSP_vadd(temp, 1, deinterleavedImpulseBuffer + 5 * fftSize, 1, temp, 1, fftSize);
|
||||
|
||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
} else {
|
||||
temp = (float *)malloc(sizeof(float) * fftSize * 2);
|
||||
if(!temp) {
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, temp, 1);
|
||||
vDSP_vadd(temp, 1, deinterleavedImpulseBuffer + 12 * fftSize, 1, temp, 1, fftSize);
|
||||
|
||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 5 * fftSize, 1, temp + fftSize, 1);
|
||||
vDSP_vadd(temp + fftSize, 1, deinterleavedImpulseBuffer + 11 * fftSize, 1, temp + fftSize, 1, fftSize);
|
||||
|
||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)(temp + fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
}
|
||||
|
||||
free(temp);
|
||||
} else {
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + leftInChannel * fftSize), 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + rightInChannel * fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
||||
}
|
||||
|
||||
vDSP_fft_zrip(fftSetup, &impulse_responses[i * 2 + 0], 1, log2n, FFT_FORWARD);
|
||||
vDSP_fft_zrip(fftSetup, &impulse_responses[i * 2 + 1], 1, log2n, FFT_FORWARD);
|
||||
}
|
||||
|
||||
_mm_free(deinterleavedImpulseBuffer);
|
||||
|
||||
left_result = (float *)_mm_malloc(sizeof(float) * fftSize, 16);
|
||||
right_result = (float *)_mm_malloc(sizeof(float) * fftSize, 16);
|
||||
if(!left_result || !right_result)
|
||||
return nil;
|
||||
|
||||
prevOverlapLeft = (float *)_mm_malloc(sizeof(float) * fftSize, 16);
|
||||
prevOverlapRight = (float *)_mm_malloc(sizeof(float) * fftSize, 16);
|
||||
if(!prevOverlapLeft || !prevOverlapRight)
|
||||
return nil;
|
||||
|
||||
left_mix_result = (float *)_mm_malloc(sizeof(float) * fftSize, 16);
|
||||
right_mix_result = (float *)_mm_malloc(sizeof(float) * fftSize, 16);
|
||||
if(!left_mix_result || !right_mix_result)
|
||||
return nil;
|
||||
|
||||
prevOverlapLength = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (fftSetup) vDSP_destroy_fftsetup(fftSetup);
|
||||
|
||||
_mm_free(paddedSignal);
|
||||
|
||||
_mm_free(signal_fft.realp);
|
||||
_mm_free(signal_fft.imagp);
|
||||
if(fftSetup) vDSP_destroy_fftsetup(fftSetup);
|
||||
|
||||
_mm_free(input_filtered_signal_per_channel[0].realp);
|
||||
_mm_free(input_filtered_signal_per_channel[0].imagp);
|
||||
_mm_free(input_filtered_signal_per_channel[1].realp);
|
||||
_mm_free(input_filtered_signal_per_channel[1].imagp);
|
||||
_mm_free(paddedSignal);
|
||||
|
||||
if (impulse_responses) {
|
||||
for (size_t i = 0; i < channelCount * 2; ++i) {
|
||||
_mm_free(impulse_responses[i].realp);
|
||||
_mm_free(impulse_responses[i].imagp);
|
||||
}
|
||||
free(impulse_responses);
|
||||
}
|
||||
_mm_free(signal_fft.realp);
|
||||
_mm_free(signal_fft.imagp);
|
||||
|
||||
_mm_free(left_result);
|
||||
_mm_free(right_result);
|
||||
|
||||
_mm_free(prevOverlapLeft);
|
||||
_mm_free(prevOverlapRight);
|
||||
_mm_free(input_filtered_signal_per_channel[0].realp);
|
||||
_mm_free(input_filtered_signal_per_channel[0].imagp);
|
||||
_mm_free(input_filtered_signal_per_channel[1].realp);
|
||||
_mm_free(input_filtered_signal_per_channel[1].imagp);
|
||||
|
||||
_mm_free(left_mix_result);
|
||||
_mm_free(right_mix_result);
|
||||
if(impulse_responses) {
|
||||
for(size_t i = 0; i < channelCount * 2; ++i) {
|
||||
_mm_free(impulse_responses[i].realp);
|
||||
_mm_free(impulse_responses[i].imagp);
|
||||
}
|
||||
free(impulse_responses);
|
||||
}
|
||||
|
||||
_mm_free(left_result);
|
||||
_mm_free(right_result);
|
||||
|
||||
_mm_free(prevOverlapLeft);
|
||||
_mm_free(prevOverlapRight);
|
||||
|
||||
_mm_free(left_mix_result);
|
||||
_mm_free(right_mix_result);
|
||||
}
|
||||
|
||||
- (void)process:(const float*)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer {
|
||||
const float scale = 1.0 / (8.0 * (float)fftSize);
|
||||
- (void)process:(const float *)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer {
|
||||
const float scale = 1.0 / (8.0 * (float)fftSize);
|
||||
|
||||
while (count > 0) {
|
||||
size_t countToDo = (count > bufferSize) ? bufferSize : count;
|
||||
while(count > 0) {
|
||||
size_t countToDo = (count > bufferSize) ? bufferSize : count;
|
||||
|
||||
vDSP_vclr(left_mix_result, 1, fftSize);
|
||||
vDSP_vclr(right_mix_result, 1, fftSize);
|
||||
vDSP_vclr(left_mix_result, 1, fftSize);
|
||||
vDSP_vclr(right_mix_result, 1, fftSize);
|
||||
|
||||
for (size_t i = 0; i < channelCount; ++i) {
|
||||
cblas_scopy((int)countToDo, inBuffer + i, (int)channelCount, paddedSignal, 1);
|
||||
for(size_t i = 0; i < channelCount; ++i) {
|
||||
cblas_scopy((int)countToDo, inBuffer + i, (int)channelCount, paddedSignal, 1);
|
||||
|
||||
vDSP_vclr(paddedSignal + countToDo, 1, paddedBufferSize - countToDo);
|
||||
vDSP_vclr(paddedSignal + countToDo, 1, paddedBufferSize - countToDo);
|
||||
|
||||
vDSP_ctoz((DSPComplex *)paddedSignal, 2, &signal_fft, 1, fftSizeOver2);
|
||||
vDSP_ctoz((DSPComplex *)paddedSignal, 2, &signal_fft, 1, fftSizeOver2);
|
||||
|
||||
vDSP_fft_zrip(fftSetup, &signal_fft, 1, log2n, FFT_FORWARD);
|
||||
vDSP_fft_zrip(fftSetup, &signal_fft, 1, log2n, FFT_FORWARD);
|
||||
|
||||
// One channel forward, then multiply and back twice
|
||||
// One channel forward, then multiply and back twice
|
||||
|
||||
float preserveIRNyq = impulse_responses[i * 2 + 0].imagp[0];
|
||||
float preserveSigNyq = signal_fft.imagp[0];
|
||||
impulse_responses[i * 2 + 0].imagp[0] = 0;
|
||||
signal_fft.imagp[0] = 0;
|
||||
float preserveIRNyq = impulse_responses[i * 2 + 0].imagp[0];
|
||||
float preserveSigNyq = signal_fft.imagp[0];
|
||||
impulse_responses[i * 2 + 0].imagp[0] = 0;
|
||||
signal_fft.imagp[0] = 0;
|
||||
|
||||
vDSP_zvmul(&signal_fft, 1, &impulse_responses[i * 2 + 0], 1, &input_filtered_signal_per_channel[0], 1, fftSizeOver2, 1);
|
||||
vDSP_zvmul(&signal_fft, 1, &impulse_responses[i * 2 + 0], 1, &input_filtered_signal_per_channel[0], 1, fftSizeOver2, 1);
|
||||
|
||||
input_filtered_signal_per_channel[0].imagp[0] = preserveIRNyq * preserveSigNyq;
|
||||
impulse_responses[i * 2 + 0].imagp[0] = preserveIRNyq;
|
||||
input_filtered_signal_per_channel[0].imagp[0] = preserveIRNyq * preserveSigNyq;
|
||||
impulse_responses[i * 2 + 0].imagp[0] = preserveIRNyq;
|
||||
|
||||
preserveIRNyq = impulse_responses[i * 2 + 1].imagp[0];
|
||||
impulse_responses[i * 2 + 1].imagp[0] = 0;
|
||||
preserveIRNyq = impulse_responses[i * 2 + 1].imagp[0];
|
||||
impulse_responses[i * 2 + 1].imagp[0] = 0;
|
||||
|
||||
vDSP_zvmul(&signal_fft, 1, &impulse_responses[i * 2 + 1], 1, &input_filtered_signal_per_channel[1], 1, fftSizeOver2, 1);
|
||||
vDSP_zvmul(&signal_fft, 1, &impulse_responses[i * 2 + 1], 1, &input_filtered_signal_per_channel[1], 1, fftSizeOver2, 1);
|
||||
|
||||
input_filtered_signal_per_channel[1].imagp[0] = preserveIRNyq * preserveSigNyq;
|
||||
impulse_responses[i * 2 + 1].imagp[0] = preserveIRNyq;
|
||||
input_filtered_signal_per_channel[1].imagp[0] = preserveIRNyq * preserveSigNyq;
|
||||
impulse_responses[i * 2 + 1].imagp[0] = preserveIRNyq;
|
||||
|
||||
vDSP_fft_zrip(fftSetup, &input_filtered_signal_per_channel[0], 1, log2n, FFT_INVERSE);
|
||||
vDSP_fft_zrip(fftSetup, &input_filtered_signal_per_channel[1], 1, log2n, FFT_INVERSE);
|
||||
vDSP_fft_zrip(fftSetup, &input_filtered_signal_per_channel[0], 1, log2n, FFT_INVERSE);
|
||||
vDSP_fft_zrip(fftSetup, &input_filtered_signal_per_channel[1], 1, log2n, FFT_INVERSE);
|
||||
|
||||
vDSP_ztoc(&input_filtered_signal_per_channel[0], 1, (DSPComplex *)left_result, 2, fftSizeOver2);
|
||||
vDSP_ztoc(&input_filtered_signal_per_channel[1], 1, (DSPComplex *)right_result, 2, fftSizeOver2);
|
||||
vDSP_ztoc(&input_filtered_signal_per_channel[0], 1, (DSPComplex *)left_result, 2, fftSizeOver2);
|
||||
vDSP_ztoc(&input_filtered_signal_per_channel[1], 1, (DSPComplex *)right_result, 2, fftSizeOver2);
|
||||
|
||||
vDSP_vadd(left_mix_result, 1, left_result, 1, left_mix_result, 1, fftSize);
|
||||
vDSP_vadd(right_mix_result, 1, right_result, 1, right_mix_result, 1, fftSize);
|
||||
}
|
||||
vDSP_vadd(left_mix_result, 1, left_result, 1, left_mix_result, 1, fftSize);
|
||||
vDSP_vadd(right_mix_result, 1, right_result, 1, right_mix_result, 1, fftSize);
|
||||
}
|
||||
|
||||
// Integrate previous overlap
|
||||
if (prevOverlapLength) {
|
||||
vDSP_vadd(prevOverlapLeft, 1, left_mix_result, 1, left_mix_result, 1, prevOverlapLength);
|
||||
vDSP_vadd(prevOverlapRight, 1, right_mix_result, 1, right_mix_result, 1, prevOverlapLength);
|
||||
}
|
||||
// Integrate previous overlap
|
||||
if(prevOverlapLength) {
|
||||
vDSP_vadd(prevOverlapLeft, 1, left_mix_result, 1, left_mix_result, 1, prevOverlapLength);
|
||||
vDSP_vadd(prevOverlapRight, 1, right_mix_result, 1, right_mix_result, 1, prevOverlapLength);
|
||||
}
|
||||
|
||||
prevOverlapLength = (int)(fftSize - countToDo);
|
||||
prevOverlapLength = (int)(fftSize - countToDo);
|
||||
|
||||
cblas_scopy(prevOverlapLength, left_mix_result + countToDo, 1, prevOverlapLeft, 1);
|
||||
cblas_scopy(prevOverlapLength, right_mix_result + countToDo, 1, prevOverlapRight, 1);
|
||||
cblas_scopy(prevOverlapLength, left_mix_result + countToDo, 1, prevOverlapLeft, 1);
|
||||
cblas_scopy(prevOverlapLength, right_mix_result + countToDo, 1, prevOverlapRight, 1);
|
||||
|
||||
vDSP_vsmul(left_mix_result, 1, &scale, left_mix_result, 1, countToDo);
|
||||
vDSP_vsmul(right_mix_result, 1, &scale, right_mix_result, 1, countToDo);
|
||||
vDSP_vsmul(left_mix_result, 1, &scale, left_mix_result, 1, countToDo);
|
||||
vDSP_vsmul(right_mix_result, 1, &scale, right_mix_result, 1, countToDo);
|
||||
|
||||
cblas_scopy((int)countToDo, left_mix_result, 1, outBuffer + 0, 2);
|
||||
cblas_scopy((int)countToDo, right_mix_result, 1, outBuffer + 1, 2);
|
||||
cblas_scopy((int)countToDo, left_mix_result, 1, outBuffer + 0, 2);
|
||||
cblas_scopy((int)countToDo, right_mix_result, 1, outBuffer + 1, 2);
|
||||
|
||||
inBuffer += countToDo * channelCount;
|
||||
outBuffer += countToDo * 2;
|
||||
inBuffer += countToDo * channelCount;
|
||||
outBuffer += countToDo * 2;
|
||||
|
||||
count -= countToDo;
|
||||
}
|
||||
count -= countToDo;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
prevOverlapLength = 0;
|
||||
prevOverlapLength = 0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
|
||||
#import "AudioDecoder.h"
|
||||
#import "Node.h"
|
||||
|
@ -20,32 +20,32 @@
|
|||
|
||||
@interface InputNode : Node {
|
||||
id<CogDecoder> decoder;
|
||||
|
||||
int bytesPerSample;
|
||||
|
||||
int bytesPerSample;
|
||||
int bytesPerFrame;
|
||||
BOOL floatingPoint;
|
||||
BOOL swapEndian;
|
||||
|
||||
BOOL floatingPoint;
|
||||
BOOL swapEndian;
|
||||
|
||||
BOOL shouldSeek;
|
||||
long seekFrame;
|
||||
|
||||
BOOL observersAdded;
|
||||
|
||||
Semaphore *exitAtTheEndOfTheStream;
|
||||
BOOL observersAdded;
|
||||
|
||||
Semaphore *exitAtTheEndOfTheStream;
|
||||
}
|
||||
@property(readonly) Semaphore *exitAtTheEndOfTheStream;
|
||||
|
||||
- (BOOL)openWithSource:(id<CogSource>)source;
|
||||
- (BOOL)openWithDecoder:(id<CogDecoder>) d;
|
||||
- (BOOL)openWithDecoder:(id<CogDecoder>)d;
|
||||
|
||||
- (void)process;
|
||||
- (NSDictionary *) properties;
|
||||
- (NSDictionary *)properties;
|
||||
- (void)seek:(long)frame;
|
||||
|
||||
- (void)registerObservers;
|
||||
|
||||
- (BOOL)setTrack:(NSURL *)track;
|
||||
|
||||
- (id<CogDecoder>) decoder;
|
||||
- (id<CogDecoder>)decoder;
|
||||
|
||||
@end
|
||||
|
|
|
@ -7,258 +7,229 @@
|
|||
//
|
||||
|
||||
#import "InputNode.h"
|
||||
#import "BufferChain.h"
|
||||
#import "Plugin.h"
|
||||
#import "CoreAudioUtils.h"
|
||||
#import "AudioPlayer.h"
|
||||
#import "BufferChain.h"
|
||||
#import "CoreAudioUtils.h"
|
||||
#import "OutputNode.h"
|
||||
|
||||
#import "Plugin.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
@implementation InputNode
|
||||
@synthesize exitAtTheEndOfTheStream;
|
||||
|
||||
|
||||
- (id)initWithController:(id)c previous:(id)p {
|
||||
self = [super initWithController:c previous:p];
|
||||
if (self) {
|
||||
exitAtTheEndOfTheStream = [[Semaphore alloc] init];
|
||||
}
|
||||
self = [super initWithController:c previous:p];
|
||||
if(self) {
|
||||
exitAtTheEndOfTheStream = [[Semaphore alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)openWithSource:(id<CogSource>)source
|
||||
{
|
||||
- (BOOL)openWithSource:(id<CogSource>)source {
|
||||
decoder = [AudioDecoder audioDecoderForSource:source];
|
||||
|
||||
if (decoder == nil)
|
||||
if(decoder == nil)
|
||||
return NO;
|
||||
|
||||
[self registerObservers];
|
||||
|
||||
if (![decoder open:source])
|
||||
{
|
||||
if(![decoder open:source]) {
|
||||
ALog(@"Couldn't open decoder...");
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue];
|
||||
int channels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
|
||||
bytesPerFrame = ((bitsPerSample + 7) / 8) * channels;
|
||||
|
||||
nodeFormat = propertiesToASBD(properties);
|
||||
nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"];
|
||||
|
||||
|
||||
nodeFormat = propertiesToASBD(properties);
|
||||
nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"];
|
||||
|
||||
shouldContinue = YES;
|
||||
shouldSeek = NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)openWithDecoder:(id<CogDecoder>) d
|
||||
{
|
||||
- (BOOL)openWithDecoder:(id<CogDecoder>)d {
|
||||
DLog(@"Opening with old decoder: %@", d);
|
||||
decoder = d;
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue];
|
||||
int channels = [[properties objectForKey:@"channels"] intValue];
|
||||
|
||||
|
||||
bytesPerFrame = ((bitsPerSample + 7) / 8) * channels;
|
||||
|
||||
nodeFormat = propertiesToASBD(properties);
|
||||
nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"];
|
||||
|
||||
nodeFormat = propertiesToASBD(properties);
|
||||
nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"];
|
||||
|
||||
[self registerObservers];
|
||||
|
||||
shouldContinue = YES;
|
||||
shouldSeek = NO;
|
||||
|
||||
|
||||
DLog(@"DONES: %@", decoder);
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (void)registerObservers
|
||||
{
|
||||
- (void)registerObservers {
|
||||
DLog(@"REGISTERING OBSERVERS");
|
||||
[decoder addObserver:self
|
||||
forKeyPath:@"properties"
|
||||
options:(NSKeyValueObservingOptionNew)
|
||||
context:NULL];
|
||||
forKeyPath:@"properties"
|
||||
options:(NSKeyValueObservingOptionNew)
|
||||
context:NULL];
|
||||
|
||||
[decoder addObserver:self
|
||||
forKeyPath:@"metadata"
|
||||
options:(NSKeyValueObservingOptionNew)
|
||||
context:NULL];
|
||||
|
||||
observersAdded = YES;
|
||||
forKeyPath:@"metadata"
|
||||
options:(NSKeyValueObservingOptionNew)
|
||||
context:NULL];
|
||||
|
||||
observersAdded = YES;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
{
|
||||
context:(void *)context {
|
||||
DLog(@"SOMETHING CHANGED!");
|
||||
if ([keyPath isEqual:@"properties"]) {
|
||||
DLog(@"Input format changed");
|
||||
// Converter may need resetting, it'll do that when it reaches the new chunks
|
||||
NSDictionary * properties = [decoder properties];
|
||||
nodeFormat = propertiesToASBD(properties);
|
||||
nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"];
|
||||
}
|
||||
else if ([keyPath isEqual:@"metadata"]) {
|
||||
//Inform something of metadata change
|
||||
if([keyPath isEqual:@"properties"]) {
|
||||
DLog(@"Input format changed");
|
||||
// Converter may need resetting, it'll do that when it reaches the new chunks
|
||||
NSDictionary *properties = [decoder properties];
|
||||
nodeFormat = propertiesToASBD(properties);
|
||||
nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"];
|
||||
} else if([keyPath isEqual:@"metadata"]) {
|
||||
// Inform something of metadata change
|
||||
}
|
||||
}
|
||||
|
||||
- (void)process
|
||||
{
|
||||
- (void)process {
|
||||
int amountInBuffer = 0;
|
||||
void *inputBuffer = malloc(CHUNK_SIZE);
|
||||
|
||||
|
||||
BOOL shouldClose = YES;
|
||||
BOOL seekError = NO;
|
||||
|
||||
while ([self shouldContinue] == YES && [self endOfStream] == NO)
|
||||
{
|
||||
if (shouldSeek == YES)
|
||||
{
|
||||
ConverterNode *converter = [[[controller controller] bufferChain] converter];
|
||||
BOOL seekError = NO;
|
||||
|
||||
while([self shouldContinue] == YES && [self endOfStream] == NO) {
|
||||
if(shouldSeek == YES) {
|
||||
ConverterNode *converter = [[[controller controller] bufferChain] converter];
|
||||
DLog(@"SEEKING! Resetting Buffer");
|
||||
|
||||
amountInBuffer = 0;
|
||||
// This resets the converter's buffer
|
||||
[self resetBuffer];
|
||||
[converter resetBuffer];
|
||||
[converter inputFormatDidChange:[[[controller controller] bufferChain] inputFormat]];
|
||||
|
||||
DLog(@"Reset buffer!");
|
||||
amountInBuffer = 0;
|
||||
// This resets the converter's buffer
|
||||
[self resetBuffer];
|
||||
[converter resetBuffer];
|
||||
[converter inputFormatDidChange:[[[controller controller] bufferChain] inputFormat]];
|
||||
|
||||
DLog(@"Reset buffer!");
|
||||
|
||||
DLog(@"SEEKING!");
|
||||
seekError = [decoder seek:seekFrame] < 0;
|
||||
|
||||
DLog(@"SEEKING!");
|
||||
seekError = [decoder seek:seekFrame] < 0;
|
||||
|
||||
shouldSeek = NO;
|
||||
DLog(@"Seeked! Resetting Buffer");
|
||||
initialBufferFilled = NO;
|
||||
}
|
||||
|
||||
if (amountInBuffer < CHUNK_SIZE) {
|
||||
int framesToRead = (CHUNK_SIZE - amountInBuffer)/bytesPerFrame;
|
||||
if(amountInBuffer < CHUNK_SIZE) {
|
||||
int framesToRead = (CHUNK_SIZE - amountInBuffer) / bytesPerFrame;
|
||||
int framesRead = [decoder readAudio:((char *)inputBuffer) + amountInBuffer frames:framesToRead];
|
||||
|
||||
if (framesRead > 0 && !seekError)
|
||||
{
|
||||
amountInBuffer += (framesRead * bytesPerFrame);
|
||||
[self writeData:inputBuffer amount:amountInBuffer];
|
||||
amountInBuffer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (initialBufferFilled == NO) {
|
||||
if(framesRead > 0 && !seekError) {
|
||||
amountInBuffer += (framesRead * bytesPerFrame);
|
||||
[self writeData:inputBuffer amount:amountInBuffer];
|
||||
amountInBuffer = 0;
|
||||
} else {
|
||||
if(initialBufferFilled == NO) {
|
||||
[controller initialBufferFilled:self];
|
||||
}
|
||||
|
||||
|
||||
DLog(@"End of stream? %@", [self properties]);
|
||||
|
||||
endOfStream = YES;
|
||||
shouldClose = [controller endOfInputReached]; //Lets us know if we should keep going or not (occassionally, for track changes within a file)
|
||||
shouldClose = [controller endOfInputReached]; // Lets us know if we should keep going or not (occassionally, for track changes within a file)
|
||||
DLog(@"closing? is %i", shouldClose);
|
||||
|
||||
// wait before exiting, as we might still get seeking request
|
||||
DLog("InputNode: Before wait")
|
||||
[exitAtTheEndOfTheStream waitIndefinitely];
|
||||
DLog("InputNode: After wait, should seek = %d", shouldSeek)
|
||||
if (shouldSeek)
|
||||
{
|
||||
endOfStream = NO;
|
||||
shouldClose = NO;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
// wait before exiting, as we might still get seeking request
|
||||
DLog("InputNode: Before wait")
|
||||
[exitAtTheEndOfTheStream waitIndefinitely];
|
||||
DLog("InputNode: After wait, should seek = %d", shouldSeek) if(shouldSeek) {
|
||||
endOfStream = NO;
|
||||
shouldClose = NO;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldClose)
|
||||
if(shouldClose)
|
||||
[decoder close];
|
||||
|
||||
free(inputBuffer);
|
||||
free(inputBuffer);
|
||||
|
||||
[exitAtTheEndOfTheStream signal];
|
||||
[exitAtTheEndOfTheStream signal];
|
||||
|
||||
DLog("Input node thread stopping");
|
||||
DLog("Input node thread stopping");
|
||||
}
|
||||
|
||||
- (void)seek:(long)frame
|
||||
{
|
||||
- (void)seek:(long)frame {
|
||||
seekFrame = frame;
|
||||
shouldSeek = YES;
|
||||
DLog(@"Should seek!");
|
||||
[semaphore signal];
|
||||
|
||||
if (endOfStream)
|
||||
{
|
||||
[exitAtTheEndOfTheStream signal];
|
||||
}
|
||||
if(endOfStream) {
|
||||
[exitAtTheEndOfTheStream signal];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)setTrack:(NSURL *)track
|
||||
{
|
||||
if ([decoder respondsToSelector:@selector(setTrack:)] && [decoder setTrack:track]) {
|
||||
- (BOOL)setTrack:(NSURL *)track {
|
||||
if([decoder respondsToSelector:@selector(setTrack:)] && [decoder setTrack:track]) {
|
||||
DLog(@"SET TRACK!");
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)removeObservers
|
||||
{
|
||||
if (observersAdded)
|
||||
{
|
||||
[decoder removeObserver:self forKeyPath:@"properties"];
|
||||
[decoder removeObserver:self forKeyPath:@"metadata"];
|
||||
observersAdded = NO;
|
||||
}
|
||||
- (void)removeObservers {
|
||||
if(observersAdded) {
|
||||
[decoder removeObserver:self forKeyPath:@"properties"];
|
||||
[decoder removeObserver:self forKeyPath:@"metadata"];
|
||||
observersAdded = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShouldContinue:(BOOL)s
|
||||
{
|
||||
[super setShouldContinue:s];
|
||||
if (!s)
|
||||
[self removeObservers];
|
||||
- (void)setShouldContinue:(BOOL)s {
|
||||
[super setShouldContinue:s];
|
||||
if(!s)
|
||||
[self removeObservers];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
DLog(@"Input Node dealloc");
|
||||
[self removeObservers];
|
||||
[self removeObservers];
|
||||
}
|
||||
|
||||
- (NSDictionary *) properties
|
||||
{
|
||||
- (NSDictionary *)properties {
|
||||
return [decoder properties];
|
||||
}
|
||||
|
||||
- (id<CogDecoder>) decoder
|
||||
{
|
||||
- (id<CogDecoder>)decoder {
|
||||
return decoder;
|
||||
}
|
||||
|
||||
- (double) secondsBuffered
|
||||
{
|
||||
return [buffer listDuration];
|
||||
- (double)secondsBuffered {
|
||||
return [buffer listDuration];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -6,37 +6,37 @@
|
|||
// Copyright 2006 Vincent Spader. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "ChunkList.h"
|
||||
#import "Semaphore.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define BUFFER_SIZE 1024 * 1024
|
||||
#define CHUNK_SIZE 16 * 1024
|
||||
|
||||
@interface Node : NSObject {
|
||||
ChunkList *buffer;
|
||||
Semaphore *semaphore;
|
||||
|
||||
NSRecursiveLock *accessLock;
|
||||
|
||||
Semaphore *semaphore;
|
||||
|
||||
NSRecursiveLock *accessLock;
|
||||
|
||||
id __weak previousNode;
|
||||
id __weak controller;
|
||||
|
||||
|
||||
BOOL shouldReset;
|
||||
|
||||
BOOL shouldContinue;
|
||||
BOOL endOfStream; //All data is now in buffer
|
||||
|
||||
BOOL shouldContinue;
|
||||
BOOL endOfStream; // All data is now in buffer
|
||||
BOOL initialBufferFilled;
|
||||
|
||||
AudioStreamBasicDescription nodeFormat;
|
||||
BOOL nodeLossless;
|
||||
|
||||
AudioStreamBasicDescription nodeFormat;
|
||||
BOOL nodeLossless;
|
||||
}
|
||||
- (id)initWithController:(id)c previous:(id)p;
|
||||
|
||||
- (void)writeData:(const void *)ptr amount:(size_t)a;
|
||||
- (AudioChunk *)readChunk:(size_t)maxFrames;
|
||||
|
||||
- (void)process; //Should be overwriten by subclass
|
||||
- (void)process; // Should be overwriten by subclass
|
||||
- (void)threadEntry:(id)arg;
|
||||
|
||||
- (void)launchThread;
|
||||
|
@ -51,7 +51,7 @@
|
|||
- (void)setShouldContinue:(BOOL)s;
|
||||
|
||||
- (ChunkList *)buffer;
|
||||
- (void)resetBuffer; //WARNING! DANGER WILL ROBINSON!
|
||||
- (void)resetBuffer; // WARNING! DANGER WILL ROBINSON!
|
||||
|
||||
- (AudioStreamBasicDescription)nodeFormat;
|
||||
- (BOOL)nodeLossless;
|
||||
|
|
|
@ -8,194 +8,169 @@
|
|||
|
||||
#import "Node.h"
|
||||
|
||||
#import "Logging.h"
|
||||
#import "BufferChain.h"
|
||||
#import "Logging.h"
|
||||
|
||||
@implementation Node
|
||||
|
||||
- (id)initWithController:(id)c previous:(id)p
|
||||
{
|
||||
- (id)initWithController:(id)c previous:(id)p {
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
buffer = [[ChunkList alloc] initWithMaximumDuration:3.0];
|
||||
semaphore = [[Semaphore alloc] init];
|
||||
|
||||
accessLock = [[NSRecursiveLock alloc] init];
|
||||
|
||||
if(self) {
|
||||
buffer = [[ChunkList alloc] initWithMaximumDuration:3.0];
|
||||
semaphore = [[Semaphore alloc] init];
|
||||
|
||||
accessLock = [[NSRecursiveLock alloc] init];
|
||||
|
||||
initialBufferFilled = NO;
|
||||
|
||||
|
||||
controller = c;
|
||||
endOfStream = NO;
|
||||
shouldContinue = YES;
|
||||
|
||||
nodeLossless = NO;
|
||||
|
||||
nodeLossless = NO;
|
||||
|
||||
[self setPreviousNode:p];
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (AudioStreamBasicDescription)nodeFormat
|
||||
{
|
||||
return nodeFormat;
|
||||
- (AudioStreamBasicDescription)nodeFormat {
|
||||
return nodeFormat;
|
||||
}
|
||||
|
||||
- (BOOL)nodeLossless
|
||||
{
|
||||
return nodeLossless;
|
||||
- (BOOL)nodeLossless {
|
||||
return nodeLossless;
|
||||
}
|
||||
|
||||
- (void)writeData:(const void *)ptr amount:(size_t)amount
|
||||
{
|
||||
[accessLock lock];
|
||||
|
||||
AudioChunk * chunk = [[AudioChunk alloc] init];
|
||||
[chunk setFormat:nodeFormat];
|
||||
[chunk setLossless:nodeLossless];
|
||||
[chunk assignSamples:ptr frameCount:amount / nodeFormat.mBytesPerPacket];
|
||||
|
||||
const double chunkDuration = [chunk duration];
|
||||
double durationLeft = [buffer maxDuration] - [buffer listDuration];
|
||||
|
||||
while (shouldContinue == YES && chunkDuration > durationLeft)
|
||||
{
|
||||
if (durationLeft < chunkDuration) {
|
||||
if (initialBufferFilled == NO) {
|
||||
initialBufferFilled = YES;
|
||||
if ([controller respondsToSelector:@selector(initialBufferFilled:)])
|
||||
[controller performSelector:@selector(initialBufferFilled:) withObject:self];
|
||||
}
|
||||
}
|
||||
|
||||
if (durationLeft < chunkDuration || shouldReset) {
|
||||
[accessLock unlock];
|
||||
[semaphore wait];
|
||||
[accessLock lock];
|
||||
}
|
||||
|
||||
durationLeft = [buffer maxDuration] - [buffer listDuration];
|
||||
}
|
||||
|
||||
[buffer addChunk:chunk];
|
||||
|
||||
[accessLock unlock];
|
||||
- (void)writeData:(const void *)ptr amount:(size_t)amount {
|
||||
[accessLock lock];
|
||||
|
||||
AudioChunk *chunk = [[AudioChunk alloc] init];
|
||||
[chunk setFormat:nodeFormat];
|
||||
[chunk setLossless:nodeLossless];
|
||||
[chunk assignSamples:ptr frameCount:amount / nodeFormat.mBytesPerPacket];
|
||||
|
||||
const double chunkDuration = [chunk duration];
|
||||
double durationLeft = [buffer maxDuration] - [buffer listDuration];
|
||||
|
||||
while(shouldContinue == YES && chunkDuration > durationLeft) {
|
||||
if(durationLeft < chunkDuration) {
|
||||
if(initialBufferFilled == NO) {
|
||||
initialBufferFilled = YES;
|
||||
if([controller respondsToSelector:@selector(initialBufferFilled:)])
|
||||
[controller performSelector:@selector(initialBufferFilled:) withObject:self];
|
||||
}
|
||||
}
|
||||
|
||||
if(durationLeft < chunkDuration || shouldReset) {
|
||||
[accessLock unlock];
|
||||
[semaphore wait];
|
||||
[accessLock lock];
|
||||
}
|
||||
|
||||
durationLeft = [buffer maxDuration] - [buffer listDuration];
|
||||
}
|
||||
|
||||
[buffer addChunk:chunk];
|
||||
|
||||
[accessLock unlock];
|
||||
}
|
||||
|
||||
//Should be overwriten by subclass.
|
||||
- (void)process
|
||||
{
|
||||
// Should be overwriten by subclass.
|
||||
- (void)process {
|
||||
}
|
||||
|
||||
- (void)threadEntry:(id)arg
|
||||
{
|
||||
@autoreleasepool {
|
||||
[self process];
|
||||
}
|
||||
- (void)threadEntry:(id)arg {
|
||||
@autoreleasepool {
|
||||
[self process];
|
||||
}
|
||||
}
|
||||
|
||||
- (AudioChunk *)readChunk:(size_t)maxFrames
|
||||
{
|
||||
[accessLock lock];
|
||||
|
||||
if ([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES)
|
||||
{
|
||||
endOfStream = YES;
|
||||
[accessLock unlock];
|
||||
return [[AudioChunk alloc] init];
|
||||
}
|
||||
|
||||
if ([previousNode shouldReset] == YES) {
|
||||
- (AudioChunk *)readChunk:(size_t)maxFrames {
|
||||
[accessLock lock];
|
||||
|
||||
if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) {
|
||||
endOfStream = YES;
|
||||
[accessLock unlock];
|
||||
return [[AudioChunk alloc] init];
|
||||
}
|
||||
|
||||
if([previousNode shouldReset] == YES) {
|
||||
[buffer reset];
|
||||
|
||||
shouldReset = YES;
|
||||
[previousNode setShouldReset: NO];
|
||||
|
||||
[[previousNode semaphore] signal];
|
||||
[previousNode setShouldReset:NO];
|
||||
|
||||
[[previousNode semaphore] signal];
|
||||
}
|
||||
|
||||
AudioChunk * ret = [[previousNode buffer] removeSamples:maxFrames];
|
||||
AudioChunk *ret = [[previousNode buffer] removeSamples:maxFrames];
|
||||
|
||||
[accessLock unlock];
|
||||
|
||||
if ([ret frameCount])
|
||||
{
|
||||
[[previousNode semaphore] signal];
|
||||
}
|
||||
[accessLock unlock];
|
||||
|
||||
return ret;
|
||||
if([ret frameCount]) {
|
||||
[[previousNode semaphore] signal];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void)launchThread
|
||||
{
|
||||
- (void)launchThread {
|
||||
[NSThread detachNewThreadSelector:@selector(threadEntry:) toTarget:self withObject:nil];
|
||||
}
|
||||
|
||||
- (void)setPreviousNode:(id)p
|
||||
{
|
||||
- (void)setPreviousNode:(id)p {
|
||||
previousNode = p;
|
||||
}
|
||||
|
||||
- (id)previousNode
|
||||
{
|
||||
- (id)previousNode {
|
||||
return previousNode;
|
||||
}
|
||||
|
||||
- (BOOL)shouldContinue
|
||||
{
|
||||
- (BOOL)shouldContinue {
|
||||
return shouldContinue;
|
||||
}
|
||||
|
||||
- (void)setShouldContinue:(BOOL)s
|
||||
{
|
||||
- (void)setShouldContinue:(BOOL)s {
|
||||
shouldContinue = s;
|
||||
}
|
||||
|
||||
- (ChunkList *)buffer
|
||||
{
|
||||
- (ChunkList *)buffer {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
- (void)resetBuffer
|
||||
{
|
||||
shouldReset = YES; //Will reset on next write.
|
||||
if (previousNode == nil) {
|
||||
[accessLock lock];
|
||||
- (void)resetBuffer {
|
||||
shouldReset = YES; // Will reset on next write.
|
||||
if(previousNode == nil) {
|
||||
[accessLock lock];
|
||||
[buffer reset];
|
||||
[accessLock unlock];
|
||||
[accessLock unlock];
|
||||
}
|
||||
}
|
||||
|
||||
- (Semaphore *)semaphore
|
||||
{
|
||||
- (Semaphore *)semaphore {
|
||||
return semaphore;
|
||||
}
|
||||
|
||||
- (BOOL)endOfStream
|
||||
{
|
||||
- (BOOL)endOfStream {
|
||||
return endOfStream;
|
||||
}
|
||||
|
||||
- (void)setEndOfStream:(BOOL)e
|
||||
{
|
||||
- (void)setEndOfStream:(BOOL)e {
|
||||
endOfStream = e;
|
||||
}
|
||||
|
||||
- (void)setShouldReset:(BOOL)s
|
||||
{
|
||||
- (void)setShouldReset:(BOOL)s {
|
||||
shouldReset = s;
|
||||
}
|
||||
- (BOOL)shouldReset
|
||||
{
|
||||
- (BOOL)shouldReset {
|
||||
return shouldReset;
|
||||
}
|
||||
|
||||
// Buffering nodes should implement this
|
||||
- (double)secondsBuffered
|
||||
{
|
||||
return 0.0;
|
||||
- (double)secondsBuffered {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,22 +8,22 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
|
||||
#import "Node.h"
|
||||
#import "OutputCoreAudio.h"
|
||||
|
||||
@interface OutputNode : Node {
|
||||
AudioStreamBasicDescription format;
|
||||
|
||||
|
||||
double amountPlayed;
|
||||
double sampleRatio;
|
||||
double sampleRatio;
|
||||
OutputCoreAudio *output;
|
||||
|
||||
BOOL paused;
|
||||
BOOL started;
|
||||
|
||||
BOOL paused;
|
||||
BOOL started;
|
||||
}
|
||||
|
||||
- (void)beginEqualizer:(AudioUnit)eq;
|
||||
|
@ -49,9 +49,9 @@
|
|||
- (AudioChunk *)readChunk:(size_t)amount;
|
||||
|
||||
- (void)setFormat:(AudioStreamBasicDescription *)f;
|
||||
- (AudioStreamBasicDescription) format;
|
||||
- (AudioStreamBasicDescription)format;
|
||||
|
||||
- (void)setVolume:(double) v;
|
||||
- (void)setVolume:(double)v;
|
||||
|
||||
- (void)setShouldContinue:(BOOL)s;
|
||||
|
||||
|
|
|
@ -7,118 +7,102 @@
|
|||
//
|
||||
|
||||
#import "OutputNode.h"
|
||||
#import "OutputCoreAudio.h"
|
||||
#import "AudioPlayer.h"
|
||||
#import "BufferChain.h"
|
||||
#import "OutputCoreAudio.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
@implementation OutputNode
|
||||
|
||||
- (void)setup
|
||||
{
|
||||
- (void)setup {
|
||||
amountPlayed = 0.0;
|
||||
sampleRatio = 0.0;
|
||||
|
||||
paused = YES;
|
||||
started = NO;
|
||||
sampleRatio = 0.0;
|
||||
|
||||
paused = YES;
|
||||
started = NO;
|
||||
|
||||
output = [[OutputCoreAudio alloc] initWithController:self];
|
||||
|
||||
|
||||
[output setup];
|
||||
}
|
||||
|
||||
- (void)seek:(double)time
|
||||
{
|
||||
// [output pause];
|
||||
[self resetBuffer];
|
||||
- (void)seek:(double)time {
|
||||
// [output pause];
|
||||
[self resetBuffer];
|
||||
|
||||
amountPlayed = time;
|
||||
}
|
||||
|
||||
- (void)process
|
||||
{
|
||||
paused = NO;
|
||||
[output start];
|
||||
- (void)process {
|
||||
paused = NO;
|
||||
[output start];
|
||||
}
|
||||
|
||||
- (void)pause
|
||||
{
|
||||
paused = YES;
|
||||
- (void)pause {
|
||||
paused = YES;
|
||||
[output pause];
|
||||
}
|
||||
|
||||
- (void)resume
|
||||
{
|
||||
paused = NO;
|
||||
- (void)resume {
|
||||
paused = NO;
|
||||
[output resume];
|
||||
}
|
||||
|
||||
- (void)incrementAmountPlayed:(double)seconds
|
||||
{
|
||||
amountPlayed += seconds;
|
||||
- (void)incrementAmountPlayed:(double)seconds {
|
||||
amountPlayed += seconds;
|
||||
}
|
||||
|
||||
- (void)resetAmountPlayed
|
||||
{
|
||||
amountPlayed = 0;
|
||||
- (void)resetAmountPlayed {
|
||||
amountPlayed = 0;
|
||||
}
|
||||
|
||||
- (void)endOfInputPlayed
|
||||
{
|
||||
[controller endOfInputPlayed];
|
||||
- (void)endOfInputPlayed {
|
||||
[controller endOfInputPlayed];
|
||||
}
|
||||
|
||||
- (BOOL)chainQueueHasTracks
|
||||
{
|
||||
return [controller chainQueueHasTracks];
|
||||
- (BOOL)chainQueueHasTracks {
|
||||
return [controller chainQueueHasTracks];
|
||||
}
|
||||
|
||||
- (double)secondsBuffered
|
||||
{
|
||||
return [buffer listDuration];
|
||||
- (double)secondsBuffered {
|
||||
return [buffer listDuration];
|
||||
}
|
||||
|
||||
- (AudioChunk *)readChunk:(size_t)amount
|
||||
{
|
||||
@autoreleasepool {
|
||||
[self setPreviousNode:[[controller bufferChain] finalNode]];
|
||||
|
||||
AudioChunk * ret = [super readChunk:amount];
|
||||
|
||||
/* if (n == 0) {
|
||||
DLog(@"Output Buffer dry!");
|
||||
- (AudioChunk *)readChunk:(size_t)amount {
|
||||
@autoreleasepool {
|
||||
[self setPreviousNode:[[controller bufferChain] finalNode]];
|
||||
|
||||
AudioChunk *ret = [super readChunk:amount];
|
||||
|
||||
/* if (n == 0) {
|
||||
DLog(@"Output Buffer dry!");
|
||||
}
|
||||
*/
|
||||
return ret;
|
||||
}
|
||||
*/
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
- (double)amountPlayed
|
||||
{
|
||||
return amountPlayed;
|
||||
- (double)amountPlayed {
|
||||
return amountPlayed;
|
||||
}
|
||||
|
||||
- (AudioStreamBasicDescription) format
|
||||
{
|
||||
- (AudioStreamBasicDescription)format {
|
||||
return format;
|
||||
}
|
||||
|
||||
- (void)setFormat:(AudioStreamBasicDescription *)f
|
||||
{
|
||||
- (void)setFormat:(AudioStreamBasicDescription *)f {
|
||||
format = *f;
|
||||
// Calculate a ratio and add to double(seconds) instead, as format may change
|
||||
// double oldSampleRatio = sampleRatio;
|
||||
sampleRatio = 1.0 / (format.mSampleRate * format.mBytesPerPacket);
|
||||
BufferChain *bufferChain = [controller bufferChain];
|
||||
if (bufferChain)
|
||||
{
|
||||
ConverterNode *converter = [bufferChain converter];
|
||||
if (converter)
|
||||
{
|
||||
// This clears the resampler buffer, but not the input buffer
|
||||
// We also have to jump the play position ahead accounting for
|
||||
// the data we are flushing
|
||||
// Calculate a ratio and add to double(seconds) instead, as format may change
|
||||
// double oldSampleRatio = sampleRatio;
|
||||
sampleRatio = 1.0 / (format.mSampleRate * format.mBytesPerPacket);
|
||||
BufferChain *bufferChain = [controller bufferChain];
|
||||
if(bufferChain) {
|
||||
ConverterNode *converter = [bufferChain converter];
|
||||
if(converter) {
|
||||
// This clears the resampler buffer, but not the input buffer
|
||||
// We also have to jump the play position ahead accounting for
|
||||
// the data we are flushing
|
||||
#if 0
|
||||
// We no longer need to do this, because outputchanged converter
|
||||
// now uses the RefillNode to slap the previous samples into
|
||||
|
@ -126,54 +110,46 @@
|
|||
if (oldSampleRatio)
|
||||
amountPlayed += oldSampleRatio * [[converter buffer] bufferedLength];
|
||||
#endif
|
||||
[converter setOutputFormat:format];
|
||||
[converter inputFormatDidChange:[bufferChain inputFormat]];
|
||||
}
|
||||
}
|
||||
[converter setOutputFormat:format];
|
||||
[converter inputFormatDidChange:[bufferChain inputFormat]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)close
|
||||
{
|
||||
- (void)close {
|
||||
[output stop];
|
||||
output = nil;
|
||||
output = nil;
|
||||
}
|
||||
|
||||
- (void)setVolume:(double) v
|
||||
{
|
||||
- (void)setVolume:(double)v {
|
||||
[output setVolume:v];
|
||||
}
|
||||
|
||||
- (void)setShouldContinue:(BOOL)s
|
||||
{
|
||||
- (void)setShouldContinue:(BOOL)s {
|
||||
[super setShouldContinue:s];
|
||||
|
||||
// if (s == NO)
|
||||
// [output stop];
|
||||
|
||||
// if (s == NO)
|
||||
// [output stop];
|
||||
}
|
||||
|
||||
- (BOOL)isPaused
|
||||
{
|
||||
return paused;
|
||||
- (BOOL)isPaused {
|
||||
return paused;
|
||||
}
|
||||
|
||||
- (void)beginEqualizer:(AudioUnit)eq
|
||||
{
|
||||
[controller beginEqualizer:eq];
|
||||
- (void)beginEqualizer:(AudioUnit)eq {
|
||||
[controller beginEqualizer:eq];
|
||||
}
|
||||
|
||||
- (void)refreshEqualizer:(AudioUnit)eq
|
||||
{
|
||||
[controller refreshEqualizer:eq];
|
||||
- (void)refreshEqualizer:(AudioUnit)eq {
|
||||
[controller refreshEqualizer:eq];
|
||||
}
|
||||
|
||||
- (void)endEqualizer:(AudioUnit)eq
|
||||
{
|
||||
[controller endEqualizer:eq];
|
||||
- (void)endEqualizer:(AudioUnit)eq {
|
||||
[controller endEqualizer:eq];
|
||||
}
|
||||
|
||||
- (void)sustainHDCD
|
||||
{
|
||||
[output sustainHDCD];
|
||||
- (void)sustainHDCD {
|
||||
[output sustainHDCD];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
|
||||
#import "Node.h"
|
||||
#import "Plugin.h"
|
||||
|
@ -18,9 +18,9 @@
|
|||
#define INPUT_NODE_SEEK
|
||||
|
||||
@interface RefillNode : Node {
|
||||
// This node just slaps pre-converted data into its buffer for re-buffering
|
||||
// This node just slaps pre-converted data into its buffer for re-buffering
|
||||
}
|
||||
|
||||
- (void) setFormat:(AudioStreamBasicDescription)format;
|
||||
- (void)setFormat:(AudioStreamBasicDescription)format;
|
||||
|
||||
@end
|
||||
|
|
|
@ -6,44 +6,40 @@
|
|||
// Copyright 2022 __LoSnoCo__. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Plugin.h"
|
||||
#import "RefillNode.h"
|
||||
#import "Plugin.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
@implementation RefillNode
|
||||
|
||||
- (id)initWithController:(id)c previous:(id)p
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
// This special node should be able to handle up to four buffers
|
||||
buffer = [[ChunkList alloc] initWithMaximumDuration:12.0];
|
||||
semaphore = [[Semaphore alloc] init];
|
||||
|
||||
initialBufferFilled = NO;
|
||||
|
||||
controller = c;
|
||||
endOfStream = NO;
|
||||
shouldContinue = YES;
|
||||
|
||||
nodeLossless = NO;
|
||||
- (id)initWithController:(id)c previous:(id)p {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
// This special node should be able to handle up to four buffers
|
||||
buffer = [[ChunkList alloc] initWithMaximumDuration:12.0];
|
||||
semaphore = [[Semaphore alloc] init];
|
||||
|
||||
[self setPreviousNode:p];
|
||||
}
|
||||
|
||||
return self;
|
||||
initialBufferFilled = NO;
|
||||
|
||||
controller = c;
|
||||
endOfStream = NO;
|
||||
shouldContinue = YES;
|
||||
|
||||
nodeLossless = NO;
|
||||
|
||||
[self setPreviousNode:p];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
DLog(@"Refill Node dealloc");
|
||||
}
|
||||
|
||||
- (void)setFormat:(AudioStreamBasicDescription)format
|
||||
{
|
||||
nodeFormat = format;
|
||||
- (void)setFormat:(AudioStreamBasicDescription)format {
|
||||
nodeFormat = format;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
//
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "Plugin.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface CogDecoderMulti : NSObject <CogDecoder> {
|
||||
NSArray *theDecoders;
|
||||
id<CogDecoder> theDecoder;
|
||||
NSMutableArray *cachedObservers;
|
||||
NSArray *theDecoders;
|
||||
id<CogDecoder> theDecoder;
|
||||
NSMutableArray *cachedObservers;
|
||||
}
|
||||
|
||||
-(id)initWithDecoders:(NSArray *)decoders;
|
||||
- (id)initWithDecoders:(NSArray *)decoders;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -8,200 +8,180 @@
|
|||
|
||||
#import "CogPluginMulti.h"
|
||||
|
||||
NSArray * sortClassesByPriority(NSArray * theClasses)
|
||||
{
|
||||
NSMutableArray *sortedClasses = [NSMutableArray arrayWithArray:theClasses];
|
||||
[sortedClasses sortUsingComparator:
|
||||
^NSComparisonResult(id obj1, id obj2)
|
||||
{
|
||||
NSString *classString1 = (NSString *)obj1;
|
||||
NSString *classString2 = (NSString *)obj2;
|
||||
|
||||
Class class1 = NSClassFromString(classString1);
|
||||
Class class2 = NSClassFromString(classString2);
|
||||
|
||||
float priority1 = [class1 priority];
|
||||
float priority2 = [class2 priority];
|
||||
|
||||
if (priority1 == priority2) return NSOrderedSame;
|
||||
else if (priority1 > priority2) return NSOrderedAscending;
|
||||
else return NSOrderedDescending;
|
||||
}];
|
||||
return sortedClasses;
|
||||
NSArray *sortClassesByPriority(NSArray *theClasses) {
|
||||
NSMutableArray *sortedClasses = [NSMutableArray arrayWithArray:theClasses];
|
||||
[sortedClasses sortUsingComparator:
|
||||
^NSComparisonResult(id obj1, id obj2) {
|
||||
NSString *classString1 = (NSString *)obj1;
|
||||
NSString *classString2 = (NSString *)obj2;
|
||||
|
||||
Class class1 = NSClassFromString(classString1);
|
||||
Class class2 = NSClassFromString(classString2);
|
||||
|
||||
float priority1 = [class1 priority];
|
||||
float priority2 = [class2 priority];
|
||||
|
||||
if(priority1 == priority2)
|
||||
return NSOrderedSame;
|
||||
else if(priority1 > priority2)
|
||||
return NSOrderedAscending;
|
||||
else
|
||||
return NSOrderedDescending;
|
||||
}];
|
||||
return sortedClasses;
|
||||
}
|
||||
|
||||
@implementation CogDecoderMulti
|
||||
|
||||
+ (NSArray *)mimeTypes
|
||||
{
|
||||
return nil;
|
||||
+ (NSArray *)mimeTypes {
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes
|
||||
{
|
||||
return nil;
|
||||
+ (NSArray *)fileTypes {
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (float)priority
|
||||
{
|
||||
return -1.0;
|
||||
+ (float)priority {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypeAssociations {
|
||||
return nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)initWithDecoders:(NSArray *)decoders
|
||||
{
|
||||
self = [super init];
|
||||
if ( self )
|
||||
{
|
||||
theDecoders = sortClassesByPriority(decoders);
|
||||
theDecoder = nil;
|
||||
cachedObservers = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
- (id)initWithDecoders:(NSArray *)decoders {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
theDecoders = sortClassesByPriority(decoders);
|
||||
theDecoder = nil;
|
||||
cachedObservers = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)properties
|
||||
{
|
||||
if ( theDecoder != nil ) return [theDecoder properties];
|
||||
return nil;
|
||||
- (NSDictionary *)properties {
|
||||
if(theDecoder != nil) return [theDecoder properties];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (int)readAudio:(void *)buffer frames:(UInt32)frames
|
||||
{
|
||||
if ( theDecoder != nil ) return [theDecoder readAudio:buffer frames:frames];
|
||||
return 0;
|
||||
- (int)readAudio:(void *)buffer frames:(UInt32)frames {
|
||||
if(theDecoder != nil) return [theDecoder readAudio:buffer frames:frames];
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (BOOL)open:(id<CogSource>)source
|
||||
{
|
||||
for (NSString *classString in theDecoders)
|
||||
{
|
||||
Class decoder = NSClassFromString(classString);
|
||||
theDecoder = [[decoder alloc] init];
|
||||
for (NSDictionary *obsItem in cachedObservers) {
|
||||
[theDecoder addObserver:[obsItem objectForKey:@"observer"]
|
||||
forKeyPath:[obsItem objectForKey:@"keyPath"]
|
||||
options:[[obsItem objectForKey:@"options"] unsignedIntegerValue]
|
||||
context:(__bridge void *)([obsItem objectForKey:@"context"])];
|
||||
}
|
||||
if ([theDecoder open:source])
|
||||
return YES;
|
||||
for (NSDictionary *obsItem in cachedObservers) {
|
||||
[theDecoder removeObserver:[obsItem objectForKey:@"observer"] forKeyPath:[obsItem objectForKey:@"keyPath"]];
|
||||
}
|
||||
if ([source seekable])
|
||||
[source seek:0 whence:SEEK_SET];
|
||||
}
|
||||
theDecoder = nil;
|
||||
return NO;
|
||||
- (BOOL)open:(id<CogSource>)source {
|
||||
for(NSString *classString in theDecoders) {
|
||||
Class decoder = NSClassFromString(classString);
|
||||
theDecoder = [[decoder alloc] init];
|
||||
for(NSDictionary *obsItem in cachedObservers) {
|
||||
[theDecoder addObserver:[obsItem objectForKey:@"observer"]
|
||||
forKeyPath:[obsItem objectForKey:@"keyPath"]
|
||||
options:[[obsItem objectForKey:@"options"] unsignedIntegerValue]
|
||||
context:(__bridge void *)([obsItem objectForKey:@"context"])];
|
||||
}
|
||||
if([theDecoder open:source])
|
||||
return YES;
|
||||
for(NSDictionary *obsItem in cachedObservers) {
|
||||
[theDecoder removeObserver:[obsItem objectForKey:@"observer"] forKeyPath:[obsItem objectForKey:@"keyPath"]];
|
||||
}
|
||||
if([source seekable])
|
||||
[source seek:0 whence:SEEK_SET];
|
||||
}
|
||||
theDecoder = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (long)seek:(long)frame
|
||||
{
|
||||
if ( theDecoder != nil ) return [theDecoder seek:frame];
|
||||
return -1;
|
||||
- (long)seek:(long)frame {
|
||||
if(theDecoder != nil) return [theDecoder seek:frame];
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (void)close
|
||||
{
|
||||
if ( theDecoder != nil ) {
|
||||
for (NSDictionary *obsItem in cachedObservers) {
|
||||
[theDecoder removeObserver:[obsItem objectForKey:@"observer"] forKeyPath:[obsItem objectForKey:@"keyPath"]];
|
||||
}
|
||||
[cachedObservers removeAllObjects];
|
||||
[theDecoder close];
|
||||
theDecoder = nil;
|
||||
}
|
||||
- (void)close {
|
||||
if(theDecoder != nil) {
|
||||
for(NSDictionary *obsItem in cachedObservers) {
|
||||
[theDecoder removeObserver:[obsItem objectForKey:@"observer"] forKeyPath:[obsItem objectForKey:@"keyPath"]];
|
||||
}
|
||||
[cachedObservers removeAllObjects];
|
||||
[theDecoder close];
|
||||
theDecoder = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)setTrack:(NSURL *)track
|
||||
{
|
||||
if ( theDecoder != nil && [theDecoder respondsToSelector: @selector(setTrack:)] ) return [theDecoder setTrack:track];
|
||||
return NO;
|
||||
- (BOOL)setTrack:(NSURL *)track {
|
||||
if(theDecoder != nil && [theDecoder respondsToSelector:@selector(setTrack:)]) return [theDecoder setTrack:track];
|
||||
return NO;
|
||||
}
|
||||
|
||||
/* By the current design, the core adds its observers to decoders before they are opened */
|
||||
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
|
||||
{
|
||||
if(context != nil) {
|
||||
[cachedObservers addObject:[NSDictionary dictionaryWithObjectsAndKeys:observer, @"observer", keyPath, @"keyPath", @(options), @"options", context, @"context", nil]];
|
||||
} else {
|
||||
[cachedObservers addObject:[NSDictionary dictionaryWithObjectsAndKeys:observer, @"observer", keyPath, @"keyPath", @(options), @"options", nil]];
|
||||
}
|
||||
if (theDecoder) {
|
||||
[theDecoder addObserver:observer forKeyPath:keyPath options:options context:context];
|
||||
}
|
||||
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
|
||||
if(context != nil) {
|
||||
[cachedObservers addObject:[NSDictionary dictionaryWithObjectsAndKeys:observer, @"observer", keyPath, @"keyPath", @(options), @"options", context, @"context", nil]];
|
||||
} else {
|
||||
[cachedObservers addObject:[NSDictionary dictionaryWithObjectsAndKeys:observer, @"observer", keyPath, @"keyPath", @(options), @"options", nil]];
|
||||
}
|
||||
if(theDecoder) {
|
||||
[theDecoder addObserver:observer forKeyPath:keyPath options:options context:context];
|
||||
}
|
||||
}
|
||||
|
||||
/* And this is currently called after the decoder is closed */
|
||||
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
|
||||
{
|
||||
for (NSDictionary *obsItem in cachedObservers) {
|
||||
if ([obsItem objectForKey:@"observer"] == observer && [keyPath isEqualToString:[obsItem objectForKey:@"keyPath"]]) {
|
||||
[cachedObservers removeObject:obsItem];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (theDecoder) {
|
||||
[theDecoder removeObserver:observer forKeyPath:keyPath];
|
||||
}
|
||||
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
|
||||
for(NSDictionary *obsItem in cachedObservers) {
|
||||
if([obsItem objectForKey:@"observer"] == observer && [keyPath isEqualToString:[obsItem objectForKey:@"keyPath"]]) {
|
||||
[cachedObservers removeObject:obsItem];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(theDecoder) {
|
||||
[theDecoder removeObserver:observer forKeyPath:keyPath];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation CogContainerMulti
|
||||
|
||||
+ (NSArray *)urlsForContainerURL:(NSURL *)url containers:(NSArray *)containers
|
||||
{
|
||||
NSArray * sortedContainers = sortClassesByPriority(containers);
|
||||
for (NSString *classString in sortedContainers)
|
||||
{
|
||||
Class container = NSClassFromString(classString);
|
||||
NSArray * urls = [container urlsForContainerURL:url];
|
||||
if ([urls count])
|
||||
return urls;
|
||||
}
|
||||
return nil;
|
||||
+ (NSArray *)urlsForContainerURL:(NSURL *)url containers:(NSArray *)containers {
|
||||
NSArray *sortedContainers = sortClassesByPriority(containers);
|
||||
for(NSString *classString in sortedContainers) {
|
||||
Class container = NSClassFromString(classString);
|
||||
NSArray *urls = [container urlsForContainerURL:url];
|
||||
if([urls count])
|
||||
return urls;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CogMetadataReaderMulti
|
||||
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url readers:(NSArray *)readers
|
||||
{
|
||||
NSArray * sortedReaders = sortClassesByPriority(readers);
|
||||
for (NSString *classString in sortedReaders)
|
||||
{
|
||||
Class reader = NSClassFromString(classString);
|
||||
NSDictionary * data = [reader metadataForURL:url];
|
||||
if ([data count])
|
||||
return data;
|
||||
}
|
||||
return nil;
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url readers:(NSArray *)readers {
|
||||
NSArray *sortedReaders = sortClassesByPriority(readers);
|
||||
for(NSString *classString in sortedReaders) {
|
||||
Class reader = NSClassFromString(classString);
|
||||
NSDictionary *data = [reader metadataForURL:url];
|
||||
if([data count])
|
||||
return data;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CogPropertiesReaderMulti
|
||||
|
||||
+ (NSDictionary *)propertiesForSource:(id<CogSource>)source readers:(NSArray *)readers
|
||||
{
|
||||
NSArray * sortedReaders = sortClassesByPriority(readers);
|
||||
for (NSString *classString in sortedReaders)
|
||||
{
|
||||
Class reader = NSClassFromString(classString);
|
||||
NSDictionary * data = [reader propertiesForSource:source];
|
||||
if ([data count])
|
||||
return data;
|
||||
if ([source seekable])
|
||||
[source seek:0 whence:SEEK_SET];
|
||||
}
|
||||
return nil;
|
||||
+ (NSDictionary *)propertiesForSource:(id<CogSource>)source readers:(NSArray *)readers {
|
||||
NSArray *sortedReaders = sortClassesByPriority(readers);
|
||||
for(NSString *classString in sortedReaders) {
|
||||
Class reader = NSClassFromString(classString);
|
||||
NSDictionary *data = [reader propertiesForSource:source];
|
||||
if([data count])
|
||||
return data;
|
||||
if([source seekable])
|
||||
[source seek:0 whence:SEEK_SET];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -7,21 +7,19 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "Helper.h"
|
||||
#include <math.h>
|
||||
|
||||
//These functions are helpers for the process of converting volume from a linear to logarithmic scale.
|
||||
//Numbers that goes in to audioPlayer should be logarithmic. Numbers that are displayed to the user should be linear.
|
||||
//Here's why: http://www.dr-lex.34sp.com/info-stuff/volumecontrols.html
|
||||
//We are using the approximation of X^4.
|
||||
//Input/Output values are in percents.
|
||||
double logarithmicToLinear(double logarithmic, double MAX_VOLUME)
|
||||
{
|
||||
return (MAX_VOLUME == 100.0) ? logarithmic : pow((logarithmic/MAX_VOLUME), 0.25) * 100.0;
|
||||
// These functions are helpers for the process of converting volume from a linear to logarithmic scale.
|
||||
// Numbers that goes in to audioPlayer should be logarithmic. Numbers that are displayed to the user should be linear.
|
||||
// Here's why: http://www.dr-lex.34sp.com/info-stuff/volumecontrols.html
|
||||
// We are using the approximation of X^4.
|
||||
// Input/Output values are in percents.
|
||||
double logarithmicToLinear(double logarithmic, double MAX_VOLUME) {
|
||||
return (MAX_VOLUME == 100.0) ? logarithmic : pow((logarithmic / MAX_VOLUME), 0.25) * 100.0;
|
||||
}
|
||||
|
||||
double linearToLogarithmic(double linear, double MAX_VOLUME)
|
||||
{
|
||||
return (MAX_VOLUME == 100.0) ? linear : (linear/100.0) * (linear/100.0) * (linear/100.0) * (linear/100.0) * MAX_VOLUME;
|
||||
double linearToLogarithmic(double linear, double MAX_VOLUME) {
|
||||
return (MAX_VOLUME == 100.0) ? linear : (linear / 100.0) * (linear / 100.0) * (linear / 100.0) * (linear / 100.0) * MAX_VOLUME;
|
||||
}
|
||||
//End helper volume function thingies. ONWARDS TO GLORY!
|
||||
// End helper volume function thingies. ONWARDS TO GLORY!
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
#import <AssertMacros.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <CoreAudio/CoreAudioTypes.h>
|
||||
|
||||
#import <stdatomic.h>
|
||||
|
@ -29,46 +29,46 @@
|
|||
@class OutputNode;
|
||||
|
||||
@interface OutputCoreAudio : NSObject {
|
||||
OutputNode * outputController;
|
||||
|
||||
Semaphore * writeSemaphore;
|
||||
Semaphore * readSemaphore;
|
||||
|
||||
BOOL stopInvoked;
|
||||
BOOL running;
|
||||
BOOL stopping;
|
||||
BOOL stopped;
|
||||
BOOL started;
|
||||
BOOL paused;
|
||||
BOOL stopNext;
|
||||
|
||||
BOOL eqEnabled;
|
||||
|
||||
BOOL streamFormatStarted;
|
||||
|
||||
atomic_long bytesRendered;
|
||||
atomic_long bytesHdcdSustained;
|
||||
|
||||
BOOL listenerapplied;
|
||||
BOOL observersapplied;
|
||||
|
||||
float volume;
|
||||
|
||||
AVAudioFormat *_deviceFormat;
|
||||
OutputNode *outputController;
|
||||
|
||||
AudioDeviceID outputDeviceID;
|
||||
AudioStreamBasicDescription deviceFormat; // info about the default device
|
||||
AudioStreamBasicDescription streamFormat; // stream format last seen in render callback
|
||||
Semaphore *writeSemaphore;
|
||||
Semaphore *readSemaphore;
|
||||
|
||||
BOOL stopInvoked;
|
||||
BOOL running;
|
||||
BOOL stopping;
|
||||
BOOL stopped;
|
||||
BOOL started;
|
||||
BOOL paused;
|
||||
BOOL stopNext;
|
||||
|
||||
BOOL eqEnabled;
|
||||
|
||||
BOOL streamFormatStarted;
|
||||
|
||||
atomic_long bytesRendered;
|
||||
atomic_long bytesHdcdSustained;
|
||||
|
||||
BOOL listenerapplied;
|
||||
BOOL observersapplied;
|
||||
|
||||
float volume;
|
||||
|
||||
AVAudioFormat *_deviceFormat;
|
||||
|
||||
AudioDeviceID outputDeviceID;
|
||||
AudioStreamBasicDescription deviceFormat; // info about the default device
|
||||
AudioStreamBasicDescription streamFormat; // stream format last seen in render callback
|
||||
|
||||
AUAudioUnit *_au;
|
||||
size_t _bufferSize;
|
||||
|
||||
AudioUnit _eq;
|
||||
|
||||
DownmixProcessor *downmixer;
|
||||
|
||||
AUAudioUnit *_au;
|
||||
size_t _bufferSize;
|
||||
|
||||
AudioUnit _eq;
|
||||
|
||||
DownmixProcessor * downmixer;
|
||||
|
||||
#ifdef OUTPUT_LOG
|
||||
FILE *_logFile;
|
||||
FILE *_logFile;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@
|
|||
- (void)resume;
|
||||
- (void)stop;
|
||||
|
||||
- (void)setVolume:(double) v;
|
||||
- (void)setVolume:(double)v;
|
||||
|
||||
- (void)setEqualizerEnabled:(BOOL)enabled;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
//Plugins! HOORAY!
|
||||
// Plugins! HOORAY!
|
||||
|
||||
@protocol CogSource <NSObject>
|
||||
+ (NSArray *)schemes; //http, file, etc
|
||||
+ (NSArray *)schemes; // http, file, etc
|
||||
|
||||
- (NSURL *)url;
|
||||
- (NSString *)mimeType;
|
||||
|
@ -10,7 +10,7 @@
|
|||
- (BOOL)seekable;
|
||||
- (BOOL)seek:(long)position whence:(int)whence;
|
||||
- (long)tell;
|
||||
- (long)read:(void *)buffer amount:(long)amount; //reads UP TO amount, returns amount read.
|
||||
- (long)read:(void *)buffer amount:(long)amount; // reads UP TO amount, returns amount read.
|
||||
- (void)close;
|
||||
- (void)dealloc;
|
||||
@end
|
||||
|
@ -19,22 +19,22 @@
|
|||
+ (BOOL)shouldLoadForOSVersion:(NSOperatingSystemVersion)version;
|
||||
@end
|
||||
|
||||
@protocol CogContainer <NSObject>
|
||||
+ (NSArray *)fileTypes; //mp3, ogg, etc
|
||||
@protocol CogContainer <NSObject>
|
||||
+ (NSArray *)fileTypes; // mp3, ogg, etc
|
||||
+ (NSArray *)mimeTypes;
|
||||
+ (float)priority;
|
||||
|
||||
+ (NSArray *)urlsForContainerURL:(NSURL *)url;
|
||||
@end
|
||||
|
||||
@protocol CogDecoder <NSObject>
|
||||
@protocol CogDecoder <NSObject>
|
||||
@required
|
||||
+ (NSArray *)mimeTypes;
|
||||
+ (NSArray *)fileTypes; //mp3, ogg, etc
|
||||
+ (NSArray *)fileTypes; // mp3, ogg, etc
|
||||
+ (NSArray *)fileTypeAssociations; // array of NSArray of NSString, where first item in array is the type name, the second is the icon name, and the rest are the extensions
|
||||
+ (float)priority; // should be 0.0 ... 1.0, higher means you get selected first, should default to 1.0 unless you know a reason why any of your extensions may behave badly, ie. greedily taking over some file type extension without performing any header validation on it
|
||||
|
||||
//For KVO
|
||||
// For KVO
|
||||
//- (void)setProperties:(NSDictionary *)p;
|
||||
- (NSDictionary *)properties;
|
||||
|
||||
|
@ -49,7 +49,7 @@
|
|||
|
||||
- (BOOL)setTrack:(NSURL *)track;
|
||||
|
||||
//These are in NSObject, so as long as you are a subclass of that, you are ok.
|
||||
// These are in NSObject, so as long as you are a subclass of that, you are ok.
|
||||
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
|
||||
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
|
||||
@end
|
||||
|
@ -87,14 +87,11 @@
|
|||
- (NSDictionary *)decodersByExtension;
|
||||
- (NSDictionary *)decodersByMimeType;
|
||||
|
||||
- (id<CogSource>) audioSourceForURL:(NSURL *)url;
|
||||
- (NSArray *) urlsForContainerURL:(NSURL *)url;
|
||||
- (NSDictionary *) metadataForURL:(NSURL *)url skipCue:(BOOL)skip;
|
||||
- (NSDictionary *) propertiesForURL:(NSURL *)url;
|
||||
- (id<CogDecoder>) audioDecoderForSource:(id<CogSource>)source skipCue:(BOOL)skip;
|
||||
- (id<CogSource>)audioSourceForURL:(NSURL *)url;
|
||||
- (NSArray *)urlsForContainerURL:(NSURL *)url;
|
||||
- (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip;
|
||||
- (NSDictionary *)propertiesForURL:(NSURL *)url;
|
||||
- (id<CogDecoder>)audioDecoderForSource:(id<CogSource>)source skipCue:(BOOL)skip;
|
||||
|
||||
- (int) putMetadataInURL:(NSURL *)url;
|
||||
- (int)putMetadataInURL:(NSURL *)url;
|
||||
@end
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
|
||||
#import "Plugin.h"
|
||||
|
||||
//Singletonish
|
||||
@interface PluginController : NSObject <CogPluginController>
|
||||
{
|
||||
// Singletonish
|
||||
@interface PluginController : NSObject <CogPluginController> {
|
||||
NSMutableDictionary *sources;
|
||||
NSMutableDictionary *containers;
|
||||
NSMutableDictionary *metadataReaders;
|
||||
|
@ -16,7 +15,7 @@
|
|||
|
||||
NSMutableDictionary *decodersByExtension;
|
||||
NSMutableDictionary *decodersByMimeType;
|
||||
|
||||
|
||||
BOOL configured;
|
||||
}
|
||||
|
||||
|
@ -35,7 +34,7 @@
|
|||
- (void)setup;
|
||||
- (void)printPluginInfo;
|
||||
|
||||
- (void)loadPlugins;
|
||||
- (void)loadPlugins;
|
||||
- (void)loadPluginsAtPath:(NSString *)path;
|
||||
|
||||
- (void)setupSource:(NSString *)className;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#import "PluginController.h"
|
||||
#import "Plugin.h"
|
||||
#import "CogPluginMulti.h"
|
||||
#import "Plugin.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
|
@ -22,239 +22,202 @@
|
|||
|
||||
static PluginController *sharedPluginController = nil;
|
||||
|
||||
+ (id<CogPluginController>)sharedPluginController
|
||||
{
|
||||
+ (id<CogPluginController>)sharedPluginController {
|
||||
@synchronized(self) {
|
||||
if (sharedPluginController == nil) {
|
||||
if(sharedPluginController == nil) {
|
||||
sharedPluginController = [[self alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return sharedPluginController;
|
||||
}
|
||||
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.sources = [[NSMutableDictionary alloc] init];
|
||||
self.containers = [[NSMutableDictionary alloc] init];
|
||||
|
||||
self.metadataReaders = [[NSMutableDictionary alloc] init];
|
||||
|
||||
self.propertiesReadersByExtension = [[NSMutableDictionary alloc] init];
|
||||
self.propertiesReadersByMimeType = [[NSMutableDictionary alloc] init];
|
||||
|
||||
self.decodersByExtension = [[NSMutableDictionary alloc] init];
|
||||
self.decodersByMimeType = [[NSMutableDictionary alloc] init];
|
||||
|
||||
[self setup];
|
||||
if(self) {
|
||||
self.sources = [[NSMutableDictionary alloc] init];
|
||||
self.containers = [[NSMutableDictionary alloc] init];
|
||||
|
||||
self.metadataReaders = [[NSMutableDictionary alloc] init];
|
||||
|
||||
self.propertiesReadersByExtension = [[NSMutableDictionary alloc] init];
|
||||
self.propertiesReadersByMimeType = [[NSMutableDictionary alloc] init];
|
||||
|
||||
self.decodersByExtension = [[NSMutableDictionary alloc] init];
|
||||
self.decodersByMimeType = [[NSMutableDictionary alloc] init];
|
||||
|
||||
[self setup];
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setup
|
||||
{
|
||||
if (self.configured == NO) {
|
||||
- (void)setup {
|
||||
if(self.configured == NO) {
|
||||
self.configured = YES;
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bundleDidLoad:) name:NSBundleDidLoadNotification object:nil];
|
||||
|
||||
[self loadPlugins];
|
||||
[self printPluginInfo];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)bundleDidLoad:(NSNotification *)notification
|
||||
{
|
||||
- (void)bundleDidLoad:(NSNotification *)notification {
|
||||
NSArray *classNames = [[notification userInfo] objectForKey:@"NSLoadedClasses"];
|
||||
for (NSString *className in classNames)
|
||||
{
|
||||
Class bundleClass = NSClassFromString(className);
|
||||
if ([bundleClass conformsToProtocol:@protocol(CogVersionCheck)]) {
|
||||
DLog(@"Component has version check: %@", className);
|
||||
if (![bundleClass shouldLoadForOSVersion:[[NSProcessInfo processInfo] operatingSystemVersion]])
|
||||
{
|
||||
DLog(@"Plugin fails OS version check, ignoring");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (NSString *className in classNames)
|
||||
{
|
||||
for(NSString *className in classNames) {
|
||||
Class bundleClass = NSClassFromString(className);
|
||||
if([bundleClass conformsToProtocol:@protocol(CogVersionCheck)]) {
|
||||
DLog(@"Component has version check: %@", className);
|
||||
if(![bundleClass shouldLoadForOSVersion:[[NSProcessInfo processInfo] operatingSystemVersion]]) {
|
||||
DLog(@"Plugin fails OS version check, ignoring");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(NSString *className in classNames) {
|
||||
DLog(@"Class loaded: %@", className);
|
||||
Class bundleClass = NSClassFromString(className);
|
||||
if ([bundleClass conformsToProtocol:@protocol(CogContainer)]) {
|
||||
if([bundleClass conformsToProtocol:@protocol(CogContainer)]) {
|
||||
[self setupContainer:className];
|
||||
}
|
||||
if ([bundleClass conformsToProtocol:@protocol(CogDecoder)]) {
|
||||
if([bundleClass conformsToProtocol:@protocol(CogDecoder)]) {
|
||||
[self setupDecoder:className];
|
||||
}
|
||||
if ([bundleClass conformsToProtocol:@protocol(CogMetadataReader)]) {
|
||||
if([bundleClass conformsToProtocol:@protocol(CogMetadataReader)]) {
|
||||
[self setupMetadataReader:className];
|
||||
}
|
||||
if ([bundleClass conformsToProtocol:@protocol(CogPropertiesReader)]) {
|
||||
if([bundleClass conformsToProtocol:@protocol(CogPropertiesReader)]) {
|
||||
[self setupPropertiesReader:className];
|
||||
}
|
||||
if ([bundleClass conformsToProtocol:@protocol(CogSource)]) {
|
||||
if([bundleClass conformsToProtocol:@protocol(CogSource)]) {
|
||||
[self setupSource:className];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadPluginsAtPath:(NSString *)path
|
||||
{
|
||||
|
||||
- (void)loadPluginsAtPath:(NSString *)path {
|
||||
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
||||
|
||||
for (NSString *pname in dirContents)
|
||||
{
|
||||
for(NSString *pname in dirContents) {
|
||||
NSString *ppath;
|
||||
ppath = [NSString pathWithComponents:@[path,pname]];
|
||||
|
||||
if ([[pname pathExtension] isEqualToString:@"bundle"])
|
||||
{
|
||||
ppath = [NSString pathWithComponents:@[path, pname]];
|
||||
|
||||
if([[pname pathExtension] isEqualToString:@"bundle"]) {
|
||||
NSBundle *b = [NSBundle bundleWithPath:ppath];
|
||||
[b load];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadPlugins
|
||||
{
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
|
||||
NSString *basePath = [[paths firstObject] stringByAppendingPathComponent:@"Cog"];
|
||||
- (void)loadPlugins {
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
|
||||
NSString *basePath = [[paths firstObject] stringByAppendingPathComponent:@"Cog"];
|
||||
|
||||
[self loadPluginsAtPath:[[NSBundle mainBundle] builtInPlugInsPath]];
|
||||
[self loadPluginsAtPath:[[NSBundle mainBundle] builtInPlugInsPath]];
|
||||
[self loadPluginsAtPath:[basePath stringByAppendingPathComponent:@"Plugins"]];
|
||||
}
|
||||
|
||||
- (void)setupContainer:(NSString *)className
|
||||
{
|
||||
- (void)setupContainer:(NSString *)className {
|
||||
Class container = NSClassFromString(className);
|
||||
if (container && [container respondsToSelector:@selector(fileTypes)]) {
|
||||
for (id fileType in [container fileTypes])
|
||||
{
|
||||
NSString *ext = [fileType lowercaseString];
|
||||
NSMutableArray *containerSet;
|
||||
if (![containers objectForKey:ext])
|
||||
{
|
||||
containerSet = [[NSMutableArray alloc] init];
|
||||
[containers setObject:containerSet forKey:ext];
|
||||
}
|
||||
else
|
||||
containerSet = [containers objectForKey:ext];
|
||||
[containerSet addObject:className];
|
||||
if(container && [container respondsToSelector:@selector(fileTypes)]) {
|
||||
for(id fileType in [container fileTypes]) {
|
||||
NSString *ext = [fileType lowercaseString];
|
||||
NSMutableArray *containerSet;
|
||||
if(![containers objectForKey:ext]) {
|
||||
containerSet = [[NSMutableArray alloc] init];
|
||||
[containers setObject:containerSet forKey:ext];
|
||||
} else
|
||||
containerSet = [containers objectForKey:ext];
|
||||
[containerSet addObject:className];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupDecoder:(NSString *)className
|
||||
{
|
||||
- (void)setupDecoder:(NSString *)className {
|
||||
Class decoder = NSClassFromString(className);
|
||||
if (decoder && [decoder respondsToSelector:@selector(fileTypes)]) {
|
||||
for (id fileType in [decoder fileTypes])
|
||||
{
|
||||
NSString *ext = [fileType lowercaseString];
|
||||
NSMutableArray *decoders;
|
||||
if (![decodersByExtension objectForKey:ext])
|
||||
{
|
||||
decoders = [[NSMutableArray alloc] init];
|
||||
[decodersByExtension setObject:decoders forKey:ext];
|
||||
}
|
||||
else
|
||||
decoders = [decodersByExtension objectForKey:ext];
|
||||
if(decoder && [decoder respondsToSelector:@selector(fileTypes)]) {
|
||||
for(id fileType in [decoder fileTypes]) {
|
||||
NSString *ext = [fileType lowercaseString];
|
||||
NSMutableArray *decoders;
|
||||
if(![decodersByExtension objectForKey:ext]) {
|
||||
decoders = [[NSMutableArray alloc] init];
|
||||
[decodersByExtension setObject:decoders forKey:ext];
|
||||
} else
|
||||
decoders = [decodersByExtension objectForKey:ext];
|
||||
[decoders addObject:className];
|
||||
}
|
||||
}
|
||||
|
||||
if (decoder && [decoder respondsToSelector:@selector(mimeTypes)]) {
|
||||
for (id mimeType in [decoder mimeTypes])
|
||||
{
|
||||
NSString *mimetype = [mimeType lowercaseString];
|
||||
NSMutableArray *decoders;
|
||||
if (![decodersByMimeType objectForKey:mimetype])
|
||||
{
|
||||
decoders = [[NSMutableArray alloc] init];
|
||||
[decodersByMimeType setObject:decoders forKey:mimetype];
|
||||
}
|
||||
else
|
||||
decoders = [decodersByMimeType objectForKey:mimetype];
|
||||
[decoders addObject:className];
|
||||
|
||||
if(decoder && [decoder respondsToSelector:@selector(mimeTypes)]) {
|
||||
for(id mimeType in [decoder mimeTypes]) {
|
||||
NSString *mimetype = [mimeType lowercaseString];
|
||||
NSMutableArray *decoders;
|
||||
if(![decodersByMimeType objectForKey:mimetype]) {
|
||||
decoders = [[NSMutableArray alloc] init];
|
||||
[decodersByMimeType setObject:decoders forKey:mimetype];
|
||||
} else
|
||||
decoders = [decodersByMimeType objectForKey:mimetype];
|
||||
[decoders addObject:className];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupMetadataReader:(NSString *)className
|
||||
{
|
||||
- (void)setupMetadataReader:(NSString *)className {
|
||||
Class metadataReader = NSClassFromString(className);
|
||||
if (metadataReader && [metadataReader respondsToSelector:@selector(fileTypes)]) {
|
||||
for (id fileType in [metadataReader fileTypes])
|
||||
{
|
||||
NSString *ext = [fileType lowercaseString];
|
||||
NSMutableArray *readers;
|
||||
if (![metadataReaders objectForKey:ext])
|
||||
{
|
||||
readers = [[NSMutableArray alloc] init];
|
||||
[metadataReaders setObject:readers forKey:ext];
|
||||
}
|
||||
else
|
||||
readers = [metadataReaders objectForKey:ext];
|
||||
[readers addObject:className];
|
||||
if(metadataReader && [metadataReader respondsToSelector:@selector(fileTypes)]) {
|
||||
for(id fileType in [metadataReader fileTypes]) {
|
||||
NSString *ext = [fileType lowercaseString];
|
||||
NSMutableArray *readers;
|
||||
if(![metadataReaders objectForKey:ext]) {
|
||||
readers = [[NSMutableArray alloc] init];
|
||||
[metadataReaders setObject:readers forKey:ext];
|
||||
} else
|
||||
readers = [metadataReaders objectForKey:ext];
|
||||
[readers addObject:className];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupPropertiesReader:(NSString *)className
|
||||
{
|
||||
- (void)setupPropertiesReader:(NSString *)className {
|
||||
Class propertiesReader = NSClassFromString(className);
|
||||
if (propertiesReader && [propertiesReader respondsToSelector:@selector(fileTypes)]) {
|
||||
for (id fileType in [propertiesReader fileTypes])
|
||||
{
|
||||
NSString *ext = [fileType lowercaseString];
|
||||
NSMutableArray *readers;
|
||||
if (![propertiesReadersByExtension objectForKey:ext])
|
||||
{
|
||||
readers = [[NSMutableArray alloc] init];
|
||||
[propertiesReadersByExtension setObject:readers forKey:ext];
|
||||
}
|
||||
else
|
||||
readers = [propertiesReadersByExtension objectForKey:ext];
|
||||
[readers addObject:className];
|
||||
if(propertiesReader && [propertiesReader respondsToSelector:@selector(fileTypes)]) {
|
||||
for(id fileType in [propertiesReader fileTypes]) {
|
||||
NSString *ext = [fileType lowercaseString];
|
||||
NSMutableArray *readers;
|
||||
if(![propertiesReadersByExtension objectForKey:ext]) {
|
||||
readers = [[NSMutableArray alloc] init];
|
||||
[propertiesReadersByExtension setObject:readers forKey:ext];
|
||||
} else
|
||||
readers = [propertiesReadersByExtension objectForKey:ext];
|
||||
[readers addObject:className];
|
||||
}
|
||||
}
|
||||
|
||||
if (propertiesReader && [propertiesReader respondsToSelector:@selector(mimeTypes)]) {
|
||||
for (id mimeType in [propertiesReader mimeTypes])
|
||||
{
|
||||
NSString *mimetype = [mimeType lowercaseString];
|
||||
NSMutableArray *readers;
|
||||
if (![propertiesReadersByMimeType objectForKey:mimetype])
|
||||
{
|
||||
readers = [[NSMutableArray alloc] init];
|
||||
[propertiesReadersByMimeType setObject:readers forKey:mimetype];
|
||||
}
|
||||
else
|
||||
readers = [propertiesReadersByMimeType objectForKey:mimetype];
|
||||
[readers addObject:className];
|
||||
if(propertiesReader && [propertiesReader respondsToSelector:@selector(mimeTypes)]) {
|
||||
for(id mimeType in [propertiesReader mimeTypes]) {
|
||||
NSString *mimetype = [mimeType lowercaseString];
|
||||
NSMutableArray *readers;
|
||||
if(![propertiesReadersByMimeType objectForKey:mimetype]) {
|
||||
readers = [[NSMutableArray alloc] init];
|
||||
[propertiesReadersByMimeType setObject:readers forKey:mimetype];
|
||||
} else
|
||||
readers = [propertiesReadersByMimeType objectForKey:mimetype];
|
||||
[readers addObject:className];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupSource:(NSString *)className
|
||||
{
|
||||
- (void)setupSource:(NSString *)className {
|
||||
Class source = NSClassFromString(className);
|
||||
if (source && [source respondsToSelector:@selector(schemes)]) {
|
||||
for (id scheme in [source schemes])
|
||||
{
|
||||
if(source && [source respondsToSelector:@selector(schemes)]) {
|
||||
for(id scheme in [source schemes]) {
|
||||
[sources setObject:className forKey:scheme];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)printPluginInfo
|
||||
{
|
||||
- (void)printPluginInfo {
|
||||
ALog(@"Sources: %@", self.sources);
|
||||
ALog(@"Containers: %@", self.containers);
|
||||
ALog(@"Metadata Readers: %@", self.metadataReaders);
|
||||
|
@ -264,7 +227,7 @@ static PluginController *sharedPluginController = nil;
|
|||
|
||||
ALog(@"Decoders by Extension: %@", self.decodersByExtension);
|
||||
ALog(@"Decoders by Mime Type: %@", self.decodersByMimeType);
|
||||
|
||||
|
||||
#if 0
|
||||
// XXX Keep in sync with Info.plist on disk!
|
||||
NSString * plistHeader = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||
|
@ -420,194 +383,167 @@ static PluginController *sharedPluginController = nil;
|
|||
#endif
|
||||
}
|
||||
|
||||
- (id<CogSource>) audioSourceForURL:(NSURL *)url
|
||||
{
|
||||
- (id<CogSource>)audioSourceForURL:(NSURL *)url {
|
||||
NSString *scheme = [url scheme];
|
||||
|
||||
|
||||
Class source = NSClassFromString([sources objectForKey:scheme]);
|
||||
|
||||
|
||||
return [[source alloc] init];
|
||||
}
|
||||
|
||||
- (NSArray *) urlsForContainerURL:(NSURL *)url
|
||||
{
|
||||
- (NSArray *)urlsForContainerURL:(NSURL *)url {
|
||||
NSString *ext = [url pathExtension];
|
||||
NSArray *containerSet = [containers objectForKey:[ext lowercaseString]];
|
||||
NSString *classString;
|
||||
if (containerSet) {
|
||||
if ( [containerSet count] > 1 ) {
|
||||
return [CogContainerMulti urlsForContainerURL:url containers:containerSet];
|
||||
}
|
||||
else {
|
||||
classString = [containerSet objectAtIndex:0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSArray *containerSet = [containers objectForKey:[ext lowercaseString]];
|
||||
NSString *classString;
|
||||
if(containerSet) {
|
||||
if([containerSet count] > 1) {
|
||||
return [CogContainerMulti urlsForContainerURL:url containers:containerSet];
|
||||
} else {
|
||||
classString = [containerSet objectAtIndex:0];
|
||||
}
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
Class container = NSClassFromString(classString);
|
||||
|
||||
|
||||
return [container urlsForContainerURL:url];
|
||||
}
|
||||
|
||||
//Note: Source is assumed to already be opened.
|
||||
- (id<CogDecoder>) audioDecoderForSource:(id <CogSource>)source skipCue:(BOOL)skip
|
||||
{
|
||||
// Note: Source is assumed to already be opened.
|
||||
- (id<CogDecoder>)audioDecoderForSource:(id<CogSource>)source skipCue:(BOOL)skip {
|
||||
NSString *ext = [[source url] pathExtension];
|
||||
NSArray *decoders = [decodersByExtension objectForKey:[ext lowercaseString]];
|
||||
NSString *classString;
|
||||
if (decoders) {
|
||||
if ( [decoders count] > 1 ) {
|
||||
if (skip)
|
||||
{
|
||||
NSMutableArray * _decoders = [decoders mutableCopy];
|
||||
for (int i = 0; i < [_decoders count];)
|
||||
{
|
||||
if ([[_decoders objectAtIndex:i] isEqualToString:@"CueSheetDecoder"])
|
||||
[_decoders removeObjectAtIndex:i];
|
||||
else
|
||||
++i;
|
||||
}
|
||||
return [[CogDecoderMulti alloc] initWithDecoders:_decoders];
|
||||
}
|
||||
return [[CogDecoderMulti alloc] initWithDecoders:decoders];
|
||||
}
|
||||
else {
|
||||
classString = [decoders objectAtIndex:0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
decoders = [decodersByMimeType objectForKey:[[source mimeType] lowercaseString]];
|
||||
if (decoders) {
|
||||
if ( [decoders count] > 1 ) {
|
||||
return [[CogDecoderMulti alloc] initWithDecoders:decoders];
|
||||
}
|
||||
else {
|
||||
classString = [decoders objectAtIndex:0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
classString = @"SilenceDecoder";
|
||||
}
|
||||
NSString *classString;
|
||||
if(decoders) {
|
||||
if([decoders count] > 1) {
|
||||
if(skip) {
|
||||
NSMutableArray *_decoders = [decoders mutableCopy];
|
||||
for(int i = 0; i < [_decoders count];) {
|
||||
if([[_decoders objectAtIndex:i] isEqualToString:@"CueSheetDecoder"])
|
||||
[_decoders removeObjectAtIndex:i];
|
||||
else
|
||||
++i;
|
||||
}
|
||||
return [[CogDecoderMulti alloc] initWithDecoders:_decoders];
|
||||
}
|
||||
return [[CogDecoderMulti alloc] initWithDecoders:decoders];
|
||||
} else {
|
||||
classString = [decoders objectAtIndex:0];
|
||||
}
|
||||
} else {
|
||||
decoders = [decodersByMimeType objectForKey:[[source mimeType] lowercaseString]];
|
||||
if(decoders) {
|
||||
if([decoders count] > 1) {
|
||||
return [[CogDecoderMulti alloc] initWithDecoders:decoders];
|
||||
} else {
|
||||
classString = [decoders objectAtIndex:0];
|
||||
}
|
||||
} else {
|
||||
classString = @"SilenceDecoder";
|
||||
}
|
||||
}
|
||||
|
||||
Class decoder = NSClassFromString(classString);
|
||||
|
||||
|
||||
return [[decoder alloc] init];
|
||||
}
|
||||
|
||||
- (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip
|
||||
{
|
||||
NSString * urlScheme = [url scheme];
|
||||
if ([urlScheme isEqualToString:@"http"] ||
|
||||
[urlScheme isEqualToString:@"https"])
|
||||
return nil;
|
||||
|
||||
- (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip {
|
||||
NSString *urlScheme = [url scheme];
|
||||
if([urlScheme isEqualToString:@"http"] ||
|
||||
[urlScheme isEqualToString:@"https"])
|
||||
return nil;
|
||||
|
||||
NSString *ext = [url pathExtension];
|
||||
NSArray *readers = [metadataReaders objectForKey:[ext lowercaseString]];
|
||||
NSString *classString;
|
||||
if (readers) {
|
||||
if ( [readers count] > 1 ) {
|
||||
if (skip)
|
||||
{
|
||||
NSMutableArray *_readers = [readers mutableCopy];
|
||||
for (int i = 0; i < [_readers count];)
|
||||
{
|
||||
if ([[_readers objectAtIndex:i] isEqualToString:@"CueSheetMetadataReader"])
|
||||
[_readers removeObjectAtIndex:i];
|
||||
else
|
||||
++i;
|
||||
}
|
||||
return [CogMetadataReaderMulti metadataForURL:url readers:_readers];
|
||||
}
|
||||
return [CogMetadataReaderMulti metadataForURL:url readers:readers];
|
||||
}
|
||||
else {
|
||||
classString = [readers objectAtIndex:0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSArray *readers = [metadataReaders objectForKey:[ext lowercaseString]];
|
||||
NSString *classString;
|
||||
if(readers) {
|
||||
if([readers count] > 1) {
|
||||
if(skip) {
|
||||
NSMutableArray *_readers = [readers mutableCopy];
|
||||
for(int i = 0; i < [_readers count];) {
|
||||
if([[_readers objectAtIndex:i] isEqualToString:@"CueSheetMetadataReader"])
|
||||
[_readers removeObjectAtIndex:i];
|
||||
else
|
||||
++i;
|
||||
}
|
||||
return [CogMetadataReaderMulti metadataForURL:url readers:_readers];
|
||||
}
|
||||
return [CogMetadataReaderMulti metadataForURL:url readers:readers];
|
||||
} else {
|
||||
classString = [readers objectAtIndex:0];
|
||||
}
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
Class metadataReader = NSClassFromString(classString);
|
||||
|
||||
|
||||
return [metadataReader metadataForURL:url];
|
||||
}
|
||||
|
||||
|
||||
//If no properties reader is defined, use the decoder's properties.
|
||||
- (NSDictionary *)propertiesForURL:(NSURL *)url
|
||||
{
|
||||
NSString * urlScheme = [url scheme];
|
||||
if ([urlScheme isEqualToString:@"http"] ||
|
||||
[urlScheme isEqualToString:@"https"])
|
||||
return nil;
|
||||
|
||||
NSDictionary *properties = nil;
|
||||
NSString *ext = [url pathExtension];
|
||||
|
||||
id<CogSource> source = [self audioSourceForURL:url];
|
||||
if (![source open:url])
|
||||
// If no properties reader is defined, use the decoder's properties.
|
||||
- (NSDictionary *)propertiesForURL:(NSURL *)url {
|
||||
NSString *urlScheme = [url scheme];
|
||||
if([urlScheme isEqualToString:@"http"] ||
|
||||
[urlScheme isEqualToString:@"https"])
|
||||
return nil;
|
||||
|
||||
NSArray *readers = [propertiesReadersByExtension objectForKey:[ext lowercaseString]];
|
||||
NSString *classString = nil;
|
||||
if (readers)
|
||||
{
|
||||
if ( [readers count] > 1 ) {
|
||||
properties = [CogPropertiesReaderMulti propertiesForSource:source readers:readers];
|
||||
if (properties != nil && [properties count])
|
||||
return properties;
|
||||
}
|
||||
else {
|
||||
classString = [readers objectAtIndex:0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
readers = [propertiesReadersByMimeType objectForKey:[[source mimeType] lowercaseString]];
|
||||
if (readers)
|
||||
{
|
||||
if ( [readers count] > 1 ) {
|
||||
properties = [CogPropertiesReaderMulti propertiesForSource:source readers:readers];
|
||||
if (properties != nil && [properties count])
|
||||
return properties;
|
||||
}
|
||||
else {
|
||||
classString = [readers objectAtIndex:0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (classString)
|
||||
{
|
||||
NSDictionary *properties = nil;
|
||||
NSString *ext = [url pathExtension];
|
||||
|
||||
id<CogSource> source = [self audioSourceForURL:url];
|
||||
if(![source open:url])
|
||||
return nil;
|
||||
|
||||
NSArray *readers = [propertiesReadersByExtension objectForKey:[ext lowercaseString]];
|
||||
NSString *classString = nil;
|
||||
if(readers) {
|
||||
if([readers count] > 1) {
|
||||
properties = [CogPropertiesReaderMulti propertiesForSource:source readers:readers];
|
||||
if(properties != nil && [properties count])
|
||||
return properties;
|
||||
} else {
|
||||
classString = [readers objectAtIndex:0];
|
||||
}
|
||||
} else {
|
||||
readers = [propertiesReadersByMimeType objectForKey:[[source mimeType] lowercaseString]];
|
||||
if(readers) {
|
||||
if([readers count] > 1) {
|
||||
properties = [CogPropertiesReaderMulti propertiesForSource:source readers:readers];
|
||||
if(properties != nil && [properties count])
|
||||
return properties;
|
||||
} else {
|
||||
classString = [readers objectAtIndex:0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(classString) {
|
||||
Class propertiesReader = NSClassFromString(classString);
|
||||
|
||||
properties = [propertiesReader propertiesForSource:source];
|
||||
if (properties != nil && [properties count])
|
||||
return properties;
|
||||
if(properties != nil && [properties count])
|
||||
return properties;
|
||||
}
|
||||
|
||||
{
|
||||
id<CogDecoder> decoder = [self audioDecoderForSource:source skipCue:NO];
|
||||
if (![decoder open:source])
|
||||
{
|
||||
{
|
||||
id<CogDecoder> decoder = [self audioDecoderForSource:source skipCue:NO];
|
||||
if(![decoder open:source]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
NSDictionary *properties = [decoder properties];
|
||||
|
||||
|
||||
[decoder close];
|
||||
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
- (int)putMetadataInURL:(NSURL *)url
|
||||
{
|
||||
return 0;
|
||||
- (int)putMetadataInURL:(NSURL *)url {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, CogStatus) {
|
||||
CogStatusStopped = 0,
|
||||
CogStatusPaused,
|
||||
CogStatusPlaying,
|
||||
CogStatusStopping,
|
||||
CogStatusStopped = 0,
|
||||
CogStatusPaused,
|
||||
CogStatusPlaying,
|
||||
CogStatusStopping,
|
||||
};
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
semaphore_t semaphore;
|
||||
}
|
||||
|
||||
-(id)init;
|
||||
-(void)signal;
|
||||
-(void)timedWait:(int)microseconds;
|
||||
-(void)wait;
|
||||
-(void)waitIndefinitely;
|
||||
- (id)init;
|
||||
- (void)signal;
|
||||
- (void)timedWait:(int)microseconds;
|
||||
- (void)wait;
|
||||
- (void)waitIndefinitely;
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,41 +8,34 @@
|
|||
|
||||
#import "Semaphore.h"
|
||||
|
||||
|
||||
@implementation Semaphore
|
||||
|
||||
-(id)init
|
||||
{
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
if(self) {
|
||||
semaphore_create(mach_task_self(), &semaphore, SYNC_POLICY_FIFO, 0);
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)signal
|
||||
{
|
||||
- (void)signal {
|
||||
semaphore_signal_all(semaphore);
|
||||
}
|
||||
|
||||
-(void)timedWait:(int)microseconds
|
||||
{
|
||||
mach_timespec_t timeout = {0, microseconds * 1000UL};
|
||||
|
||||
- (void)timedWait:(int)microseconds {
|
||||
mach_timespec_t timeout = { 0, microseconds * 1000UL };
|
||||
|
||||
semaphore_timedwait(semaphore, timeout);
|
||||
}
|
||||
|
||||
-(void)wait
|
||||
{
|
||||
mach_timespec_t t = {2.0, 0.0}; //2 second timeout
|
||||
- (void)wait {
|
||||
mach_timespec_t t = { 2.0, 0.0 }; // 2 second timeout
|
||||
semaphore_timedwait(semaphore, t);
|
||||
}
|
||||
|
||||
-(void)waitIndefinitely
|
||||
{
|
||||
semaphore_wait(semaphore);
|
||||
- (void)waitIndefinitely {
|
||||
semaphore_wait(semaphore);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -24,23 +24,22 @@
|
|||
|
||||
@class PlaylistEntry;
|
||||
|
||||
@interface AudioScrobbler : NSObject
|
||||
{
|
||||
NSString *_pluginID;
|
||||
NSMutableArray *_queue;
|
||||
@interface AudioScrobbler : NSObject {
|
||||
NSString *_pluginID;
|
||||
NSMutableArray *_queue;
|
||||
|
||||
BOOL _audioScrobblerThreadCompleted;
|
||||
BOOL _keepProcessingAudioScrobblerCommands;
|
||||
semaphore_t _semaphore;
|
||||
BOOL _audioScrobblerThreadCompleted;
|
||||
BOOL _keepProcessingAudioScrobblerCommands;
|
||||
semaphore_t _semaphore;
|
||||
}
|
||||
|
||||
+ (BOOL) isRunning;
|
||||
+ (BOOL)isRunning;
|
||||
|
||||
- (void) start:(PlaylistEntry *)pe;
|
||||
- (void) stop;
|
||||
- (void) pause;
|
||||
- (void) resume;
|
||||
- (void)start:(PlaylistEntry *)pe;
|
||||
- (void)stop;
|
||||
- (void)pause;
|
||||
- (void)resume;
|
||||
|
||||
- (void) shutdown;
|
||||
- (void)shutdown;
|
||||
|
||||
@end
|
||||
|
|
|
@ -28,128 +28,117 @@
|
|||
// ========================================
|
||||
// Symbolic Constants
|
||||
// ========================================
|
||||
NSString * const AudioScrobblerRunLoopMode = @"org.cogx.Cog.AudioScrobbler.RunLoopMode";
|
||||
NSString *const AudioScrobblerRunLoopMode = @"org.cogx.Cog.AudioScrobbler.RunLoopMode";
|
||||
|
||||
// ========================================
|
||||
// Helpers
|
||||
// ========================================
|
||||
static NSString *
|
||||
escapeForLastFM(NSString *string)
|
||||
{
|
||||
static NSString *
|
||||
escapeForLastFM(NSString *string) {
|
||||
NSMutableString *result = [string mutableCopy];
|
||||
|
||||
[result replaceOccurrencesOfString:@"&"
|
||||
withString:@"&&"
|
||||
options:NSLiteralSearch
|
||||
range:NSMakeRange(0, [result length])];
|
||||
|
||||
|
||||
[result replaceOccurrencesOfString:@"&"
|
||||
withString:@"&&"
|
||||
options:NSLiteralSearch
|
||||
range:NSMakeRange(0, [result length])];
|
||||
|
||||
return (nil == result ? @"" : result);
|
||||
}
|
||||
|
||||
@interface AudioScrobbler (Private)
|
||||
|
||||
- (NSMutableArray *) queue;
|
||||
- (NSString *) pluginID;
|
||||
- (NSMutableArray *)queue;
|
||||
- (NSString *)pluginID;
|
||||
|
||||
- (void) sendCommand:(NSString *)command;
|
||||
- (void)sendCommand:(NSString *)command;
|
||||
|
||||
- (BOOL) keepProcessingAudioScrobblerCommands;
|
||||
- (void) setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands;
|
||||
- (BOOL)keepProcessingAudioScrobblerCommands;
|
||||
- (void)setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands;
|
||||
|
||||
- (BOOL) audioScrobblerThreadCompleted;
|
||||
- (void) setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted;
|
||||
- (BOOL)audioScrobblerThreadCompleted;
|
||||
- (void)setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted;
|
||||
|
||||
- (semaphore_t) semaphore;
|
||||
- (semaphore_t)semaphore;
|
||||
|
||||
- (void) processAudioScrobblerCommands:(id)unused;
|
||||
- (void)processAudioScrobblerCommands:(id)unused;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AudioScrobbler
|
||||
|
||||
+ (BOOL) isRunning
|
||||
{
|
||||
NSArray *launchedApps = [[NSWorkspace sharedWorkspace] runningApplications];
|
||||
BOOL running = NO;
|
||||
for(NSRunningApplication *app in launchedApps) {
|
||||
if([[app bundleIdentifier] isEqualToString:@"fm.last.Last.fm"] ||
|
||||
[[app bundleIdentifier] isEqualToString:@"fm.last.Scrobbler"]) {
|
||||
running = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return running;
|
||||
+ (BOOL)isRunning {
|
||||
NSArray *launchedApps = [[NSWorkspace sharedWorkspace] runningApplications];
|
||||
BOOL running = NO;
|
||||
for(NSRunningApplication *app in launchedApps) {
|
||||
if([[app bundleIdentifier] isEqualToString:@"fm.last.Last.fm"] ||
|
||||
[[app bundleIdentifier] isEqualToString:@"fm.last.Scrobbler"]) {
|
||||
running = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return running;
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
- (id)init {
|
||||
if((self = [super init])) {
|
||||
|
||||
_pluginID = @"cog";
|
||||
|
||||
|
||||
if([[NSUserDefaults standardUserDefaults] boolForKey:@"automaticallyLaunchLastFM"]) {
|
||||
|
||||
if(![AudioScrobbler isRunning]) {
|
||||
[[NSWorkspace sharedWorkspace] launchApplication:@"Last.fm.app"];
|
||||
}
|
||||
}
|
||||
|
||||
if(![AudioScrobbler isRunning]) {
|
||||
[[NSWorkspace sharedWorkspace] launchApplication:@"Last.fm.app"];
|
||||
}
|
||||
}
|
||||
|
||||
_keepProcessingAudioScrobblerCommands = YES;
|
||||
|
||||
kern_return_t result = semaphore_create(mach_task_self(), &_semaphore, SYNC_POLICY_FIFO, 0);
|
||||
|
||||
|
||||
if(KERN_SUCCESS != result) {
|
||||
ALog(@"Couldn't create semaphore (%s).", mach_error_type(result));
|
||||
|
||||
self = nil;
|
||||
self = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
[NSThread detachNewThreadSelector:@selector(processAudioScrobblerCommands:) toTarget:self withObject:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
if([self keepProcessingAudioScrobblerCommands] || NO == [self audioScrobblerThreadCompleted])
|
||||
[self shutdown];
|
||||
|
||||
|
||||
_queue = nil;
|
||||
|
||||
semaphore_destroy(mach_task_self(), _semaphore); _semaphore = 0;
|
||||
|
||||
semaphore_destroy(mach_task_self(), _semaphore);
|
||||
_semaphore = 0;
|
||||
}
|
||||
|
||||
- (void) start:(PlaylistEntry *)pe
|
||||
{
|
||||
[self sendCommand:[NSString stringWithFormat:@"START c=%@&a=%@&t=%@&b=%@&m=%@&l=%i&p=%@\n",
|
||||
[self pluginID],
|
||||
escapeForLastFM([pe artist]),
|
||||
escapeForLastFM([pe title]),
|
||||
escapeForLastFM([pe album]),
|
||||
@"", // TODO: MusicBrainz support
|
||||
[[pe length] intValue],
|
||||
escapeForLastFM([[pe URL] path])
|
||||
]];
|
||||
- (void)start:(PlaylistEntry *)pe {
|
||||
[self sendCommand:[NSString stringWithFormat:@"START c=%@&a=%@&t=%@&b=%@&m=%@&l=%i&p=%@\n",
|
||||
[self pluginID],
|
||||
escapeForLastFM([pe artist]),
|
||||
escapeForLastFM([pe title]),
|
||||
escapeForLastFM([pe album]),
|
||||
@"", // TODO: MusicBrainz support
|
||||
[[pe length] intValue],
|
||||
escapeForLastFM([[pe URL] path])]];
|
||||
}
|
||||
|
||||
- (void) stop
|
||||
{
|
||||
- (void)stop {
|
||||
[self sendCommand:[NSString stringWithFormat:@"STOP c=%@\n", [self pluginID]]];
|
||||
}
|
||||
|
||||
- (void) pause
|
||||
{
|
||||
- (void)pause {
|
||||
[self sendCommand:[NSString stringWithFormat:@"PAUSE c=%@\n", [self pluginID]]];
|
||||
}
|
||||
|
||||
- (void) resume
|
||||
{
|
||||
- (void)resume {
|
||||
[self sendCommand:[NSString stringWithFormat:@"RESUME c=%@\n", [self pluginID]]];
|
||||
}
|
||||
|
||||
- (void) shutdown
|
||||
{
|
||||
- (void)shutdown {
|
||||
[self setKeepProcessingAudioScrobblerCommands:NO];
|
||||
semaphore_signal([self semaphore]);
|
||||
|
||||
|
@ -162,119 +151,109 @@ escapeForLastFM(NSString *string)
|
|||
|
||||
@implementation AudioScrobbler (Private)
|
||||
|
||||
- (NSMutableArray *) queue
|
||||
{
|
||||
- (NSMutableArray *)queue {
|
||||
if(nil == _queue)
|
||||
_queue = [[NSMutableArray alloc] init];
|
||||
|
||||
|
||||
return _queue;
|
||||
}
|
||||
|
||||
- (NSString *) pluginID
|
||||
{
|
||||
- (NSString *)pluginID {
|
||||
return _pluginID;
|
||||
}
|
||||
|
||||
- (void) sendCommand:(NSString *)command
|
||||
{
|
||||
- (void)sendCommand:(NSString *)command {
|
||||
@synchronized([self queue]) {
|
||||
[[self queue] addObject:command];
|
||||
}
|
||||
semaphore_signal([self semaphore]);
|
||||
}
|
||||
|
||||
- (BOOL) keepProcessingAudioScrobblerCommands
|
||||
{
|
||||
- (BOOL)keepProcessingAudioScrobblerCommands {
|
||||
return _keepProcessingAudioScrobblerCommands;
|
||||
}
|
||||
|
||||
- (void) setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands
|
||||
{
|
||||
- (void)setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands {
|
||||
_keepProcessingAudioScrobblerCommands = keepProcessingAudioScrobblerCommands;
|
||||
}
|
||||
|
||||
- (BOOL) audioScrobblerThreadCompleted
|
||||
{
|
||||
- (BOOL)audioScrobblerThreadCompleted {
|
||||
return _audioScrobblerThreadCompleted;
|
||||
}
|
||||
|
||||
- (void) setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted
|
||||
{
|
||||
- (void)setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted {
|
||||
_audioScrobblerThreadCompleted = audioScrobblerThreadCompleted;
|
||||
}
|
||||
|
||||
- (semaphore_t) semaphore
|
||||
{
|
||||
- (semaphore_t)semaphore {
|
||||
return _semaphore;
|
||||
}
|
||||
|
||||
- (void) processAudioScrobblerCommands:(id)unused
|
||||
{
|
||||
@autoreleasepool {
|
||||
AudioScrobblerClient *client = [[AudioScrobblerClient alloc] init];
|
||||
mach_timespec_t timeout = { 5, 0 };
|
||||
NSString *command = nil;
|
||||
NSString *response = nil;
|
||||
in_port_t port = 33367;
|
||||
|
||||
while([self keepProcessingAudioScrobblerCommands]) {
|
||||
@autoreleasepool {
|
||||
- (void)processAudioScrobblerCommands:(id)unused {
|
||||
@autoreleasepool {
|
||||
AudioScrobblerClient *client = [[AudioScrobblerClient alloc] init];
|
||||
mach_timespec_t timeout = { 5, 0 };
|
||||
NSString *command = nil;
|
||||
NSString *response = nil;
|
||||
in_port_t port = 33367;
|
||||
|
||||
// Get the first command to be sent
|
||||
@synchronized([self queue]) {
|
||||
if ([[self queue] count]) {
|
||||
command = [[self queue] objectAtIndex:0];
|
||||
[[self queue] removeObjectAtIndex:0];
|
||||
}
|
||||
}
|
||||
while([self keepProcessingAudioScrobblerCommands]) {
|
||||
@autoreleasepool {
|
||||
// Get the first command to be sent
|
||||
@synchronized([self queue]) {
|
||||
if([[self queue] count]) {
|
||||
command = [[self queue] objectAtIndex:0];
|
||||
[[self queue] removeObjectAtIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
if(nil != command) {
|
||||
@try {
|
||||
if([client connectToHost:@"localhost" port:port]) {
|
||||
port = [client connectedPort];
|
||||
[client send:command];
|
||||
command = nil;
|
||||
|
||||
response = [client receive];
|
||||
if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)])
|
||||
ALog(@"AudioScrobbler error: %@", response);
|
||||
|
||||
[client shutdown];
|
||||
}
|
||||
}
|
||||
|
||||
@catch(NSException *exception) {
|
||||
command = nil;
|
||||
|
||||
[client shutdown];
|
||||
// ALog(@"Exception: %@",exception);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
semaphore_timedwait([self semaphore], timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Send a final stop command to cleanup
|
||||
@try {
|
||||
if([client connectToHost:@"localhost" port:port]) {
|
||||
[client send:[NSString stringWithFormat:@"STOP c=%@\n", [self pluginID]]];
|
||||
|
||||
response = [client receive];
|
||||
if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)])
|
||||
ALog(@"AudioScrobbler error: %@", response);
|
||||
|
||||
[client shutdown];
|
||||
}
|
||||
}
|
||||
|
||||
@catch(NSException *exception) {
|
||||
[client shutdown];
|
||||
}
|
||||
|
||||
[self setAudioScrobblerThreadCompleted:YES];
|
||||
}
|
||||
if(nil != command) {
|
||||
@try {
|
||||
if([client connectToHost:@"localhost" port:port]) {
|
||||
port = [client connectedPort];
|
||||
[client send:command];
|
||||
command = nil;
|
||||
|
||||
response = [client receive];
|
||||
if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0, 2)])
|
||||
ALog(@"AudioScrobbler error: %@", response);
|
||||
|
||||
[client shutdown];
|
||||
}
|
||||
}
|
||||
|
||||
@catch(NSException *exception) {
|
||||
command = nil;
|
||||
|
||||
[client shutdown];
|
||||
// ALog(@"Exception: %@",exception);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
semaphore_timedwait([self semaphore], timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Send a final stop command to cleanup
|
||||
@try {
|
||||
if([client connectToHost:@"localhost" port:port]) {
|
||||
[client send:[NSString stringWithFormat:@"STOP c=%@\n", [self pluginID]]];
|
||||
|
||||
response = [client receive];
|
||||
if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0, 2)])
|
||||
ALog(@"AudioScrobbler error: %@", response);
|
||||
|
||||
[client shutdown];
|
||||
}
|
||||
}
|
||||
|
||||
@catch(NSException *exception) {
|
||||
[client shutdown];
|
||||
}
|
||||
|
||||
[self setAudioScrobblerThreadCompleted:YES];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -22,21 +22,20 @@
|
|||
|
||||
#include <netdb.h>
|
||||
|
||||
@interface AudioScrobblerClient : NSObject
|
||||
{
|
||||
int _socket;
|
||||
BOOL _doPortStepping;
|
||||
in_port_t _port;
|
||||
@interface AudioScrobblerClient : NSObject {
|
||||
int _socket;
|
||||
BOOL _doPortStepping;
|
||||
in_port_t _port;
|
||||
}
|
||||
|
||||
- (BOOL) connectToHost:(NSString *)hostname port:(in_port_t)port;
|
||||
- (BOOL)connectToHost:(NSString *)hostname port:(in_port_t)port;
|
||||
|
||||
- (BOOL) isConnected;
|
||||
- (in_port_t) connectedPort;
|
||||
- (BOOL)isConnected;
|
||||
- (in_port_t)connectedPort;
|
||||
|
||||
- (void) send:(NSString *)data;
|
||||
- (NSString *) receive;
|
||||
- (void)send:(NSString *)data;
|
||||
- (NSString *)receive;
|
||||
|
||||
- (void) shutdown;
|
||||
- (void)shutdown;
|
||||
|
||||
@end
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* This is a port of the BlockingClient client class from
|
||||
* This is a port of the BlockingClient client class from
|
||||
* the Last.fm ScrobSub library by sharevari
|
||||
*/
|
||||
|
||||
|
@ -29,135 +29,127 @@
|
|||
|
||||
#import "Logging.h"
|
||||
|
||||
#define kBufferSize 1024
|
||||
#define kPortsToStep 5
|
||||
#define kBufferSize 1024
|
||||
#define kPortsToStep 5
|
||||
|
||||
static in_addr_t
|
||||
addressForHost(NSString *hostname)
|
||||
{
|
||||
static in_addr_t
|
||||
addressForHost(NSString *hostname) {
|
||||
NSCParameterAssert(nil != hostname);
|
||||
|
||||
in_addr_t address;
|
||||
struct hostent *hostinfo;
|
||||
|
||||
|
||||
in_addr_t address;
|
||||
struct hostent *hostinfo;
|
||||
|
||||
address = inet_addr([hostname cStringUsingEncoding:NSASCIIStringEncoding]);
|
||||
|
||||
if(INADDR_NONE == address) {
|
||||
hostinfo = gethostbyname([hostname cStringUsingEncoding:NSASCIIStringEncoding]);
|
||||
if(NULL == hostinfo) {
|
||||
|
||||
if(INADDR_NONE == address) {
|
||||
hostinfo = gethostbyname([hostname cStringUsingEncoding:NSASCIIStringEncoding]);
|
||||
if(NULL == hostinfo) {
|
||||
ALog(@"AudioScrobblerClient error: Unable to resolve address for \"%@\".", hostname);
|
||||
return INADDR_NONE;
|
||||
}
|
||||
|
||||
address = *((in_addr_t *)hostinfo->h_addr_list[0]);
|
||||
}
|
||||
}
|
||||
|
||||
address = *((in_addr_t *)hostinfo->h_addr_list[0]);
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
@interface AudioScrobblerClient (Private)
|
||||
- (BOOL) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port;
|
||||
- (BOOL)connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port;
|
||||
@end
|
||||
|
||||
@implementation AudioScrobblerClient
|
||||
|
||||
- (id) init
|
||||
{
|
||||
- (id)init {
|
||||
if((self = [super init])) {
|
||||
_socket = -1;
|
||||
_doPortStepping = YES;
|
||||
_socket = -1;
|
||||
_doPortStepping = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL) connectToHost:(NSString *)hostname port:(in_port_t)port
|
||||
{
|
||||
- (BOOL)connectToHost:(NSString *)hostname port:(in_port_t)port {
|
||||
NSParameterAssert(nil != hostname);
|
||||
|
||||
|
||||
in_addr_t remoteAddress = addressForHost(hostname);
|
||||
|
||||
|
||||
if(INADDR_NONE != remoteAddress)
|
||||
return [self connectToSocket:remoteAddress port:port];
|
||||
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL) isConnected
|
||||
{
|
||||
- (BOOL)isConnected {
|
||||
return (-1 != _socket);
|
||||
}
|
||||
|
||||
- (in_port_t) connectedPort
|
||||
{
|
||||
- (in_port_t)connectedPort {
|
||||
return _port;
|
||||
}
|
||||
|
||||
- (void) send:(NSString *)data
|
||||
{
|
||||
const char *utf8data = [data UTF8String];
|
||||
unsigned len = (unsigned int) strlen(utf8data);
|
||||
unsigned bytesToSend = len;
|
||||
unsigned totalBytesSent = 0;
|
||||
ssize_t bytesSent = 0;
|
||||
- (void)send:(NSString *)data {
|
||||
const char *utf8data = [data UTF8String];
|
||||
unsigned len = (unsigned int)strlen(utf8data);
|
||||
unsigned bytesToSend = len;
|
||||
unsigned totalBytesSent = 0;
|
||||
ssize_t bytesSent = 0;
|
||||
|
||||
if(NO == [self isConnected]) {
|
||||
ALog(@"AudioScrobblerClient error: Can't send data, client not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
while(totalBytesSent < bytesToSend && -1 != bytesSent) {
|
||||
bytesSent = send(_socket, utf8data + totalBytesSent, bytesToSend - totalBytesSent, 0);
|
||||
|
||||
|
||||
if(-1 == bytesSent || 0 == bytesSent)
|
||||
ALog(@"AudioScrobblerClient error: Unable to send data through socket: %s", strerror(errno));
|
||||
|
||||
|
||||
totalBytesSent += bytesSent;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *) receive
|
||||
{
|
||||
char buffer [ kBufferSize ];
|
||||
int readSize = kBufferSize - 1;
|
||||
ssize_t bytesRead = 0;
|
||||
BOOL keepGoing = YES;
|
||||
NSString *result = nil;
|
||||
- (NSString *)receive {
|
||||
char buffer[kBufferSize];
|
||||
int readSize = kBufferSize - 1;
|
||||
ssize_t bytesRead = 0;
|
||||
BOOL keepGoing = YES;
|
||||
NSString *result = nil;
|
||||
|
||||
if(NO == [self isConnected]) {
|
||||
ALog(@"AudioScrobblerClient error: Can't receive data, client not connected");
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
do {
|
||||
bytesRead = recv(_socket, buffer, readSize, 0);
|
||||
if(-1 == bytesRead || 0 == bytesRead) {
|
||||
ALog(@"AudioScrobblerClient error: Unable to receive data through socket: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
if('\n' == buffer[bytesRead - 1]) {
|
||||
|
||||
if('\n' == buffer[bytesRead - 1]) {
|
||||
--bytesRead;
|
||||
keepGoing = NO;
|
||||
}
|
||||
|
||||
buffer[bytesRead] = '\0';
|
||||
result = [[NSString alloc] initWithUTF8String:buffer];
|
||||
|
||||
buffer[bytesRead] = '\0';
|
||||
result = [[NSString alloc] initWithUTF8String:buffer];
|
||||
|
||||
} while(keepGoing);
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void) shutdown
|
||||
{
|
||||
int result;
|
||||
char buffer [ kBufferSize ];
|
||||
ssize_t bytesRead;
|
||||
|
||||
- (void)shutdown {
|
||||
int result;
|
||||
char buffer[kBufferSize];
|
||||
ssize_t bytesRead;
|
||||
|
||||
if(NO == [self isConnected]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
result = shutdown(_socket, SHUT_WR);
|
||||
if(-1 == result)
|
||||
ALog(@"AudioScrobblerClient error: Socket shutdown failed: %s", strerror(errno));
|
||||
|
@ -166,36 +158,34 @@ addressForHost(NSString *hostname)
|
|||
bytesRead = recv(_socket, buffer, kBufferSize, 0);
|
||||
if(-1 == bytesRead)
|
||||
ALog(@"AudioScrobblerClient error: Waiting for shutdown confirmation failed: %s", strerror(errno));
|
||||
|
||||
|
||||
if(0 != bytesRead) {
|
||||
NSString *received = [[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding];
|
||||
ALog(@"Received unexpected bytes during shutdown: %@", received);
|
||||
}
|
||||
else
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
result = close(_socket);
|
||||
if(-1 == result)
|
||||
ALog(@"Couldn't close socket (%s)", strerror(errno));
|
||||
|
||||
_socket = -1;
|
||||
_port = 0;
|
||||
|
||||
_socket = -1;
|
||||
_port = 0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation AudioScrobblerClient (Private)
|
||||
|
||||
- (BOOL) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port
|
||||
{
|
||||
- (BOOL)connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port {
|
||||
NSParameterAssert(INADDR_NONE != remoteAddress);
|
||||
|
||||
|
||||
_port = port;
|
||||
|
||||
|
||||
int result;
|
||||
do {
|
||||
struct sockaddr_in socketAddress;
|
||||
struct sockaddr_in socketAddress;
|
||||
|
||||
_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if(-1 == _socket) {
|
||||
|
@ -203,9 +193,9 @@ addressForHost(NSString *hostname)
|
|||
return NO;
|
||||
}
|
||||
|
||||
socketAddress.sin_family = AF_INET;
|
||||
socketAddress.sin_addr.s_addr = remoteAddress;
|
||||
socketAddress.sin_port = htons(_port);
|
||||
socketAddress.sin_family = AF_INET;
|
||||
socketAddress.sin_addr.s_addr = remoteAddress;
|
||||
socketAddress.sin_port = htons(_port);
|
||||
|
||||
result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in));
|
||||
if(-1 == result) {
|
||||
|
@ -214,7 +204,7 @@ addressForHost(NSString *hostname)
|
|||
|
||||
_port++;
|
||||
}
|
||||
} while (YES == _doPortStepping && -1 == result && _port < (port + kPortsToStep));
|
||||
} while(YES == _doPortStepping && -1 == result && _port < (port + kPortsToStep));
|
||||
|
||||
return (-1 != result);
|
||||
}
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
// Copyright 2005 Vincent Spader All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "FeedbackSocket.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface FeedbackController : NSWindowController<FeedbackSocketDelegate> {
|
||||
@interface FeedbackController : NSWindowController <FeedbackSocketDelegate> {
|
||||
IBOutlet NSTextField* fromView;
|
||||
IBOutlet NSTextField* subjectView;
|
||||
IBOutlet NSTextView* messageView;
|
||||
IBOutlet NSProgressIndicator *sendingIndicator;
|
||||
|
||||
FeedbackSocket *feedbackSocket;
|
||||
IBOutlet NSProgressIndicator* sendingIndicator;
|
||||
|
||||
FeedbackSocket* feedbackSocket;
|
||||
}
|
||||
|
||||
- (IBAction)sendFeedback:(id)sender;
|
||||
|
|
|
@ -12,43 +12,37 @@
|
|||
|
||||
@implementation FeedbackController
|
||||
|
||||
- (id)init
|
||||
{
|
||||
- (id)init {
|
||||
return [super initWithWindowNibName:@"Feedback"];
|
||||
}
|
||||
|
||||
- (IBAction)showWindow:(id)sender
|
||||
{
|
||||
- (IBAction)showWindow:(id)sender {
|
||||
[fromView setStringValue:@""];
|
||||
[subjectView setStringValue:@""];
|
||||
[messageView setString:@""];
|
||||
|
||||
|
||||
[super showWindow:sender];
|
||||
}
|
||||
|
||||
- (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo
|
||||
{
|
||||
if ([(NSNumber *)CFBridgingRelease(contextInfo) boolValue]== YES)
|
||||
{
|
||||
- (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo {
|
||||
if([(NSNumber *)CFBridgingRelease(contextInfo) boolValue] == YES) {
|
||||
[[self window] close];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)feedbackDidNotSend:(FeedbackSocket *)feedback
|
||||
{
|
||||
- (void)feedbackDidNotSend:(FeedbackSocket *)feedback {
|
||||
ALog(@"Error sending feedback");
|
||||
|
||||
|
||||
[sendingIndicator stopAnimation:self];
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:NSLocalizedString(@"FeedbackFailedMessageText", @"")];
|
||||
[alert setInformativeText:NSLocalizedString(@"FeedbackFailedInformativeText", @"")];
|
||||
|
||||
|
||||
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:(void *)CFBridgingRetain([NSNumber numberWithBool:NO])];
|
||||
}
|
||||
|
||||
- (void)feedbackDidSend:(FeedbackSocket *)feedback
|
||||
{
|
||||
- (void)feedbackDidSend:(FeedbackSocket *)feedback {
|
||||
[sendingIndicator stopAnimation:self];
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
|
@ -58,22 +52,19 @@
|
|||
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:(void *)CFBridgingRetain([NSNumber numberWithBool:YES])];
|
||||
}
|
||||
|
||||
|
||||
- (IBAction)sendFeedback:(id)sender
|
||||
{
|
||||
- (IBAction)sendFeedback:(id)sender {
|
||||
[sendingIndicator startAnimation:self];
|
||||
|
||||
//Using this so that if its a bad connection, it doesnt sit there looking stupid..or should it
|
||||
|
||||
// Using this so that if its a bad connection, it doesnt sit there looking stupid..or should it
|
||||
feedbackSocket = [[FeedbackSocket alloc] init];
|
||||
[feedbackSocket setDelegate:self];
|
||||
|
||||
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
|
||||
|
||||
[feedbackSocket sendFeedback:[fromView stringValue] subject:[subjectView stringValue] message:[messageView string] version:version];
|
||||
}
|
||||
|
||||
- (IBAction)cancel:(id)sender
|
||||
{
|
||||
- (IBAction)cancel:(id)sender {
|
||||
[[self window] close];
|
||||
}
|
||||
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
NSString *subject;
|
||||
NSString *message;
|
||||
NSString *version;
|
||||
|
||||
|
||||
id<FeedbackSocketDelegate> delegate;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<FeedbackSocketDelegate>)d;
|
||||
- (void)sendFeedback: (NSString *)f subject:(NSString *)s message:(NSString *)m version:(NSString *)v;
|
||||
- (void)sendFeedback:(NSString *)f subject:(NSString *)s message:(NSString *)m version:(NSString *)v;
|
||||
|
||||
- (void)setFrom:(NSString *)f;
|
||||
- (void)setSubject:(NSString *)s;
|
||||
|
@ -29,7 +29,7 @@
|
|||
|
||||
@end
|
||||
|
||||
@protocol FeedbackSocketDelegate<NSObject>
|
||||
@protocol FeedbackSocketDelegate <NSObject>
|
||||
|
||||
- (void)feedbackDidSend:(FeedbackSocket *)feedback;
|
||||
- (void)feedbackDidNotSend:(FeedbackSocket *)feedback;
|
||||
|
|
|
@ -12,97 +12,82 @@
|
|||
|
||||
@implementation FeedbackSocket
|
||||
|
||||
NSString *encodeForURL(NSString *s)
|
||||
{
|
||||
return (NSString*) CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)s, NULL, NULL, kCFStringEncodingUTF8));
|
||||
NSString *encodeForURL(NSString *s) {
|
||||
return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)s, NULL, NULL, kCFStringEncodingUTF8));
|
||||
}
|
||||
|
||||
- (void)sendFeedbackThread:(id)sender
|
||||
{
|
||||
@autoreleasepool {
|
||||
NSString *f = encodeForURL(from);
|
||||
NSString *s = encodeForURL(subject);
|
||||
NSString *m = encodeForURL(message);
|
||||
NSString *v = encodeForURL(version);
|
||||
|
||||
NSString *postString = [NSString stringWithFormat:@"from=%@&subject=%@&message=%@&version=%@", f, s, m, v];
|
||||
|
||||
NSData *postData = [postString dataUsingEncoding:NSASCIIStringEncoding];
|
||||
|
||||
NSURL *url = [NSURL URLWithString:@"https://kode54.net/cog/feedback.php"];
|
||||
NSMutableURLRequest *post = [NSMutableURLRequest requestWithURL:url];
|
||||
|
||||
[post addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
|
||||
[post setHTTPMethod:@"POST"];
|
||||
[post setHTTPBody:postData];
|
||||
|
||||
NSError* error;
|
||||
NSURLResponse* response;
|
||||
NSData* resultData = [NSURLConnection sendSynchronousRequest:post returningResponse:&response error:&error];
|
||||
NSString *resultString = [[NSString alloc] initWithData:resultData encoding:NSASCIIStringEncoding];
|
||||
//DLog(@"RESULT: %@", resultString);
|
||||
if ([resultString caseInsensitiveCompare:@"SUCCESS"] == NSOrderedSame)
|
||||
{
|
||||
[self performSelectorOnMainThread:@selector(returnSuccess:) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self performSelectorOnMainThread:@selector(returnFailure:) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
}
|
||||
- (void)sendFeedbackThread:(id)sender {
|
||||
@autoreleasepool {
|
||||
NSString *f = encodeForURL(from);
|
||||
NSString *s = encodeForURL(subject);
|
||||
NSString *m = encodeForURL(message);
|
||||
NSString *v = encodeForURL(version);
|
||||
|
||||
NSString *postString = [NSString stringWithFormat:@"from=%@&subject=%@&message=%@&version=%@", f, s, m, v];
|
||||
|
||||
NSData *postData = [postString dataUsingEncoding:NSASCIIStringEncoding];
|
||||
|
||||
NSURL *url = [NSURL URLWithString:@"https://kode54.net/cog/feedback.php"];
|
||||
NSMutableURLRequest *post = [NSMutableURLRequest requestWithURL:url];
|
||||
|
||||
[post addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
|
||||
[post setHTTPMethod:@"POST"];
|
||||
[post setHTTPBody:postData];
|
||||
|
||||
NSError *error;
|
||||
NSURLResponse *response;
|
||||
NSData *resultData = [NSURLConnection sendSynchronousRequest:post returningResponse:&response error:&error];
|
||||
NSString *resultString = [[NSString alloc] initWithData:resultData encoding:NSASCIIStringEncoding];
|
||||
// DLog(@"RESULT: %@", resultString);
|
||||
if([resultString caseInsensitiveCompare:@"SUCCESS"] == NSOrderedSame) {
|
||||
[self performSelectorOnMainThread:@selector(returnSuccess:) withObject:nil waitUntilDone:NO];
|
||||
} else {
|
||||
[self performSelectorOnMainThread:@selector(returnFailure:) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendFeedback: (NSString *)f subject:(NSString *)s message:(NSString *)m version:(NSString *)v
|
||||
{
|
||||
if ([f isEqualToString:@""])
|
||||
{
|
||||
- (void)sendFeedback:(NSString *)f subject:(NSString *)s message:(NSString *)m version:(NSString *)v {
|
||||
if([f isEqualToString:@""]) {
|
||||
f = @"Anonymous";
|
||||
}
|
||||
[self setFrom:f];
|
||||
[self setSubject:s];
|
||||
[self setMessage:m];
|
||||
[self setVersion:v];
|
||||
|
||||
[NSThread detachNewThreadSelector:@selector(sendFeedbackThread:) toTarget:self withObject:nil];
|
||||
|
||||
[NSThread detachNewThreadSelector:@selector(sendFeedbackThread:) toTarget:self withObject:nil];
|
||||
}
|
||||
|
||||
- (void)returnSuccess:(id)userInfo
|
||||
{
|
||||
if ([delegate respondsToSelector:@selector(feedbackDidSend:)]) {
|
||||
- (void)returnSuccess:(id)userInfo {
|
||||
if([delegate respondsToSelector:@selector(feedbackDidSend:)]) {
|
||||
[delegate feedbackDidSend:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)returnFailure:(id)userInfo
|
||||
{
|
||||
if ([delegate respondsToSelector:@selector(feedbackDidNotSend:)]) {
|
||||
- (void)returnFailure:(id)userInfo {
|
||||
if([delegate respondsToSelector:@selector(feedbackDidNotSend:)]) {
|
||||
[delegate feedbackDidNotSend:self];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)setDelegate:(id<FeedbackSocketDelegate>)d
|
||||
{
|
||||
delegate = d;
|
||||
- (void)setDelegate:(id<FeedbackSocketDelegate>)d {
|
||||
delegate = d;
|
||||
}
|
||||
|
||||
|
||||
- (void)setFrom:(NSString *)f
|
||||
{
|
||||
- (void)setFrom:(NSString *)f {
|
||||
from = f;
|
||||
}
|
||||
|
||||
- (void)setSubject:(NSString *)s
|
||||
{
|
||||
- (void)setSubject:(NSString *)s {
|
||||
subject = s;
|
||||
}
|
||||
|
||||
- (void)setMessage:(NSString *)m
|
||||
{
|
||||
- (void)setMessage:(NSString *)m {
|
||||
message = m;
|
||||
}
|
||||
|
||||
- (void)setVersion:(NSString *)v
|
||||
{
|
||||
- (void)setVersion:(NSString *)v {
|
||||
version = v;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
#import "PathNode.h"
|
||||
|
||||
@interface ContainedNode : PathNode {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -9,38 +9,33 @@
|
|||
#import "ContainedNode.h"
|
||||
#import "CogAudio/AudioMetadataReader.h"
|
||||
|
||||
|
||||
@implementation ContainedNode
|
||||
|
||||
- (BOOL)isLeaf
|
||||
{
|
||||
- (BOOL)isLeaf {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setURL:(NSURL *)u
|
||||
{
|
||||
- (void)setURL:(NSURL *)u {
|
||||
[super setURL:u];
|
||||
|
||||
if ([u fragment])
|
||||
{
|
||||
NSDictionary *metadata = [AudioMetadataReader metadataForURL:u];
|
||||
NSString *title = nil;
|
||||
NSString *artist = nil;
|
||||
if (metadata)
|
||||
{
|
||||
title = [metadata valueForKey:@"title"];
|
||||
artist = [metadata valueForKey:@"artist"];
|
||||
}
|
||||
|
||||
if (title && [title length])
|
||||
{
|
||||
if (artist && [artist length]) { display = [[u fragment] stringByAppendingFormat:@": %@ - %@", artist, title];}
|
||||
else { display = [[u fragment] stringByAppendingFormat:@": %@", title]; }
|
||||
}
|
||||
else
|
||||
{
|
||||
display = [u fragment];
|
||||
}
|
||||
|
||||
if([u fragment]) {
|
||||
NSDictionary *metadata = [AudioMetadataReader metadataForURL:u];
|
||||
NSString *title = nil;
|
||||
NSString *artist = nil;
|
||||
if(metadata) {
|
||||
title = [metadata valueForKey:@"title"];
|
||||
artist = [metadata valueForKey:@"artist"];
|
||||
}
|
||||
|
||||
if(title && [title length]) {
|
||||
if(artist && [artist length]) {
|
||||
display = [[u fragment] stringByAppendingFormat:@": %@ - %@", artist, title];
|
||||
} else {
|
||||
display = [[u fragment] stringByAppendingFormat:@": %@", title];
|
||||
}
|
||||
} else {
|
||||
display = [u fragment];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#import "PathNode.h"
|
||||
|
||||
@interface ContainerNode : PathNode {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -15,23 +15,20 @@
|
|||
|
||||
@implementation ContainerNode
|
||||
|
||||
- (BOOL)isLeaf
|
||||
{
|
||||
- (BOOL)isLeaf {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)updatePath
|
||||
{
|
||||
- (void)updatePath {
|
||||
NSArray *urls = [AudioContainer urlsForContainerURL:url];
|
||||
|
||||
|
||||
NSMutableArray *paths = [[NSMutableArray alloc] init];
|
||||
for (NSURL *u in urls)
|
||||
{
|
||||
for(NSURL *u in urls) {
|
||||
ContainedNode *node = [[ContainedNode alloc] initWithDataSource:dataSource url:u];
|
||||
DLog(@"Node: %@", u);
|
||||
[paths addObject:node];
|
||||
}
|
||||
|
||||
|
||||
[self setSubpaths:paths];
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
// Copyright 2006 Vincent Spader. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "PathNode.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface DirectoryNode : PathNode
|
||||
{
|
||||
@interface DirectoryNode : PathNode {
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -15,24 +15,24 @@
|
|||
|
||||
@implementation DirectoryNode
|
||||
|
||||
- (BOOL)isLeaf
|
||||
{
|
||||
- (BOOL)isLeaf {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)updatePath
|
||||
{
|
||||
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:url includingPropertiesForKeys:@[NSURLNameKey, NSURLIsDirectoryKey] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsPackageDescendants | NSDirectoryEnumerationSkipsHiddenFiles) errorHandler:^BOOL(NSURL *url, NSError *error) {
|
||||
return NO;
|
||||
}];
|
||||
- (void)updatePath {
|
||||
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:url
|
||||
includingPropertiesForKeys:@[NSURLNameKey, NSURLIsDirectoryKey]
|
||||
options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsPackageDescendants | NSDirectoryEnumerationSkipsHiddenFiles)
|
||||
errorHandler:^BOOL(NSURL *url, NSError *error) {
|
||||
return NO;
|
||||
}];
|
||||
NSMutableArray *fullPaths = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSURL * theUrl in enumerator)
|
||||
{
|
||||
for(NSURL *theUrl in enumerator) {
|
||||
[fullPaths addObject:[theUrl path]];
|
||||
}
|
||||
|
||||
[self processPaths: [fullPaths sortedArrayUsingSelector:@selector(finderCompare:)]];
|
||||
[self processPaths:[fullPaths sortedArrayUsingSelector:@selector(finderCompare:)]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
// Copyright 2006 Vincent Spader. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "ImageTextCell.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface FileIconCell : ImageTextCell {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -11,13 +11,11 @@
|
|||
|
||||
@implementation FileIconCell
|
||||
|
||||
- (void)setObjectValue:(PathNode *)o
|
||||
{
|
||||
if ([o respondsToSelector:@selector(icon)] && [o respondsToSelector:@selector(display)]) {
|
||||
- (void)setObjectValue:(PathNode *)o {
|
||||
if([o respondsToSelector:@selector(icon)] && [o respondsToSelector:@selector(display)]) {
|
||||
[super setObjectValue:[o display]];
|
||||
[super setImage: [o icon]];
|
||||
}
|
||||
else {
|
||||
[super setImage:[o icon]];
|
||||
} else {
|
||||
[super setObjectValue:(id)o];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,10 @@
|
|||
// Copyright 2006 Vincent Spader. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "PathNode.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface FileNode : PathNode
|
||||
{
|
||||
|
||||
@interface FileNode : PathNode {
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
|
||||
@implementation FileNode
|
||||
|
||||
- (BOOL)isLeaf
|
||||
{
|
||||
- (BOOL)isLeaf {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
// Copyright 2008 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "FileTreeDataSource.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class SideViewController;
|
||||
@interface FileTreeController : NSObject {
|
||||
|
|
|
@ -6,86 +6,73 @@
|
|||
// Copyright 2008 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PlaylistController.h"
|
||||
#import "FileTreeController.h"
|
||||
#import "PlaylistController.h"
|
||||
#import "SideViewController.h"
|
||||
|
||||
|
||||
@implementation FileTreeController
|
||||
|
||||
- (IBAction)addToPlaylist:(id)sender
|
||||
{
|
||||
- (IBAction)addToPlaylist:(id)sender {
|
||||
[self doAddToPlaylist:sender origin:URLOriginInternal];
|
||||
}
|
||||
|
||||
- (void)doAddToPlaylist:(id)sender origin:(URLOrigin)origin
|
||||
{
|
||||
NSUInteger index;
|
||||
NSIndexSet *selectedIndexes = [outlineView selectedRowIndexes];
|
||||
NSMutableArray *urls = [[NSMutableArray alloc] init];
|
||||
|
||||
for (index = [selectedIndexes firstIndex];
|
||||
index != NSNotFound; index = [selectedIndexes indexGreaterThanIndex: index])
|
||||
{
|
||||
[urls addObject:[[outlineView itemAtRow:index] URL]];
|
||||
}
|
||||
|
||||
[controller doAddToPlaylist:urls origin:origin];
|
||||
- (void)doAddToPlaylist:(id)sender origin:(URLOrigin)origin {
|
||||
NSUInteger index;
|
||||
NSIndexSet *selectedIndexes = [outlineView selectedRowIndexes];
|
||||
NSMutableArray *urls = [[NSMutableArray alloc] init];
|
||||
|
||||
for(index = [selectedIndexes firstIndex];
|
||||
index != NSNotFound; index = [selectedIndexes indexGreaterThanIndex:index]) {
|
||||
[urls addObject:[[outlineView itemAtRow:index] URL]];
|
||||
}
|
||||
|
||||
[controller doAddToPlaylist:urls origin:origin];
|
||||
}
|
||||
|
||||
- (void)addToPlaylistExternal:(id)sender
|
||||
{
|
||||
[self doAddToPlaylist:sender origin:URLOriginExternal];
|
||||
- (void)addToPlaylistExternal:(id)sender {
|
||||
[self doAddToPlaylist:sender origin:URLOriginExternal];
|
||||
}
|
||||
|
||||
- (IBAction)setAsPlaylist:(id)sender
|
||||
{
|
||||
- (IBAction)setAsPlaylist:(id)sender {
|
||||
[controller clear:sender];
|
||||
[self addToPlaylist:sender];
|
||||
}
|
||||
|
||||
- (IBAction)playPauseResume:(NSObject *)id
|
||||
{
|
||||
- (IBAction)playPauseResume:(NSObject *)id {
|
||||
[controller playPauseResume:id];
|
||||
}
|
||||
|
||||
- (IBAction)showEntryInFinder:(id)sender
|
||||
{
|
||||
- (IBAction)showEntryInFinder:(id)sender {
|
||||
NSUInteger index;
|
||||
NSWorkspace* ws = [NSWorkspace sharedWorkspace];
|
||||
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
|
||||
NSIndexSet *selectedIndexes = [outlineView selectedRowIndexes];
|
||||
|
||||
for (index = [selectedIndexes firstIndex];
|
||||
index != NSNotFound; index = [selectedIndexes indexGreaterThanIndex: index])
|
||||
{
|
||||
|
||||
for(index = [selectedIndexes firstIndex];
|
||||
index != NSNotFound; index = [selectedIndexes indexGreaterThanIndex:index]) {
|
||||
NSURL *url = [[outlineView itemAtRow:index] URL];
|
||||
[ws selectFile:[url path] inFileViewerRootedAtPath:[url path]];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)setAsRoot:(id)sender
|
||||
{
|
||||
- (IBAction)setAsRoot:(id)sender {
|
||||
NSUInteger index = [[outlineView selectedRowIndexes] firstIndex];
|
||||
|
||||
if (index != NSNotFound)
|
||||
{
|
||||
|
||||
if(index != NSNotFound) {
|
||||
[dataSource changeURL:[[outlineView itemAtRow:index] URL]];
|
||||
}
|
||||
}
|
||||
|
||||
-(BOOL)validateMenuItem:(NSMenuItem*)menuItem
|
||||
{
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
|
||||
SEL action = [menuItem action];
|
||||
|
||||
if ([outlineView numberOfSelectedRows] == 0)
|
||||
if([outlineView numberOfSelectedRows] == 0)
|
||||
return NO;
|
||||
|
||||
if (action == @selector(setAsRoot:))
|
||||
{
|
||||
if(action == @selector(setAsRoot:)) {
|
||||
BOOL isDir;
|
||||
NSInteger row = [outlineView selectedRow];
|
||||
|
||||
if ([outlineView numberOfSelectedRows] > 1)
|
||||
if([outlineView numberOfSelectedRows] > 1)
|
||||
return NO;
|
||||
|
||||
// Only let directories be Set as Root
|
||||
|
|
|
@ -14,158 +14,156 @@
|
|||
#import "Logging.h"
|
||||
|
||||
static NSURL *defaultMusicDirectory(void) {
|
||||
return [[NSFileManager defaultManager] URLForDirectory:NSMusicDirectory
|
||||
inDomain:NSUserDomainMask
|
||||
appropriateForURL:nil
|
||||
create:NO
|
||||
error:nil];
|
||||
return [[NSFileManager defaultManager] URLForDirectory:NSMusicDirectory
|
||||
inDomain:NSUserDomainMask
|
||||
appropriateForURL:nil
|
||||
create:NO
|
||||
error:nil];
|
||||
}
|
||||
|
||||
@interface FileTreeDataSource()
|
||||
@interface FileTreeDataSource ()
|
||||
|
||||
@property NSURL *rootURL;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FileTreeDataSource {
|
||||
PathNode *rootNode;
|
||||
PathNode *rootNode;
|
||||
}
|
||||
|
||||
+ (void)initialize {
|
||||
NSString *path = [defaultMusicDirectory() absoluteString];
|
||||
NSDictionary *userDefaultsValuesDict = @{@"fileTreeRootURL": path};
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict];
|
||||
NSString *path = [defaultMusicDirectory() absoluteString];
|
||||
NSDictionary *userDefaultsValuesDict = @{ @"fileTreeRootURL": path };
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib {
|
||||
[self.pathControl setTarget:self];
|
||||
[self.pathControl setAction:@selector(pathControlAction:)];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
|
||||
forKeyPath:@"values.fileTreeRootURL"
|
||||
options:NSKeyValueObservingOptionNew |
|
||||
NSKeyValueObservingOptionInitial
|
||||
context:nil];
|
||||
[self.pathControl setTarget:self];
|
||||
[self.pathControl setAction:@selector(pathControlAction:)];
|
||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
|
||||
forKeyPath:@"values.fileTreeRootURL"
|
||||
options:NSKeyValueObservingOptionNew |
|
||||
NSKeyValueObservingOptionInitial
|
||||
context:nil];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context {
|
||||
if ([keyPath isEqualToString:@"values.fileTreeRootURL"]) {
|
||||
NSString *url =
|
||||
[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"fileTreeRootURL"];
|
||||
DLog(@"File tree root URL: %@\n", url);
|
||||
self.rootURL = [NSURL URLWithString:url];
|
||||
}
|
||||
if([keyPath isEqualToString:@"values.fileTreeRootURL"]) {
|
||||
NSString *url =
|
||||
[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"fileTreeRootURL"];
|
||||
DLog(@"File tree root URL: %@\n", url);
|
||||
self.rootURL = [NSURL URLWithString:url];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)changeURL:(NSURL *)url {
|
||||
if (url != nil) {
|
||||
[[[NSUserDefaultsController sharedUserDefaultsController] defaults] setObject:[url absoluteString]
|
||||
forKey:@"fileTreeRootURL"];
|
||||
}
|
||||
if(url != nil) {
|
||||
[[[NSUserDefaultsController sharedUserDefaultsController] defaults] setObject:[url absoluteString]
|
||||
forKey:@"fileTreeRootURL"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pathControlAction:(id)sender {
|
||||
NSPathControlItem *item = [self.pathControl clickedPathItem];
|
||||
if (item != nil && item.URL != nil) {
|
||||
[self changeURL:item.URL];
|
||||
}
|
||||
NSPathControlItem *item = [self.pathControl clickedPathItem];
|
||||
if(item != nil && item.URL != nil) {
|
||||
[self changeURL:item.URL];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSURL *)rootURL {
|
||||
return [rootNode URL];
|
||||
return [rootNode URL];
|
||||
}
|
||||
|
||||
- (void)setRootURL:(NSURL *)rootURL {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[rootURL path]]) {
|
||||
rootURL = defaultMusicDirectory();
|
||||
}
|
||||
if(![[NSFileManager defaultManager] fileExistsAtPath:[rootURL path]]) {
|
||||
rootURL = defaultMusicDirectory();
|
||||
}
|
||||
|
||||
rootNode = [[DirectoryNode alloc] initWithDataSource:self url:rootURL];
|
||||
rootNode = [[DirectoryNode alloc] initWithDataSource:self url:rootURL];
|
||||
|
||||
[self.watcher setPath:[rootURL path]];
|
||||
[self.watcher setPath:[rootURL path]];
|
||||
|
||||
[self reloadPathNode:rootNode];
|
||||
[self reloadPathNode:rootNode];
|
||||
}
|
||||
|
||||
- (PathNode *)nodeForPath:(NSString *)path {
|
||||
NSString *relativePath = [[path stringByReplacingOccurrencesOfString:[[[self rootURL] path] stringByAppendingString:@"/"]
|
||||
withString:@""
|
||||
options:NSAnchoredSearch
|
||||
range:NSMakeRange(0, [path length])
|
||||
] stringByStandardizingPath];
|
||||
PathNode *node = rootNode;
|
||||
DLog(@"Root | Relative | Path: %@ | %@ | %@", [[self rootURL] path], relativePath, path);
|
||||
for (NSString *c in [relativePath pathComponents]) {
|
||||
DLog(@"COMPONENT: %@", c);
|
||||
BOOL found = NO;
|
||||
for (PathNode *subnode in [node subpaths]) {
|
||||
if ([[[[subnode URL] path] lastPathComponent] isEqualToString:c]) {
|
||||
node = subnode;
|
||||
found = YES;
|
||||
}
|
||||
}
|
||||
NSString *relativePath = [[path stringByReplacingOccurrencesOfString:[[[self rootURL] path] stringByAppendingString:@"/"]
|
||||
withString:@""
|
||||
options:NSAnchoredSearch
|
||||
range:NSMakeRange(0, [path length])] stringByStandardizingPath];
|
||||
PathNode *node = rootNode;
|
||||
DLog(@"Root | Relative | Path: %@ | %@ | %@", [[self rootURL] path], relativePath, path);
|
||||
for(NSString *c in [relativePath pathComponents]) {
|
||||
DLog(@"COMPONENT: %@", c);
|
||||
BOOL found = NO;
|
||||
for(PathNode *subnode in [node subpaths]) {
|
||||
if([[[[subnode URL] path] lastPathComponent] isEqualToString:c]) {
|
||||
node = subnode;
|
||||
found = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
DLog(@"Not found!");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
DLog(@"Not found!");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
return node;
|
||||
}
|
||||
|
||||
- (void)pathDidChange:(NSString *)path {
|
||||
DLog(@"PATH DID CHANGE: %@", path);
|
||||
//Need to find the corresponding node...and call [node reloadPath], then [self reloadPathNode:node]
|
||||
PathNode *node = [self nodeForPath:path];
|
||||
DLog(@"NODE IS: %@", node);
|
||||
[node updatePath];
|
||||
[self reloadPathNode:node];
|
||||
DLog(@"PATH DID CHANGE: %@", path);
|
||||
// Need to find the corresponding node...and call [node reloadPath], then [self reloadPathNode:node]
|
||||
PathNode *node = [self nodeForPath:path];
|
||||
DLog(@"NODE IS: %@", node);
|
||||
[node updatePath];
|
||||
[self reloadPathNode:node];
|
||||
}
|
||||
|
||||
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
|
||||
PathNode *n = (item == nil ? rootNode : item);
|
||||
PathNode *n = (item == nil ? rootNode : item);
|
||||
|
||||
return (int) [[n subpaths] count];
|
||||
return (int)[[n subpaths] count];
|
||||
}
|
||||
|
||||
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
|
||||
PathNode *n = (item == nil ? rootNode : item);
|
||||
PathNode *n = (item == nil ? rootNode : item);
|
||||
|
||||
return ![n isLeaf];
|
||||
return ![n isLeaf];
|
||||
}
|
||||
|
||||
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
|
||||
PathNode *n = (item == nil ? rootNode : item);
|
||||
PathNode *n = (item == nil ? rootNode : item);
|
||||
|
||||
return [n subpaths][(NSUInteger) index];
|
||||
return [n subpaths][(NSUInteger)index];
|
||||
}
|
||||
|
||||
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
|
||||
PathNode *n = (item == nil ? rootNode : item);
|
||||
PathNode *n = (item == nil ? rootNode : item);
|
||||
|
||||
return n;
|
||||
return n;
|
||||
}
|
||||
|
||||
- (id <NSPasteboardWriting>)outlineView:(NSOutlineView *)outlineView pasteboardWriterForItem:(id)item {
|
||||
NSPasteboardItem *paste = [[NSPasteboardItem alloc] init];
|
||||
if (@available(macOS 10.13, *)) {
|
||||
[paste setData:[[item URL] dataRepresentation] forType:NSPasteboardTypeFileURL];
|
||||
}
|
||||
else {
|
||||
[paste setPropertyList:@[[item URL]] forType:NSFilenamesPboardType];
|
||||
}
|
||||
return paste;
|
||||
- (id<NSPasteboardWriting>)outlineView:(NSOutlineView *)outlineView pasteboardWriterForItem:(id)item {
|
||||
NSPasteboardItem *paste = [[NSPasteboardItem alloc] init];
|
||||
if(@available(macOS 10.13, *)) {
|
||||
[paste setData:[[item URL] dataRepresentation] forType:NSPasteboardTypeFileURL];
|
||||
} else {
|
||||
[paste setPropertyList:@[[item URL]] forType:NSFilenamesPboardType];
|
||||
}
|
||||
return paste;
|
||||
}
|
||||
|
||||
- (void)reloadPathNode:(PathNode *)item {
|
||||
if (item == rootNode) {
|
||||
[self.outlineView reloadData];
|
||||
} else {
|
||||
[self.outlineView reloadItem:item reloadChildren:YES];
|
||||
}
|
||||
if(item == rootNode) {
|
||||
[self.outlineView reloadData];
|
||||
} else {
|
||||
[self.outlineView reloadItem:item reloadChildren:YES];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
@interface FileTreeOutlineView : NSOutlineView {
|
||||
}
|
||||
|
||||
|
|
|
@ -6,71 +6,61 @@
|
|||
// Copyright 2008 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FileTreeController.h"
|
||||
#import "FileTreeOutlineView.h"
|
||||
#import "FileTreeController.h"
|
||||
#import "FileTreeViewController.h"
|
||||
#import "PlaybackController.h"
|
||||
|
||||
@implementation FileTreeOutlineView
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[self setDoubleAction:@selector(addToPlaylistExternal:)];
|
||||
[self setTarget:[self delegate]];
|
||||
- (void)awakeFromNib {
|
||||
[self setDoubleAction:@selector(addToPlaylistExternal:)];
|
||||
[self setTarget:[self delegate]];
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent *)e
|
||||
{
|
||||
unsigned int modifiers = [e modifierFlags] & (NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagControl | NSEventModifierFlagOption);
|
||||
NSString *characters = [e characters];
|
||||
unichar c;
|
||||
|
||||
if ([characters length] == 1)
|
||||
{
|
||||
c = [characters characterAtIndex:0];
|
||||
|
||||
if (modifiers == 0 && (c == NSEnterCharacter || c == NSCarriageReturnCharacter))
|
||||
{
|
||||
[(FileTreeController *)[self delegate] addToPlaylistExternal:self];
|
||||
|
||||
return;
|
||||
}
|
||||
else if (modifiers == 0 && c == ' ')
|
||||
{
|
||||
[(FileTreeController *)[self delegate] playPauseResume:self];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[super keyDown:e];
|
||||
|
||||
return;
|
||||
- (void)keyDown:(NSEvent *)e {
|
||||
unsigned int modifiers = [e modifierFlags] & (NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagControl | NSEventModifierFlagOption);
|
||||
NSString *characters = [e characters];
|
||||
unichar c;
|
||||
|
||||
if([characters length] == 1) {
|
||||
c = [characters characterAtIndex:0];
|
||||
|
||||
if(modifiers == 0 && (c == NSEnterCharacter || c == NSCarriageReturnCharacter)) {
|
||||
[(FileTreeController *)[self delegate] addToPlaylistExternal:self];
|
||||
|
||||
return;
|
||||
} else if(modifiers == 0 && c == ' ') {
|
||||
[(FileTreeController *)[self delegate] playPauseResume:self];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[super keyDown:e];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// enables right-click selection for "Show in Finder" contextual menu
|
||||
-(NSMenu*)menuForEvent:(NSEvent*)event
|
||||
{
|
||||
//Find which row is under the cursor
|
||||
[[self window] makeFirstResponder:self];
|
||||
NSPoint menuPoint = [self convertPoint:[event locationInWindow] fromView:nil];
|
||||
NSInteger iRow = [self rowAtPoint:menuPoint];
|
||||
NSMenu* contextMenu = [self menu];
|
||||
|
||||
/* Update the file tree selection before showing menu
|
||||
Preserves the selection if the row under the mouse is selected (to allow for
|
||||
multiple items to be selected), otherwise selects the row under the mouse */
|
||||
BOOL currentRowIsSelected = [[self selectedRowIndexes] containsIndex:iRow];
|
||||
|
||||
if (iRow == -1)
|
||||
{
|
||||
[self deselectAll:self];
|
||||
}
|
||||
else if (!currentRowIsSelected)
|
||||
{
|
||||
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:iRow] byExtendingSelection:NO];
|
||||
}
|
||||
|
||||
return contextMenu;
|
||||
- (NSMenu *)menuForEvent:(NSEvent *)event {
|
||||
// Find which row is under the cursor
|
||||
[[self window] makeFirstResponder:self];
|
||||
NSPoint menuPoint = [self convertPoint:[event locationInWindow] fromView:nil];
|
||||
NSInteger iRow = [self rowAtPoint:menuPoint];
|
||||
NSMenu *contextMenu = [self menu];
|
||||
|
||||
/* Update the file tree selection before showing menu
|
||||
Preserves the selection if the row under the mouse is selected (to allow for
|
||||
multiple items to be selected), otherwise selects the row under the mouse */
|
||||
BOOL currentRowIsSelected = [[self selectedRowIndexes] containsIndex:iRow];
|
||||
|
||||
if(iRow == -1) {
|
||||
[self deselectAll:self];
|
||||
} else if(!currentRowIsSelected) {
|
||||
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:iRow] byExtendingSelection:NO];
|
||||
}
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
// Copyright 2008 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "SideViewController.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class PlaylistLoader;
|
||||
@class PlaybackController;
|
||||
|
@ -15,9 +15,9 @@
|
|||
@interface FileTreeViewController : SideViewController {
|
||||
IBOutlet PlaylistLoader *playlistLoader;
|
||||
IBOutlet PlaybackController *playbackController;
|
||||
IBOutlet FileTreeOutlineView *fileTreeOutlineView;
|
||||
IBOutlet FileTreeOutlineView *fileTreeOutlineView;
|
||||
}
|
||||
|
||||
- (FileTreeOutlineView*)outlineView;
|
||||
- (FileTreeOutlineView *)outlineView;
|
||||
|
||||
@end
|
||||
|
|
|
@ -7,45 +7,38 @@
|
|||
//
|
||||
|
||||
#import "FileTreeViewController.h"
|
||||
#import "PlaylistLoader.h"
|
||||
#import "PlaybackController.h"
|
||||
#import "PlaylistLoader.h"
|
||||
|
||||
@implementation FileTreeViewController
|
||||
|
||||
- (id)init
|
||||
{
|
||||
- (id)init {
|
||||
return [super initWithNibName:@"FileTree" bundle:[NSBundle mainBundle]];
|
||||
}
|
||||
|
||||
- (void)addToPlaylistInternal:(NSArray *)urls
|
||||
{
|
||||
- (void)addToPlaylistInternal:(NSArray *)urls {
|
||||
[self doAddToPlaylist:urls origin:URLOriginInternal];
|
||||
}
|
||||
|
||||
- (void)addToPlaylistExternal:(NSArray *)urls
|
||||
{
|
||||
[self doAddToPlaylist:urls origin:URLOriginExternal];
|
||||
- (void)addToPlaylistExternal:(NSArray *)urls {
|
||||
[self doAddToPlaylist:urls origin:URLOriginExternal];
|
||||
}
|
||||
|
||||
- (void)doAddToPlaylist:(NSArray *)urls origin:(URLOrigin)origin
|
||||
{
|
||||
[playlistLoader willInsertURLs:urls origin:origin];
|
||||
[playlistLoader didInsertURLs:[playlistLoader addURLs:urls sort:YES] origin:origin];
|
||||
- (void)doAddToPlaylist:(NSArray *)urls origin:(URLOrigin)origin {
|
||||
[playlistLoader willInsertURLs:urls origin:origin];
|
||||
[playlistLoader didInsertURLs:[playlistLoader addURLs:urls sort:YES] origin:origin];
|
||||
}
|
||||
|
||||
- (void)clear:(id)sender
|
||||
{
|
||||
- (void)clear:(id)sender {
|
||||
[playlistLoader clear:sender];
|
||||
}
|
||||
|
||||
- (void)playPauseResume:(NSObject *)id
|
||||
{
|
||||
- (void)playPauseResume:(NSObject *)id {
|
||||
[playbackController playPauseResume:id];
|
||||
}
|
||||
|
||||
- (FileTreeOutlineView*)outlineView
|
||||
{
|
||||
return fileTreeOutlineView;
|
||||
- (FileTreeOutlineView *)outlineView {
|
||||
return fileTreeOutlineView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -10,13 +10,12 @@
|
|||
|
||||
@class FileTreeDataSource;
|
||||
|
||||
@interface PathNode : NSObject
|
||||
{
|
||||
@interface PathNode : NSObject {
|
||||
FileTreeDataSource *dataSource;
|
||||
|
||||
|
||||
NSURL *url;
|
||||
NSString *display; //The pretty path to display.
|
||||
|
||||
NSString *display; // The pretty path to display.
|
||||
|
||||
NSImage *icon;
|
||||
|
||||
NSArray *subpaths;
|
||||
|
@ -27,7 +26,7 @@
|
|||
- (NSURL *)URL;
|
||||
- (void)setURL:(NSURL *)url;
|
||||
|
||||
- (void)processPaths: (NSArray *)contents;
|
||||
- (void)processPaths:(NSArray *)contents;
|
||||
|
||||
- (NSArray *)subpaths;
|
||||
- (void)setSubpaths:(NSArray *)s;
|
||||
|
@ -41,5 +40,4 @@
|
|||
|
||||
- (void)updatePath;
|
||||
|
||||
|
||||
@end
|
||||
|
|
|
@ -12,166 +12,138 @@
|
|||
|
||||
#import "FileTreeDataSource.h"
|
||||
|
||||
#import "FileNode.h"
|
||||
#import "DirectoryNode.h"
|
||||
#import "SmartFolderNode.h"
|
||||
#import "ContainerNode.h"
|
||||
#import "DirectoryNode.h"
|
||||
#import "FileNode.h"
|
||||
#import "SmartFolderNode.h"
|
||||
|
||||
#import "Logging.h"
|
||||
|
||||
@implementation PathNode
|
||||
|
||||
//From http://developer.apple.com/documentation/Cocoa/Conceptual/LowLevelFileMgmt/Tasks/ResolvingAliases.html
|
||||
//Updated 2018-06-28
|
||||
NSURL *resolveAliases(NSURL *url)
|
||||
{
|
||||
CFErrorRef error;
|
||||
CFDataRef bookmarkRef = CFURLCreateBookmarkDataFromFile(kCFAllocatorDefault, (__bridge CFURLRef)url, &error);
|
||||
if (bookmarkRef)
|
||||
{
|
||||
Boolean isStale;
|
||||
CFURLRef urlRef = CFURLCreateByResolvingBookmarkData(kCFAllocatorDefault, bookmarkRef, kCFURLBookmarkResolutionWithSecurityScope, NULL, NULL, &isStale, &error);
|
||||
// From http://developer.apple.com/documentation/Cocoa/Conceptual/LowLevelFileMgmt/Tasks/ResolvingAliases.html
|
||||
// Updated 2018-06-28
|
||||
NSURL *resolveAliases(NSURL *url) {
|
||||
CFErrorRef error;
|
||||
CFDataRef bookmarkRef = CFURLCreateBookmarkDataFromFile(kCFAllocatorDefault, (__bridge CFURLRef)url, &error);
|
||||
if(bookmarkRef) {
|
||||
Boolean isStale;
|
||||
CFURLRef urlRef = CFURLCreateByResolvingBookmarkData(kCFAllocatorDefault, bookmarkRef, kCFURLBookmarkResolutionWithSecurityScope, NULL, NULL, &isStale, &error);
|
||||
|
||||
if (urlRef && !isStale)
|
||||
{
|
||||
if(urlRef && !isStale) {
|
||||
return (NSURL *)CFBridgingRelease(urlRef);
|
||||
}
|
||||
}
|
||||
|
||||
//DLog(@"Not resolved");
|
||||
// DLog(@"Not resolved");
|
||||
return url;
|
||||
}
|
||||
|
||||
- (id)initWithDataSource:(FileTreeDataSource *)ds url:(NSURL *)u
|
||||
{
|
||||
- (id)initWithDataSource:(FileTreeDataSource *)ds url:(NSURL *)u {
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
if(self) {
|
||||
dataSource = ds;
|
||||
[self setURL: u];
|
||||
[self setURL:u];
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setURL:(NSURL *)u
|
||||
{
|
||||
- (void)setURL:(NSURL *)u {
|
||||
url = u;
|
||||
|
||||
display = [[NSFileManager defaultManager] displayNameAtPath:[u path]];
|
||||
|
||||
|
||||
icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]];
|
||||
|
||||
[icon setSize: NSMakeSize(16.0, 16.0)];
|
||||
|
||||
[icon setSize:NSMakeSize(16.0, 16.0)];
|
||||
}
|
||||
|
||||
- (NSURL *)URL
|
||||
{
|
||||
- (NSURL *)URL {
|
||||
return url;
|
||||
}
|
||||
|
||||
- (void)updatePath
|
||||
{
|
||||
- (void)updatePath {
|
||||
}
|
||||
|
||||
- (void)processPaths: (NSArray *)contents
|
||||
{
|
||||
NSMutableArray *newSubpathsDirs = [[NSMutableArray alloc] init];
|
||||
- (void)processPaths:(NSArray *)contents {
|
||||
NSMutableArray *newSubpathsDirs = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *newSubpaths = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSString *s in contents)
|
||||
{
|
||||
if ([s characterAtIndex:0] == '.')
|
||||
{
|
||||
|
||||
for(NSString *s in contents) {
|
||||
if([s characterAtIndex:0] == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
NSURL *u = [NSURL fileURLWithPath:s];
|
||||
NSString *displayName = [[NSFileManager defaultManager] displayNameAtPath:[u path]];
|
||||
|
||||
PathNode *newNode;
|
||||
|
||||
//DLog(@"Before: %@", u);
|
||||
u = resolveAliases(u);
|
||||
//DLog(@"After: %@", u);
|
||||
|
||||
BOOL isDir;
|
||||
|
||||
if ([[s pathExtension] caseInsensitiveCompare:@"savedSearch"] == NSOrderedSame)
|
||||
{
|
||||
PathNode *newNode;
|
||||
|
||||
// DLog(@"Before: %@", u);
|
||||
u = resolveAliases(u);
|
||||
// DLog(@"After: %@", u);
|
||||
|
||||
BOOL isDir;
|
||||
|
||||
if([[s pathExtension] caseInsensitiveCompare:@"savedSearch"] == NSOrderedSame) {
|
||||
DLog(@"Smart folder!");
|
||||
newNode = [[SmartFolderNode alloc] initWithDataSource:dataSource url:u];
|
||||
isDir = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
isDir = NO;
|
||||
} else {
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:[u path] isDirectory:&isDir];
|
||||
|
||||
if (!isDir && ![[AudioPlayer fileTypes] containsObject:[[u pathExtension] lowercaseString]])
|
||||
{
|
||||
if(!isDir && ![[AudioPlayer fileTypes] containsObject:[[u pathExtension] lowercaseString]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isDir)
|
||||
{
|
||||
|
||||
if(isDir) {
|
||||
newNode = [[DirectoryNode alloc] initWithDataSource:dataSource url:u];
|
||||
}
|
||||
else if ([[AudioPlayer containerTypes] containsObject:[[u pathExtension] lowercaseString]])
|
||||
{
|
||||
} else if([[AudioPlayer containerTypes] containsObject:[[u pathExtension] lowercaseString]]) {
|
||||
newNode = [[ContainerNode alloc] initWithDataSource:dataSource url:u];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
newNode = [[FileNode alloc] initWithDataSource:dataSource url:u];
|
||||
}
|
||||
}
|
||||
|
||||
[newNode setDisplay:displayName];
|
||||
|
||||
if (isDir)
|
||||
[newSubpathsDirs addObject:newNode];
|
||||
else
|
||||
[newSubpaths addObject:newNode];
|
||||
|
||||
if(isDir)
|
||||
[newSubpathsDirs addObject:newNode];
|
||||
else
|
||||
[newSubpaths addObject:newNode];
|
||||
}
|
||||
|
||||
[newSubpathsDirs addObjectsFromArray:newSubpaths];
|
||||
|
||||
[newSubpathsDirs addObjectsFromArray:newSubpaths];
|
||||
[self setSubpaths:newSubpathsDirs];
|
||||
}
|
||||
|
||||
- (NSArray *)subpaths
|
||||
{
|
||||
if (subpaths == nil)
|
||||
{
|
||||
- (NSArray *)subpaths {
|
||||
if(subpaths == nil) {
|
||||
[self updatePath];
|
||||
}
|
||||
|
||||
|
||||
return subpaths;
|
||||
}
|
||||
|
||||
- (void)setSubpaths:(NSArray *)s
|
||||
{
|
||||
- (void)setSubpaths:(NSArray *)s {
|
||||
subpaths = s;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isLeaf
|
||||
{
|
||||
- (BOOL)isLeaf {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setDisplay:(NSString *)s
|
||||
{
|
||||
- (void)setDisplay:(NSString *)s {
|
||||
display = s;
|
||||
}
|
||||
|
||||
- (NSString *)display
|
||||
{
|
||||
- (NSString *)display {
|
||||
return display;
|
||||
}
|
||||
|
||||
- (NSImage *)icon
|
||||
{
|
||||
- (NSImage *)icon {
|
||||
return icon;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,9 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
|
||||
@interface PathWatcher : NSObject {
|
||||
FSEventStreamRef stream;
|
||||
FSEventStreamContext *context;
|
||||
FSEventStreamContext *context;
|
||||
|
||||
IBOutlet id delegate;
|
||||
}
|
||||
|
@ -20,7 +19,7 @@
|
|||
- (void)setDelegate:(id)d;
|
||||
- (id)delegate;
|
||||
|
||||
- (void)setPath:(NSString *)path; //Set the path to watch
|
||||
- (void)setPath:(NSString *)path; // Set the path to watch
|
||||
- (void)cleanUp;
|
||||
@end
|
||||
|
||||
|
|
|
@ -9,81 +9,74 @@
|
|||
#import "PathWatcher.h"
|
||||
|
||||
static void myFSEventCallback(
|
||||
ConstFSEventStreamRef streamRef,
|
||||
void *clientCallBackInfo,
|
||||
size_t numEvents,
|
||||
void *eventPaths,
|
||||
const FSEventStreamEventFlags eventFlags[],
|
||||
const FSEventStreamEventId eventIds[])
|
||||
{
|
||||
ConstFSEventStreamRef streamRef,
|
||||
void *clientCallBackInfo,
|
||||
size_t numEvents,
|
||||
void *eventPaths,
|
||||
const FSEventStreamEventFlags eventFlags[],
|
||||
const FSEventStreamEventId eventIds[]) {
|
||||
int i;
|
||||
char **paths = eventPaths;
|
||||
PathWatcher *pathWatcher = (__bridge PathWatcher *)clientCallBackInfo;
|
||||
|
||||
|
||||
printf("Callback called\n");
|
||||
for (i=0; i<numEvents; i++) {
|
||||
for(i = 0; i < numEvents; i++) {
|
||||
NSString *pathString = [[NSString alloc] initWithUTF8String:paths[i]];
|
||||
[[pathWatcher delegate] pathDidChange:pathString];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@implementation PathWatcher
|
||||
|
||||
- (void)cleanUp
|
||||
{
|
||||
if (stream) {
|
||||
- (void)cleanUp {
|
||||
if(stream) {
|
||||
FSEventStreamStop(stream);
|
||||
FSEventStreamInvalidate(stream);
|
||||
FSEventStreamRelease(stream);
|
||||
stream = NULL;
|
||||
}
|
||||
if (context) {
|
||||
free(context);
|
||||
context = NULL;
|
||||
if(context) {
|
||||
free(context);
|
||||
context = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPath:(NSString *)path
|
||||
{
|
||||
- (void)setPath:(NSString *)path {
|
||||
[self cleanUp];
|
||||
|
||||
//Create FSEvent stream
|
||||
NSArray *pathsToWatch = @[path];
|
||||
|
||||
context = (FSEventStreamContext*)malloc(sizeof(FSEventStreamContext));
|
||||
context->version = 0;
|
||||
context->info = (__bridge void *)self;
|
||||
context->retain = NULL;
|
||||
context->release = NULL;
|
||||
// Create FSEvent stream
|
||||
NSArray *pathsToWatch = @[path];
|
||||
|
||||
context = (FSEventStreamContext *)malloc(sizeof(FSEventStreamContext));
|
||||
context->version = 0;
|
||||
context->info = (__bridge void *)self;
|
||||
context->retain = NULL;
|
||||
context->release = NULL;
|
||||
|
||||
// Create the stream, passing in a callback
|
||||
stream = FSEventStreamCreate(NULL,
|
||||
&myFSEventCallback,
|
||||
context,
|
||||
(__bridge CFArrayRef)pathsToWatch,
|
||||
kFSEventStreamEventIdSinceNow, // Or a previous event ID
|
||||
1.0, // latency in seconds
|
||||
kFSEventStreamCreateFlagNone // Watch this and all its subdirectories
|
||||
);
|
||||
|
||||
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
|
||||
// Create the stream, passing in a callback
|
||||
stream = FSEventStreamCreate(NULL,
|
||||
&myFSEventCallback,
|
||||
context,
|
||||
(__bridge CFArrayRef)pathsToWatch,
|
||||
kFSEventStreamEventIdSinceNow, // Or a previous event ID
|
||||
1.0, //latency in seconds
|
||||
kFSEventStreamCreateFlagNone // Watch this and all its subdirectories
|
||||
);
|
||||
|
||||
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
|
||||
FSEventStreamStart(stream);
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id)d
|
||||
{
|
||||
- (void)setDelegate:(id)d {
|
||||
delegate = d;
|
||||
}
|
||||
- (id)delegate
|
||||
{
|
||||
- (id)delegate {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
[self cleanUp];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#import "PathNode.h"
|
||||
|
||||
@interface SmartFolderNode : PathNode {
|
||||
MDQueryRef _query;
|
||||
MDQueryRef _query;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -15,87 +15,76 @@
|
|||
|
||||
@implementation SmartFolderNode
|
||||
|
||||
- (BOOL)isLeaf
|
||||
{
|
||||
- (BOOL)isLeaf {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)updatePath
|
||||
{
|
||||
- (void)updatePath {
|
||||
NSDictionary *doc = [NSDictionary dictionaryWithContentsOfFile:[url path]];
|
||||
NSString *rawQuery = [doc objectForKey:@"RawQuery"];
|
||||
NSArray *searchPaths = [[doc objectForKey:@"SearchCriteria"] objectForKey:@"CurrentFolderPath"];
|
||||
|
||||
|
||||
// Ugh, Carbon from now on...
|
||||
MDQueryRef query = MDQueryCreate(kCFAllocatorDefault, (CFStringRef)rawQuery, NULL, NULL);
|
||||
_query = query;
|
||||
|
||||
_query = query;
|
||||
|
||||
MDQuerySetSearchScope(query, (CFArrayRef)searchPaths, 0);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryFinished:) name:(NSString*)kMDQueryDidFinishNotification object:(__bridge id)query];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryUpdate:) name:(NSString*)kMDQueryDidUpdateNotification object:(__bridge id)query];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryFinished:) name:(NSString *)kMDQueryDidFinishNotification object:(__bridge id)query];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryUpdate:) name:(NSString *)kMDQueryDidUpdateNotification object:(__bridge id)query];
|
||||
|
||||
DLog(@"Making query!");
|
||||
MDQueryExecute(query, kMDQueryWantsUpdates);
|
||||
|
||||
//Note: This is asynchronous!
|
||||
|
||||
// Note: This is asynchronous!
|
||||
}
|
||||
|
||||
- (void)setSubpaths:(id)s
|
||||
{
|
||||
- (void)setSubpaths:(id)s {
|
||||
subpaths = s;
|
||||
}
|
||||
|
||||
- (unsigned int)countOfSubpaths
|
||||
{
|
||||
return (unsigned int) [[self subpaths] count];
|
||||
- (unsigned int)countOfSubpaths {
|
||||
return (unsigned int)[[self subpaths] count];
|
||||
}
|
||||
|
||||
- (PathNode *)objectInSubpathsAtIndex:(unsigned int)index
|
||||
{
|
||||
- (PathNode *)objectInSubpathsAtIndex:(unsigned int)index {
|
||||
return [[self subpaths] objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (void)queryFinished:(NSNotification *)notification
|
||||
{
|
||||
- (void)queryFinished:(NSNotification *)notification {
|
||||
DLog(@"Query finished!");
|
||||
MDQueryRef query = (__bridge MDQueryRef)[notification object];
|
||||
|
||||
NSMutableArray *results = [NSMutableArray array];
|
||||
|
||||
MDQueryDisableUpdates(query);
|
||||
int c = (int) MDQueryGetResultCount(query);
|
||||
|
||||
int c = (int)MDQueryGetResultCount(query);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < c; i++)
|
||||
{
|
||||
MDItemRef item = (MDItemRef)MDQueryGetResultAtIndex(query, i);
|
||||
|
||||
NSString *itemPath = (NSString *) CFBridgingRelease(MDItemCopyAttribute(item, kMDItemPath));
|
||||
for(i = 0; i < c; i++) {
|
||||
MDItemRef item = (MDItemRef)MDQueryGetResultAtIndex(query, i);
|
||||
|
||||
NSString *itemPath = (NSString *)CFBridgingRelease(MDItemCopyAttribute(item, kMDItemPath));
|
||||
|
||||
[results addObject:itemPath];
|
||||
}
|
||||
|
||||
MDQueryEnableUpdates(query);
|
||||
|
||||
|
||||
DLog(@"Query update!");
|
||||
|
||||
|
||||
[self processPaths:[results sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]];
|
||||
|
||||
|
||||
[dataSource reloadPathNode:self];
|
||||
}
|
||||
|
||||
- (void)queryUpdate:(NSNotification *)notification
|
||||
{
|
||||
- (void)queryUpdate:(NSNotification *)notification {
|
||||
DLog(@"Query update!");
|
||||
[self queryFinished: notification];
|
||||
[self queryFinished:notification];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CFRelease(_query);
|
||||
- (void)dealloc {
|
||||
CFRelease(_query);
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -8,10 +8,7 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
@interface BlankZeroFormatter : NSFormatter {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,43 +8,39 @@
|
|||
|
||||
#import "BlankZeroFormatter.h"
|
||||
|
||||
|
||||
@implementation BlankZeroFormatter
|
||||
|
||||
- (NSString *) stringForObjectValue:(id)object
|
||||
{
|
||||
NSString *result = nil;
|
||||
- (NSString *)stringForObjectValue:(id)object {
|
||||
NSString *result = nil;
|
||||
int value;
|
||||
|
||||
if(nil == object || NO == [object isKindOfClass:[NSNumber class]]) {
|
||||
return [NSString string];
|
||||
}
|
||||
|
||||
|
||||
value = [object intValue];
|
||||
|
||||
if (value)
|
||||
|
||||
if(value)
|
||||
result = [NSString stringWithFormat:@"%i", value];
|
||||
else
|
||||
result = [NSString string];
|
||||
|
||||
else
|
||||
result = [NSString string];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL) getObjectValue:(id *)object forString:(NSString *)string errorDescription:(NSString **)error
|
||||
{
|
||||
- (BOOL)getObjectValue:(id *)object forString:(NSString *)string errorDescription:(NSString **)error {
|
||||
if(NULL != object) {
|
||||
*object = [NSNumber numberWithInt:[string intValue]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSAttributedString *) attributedStringForObjectValue:(id)object withDefaultAttributes:(NSDictionary *)attributes
|
||||
{
|
||||
NSAttributedString *result = nil;
|
||||
|
||||
- (NSAttributedString *)attributedStringForObjectValue:(id)object withDefaultAttributes:(NSDictionary *)attributes {
|
||||
NSAttributedString *result = nil;
|
||||
|
||||
result = [[NSAttributedString alloc] initWithString:[self stringForObjectValue:object] attributes:attributes];
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -8,9 +8,7 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
@interface IndexFormatter : NSFormatter {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,40 +8,36 @@
|
|||
|
||||
#import "IndexFormatter.h"
|
||||
|
||||
|
||||
@implementation IndexFormatter
|
||||
|
||||
- (NSString *) stringForObjectValue:(id)object
|
||||
{
|
||||
NSString *result = nil;
|
||||
- (NSString *)stringForObjectValue:(id)object {
|
||||
NSString *result = nil;
|
||||
int value;
|
||||
|
||||
if(nil == object || NO == [object isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
value = ([object intValue] + 1);
|
||||
|
||||
|
||||
result = [NSString stringWithFormat:@"%i", value];
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL) getObjectValue:(id *)object forString:(NSString *)string errorDescription:(NSString **)error
|
||||
{
|
||||
- (BOOL)getObjectValue:(id *)object forString:(NSString *)string errorDescription:(NSString **)error {
|
||||
if(NULL != object) {
|
||||
*object = [NSNumber numberWithInt:[string intValue]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSAttributedString *) attributedStringForObjectValue:(id)object withDefaultAttributes:(NSDictionary *)attributes
|
||||
{
|
||||
NSAttributedString *result = nil;
|
||||
|
||||
- (NSAttributedString *)attributedStringForObjectValue:(id)object withDefaultAttributes:(NSDictionary *)attributes {
|
||||
NSAttributedString *result = nil;
|
||||
|
||||
result = [[NSAttributedString alloc] initWithString:[self stringForObjectValue:object] attributes:attributes];
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -20,8 +20,7 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface SecondsFormatter : NSFormatter
|
||||
{
|
||||
@interface SecondsFormatter : NSFormatter {
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -22,29 +22,28 @@
|
|||
|
||||
@implementation SecondsFormatter
|
||||
|
||||
- (NSString *) stringForObjectValue:(id)object
|
||||
{
|
||||
NSString *result = nil;
|
||||
unsigned value;
|
||||
unsigned days = 0;
|
||||
unsigned hours = 0;
|
||||
unsigned minutes = 0;
|
||||
unsigned seconds = 0;
|
||||
|
||||
- (NSString *)stringForObjectValue:(id)object {
|
||||
NSString *result = nil;
|
||||
unsigned value;
|
||||
unsigned days = 0;
|
||||
unsigned hours = 0;
|
||||
unsigned minutes = 0;
|
||||
unsigned seconds = 0;
|
||||
|
||||
if(nil == object || NO == [object isKindOfClass:[NSNumber class]] || isnan([object doubleValue])) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
value = (unsigned)([object doubleValue]);
|
||||
|
||||
seconds = value % 60;
|
||||
minutes = value / 60;
|
||||
|
||||
value = (unsigned)([object doubleValue]);
|
||||
|
||||
seconds = value % 60;
|
||||
minutes = value / 60;
|
||||
|
||||
while(60 <= minutes) {
|
||||
minutes -= 60;
|
||||
++hours;
|
||||
}
|
||||
|
||||
|
||||
while(24 <= hours) {
|
||||
hours -= 24;
|
||||
++days;
|
||||
|
@ -52,56 +51,49 @@
|
|||
|
||||
if(0 < days) {
|
||||
result = [NSString stringWithFormat:@"%u:%.2u:%.2u:%.2u", days, hours, minutes, seconds];
|
||||
}
|
||||
else if(0 < hours) {
|
||||
} else if(0 < hours) {
|
||||
result = [NSString stringWithFormat:@"%u:%.2u:%.2u", hours, minutes, seconds];
|
||||
}
|
||||
else if(0 < minutes) {
|
||||
} else if(0 < minutes) {
|
||||
result = [NSString stringWithFormat:@"%u:%.2u", minutes, seconds];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result = [NSString stringWithFormat:@"0:%.2u", seconds];
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL) getObjectValue:(id *)object forString:(NSString *)string errorDescription:(NSString **)error
|
||||
{
|
||||
NSScanner *scanner = nil;
|
||||
BOOL result = NO;
|
||||
int value = 0;
|
||||
unsigned seconds = 0;
|
||||
- (BOOL)getObjectValue:(id *)object forString:(NSString *)string errorDescription:(NSString **)error {
|
||||
NSScanner *scanner = nil;
|
||||
BOOL result = NO;
|
||||
int value = 0;
|
||||
unsigned seconds = 0;
|
||||
|
||||
scanner = [NSScanner scannerWithString:string];
|
||||
|
||||
scanner = [NSScanner scannerWithString:string];
|
||||
|
||||
while(NO == [scanner isAtEnd]) {
|
||||
|
||||
// Grab a value
|
||||
if([scanner scanInt:&value]) {
|
||||
seconds *= 60;
|
||||
seconds += value;
|
||||
result = YES;
|
||||
seconds *= 60;
|
||||
seconds += value;
|
||||
result = YES;
|
||||
}
|
||||
|
||||
|
||||
// Grab the separator, if present
|
||||
[scanner scanString:@":" intoString:NULL];
|
||||
}
|
||||
|
||||
|
||||
if(result && NULL != object) {
|
||||
*object = [NSNumber numberWithUnsignedInt:seconds];
|
||||
}
|
||||
else if(NULL != error) {
|
||||
} else if(NULL != error) {
|
||||
*error = @"Couldn't convert value to seconds";
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSAttributedString *) attributedStringForObjectValue:(id)object withDefaultAttributes:(NSDictionary *)attributes
|
||||
{
|
||||
NSAttributedString *result = nil;
|
||||
|
||||
- (NSAttributedString *)attributedStringForObjectValue:(id)object withDefaultAttributes:(NSDictionary *)attributes {
|
||||
NSAttributedString *result = nil;
|
||||
|
||||
result = [[NSAttributedString alloc] initWithString:[self stringForObjectValue:object] attributes:attributes];
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -6,17 +6,15 @@
|
|||
// Copyright 2009 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "AppController.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
@interface InfoWindowController : NSWindowController
|
||||
{
|
||||
@interface InfoWindowController : NSWindowController {
|
||||
IBOutlet id playlistSelectionController;
|
||||
IBOutlet id currentEntryController;
|
||||
IBOutlet AppController *appController;
|
||||
IBOutlet id currentEntryController;
|
||||
IBOutlet AppController *appController;
|
||||
|
||||
id __unsafe_unretained valueToDisplay;
|
||||
id __unsafe_unretained valueToDisplay;
|
||||
}
|
||||
|
||||
@property(assign) id valueToDisplay;
|
||||
|
|
|
@ -7,58 +7,53 @@
|
|||
//
|
||||
|
||||
#import "InfoWindowController.h"
|
||||
#import "AppController.h"
|
||||
#import "Logging.h"
|
||||
#import "MissingAlbumArtTransformer.h"
|
||||
#import "PlaylistEntry.h"
|
||||
#import "Logging.h"
|
||||
#import "AppController.h"
|
||||
|
||||
@implementation InfoWindowController
|
||||
|
||||
@synthesize valueToDisplay;
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
+ (void)initialize {
|
||||
NSValueTransformer *missingAlbumArtTransformer = [[MissingAlbumArtTransformer alloc] init];
|
||||
[NSValueTransformer setValueTransformer:missingAlbumArtTransformer
|
||||
forName:@"MissingAlbumArtTransformer"];
|
||||
[NSValueTransformer setValueTransformer:missingAlbumArtTransformer
|
||||
forName:@"MissingAlbumArtTransformer"];
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
- (id)init {
|
||||
return [super initWithWindowNibName:@"InfoInspector"];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[playlistSelectionController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:nil];
|
||||
[currentEntryController addObserver:self forKeyPath:@"content" options:NSKeyValueObservingOptionNew context:nil];
|
||||
[appController addObserver:self forKeyPath:@"miniMode" options:NSKeyValueObservingOptionNew context:nil];
|
||||
|
||||
- (void)awakeFromNib {
|
||||
[playlistSelectionController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:nil];
|
||||
[currentEntryController addObserver:self forKeyPath:@"content" options:NSKeyValueObservingOptionNew context:nil];
|
||||
[appController addObserver:self forKeyPath:@"miniMode" options:NSKeyValueObservingOptionNew context:nil];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
// Avoid "selection" because it creates a proxy that's hard to reason with when we don't need to write.
|
||||
PlaylistEntry* currentSelection = [[playlistSelectionController selectedObjects] firstObject];
|
||||
if (currentSelection != NULL) {
|
||||
[self setValueToDisplay: currentSelection];
|
||||
} else {
|
||||
[self setValueToDisplay:[currentEntryController content]];
|
||||
}
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
// Avoid "selection" because it creates a proxy that's hard to reason with when we don't need to write.
|
||||
PlaylistEntry *currentSelection = [[playlistSelectionController selectedObjects] firstObject];
|
||||
if(currentSelection != NULL) {
|
||||
[self setValueToDisplay:currentSelection];
|
||||
} else {
|
||||
[self setValueToDisplay:[currentEntryController content]];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)toggleWindow:(id)sender
|
||||
{
|
||||
if ([[self window] isVisible])
|
||||
- (IBAction)toggleWindow:(id)sender {
|
||||
if([[self window] isVisible])
|
||||
[[self window] orderOut:self];
|
||||
else {
|
||||
if ([NSApp mainWindow]) {
|
||||
NSRect rect = [[NSApp mainWindow] frame];
|
||||
// Align Info Inspector HUD Panel to the right of Main Window.
|
||||
NSPoint point = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
|
||||
[[self window] setFrameTopLeftPoint:point];
|
||||
}
|
||||
[self showWindow:self];
|
||||
}
|
||||
else {
|
||||
if([NSApp mainWindow]) {
|
||||
NSRect rect = [[NSApp mainWindow] frame];
|
||||
// Align Info Inspector HUD Panel to the right of Main Window.
|
||||
NSPoint point = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
|
||||
[[self window] setFrameTopLeftPoint:point];
|
||||
}
|
||||
[self showWindow:self];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,9 +8,7 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
@interface MissingAlbumArtTransformer : NSValueTransformer {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,18 +8,21 @@
|
|||
|
||||
#import "MissingAlbumArtTransformer.h"
|
||||
|
||||
|
||||
@implementation MissingAlbumArtTransformer
|
||||
|
||||
+ (Class)transformedValueClass { return [NSImage class]; }
|
||||
+ (BOOL)allowsReverseTransformation { return NO; }
|
||||
+ (Class)transformedValueClass {
|
||||
return [NSImage class];
|
||||
}
|
||||
+ (BOOL)allowsReverseTransformation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Convert from NSImage to NSImage
|
||||
- (id)transformedValue:(id)value {
|
||||
if (value == nil) {
|
||||
if(value == nil) {
|
||||
return [NSImage imageNamed:@"missingArt"];
|
||||
}
|
||||
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
12
Playlist/DNDArrayController.h
Executable file → Normal file
12
Playlist/DNDArrayController.h
Executable file → Normal file
|
@ -10,24 +10,24 @@ extern NSString *iTunesDropType;
|
|||
@property IBOutlet NSTableView *tableView;
|
||||
|
||||
// table view drag and drop support
|
||||
- (id <NSPasteboardWriting>)tableView:(NSTableView *)tableView
|
||||
pasteboardWriterForRow:(NSInteger)row;
|
||||
- (id<NSPasteboardWriting>)tableView:(NSTableView *)tableView
|
||||
pasteboardWriterForRow:(NSInteger)row;
|
||||
- (void)tableView:(NSTableView *)tableView
|
||||
draggingSession:(NSDraggingSession *)session
|
||||
willBeginAtPoint:(NSPoint)screenPoint
|
||||
forRowIndexes:(NSIndexSet *)rowIndexes;
|
||||
- (NSDragOperation)tableView:(NSTableView *)tableView
|
||||
validateDrop:(id <NSDraggingInfo>)info
|
||||
validateDrop:(id<NSDraggingInfo>)info
|
||||
proposedRow:(NSInteger)row
|
||||
proposedDropOperation:(NSTableViewDropOperation)dropOperation;
|
||||
- (BOOL)tableView:(NSTableView *)tableView
|
||||
acceptDrop:(id <NSDraggingInfo>)info
|
||||
acceptDrop:(id<NSDraggingInfo>)info
|
||||
row:(NSInteger)row
|
||||
dropOperation:(NSTableViewDropOperation)dropOperation;
|
||||
|
||||
// utility methods
|
||||
-(void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet
|
||||
toIndex:(NSUInteger)insertIndex;
|
||||
- (void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet
|
||||
toIndex:(NSUInteger)insertIndex;
|
||||
// This is needed to undo the above
|
||||
- (void)moveObjectsFromIndex:(NSUInteger)fromIndex
|
||||
toArrangedObjectIndexes:(NSIndexSet *)indexSet;
|
||||
|
|
233
Playlist/DNDArrayController.m
Executable file → Normal file
233
Playlist/DNDArrayController.m
Executable file → Normal file
|
@ -10,169 +10,162 @@ NSString *iTunesDropType = @"com.apple.tv.metadata";
|
|||
@implementation DNDArrayController
|
||||
|
||||
- (void)awakeFromNib {
|
||||
[super awakeFromNib];
|
||||
// register for drag and drop
|
||||
NSPasteboardType fileType;
|
||||
if (@available(macOS 10.13, *)) {
|
||||
fileType = NSPasteboardTypeFileURL;
|
||||
}
|
||||
else {
|
||||
fileType = NSFilenamesPboardType;
|
||||
}
|
||||
[self.tableView registerForDraggedTypes:@[CogDNDIndexType,
|
||||
CogUrlsPboardType,
|
||||
fileType,
|
||||
iTunesDropType]];
|
||||
[super awakeFromNib];
|
||||
// register for drag and drop
|
||||
NSPasteboardType fileType;
|
||||
if(@available(macOS 10.13, *)) {
|
||||
fileType = NSPasteboardTypeFileURL;
|
||||
} else {
|
||||
fileType = NSFilenamesPboardType;
|
||||
}
|
||||
[self.tableView registerForDraggedTypes:@[CogDNDIndexType,
|
||||
CogUrlsPboardType,
|
||||
fileType,
|
||||
iTunesDropType]];
|
||||
}
|
||||
|
||||
- (id<NSPasteboardWriting>)tableView:(NSTableView *)tableView
|
||||
pasteboardWriterForRow:(NSInteger)row {
|
||||
NSPasteboardItem *item = [[NSPasteboardItem alloc] init];
|
||||
[item setString:[@(row) stringValue] forType:CogDNDIndexType];
|
||||
|
||||
- (id <NSPasteboardWriting>)tableView:(NSTableView *)tableView
|
||||
pasteboardWriterForRow:(NSInteger)row {
|
||||
NSPasteboardItem *item = [[NSPasteboardItem alloc] init];
|
||||
[item setString:[@(row) stringValue] forType:CogDNDIndexType];
|
||||
|
||||
return item;
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
- (void)tableView:(NSTableView *)tableView
|
||||
draggingSession:(NSDraggingSession *)session
|
||||
willBeginAtPoint:(NSPoint)screenPoint
|
||||
forRowIndexes:(NSIndexSet *)rowIndexes {
|
||||
DLog(@"Drag session started with indexes: %@", rowIndexes);
|
||||
DLog(@"Drag session started with indexes: %@", rowIndexes);
|
||||
}
|
||||
|
||||
|
||||
- (NSDragOperation)tableView:(NSTableView *)tableView
|
||||
validateDrop:(id <NSDraggingInfo>)info
|
||||
validateDrop:(id<NSDraggingInfo>)info
|
||||
proposedRow:(NSInteger)row
|
||||
proposedDropOperation:(NSTableViewDropOperation)dropOperation {
|
||||
NSDragOperation dragOp = NSDragOperationCopy;
|
||||
NSDragOperation dragOp = NSDragOperationCopy;
|
||||
|
||||
if ([info draggingSource] == tableView)
|
||||
dragOp = NSDragOperationMove;
|
||||
if([info draggingSource] == tableView)
|
||||
dragOp = NSDragOperationMove;
|
||||
|
||||
DLog(@"VALIDATING DROP!");
|
||||
// we want to put the object at, not over,
|
||||
// the current row (contrast NSTableViewDropOn)
|
||||
[tableView setDropRow:row dropOperation:NSTableViewDropAbove];
|
||||
DLog(@"VALIDATING DROP!");
|
||||
// we want to put the object at, not over,
|
||||
// the current row (contrast NSTableViewDropOn)
|
||||
[tableView setDropRow:row dropOperation:NSTableViewDropAbove];
|
||||
|
||||
return dragOp;
|
||||
return dragOp;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)tableView:(NSTableView *)tableView
|
||||
acceptDrop:(id <NSDraggingInfo>)info
|
||||
acceptDrop:(id<NSDraggingInfo>)info
|
||||
row:(NSInteger)row
|
||||
dropOperation:(NSTableViewDropOperation)dropOperation {
|
||||
if (row < 0) {
|
||||
row = 0;
|
||||
}
|
||||
if(row < 0) {
|
||||
row = 0;
|
||||
}
|
||||
|
||||
NSArray<NSPasteboardItem *> *items = info.draggingPasteboard.pasteboardItems;
|
||||
// if drag source is self, it's a move
|
||||
if ([info draggingSource] == tableView || items == nil) {
|
||||
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
|
||||
for (NSPasteboardItem *item in items) {
|
||||
[indexSet addIndex:(NSUInteger) [[item stringForType:CogDNDIndexType] intValue]];
|
||||
}
|
||||
if ([indexSet count] > 0) {
|
||||
DLog(@"INDEX SET ON DROP: %@", indexSet);
|
||||
NSArray *selected = [[self arrangedObjects] objectsAtIndexes:indexSet];
|
||||
[self moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:(unsigned int) row];
|
||||
NSArray<NSPasteboardItem *> *items = info.draggingPasteboard.pasteboardItems;
|
||||
// if drag source is self, it's a move
|
||||
if([info draggingSource] == tableView || items == nil) {
|
||||
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
|
||||
for(NSPasteboardItem *item in items) {
|
||||
[indexSet addIndex:(NSUInteger)[[item stringForType:CogDNDIndexType] intValue]];
|
||||
}
|
||||
if([indexSet count] > 0) {
|
||||
DLog(@"INDEX SET ON DROP: %@", indexSet);
|
||||
NSArray *selected = [[self arrangedObjects] objectsAtIndexes:indexSet];
|
||||
[self moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:(unsigned int)row];
|
||||
|
||||
[self setSelectedObjects:selected];
|
||||
[self setSelectedObjects:selected];
|
||||
|
||||
DLog(@"ACCEPTING DROP!");
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
DLog(@"REJECTING DROP!");
|
||||
return NO;
|
||||
DLog(@"ACCEPTING DROP!");
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
DLog(@"REJECTING DROP!");
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
- (void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet
|
||||
toIndex:(NSUInteger)insertIndex {
|
||||
__block NSUInteger rangeCount = 0;
|
||||
__block NSUInteger firstIndex = 0;
|
||||
[indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
||||
if (++rangeCount == 1)
|
||||
firstIndex = range.location;
|
||||
}];
|
||||
|
||||
if (rangeCount == 1 &&
|
||||
(insertIndex >= firstIndex &&
|
||||
insertIndex < firstIndex + [indexSet count])) // Null operation
|
||||
return;
|
||||
|
||||
NSArray *objects = [self arrangedObjects];
|
||||
NSUInteger index = [indexSet lastIndex];
|
||||
__block NSUInteger rangeCount = 0;
|
||||
__block NSUInteger firstIndex = 0;
|
||||
[indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL *_Nonnull stop) {
|
||||
if(++rangeCount == 1)
|
||||
firstIndex = range.location;
|
||||
}];
|
||||
|
||||
NSUInteger aboveInsertIndexCount = 0;
|
||||
id object;
|
||||
NSUInteger removeIndex;
|
||||
if(rangeCount == 1 &&
|
||||
(insertIndex >= firstIndex &&
|
||||
insertIndex < firstIndex + [indexSet count])) // Null operation
|
||||
return;
|
||||
|
||||
while (NSNotFound != index) {
|
||||
if (index >= insertIndex) {
|
||||
removeIndex = index + aboveInsertIndexCount;
|
||||
aboveInsertIndexCount += 1;
|
||||
} else {
|
||||
removeIndex = index;
|
||||
insertIndex -= 1;
|
||||
}
|
||||
NSArray *objects = [self arrangedObjects];
|
||||
NSUInteger index = [indexSet lastIndex];
|
||||
|
||||
object = objects[removeIndex];
|
||||
NSUInteger aboveInsertIndexCount = 0;
|
||||
id object;
|
||||
NSUInteger removeIndex;
|
||||
|
||||
[self removeObjectAtArrangedObjectIndex:removeIndex];
|
||||
[self insertObject:object atArrangedObjectIndex:insertIndex];
|
||||
while(NSNotFound != index) {
|
||||
if(index >= insertIndex) {
|
||||
removeIndex = index + aboveInsertIndexCount;
|
||||
aboveInsertIndexCount += 1;
|
||||
} else {
|
||||
removeIndex = index;
|
||||
insertIndex -= 1;
|
||||
}
|
||||
|
||||
index = [indexSet indexLessThanIndex:index];
|
||||
}
|
||||
object = objects[removeIndex];
|
||||
|
||||
[self removeObjectAtArrangedObjectIndex:removeIndex];
|
||||
[self insertObject:object atArrangedObjectIndex:insertIndex];
|
||||
|
||||
index = [indexSet indexLessThanIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveObjectsFromIndex:(NSUInteger)fromIndex
|
||||
toArrangedObjectIndexes:(NSIndexSet *)indexSet {
|
||||
__block NSUInteger rangeCount = 0;
|
||||
__block NSUInteger firstIndex = 0;
|
||||
__block NSUInteger _fromIndex = fromIndex;
|
||||
[indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
||||
if (++rangeCount == 1)
|
||||
firstIndex = range.location;
|
||||
if (_fromIndex >= range.location) {
|
||||
if (_fromIndex < range.location + range.length)
|
||||
_fromIndex = range.location;
|
||||
else
|
||||
_fromIndex -= range.length;
|
||||
}
|
||||
}];
|
||||
|
||||
if (rangeCount == 1 &&
|
||||
(fromIndex >= firstIndex &&
|
||||
fromIndex < firstIndex + [indexSet count])) // Null operation
|
||||
return;
|
||||
__block NSUInteger rangeCount = 0;
|
||||
__block NSUInteger firstIndex = 0;
|
||||
__block NSUInteger _fromIndex = fromIndex;
|
||||
[indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL *_Nonnull stop) {
|
||||
if(++rangeCount == 1)
|
||||
firstIndex = range.location;
|
||||
if(_fromIndex >= range.location) {
|
||||
if(_fromIndex < range.location + range.length)
|
||||
_fromIndex = range.location;
|
||||
else
|
||||
_fromIndex -= range.length;
|
||||
}
|
||||
}];
|
||||
|
||||
fromIndex = _fromIndex;
|
||||
|
||||
NSArray *objects = [[self arrangedObjects] subarrayWithRange:NSMakeRange(fromIndex, [indexSet count])];
|
||||
NSUInteger index = [indexSet firstIndex];
|
||||
if(rangeCount == 1 &&
|
||||
(fromIndex >= firstIndex &&
|
||||
fromIndex < firstIndex + [indexSet count])) // Null operation
|
||||
return;
|
||||
|
||||
NSUInteger itemIndex = 0;
|
||||
id object;
|
||||
|
||||
fromIndex += [objects count];
|
||||
for (NSUInteger i = 0; i < [objects count]; i++) {
|
||||
[self removeObjectAtArrangedObjectIndex:--fromIndex];
|
||||
}
|
||||
fromIndex = _fromIndex;
|
||||
|
||||
while (NSNotFound != index) {
|
||||
object = objects[itemIndex++];
|
||||
NSArray *objects = [[self arrangedObjects] subarrayWithRange:NSMakeRange(fromIndex, [indexSet count])];
|
||||
NSUInteger index = [indexSet firstIndex];
|
||||
|
||||
[self insertObject:object atArrangedObjectIndex:index];
|
||||
NSUInteger itemIndex = 0;
|
||||
id object;
|
||||
|
||||
index = [indexSet indexGreaterThanIndex:index];
|
||||
}
|
||||
fromIndex += [objects count];
|
||||
for(NSUInteger i = 0; i < [objects count]; i++) {
|
||||
[self removeObjectAtArrangedObjectIndex:--fromIndex];
|
||||
}
|
||||
|
||||
while(NSNotFound != index) {
|
||||
object = objects[itemIndex++];
|
||||
|
||||
[self insertObject:object atArrangedObjectIndex:index];
|
||||
|
||||
index = [indexSet indexGreaterThanIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
// Copyright 2005 Vincent Spader All rights reserved.
|
||||
//
|
||||
|
||||
#import "DNDArrayController.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Foundation/NSUndoManager.h>
|
||||
#import "DNDArrayController.h"
|
||||
|
||||
@class PlaylistLoader;
|
||||
@class PlaylistEntry;
|
||||
|
@ -16,60 +16,62 @@
|
|||
@class PlaybackController;
|
||||
|
||||
typedef NS_ENUM(NSInteger, RepeatMode) {
|
||||
RepeatModeNoRepeat = 0,
|
||||
RepeatModeRepeatOne,
|
||||
RepeatModeRepeatAlbum,
|
||||
RepeatModeRepeatAll
|
||||
RepeatModeNoRepeat = 0,
|
||||
RepeatModeRepeatOne,
|
||||
RepeatModeRepeatAlbum,
|
||||
RepeatModeRepeatAll
|
||||
};
|
||||
|
||||
static inline BOOL IsRepeatOneSet() {
|
||||
return [[NSUserDefaults standardUserDefaults] integerForKey:@"repeat"] == RepeatModeRepeatOne;
|
||||
return [[NSUserDefaults standardUserDefaults] integerForKey:@"repeat"] == RepeatModeRepeatOne;
|
||||
}
|
||||
|
||||
typedef enum { ShuffleOff = 0, ShuffleAlbums, ShuffleAll } ShuffleMode;
|
||||
typedef enum { ShuffleOff = 0,
|
||||
ShuffleAlbums,
|
||||
ShuffleAll } ShuffleMode;
|
||||
|
||||
typedef NS_ENUM(NSInteger, URLOrigin) {
|
||||
URLOriginInternal = 0,
|
||||
URLOriginExternal
|
||||
URLOriginInternal = 0,
|
||||
URLOriginExternal
|
||||
};
|
||||
|
||||
@interface PlaylistController : DNDArrayController <NSTableViewDelegate> {
|
||||
IBOutlet PlaylistLoader *playlistLoader;
|
||||
IBOutlet SpotlightWindowController *spotlightWindowController;
|
||||
IBOutlet PlaybackController *playbackController;
|
||||
|
||||
NSValueTransformer * statusImageTransformer;
|
||||
IBOutlet PlaylistLoader *playlistLoader;
|
||||
IBOutlet SpotlightWindowController *spotlightWindowController;
|
||||
IBOutlet PlaybackController *playbackController;
|
||||
|
||||
NSMutableArray *shuffleList;
|
||||
NSMutableArray *queueList;
|
||||
NSValueTransformer *statusImageTransformer;
|
||||
|
||||
NSString *totalTime;
|
||||
NSMutableArray *shuffleList;
|
||||
NSMutableArray *queueList;
|
||||
|
||||
PlaylistEntry *currentEntry;
|
||||
NSString *totalTime;
|
||||
|
||||
NSUndoManager *undoManager;
|
||||
PlaylistEntry *currentEntry;
|
||||
|
||||
NSUndoManager *undoManager;
|
||||
}
|
||||
|
||||
@property(nonatomic, retain) PlaylistEntry * _Nullable currentEntry;
|
||||
@property(retain) NSString * _Nullable totalTime;
|
||||
@property(nonatomic, retain) PlaylistEntry *_Nullable currentEntry;
|
||||
@property(retain) NSString *_Nullable totalTime;
|
||||
|
||||
// Private Methods
|
||||
- (void)updateTotalTime;
|
||||
- (void)updatePlaylistIndexes;
|
||||
- (IBAction)stopAfterCurrent:(id _Nullable )sender;
|
||||
- (IBAction)stopAfterCurrent:(id _Nullable)sender;
|
||||
|
||||
// PUBLIC METHODS
|
||||
- (void)setShuffle:(ShuffleMode)s;
|
||||
- (ShuffleMode)shuffle;
|
||||
- (void)setRepeat:(RepeatMode)r;
|
||||
- (RepeatMode)repeat;
|
||||
- (NSArray * _Nullable)filterPlaylistOnAlbum:(NSString * _Nullable)album;
|
||||
- (NSArray *_Nullable)filterPlaylistOnAlbum:(NSString *_Nullable)album;
|
||||
|
||||
- (PlaylistEntry * _Nullable)getNextEntry:(PlaylistEntry * _Nullable)pe;
|
||||
- (PlaylistEntry * _Nullable)getPrevEntry:(PlaylistEntry * _Nullable)pe;
|
||||
- (PlaylistEntry *_Nullable)getNextEntry:(PlaylistEntry *_Nullable)pe;
|
||||
- (PlaylistEntry *_Nullable)getPrevEntry:(PlaylistEntry *_Nullable)pe;
|
||||
|
||||
/* Methods for undoing various actions */
|
||||
- (NSUndoManager * _Nullable)undoManager;
|
||||
- (NSUndoManager *_Nullable)undoManager;
|
||||
|
||||
- (IBAction)toggleShuffle:(id _Nullable)sender;
|
||||
|
||||
|
@ -98,25 +100,25 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
|
|||
- (void)addShuffledListToFront;
|
||||
- (void)resetShuffleList;
|
||||
|
||||
- (PlaylistEntry * _Nullable)shuffledEntryAtIndex:(NSInteger)i;
|
||||
- (PlaylistEntry * _Nullable)entryAtIndex:(NSInteger)i;
|
||||
- (PlaylistEntry *_Nullable)shuffledEntryAtIndex:(NSInteger)i;
|
||||
- (PlaylistEntry *_Nullable)entryAtIndex:(NSInteger)i;
|
||||
|
||||
// Event inlets:
|
||||
- (void)willInsertURLs:(NSArray * _Nullable)urls origin:(URLOrigin)origin;
|
||||
- (void)didInsertURLs:(NSArray * _Nullable)urls origin:(URLOrigin)origin;
|
||||
- (void)willInsertURLs:(NSArray *_Nullable)urls origin:(URLOrigin)origin;
|
||||
- (void)didInsertURLs:(NSArray *_Nullable)urls origin:(URLOrigin)origin;
|
||||
|
||||
// queue methods
|
||||
- (IBAction)toggleQueued:(id _Nullable)sender;
|
||||
- (IBAction)emptyQueueList:(id _Nullable)sender;
|
||||
- (void)emptyQueueListUnsynced;
|
||||
- (NSMutableArray * _Nullable)queueList;
|
||||
- (NSMutableArray *_Nullable)queueList;
|
||||
|
||||
// reload metadata of selection
|
||||
- (IBAction)reloadTags:(id _Nullable)sender;
|
||||
|
||||
- (void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet * _Nullable)indexSet
|
||||
- (void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *_Nullable)indexSet
|
||||
toIndex:(NSUInteger)insertIndex;
|
||||
|
||||
- (void)insertObjectsUnsynced:(NSArray * _Nullable)objects atArrangedObjectIndexes:(NSIndexSet * _Nullable)indexes;
|
||||
- (void)insertObjectsUnsynced:(NSArray *_Nullable)objects atArrangedObjectIndexes:(NSIndexSet *_Nullable)indexes;
|
||||
|
||||
@end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue