├── linux ├── caselights_icon.png ├── de.xythobuz.caselights.desktop ├── README.md ├── PKGBUILD └── caselights ├── CaseLights ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── 1024.png │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 512.png │ │ ├── 64.png │ │ └── Contents.json │ └── MenuIcon.imageset │ │ ├── icon_big.png │ │ ├── icon_small.png │ │ ├── icon_small_2x.png │ │ └── Contents.json ├── main.m ├── GPUStats.h ├── Serial.h ├── AudioVisualizer.h ├── Screenshot.h ├── AppDelegate.h ├── Info.plist ├── GPUStats.m ├── Screenshot.m ├── Serial.m ├── AudioVisualizer.m ├── AppDelegate.m └── Base.lproj │ └── MainMenu.xib ├── .gitmodules ├── CaseLights.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── .gitignore ├── COPYING ├── README.md └── CaseLights.ino /linux/caselights_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/linux/caselights_icon.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/MenuIcon.imageset/icon_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/MenuIcon.imageset/icon_big.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/MenuIcon.imageset/icon_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/MenuIcon.imageset/icon_small.png -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/MenuIcon.imageset/icon_small_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xythobuz/CaseLights/HEAD/CaseLights/Assets.xcassets/MenuIcon.imageset/icon_small_2x.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "JSystemInfoKit"] 2 | path = JSystemInfoKit 3 | url = https://github.com/xythobuz/JSystemInfoKit.git 4 | [submodule "EZAudio"] 5 | path = EZAudio 6 | url = https://github.com/syedhali/EZAudio.git 7 | -------------------------------------------------------------------------------- /CaseLights.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /linux/de.xythobuz.caselights.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Version=0.2 4 | Name=CaseLights 5 | Comment=RGB LED and UV strip controls 6 | Path=/usr/bin 7 | Exec=caselights 8 | Icon=/usr/share/pixmaps/caselights_icon.png 9 | Terminal=false 10 | Categories=Utility; 11 | -------------------------------------------------------------------------------- /CaseLights/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CaseLights 4 | // 5 | // Created by Thomas Buck on 21.12.15. 6 | // Copyright © 2015 xythobuz. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /CaseLights/GPUStats.h: -------------------------------------------------------------------------------- 1 | // 2 | // GPUStats.h 3 | // CaseLights 4 | // 5 | // Created by Thomas Buck on 23.12.15. 6 | // Copyright © 2015 xythobuz. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GPUStats : NSObject 12 | 13 | + (NSInteger)getGPUUsage:(NSNumber **)usage freeVRAM:(NSNumber **)free usedVRAM:(NSNumber **)used; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | build/ 4 | DerivedData 5 | 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | 16 | *.xccheckout 17 | *.moved-aside 18 | *.xcuserstate 19 | *.xcscmblueprint 20 | 21 | *.hmap 22 | *.ipa 23 | 24 | .VolumeIcon.icns 25 | .fseventsd 26 | Icon 27 | 28 | -------------------------------------------------------------------------------- /CaseLights/Serial.h: -------------------------------------------------------------------------------- 1 | // 2 | // Serial.h 3 | // CaseLights 4 | // 5 | // Created by Thomas Buck on 14.12.15. 6 | // Copyright © 2015 xythobuz. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Serial : NSObject 12 | 13 | @property (strong) NSString *portName; 14 | 15 | - (NSInteger)openPort; 16 | - (void)closePort; 17 | - (BOOL)isOpen; 18 | - (BOOL)hasData; 19 | - (void)sendString:(NSString *)string; 20 | 21 | + (NSArray *)listSerialPorts; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/MenuIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_small.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon_small_2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon_big.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CaseLights/AudioVisualizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // AudioVisualizer.h 3 | // CaseLights 4 | // 5 | // Created by Thomas Buck on 01.01.16. 6 | // Copyright © 2016 xythobuz. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AppDelegate; 12 | 13 | @interface AudioVisualizer : NSObject 14 | 15 | + (void)setDelegate:(AppDelegate *)delegate; 16 | + (void)setSensitivity:(float)sens; 17 | + (void)setShowWindow:(BOOL)showWindow; 18 | + (void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /linux/README.md: -------------------------------------------------------------------------------- 1 | # CaseLights Linux Qt client 2 | 3 | Simple Python Qt Linux client for CaseLights. Install on Arch Linux like this: 4 | 5 | makepkg 6 | sudo pacman -U CaseLights-0.2-1-any.pkg.tar.xz 7 | 8 | Or on all other linux distros: 9 | 10 | sudo cp caselights /usr/bin/caselights 11 | sudo cp caselights_icon.png /usr/share/pixmaps/caselights_icon.png 12 | sudo cp de.xythobuz.caselights.desktop /usr/share/applications/de.xythobuz.caselights.desktop 13 | 14 | Then run it from your desktop environment menu or even add it to the autostart there. 15 | 16 | -------------------------------------------------------------------------------- /CaseLights/Screenshot.h: -------------------------------------------------------------------------------- 1 | // 2 | // Screenshot.h 3 | // CaseLights 4 | // 5 | // Created by Thomas Buck on 27.12.15. 6 | // Copyright © 2015 xythobuz. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Screenshot : NSThread 12 | 13 | // Register and de-register the callback for configuration changes 14 | + (void)init:(AppDelegate *)appDelegate; 15 | + (void)close:(AppDelegate *)appDelegate; 16 | 17 | // List available displays. Returns an array of numbers. 18 | + (NSArray *)listDisplays; 19 | 20 | + (NSString *)displayNameFromDisplayID:(NSNumber *)displayID; 21 | + (NSBitmapImageRep *)screenshot:(NSNumber *)displayID; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /linux/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Thomas Buck 2 | pkgname=CaseLights 3 | pkgver=0.2 4 | pkgrel=3 5 | pkgdesc="RGB LED and UV strip controls" 6 | arch=('any') 7 | license=('unknown') 8 | depends=('python-pyqt5' 9 | 'python-pyserial') 10 | source=("caselights" 11 | "caselights_icon.png" 12 | "de.xythobuz.caselights.desktop") 13 | md5sums=(SKIP 14 | SKIP 15 | SKIP) 16 | 17 | package() { 18 | mkdir -p "$pkgdir/usr/bin" 19 | cp caselights "$pkgdir/usr/bin/caselights" 20 | mkdir -p "$pkgdir/usr/share/pixmaps" 21 | cp caselights_icon.png "$pkgdir/usr/share/pixmaps/caselights_icon.png" 22 | mkdir -p "$pkgdir/usr/share/applications" 23 | cp de.xythobuz.caselights.desktop "$pkgdir/usr/share/applications/de.xythobuz.caselights.desktop" 24 | } 25 | -------------------------------------------------------------------------------- /CaseLights/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CaseLights 4 | // 5 | // Created by Thomas Buck on 21.12.15. 6 | // Copyright © 2015 xythobuz. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "EZAudio.h" 12 | #import "SystemInfoKit/SystemInfoKit.h" 13 | 14 | @class Serial; 15 | 16 | @interface AppDelegate : NSObject 17 | 18 | @property (weak) IBOutlet NSApplication *application; 19 | 20 | @property (strong) EZMicrophone *microphone; 21 | 22 | - (void)clearDisplayUI; 23 | - (void)updateDisplayUI:(NSArray *)displayIDs; 24 | 25 | - (void)setLightsR:(unsigned char)r G:(unsigned char)g B:(unsigned char)b; 26 | 27 | + (double)map:(double)val FromMin:(double)fmin FromMax:(double)fmax ToMin:(double)tmin ToMax:(double)tmax; 28 | + (void)convertH:(double)h S:(double)s V:(double)v toR:(unsigned char *)r G:(unsigned char *)g B:(unsigned char *)b; 29 | 30 | @end 31 | 32 | -------------------------------------------------------------------------------- /CaseLights/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 415 25 | LSApplicationCategoryType 26 | public.app-category.utilities 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | LSUIElement 30 | 31 | NSHumanReadableCopyright 32 | Copyright © 2015 xythobuz. All rights reserved. 33 | NSMainNibFile 34 | MainMenu 35 | NSPrincipalClass 36 | NSApplication 37 | 38 | 39 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Thomas Buck 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | - Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | - Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 19 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /CaseLights/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /CaseLights/GPUStats.m: -------------------------------------------------------------------------------- 1 | // 2 | // GPUStats.m 3 | // CaseLights 4 | // 5 | // For more informations refer to this StackOverflow answer: 6 | // http://stackoverflow.com/a/22440235 7 | // 8 | // Created by Thomas Buck on 23.12.15. 9 | // Copyright © 2015 xythobuz. All rights reserved. 10 | // 11 | 12 | #import 13 | 14 | #import "GPUStats.h" 15 | 16 | @implementation GPUStats 17 | 18 | + (NSInteger)getGPUUsage:(NSNumber **)usage freeVRAM:(NSNumber **)free usedVRAM:(NSNumber **)used { 19 | if ((usage == nil) || (free == nil) || (used == nil)) { 20 | NSLog(@"Invalid use of getGPUUsage!\n"); 21 | return 1; 22 | } 23 | 24 | *usage = nil; 25 | *free = nil; 26 | *used = nil; 27 | 28 | CFMutableDictionaryRef pciDevices = IOServiceMatching(kIOAcceleratorClassName); 29 | io_iterator_t iterator; 30 | if (IOServiceGetMatchingServices(kIOMasterPortDefault, pciDevices, &iterator) == kIOReturnSuccess) { 31 | io_registry_entry_t registry; 32 | while ((registry = IOIteratorNext(iterator))) { 33 | CFMutableDictionaryRef services; 34 | if (IORegistryEntryCreateCFProperties(registry, &services, kCFAllocatorDefault, kNilOptions) == kIOReturnSuccess) { 35 | CFMutableDictionaryRef properties = (CFMutableDictionaryRef)CFDictionaryGetValue(services, CFSTR("PerformanceStatistics")); 36 | if (properties) { 37 | const void *gpuUsage = CFDictionaryGetValue(properties, CFSTR("GPU Core Utilization")); 38 | const void *freeVRAM = CFDictionaryGetValue(properties, CFSTR("vramFreeBytes")); 39 | const void *usedVRAM = CFDictionaryGetValue(properties, CFSTR("vramUsedBytes")); 40 | 41 | if (gpuUsage && freeVRAM && usedVRAM) { 42 | // Found the GPU. Store this reference for the next call 43 | static ssize_t gpuUsageNum = 0; 44 | static ssize_t freeVRAMNum = 0; 45 | static ssize_t usedVRAMNum = 0; 46 | CFNumberGetValue((CFNumberRef)gpuUsage, kCFNumberSInt64Type, &gpuUsageNum); 47 | CFNumberGetValue((CFNumberRef)freeVRAM, kCFNumberSInt64Type, &freeVRAMNum); 48 | CFNumberGetValue((CFNumberRef)usedVRAM, kCFNumberSInt64Type, &usedVRAMNum); 49 | *usage = [NSNumber numberWithDouble:gpuUsageNum / 10000000.0]; 50 | *free = [NSNumber numberWithDouble:freeVRAMNum]; 51 | *used = [NSNumber numberWithDouble:usedVRAMNum]; 52 | 53 | #ifdef DEBUG 54 | NSLog(@"GPU: %.3f%% VRAM: %.3f%% (%.2fMB)\n", gpuUsageNum / 10000000.0, ((double)usedVRAMNum) / (freeVRAMNum + usedVRAMNum) * 100.0, (usedVRAMNum + freeVRAMNum) / (1024.0 * 1024.0)); 55 | #endif 56 | } 57 | } 58 | CFRelease(services); 59 | } 60 | IOObjectRelease(registry); 61 | } 62 | IOObjectRelease(iterator); 63 | } else { 64 | NSLog(@"Couldn't list PCI devices!\n"); 65 | } 66 | 67 | if ((*usage != nil) && (*free != nil) && (*used != nil)) { 68 | return 0; 69 | } else { 70 | NSLog(@"Error reading GPU data!\n"); 71 | return 1; 72 | } 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CaseLights 2 | 3 | CaseLights is an Arduino based RGB LED controller using a simple MOSFET setup. The lights are controlled with a Mac OS X Menu Bar App that can set static colors, animations, or visualize various computer status values using the included libraries [JSystemInfoKit](https://github.com/jBot-42/JSystemInfoKit) and [EZAudio](https://github.com/syedhali/EZAudio). 4 | 5 | If you don't want to compile the CaseLights App yourself, [download the latest binary from the GitHub releases page](https://github.com/xythobuz/CaseLights/releases). 6 | 7 | ## Arduino Sketch 8 | 9 | You could connect pretty much any hardware. I'm using a N-Channel MOSFET Setup with IRF530 and a 10-piece RGB LED strip as well as an UV light tube. 10 | 11 | [![fritzing Schematic](https://i.imgur.com/jWLW22F.png)](https://i.imgur.com/sXAADUs.png) 12 | 13 | My finished setup is made with a cheap Arduino Pro Mini clone from China and a [dead simple RS232-TTL converter](http://picprojects.org.uk/projects/simpleSIO/ssio.htm) connected to its serial port. You may need to change `Serial` to `Serial1` in the Sketch if you're trying to do this with an Arduino Leonardo, as I did at first. 14 | 15 | Uncomment the `#define DEBUG` at the beginning of the Sketch to enable very verbose debug messages sent on the serial port. This is not recommended for use with the CaseLights App. 16 | 17 | ## Mac OS X App 18 | 19 | The CaseLights XCode project includes the projects from the submodules in this repository. Just run `xcodebuild` on the command line or open the project in XCode and click `Run` to start the App. 20 | 21 | ![Screenshot](https://i.imgur.com/K7HuJPK.png) 22 | 23 | CaseLights is only visible in the system menu bar. You can enable or disable the fourth channel (used for UV lighting in my case), set the RGB LEDs to static colors or simple animations, and select different visualizations for the RGB LEDs like CPU, GPU and RAM usage or hardware temperatures. The minimum and maximum values for these modes are hardcoded, but can be modified easily. 24 | 25 | You can also select one of the displays connected to the Host machine. The CaseLights App will then create a Screenshot of this display 10-times per second and calculate the average color to display it on the RGB LEDs. 26 | 27 | CaseLights is also able to visualize sound coming from a system audio input. To be able to directly visualize the system sound output, install [Soundflower](https://github.com/mattingalls/Soundflower) and create a Multi-Output-Device in the Mac `Audio Midi Setup.app` consisting of `Soundflower (2ch)` and your normally used output device. Set this device as audio output. Then, in CaseLights, select `Soundflower (2ch)` as input. 28 | Click on an audio device with a CTRL-click to display a window with the fast-fourier-transformation plot. 29 | 30 | ## Working with Git Submodules 31 | 32 | To clone this repository, enter the following: 33 | 34 | git clone https://xythobuz.de/git/CaseLights.git 35 | git submodule init 36 | git submodule update 37 | 38 | When pulling changes from this repository, you may need to update the submodule: 39 | 40 | git submodule update 41 | 42 | ## Licensing 43 | 44 | The included [JSystemInfoKit](https://github.com/jBot-42/JSystemInfoKit) project is licensed under the GPLv2. See the LICENSE file in the submodule directory. 45 | 46 | The included [EZAudio](https://github.com/syedhali/EZAudio) project is licensed under the MIT license. See the LICENSE file in the submodule directory. 47 | 48 | CaseLights itself is made by Thomas Buck and released under a BSD 2-Clause License. See the accompanying COPYING file. 49 | 50 | -------------------------------------------------------------------------------- /CaseLights.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * CaseLights 3 | * 4 | * Arduino RGB LED Controller with Serial interface. 5 | * 6 | * Two commands are supported, ending with a new-line: 7 | * 8 | * RGB r g b 9 | * 10 | * UV s 11 | * 12 | * The RGB command sets the PWM output for the LEDs. 13 | * The UV command turns the UV lights on or off (s can be 0 or 1). 14 | */ 15 | 16 | //#define DEBUG 17 | 18 | enum LoopState { 19 | LOOP_IDLE, 20 | LOOP_R, 21 | LOOP_G, 22 | LOOP_B, 23 | LOOP_NUM1, 24 | LOOP_NUM2, 25 | LOOP_U, 26 | LOOP_V 27 | }; 28 | 29 | static int redPin = 10; 30 | static int greenPin = 9; 31 | static int bluePin = 11; 32 | static int uvPin = 13; 33 | static LoopState state = LOOP_IDLE; 34 | static int r = 0, g = 0, b = 0; 35 | 36 | void setup() { 37 | Serial.begin(115200); 38 | Serial.setTimeout(5000); 39 | 40 | pinMode(redPin, OUTPUT); 41 | pinMode(greenPin, OUTPUT); 42 | pinMode(bluePin, OUTPUT); 43 | pinMode(uvPin, OUTPUT); 44 | 45 | analogWrite(redPin, 0); 46 | analogWrite(greenPin, 0); 47 | analogWrite(bluePin, 0); 48 | digitalWrite(uvPin, LOW); 49 | 50 | #ifdef DEBUG 51 | Serial.println("CaseLights initialized"); 52 | #endif 53 | } 54 | 55 | void loop() { 56 | if (Serial.available() > 0) { 57 | if (state == LOOP_IDLE) { 58 | int c = Serial.read(); 59 | if ((c == 'R') || (c == 'r')) { 60 | state = LOOP_R; 61 | } else if ((c == 'U') || (c == 'u')) { 62 | state = LOOP_U; 63 | } else if ((c == '\r') || (c == '\n')) { 64 | #ifdef DEBUG 65 | Serial.println("Skipping newline..."); 66 | #endif 67 | } else { 68 | #ifdef DEBUG 69 | Serial.print("Invalid character: "); 70 | Serial.print(c); 71 | Serial.println(); 72 | #endif 73 | } 74 | } else if (state == LOOP_R) { 75 | int c = Serial.read(); 76 | if ((c == 'G') || (c == 'g')) { 77 | state = LOOP_G; 78 | } else { 79 | state = LOOP_IDLE; 80 | #ifdef DEBUG 81 | Serial.print("Invalid character after R: "); 82 | Serial.print(c); 83 | Serial.println(); 84 | #endif 85 | } 86 | } else if (state == LOOP_G) { 87 | int c = Serial.read(); 88 | if ((c == 'B') || (c == 'b')) { 89 | state = LOOP_B; 90 | } else { 91 | state = LOOP_IDLE; 92 | #ifdef DEBUG 93 | Serial.print("Invalid character after G: "); 94 | Serial.print(c); 95 | Serial.println(); 96 | #endif 97 | } 98 | } else if (state == LOOP_B) { 99 | r = Serial.parseInt(); 100 | state = LOOP_NUM1; 101 | } else if (state == LOOP_NUM1) { 102 | g = Serial.parseInt(); 103 | state = LOOP_NUM2; 104 | } else if (state == LOOP_NUM2) { 105 | b = Serial.parseInt(); 106 | analogWrite(redPin, r); 107 | analogWrite(greenPin, g); 108 | analogWrite(bluePin, b); 109 | #ifdef DEBUG 110 | Serial.print("RGB set "); 111 | Serial.print(r); 112 | Serial.print(' '); 113 | Serial.print(g); 114 | Serial.print(' '); 115 | Serial.print(b); 116 | Serial.println(); 117 | #endif 118 | state = LOOP_IDLE; 119 | } else if (state == LOOP_U) { 120 | int c = Serial.read(); 121 | if ((c == 'V') || (c == 'v')) { 122 | state = LOOP_V; 123 | } else { 124 | state = LOOP_IDLE; 125 | #ifdef DEBUG 126 | Serial.print("Invalid character after U: "); 127 | Serial.print(c); 128 | Serial.println(); 129 | #endif 130 | } 131 | } else if (state == LOOP_V) { 132 | int n = Serial.parseInt(); 133 | if (n == 0) { 134 | digitalWrite(uvPin, LOW); 135 | #ifdef DEBUG 136 | Serial.println("UV off"); 137 | #endif 138 | } else { 139 | digitalWrite(uvPin, HIGH); 140 | #ifdef DEBUG 141 | Serial.println("UV on"); 142 | #endif 143 | } 144 | state = LOOP_IDLE; 145 | } else { 146 | state = LOOP_IDLE; 147 | #ifdef DEBUG 148 | Serial.print("Invalid state: "); 149 | Serial.print(state); 150 | Serial.println(); 151 | #endif 152 | } 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /CaseLights/Screenshot.m: -------------------------------------------------------------------------------- 1 | // 2 | // Screenshot.m 3 | // CaseLights 4 | // 5 | // Based on the Apple ScreenSnapshot example: 6 | // https://developer.apple.com/library/mac/samplecode/ScreenSnapshot/Listings/ScreenSnapshot_ScreenSnapshotAppDelegate_m.html 7 | // 8 | // Created by Thomas Buck on 27.12.15. 9 | // Copyright © 2015 xythobuz. All rights reserved. 10 | // 11 | 12 | // Uncomment to store a screenshot for each display in the build directory 13 | //#define DEBUG_SCREENSHOT 14 | 15 | #import "AppDelegate.h" 16 | #import "Screenshot.h" 17 | 18 | static BOOL displayRegistrationCallBackSuccessful; 19 | 20 | static void displayRegisterReconfigurationCallback(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo) { 21 | AppDelegate *appDelegate = (__bridge AppDelegate*)userInfo; 22 | static BOOL DisplayConfigurationChanged = NO; 23 | 24 | // Before display reconfiguration, this callback fires to inform 25 | // applications of a pending configuration change. The callback runs 26 | // once for each on-line display. The flags passed in are set to 27 | // kCGDisplayBeginConfigurationFlag. This callback does not 28 | // carry other per-display information, as details of how a 29 | // reconfiguration affects a particular device rely on device-specific 30 | // behaviors which may not be exposed by a device driver. 31 | // 32 | // After display reconfiguration, at the time the callback function 33 | // is invoked, all display state reported by CoreGraphics, QuickDraw, 34 | // and the Carbon Display Manager API will be up to date. This callback 35 | // runs after the Carbon Display Manager notification callbacks. 36 | // The callback runs once for each added, removed, and currently 37 | // on-line display. Note that in the case of removed displays, calls into 38 | // the CoreGraphics API with the removed display ID will fail. 39 | 40 | // Because the callback is called for each display I use DisplayConfigurationChanged to 41 | // make sure we only disable the menu to change displays once and then refresh it only once. 42 | if (flags == kCGDisplayBeginConfigurationFlag) { 43 | if (DisplayConfigurationChanged == NO) { 44 | [appDelegate clearDisplayUI]; 45 | DisplayConfigurationChanged = YES; 46 | } 47 | } else if (DisplayConfigurationChanged == YES) { 48 | [appDelegate updateDisplayUI:[Screenshot listDisplays]]; 49 | DisplayConfigurationChanged = NO; 50 | } 51 | } 52 | 53 | @implementation Screenshot 54 | 55 | + (void)init:(AppDelegate *)appDelegate { 56 | // Applications who want to register for notifications of display changes would use 57 | // CGDisplayRegisterReconfigurationCallback 58 | // 59 | // Display changes are reported via a callback mechanism. 60 | // 61 | // Callbacks are invoked when the app is listening for events, 62 | // on the event processing thread, or from within the display 63 | // reconfiguration function when in the program that is driving the 64 | // reconfiguration. 65 | displayRegistrationCallBackSuccessful = NO; // Hasn't been tried yet. 66 | CGError err = CGDisplayRegisterReconfigurationCallback(displayRegisterReconfigurationCallback, (__bridge void * _Nullable)(appDelegate)); 67 | if (err == kCGErrorSuccess) { 68 | displayRegistrationCallBackSuccessful = YES; 69 | } 70 | } 71 | 72 | + (void)close:(AppDelegate *)appDelegate { 73 | // CGDisplayRemoveReconfigurationCallback Removes the registration of a callback function that’s invoked 74 | // whenever a local display is reconfigured. We only remove the registration if it was successful in the first place. 75 | if (displayRegistrationCallBackSuccessful == YES) { 76 | CGDisplayRemoveReconfigurationCallback(displayRegisterReconfigurationCallback, (__bridge void * _Nullable)(appDelegate)); 77 | } 78 | } 79 | 80 | + (NSArray *)listDisplays { 81 | CGDisplayCount dspCount = 0; 82 | CGError err = CGGetActiveDisplayList(0, NULL, &dspCount); 83 | if (err != CGDisplayNoErr) { 84 | NSLog(@"Couldn't list any active displays (%d)!\n", err); 85 | return nil; 86 | } 87 | 88 | CGDirectDisplayID *displays = calloc((size_t)dspCount, sizeof(CGDirectDisplayID)); 89 | err = CGGetActiveDisplayList(dspCount, displays, &dspCount); 90 | if (err != CGDisplayNoErr) { 91 | NSLog(@"Couldn't get active display list (%d)!\n", err); 92 | return nil; 93 | } 94 | 95 | if (dspCount > 0) { 96 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:dspCount]; 97 | for (int i = 0; i < dspCount; i++) { 98 | [array addObject:[NSNumber numberWithInt:displays[i]]]; 99 | } 100 | 101 | return [array copy]; 102 | } else { 103 | NSLog(@"No displays found!\n"); 104 | return nil; 105 | } 106 | } 107 | 108 | + (NSString *)displayNameFromDisplayID:(NSNumber *)displayID { 109 | NSDictionary *displayInfo = CFBridgingRelease(IODisplayCreateInfoDictionary(CGDisplayIOServicePort([displayID unsignedIntValue]), kIODisplayOnlyPreferredName)); 110 | NSDictionary *localizedNames = [displayInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; 111 | 112 | // Use the first name 113 | NSString *displayProductName = nil; 114 | if ([localizedNames count] > 0) { 115 | displayProductName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]]; 116 | 117 | #ifdef DEBUG 118 | NSLog(@"Display %u named \"%@\"!\n", [displayID unsignedIntValue], displayProductName); 119 | #endif 120 | } 121 | 122 | return displayProductName; 123 | } 124 | 125 | + (NSBitmapImageRep *)screenshot:(NSNumber *)displayID { 126 | CGImageRef image = CGDisplayCreateImage([displayID unsignedIntValue]); 127 | NSBitmapImageRep *imgRep = [[NSBitmapImageRep alloc] initWithCGImage:image]; 128 | CFRelease(image); 129 | 130 | #ifdef DEBUG_SCREENSHOT 131 | NSData *data = [imgRep representationUsingType:NSPNGFileType properties:[NSDictionary dictionary]]; 132 | NSString *path = [NSString stringWithFormat:@"test-%u-%@.png", [displayID unsignedIntValue], [Screenshot displayNameFromDisplayID:displayID]]; 133 | NSError *error; 134 | if ([data writeToFile:path options:0 error:&error] == YES) { 135 | NSLog(@"Wrote debug image to \"%@\"\n", path); 136 | } else { 137 | NSLog(@"Error writing debug image to \"%@\": %@\n", path, error); 138 | } 139 | #endif 140 | 141 | return imgRep; 142 | } 143 | 144 | @end 145 | -------------------------------------------------------------------------------- /CaseLights/Serial.m: -------------------------------------------------------------------------------- 1 | // 2 | // Serial.m 3 | // CaseLights 4 | // 5 | // For more informations refer to this document: 6 | // https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html 7 | // 8 | // Created by Thomas Buck on 14.12.15. 9 | // Copyright © 2015 xythobuz. All rights reserved. 10 | // 11 | 12 | //#define DEBUG_TEXT 13 | 14 | #import 15 | #import 16 | #import 17 | 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | 24 | #import "Serial.h" 25 | 26 | #define MAX_SEND_ERRORS 10 27 | 28 | @interface Serial () 29 | 30 | @property (assign) int fd; 31 | 32 | + (kern_return_t)findSerialPorts:(io_iterator_t *)matches; 33 | + (kern_return_t)getSerialPortPath:(io_iterator_t)serialPortIterator to:(char **)deviceFilePath with:(CFIndex)maxPathCount and:(CFIndex)maxPathSize; 34 | 35 | @end 36 | 37 | @implementation Serial 38 | 39 | @synthesize fd, portName; 40 | 41 | - (id)init { 42 | self = [super init]; 43 | if (self != nil) { 44 | fd = -1; 45 | portName = nil; 46 | } 47 | return self; 48 | } 49 | 50 | - (NSInteger)openPort { 51 | // We need a port name 52 | if (portName == nil) { 53 | NSLog(@"Can't open serial port without name!\n"); 54 | return 1; 55 | } 56 | 57 | // Check if there was already a port opened 58 | if (fd > -1) { 59 | NSLog(@"Closing previously opened serial port \"%@\"!\n", portName); 60 | close(fd); 61 | } 62 | 63 | #ifdef DEBUG 64 | NSLog(@"Opening serial port \"%@\"...\n", portName); 65 | #endif 66 | 67 | // Open port read-only, without controlling terminal, non-blocking 68 | fd = open([portName UTF8String], O_RDWR | O_NOCTTY | O_NONBLOCK); 69 | if (fd == -1) { 70 | NSLog(@"Error opening serial port \"%@\": %s (%d)!\n", portName, strerror(errno), errno); 71 | return 1; 72 | } 73 | 74 | // Prevent additional opens except by root-owned processes 75 | if (ioctl(fd, TIOCEXCL) == -1) { 76 | NSLog(@"Error enabling exclusive access on \"%@\": %s (%d)!\n", portName, strerror(errno), errno); 77 | return 1; 78 | } 79 | 80 | fcntl(fd, F_SETFL, 0); // Enable blocking I/O 81 | 82 | // Read current settings 83 | struct termios options; 84 | tcgetattr(fd, &options); 85 | 86 | // Clear all settings 87 | options.c_lflag = 0; 88 | options.c_oflag = 0; 89 | options.c_iflag = 0; 90 | options.c_cflag = 0; 91 | 92 | options.c_cflag |= CS8; // 8 data bits 93 | options.c_cflag |= CREAD; // Enable receiver 94 | options.c_cflag |= CLOCAL; // Ignore modem status lines 95 | 96 | // Set Baudrate 97 | cfsetispeed(&options, B115200); 98 | cfsetospeed(&options, B115200); 99 | 100 | options.c_cc[VMIN] = 0; // Return even with zero bytes... 101 | options.c_cc[VTIME] = 1; // ...but only after .1 seconds 102 | 103 | // Set new settings 104 | tcsetattr(fd, TCSANOW, &options); 105 | tcflush(fd, TCIOFLUSH); 106 | 107 | return 0; 108 | } 109 | 110 | - (void)closePort { 111 | #ifdef DEBUG 112 | NSLog(@"Closing serial port \"%@\"...\n", portName); 113 | #endif 114 | 115 | if (fd > -1) { 116 | close(fd); 117 | } else { 118 | NSLog(@"Trying to close already closed port!\n"); 119 | } 120 | fd = -1; 121 | } 122 | 123 | - (BOOL)isOpen { 124 | if (fd > -1) { 125 | return YES; 126 | } else { 127 | return NO; 128 | } 129 | } 130 | 131 | - (BOOL)hasData { 132 | if (fd < 0) { 133 | NSLog(@"Error trying to poll a closed port!\n"); 134 | return NO; 135 | } 136 | 137 | struct pollfd fds; 138 | fds.fd = fd; 139 | fds.events = (POLLIN | POLLPRI); // Data may be read 140 | int val = poll(&fds, 1, 0); 141 | if (val > 0) { 142 | return YES; 143 | } else if (val == 0) { 144 | return NO; 145 | } else { 146 | NSLog(@"Error polling serial port: %s (%d)!\n", strerror(errno), errno); 147 | return NO; 148 | } 149 | } 150 | 151 | - (void)sendString:(NSString *)string { 152 | if (fd < 0) { 153 | NSLog(@"Error trying to send to a closed port!\n"); 154 | return; 155 | } 156 | 157 | const char *data = [string UTF8String]; 158 | size_t length = strlen(data); 159 | 160 | #ifdef DEBUG_TEXT 161 | NSLog(@"Sending string %s", data); 162 | #endif 163 | 164 | int errorCount = 0; 165 | ssize_t sent = 0; 166 | while (sent < length) { 167 | ssize_t ret = write(fd, data + sent, length - sent); 168 | if (ret < 0) { 169 | NSLog(@"Error writing to serial port: %s (%d)!\n", strerror(errno), errno); 170 | errorCount++; 171 | if (errorCount >= MAX_SEND_ERRORS) { 172 | #ifdef DEBUG 173 | NSLog(@"Too many send errors! Giving up...\n"); 174 | #endif 175 | return; 176 | } 177 | } else { 178 | sent += ret; 179 | } 180 | } 181 | } 182 | 183 | + (NSArray *)listSerialPorts { 184 | // Get Iterator with all serial ports 185 | io_iterator_t serialPortIterator; 186 | kern_return_t kernResult = [Serial findSerialPorts:&serialPortIterator]; 187 | 188 | // Create 2D array 189 | char **portList; 190 | portList = malloc(100 * sizeof(char *)); 191 | for (int i = 0; i < 100; i++) portList[i] = malloc(200 * sizeof(char)); 192 | 193 | // Copy device name into C-String array 194 | kernResult = [Serial getSerialPortPath:serialPortIterator to:portList with:100 and:200]; 195 | IOObjectRelease(serialPortIterator); 196 | 197 | // Copy contents into NSString Array 198 | NSString *stringList[100]; 199 | NSUInteger realCount = 0; 200 | while (portList[realCount] != NULL) { 201 | stringList[realCount] = [NSString stringWithCString:portList[realCount] encoding:NSUTF8StringEncoding]; 202 | realCount++; 203 | } 204 | 205 | // Destroy 2D array 206 | for (int i = 0; i < 100; i++) free(portList[i]); 207 | free(portList); 208 | 209 | // And return them as NSArray 210 | return [[NSArray alloc] initWithObjects:stringList count:realCount]; 211 | } 212 | 213 | + (kern_return_t)findSerialPorts:(io_iterator_t *)matches { 214 | kern_return_t kernResult; 215 | mach_port_t masterPort; 216 | CFMutableDictionaryRef classesToMatch; 217 | 218 | kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort); 219 | if (KERN_SUCCESS != kernResult) { 220 | NSLog(@"IOMasterPort returned %d\n", kernResult); 221 | return kernResult; 222 | } 223 | 224 | // Serial devices are instances of class IOSerialBSDClient. 225 | classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); 226 | if (classesToMatch == NULL) { 227 | NSLog(@"IOServiceMatching returned a NULL dictionary.\n"); 228 | } else { 229 | CFDictionarySetValue(classesToMatch, 230 | CFSTR(kIOSerialBSDTypeKey), 231 | CFSTR(kIOSerialBSDAllTypes)); 232 | 233 | // Each serial device object has a property with key 234 | // kIOSerialBSDTypeKey and a value that is one of 235 | // kIOSerialBSDAllTypes, kIOSerialBSDModemType, 236 | // or kIOSerialBSDRS232Type. You can change the 237 | // matching dictionary to find other types of serial 238 | // devices by changing the last parameter in the above call 239 | // to CFDictionarySetValue. 240 | } 241 | 242 | kernResult = IOServiceGetMatchingServices(masterPort, classesToMatch, matches); 243 | if (KERN_SUCCESS != kernResult) { 244 | NSLog(@"IOServiceGetMatchingServices returned %d\n", kernResult); 245 | return kernResult; 246 | } 247 | 248 | return kernResult; 249 | } 250 | 251 | + (kern_return_t)getSerialPortPath:(io_iterator_t)serialPortIterator to:(char **)deviceFilePath with:(CFIndex)maxPathCount and:(CFIndex)maxPathSize { 252 | io_object_t modemService; 253 | kern_return_t kernResult = KERN_FAILURE; 254 | CFIndex i = 0; 255 | 256 | while ((modemService = IOIteratorNext(serialPortIterator)) && (i < (maxPathCount - 1))) { 257 | CFTypeRef deviceFilePathAsCFString; 258 | 259 | // Get the callout device's path (/dev/cu.xxxxx). 260 | // The callout device should almost always be 261 | // used. You would use the dialin device (/dev/tty.xxxxx) when 262 | // monitoring a serial port for 263 | // incoming calls, for example, a fax listener. 264 | 265 | deviceFilePathAsCFString = IORegistryEntryCreateCFProperty(modemService, 266 | CFSTR(kIOCalloutDeviceKey), 267 | kCFAllocatorDefault, 268 | 0); 269 | if (deviceFilePathAsCFString) { 270 | Boolean result; 271 | 272 | deviceFilePath[i][0] = '\0'; 273 | 274 | // Convert the path from a CFString to a NULL-terminated C string 275 | // for use with the POSIX open() call. 276 | 277 | result = CFStringGetCString(deviceFilePathAsCFString, 278 | deviceFilePath[i], 279 | maxPathSize, 280 | kCFStringEncodingASCII); 281 | CFRelease(deviceFilePathAsCFString); 282 | 283 | if (result) { 284 | //NSLog(@"BSD path: %s\n", deviceFilePath[i]); 285 | i++; 286 | kernResult = KERN_SUCCESS; 287 | } 288 | } 289 | 290 | // Release the io_service_t now that we are done with it. 291 | 292 | (void) IOObjectRelease(modemService); 293 | } 294 | 295 | deviceFilePath[i] = NULL; 296 | 297 | return kernResult; 298 | } 299 | 300 | @end 301 | -------------------------------------------------------------------------------- /CaseLights/AudioVisualizer.m: -------------------------------------------------------------------------------- 1 | // 2 | // AudioVisualizer.m 3 | // CaseLights 4 | // 5 | // Based on the ideas in: 6 | // http://archive.gamedev.net/archive/reference/programming/features/beatdetection/ 7 | // 8 | // The detected sound frequency of beats will be mapped to the hue of the resulting color, 9 | // the variance of the beat is mapped to the brightness of the color. The colors 10 | // of all detected beats will be added together to form the final displayed color. 11 | // 12 | // Created by Thomas Buck on 01.01.16. 13 | // Copyright © 2016 xythobuz. All rights reserved. 14 | // 15 | 16 | #ifdef DEBUG 17 | #define DEBUG_LOG_BEATS 18 | #endif 19 | 20 | #import "AudioVisualizer.h" 21 | #import "AppDelegate.h" 22 | 23 | #import "EZAudioFFT.h" 24 | #import "EZAudioPlot.h" 25 | 26 | // Parameters for fine-tuning beat detection 27 | #define FFT_BUCKET_COUNT 64 28 | #define FFT_BUCKET_HISTORY 45 29 | #define FFT_C_FACTOR 3.3 30 | #define FFT_V0_FACTOR 0.00001 31 | #define FFT_MAX_V0_COLOR 0.00025 32 | #define FFT_COLOR_DECAY 0.98 33 | 34 | // Use this to skip specific frequencies 35 | // Only check bass frequencies 36 | //#define FFT_BUCKET_SKIP_CONDITION (i > (FFT_BUCKET_COUNT / 4)) 37 | // Only check mid frequencies 38 | //#define FFT_BUCKET_SKIP_CONDITION ((i < (FFT_BUCKET_COUNT / 4)) || (i > (FFT_BUCKET_COUNT * 3 / 4))) 39 | // Only check high frequencies 40 | //#define FFT_BUCKET_SKIP_CONDITION (i < (FFT_BUCKET_COUNT * 3 / 4)) 41 | 42 | // Factors for nicer debug display 43 | #define FFT_DEBUG_RAW_FACTOR 42.0 44 | #define FFT_DEBUG_FACTOR 230.0 45 | 46 | static AppDelegate *appDelegate = nil; 47 | static EZAudioFFT *fft = nil; 48 | static int maxBufferSize = 0; 49 | static float sensitivity = 1.0f; 50 | static float history[FFT_BUCKET_COUNT][FFT_BUCKET_HISTORY]; 51 | static int nextHistory = 0; 52 | static int samplesPerBucket = 0; 53 | static unsigned char lastRed = 0, lastGreen = 0, lastBlue = 0; 54 | 55 | static BOOL shouldShowWindow = NO; 56 | static NSWindow *window = nil; 57 | static EZAudioPlot *plot = nil; 58 | static NSTextField *label = nil; 59 | 60 | @implementation AudioVisualizer 61 | 62 | + (void)setDelegate:(AppDelegate *)delegate { 63 | appDelegate = delegate; 64 | 65 | // Initialize static history variables 66 | for (int i = 0; i < FFT_BUCKET_COUNT; i++) { 67 | for (int j = 0; j < FFT_BUCKET_HISTORY; j++) { 68 | history[i][j] = 0.5f; 69 | } 70 | } 71 | } 72 | 73 | + (void)setSensitivity:(float)sens { 74 | sensitivity = sens / 100.0; 75 | } 76 | 77 | + (void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize { 78 | // Create Fast Fourier Transformation object 79 | if (fft == nil) { 80 | maxBufferSize = bufferSize; 81 | samplesPerBucket = bufferSize / FFT_BUCKET_COUNT; 82 | fft = [EZAudioFFT fftWithMaximumBufferSize:maxBufferSize sampleRate:appDelegate.microphone.audioStreamBasicDescription.mSampleRate]; 83 | 84 | #ifdef DEBUG 85 | NSLog(@"Created FFT with max. freq.: %.2f\n", appDelegate.microphone.audioStreamBasicDescription.mSampleRate / 2); 86 | #endif 87 | } 88 | 89 | // Check for changing buffer sizes 90 | if (bufferSize > maxBufferSize) { 91 | NSLog(@"Buffer Size changed?! %d != %d\n", maxBufferSize, bufferSize); 92 | maxBufferSize = bufferSize; 93 | samplesPerBucket = bufferSize / FFT_BUCKET_COUNT; 94 | fft = [EZAudioFFT fftWithMaximumBufferSize:maxBufferSize sampleRate:appDelegate.microphone.audioStreamBasicDescription.mSampleRate]; 95 | } 96 | 97 | // Scale input if required 98 | if (sensitivity != 1.0f) { 99 | for (int i = 0; i < bufferSize; i++) { 100 | buffer[i] *= sensitivity; 101 | } 102 | } 103 | 104 | // Perform fast fourier transformation 105 | [fft computeFFTWithBuffer:buffer withBufferSize:bufferSize]; 106 | 107 | // Split FFT output into a small number of 'buckets' or 'bins' and add to circular history buffer 108 | for (int i = 0; i < FFT_BUCKET_COUNT; i++) { 109 | float sum = 0.0f; 110 | for (int j = 0; j < samplesPerBucket; j++) { 111 | sum += fft.fftData[(i + samplesPerBucket) + j]; 112 | } 113 | history[i][nextHistory] = sum / samplesPerBucket; 114 | } 115 | 116 | // Slowly fade old colors to black 117 | lastRed = lastRed * FFT_COLOR_DECAY; 118 | lastGreen = lastGreen * FFT_COLOR_DECAY; 119 | lastBlue = lastBlue * FFT_COLOR_DECAY; 120 | 121 | // Check for any beats 122 | int beatCount = 0; 123 | for (int i = 0; i < FFT_BUCKET_COUNT; i++) { 124 | // Skip frequency bands, if required 125 | #ifdef FFT_BUCKET_SKIP_CONDITION 126 | if (FFT_BUCKET_SKIP_CONDITION) continue; 127 | #endif 128 | 129 | // Calculate average of history of this frequency 130 | float average = 0.0f; 131 | for (int j = 0; j < FFT_BUCKET_HISTORY; j++) { 132 | average += history[i][j]; 133 | } 134 | average /= FFT_BUCKET_HISTORY; 135 | 136 | // Calculate variance of current bucket in history 137 | float v = 0.0f; 138 | for (int j = 0; j < FFT_BUCKET_HISTORY; j++) { 139 | float tmp = history[i][j] - average; 140 | tmp *= tmp; 141 | v += tmp; 142 | } 143 | v /= FFT_BUCKET_HISTORY; 144 | 145 | // Check for beat conditions 146 | if ((history[i][nextHistory] > (FFT_C_FACTOR * average)) && (v > FFT_V0_FACTOR)) { 147 | // Found a beat on this frequency band, map to a single color 148 | if (v < FFT_V0_FACTOR) v = FFT_V0_FACTOR; 149 | if (v > FFT_MAX_V0_COLOR) v = FFT_MAX_V0_COLOR; 150 | float bright = [AppDelegate map:v FromMin:FFT_V0_FACTOR FromMax:FFT_MAX_V0_COLOR ToMin:0.0 ToMax:100.0]; 151 | float hue = [AppDelegate map:i FromMin:0.0 FromMax:FFT_BUCKET_COUNT ToMin:0.0 ToMax:360.0]; 152 | unsigned char r, g, b; 153 | [AppDelegate convertH:hue S:1.0 V:bright toR:&r G:&g B:&b]; 154 | 155 | // Blend with last color using averaging 156 | int tmpR = (lastRed + r) / 2; 157 | int tmpG = (lastGreen + g) / 2; 158 | int tmpB = (lastBlue + b) / 2; 159 | lastRed = tmpR; 160 | lastGreen = tmpG; 161 | lastBlue = tmpB; 162 | 163 | #ifdef DEBUG_LOG_BEATS 164 | NSLog(@"Beat in %d with c: %f v: %f", i, (history[i][nextHistory] / average), v); 165 | #endif 166 | 167 | beatCount++; 168 | } 169 | } 170 | 171 | // Send new RGB value to lights, if it has changed 172 | static unsigned char lastSentRed = 42, lastSentGreen = 23, lastSentBlue = 99; 173 | if ((lastSentRed != lastRed) || (lastSentGreen != lastGreen) || (lastSentBlue != lastBlue)) { 174 | [appDelegate setLightsR:lastRed G:lastGreen B:lastBlue]; 175 | lastSentRed = lastRed; 176 | lastSentGreen = lastGreen; 177 | lastSentBlue = lastBlue; 178 | } 179 | 180 | // Update debug FFT plot, if required 181 | if (shouldShowWindow && (window != nil) && (plot != nil) && (label != nil)) { 182 | for (UInt32 i = 0; i < FFT_BUCKET_COUNT; i++) { 183 | // Copy output to input buffer (a bit ugly, but is always big enough) 184 | buffer[i] = history[i][nextHistory]; 185 | 186 | // Scale so user can see something 187 | buffer[i] *= FFT_DEBUG_FACTOR; 188 | if (buffer[i] > 1.0f) buffer[i] = 1.0f; 189 | if (buffer[i] < -1.0f) buffer[i] = -1.0f; 190 | } 191 | [plot updateBuffer:buffer withBufferSize:bufferSize]; 192 | 193 | // Change background color to match color output and show beat counter 194 | [window setBackgroundColor:[NSColor colorWithCalibratedRed:lastRed / 255.0 green:lastGreen / 255.0 blue:lastBlue / 255.0 alpha:1.0]]; 195 | [label setStringValue:[NSString stringWithFormat:@"Beats: %d", beatCount]]; 196 | } 197 | 198 | // Point to next history buffer 199 | nextHistory++; 200 | if (nextHistory >= FFT_BUCKET_HISTORY) { 201 | nextHistory = 0; 202 | } 203 | } 204 | 205 | + (void)setShowWindow:(BOOL)showWindow { 206 | shouldShowWindow = showWindow; 207 | 208 | // Close window if it was visible and should no longer be 209 | if (showWindow == YES) { 210 | if ((window == nil) || (plot == nil) || (label == nil)) { 211 | // Create window 212 | NSRect frame = NSMakeRect(450, 300, 600, 400); 213 | window = [[NSWindow alloc] initWithContentRect:frame 214 | styleMask:NSClosableWindowMask | NSTitledWindowMask | NSBorderlessWindowMask 215 | backing:NSBackingStoreBuffered 216 | defer:NO]; 217 | [window setTitle:@"CaseLights FFT"]; 218 | [window setReleasedWhenClosed:NO]; 219 | 220 | // Create FFT Plot and add to window 221 | plot = [[EZAudioPlot alloc] initWithFrame:window.contentView.frame]; 222 | plot.color = [NSColor whiteColor]; 223 | plot.shouldOptimizeForRealtimePlot = NO; // Not working with 'YES' here?! 224 | plot.shouldFill = YES; 225 | plot.shouldCenterYAxis = NO; 226 | plot.shouldMirror = NO; 227 | plot.plotType = EZPlotTypeBuffer; 228 | [window.contentView addSubview:plot]; 229 | 230 | // Create beat count label 231 | label = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 380, 600, 20)]; 232 | [label setTextColor:[NSColor whiteColor]]; 233 | [label setEditable:NO]; 234 | [label setBezeled:NO]; 235 | [label setDrawsBackground:NO]; 236 | [label setSelectable:NO]; 237 | [label setStringValue:@"-"]; 238 | [window.contentView addSubview:label]; 239 | 240 | #ifdef DEBUG 241 | NSLog(@"Created debugging FFT Plot window...\n"); 242 | #endif 243 | } 244 | 245 | if ([window isVisible] == NO) { 246 | // Make window visible 247 | [window makeKeyAndOrderFront:appDelegate.application]; 248 | 249 | #ifdef DEBUG 250 | NSLog(@"Made debugging FFT Plot window visible...\n"); 251 | #endif 252 | } 253 | } else { 254 | if (window != nil) { 255 | if ([window isVisible] == YES) { 256 | [window close]; 257 | 258 | #ifdef DEBUG 259 | NSLog(@"Closed debugging FFT Plot window...\n"); 260 | #endif 261 | } 262 | } 263 | } 264 | } 265 | 266 | @end 267 | -------------------------------------------------------------------------------- /linux/caselights: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # CaseLights Linux Qt System Tray client 4 | # depends on: 5 | # - python-pyqt5 6 | # - python-pyserial 7 | 8 | import subprocess 9 | import sys 10 | import os.path 11 | import threading 12 | import time 13 | import colorsys 14 | import serial, serial.tools, serial.tools.list_ports 15 | from PyQt5 import QtWidgets, QtGui, QtCore 16 | from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu 17 | from PyQt5.QtGui import QIcon, QPixmap, QCursor 18 | from PyQt5.QtCore import QCoreApplication, QSettings 19 | 20 | class CaseLights(): 21 | name = "CaseLights" 22 | vendor = "xythobuz" 23 | version = "0.2" 24 | 25 | iconPath = "/usr/share/pixmaps/" 26 | iconName = "caselights_icon.png" 27 | 28 | staticColors = [ 29 | [ "Off", "0", "0", "0", None ], 30 | [ "Red", "255", "0", "0", None ], 31 | [ "Green", "0", "255", "0", None ], 32 | [ "Blue", "0", "0", "255", None ], 33 | [ "White", "255", "255", "255", None ], 34 | ] 35 | 36 | slowFadeUpdateFreq = 5 37 | fastFadeUpdateFreq = 20 38 | cpuUsageUpdateFreq = 2 39 | fadeSaturation = 1.0 40 | fadeValue = 1.0 41 | fadeHueCounter = 0 42 | 43 | usedPort = None 44 | serial = None 45 | animation = None 46 | animationRunning = False 47 | menu = None 48 | portMenu = None 49 | portActions = None 50 | refreshAction = None 51 | quitAction = None 52 | 53 | def __init__(self): 54 | app = QtWidgets.QApplication(sys.argv) 55 | QCoreApplication.setApplicationName(self.name) 56 | 57 | if not QSystemTrayIcon.isSystemTrayAvailable(): 58 | print("System Tray is not available on this platform!") 59 | sys.exit(0) 60 | 61 | self.readSettings() 62 | if self.usedPort is not None: 63 | self.connect() 64 | 65 | self.menu = QMenu() 66 | 67 | colorMenu = QMenu("&Colors") 68 | for color in self.staticColors: 69 | color[4] = QAction(color[0]) 70 | colorMenu.addAction(color[4]) 71 | colorMenu.triggered.connect(self.setStaticColor) 72 | self.menu.addMenu(colorMenu) 73 | 74 | animMenu = QMenu("&Animations") 75 | noFadeAction = QAction("Off") 76 | noFadeAction.triggered.connect(self.animOff) 77 | animMenu.addAction(noFadeAction) 78 | slowFadeAction = QAction("Slow Fade") 79 | slowFadeAction.triggered.connect(self.slowFadeOn) 80 | animMenu.addAction(slowFadeAction) 81 | fastFadeAction = QAction("Fast Fade") 82 | fastFadeAction.triggered.connect(self.fastFadeOn) 83 | animMenu.addAction(fastFadeAction) 84 | self.menu.addMenu(animMenu) 85 | 86 | visualMenu = QMenu("&Visualizations") 87 | noVisualAction = QAction("Off") 88 | noVisualAction.triggered.connect(self.animOff) 89 | visualMenu.addAction(noVisualAction) 90 | cpuUsageAction = QAction("CPU Usage") 91 | cpuUsageAction.triggered.connect(self.cpuUsageOn) 92 | visualMenu.addAction(cpuUsageAction) 93 | self.menu.addMenu(visualMenu) 94 | 95 | lightMenu = QMenu("&UV-Light") 96 | lightOnAction = QAction("O&n") 97 | lightOnAction.triggered.connect(self.lightsOn) 98 | lightMenu.addAction(lightOnAction) 99 | lightOffAction = QAction("O&ff") 100 | lightOffAction.triggered.connect(self.lightsOff) 101 | lightMenu.addAction(lightOffAction) 102 | self.menu.addMenu(lightMenu) 103 | 104 | self.refreshSerialPorts() 105 | 106 | self.quitAction = QAction("&Quit") 107 | self.quitAction.triggered.connect(self.exit) 108 | self.menu.addAction(self.quitAction) 109 | 110 | iconPathName = "" 111 | if os.path.isfile(self.iconName): 112 | iconPathName = self.iconName 113 | elif os.path.isfile(self.iconPath + self.iconName): 114 | iconPathName = self.iconPath + self.iconName 115 | else: 116 | print("no icon found") 117 | 118 | icon = QIcon() 119 | if iconPathName != "": 120 | pic = QPixmap(32, 32) 121 | pic.load(iconPathName) 122 | icon = QIcon(pic) 123 | 124 | trayIcon = QSystemTrayIcon(icon) 125 | trayIcon.setToolTip(self.name + " " + self.version) 126 | trayIcon.setContextMenu(self.menu) 127 | trayIcon.activated.connect(self.showHide) 128 | trayIcon.setVisible(True) 129 | 130 | sys.exit(app.exec_()) 131 | 132 | def showHide(self, activationReason): 133 | if activationReason == QSystemTrayIcon.Trigger: 134 | self.menu.popup(QCursor.pos()) 135 | 136 | def exit(self): 137 | if self.serial is not None: 138 | if self.serial.is_open: 139 | print("stopping animations") 140 | self.animOff() 141 | print("turning off lights") 142 | self.serial.write(b'RGB 0 0 0\n') 143 | self.serial.write(b'UV 0\n') 144 | print("closing connection") 145 | self.serial.close() 146 | QCoreApplication.quit() 147 | 148 | def readSettings(self): 149 | settings = QSettings(self.vendor, self.name) 150 | self.usedPort = settings.value("serial_port") 151 | if self.usedPort is not None: 152 | print("serial port stored: " + self.usedPort) 153 | else: 154 | print("no serial port stored") 155 | 156 | def writeSettings(self): 157 | settings = QSettings(self.vendor, self.name) 158 | settings.setValue("serial_port", self.usedPort) 159 | if self.usedPort is not None: 160 | print("storing serial port: " + self.usedPort) 161 | else: 162 | print("not storing any serial port") 163 | del settings 164 | 165 | def refreshSerialPorts(self): 166 | self.portMenu = QMenu("Port") 167 | ports = serial.tools.list_ports.comports() 168 | self.portActions = [] 169 | for port in ports: 170 | action = QAction(port.device) 171 | self.portActions.append(action) 172 | self.portMenu.addAction(action) 173 | self.portMenu.triggered.connect(self.selectSerialPort) 174 | 175 | if self.refreshAction == None: 176 | self.refreshAction = QAction("&Refresh") 177 | self.refreshAction.triggered.connect(self.refreshSerialPorts) 178 | self.portMenu.addAction(self.refreshAction) 179 | self.menu.insertMenu(self.quitAction, self.portMenu) 180 | 181 | def selectSerialPort(self, action): 182 | self.usedPort = action.text() 183 | self.writeSettings() 184 | if self.connect(): 185 | self.portMenu.setActiveAction(action) 186 | 187 | def connect(self): 188 | if self.usedPort is None: 189 | print("not connecting to any serial port") 190 | return False 191 | 192 | if self.serial is not None: 193 | print("closing previous port") 194 | self.serial.close() 195 | 196 | self.serial = serial.Serial() 197 | self.serial.port = self.usedPort 198 | self.serial.baudrate = 115200 199 | 200 | try: 201 | self.serial.open() 202 | if self.serial.is_open: 203 | print("connected to: " + self.usedPort) 204 | else: 205 | print("error connecting to: " + self.usedPort) 206 | return self.serial.is_open 207 | except serial.serialutil.SerialException: 208 | print("error connecting to: " + self.usedPort) 209 | return False 210 | 211 | def printRGBStrings(self, rs, gs, bs): 212 | if self.serial.is_open: 213 | r = str.encode(rs) 214 | g = str.encode(gs) 215 | b = str.encode(bs) 216 | rgb = b'RGB ' + r + b' ' + g + b' ' + b + b'\n' 217 | self.serial.write(rgb) 218 | else: 219 | print("not connected") 220 | 221 | def setStaticColor(self, action): 222 | self.animOff() 223 | for color in self.staticColors: 224 | if color[4] is action: 225 | self.printRGBStrings(color[1], color[2], color[3]) 226 | return True 227 | print("color not found") 228 | return False 229 | 230 | def hsvToRgb(self, h, s, v): 231 | (r, g, b) = colorsys.hsv_to_rgb(h, s, v) 232 | return (round(r * 255), round(g * 255), round(b * 255)) 233 | 234 | def getCurrentCpuUsage(self): 235 | # https://stackoverflow.com/a/9229692 236 | # "top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}'" 237 | # https://stackoverflow.com/a/4760517 238 | cmd = ["top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'"] 239 | result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE) 240 | num = result.stdout.decode('utf-8') 241 | return float(num) 242 | 243 | def cpuUsageRunner(self): 244 | while self.animationRunning is True: 245 | cpu = self.getCurrentCpuUsage() 246 | color = cpu / 100.0 * 120.0 247 | (r, g, b) = self.hsvToRgb((120.0 - color) / 360.0, self.fadeSaturation, self.fadeValue) 248 | self.printRGBStrings(str(r), str(g), str(b)) 249 | time.sleep(1.0 / self.cpuUsageUpdateFreq) 250 | 251 | def cpuUsageOn(self): 252 | self.animOff() 253 | self.animationRunning = True 254 | self.animation = threading.Thread(target=self.cpuUsageRunner) 255 | self.animation.start() 256 | 257 | def fadeRunner(self, freq): 258 | while self.animationRunning is True: 259 | self.fadeHueCounter += 1 260 | if self.fadeHueCounter >= 360: 261 | self.fadeHueCounter = 0 262 | (r, g, b) = self.hsvToRgb(self.fadeHueCounter / 360.0, self.fadeSaturation, self.fadeValue) 263 | self.printRGBStrings(str(r), str(g), str(b)) 264 | time.sleep(1.0 / freq) 265 | 266 | def slowFadeOn(self): 267 | self.animOff() 268 | self.animationRunning = True 269 | self.animation = threading.Thread(target=self.fadeRunner, args=[self.slowFadeUpdateFreq]) 270 | self.animation.start() 271 | 272 | def fastFadeOn(self): 273 | self.animOff() 274 | self.animationRunning = True 275 | self.animation = threading.Thread(target=self.fadeRunner, args=[self.fastFadeUpdateFreq]) 276 | self.animation.start() 277 | 278 | def animOff(self): 279 | self.animationRunning = False 280 | if self.animation != None: 281 | self.animation.join() 282 | self.animation = None 283 | self.printRGBStrings("0", "0", "0") 284 | time.sleep(0.1) 285 | 286 | def lightsOn(self): 287 | if self.serial.is_open: 288 | self.serial.write(b'UV 1\n') 289 | else: 290 | print("not connected") 291 | 292 | def lightsOff(self): 293 | if self.serial.is_open: 294 | self.serial.write(b'UV 0\n') 295 | else: 296 | print("not connected") 297 | 298 | if __name__ == "__main__": 299 | tray = CaseLights() 300 | -------------------------------------------------------------------------------- /CaseLights.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E9101B871C36B11500720A6E /* EZAudioOSX.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = E9101B831C36B0C100720A6E /* EZAudioOSX.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 11 | E9101B881C36B4B100720A6E /* EZAudioOSX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9101B831C36B0C100720A6E /* EZAudioOSX.framework */; }; 12 | E9101B8B1C36C8D400720A6E /* AudioVisualizer.m in Sources */ = {isa = PBXBuildFile; fileRef = E9101B8A1C36C8D400720A6E /* AudioVisualizer.m */; }; 13 | E9109B111C2AACF400726111 /* SystemInfoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9109B0B1C2AAC1B00726111 /* SystemInfoKit.framework */; }; 14 | E9109B151C2AAEFD00726111 /* SystemInfoKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = E9109B0B1C2AAC1B00726111 /* SystemInfoKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | E9109B191C2AAFE100726111 /* GPUStats.m in Sources */ = {isa = PBXBuildFile; fileRef = E9109B181C2AAFE100726111 /* GPUStats.m */; }; 16 | E9640E8A1C30511B0081D46C /* Screenshot.m in Sources */ = {isa = PBXBuildFile; fileRef = E9640E891C30511B0081D46C /* Screenshot.m */; }; 17 | E9AB18011C29F8CD00BF3C51 /* Serial.m in Sources */ = {isa = PBXBuildFile; fileRef = E9AB18001C29F8CD00BF3C51 /* Serial.m */; }; 18 | E9F13E031C28B3D3004C6B95 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F13E021C28B3D3004C6B95 /* AppDelegate.m */; }; 19 | E9F13E061C28B3D3004C6B95 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F13E051C28B3D3004C6B95 /* main.m */; }; 20 | E9F13E081C28B3D3004C6B95 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E9F13E071C28B3D3004C6B95 /* Assets.xcassets */; }; 21 | E9F13E0B1C28B3D3004C6B95 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9F13E091C28B3D3004C6B95 /* MainMenu.xib */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | E9101B801C36B0C100720A6E /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = E9101B7B1C36B0C100720A6E /* EZAudio.xcodeproj */; 28 | proxyType = 2; 29 | remoteGlobalIDString = 469F4CF31B749F7800666A46; 30 | remoteInfo = EZAudioiOS; 31 | }; 32 | E9101B821C36B0C100720A6E /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = E9101B7B1C36B0C100720A6E /* EZAudio.xcodeproj */; 35 | proxyType = 2; 36 | remoteGlobalIDString = 8A5A4B9C1BBBDCB200A8A048; 37 | remoteInfo = EZAudioOSX; 38 | }; 39 | E9101B851C36B10C00720A6E /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = E9101B7B1C36B0C100720A6E /* EZAudio.xcodeproj */; 42 | proxyType = 1; 43 | remoteGlobalIDString = 8A5A4B9B1BBBDCB200A8A048; 44 | remoteInfo = EZAudioOSX; 45 | }; 46 | E9109B0A1C2AAC1B00726111 /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = E9109B051C2AAC1B00726111 /* SystemInfoKit.xcodeproj */; 49 | proxyType = 2; 50 | remoteGlobalIDString = 1D43C4CA1BD49B78001D8A55; 51 | remoteInfo = SystemInfoKit; 52 | }; 53 | E9109B0C1C2AAC1B00726111 /* PBXContainerItemProxy */ = { 54 | isa = PBXContainerItemProxy; 55 | containerPortal = E9109B051C2AAC1B00726111 /* SystemInfoKit.xcodeproj */; 56 | proxyType = 2; 57 | remoteGlobalIDString = 1D43C50E1BD56B1D001D8A55; 58 | remoteInfo = SystemInfoKitTests; 59 | }; 60 | E9109B121C2AAD2E00726111 /* PBXContainerItemProxy */ = { 61 | isa = PBXContainerItemProxy; 62 | containerPortal = E9109B051C2AAC1B00726111 /* SystemInfoKit.xcodeproj */; 63 | proxyType = 1; 64 | remoteGlobalIDString = 1D43C4C91BD49B78001D8A55; 65 | remoteInfo = SystemInfoKit; 66 | }; 67 | /* End PBXContainerItemProxy section */ 68 | 69 | /* Begin PBXCopyFilesBuildPhase section */ 70 | E9109B141C2AAEEC00726111 /* CopyFiles */ = { 71 | isa = PBXCopyFilesBuildPhase; 72 | buildActionMask = 2147483647; 73 | dstPath = ""; 74 | dstSubfolderSpec = 10; 75 | files = ( 76 | E9101B871C36B11500720A6E /* EZAudioOSX.framework in CopyFiles */, 77 | E9109B151C2AAEFD00726111 /* SystemInfoKit.framework in CopyFiles */, 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | /* End PBXCopyFilesBuildPhase section */ 82 | 83 | /* Begin PBXFileReference section */ 84 | E9101B7B1C36B0C100720A6E /* EZAudio.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = EZAudio.xcodeproj; path = EZAudio/EZAudio.xcodeproj; sourceTree = ""; }; 85 | E9101B891C36C8D400720A6E /* AudioVisualizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioVisualizer.h; sourceTree = ""; }; 86 | E9101B8A1C36C8D400720A6E /* AudioVisualizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioVisualizer.m; sourceTree = ""; }; 87 | E9109B051C2AAC1B00726111 /* SystemInfoKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SystemInfoKit.xcodeproj; path = JSystemInfoKit/SystemInfoKit.xcodeproj; sourceTree = ""; }; 88 | E9109B171C2AAFE100726111 /* GPUStats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GPUStats.h; path = CaseLights/GPUStats.h; sourceTree = ""; }; 89 | E9109B181C2AAFE100726111 /* GPUStats.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GPUStats.m; path = CaseLights/GPUStats.m; sourceTree = ""; }; 90 | E9640E881C30511B0081D46C /* Screenshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Screenshot.h; path = CaseLights/Screenshot.h; sourceTree = ""; }; 91 | E9640E891C30511B0081D46C /* Screenshot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Screenshot.m; path = CaseLights/Screenshot.m; sourceTree = ""; }; 92 | E9AB17FF1C29F8CD00BF3C51 /* Serial.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Serial.h; path = CaseLights/Serial.h; sourceTree = ""; }; 93 | E9AB18001C29F8CD00BF3C51 /* Serial.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Serial.m; path = CaseLights/Serial.m; sourceTree = ""; }; 94 | E9F13DFE1C28B3D3004C6B95 /* CaseLights.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CaseLights.app; sourceTree = BUILT_PRODUCTS_DIR; }; 95 | E9F13E011C28B3D3004C6B95 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 96 | E9F13E021C28B3D3004C6B95 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 97 | E9F13E051C28B3D3004C6B95 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 98 | E9F13E071C28B3D3004C6B95 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 99 | E9F13E0A1C28B3D3004C6B95 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 100 | E9F13E0C1C28B3D3004C6B95 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 101 | /* End PBXFileReference section */ 102 | 103 | /* Begin PBXFrameworksBuildPhase section */ 104 | E9F13DFB1C28B3D3004C6B95 /* Frameworks */ = { 105 | isa = PBXFrameworksBuildPhase; 106 | buildActionMask = 2147483647; 107 | files = ( 108 | E9109B111C2AACF400726111 /* SystemInfoKit.framework in Frameworks */, 109 | E9101B881C36B4B100720A6E /* EZAudioOSX.framework in Frameworks */, 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | /* End PBXFrameworksBuildPhase section */ 114 | 115 | /* Begin PBXGroup section */ 116 | E9101B7C1C36B0C100720A6E /* Products */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | E9101B811C36B0C100720A6E /* EZAudioiOS.framework */, 120 | E9101B831C36B0C100720A6E /* EZAudioOSX.framework */, 121 | ); 122 | name = Products; 123 | sourceTree = ""; 124 | }; 125 | E9109B061C2AAC1B00726111 /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | E9109B0B1C2AAC1B00726111 /* SystemInfoKit.framework */, 129 | E9109B0D1C2AAC1B00726111 /* SystemInfoKitTests.xctest */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | E9109B161C2AAFA200726111 /* LowLevel */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | E9AB17FF1C29F8CD00BF3C51 /* Serial.h */, 138 | E9AB18001C29F8CD00BF3C51 /* Serial.m */, 139 | E9640E881C30511B0081D46C /* Screenshot.h */, 140 | E9640E891C30511B0081D46C /* Screenshot.m */, 141 | E9109B171C2AAFE100726111 /* GPUStats.h */, 142 | E9109B181C2AAFE100726111 /* GPUStats.m */, 143 | ); 144 | name = LowLevel; 145 | path = ..; 146 | sourceTree = ""; 147 | }; 148 | E9F13DF51C28B3D3004C6B95 = { 149 | isa = PBXGroup; 150 | children = ( 151 | E9101B7B1C36B0C100720A6E /* EZAudio.xcodeproj */, 152 | E9109B051C2AAC1B00726111 /* SystemInfoKit.xcodeproj */, 153 | E9F13E001C28B3D3004C6B95 /* CaseLights */, 154 | E9F13DFF1C28B3D3004C6B95 /* Products */, 155 | ); 156 | sourceTree = ""; 157 | }; 158 | E9F13DFF1C28B3D3004C6B95 /* Products */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | E9F13DFE1C28B3D3004C6B95 /* CaseLights.app */, 162 | ); 163 | name = Products; 164 | sourceTree = ""; 165 | }; 166 | E9F13E001C28B3D3004C6B95 /* CaseLights */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | E9F13E011C28B3D3004C6B95 /* AppDelegate.h */, 170 | E9F13E021C28B3D3004C6B95 /* AppDelegate.m */, 171 | E9101B891C36C8D400720A6E /* AudioVisualizer.h */, 172 | E9101B8A1C36C8D400720A6E /* AudioVisualizer.m */, 173 | E9109B161C2AAFA200726111 /* LowLevel */, 174 | E9F13E071C28B3D3004C6B95 /* Assets.xcassets */, 175 | E9F13E091C28B3D3004C6B95 /* MainMenu.xib */, 176 | E9F13E0C1C28B3D3004C6B95 /* Info.plist */, 177 | E9F13E041C28B3D3004C6B95 /* Supporting Files */, 178 | ); 179 | path = CaseLights; 180 | sourceTree = ""; 181 | }; 182 | E9F13E041C28B3D3004C6B95 /* Supporting Files */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | E9F13E051C28B3D3004C6B95 /* main.m */, 186 | ); 187 | name = "Supporting Files"; 188 | sourceTree = ""; 189 | }; 190 | /* End PBXGroup section */ 191 | 192 | /* Begin PBXNativeTarget section */ 193 | E9F13DFD1C28B3D3004C6B95 /* CaseLights */ = { 194 | isa = PBXNativeTarget; 195 | buildConfigurationList = E9F13E0F1C28B3D3004C6B95 /* Build configuration list for PBXNativeTarget "CaseLights" */; 196 | buildPhases = ( 197 | E9AB17FC1C28B43B00BF3C51 /* Increment build number */, 198 | E9F13DFA1C28B3D3004C6B95 /* Sources */, 199 | E9F13DFB1C28B3D3004C6B95 /* Frameworks */, 200 | E9F13DFC1C28B3D3004C6B95 /* Resources */, 201 | E9109B141C2AAEEC00726111 /* CopyFiles */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | E9101B861C36B10C00720A6E /* PBXTargetDependency */, 207 | E9109B131C2AAD2E00726111 /* PBXTargetDependency */, 208 | ); 209 | name = CaseLights; 210 | productName = CaseLights; 211 | productReference = E9F13DFE1C28B3D3004C6B95 /* CaseLights.app */; 212 | productType = "com.apple.product-type.application"; 213 | }; 214 | /* End PBXNativeTarget section */ 215 | 216 | /* Begin PBXProject section */ 217 | E9F13DF61C28B3D3004C6B95 /* Project object */ = { 218 | isa = PBXProject; 219 | attributes = { 220 | LastUpgradeCheck = 0720; 221 | ORGANIZATIONNAME = xythobuz; 222 | TargetAttributes = { 223 | E9F13DFD1C28B3D3004C6B95 = { 224 | CreatedOnToolsVersion = 7.2; 225 | }; 226 | }; 227 | }; 228 | buildConfigurationList = E9F13DF91C28B3D3004C6B95 /* Build configuration list for PBXProject "CaseLights" */; 229 | compatibilityVersion = "Xcode 3.2"; 230 | developmentRegion = English; 231 | hasScannedForEncodings = 0; 232 | knownRegions = ( 233 | en, 234 | Base, 235 | ); 236 | mainGroup = E9F13DF51C28B3D3004C6B95; 237 | productRefGroup = E9F13DFF1C28B3D3004C6B95 /* Products */; 238 | projectDirPath = ""; 239 | projectReferences = ( 240 | { 241 | ProductGroup = E9101B7C1C36B0C100720A6E /* Products */; 242 | ProjectRef = E9101B7B1C36B0C100720A6E /* EZAudio.xcodeproj */; 243 | }, 244 | { 245 | ProductGroup = E9109B061C2AAC1B00726111 /* Products */; 246 | ProjectRef = E9109B051C2AAC1B00726111 /* SystemInfoKit.xcodeproj */; 247 | }, 248 | ); 249 | projectRoot = ""; 250 | targets = ( 251 | E9F13DFD1C28B3D3004C6B95 /* CaseLights */, 252 | ); 253 | }; 254 | /* End PBXProject section */ 255 | 256 | /* Begin PBXReferenceProxy section */ 257 | E9101B811C36B0C100720A6E /* EZAudioiOS.framework */ = { 258 | isa = PBXReferenceProxy; 259 | fileType = wrapper.framework; 260 | path = EZAudioiOS.framework; 261 | remoteRef = E9101B801C36B0C100720A6E /* PBXContainerItemProxy */; 262 | sourceTree = BUILT_PRODUCTS_DIR; 263 | }; 264 | E9101B831C36B0C100720A6E /* EZAudioOSX.framework */ = { 265 | isa = PBXReferenceProxy; 266 | fileType = wrapper.framework; 267 | path = EZAudioOSX.framework; 268 | remoteRef = E9101B821C36B0C100720A6E /* PBXContainerItemProxy */; 269 | sourceTree = BUILT_PRODUCTS_DIR; 270 | }; 271 | E9109B0B1C2AAC1B00726111 /* SystemInfoKit.framework */ = { 272 | isa = PBXReferenceProxy; 273 | fileType = wrapper.framework; 274 | path = SystemInfoKit.framework; 275 | remoteRef = E9109B0A1C2AAC1B00726111 /* PBXContainerItemProxy */; 276 | sourceTree = BUILT_PRODUCTS_DIR; 277 | }; 278 | E9109B0D1C2AAC1B00726111 /* SystemInfoKitTests.xctest */ = { 279 | isa = PBXReferenceProxy; 280 | fileType = wrapper.cfbundle; 281 | path = SystemInfoKitTests.xctest; 282 | remoteRef = E9109B0C1C2AAC1B00726111 /* PBXContainerItemProxy */; 283 | sourceTree = BUILT_PRODUCTS_DIR; 284 | }; 285 | /* End PBXReferenceProxy section */ 286 | 287 | /* Begin PBXResourcesBuildPhase section */ 288 | E9F13DFC1C28B3D3004C6B95 /* Resources */ = { 289 | isa = PBXResourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | E9F13E081C28B3D3004C6B95 /* Assets.xcassets in Resources */, 293 | E9F13E0B1C28B3D3004C6B95 /* MainMenu.xib in Resources */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | /* End PBXResourcesBuildPhase section */ 298 | 299 | /* Begin PBXShellScriptBuildPhase section */ 300 | E9AB17FC1C28B43B00BF3C51 /* Increment build number */ = { 301 | isa = PBXShellScriptBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | ); 305 | inputPaths = ( 306 | ); 307 | name = "Increment build number"; 308 | outputPaths = ( 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | shellPath = /bin/sh; 312 | shellScript = "#!/bin/sh\n\nplist=\"${PROJECT_DIR}/${INFOPLIST_FILE}\"\ndir=\"$(dirname \"$plist\")\"\n\n# Only increment the build number if source files have changed\nif [ -n \"$(find \"$dir\" \\! -path \"*xcuserdata*\" \\! -path \"*.git*\" \\! -name .DS_Store \\! -path \"*Website*\" -newer \"$plist\")\" ]; then\nbuildnum=$(/usr/libexec/Plistbuddy -c \"Print CFBundleVersion\" \"$plist\")\nif [ -z \"$buildnum\" ]; then\necho \"No build number in $plist\"\nexit 2\nfi\nbuildnum=$(expr $buildnum + 1)\n/usr/libexec/Plistbuddy -c \"Set CFBundleVersion $buildnum\" \"$plist\"\necho \"Incremented build number to $buildnum\"\nelse\necho \"Not incrementing build number as source files have not changed\"\nfi\n"; 313 | }; 314 | /* End PBXShellScriptBuildPhase section */ 315 | 316 | /* Begin PBXSourcesBuildPhase section */ 317 | E9F13DFA1C28B3D3004C6B95 /* Sources */ = { 318 | isa = PBXSourcesBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | E9AB18011C29F8CD00BF3C51 /* Serial.m in Sources */, 322 | E9109B191C2AAFE100726111 /* GPUStats.m in Sources */, 323 | E9101B8B1C36C8D400720A6E /* AudioVisualizer.m in Sources */, 324 | E9640E8A1C30511B0081D46C /* Screenshot.m in Sources */, 325 | E9F13E061C28B3D3004C6B95 /* main.m in Sources */, 326 | E9F13E031C28B3D3004C6B95 /* AppDelegate.m in Sources */, 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | /* End PBXSourcesBuildPhase section */ 331 | 332 | /* Begin PBXTargetDependency section */ 333 | E9101B861C36B10C00720A6E /* PBXTargetDependency */ = { 334 | isa = PBXTargetDependency; 335 | name = EZAudioOSX; 336 | targetProxy = E9101B851C36B10C00720A6E /* PBXContainerItemProxy */; 337 | }; 338 | E9109B131C2AAD2E00726111 /* PBXTargetDependency */ = { 339 | isa = PBXTargetDependency; 340 | name = SystemInfoKit; 341 | targetProxy = E9109B121C2AAD2E00726111 /* PBXContainerItemProxy */; 342 | }; 343 | /* End PBXTargetDependency section */ 344 | 345 | /* Begin PBXVariantGroup section */ 346 | E9F13E091C28B3D3004C6B95 /* MainMenu.xib */ = { 347 | isa = PBXVariantGroup; 348 | children = ( 349 | E9F13E0A1C28B3D3004C6B95 /* Base */, 350 | ); 351 | name = MainMenu.xib; 352 | sourceTree = ""; 353 | }; 354 | /* End PBXVariantGroup section */ 355 | 356 | /* Begin XCBuildConfiguration section */ 357 | E9F13E0D1C28B3D3004C6B95 /* Debug */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 362 | CLANG_CXX_LIBRARY = "libc++"; 363 | CLANG_ENABLE_MODULES = YES; 364 | CLANG_ENABLE_OBJC_ARC = YES; 365 | CLANG_WARN_BOOL_CONVERSION = YES; 366 | CLANG_WARN_CONSTANT_CONVERSION = YES; 367 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 368 | CLANG_WARN_EMPTY_BODY = YES; 369 | CLANG_WARN_ENUM_CONVERSION = YES; 370 | CLANG_WARN_INT_CONVERSION = YES; 371 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 372 | CLANG_WARN_UNREACHABLE_CODE = YES; 373 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 374 | CODE_SIGN_IDENTITY = "-"; 375 | COPY_PHASE_STRIP = NO; 376 | DEBUG_INFORMATION_FORMAT = dwarf; 377 | ENABLE_STRICT_OBJC_MSGSEND = YES; 378 | ENABLE_TESTABILITY = YES; 379 | GCC_C_LANGUAGE_STANDARD = gnu99; 380 | GCC_DYNAMIC_NO_PIC = NO; 381 | GCC_NO_COMMON_BLOCKS = YES; 382 | GCC_OPTIMIZATION_LEVEL = 0; 383 | GCC_PREPROCESSOR_DEFINITIONS = ( 384 | "DEBUG=1", 385 | "$(inherited)", 386 | ); 387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 389 | GCC_WARN_UNDECLARED_SELECTOR = YES; 390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 391 | GCC_WARN_UNUSED_FUNCTION = YES; 392 | GCC_WARN_UNUSED_VARIABLE = YES; 393 | MACOSX_DEPLOYMENT_TARGET = 10.8; 394 | MTL_ENABLE_DEBUG_INFO = YES; 395 | ONLY_ACTIVE_ARCH = YES; 396 | SDKROOT = macosx; 397 | }; 398 | name = Debug; 399 | }; 400 | E9F13E0E1C28B3D3004C6B95 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | ALWAYS_SEARCH_USER_PATHS = NO; 404 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 405 | CLANG_CXX_LIBRARY = "libc++"; 406 | CLANG_ENABLE_MODULES = YES; 407 | CLANG_ENABLE_OBJC_ARC = YES; 408 | CLANG_WARN_BOOL_CONVERSION = YES; 409 | CLANG_WARN_CONSTANT_CONVERSION = YES; 410 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 411 | CLANG_WARN_EMPTY_BODY = YES; 412 | CLANG_WARN_ENUM_CONVERSION = YES; 413 | CLANG_WARN_INT_CONVERSION = YES; 414 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 415 | CLANG_WARN_UNREACHABLE_CODE = YES; 416 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 417 | CODE_SIGN_IDENTITY = "-"; 418 | COPY_PHASE_STRIP = NO; 419 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 420 | ENABLE_NS_ASSERTIONS = NO; 421 | ENABLE_STRICT_OBJC_MSGSEND = YES; 422 | GCC_C_LANGUAGE_STANDARD = gnu99; 423 | GCC_NO_COMMON_BLOCKS = YES; 424 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 425 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 426 | GCC_WARN_UNDECLARED_SELECTOR = YES; 427 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 428 | GCC_WARN_UNUSED_FUNCTION = YES; 429 | GCC_WARN_UNUSED_VARIABLE = YES; 430 | MACOSX_DEPLOYMENT_TARGET = 10.8; 431 | MTL_ENABLE_DEBUG_INFO = NO; 432 | SDKROOT = macosx; 433 | }; 434 | name = Release; 435 | }; 436 | E9F13E101C28B3D3004C6B95 /* Debug */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 440 | COMBINE_HIDPI_IMAGES = YES; 441 | INFOPLIST_FILE = CaseLights/Info.plist; 442 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 443 | MACOSX_DEPLOYMENT_TARGET = 10.8; 444 | PRODUCT_BUNDLE_IDENTIFIER = de.xythobuz.CaseLights; 445 | PRODUCT_NAME = "$(TARGET_NAME)"; 446 | USER_HEADER_SEARCH_PATHS = "JSystemInfoKit/** EZAudio/EZAudio/**"; 447 | }; 448 | name = Debug; 449 | }; 450 | E9F13E111C28B3D3004C6B95 /* Release */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 454 | COMBINE_HIDPI_IMAGES = YES; 455 | HEADER_SEARCH_PATHS = ""; 456 | INFOPLIST_FILE = CaseLights/Info.plist; 457 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 458 | MACOSX_DEPLOYMENT_TARGET = 10.8; 459 | PRODUCT_BUNDLE_IDENTIFIER = de.xythobuz.CaseLights; 460 | PRODUCT_NAME = "$(TARGET_NAME)"; 461 | USER_HEADER_SEARCH_PATHS = "JSystemInfoKit/** EZAudio/EZAudio/**"; 462 | }; 463 | name = Release; 464 | }; 465 | /* End XCBuildConfiguration section */ 466 | 467 | /* Begin XCConfigurationList section */ 468 | E9F13DF91C28B3D3004C6B95 /* Build configuration list for PBXProject "CaseLights" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | E9F13E0D1C28B3D3004C6B95 /* Debug */, 472 | E9F13E0E1C28B3D3004C6B95 /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Release; 476 | }; 477 | E9F13E0F1C28B3D3004C6B95 /* Build configuration list for PBXNativeTarget "CaseLights" */ = { 478 | isa = XCConfigurationList; 479 | buildConfigurations = ( 480 | E9F13E101C28B3D3004C6B95 /* Debug */, 481 | E9F13E111C28B3D3004C6B95 /* Release */, 482 | ); 483 | defaultConfigurationIsVisible = 0; 484 | defaultConfigurationName = Release; 485 | }; 486 | /* End XCConfigurationList section */ 487 | }; 488 | rootObject = E9F13DF61C28B3D3004C6B95 /* Project object */; 489 | } 490 | -------------------------------------------------------------------------------- /CaseLights/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CaseLights 4 | // 5 | // Created by Thomas Buck on 21.12.15. 6 | // Copyright © 2015 xythobuz. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "Serial.h" 11 | #import "GPUStats.h" 12 | #import "Screenshot.h" 13 | #import "AudioVisualizer.h" 14 | 15 | // These are the values stored persistently in the preferences 16 | #define PREF_SERIAL_PORT @"SerialPort" 17 | #define PREF_LIGHTS_STATE @"LightState" 18 | // LED Mode contains the last selected mode as menu item text 19 | #define PREF_LED_MODE @"LEDMode" 20 | #define PREF_BRIGHTNESS @"Brightness" 21 | #define PREF_COLOR @"ManualColor" 22 | #define PREF_SENSITIVITY @"Sensitivity" 23 | #define PREF_COLORED_ICON @"ColorizeIcon" 24 | 25 | #define TEXT_MANUAL @"Select..." 26 | #define TEXT_CPU_USAGE @"CPU Usage" 27 | #define TEXT_RAM_USAGE @"RAM Usage" 28 | #define TEXT_GPU_USAGE @"GPU Usage" 29 | #define TEXT_VRAM_USAGE @"VRAM Usage" 30 | #define TEXT_CPU_TEMPERATURE @"CPU Temperature" 31 | #define TEXT_GPU_TEMPERATURE @"GPU Temperature" 32 | #define TEXT_RGB_FADE @"RGB Fade" 33 | #define TEXT_HSV_FADE @"HSV Fade" 34 | #define TEXT_RANDOM @"Random" 35 | #define TEXT_TEMPLATE_AUDIO @"AudioDevice_%@" 36 | 37 | // SMC keys are checked for existence and used for reading 38 | #define KEY_CPU_TEMPERATURE @"TC0D" 39 | #define KEY_GPU_TEMPERATURE @"TG0D" 40 | 41 | // Temperature in Celsius 42 | #define CPU_TEMP_MIN 20 43 | #define CPU_TEMP_MAX 90 44 | 45 | // HSV Color (S = V = 1) 46 | #define CPU_COLOR_MIN 120 47 | #define CPU_COLOR_MAX 0 48 | 49 | #define GPU_TEMP_MIN 20 50 | #define GPU_TEMP_MAX 90 51 | #define GPU_COLOR_MIN 120 52 | #define GPU_COLOR_MAX 0 53 | 54 | #define RAM_COLOR_MIN 0 55 | #define RAM_COLOR_MAX 120 56 | 57 | // You can play around with these values (skipped pixels, display timer delay) to change CPU usage in display mode 58 | #define AVERAGE_COLOR_PERFORMANCE_INC 10 59 | #define DISPLAY_DELAY 0.1 60 | 61 | // Used to identify selected menu items 62 | // displays are all tags >= 0 63 | #define MENU_ITEM_TAG_NOTHING -1 64 | #define MENU_ITEM_TAG_AUDIO -2 65 | 66 | @interface AppDelegate () 67 | 68 | @property (weak) IBOutlet NSMenu *statusMenu; 69 | 70 | @property (weak) IBOutlet NSMenu *menuColors; 71 | @property (weak) IBOutlet NSMenu *menuAnimations; 72 | @property (weak) IBOutlet NSMenu *menuVisualizations; 73 | @property (weak) IBOutlet NSMenuItem *menuItemDisplays; 74 | @property (weak) IBOutlet NSMenu *menuDisplays; 75 | @property (weak) IBOutlet NSMenuItem *menuItemAudio; 76 | @property (weak) IBOutlet NSMenu *menuAudio; 77 | @property (weak) IBOutlet NSMenu *menuPorts; 78 | @property (weak) IBOutlet NSMenuItem *buttonOff; 79 | @property (weak) IBOutlet NSMenuItem *brightnessItem; 80 | @property (weak) IBOutlet NSSlider *brightnessSlider; 81 | @property (weak) IBOutlet NSMenuItem *brightnessLabel; 82 | @property (weak) IBOutlet NSMenuItem *buttonLights; 83 | @property (weak) IBOutlet NSMenuItem *sensitivityItem; 84 | @property (weak) IBOutlet NSSlider *sensitivitySlider; 85 | @property (weak) IBOutlet NSMenuItem *sensitivityLabel; 86 | @property (weak) IBOutlet NSMenuItem *sensitivityMenu; 87 | @property (weak) IBOutlet NSMenuItem *colorizeIconItem; 88 | 89 | @property (strong) NSMenuItem *menuItemColor; 90 | 91 | @property (strong) NSStatusItem *statusItem; 92 | @property (strong) NSImage *statusImage; 93 | @property (strong) NSDictionary *staticColors; 94 | @property (strong) NSTimer *animation; 95 | @property (strong) Serial *serial; 96 | @property (strong) NSMenuItem *lastLEDMode; 97 | 98 | @end 99 | 100 | @implementation AppDelegate 101 | 102 | @synthesize statusMenu, application; 103 | @synthesize menuColors, menuAnimations, menuVisualizations, menuPorts; 104 | @synthesize menuItemDisplays, menuDisplays; 105 | @synthesize menuItemAudio, menuAudio; 106 | @synthesize buttonOff, buttonLights; 107 | @synthesize brightnessItem, brightnessSlider, brightnessLabel; 108 | @synthesize sensitivityItem, sensitivitySlider, sensitivityLabel, sensitivityMenu; 109 | @synthesize statusItem, statusImage; 110 | @synthesize staticColors, animation; 111 | @synthesize serial, lastLEDMode, microphone; 112 | @synthesize menuItemColor, colorizeIconItem; 113 | 114 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 115 | srand((unsigned)time(NULL)); 116 | [AudioVisualizer setDelegate:self]; 117 | 118 | serial = [[Serial alloc] init]; 119 | lastLEDMode = nil; 120 | animation = nil; 121 | microphone = nil; 122 | 123 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(darkModeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil]; 124 | 125 | // Set default configuration values, load existing ones 126 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 127 | NSMutableDictionary *appDefaults = [NSMutableDictionary dictionaryWithObject:@"" forKey:PREF_SERIAL_PORT]; 128 | [appDefaults setObject:[NSNumber numberWithBool:NO] forKey:PREF_LIGHTS_STATE]; 129 | [appDefaults setObject:@"" forKey:PREF_LED_MODE]; 130 | [appDefaults setObject:[NSNumber numberWithFloat:50.0] forKey:PREF_BRIGHTNESS]; 131 | [appDefaults setObject:[NSNumber numberWithFloat:100.0] forKey:PREF_SENSITIVITY]; 132 | [appDefaults setObject:[NSNumber numberWithBool:YES] forKey:PREF_COLORED_ICON]; 133 | [store registerDefaults:appDefaults]; 134 | [store synchronize]; 135 | NSString *savedPort = [store stringForKey:PREF_SERIAL_PORT]; 136 | BOOL turnOnLights = [store boolForKey:PREF_LIGHTS_STATE]; 137 | NSString *lastMode = [store stringForKey:PREF_LED_MODE]; 138 | float brightness = [store floatForKey:PREF_BRIGHTNESS]; 139 | NSData *lastColorData = [store dataForKey:PREF_COLOR]; 140 | float sensitivity = [store floatForKey:PREF_SENSITIVITY]; 141 | NSColor *lastColor = nil; 142 | if (lastColorData != nil) { 143 | lastColor = (NSColor *)[NSUnarchiver unarchiveObjectWithData:lastColorData]; 144 | } 145 | BOOL coloredIcon = [store boolForKey:PREF_COLORED_ICON]; 146 | 147 | // Prepare status bar menu 148 | statusImage = [NSImage imageNamed:@"MenuIcon"]; 149 | statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; 150 | if (coloredIcon == YES) { 151 | [statusImage setTemplate:NO]; 152 | [statusItem setImage:[AppDelegate tintedImage:statusImage WithColor:[NSColor colorWithCalibratedRed:0.0f green:0.0f blue:0.0f alpha:1.0f]]]; 153 | [colorizeIconItem setState:NSOnState]; 154 | } else { 155 | [statusImage setTemplate:YES]; 156 | [statusItem setImage:statusImage]; 157 | [colorizeIconItem setState:NSOffState]; 158 | } 159 | [statusItem setMenu:statusMenu]; 160 | 161 | // Prepare brightness menu 162 | brightnessItem.view = brightnessSlider; 163 | [brightnessSlider setFloatValue:brightness]; 164 | [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", brightness]]; 165 | 166 | // Prepare serial port menu 167 | NSArray *ports = [Serial listSerialPorts]; 168 | if ([ports count] > 0) { 169 | [menuPorts removeAllItems]; 170 | for (int i = 0; i < [ports count]; i++) { 171 | // Add Menu Item for this port 172 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""]; 173 | [item setTag:MENU_ITEM_TAG_NOTHING]; 174 | [menuPorts addItem:item]; 175 | 176 | // Set Enabled if it was used the last time 177 | if ((savedPort != nil) && [[ports objectAtIndex:i] isEqualToString:savedPort]) { 178 | [[menuPorts itemAtIndex:i] setState:NSOnState]; 179 | 180 | // Try to open serial port 181 | [serial setPortName:savedPort]; 182 | if ([serial openPort]) { 183 | // Unselect it when an error occured opening the port 184 | [[menuPorts itemAtIndex:i] setState:NSOffState]; 185 | } 186 | } 187 | } 188 | } 189 | 190 | // Select "Off" button if it was last selected 191 | if ([lastMode isEqualToString:@""]) { 192 | [buttonOff setState:NSOffState]; 193 | [self turnLEDsOff:buttonOff]; 194 | } 195 | 196 | // Prepare static colors menu 197 | staticColors = [NSDictionary dictionaryWithObjectsAndKeys: 198 | [NSColor colorWithCalibratedRed:1.0f green:0.0f blue:0.0f alpha:0.0f], @"Red", 199 | [NSColor colorWithCalibratedRed:0.0f green:1.0f blue:0.0f alpha:0.0f], @"Green", 200 | [NSColor colorWithCalibratedRed:0.0f green:0.0f blue:1.0f alpha:0.0f], @"Blue", 201 | [NSColor colorWithCalibratedRed:0.0f green:1.0f blue:1.0f alpha:0.0f], @"Cyan", 202 | [NSColor colorWithCalibratedRed:1.0f green:0.0f blue:1.0f alpha:0.0f], @"Magenta", 203 | [NSColor colorWithCalibratedRed:1.0f green:1.0f blue:0.0f alpha:0.0f], @"Yellow", 204 | [NSColor colorWithCalibratedRed:1.0f green:1.0f blue:1.0f alpha:0.0f], @"White", 205 | nil]; 206 | for (NSString *key in [staticColors allKeys]) { 207 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key action:@selector(selectedVisualization:) keyEquivalent:@""]; 208 | [item setTag:MENU_ITEM_TAG_NOTHING]; 209 | if ([key isEqualToString:lastMode]) { 210 | [self selectedVisualization:item]; 211 | } 212 | [menuColors addItem:item]; 213 | } 214 | menuItemColor = [[NSMenuItem alloc] initWithTitle:TEXT_MANUAL action:@selector(setColorSelected:) keyEquivalent:@""]; 215 | if ([lastMode isEqualToString:TEXT_MANUAL]) { 216 | if (lastColor != nil) { 217 | // Restore previously set RGB color 218 | [self setLightsColor:lastColor]; 219 | } 220 | [menuItemColor setState:NSOnState]; 221 | } 222 | [menuItemColor setTag:MENU_ITEM_TAG_NOTHING]; 223 | [menuColors addItem:menuItemColor]; 224 | 225 | // Prepare animations menu 226 | NSArray *animationStrings = [NSArray arrayWithObjects: 227 | TEXT_RGB_FADE, 228 | TEXT_HSV_FADE, 229 | TEXT_RANDOM, 230 | nil]; 231 | for (NSString *key in animationStrings) { 232 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key action:@selector(selectedVisualization:) keyEquivalent:@""]; 233 | [item setTag:MENU_ITEM_TAG_NOTHING]; 234 | if ([key isEqualToString:lastMode]) { 235 | [self selectedVisualization:item]; 236 | } 237 | [menuAnimations addItem:item]; 238 | } 239 | 240 | // Add CPU Usage menu item 241 | NSMenuItem *cpuUsageItem = [[NSMenuItem alloc] initWithTitle:TEXT_CPU_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""]; 242 | [cpuUsageItem setTag:MENU_ITEM_TAG_NOTHING]; 243 | if ([lastMode isEqualToString:TEXT_CPU_USAGE]) { 244 | [self selectedVisualization:cpuUsageItem]; 245 | } 246 | [menuVisualizations addItem:cpuUsageItem]; 247 | 248 | // Add Memory Usage item 249 | NSMenuItem *memoryUsageItem = [[NSMenuItem alloc] initWithTitle:TEXT_RAM_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""]; 250 | [memoryUsageItem setTag:MENU_ITEM_TAG_NOTHING]; 251 | if ([lastMode isEqualToString:TEXT_RAM_USAGE]) { 252 | [self selectedVisualization:memoryUsageItem]; 253 | } 254 | [menuVisualizations addItem:memoryUsageItem]; 255 | 256 | // Check if GPU Stats are available, add menu items if so 257 | NSNumber *usage; 258 | NSNumber *freeVRAM; 259 | NSNumber *usedVRAM; 260 | if ([GPUStats getGPUUsage:&usage freeVRAM:&freeVRAM usedVRAM:&usedVRAM] != 0) { 261 | NSLog(@"Error reading GPU information\n"); 262 | } else { 263 | NSMenuItem *itemUsage = [[NSMenuItem alloc] initWithTitle:TEXT_GPU_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""]; 264 | [itemUsage setTag:MENU_ITEM_TAG_NOTHING]; 265 | if ([lastMode isEqualToString:TEXT_GPU_USAGE]) { 266 | [self selectedVisualization:itemUsage]; 267 | } 268 | [menuVisualizations addItem:itemUsage]; 269 | 270 | NSMenuItem *itemVRAM = [[NSMenuItem alloc] initWithTitle:TEXT_VRAM_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""]; 271 | [itemVRAM setTag:MENU_ITEM_TAG_NOTHING]; 272 | if ([lastMode isEqualToString:TEXT_VRAM_USAGE]) { 273 | [self selectedVisualization:itemVRAM]; 274 | } 275 | [menuVisualizations addItem:itemVRAM]; 276 | } 277 | 278 | // Check available temperatures and add menu items 279 | JSKSMC *smc = [JSKSMC smc]; 280 | for (int i = 0; i < [[smc workingTempKeys] count]; i++) { 281 | NSString *key = [smc.workingTempKeys objectAtIndex:i]; 282 | 283 | #ifdef DEBUG 284 | NSString *name = [smc humanReadableNameForKey:key]; 285 | NSLog(@"Sensor \"%@\": \"%@\"\n", key, name); 286 | #endif 287 | 288 | if ([key isEqualToString:KEY_CPU_TEMPERATURE]) { 289 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:TEXT_CPU_TEMPERATURE action:@selector(selectedVisualization:) keyEquivalent:@""]; 290 | [item setTag:MENU_ITEM_TAG_NOTHING]; 291 | if ([lastMode isEqualToString:TEXT_CPU_TEMPERATURE]) { 292 | [self selectedVisualization:item]; 293 | } 294 | [menuVisualizations addItem:item]; 295 | } 296 | 297 | if ([key isEqualToString:KEY_GPU_TEMPERATURE]) { 298 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:TEXT_GPU_TEMPERATURE action:@selector(selectedVisualization:) keyEquivalent:@""]; 299 | [item setTag:MENU_ITEM_TAG_NOTHING]; 300 | if ([lastMode isEqualToString:TEXT_GPU_TEMPERATURE]) { 301 | [self selectedVisualization:item]; 302 | } 303 | [menuVisualizations addItem:item]; 304 | } 305 | } 306 | 307 | // Restore previously used lights configuration 308 | if (turnOnLights) { 309 | // Turn on lights 310 | if ([serial isOpen]) { 311 | [serial sendString:@"UV 1\n"]; 312 | } 313 | 314 | [buttonLights setState:NSOnState]; 315 | } else { 316 | // Turn off lights 317 | if ([serial isOpen]) { 318 | [serial sendString:@"UV 0\n"]; 319 | } 320 | } 321 | 322 | // List available displays and add menu items 323 | [Screenshot init:self]; 324 | NSArray *displayIDs = [Screenshot listDisplays]; 325 | [self updateDisplayUI:displayIDs]; 326 | 327 | // List available audio input devices and add menu items 328 | NSArray *inputDevices = [EZAudioDevice inputDevices]; 329 | [menuAudio removeAllItems]; 330 | for (int i = 0; i < [inputDevices count]; i++) { 331 | EZAudioDevice *dev = [inputDevices objectAtIndex:i]; 332 | 333 | #ifdef DEBUG 334 | NSLog(@"Audio input device: \"%@\"\n", [dev name]); 335 | #endif 336 | 337 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[dev name] action:@selector(selectedVisualization:) keyEquivalent:@""]; 338 | [item setTag:MENU_ITEM_TAG_AUDIO]; 339 | NSString *lastModeString = [NSString stringWithFormat:TEXT_TEMPLATE_AUDIO, [dev name]]; 340 | if ([lastModeString isEqualToString:lastMode]) { 341 | [self selectedVisualization:item]; 342 | } 343 | [menuAudio addItem:item]; 344 | } 345 | if ([inputDevices count] > 0) { 346 | [menuItemAudio setHidden:NO]; 347 | 348 | // Prepare sensitivity menu 349 | sensitivityItem.view = sensitivitySlider; 350 | [sensitivitySlider setFloatValue:sensitivity]; 351 | [sensitivityLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", sensitivity]]; 352 | [sensitivityMenu setHidden:NO]; 353 | [AudioVisualizer setSensitivity:sensitivity]; 354 | } 355 | } 356 | 357 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 358 | // Stop previous timer setting 359 | if (animation != nil) { 360 | [animation invalidate]; 361 | animation = nil; 362 | } 363 | 364 | // Stop previous audio data retrieval 365 | if (microphone != nil) { 366 | [microphone setMicrophoneOn:NO]; 367 | } 368 | 369 | // Remove display callback 370 | [Screenshot close:self]; 371 | 372 | // Turn off all lights if possible 373 | if ([serial isOpen]) { 374 | [serial sendString:@"RGB 0 0 0\n"]; 375 | [serial sendString:@"UV 0\n"]; 376 | [serial closePort]; 377 | } 378 | } 379 | 380 | - (void)clearDisplayUI { 381 | for (int i = 0; i < [menuDisplays numberOfItems]; i++) { 382 | if ([[menuDisplays itemAtIndex:i] isEnabled] == YES) { 383 | // A display configuration is currently selected. Disable the timer 384 | if (animation != nil) { 385 | [animation invalidate]; 386 | animation = nil; 387 | } 388 | } 389 | } 390 | [menuDisplays removeAllItems]; 391 | [menuItemDisplays setHidden:YES]; 392 | } 393 | 394 | - (void)updateDisplayUI:(NSArray *)displayIDs { 395 | if ([displayIDs count] > 0) { 396 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 397 | NSString *lastMode = [store stringForKey:PREF_LED_MODE]; 398 | [menuItemDisplays setHidden:NO]; 399 | for (int i = 0; i < [displayIDs count]; i++) { 400 | NSString *title = [Screenshot displayNameFromDisplayID:[displayIDs objectAtIndex:i]]; 401 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title 402 | action:@selector(selectedVisualization:) 403 | keyEquivalent:@""]; 404 | [item setTag:[[displayIDs objectAtIndex:i] integerValue]]; 405 | if ([title isEqualToString:lastMode]) { 406 | [self selectedVisualization:item]; 407 | } 408 | [menuDisplays addItem:item]; 409 | } 410 | } 411 | } 412 | 413 | - (void)setLightsColor:(NSColor *)color { 414 | CGFloat red, green, blue, alpha; 415 | [color getRed:&red green:&green blue:&blue alpha:&alpha]; 416 | [self setLightsR:red * 255 G:green * 255 B:blue * 255]; 417 | 418 | // Stop previous timer setting 419 | if (animation != nil) { 420 | [animation invalidate]; 421 | animation = nil; 422 | } 423 | 424 | // Stop previous audio data retrieval 425 | if (microphone != nil) { 426 | [microphone setMicrophoneOn:NO]; 427 | } 428 | 429 | // Turn off all other LED menu items 430 | if (menuColors != nil) { 431 | for (int i = 0; i < [menuColors numberOfItems]; i++) { 432 | [[menuColors itemAtIndex:i] setState:NSOffState]; 433 | } 434 | } 435 | if (menuAnimations != nil) { 436 | for (int i = 0; i < [menuAnimations numberOfItems]; i++) { 437 | [[menuAnimations itemAtIndex:i] setState:NSOffState]; 438 | } 439 | } 440 | if (menuVisualizations != nil) { 441 | for (int i = 0; i < [menuVisualizations numberOfItems]; i++) { 442 | [[menuVisualizations itemAtIndex:i] setState:NSOffState]; 443 | } 444 | } 445 | if (menuAudio != nil) { 446 | for (int i = 0; i < [menuAudio numberOfItems]; i++) { 447 | [[menuAudio itemAtIndex:i] setState:NSOffState]; 448 | } 449 | } 450 | if (menuDisplays != nil) { 451 | for (int i = 0; i < [menuDisplays numberOfItems]; i++) { 452 | [[menuDisplays itemAtIndex:i] setState:NSOffState]; 453 | } 454 | } 455 | [buttonOff setState:NSOffState]; 456 | [menuItemColor setState:NSOnState]; 457 | 458 | // Store new manually selected color 459 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 460 | NSData *data = [NSArchiver archivedDataWithRootObject:color]; 461 | [store setObject:data forKey:PREF_COLOR]; 462 | [store setObject:TEXT_MANUAL forKey:PREF_LED_MODE]; 463 | [store synchronize]; 464 | } 465 | 466 | - (void)setLightsR:(unsigned char)r G:(unsigned char)g B:(unsigned char)b { 467 | if ([serial isOpen]) { 468 | unsigned char red = r * ([brightnessSlider floatValue] / 100.0); 469 | unsigned char green = g * ([brightnessSlider floatValue] / 100.0); 470 | unsigned char blue = b * ([brightnessSlider floatValue] / 100.0); 471 | [serial sendString:[NSString stringWithFormat:@"RGB %d %d %d\n", red, green, blue]]; 472 | } else { 473 | #ifdef DEBUG 474 | NSLog(@"Trying to send RGB without opened port!\n"); 475 | #endif 476 | } 477 | 478 | if (colorizeIconItem.state == NSOnState) { 479 | [statusItem setImage:[AppDelegate tintedImage:statusImage WithColor:[NSColor colorWithCalibratedRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:1.0f]]]; 480 | } 481 | } 482 | 483 | - (IBAction)colorizeIconSelected:(NSMenuItem *)sender { 484 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 485 | if ([sender state] == NSOffState) { 486 | // Colorize icon 487 | [statusImage setTemplate:NO]; 488 | [statusItem setImage:[AppDelegate tintedImage:statusImage WithColor:[NSColor colorWithCalibratedRed:0.0f green:0.0f blue:0.0f alpha:1.0f]]]; 489 | [colorizeIconItem setState:NSOnState]; 490 | [store setObject:[NSNumber numberWithBool:YES] forKey:PREF_COLORED_ICON]; 491 | } else { 492 | // Don't colorize icon 493 | [statusImage setTemplate:YES]; 494 | [statusItem setImage:statusImage]; 495 | [colorizeIconItem setState:NSOffState]; 496 | [store setObject:[NSNumber numberWithBool:NO] forKey:PREF_COLORED_ICON]; 497 | } 498 | [store synchronize]; 499 | } 500 | 501 | - (IBAction)relistSerialPorts:(id)sender { 502 | // Refill audio device list 503 | NSArray *inputDevices = [EZAudioDevice inputDevices]; 504 | [menuAudio removeAllItems]; 505 | for (int i = 0; i < [inputDevices count]; i++) { 506 | EZAudioDevice *dev = [inputDevices objectAtIndex:i]; 507 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[dev name] action:@selector(selectedVisualization:) keyEquivalent:@""]; 508 | [item setTag:MENU_ITEM_TAG_AUDIO]; 509 | NSString *lastModeString = [NSString stringWithFormat:TEXT_TEMPLATE_AUDIO, [dev name]]; 510 | if ([lastModeString isEqualToString:[[NSUserDefaults standardUserDefaults] stringForKey:PREF_LED_MODE]]) { 511 | [self selectedVisualization:item]; 512 | } 513 | [menuAudio addItem:item]; 514 | } 515 | if ([inputDevices count] > 0) { 516 | [menuItemAudio setHidden:NO]; 517 | } else { 518 | [menuItemAudio setHidden:YES]; 519 | } 520 | 521 | // Refill port list 522 | NSArray *ports = [Serial listSerialPorts]; 523 | [menuPorts removeAllItems]; 524 | for (int i = 0; i < [ports count]; i++) { 525 | // Add Menu Item for this port 526 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""]; 527 | [item setTag:MENU_ITEM_TAG_NOTHING]; 528 | [menuPorts addItem:item]; 529 | 530 | // Mark it if it is currently open 531 | if ([serial isOpen]) { 532 | if ([[ports objectAtIndex:i] isEqualToString:[serial portName]]) { 533 | [[menuPorts itemAtIndex:i] setState:NSOnState]; 534 | } 535 | } 536 | } 537 | } 538 | 539 | - (void)setColorSelected:(NSMenuItem *)sender { 540 | NSColorPanel *cp = [NSColorPanel sharedColorPanel]; 541 | [cp setTarget:self]; 542 | [cp setAction:@selector(colorSelected:)]; 543 | [cp setShowsAlpha:NO]; 544 | [cp setContinuous:NO]; 545 | [cp setMode:NSRGBModeColorPanel]; 546 | 547 | // Try to restore last manually selected color 548 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 549 | NSData *lastColorData = [store dataForKey:PREF_COLOR]; 550 | NSColor *lastColor = nil; 551 | if (lastColorData != nil) { 552 | lastColor = (NSColor *)[NSUnarchiver unarchiveObjectWithData:lastColorData]; 553 | [cp setColor:lastColor]; 554 | } 555 | 556 | [NSApp activateIgnoringOtherApps:YES]; 557 | [application orderFrontColorPanel:cp]; 558 | } 559 | 560 | - (void)colorSelected:(NSColorPanel *)sender { 561 | [self setLightsColor:[sender color]]; 562 | } 563 | 564 | - (IBAction)sensitivityMoved:(NSSlider *)sender { 565 | [sensitivityLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", [sender floatValue]]]; 566 | [AudioVisualizer setSensitivity:[sender floatValue]]; 567 | 568 | // Store changed value in preferences 569 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 570 | [store setObject:[NSNumber numberWithFloat:[sender floatValue]] forKey:PREF_SENSITIVITY]; 571 | [store synchronize]; 572 | } 573 | 574 | - (IBAction)brightnessMoved:(NSSlider *)sender { 575 | [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", [sender floatValue]]]; 576 | 577 | // Restore the current configuration for items where it won't happen automatically 578 | for (int i = 0; i < [menuColors numberOfItems]; i++) { 579 | if ([[menuColors itemAtIndex:i] state] == NSOnState) { 580 | [self selectedVisualization:[menuColors itemAtIndex:i]]; 581 | } 582 | } 583 | 584 | // Store changed value in preferences 585 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 586 | [store setObject:[NSNumber numberWithFloat:[sender floatValue]] forKey:PREF_BRIGHTNESS]; 587 | [store synchronize]; 588 | } 589 | 590 | - (IBAction)turnLEDsOff:(NSMenuItem *)sender { 591 | if ([sender state] == NSOffState) { 592 | lastLEDMode = nil; 593 | 594 | // Stop previous timer setting 595 | if (animation != nil) { 596 | [animation invalidate]; 597 | animation = nil; 598 | } 599 | 600 | // Stop previous audio data retrieval 601 | if (microphone != nil) { 602 | [microphone setMicrophoneOn:NO]; 603 | } 604 | 605 | // Turn off all other LED menu items 606 | for (int i = 0; i < [menuColors numberOfItems]; i++) { 607 | if ([[menuColors itemAtIndex:i] state] == NSOnState) { 608 | lastLEDMode = [menuColors itemAtIndex:i]; 609 | } 610 | [[menuColors itemAtIndex:i] setState:NSOffState]; 611 | } 612 | for (int i = 0; i < [menuAnimations numberOfItems]; i++) { 613 | if ([[menuAnimations itemAtIndex:i] state] == NSOnState) { 614 | lastLEDMode = [menuAnimations itemAtIndex:i]; 615 | } 616 | [[menuAnimations itemAtIndex:i] setState:NSOffState]; 617 | } 618 | for (int i = 0; i < [menuVisualizations numberOfItems]; i++) { 619 | if ([[menuVisualizations itemAtIndex:i] state] == NSOnState) { 620 | lastLEDMode = [menuVisualizations itemAtIndex:i]; 621 | } 622 | [[menuVisualizations itemAtIndex:i] setState:NSOffState]; 623 | } 624 | for (int i = 0; i < [menuAudio numberOfItems]; i++) { 625 | if ([[menuAudio itemAtIndex:i] state] == NSOnState) { 626 | lastLEDMode = [menuAudio itemAtIndex:i]; 627 | } 628 | [[menuAudio itemAtIndex:i] setState:NSOffState]; 629 | } 630 | for (int i = 0; i < [menuDisplays numberOfItems]; i++) { 631 | if ([[menuDisplays itemAtIndex:i] state] == NSOnState) { 632 | lastLEDMode = [menuDisplays itemAtIndex:i]; 633 | } 634 | [[menuDisplays itemAtIndex:i] setState:NSOffState]; 635 | } 636 | 637 | // Turn on "off" menu item 638 | [sender setState:NSOnState]; 639 | 640 | // Store changed value in preferences 641 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 642 | [store setObject:@"" forKey:PREF_LED_MODE]; 643 | [store synchronize]; 644 | 645 | #ifdef DEBUG 646 | NSLog(@"Stored new mode: \"off\"!\n"); 647 | #endif 648 | 649 | // Send command to turn off LEDs 650 | [self setLightsR:0 G:0 B:0]; 651 | 652 | // Close debug window, if created 653 | [AudioVisualizer setShowWindow:NO]; 654 | } else { 655 | // Try to restore last LED setting 656 | if (lastLEDMode != nil) { 657 | [self selectedVisualization:lastLEDMode]; 658 | } 659 | } 660 | } 661 | 662 | - (IBAction)toggleLights:(NSMenuItem *)sender { 663 | if ([sender state] == NSOffState) { 664 | // Turn on lights 665 | if ([serial isOpen]) { 666 | [serial sendString:@"UV 1\n"]; 667 | } 668 | 669 | [sender setState:NSOnState]; 670 | } else { 671 | // Turn off lights 672 | if ([serial isOpen]) { 673 | [serial sendString:@"UV 0\n"]; 674 | } 675 | 676 | [sender setState:NSOffState]; 677 | } 678 | 679 | // Store changed value in preferences 680 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 681 | [store setBool:([sender state] == NSOnState) forKey:PREF_LIGHTS_STATE]; 682 | [store synchronize]; 683 | } 684 | 685 | - (BOOL)timedVisualization:(NSString *)mode { 686 | // Stop previous timer setting 687 | if (animation != nil) { 688 | [animation invalidate]; 689 | animation = nil; 690 | } 691 | 692 | // Stop previous audio data retrieval 693 | if (microphone != nil) { 694 | [microphone setMicrophoneOn:NO]; 695 | } 696 | 697 | // Schedule next invocation for this animation... 698 | if ([mode isEqualToString:TEXT_GPU_USAGE]) { 699 | animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeGPUUsage:) userInfo:mode repeats:YES]; 700 | } else if ([mode isEqualToString:TEXT_VRAM_USAGE]) { 701 | animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeVRAMUsage:) userInfo:mode repeats:YES]; 702 | } else if ([mode isEqualToString:TEXT_CPU_USAGE]) { 703 | animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeCPUUsage:) userInfo:mode repeats:YES]; 704 | } else if ([mode isEqualToString:TEXT_RAM_USAGE]) { 705 | animation = [NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:@selector(visualizeRAMUsage:) userInfo:mode repeats:YES]; 706 | } else if ([mode isEqualToString:TEXT_CPU_TEMPERATURE]) { 707 | animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeCPUTemperature:) userInfo:mode repeats:YES]; 708 | } else if ([mode isEqualToString:TEXT_GPU_TEMPERATURE]) { 709 | animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeGPUTemperature:) userInfo:mode repeats:YES]; 710 | } else if ([mode isEqualToString:TEXT_RGB_FADE]) { 711 | animation = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(visualizeRGBFade:) userInfo:mode repeats:YES]; 712 | } else if ([mode isEqualToString:TEXT_HSV_FADE]) { 713 | animation = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(visualizeHSVFade:) userInfo:mode repeats:YES]; 714 | } else if ([mode isEqualToString:TEXT_RANDOM]) { 715 | animation = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(visualizeRandom:) userInfo:mode repeats:YES]; 716 | } else { 717 | return NO; 718 | } 719 | 720 | #ifdef DEBUG 721 | NSLog(@"Scheduled animation for \"%@\"!\n", mode); 722 | #endif 723 | 724 | // ...and also execute it right now 725 | [animation fire]; 726 | return YES; 727 | } 728 | 729 | - (void)displayVisualization:(NSMenuItem *)sender { 730 | // Stop previous timer setting 731 | if (animation != nil) { 732 | [animation invalidate]; 733 | animation = nil; 734 | } 735 | 736 | // Stop previous audio data retrieval 737 | if (microphone != nil) { 738 | [microphone setMicrophoneOn:NO]; 739 | } 740 | 741 | // Schedule next invocation for this animation... 742 | animation = [NSTimer scheduledTimerWithTimeInterval:DISPLAY_DELAY target:self selector:@selector(visualizeDisplay:) userInfo:[NSNumber numberWithInteger:[sender tag]] repeats:YES]; 743 | 744 | // ...and also execute it right now 745 | [animation fire]; 746 | } 747 | 748 | - (void)selectedVisualization:(NSMenuItem *)sender { 749 | // Turn off all other LED menu items 750 | if (menuColors != nil) { 751 | for (int i = 0; i < [menuColors numberOfItems]; i++) { 752 | [[menuColors itemAtIndex:i] setState:NSOffState]; 753 | } 754 | } 755 | if (menuAnimations != nil) { 756 | for (int i = 0; i < [menuAnimations numberOfItems]; i++) { 757 | [[menuAnimations itemAtIndex:i] setState:NSOffState]; 758 | } 759 | } 760 | if (menuVisualizations != nil) { 761 | for (int i = 0; i < [menuVisualizations numberOfItems]; i++) { 762 | [[menuVisualizations itemAtIndex:i] setState:NSOffState]; 763 | } 764 | } 765 | if (menuAudio != nil) { 766 | for (int i = 0; i < [menuAudio numberOfItems]; i++) { 767 | [[menuAudio itemAtIndex:i] setState:NSOffState]; 768 | } 769 | } 770 | if (menuDisplays != nil) { 771 | for (int i = 0; i < [menuDisplays numberOfItems]; i++) { 772 | [[menuDisplays itemAtIndex:i] setState:NSOffState]; 773 | } 774 | } 775 | [buttonOff setState:NSOffState]; 776 | [sender setState:NSOnState]; 777 | 778 | // Check if it is a display 779 | BOOL found = NO; 780 | if ([sender tag] > MENU_ITEM_TAG_NOTHING) { 781 | found = YES; 782 | [self displayVisualization:sender]; 783 | } 784 | 785 | // Check if it is an audio input device 786 | BOOL foundAudioDev = NO; 787 | if ((found == NO) && ([sender tag] == MENU_ITEM_TAG_AUDIO)) { 788 | // Stop previous timer setting 789 | if (animation != nil) { 790 | [animation invalidate]; 791 | animation = nil; 792 | } 793 | 794 | found = YES; 795 | NSArray *audioDevices = [EZAudioDevice inputDevices]; 796 | for (int i = 0; i < [audioDevices count]; i++) { 797 | EZAudioDevice *dev = [audioDevices objectAtIndex:i]; 798 | if ([[dev name] isEqualToString:[sender title]]) { 799 | // Send command to turn off LEDs 800 | [self setLightsR:0 G:0 B:0]; 801 | 802 | // Found device 803 | foundAudioDev = YES; 804 | if (microphone != nil) { 805 | [microphone setMicrophoneOn:NO]; 806 | } else { 807 | microphone = [EZMicrophone microphoneWithDelegate:self]; 808 | } 809 | [microphone setDevice:dev]; 810 | [microphone setMicrophoneOn:YES]; 811 | break; 812 | } 813 | } 814 | 815 | if (foundAudioDev == NO) { 816 | NSLog(@"Couldn't find device \"%@\"\n", [sender title]); 817 | [sender setState:NSOffState]; 818 | 819 | // List available audio input devices and add menu items 820 | NSArray *inputDevices = [EZAudioDevice inputDevices]; 821 | [menuAudio removeAllItems]; 822 | for (int i = 0; i < [inputDevices count]; i++) { 823 | EZAudioDevice *dev = [inputDevices objectAtIndex:i]; 824 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[dev name] action:@selector(selectedVisualization:) keyEquivalent:@""]; 825 | [item setTag:MENU_ITEM_TAG_AUDIO]; 826 | if ([[dev name] isEqualToString:[sender title]]) { 827 | // Found the device the user really wanted 828 | [self selectedVisualization:item]; 829 | } 830 | [menuAudio addItem:item]; 831 | } 832 | 833 | return; // Don't store new mode 834 | } else { 835 | // Show debug window if CTRL is held while clicking on audio device 836 | NSUInteger mods = [NSEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; 837 | if (mods & NSControlKeyMask) { 838 | [AudioVisualizer setShowWindow:YES]; 839 | } else { 840 | [AudioVisualizer setShowWindow:NO]; 841 | } 842 | } 843 | } 844 | 845 | // Check if it is the manual color select item 846 | if ((found == NO) && ([sender.title isEqualToString:TEXT_MANUAL])) { 847 | found = YES; 848 | [self colorSelected:[NSColorPanel sharedColorPanel]]; 849 | } 850 | 851 | // Check if a static color was selected 852 | if ((found == NO) && (staticColors != nil)) { 853 | for (NSString *key in [staticColors allKeys]) { 854 | if ([sender.title isEqualToString:key]) { 855 | found = YES; 856 | 857 | // Stop previous timer setting 858 | if (animation != nil) { 859 | [animation invalidate]; 860 | animation = nil; 861 | } 862 | 863 | // Stop previous audio data retrieval 864 | if (microphone != nil) { 865 | [microphone setMicrophoneOn:NO]; 866 | } 867 | 868 | NSColor *color = [staticColors valueForKey:key]; 869 | unsigned char red = [color redComponent] * 255; 870 | unsigned char green = [color greenComponent] * 255; 871 | unsigned char blue = [color blueComponent] * 255; 872 | [self setLightsR:red G:green B:blue]; 873 | 874 | break; 875 | } 876 | } 877 | } 878 | 879 | if (found == NO) { 880 | // Check if an animated visualization was selected 881 | if ([self timedVisualization:[sender title]] == NO) { 882 | NSLog(@"Unknown LED Visualization selected!\n"); 883 | return; 884 | } 885 | } else if (foundAudioDev == NO) { 886 | [AudioVisualizer setShowWindow:NO]; 887 | } 888 | 889 | // Store changed value in preferences 890 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 891 | if ([sender tag] == MENU_ITEM_TAG_AUDIO) { 892 | // Prepend text for audio device names 893 | NSString *tmp = [NSString stringWithFormat:TEXT_TEMPLATE_AUDIO, [sender title]]; 894 | [store setObject:tmp forKey:PREF_LED_MODE]; 895 | } else { 896 | [store setObject:[sender title] forKey:PREF_LED_MODE]; 897 | } 898 | [store synchronize]; 899 | 900 | #ifdef DEBUG 901 | NSLog(@"Stored new mode: \"%@\"!\n", [sender title]); 902 | #endif 903 | } 904 | 905 | - (void)selectedSerialPort:(NSMenuItem *)source { 906 | // Store selection for next start-up 907 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 908 | [store setObject:[source title] forKey:PREF_SERIAL_PORT]; 909 | [store synchronize]; 910 | 911 | // De-select all other ports 912 | for (int i = 0; i < [menuPorts numberOfItems]; i++) { 913 | [[menuPorts itemAtIndex:i] setState:NSOffState]; 914 | } 915 | 916 | // Select only the current port 917 | [source setState:NSOnState]; 918 | 919 | // Close previously opened port, if any 920 | if ([serial isOpen]) { 921 | [serial closePort]; 922 | } 923 | 924 | // Try to open selected port 925 | [serial setPortName:[source title]]; 926 | if ([serial openPort] != 0) { 927 | [source setState:NSOffState]; 928 | } else { 929 | // Restore the current configuration 930 | for (int i = 0; i < [menuColors numberOfItems]; i++) { 931 | if ([[menuColors itemAtIndex:i] state] == NSOnState) { 932 | [self selectedVisualization:[menuColors itemAtIndex:i]]; 933 | } 934 | } 935 | for (int i = 0; i < [menuAnimations numberOfItems]; i++) { 936 | if ([[menuAnimations itemAtIndex:i] state] == NSOnState) { 937 | [self selectedVisualization:[menuAnimations itemAtIndex:i]]; 938 | } 939 | } 940 | for (int i = 0; i < [menuVisualizations numberOfItems]; i++) { 941 | if ([[menuVisualizations itemAtIndex:i] state] == NSOnState) { 942 | [self selectedVisualization:[menuVisualizations itemAtIndex:i]]; 943 | } 944 | } 945 | for (int i = 0; i < [menuAudio numberOfItems]; i++) { 946 | if ([[menuAudio itemAtIndex:i] state] == NSOnState) { 947 | [self selectedVisualization:[menuAudio itemAtIndex:i]]; 948 | } 949 | } 950 | for (int i = 0; i < [menuDisplays numberOfItems]; i++) { 951 | if ([[menuDisplays itemAtIndex:i] state] == NSOnState) { 952 | [self selectedVisualization:[menuDisplays itemAtIndex:i]]; 953 | } 954 | } 955 | if ([buttonOff state] == NSOnState) { 956 | [buttonOff setState:NSOffState]; 957 | [self turnLEDsOff:buttonOff]; 958 | } 959 | if ([buttonLights state] == NSOnState) { 960 | [serial sendString:@"UV 1\n"]; 961 | } else { 962 | [serial sendString:@"UV 0\n"]; 963 | } 964 | } 965 | } 966 | 967 | - (IBAction)showAbout:(id)sender { 968 | [NSApp activateIgnoringOtherApps:YES]; 969 | [application orderFrontStandardAboutPanel:self]; 970 | } 971 | 972 | - (void)darkModeChanged:(NSNotification *)notification { 973 | NSUserDefaults *store = [NSUserDefaults standardUserDefaults]; 974 | BOOL colorize = [store boolForKey:PREF_COLORED_ICON]; 975 | if (colorize == YES) { 976 | [statusItem setImage:[AppDelegate tintedImage:statusImage WithColor:nil]]; 977 | } 978 | } 979 | 980 | // ------------------------------------------------------ 981 | // ----------------- Microphone Delegate ---------------- 982 | // ------------------------------------------------------ 983 | 984 | - (void)microphone:(EZMicrophone *)microphone hasAudioReceived:(float **)buffer withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels { 985 | __weak typeof (self) weakSelf = self; 986 | 987 | // Getting audio data as an array of float buffer arrays that can be fed into the 988 | // EZAudioPlot, EZAudioPlotGL, or whatever visualization you would like to do with 989 | // the microphone data. 990 | dispatch_async(dispatch_get_main_queue(),^{ 991 | if (weakSelf.microphone.microphoneOn == NO) { 992 | return; 993 | } 994 | 995 | // buffer[0] = left channel, buffer[1] = right channel 996 | [AudioVisualizer updateBuffer:buffer[0] withBufferSize:bufferSize]; 997 | }); 998 | } 999 | 1000 | - (void)microphone:(EZMicrophone *)microphone changedDevice:(EZAudioDevice *)device { 1001 | dispatch_async(dispatch_get_main_queue(), ^{ 1002 | NSLog(@"Changed audio input device: %@", [device name]); 1003 | }); 1004 | } 1005 | 1006 | // ------------------------------------------------------ 1007 | // ------------------- Visualizations ------------------- 1008 | // ------------------------------------------------------ 1009 | 1010 | - (void)visualizeDisplay:(NSTimer *)timer { 1011 | NSBitmapImageRep *screen = [Screenshot screenshot:[timer userInfo]]; 1012 | NSInteger spp = [screen samplesPerPixel]; 1013 | 1014 | if (((spp != 3) && (spp != 4)) || ([screen isPlanar] == YES) || ([screen numberOfPlanes] != 1)) { 1015 | NSLog(@"Unknown image format (%ld, %c, %ld)!\n", (long)spp, ([screen isPlanar] == YES) ? 'p' : 'n', (long)[screen numberOfPlanes]); 1016 | return; 1017 | } 1018 | 1019 | int redC = 0, greenC = 1, blueC = 2; 1020 | if ([screen bitmapFormat] & NSAlphaFirstBitmapFormat) { 1021 | redC = 1; greenC = 2; blueC = 3; 1022 | } 1023 | 1024 | unsigned char *data = [screen bitmapData]; 1025 | unsigned long width = [screen pixelsWide]; 1026 | unsigned long height = [screen pixelsHigh]; 1027 | unsigned long max = width * height; 1028 | unsigned long red = 0, green = 0, blue = 0; 1029 | for (unsigned long i = 0; i < max; i += AVERAGE_COLOR_PERFORMANCE_INC) { 1030 | unsigned long off = spp * i; 1031 | red += data[off + redC]; 1032 | green += data[off + greenC]; 1033 | blue += data[off + blueC]; 1034 | } 1035 | max /= AVERAGE_COLOR_PERFORMANCE_INC; 1036 | [self setLightsR:(red / max) G:(green / max) B:(blue / max)]; 1037 | } 1038 | 1039 | - (void)visualizeGPUUsage:(NSTimer *)timer { 1040 | NSNumber *usage; 1041 | NSNumber *freeVRAM; 1042 | NSNumber *usedVRAM; 1043 | if ([GPUStats getGPUUsage:&usage freeVRAM:&freeVRAM usedVRAM:&usedVRAM] != 0) { 1044 | NSLog(@"Error reading GPU information\n"); 1045 | } else { 1046 | double h = [AppDelegate map:[usage doubleValue] FromMin:0.0 FromMax:100.0 ToMin:GPU_COLOR_MIN ToMax:GPU_COLOR_MAX]; 1047 | 1048 | #ifdef DEBUG 1049 | NSLog(@"GPU Usage: %.3f%%\n", [usage doubleValue]); 1050 | #endif 1051 | 1052 | unsigned char r, g, b; 1053 | [AppDelegate convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b]; 1054 | [self setLightsR:r G:g B:b]; 1055 | } 1056 | } 1057 | 1058 | - (void)visualizeVRAMUsage:(NSTimer *)timer { 1059 | NSNumber *usage; 1060 | NSNumber *freeVRAM; 1061 | NSNumber *usedVRAM; 1062 | if ([GPUStats getGPUUsage:&usage freeVRAM:&freeVRAM usedVRAM:&usedVRAM] != 0) { 1063 | NSLog(@"Error reading GPU information\n"); 1064 | } else { 1065 | double h = [AppDelegate map:[freeVRAM doubleValue] FromMin:0.0 FromMax:([freeVRAM doubleValue] + [usedVRAM doubleValue]) ToMin:RAM_COLOR_MIN ToMax:RAM_COLOR_MAX]; 1066 | 1067 | #ifdef DEBUG 1068 | NSLog(@"VRAM %.2fGB Free + %.2fGB Used = %.2fGB mapped to color %.2f!\n", [freeVRAM doubleValue] / (1024.0 * 1024.0 * 1024.0), [usedVRAM doubleValue] / (1024.0 * 1024.0 * 1024.0), ([freeVRAM doubleValue] + [usedVRAM doubleValue]) / (1024.0 * 1024.0 * 1024.0), h); 1069 | #endif 1070 | 1071 | unsigned char r, g, b; 1072 | [AppDelegate convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b]; 1073 | [self setLightsR:r G:g B:b]; 1074 | } 1075 | } 1076 | 1077 | - (void)visualizeCPUUsage:(NSTimer *)timer { 1078 | JSKMCPUUsageInfo cpuUsageInfo = [JSKSystemMonitor systemMonitor].cpuUsageInfo; 1079 | 1080 | double h = [AppDelegate map:cpuUsageInfo.usage FromMin:0.0 FromMax:100.0 ToMin:CPU_COLOR_MIN ToMax:CPU_COLOR_MAX]; 1081 | 1082 | #ifdef DEBUG 1083 | NSLog(@"CPU Usage: %.3f%%\n", cpuUsageInfo.usage); 1084 | #endif 1085 | 1086 | unsigned char r, g, b; 1087 | [AppDelegate convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b]; 1088 | [self setLightsR:r G:g B:b]; 1089 | } 1090 | 1091 | - (void)visualizeRAMUsage:(NSTimer *)timer { 1092 | JSKMMemoryUsageInfo memoryUsageInfo = [JSKSystemMonitor systemMonitor].memoryUsageInfo; 1093 | 1094 | double h = [AppDelegate map:memoryUsageInfo.freeMemory FromMin:0.0 FromMax:(memoryUsageInfo.usedMemory + memoryUsageInfo.freeMemory) ToMin:RAM_COLOR_MIN ToMax:RAM_COLOR_MAX]; 1095 | 1096 | #ifdef DEBUG 1097 | NSLog(@"RAM %.2fGB Free + %.2fGB Used = %.2fGB mapped to color %.2f!\n", memoryUsageInfo.freeMemory / (1024.0 * 1024.0 * 1024.0), memoryUsageInfo.usedMemory / (1024.0 * 1024.0 * 1024.0), (memoryUsageInfo.freeMemory + memoryUsageInfo.usedMemory) / (1024.0 * 1024.0 * 1024.0), h); 1098 | #endif 1099 | 1100 | unsigned char r, g, b; 1101 | [AppDelegate convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b]; 1102 | [self setLightsR:r G:g B:b]; 1103 | } 1104 | 1105 | - (void)visualizeGPUTemperature:(NSTimer *)timer { 1106 | JSKSMC *smc = [JSKSMC smc]; 1107 | double temp = [smc temperatureInCelsiusForKey:KEY_GPU_TEMPERATURE]; 1108 | 1109 | if (temp > 1000.0) { 1110 | temp /= 1000.0; 1111 | } 1112 | 1113 | if (temp > GPU_TEMP_MAX) { 1114 | temp = GPU_TEMP_MAX; 1115 | } 1116 | 1117 | if (temp < GPU_TEMP_MIN) { 1118 | temp = GPU_TEMP_MIN; 1119 | } 1120 | 1121 | double h = [AppDelegate map:temp FromMin:GPU_TEMP_MIN FromMax:GPU_TEMP_MAX ToMin:GPU_COLOR_MIN ToMax:GPU_COLOR_MAX]; 1122 | 1123 | #ifdef DEBUG 1124 | NSLog(@"GPU Temp %.2f mapped to color %.2f!\n", temp, h); 1125 | #endif 1126 | 1127 | unsigned char r, g, b; 1128 | [AppDelegate convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b]; 1129 | [self setLightsR:r G:g B:b]; 1130 | } 1131 | 1132 | - (void)visualizeCPUTemperature:(NSTimer *)timer { 1133 | JSKSMC *smc = [JSKSMC smc]; 1134 | double temp = [smc temperatureInCelsiusForKey:KEY_CPU_TEMPERATURE]; 1135 | 1136 | if (temp > 1000.0) { 1137 | temp /= 1000.0; 1138 | } 1139 | 1140 | if (temp > CPU_TEMP_MAX) { 1141 | temp = CPU_TEMP_MAX; 1142 | } 1143 | 1144 | if (temp < CPU_TEMP_MIN) { 1145 | temp = CPU_TEMP_MIN; 1146 | } 1147 | 1148 | double h = [AppDelegate map:temp FromMin:CPU_TEMP_MIN FromMax:CPU_TEMP_MAX ToMin:CPU_COLOR_MIN ToMax:CPU_COLOR_MAX]; 1149 | 1150 | #ifdef DEBUG 1151 | NSLog(@"CPU Temp %.2f mapped to color %.2f!\n", temp, h); 1152 | #endif 1153 | 1154 | unsigned char r, g, b; 1155 | [AppDelegate convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b]; 1156 | [self setLightsR:r G:g B:b]; 1157 | } 1158 | 1159 | - (void)visualizeRGBFade:(NSTimer *)timer { 1160 | static unsigned char color[3] = { 255, 0, 0 }; 1161 | static int dec = 0; 1162 | static int val = 0; 1163 | 1164 | // Adapted from: 1165 | // https://gist.github.com/jamesotron/766994 1166 | 1167 | if (dec < 3) { 1168 | int inc = (dec == 2) ? 0 : (dec + 1); 1169 | if (val < 255) { 1170 | color[dec] -= 1; 1171 | color[inc] += 1; 1172 | val++; 1173 | } else { 1174 | val = 0; 1175 | dec++; 1176 | } 1177 | } else { 1178 | dec = 0; 1179 | } 1180 | [self setLightsR:color[0] G:color[1] B:color[2]]; 1181 | } 1182 | 1183 | - (void)visualizeHSVFade:(NSTimer *)timer { 1184 | static float h = 0.0; 1185 | 1186 | if (h < 359.0) { 1187 | h += 0.5; 1188 | } else { 1189 | h = 0.0; 1190 | } 1191 | 1192 | unsigned char r, g, b; 1193 | [AppDelegate convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b]; 1194 | [self setLightsR:r G:g B:b]; 1195 | } 1196 | 1197 | - (void)visualizeRandom:(NSTimer *)timer { 1198 | [self setLightsR:rand() % 256 G:rand() % 256 B:rand() % 256]; 1199 | } 1200 | 1201 | // ----------------------------------------------------- 1202 | // --------------------- Utilities --------------------- 1203 | // ----------------------------------------------------- 1204 | 1205 | + (NSImage *)tintedImage:(NSImage *)image WithColor:(NSColor *)tint { 1206 | NSSize size = [image size]; 1207 | NSRect imageBounds = NSMakeRect(0, 0, size.width, size.height); 1208 | 1209 | NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; 1210 | BOOL darkMode = ((osxMode != nil) && ([osxMode isEqualToString:@"Dark"])) ? YES : NO; 1211 | 1212 | NSImage *copiedImage = [image copy]; 1213 | static NSColor *lastTint = nil; 1214 | NSColor *copiedTint = nil; 1215 | if (tint != nil) { 1216 | // Just use the provided color 1217 | copiedTint = [tint copy]; 1218 | lastTint = copiedTint; 1219 | } else { 1220 | // Try to use the same color as the last time 1221 | if (lastTint != nil) { 1222 | copiedTint = lastTint; 1223 | } else { 1224 | #ifdef DEBUG 1225 | NSLog(@"Trying to set last status bar icon color, but was not set!\n"); 1226 | #endif 1227 | 1228 | if (darkMode == YES) { 1229 | copiedTint = [NSColor whiteColor]; 1230 | } else { 1231 | copiedTint = [NSColor blackColor]; 1232 | } 1233 | } 1234 | } 1235 | 1236 | // Fix colors for different interface styles 1237 | CGFloat r, g, b, a; 1238 | [copiedTint getRed:&r green:&g blue:&b alpha:&a]; 1239 | if (darkMode == YES) { 1240 | if ((r < 0.001f) && (g < 0.001f) && (b < 0.001f)) { 1241 | copiedTint = [NSColor whiteColor]; 1242 | } 1243 | } else { 1244 | if ((r > 0.999f) && (g > 0.999f) && (b > 0.999f)) { 1245 | copiedTint = [NSColor blackColor]; 1246 | } 1247 | } 1248 | 1249 | [copiedImage lockFocus]; 1250 | [copiedTint set]; 1251 | NSRectFillUsingOperation(imageBounds, NSCompositeSourceAtop); 1252 | [copiedImage unlockFocus]; 1253 | 1254 | return copiedImage; 1255 | } 1256 | 1257 | + (double)map:(double)val FromMin:(double)fmin FromMax:(double)fmax ToMin:(double)tmin ToMax:(double)tmax { 1258 | double norm = (val - fmin) / (fmax - fmin); 1259 | return (norm * (tmax - tmin)) + tmin; 1260 | } 1261 | 1262 | + (void)convertH:(double)h S:(double)s V:(double)v toR:(unsigned char *)r G:(unsigned char *)g B:(unsigned char *)b { 1263 | // Adapted from: 1264 | // https://gist.github.com/hdznrrd/656996 1265 | 1266 | if (s == 0.0) { 1267 | // Achromatic 1268 | *r = *g = *b = (unsigned char)(v * 255); 1269 | return; 1270 | } 1271 | 1272 | h /= 60; // sector 0 to 5 1273 | int i = floor(h); 1274 | double f = h - i; // factorial part of h 1275 | double p = v * (1 - s); 1276 | double q = v * (1 - s * f); 1277 | double t = v * (1 - s * (1 - f)); 1278 | 1279 | switch (i) { 1280 | case 0: 1281 | *r = (unsigned char)round(255 * v); 1282 | *g = (unsigned char)round(255 * t); 1283 | *b = (unsigned char)round(255 * p); 1284 | break; 1285 | 1286 | case 1: 1287 | *r = (unsigned char)round(255 * q); 1288 | *g = (unsigned char)round(255 * v); 1289 | *b = (unsigned char)round(255 * p); 1290 | break; 1291 | 1292 | case 2: 1293 | *r = (unsigned char)round(255 * p); 1294 | *g = (unsigned char)round(255 * v); 1295 | *b = (unsigned char)round(255 * t); 1296 | break; 1297 | 1298 | case 3: 1299 | *r = (unsigned char)round(255 * p); 1300 | *g = (unsigned char)round(255 * q); 1301 | *b = (unsigned char)round(255 * v); 1302 | break; 1303 | 1304 | case 4: 1305 | *r = (unsigned char)round(255 * t); 1306 | *g = (unsigned char)round(255 * p); 1307 | *b = (unsigned char)round(255 * v); 1308 | break; 1309 | 1310 | default: case 5: 1311 | *r = (unsigned char)round(255 * v); 1312 | *g = (unsigned char)round(255 * p); 1313 | *b = (unsigned char)round(255 * q); 1314 | break; 1315 | } 1316 | } 1317 | 1318 | @end 1319 | -------------------------------------------------------------------------------- /CaseLights/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | Default 559 | 560 | 561 | 562 | 563 | 564 | 565 | Left to Right 566 | 567 | 568 | 569 | 570 | 571 | 572 | Right to Left 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | Default 584 | 585 | 586 | 587 | 588 | 589 | 590 | Left to Right 591 | 592 | 593 | 594 | 595 | 596 | 597 | Right to Left 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 |