2

Hello StackOverflow gurus. This is my first question on here so I am excited to jump right in.

I am trying to understand iOS arrays a little better and I've hit a brick wall. I am making a sound app that is using FMOD. I have everything working perfectly but I have 9 buttons that all perform nearly the exact same thing except each play a different .wav file on press then on release stop that sound. I'd like to put it into an array and simplify and shorten my code, that is where I get lost. I stripped down the code to show what I currently have going on. Any ideas?

.h

@interface {

FMOD::Sound    *sound1;
FMOD::Sound    *sound2;
FMOD::Sound    *sound3;
FMOD::Sound    *sound4;
FMOD::Sound    *sound5;
FMOD::Sound    *sound6;
FMOD::Sound    *sound7;
FMOD::Sound    *sound8;
FMOD::Sound    *sound9;

}

- (IBAction)playSound1:(id)sender;
- (IBAction)stopSound1:(id)sender;
- (IBAction)playSound2:(id)sender;
- (IBAction)stopSound2:(id)sender;
- (IBAction)playSound3:(id)sender;
- (IBAction)stopSound3:(id)sender;
- (IBAction)playSound4:(id)sender;
- (IBAction)stopSound4:(id)sender;
- (IBAction)playSound5:(id)sender;
- (IBAction)stopSound5:(id)sender;
- (IBAction)playSound6:(id)sender;
- (IBAction)stopSound6:(id)sender;
- (IBAction)playSound7:(id)sender;
- (IBAction)stopSound7:(id)sender;
- (IBAction)playSound8:(id)sender;
- (IBAction)stopSound8:(id)sender;
- (IBAction)playSound9:(id)sender;
- (IBAction)stopSound9:(id)sender;

m.

- (void)viewWillAppear:(BOOL)animated {

[[NSString stringWithFormat:@"%@/sound1.wav", [[NSBundle mainBundle] resourcePath]] getCString:buffer maxLength:200 encoding:NSASCIIStringEncoding];
    result = system->createSound(buffer, FMOD_SOFTWARE | FMOD_LOOP_NORMAL, NULL, &sound1);
    ERRCHECK(result);


    [[NSString stringWithFormat:@"%@/sound2.wav", [[NSBundle mainBundle] resourcePath]] getCString:buffer maxLength:200 encoding:NSASCIIStringEncoding];
    result = system->createSound(buffer, FMOD_SOFTWARE | FMOD_LOOP_NORMAL, NULL, &sound2);
    ERRCHECK(result);


    [[NSString stringWithFormat:@"%@/sound3.wav", [[NSBundle mainBundle] resourcePath]] getCString:buffer maxLength:200 encoding:NSASCIIStringEncoding];
    result = system->createSound(buffer, FMOD_SOFTWARE | FMOD_LOOP_NORMAL, NULL, &sound3);
    ERRCHECK(result);


    [[NSString stringWithFormat:@"%@/sound4.wav", [[NSBundle mainBundle] resourcePath]] getCString:buffer maxLength:200 encoding:NSASCIIStringEncoding];
    result = system->createSound(buffer, FMOD_SOFTWARE, NULL, &sound4);
    ERRCHECK(result);
    result = sound4->setMode(FMOD_LOOP_NORMAL);
    ERRCHECK(result);

    [[NSString stringWithFormat:@"%@/sound5.wav", [[NSBundle mainBundle] resourcePath]] getCString:buffer maxLength:200 encoding:NSASCIIStringEncoding];
    result = system->createSound(buffer, FMOD_SOFTWARE | FMOD_LOOP_NORMAL, NULL, &sound5);
    ERRCHECK(result);


    [[NSString stringWithFormat:@"%@/sound6.wav", [[NSBundle mainBundle] resourcePath]] getCString:buffer maxLength:200 encoding:NSASCIIStringEncoding];
    result = system->createSound(buffer, FMOD_SOFTWARE | FMOD_LOOP_NORMAL, NULL, &sound6);
    ERRCHECK(result);


    [[NSString stringWithFormat:@"%@/sound7.wav", [[NSBundle mainBundle] resourcePath]] getCString:buffer maxLength:200 encoding:NSASCIIStringEncoding];
    result = system->createSound(buffer, FMOD_SOFTWARE | FMOD_LOOP_NORMAL, NULL, &sound7);
    ERRCHECK(result);


    [[NSString stringWithFormat:@"%@/sound8.wav", [[NSBundle mainBundle] resourcePath]] getCString:buffer maxLength:200 encoding:NSASCIIStringEncoding];
    result = system->createSound(buffer, FMOD_SOFTWARE | FMOD_LOOP_NORMAL, NULL, &sound8);
    ERRCHECK(result);


    [[NSString stringWithFormat:@"%@/sound9.wav", [[NSBundle mainBundle] resourcePath]] getCString:buffer maxLength:200 encoding:NSASCIIStringEncoding];
    result = system->createSound(buffer, FMOD_SOFTWARE | FMOD_LOOP_NORMAL, NULL, &sound9);
    ERRCHECK(result);

}



- (IBAction)playSound1:(id)sender
{
    FMOD_RESULT result = FMOD_OK;
    result = system->playSound(FMOD_CHANNEL_FREE, sound1, false, &wob01);
    ERRCHECK(result);    
}

- (IBAction)stopSound1:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = wob01->stop();
    ERRCHECK(result);   
}

- (IBAction)playSound2:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = system->playSound(FMOD_CHANNEL_FREE, sound2, false, &wob02);
    ERRCHECK(result);    
}

- (IBAction)stopSound2:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = wob02->stop();
    ERRCHECK(result);   
}

- (IBAction)playSound3:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = system->playSound(FMOD_CHANNEL_FREE, sound3, false, &wob03);
    ERRCHECK(result);    
}

- (IBAction)stopSound3:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = wob03->stop();
    ERRCHECK(result);   
}

- (IBAction)playSound4:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = system->playSound(FMOD_CHANNEL_FREE, sound4, false, &wob04);
    ERRCHECK(result);    
}

- (IBAction)stopSound4:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = wob04->stop();
    ERRCHECK(result);   
}

- (IBAction)playSound5:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = system->playSound(FMOD_CHANNEL_FREE, sound5, false, &wob05);
    ERRCHECK(result);    
}

- (IBAction)stopSound5:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = wob05->stop();
    ERRCHECK(result);   
}

- (IBAction)playSound6:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = system->playSound(FMOD_CHANNEL_FREE, sound6, false, &wob06);
    ERRCHECK(result);    
}

- (IBAction)stopSound6:(id)sender
{
    FMOD_RESULT result = FMOD_OK;

    result = wob06->stop();
    ERRCHECK(result);   
}

- (IBAction)playSound7:(id)sender
{
    FMOD_RESULT result = FMOD_OK;
    result = system->playSound(FMOD_CHANNEL_FREE, sound7, false, &wob07);
    ERRCHECK(result);    
}

- (IBAction)stopSound7:(id)sender
{
    FMOD_RESULT result = FMOD_OK;
    result = wob07->stop();
    ERRCHECK(result);   
}

- (IBAction)playSound8:(id)sender
{
    FMOD_RESULT result = FMOD_OK;
    result = system->playSound(FMOD_CHANNEL_FREE, sound8, false, &wob08);
    ERRCHECK(result);    
}

- (IBAction)stopSound8:(id)sender
{
    FMOD_RESULT result = FMOD_OK;
    result = wob08->stop();
    ERRCHECK(result);   
}

- (IBAction)playSound9:(id)sender
{
    FMOD_RESULT result = FMOD_OK;
    result = system->playSound(FMOD_CHANNEL_FREE, sound9, false, &wob09);
    ERRCHECK(result);    
}

- (IBAction)stopSound9:(id)sender
{
    FMOD_RESULT result = FMOD_OK;
    result = wob09->stop();
    ERRCHECK(result);   
}

As you can see, all the code is just repeated. This is the only way I've been able to get it to work but I know that these can be put into an array, I just can't figure it out. Possibly an NSMutableArray and list out "sound1", "sound2", etc etc.. then assign each button a tag in interface builder? Ideally, I'd like to have one function for stopSound, one for playSound, etc. that uses a tag to play or stop the correct sound file. When using FMOD's system->createSound(), the last argument is a variable to store the newly created sound in. Is there any way to store it in an array or dictionary instead? If so I can't figure it out.

Any advice would be MORE than appreciated. I'd love to stop beating my head against this simple issue.

Thank you!

Sean Herman
  • 313
  • 2
  • 15

3 Answers3

8

I would wrap the sound into a subclass of NSObject and make it a self contained unit. A sound would have operations like play, stop, pause, and accessors like isPlaying, etc.

Then to make it even more generic I would search for all files matching the pattern "*.wav" and then for each matched file name, initialize a Sound object with that file name, and add it to an array.

Here's what I imagine the Sound object would look like:

@interface Sound : NSObject

@property FMOD::Sound *sound;

- (id)initWithSoundFilePath:(NSString *)path;
- (void)play;
- (void)stop;

@end

@implementation Sound

- (void)dealloc {
    // free the memory occupied by the sound pointer here
}

- (id)initWithSoundFilePath:(NSString *)path {
    self = [super init];
    if (self) {
        result = system->createSound(path, FMOD_SOFTWARE | FMOD_LOOP_NORMAL, NULL, &sound);
        ERRCHECK(result);
    }
    return self;
}

- (void)play {
    FMOD_RESULT result = FMOD_OK;
    result = system->playSound(FMOD_CHANNEL_FREE, sound, false, /* What is this wob? */);
    ERRCHECK(result);
}

- (void)stop {
    FMOD_RESULT result = FMOD_OK;
    result = /* What is this wob */->stop();
    ERRCHECK(result);   
}

@end

So there you have it. A sound is nicely encapsulated now. I found this answer helpful in finding a list of all files in a certain directory matching some criteria. You could use that in your view controller to automatically generate all relevant Sound objects and add it to an array.

- (NSArray *)getPathsOfSoundFiles {
    NSString *rootPath = [[NSBundle mainBundle] resourcePath];
    NSFileManager *fm = [NSFileManager defaultManager];
    NSArray *files = [fm contentsOfDirectoryAtPath:rootPath error:nil];
    NSPredicate *soundFileFilter = [NSPredicate predicateWithFormat:@"self ENDSWITH '.wav'"];
    NSArray *soundFilePaths = [files filteredArrayUsingPredicate:soundFileFilter];
    return soundFilePaths;
}

Ok, now that you can retrieve the paths to all .wav files, the next step is to initialize them in your viewWillAppear or whatever method makes most sense.

- (void)viewWillAppear:(BOOL)animated {
    NSArray *paths = [self getPathsOfSoundFiles];
    NSMutableArray *sounds = [NSMutableArray array];
    for (NSString *path in paths) {
        Sound *sound = [[Sound alloc] initWithSoundFilePath:path];
        [sounds addObject:sound];
    }
    self.sounds = sounds;
}

And with the sounds array setup, playing and stopping a given sound becomes rather easy. Use could create a method that takes an index into the array, or maybe a Sound object itself and does the job.

- (void)playSoundAtIndex:(NSUInteger)soundIndex {
    Sound *sound = [self.sounds objectAtIndex:soundIndex];
    [sound play];
}

- (void)stopSoundAtIndex:(NSUInteger)soundIndex {
    Sound *sound = [self.sounds objectAtIndex:soundIndex];
    [sound stop];
}
Community
  • 1
  • 1
Anurag
  • 140,337
  • 36
  • 221
  • 257
  • Thanks for the reply. I am looking over it now, but I wanted to mention that the "wob1" etc, are just channels. So with FMOD you can put each sound into a series of channels if you want. That is all it is. – Sean Herman Sep 08 '12 at 22:01
  • You could pass the channel to play into to the `Sound` object as part of the initialization in that case. – Anurag Sep 08 '12 at 22:09
  • I understand this approach, but I’m having some trouble implementing it because when I make a new Sound object, any reference to FMOD and its classes throw errors. For example: @property FMOD::Sound *sound; Gives these errors: Expected expression, Type specifier missing, defaults to ‘int’, Type name requires a specifier or qualifier – Sean Herman Sep 08 '12 at 23:15
  • It seems that whether or not I include FMOD again in the Sound.h file, FMOD::Sound is not considerred a valid type. Any idea why? I also tried naming the new object SoundObj in case it was a naming conflict. I’ve tried declaring the following variables in the Sound.h file: FMOD::System *system; FMOD::Sound *sound; But it seems that any time I try to access a class from FMOD, I get an error. I’m a bit new to this so it could be something simple. Any idea why this isn’t working? Other than that, I understand this solution and it was what I was looking for! – Sean Herman Sep 08 '12 at 23:16
  • Rename your implementation file extension from ".m" to ".mm" to tell the compiler that it contains C++ code. And change the type specifier to `@property (nonatomic, strong) FMOD::Sound *sound;`. – Anurag Sep 08 '12 at 23:39
  • Dangit. It's just not working. Anurag - any way I could IM ya? :) – Sean Herman Sep 09 '12 at 00:32
  • http://chat.stackoverflow.com/rooms/16452/discussion-between-anurag-and-sean-herman – Anurag Sep 09 '12 at 00:41
  • never mind that, you won't be able to join cause of low rep. try this - http://collabedit.com/cxkhm – Anurag Sep 09 '12 at 00:46
  • Technically - I think this will work, however I am still having troubles! I'll keep you updated! Thank you so much for your help on this. – Sean Herman Sep 11 '12 at 15:25
1

You can generalize your code a lot:

  • using arrays to store your sounds and "wobs"
  • using a loop with something like [NSString stringWithFormat:@"%@/sound%i.wav", [[NSBundle mainBundle] resourcePath], index]
  • only having one -playSound: and one -stopStound: action that uses sender's tag (that you can set up in Interface Builder or automatize when creating your buttons in your view controller's code) to determine which sound to play or which wob to stop.
Julien
  • 3,427
  • 20
  • 22
-3

I would highly recommend that you subclass UIButton and override the touch methods to play an audio file when those actions occur. When you subclass UIButton you'll want to set a property in your header file so you can pass in an audio file, or string of where an audio file is located, so it can easily be made dynamic.

Then in the main file like I said just override those touch methods (touchesBegan, touchesEnded, etc) to play that custom property you can pass in that we defined in the header file.

That's how I'd tackle your problem at least, then you only have one UIButton subclass that can do the exact same thing depending on what audio file you pass into it.

Brayden
  • 1,795
  • 14
  • 20
  • 2
    -1 it is rarely necessary, in *any* circumstances, to subclass `UIButton`. – Dave DeLong Sep 08 '12 at 21:53
  • 1
    -1: This design goes against MVC, the target/action design pattern and the idea of what `UIButton` should have responsibility for. `UIButton` should be responsible for knowing its targets and associated actions, for knowing how it should lay out its subviews, for knowing its state and how to display that to the screen, and so on. It should **not** implement the actual application behavior associated with tapping it. – Carl Veazey Sep 08 '12 at 21:55