2012-11-08 44 views
5

我正在構建一款iPhone應用程序,通過以「caf」格式回放各個錄製的吉他音符來生成隨機吉他音樂。這些音符的持續時間從3秒到11秒不等,具體取決於延音量。在音頻應用程序中使用Novocaine

我最初使用AVAudioPlayer進行播放,在模擬器中以120bpm的速度播放第16個音符,但是在我的手機上,只要我的 將速度提高了60 bpm,只播放了1/4筆記,它像狗一樣跑,並不會及時。我的歡樂時光非常短暫。

爲了減少延遲,我嘗試使用Apple MixerHost項目作爲音頻引擎的模板來實現通過音頻單元進行回放,但是在我將其栓上並連接好之後,仍然出現訪問錯誤。

經過幾個小時的努力,我放棄了這種思維方式,而是放棄了Novocaine音頻引擎。

我現在碰到了一堵磚牆,試圖連接到我的模型。

在最基本的層面上,我的模型是一個包含Note對象的NSDictionary的Neck對象。

每個Note對象都知道它所在的吉他脖子上的字符串和音品,並且包含它自己的AVAudioPlayer。

根據用戶偏好中選擇的脖子尺寸,我製作了一個包含122個音符(22根22根6根弦)或144根音符(6根24根24根弦)的吉他琴頸。

我使用這些Notes作爲我的單一事實點,因此音樂引擎生成的所有標量音符都是指向此彩色音符存儲桶的指針。

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

我總是開始播放與所選規模的根音符或和絃,然後生成音符播放下,所以我總是打產生的音符背後的​​一個音符。通過這種方式,下一個要播放的音符總是排隊準備就緒。這些票據

播放控制是一個用下面的代碼實現:

- (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]; 
    } 

每個音符的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; 
} 

這裏是我栽跟頭。

按照奴佛卡因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); 
}]; 

這裏是我真正開始感到困惑的地方,因爲第一個方法使用了一個float,而第二個方法使用了一個URL。

如何將「caf」文件傳遞給float?我不知道如何實施諾沃卡因 - 這在我的腦海中仍然很模糊。

我的問題,我希望有人能幫助我的如下...

  1. 是奴佛卡因對象類似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. 我可以設置音符的播放長度嗎?即僅播放10秒鐘的音符1秒?

  8. 我可以修改上面的代碼來使用Novocaine嗎?

  9. 我爲runMusicGenerator使用的方法是否正確使用以保持符合專業標準的節奏?

回答

10

由於無需手動設置RemoteIO AudioUnit,因此可以使您的生活更輕鬆。這包括必須痛苦地填充一堆CoreAudio結構,並提供一些回調,如音頻處理回調。

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

相反奴佛卡因處理,在其執行,然後調用你的模塊,您可以通過這樣設置。

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

不管你寫 audioToPlay

得到發揮。

  1. Novocaine爲您設置了RemoteIO AudioUnit。這是一個低級別的CoreAudio API,與高級AVFoundation不同,且延遲非常低。你說得對,因爲諾沃卡因是獨立的。您可以實時錄製,生成和處理音頻。

  2. 諾沃卡因是一個單身人士,你不能有多個諾沃卡因實例。一種方法是將你的吉他聲音/聲音存儲在一個單獨的類或數組中,然後編寫一些方法,使用Novocaine播放它們。

  3. 你有一堆選項。您可以使用Novocaine的AudioFileReader爲您播放.caf文件。你可以通過分配一個AudioFileReader然後傳遞URL的URL來完成。根據示例代碼,您要播放的caf文件。然後按照示例代碼在塊中粘貼[fileReader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels]。每次調用塊時,AudioFileReader都會抓取並緩衝來自磁盤的一段音頻,並將其放入audioToPlay,隨後播放。這有一些缺點。對於短暫的聲音(例如我假設的你的吉他聲音)反覆調用retrieveFreshAudio是一個表現。對於整個文件執行同步,順序讀取到內存中通常是一個更好的主意(對於簡短的聲音)。 Novocaine沒有提供一種方法來做到這一點(還)。你將不得不使用ExtAudioFileServices來做到這一點。 Apple示例項目MixerHost詳細介紹瞭如何執行此操作。

  4. 如果您使用AudioFileReader yes。當你使用Obj-C++頭文件或#include時,你只需重新命名爲.mm。

  5. 如前所述,只允許1個Novocaine實例。您可以通過混合多個音頻源來實現複音。這只是簡單地將緩衝區添加到一起。如果您以不同的音高製作了多個相同吉他聲音的版本,只需將它們全部讀入內存,然後混合即可。如果您只想要一個吉他聲音,那麼您必須實時更改正在播放的許多音符的播放速率,然後進行混音。

  6. 對於你實際上在玩什麼遊戲並且不關心你玩了多長時間樣本,諾沃卡因是不可知的。爲了循環播放聲音,您必須保持已經過多少個樣本的計數,檢查您是否處於聲音末尾,然後將該計數設置回0.

  7. 是的。假設44.1k採樣率,1秒音頻= 44100個採樣。當它達到44100時,您將重置您的計數。

  8. 是的。它看起來像這樣。假設你有4首單聲道,長於1秒的吉他聲音,並且你已經將它們讀入內存float *guitarC,*guitarE,*guitarG,*guitarB;(jazzy CMaj7和絃w00t),並且想要將它們混合1秒鐘並循環回去單聲道:

[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負載波動時,您可能會遇到時序不規律。如果你想採樣準確的時間使用音頻塊。
相關問題