From fdac751d27cd865e0fa2e9d0281150f403139ec7 Mon Sep 17 00:00:00 2001 From: roux george buciu Date: Sun, 25 Nov 2018 10:10:05 -0500 Subject: [PATCH] Update repo with music theory work --- .../project.pbxproj | 514 ++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../UserInterfaceState.xcuserstate | Bin 0 -> 26120 bytes .../xcschemes/xcschememanagement.plist | 14 + .../Swift Musicology/Accidental.swift | 226 ++++++ .../Swift Musicology/Chord Progression.swift | 251 ++++++ Swift Musicology/Swift Musicology/Chord.swift | 753 ++++++++++++++++++ Swift Musicology/Swift Musicology/Info.plist | 22 + .../Swift Musicology/Interval.swift | 238 ++++++ Swift Musicology/Swift Musicology/Key.swift | 233 ++++++ .../Swift Musicology/NoteValue.swift | 75 ++ Swift Musicology/Swift Musicology/Pitch.swift | 279 +++++++ Swift Musicology/Swift Musicology/Scale.swift | 147 ++++ .../Swift Musicology/ScaleType.swift | 280 +++++++ .../Swift Musicology/Swift_Musicology.h | 19 + Swift Musicology/Swift Musicology/Tempo.swift | 26 + .../Swift Musicology/TimeSignature.swift | 46 ++ .../Swift MusicologyTests/Info.plist | 22 + .../MusicTheoryTests.swift | 351 ++++++++ .../Swift_MusicologyTests.swift | 34 + 21 files changed, 3545 insertions(+) create mode 100644 Swift Musicology/Swift Musicology.xcodeproj/project.pbxproj create mode 100644 Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcuserdata/acmelabs.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 Swift Musicology/Swift Musicology.xcodeproj/xcuserdata/acmelabs.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 Swift Musicology/Swift Musicology/Accidental.swift create mode 100644 Swift Musicology/Swift Musicology/Chord Progression.swift create mode 100644 Swift Musicology/Swift Musicology/Chord.swift create mode 100644 Swift Musicology/Swift Musicology/Info.plist create mode 100644 Swift Musicology/Swift Musicology/Interval.swift create mode 100644 Swift Musicology/Swift Musicology/Key.swift create mode 100644 Swift Musicology/Swift Musicology/NoteValue.swift create mode 100644 Swift Musicology/Swift Musicology/Pitch.swift create mode 100644 Swift Musicology/Swift Musicology/Scale.swift create mode 100644 Swift Musicology/Swift Musicology/ScaleType.swift create mode 100644 Swift Musicology/Swift Musicology/Swift_Musicology.h create mode 100644 Swift Musicology/Swift Musicology/Tempo.swift create mode 100644 Swift Musicology/Swift Musicology/TimeSignature.swift create mode 100644 Swift Musicology/Swift MusicologyTests/Info.plist create mode 100644 Swift Musicology/Swift MusicologyTests/MusicTheoryTests.swift create mode 100644 Swift Musicology/Swift MusicologyTests/Swift_MusicologyTests.swift diff --git a/Swift Musicology/Swift Musicology.xcodeproj/project.pbxproj b/Swift Musicology/Swift Musicology.xcodeproj/project.pbxproj new file mode 100644 index 0000000..136096e --- /dev/null +++ b/Swift Musicology/Swift Musicology.xcodeproj/project.pbxproj @@ -0,0 +1,514 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1207E0CF21AAE05300915CCD /* Swift_Musicology.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1207E0C521AAE05300915CCD /* Swift_Musicology.framework */; }; + 1207E0D421AAE05300915CCD /* Swift_MusicologyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0D321AAE05300915CCD /* Swift_MusicologyTests.swift */; }; + 1207E0D621AAE05300915CCD /* Swift_Musicology.h in Headers */ = {isa = PBXBuildFile; fileRef = 1207E0C821AAE05300915CCD /* Swift_Musicology.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1207E0E021AAE09500915CCD /* NoteValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0DF21AAE09500915CCD /* NoteValue.swift */; }; + 1207E0E221AAE0D100915CCD /* TimeSignature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0E121AAE0D100915CCD /* TimeSignature.swift */; }; + 1207E0E421AAE10E00915CCD /* Tempo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0E321AAE10E00915CCD /* Tempo.swift */; }; + 1207E0E621AAE13E00915CCD /* Accidental.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0E521AAE13E00915CCD /* Accidental.swift */; }; + 1207E0E821AAE15100915CCD /* Interval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0E721AAE15100915CCD /* Interval.swift */; }; + 1207E0EA21AAE15700915CCD /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0E921AAE15700915CCD /* Key.swift */; }; + 1207E0EC21AAE16000915CCD /* Pitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0EB21AAE16000915CCD /* Pitch.swift */; }; + 1207E0EE21AAE16600915CCD /* Chord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0ED21AAE16600915CCD /* Chord.swift */; }; + 1207E0F021AAE17000915CCD /* Chord Progression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0EF21AAE17000915CCD /* Chord Progression.swift */; }; + 1207E0F221AAE17B00915CCD /* Scale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0F121AAE17B00915CCD /* Scale.swift */; }; + 1207E0F421AAE24300915CCD /* MusicTheoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0F321AAE24300915CCD /* MusicTheoryTests.swift */; }; + 1207E0F621AAEF4800915CCD /* ScaleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1207E0F521AAEF4800915CCD /* ScaleType.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 1207E0D021AAE05300915CCD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1207E0BC21AAE05300915CCD /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1207E0C421AAE05300915CCD; + remoteInfo = "Swift Musicology"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 1207E0C521AAE05300915CCD /* Swift_Musicology.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swift_Musicology.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1207E0C821AAE05300915CCD /* Swift_Musicology.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Swift_Musicology.h; sourceTree = ""; }; + 1207E0C921AAE05300915CCD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1207E0CE21AAE05300915CCD /* Swift MusicologyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Swift MusicologyTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1207E0D321AAE05300915CCD /* Swift_MusicologyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swift_MusicologyTests.swift; sourceTree = ""; }; + 1207E0D521AAE05300915CCD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1207E0DF21AAE09500915CCD /* NoteValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteValue.swift; sourceTree = ""; }; + 1207E0E121AAE0D100915CCD /* TimeSignature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeSignature.swift; sourceTree = ""; }; + 1207E0E321AAE10E00915CCD /* Tempo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tempo.swift; sourceTree = ""; }; + 1207E0E521AAE13E00915CCD /* Accidental.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accidental.swift; sourceTree = ""; }; + 1207E0E721AAE15100915CCD /* Interval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interval.swift; sourceTree = ""; }; + 1207E0E921AAE15700915CCD /* Key.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = ""; }; + 1207E0EB21AAE16000915CCD /* Pitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pitch.swift; sourceTree = ""; }; + 1207E0ED21AAE16600915CCD /* Chord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chord.swift; sourceTree = ""; }; + 1207E0EF21AAE17000915CCD /* Chord Progression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Chord Progression.swift"; sourceTree = ""; }; + 1207E0F121AAE17B00915CCD /* Scale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scale.swift; sourceTree = ""; }; + 1207E0F321AAE24300915CCD /* MusicTheoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicTheoryTests.swift; sourceTree = ""; }; + 1207E0F521AAEF4800915CCD /* ScaleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleType.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1207E0C221AAE05300915CCD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1207E0CB21AAE05300915CCD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1207E0CF21AAE05300915CCD /* Swift_Musicology.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1207E0BB21AAE05300915CCD = { + isa = PBXGroup; + children = ( + 1207E0C721AAE05300915CCD /* Swift Musicology */, + 1207E0D221AAE05300915CCD /* Swift MusicologyTests */, + 1207E0C621AAE05300915CCD /* Products */, + ); + sourceTree = ""; + }; + 1207E0C621AAE05300915CCD /* Products */ = { + isa = PBXGroup; + children = ( + 1207E0C521AAE05300915CCD /* Swift_Musicology.framework */, + 1207E0CE21AAE05300915CCD /* Swift MusicologyTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 1207E0C721AAE05300915CCD /* Swift Musicology */ = { + isa = PBXGroup; + children = ( + 1207E0C821AAE05300915CCD /* Swift_Musicology.h */, + 1207E0C921AAE05300915CCD /* Info.plist */, + 1207E0DF21AAE09500915CCD /* NoteValue.swift */, + 1207E0E121AAE0D100915CCD /* TimeSignature.swift */, + 1207E0E321AAE10E00915CCD /* Tempo.swift */, + 1207E0E521AAE13E00915CCD /* Accidental.swift */, + 1207E0E721AAE15100915CCD /* Interval.swift */, + 1207E0E921AAE15700915CCD /* Key.swift */, + 1207E0EB21AAE16000915CCD /* Pitch.swift */, + 1207E0ED21AAE16600915CCD /* Chord.swift */, + 1207E0EF21AAE17000915CCD /* Chord Progression.swift */, + 1207E0F121AAE17B00915CCD /* Scale.swift */, + 1207E0F521AAEF4800915CCD /* ScaleType.swift */, + ); + path = "Swift Musicology"; + sourceTree = ""; + }; + 1207E0D221AAE05300915CCD /* Swift MusicologyTests */ = { + isa = PBXGroup; + children = ( + 1207E0D321AAE05300915CCD /* Swift_MusicologyTests.swift */, + 1207E0F321AAE24300915CCD /* MusicTheoryTests.swift */, + 1207E0D521AAE05300915CCD /* Info.plist */, + ); + path = "Swift MusicologyTests"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 1207E0C021AAE05300915CCD /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 1207E0D621AAE05300915CCD /* Swift_Musicology.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 1207E0C421AAE05300915CCD /* Swift Musicology */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1207E0D921AAE05300915CCD /* Build configuration list for PBXNativeTarget "Swift Musicology" */; + buildPhases = ( + 1207E0C021AAE05300915CCD /* Headers */, + 1207E0C121AAE05300915CCD /* Sources */, + 1207E0C221AAE05300915CCD /* Frameworks */, + 1207E0C321AAE05300915CCD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Swift Musicology"; + productName = "Swift Musicology"; + productReference = 1207E0C521AAE05300915CCD /* Swift_Musicology.framework */; + productType = "com.apple.product-type.framework"; + }; + 1207E0CD21AAE05300915CCD /* Swift MusicologyTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1207E0DC21AAE05300915CCD /* Build configuration list for PBXNativeTarget "Swift MusicologyTests" */; + buildPhases = ( + 1207E0CA21AAE05300915CCD /* Sources */, + 1207E0CB21AAE05300915CCD /* Frameworks */, + 1207E0CC21AAE05300915CCD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 1207E0D121AAE05300915CCD /* PBXTargetDependency */, + ); + name = "Swift MusicologyTests"; + productName = "Swift MusicologyTests"; + productReference = 1207E0CE21AAE05300915CCD /* Swift MusicologyTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 1207E0BC21AAE05300915CCD /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1010; + ORGANIZATIONNAME = "roux g. buciu"; + TargetAttributes = { + 1207E0C421AAE05300915CCD = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1010; + }; + 1207E0CD21AAE05300915CCD = { + CreatedOnToolsVersion = 10.1; + }; + }; + }; + buildConfigurationList = 1207E0BF21AAE05300915CCD /* Build configuration list for PBXProject "Swift Musicology" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 1207E0BB21AAE05300915CCD; + productRefGroup = 1207E0C621AAE05300915CCD /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1207E0C421AAE05300915CCD /* Swift Musicology */, + 1207E0CD21AAE05300915CCD /* Swift MusicologyTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1207E0C321AAE05300915CCD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1207E0CC21AAE05300915CCD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1207E0C121AAE05300915CCD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1207E0E821AAE15100915CCD /* Interval.swift in Sources */, + 1207E0EC21AAE16000915CCD /* Pitch.swift in Sources */, + 1207E0F021AAE17000915CCD /* Chord Progression.swift in Sources */, + 1207E0E421AAE10E00915CCD /* Tempo.swift in Sources */, + 1207E0E021AAE09500915CCD /* NoteValue.swift in Sources */, + 1207E0E221AAE0D100915CCD /* TimeSignature.swift in Sources */, + 1207E0E621AAE13E00915CCD /* Accidental.swift in Sources */, + 1207E0F621AAEF4800915CCD /* ScaleType.swift in Sources */, + 1207E0EE21AAE16600915CCD /* Chord.swift in Sources */, + 1207E0F221AAE17B00915CCD /* Scale.swift in Sources */, + 1207E0EA21AAE15700915CCD /* Key.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1207E0CA21AAE05300915CCD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1207E0F421AAE24300915CCD /* MusicTheoryTests.swift in Sources */, + 1207E0D421AAE05300915CCD /* Swift_MusicologyTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 1207E0D121AAE05300915CCD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1207E0C421AAE05300915CCD /* Swift Musicology */; + targetProxy = 1207E0D021AAE05300915CCD /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1207E0D721AAE05300915CCD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 1207E0D821AAE05300915CCD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 1207E0DA21AAE05300915CCD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 8DD4C7KK4P; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Swift Musicology/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.acmelabs.Swift-Musicology"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1207E0DB21AAE05300915CCD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 8DD4C7KK4P; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Swift Musicology/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.acmelabs.Swift-Musicology"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 1207E0DD21AAE05300915CCD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 8DD4C7KK4P; + INFOPLIST_FILE = "Swift MusicologyTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.acmelabs.Swift-MusicologyTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1207E0DE21AAE05300915CCD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 8DD4C7KK4P; + INFOPLIST_FILE = "Swift MusicologyTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.acmelabs.Swift-MusicologyTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1207E0BF21AAE05300915CCD /* Build configuration list for PBXProject "Swift Musicology" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1207E0D721AAE05300915CCD /* Debug */, + 1207E0D821AAE05300915CCD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1207E0D921AAE05300915CCD /* Build configuration list for PBXNativeTarget "Swift Musicology" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1207E0DA21AAE05300915CCD /* Debug */, + 1207E0DB21AAE05300915CCD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1207E0DC21AAE05300915CCD /* Build configuration list for PBXNativeTarget "Swift MusicologyTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1207E0DD21AAE05300915CCD /* Debug */, + 1207E0DE21AAE05300915CCD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 1207E0BC21AAE05300915CCD /* Project object */; +} diff --git a/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..e4a725c --- /dev/null +++ b/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcuserdata/acmelabs.xcuserdatad/UserInterfaceState.xcuserstate b/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcuserdata/acmelabs.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..a85fc40990f125f7d54adb0b7f7edb535f89f45e GIT binary patch literal 26120 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 literal 0 HcmV?d00001 diff --git a/Swift Musicology/Swift Musicology.xcodeproj/xcuserdata/acmelabs.xcuserdatad/xcschemes/xcschememanagement.plist b/Swift Musicology/Swift Musicology.xcodeproj/xcuserdata/acmelabs.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..a153310 --- /dev/null +++ b/Swift Musicology/Swift Musicology.xcodeproj/xcuserdata/acmelabs.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Swift Musicology.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Swift Musicology/Swift Musicology/Accidental.swift b/Swift Musicology/Swift Musicology/Accidental.swift new file mode 100644 index 0000000..e20c40a --- /dev/null +++ b/Swift Musicology/Swift Musicology/Accidental.swift @@ -0,0 +1,226 @@ +// +// Accidental.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +/// Used for calculating values of the `Key`s and `Pitche`s. +public enum Accidental: Codable, Equatable, Hashable { + + // Base definition of accidentals. + /// No accidental. + case natural + /// Reduces the `Key` or `Pitch` value by the given amount of semitones. + case flats(amount: Int) + /// Increases the `Key` or `Pitch` value by the given amount of semitones. + case sharps(amount: Int) + + + // Human friendly definition of accidentals. + public static let flat: Accidental = .flats(amount: 1) + public static let sharp: Accidental = .sharps(amount: 1) + public static let doubleFlat: Accidental = .flats(amount: 2) + public static let doubleSharp: Accidental = .sharps(amount: 2) + + /// A flag for `description` function that determines if it should use double sharp + /// and double flat symbols. It's useful to set it to `false` where fonts do not + /// support those symbols. Defaults to `true`. + public static var shouldUseDoubleFlatAndDoubleSharpNotation = true +} + + +extension Accidental: RawRepresentable { + + public typealias RawValue = Int + + /// Value of the accidental in terms of semitones. + public var rawValue: Int { + switch self { + case .natural: + return 0 + case let .flats(amount): + return -amount + case let .sharps(amount): + return amount + } + } + + /// Initilizes the accidental with an integer that represents the semitone amount. + /// + /// - Parameter rawValue: semitone value of the accidental. Zero if natural; above + /// zero if sharp; below zero if flat. + public init?(rawValue: Accidental.RawValue) { + if rawValue == 0 { + self = .natural + } else if rawValue > 0 { + self = .sharps(amount: rawValue) + } else { + self = .flats(amount: -rawValue) + } + } +} + + +extension Accidental: ExpressibleByIntegerLiteral { + + public typealias IntegerLiteralType = Int + + /// Initilizes the accidental with an integer literal value. + /// + /// - Parameter value: semitone value of the accidental. Zero if natural; above + /// zero if sharp; below zero if flat. + public init(integerLiteral value: Accidental.IntegerLiteralType) { + self = Accidental(rawValue: value) ?? .natural + } +} + + +extension Accidental: ExpressibleByStringLiteral { + + public typealias StringLiteralType = String + + public init(stringLiteral value: Accidental.StringLiteralType) { + var sum = 0 + for i in 0 ..< value.count { + switch value[value.index(value.startIndex, offsetBy: i)] { + case "#", "♯": + sum += 1 + case "b", "♭": + sum -= 1 + default: + break + } + } + self = Accidental(rawValue: sum) ?? .natural + } +} + + +extension Accidental: CustomStringConvertible { + + /// Returns the notation string of the accidental. + public var notation: String { + if case .natural = self { + return "♮" + } + return description + } + + /// Returns the notation string of the accidental. Returns empty string if the Accidental is natural. + public var description: String { + switch self { + case .natural: + return "" + case let .flats(amount): + switch amount { + case 0: return Accidental.natural.description + case 1: return "♭" + case 2 where Accidental.shouldUseDoubleFlatAndDoubleSharpNotation: return "𝄫" + default: return amount > 0 ? (0 ..< amount).map({ _ in Accidental.flats(amount: 1).description }).joined() : "" + } + case let .sharps(amount): + switch amount { + case 0: return Accidental.natural.description + case 1: return "♯" + case 2 where Accidental.shouldUseDoubleFlatAndDoubleSharpNotation: return "𝄪" + default: return amount > 0 ? (0 ..< amount).map({ _ in Accidental.sharps(amount: 1).description }).joined() : "" + } + } + } +} + + +// MARK: - Accidental Operations + +/// Returns a new accidental by adding up two accidentals in the equation. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns the sum of two accidentals. +public func + (lhs: Accidental, rhs: Accidental) -> Accidental { + return Accidental(integerLiteral: lhs.rawValue + rhs.rawValue) +} + +/// Returns a new accidental by substracting two accidentals in the equation. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns the difference of two accidentals. +public func - (lhs: Accidental, rhs: Accidental) -> Accidental { + return Accidental(integerLiteral: lhs.rawValue - rhs.rawValue) +} + +/// Returns a new accidental by adding up an int to the accidental in the equation. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns the sum of two accidentals. +public func + (lhs: Accidental, rhs: Int) -> Accidental { + return Accidental(integerLiteral: lhs.rawValue + rhs) +} + +/// Returns a new accidental by substracting an int from the accidental in the equation. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns the difference of two accidentals. +public func - (lhs: Accidental, rhs: Int) -> Accidental { + return Accidental(integerLiteral: lhs.rawValue - rhs) +} + +/// Multiples an accidental with a multiplier. +/// +/// - Parameters: +/// - lhs: Accidental you want to multiply. +/// - rhs: Multiplier. +/// - Returns: Returns a multiplied acceident. +public func * (lhs: Accidental, rhs: Int) -> Accidental { + return Accidental(integerLiteral: lhs.rawValue * rhs) +} + +/// Divides an accidental with a multiplier +/// +/// - Parameters: +/// - lhs: Accidental you want to divide. +/// - rhs: Multiplier. +/// - Returns: Returns a divided accidental. +public func / (lhs: Accidental, rhs: Int) -> Accidental { + return Accidental(integerLiteral: lhs.rawValue / rhs) +} + +/// Checks if the two accidental is identical in terms of their semitone values. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns true if two accidentalals is identical. +public func == (lhs: Accidental, rhs: Accidental) -> Bool { + return lhs.rawValue == rhs.rawValue +} + +/// Checks if the two accidental is exactly identical. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns true if two accidentalals is identical. +public func === (lhs: Accidental, rhs: Accidental) -> Bool { + switch (lhs, rhs) { + case (.natural, .natural): + return true + case let (.sharps(a), .sharps(b)): + return a == b + case let (.flats(a), .flats(b)): + return a == b + default: + return false + } +} diff --git a/Swift Musicology/Swift Musicology/Chord Progression.swift b/Swift Musicology/Swift Musicology/Chord Progression.swift new file mode 100644 index 0000000..bc9ca0f --- /dev/null +++ b/Swift Musicology/Swift Musicology/Chord Progression.swift @@ -0,0 +1,251 @@ +// +// Chord Progression.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +/// A struct for storing custom progressions. +public struct CustomChordProgression: Codable { + /// Name of the progression. + public var name: String + /// Chord progression with `ChordProgresion.custom` type. + public var progression: ChordProgression +} + +/// A node of chord progression in intervals. +public enum ChordProgressionNode: Int, CustomStringConvertible, Codable { + /// First-degree node + case i + /// Second-degree node + case ii + /// Third-degree node + case iii + /// Fourth-degree node + case iv + /// Fifth-degree node + case v + /// Sixth-degree node + case vi + /// Seventh-degree node + case vii + + /// Meaningful next nodes, useful for a recommendation engine. + public var next: [ChordProgressionNode] { + switch self { + case .i: + return [.i, .ii, .iii, .iv, .v, .vi, .vii] + case .ii: + return [.v, .iii, .vi, .vii] + case .iii: + return [.ii, .iv, .vi] + case .iv: + return [.i, .iii, .v, .vii] + case .v: + return [.i] + case .vi: + return [.ii, .iv] + case .vii: + return [.vi] + } + } + + /// All nodes. + public static let all: [ChordProgressionNode] = [.i, .ii, .iii, .iv, .v, .vi, .vii] + + // MARK: CustomStringConvertible + + /// Returns roman numeric string of the node. + public var description: String { + switch self { + case .i: return "I" + case .ii: return "II" + case .iii: return "III" + case .iv: return "IV" + case .v: return "V" + case .vi: return "VI" + case .vii: return "VII" + } + } +} + +/// Chord progression enum that you can create hard-coded and custom progressions. +public enum ChordProgression: CustomStringConvertible, Codable { + /// All nodes from first to seventh. + case allNodes + /// I - V - VI - IV progression. + case i_v_vi_iv + /// VI - V - IV - V progression. + case vi_v_iv_v + /// I - VI - IV - V progression. + case i_vi_iv_v + /// I - IV - VI - V progression. + case i_iv_vi_v + /// I - V - IV - V progression. + case i_v_iv_v + /// VI - II - V - I progression. + case vi_ii_v_i + /// I - VI - II - V progression. + case i_vi_ii_v + /// I - IV - II - V progression. + case i_iv_ii_v + /// VI - IV - I - V progression. + case vi_iv_i_v + /// I - VI - III - VII progression. + case i_vi_iii_vii + /// VI - V - IV - III progression. + case vi_v_iv_iii + /// I - V - VI - III - IV - I - IV - V progression. + case i_v_vi_iii_iv_i_iv_v + /// IV - I - V - IV progression. + case iv_i_v_iv + /// I - II - VI - IV progression. + case i_ii_vi_iv + /// I - III - VI - IV progression. + case i_iii_vi_iv + /// I - V - II - IV progression. + case i_v_ii_iv + /// II - IV - I - V progression. + case ii_iv_i_v + /// Custom progression with any node sequence. + case custom([ChordProgressionNode]) + + /// Initilizes the chord progression with its nodes. + /// + /// - Parameter nodes: Nodes of the chord progression. + public init(nodes: [ChordProgressionNode]) { + if let progression = ChordProgression.all.filter({ $0.nodes == nodes }).first { + self = progression + } else { + self = .custom(nodes) + } + } + + /// Returns all nodes of the progression. + public var nodes: [ChordProgressionNode] { + switch self { + case .allNodes: + return [.i, .ii, .iii, .iv, .v, .vi, .vii] + case .i_v_vi_iv: + return [.i, .v, .vi, .iv] + case .vi_v_iv_v: + return [.vi, .v, .iv, .v] + case .i_vi_iv_v: + return [.i, .vi, .iv, .v] + case .i_iv_vi_v: + return [.i, .iv, .vi, .v] + case .i_v_iv_v: + return [.i, .v, .iv, .v] + case .vi_ii_v_i: + return [.v, .ii, .v, .i] + case .i_vi_ii_v: + return [.i, .vi, .ii, .v] + case .i_iv_ii_v: + return [.i, .iv, .ii, .v] + case .vi_iv_i_v: + return [.vi, .iv, .i, .v] + case .i_vi_iii_vii: + return [.i, .vi, .iii, .vii] + case .vi_v_iv_iii: + return [.vi, .v, .vi, .iii] + case .i_v_vi_iii_iv_i_iv_v: + return [.i, .v, .vi, .iii, .iv, .i, .iv, .v] + case .iv_i_v_iv: + return [.iv, .i, .v, .iv] + case .i_ii_vi_iv: + return [.i, .ii, .vi, .iv] + case .i_iii_vi_iv: + return [.i, .iii, .vi, .iv] + case .i_v_ii_iv: + return [.i, .v, .ii, .iv] + case .ii_iv_i_v: + return [.ii, .iv, .i, .v] + case let .custom(nodes): + return nodes + } + } + + /// All hard-coded chord progressions. + public static var all: [ChordProgression] { + return [ + .allNodes, + .i_v_vi_iv, + .vi_v_iv_v, + .i_vi_iv_v, + .i_iv_vi_v, + .i_v_iv_v, + .vi_ii_v_i, + .i_vi_ii_v, + .i_iv_ii_v, + .vi_iv_i_v, + .i_vi_iii_vii, + .vi_v_iv_iii, + .i_v_vi_iii_iv_i_iv_v, + .iv_i_v_iv, + .i_ii_vi_iv, + .i_iii_vi_iv, + .i_v_ii_iv, + .ii_iv_i_v, + ] + } + + /// Generates chord progression for a `Scale` with `Scale.HarmonicField` and optionally inverted chords. + /// + /// - Parameters: + /// - scale: Scale of the chords going to be generated. + /// - harmonicField: Harmonic field of the chords going to be generated. + /// - inversion: Inversion of the chords going to be generated. + /// - Returns: Returns all possible chords for a scale. Returns nil if the chord is not generated for particular `ChordProgressionNode`. + public func chords(for scale: Scale, harmonicField: Scale.HarmonicField, inversion: Int = 0) -> [Chord?] { + let indices = nodes.map({ $0.rawValue }) + let harmonics = scale.harmonicField(for: harmonicField, inversion: inversion) + var chords = [Chord?]() + for index in indices { + if index < harmonics.count { + chords.append(harmonics[index]) + } + } + return chords + } + + // MARK: CustomStringConvertible + + /// Returns the chord progression name. + public var description: String { + if case .allNodes = self { + return "All" + } + return nodes.map({ "\($0)" }).joined(separator: " - ") + } + + // MARK: Codable + + /// Codable protocol `CodingKey`s + /// + /// - nodes: Coding key for the nodes. + private enum CodingKeys: String, CodingKey { + case nodes + } + + /// Initilizes chord progression with a `Decoder`. + /// + /// - Parameter decoder: The decoder. + /// - Throws: Throws error if can not decodes. + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + let nodes = try values.decode([ChordProgressionNode].self, forKey: .nodes) + self = ChordProgression(nodes: nodes) + } + + /// Encodes the chord progression. + /// + /// - Parameter encoder: The encoder. + /// - Throws: Throws error if can not encodes. + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(nodes, forKey: .nodes) + } +} diff --git a/Swift Musicology/Swift Musicology/Chord.swift b/Swift Musicology/Swift Musicology/Chord.swift new file mode 100644 index 0000000..c33a8d3 --- /dev/null +++ b/Swift Musicology/Swift Musicology/Chord.swift @@ -0,0 +1,753 @@ +// +// Chord.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +// MARK: - ChordPart + +/// Protocol that defines a printable chord part. +public protocol ChordDescription: CustomStringConvertible, Codable { + /// Notation of chord. + var notation: String { get } +} + +/// Protocol that defines a chord part. +public protocol ChordPart: ChordDescription { + /// Interval between the root. + var interval: Interval { get } + /// Initilize chord part with interval. + init?(interval: Interval) +} + +extension Interval { + /// Returns the sum of two intervals semitones. + /// + /// - Parameters: + /// - lhs: Left hand side of the equation. + /// - rhs: Right hand side of the equation. + /// - Returns: Sum of two intervals in terms of their semitones. + fileprivate static func + (lhs: Interval, rhs: Accidental) -> Int { + return lhs.semitones + rhs.rawValue + } +} + +/// Defines third part of the chord. Second note after the root. +public enum ChordThirdType: Int, ChordPart { + /// Defines major chord. 4 semitones between root. + case major + /// Defines minor chord. 3 semitones between root. + case minor + + /// Initilize chord part with interval. + public init?(interval: Interval) { + switch interval { + case ChordThirdType.major.interval: + self = .major + case ChordThirdType.minor.interval: + self = .minor + default: + return nil + } + } + + /// Interval between root. + public var interval: Interval { + switch self { + case .major: + return .M3 + case .minor: + return .m3 + } + } + + /// Notation of chord part. + public var notation: String { + switch self { + case .major: return "" + case .minor: return "m" + } + } + + /// Description of chord part. + public var description: String { + switch self { + case .major: return "Major" + case .minor: return "Minor" + } + } + + /// All values of `ChordThirdType`. + public static var all: [ChordThirdType] { + return [.major, .minor] + } +} + +/// Defines fifth part of the chord. Third note after root note. +public enum ChordFifthType: Int, ChordPart { + /// Perfect fifth interval between root. + case perfect + /// Half step down of perfect fifth. + case diminished + /// Half step up of perfect fifth. + case agumented + + /// Initilize chord part with interval. + public init?(interval: Interval) { + switch interval { + case ChordFifthType.perfect.interval: + self = .perfect + case ChordFifthType.diminished.interval: + self = .diminished + case ChordFifthType.agumented.interval: + self = .agumented + default: + return nil + } + } + + /// Interval between root. + public var interval: Interval { + switch self { + case .perfect: + return .P5 + case .diminished: + return .d5 + case .agumented: + return .A5 + } + } + + /// Notation of chord part. + public var notation: String { + switch self { + case .perfect: return "" + case .agumented: return "♯5" + case .diminished: return "♭5" + } + } + + /// Description of chord part. + public var description: String { + switch self { + case .perfect: return "" + case .agumented: return "Agumented" + case .diminished: return "Diminished" + } + } + + /// All values of `ChordFifthType`. + public static var all: [ChordFifthType] { + return [.perfect, .diminished, .agumented] + } +} + +/// Defiens sixth chords. If you add the sixth note, you have sixth chord. +public struct ChordSixthType: ChordPart { + /// Default initilizer. + public init() {} + + /// Initilize chord part with interval. + public init?(interval: Interval) { + switch interval { + case .M6: + self.init() + default: + return nil + } + } + + /// Interval between root. + public var interval: Interval { + return .M6 + } + + /// Notation of chord part. + public var notation: String { + return "6" + } + + /// Description of chord part. + public var description: String { + return "Sixth" + } +} + +/// Defiens seventh chords. If you add seventh note, you have seventh chord. +public enum ChordSeventhType: Int, ChordPart { + /// Seventh note of the chord. 11 semitones between root. + case major + /// semitone down of seventh note. 10 semitones between root. + case dominant + + /// Initilize chord part with interval. + public init?(interval: Interval) { + switch interval { + case ChordSeventhType.major.interval: + self = .major + case ChordSeventhType.dominant.interval: + self = .dominant + default: + return nil + } + } + + /// Interval between root. + public var interval: Interval { + switch self { + case .major: + return .M7 + case .dominant: + return .m7 + } + } + + /// Notation of chord part. + public var notation: String { + switch self { + case .major: return "maj7" + case .dominant: return "7" + } + } + + /// Description of chord part. + public var description: String { + switch self { + case .major: return "Major 7th" + case .dominant: return "Dominant 7th" + } + } + + /// All values of `ChordSeventhType`. + public static var all: [ChordSeventhType] { + return [.major, .dominant] + } +} + +/// Defines suspended chords. +/// If you play second or fourth note of chord, instead of thirds, than you have suspended chords. +public enum ChordSuspendedType: Int, ChordPart { + /// Second note of chord instead of third part. 2 semitones between root. + case sus2 + /// Fourth note of chord instead of third part. 5 semitones between root. + case sus4 + + /// Initilize chord part with interval + public init?(interval: Interval) { + switch interval { + case ChordSuspendedType.sus2.interval: + self = .sus2 + case ChordSuspendedType.sus4.interval: + self = .sus4 + default: + return nil + } + } + + /// Interval between root. + public var interval: Interval { + switch self { + case .sus2: + return .M2 + case .sus4: + return .P4 + } + } + + /// Notation of chord part. + public var notation: String { + switch self { + case .sus2: return "(sus2)" + case .sus4: return "(sus4)" + } + } + + /// Description of chord part. + public var description: String { + switch self { + case .sus2: return "Suspended 2nd" + case .sus4: return "Suspended 4th" + } + } + + /// All values of `ChordSuspendedType`. + public static var all: [ChordSuspendedType] { + return [.sus2, .sus4] + } +} + +/// Defines extended chords. +/// If you add one octave up of second, fourth or sixth notes of the chord, you have extended chords. +/// You can combine extended chords more than one in a chord. +public struct ChordExtensionType: ChordPart { + /// Defines type of the extended chords. + public enum ExtensionType: Int, ChordDescription { + /// 9th chord. Second note of the chord, one octave up from root. + case ninth = 9 + /// 11th chord. Eleventh note of the chord, one octave up from root. + case eleventh = 11 + /// 13th chord. Sixth note of the chord, one octave up from root. + case thirteenth = 13 + + /// Interval between root. + public var interval: Interval { + switch self { + case .ninth: + return .M9 + case .eleventh: + return .P11 + case .thirteenth: + return .M13 + } + } + + /// Notation of the chord part. + public var notation: String { + switch self { + case .ninth: + return "9" + case .eleventh: + return "11" + case .thirteenth: + return "13" + } + } + + /// Description of the chord part. + public var description: String { + switch self { + case .ninth: + return "9th" + case .eleventh: + return "11th" + case .thirteenth: + return "13th" + } + } + + /// All values of `ExtensionType`. + public static var all: [ExtensionType] { + return [.ninth, .eleventh, .thirteenth] + } + } + + /// Type of extended chord. + public var type: ExtensionType + /// Accident of extended chord. + public var accidental: Accidental + /// If there are no seventh note and only one extended part is this. Defaults false + internal var isAdded: Bool + + /// Initilizes extended chord. + /// + /// - Parameters: + /// - type: Type of extended chord. + /// - accident: Accident of extended chord. Defaults natural. + public init(type: ExtensionType, accidental: Accidental = .natural) { + self.type = type + self.accidental = accidental + isAdded = false + } + + /// Initilize chord part with interval + public init?(interval: Interval) { + switch interval.semitones { + case ExtensionType.ninth.interval + Accidental.natural: + self = ChordExtensionType(type: .ninth, accidental: .natural) + case ExtensionType.ninth.interval + Accidental.flat: + self = ChordExtensionType(type: .ninth, accidental: .flat) + case ExtensionType.ninth.interval + Accidental.sharp: + self = ChordExtensionType(type: .ninth, accidental: .sharp) + case ExtensionType.eleventh.interval + Accidental.natural: + self = ChordExtensionType(type: .eleventh, accidental: .natural) + case ExtensionType.eleventh.interval + Accidental.flat: + self = ChordExtensionType(type: .eleventh, accidental: .flat) + case ExtensionType.eleventh.interval + Accidental.sharp: + self = ChordExtensionType(type: .eleventh, accidental: .sharp) + case ExtensionType.thirteenth.interval + Accidental.natural: + self = ChordExtensionType(type: .thirteenth, accidental: .natural) + case ExtensionType.thirteenth.interval + Accidental.flat: + self = ChordExtensionType(type: .thirteenth, accidental: .flat) + case ExtensionType.thirteenth.interval + Accidental.sharp: + self = ChordExtensionType(type: .thirteenth, accidental: .sharp) + default: + return nil + } + } + + /// Interval between root. + public var interval: Interval { + switch (type, accidental) { + case (.ninth, .natural): return .M9 + case (.ninth, .flat): return .m9 + case (.ninth, .sharp): return .A9 + + case (.eleventh, .natural): return .P11 + case (.eleventh, .flat): return .P11 + case (.eleventh, .sharp): return .A11 + + case (.thirteenth, .natural): return .M13 + case (.thirteenth, .flat): return .m13 + case (.thirteenth, .sharp): return .A13 + + case (.ninth, _): return .M9 + case (.eleventh, _): return .P11 + case (.thirteenth, _): return .M13 + } + } + + /// Notation of chord part. + public var notation: String { + return "\(accidental.notation)\(type.notation)" + } + + /// Description of chord part. + public var description: String { + return "\(isAdded ? "Added " : "")\(accidental.description) \(type.description)" + } + + /// All values of `ChordExtensionType` + public static var all: [ChordExtensionType] { + var all = [ChordExtensionType]() + for type in ExtensionType.all { + for accident in [Accidental.natural, Accidental.flat, Accidental.sharp] { + all.append(ChordExtensionType(type: type, accidental: accident)) + } + } + return all + } +} + +// MARK: - ChordType + +/// Checks the equability between two `ChordType`s by their intervals. +/// +/// - Parameters: +/// - left: Left handside of the equation. +/// - right: Right handside of the equation. +/// - Returns: Returns Bool value of equation of two given chord types. +public func == (left: ChordType?, right: ChordType?) -> Bool { + switch (left, right) { + case let (.some(left), .some(right)): + return left.intervals == right.intervals + case (.none, .none): + return true + default: + return false + } +} + +/// Defines full type of chord with all chord parts. +public struct ChordType: ChordDescription { + /// Thirds part. Second note of the chord. + public var third: ChordThirdType + /// Fifths part. Third note of the chord. + public var fifth: ChordFifthType + /// Defines a sixth chord. Defaults nil. + public var sixth: ChordSixthType? + /// Defines a seventh chord. Defaults nil. + public var seventh: ChordSeventhType? + /// Defines a suspended chord. Defaults nil. + public var suspended: ChordSuspendedType? + /// Defines extended chord. Defaults nil. + public var extensions: [ChordExtensionType]? { + didSet { + if extensions?.count == 1 { + extensions![0].isAdded = seventh == nil + // Add other extensions if needed + if let ext = extensions?.first, ext.type == .eleventh, !ext.isAdded { + extensions?.append(ChordExtensionType(type: .ninth)) + } else if let ext = extensions?.first, ext.type == .thirteenth, !ext.isAdded { + extensions?.append(ChordExtensionType(type: .ninth)) + extensions?.append(ChordExtensionType(type: .eleventh)) + } + } + } + } + + /// Initilze the chord type with its parts. + /// + /// - Parameters: + /// - third: Thirds part. + /// - fifth: Fifths part. Defaults perfect fifth. + /// - sixth: Sixth part. Defaults nil. + /// - seventh: Seventh part. Defaults nil. + /// - suspended: Suspended part. Defaults nil. + /// - extensions: Extended chords part. Defaults nil. Could be add more than one extended chord. + public init(third: ChordThirdType, fifth: ChordFifthType = .perfect, sixth: ChordSixthType? = nil, seventh: ChordSeventhType? = nil, suspended: ChordSuspendedType? = nil, extensions: [ChordExtensionType]? = nil) { + self.third = third + self.fifth = fifth + self.sixth = sixth + self.seventh = seventh + self.suspended = suspended + self.extensions = extensions + + if extensions?.count == 1 { + self.extensions![0].isAdded = seventh == nil + // Add other extensions if needed + if let ext = self.extensions?.first, ext.type == .eleventh, !ext.isAdded { + self.extensions?.append(ChordExtensionType(type: .ninth)) + } else if let ext = self.extensions?.first, ext.type == .thirteenth, !ext.isAdded { + self.extensions?.append(ChordExtensionType(type: .ninth)) + self.extensions?.append(ChordExtensionType(type: .eleventh)) + } + } + } + + /// Initilze the chord type with its intervals. + /// + /// - Parameters: + /// - intervals: Intervals of chord notes distances between root note for each. + public init?(intervals: [Interval]) { + var third: ChordThirdType? + var fifth: ChordFifthType? + var sixth: ChordSixthType? + var seventh: ChordSeventhType? + var suspended: ChordSuspendedType? + var extensions = [ChordExtensionType]() + + for interval in intervals { + if let thirdPart = ChordThirdType(interval: interval) { + third = thirdPart + } else if let fifthPart = ChordFifthType(interval: interval) { + fifth = fifthPart + } else if let sixthPart = ChordSixthType(interval: interval) { + sixth = sixthPart + } else if let seventhPart = ChordSeventhType(interval: interval) { + seventh = seventhPart + } else if let suspendedPart = ChordSuspendedType(interval: interval) { + suspended = suspendedPart + } else if let extensionPart = ChordExtensionType(interval: interval) { + extensions.append(extensionPart) + } + } + + guard let thirdPart = third, + let fifthPart = fifth + else { return nil } + + self = ChordType( + third: thirdPart, + fifth: fifthPart, + sixth: sixth, + seventh: seventh, + suspended: suspended, + extensions: extensions + ) + } + + /// Intervals of parts between root. + public var intervals: [Interval] { + var parts: [ChordPart?] = [sixth == nil ? third : nil, suspended, fifth, sixth, seventh] + parts += extensions?.sorted(by: { $0.type.rawValue < $1.type.rawValue }).map({ $0 as ChordPart? }) ?? [] + return [.P1] + parts.compactMap({ $0?.interval }) + } + + /// Notation of the chord type. + public var notation: String { + var seventhNotation = seventh?.notation ?? "" + var sixthNotation = sixth == nil ? "" : "\(sixth!.notation)\(seventh == nil ? "" : "/")" + let suspendedNotation = suspended?.notation ?? "" + var extensionNotation = "" + let ext = extensions?.sorted(by: { $0.type.rawValue < $1.type.rawValue }) ?? [] + + var singleNotation = !ext.isEmpty && true + for i in 0 ..< max(0, ext.count - 1) { + if ext[i].accidental != .natural { + singleNotation = false + } + } + + if singleNotation { + extensionNotation = "(\(ext.last!.notation))" + } else { + extensionNotation = ext + .compactMap({ $0.notation }) + .joined(separator: "/") + extensionNotation = extensionNotation.isEmpty ? "" : "(\(extensionNotation))" + } + + if seventh != nil { + // Don't show major seventh note if extended is a major as well + if seventh == .major, (extensions ?? []).count > 0 { + seventhNotation = "" + sixthNotation = sixth == nil ? "" : sixth!.notation + } + // Show fifth note after seventh in parenthesis + if fifth == .agumented || fifth == .diminished { + return "\(third.notation)\(sixthNotation)\(seventhNotation)(\(fifth.notation))\(suspendedNotation)\(extensionNotation)" + } + } + + return "\(third.notation)\(fifth.notation)\(sixthNotation)\(seventhNotation)\(suspendedNotation)\(extensionNotation)" + } + + /// Description of the chord type. + public var description: String { + let seventhNotation = seventh?.description + let sixthNotation = sixth?.description + let suspendedNotation = suspended?.description + let extensionsNotation = extensions? + .sorted(by: { $0.type.rawValue < $1.type.rawValue }) + .map({ $0.description as String? }) ?? [] + let desc = [third.description, fifth.description, sixthNotation, seventhNotation, suspendedNotation] + extensionsNotation + return desc.compactMap({ $0 }).joined(separator: " ") + } + + /// All possible chords could be generated. + public static var all: [ChordType] { + func combinations(_ elements: [ChordExtensionType], taking: Int = 1) -> [[ChordExtensionType]] { + guard elements.count >= taking else { return [] } + guard elements.count > 0 && taking > 0 else { return [[]] } + + if taking == 1 { + return elements.map { [$0] } + } + + var comb = [[ChordExtensionType]]() + for (index, element) in elements.enumerated() { + var reducedElements = elements + reducedElements.removeFirst(index + 1) + comb += combinations(reducedElements, taking: taking - 1).map { [element] + $0 } + } + return comb + } + + var all = [ChordType]() + let allThird = ChordThirdType.all + let allFifth = ChordFifthType.all + let allSixth: [ChordSixthType?] = [ChordSixthType(), nil] + let allSeventh: [ChordSeventhType?] = ChordSeventhType.all + [nil] + let allSus: [ChordSuspendedType?] = ChordSuspendedType.all + [nil] + let allExt = combinations(ChordExtensionType.all) + + combinations(ChordExtensionType.all, taking: 2) + + combinations(ChordExtensionType.all, taking: 3) + + for third in allThird { + for fifth in allFifth { + for sixth in allSixth { + for seventh in allSeventh { + for sus in allSus { + for ext in allExt { + all.append(ChordType( + third: third, + fifth: fifth, + sixth: sixth, + seventh: seventh, + suspended: sus, + extensions: ext + )) + } + } + } + } + } + } + return all + } +} + +// MARK: - Chord + +/// Checks the equability between two chords by their base key and notes. +/// +/// - Parameters: +/// - left: Left handside of the equation. +/// - right: Right handside of the equation. +/// - Returns: Returns Bool value of equation of two given chords. +public func == (left: Chord?, right: Chord?) -> Bool { + switch (left, right) { + case let (.some(left), .some(right)): + return left.key == right.key && left.type == right.type + case (.none, .none): + return true + default: + return false + } +} + +/// Defines a chord with a root note and type. +public struct Chord: ChordDescription { + /// Type of the chord. + public var type: ChordType + /// Root key of the chord. + public var key: Key + /// Inversion index of the chord. + public private(set) var inversion: Int + + /// Initilizes chord with root note and type. + /// + /// - Parameters: + /// - key: Root key of the chord. + /// - type: Tyoe of the chord. + public init(type: ChordType, key: Key, inversion: Int = 0) { + self.type = type + self.key = key + self.inversion = inversion + } + + /// Generates notes of the chord for octave. + /// + /// - Parameter octave: Octave of the root note for the build chord from. + /// - Returns: Generates notes of the chord. + public func pitches(octave: Int) -> [Pitch] { + var intervals = type.intervals + for _ in 0 ..< inversion { + intervals = intervals.shifted + } + + let root = Pitch(key: key, octave: octave) + let invertedPitches = intervals.map({ root + $0 }) + return invertedPitches + .enumerated() + .map({ index, item in + index < type.intervals.count - inversion ? item : Pitch(key: item.key, octave: item.octave + 1) + }) + } + + /// Generates notes of the chord for octave range. + /// + /// - Parameter octaves: Octaves of the root note to build chord from. + /// - Returns: Generates notes of the chord. + public func pitches(octaves: [Int]) -> [Pitch] { + return octaves.flatMap({ pitches(octave: $0) }).sorted(by: { $0.rawValue < $1.rawValue }) + } + + /// Types of notes in chord. + public var keys: [Key] { + return pitches(octave: 1).map({ $0.key }) + } + + /// Possible inversions of the chord. + public var inversions: [Chord] { + return [Int](0 ..< keys.count).map({ Chord(type: type, key: key, inversion: $0) }) + } + + /// Notation of the chord. + public var notation: String { + let inversionNotation = inversion > 0 && inversion < keys.count ? "/\(keys[0])" : "" + return "\(key)\(type.notation)\(inversionNotation)" + } + + /// Description of the chord. + public var description: String { + let inversionNotation = inversion > 0 ? " \(inversion). Inversion" : "" + return "\(key) \(type)\(inversionNotation)" + } +} + +// MARK: - Extensions + +extension Array { + internal var shifted: Array { + guard let firstElement = first else { return self } + var arr = self + arr.removeFirst() + arr.append(firstElement) + return arr + } +} diff --git a/Swift Musicology/Swift Musicology/Info.plist b/Swift Musicology/Swift Musicology/Info.plist new file mode 100644 index 0000000..e1fe4cf --- /dev/null +++ b/Swift Musicology/Swift Musicology/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/Swift Musicology/Swift Musicology/Interval.swift b/Swift Musicology/Swift Musicology/Interval.swift new file mode 100644 index 0000000..4d57b59 --- /dev/null +++ b/Swift Musicology/Swift Musicology/Interval.swift @@ -0,0 +1,238 @@ +// +// Interval.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +/// Defines the interval between `Pitch`es in semitones. +public struct Interval: Codable, Equatable { + + /// Quality type of the interval. + public enum Quality: Int, Codable, Equatable, CustomStringConvertible { + case diminished + case perfect + case minor + case major + case augmented + + + // MARK: CustomStringConvertible + + /// Returns the notation of the interval quality. + public var notation: String { + switch self { + case .diminished: return "d" + case .perfect: return "P" + case .minor: return "m" + case .major: return "M" + case .augmented: return "A" + } + } + + /// Returns the name of the interval quality. + public var description: String { + switch self { + case .diminished: return "Diminished" + case .perfect: return "Perfect" + case .minor: return "Minor" + case .major: return "Major" + case .augmented: return "Augmented" + } + } + } + + /// Quality of the interval. + public var quality: Quality + /// Degree of the interval. + public var degree: Int + /// Semitones interval affect on a pitch. + public var semitones: Int + + /// Initilizes the interval with its quality, degree and semitones. This can be used to + /// to create an interval, but it is advised to use the already predefined intervals + /// to avoid any confusion or needless mistakes. + /// + /// - Parameters: + /// - quality: Quality of the interval. + /// - degree: Degree of the interval. + /// - semitones: Semitones of the interval from the root. + public init(quality: Quality, degree: Int, semitones: Int) { + self.quality = quality + self.degree = degree + self.semitones = semitones + } + + + // MARK: - Predefined Internvals + + // Here, all the standard interval qualities are predefined for ease of use. + + /// Unison. + public static let P1 = Interval(quality: .perfect, degree: 1, semitones: 0) + /// Perfect fourth. + public static let P4 = Interval(quality: .perfect, degree: 4, semitones: 5) + /// Perfect fifth. + public static let P5 = Interval(quality: .perfect, degree: 5, semitones: 7) + /// Octave. + public static let P8 = Interval(quality: .perfect, degree: 8, semitones: 12) + /// Perfect eleventh. + public static let P11 = Interval(quality: .perfect, degree: 11, semitones: 17) + /// Perfect twelfth. + public static let P12 = Interval(quality: .perfect, degree: 12, semitones: 19) + /// Perfect fifteenth, double octave. + public static let P15 = Interval(quality: .perfect, degree: 15, semitones: 24) + + /// Minor second. + public static let m2 = Interval(quality: .minor, degree: 2, semitones: 1) + /// Minor third. + public static let m3 = Interval(quality: .minor, degree: 3, semitones: 3) + /// Minor sixth. + public static let m6 = Interval(quality: .minor, degree: 6, semitones: 8) + /// Minor seventh. + public static let m7 = Interval(quality: .minor, degree: 7, semitones: 10) + /// Minor ninth. + public static let m9 = Interval(quality: .minor, degree: 9, semitones: 13) + /// Minor tenth. + public static let m10 = Interval(quality: .minor, degree: 10, semitones: 15) + /// Minor thirteenth. + public static let m13 = Interval(quality: .minor, degree: 13, semitones: 20) + /// Minor fourteenth. + public static let m14 = Interval(quality: .minor, degree: 14, semitones: 22) + + /// Major second. + public static let M2 = Interval(quality: .major, degree: 2, semitones: 2) + /// Major third. + public static let M3 = Interval(quality: .major, degree: 3, semitones: 4) + /// Major sixth. + public static let M6 = Interval(quality: .major, degree: 6, semitones: 9) + /// Major seventh. + public static let M7 = Interval(quality: .major, degree: 7, semitones: 11) + /// Major ninth. + public static let M9 = Interval(quality: .major, degree: 9, semitones: 14) + /// Major tenth. + public static let M10 = Interval(quality: .major, degree: 10, semitones: 16) + /// Major thirteenth. + public static let M13 = Interval(quality: .major, degree: 13, semitones: 21) + /// Major fourteenth. + public static let M14 = Interval(quality: .major, degree: 14, semitones: 23) + + /// Diminished first. + public static let d1 = Interval(quality: .diminished, degree: 1, semitones: -1) + /// Diminished second. + public static let d2 = Interval(quality: .diminished, degree: 2, semitones: 0) + /// Diminished third. + public static let d3 = Interval(quality: .diminished, degree: 3, semitones: 2) + /// Diminished fourth. + public static let d4 = Interval(quality: .diminished, degree: 4, semitones: 4) + /// Diminished fifth. + public static let d5 = Interval(quality: .diminished, degree: 5, semitones: 6) + /// Diminished sixth. + public static let d6 = Interval(quality: .diminished, degree: 6, semitones: 7) + /// Diminished seventh. + public static let d7 = Interval(quality: .diminished, degree: 7, semitones: 9) + /// Diminished eighth. + public static let d8 = Interval(quality: .diminished, degree: 8, semitones: 11) + /// Diminished ninth. + public static let d9 = Interval(quality: .diminished, degree: 9, semitones: 12) + /// Diminished tenth. + public static let d10 = Interval(quality: .diminished, degree: 10, semitones: 14) + /// Diminished eleventh. + public static let d11 = Interval(quality: .diminished, degree: 11, semitones: 16) + /// Diminished twelfth. + public static let d12 = Interval(quality: .diminished, degree: 12, semitones: 18) + /// Diminished thirteenth. + public static let d13 = Interval(quality: .diminished, degree: 13, semitones: 19) + /// Diminished fourteenth. + public static let d14 = Interval(quality: .diminished, degree: 14, semitones: 21) + /// Diminished fifteenth. + public static let d15 = Interval(quality: .diminished, degree: 15, semitones: 23) + + /// Augmented first. + public static let A1 = Interval(quality: .augmented, degree: 1, semitones: 1) + /// Augmented second. + public static let A2 = Interval(quality: .augmented, degree: 2, semitones: 3) + /// Augmented third. + public static let A3 = Interval(quality: .augmented, degree: 3, semitones: 5) + /// Augmented fourth. + public static let A4 = Interval(quality: .augmented, degree: 4, semitones: 6) + /// Augmented fifth. + public static let A5 = Interval(quality: .augmented, degree: 5, semitones: 8) + /// Augmented sixth. + public static let A6 = Interval(quality: .augmented, degree: 6, semitones: 10) + /// Augmented seventh. + public static let A7 = Interval(quality: .augmented, degree: 7, semitones: 12) + /// Augmented octave. + public static let A8 = Interval(quality: .augmented, degree: 8, semitones: 13) + /// Augmented ninth. + public static let A9 = Interval(quality: .augmented, degree: 9, semitones: 15) + /// Augmented tenth. + public static let A10 = Interval(quality: .augmented, degree: 10, semitones: 17) + /// Augmented eleventh. + public static let A11 = Interval(quality: .augmented, degree: 11, semitones: 18) + /// Augmented twelfth. + public static let A12 = Interval(quality: .augmented, degree: 12, semitones: 20) + /// Augmented thirteenth. + public static let A13 = Interval(quality: .augmented, degree: 13, semitones: 22) + /// Augmented fourteenth. + public static let A14 = Interval(quality: .augmented, degree: 14, semitones: 24) + /// Augmented fifteenth. + public static let A15 = Interval(quality: .augmented, degree: 15, semitones: 25) + + /// All pre-defined intervals in a static array. You can filter it out with qualities, degrees or semitones. + public static let all: [Interval] = [ + .P1, .P4, .P5, .P8, .P11, .P12, .P15, + .m2, .m3, .m6, .m7, .m9, .m10, .m13, .m14, + .M2, .M3, .M6, .M7, .M9, .M10, .M13, .M14, + .d1, .d2, .d3, .d4, .d5, .d6, .d7, .d8, .d9, .d10, .d11, .d12, .d13, .d14, .d15, + .A1, .A2, .A3, .A4, .A5, .A6, .A7, .A8, .A9, .A10, .A11, .A12, .A13, .A14, .A15, + ] +} + + +extension Interval: CustomStringConvertible { + + /// Returns the notation of the interval. + public var notation: String { + return "\(quality.notation)\(degree)" + } + + /// Returns the name of the interval. + public var description: String { + var formattedDegree = "\(degree)" + + if #available(OSX 10.11, iOS 9.0, *) { + let formatter = NumberFormatter() + formatter.numberStyle = .ordinal + formattedDegree = formatter.string(from: NSNumber(integerLiteral: degree)) ?? formattedDegree + } + + return "\(quality) \(formattedDegree)" + } +} + + +// MARK: - Interval Operations + +/// Checks the equality of two `Interval`s in terms of their semitones. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns true if two `Interval`s are equal. +public func == (lhs: Interval, rhs: Interval) -> Bool { + return lhs.semitones == rhs.semitones +} + +/// Checks the equality of two `Interval`s in terms of their quality, degree and semitones. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns true if two `Interval`s are equal. +public func === (lhs: Interval, rhs: Interval) -> Bool { + return lhs.quality == rhs.quality && rhs.degree == rhs.degree && lhs.semitones == rhs.semitones +} diff --git a/Swift Musicology/Swift Musicology/Key.swift b/Swift Musicology/Swift Musicology/Key.swift new file mode 100644 index 0000000..701a85b --- /dev/null +++ b/Swift Musicology/Swift Musicology/Key.swift @@ -0,0 +1,233 @@ +// +// Key.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +/// Represents the keys that notes and pitches are based on. +public struct Key: Codable, Equatable, Hashable { + + /// Base pitch of the key without accidentals. Accidentals will take account in the + /// parent struct, `Key`. Integer values are based on C = 0 on western chromatic scale. + public enum KeyType: Int, Codable, Equatable, Hashable, ExpressibleByStringLiteral, CustomStringConvertible { + case c = 0 + case d = 2 + case e = 4 + case f = 5 + case g = 7 + case a = 9 + case b = 11 + + /// Returns all members of the `KeyType`. + public static let all: [KeyType] = [.c, .d, .e, .f, .g, .a, .b] + + /// Returns neighbour `KeyType` at `distance` away. Works on both directions. + /// Use negative distance value for going towards left direction, and positive distance + /// value for going on right direction. This function iterates the `KeyType.all` + /// array circullar to find the target KeyType. + /// + /// - Parameter distance: Target KeyType distance. Zero is self. + /// - Returns: Returns the neighbouring KeyType distance away. + public func key(at distance: Int) -> KeyType { + guard let index = KeyType.all.index(of: self) + else { return self } + + let normalizedDistance = (distance + index) % KeyType.all.count + let keyIndex = normalizedDistance < 0 ? (KeyType.all.count + normalizedDistance) : normalizedDistance + return KeyType.all[keyIndex] + } + + /// Calculates the distance of two `KeyType`s. + /// + /// - Parameter keyType: Target `KeyType` you want to compare. + /// - Returns: Returns the integer value of distance in terms of their array index values. + public func distance(from keyType: KeyType) -> Int { + guard let index = KeyType.all.index(of: self), + let targetIndex = KeyType.all.index(of: keyType) + else { return 0 } + return targetIndex - index + } + + /// Calculates the octave difference for a neighbouring `KeyType` at given interval away higher or lower. + /// + /// - Parameters: + /// - interval: Interval you want to calculate octave difference. + /// - isHigher: You want to calculate interval higher or lower from current key. + /// - Returns: Returns the octave difference for a given interval higher or lower. + public func octaveDiff(for interval: Interval, isHigher: Bool) -> Int { + var diff = 0 + var currentKey = self + for _ in 0 ..< (interval.degree - 1) { + let next = currentKey.key(at: isHigher ? 1 : -1) + + if isHigher { + if currentKey == .b, next == .c { + diff += 1 + } + } else { + if currentKey == .c, next == .b { + diff -= 1 + } + } + + currentKey = next + } + return diff + } + + + // MARK: ExpressibleByStringLiteral + + public typealias StringLiteralType = String + + /// Initilizes with a string. + /// + /// - Parameter value: String representation of type. + public init(stringLiteral value: KeyType.StringLiteralType) { + switch value { + case "a": self = .a + case "b": self = .b + case "c": self = .c + case "d": self = .d + case "e": self = .e + case "f": self = .f + case "g": self = .g + default: self = .c + } + } + + + // MARK: CustomStringConvertible + + /// Returns the key notation. + public var description: String { + switch self { + case .c: return "C" + case .d: return "D" + case .e: return "E" + case .f: return "F" + case .g: return "G" + case .a: return "A" + case .b: return "B" + } + } + } + + /// Type of the key. + public var type: KeyType + + /// Accidental of the key. + public var accidental: Accidental + + /// All notes in an octave with sharp notes. + public static let keysWithSharps = [ + Key(type: .c, accidental: .natural), + Key(type: .c, accidental: .sharp), + Key(type: .d, accidental: .natural), + Key(type: .d, accidental: .sharp), + Key(type: .e, accidental: .natural), + Key(type: .f, accidental: .natural), + Key(type: .f, accidental: .sharp), + Key(type: .g, accidental: .natural), + Key(type: .g, accidental: .sharp), + Key(type: .a, accidental: .natural), + Key(type: .a, accidental: .sharp), + Key(type: .b, accidental: .natural), + ] + + /// All notes in an octave with flat notes. + public static let keysWithFlats = [ + Key(type: .c, accidental: .natural), + Key(type: .d, accidental: .flat), + Key(type: .d, accidental: .natural), + Key(type: .e, accidental: .flat), + Key(type: .e, accidental: .natural), + Key(type: .f, accidental: .natural), + Key(type: .g, accidental: .flat), + Key(type: .g, accidental: .natural), + Key(type: .a, accidental: .flat), + Key(type: .a, accidental: .natural), + Key(type: .b, accidental: .flat), + Key(type: .b, accidental: .natural), + ] + + /// Initilizes the key with its type and accidental. + /// + /// - Parameters: + /// - type: The type of the key. + /// - accidental: Accidental of the key. Defaults natural. + public init(type: KeyType, accidental: Accidental = .natural) { + self.type = type + self.accidental = accidental + } +} + + +extension Key: ExpressibleByStringLiteral { + + public typealias StringLiteralType = String + + /// Initilizes with a string. + /// + /// - Parameter value: String representation of type. + public init(stringLiteral value: Key.StringLiteralType) { + var keyType = KeyType.c + var accidental = Accidental.natural + let pattern = "([A-Ga-g])([#♯♭b]*)" + let regex = try? NSRegularExpression(pattern: pattern, options: []) + if let regex = regex, + let match = regex.firstMatch(in: value, options: [], range: NSRange(0 ..< value.count)), + let keyTypeRange = Range(match.range(at: 1), in: value), + let accidentalRange = Range(match.range(at: 2), in: value), + match.numberOfRanges == 3 { + // Set key type + keyType = KeyType(stringLiteral: String(value[keyTypeRange])) + // Set accidental + accidental = Accidental(stringLiteral: String(value[accidentalRange])) + } + + self = Key(type: keyType, accidental: accidental) + } +} + + +extension Key: CustomStringConvertible { + + /// Returns the key notation with its type and accidental, if has any. + public var description: String { + return "\(type)\(accidental)" + } +} + + +// MARK: - Key Operations + +/// Checks if two `Key` types are equal in terms of their int values. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns the equation value. +public func == (lhs: Key, rhs: Key) -> Bool { + let lhsMod = (lhs.type.rawValue + lhs.accidental.rawValue) % 12 + let normalizedLhs = lhsMod < 0 ? (12 + lhsMod) : lhsMod + + let rhsMod = (rhs.type.rawValue + rhs.accidental.rawValue) % 12 + let normalizedRhs = rhsMod < 0 ? (12 + rhsMod) : rhsMod + + return normalizedLhs == normalizedRhs +} + +/// Checks if two `Key` types are equal in terms of their type and accidental values. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns the equation value. +public func === (lhs: Key, rhs: Key) -> Bool { + return lhs.type == rhs.type && lhs.accidental == rhs.accidental +} diff --git a/Swift Musicology/Swift Musicology/NoteValue.swift b/Swift Musicology/Swift Musicology/NoteValue.swift new file mode 100644 index 0000000..8abf61e --- /dev/null +++ b/Swift Musicology/Swift Musicology/NoteValue.swift @@ -0,0 +1,75 @@ +// +// NoteValue.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + + +// MARK: - NoteValueType + +/// Defines the different types of note values that are generally available in music notation, +/// from whole notes to sixtyfourth notes. +public enum NoteValueType: Double, Codable { + case whole = 1 + case half = 2 + case quarter = 4 + case eighth = 8 + case sixteenth = 16 + case thirtysecond = 32 + case sixtyfourth = 64 +} + + +// MARK: - NoteModifier + +/// Used to define the length a `NoteValue` will have. +public enum NoteModifier: Double, Codable { + /// No additional length. + case `default` = 1 + /// Adds half of its own value. + case dotted = 1.5 + /// Three notes of the same value. + case triplet = 0.6667 + /// Five of the indicated note value total the duration normally occupied by four. + case quintuplet = 0.8 +} + + +// MARK: - NoteValue + +/// Used to denfine the duration of a note. +public struct NoteValue: Codable { + + /// The standard duration of the note. + public var type: NoteValueType + + /// Modifier for `NoteType` that modifies the note's default duration. + public var modifier: NoteModifier + + /// Initilize the NoteValue with its type and an optional modifier. + /// + /// - Parameters: + /// - type: NoteTypeValue that represents note duration. + /// - modifier: Modifier of note value. Defaults `default`. + public init(type: NoteValueType, modifier: NoteModifier = .default) { + self.type = type + self.modifier = modifier + } +} + + +// MARK: - NoteValue Operations + +/// Calculates how many notes of a single `NoteValueType` is equivalent to a given `NoteValue`. +/// +/// - Parameters: +/// - noteValue: The note value to be measured. +/// - noteValueType: The note value type to measure the length of the note value. +/// - Returns: Returns how many notes of a single `NoteValueType` is equivalent to a given `NoteValue`. +public func / (noteValue: NoteValue, noteValueType: NoteValueType) -> Double { + return noteValue.modifier.rawValue * noteValueType.rawValue / noteValue.type.rawValue +} diff --git a/Swift Musicology/Swift Musicology/Pitch.swift b/Swift Musicology/Swift Musicology/Pitch.swift new file mode 100644 index 0000000..041a613 --- /dev/null +++ b/Swift Musicology/Swift Musicology/Pitch.swift @@ -0,0 +1,279 @@ +// +// Pitch.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +/// Pitch object with a `Key` and an octave. +/// Could be initilized with MIDI note number and preferred accidental type. +public struct Pitch: Codable, Equatable, Comparable { + /// Key of the pitch like C, D, A, B with accidentals. + public var key: Key + + /// Octave of the pitch. In theory this must be zero or a positive integer. But `Pitch` + /// does not limit octave and calculates every possible octave including the negative ones. + public var octave: Int + + /// This function returns the nearest pitch to the given frequency in Hz. + /// + /// - Parameter frequency: The frequency in Hz + /// - Returns: The nearest pitch for given frequency + public static func nearest(frequency: Float) -> Pitch? { + let allPitches = Array((1 ... 7).map { octave -> [Pitch] in + Key.keysWithSharps.map { key -> Pitch in + Pitch(key: key, octave: octave) + } + }.joined()) + + var results = allPitches.map { pitch -> (pitch: Pitch, distance: Float) in + (pitch: pitch, distance: abs(pitch.frequency - frequency)) + } + + results.sort { $0.distance < $1.distance } + return results.first?.pitch + } + + /// Initilizes the `Pitch` with MIDI note number. + /// + /// - Parameter midiNote: Midi note in range of [0 - 127]. + /// - Parameter isPreferredAccidentalSharps: Make it true if preferred accidentals is sharps. Defaults true. + public init(midiNote: Int, isPreferredAccidentalSharps: Bool = true) { + octave = (midiNote / 12) - 1 + let keyIndex = midiNote % 12 + key = (isPreferredAccidentalSharps ? Key.keysWithSharps : Key.keysWithFlats)[keyIndex] + } + + /// Initilizes the `Pitch` with `Key` and octave + /// + /// - Parameters: + /// - key: Key of the pitch. + /// - octave: Octave of the pitch. + public init(key: Key, octave: Int) { + self.key = key + self.octave = octave + } + + /// Calculates and returns the frequency of note on octave based on its location of + /// piano keys. Bases A4 note of 440Hz frequency standard. + public var frequency: Float { + let fn = powf(2.0, Float(rawValue - 69) / 12.0) + return fn * 440.0 + } +} + + +extension Pitch: RawRepresentable { + + public typealias RawValue = Int + + /// Returns midi note number. In theory, this must be in range [0 - 127], but it does + /// not limits the midi note value. + public var rawValue: Int { + let semitones = key.type.rawValue + key.accidental.rawValue + return semitones + ((octave + 1) * 12) + } + + /// Initilizes the pitch with an integer value that represents the MIDI note number of the pitch. + /// + /// - Parameter rawValue: MIDI note number of the pitch. + public init?(rawValue: Pitch.RawValue) { + self = Pitch(midiNote: rawValue) + } +} + + +extension Pitch: ExpressibleByIntegerLiteral { + + public typealias IntegerLiteralType = Int + + /// Initilizes the pitch with an integer value that represents the MIDI note number of the pitch. + /// + /// - Parameter value: MIDI note number of the pitch. + public init(integerLiteral value: Pitch.IntegerLiteralType) { + self = Pitch(midiNote: value) + } +} + + +extension Pitch: ExpressibleByStringLiteral { + + public typealias StringLiteralType = String + + /// Initilizes with a string. + /// + /// - Parameter value: String representation of type. + public init(stringLiteral value: Pitch.StringLiteralType) { + var keyType = Key.KeyType.c + var accidental = Accidental.natural + var octave = 0 + let pattern = "([A-Ga-g])([#♯♭b]*)(-?)(\\d+)" + let regex = try? NSRegularExpression(pattern: pattern, options: []) + if let regex = regex, + let match = regex.firstMatch(in: value, options: [], range: NSRange(0 ..< value.count)), + let keyTypeRange = Range(match.range(at: 1), in: value), + let accidentalRange = Range(match.range(at: 2), in: value), + let signRange = Range(match.range(at: 3), in: value), + let octaveRange = Range(match.range(at: 4), in: value), + match.numberOfRanges == 5 { + // key type + keyType = Key.KeyType(stringLiteral: String(value[keyTypeRange])) + // accidental + accidental = Accidental(stringLiteral: String(value[accidentalRange])) + // sign + let sign = String(value[signRange]) + // octave + octave = (Int(String(value[octaveRange])) ?? 0) * (sign == "-" ? -1 : 1) + } + + self = Pitch(key: Key(type: keyType, accidental: accidental), octave: octave) + } +} + +extension Pitch: CustomStringConvertible { + /// Converts `Pitch` to string with its key and octave. + public var description: String { + return "\(key)\(octave)" + } +} + + +// MARK: - Pitch Operations + +/// Returns the pitch above target interval from target pitch. +/// +/// - Parameters: +/// - lhs: Target `Pitch`. +/// - rhs: Target `Interval`. +/// - Returns: Returns new pitch above target interval from target pitch. +public func + (lhs: Pitch, rhs: Interval) -> Pitch { + let degree = rhs.degree - 1 + let targetKeyType = lhs.key.type.key(at: degree) + let targetPitch = lhs + rhs.semitones + let targetOctave = lhs.octave + lhs.key.type.octaveDiff(for: rhs, isHigher: true) + + // Convert pitch + var convertedPitch = Pitch(key: Key(type: targetKeyType), octave: targetOctave) + let diff = targetPitch.rawValue - convertedPitch.rawValue + convertedPitch.key.accidental = Accidental(integerLiteral: diff) + return convertedPitch +} + +/// Returns the pitch below target interval from target pitch. +/// +/// - Parameters: +/// - lhs: Target `Pitch`. +/// - rhs: Target `Interval`. +/// - Returns: Returns new pitch below target interval from target pitch. +public func - (lhs: Pitch, rhs: Interval) -> Pitch { + let degree = -(rhs.degree - 1) + let targetKeyType = lhs.key.type.key(at: degree) + let targetPitch = lhs - rhs.semitones + let targetOctave = lhs.octave + lhs.key.type.octaveDiff(for: rhs, isHigher: false) + + // Convert pitch + var convertedPitch = Pitch(key: Key(type: targetKeyType), octave: targetOctave) + let diff = targetPitch.rawValue - convertedPitch.rawValue + convertedPitch.key.accidental = Accidental(integerLiteral: diff) + return convertedPitch +} + +/// Calculates the interval between two pitches. +/// Doesn't matter left hand side and right hand side note places. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: `Intreval` between two pitches. You can get the semitones from interval as well. +public func - (lhs: Pitch, rhs: Pitch) -> Interval { + let top = max(lhs, rhs) + let bottom = min(lhs, rhs) + let diff = top.rawValue - bottom.rawValue + + let bottomKeyIndex = Key.KeyType.all.index(of: bottom.key.type) ?? 0 + let topKeyIndex = Key.KeyType.all.index(of: top.key.type) ?? 0 + let degree = abs(topKeyIndex - bottomKeyIndex) + 1 + let isMajor = (degree == 2 || degree == 3 || degree == 6 || degree == 7) + + let majorScale = Scale(type: .major, key: bottom.key) + if majorScale.keys.contains(top.key) { // Major or Perfect + return Interval( + quality: isMajor ? .major : .perfect, + degree: degree, + semitones: diff + ) + } else { // Augmented, Diminished or Minor + if isMajor { + let majorPitch = bottom + Interval(quality: .major, degree: degree, semitones: diff) + let offset = top.rawValue - majorPitch.rawValue + return Interval( + quality: offset > 0 ? .augmented : .minor, + degree: degree, + semitones: diff + ) + } else { + let perfectPitch = bottom + Interval(quality: .perfect, degree: degree, semitones: diff) + let offset = top.rawValue - perfectPitch.rawValue + return Interval( + quality: offset > 0 ? .augmented : .diminished, + degree: degree, + semitones: diff + ) + } + } +} + +/// Calculates the `Pitch` above semitones. +/// +/// - Parameters: +/// - note: The pitch that is being added semitones. +/// - semitone: semitones above. +/// - Returns: Returns `Pitch` above semitones. +public func + (pitch: Pitch, semitone: Int) -> Pitch { + return Pitch(midiNote: pitch.rawValue + semitone) +} + +/// Calculates the `Pitch` below semitones. +/// +/// - Parameters: +/// - note: The pitch that is being calculated. +/// - semitone: semitones below. +/// - Returns: Returns `Pitch` below semitones. +public func - (pitch: Pitch, semitone: Int) -> Pitch { + return Pitch(midiNote: pitch.rawValue - semitone) +} + +/// Compares the equality of two pitches by their MIDI note value. +/// Alternative notes passes this equality. Use `===` function if you want to check exact equality in terms of exact keys. +/// +/// - Parameters: +/// - left: Left handside `Pitch` to be compared. +/// - right: Right handside `Pitch` to be compared. +/// - Returns: Returns the bool value of comparisation of two pitches. +public func == (left: Pitch, right: Pitch) -> Bool { + return left.rawValue == right.rawValue +} + +/// Compares the exact equality of two pitches by their keys and octaves. +/// Alternative notes not passes this equality. Use `==` function if you want to check equality in terms of MIDI note value. +/// +/// - Parameters: +/// - left: Left handside `Pitch` to be compared. +/// - right: Right handside `Pitch` to be compared. +/// - Returns: Returns the bool value of comparisation of two pitches. +public func === (left: Pitch, right: Pitch) -> Bool { + return left.key == right.key && left.octave == right.octave +} + +/// Compares two `Pitch`es in terms of their semitones. +/// +/// - Parameters: +/// - lhs: Left hand side of the equation. +/// - rhs: Right hand side of the equation. +/// - Returns: Returns true if left hand side `Pitch` lower than right hand side `Pitch`. +public func < (lhs: Pitch, rhs: Pitch) -> Bool { + return lhs.rawValue < rhs.rawValue +} diff --git a/Swift Musicology/Swift Musicology/Scale.swift b/Swift Musicology/Swift Musicology/Scale.swift new file mode 100644 index 0000000..f3b512b --- /dev/null +++ b/Swift Musicology/Swift Musicology/Scale.swift @@ -0,0 +1,147 @@ +// +// Scale.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +/// Scale object with `ScaleType` and scale's key of `NoteType`. +/// Could calculate note sequences in [Pitch] format. +public struct Scale: Equatable, Codable { + + /// Type of the scale that has `interval` info. + public var type: ScaleType + /// Root key of the scale that will built onto. + public var key: Key + + /// Initilizes the scale with its type and key. + /// + /// - Parameters: + /// - type: Type of scale being initilized. + /// - key: Key of scale being initilized. + public init(type: ScaleType, key: Key) { + self.type = type + self.key = key + } + + /// Keys generated by the intervals of the scale. + public var keys: [Key] { + return pitches(octave: 1).map({ $0.key }) + } + + /// Generates `Pitch` array of scale in given octave. + /// + /// - Parameter octave: Octave value of notes in scale. + /// - Returns: Returns `Pitch` array of the scale in given octave. + public func pitches(octave: Int) -> [Pitch] { + return pitches(octaves: octave) + } + + /// Generates `Pitch` array of scale in given octaves. + /// + /// - Parameter octaves: Variadic value of octaves to generate pitches in scale. + /// - Returns: Returns `Pitch` array of the scale in given octaves. + public func pitches(octaves: Int...) -> [Pitch] { + return pitches(octaves: octaves) + } + + /// Generates `Pitch` array of scale in given octaves. + /// + /// - Parameter octaves: Array value of octaves to generate pitches in scale. + /// - Returns: Returns `Pitch` array of the scale in given octaves. + public func pitches(octaves: [Int]) -> [Pitch] { + var pitches = [Pitch]() + octaves.forEach({ octave in + let root = Pitch(key: key, octave: octave) + pitches += type.intervals.map({ root + $0 }) + }) + return pitches + } +} + +extension Scale { + + /// Stack of notes to generate chords for each note in the scale. + public enum HarmonicField: Int, Codable { + /// First, third and fifth degree notes builds a triad chord. + case triad + /// First, third, fifth and seventh notes builds a tetrad chord. + case tetrad + /// First, third, fifth, seventh and ninth notes builds a 9th chord. + case ninth + /// First, third, fifth, seventh, ninth and eleventh notes builds a 11th chord. + case eleventh + /// First, third, fifth, seventh, ninth, eleventh and thirteenth notes builds a 13th chord. + case thirteenth + + /// All possible harmonic fields constructed from. + public static let all: [HarmonicField] = [.triad, .tetrad, .ninth, .eleventh, .thirteenth] + } + + /// Generates chords for harmonic field of scale. + /// + /// - Parameter field: Type of chords you want to generate. + /// - Parameter inversion: Inversion degree of the chords. Defaults 0. + /// - Returns: Returns triads or tetrads of chord for each note in scale. + public func harmonicField(for field: HarmonicField, inversion: Int = 0) -> [Chord?] { + var chords = [Chord?]() + + // Extended notes for picking notes. + let octaves = [0, 1, 2, 3, 4] + let scalePitches = pitches(octaves: octaves) + + // Build chords for each note in scale. + for i in 0 ..< scalePitches.count / octaves.count { + var chordPitches = [Pitch]() + switch field { + case .triad: + chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4]] + case .tetrad: + chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4], scalePitches[i + 6]] + case .ninth: + chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4], scalePitches[i + 6], scalePitches[i + 8]] + case .eleventh: + chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4], scalePitches[i + 6], scalePitches[i + 8], scalePitches[i + 10]] + case .thirteenth: + chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4], scalePitches[i + 6], scalePitches[i + 8], scalePitches[i + 10], scalePitches[i + 12]] + } + + // Build intervals + let root = chordPitches[0] + let intervals = chordPitches.map({ $0 - root }) + + // Build chord + if let chordType = ChordType(intervals: intervals) { + let chord = Chord(type: chordType, key: root.key, inversion: inversion) + chords.append(chord) + } else { + chords.append(nil) + } + } + + return chords + } +} + +extension Scale: CustomStringConvertible { + /// Converts `Scale` to string with its key and type. + public var description: String { + return "\(key) \(type): " + keys.map({ "\($0)" }).joined(separator: ", ") + } +} + + +// MARK: - Scale Operations + +/// Checks the equability between two `Scale`s by their base key and notes. +/// +/// - Parameters: +/// - left: Left handside of the equation. +/// - right: Right handside of the equation. +/// - Returns: Returns Bool value of equation of two given scales. +public func == (left: Scale, right: Scale) -> Bool { + return left.key == right.key && left.type == right.type +} diff --git a/Swift Musicology/Swift Musicology/ScaleType.swift b/Swift Musicology/Swift Musicology/ScaleType.swift new file mode 100644 index 0000000..3ad0811 --- /dev/null +++ b/Swift Musicology/Swift Musicology/ScaleType.swift @@ -0,0 +1,280 @@ +// +// ScaleType.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +/// Represents scale by the intervals between note sequences. +public enum ScaleType: Equatable { + case major + case minor + case harmonicMinor + case melodicMinor + case pentatonicMajor + case pentatonicMinor + case pentatonicBlues + case pentatonicNeutral + case ionian + case aeolian + case dorian + case mixolydian + case phrygian + case lydian + case locrian + case dimHalf + case dimWhole + case whole + case augmented + case chromatic + case roumanianMinor + case spanishGypsy + case blues + case diatonic + case doubleHarmonic + case eightToneSpanish + case enigmatic + case leadingWholeTone + case lydianAugmented + case neopolitanMajor + case neopolitanMinor + case pelog + case prometheus + case prometheusNeopolitan + case sixToneSymmetrical + case superLocrian + case lydianMinor + case lydianDiminished + case nineToneScale + case auxiliaryDiminished + case auxiliaryAugmented + case auxiliaryDimBlues + case majorLocrian + case overtone + case diminishedWholeTone + case pureMinor + case dominant7th + + /// Custom scale with given interval set. + case custom(intervals: [Interval], description: String) + + /// Tries to initilize scale with a matching interval series. If no scale matched + /// with intervals, than initlizes custom scale. + /// + /// - Parameters: + /// - intervals: Intervals of the chord. + /// - description: In case of .custom type scale, you probably need description. + public init(intervals: [Interval], description: String = "") { + if let scale = ScaleType.all.filter({ $0.intervals == intervals }).first { + self = scale + } else { + self = .custom(intervals: intervals, description: description) + } + } + + /// Intervals of the different scales. + public var intervals: [Interval] { + switch self { + case .major: return [.P1, .M2, .M3, .P4, .P5, .M6, .M7] + case .minor: return [.P1, .M2, .m3, .P4, .P5, .m6, .m7] + case .harmonicMinor: return [.P1, .M2, .m3, .P4, .P5, .m6, .M7] + case .dorian: return [.P1, .M2, .m3, .P4, .P5, .M6, .m7] + case .phrygian: return [.P1, .m2, .m3, .P4, .P5, .m6, .m7] + case .lydian: return [.P1, .M2, .M3, .d5, .P5, .M6, .M7] + case .mixolydian: return [.P1, .M2, .M3, .P4, .P5, .M6, .m7] + case .locrian: return [.P1, .m2, .m3, .P4, .d5, .m6, .m7] + case .melodicMinor: return [.P1, .M2, .m3, .P4, .P5, .M6, .M7] + case .pentatonicMajor: return [.P1, .M2, .M3, .P5, .M6] + case .pentatonicMinor: return [.P1, .m3, .P4, .P5, .m7] + case .pentatonicBlues: return [.P1, .m3, .P4, .d5, .P5, .m7] + case .pentatonicNeutral: return [.P1, .M2, .P4, .P5, .m7] + case .ionian: return [.P1, .M2, .M3, .P4, .P5, .M6, .M7] + case .aeolian: return [.P1, .M2, .m3, .P4, .P5, .m6, .m7] + case .dimHalf: return [.P1, .m2, .m3, .M3, .d5, .P5, .M6, .m7] + case .dimWhole: return [.P1, .M2, .m3, .P4, .d5, .m6, .M6, .M7] + case .whole: return [.P1, .M2, .M3, .d5, .m6, .m7] + case .augmented: return [.m3, .M3, .P5, .m6, .M7] + case .chromatic: return [.P1, .m2, .M2, .m3, .M3, .P4, .d5, .P5, .m6, .M6, .m7, .M7] + case .roumanianMinor: return [.P1, .M2, .m3, .d5, .P5, .M6, .m7] + case .spanishGypsy: return [.P1, .m2, .M3, .P4, .P5, .m6, .m7] + case .blues: return [.P1, .m3, .P4, .d5, .P5, .m7] + case .diatonic: return [.P1, .M2, .M3, .P5, .M6] + case .doubleHarmonic: return [.P1, .m2, .M3, .P4, .P5, .m6, .M7] + case .eightToneSpanish: return [.P1, .m2, .m3, .M3, .P4, .d5, .m6, .m7] + case .enigmatic: return [.P1, .m2, .M3, .d5, .m6, .m7, .M7] + case .leadingWholeTone: return [.P1, .M2, .M3, .d5, .m6, .M6, .m7] + case .lydianAugmented: return [.P1, .M2, .M3, .d5, .m6, .M6, .M7] + case .neopolitanMajor: return [.P1, .m2, .m3, .P4, .P5, .M6, .M7] + case .neopolitanMinor: return [.P1, .m2, .m3, .P4, .P5, .m6, .m7] + case .pelog: return [.P1, .m2, .m3, .d5, .m7, .M7] + case .prometheus: return [.P1, .M2, .M3, .d5, .M6, .m7] + case .prometheusNeopolitan: return [.P1, .m2, .M3, .d5, .M6, .m7] + case .sixToneSymmetrical: return [.P1, .m2, .M3, .P4, .m6, .M6] + case .superLocrian: return [.P1, .m2, .m3, .M3, .d5, .m6, .m7] + case .lydianMinor: return [.P1, .M2, .M3, .d5, .P5, .m6, .m7] + case .lydianDiminished: return [.P1, .M2, .m3, .d5, .P5, .m6, .m7] + case .nineToneScale: return [.P1, .M2, .m3, .M3, .d5, .P5, .m6, .M6, .M7] + case .auxiliaryDiminished: return [.P1, .M2, .m3, .P4, .d5, .m6, .M6, .M7] + case .auxiliaryAugmented: return [.P1, .M2, .M3, .d5, .m6, .m7] + case .auxiliaryDimBlues: return [.P1, .m2, .m3, .M3, .d5, .P5, .M6, .m7] + case .majorLocrian: return [.P1, .M2, .M3, .P4, .d5, .m6, .m7] + case .overtone: return [.P1, .M2, .M3, .d5, .P5, .M6, .m7] + case .diminishedWholeTone: return [.P1, .m2, .m3, .M3, .d5, .m6, .m7] + case .pureMinor: return [.P1, .M2, .m3, .P4, .P5, .m6, .m7] + case .dominant7th: return [.P1, .M2, .M3, .P4, .P5, .M6, .m7] + case .custom(let intervals, _): return intervals + } + } + + /// An array of all `ScaleType` values. + public static var all: [ScaleType] { + return [ + .major, + .minor, + .harmonicMinor, + .melodicMinor, + .pentatonicMajor, + .pentatonicMinor, + .pentatonicBlues, + .pentatonicNeutral, + .ionian, + .aeolian, + .dorian, + .mixolydian, + .phrygian, + .lydian, + .locrian, + .dimHalf, + .dimWhole, + .whole, + .augmented, + .chromatic, + .roumanianMinor, + .spanishGypsy, + .blues, + .diatonic, + .doubleHarmonic, + .eightToneSpanish, + .enigmatic, + .leadingWholeTone, + .lydianAugmented, + .neopolitanMajor, + .neopolitanMinor, + .pelog, + .prometheus, + .prometheusNeopolitan, + .sixToneSymmetrical, + .superLocrian, + .lydianMinor, + .lydianDiminished, + .nineToneScale, + .auxiliaryDiminished, + .auxiliaryAugmented, + .auxiliaryDimBlues, + .majorLocrian, + .overtone, + .diminishedWholeTone, + .pureMinor, + .dominant7th, + ] + } +} + +extension ScaleType: Codable { + /// Keys that conforms CodingKeys protocol to map properties. + private enum CodingKeys: String, CodingKey { + /// semitone property of `Interval`. + case intervals + } + + /// Decodes struct with a decoder. + /// + /// - Parameter decoder: Decodes encoded struct. + /// - Throws: Tries to initlize struct with a decoder. + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + let intervals = try values.decode([Interval].self, forKey: .intervals) + self = ScaleType(intervals: intervals) + } + + /// Encodes struct with an ecoder. + /// + /// - Parameter encoder: Encodes struct. + /// - Throws: Tries to encode struct. + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(intervals, forKey: .intervals) + } +} + +extension ScaleType: CustomStringConvertible { + /// Converts `ScaleType` to string with its name. + public var description: String { + switch self { + case .major: return "Major" + case .minor: return "Minor" + case .harmonicMinor: return "Harmonic Minor" + case .melodicMinor: return "Melodic Minor" + case .pentatonicMajor: return "Pentatonic Major" + case .pentatonicMinor: return "Pentatonic Minor" + case .pentatonicBlues: return "Pentatonic Blues" + case .pentatonicNeutral: return "Pentatonic Neutral" + case .ionian: return "Ionian" + case .aeolian: return "Aeolian" + case .dorian: return "Dorian" + case .mixolydian: return "Mixolydian" + case .phrygian: return "Phrygian" + case .lydian: return "Lydian" + case .locrian: return "Locrian" + case .dimHalf: return "Half Diminished" + case .dimWhole: return "Whole Diminished" + case .whole: return "Whole" + case .augmented: return "Augmented" + case .chromatic: return "Chromatic" + case .roumanianMinor: return "Roumanian Minor" + case .spanishGypsy: return "Spanish Gypsy" + case .blues: return "Blues" + case .diatonic: return "Diatonic" + case .doubleHarmonic: return "Double Harmonic" + case .eightToneSpanish: return "Eight Tone Spanish" + case .enigmatic: return "Enigmatic" + case .leadingWholeTone: return "Leading Whole Tone" + case .lydianAugmented: return "Lydian Augmented" + case .neopolitanMajor: return "Neopolitan Major" + case .neopolitanMinor: return "Neopolitan Minor" + case .pelog: return "Pelog" + case .prometheus: return "Prometheus" + case .prometheusNeopolitan: return "Prometheus Neopolitan" + case .sixToneSymmetrical: return "Six Tone Symmetrical" + case .superLocrian: return "Super Locrian" + case .lydianMinor: return "Lydian Minor" + case .lydianDiminished: return "Lydian Diminished" + case .nineToneScale: return "Nine Tone Scale" + case .auxiliaryDiminished: return "Auxiliary Diminished" + case .auxiliaryAugmented: return "Auxiliary Augmented" + case .auxiliaryDimBlues: return "Auxiliary Diminished Blues" + case .majorLocrian: return "Major Locrian" + case .overtone: return "Overtone" + case .diminishedWholeTone: return "Diminished Whole Tone" + case .pureMinor: return "Pure Minor" + case .dominant7th: return "Dominant 7th" + case let .custom(_, description): return description + } + } +} + + +// MARK: - ScaleType Operations + +/// Checks the equability between two `ScaleType`s by their intervals. +/// +/// - Parameters: +/// - left: Left handside of the equation. +/// - right: Right handside of the equation. +/// - Returns: Returns Bool value of equation of two given scale types. +public func == (left: ScaleType, right: ScaleType) -> Bool { + return left.intervals == right.intervals +} diff --git a/Swift Musicology/Swift Musicology/Swift_Musicology.h b/Swift Musicology/Swift Musicology/Swift_Musicology.h new file mode 100644 index 0000000..f00c7bb --- /dev/null +++ b/Swift Musicology/Swift Musicology/Swift_Musicology.h @@ -0,0 +1,19 @@ +// +// Swift_Musicology.h +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +#import + +//! Project version number for Swift_Musicology. +FOUNDATION_EXPORT double Swift_MusicologyVersionNumber; + +//! Project version string for Swift_Musicology. +FOUNDATION_EXPORT const unsigned char Swift_MusicologyVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Swift Musicology/Swift Musicology/Tempo.swift b/Swift Musicology/Swift Musicology/Tempo.swift new file mode 100644 index 0000000..6b34a56 --- /dev/null +++ b/Swift Musicology/Swift Musicology/Tempo.swift @@ -0,0 +1,26 @@ +// +// Tempo.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +/// Defines the tempo with beats per second and a time signature. +public struct Tempo: Codable { + + public var timeSignature: TimeSignature + public var bpm: Double + + /// Initilizes tempo with time signature and BPM, with a default of 4/4 at 120 BPM. + /// + /// - Parameters: + /// - timeSignature: A TimeSignature object. + /// - bpm: Beats per minute. + public init(timeSignature: TimeSignature = TimeSignature(), bpm: Double = 120.0) { + self.timeSignature = timeSignature + self.bpm = bpm + } +} diff --git a/Swift Musicology/Swift Musicology/TimeSignature.swift b/Swift Musicology/Swift Musicology/TimeSignature.swift new file mode 100644 index 0000000..b993cde --- /dev/null +++ b/Swift Musicology/Swift Musicology/TimeSignature.swift @@ -0,0 +1,46 @@ +// +// TimeSignature.swift +// Swift Musicology +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import Foundation + +// Defines how many beats in a measure with which note value. +public struct TimeSignature: Codable { + + /// Beats per measure. + public var beats: Int + + /// Note value per beat. + public var noteValue: NoteValueType + + /// Initilizes a TimeSignature object with beats per measure and the note value + /// of each beat, with 4/4 as the default time signature. + /// + /// - Parameters: + /// - beats: Number of beats in a measure + /// - noteValue: Note value of the beats. + public init(beats: Int = 4, noteValue: NoteValueType = .quarter) { + self.beats = beats + self.noteValue = noteValue + } + + /// Initilizes a TimeSignature object with beats per measure and the note value of + /// each beat. Returns nil if a division is not match a `NoteValue`. + /// + /// - Parameters: + /// - beats: Number of beats in a measure + /// - division: Number of the beats. + public init?(beats: Int, division: Int) { + guard let noteValue = NoteValueType(rawValue: Double(division)) else { + return nil + } + + self.beats = beats + self.noteValue = noteValue + } +} + diff --git a/Swift Musicology/Swift MusicologyTests/Info.plist b/Swift Musicology/Swift MusicologyTests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/Swift Musicology/Swift MusicologyTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Swift Musicology/Swift MusicologyTests/MusicTheoryTests.swift b/Swift Musicology/Swift MusicologyTests/MusicTheoryTests.swift new file mode 100644 index 0000000..0ba2679 --- /dev/null +++ b/Swift Musicology/Swift MusicologyTests/MusicTheoryTests.swift @@ -0,0 +1,351 @@ +//// +//// MusicTheoryTests.swift +//// Swift MusicologyTests +//// +//// Created by roux g. buciu on 2018-11-25. +//// Copyright © 2018 roux g. buciu. All rights reserved. +//// +// +//import XCTest +// +//class MusicTheoryTests: XCTestCase { +// override func setUp() { +// super.setUp() +// // Put setup code here. This method is called before the invocation of each test method in the class. +// } +// +// override func tearDown() { +// // Put teardown code here. This method is called after the invocation of each test method in the class. +// super.tearDown() +// } +//} +// +//// MARK: - Note Tests +// +//extension MusicTheoryTests { +// func testIntervals() { +// let key = Key(type: .c) +// let pitch = Pitch(key: key, octave: 1) +// XCTAssert((pitch + 12).octave == pitch.octave + 1) +// XCTAssert((pitch + 1).key == Key(type: .c, accidental: .sharp)) +// XCTAssert((pitch - 1) == Pitch(key: Key(type: .b), octave: 0)) +// +// let c1 = Pitch(key: Key(type: .c), octave: 1) +// let d1 = Pitch(key: Key(type: .d), octave: 1) +// XCTAssert(d1 - c1 == .M2) +// } +// +// func testAccidentals() { +// XCTAssert(Accidental.flat * 2 == Accidental.doubleFlat) +// XCTAssert(Accidental.doubleFlat / 2 == Accidental.flat) +// XCTAssert(Accidental.sharps(amount: 2) - 2 == Accidental.natural) +// XCTAssert(Accidental.flats(amount: 2) + 2 == 0) +// XCTAssert(Accidental.sharps(amount: 2) + Accidental.sharps(amount: 1) == Accidental.sharps(amount: 3)) +// XCTAssert(Accidental(integerLiteral: -3) + Accidental(rawValue: 3)! == 0) +// } +// +// func testKeys() { +// let d = Key.KeyType.d +// XCTAssert(d.key(at: -2) == .b) +// XCTAssert(d.key(at: -19) == .f) +// XCTAssert(d.key(at: 12) == .b) +// XCTAssert(d.key(at: 0) == .d) +// XCTAssert(d.key(at: 1) == .e) +// XCTAssert(d.key(at: 2) == .f) +// XCTAssert(d.key(at: -3) == .a) +// XCTAssert(d.key(at: -301) == .d) +// +// let f = Key.KeyType.f +// XCTAssert(f.key(at: -3) == .c) +// +// let k: Key = "a##b" +// XCTAssert(k.accidental == .sharp && k.type == .a) +// +// let b = Key(type: .b, accidental: .natural) +// XCTAssert(Key(type: .c, accidental: .flat) == b) +// XCTAssert(Key(type: .c, accidental: .sharps(amount: 23)) == b) +// XCTAssert(Key(type: .c, accidental: .flats(amount: 13)) == b) +// XCTAssert(Key(type: .c, accidental: .flats(amount: 25)) == b) +// XCTAssert(Key(type: .c, accidental: .flats(amount: 24)) != b) +// } +// +// func testPitches() { +// let c0: Pitch = 12 +// XCTAssert(c0.octave == 0 && c0.key.accidental == .natural && c0.key.type == .c) +// XCTAssert(c0 - 12 == 0) +// +// var pitch = Pitch(midiNote: 127) +// XCTAssert(pitch.key == Key(type: .g)) +// pitch = Pitch(midiNote: 0) +// XCTAssert(pitch.key == Key(type: .c)) +// pitch = Pitch(midiNote: 66, isPreferredAccidentalSharps: false) +// XCTAssert(pitch.key == Key(type: .g, accidental: .flat)) +// +// let c1 = Pitch(key: Key(type: .c), octave: 1) +// XCTAssert(c1 + .m2 == Pitch(key: Key(type: .d, accidental: .flat), octave: 1)) +// XCTAssert(c1 + .M2 == Pitch(key: Key(type: .d, accidental: .natural), octave: 1)) +// XCTAssert(c1 + .m3 == Pitch(key: Key(type: .e, accidental: .flat), octave: 1)) +// XCTAssert(c1 + .M3 == Pitch(key: Key(type: .e, accidental: .natural), octave: 1)) +// XCTAssert(c1 + .P8 == Pitch(key: Key(type: .c, accidental: .natural), octave: 2)) +// +// let d1 = Pitch(key: Key(type: .d), octave: 1) +// XCTAssert(d1 - .m2 == Pitch(key: Key(type: .c, accidental: .sharp), octave: 1)) +// XCTAssert(d1 - .M2 == Pitch(key: Key(type: .c, accidental: .natural), octave: 1)) +// +// let p: Pitch = "f#-5" +// XCTAssert(p.key === Key(type: .f, accidental: .sharp)) +// XCTAssert(p.octave == -5) +// } +// +// func testFrequency() { +// let note = Pitch(key: Key(type: .a), octave: 4) +// XCTAssertEqual(note.frequency, 440.0) +// +// let a4 = Pitch.nearest(frequency: 440.0) +// XCTAssertEqual(note, a4) +// } +//} +// +//// MARK: - Tempo Tests +// +//extension MusicTheoryTests { +// func testNoteValueConversions() { +// let noteValue = NoteValue(type: .half, modifier: .dotted) +// XCTAssertEqual(noteValue / NoteValueType.sixteenth, 12) +// XCTAssertEqual(noteValue / NoteValueType.whole, 0.75) +// } +// +// func testDurations() { +// let timeSignature = TimeSignature(beats: 4, noteValue: .quarter) // 4/4 +// let tempo = Tempo(timeSignature: timeSignature, bpm: 120) // 120BPM +// var noteValue = NoteValue(type: .quarter) +// var duration = tempo.duration(of: noteValue) +// XCTAssert(duration == 0.5) +// +// noteValue.modifier = .dotted +// duration = tempo.duration(of: noteValue) +// XCTAssert(duration == 0.75) +// } +// +// func testSampleLengthCalcuation() { +// let rates = [ +// NoteValue(type: .whole, modifier: .default), +// NoteValue(type: .half, modifier: .default), +// NoteValue(type: .half, modifier: .dotted), +// NoteValue(type: .half, modifier: .triplet), +// NoteValue(type: .quarter, modifier: .default), +// NoteValue(type: .quarter, modifier: .dotted), +// NoteValue(type: .quarter, modifier: .triplet), +// NoteValue(type: .eighth, modifier: .default), +// NoteValue(type: .eighth, modifier: .dotted), +// NoteValue(type: .sixteenth, modifier: .default), +// NoteValue(type: .sixteenth, modifier: .dotted), +// NoteValue(type: .thirtysecond, modifier: .default), +// NoteValue(type: .sixtyfourth, modifier: .default), +// ] +// +// let tempo = Tempo() +// let sampleLengths = rates +// .map({ tempo.sampleLength(of: $0) }) +// .map({ round(100 * $0) / 100 }) +// +// let expected: [Double] = [ +// 88200.0, +// 44100.0, +// 66150.0, +// 29401.47, +// 22050.0, +// 33075.0, +// 14700.73, +// 11025.0, +// 16537.5, +// 5512.5, +// 8268.75, +// 2756.25, +// 1378.13, +// ] +// +// XCTAssertEqual(sampleLengths, expected) +// } +//} +// +//// MARK: - Scale Tests +// +//extension MusicTheoryTests { +// func testScale() { +// let cMaj: [Key] = [ +// Key(type: .c), +// Key(type: .d), +// Key(type: .e), +// Key(type: .f), +// Key(type: .g), +// Key(type: .a), +// Key(type: .b), +// ] +// +// let cMajScale = Scale(type: .major, key: Key(type: .c)) +// XCTAssert(cMajScale.keys == cMaj) +// +// let cMin: [Key] = [ +// Key(type: .c), +// Key(type: .d), +// Key(type: .e, accidental: .flat), +// Key(type: .f), +// Key(type: .g), +// Key(type: .a, accidental: .flat), +// Key(type: .b, accidental: .flat), +// ] +// +// let cMinScale = Scale(type: .minor, key: Key(type: .c)) +// XCTAssert(cMinScale.keys == cMin) +// } +// +// func testHarmonicFields() { +// let cmaj = Scale(type: .major, key: Key(type: .c)) +// let triads = cmaj.harmonicField(for: .triad) +// let triadsExpected = [ +// Chord(type: ChordType(third: .major), key: Key(type: .c)), +// Chord(type: ChordType(third: .minor), key: Key(type: .d)), +// Chord(type: ChordType(third: .minor), key: Key(type: .e)), +// Chord(type: ChordType(third: .major), key: Key(type: .f)), +// Chord(type: ChordType(third: .major), key: Key(type: .g)), +// Chord(type: ChordType(third: .minor), key: Key(type: .a)), +// Chord(type: ChordType(third: .minor, fifth: .diminished), key: Key(type: .b)), +// ] +// XCTAssert(triads.enumerated().map({ $1 == triadsExpected[$0] }).filter({ $0 == false }).count == 0) +// } +//} +// +//// MARK: - Chord Tests +// +//extension MusicTheoryTests { +// func testChords() { +// let cmajNotes: [Key] = [Key(type: .c), Key(type: .e), Key(type: .g)] +// let cmaj = Chord(type: ChordType(third: .major), key: Key(type: .c)) +// XCTAssert(cmajNotes == cmaj.keys) +// +// let cminNotes: [Key] = [ +// Key(type: .c), +// Key(type: .e, accidental: .flat), +// Key(type: .g), +// ] +// let cmin = Chord(type: ChordType(third: .minor), key: Key(type: .c)) +// XCTAssert(cminNotes == cmin.keys) +// +// let c13Notes: [Pitch] = [ +// Pitch(key: Key(type: .c), octave: 1), +// Pitch(key: Key(type: .e), octave: 1), +// Pitch(key: Key(type: .g), octave: 1), +// Pitch(key: Key(type: .b, accidental: .flat), octave: 1), +// Pitch(key: Key(type: .d), octave: 2), +// Pitch(key: Key(type: .f), octave: 2), +// Pitch(key: Key(type: .a), octave: 2), +// ] +// let c13 = Chord( +// type: ChordType( +// third: .major, +// seventh: .dominant, +// extensions: [ +// ChordExtensionType(type: .thirteenth), +// ] +// ), +// key: Key(type: .c) +// ) +// XCTAssert(c13.pitches(octave: 1) === c13Notes) +// +// let cm13Notes: [Pitch] = [ +// Pitch(key: Key(type: .c), octave: 1), +// Pitch(key: Key(type: .e, accidental: .flat), octave: 1), +// Pitch(key: Key(type: .g), octave: 1), +// Pitch(key: Key(type: .b, accidental: .flat), octave: 1), +// Pitch(key: Key(type: .d), octave: 2), +// Pitch(key: Key(type: .f), octave: 2), +// Pitch(key: Key(type: .a), octave: 2), +// ] +// let cm13 = Chord( +// type: ChordType( +// third: .minor, +// seventh: .dominant, +// extensions: [ +// ChordExtensionType(type: .thirteenth), +// ] +// ), +// key: Key(type: .c) +// ) +// XCTAssert(cm13.pitches(octave: 1) === cm13Notes) +// +// let minorIntervals: [Interval] = [.P1, .m3, .P5] +// guard let minorChord = ChordType(intervals: minorIntervals.map({ $0 })) else { return XCTFail() } +// XCTAssert(minorChord == ChordType(third: .minor)) +// +// let majorIntervals: [Interval] = [.P1, .M3, .P5] +// guard let majorChord = ChordType(intervals: majorIntervals.map({ $0 })) else { return XCTFail() } +// XCTAssert(majorChord == ChordType(third: .major)) +// +// let cmadd13Notes: [Pitch] = [ +// Pitch(key: Key(type: .c), octave: 1), +// Pitch(key: Key(type: .e, accidental: .flat), octave: 1), +// Pitch(key: Key(type: .g), octave: 1), +// Pitch(key: Key(type: .a), octave: 2), +// ] +// let cmadd13 = Chord( +// type: ChordType( +// third: .minor, +// extensions: [ChordExtensionType(type: .thirteenth)] +// ), +// key: Key(type: .c) +// ) +// XCTAssert(cmadd13.pitches(octave: 1) === cmadd13Notes) +// } +// +// func testInversions() { +// let c7 = Chord( +// type: ChordType(third: .major, seventh: .dominant), +// key: Key(type: .c) +// ) +// let c7Inversions = [ +// [ +// Pitch(key: Key(type: .c), octave: 1), +// Pitch(key: Key(type: .e), octave: 1), +// Pitch(key: Key(type: .g), octave: 1), +// Pitch(key: Key(type: .b, accidental: .flat), octave: 1), +// ], +// [ +// Pitch(key: Key(type: .e), octave: 1), +// Pitch(key: Key(type: .g), octave: 1), +// Pitch(key: Key(type: .b, accidental: .flat), octave: 1), +// Pitch(key: Key(type: .c), octave: 2), +// ], +// [ +// Pitch(key: Key(type: .g), octave: 1), +// Pitch(key: Key(type: .b, accidental: .flat), octave: 1), +// Pitch(key: Key(type: .c), octave: 2), +// Pitch(key: Key(type: .e), octave: 2), +// ], +// [ +// Pitch(key: Key(type: .b, accidental: .flat), octave: 1), +// Pitch(key: Key(type: .c), octave: 2), +// Pitch(key: Key(type: .e), octave: 2), +// Pitch(key: Key(type: .g), octave: 2), +// ], +// ] +// for (index, chord) in c7.inversions.enumerated() { +// XCTAssert(chord.pitches(octave: 1) === c7Inversions[index]) +// } +// } +//} +// +//// MARK: - [Pitch] Extension +// +//// A function for checking pitche arrays exactly equal in terms of their pitches keys and octaves. +//func === (lhs: [Pitch], rhs: [Pitch]) -> Bool { +// guard lhs.count == rhs.count else { return false } +// for i in 0 ..< lhs.count { +// if lhs[i] === rhs[i] { +// continue +// } else { +// return false +// } +// } +// return true +//} diff --git a/Swift Musicology/Swift MusicologyTests/Swift_MusicologyTests.swift b/Swift Musicology/Swift MusicologyTests/Swift_MusicologyTests.swift new file mode 100644 index 0000000..6407606 --- /dev/null +++ b/Swift Musicology/Swift MusicologyTests/Swift_MusicologyTests.swift @@ -0,0 +1,34 @@ +// +// Swift_MusicologyTests.swift +// Swift MusicologyTests +// +// Created by roux g. buciu on 2018-11-25. +// Copyright © 2018 roux g. buciu. All rights reserved. +// + +import XCTest +@testable import Swift_Musicology + +class Swift_MusicologyTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +}