Loading an audio sample from the documents folder - c4

I'm trying to have an app play audio files added via itunes file-sharing. I've managed the app to retrieve the app's sandbox folder's content, but I'm not able to load such files into a C4Sample by specifying the complete path.
NSString *documentsFolder;
NSString *clickAudioPath;
C4Sample *clicksample;
-(void)setup {
// Retrieve the app's Documents folder
documentsFolder = [self applicationDocumentsDirectory];
clickAudioPath = [NSString stringWithFormat:#"%#/click.mp3", documentsFolder];
// Add test click audio
clicksample = [C4Sample sampleNamed:clickAudioPath];
[clicksample prepareToPlay];
[clicksample play];
}
// Get Documents folder
- (NSString *) applicationDocumentsDirectory{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
The above code doesn't play any sound, although I've doubled checked that clicksample actually refers to an existing file. How can I specify a complete path instead of just a name to load the audio?

Add a new method as below.
-(id) initWithURL:(NSURL *) soundFileURL
{
self = [super init];
if(self != nil) {
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
self.enableRate = YES;
self.player.delegate = self;
[self setup];
}
return self;
}

Related

Repeating Audio in WatchKit (AVAudioPlayer?)?

I am wanting to loop a local audio file in my Apple Watch App. Currently I am using AVAudioPlayerNode and AVAudioEngine which works well but I cannot figure out how to loop the sound.
I noticed that I can use AVAudioPlayer, which has the handy "numberOfLoops" but, for some reason AVAudioPlayer is not working on the watch. I have no idea why.
Here is my current code to play a sound:
_audioPlayer = [[AVAudioPlayerNode alloc] init];
_audioEngine = [[AVAudioEngine alloc] init];
[_audioEngine attachNode:_audioPlayer];
AVAudioFormat *stereoFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:2];
[_audioEngine connect:_audioPlayer to:_audioEngine.mainMixerNode format:stereoFormat];
if (!_audioEngine.isRunning) {
NSError* error;
[_audioEngine startAndReturnError:&error];
}
NSError *error;
NSBundle* appBundle = [NSBundle mainBundle];
NSURL *url = [NSURL fileURLWithPath:[appBundle pathForResource:#"FILE_NAME" ofType:#"mp3"]];
AVAudioFile *asset = [[AVAudioFile alloc] initForReading:url error:&error];
[_audioPlayer scheduleFile:asset atTime:nil completionHandler:nil];
[_audioPlayer play];
Here is the code i've tried to use for AVAudioPlayer, but does not work:
NSError *audioError;
AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"FILE_NAME" ofType:#"mp3"]] error:&audioError];
player.numberOfLoops = MAXFLOAT;
player.delegate = self;
[player play];
I am using WatchKit 5.0(+).
You can loop your AVAudioFile by recursively scheduling it:
__block __weak void (^weakSheduleFile)(void);
void (^scheduleFile)(void);
weakSheduleFile = scheduleFile = ^{ [self->_audioPlayer scheduleFile:asset atTime:nil completionHandler:weakSheduleFile]; };
scheduleFile();
I'm not sure if this will be a seamless loop. If it's not, you can try always having two files scheduled:
scheduleFile();
scheduleFile();
If your audio file fits into memory, you could schedule playback as an AVAudioBuffer with the AVAudioPlayerNodeBufferLoops option (N.B. only tested on simulator!):
AVAudioFormat *outputFormat = [_audioPlayer outputFormatForBus:0];
__block AVAudioPCMBuffer *srcBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:asset.processingFormat frameCapacity:(AVAudioFrameCount)asset.length];
if (![asset readIntoBuffer:srcBuffer error:&error]) {
NSLog(#"Read error: %#", error);
abort();
}
AVAudioPCMBuffer *dstBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)asset.length];
AVAudioConverter *converter = [[AVAudioConverter alloc] initFromFormat:srcBuffer.format toFormat:dstBuffer.format];
AVAudioConverterOutputStatus status = [converter convertToBuffer:dstBuffer error:&error withInputFromBlock:^AVAudioBuffer * _Nullable(AVAudioPacketCount inNumberOfPackets, AVAudioConverterInputStatus * _Nonnull outStatus) {
if (srcBuffer) {
AVAudioBuffer *result = srcBuffer;
srcBuffer = NULL;
*outStatus = AVAudioConverterInputStatus_HaveData;
return result;
} else {
*outStatus = AVAudioConverterInputStatus_EndOfStream;
return NULL;
}
}];
assert(status != AVAudioConverterOutputStatus_Error);
[_audioPlayer scheduleBuffer:dstBuffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
[_audioPlayer play];

How to update Core Data and maintain user-saved data?

I have an app on the App Store that uses Core Data and I want to make an update. I use a preloaded sqlite file to create the persistentStore. There are two entities in the model, one of which holds user-saved data.
My issue: I want to update the existing Core Data with a new preloaded sqlite, but preserve the user-saved data. Is there a way to merge the new data of the preloaded file with the user-saved data?
The model remains the same; I haven't added any new entities or attributes, so I don't think a migration is appropriate (I could be wrong).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CreatureData.sqlite"];
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"CreatureData" ofType:#"sqlite"]];
NSLog(#"JUST LOADED COREDATA SQLITE");
NSError* err = nil;
if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
NSLog(#"Oops, couldn't copy preloaded data");
}
} else {
NSLog(#"Core Data SQLITE already exists");
}
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
//abort();
}
return _persistentStoreCoordinator;
}
EDIT:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CreatureData.sqlite"];
NSURL *secondStoreURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"CreatureData" ofType:#"sqlite"]];
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"CreatureData" ofType:#"sqlite"]];
NSLog(#"JUST LOADED COREDATA SQLITE");
NSError* err = nil;
if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
NSLog(#"Oops, couldn't copy preloaded data");
}
}
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES };
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:#"UserSavedData" URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:#"StaticTableData" URL:secondStoreURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
Apple recommends that you separate static and user data, in this case into 2 persistent stores. You could do one of the following:
First and preferable option, you use a separate store for the user data. You need to update the data model to accommodate two stores, so it will be a lightweight migration.
You would then extract the user data on the first startup from the existing sqlite store and store them to your new dynamic store. You can then delete the old sqlite store.
Second option is to keep the current setup. On first run, copy your new file with a different name, then extract the user data from the old store and copy it to the new store. Then delete the old store.

AVAudioPlayer sounds garbled on iPhone without headphones

I have 23 music files (10-20 seconds in length) that I want to play when the user selects them and presses the play button. It works find on my iPad and on the simulators, with or without earphones. But on the iPhone device without earphones, around 80% of the files sound garbled, the volume is low, and some are barely legible. I can plug the earphones in, and they sound fine. As soon as I unplug the earphones, the sounds are garbled again.
Since some of the files always play fine, I converted all the original mp3's that I was using into the AAC format with a sample rate of 44100. But the same files play fine, while most are distorted when playing on the iphone device.
I've tried several settings for AVAudioSession category: playback, playAndRecord, ambient. I've tried storing the files in core data and using initWithData instead of initWithContentsOfURL. Can anyone suggest what else I might try? Or how I might get clues into why I'm having this problem?
Here is my code. There are 3 relevant files. The ViewController that has a button to play a selected music file, an AVPlaybackSoundController that has all the audio logic, and a Singleton that has an array with the filenames.
My ViewController creates an AVPlaybackSoundController through the Nib.
IBOutlet AVPlaybackSoundController *avPlaybackSoundController;
#property (nonatomic, retain) AVPlaybackSoundController *avPlaybackSoundController;
- (IBAction) playButtonPressed:(id)the_sender
{
self.currentRhythmSampleIndex = arc4random() % [[CommonData sharedInstance].rhythmSampleArray count];
[self.avPlaybackSoundController playMusic:self.currentRhythmSampleIndex];
self.playBtn.hidden=YES;
self.pauseBtn.hidden=NO;
}
AVPlaybackSoundController.m
- (void) awakeFromNib
{
[self initAudioSession:AVAudioSessionCategoryPlayback];
[self initMusicPlayer];
}
- (void) initAudioSession:(NSString *const)audioSessionCategory
{
NSError* audio_session_error = nil;
BOOL is_success = YES;
is_success = [[AVAudioSession sharedInstance] setCategory:audioSessionCategory error:&audio_session_error];
if(!is_success || audio_session_error){
NSLog(#"Error setting Audio Session category: %#", [audio_session_error localizedDescription]);
}
[[AVAudioSession sharedInstance] setDelegate:self];
audio_session_error = nil;
is_success = [[AVAudioSession sharedInstance] setActive:YES error:&audio_session_error];
if(!is_success || audio_session_error){
NSLog(#"Error setting Audio Session active: %#", [audio_session_error
localizedDescription]);
}
}
- (void) initMusicPlayer
{
NSError* file_error = nil;
NSURL* file_url = [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle]
pathForResource:#"musicFile1" ofType:#"m4a"]
isDirectory:NO];
avMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:file_url
error:&file_error];
if(!file_url || file_error){
NSLog(#"Error loading music file: %#", [file_error
localizedDescription]);
}
self.avMusicPlayer.delegate = self;
self.avMusicPlayer.numberOfLoops = 0xFF;
[file_url release];
}
-(void) playMusic:(NSUInteger)rhythmSampleIdx
{
CommonData *glob = [CommonData sharedInstance];
if (rhythmSampleIdx > [glob.rhythmSampleArray count])
return; // bug if we hit here
// if we were already playing it, just resume
if ((self.avMusicPlayer) && (rhythmSampleIdx == self.currentRhythmSampleIndex)){
[self.avMusicPlayer play];
}
else{ // re-init player with new rhythm
if (self.avMusicPlayer){
[self.avMusicPlayer stop];
[self.avMusicPlayer setCurrentTime:0.0];
}
NSError* error = nil;
RhythmSample *rhythmSample = [glob.rhythmSampleArray objectAtIndex:rhythmSampleIdx];
NSString* rhythm_filename = [self getRhythmFileName:rhythmSampleIdx];
NSURL* file_url = [[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle]
pathForResource:rhythm_filename ofType:#"m4a"] isDirectory:NO]autorelease];
self.avMusicPlayer = [[[AVAudioPlayer alloc] initWithContentsOfURL:file_url error:&error]autorelease];
[self.avMusicPlayer prepareToPlay]; // shouldn't be needed since we are playing immediately
if(error){
NSLog(#"Error loading music file: %#", [error localizedDescription]);
}else{
self.avMusicPlayer.delegate = self;
self.avMusicPlayer.numberOfLoops = 0xFF; // repeat infinitely
self.currentRhythmSampleIndex = rhythmSampleIdx;
[self.avMusicPlayer play];
}
}
}
I finally decided to obtain the sound files from a different source. The new sound files play fine. I still have no theory as to why the old sound files would play with earphones, but not without them. I really dislike finding solutions without understanding why they work.

Core Data Fetch is Returning Nothing

In this app, I am using an sqlite database that was created by another app. I can query the database using the Firefox SQLite Manager and see that what I am searching for does exist in the database. I have reduced my query to something very simple, but still get nothing returned in my NSFetchedResultsController.
Here is my code:
- (NSFetchedResultsController*) frc {
if (!frc_) {
#autoreleasepool {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription * entity = [NSEntityDescription entityForName:#"INDEX" inManagedObjectContext:[ManagedObjectContext moc]];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:15];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"lemma = 'dog'"];
[NSFetchedResultsController deleteCacheWithName:nil];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"lemma" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:[ManagedObjectContext moc]
sectionNameKeyPath:#"lemma"
cacheName:nil];
aFetchedResultsController.delegate = (id<NSFetchedResultsControllerDelegate>)self;
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved Error %#, %#", error, [error userInfo]);
abort();
}
self.frc = aFetchedResultsController;
}
}
return frc_;
}
There is an entity called "INDEX" in the data model. To confirm that the entity has the lemma property, I show the content of its lemma property, getting this:
po [[entity propertiesByName] objectForKey:#"lemma"]
(id) $3 = 0x1104f740 (<NSAttributeDescription: 0x1104f740>), name lemma, isOptional 1, isTransient 0, entity INDEX, renamingIdentifier lemma, validation predicates (
), warnings (
), versionHashModifier (null)
userInfo {
}, attributeType 700 , attributeValueClassName NSString, defaultValue (null)
Examining the contents of the aFetchedResultsController immediately after the fetch (where it is assigned to self.frc) gives this:
po aFetchedResultsController
(NSFetchedResultsController *) $4 = 0x1105c5c0 <NSFetchedResultsController: 0x1105c5c0>
po [[aFetchedResultsController fetchedObjects] count]
(id) $1 = 0x00000000 <nil>
I suppose the problem here is something very basic, but I don't know what I am overlooking.
I found the answer at the Ray Wenderlich site. The problem is that the sqlite file must be moved from the application bundle to the application documents directory.
This is done by copying it form the bundle into the documents directory if it is not already there.
Here is the code for creating the persistent store coordinator:
- (NSPersistentStoreCoordinator *)pstore {
if (pstore_ != nil) {
return pstore_;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"words.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath: storePath];
// THIS IS THE KEY PIECE TO IMPORTING AN EXISTING SQLITE FILE:
// Put down default db if it doesn't already exist
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle]
pathForResource:#"words" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
pstore_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.mom];
if(![pstore_ addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:options error:&error]) {
// Error for store creation should be handled in here
}
return pstore_;
}

Loading database at start up slows down the app

i m not sure how to describe this as i m new with all the developing and i m really looking forward to an answer from you guys. I know you can be very busy but please try to HELP me!
Here it goes. I have an app that loads a very large database (although it only has 100 entries it contains HiRes images (100MB) ).
At start up a tableview presents the rows -records (using only 3 attributes from the database). However it seems that the WHOLE database (including the images) is loaded at start up!
IS THERE A WAY TO ONLY LOAD THE 3 attributes (something like "select") when the app starts and then when the user moves to didselectrowatindexpath load the rest of the record?
Because i have NO IDEA where to look or what to do i would appreciate some coding help!
here is the code i m using:
#pragma mark -
#pragma mark App support
- (NSFetchedResultsController *)resetFetchedResultsController:(NSPredicate *)predicate cached:(BOOL)cached
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Records" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *partDescriptor = [[NSSortDescriptor alloc] initWithKey:#"displayOrder" ascending:YES];
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: partDescriptor, nameDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
if (predicate != nil)
[fetchRequest setPredicate:predicate];
NSString *cacheName = nil;
if (cached)
cacheName = #"Root";
NSFetchedResultsController *aFetchedResultsController = [[[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:cacheName] autorelease];
aFetchedResultsController.delegate = self;
[fetchRequest release];
[partDescriptor release];
[nameDescriptor release];
[sortDescriptors release];
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error]) {
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return aFetchedResultsController;
}
- (void)showRecords:(Records *)records animated:(BOOL)animated {
.
RecordsDetailViewController *detailViewController = [[RecordsDetailViewController alloc] initWithStyle:UITableViewStyleGrouped];
detailViewController.records = records;
[self.navigationController pushViewController:detailViewController animated:animated];
[detailViewController release];
}
#pragma mark -
#pragma mark Table view methods
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.backgroundColor = [UIColor lightGrayColor];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSInteger count = [[fetchedResultsController sections] count];
if (count == 0) {
count = 1;
}
return count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger numberOfRows = 0;
if ([[fetchedResultsController sections] count] > 0) {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
numberOfRows = [sectionInfo numberOfObjects];
}
return numberOfRows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *RecordCellIdentifier = #"RecordCellIdentifier";
RecordTableViewCell *recordCell = (RecordTableViewCell *)[tableView dequeueReusableCellWithIdentifier:RecordCellIdentifier];
if (recordCell == nil) {
recordCell = [[[RecordTableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:RecordCellIdentifier] autorelease];
recordCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
[self configureCell:recordCell atIndexPath:indexPath];
return recordCell;
}
- (void)configureCell:(RecordTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
// Configure the cell
Records *records = (Records *)[fetchedResultsController objectAtIndexPath:indexPath];
cell.records = records;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.searchDisplayController.isActive)
[self.tableView reloadData];
Records *records = (Records *)[fetchedResultsController objectAtIndexPath:indexPath];
[self showRecords:records animated:YES];
}
//this is from the RecordTableViewCell.m to show you the attributes i m using:
#pragma mark -
#pragma mark Record set accessor
- (void)setRecord:(Record *)newRecord {
if (newRecord != record) {
[record release];
record = [newRecord retain];
}
imageView.image = [UIImage imageNamed:#"icon.png"];
nameLabel.text = record.name;
overviewLabel.text = record.overview;
partLabel.text = record.part;
}
thanks again...
I would separate the large file from the metadata, because I like to have the freedom to manage these expensive resources seParately. Then I could store them differently, eg filesystem, or http server. This allows me to cache them or send them to remote locAtions proactively to reduce the download times
The rest of the table can then fit in less blocks in the database so less disk access is needed. Many databases do this internally anyway, eg postgresql
You can the just refer to the heavy resource by Id
Ok here it goes. I ve gave up the idea of loading separately the attributes that i need at start up.
What i ve done AND NOW WORKS FLAWLESSLY is to do create RELATIONSHIPS in my model. Images are now loading only when called!
This solution was in my mind but because i had already populated my database it was difficult for me to repeat this step.
However i m glad that i did!
NOW it works as it should!!
Happy Developer!!

Resources