从哪里开始在iPhone上进行音频合成

时间:2010-01-14 20:16:10

标签: iphone audio

我想为iPhone制作一个合成器。我知道可以为iPhone使用自定义音频单元。乍一看,这听起来很有希望,因为有很多可用的音频单元编程资源。但是,在iPhone上使用自定义音频设备似乎有点棘手(参见:http://lists.apple.com/archives/Coreaudio-api/2008/Nov/msg00262.html

这似乎是很多人必须做的事情,但是一个简单的谷歌搜索“iphone音频合成”并没有出现任何简单的教程或推荐的工具包。< / p>

那么,这里的任何人都有在iPhone上合成声音的经验吗?自定义音频单元是可行的,还是我应该考虑采用另一种更简单的方法?

8 个答案:

答案 0 :(得分:21)

我也正在研究这个问题。我认为AudioQueue API可能就是这样。

就我而言,似乎工作正常。

文件: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 );

文件: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 ) );
//}

更新:基本使用信息

  1. 初始化。接近开始,我在我的代码中使用initFromNib:

    m_bleepMachine = new BleepMachine;
    m_bleepMachine->Initialise();
    m_bleepMachine->Start();
    
  2. 现在声音播放正在运行,但却产生了静音。

  3. 在您的代码中,当您想要更改音调生成时调用此方法

    m_bleepMachine->SetWave( ch, frq, vol );
    
    • 其中ch是通道(0或1)
    • 其中frq是以Hz为单位设置的频率
    • 其中vol是音量(0 = -Inf db,1 = -0db)
  4. 在程序终止时

    delete m_bleepMachine;
    

答案 1 :(得分:16)

自我近一年前的原帖以来,我已经走了很长的路。经过一次非常详尽的搜索,我提出了很少适合iOS开发的高级综合工具。有许多是GPL许可,但GPL许可证限制太多,我觉得使用它很舒服。 LibPD工作得很好,是rjdj使用的,但我发现自己对图形编程范例感到非常沮丧。 JSyn的基于c的引擎csyn是一个选项,但它需要许可,我真的习惯使用开源工具进行编程。它看起来确实值得一看。

最后,我使用STK作为我的基本框架。 STK是一种非常低级的工具,需要大量的缓冲级编程才能正常工作。这与PD或SuperCollider等更高级别相反,它允许您简单地将单位发生器插在一起,而不用担心处理原始音频数据。

使用STK这种方式肯定比使用高级工具慢一点,但我对此感到满意。特别是现在我对C / C ++编程感到越来越熟悉。

正在开发一个新项目来为Open Frameworks创建补丁式添加。在温哥华大学,我认为它叫做Cleo。它尚未发布,但它看起来像C ++中单元生成器的修补样式连接的非常好的混合,而不是要求使用另一种语言。并且它与Open Frameworks紧密集成,这可能具有吸引力或不吸引人。

因此,要回答我原来的问题,首先需要学习如何写入输出缓冲区。以下是一些很好的示例代码:

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

然后你需要做一些综合来生成音频数据。如果你喜欢打补丁,我会毫不犹豫地推荐libpd。它似乎工作得很好,你可以按照习惯的方式工作。如果你讨厌图形修补(像我一样),你现在最好的起点可能是STK。如果STK和低级音频编程看起来有点过头(就像对我来说那样),只需卷起袖子,打个帐篷,然后在学习曲线上进行一次长途徒步。你最终会成为一名更好的程序员。

我希望我能在一年前给自己一些建议:加入Apple的Core Audio邮件列表。

============== 2014年编辑===========

我现在正在使用(并积极参与)Tonic音频合成库。如果我自己不这么说的话,这太棒了。

答案 2 :(得分:3)

由于我还没有通过所有文档或完成浏览一些类/示例代码的巨大警告,看起来CCRMA在斯坦福大学的优秀人员可能已经为我们的音频黑客乐趣放了一些漂亮的工具包。不保证这些会完全符合您的要求,但根据我对原始STK的了解,他们应该做到这一点。我即将开始使用音频合成器应用程序,我可以重用的代码越多越好。

来自其网站的链接/说明......

MoMu:MoMu是一款轻量级软件工具包,用于在移动设备上创建乐器和体验,目前支持iPhone平台(iPhone,iPad,iPod Touches)。 MoMu提供实时全双工音频,加速度计,定位,多点触控,网络(通过OpenSoundControl),图形和实用程序的API。 (yada yada)

•和•

MoMu STK:合成工具包的MoMu版本(STK,最初由Perry R. Cook和Gary P. Scavone完成)是STK 4.4.2的轻微修改版本,目前支持iPhone平台(iPhone ,iPad,iPod Touches)。

答案 3 :(得分:1)

答案 4 :(得分:1)

我是Tonic和morgancodes的其他贡献者之一。为了在更高级别的框架中对CoreAudio进行争论,我无法对The Amazing Audio Engine给予足够的赞扬。

我们在许多项目中都将它与Tonic结合使用。直接处理CoreAudio需要花费很多精力,让你专注于实际的内容和综合,而不是硬件抽象层。

答案 5 :(得分:1)

最近我一直在使用AudioKit

这是一个新鲜且设计精良的包裹物CSound已经存在了很长时间

我正在使用openframeworks的补品,我发现自己在swift中缺少编程。

虽然补品和openframeworks都是强大的工具,

我选择快速上床

答案 6 :(得分:0)

运行在iphone上的

PD has a version,由RjDj使用。如果您可以使用别人的应用程序而不是编写自己的应用程序,那么您可以在RjDj场景中做很多事情,并且有一组对象可以让您将其修补并在您自己的计算机上的常规PD上进行测试

我应该提一下:PD是一种可视化的数据流编程语言,也就是说,它是图灵完整的,可用于开发图形应用程序 - 但如果你要做任何有趣的事情,我肯定会研究{{ 3}}

答案 7 :(得分:0)

上次检查时,您无法在iOS上使用自定义AU,以便允许所有已安装的应用程序使用它(例如在MacOS X上)。

理论上,您可以从iOS应用程序中使用自定义AU,方法是从应用程序包中加载它并直接调用AU的渲染功能,但您也可以将代码直接添加到您的应用程序中。此外,我非常确定加载和调用位于动态库中的代码会违反AppStore策略。

因此,您必须在远程IO回调中进行处理,或者在AUGraph中使用预先安装的Apple AU。