{"id":16702336,"url":"https://github.com/jagcesar/allihoopa-ios","last_synced_at":"2025-06-15T07:37:52.489Z","repository":{"id":145181248,"uuid":"92950951","full_name":"JagCesar/Allihoopa-iOS","owner":"JagCesar","description":"Allihoopa SDK for iOS","archived":false,"fork":false,"pushed_at":"2017-07-11T13:45:07.000Z","size":2122,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-14T06:44:05.326Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Objective-C","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JagCesar.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-31T13:53:02.000Z","updated_at":"2019-05-27T07:26:49.000Z","dependencies_parsed_at":null,"dependency_job_id":"3f865d5f-266d-45ec-ae76-5726e5f32ef0","html_url":"https://github.com/JagCesar/Allihoopa-iOS","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/JagCesar/Allihoopa-iOS","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JagCesar%2FAllihoopa-iOS","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JagCesar%2FAllihoopa-iOS/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JagCesar%2FAllihoopa-iOS/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JagCesar%2FAllihoopa-iOS/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JagCesar","download_url":"https://codeload.github.com/JagCesar/Allihoopa-iOS/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JagCesar%2FAllihoopa-iOS/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259940949,"owners_count":22935290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-12T18:47:35.354Z","updated_at":"2025-06-15T07:37:52.445Z","avatar_url":"https://github.com/JagCesar.png","language":"Objective-C","funding_links":[],"categories":[],"sub_categories":[],"readme":"Allihoopa SDK for iOS\n=====================\n\n[![Travis](https://travis-ci.org/allihoopa/Allihoopa-iOS.svg?branch=master)](https://travis-ci.org/allihoopa/Allihoopa-iOS)\n[![CocoaPods](https://cocoapod-badges.herokuapp.com/v/Allihoopa/badge.svg)](https://cocoapods.org/pods/Allihoopa)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg)](https://github.com/Carthage/Carthage)\n\n----\n\n\u003e Objective-C/Swift SDK to interface with [Allihoopa].\n\n# Installation\n\nThere are multiple ways of installing the Allihoopa SDK depending on how your\nsetup looks like.\n\nIf you use [CocoaPods], you can simply add this SDK to your `Podfile`:\n\n```ruby\ntarget 'TargetName' do\n  pod 'Allihoopa', '~\u003e 1.3.0'\nend\n```\n\nAnd then running the following command to download the dependency:\n\n```bash\npod install\n```\n\nIf you use [Carthage], you instead add this SDK to your `Cartfile`:\n\n```\ngithub \"Allihoopa/Allihoopa-iOS\" ~\u003e 1.3.0\n```\n\nAfter this, you run `carthage update` to build the framework, and then drag the\nresulting `Allihoopa.framework` _and_ `AllihoopaCore.framework` from the\n`Carthage/Build` folder into the \"Embedded binaries\" of your target.\n\n\n### Manual build\n\nIf you want, you can include the `Allihoopa-macOS` project as a sub-project\nto your application and build the the framework as a dependency. In this case,\nyou will need to include\n[AllihoopaCore-ObjC](https://github.com/allihoopa/AllihoopaCore-ObjC) too as a\ndependency since this project share a lot of code and functionality with the\nmacOS SDK.\n\nIf you use one of the methods described above, this dependency is managed\nautomatically for you.\n\n## Configuration\n\nYou need to add a URL scheme to your app's `Info.plist`: `ah-{APP_IDENTIFIER}`,\ne.g. `ah-figure` if your application identifier is `figure`. You will receive\nyour application identifier and API key when your register your application with\nAllihoopa. If you want to get on board, please send an email to\n[developer@allihoopa.com](mailto:developer@allihoopa.com).\n\nFor the drop flow to work, you will _also_ need to add the following keys to\nyour `Info.plist`:\n\n```plist\n\u003ckey\u003eNSCameraUsageDescription\u003c/key\u003e\n\u003cstring\u003e$(PRODUCT_NAME) wants to access your camera\u003c/string\u003e\n\u003ckey\u003eNSPhotoLibraryUsageDescription\u003c/key\u003e\n\u003cstring\u003e$(PRODUCT_NAME) wants to access your photo library\u003c/string\u003e\n```\n\niOS 10 started enforcing these keys - the app will crash when the user taps the\nedit cover image button unless these are specified. Read more about this in the\n[Technical Q\u0026A QA1937](Technical Q\u0026A QA1937: Resolving the Privacy-Sensitive\nData App Rejection).\n\n## Development setup\n\nLook in the [SDKExample] folder for instructions how to work on this SDK.\n\n\n# API documentation\n\nGenerated API documentation for the latest release can be found on\n[http://cocoadocs.org/docsets/Allihoopa](http://cocoadocs.org/docsets/Allihoopa).\n\n## Setting up the SDK\n\nYou need a class, e.g. your app delegate, to implement the\n`AHAAllihoopaSDKDelegate` to support the \"open in\" feature. See below how to\nimplement this feature.\n\n```swift\nimport Allihoopa\n\n// In your UIApplicationDelegate implementation\nfunc applicationDidFinishLaunching(_ application: UIApplication) {\n    AHAAllihoopaSDK.shared().setup([\n        .applicationIdentifier: \"your-application-identifier\",\n        .apiKey: \"your-api-key\",\n        .delegate: self,\n    ])\n}\n\nfunc application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -\u003e Bool {\n    if AHAAllihoopaSDK.shared().handleOpen(url) {\n        return true\n    }\n\n    // Call other SDKs' URL handling methods\n\n    return false\n}\n```\n\n```objective-c\n#import \u003cAllihoopa/Allihoopa.h\u003e\n\n// In your UIApplicationDelegate implementation\n- (void)applicationDidFinishLaunching:(UIApplication*)application {\n    [[AHAAllihoopaSDK sharedInstance] setupWithConfiguration:@{\n        AHAConfigKeyApplicationIdentifier: @\"your-application-identifier\",\n        AHAConfigKeyAPIKey: @\"your-api-key\",\n        AHAConfigKeySDKDelegate: self,\n    }];\n}\n\n- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary\u003cUIApplicationOpenURLOptionsKey,id\u003e *)options {\n    if ([[AHAAllihoopaSDK sharedInstance] handleOpenURL:url]) {\n        return YES;\n    }\n\n    // Call other SDKs' URL handling methods\n\n    return NO;\n}\n```\n\n`setupWithConfiguration:` must be called *before* any other API calls can be\nmade. It will throw an exception if the SDK is improperly setup: if the\ncredentials are missing or if you've not set up the URL scheme properly. For\nmore information, see the \"Steting up the SDK\" heading above.\n\nThe configuration dictionary supports the following keys - names in Objective-C\nvs. Swift:\n\n* `AHAConfigKeyApplicationIdentifier`/`.applicationIdentifier`: required string\n  containing the application identifier provided by Allihoopa.\n* `AHAConfigKeyAPIKey`/`.apiKey`: required string containing the app's API key.\n* `AHAConfigKeySDKDelegate`/`.sdkDelegate`: optional instance used to notify\n  the application when a user tries to import a piece into this app. If provided,\n  the instance must conform to the `AHAAllihoopaSDKDelegate` protocol.\n* `AHAConfigKeyFacebookAppID`/`.facebookAppID`: optional string containing the\n  Facebook App ID of the application. This will enable secondary social sharing\n  through the Accounts and Social frameworks built into iOS.\n\n`handleOpenURL:` must be called inside the URL handler of your application. It\nwill only return true if it successfully handled the URL, making it possible to\nchain this call with other URL handlers.\n\n## Dropping pieces\n\n```swift\nlet piece = try! AHADropPieceData(\n    defaultTitle: \"Default title\", // The default title of the piece\n    lengthMicroseconds: 40000000, // Length of the piece, in microseconds\n    tempo: nil,\n    loopMarkers: nil,\n    timeSignature: nil,\n    basedOn: [])\n\nlet vc = AHAAllihoopaSDK.shared().dropViewController(forPiece: piece, delegate: self)\n\nself.present(vc, animated: true, completion: nil)\n```\n\n```swift\nextension ViewController : AHADropDelegate {\n    // The drop view controller will ask your application for audio data.\n    // You should perform work in the background and call the completion\n    // handler on the main queue. If you already have the data available,\n    // you can just call the completion handler directly.\n    //\n    // This method *must* call completion with a data bundle for the drop to\n    // succeed. If it doesn't, an error screen will be shown.\n    func renderMixStem(forPiece piece: AHADropPieceData, completion: @escaping (AHAAudioDataBundle?, Error?) -\u003e Void) {\n        DispatchQueue.global().async {\n            // Render Wave data into an NSData object\n            let bundle = AHAAudioDataBundle(format: .wave, data: data)\n            DispatchQueue.main.async {\n                completion(bundle, nil)\n            }\n        }\n    }\n}\n```\n\n---\n\n```objective-c\nNSError* error;\nAHADropPieceData* piece = [[AHADropPieceData alloc] initWithDefaultTitle:@\"Default title\"\n                                                      lengthMicroseconds:40000000\n                                                                   tempo:nil\n                                                             loopMarkers:nil\n                                                           timeSignature:nil\n                                                         basedOnPieceIDs:@[]\n                                                                   error:\u0026error];\nif (piece) {\n    UIViewController* vc = [[AHAAllihoopaSDK sharedInstance] dropViewControllerForPiece:piece delegate:self];\n    [self presentViewController:vc animated:YES completion:nil];\n}\n```\n\n```objective-c\n@implementation ViewController\n\n- (void)renderMixStemForPiece:(AHADropPieceData *)piece\n                   completion:(void (^)(AHAAudioDataBundle * _Nullable, NSError * _Nullable))completion {\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        // Render wave data into an NSData object\n        AHAAudioDataBundle* bundle = [[AHAAudioDataBundle alloc] initWithFormat:AHAAudioFormatWave data:data];\n\n        dispatch_async(dispatch_get_main_queue(), ^{\n            completion(bundle, nil);\n        });\n    });\n}\n\n@end\n```\n\n\n`dropViewController` creates a view controller responsible for dropping the\npiece you supplied with the help of the delegate object. If the user is not\nlogged in, a log in dialog is presented first. When the user is finished, or if\nthey cancel, the view controller will dismiss itself and inform the delegate.\n\nA piece can contain different kinds of metadata. The above example shows off the\nminimum amount of data we require: a default title, the length of the piece\naudio data, and a delegate method that renders the audio into a known format.\n\nThe `AHADropPieceData` performs basic validation on the inputs: it will return a\n`NSError` containing information on what went wrong. Errors can include things\nlike the loop markers being inverted or the length outside of reasonable limits.\nThese are *usually* programmer errors - not runtime errors that can be handled\nin a meaningful way.\n\nIf your application knows about it, it can supply a lot more metadata to\n`AHADropPieceData` more information, as well as implementing more methods on\n`AHADropDelegate` than shown above. Here's a complete example showing all data\nyou can set:\n\n```swift\nlet piece = try! AHADropPieceData(\n    defaultTitle: \"Default title\",\n    lengthMicroseconds: 100000000,\n    // The fixed tempo in BPM. Allowed range: 1 - 999.999 BPM\n    tempo: AHAFixedTempo(fixedTempo: 128),\n\n    // If the piece is a loop, you can provide the loop markers.\n    loopMarkers: AHALoopMarkers(startMicroseconds: 0, endMicroseconds: 500000),\n\n    // If the time signature is available and fixed, you can provide a time\n    // signature object.\n    //\n    // The upper numeral can range from 1 to 16, incnlusive. The lower numeral\n    // must be one of 2, 4, 8, and 16.\n    timeSignature: AHATimeSignature(upper: 8, lower: 4),\n\n    // If the piece is based on other pieces, provide a list of the IDs of those\n    // pieces here.\n    basedOn: [],\n\n    // If the piece has a known tonality, provide the scale and root here.\n    // Other values are AHATonality.unknown() (default if omitted), and\n    // AHATonality.atonal() for pieces that contain audio that doesn't have a\n    // tonality, e.g. drum loops.\n    tonality: AHATonality(tonalScale: AHAGetMajorScale(4), root: 4)\n)\n```\n\n```swift\nextension ViewController : AHADropDelegate {\n    // The \"mix stem\" is the audio data that should be used to place the piece\n    // on a timeline. Call the completion handler with a data bundle instance\n    // on the main queue when data is available.\n    //\n    // The mix stem is mandatory.\n    func renderMixStem(forPiece piece: AHADropPieceData, completion: @escaping (AHAAudioDataBundle?, Error?) -\u003e Void) {\n\t\t...\n    }\n\n    // You can supply a default cover image that the user can upload or change.\n    // Call the completion handler with an image of size 640x640 px, or nil.\n    func renderCoverImage(forPiece piece: AHADropPieceData, completion: @escaping (UIImage?) -\u003e Void) {\n\t\t...\n    }\n\n    // You can supply a file as an attachment to the piece. The file can be of\n    // any format. This file can be read back by clients when fetching a piece.\n    // Attachment max size is 30mb.\n    // Call the completion handler with a data bundle instance on the main queue\n    // when data is available.\n    renderAttachment(forPiece piece: AHADropPieceData, completion: @escaping (AHAAttachmentBundle?, Error?) -\u003e Void) {\n        ...\n    }\n\n    // If the audio to be placed on the timeline is different from what users\n    // should listen to, use this delegate method to provide a \"preview\"\n    // audio bundle.\n    //\n    // For example, if you're providing a short loop you can supply only the\n    // loop data in a lossless format as the mix stem, and then a longer track\n    // containing a few loops with fade in/out in a lossy format in the\n    // preview audio.\n    //\n    // The preview audio is what's going to be played on the website.\n    //\n    // If no preview audio is provided, the mix stem will be used instead. This\n    // replacement is done server-side, the mix stem data will only be uploaded\n    // once from the client.\n    func renderPreviewAudio(forPiece piece: AHADropPieceData, completion: @escaping (AHAAudioDataBundle?, Error?) -\u003e Void) {\n    }\n\n    // You can implement this method to get notified when the user either\n    // cancels or completes a drop.\n    func dropViewController(forPieceWillClose piece: AHADropPieceData, afterSuccessfulDrop successfulDrop: Bool) {\n    }\n}\n```\n\n### Dropping from `UIActivityViewController`\n\n```swift\n@IBAction func share(_ sender: UIView?) {\n    let piece = try! AHADropPieceData(\n        defaultTitle: \"Default title\",\n        lengthMicroseconds: 100000000,\n        tempo: nil,\n        loopMarkers: nil,\n        timeSignature: nil,\n        basedOn: [])\n\n    let vc = UIActivityViewController(\n        activityItems: [],\n        applicationActivities: [AHAAllihoopaSDK.shared().activity(forPiece: piece, delegate: self)])\n    vc.modalPresentationStyle = .popover\n\n    self.present(vc, animated: true, completion: nil)\n\n    let pop = vc.popoverPresentationController!\n    pop.sourceView = sender\n    pop.sourceRect = sender!.bounds\n}\n```\n\nIf you are already using `UIActivityViewController` to present a share popover\nto your users, you can use `activityForPiece:delegate:` to create a\n`UIActivity`. It has the same interface as for creating the drop view controller\nabove, and uses the same delegate protocol.\n\n### Some notes on tonality\n\n#### Representation\n\nIn Allihoopa, a piece's tonality can be in one of three different states:\n\n* _Unknown_, used when the application can't determine the tonal content of the\n  piece when dropping.\n* _Atonal_, used when the application knows that the piece does not contain\n  tonal content. This is true in e.g. drum loops.\n* _Tonal_, used when the application knows about the tonal content of the piece.\n\nFor pieces with known tonal content, tonality is represented by two values: a\n_scale_ and a _root_. The scale consists of twelve boolean values each\nrepresenting whether that pitch class is a member of the tonality. The root\nindicates on which index in the array the tonality begins.\n\nThis can be a bit confusing, but a simple example might help. C major is\nrepresented by \"all white keys\" in the scale:\n\n```\n        C  C#   D  D#   E   F  F#   G  G#   A  A#   B\nScale: [1,  0,  1,  0,  1,  1,  0,  1,  0,  1,  0,  1]\nRoot:   0\n```\n\nIf we instead look at A minor; the parallel minor scale of C major, we can see\nthat the scale array is the same, but the root has been shifted:\n\n```\n        C  C#   D  D#   E   F  F#   G  G#   A  A#   B\nScale: [1,  0,  1,  0,  1,  1,  0,  1,  0,  1,  0,  1]\nRoot:                                       9\n```\n\n#### Application\n\nHow to deal with the tonality metadata of a piece is very dependent on the type\nof app. Generally, if your app contains a tonality field in its document format\nit _should_ be used both when importing and dropping. For a more in-depth\nintegration you can look at [Take], our vocal recording app:\n\n* If a known and defined tonality is available, it is used to set up the auto-\n  tuning system to only tune to pitches that are in the song's key.\n* The user can adjust which key they want to sing in. The imported piece is\n  pitch shifted to accomodate for the new key, _unless_ it's an atonal piece in\n  which case pitch shifting makes no sense.\n\n#### Display\n\nSince there are 12 * 2^12 possible values for the tonality field, we make no\nattempt at displaying the _name_ of the tonality correctly. The Allihoopa\nwebsite is currently very naive: it completely ignores the root value and shows\nthe name of the matching major scale. If no matching major scale can be found,\nthe website will instead display \"Exotic\". Both examples above would be labeled\n\"C\".\n\n## Importing pieces\n\nWhen a user picks \"Open in [your app]\" on the website, the SDK will pick up the\nrequest, fetch piece metadata, and call the `openPieceFromAllihoopa:error:` with\nthe piece the user wanted to open. The `AHAPiece` instance has methods for\ndownloading the audio data in the specified format, which you can use to import\nthe audio into the current document, or save it for later. `AHAPiece` also\ncontain metadata, similar to `AHADropPiece`.\n\n```swift\nfunc openPiece(fromAllihoopa piece: AHAPiece?, error: Error?) {\n    if let piece = piece {\n        // The user wanted to open a piece\n\n        // Download the mix stem audio in AAC/M4A format. You can also use .wave\n        // and .oggVorbis\n        piece.downloadMixStem(format: .AACM4A, completion: { (data, error) in\n            if let data = data {\n                // Data downloaded successfully\n            }\n        })\n    }\n    else {\n        // Handle the error\n        //\n        // This should not *usually* happen, but if the piece was removed after\n        // opening, or if there were some connection issues we can end up here\n    }\n}\n```\n\n```objective-c\n- (void)openPieceFromAllihoopa:(AHAPiece*)piece error:(NSError*)error {\n    if (piece != nil) {\n        // The user wanted to open a piece\n        [piece downloadMixStemWithFormat:AHAAudioFormatAACM4A completion:^(NSData* data, NSError* error) {\n            if (data != nil) {\n                // Data downloaded successfully\n            }\n        }];\n    }\n    else {\n        // Handle the error\n    }\n}\n```\n\n\n[Allihoopa]: https://allihoopa.com\n[CocoaPods]: https://cocoapods.org\n[Carthage]: https://github.com/carthage/carthage\n[Releases tab]: https://github.com/allihoopa/Allihoopa-iOS/releases\n[SDKExample]: SDKExample\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjagcesar%2Fallihoopa-ios","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjagcesar%2Fallihoopa-ios","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjagcesar%2Fallihoopa-ios/lists"}