diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS-Bridging-Header.h b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS-Bridging-Header.h new file mode 100644 index 00000000..705fbf63 --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS-Bridging-Header.h @@ -0,0 +1,6 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. (Needed for Swift only) +// + +#import "MIKMIDIToAudioExporter.h" +#import "MIKOfflineMIDISynthesizer.h" diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS.xcodeproj/project.pbxproj b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c24478a5 --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS.xcodeproj/project.pbxproj @@ -0,0 +1,444 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + D92256921E0F9D3200A8815D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D92256911E0F9D3200A8815D /* AppDelegate.swift */; }; + D92256941E0F9D3200A8815D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D92256931E0F9D3200A8815D /* ViewController.swift */; }; + D92256971E0F9D3200A8815D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D92256951E0F9D3200A8815D /* Main.storyboard */; }; + D92256991E0F9D3200A8815D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D92256981E0F9D3200A8815D /* Assets.xcassets */; }; + D922569C1E0F9D3200A8815D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D922569A1E0F9D3200A8815D /* LaunchScreen.storyboard */; }; + D92256C31E0FA5DC00A8815D /* Stereo Piano.sf2 in Resources */ = {isa = PBXBuildFile; fileRef = D92256C11E0FA5DC00A8815D /* Stereo Piano.sf2 */; }; + D92256C41E0FA5DC00A8815D /* TheOriginal.mid in Resources */ = {isa = PBXBuildFile; fileRef = D92256C21E0FA5DC00A8815D /* TheOriginal.mid */; }; + D92256D61E0FB46E00A8815D /* MIKMIDIToAudioExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = D92256D31E0FB46E00A8815D /* MIKMIDIToAudioExporter.m */; }; + D92256D71E0FB46E00A8815D /* MIKOfflineMIDISynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = D92256D51E0FB46E00A8815D /* MIKOfflineMIDISynthesizer.m */; }; + D92256D81E0FB48800A8815D /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D92256CF1E0FB43E00A8815D /* MIKMIDI.framework */; }; + D92256D91E0FB48800A8815D /* MIKMIDI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D92256CF1E0FB43E00A8815D /* MIKMIDI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + D92256CC1E0FB43E00A8815D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D92256C61E0FB43D00A8815D /* MIKMIDI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9D74EEA517A7129300BEE89F; + remoteInfo = MIKMIDI; + }; + D92256CE1E0FB43E00A8815D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D92256C61E0FB43D00A8815D /* MIKMIDI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9DAF8B061A7AFF1100F46528; + remoteInfo = "MIKMIDI-iOS"; + }; + D92256D01E0FB43E00A8815D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D92256C61E0FB43D00A8815D /* MIKMIDI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9D4DF13A1AAB57430065F004; + remoteInfo = "MIKMIDI Tests"; + }; + D92256DA1E0FB48800A8815D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D92256C61E0FB43D00A8815D /* MIKMIDI.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 9DAF8B051A7AFF1100F46528; + remoteInfo = "MIKMIDI-iOS"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + D92256B31E0F9DA000A8815D /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D92256D91E0FB48800A8815D /* MIKMIDI.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + D922568E1E0F9D3200A8815D /* MIDI to Audio iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MIDI to Audio iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + D92256911E0F9D3200A8815D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + D92256931E0F9D3200A8815D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + D92256961E0F9D3200A8815D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + D92256981E0F9D3200A8815D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D922569B1E0F9D3200A8815D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + D922569D1E0F9D3200A8815D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D92256B51E0FA15E00A8815D /* MIDI to Audio iOS-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MIDI to Audio iOS-Bridging-Header.h"; sourceTree = ""; }; + D92256C11E0FA5DC00A8815D /* Stereo Piano.sf2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Stereo Piano.sf2"; sourceTree = ""; }; + D92256C21E0FA5DC00A8815D /* TheOriginal.mid */ = {isa = PBXFileReference; lastKnownFileType = audio.midi; path = TheOriginal.mid; sourceTree = ""; }; + D92256C61E0FB43D00A8815D /* MIKMIDI.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MIKMIDI.xcodeproj; path = ../../../Framework/MIKMIDI.xcodeproj; sourceTree = ""; }; + D92256D21E0FB46E00A8815D /* MIKMIDIToAudioExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIToAudioExporter.h; sourceTree = ""; }; + D92256D31E0FB46E00A8815D /* MIKMIDIToAudioExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIToAudioExporter.m; sourceTree = ""; }; + D92256D41E0FB46E00A8815D /* MIKOfflineMIDISynthesizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKOfflineMIDISynthesizer.h; sourceTree = ""; }; + D92256D51E0FB46E00A8815D /* MIKOfflineMIDISynthesizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKOfflineMIDISynthesizer.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D922568B1E0F9D3200A8815D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D92256D81E0FB48800A8815D /* MIKMIDI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D92256851E0F9D3200A8815D = { + isa = PBXGroup; + children = ( + D92256C61E0FB43D00A8815D /* MIKMIDI.xcodeproj */, + D92256901E0F9D3200A8815D /* MIDI to Audio iOS */, + D922568F1E0F9D3200A8815D /* Products */, + ); + sourceTree = ""; + }; + D922568F1E0F9D3200A8815D /* Products */ = { + isa = PBXGroup; + children = ( + D922568E1E0F9D3200A8815D /* MIDI to Audio iOS.app */, + ); + name = Products; + sourceTree = ""; + }; + D92256901E0F9D3200A8815D /* MIDI to Audio iOS */ = { + isa = PBXGroup; + children = ( + D92256BD1E0FA18B00A8815D /* Converting Files */, + D92256911E0F9D3200A8815D /* AppDelegate.swift */, + D92256931E0F9D3200A8815D /* ViewController.swift */, + D92256951E0F9D3200A8815D /* Main.storyboard */, + D92256981E0F9D3200A8815D /* Assets.xcassets */, + D922569A1E0F9D3200A8815D /* LaunchScreen.storyboard */, + D922569D1E0F9D3200A8815D /* Info.plist */, + D92256C11E0FA5DC00A8815D /* Stereo Piano.sf2 */, + D92256C21E0FA5DC00A8815D /* TheOriginal.mid */, + ); + path = "MIDI to Audio iOS"; + sourceTree = ""; + }; + D92256BD1E0FA18B00A8815D /* Converting Files */ = { + isa = PBXGroup; + children = ( + D92256B51E0FA15E00A8815D /* MIDI to Audio iOS-Bridging-Header.h */, + D92256D21E0FB46E00A8815D /* MIKMIDIToAudioExporter.h */, + D92256D31E0FB46E00A8815D /* MIKMIDIToAudioExporter.m */, + D92256D41E0FB46E00A8815D /* MIKOfflineMIDISynthesizer.h */, + D92256D51E0FB46E00A8815D /* MIKOfflineMIDISynthesizer.m */, + ); + name = "Converting Files"; + path = ..; + sourceTree = ""; + }; + D92256C71E0FB43D00A8815D /* Products */ = { + isa = PBXGroup; + children = ( + D92256CD1E0FB43E00A8815D /* MIKMIDI.framework */, + D92256CF1E0FB43E00A8815D /* MIKMIDI.framework */, + D92256D11E0FB43E00A8815D /* MIKMIDI Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D922568D1E0F9D3200A8815D /* MIDI to Audio iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = D92256A01E0F9D3200A8815D /* Build configuration list for PBXNativeTarget "MIDI to Audio iOS" */; + buildPhases = ( + D922568A1E0F9D3200A8815D /* Sources */, + D922568B1E0F9D3200A8815D /* Frameworks */, + D922568C1E0F9D3200A8815D /* Resources */, + D92256B31E0F9DA000A8815D /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D92256DB1E0FB48800A8815D /* PBXTargetDependency */, + ); + name = "MIDI to Audio iOS"; + productName = "MIDI to Audio iOS"; + productReference = D922568E1E0F9D3200A8815D /* MIDI to Audio iOS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D92256861E0F9D3200A8815D /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0820; + LastUpgradeCheck = 0820; + ORGANIZATIONNAME = madebysasha; + TargetAttributes = { + D922568D1E0F9D3200A8815D = { + CreatedOnToolsVersion = 8.2.1; + LastSwiftMigration = 0820; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = D92256891E0F9D3200A8815D /* Build configuration list for PBXProject "MIDI to Audio iOS" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D92256851E0F9D3200A8815D; + productRefGroup = D922568F1E0F9D3200A8815D /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = D92256C71E0FB43D00A8815D /* Products */; + ProjectRef = D92256C61E0FB43D00A8815D /* MIKMIDI.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + D922568D1E0F9D3200A8815D /* MIDI to Audio iOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + D92256CD1E0FB43E00A8815D /* MIKMIDI.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MIKMIDI.framework; + remoteRef = D92256CC1E0FB43E00A8815D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + D92256CF1E0FB43E00A8815D /* MIKMIDI.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MIKMIDI.framework; + remoteRef = D92256CE1E0FB43E00A8815D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + D92256D11E0FB43E00A8815D /* MIKMIDI Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "MIKMIDI Tests.xctest"; + remoteRef = D92256D01E0FB43E00A8815D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + D922568C1E0F9D3200A8815D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D922569C1E0F9D3200A8815D /* LaunchScreen.storyboard in Resources */, + D92256991E0F9D3200A8815D /* Assets.xcassets in Resources */, + D92256C31E0FA5DC00A8815D /* Stereo Piano.sf2 in Resources */, + D92256971E0F9D3200A8815D /* Main.storyboard in Resources */, + D92256C41E0FA5DC00A8815D /* TheOriginal.mid in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D922568A1E0F9D3200A8815D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D92256941E0F9D3200A8815D /* ViewController.swift in Sources */, + D92256D71E0FB46E00A8815D /* MIKOfflineMIDISynthesizer.m in Sources */, + D92256D61E0FB46E00A8815D /* MIKMIDIToAudioExporter.m in Sources */, + D92256921E0F9D3200A8815D /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + D92256DB1E0FB48800A8815D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "MIKMIDI-iOS"; + targetProxy = D92256DA1E0FB48800A8815D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + D92256951E0F9D3200A8815D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D92256961E0F9D3200A8815D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + D922569A1E0F9D3200A8815D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D922569B1E0F9D3200A8815D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + D922569E1E0F9D3200A8815D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 10.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + D922569F1E0F9D3200A8815D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 10.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D92256A11E0F9D3200A8815D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "MIDI to Audio iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.madebysasha.MIDI-to-Audio-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "MIDI to Audio iOS-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + D92256A21E0F9D3200A8815D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "MIDI to Audio iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.madebysasha.MIDI-to-Audio-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "MIDI to Audio iOS-Bridging-Header.h"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D92256891E0F9D3200A8815D /* Build configuration list for PBXProject "MIDI to Audio iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D922569E1E0F9D3200A8815D /* Debug */, + D922569F1E0F9D3200A8815D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D92256A01E0F9D3200A8815D /* Build configuration list for PBXNativeTarget "MIDI to Audio iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D92256A11E0F9D3200A8815D /* Debug */, + D92256A21E0F9D3200A8815D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D92256861E0F9D3200A8815D /* Project object */; +} diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/AppDelegate.swift b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/AppDelegate.swift new file mode 100644 index 00000000..ecede7fe --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/AppDelegate.swift @@ -0,0 +1,38 @@ +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..b8236c65 --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,48 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Base.lproj/LaunchScreen.storyboard b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..fdf3f97d --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Base.lproj/Main.storyboard b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Base.lproj/Main.storyboard new file mode 100644 index 00000000..5fa7fa53 --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Base.lproj/Main.storyboard @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Info.plist b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Info.plist new file mode 100644 index 00000000..38e98af2 --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Stereo Piano.sf2 b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Stereo Piano.sf2 new file mode 100644 index 00000000..b5d267ea Binary files /dev/null and b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/Stereo Piano.sf2 differ diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/TheOriginal.mid b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/TheOriginal.mid new file mode 100644 index 00000000..ab6a5e70 Binary files /dev/null and b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/TheOriginal.mid differ diff --git a/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/ViewController.swift b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/ViewController.swift new file mode 100644 index 00000000..6682f0af --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIDI to Audio iOS/ViewController.swift @@ -0,0 +1,130 @@ +// +// ViewController.swift +// MIDI to Audio iOS +// +// Created by Sasha Ivanov on 2016-12-24. +// Copyright © 2016 madebysasha. All rights reserved. + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +import UIKit +import AVFoundation +import MIKMIDI + +class ViewController: UIViewController { + + + + var midiUrl:URL? + var soundfontUrl:URL? + var sequencer:MIKMIDISequencer? + + var audioUrl:URL? + var audioPlayer:AVPlayer? + + @IBOutlet weak var playOriginalButton: UIButton! + @IBOutlet weak var convertToAudioFileButton: UIButton! + @IBOutlet weak var playConvertedAudioFileButton: UIButton! + + @IBAction func playOriginalButtonPressed(_ sender: Any) { + + // Play the sequencer + sequencer?.startPlayback() + + } + + @IBAction func convertToAudioFileButtonPressed(_ sender: Any) { + + // Create the MIKMIDI Audio Exporter + let audioExporter = MIKMIDIToAudioExporter(midiFileAt: midiUrl) + + // Add the soundfont to the Audio Exporter (NEW from macOS Example) + audioExporter?.addSoundfont(soundfontUrl) + + // Render the MIDI file as a CAF file with the added soundfont + audioExporter?.exportWithSoundfontToAudioFile(completionHandler: { (audioOutputUrl, error) in + + // Check if there was an error durring the export + if (audioOutputUrl == nil){ + if((error) != nil){ + NSLog(error as! String) + print("Audio Conversion Failed") + } + } + + // Otherwise save the output url and enable the Play Converted Audio button + else{ + self.audioUrl = audioOutputUrl + self.playConvertedAudioFileButton.isEnabled = true + } + }) + } + + @IBAction func playConvertedButtonPressed(_ sender: Any) { + + // Create an AV Audio Player to playback the converted file + audioPlayer = AVPlayer(url: audioUrl!) + + // Play the converted file + audioPlayer?.play() + } + + + override func viewDidLoad() { + super.viewDidLoad() + + // 1. Disable the Play Converted Audio button. (Will enable when the convert button is pressed) + playConvertedAudioFileButton.isEnabled = false + + // 2. Set the URLs of the files we want to use for playback (midi file and soundfont file) + midiUrl = Bundle.main.url(forResource: "TheOriginal", withExtension: "mid") + soundfontUrl = Bundle.main.url(forResource: "Stereo Piano", withExtension: ".sf2") + + // 3. Setup the MIKMIDI Sequencer for Playing Back Original Midi File + do{ + // Create a MIKMIDI Sequence from the midi file + let sequence = try MIKMIDISequence(fileAt: midiUrl!) + + // Create a MIKMIDI Sequencer from the sequence + sequencer = MIKMIDISequencer(sequence: sequence) + + // Use the Soundfont File to set the Synth for each Track in the Sequence. + for track in sequence.tracks{ + let synth = sequencer?.builtinSynthesizer(for: track) + try synth!.loadSoundfontFromFile(at: soundfontUrl!) + } + } + catch{ + print("Unable to Create Sequencer") + } + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + + + + +} + diff --git a/Examples/iOS/MIDI to Audio iOS/MIKMIDIToAudioExporter.h b/Examples/iOS/MIDI to Audio iOS/MIKMIDIToAudioExporter.h new file mode 100755 index 00000000..aeb239e8 --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIKMIDIToAudioExporter.h @@ -0,0 +1,48 @@ +// +// MIKStudioMIDIToAudioExporter.h +// StudioTime +// +// Created by Andrew Madsen on 2/13/15. +// Copyright (c) 2015-2016 Mixed In Key LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#import + +typedef void(^MIKStudioMIDIToAudioExporterCompletionBlock)(NSURL *audioFileURL, NSError *error); + +@interface MIKMIDIToAudioExporter : NSObject + ++ (instancetype)exporterWithMIDIFileAtURL:(NSURL *)fileURL; +- (instancetype)initWithMIDIFileAtURL:(NSURL *)fileURL NS_DESIGNATED_INITIALIZER; + +- (void)exportToAudioFileWithCompletionHandler:(MIKStudioMIDIToAudioExporterCompletionBlock)completionBlock; + + +- (void)addSoundfont: (NSURL*)fileUrl; +- (void)exportWithSoundfontToAudioFileWithCompletionHandler:(MIKStudioMIDIToAudioExporterCompletionBlock)completionBlock; + + +// Properties + +@property (nonatomic, strong, readonly) NSURL *midiFileURL; + +@end diff --git a/Examples/iOS/MIDI to Audio iOS/MIKMIDIToAudioExporter.m b/Examples/iOS/MIDI to Audio iOS/MIKMIDIToAudioExporter.m new file mode 100755 index 00000000..8acb4ab5 --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIKMIDIToAudioExporter.m @@ -0,0 +1,112 @@ +// +// MIKStudioMIDIToAudioExporter.m +// StudioTime +// +// Created by Andrew Madsen on 2/13/15. +// Copyright (c) 2015-2016 Mixed In Key LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#import "MIKMIDIToAudioExporter.h" +#import +#import "MIKOfflineMIDISynthesizer.h" + +@interface MIKMIDIToAudioExporter () + +@property (nonatomic, strong) MIKMIDISequencer *sequencer; +@property (nonatomic, strong) MIKOfflineMIDISynthesizer *synthesizer; + +@property (nonatomic, copy) MIKStudioMIDIToAudioExporterCompletionBlock completionBlock; + +@property (nonatomic,strong) NSURL *soundfontUrl; + +@end + +@implementation MIKMIDIToAudioExporter + ++ (instancetype)exporterWithMIDIFileAtURL:(NSURL *)fileURL +{ + return [[self alloc] initWithMIDIFileAtURL:fileURL]; +} + +- (instancetype)initWithMIDIFileAtURL:(NSURL *)fileURL; +{ + self = [super init]; + if (self) { + _midiFileURL = fileURL; + } + return self; +} + +- (instancetype)init +{ + [NSException raise:NSInternalInconsistencyException format:@"Use -initWithMIDIFileAtURL:"]; + self = [self init]; + return nil; +} + +- (void)exportToAudioFileWithCompletionHandler:(MIKStudioMIDIToAudioExporterCompletionBlock)completionBlock +{ + self.completionBlock = ^(NSURL *url, NSError *error){ if (completionBlock) completionBlock(url, error); }; + + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:self.midiFileURL error:&error]; + if (!sequence) return [self finishWithError:error]; + + self.synthesizer = [[MIKOfflineMIDISynthesizer alloc] init]; + self.synthesizer.tracks = sequence.tracks; + self.synthesizer.tempo = [sequence tempoAtTimeStamp:0]; + [self.synthesizer export]; + + [self finishWithError:nil]; +} + +// Added by Sasha: Use this to add the URL of a sound font to the exporter BEFORE called exportWithSoundfontToAudioFileWithCompletion +- (void)addSoundfont: (NSURL*)fileUrl +{ + self.soundfontUrl = fileUrl; +} + +// Added by Sasha: Use this to export with a Soundfont instead of basic tones. IMPORTANT: Use addSoundfont before calling!!! +- (void)exportWithSoundfontToAudioFileWithCompletionHandler: (MIKStudioMIDIToAudioExporterCompletionBlock)completionBlock +{ + self.completionBlock = ^(NSURL *url, NSError *error){ if (completionBlock) completionBlock(url, error); }; + + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:self.midiFileURL error:&error]; + if (!sequence) return [self finishWithError:error]; + + self.synthesizer = [[MIKOfflineMIDISynthesizer alloc] init]; + self.synthesizer.tracks = sequence.tracks; + self.synthesizer.tempo = [sequence tempoAtTimeStamp:0]; + [self.synthesizer loadSoundfontFromFileAtURL:self.soundfontUrl error:&error]; // Added by Sasha + [self.synthesizer export]; + + [self finishWithError:nil]; +} + +- (void)finishWithError:(NSError *)error +{ + if (error) NSLog(@"MIKStudioMIDIToAudioExporter encountered an error: %@", error); + self.completionBlock(self.synthesizer.outputFileURL, error); +} + +@end diff --git a/Examples/iOS/MIDI to Audio iOS/MIKOfflineMIDISynthesizer.h b/Examples/iOS/MIDI to Audio iOS/MIKOfflineMIDISynthesizer.h new file mode 100755 index 00000000..379c089c --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIKOfflineMIDISynthesizer.h @@ -0,0 +1,39 @@ +// +// MIKOfflineMIDISynthesizer.h +// MIDI To Audio +// +// Created by Andrew Madsen on 2/17/15. +// Copyright (c) 2015-2016 Mixed In Key. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#import + +@interface MIKOfflineMIDISynthesizer : MIKMIDISynthesizer + +- (BOOL)loadSoundfontFromFileAtURL:(NSURL *)fileURL error:(NSError **)error; +- (void)export; + +@property (nonatomic, strong) NSArray *tracks; // Array of MIKMIDITracks. +@property (nonatomic) Float64 tempo; +@property (nonatomic, strong, readonly) NSURL *outputFileURL; + +@end diff --git a/Examples/iOS/MIDI to Audio iOS/MIKOfflineMIDISynthesizer.m b/Examples/iOS/MIDI to Audio iOS/MIKOfflineMIDISynthesizer.m new file mode 100755 index 00000000..173aeff2 --- /dev/null +++ b/Examples/iOS/MIDI to Audio iOS/MIKOfflineMIDISynthesizer.m @@ -0,0 +1,426 @@ +// +// MIKOfflineMIDISynthesizer.m +// MIDI To Audio +// +// Created by Andrew Madsen on 2/17/15. +// Copyright (c) 2015-2016 Mixed In Key. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#import "MIKOfflineMIDISynthesizer.h" +#import + +// the # of frames to process on each render operation +#define RENDER_FRAMES (512) + +// [thread] save audio stream through a subclass of MIKRecorder +OSStatus recordCallback(void *extFileRef, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + ExtAudioFileRef cafFile = (ExtAudioFileRef)extFileRef; + if (!cafFile) { + NSLog(@"No audio file to record to! %s", __PRETTY_FUNCTION__); + return kAudio_ParamError; // Changed by Sasha + } + if ( (*ioActionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0 ) { + return noErr; // silence, end of input + } + if ( (*ioActionFlags & kAudioUnitRenderAction_PostRender) == 0 ) { + return noErr; // record only after rendering + } + if ( ioData->mBuffers[0].mData == NULL ) { + NSLog(@"Invalid recording data"); + return kAudio_ParamError; // Changed by Sasha + } + + return ExtAudioFileWrite(cafFile, inNumberFrames, ioData); +} + +// the stream format at the end of the graph +AudioStreamBasicDescription LPCMASBD(void) +{ + UInt32 nChannels = 2; /* always process stereo */ + AudioStreamBasicDescription asbd = { 0 }; + asbd.mSampleRate = 44100.0; + asbd.mFormatID = kAudioFormatLinearPCM; + asbd.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; + asbd.mBytesPerPacket = nChannels * sizeof (float); + asbd.mFramesPerPacket = 1; + asbd.mBytesPerFrame = nChannels * sizeof (float); + asbd.mChannelsPerFrame = nChannels; + asbd.mBitsPerChannel = 8 * sizeof (float); + return asbd; +} + +@interface MIKOfflineMIDISynthesizer () + +@property (nonatomic, strong, readwrite) NSURL *outputFileURL; + +@property (nonatomic) ExtAudioFileRef cafFile; +@property (nonatomic) AudioUnit outputUnit; +@property (nonatomic, strong) MIKMIDIClock *midiClock; + +@property (nonatomic, copy) NSArray *allNoteMessages; +@property (nonatomic) MusicTimeStamp maxTrackLength; + +@end + +@implementation MIKOfflineMIDISynthesizer +{ + BOOL _doneRendering; + + MIKMIDITrack *__unsafe_unretained *_rawTracks; + NSUInteger _numTracks; +} + +- (instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componentDescription +{ + self = [super initWithAudioUnitDescription:componentDescription]; + if (self) { + _midiClock = [MIKMIDIClock clock]; + } + return self; +} + + +- (BOOL)loadSoundfontFromFileAtURL:(NSURL *)fileURL error:(NSError **)error +{ + error = error ? error : &(NSError *__autoreleasing){ nil }; + OSStatus err = noErr; + + if (self.componentDescription.componentSubType == kAudioUnitSubType_Sampler) { + // fill out a bank preset data structure + NSDictionary *typesByFileExtension = @{@"sf2" : @(kInstrumentType_SF2Preset), + @"dls" : @(kInstrumentType_DLSPreset), + @"aupreset" : @(kInstrumentType_AUPreset)}; + AUSamplerInstrumentData instrumentData; + instrumentData.fileURL = (__bridge CFURLRef)fileURL; + instrumentData.instrumentType = [typesByFileExtension[[fileURL pathExtension]] intValue]; + instrumentData.bankMSB = kAUSampler_DefaultMelodicBankMSB; + instrumentData.bankLSB = kAUSampler_DefaultBankLSB; + instrumentData.presetID = 0; + + // Added by Sasha: Set the kAUSamplerProperty_LoadPresetFromBank property + err = AudioUnitSetProperty(self.instrumentUnit, + kAUSamplerProperty_LoadInstrument, + kAudioUnitScope_Global, + 0, + &instrumentData, + sizeof(instrumentData)); + + if (err != noErr) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + return YES; + } else { +#if TARGET_OS_IPHONE + return NO; + } +#else + FSRef fsRef; + err = FSPathMakeRef((const UInt8*)[[fileURL path] cStringUsingEncoding:NSUTF8StringEncoding], &fsRef, 0); + if (err != noErr) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + + err = AudioUnitSetProperty(self.instrumentUnit, + kMusicDeviceProperty_SoundBankFSRef, + kAudioUnitScope_Global, 0, + &fsRef, sizeof(fsRef)); + if (err != noErr) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + return YES; +} +#endif +} + +- (BOOL)setupAUGraph +{ + AUGraph graph; + OSStatus err = 0; + if ((err = NewAUGraph(&graph))) { + NSLog(@"Unable to create AU graph: %i", err); + return NO; + } + + AudioComponentDescription outputcd = {0}; + outputcd.componentManufacturer = kAudioUnitManufacturer_Apple; + outputcd.componentType = kAudioUnitType_Output; + outputcd.componentSubType = kAudioUnitSubType_GenericOutput; + + AUNode outputNode; + if ((err = AUGraphAddNode(graph, &outputcd, &outputNode))) { + NSLog(@"Unable to add ouptput node to graph: %i", err); + return NO; + } + + AudioComponentDescription instrumentcd = self.componentDescription; + + AUNode instrumentNode; + if ((err = AUGraphAddNode(graph, &instrumentcd, &instrumentNode))) { + NSLog(@"Unable to add instrument node to AU graph: %i", err); + return NO; + } + + if ((err = AUGraphOpen(graph))) { + NSLog(@"Unable to open AU graph: %i", err); + return NO; + } + + /*This is new compared to the standard synth*/ + AudioUnit outputUnit; + if ((err = AUGraphNodeInfo(graph, outputNode, NULL, &outputUnit))) { + NSLog(@"Unable to get output unit: %i", err); + return NO; + } + + AudioUnit instrumentUnit; + if ((err = AUGraphNodeInfo(graph, instrumentNode, NULL, &instrumentUnit))) { + NSLog(@"Unable to get instrument AU unit: %i", err); + return NO; + } + + if ((err = AUGraphConnectNodeInput(graph, instrumentNode, 0, outputNode, 0))) { + NSLog(@"Unable to connect instrument to output: %i", err); + return NO; + } + + /* For setting up the format and file path for the output audio file..*/ + NSString *fileName = [[NSProcessInfo processInfo] globallyUniqueString]; + fileName = [fileName stringByAppendingPathExtension:@"caf"]; + NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentPath = [searchPaths objectAtIndex:0]; + NSString *filePath = [documentPath stringByAppendingPathComponent:fileName]; + NSFileManager *fm = [NSFileManager defaultManager]; + if (![fm fileExistsAtPath:documentPath]) { + [fm createDirectoryAtPath:documentPath withIntermediateDirectories:NO attributes:nil error:NULL]; + } + self.outputFileURL = [NSURL fileURLWithPath:filePath]; + + ExtAudioFileRef cafFile; + AudioStreamBasicDescription asbd = LPCMASBD(); + err = ExtAudioFileCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:filePath], + kAudioFileCAFType, + &asbd, + NULL, + kAudioFileFlags_EraseFile, + &cafFile); + if (err) { + NSLog(@"Unable to create CAF file: %i", err); + return NO; + } + self.cafFile = cafFile; + + if ((err = AudioUnitAddRenderNotify(outputUnit, recordCallback, (void *)self.cafFile))) { + NSLog(@"Unable to add record callback: %i", err); + return NO; + } + + if ((err = AUGraphInitialize(graph))) { + NSLog(@"Unable to initialize AU graph: %i", err); + return NO; + } + + err = AudioUnitAddRenderNotify(instrumentUnit, instrumentRenderCallback, (__bridge void *)self); + if (err) { + NSLog(@"Unable to set set render callback on instrument unit: %i", err); + return NO; + } + +#if !TARGET_OS_IPHONE + // Turn down reverb which is way too high by default + if (instrumentcd.componentSubType == kAudioUnitSubType_DLSSynth) { + if ((err = AudioUnitSetParameter(instrumentUnit, kMusicDeviceParam_ReverbVolume, kAudioUnitScope_Global, 0, -120, 0))) { + NSLog(@"Unable to set reverb level to -120: %i", err); + } + } +#endif + + AudioStreamBasicDescription absd = LPCMASBD(); + if ((err = AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &absd, sizeof(absd)))) { + NSLog(@"Unable to set output unit's format: %i", err); + return NO; + } + + if ((err = AUGraphStart(graph))) { + NSLog(@"Unable to start AU graph: %i", err); + return NO; + } + + self.graph = graph; + self.outputUnit = outputUnit; + self.instrumentUnit = instrumentUnit; + + return YES; +} + +- (void)storeAllEvents +{ + NSPredicate *notePredicate = [NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *b) { + return [obj isKindOfClass:[MIKMIDINoteEvent class]]; + }]; + self.maxTrackLength = 0; + NSMutableArray *noteEvents = [NSMutableArray array]; + for (NSUInteger i=0; i<[self.tracks count]; i++) { + MIKMIDITrack *track = self.tracks[i]; + self.maxTrackLength = MAX(self.maxTrackLength, track.length); + NSArray *events = [[track events] filteredArrayUsingPredicate:notePredicate]; + for (MIKMIDINoteEvent *event in events) { + MIKMutableMIDINoteEvent *eventScratch = [event mutableCopy]; + eventScratch.channel = i; + [noteEvents addObject:[eventScratch copy]]; + NSLog(@"note: %i, timestamp: %f", eventScratch.note, eventScratch.timeStamp); // Added by sasha... The notes are correct + + } + if (events) [noteEvents addObjectsFromArray:events]; + } + + [noteEvents filterUsingPredicate:notePredicate]; + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]; + [noteEvents sortUsingDescriptors:@[sortDescriptor]]; + + NSMutableArray *noteCommands = [NSMutableArray array]; + for (MIKMIDINoteEvent *event in noteEvents) { + [noteCommands addObjectsFromArray:[MIKMIDICommand commandsFromNoteEvent:event clock:self.midiClock]]; + } + + self.allNoteMessages = noteCommands; + NSLog(@"noteCommands: %lu", (unsigned long)noteCommands.count); + +} + +- (void)export +{ + [self storeAllEvents]; + // dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // render settings + + CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); + + AudioTimeStamp timestamp = (AudioTimeStamp) { 0, kAudioTimeStampSampleTimeValid }; + + UInt32 channels = LPCMASBD().mChannelsPerFrame; + AudioBufferList bufferList; + bufferList.mNumberBuffers = 1; + bufferList.mBuffers[0].mNumberChannels = channels; + bufferList.mBuffers[0].mDataByteSize = RENDER_FRAMES * channels * sizeof (float); + + AudioUnitRenderActionFlags flags = 0; + OSStatus err = 0; + + do { + // output unit provides its own buffer + bufferList.mBuffers[0].mData = NULL; + + err = AudioUnitRender(self.outputUnit, &flags, ×tamp, 0, RENDER_FRAMES, &bufferList); + if (err) { + NSLog(@"Error rendering: %i", err); + break; + } + + // update state + timestamp.mSampleTime += RENDER_FRAMES; + + } while (!_doneRendering); + + AUGraphStop(self.graph); + ExtAudioFileDispose(self.cafFile); + self.cafFile = NULL; + + NSLog(@"rendering took %f (%fs file)", CFAbsoluteTimeGetCurrent() - startTime, [self.midiClock midiTimeStampForMusicTimeStamp:self.maxTrackLength] * MIKMIDIClockSecondsPerMIDITimeStamp()); + + // }); +} + +#pragma mark - Callbacks + +OSStatus instrumentRenderCallback(void *info, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + @autoreleasepool { + MIKOfflineMIDISynthesizer *self = (__bridge MIKOfflineMIDISynthesizer *)info; + OSStatus error = noErr; + + if ( (*ioActionFlags & kAudioUnitRenderAction_PreRender) == 0 ) return noErr; + + NSTimeInterval startTimeInterval = inTimeStamp->mSampleTime / 44100.0; + NSTimeInterval duration = inNumberFrames / 44100.0; + + static mach_timebase_info_data_t timebaseInfo = {0,0}; + if (timebaseInfo.denom == 0) mach_timebase_info(&timebaseInfo); + uint64_t midiStartTime = startTimeInterval * NSEC_PER_SEC * timebaseInfo.denom / timebaseInfo.numer; + uint64_t midiEndTime = midiStartTime + duration * NSEC_PER_SEC; + + NSMutableArray *commands = [NSMutableArray array]; + for (MIKMIDICommand *command in self.allNoteMessages) { + if (midiStartTime > command.midiTimestamp) continue; + if (midiEndTime <= command.midiTimestamp) break; + + [commands addObject:command]; + } + + if ([self.midiClock musicTimeStampForMIDITimeStamp:midiEndTime] >= self.maxTrackLength) + self->_doneRendering = YES; + + for (MIKMIDINoteOnCommand *command in commands) { + NSLog(@"channel: %i status: %x (%@)", command.channel, (unsigned int)command.statusByte, command.data); + error = MusicDeviceMIDIEvent(self.instrumentUnit, command.statusByte, command.dataByte1, command.dataByte2, 0); + if (error) return error; + } + } + return noErr; +} + +#pragma mark - Properties + +- (void)setTracks:(NSArray *)tracks +{ + if (tracks != _tracks) { + free(_rawTracks); + _tracks = tracks; + + _numTracks = [_tracks count]; + _rawTracks = (MIKMIDITrack *__unsafe_unretained *)malloc(sizeof(MIKMIDITrack *) * _numTracks); + [_tracks getObjects:_rawTracks]; + } +} + +- (void)setTempo:(Float64)tempo +{ + if (tempo != _tempo) { + _tempo = tempo; + [self.midiClock syncMusicTimeStamp:0 withMIDITimeStamp:0 tempo:_tempo]; + } +} + +@end