├── ss.png
├── notif.png
├── ChordDetector
├── Assets.xcassets
│ ├── Contents.json
│ ├── menuBar.imageset
│ │ ├── menuBar.png
│ │ ├── menuBar@2x.png
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ ├── Icon_128x128.png
│ │ ├── Icon_16x16.png
│ │ ├── Icon_256x256.png
│ │ ├── Icon_32x32.png
│ │ ├── Icon_512x512.png
│ │ ├── Icon_16x16@2x.png
│ │ ├── Icon_32x32@2x.png
│ │ ├── Icon_128x128@2x.png
│ │ ├── Icon_256x256@2x.png
│ │ ├── Icon_512x512@2x.png
│ │ └── Contents.json
├── Chord Detector.entitlements
├── Info.plist
├── AppDelegate.swift
├── ChordDetector.swift
└── Base.lproj
│ └── Main.storyboard
├── ChordDetector.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── Cem.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── ChordDetectorTests.xcscheme
│ │ └── ChordDetector.xcscheme
└── project.pbxproj
├── .travis.yml
├── ChordDetectorTests
├── Info.plist
└── ChordDetectorTests.swift
├── README.md
├── LICENSE
└── .gitignore
/ss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ss.png
--------------------------------------------------------------------------------
/notif.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/notif.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/menuBar.imageset/menuBar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/menuBar.imageset/menuBar.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/menuBar.imageset/menuBar@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/menuBar.imageset/menuBar@2x.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cemolcay/ChordDetector/HEAD/ChordDetector/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png
--------------------------------------------------------------------------------
/ChordDetector.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ChordDetector.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/menuBar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "filename" : "menuBar.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "filename" : "menuBar@2x.png",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | },
18 | "properties" : {
19 | "template-rendering-intent" : "template"
20 | }
21 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 |
3 | project: ChordDetector.xcodeproj
4 | scheme: ChordDetector
5 | test_scheme: ChordDetectorTests
6 | osx_image: xcode11
7 | xcode_sdk: macosx10.15
8 |
9 | script:
10 | - xcodebuild -project ChordDetector.xcodeproj -scheme ChordDetector ONLY_ACTIVE_ARCH=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""
11 | - xcodebuild -project ChordDetector.xcodeproj -scheme ChordDetectorTests test ONLY_ACTIVE_ARCH=YES CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""
12 |
--------------------------------------------------------------------------------
/ChordDetector/Chord Detector.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 | com.apple.security.temporary-exception.apple-events
10 |
11 | com.apple.iTunes
12 |
13 | com.apple.iTunes.playerInfo
14 |
15 | com.spotify.client
16 |
17 | com.spotify.client.PlaybackStateChanged
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/ChordDetectorTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ChordDetector [](https://travis-ci.org/cemolcay/ChordDetector)
2 | ===
3 |
4 |
5 | A tiny menu bar app that listens iTunes and Spotify to detect chords of songs!
6 |
7 | [](https://geo.itunes.apple.com/us/app/chord-detector/id1219549675?mt=12)
8 |
9 | Demo
10 | ----
11 |
12 | 
13 | 
14 |
15 | Features
16 | ----
17 |
18 | - iTunes and Spotify support.
19 | - Saves up to 20 chords in history.
20 | - Presents a notification on notification center.
21 | - Searches `ultimate-guitar` to find top rated chord.
22 |
--------------------------------------------------------------------------------
/ChordDetector.xcodeproj/xcuserdata/Cem.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | ChordDetector.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | ChordDetectorTests.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 7
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | B27643A61E8494DB00B67425
21 |
22 | primary
23 |
24 |
25 | B2C64CC11E8E6A0200E7B74F
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2017, Cem Olcay
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a
4 | copy of this software and associated documentation files (the "Software"),
5 | to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | DEALINGS IN THE SOFTWARE.
20 |
21 |
--------------------------------------------------------------------------------
/ChordDetector/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "Icon_16x16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "Icon_16x16@2x.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "Icon_32x32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "Icon_32x32@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "Icon_128x128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "Icon_128x128@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "Icon_256x256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "Icon_256x256@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "Icon_512x512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "Icon_512x512@2x.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/ChordDetector/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 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | Fabric
24 |
25 | APIKey
26 | e221289244d971094d2d79277c2b57efd5d70441
27 | Kits
28 |
29 |
30 | KitInfo
31 |
32 | KitName
33 | Crashlytics
34 |
35 |
36 |
37 | LSApplicationCategoryType
38 | public.app-category.music
39 | LSMinimumSystemVersion
40 | $(MACOSX_DEPLOYMENT_TARGET)
41 | LSUIElement
42 |
43 | NSAppleEventsUsageDescription
44 | Allow ChordDetector to listen song changes on iTunes or Spotify.
45 | NSHumanReadableCopyright
46 | Copyright © 2017 cemolcay. All rights reserved.
47 | NSMainStoryboardFile
48 | Main
49 | NSPrincipalClass
50 | NSApplication
51 |
52 |
53 |
--------------------------------------------------------------------------------
/ChordDetector/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ChordDetector
4 | //
5 | // Created by Cem Olcay on 24/03/2017.
6 | // Copyright © 2017 cemolcay. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 | let detector = ChordDetector.shared
14 | let statusItem = NSStatusBar.system.statusItem(withLength: -2)
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | statusItem.menu = menu
18 | if let button = statusItem.button {
19 | button.image = NSImage(named: "menuBar")
20 | button.imageScaling = .scaleProportionallyUpOrDown
21 | }
22 | }
23 |
24 | // MARK: Menu
25 |
26 | var menu: NSMenu {
27 | let menu = NSMenu()
28 |
29 | // History
30 | let historyTitleItem = NSMenuItem(title: "History", action: nil, keyEquivalent: "")
31 | historyTitleItem.isEnabled = false
32 | menu.addItem(historyTitleItem)
33 |
34 | for (index, historyItem) in detector.history.items.enumerated() {
35 | let item = NSMenuItem(
36 | title: historyItem.title,
37 | action: #selector(historyItemDidPress(sender:)),
38 | keyEquivalent: "")
39 | item.tag = index
40 | menu.addItem(item)
41 | }
42 |
43 | if !detector.history.items.isEmpty {
44 | menu.addItem(
45 | withTitle: "Clear History",
46 | action: #selector(clearHistoryDidPress),
47 | keyEquivalent: "")
48 | }
49 |
50 | // Quit
51 | menu.addItem(.separator())
52 | menu.addItem(
53 | withTitle: "Quit",
54 | action: #selector(NSApplication.terminate(_:)),
55 | keyEquivalent: "")
56 |
57 | return menu
58 | }
59 |
60 | @objc func historyItemDidPress(sender: NSMenuItem) {
61 | let url = detector.history.items[sender.tag].url
62 | NSWorkspace.shared.open(url)
63 | }
64 |
65 | @objc func clearHistoryDidPress() {
66 | detector.history.clear()
67 | statusItem.menu = menu
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/swift,xcode
3 |
4 | ### Swift ###
5 | # Xcode
6 | #
7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
8 |
9 | ## Build generated
10 | build/
11 | DerivedData/
12 |
13 | ## Various settings
14 | *.pbxuser
15 | !default.pbxuser
16 | *.mode1v3
17 | !default.mode1v3
18 | *.mode2v3
19 | !default.mode2v3
20 | *.perspectivev3
21 | !default.perspectivev3
22 | xcuserdata/
23 |
24 | ## Other
25 | *.moved-aside
26 | *.xcuserstate
27 |
28 | ## Obj-C/Swift specific
29 | *.hmap
30 | *.ipa
31 | *.dSYM.zip
32 | *.dSYM
33 |
34 | ## Playgrounds
35 | timeline.xctimeline
36 | playground.xcworkspace
37 |
38 | # Swift Package Manager
39 | #
40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
41 | # Packages/
42 | .build/
43 |
44 | # CocoaPods
45 | #
46 | # We recommend against adding the Pods directory to your .gitignore. However
47 | # you should judge for yourself, the pros and cons are mentioned at:
48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
49 | #
50 | Pods/
51 |
52 | # Carthage
53 | #
54 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
55 | Carthage/Checkouts
56 |
57 | Carthage/Build
58 |
59 | # fastlane
60 | #
61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
62 | # screenshots whenever they are needed.
63 | # For more information about the recommended setup visit:
64 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
65 |
66 | fastlane/report.xml
67 | fastlane/Preview.html
68 | fastlane/screenshots
69 | fastlane/test_output
70 |
71 |
72 | ### Xcode ###
73 | # Xcode
74 | #
75 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
76 |
77 | ## Build generated
78 |
79 | ## Various settings
80 |
81 | ## Other
82 | *.xccheckout
83 | *.xcscmblueprint
84 |
85 | # Docs
86 | docs/
87 |
88 | # End of https://www.gitignore.io/api/swift,xcode
89 | .DS_Store
90 |
--------------------------------------------------------------------------------
/ChordDetectorTests/ChordDetectorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChordDetectorTests.swift
3 | // ChordDetectorTests
4 | //
5 | // Created by Cem Olcay on 31/03/2017.
6 | // Copyright © 2017 cemolcay. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import WebKit
11 | @testable import Chord_Detector
12 |
13 | class ChordDetectorTests: XCTestCase, WKNavigationDelegate {
14 | var webView = WKWebView()
15 | var wkExpectation = XCTestExpectation(description: "WKNavigationDelegate")
16 | let artist = "The Animals"
17 | let song = "House Of The Rising Sun"
18 |
19 | func testUltimateGuitarParse() {
20 | var url = "https://www.ultimate-guitar.com/search.php?search_type=title&order=&value="
21 | url += "\(artist.replacingOccurrences(of: " ", with: "+"))+"
22 | url += "\(song.replacingOccurrences(of: " ", with: "+"))"
23 |
24 | guard let chordUrl = URL(string: url) else { return XCTFail("URL not parsed.") }
25 | webView.navigationDelegate = self
26 | webView.load(URLRequest(url: chordUrl))
27 | wait(for: [wkExpectation], timeout: 10.0)
28 | }
29 |
30 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
31 | let js = """
32 | window.UGAPP.store.page.data.results
33 | .filter(function(item){ return (item.type == "Chords") })
34 | .sort(function(a, b){ return b.rating > a.rating })[0]
35 | """
36 | webView.evaluateJavaScript(js, completionHandler: { result, error in
37 | guard let json = result as? [String: Any],
38 | error == nil
39 | else { XCTFail("Can not evaluate javascript"); return }
40 |
41 | guard let url = json["tab_url"] as? String,
42 | let artist = json["artist_name"] as? String,
43 | let song = json["song_name"] as? String,
44 | let item = UGItem(dict: json)
45 | else { XCTFail("Can not serialize javascript object"); return }
46 |
47 | XCTAssertEqual(self.artist, artist)
48 | XCTAssertEqual(self.song, song)
49 | XCTAssertNotNil(URL(string: url), "Url is nil")
50 | XCTAssertEqual(self.artist, item.artist)
51 | XCTAssertEqual(self.song, item.song)
52 | XCTAssertEqual(url, item.url.absoluteString)
53 | self.wkExpectation.fulfill()
54 | })
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/ChordDetector.xcodeproj/xcshareddata/xcschemes/ChordDetectorTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
21 |
22 |
23 |
24 |
26 |
32 |
33 |
34 |
35 |
36 |
46 |
48 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/ChordDetector.xcodeproj/xcshareddata/xcschemes/ChordDetector.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
64 |
70 |
71 |
72 |
73 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/ChordDetector/ChordDetector.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChordDetector.swift
3 | // ChordDetector
4 | //
5 | // Created by Cem Olcay on 24/03/2017.
6 | // Copyright © 2017 cemolcay. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import WebKit
11 |
12 | // MARK: - UGItem
13 |
14 | struct UGItem: Codable, Equatable {
15 | let artist: String
16 | let song: String
17 | let url: URL
18 |
19 | var title: String {
20 | return "\(artist) - \(song)"
21 | }
22 |
23 | var dictionaryValue: [String: Any] {
24 | guard let data = try? JSONEncoder().encode(self),
25 | let dict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
26 | else { return [:] }
27 | return dict
28 | }
29 |
30 | enum CodingKeys: String, CodingKey {
31 | case artist = "artist_name"
32 | case song = "song_name"
33 | case url = "tab_url"
34 | }
35 |
36 | init(from decoder: Decoder) throws {
37 | let values = try decoder.container(keyedBy: CodingKeys.self)
38 | artist = try values.decode(String.self, forKey: .artist)
39 | song = try values.decode(String.self, forKey: .song)
40 | url = try values.decode(URL.self, forKey: .url)
41 | }
42 |
43 | init?(dict: [String: Any]) {
44 | guard let data = try? JSONSerialization.data(withJSONObject: dict, options: []),
45 | let item = try? JSONDecoder().decode(UGItem.self, from: data)
46 | else { return nil }
47 | self = item
48 | }
49 |
50 | func encode(to encoder: Encoder) throws {
51 | var container = encoder.container(keyedBy: CodingKeys.self)
52 | try container.encode(artist, forKey: .artist)
53 | try container.encode(song, forKey: .song)
54 | try container.encode(url, forKey: .url)
55 | }
56 |
57 | func pushNotification() {
58 | let notification = NSUserNotification()
59 | notification.title = "Chord Detected!"
60 | notification.informativeText = title
61 | notification.userInfo = dictionaryValue
62 | NSUserNotificationCenter.default.deliver(notification)
63 | }
64 | }
65 |
66 | // MARK: - History
67 |
68 | class History {
69 | private(set) var items: [UGItem]
70 | private let historyKey = "history"
71 | private let limit = 20
72 |
73 | init() {
74 | if let data = UserDefaults.standard.data(forKey: historyKey),
75 | let history = try? PropertyListDecoder().decode([UGItem].self, from: data) {
76 | items = history
77 | } else {
78 | items = []
79 | }
80 | persist()
81 | }
82 |
83 | func clear() {
84 | items.removeAll()
85 | persist()
86 | }
87 |
88 | func push(item: UGItem) {
89 | guard items.contains(item) == false else { return }
90 | items.append(item)
91 | // Check limit
92 | if items.count > limit {
93 | items.removeFirst()
94 | }
95 | persist()
96 | }
97 |
98 | func persist() {
99 | // Save
100 | let defaults = UserDefaults.standard
101 | guard let data = try? PropertyListEncoder().encode(items) else { return }
102 | defaults.set(data, forKey: historyKey)
103 | defaults.synchronize()
104 | // Update UI
105 | if let appdelegate = NSApplication.shared.delegate as? AppDelegate {
106 | appdelegate.statusItem.menu = appdelegate.menu
107 | }
108 | }
109 | }
110 |
111 | // MARK: - ChordDetector
112 |
113 | class ChordDetector: NSObject, WKNavigationDelegate, NSUserNotificationCenterDelegate {
114 | static let shared = ChordDetector()
115 | let history = History()
116 | let webView = WKWebView()
117 |
118 | private let spotifyNotificationName = "com.spotify.client.PlaybackStateChanged"
119 | private let itunesNotificationName = "com.apple.iTunes.playerInfo"
120 |
121 | // MARK: Lifecycle
122 |
123 | override init() {
124 | super.init()
125 | webView.navigationDelegate = self
126 | NSUserNotificationCenter.default.delegate = self
127 | registerNotifications(for: [
128 | spotifyNotificationName,
129 | itunesNotificationName,
130 | ])
131 | }
132 |
133 | // MARK: Player Notifications
134 |
135 | private func registerNotifications(for items: [String]) {
136 | for item in items {
137 | DistributedNotificationCenter.default().addObserver(
138 | self,
139 | selector: #selector(playerItemDidChange(notification:)),
140 | name: NSNotification.Name(rawValue: item),
141 | object: nil)
142 | }
143 | }
144 |
145 | @objc private func playerItemDidChange(notification: NSNotification) {
146 | guard let artist = notification.userInfo?["Artist"] as? String,
147 | let song = notification.userInfo?["Name"] as? String,
148 | notification.userInfo?["Player State"] as? String == "Playing"
149 | else { return }
150 | searchChord(
151 | artist: artist,
152 | song: song)
153 | }
154 |
155 | func searchChord(artist: String, song: String) {
156 | var url = "https://www.ultimate-guitar.com/search.php?search_type=title&order=&value="
157 | url += "\(artist.replacingOccurrences(of: " ", with: "+"))+"
158 | url += "\(song.replacingOccurrences(of: " ", with: "+"))"
159 |
160 | guard let chordUrl = URL(string: url) else { return }
161 | webView.load(URLRequest(url: chordUrl))
162 | }
163 |
164 | // MARK: WKNavigationDelegate
165 |
166 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
167 | let script = """
168 | window.UGAPP.store.page.data.results
169 | .filter(function(item){ return (item.type == "Chords") })
170 | .sort(function(a, b){ return b.rating > a.rating })[0]
171 | """
172 |
173 | webView.evaluateJavaScript(script, completionHandler: { result, error in
174 | guard error == nil,
175 | let result = result as? [String: Any],
176 | let item = UGItem(dict: result)
177 | else { return }
178 | item.pushNotification()
179 | self.history.push(item: item)
180 | })
181 | }
182 |
183 | // MARK: NSUserNotificationCenterDelegate
184 |
185 | func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) {
186 | guard let dict = notification.userInfo,
187 | let item = UGItem(dict: dict)
188 | else { return }
189 | // Open url
190 | NSWorkspace.shared.open(item.url)
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/ChordDetector.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B27643AB1E8494DB00B67425 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B27643AA1E8494DB00B67425 /* AppDelegate.swift */; };
11 | B27643AF1E8494DB00B67425 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B27643AE1E8494DB00B67425 /* Assets.xcassets */; };
12 | B27643B21E8494DB00B67425 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B27643B01E8494DB00B67425 /* Main.storyboard */; };
13 | B27643BA1E8494F100B67425 /* ChordDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = B27643B91E8494F100B67425 /* ChordDetector.swift */; };
14 | B2C64CC51E8E6A0300E7B74F /* ChordDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C64CC41E8E6A0300E7B74F /* ChordDetectorTests.swift */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXContainerItemProxy section */
18 | B2C64CC71E8E6A0300E7B74F /* PBXContainerItemProxy */ = {
19 | isa = PBXContainerItemProxy;
20 | containerPortal = B276439F1E8494DB00B67425 /* Project object */;
21 | proxyType = 1;
22 | remoteGlobalIDString = B27643A61E8494DB00B67425;
23 | remoteInfo = ChordDetector;
24 | };
25 | /* End PBXContainerItemProxy section */
26 |
27 | /* Begin PBXFileReference section */
28 | 53D66BC023966C7B0084C167 /* .travis.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = .travis.yml; sourceTree = ""; };
29 | B27643A71E8494DB00B67425 /* Chord Detector.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Chord Detector.app"; sourceTree = BUILT_PRODUCTS_DIR; };
30 | B27643AA1E8494DB00B67425 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
31 | B27643AE1E8494DB00B67425 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
32 | B27643B11E8494DB00B67425 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
33 | B27643B31E8494DB00B67425 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
34 | B27643B91E8494F100B67425 /* ChordDetector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChordDetector.swift; sourceTree = ""; };
35 | B2C3DA601E85F6F800BD3074 /* Chord Detector.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Chord Detector.entitlements"; sourceTree = ""; };
36 | B2C64CC21E8E6A0200E7B74F /* ChordDetectorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChordDetectorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
37 | B2C64CC41E8E6A0300E7B74F /* ChordDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChordDetectorTests.swift; sourceTree = ""; };
38 | B2C64CC61E8E6A0300E7B74F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
39 | /* End PBXFileReference section */
40 |
41 | /* Begin PBXFrameworksBuildPhase section */
42 | B27643A41E8494DB00B67425 /* Frameworks */ = {
43 | isa = PBXFrameworksBuildPhase;
44 | buildActionMask = 2147483647;
45 | files = (
46 | );
47 | runOnlyForDeploymentPostprocessing = 0;
48 | };
49 | B2C64CBF1E8E6A0200E7B74F /* Frameworks */ = {
50 | isa = PBXFrameworksBuildPhase;
51 | buildActionMask = 2147483647;
52 | files = (
53 | );
54 | runOnlyForDeploymentPostprocessing = 0;
55 | };
56 | /* End PBXFrameworksBuildPhase section */
57 |
58 | /* Begin PBXGroup section */
59 | B276439E1E8494DB00B67425 = {
60 | isa = PBXGroup;
61 | children = (
62 | 53D66BC023966C7B0084C167 /* .travis.yml */,
63 | B27643A91E8494DB00B67425 /* ChordDetector */,
64 | B2C64CC31E8E6A0300E7B74F /* ChordDetectorTests */,
65 | B27643A81E8494DB00B67425 /* Products */,
66 | );
67 | sourceTree = "";
68 | };
69 | B27643A81E8494DB00B67425 /* Products */ = {
70 | isa = PBXGroup;
71 | children = (
72 | B27643A71E8494DB00B67425 /* Chord Detector.app */,
73 | B2C64CC21E8E6A0200E7B74F /* ChordDetectorTests.xctest */,
74 | );
75 | name = Products;
76 | sourceTree = "";
77 | };
78 | B27643A91E8494DB00B67425 /* ChordDetector */ = {
79 | isa = PBXGroup;
80 | children = (
81 | B2C3DA601E85F6F800BD3074 /* Chord Detector.entitlements */,
82 | B27643AA1E8494DB00B67425 /* AppDelegate.swift */,
83 | B27643B91E8494F100B67425 /* ChordDetector.swift */,
84 | B27643AE1E8494DB00B67425 /* Assets.xcassets */,
85 | B27643B01E8494DB00B67425 /* Main.storyboard */,
86 | B27643B31E8494DB00B67425 /* Info.plist */,
87 | );
88 | path = ChordDetector;
89 | sourceTree = "";
90 | };
91 | B2C64CC31E8E6A0300E7B74F /* ChordDetectorTests */ = {
92 | isa = PBXGroup;
93 | children = (
94 | B2C64CC41E8E6A0300E7B74F /* ChordDetectorTests.swift */,
95 | B2C64CC61E8E6A0300E7B74F /* Info.plist */,
96 | );
97 | path = ChordDetectorTests;
98 | sourceTree = "";
99 | };
100 | /* End PBXGroup section */
101 |
102 | /* Begin PBXNativeTarget section */
103 | B27643A61E8494DB00B67425 /* ChordDetector */ = {
104 | isa = PBXNativeTarget;
105 | buildConfigurationList = B27643B61E8494DB00B67425 /* Build configuration list for PBXNativeTarget "ChordDetector" */;
106 | buildPhases = (
107 | B27643A31E8494DB00B67425 /* Sources */,
108 | B27643A41E8494DB00B67425 /* Frameworks */,
109 | B27643A51E8494DB00B67425 /* Resources */,
110 | );
111 | buildRules = (
112 | );
113 | dependencies = (
114 | );
115 | name = ChordDetector;
116 | productName = ChordDetector;
117 | productReference = B27643A71E8494DB00B67425 /* Chord Detector.app */;
118 | productType = "com.apple.product-type.application";
119 | };
120 | B2C64CC11E8E6A0200E7B74F /* ChordDetectorTests */ = {
121 | isa = PBXNativeTarget;
122 | buildConfigurationList = B2C64CC91E8E6A0300E7B74F /* Build configuration list for PBXNativeTarget "ChordDetectorTests" */;
123 | buildPhases = (
124 | B2C64CBE1E8E6A0200E7B74F /* Sources */,
125 | B2C64CBF1E8E6A0200E7B74F /* Frameworks */,
126 | B2C64CC01E8E6A0200E7B74F /* Resources */,
127 | );
128 | buildRules = (
129 | );
130 | dependencies = (
131 | B2C64CC81E8E6A0300E7B74F /* PBXTargetDependency */,
132 | );
133 | name = ChordDetectorTests;
134 | productName = ChordDetectorTests;
135 | productReference = B2C64CC21E8E6A0200E7B74F /* ChordDetectorTests.xctest */;
136 | productType = "com.apple.product-type.bundle.unit-test";
137 | };
138 | /* End PBXNativeTarget section */
139 |
140 | /* Begin PBXProject section */
141 | B276439F1E8494DB00B67425 /* Project object */ = {
142 | isa = PBXProject;
143 | attributes = {
144 | LastSwiftUpdateCheck = 0830;
145 | LastUpgradeCheck = 1110;
146 | ORGANIZATIONNAME = cemolcay;
147 | TargetAttributes = {
148 | B27643A61E8494DB00B67425 = {
149 | CreatedOnToolsVersion = 8.2.1;
150 | DevelopmentTeam = 77Y3N48SNF;
151 | LastSwiftMigration = 1110;
152 | ProvisioningStyle = Automatic;
153 | SystemCapabilities = {
154 | com.apple.Sandbox = {
155 | enabled = 1;
156 | };
157 | };
158 | };
159 | B2C64CC11E8E6A0200E7B74F = {
160 | CreatedOnToolsVersion = 8.3;
161 | DevelopmentTeam = 77Y3N48SNF;
162 | LastSwiftMigration = 1110;
163 | ProvisioningStyle = Automatic;
164 | TestTargetID = B27643A61E8494DB00B67425;
165 | };
166 | };
167 | };
168 | buildConfigurationList = B27643A21E8494DB00B67425 /* Build configuration list for PBXProject "ChordDetector" */;
169 | compatibilityVersion = "Xcode 3.2";
170 | developmentRegion = en;
171 | hasScannedForEncodings = 0;
172 | knownRegions = (
173 | en,
174 | Base,
175 | );
176 | mainGroup = B276439E1E8494DB00B67425;
177 | productRefGroup = B27643A81E8494DB00B67425 /* Products */;
178 | projectDirPath = "";
179 | projectRoot = "";
180 | targets = (
181 | B27643A61E8494DB00B67425 /* ChordDetector */,
182 | B2C64CC11E8E6A0200E7B74F /* ChordDetectorTests */,
183 | );
184 | };
185 | /* End PBXProject section */
186 |
187 | /* Begin PBXResourcesBuildPhase section */
188 | B27643A51E8494DB00B67425 /* Resources */ = {
189 | isa = PBXResourcesBuildPhase;
190 | buildActionMask = 2147483647;
191 | files = (
192 | B27643AF1E8494DB00B67425 /* Assets.xcassets in Resources */,
193 | B27643B21E8494DB00B67425 /* Main.storyboard in Resources */,
194 | );
195 | runOnlyForDeploymentPostprocessing = 0;
196 | };
197 | B2C64CC01E8E6A0200E7B74F /* Resources */ = {
198 | isa = PBXResourcesBuildPhase;
199 | buildActionMask = 2147483647;
200 | files = (
201 | );
202 | runOnlyForDeploymentPostprocessing = 0;
203 | };
204 | /* End PBXResourcesBuildPhase section */
205 |
206 | /* Begin PBXSourcesBuildPhase section */
207 | B27643A31E8494DB00B67425 /* Sources */ = {
208 | isa = PBXSourcesBuildPhase;
209 | buildActionMask = 2147483647;
210 | files = (
211 | B27643AB1E8494DB00B67425 /* AppDelegate.swift in Sources */,
212 | B27643BA1E8494F100B67425 /* ChordDetector.swift in Sources */,
213 | );
214 | runOnlyForDeploymentPostprocessing = 0;
215 | };
216 | B2C64CBE1E8E6A0200E7B74F /* Sources */ = {
217 | isa = PBXSourcesBuildPhase;
218 | buildActionMask = 2147483647;
219 | files = (
220 | B2C64CC51E8E6A0300E7B74F /* ChordDetectorTests.swift in Sources */,
221 | );
222 | runOnlyForDeploymentPostprocessing = 0;
223 | };
224 | /* End PBXSourcesBuildPhase section */
225 |
226 | /* Begin PBXTargetDependency section */
227 | B2C64CC81E8E6A0300E7B74F /* PBXTargetDependency */ = {
228 | isa = PBXTargetDependency;
229 | target = B27643A61E8494DB00B67425 /* ChordDetector */;
230 | targetProxy = B2C64CC71E8E6A0300E7B74F /* PBXContainerItemProxy */;
231 | };
232 | /* End PBXTargetDependency section */
233 |
234 | /* Begin PBXVariantGroup section */
235 | B27643B01E8494DB00B67425 /* Main.storyboard */ = {
236 | isa = PBXVariantGroup;
237 | children = (
238 | B27643B11E8494DB00B67425 /* Base */,
239 | );
240 | name = Main.storyboard;
241 | sourceTree = "";
242 | };
243 | /* End PBXVariantGroup section */
244 |
245 | /* Begin XCBuildConfiguration section */
246 | B27643B41E8494DB00B67425 /* Debug */ = {
247 | isa = XCBuildConfiguration;
248 | buildSettings = {
249 | ALWAYS_SEARCH_USER_PATHS = NO;
250 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
251 | CLANG_ANALYZER_NONNULL = YES;
252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
253 | CLANG_CXX_LIBRARY = "libc++";
254 | CLANG_ENABLE_MODULES = YES;
255 | CLANG_ENABLE_OBJC_ARC = YES;
256 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
257 | CLANG_WARN_BOOL_CONVERSION = YES;
258 | CLANG_WARN_COMMA = YES;
259 | CLANG_WARN_CONSTANT_CONVERSION = YES;
260 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
262 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
263 | CLANG_WARN_EMPTY_BODY = YES;
264 | CLANG_WARN_ENUM_CONVERSION = YES;
265 | CLANG_WARN_INFINITE_RECURSION = YES;
266 | CLANG_WARN_INT_CONVERSION = YES;
267 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
268 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
269 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
271 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
272 | CLANG_WARN_STRICT_PROTOTYPES = YES;
273 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
274 | CLANG_WARN_UNREACHABLE_CODE = YES;
275 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
276 | CODE_SIGN_IDENTITY = "-";
277 | COPY_PHASE_STRIP = NO;
278 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
279 | ENABLE_STRICT_OBJC_MSGSEND = YES;
280 | ENABLE_TESTABILITY = YES;
281 | GCC_C_LANGUAGE_STANDARD = gnu99;
282 | GCC_DYNAMIC_NO_PIC = NO;
283 | GCC_NO_COMMON_BLOCKS = YES;
284 | GCC_OPTIMIZATION_LEVEL = 0;
285 | GCC_PREPROCESSOR_DEFINITIONS = (
286 | "DEBUG=1",
287 | "$(inherited)",
288 | );
289 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
290 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
291 | GCC_WARN_UNDECLARED_SELECTOR = YES;
292 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
293 | GCC_WARN_UNUSED_FUNCTION = YES;
294 | GCC_WARN_UNUSED_VARIABLE = YES;
295 | MACOSX_DEPLOYMENT_TARGET = 10.10;
296 | MTL_ENABLE_DEBUG_INFO = YES;
297 | ONLY_ACTIVE_ARCH = YES;
298 | SDKROOT = macosx;
299 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
300 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
301 | SWIFT_VERSION = 4.0;
302 | };
303 | name = Debug;
304 | };
305 | B27643B51E8494DB00B67425 /* Release */ = {
306 | isa = XCBuildConfiguration;
307 | buildSettings = {
308 | ALWAYS_SEARCH_USER_PATHS = NO;
309 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
310 | CLANG_ANALYZER_NONNULL = YES;
311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
312 | CLANG_CXX_LIBRARY = "libc++";
313 | CLANG_ENABLE_MODULES = YES;
314 | CLANG_ENABLE_OBJC_ARC = YES;
315 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
316 | CLANG_WARN_BOOL_CONVERSION = YES;
317 | CLANG_WARN_COMMA = YES;
318 | CLANG_WARN_CONSTANT_CONVERSION = YES;
319 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
321 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
322 | CLANG_WARN_EMPTY_BODY = YES;
323 | CLANG_WARN_ENUM_CONVERSION = YES;
324 | CLANG_WARN_INFINITE_RECURSION = YES;
325 | CLANG_WARN_INT_CONVERSION = YES;
326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
331 | CLANG_WARN_STRICT_PROTOTYPES = YES;
332 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
333 | CLANG_WARN_UNREACHABLE_CODE = YES;
334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
335 | CODE_SIGN_IDENTITY = "-";
336 | COPY_PHASE_STRIP = NO;
337 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
338 | ENABLE_NS_ASSERTIONS = NO;
339 | ENABLE_STRICT_OBJC_MSGSEND = YES;
340 | GCC_C_LANGUAGE_STANDARD = gnu99;
341 | GCC_NO_COMMON_BLOCKS = YES;
342 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
343 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
344 | GCC_WARN_UNDECLARED_SELECTOR = YES;
345 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
346 | GCC_WARN_UNUSED_FUNCTION = YES;
347 | GCC_WARN_UNUSED_VARIABLE = YES;
348 | MACOSX_DEPLOYMENT_TARGET = 10.10;
349 | MTL_ENABLE_DEBUG_INFO = NO;
350 | SDKROOT = macosx;
351 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
352 | SWIFT_VERSION = 4.0;
353 | };
354 | name = Release;
355 | };
356 | B27643B71E8494DB00B67425 /* Debug */ = {
357 | isa = XCBuildConfiguration;
358 | buildSettings = {
359 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
360 | CODE_SIGN_ENTITLEMENTS = "ChordDetector/Chord Detector.entitlements";
361 | CODE_SIGN_IDENTITY = "Mac Developer";
362 | COMBINE_HIDPI_IMAGES = YES;
363 | CURRENT_PROJECT_VERSION = 16;
364 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
365 | DEVELOPMENT_TEAM = 77Y3N48SNF;
366 | ENABLE_HARDENED_RUNTIME = YES;
367 | INFOPLIST_FILE = ChordDetector/Info.plist;
368 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
369 | MACOSX_DEPLOYMENT_TARGET = 10.10;
370 | MARKETING_VERSION = 2.1;
371 | PRODUCT_BUNDLE_IDENTIFIER = com.cemolcay.chordDetector;
372 | PRODUCT_NAME = "Chord Detector";
373 | SWIFT_VERSION = 5.0;
374 | };
375 | name = Debug;
376 | };
377 | B27643B81E8494DB00B67425 /* Release */ = {
378 | isa = XCBuildConfiguration;
379 | buildSettings = {
380 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
381 | CODE_SIGN_ENTITLEMENTS = "ChordDetector/Chord Detector.entitlements";
382 | CODE_SIGN_IDENTITY = "Mac Developer";
383 | COMBINE_HIDPI_IMAGES = YES;
384 | CURRENT_PROJECT_VERSION = 16;
385 | DEVELOPMENT_TEAM = 77Y3N48SNF;
386 | ENABLE_HARDENED_RUNTIME = YES;
387 | INFOPLIST_FILE = ChordDetector/Info.plist;
388 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
389 | MACOSX_DEPLOYMENT_TARGET = 10.10;
390 | MARKETING_VERSION = 2.1;
391 | PRODUCT_BUNDLE_IDENTIFIER = com.cemolcay.chordDetector;
392 | PRODUCT_NAME = "Chord Detector";
393 | SWIFT_VERSION = 5.0;
394 | };
395 | name = Release;
396 | };
397 | B2C64CCA1E8E6A0300E7B74F /* Debug */ = {
398 | isa = XCBuildConfiguration;
399 | buildSettings = {
400 | BUNDLE_LOADER = "$(TEST_HOST)";
401 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
402 | CODE_SIGN_IDENTITY = "Mac Developer";
403 | COMBINE_HIDPI_IMAGES = YES;
404 | DEVELOPMENT_TEAM = "";
405 | INFOPLIST_FILE = ChordDetectorTests/Info.plist;
406 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
407 | PRODUCT_BUNDLE_IDENTIFIER = com.example.ChordDetectorTests;
408 | PRODUCT_NAME = "$(TARGET_NAME)";
409 | PROVISIONING_PROFILE_SPECIFIER = "";
410 | SWIFT_VERSION = 5.0;
411 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Chord Detector.app/Contents/MacOS/Chord Detector";
412 | };
413 | name = Debug;
414 | };
415 | B2C64CCB1E8E6A0300E7B74F /* Release */ = {
416 | isa = XCBuildConfiguration;
417 | buildSettings = {
418 | BUNDLE_LOADER = "$(TEST_HOST)";
419 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
420 | CODE_SIGN_IDENTITY = "Mac Developer";
421 | COMBINE_HIDPI_IMAGES = YES;
422 | DEVELOPMENT_TEAM = "";
423 | INFOPLIST_FILE = ChordDetectorTests/Info.plist;
424 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
425 | PRODUCT_BUNDLE_IDENTIFIER = com.example.ChordDetectorTests;
426 | PRODUCT_NAME = "$(TARGET_NAME)";
427 | PROVISIONING_PROFILE_SPECIFIER = "";
428 | SWIFT_VERSION = 5.0;
429 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Chord Detector.app/Contents/MacOS/Chord Detector";
430 | };
431 | name = Release;
432 | };
433 | /* End XCBuildConfiguration section */
434 |
435 | /* Begin XCConfigurationList section */
436 | B27643A21E8494DB00B67425 /* Build configuration list for PBXProject "ChordDetector" */ = {
437 | isa = XCConfigurationList;
438 | buildConfigurations = (
439 | B27643B41E8494DB00B67425 /* Debug */,
440 | B27643B51E8494DB00B67425 /* Release */,
441 | );
442 | defaultConfigurationIsVisible = 0;
443 | defaultConfigurationName = Release;
444 | };
445 | B27643B61E8494DB00B67425 /* Build configuration list for PBXNativeTarget "ChordDetector" */ = {
446 | isa = XCConfigurationList;
447 | buildConfigurations = (
448 | B27643B71E8494DB00B67425 /* Debug */,
449 | B27643B81E8494DB00B67425 /* Release */,
450 | );
451 | defaultConfigurationIsVisible = 0;
452 | defaultConfigurationName = Release;
453 | };
454 | B2C64CC91E8E6A0300E7B74F /* Build configuration list for PBXNativeTarget "ChordDetectorTests" */ = {
455 | isa = XCConfigurationList;
456 | buildConfigurations = (
457 | B2C64CCA1E8E6A0300E7B74F /* Debug */,
458 | B2C64CCB1E8E6A0300E7B74F /* Release */,
459 | );
460 | defaultConfigurationIsVisible = 0;
461 | defaultConfigurationName = Release;
462 | };
463 | /* End XCConfigurationList section */
464 | };
465 | rootObject = B276439F1E8494DB00B67425 /* Project object */;
466 | }
467 |
--------------------------------------------------------------------------------
/ChordDetector/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
--------------------------------------------------------------------------------