2010-01-14 13 views
17

Mi piacerebbe costruire un sintetizzatore per l'iPhone. Capisco che è possibile utilizzare unità audio personalizzate per l'iPhone. A prima vista, questo sembra promettente, dal momento che ci sono molte e molte risorse di programmazione Audio Unit disponibili. Tuttavia, l'uso di unità audio personalizzate su iPhone sembra un po 'complicato (vedi: http://lists.apple.com/archives/Coreaudio-api/2008/Nov/msg00262.html)da dove iniziare con sintesi audio su iPhone

Questo sembra il tipo di cosa che un sacco di gente deve fare, ma una semplice ricerca su google per "sintesi audio iphone" non lo fa t visualizzare qualsiasi cosa sulla falsariga di un semplice tutorial o di un toolkit consigliato.

Quindi, qui qualcuno ha esperienza nel sintetizzare il suono su iPhone? Le unità audio personalizzate sono la strada da percorrere, oppure esiste un altro approccio più semplice che dovrei prendere in considerazione?

+0

su Mac OS l'uso di Audio Units è davvero conveniente e offre un sacco di funzionalità. Sfortunatamente sembra che non ci sia quasi nulla di implementato su iPhone. Ci sono alcuni AU di base, ma da nessuna parte vicino a ciò che è disponibile su MacOS. Ho fatto un bug un anno fa, ma non ho ancora fatto progressi. –

+0

Ho cercato su Google l'unità audio e ho trovato qualcuno che parlava dell'utilizzo di AU personalizzate su iPhone, ma sembra un po 'peloso. – morgancodes

risposta

21

Sto anche indagando su questo. Penso che l'API AudioQueue sia probabilmente la strada da percorrere.

Ecco fino a che ho ottenuto, sembra funzionare bene.

File: BleepMachine.h

// 
// BleepMachine.h 
// WgHeroPrototype 
// 
// Created by Andy Buchanan on 05/01/2010. 
// Copyright 2010 Andy Buchanan. All rights reserved. 
// 

#include <AudioToolbox/AudioToolbox.h> 

// Class to implement sound playback using the AudioQueue API's 
// Currently just supports playing two sine wave tones, one per 
// stereo channel. The sound data is liitle-endian signed 16-bit @ 44.1KHz 
// 
class BleepMachine 
{ 
    static void staticQueueCallback(void* userData, AudioQueueRef outAQ, AudioQueueBufferRef outBuffer) 
    { 
     BleepMachine* pThis = reinterpret_cast<BleepMachine*> (userData); 
     pThis->queueCallback(outAQ, outBuffer); 
    } 
    void queueCallback(AudioQueueRef outAQ, AudioQueueBufferRef outBuffer); 

    AudioStreamBasicDescription m_outFormat; 

    AudioQueueRef m_outAQ; 

    enum 
    { 
     kBufferSizeInFrames = 512, 
     kNumBuffers = 4, 
     kSampleRate = 44100, 
    }; 

    AudioQueueBufferRef m_buffers[kNumBuffers]; 

    bool m_isInitialised; 

    struct Wave 
    { 
     Wave(): volume(1.f), phase(0.f), frequency(0.f), fStep(0.f) {} 
     float volume; 
     float phase; 
     float frequency; 
     float fStep; 
    }; 

    enum 
    { 
     kLeftWave = 0, 
     kRightWave = 1, 
     kNumWaves, 
    }; 

    Wave m_waves[kNumWaves]; 

public: 
    BleepMachine(); 
    ~BleepMachine(); 

    bool Initialise(); 
    void Shutdown(); 

    bool Start(); 
    bool Stop(); 

    bool SetWave(int id, float frequency, float volume); 
}; 

// Notes by name. Integer value is number of semitones above A. 
enum Note 
{ 
    A  = 0, 
    Asharp, 
    B, 
    C, 
    Csharp, 
    D, 
    Dsharp, 
    E, 
    F, 
    Fsharp, 
    G, 
    Gsharp, 

    Bflat = Asharp, 
    Dflat = Csharp, 
    Eflat = Dsharp, 
    Gflat = Fsharp, 
    Aflat = Gsharp, 
}; 

// Helper function calculates fundamental frequency for a given note 
float CalculateFrequencyFromNote(SInt32 semiTones, SInt32 octave=4); 
float CalculateFrequencyFromMIDINote(SInt32 midiNoteNumber); 

File: BleepMachine.mm

// 
// BleepMachine.mm 
// WgHeroPrototype 
// 
// Created by Andy Buchanan on 05/01/2010. 
// Copyright 2010 Andy Buchanan. All rights reserved. 
// 

#include "BleepMachine.h" 

void BleepMachine::queueCallback(AudioQueueRef outAQ, AudioQueueBufferRef outBuffer) 
{ 
    // Render the wave 

    // AudioQueueBufferRef is considered "opaque", but it's a reference to 
    // an AudioQueueBuffer which is not. 
    // All the samples manipulate this, so I'm not quite sure what they mean by opaque 
    // saying.... 
    SInt16* coreAudioBuffer = (SInt16*)outBuffer->mAudioData; 

    // Specify how many bytes we're providing 
    outBuffer->mAudioDataByteSize = kBufferSizeInFrames * m_outFormat.mBytesPerFrame; 

    // Generate the sine waves to Signed 16-Bit Stero interleaved (Little Endian) 
    float volumeL = m_waves[kLeftWave].volume; 
    float volumeR = m_waves[kRightWave].volume; 
    float phaseL = m_waves[kLeftWave].phase; 
    float phaseR = m_waves[kRightWave].phase; 
    float fStepL = m_waves[kLeftWave].fStep; 
    float fStepR = m_waves[kRightWave].fStep; 

    for(int s=0; s<kBufferSizeInFrames*2; s+=2) 
    { 
     float sampleL = (volumeL * sinf(phaseL)); 
     float sampleR = (volumeR * sinf(phaseR)); 

     short sampleIL = (int)(sampleL * 32767.0); 
     short sampleIR = (int)(sampleR * 32767.0); 

     coreAudioBuffer[s] = sampleIL; 
     coreAudioBuffer[s+1] = sampleIR; 

     phaseL += fStepL; 
     phaseR += fStepR; 
    } 

    m_waves[kLeftWave].phase = fmodf(phaseL, 2 * M_PI); // Take modulus to preserve precision 
    m_waves[kRightWave].phase = fmodf(phaseR, 2 * M_PI); 

    // Enqueue the buffer 
    AudioQueueEnqueueBuffer(m_outAQ, outBuffer, 0, NULL); 
} 

bool BleepMachine::SetWave(int id, float frequency, float volume) 
{ 
    if ((id < kLeftWave) || (id >= kNumWaves)) return false; 

    Wave& wave = m_waves[ id ]; 

    wave.volume = volume; 
    wave.frequency = frequency; 
    wave.fStep = 2 * M_PI * frequency/kSampleRate; 

    return true; 
} 

bool BleepMachine::Initialise() 
{ 
    m_outFormat.mSampleRate = kSampleRate; 
    m_outFormat.mFormatID = kAudioFormatLinearPCM; 
    m_outFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 
    m_outFormat.mFramesPerPacket = 1; 
    m_outFormat.mChannelsPerFrame = 2; 
    m_outFormat.mBytesPerPacket = m_outFormat.mBytesPerFrame = sizeof(UInt16) * 2; 
    m_outFormat.mBitsPerChannel = 16; 
    m_outFormat.mReserved = 0; 

    OSStatus result = AudioQueueNewOutput(
              &m_outFormat, 
              BleepMachine::staticQueueCallback, 
              this, 
              NULL, 
              NULL, 
              0, 
              &m_outAQ 
             ); 

    if (result < 0) 
    { 
     printf("ERROR: %d\n", (int)result); 
     return false; 
    } 

    // Allocate buffers for the audio 
    UInt32 bufferSizeBytes = kBufferSizeInFrames * m_outFormat.mBytesPerFrame; 

    for (int buf=0; buf<kNumBuffers; buf++) 
    { 
     OSStatus result = AudioQueueAllocateBuffer(m_outAQ, bufferSizeBytes, &m_buffers[ buf ]); 
     if (result) 
     { 
      printf("ERROR: %d\n", (int)result); 
      return false; 
     } 

     // Prime the buffers 
     queueCallback(m_outAQ, m_buffers[ buf ]); 
    } 

    m_isInitialised = true; 
    return true; 
} 

void BleepMachine::Shutdown() 
{ 
    Stop(); 

    if (m_outAQ) 
    { 
     // AudioQueueDispose also chucks any audio buffers it has 
     AudioQueueDispose(m_outAQ, true); 
    } 

    m_isInitialised = false; 
} 

BleepMachine::BleepMachine() 
: m_isInitialised(false), m_outAQ(0) 
{ 
    for (int buf=0; buf<kNumBuffers; buf++) 
    { 
     m_buffers[ buf ] = NULL; 
    } 
} 

BleepMachine::~BleepMachine() 
{ 
    Shutdown(); 
} 

bool BleepMachine::Start() 
{ 
    OSStatus result = AudioQueueSetParameter(m_outAQ, kAudioQueueParam_Volume, 1.0); 
    if (result) printf("ERROR: %d\n", (int)result); 

    // Start the queue 
    result = AudioQueueStart(m_outAQ, NULL); 
    if (result) printf("ERROR: %d\n", (int)result); 

    return true; 
} 

bool BleepMachine::Stop() 
{ 
    OSStatus result = AudioQueueStop(m_outAQ, true); 
    if (result) printf("ERROR: %d\n", (int)result); 

    return true; 
} 

// A (A4=440) 
// A# f(n)=2^(n/12) * r 
// B where n = number of semitones 
// C and r is the root frequency e.g. 440 
// C# 
// D frq -> MIDI note number 
// D# p = 69 + 12 x log2(f/440) 
// E 
// F  
// F# 
// G 
// G# 
// 
// MIDI Note ref: http://www.phys.unsw.edu.au/jw/notes.html 
// 
// MIDI Node numbers: 
// A3 57 
// A#3 58 
// B3 59 
// C4 60 <-- 
// C#4 61 
// D4 62 
// D#4 63 
// E4 64 
// F4 65 
// F#4 66 
// G4 67 
// G#4 68 
// A4 69 <-- 
// A#4 70 
// B4 71 
// C5 72 

float CalculateFrequencyFromNote(SInt32 semiTones, SInt32 octave) 
{ 
    semiTones += (12 * (octave-4)); 
    float root = 440.f; 
    float fn = powf(2.f, (float)semiTones/12.f) * root; 
    return fn; 
} 

float CalculateFrequencyFromMIDINote(SInt32 midiNoteNumber) 
{ 
    SInt32 semiTones = midiNoteNumber - 69; 
    return CalculateFrequencyFromNote(semiTones, 4); 
} 

//for (SInt32 midiNote=21; midiNote<=108; ++midiNote) 
//{ 
// printf("MIDI Note %d: %f Hz \n",(int)midiNote,CalculateFrequencyFromMIDINote(midiNote)); 
//} 

Aggiornamento: Informazioni di base l'utilizzo

  1. inizializzazione. Somehere in prossimità della partenza, sto usando initFromNib: nel mio codice

    m_bleepMachine = new BleepMachine; 
    m_bleepMachine->Initialise(); 
    m_bleepMachine->Start(); 
    
  2. Ora la riproduzione audio è in funzione, ma la generazione di silenzio.

  3. Nel codice, chiamare questo quando si desidera modificare la generazione sonora

    m_bleepMachine->SetWave(ch, frq, vol); 
    
    • dove ch è il canale (0 o 1)
    • dove frq è la frequenza di impostare in Hz
    • dove vol è il volume (0 = -Inf db, 1 = -0dB)
  4. alla conclusione del programma

    delete m_bleepMachine; 
    
+0

Grazie Andy. Molto apprezzato. Spero di trovare un toolkit che gestirà alcune di queste cose di livello inferiore per me. Vengo da SuperCollider e mi piace davvero essere in grado di collegare i generatori di unità l'uno all'altro. Ma vedere un esempio di implementazione di livello inferiore è grandioso. Domanda per principianti: qual è il.estensione mm? Inoltre, se hai voglia di incollare in un esempio di utilizzo della tua classe BleepMachine, sarei molto grato. – morgancodes

+2

.mm extension = objective-C++ –

+0

Aggiunte informazioni di base sull'utilizzo per rispondere. –

0

PD has a version che gira su iPhone, usato da RjDj. Se stai usando l'app di qualcun altro invece di crearne una tua, puoi fare un bel po 'in una scena RjDj, e c'è un set di oggetti che ti permettono di ripararlo e testarlo su un normale PD sul tuo computer .

Vorrei menzionare: PD è un linguaggio di programmazione di flusso di dati visivi, vale a dire, è completo e può essere utilizzato per sviluppare applicazioni grafiche, ma se si sta per fare qualcosa di interessante, mi piacerebbe dare un'occhiata a best practices for patching .

+0

Grazie Justin, Sicuramente voglio essere in grado di sviluppare la mia app e non essere legato a RJDJ. So che SuperCollider è compatibile con l'iPhone, ma penso che la licenza SC precluda l'utilizzo in qualsiasi tipo di software commerciale, quindi è un po 'una svolta. – morgancodes

+0

In realtà sono un contributore al supercollider e non ha tale restrizione. È GPL, il che significa che se si modifica il sorgente del supercollider, è necessario condividere tali modifiche con chiunque abbia l'app (proprio come gcc è GPL, quindi tutte le modifiche apportate a gcc per far funzionare l'app devono essere condivise) , ma se il tuo codice sta usando il supercollider ma non sta modificando il supercollider stesso, non esiste affatto tale restrizione. Detto questo, sono abbastanza sicuro che al momento puoi solo utilizzare la versione per iPhone di SC sui telefoni jailbroken, motivo per cui non l'ho suggerito. –

+0

Se ricordo correttamente, il motivo per cui sc non è disponibile attraverso l'app store è che si tratta di un vero e proprio interprete. Con questi standard dovrebbe essere escluso anche RjDj (PD è una piattaforma insolita per la programmazione, ma è un ambiente di programmazione), ma per quanto mi riguarda non dovrebbe esserlo. –

0

L'ultima volta che ho controllato non è stato possibile utilizzare AU personalizzate su iOS in modo da consentire a tutte le app installate di utilizzarlo (come su MacOS X).

Si potrebbe teoricamente utilizzare una AU personalizzata dall'interno della propria app iOS caricandola dal pacchetto dell'app e chiamando direttamente la funzione di rendering dell'AU, ma si potrebbe anche aggiungere il codice direttamente alla propria app. Inoltre, sono abbastanza sicuro che caricare e chiamare il codice che si trova in una libreria dinamica andrebbe contro le politiche di AppStore.

Quindi è necessario eseguire l'elaborazione nella richiamata IO remota o utilizzare le AU Apple preinstallate, all'interno di un AUGraph.

3

Con l'enorme avvertenza che devo ancora superare tutta la documentazione o terminare la navigazione di alcune classi/codice di esempio, sembra che i bravi ragazzi di CCRMA di Stanford possano aver messo insieme dei bei kit di strumenti per il nostro piacere di hacking audio . Nessuna garanzia questi faranno esattamente quello che vuoi, ma sulla base di ciò che so sull'SK originale, dovrebbero fare il trucco. Sto per imbarcarmi su un'app di audio synth e più codice posso riutilizzare, meglio è.

Links/descrizioni da loro sito ...

MoMu: MoMu è un toolkit software leggero per la creazione di strumenti musicali ed esperienze su dispositivi mobili, e attualmente supporta la piattaforma iPhone (iPhone, iPad, iPod Touch). MoMu fornisce API per audio full-duplex in tempo reale, accelerometro, posizione, multi-touch, networking (tramite OpenSoundControl), grafica e utility. (Bla bla)

• e •

MoMu STK: Il rilascio MoMu del Toolkit Synthesis (STK, in origine da Perry R. Cook e Gary P. Scavone) è una versione leggermente modificata del STK 4.4.2, e attualmente supporta la piattaforma iPhone (iPhone, iPad, iPod Touches).

+0

Grazie Eric. STK è quello che ho finito usando per la mia app, Thicket. Non sai quanta parte di una gamba MoMu ti darà. Ho trovato abbastanza facile usare solo alcune classi STK e scrivere direttamente al callback di RemoteIO. Non del tutto sicuro di quale sia il punto di Momu. La maggior parte delle cose che sembra offrire è abbastanza facile da fare direttamente attraverso le classi di Cocoa (con l'accettazione di OSC). – morgancodes

+1

Un piccolo consiglio riguardo a stk: Ho trovato la scorsa notte che ottengo un aumento delle prestazioni del 15% se cambio StkFloat per usare float invece di double. Inoltre, questo sarà ovvio per chiunque abbia esperienza nell'elaborazione del segnale, ma non era per me - è molto più efficiente avere ogni procedura di chiamata processare un buffer di campioni piuttosto che un singolo campione. – morgancodes

+0

+1 Buona chiamata Eric! Piacere di incontrare il tuo nome su Stack Overflow. -montag –

16

Dal mio post originale quasi un anno fa, ho fatto molta strada. Dopo una ricerca piuttosto esaustiva, mi sono inventato pochissimi strumenti di sintesi di alto livello adatti allo sviluppo di iOS. Ce ne sono molti con licenza GPL, ma la licenza GPL è troppo restrittiva per farmi sentire a mio agio nell'utilizzarlo. LibPD funziona alla grande, ed è ciò che rjdj usa, ma mi sono trovato davvero frustrato dal paradigma di programmazione grafica. Il motore basato su c di JSyn, csyn, è un'opzione, ma richiede la licenza e sono abituato a programmare con strumenti open source. Però vale la pena dare un'occhiata da vicino.

Alla fine, sto usando STK come framework di base. STK è uno strumento di livello molto basso e richiede un'estesa programmazione a livello di buffer per funzionare. Questo è in contrasto con qualcosa di più alto livello come PD o SuperCollider, che ti permette semplicemente di collegare i generatori di unità insieme e non preoccuparti di gestire i dati audio grezzi.

Lavorare in questo modo con STK è sicuramente un po 'più lento rispetto a uno strumento di alto livello, ma mi sto abituando. Soprattutto ora che sto diventando più a mio agio con la programmazione C/C++ in generale.

C'è un nuovo progetto in corso per creare un add-on in stile patch su Open Frameworks. Si chiama Cleo, penso, fuori dall'università di Vancouver. Non è ancora stato rilasciato, ma sembra un bel mix di connessione in stile patch di generatori di unità in C++ piuttosto che richiedere l'uso di un'altra lingua. Ed è strettamente integrato con Open Frameworks, che può essere attraente o meno, a seconda.

Quindi, per rispondere alla mia domanda iniziale, per prima cosa è necessario imparare a scrivere sul buffer di output.Ecco alcuni esempi di codice buono per questo:

http://atastypixel.com/blog/using-remoteio-audio-unit/

Allora avete bisogno di fare un po 'la sintesi per generare i dati audio. Se ti piacciono le patch, non esiterei a raccomandare libpd. Sembra funzionare alla grande e tu puoi lavorare nel modo in cui sei abituato. Se si odia la patch grafica (come me), il miglior punto di partenza per ora è probabilmente STK. Se la STK e la programmazione audio di basso livello ti sembrano un po 'troppo alte (come per me), rimboccati le maniche, metti in valigia una tenda e inizia la lunga escursione sulla curva di apprendimento. Alla fine sarai un programmatore molto migliore.

Un altro consiglio che avrei potuto darmi un anno fa: iscrivermi alla mailing list di Core Audio di Apple.

============== 2014 Modifica ===========

Ora sto utilizzando (e contribuendo attivamente a) la sintesi audio Tonic biblioteca. È fantastico, se non lo dico anch'io.

+0

grazie per aver condiviso le tue esperienze – johnbakers

+0

Grazie per l'aggiornamento. –

+1

C'è qualche documentazione su Tonic oltre al README? Mi piacerebbe usarlo per un'app per iOS. Grazie per il lavoro. – guardabrazo

1
+0

Inoltre, questa guida è fantastica: http://timbolstad.com/2010/03/14/core-audio-getting-started/ –

1

io sono uno degli altri contribuenti al Tonic insieme con morgancodes. Per la discussione di CoreAudio in un framework di livello superiore, non posso elogiare abbastanza il numero The Amazing Audio Engine.

Abbiamo usato entrambi in tandem con Tonic in numerosi progetti. Ci vuole molto del dolore per interagire con CoreAudio direttamente, permettendoti di concentrarti sul contenuto e sulla sintesi effettivi invece del livello di astrazione hardware.

1

Ultimamente sto usando AudioKit

Si tratta di un wrapper fresco e ben progettato oltre CSound che è stato intorno per le età

stavo usando tonico con openFrameworks e mi trovavo io stesso manca la programmazione in rapida.

Anche se tonica e openFrameworks sono entrambi strumenti potenti,

che ho scelto per arrivare a letto con rapido

+0

Aggiornamento: sono tornato ai buoni vecchi framework aperti dopo che non stavo ottenendo il tipo di suono che volevo /// –

+0

Ciao Paul, in realtà sono uno degli sviluppatori di AudioKit ... mi spiace che non avevi il tipo di suono che cercavi. Mi piacerebbe sapere di più su cosa stai cercando con AudioKit ... sentiti libero di mandarmi una mail a nick (at) audiokit (punto) io – narner

Problemi correlati