TestsTested | ✗ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Jun 2017 |
Maintained by Ali Baaba.
Objective-C/Swift SDK to interface with Allihoopa from macOS.
If you're working on an iOS app, check out our iOS SDK.
There are multiple ways of installing the Allihoopa SDK depending on how your setup looks like.
If you use CocoaPods, you can simply add this SDK to your Podfile
:
target 'TargetName' do
pod 'Allihoopa-macOS', '~> 1.2.1'
end
And then running the following command to download the dependency:
pod install
After this, you run carthage
to build the framework, and then drag the
resulting Allihoopa.framework
from the Carthage/Build
folder into your
project.
Alternatively, you can simply download the latest built framework from the Releases tab.
If you want, you can include the Allihoopa-macOS
project as a sub-project
to your application and build the the framework as a dependency. In this case,
you will need to include
AllihoopaCore-ObjC too as a
dependency since this project share a lot of code and functionality with the
iOS SDK.
If you use one of the methods described above, this dependency is managed automatically for you.
You need to add a URL scheme to your app's Info.plist
: ah-{APP_IDENTIFIER}
,
e.g. ah-figure
if your application identifier is figure
. You will receive
your application identifier and API key when your register your application with
Allihoopa. If you want to get on board, please send an email to
[email protected].
Look in the SDKExample folder for instructions how to work on this SDK.
Generated API documentation for the latest release can be found on http://cocoadocs.org/docsets/Allihoopa-macOS.
You need a class, e.g. your app delegate, to implement the
AHAAllihoopaSDKDelegate
to support the "open in" feature. See below how to
implement this feature.
import Allihoopa
// In your UIApplicationDelegate implementation
func applicationDidFinishLaunching(_ aNotification: Notification) {
AHAAllihoopaSDK.shared().setup([
.applicationIdentifier: "your-application-identifier",
.apiKey: "your-api-key",
.delegate: self,
])
}
#import <Allihoopa/Allihoopa.h>
// In your UIApplicationDelegate implementation
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
[[AHAAllihoopaSDK sharedInstance] setupWithConfiguration:@{
AHAConfigKeyApplicationIdentifier: @"your-application-identifier",
AHAConfigKeyAPIKey: @"your-api-key",
AHAConfigKeySDKDelegate: self,
}];
}
setupWithConfiguration:
must be called before any other API calls can be
made. It will throw an exception if the SDK is improperly setup: if the
credentials are missing or if you've not set up the URL scheme properly. For
more information, see the "Steting up the SDK" heading above.
The configuration dictionary supports the following keys - names in Objective-C vs. Swift:
AHAConfigKeyApplicationIdentifier
/.applicationIdentifier
: required string
containing the application identifier provided by Allihoopa.AHAConfigKeyAPIKey
/.apiKey
: required string containing the app's API key.AHAConfigKeySDKDelegate
/.sdkDelegate
: optional instance used to notify
the application when a user tries to import a piece into this app. If provided,
the instance must conform to the AHAAllihoopaSDKDelegate
protocol.AHAConfigKeyFacebookAppID
/.facebookAppID
: optional string containing the
Facebook App ID of the application. This will enable secondary social sharing
through the Accounts and Social frameworks built into iOS.AHAConfigKeyRegisterURLEventHandler
/.registerURLEventHandler
: optional
boolean indicating whether the SDK should register its own URL opener handler.
For convenience, this defaults to true
and thus must be set to false if
you want your own URL event handler. If you set this to false
, you must
call -handleOpenURL:(NSURL*)
/handleOpen(url)
in your own event handler.let piece = try! AHADropPieceData(
defaultTitle: "Default title", // The default title of the piece
lengthMicroseconds: 40000000, // Length of the piece, in microseconds
tempo: nil,
loopMarkers: nil,
timeSignature: nil,
basedOn: [])
let vc = AHAAllihoopaSDK.shared().dropViewController(forPiece: piece, delegate: self)
self.presentViewControllerAsSheet(vc)
extension ViewController : AHADropDelegate {
// The drop view controller will ask your application for audio data.
// You should perform work in the background and call the completion
// handler on the main queue. If you already have the data available,
// you can just call the completion handler directly.
//
// This method *must* call completion with a data bundle for the drop to
// succeed. If it doesn't, an error screen will be shown.
func renderMixStem(forPiece piece: AHADropPieceData, completion: @escaping (AHAAudioDataBundle?, Error?) -> Void) {
DispatchQueue.global().async {
// Render Wave data into an NSData object
let bundle = AHAAudioDataBundle(format: .wave, data: data)
DispatchQueue.main.async {
completion(bundle, nil)
}
}
}
// You are responsible for dismissing the drop view controller in a
// suitable way, matching how you presented it.
func dropViewController(_ sender: NSViewController, willClose piece: AHADropPieceData, successfulDrop: Bool) {
self.dismissViewController(sender)
}
}
NSError* error;
AHADropPieceData* piece = [[AHADropPieceData alloc] initWithDefaultTitle:@"Default title"
lengthMicroseconds:40000000
tempo:nil
loopMarkers:nil
timeSignature:nil
basedOnPieceIDs:@[]
error:&error];
if (piece) {
UIViewController* vc = [[AHAAllihoopaSDK sharedInstance] dropViewControllerForPiece:piece delegate:self];
[self presentViewControllerAsSheet:vc];
}
@implementation ViewController
- (void)renderMixStemForPiece:(AHADropPieceData *)piece
completion:(void (^)(AHAAudioDataBundle * _Nullable, NSError * _Nullable))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Render wave data into an NSData object
AHAAudioDataBundle* bundle = [[AHAAudioDataBundle alloc] initWithFormat:AHAAudioFormatWave data:data];
dispatch_async(dispatch_get_main_queue(), ^{
completion(bundle, nil);
});
});
}
- (void)dropViewController:(NSViewController*)sender
forPieceWillClose:(AHADropPieceData*)piece
afterSuccessfulDrop:(BOOL)successfulDrop {
[self dismissViewController:sender];
}
@end
dropViewController
creates a view controller responsible for dropping the
piece you supplied with the help of the delegate object. If the user is not
logged in, the SDK will open the login dialog in the user's default browser and
proceed after the user logs in. When the user wants to close the drop view,
the delegate will be notified and must dismiss the view controller.
A piece can contain different kinds of metadata. The above example shows off the minimum amount of data we require: a default title, the length of the piece audio data, and a delegate method that renders the audio into a known format.
The AHADropPieceData
performs basic validation on the inputs: it will return a
NSError
containing information on what went wrong. Errors can include things
like the loop markers being inverted or the length outside of reasonable limits.
These are usually programmer errors - not runtime errors that can be handled
in a meaningful way.
If your application knows about it, it can supply a lot more metadata to
AHADropPieceData
more information, as well as implementing more methods on
AHADropDelegate
than shown above. Here's a complete example showing all data
you can set:
let piece = try! AHADropPieceData(
defaultTitle: "Default title",
lengthMicroseconds: 100000000,
// The fixed tempo in BPM. Allowed range: 1 - 999.999 BPM
tempo: AHAFixedTempo(fixedTempo: 128),
// If the piece is a loop, you can provide the loop markers.
loopMarkers: AHALoopMarkers(startMicroseconds: 0, endMicroseconds: 500000),
// If the time signature is available and fixed, you can provide a time
// signature object.
//
// The upper numeral can range from 1 to 16, incnlusive. The lower numeral
// must be one of 2, 4, 8, and 16.
timeSignature: AHATimeSignature(upper: 8, lower: 4),
// If the piece is based on other pieces, provide a list of the IDs of those
// pieces here.
basedOn: [],
// If the piece has a known tonality, provide the scale and root here.
// Other values are AHATonality.unknown() (default if omitted), and
// AHATonality.atonal() for pieces that contain audio that doesn't have a
// tonality, e.g. drum loops.
tonality: AHATonality(tonalScale: AHAGetMajorScale(4), root: 4)
)
extension ViewController : AHADropDelegate {
// The "mix stem" is the audio data that should be used to place the piece
// on a timeline. Call the completion handler with a data bundle instance
// on the main queue when data is available.
//
// The mix stem is mandatory.
func renderMixStem(forPiece piece: AHADropPieceData, completion: @escaping (AHAAudioDataBundle?, Error?) -> Void) {
}
// You can supply a default cover image that the user can upload or change.
// Call the completion handler with an image of size 640x640 px, or nil.
func renderCoverImage(forPiece piece: AHADropPieceData, completion: @escaping (UIImage?) -> Void) {
completion(nil)
}
// If the audio to be placed on the timeline is different from what users
// should listen to, use this delegate method to provide a "preview"
// audio bundle.
//
// For example, if you're providing a short loop you can supply only the
// loop data in a lossless format as the mix stem, and then a longer track
// containing a few loops with fade in/out in a lossy format in the
// preview audio.
//
// The preview audio is what's going to be played on the website.
//
// If no preview audio is provided, the mix stem will be used instead. This
// replacement is done server-side, the mix stem data will only be uploaded
// once from the client.
func renderPreviewAudio(forPiece piece: AHADropPieceData, completion: @escaping (AHAAudioDataBundle?, Error?) -> Void) {
}
// You can include an application specific attachment, which can be of
// arbitrary file type referred by a registered MIME type
func renderAttachment(forPiece piece: AHADropPieceData, completion: @escaping (AHAAttachmentBundle?, Error?) -> Void) {
}
}
For more information on tonality and other fields, please see the iOS SDK documentation.
When a user picks "Open in [your app]" on the website, the SDK will pick up the
request, fetch piece metadata, and call the openPieceFromAllihoopa:error:
with
the piece the user wanted to open. The AHAPiece
instance has methods for
downloading the audio data in the specified format, which you can use to import
the audio into the current document, or save it for later. AHAPiece
also
contain metadata, similar to AHADropPiece
.
func openPiece(fromAllihoopa piece: AHAPiece?, error: Error?) {
if let piece = piece {
// The user wanted to open a piece
// Download the mix stem audio in Ogg format. You can also use .wave
piece.downloadMixStem(format: .oggVorbis, completion: { (data, error) in
if let data = data {
// Data downloaded successfully
}
})
}
else {
// Handle the error
//
// This should not *usually* happen, but if the piece was removed after
// opening, or if there were some connection issues we can end up here
}
}
- (void)openPieceFromAllihoopa:(AHAPiece*)piece error:(NSError*)error {
if (piece != nil) {
// The user wanted to open a piece
[piece downloadMixStemWithFormat:AHAAudioFormatOggVorbis completion:^(NSData* data, NSError* error) {
if (data != nil) {
// Data downloaded successfully
}
}];
}
else {
// Handle the error
}
}
You can also include an application specific attachment when dropping a piece and similarly download that attachment when importing a piece. An attachment can be of arbitrary file type and is referred by a MIME type string. The MIME type must be registered to the Allihoopa service in advance, i.e. if your application needs to use attachments, register them by sending an email to [email protected].
Application specific attachment can be included in the drop delegate object.
extension ViewController : AHADropDelegate {
// ...
// You can include an application specific attachment along other asset files
func renderAttachment(forPiece piece: AHADropPieceData, completion: @escaping (AHAAttachmentBundle?, Error?) -> Void) {
DispatchQueue.global().async {
do {
let data = try Data(contentsOf: Bundle.main.url(forResource: "figure_song", withExtension: "xml")!)
let bundle = AHAAttachmentBundle(mimeType: "application/figure", data: data)
DispatchQueue.main.async {
completion(bundle, nil)
}
}
catch let error {
DispatchQueue.main.async {
completion(nil, error)
}
}
}
}
}
And when importing a piece, you can download the attachment using the same MIME type.
func openPiece(fromAllihoopa piece: AHAPiece?, error: Error?) {
if let piece = piece {
// ...
// Download an attachment based on specific MIME type
piece.downloadAttachment(mimeType: "application/figure", completion: { (data, error) in
if let data = data {
// Data downloaded successfully
}
})
}
}