2012-11-08 5 views
5

Sto costruendo un'applicazione per iPhone che genera musica di chitarra casuale riproducendo le note di chitarra registrate individualmente in formato "caf". Queste note variano in durata da 3 a 11 secondi, a seconda della quantità di sustain.Uso di Novocaine in un'app audio

Originariamente utilizzavo AVAudioPlayer per la riproduzione, e nel simulatore a 120 bpm, suonavo le note da 16 °, cantato magnificamente, ma sul mio telefono, non appena ho aumentato il tempo di poco superiore a 60 bpm riproducendo solo 1/4 osserva, correva come un cane e non voleva restare in tempo. La mia esaltazione è stata molto breve.

Per ridurre la latenza, ho provato a implementare la riproduzione tramite unità audio utilizzando il progetto Apple MixerHost come modello per un motore audio, ma continuavo a ricevere un errore di accesso errato dopo averlo spinto e collegato tutto.

Dopo molte ore di riflessione, ho rinunciato a quella linea di pensiero e ho optato invece per il motore audio Novocaine.

Ora mi sono imbattuto in un muro di mattoni cercando di collegarlo al mio modello.

Sul livello più elementare, il mio modello è un oggetto Neck contenente un oggetto NSDictionary of Note.

Ogni oggetto Note sa quale stringa e tasto del manico della chitarra è acceso e contiene il proprio AVAudioPlayer.

Costruisco un manico della chitarra cromatico contenente 122 note (6 corde per 22 tasti) o 144 note (6 corde per 24 tasti) a seconda della dimensione del collo selezionata nelle preferenze dell'utente.

Io uso queste note come il mio unico punto di verità in modo che tutte le note scalari generate dal motore musicale siano puntatori a questo secchio di note cromatiche.

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

Parto sempre fuori la riproduzione con la radice nota o un accordo della scala selezionata e quindi generare la nota di giocare la prossima così io sono sempre giocando una nota dietro la nota generata. In questo modo, la prossima nota da giocare è sempre pronta per essere pronta.

il controllo della riproduzione di queste note è un ottenuta con il seguente codice:

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

di ciascuna nota AVAudioPlayer viene caricato come segue:

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

Qui è dove vengo un fiasco.

Secondo la pagina Novocaine Github ...

Riproduzione Audio

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

Ma nel progetto scaricato, è possibile utilizzare il seguente codice per caricare l'audio ...

// 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); 
}]; 

Qui è dove davvero inizio a essere confuso perché il primo metodo utilizza un float e il secondo utilizza un URL.

Come si passa un file "caf" a un float? Non sono sicuro di come implementare Novocaine - è ancora confusa nella mia testa.

Le mie domande che spero che qualcuno mi può aiutare con sono i seguenti ...

  1. Are Novocaine oggetti simili agli oggetti AVAudioPlayer, solo più versatili e ottimizzato al massimo per una latenza minima? cioè unità di riproduzione audio (/ registrazione/generazione) autonoma?

  2. Posso utilizzare Novocaine nel mio modello così com'è? vale a dire 1 oggetto di Novocaine per nota cromatica o dovrei avere 1 oggetto novocain che contiene tutte le note cromatiche? O semplicemente memorizzo l'URL nella nota e lo trasmetto a un giocatore di Novocaine?

  3. Come posso mettere il mio audio in "audioToPlay" quando il mio audio è un file "caf" e "audioToPlay" prendere un float?

  4. Se includo e dichiaro una proprietà Novocaine in Note.m, devo quindi rinominare la classe in Note.mm per utilizzare l'oggetto Novocaine?

  5. Come si riproducono più oggetti Novocaine contemporaneamente per riprodurre accordi e intervalli?

  6. Posso eseguire il loop della riproduzione di un oggetto Novocaine?

  7. Posso impostare la lunghezza di riproduzione di una nota? Ad esempio, riprodurre una nota di 10 secondi per solo 1 secondo?

  8. Posso modificare il codice sopra riportato per usare Novocaine?

  9. Il metodo che sto utilizzando per runMusicGenerator è quello corretto da utilizzare per mantenere un tempo che sia conforme agli standard professionali?

risposta

10

La novocaina semplifica la vita eliminando la necessità di impostare manualmente RemoteIO AudioUnit. Ciò include il dover riempire dolorosamente un sacco di strutture CoreAudio e fornire una serie di callback come questo callback del processo audio.

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

Invece Novocaine maniglie che nella sua attuazione e quindi chiama il tuo blocco, che si imposta in questo modo.

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

Qualunque cosa si scrive a audioToPlay viene giocato.

  1. Novocaine imposta RemoteIO AudioUnit per voi. Questa è un'API CoreAudio di basso livello, diversa dalla AVFoundation di alto livello e molto a bassa latenza come previsto. Hai ragione, in quella Novocaina è autosufficiente. È possibile registrare, generare ed elaborare l'audio in tempo reale.

  2. La novocaina è un singleton, non è possibile avere più istanze di Novocaine. Un modo per farlo è quello di memorizzare il suono/i suoni della chitarra in una classe o matrice separata, e quindi scrivere una serie di metodi, utilizzando Novocaine per riprodurli.

  3. Hai un sacco di opzioni. Puoi utilizzare Novocaine's AudioFileReader per riprodurre il tuo file .caf per te. Lo fai allocando un AudioFileReader e quindi passando l'URL del file.file caf che si desidera riprodurre, come nel codice di esempio. Quindi inserisci il numero [fileReader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels] nel blocco, come nel codice di esempio. Ogni volta che viene chiamato il blocco, AudioFileReader cattura e memorizza un blocco di audio dal disco e lo inserisce in audioToPlay che viene successivamente riprodotto. Ci sono alcuni svantaggi con questo. Per i suoni corti (come il suono della chitarra che presumo) ripetutamente chiamando retrieveFreshAudio è un successo in termini di prestazioni. In genere è un'idea migliore (per i suoni brevi) eseguire una lettura sequenziale e sincronizzata dell'intero file in memoria. La novocaina non fornisce un modo per farlo (ancora). Dovrai usare ExtAudioFileServices per fare questo. Il progetto di esempio di Apple MixerHost spiega come farlo.

  4. Se si utilizza AudioFileReader sì. Si rinomina in .mm solo quando si è #import dalle intestazioni Obj-C++ o #include in intestazioni C++.

  5. Come accennato in precedenza, è consentita solo 1 istanza di Novocaine. È possibile ottenere polifonia mescolando più fonti audio. Si tratta semplicemente di aggiungere i buffer insieme. Se hai realizzato più versioni dello stesso suono di chitarra a diversi pitch, basta leggerle tutte in memoria e mescolare. Se vuoi solo avere un suono di chitarra, allora devi, in tempo reale, cambiare la velocità di riproduzione di molte note che stai suonando e poi del mixdown.

  6. La novocaina è agnostica a quello che stai suonando e non importa per quanto tempo stai suonando un campione. Per riprodurre un suono, devi mantenere un conteggio di quanti campioni sono passati, controlla se sei alla fine del tuo suono, quindi imposta nuovamente il conteggio a 0.

  7. Sì. Supponendo una frequenza di campionamento di 44.1k, 1 secondo di audio = 44100 campioni. Dovresti quindi reimpostare il conteggio quando raggiunge 44100.

  8. Sì. Sembra qualcosa del genere. Supponendo che tu abbia 4 suoni di chitarra che sono mono e lunghi più di 1 secondo, e li hai letti nella memoria float *guitarC, *guitarE, *guitarG, *guitarB; (accordo jazzy CMaj7 w00t), e vuoi mescolarli per 1 secondo e farli tornare indietro in mono:

[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 
    } 
}]; 
    Non
  1. esattamente. L'utilizzo di performSelector o di qualsiasi meccanismo pianificato su un runloop o thread non è garantito per la precisione. Ad esempio, potresti riscontrare irregolarità di temporizzazione quando il carico della CPU fluttua. Usa il blocco audio se vuoi un timing accurato del campione.