在音频应用中使用Novocaine

时间:2012-11-08 14:38:32

标签: iphone objective-c ios avaudioplayer

我正在构建一个iPhone应用程序,通过以“caf”格式播放单独录制的吉他音符来生成随机吉他音乐。这些音符的持续时间从3秒到11秒不等,具体取决于维持量。

我最初使用AVAudioPlayer进行播放,并在模拟器中以每分钟120点的速度播放第16个音符,它演唱得非常漂亮,但是在我的手机上,我一听到 将节奏提高了60多分钟,只演奏1/4音符,它像狗一样跑,不会及时保持。我的兴奋很短暂。

为了减少延迟,我尝试使用Apple MixerHost项目作为音频引擎的模板,通过音频单元实现播放,但在我用螺栓连接并连接所有内容后仍然出现错误的访问错误。

经过几个小时的努力,我放弃了那条思路,然后我依旧使用了Novocaine音频引擎。

我现在遇到了一堵砖墙,试图将它连接到我的模型。

在最基本的层面上,我的模型是一个包含Note对象的NSDictionary的Neck对象。

每个Note对象都知道它所在的吉他琴颈的弦乐和音品,并包含自己的AVAudioPlayer。

我制作了一个包含122个音符(6个字符串乘22个音符)或144个音符(6个字符串乘24个音符)的彩色吉他琴颈,具体取决于用户偏好中选择的琴颈尺寸。

我使用这些Notes作为我的唯一真实点,因此音乐引擎生成的所有标量音符都是指向此彩色音符桶的指针。

@interface Note : NSObject <NSCopying>
{
    NSString *name;
    AVAudioPlayer *soundFilePlayer;
    int stringNumber;
    int fretNumber; 
}

我总是用所选音阶的根音符或和弦开始播放,然后生成下一个音符,这样我总是在生成的音符后面播放一个音符。这样,下一个要播放的音符总是排队等待准备就绪。

使用以下代码实现对这些Notes的回放控制:

- (void)runMusicGenerator:(NSNumber *)counter
{
if (self.isRunning) {
    Note *NoteToPlay;
    // pulseRate is the time interval between beats
    // staticNoteLength = 1/4 notes, 1/8th notes, 16th notes, etc.

    float delay = self.pulseRate / [self grabStaticNoteLength];

    // user setting to play single, double or triplet notes. 
    if (self.beatCounter == CONST_BEAT_COUNTER_INIT_VAL) {
        NoteToPlay = [self.GuitarNeck generateNoteToPlayNext];
    } else {
        NoteToPlay = [self.GuitarNeck cloneNote:self.GuitarNeck.NoteToPlayNow];
    }

    self.GuitarNeck.NoteToPlayNow = NoteToPlay;

    [self callOutNoteToPlay];

    [self performSelector:@selector(runDrill:) withObject:NoteToPlay afterDelay:delay];
}

- (Note *)generateNoteToPlayNext
{
    if ((self.musicPaused) || (self.musicStopped)) {
        // grab the root note on the string to resume
        self.NoteToPlayNow = [self grabRootNoteForString];

        //reset the flags
        self.musicPaused = NO;
        self.musicStopped = NO;
    } else {
        // Set NoteRingingOut to NoteToPlayNow
        self.NoteRingingOut = self.NoteToPlayNow;

        // Set NoteToPlaNowy to NoteToPlayNext
        self.NoteToPlayNow = self.NoteToPlayNext;
        if (!self.NoteToPlayNow) {
            self.NoteToPlayNow = [self grabRootNoteForString];

            // now prep the note's audio player for playback
            [self.NoteToPlayNow.soundFilePlayer prepareToPlay];
        }        
    }

    // Load NoteToPlayNext
        self.NoteToPlayNext = [self generateRandomNote];
    }

    - (void)callOutNoteToPlay
    {    
        self.GuitarNeck.NoteToPlayNow.soundFilePlayer.delegate = (id)self;
        [self.GuitarNeck.NoteToPlayNow.soundFilePlayer setVolume:1.0];
        [self.GuitarNeck.NoteToPlayNow.soundFilePlayer setCurrentTime:0];
        [self.GuitarNeck.NoteToPlayNow.soundFilePlayer play];
    }

每个Note的AVAudioPlayer加载如下:

- (AVAudioPlayer *)buildStringNotePlayer:(NSString *)nameOfNote
{
    NSString *soundFileName = @"S";
    soundFileName = [soundFileName stringByAppendingString:[NSString stringWithFormat:@"%d", stringNumber]];
    soundFileName = [soundFileName stringByAppendingString:@"F"];

    if (fretNumber < 10) {
        soundFileName = [soundFileName stringByAppendingString:@"0"];
    }
    soundFileName = [soundFileName stringByAppendingString:[NSString stringWithFormat:@"%d", fretNumber]];

    NSString *soundPath = [[NSBundle mainBundle] pathForResource:soundFileName ofType:@"caf"];
    NSURL *fileURL = [NSURL fileURLWithPath:soundPath];
    AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];

    return notePlayer;
}

这是我来自裁剪的地方。

根据Novocaine Github页面...

播放音频

Novocaine *audioManager = [Novocaine audioManager];
[audioManager setOutputBlock:^(float *audioToPlay, UInt32 numSamples, UInt32 numChannels) {
    // All you have to do is put your audio into "audioToPlay".
}];

但是在下载的项目中,您使用以下代码加载音频......

// AUDIO FILE READING OHHH YEAHHHH
// ========================================
NSURL *inputFileURL = [[NSBundle mainBundle] URLForResource:@"TLC" withExtension:@"mp3"];

fileReader = [[AudioFileReader alloc]
              initWithAudioFileURL:inputFileURL
              samplingRate:audioManager.samplingRate
              numChannels:audioManager.numOutputChannels];

[fileReader play];
fileReader.currentTime = 30.0;

[audioManager setOutputBlock:^(float *data, UInt32 numFrames, UInt32 numChannels)
{
    [fileReader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels];
    NSLog(@"Time: %f", fileReader.currentTime);
}];

这是我真正开始困惑的地方,因为第一种方法使用浮点数,第二种方法使用URL。

如何将“caf”文件传递给浮点数?我不确定如何实施诺沃卡因 - 它仍然模糊不清。

我希望有人可以帮助我的问题如下......

  1. Novocaine对象是否与AVAudioPlayer对象类似,只是用途更广泛,并且调整到最大延迟时间最短?即自包含音频播放(/录制/生成)单元?

  2. 我可以在我的模型中使用Novocaine吗?即每个彩色音符有1个Novocaine物体,或者我应该有1个包含所有彩色音符的novocain物体?或者我只是将URL存储在笔记中并将其传递给Novocaine播放器?

  3. 当我的音频是“caf”文件而“audioToPlay”浮动时,如何将音频输入“audioToPlay”?

  4. 如果我在Note.m中包含并声明了Novocaine属性,那么我是否必须将该类重命名为Note.mm才能使用Novocaine对象?

  5. 如何同时播放多个Novocaine对象以重现和弦和间隔?

  6. 我可以循环播放Novocaine对象吗?

  7. 我可以设置音符的播放长度吗?即只用1秒钟播放10秒音符?

  8. 我可以修改上面的代码以使用诺沃卡因吗?

  9. 我使用的runMusicGenerator方法是否正确使用,以保持达到专业标准的速度?

1 个答案:

答案 0 :(得分:10)

Novocaine无需手动设置RemoteIO AudioUnit,让您的生活更轻松。这包括不得不痛苦地填充一堆CoreAudio结构并提供一堆回调,例如此音频进程回调。

static OSStatus PerformThru(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);

相反,Novocaine在其实现中处理它,然后调用你的块,你通过这样做来设置它。

[audioManager setOutputBlock: ^(float *audioToPlay, UInt32 numSamples, UInt32 numChannels){} ];

无论你写给audioToPlay的是什么,都会被播放。

  1. Novocaine为您设置RemoteIO AudioUnit。这是一个低级CoreAudio API,不同于高级AVFoundation,并且具有非常低的延迟性。你是正确的,诺瓦卡因是独立的。您可以实时录制,生成和处理音频。

  2. Novocaine是一个单身人士,你不能有多个Novocaine实例。一种方法是将吉他声音/声音存储在一个单独的类或数组中,然后编写一系列方法,使用Novocaine播放它们。

  3. 你有很多选择。您可以使用Novocaine的AudioFileReader播放.caf文件。您可以通过分配AudioFileReader,然后根据示例代码传递要播放的.caf文件的URL来执行此操作。然后根据示例代码将[fileReader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels]粘贴到您的块中。每次调用块时,AudioFileReader都会从磁盘中抓取并缓冲一大块音频,然后将其放入audioToPlay,然后播放。这有一些缺点。对于短暂的声音(例如我假设的吉他声音),反复调用retrieveFreshAudio是一个性能损失。通常(对于短声音)执行对整个文件到存储器的同步,顺序读取通常是更好的想法。诺瓦卡因没有提供这样做的方法(还)。您必须使用ExtAudioFileServices来执行此操作。 Apple示例项目MixerHost详细说明了如何执行此操作。

  4. 如果您正在使用AudioFileReader。当您#import来自Obj-C ++标头或#include C ++标头时,您只能重命名为.mm。

  5. 如前所述,只允许使用1个Novocaine实例。您可以通过混合多个音频源来实现复音。这只是简单地添加缓冲区。如果您以不同的音高制作了相同吉他音的多个版本,只需将它们全部读入内存,然后混合即可。如果你只想要一个吉他声,那么你必须实时改变你正在播放的许多音符的播放速率然后混音。

  6. 诺沃卡因不知道你实际上在玩什么,也不关心你为样本玩多长时间。为了循环声音,您必须保持已经过了多少个样本的计数,检查您是否在声音结束时,然后将该计数设置回0。

  7. 是。假设44.1k采样率,1秒音频= 44100个样本。然后,当它达到44100时,您将重置计数。

  8. 是。它看起来像这样。假设您有4个单声道且长度超过1秒的吉他声音,并且您已将它们读入内存float *guitarC*guitarE*guitarG*guitarB;(jazzy CMaj7和弦) w00t),并希望将它们混合1秒钟并将其循环回单声道:

  9. [audioManager setOutputBlock:^(float *data, UInt32 numFrames, UInt32 numChannels){
        static int count = 0;
        for(int i=0; i<numFrames; ++i){
            //Mono mix each sample of each sound together. Since result can be 4x louder, divide the total amp by 4. 
            //You should be using `vDSP_vadd` from the accelerate framework for added performance.
            data[count] = (guitarC[count] + guitarE[count] + guitarG[count] + guitarB[count]) * 0.25;
            if(++count >= 44100) count = 0;    //Plays the mix for 1 sec
        }
    }];
    
    1. 不完全是。使用performSelector或在runloop或线程上安排的任何机制都不能保证准确。例如,当CPU负载波动时,您可能会遇到时序不规则。如果您想要样本准确计时,请使用音频块。