Implement visualization support and a spectrum
Borrowing some DFT code from deadbeef, this implements a simple spectrum visualization into the main toolbar of the app. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
parent
1309672adc
commit
417687600b
11 changed files with 471 additions and 4 deletions
|
@ -63,6 +63,10 @@
|
||||||
83725A8E27AA0DE60003F694 /* libsoxr.0.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83725A8A27AA0DBF0003F694 /* libsoxr.0.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
83725A8E27AA0DE60003F694 /* libsoxr.0.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83725A8A27AA0DBF0003F694 /* libsoxr.0.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
83725A9027AA16C90003F694 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7B27AA0D8A0003F694 /* Accelerate.framework */; };
|
83725A9027AA16C90003F694 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7B27AA0D8A0003F694 /* Accelerate.framework */; };
|
||||||
83725A9127AA16D50003F694 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */; };
|
83725A9127AA16D50003F694 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */; };
|
||||||
|
8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */ = {isa = PBXBuildFile; fileRef = 8377C64B27B8C51500E8BC0F /* fft_accelerate.c */; };
|
||||||
|
8377C64E27B8C54400E8BC0F /* fft.h in Headers */ = {isa = PBXBuildFile; fileRef = 8377C64D27B8C54400E8BC0F /* fft.h */; };
|
||||||
|
8377C65227B8CAD100E8BC0F /* VisualizationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 8377C65027B8CAD100E8BC0F /* VisualizationController.h */; };
|
||||||
|
8377C65327B8CAD100E8BC0F /* VisualizationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C65127B8CAD100E8BC0F /* VisualizationController.m */; };
|
||||||
8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; };
|
8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; };
|
||||||
839366671815923C006DD712 /* CogPluginMulti.h in Headers */ = {isa = PBXBuildFile; fileRef = 839366651815923C006DD712 /* CogPluginMulti.h */; };
|
839366671815923C006DD712 /* CogPluginMulti.h in Headers */ = {isa = PBXBuildFile; fileRef = 839366651815923C006DD712 /* CogPluginMulti.h */; };
|
||||||
839366681815923C006DD712 /* CogPluginMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogPluginMulti.m */; };
|
839366681815923C006DD712 /* CogPluginMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogPluginMulti.m */; };
|
||||||
|
@ -159,6 +163,10 @@
|
||||||
83725A7C27AA0D8E0003F694 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
83725A7C27AA0D8E0003F694 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||||
83725A8827AA0DBF0003F694 /* soxr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = soxr.h; sourceTree = "<group>"; };
|
83725A8827AA0DBF0003F694 /* soxr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = soxr.h; sourceTree = "<group>"; };
|
||||||
83725A8A27AA0DBF0003F694 /* libsoxr.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libsoxr.0.dylib; sourceTree = "<group>"; };
|
83725A8A27AA0DBF0003F694 /* libsoxr.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libsoxr.0.dylib; sourceTree = "<group>"; };
|
||||||
|
8377C64B27B8C51500E8BC0F /* fft_accelerate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fft_accelerate.c; sourceTree = "<group>"; };
|
||||||
|
8377C64D27B8C54400E8BC0F /* fft.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fft.h; sourceTree = "<group>"; };
|
||||||
|
8377C65027B8CAD100E8BC0F /* VisualizationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VisualizationController.h; sourceTree = "<group>"; };
|
||||||
|
8377C65127B8CAD100E8BC0F /* VisualizationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VisualizationController.m; sourceTree = "<group>"; };
|
||||||
8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
|
8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
|
||||||
839366651815923C006DD712 /* CogPluginMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogPluginMulti.h; sourceTree = "<group>"; };
|
839366651815923C006DD712 /* CogPluginMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogPluginMulti.h; sourceTree = "<group>"; };
|
||||||
839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = "<group>"; };
|
839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = "<group>"; };
|
||||||
|
@ -235,6 +243,7 @@
|
||||||
08FB77AEFE84172EC02AAC07 /* Classes */ = {
|
08FB77AEFE84172EC02AAC07 /* Classes */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
8377C64F27B8CAAB00E8BC0F /* Visualization */,
|
||||||
17F94DDC0B8D101100A34E87 /* Plugin.h */,
|
17F94DDC0B8D101100A34E87 /* Plugin.h */,
|
||||||
17D21EBB0B8BF44000D1EBDE /* AudioPlayer.h */,
|
17D21EBB0B8BF44000D1EBDE /* AudioPlayer.h */,
|
||||||
17D21EBC0B8BF44000D1EBDE /* AudioPlayer.m */,
|
17D21EBC0B8BF44000D1EBDE /* AudioPlayer.m */,
|
||||||
|
@ -326,6 +335,7 @@
|
||||||
17D21CD80B8BE5B400D1EBDE /* ThirdParty */ = {
|
17D21CD80B8BE5B400D1EBDE /* ThirdParty */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
8377C64A27B8C51500E8BC0F /* deadbeef */,
|
||||||
83725A8627AA0DBF0003F694 /* libsoxr */,
|
83725A8627AA0DBF0003F694 /* libsoxr */,
|
||||||
835C88AE279811A500E28EAE /* hdcd */,
|
835C88AE279811A500E28EAE /* hdcd */,
|
||||||
835C88A22797D4D400E28EAE /* lvqcl */,
|
835C88A22797D4D400E28EAE /* lvqcl */,
|
||||||
|
@ -426,6 +436,24 @@
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
8377C64A27B8C51500E8BC0F /* deadbeef */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
8377C64D27B8C54400E8BC0F /* fft.h */,
|
||||||
|
8377C64B27B8C51500E8BC0F /* fft_accelerate.c */,
|
||||||
|
);
|
||||||
|
path = deadbeef;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
8377C64F27B8CAAB00E8BC0F /* Visualization */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
8377C65027B8CAD100E8BC0F /* VisualizationController.h */,
|
||||||
|
8377C65127B8CAD100E8BC0F /* VisualizationController.m */,
|
||||||
|
);
|
||||||
|
path = Visualization;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXHeadersBuildPhase section */
|
/* Begin PBXHeadersBuildPhase section */
|
||||||
|
@ -445,6 +473,7 @@
|
||||||
17D21CF30B8BE5EF00D1EBDE /* Semaphore.h in Headers */,
|
17D21CF30B8BE5EF00D1EBDE /* Semaphore.h in Headers */,
|
||||||
17D21DC70B8BE79700D1EBDE /* CoreAudioUtils.h in Headers */,
|
17D21DC70B8BE79700D1EBDE /* CoreAudioUtils.h in Headers */,
|
||||||
17D21EBD0B8BF44000D1EBDE /* AudioPlayer.h in Headers */,
|
17D21EBD0B8BF44000D1EBDE /* AudioPlayer.h in Headers */,
|
||||||
|
8377C65227B8CAD100E8BC0F /* VisualizationController.h in Headers */,
|
||||||
834FD4F027AF93680063BC83 /* ChunkList.h in Headers */,
|
834FD4F027AF93680063BC83 /* ChunkList.h in Headers */,
|
||||||
17F94DD50B8D0F7000A34E87 /* PluginController.h in Headers */,
|
17F94DD50B8D0F7000A34E87 /* PluginController.h in Headers */,
|
||||||
17F94DDD0B8D101100A34E87 /* Plugin.h in Headers */,
|
17F94DDD0B8D101100A34E87 /* Plugin.h in Headers */,
|
||||||
|
@ -460,6 +489,7 @@
|
||||||
835C88B1279811A500E28EAE /* hdcd_decode2.h in Headers */,
|
835C88B1279811A500E28EAE /* hdcd_decode2.h in Headers */,
|
||||||
8EC1225F0B993BD500C5B3AD /* ConverterNode.h in Headers */,
|
8EC1225F0B993BD500C5B3AD /* ConverterNode.h in Headers */,
|
||||||
8384912718080FF100E7332D /* Logging.h in Headers */,
|
8384912718080FF100E7332D /* Logging.h in Headers */,
|
||||||
|
8377C64E27B8C54400E8BC0F /* fft.h in Headers */,
|
||||||
8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */,
|
8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */,
|
||||||
B0575F2D0D687A0800411D77 /* Helper.h in Headers */,
|
B0575F2D0D687A0800411D77 /* Helper.h in Headers */,
|
||||||
835C88AD2797DA5800E28EAE /* util.h in Headers */,
|
835C88AD2797DA5800E28EAE /* util.h in Headers */,
|
||||||
|
@ -546,6 +576,7 @@
|
||||||
8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */,
|
8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */,
|
||||||
17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */,
|
17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */,
|
||||||
17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */,
|
17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */,
|
||||||
|
8377C65327B8CAD100E8BC0F /* VisualizationController.m in Sources */,
|
||||||
834FD4F527AFA2150063BC83 /* Downmix.m in Sources */,
|
834FD4F527AFA2150063BC83 /* Downmix.m in Sources */,
|
||||||
17D21CC60B8BE4BA00D1EBDE /* OutputCoreAudio.m in Sources */,
|
17D21CC60B8BE4BA00D1EBDE /* OutputCoreAudio.m in Sources */,
|
||||||
835C88B2279811A500E28EAE /* hdcd_decode2.c in Sources */,
|
835C88B2279811A500E28EAE /* hdcd_decode2.c in Sources */,
|
||||||
|
@ -553,6 +584,7 @@
|
||||||
17D21CF40B8BE5EF00D1EBDE /* Semaphore.m in Sources */,
|
17D21CF40B8BE5EF00D1EBDE /* Semaphore.m in Sources */,
|
||||||
8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */,
|
8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */,
|
||||||
17D21DC80B8BE79700D1EBDE /* CoreAudioUtils.m in Sources */,
|
17D21DC80B8BE79700D1EBDE /* CoreAudioUtils.m in Sources */,
|
||||||
|
8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */,
|
||||||
839366681815923C006DD712 /* CogPluginMulti.m in Sources */,
|
839366681815923C006DD712 /* CogPluginMulti.m in Sources */,
|
||||||
835C88AA2797D4D400E28EAE /* lpc.c in Sources */,
|
835C88AA2797D4D400E28EAE /* lpc.c in Sources */,
|
||||||
17D21EBE0B8BF44000D1EBDE /* AudioPlayer.m in Sources */,
|
17D21EBE0B8BF44000D1EBDE /* AudioPlayer.m in Sources */,
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
#import "Downmix.h"
|
#import "Downmix.h"
|
||||||
|
|
||||||
|
#import "VisualizationController.h"
|
||||||
|
|
||||||
#import "Semaphore.h"
|
#import "Semaphore.h"
|
||||||
|
|
||||||
//#define OUTPUT_LOG
|
//#define OUTPUT_LOG
|
||||||
|
@ -61,6 +63,8 @@
|
||||||
AudioStreamBasicDescription deviceFormat; // info about the default device
|
AudioStreamBasicDescription deviceFormat; // info about the default device
|
||||||
AudioStreamBasicDescription streamFormat; // stream format last seen in render callback
|
AudioStreamBasicDescription streamFormat; // stream format last seen in render callback
|
||||||
|
|
||||||
|
AudioStreamBasicDescription visFormat; // Mono format for vis
|
||||||
|
|
||||||
uint32_t deviceChannelConfig;
|
uint32_t deviceChannelConfig;
|
||||||
uint32_t streamChannelConfig;
|
uint32_t streamChannelConfig;
|
||||||
|
|
||||||
|
@ -70,6 +74,9 @@
|
||||||
AudioUnit _eq;
|
AudioUnit _eq;
|
||||||
|
|
||||||
DownmixProcessor *downmixer;
|
DownmixProcessor *downmixer;
|
||||||
|
DownmixProcessor *downmixerForVis;
|
||||||
|
|
||||||
|
VisualizationController *visController;
|
||||||
|
|
||||||
#ifdef OUTPUT_LOG
|
#ifdef OUTPUT_LOG
|
||||||
FILE *_logFile;
|
FILE *_logFile;
|
||||||
|
|
|
@ -58,6 +58,9 @@ static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioAct
|
||||||
|
|
||||||
amountToRead = inNumberFrames * bytesPerPacket;
|
amountToRead = inNumberFrames * bytesPerPacket;
|
||||||
|
|
||||||
|
int visTabulated = 0;
|
||||||
|
float visAudio[512]; // Chunk size
|
||||||
|
|
||||||
if(_self->stopping == YES || [_self->outputController shouldContinue] == NO) {
|
if(_self->stopping == YES || [_self->outputController shouldContinue] == NO) {
|
||||||
// Chain is dead, fill out the serial number pointer forever with silence
|
// Chain is dead, fill out the serial number pointer forever with silence
|
||||||
clearBuffers(ioData, amountToRead / bytesPerPacket, 0);
|
clearBuffers(ioData, amountToRead / bytesPerPacket, 0);
|
||||||
|
@ -85,6 +88,7 @@ static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioAct
|
||||||
_self->streamChannelConfig = config;
|
_self->streamChannelConfig = config;
|
||||||
_self->streamFormatStarted = YES;
|
_self->streamFormatStarted = YES;
|
||||||
_self->downmixer = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->deviceFormat outputConfig:_self->deviceChannelConfig];
|
_self->downmixer = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->deviceFormat outputConfig:_self->deviceChannelConfig];
|
||||||
|
_self->downmixerForVis = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->visFormat outputConfig:AudioConfigMono];
|
||||||
}
|
}
|
||||||
|
|
||||||
double chunkDuration = [chunk duration];
|
double chunkDuration = [chunk duration];
|
||||||
|
@ -94,6 +98,9 @@ static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioAct
|
||||||
float downmixedData[frameCount * channels];
|
float downmixedData[frameCount * channels];
|
||||||
[_self->downmixer process:[samples bytes] frameCount:frameCount output:downmixedData];
|
[_self->downmixer process:[samples bytes] frameCount:frameCount output:downmixedData];
|
||||||
|
|
||||||
|
[_self->downmixerForVis process:[samples bytes] frameCount:frameCount output:visAudio];
|
||||||
|
visTabulated += frameCount;
|
||||||
|
|
||||||
fillBuffers(ioData, downmixedData, frameCount, 0);
|
fillBuffers(ioData, downmixedData, frameCount, 0);
|
||||||
amountRead = frameCount * bytesPerPacket;
|
amountRead = frameCount * bytesPerPacket;
|
||||||
[_self->outputController incrementAmountPlayed:chunkDuration];
|
[_self->outputController incrementAmountPlayed:chunkDuration];
|
||||||
|
@ -113,6 +120,7 @@ static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioAct
|
||||||
_self->streamFormat = format;
|
_self->streamFormat = format;
|
||||||
_self->streamFormatStarted = YES;
|
_self->streamFormatStarted = YES;
|
||||||
_self->downmixer = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->deviceFormat outputConfig:_self->deviceChannelConfig];
|
_self->downmixer = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->deviceFormat outputConfig:_self->deviceChannelConfig];
|
||||||
|
_self->downmixerForVis = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:_self->visFormat outputConfig:AudioConfigMono];
|
||||||
}
|
}
|
||||||
atomic_fetch_add(&_self->bytesRendered, frameCount * bytesPerPacket);
|
atomic_fetch_add(&_self->bytesRendered, frameCount * bytesPerPacket);
|
||||||
double chunkDuration = [chunk duration];
|
double chunkDuration = [chunk duration];
|
||||||
|
@ -121,6 +129,9 @@ static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioAct
|
||||||
[_self->downmixer process:[samples bytes] frameCount:frameCount output:downmixedData];
|
[_self->downmixer process:[samples bytes] frameCount:frameCount output:downmixedData];
|
||||||
fillBuffers(ioData, downmixedData, frameCount, amountRead / bytesPerPacket);
|
fillBuffers(ioData, downmixedData, frameCount, amountRead / bytesPerPacket);
|
||||||
|
|
||||||
|
[_self->downmixerForVis process:[samples bytes] frameCount:frameCount output:visAudio + visTabulated];
|
||||||
|
visTabulated += frameCount;
|
||||||
|
|
||||||
[_self->outputController incrementAmountPlayed:chunkDuration];
|
[_self->outputController incrementAmountPlayed:chunkDuration];
|
||||||
|
|
||||||
amountRead += frameCount * bytesPerPacket;
|
amountRead += frameCount * bytesPerPacket;
|
||||||
|
@ -148,8 +159,12 @@ static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioAct
|
||||||
// buffer loop if it doesn't get anything, so always produce a full
|
// buffer loop if it doesn't get anything, so always produce a full
|
||||||
// buffer, and silence anything we couldn't supply.
|
// buffer, and silence anything we couldn't supply.
|
||||||
clearBuffers(ioData, (amountToRead - amountRead) / bytesPerPacket, amountRead / bytesPerPacket);
|
clearBuffers(ioData, (amountToRead - amountRead) / bytesPerPacket, amountRead / bytesPerPacket);
|
||||||
|
|
||||||
|
vDSP_vclr(visAudio + visTabulated, 1, 512 - visTabulated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[_self->visController postVisPCM:visAudio];
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -469,7 +484,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
- (void)resetIfOutputChanged {
|
- (void)resetIfOutputChanged {
|
||||||
AVAudioFormat *format = _au.outputBusses[0].format;
|
AVAudioFormat *format = _au.outputBusses[0].format;
|
||||||
|
|
||||||
if(!restarted && !_deviceFormat || ![_deviceFormat isEqual:format]) {
|
if(!restarted && (!_deviceFormat || ![_deviceFormat isEqual:format])) {
|
||||||
[outputController restartPlaybackAtCurrentPosition];
|
[outputController restartPlaybackAtCurrentPosition];
|
||||||
restarted = YES;
|
restarted = YES;
|
||||||
}
|
}
|
||||||
|
@ -501,6 +516,11 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
deviceFormat.mBytesPerFrame = deviceFormat.mChannelsPerFrame * (deviceFormat.mBitsPerChannel / 8);
|
deviceFormat.mBytesPerFrame = deviceFormat.mChannelsPerFrame * (deviceFormat.mBitsPerChannel / 8);
|
||||||
deviceFormat.mBytesPerPacket = deviceFormat.mBytesPerFrame * deviceFormat.mFramesPerPacket;
|
deviceFormat.mBytesPerPacket = deviceFormat.mBytesPerFrame * deviceFormat.mFramesPerPacket;
|
||||||
|
|
||||||
|
visFormat = deviceFormat;
|
||||||
|
visFormat.mChannelsPerFrame = 1;
|
||||||
|
visFormat.mBytesPerFrame = visFormat.mChannelsPerFrame * (visFormat.mBitsPerChannel / 8);
|
||||||
|
visFormat.mBytesPerPacket = visFormat.mBytesPerFrame * visFormat.mFramesPerPacket;
|
||||||
|
|
||||||
/* Set the channel layout for the audio queue */
|
/* Set the channel layout for the audio queue */
|
||||||
AudioChannelLayoutTag tag = 0;
|
AudioChannelLayoutTag tag = 0;
|
||||||
switch(deviceFormat.mChannelsPerFrame) {
|
switch(deviceFormat.mChannelsPerFrame) {
|
||||||
|
@ -579,6 +599,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
restarted = NO;
|
restarted = NO;
|
||||||
|
|
||||||
downmixer = nil;
|
downmixer = nil;
|
||||||
|
downmixerForVis = nil;
|
||||||
|
|
||||||
AudioComponentDescription desc;
|
AudioComponentDescription desc;
|
||||||
NSError *err;
|
NSError *err;
|
||||||
|
@ -720,6 +741,8 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
|
|
||||||
[_au allocateRenderResourcesAndReturnError:&err];
|
[_au allocateRenderResourcesAndReturnError:&err];
|
||||||
|
|
||||||
|
visController = [VisualizationController sharedController];
|
||||||
|
|
||||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputDevice" options:0 context:NULL];
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputDevice" options:0 context:NULL];
|
||||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.GraphicEQenable" options:0 context:NULL];
|
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.GraphicEQenable" options:0 context:NULL];
|
||||||
observersapplied = YES;
|
observersapplied = YES;
|
||||||
|
@ -797,6 +820,9 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
if(downmixer) {
|
if(downmixer) {
|
||||||
downmixer = nil;
|
downmixer = nil;
|
||||||
}
|
}
|
||||||
|
if(downmixerForVis) {
|
||||||
|
downmixerForVis = nil;
|
||||||
|
}
|
||||||
#ifdef OUTPUT_LOG
|
#ifdef OUTPUT_LOG
|
||||||
if(_logFile) {
|
if(_logFile) {
|
||||||
fclose(_logFile);
|
fclose(_logFile);
|
||||||
|
@ -804,6 +830,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
outputController = nil;
|
outputController = nil;
|
||||||
|
visController = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
|
|
40
Audio/ThirdParty/deadbeef/fft.h
vendored
Normal file
40
Audio/ThirdParty/deadbeef/fft.h
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
DeaDBeeF -- the music player
|
||||||
|
Copyright (C) 2009-2021 Alexey Yakovenko and other contributors
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FFT_H
|
||||||
|
#define FFT_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void fft_calculate(const float *data, float *freq, int fft_size);
|
||||||
|
|
||||||
|
void fft_free(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
95
Audio/ThirdParty/deadbeef/fft_accelerate.c
vendored
Normal file
95
Audio/ThirdParty/deadbeef/fft_accelerate.c
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
DeaDBeeF -- the music player
|
||||||
|
Copyright (C) 2009-2021 Alexey Yakovenko and other contributors
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fft.h"
|
||||||
|
#include <Accelerate/Accelerate.h>
|
||||||
|
|
||||||
|
static int _fft_size;
|
||||||
|
static float *_input_real;
|
||||||
|
static float *_input_imaginary;
|
||||||
|
static float *_output_real;
|
||||||
|
static float *_output_imaginary;
|
||||||
|
static float *_hamming;
|
||||||
|
static float *_sq_mags;
|
||||||
|
|
||||||
|
static vDSP_DFT_Setup _dft_setup;
|
||||||
|
|
||||||
|
static void
|
||||||
|
_init_buffers(int fft_size) {
|
||||||
|
if(fft_size != _fft_size) {
|
||||||
|
fft_free();
|
||||||
|
|
||||||
|
_input_real = calloc(fft_size * 2, sizeof(float));
|
||||||
|
_input_imaginary = calloc(fft_size * 2, sizeof(float));
|
||||||
|
_hamming = calloc(fft_size * 2, sizeof(float));
|
||||||
|
_sq_mags = calloc(fft_size, sizeof(float));
|
||||||
|
_output_real = calloc(fft_size * 2, sizeof(float));
|
||||||
|
_output_imaginary = calloc(fft_size * 2, sizeof(float));
|
||||||
|
|
||||||
|
_dft_setup = vDSP_DFT_zop_CreateSetup(NULL, fft_size * 2, FFT_FORWARD);
|
||||||
|
vDSP_hamm_window(_hamming, fft_size * 2, 0);
|
||||||
|
|
||||||
|
_fft_size = fft_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fft_calculate(const float *data, float *freq, int fft_size) {
|
||||||
|
int dft_size = fft_size * 2;
|
||||||
|
|
||||||
|
_init_buffers(fft_size);
|
||||||
|
|
||||||
|
vDSP_vmul(data, 1, _hamming, 1, _input_real, 1, dft_size);
|
||||||
|
|
||||||
|
vDSP_DFT_Execute(_dft_setup, _input_real, _input_imaginary, _output_real, _output_imaginary);
|
||||||
|
|
||||||
|
DSPSplitComplex split_complex = {
|
||||||
|
.realp = _output_real,
|
||||||
|
.imagp = _output_imaginary
|
||||||
|
};
|
||||||
|
vDSP_zvmags(&split_complex, 1, _sq_mags, 1, fft_size);
|
||||||
|
|
||||||
|
int sq_count = fft_size;
|
||||||
|
vvsqrtf(_sq_mags, _sq_mags, &sq_count);
|
||||||
|
|
||||||
|
float mult = 2.f / fft_size;
|
||||||
|
vDSP_vsmul(_sq_mags, 1, &mult, freq, 1, fft_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fft_free(void) {
|
||||||
|
free(_input_real);
|
||||||
|
free(_input_imaginary);
|
||||||
|
free(_hamming);
|
||||||
|
free(_sq_mags);
|
||||||
|
free(_output_real);
|
||||||
|
free(_output_imaginary);
|
||||||
|
if(_dft_setup != NULL) {
|
||||||
|
vDSP_DFT_DestroySetup(_dft_setup);
|
||||||
|
}
|
||||||
|
_input_real = NULL;
|
||||||
|
_input_imaginary = NULL;
|
||||||
|
_hamming = NULL;
|
||||||
|
_sq_mags = NULL;
|
||||||
|
_dft_setup = NULL;
|
||||||
|
_output_real = NULL;
|
||||||
|
_output_imaginary = NULL;
|
||||||
|
}
|
23
Audio/Visualization/VisualizationController.h
Normal file
23
Audio/Visualization/VisualizationController.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// VisualizationController.h
|
||||||
|
// CogAudio Framework
|
||||||
|
//
|
||||||
|
// Created by Christopher Snowhill on 2/12/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface VisualizationController : NSObject {
|
||||||
|
float visAudio[512];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (VisualizationController *)sharedController;
|
||||||
|
|
||||||
|
- (void)postVisPCM:(const float *)inPCM;
|
||||||
|
- (void)copyVisPCM:(float *)outPCM visFFT:(float *)outFFT;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
51
Audio/Visualization/VisualizationController.m
Normal file
51
Audio/Visualization/VisualizationController.m
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// VisualizationController.m
|
||||||
|
// CogAudio Framework
|
||||||
|
//
|
||||||
|
// Created by Christopher Snowhill on 2/12/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "VisualizationController.h"
|
||||||
|
#import <Accelerate/Accelerate.h>
|
||||||
|
|
||||||
|
#import "fft.h"
|
||||||
|
|
||||||
|
@implementation VisualizationController
|
||||||
|
|
||||||
|
static VisualizationController *_sharedController = nil;
|
||||||
|
|
||||||
|
+ (VisualizationController *)sharedController {
|
||||||
|
@synchronized(self) {
|
||||||
|
if(!_sharedController) {
|
||||||
|
_sharedController = [[VisualizationController alloc] init];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _sharedController;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)init {
|
||||||
|
self = [super init];
|
||||||
|
if(self) {
|
||||||
|
vDSP_vclr(visAudio, 1, 512);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
fft_free();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)postVisPCM:(const float *)inPCM {
|
||||||
|
@synchronized(self) {
|
||||||
|
cblas_scopy(512, inPCM, 1, visAudio, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)copyVisPCM:(float *)outPCM visFFT:(float *)outFFT {
|
||||||
|
@synchronized(self) {
|
||||||
|
cblas_scopy(512, visAudio, 1, outPCM, 1);
|
||||||
|
fft_calculate(visAudio, outFFT, 256);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -32,7 +32,7 @@
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="KWC-Ti-8KY">
|
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="KWC-Ti-8KY">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="378"/>
|
<rect key="frame" x="0.0" y="0.0" width="1000" height="378"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveName="Playlist" rowHeight="18" headerView="1517" viewBased="YES" id="207" customClass="PlaylistView">
|
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveName="Playlist" rowHeight="18" headerView="1517" viewBased="YES" id="207" customClass="PlaylistView">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="361"/>
|
<rect key="frame" x="0.0" y="0.0" width="1000" height="361"/>
|
||||||
|
@ -130,11 +130,11 @@
|
||||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||||
<prototypeCellViews>
|
<prototypeCellViews>
|
||||||
<tableCellView id="ZCP-Dx-UBV">
|
<tableCellView id="ZCP-Dx-UBV">
|
||||||
<rect key="frame" x="106" y="3" width="125.5" height="18"/>
|
<rect key="frame" x="106" y="3" width="126" height="18"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="exY-Bg-Mjm">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="exY-Bg-Mjm">
|
||||||
<rect key="frame" x="0.0" y="1" width="125.5" height="16"/>
|
<rect key="frame" x="0.0" y="1" width="126" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="sdo-Sm-KPH">
|
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="sdo-Sm-KPH">
|
||||||
<font key="font" usesAppearanceFont="YES"/>
|
<font key="font" usesAppearanceFont="YES"/>
|
||||||
|
@ -876,6 +876,14 @@
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
</toolbarItem>
|
</toolbarItem>
|
||||||
|
<toolbarItem implicitItemIdentifier="5E0A643B-09FC-45CF-A562-C56F1E388E62" label="Spectrum" paletteLabel="Spectrum" sizingBehavior="auto" id="gui-fC-qTP">
|
||||||
|
<nil key="toolTip"/>
|
||||||
|
<imageView key="view" horizontalHuggingPriority="251" verticalHuggingPriority="251" id="Zaq-sS-aTV" customClass="SpectrumView">
|
||||||
|
<rect key="frame" x="0.0" y="14" width="64" height="26"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="VDu-2p-2bJ"/>
|
||||||
|
</imageView>
|
||||||
|
</toolbarItem>
|
||||||
</allowedToolbarItems>
|
</allowedToolbarItems>
|
||||||
<defaultToolbarItems>
|
<defaultToolbarItems>
|
||||||
<toolbarItem reference="ZH9-ZU-skw"/>
|
<toolbarItem reference="ZH9-ZU-skw"/>
|
||||||
|
@ -888,6 +896,7 @@
|
||||||
<toolbarItem reference="1630"/>
|
<toolbarItem reference="1630"/>
|
||||||
<toolbarItem reference="1629"/>
|
<toolbarItem reference="1629"/>
|
||||||
<toolbarItem reference="1529"/>
|
<toolbarItem reference="1529"/>
|
||||||
|
<toolbarItem reference="gui-fC-qTP"/>
|
||||||
<toolbarItem reference="1568"/>
|
<toolbarItem reference="1568"/>
|
||||||
<toolbarItem reference="1551"/>
|
<toolbarItem reference="1551"/>
|
||||||
<toolbarItem reference="1610"/>
|
<toolbarItem reference="1610"/>
|
||||||
|
@ -1122,6 +1131,14 @@
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
</toolbarItem>
|
</toolbarItem>
|
||||||
|
<toolbarItem implicitItemIdentifier="1A931C99-28FC-4209-BD5C-38E5CF96F7AF" label="Spectrum" paletteLabel="Spectrum" sizingBehavior="auto" id="K64-ro-2GI">
|
||||||
|
<nil key="toolTip"/>
|
||||||
|
<imageView key="view" horizontalHuggingPriority="251" verticalHuggingPriority="251" id="F4M-9A-fZv" customClass="SpectrumView">
|
||||||
|
<rect key="frame" x="0.0" y="14" width="64" height="26"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="br1-X9-N6H"/>
|
||||||
|
</imageView>
|
||||||
|
</toolbarItem>
|
||||||
</allowedToolbarItems>
|
</allowedToolbarItems>
|
||||||
<defaultToolbarItems>
|
<defaultToolbarItems>
|
||||||
<toolbarItem reference="qfu-F9-bOZ"/>
|
<toolbarItem reference="qfu-F9-bOZ"/>
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
836FB5A718206F2500B3AD2D /* Hively.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 836FB5471820538800B3AD2D /* Hively.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
836FB5A718206F2500B3AD2D /* Hively.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 836FB5471820538800B3AD2D /* Hively.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
8370D73D277419F700245CE0 /* SQLiteStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8370D73C277419F700245CE0 /* SQLiteStore.m */; };
|
8370D73D277419F700245CE0 /* SQLiteStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8370D73C277419F700245CE0 /* SQLiteStore.m */; };
|
||||||
8370D73F2775AE1300245CE0 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8370D73E2775AE1300245CE0 /* libsqlite3.tbd */; };
|
8370D73F2775AE1300245CE0 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8370D73E2775AE1300245CE0 /* libsqlite3.tbd */; };
|
||||||
|
8377C66327B8CF6300E8BC0F /* SpectrumView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C66127B8CF6300E8BC0F /* SpectrumView.m */; };
|
||||||
8384914018083E4E00E7332D /* filetype.icns in Resources */ = {isa = PBXBuildFile; fileRef = 8384913D18083E4E00E7332D /* filetype.icns */; };
|
8384914018083E4E00E7332D /* filetype.icns in Resources */ = {isa = PBXBuildFile; fileRef = 8384913D18083E4E00E7332D /* filetype.icns */; };
|
||||||
8384915918083EAB00E7332D /* infoTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 8384914318083EAB00E7332D /* infoTemplate.pdf */; };
|
8384915918083EAB00E7332D /* infoTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 8384914318083EAB00E7332D /* infoTemplate.pdf */; };
|
||||||
8384915A18083EAB00E7332D /* missingArt@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384914418083EAB00E7332D /* missingArt@2x.png */; };
|
8384915A18083EAB00E7332D /* missingArt@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384914418083EAB00E7332D /* missingArt@2x.png */; };
|
||||||
|
@ -942,6 +943,9 @@
|
||||||
8370D73C277419F700245CE0 /* SQLiteStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SQLiteStore.m; sourceTree = "<group>"; };
|
8370D73C277419F700245CE0 /* SQLiteStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SQLiteStore.m; sourceTree = "<group>"; };
|
||||||
8370D73E2775AE1300245CE0 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
|
8370D73E2775AE1300245CE0 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
|
||||||
8375B05117FFEA400092A79F /* OpusPlugin.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OpusPlugin.xcodeproj; path = Plugins/Opus/OpusPlugin.xcodeproj; sourceTree = "<group>"; };
|
8375B05117FFEA400092A79F /* OpusPlugin.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OpusPlugin.xcodeproj; path = Plugins/Opus/OpusPlugin.xcodeproj; sourceTree = "<group>"; };
|
||||||
|
8377C66127B8CF6300E8BC0F /* SpectrumView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectrumView.m; path = Visualization/SpectrumView.m; sourceTree = "<group>"; };
|
||||||
|
8377C66227B8CF6300E8BC0F /* SpectrumView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectrumView.h; path = Visualization/SpectrumView.h; sourceTree = "<group>"; };
|
||||||
|
8377C66427B8CF7A00E8BC0F /* VisualizationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VisualizationController.h; path = Audio/Visualization/VisualizationController.h; sourceTree = "<group>"; };
|
||||||
8384912518080F2D00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = "<group>"; };
|
8384912518080F2D00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = "<group>"; };
|
||||||
8384913D18083E4E00E7332D /* filetype.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = filetype.icns; sourceTree = "<group>"; };
|
8384913D18083E4E00E7332D /* filetype.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = filetype.icns; sourceTree = "<group>"; };
|
||||||
8384914318083EAB00E7332D /* infoTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = infoTemplate.pdf; path = Images/infoTemplate.pdf; sourceTree = "<group>"; };
|
8384914318083EAB00E7332D /* infoTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = infoTemplate.pdf; path = Images/infoTemplate.pdf; sourceTree = "<group>"; };
|
||||||
|
@ -1073,6 +1077,7 @@
|
||||||
835F00B3279BD1CD00055FCF /* Formatters */,
|
835F00B3279BD1CD00055FCF /* Formatters */,
|
||||||
177042960B8BC53600B86321 /* Application */,
|
177042960B8BC53600B86321 /* Application */,
|
||||||
17E0D5D20F520E75005B6FED /* Window */,
|
17E0D5D20F520E75005B6FED /* Window */,
|
||||||
|
8377C66027B8CF2300E8BC0F /* Visualization */,
|
||||||
8E75752A09F31D5A0080F1EE /* Playlist */,
|
8E75752A09F31D5A0080F1EE /* Playlist */,
|
||||||
8E07AAEA0AAC90DC00A4B32F /* Preferences */,
|
8E07AAEA0AAC90DC00A4B32F /* Preferences */,
|
||||||
17DDF6400E0CB6F100A2E4AD /* FileTree */,
|
17DDF6400E0CB6F100A2E4AD /* FileTree */,
|
||||||
|
@ -1686,6 +1691,16 @@
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
8377C66027B8CF2300E8BC0F /* Visualization */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
8377C66427B8CF7A00E8BC0F /* VisualizationController.h */,
|
||||||
|
8377C66227B8CF6300E8BC0F /* SpectrumView.h */,
|
||||||
|
8377C66127B8CF6300E8BC0F /* SpectrumView.m */,
|
||||||
|
);
|
||||||
|
name = Visualization;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
83B0669D180D5668008E3612 /* Products */ = {
|
83B0669D180D5668008E3612 /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2451,6 +2466,7 @@
|
||||||
1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */,
|
1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */,
|
||||||
1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */,
|
1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */,
|
||||||
8355D6B6180612F300D05687 /* NSData+MD5.m in Sources */,
|
8355D6B6180612F300D05687 /* NSData+MD5.m in Sources */,
|
||||||
|
8377C66327B8CF6300E8BC0F /* SpectrumView.m in Sources */,
|
||||||
1766C6950B911DF1004A7AE4 /* AudioScrobblerClient.m in Sources */,
|
1766C6950B911DF1004A7AE4 /* AudioScrobblerClient.m in Sources */,
|
||||||
1755E1F90BA0D2B600CA3560 /* PlaylistLoader.m in Sources */,
|
1755E1F90BA0D2B600CA3560 /* PlaylistLoader.m in Sources */,
|
||||||
8E9A30160BA792DC0091081B /* NSFileHandle+CreateFile.m in Sources */,
|
8E9A30160BA792DC0091081B /* NSFileHandle+CreateFile.m in Sources */,
|
||||||
|
|
22
Visualization/SpectrumView.h
Normal file
22
Visualization/SpectrumView.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// SpectrumView.h
|
||||||
|
// Cog
|
||||||
|
//
|
||||||
|
// Created by Christopher Snowhill on 2/12/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#import "VisualizationController.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface SpectrumView : NSImageView {
|
||||||
|
VisualizationController *visController;
|
||||||
|
NSTimer *timer;
|
||||||
|
NSImage *theImage;
|
||||||
|
BOOL stopped;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
137
Visualization/SpectrumView.m
Normal file
137
Visualization/SpectrumView.m
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
//
|
||||||
|
// SpectrumView.m
|
||||||
|
// Cog
|
||||||
|
//
|
||||||
|
// Created by Christopher Snowhill on 2/12/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SpectrumView.h"
|
||||||
|
|
||||||
|
extern NSString *CogPlaybackDidBeginNotficiation;
|
||||||
|
extern NSString *CogPlaybackDidPauseNotficiation;
|
||||||
|
extern NSString *CogPlaybackDidResumeNotficiation;
|
||||||
|
extern NSString *CogPlaybackDidStopNotficiation;
|
||||||
|
|
||||||
|
@implementation SpectrumView
|
||||||
|
|
||||||
|
- (void)awakeFromNib {
|
||||||
|
visController = [NSClassFromString(@"VisualizationController") sharedController];
|
||||||
|
timer = nil;
|
||||||
|
theImage = [NSImage imageWithSize:NSMakeSize(64, 26)
|
||||||
|
flipped:NO
|
||||||
|
drawingHandler:^BOOL(NSRect dstRect) {
|
||||||
|
NSColor *backColor = [NSColor textBackgroundColor];
|
||||||
|
[backColor drawSwatchInRect:dstRect];
|
||||||
|
return YES;
|
||||||
|
}];
|
||||||
|
|
||||||
|
stopped = YES;
|
||||||
|
|
||||||
|
[self setImage:theImage];
|
||||||
|
[self setImageScaling:NSImageScaleAxesIndependently];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(colorsDidChange:)
|
||||||
|
name:NSSystemColorsDidChangeNotification
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(playbackDidBegin:)
|
||||||
|
name:CogPlaybackDidBeginNotficiation
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(playbackDidPause:)
|
||||||
|
name:CogPlaybackDidPauseNotficiation
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(playbackDidResume:)
|
||||||
|
name:CogPlaybackDidResumeNotficiation
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(playbackDidStop:)
|
||||||
|
name:CogPlaybackDidStopNotficiation
|
||||||
|
object:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)repaint {
|
||||||
|
{
|
||||||
|
[theImage lockFocus];
|
||||||
|
|
||||||
|
NSColor *backColor = [NSColor textBackgroundColor];
|
||||||
|
[backColor drawSwatchInRect:NSMakeRect(0, 0, 64, 26)];
|
||||||
|
|
||||||
|
NSBezierPath *bezierPath = [[NSBezierPath alloc] init];
|
||||||
|
|
||||||
|
float visAudio[512], visFFT[256];
|
||||||
|
|
||||||
|
if(!self->stopped) {
|
||||||
|
[self->visController copyVisPCM:&visAudio[0] visFFT:&visFFT[0]];
|
||||||
|
} else {
|
||||||
|
memset(visFFT, 0, sizeof(visFFT));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < 60; ++i) {
|
||||||
|
CGFloat y = MAX(MIN(visFFT[i], 0.25), 0.0) * 4.0 * 22.0 + 2.0;
|
||||||
|
[bezierPath moveToPoint:NSMakePoint(2 + i, 2)];
|
||||||
|
[bezierPath lineToPoint:NSMakePoint(2 + i, y)];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSColor *lineColor = [NSColor textColor];
|
||||||
|
[lineColor setStroke];
|
||||||
|
|
||||||
|
[bezierPath stroke];
|
||||||
|
|
||||||
|
[theImage unlockFocus];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self setNeedsDisplay];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)startTimer {
|
||||||
|
[self stopTimer];
|
||||||
|
timer = [NSTimer timerWithTimeInterval:0.02
|
||||||
|
target:self
|
||||||
|
selector:@selector(timerRun:)
|
||||||
|
userInfo:nil
|
||||||
|
repeats:YES];
|
||||||
|
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)stopTimer {
|
||||||
|
[timer invalidate];
|
||||||
|
timer = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)timerRun:(NSTimer *)timer {
|
||||||
|
[self repaint];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)colorsDidChange:(NSNotification *)notification {
|
||||||
|
[self repaint];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)playbackDidBegin:(NSNotification *)notification {
|
||||||
|
stopped = NO;
|
||||||
|
[self startTimer];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)playbackDidPause:(NSNotification *)notification {
|
||||||
|
stopped = NO;
|
||||||
|
[self stopTimer];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)playbackDidResume:(NSNotification *)notification {
|
||||||
|
stopped = NO;
|
||||||
|
[self startTimer];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)playbackDidStop:(NSNotification *)notification {
|
||||||
|
[self stopTimer];
|
||||||
|
stopped = YES;
|
||||||
|
[self repaint];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)drawRect:(NSRect)dirtyRect {
|
||||||
|
[super drawRect:dirtyRect];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
Loading…
Reference in a new issue