How to wait finish current audio for playing next audio? - objective-c

I'm working with cocos2d. How to wait finish current audio for playing next audio? I can't make it with SimpleAudioEngine.

http://www.cocos2d-iphone.org/api-ref/0.99.0/interface_c_d_sound_engine.html
use CDAudioEngine. It have the method you want. By the way SimpleAudioEngine is using CDAudioEngine to play sounds.
EDIT
When your button is clicked first time - play the sound and save the sound's interval to some of your lass variable. Then in next click use this variable to determine the "enough interval". Or just use some maximum.
EDIT 2
If you'll take a look at SimpleAudioEngine implementation you will notice that it's using CDSoundEngine to play sounds. Here is the init method of SimpleAudioEngine:
static SimpleAudioEngine *sharedEngine = nil;
static CDSoundEngine* soundEngine = nil;
static CDAudioManager *am = nil;
static CDBufferManager *bufferManager = nil;
-(id) init
{
if((self=[super init])) {
am = [CDAudioManager sharedManager];
soundEngine = am.soundEngine;
bufferManager = [[CDBufferManager alloc] initWithEngine:soundEngine];
mute_ = NO;
enabled_ = YES;
}
return self;
}
So we can also access to CDSoundEngine:
CDSoundEngine *engine = [CDAudioManager sharedManager].soundEngine;
When calling playEffect method on SimpleAudioEngine (when your button is clicked) save the returned sound id.
ALuint soundId = [[SimpleAudioEngine sharedEngine] playEffect:...];
Now we can get the interval using our CDSoundEngine:
float seconds = [engine bufferDurationInSeconds:soundId];
That's the answer!
By the way you can also stop a sound with CDSoundEngine if you know the sound id.
From CocosDenshion.h
#class CDSoundSource;
#interface CDSoundEngine : NSObject <CDAudioInterruptProtocol> {
bufferInfo *_buffers;
sourceInfo *_sources;
sourceGroup *_sourceGroups;
ALCcontext *context;
int _sourceGroupTotal;
UInt32 _audioSessionCategory;
BOOL _handleAudioSession;
BOOL mute_;
BOOL enabled_;
ALfloat _preMuteGain;
ALenum lastErrorCode_;
BOOL functioning_;
float asynchLoadProgress_;
BOOL getGainWorks_;
//For managing dynamic allocation of sources and buffers
int sourceTotal_;
int bufferTotal;
}
#property (readwrite, nonatomic) ALfloat masterGain;
#property (readonly) ALenum lastErrorCode;//Last OpenAL error code that was generated
#property (readonly) BOOL functioning;//Is the sound engine functioning
#property (readwrite) float asynchLoadProgress;
#property (readonly) BOOL getGainWorks;//Does getting the gain for a source work
/** Total number of sources available */
#property (readonly) int sourceTotal;
/** Total number of source groups that have been defined */
#property (readonly) int sourceGroupTotal;
/** Sets the sample rate for the audio mixer. For best performance this should match the sample rate of your audio content */
+(void) setMixerSampleRate:(Float32) sampleRate;
/** Initializes the engine with a group definition and a total number of groups */
-(id)init;
/** Plays a sound in a channel group with a pitch, pan and gain. The sound could played looped or not */
-(ALuint) playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop;
/** Creates and returns a sound source object for the specified sound within the specified source group.
*/
-(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId;
/** Stops playing a sound */
- (void) stopSound:(ALuint) sourceId;
/** Stops playing a source group */
- (void) stopSourceGroup:(int) sourceGroupId;
/** Stops all playing sounds */
-(void) stopAllSounds;
-(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions;
-(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(int) total;
-(void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible;
-(void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled;
-(BOOL) sourceGroupEnabled:(int) sourceGroupId;
-(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq;
-(BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath;
-(void) loadBuffersAsynchronously:(NSArray *) loadRequests;
-(BOOL) unloadBuffer:(int) soundId;
-(ALCcontext *) openALContext;
/** Returns the duration of the buffer in seconds or a negative value if the buffer id is invalid */
-(float) bufferDurationInSeconds:(int) soundId;
/** Returns the size of the buffer in bytes or a negative value if the buffer id is invalid */
-(ALsizei) bufferSizeInBytes:(int) soundId;
/** Returns the sampling frequency of the buffer in hertz or a negative value if the buffer id is invalid */
-(ALsizei) bufferFrequencyInHertz:(int) soundId;
/** Used internally, never call unless you know what you are doing */
-(void) _soundSourcePreRelease:(CDSoundSource *) soundSource;
#end
I'm using cocos2D 0.99.5
implementation:
-(float) bufferDurationInSeconds:(int) soundId {
if ([self validateBufferId:soundId]) {
float factor = 0.0f;
switch (_buffers[soundId].format) {
case AL_FORMAT_MONO8:
factor = 1.0f;
break;
case AL_FORMAT_MONO16:
factor = 0.5f;
break;
case AL_FORMAT_STEREO8:
factor = 0.5f;
break;
case AL_FORMAT_STEREO16:
factor = 0.25f;
break;
}
return (float)_buffers[soundId].sizeInBytes/(float)_buffers[soundId].frequencyInHertz * factor;
} else {
return -1.0f;
}
}

// to Play the sound
Aluint soundID = [[SimpleAudioEngine sharedEngine] playEffect:#"sound.mp3"];
// to stop the sound you need to use the same soundID when you play it
[[SimpleAudioEngine sharedEngine] stopEffect:soundID];

Related

NSOutputStream is always waiting for space available after second NSOperation tries to write to it

I am creating an NSOutputStream and passing it to an NSOperation, which I call from an NSOperationQueue. In the operation, I am polling for hasSpaceAvailable so that I can write out to the socket. This works fine the first time I write out to the socket. After the operation returns, however, and I try to write again to the socket, the write never goes through as I'm infinitely waiting for space to become available in the output socket. I've tried opening/closing the output stream each time I write, but have the same problem.
I open the output stream in the init function (the NSOutputStream is created from a Bluetooth EASession:
_commandSession = [[EASession alloc] initWithAccessory:self.selectedAccessory forProtocol:commandProtocolString];
_commandOutputStream = [_commandSession outputStream];
[_commandOutputStream open];
I also create the operation queue in the init:
_senderOperationQueue = [[NSOperationQueue alloc] init];
_senderOperationQueue.name = #"Send Queue";
_senderOperationQueue.maxConcurrentOperationCount = 1;
I have a text field with the data I want to send over the output stream. This function is called each time I click the send button:
-(void)sendCommandData:(NSData *)buf
{
_commandSendOperation =[[SenderOperation alloc] initWithStream:_commandOutputStream data:buf delegate:self];
[_senderOperationQueue addOperation:_commandSendOperation];
}
This is how my operation code looks like:
(SenderOperation.h)
#import <Foundation/Foundation.h>
#protocol SenderOperationDelegate;
#interface SenderOperation : NSOperation
{
NSOutputStream *_stream;
NSData *_sendData;
}
#property (nonatomic, assign) id <SenderOperationDelegate> delegate;
#property (nonatomic, retain) NSOutputStream *stream;
#property (nonatomic, retain) NSData *sendData;
- (id)initWithStream:(NSOutputStream *)stream data:(NSData *)buf delegate:(id<SenderOperationDelegate>)theDelegate;
#end
// Delegate to notify main thread the completion of a BT input buffer stream
#protocol SenderOperationDelegate <NSObject>
-(void)sendComplete:(SenderOperation *)sender;
#end
(SenderOperation.m)
#import "SenderOperation.h"
#implementation SenderOperation
#synthesize delegate = _delegate;
#synthesize stream = _stream;
#synthesize sendData = _sendData;
- (id)initWithStream:(NSOutputStream *)stream data:(NSData *)buf delegate:(id<SenderOperationDelegate>)theDelegate
{
if (self = [super init])
{
self.delegate = theDelegate;
self.stream = stream;
self.sendData = buf;
}
return self;
}
#define MAX_PACKET_SIZE 20
- (void)main
{
if (self.isCancelled)
return;
// total length of the data packet we need to send
int totalLength = [_sendData length];
// length of packet to send (given our upper bound)
int len = (totalLength <= MAX_PACKET_SIZE) ? totalLength:MAX_PACKET_SIZE;
// stream write response
int streamWriteResponse;
// bytes already written out to the output stream
int bytesWritten = 0;
while (1)
{
if (!len) break;
if ([self.stream hasSpaceAvailable])
{
streamWriteResponse = [self.stream write:[self.sendData bytes] maxLength:len];
if (streamWriteResponse == -1)
{
break;
}
bytesWritten += streamWriteResponse;
// update the data buffer with left over data that needs to be written
if (totalLength - bytesWritten)
self.sendData = [self.sendData subdataWithRange:NSMakeRange(bytesWritten, totalLength - bytesWritten)];
// update length of next data packet write
len = (totalLength - bytesWritten) >= MAX_PACKET_SIZE ? MAX_PACKET_SIZE : (totalLength - bytesWritten);
}
}
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(sendComplete:) withObject:self waitUntilDone:NO];
}
You'll need to use run-loop scheduling for your stream, rather than polling. Quoth the docs for -[EASession outputStream]:
This stream is provided for you automatically by the session object but you must configure it if you want to receive any associated stream events. You do this by assigning a delegate object to the stream that implements the stream:handleEvent: delegate method. You must then schedule the stream in a run loop so that it can send data asynchronously from one of your application’s threads.

Trouble with NSString: using a controller to set an NSTextField?

I am using Xcode 4 writing a simple program to generate text based on user input. Im reading from two text fields successfully, but I am unable to send the result to a separate NSTextField. I'm confident I've made all the right connections in IB, and I'm sending the NSTextField the setStringValue method. I have created a ScaleGenerator object that I believe may be improperly creating the string itself. My Controller header looks like this:
#import <Cocoa/Cocoa.h>
#interface Controller : NSObject
{
IBOutlet NSTextField * notesField;
IBOutlet NSTextField * midiField;
IBOutlet NSTextField * resultField;
IBOutlet id scalegenerator;
}
- (IBAction) generate: (id) sender;
#end // Controller
The Controller implementation looks like this:
#import "Controller.h"
#import "ScaleGenerator.h"
#import <Foundation/foundation.h>
#implementation Controller
- (IBAction) generate: (id) sender
{
int midinote;
int notes;
NSString * scale;
midinote = [midiField intValue];
if(midinote < 0 || midinote > 127) {
NSRunAlertPanel(#"Bad MIDI", #"That MIDI note is out of range! (0 - 127)", #"OK", nil, nil);
return;
}
notes = [notesField intValue];
if(notes < 2 || notes > 24) {
NSRunAlertPanel(#"Bad Scale Size", #"You must have at least two notes in your scale, and no more than 25 notes!", #"OK", nil, nil);
return;
}
scale = [scalegenerator generateScale: midinote and: notes];
[resultField setStringValue:scale];
}
#end
My ScaleGenerator code looks like this:
#import "ScaleGenerator.h"
#import <math.h>
#implementation ScaleGenerator
- (NSMutableString *) generateScale: (int) midinote and: (int) numberOfNotes
{
double frequency, ratio;
double c0, c5;
double intervals[24];
int i;
NSMutableString * result;
/* calculate standard semitone ratio in order to map the midinotes */
ratio = pow(2.0, 1/12.0); // Frequency Mulitplier for one half-step
c5 = 220.0 * pow(ratio, 3); // Middle C is three semitones above A220
c0 = c5 * pow(0.5, 5); // Lowest MIDI note is 5 octaves below middle C
frequency = c0 * pow(ratio, midinote); // the first frequency is based off of my midinote
/* calculate ratio from notes and fill the frequency array */
ratio = pow(2.0, 1/numberOfNotes);
for(i = 0; i < numberOfNotes; i++) {
intervals[i] = frequency;
frequency *= ratio;
}
for(i = 0; i < n; i++){
[result appendFormat: #"#%d: %f", numberOfNotes + 1, intervals[i]];
}
return (result);
}
#end // ScaleGenerator
My ScaleGenerator object has one function, which is where I believe I may be messing things up. What kind of string can I iteratively append formatted text to?? And what method do I call??
You have not allocated/initialized your NSMutableString. Therefore the message appendFormat: goes to nil. Replace the line
NSMutableString * result;
with
NSMutableString * result = [[NSMutableString alloc] init];

SegmentedControl Always Zero

I am trying to build an application that rolls dice. Nothing fancy at all. However, I'm using a segmented control to determine the number of dice and the sides of the dice.
Here is the RollPressed code.
#import "DiceBrain.h"
#import "ViewController.h"
#interface ViewController ()
#property (readonly) DiceBrain *brain;
#end
#implementation ViewController
- (DiceBrain *) brain {
if (!brain) brain = [[DiceBrain alloc] init];
return brain;
}
- (IBAction)RollPressed:(id)sender {
int num_dice = dice.selectedSegmentIndex;
int num_sides = sides.selectedSegmentIndex;
NSLog(#"Number of Dice is %u and Number of Sides is %u", num_dice, num_sides);
int result = [self.brain RollDice:num_dice Sides: num_sides];
display.text = [NSString stringWithFormat:#"%g", result];
}
#end
According to NSLog there, I'm always using a zero. Of course, actuating using this logic to roll the dice results in the display showing me something like 3.82652e-308.
The logic used to roll the dice is
- (int)RollDice:(int)dice Sides:(int)num_sides{
total = 0;
for (int i = 0; i < dice; i++) {
total += (arc4random() % num_sides) + 1;
}
return total;
}
What could cause the segmented control to give me such funky results?
Sounds like your IBOutlets for sides and dice are not connected.
Check their value; it's probably NULL.

NSTextField continuous update

I can't figure out how to get an NSTextfield to update automatically, without having to press "Return" or click another text field.
My goal is to input a number into a field and have the other fields update simultaneously. I tried clicking "Continuous" in the text field attributes but it doesn't seem to do anything.
Here is my interface file:
#import <Foundation/Foundation.h>
#interface InchController : NSObject {
IBOutlet NSTextField *centimetersTextField;
IBOutlet NSTextField *inchesTextField;
IBOutlet NSTextField *feetTextField;
}
-(IBAction)convert:(id)sender;
#end
Here is my implementation file:
#import "InchController.h"
#implementation InchController
- (IBAction)convert:(id)sender {
if (sender == inchesTextField) {
float inches = [inchesTextField floatValue];
[feetTextField setFloatValue:(inches * 0.0833)];
[centimetersTextField setFloatValue:(inches * 2.54)];
}
else if (sender == feetTextField) {
float feet = [feetTextField floatValue];
[inchesTextField setFloatValue:(feet * 12)];
[centimetersTextField setFloatValue:(feet * 30.48)];
}
else if (sender == centimetersTextField) {
float centimeters = [centimetersTextField floatValue];
[inchesTextField setFloatValue:(centimeters * 0.394)];
[feetTextField setFloatValue:(centimeters * 0.033)];
}
}
#end
So here is the updated implementation file per Josh's solution. Commented out the IBAction since it is no longer needed in the implementation and interface files.
#import "LengthController.h"
#implementation LengthController
//- (IBAction) convert: (id)sender {
//}
-(void) controlTextDidChange:(NSNotification *) note {
NSTextField *changedField = [note object];
if (changedField == inchesTextField) {
float inches = [inchesTextField floatValue];
[feetTextField setFloatValue: (inches * 0.0833)];
[centimetersTextField setFloatValue: (inches * 2.54)];
}
if (changedField == centimetersTextField) {
float centimeters = [centimetersTextField floatValue];
[inchesTextField setFloatValue:(centimeters * 0.394)];
[feetTextField setFloatValue:(centimeters * 0.033)];
}
if (changedField == feetTextField) {
float feet = [feetTextField floatValue];
[inchesTextField setFloatValue:(feet * 12)];
[centimetersTextField setFloatValue:(feet * 30.48)];
}
}
#end
Make your controller the delegate of the text fields; you can set this in Interface Builder by Ctrl-dragging from the text fields to the controller.
In your controller, implement the "NSControl Delegate" method controlTextDidChange:, which will be called (as its name suggests) whenever the field's text changes. In that method, you can validate the text and, if appropriate, update the contents of the other fields.
The argument that is passed in can give you the text field which changed; you can then pass that on to your existing convert: method to reuse the code:
- (void) controlTextDidChange: (NSNotification *)note {
NSTextField * changedField = [note object];
[self convert:changedField];
}
There's nothing special about action methods. The IBAction return type evaluates to void; it's only used by Xcode to expose the method for use in Interface Builder. You can, therefore, call them just like any other method. Here, you get the appropriate field and pass it in as the sender parameter, as if the field had called the action method itself.
Depending on the complexity of the problem, bindings may be a viable solution, too.
You can define properties on a model or model controller object and hook them up to the corresponding text fields. Then, changes in the text field are immediately reflected in the properties, which can then trigger changes to other properties.
Text fields bound to these "derived" properties are then updated automatically.
Remember to "bracket" your changes to the derived properties with willChangeValueForKey: and didChangeValueForKey: so the changes are sent to observers. More here.
Of course, if you have loops in the dependencies, it gets ugly; in that case the controlTextDidChange: method mentioned in the other answers is probably better.

Objective-C Method to mute and unmute volume

I'm a little stuck, i'm trying to write a method which when a button is pressed; will get the current volume of iTunes, store the volume as an int declared as x. Then make the iTunes volume equal to 0, which will essentially mute the iTunes volume, but then i want the iTunes volume to return to the int x if the button is pressed again, which will essentially unmute the iTunes volume and restore it to the original volume.
Here's what i have so far:
- (IBAction)muteAndUnmute:(id)sender {
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
int x;
x = [iTunes soundVolume];
if ([iTunes soundVolume] > 0 ) {
[volumeSlider setIntValue:0];
[iTunes setSoundVolume:[volumeSlider intValue]];
[volumeLabel setIntValue:[volumeSlider intValue]];}
}
Any help would be much appreciated, i think it's quite easy to do but just can't get my head around it, thanks in advance, Sami.
make your volume value (vol) as class variable and not local, then
// MyWindowController.h
#interface MyWindowController : NSWindowController {
int vol;
}
- (IBAction)btnPressed:(id)sender;
#end
// MyWindowController.m
#implementation MyWindowController
- (id)init {
if (self = [super init]) {
vol = 0;
}
return self;
}
- (IBAction)btnPressed:(id)sender
{
id iTunes = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
if ([iTunes soundVolume] > 0 )
{
vol = [iTunes soundVolume];
[iTunes setSoundVolume:0];
}
else
[iTunes setSoundVolume:vol];
}
#end
you can use this:
fVolume = [MPMusicPlayerController iPodMusicPlayer].volume;
[MPMusicPlayerController iPodMusicPlayer].volume = 0.0;
//do whatever you want
[MPMusicPlayerController iPodMusicPlayer].volume = fVolume;