с чего начать синтез звука на айфоне - PullRequest
17 голосов
/ 14 января 2010

Я бы хотел создать синтезатор для iPhone. Я понимаю, что для iPhone можно использовать собственные аудиоустройства. На первый взгляд, это звучит многообещающе, так как доступно множество ресурсов программирования Audio Unit. Однако использование пользовательских аудиоустройств на iPhone кажется немного сложным (см .: http://lists.apple.com/archives/Coreaudio-api/2008/Nov/msg00262.html)

Это похоже на то, что должны делать многие люди, но простой поиск в Google по запросу "iphone audio synthes" не дает ничего общего с хорошим и простым руководством или рекомендуемым набором инструментов. 1004 *

Так, у кого-нибудь здесь есть опыт синтезирования звука на iPhone? Можно ли использовать нестандартные аудиоустройства или есть другой, более простой подход, который я должен рассмотреть?

Ответы [ 8 ]

21 голосов
/ 15 января 2010

Я тоже расследую это. Я думаю, что 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. Initialise. Где-то в начале, я использую initFromNib: в моем коде

    m_bleepMachine = new BleepMachine;
    m_bleepMachine->Initialise();
    m_bleepMachine->Start();
    
  2. Теперь воспроизводится звук, но генерируется тишина.

  3. В своем коде вызывайте его, когда хотите изменить генерацию тона

    m_bleepMachine->SetWave( ch, frq, vol );
    
    • где ch - канал (0 или 1)
    • где frq - частота, устанавливаемая в Гц
    • где vol - объем (0 = -Inf дБ, 1 = -0 дБ)
  4. При завершении программы

    delete m_bleepMachine;
    
16 голосов
/ 24 февраля 2011

С момента моего первоначального поста почти год назад я прошел большой путь. После довольно тщательного поиска я нашел очень мало инструментов синтеза высокого уровня, подходящих для разработки под iOS. Многие имеют лицензию GPL, но лицензия GPL слишком ограничительна, чтобы я чувствовал себя комфортно при ее использовании. LibPD прекрасно работает, и это то, что использует rjdj, но я оказался очень разочарован парадигмой графического программирования. Движок JSyn на основе c, csyn, является опцией, но требует лицензирования, и я действительно привык к программированию с помощью инструментов с открытым исходным кодом. Тем не менее, стоит посмотреть поближе.

В конце концов, я использую STK в качестве базового фреймворка. STK - инструмент очень низкого уровня, и для его работы требуется обширное программирование на уровне буфера. Это отличается от чего-то более высокого уровня, такого как PD или SuperCollider, который позволяет просто соединять генераторы модулей и не беспокоиться об обработке необработанных аудиоданных.

Работать таким образом с STK, конечно, немного медленнее, чем с высокоуровневым инструментом, но я чувствую себя комфортно с ним. Особенно сейчас, когда я все больше чувствую себя комфортно в программировании на C / C ++.

Идет новый проект по созданию дополнения в стиле исправлений для Open Frameworks. Это называется Клео, я думаю, из Университета Ванкувера. Он еще не был выпущен, но выглядит как очень приятное сочетание патч-стилей соединения генераторов модулей в C ++, а не требует использования другого языка. И он тесно интегрирован с Open Frameworks, что может быть привлекательным или нет, в зависимости.

Итак, чтобы ответить на мой оригинальный вопрос, сначала вам нужно научиться писать в выходной буфер. Вот хороший пример кода для этого:

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

Затем вам нужно сделать синтез, чтобы сгенерировать аудиоданные. Если вам нравится патчить, я без колебаний рекомендую libpd . Кажется, он работает отлично, и вы можете работать так, как привыкли. Если вы ненавидите графические патчи (как и я), ваше лучшее начальное место на данный момент, вероятно, STK . Если STK и низкоуровневое звуковое программирование кажутся вам не совсем понятными (как это было для меня), просто засучите рукава, возьмите палатку и приступайте к долгому подъему по кривой обучения. В конце концов, вы станете намного лучшим программистом.

Еще один совет, который я хотел бы дать себе год назад: присоединиться к списку рассылки Apple Core Audio.

============== Редактирование 2014 года ===========

Сейчас я использую (и активно помогаю) библиотеку аудиосинтеза Tonic . Это здорово, если я сам этого не скажу.

3 голосов
/ 23 февраля 2011

С огромным предостережением, которое мне еще предстоит пройти через всю документацию или закончить просмотр некоторых классов / примеров кода, похоже, что прекрасные люди из CCRMA в Стэнфорде, возможно, собрали несколько хороших наборов инструментов для нашего удовольствия от взлома аудио. Нет гарантий, что они будут делать именно то, что вы хотите, но, основываясь на том, что я знаю об оригинальном STK, они должны сделать свое дело. Я собираюсь приступить к созданию приложения для синтезатора звука, и чем больше кода я смогу использовать повторно, тем лучше.

Ссылки / описания с их сайта ...

MoMu : MoMu - это легкий программный инструментарий для создания музыкальных инструментов и впечатлений на мобильном устройстве, который в настоящее время поддерживает платформу iPhone (iPhone, iPad, iPod Touches). MoMu предоставляет API для полнодуплексного звука в реальном времени, акселерометра, определения местоположения, мультитач, работы в сети (через OpenSoundControl), графики и утилит. (Яда Яда)

• и •

MoMu STK : версия MoMu Synthesis Toolkit (STK, первоначально разработанная Perry R. Cook и Gary P. Scavone) является слегка измененной версией STK 4.4.2 и в настоящее время поддерживает iPhone платформа (iPhone, iPad, iPod Touch).

1 голос
/ 03 апреля 2015

В последнее время я использую AudioKit

Это новая и хорошо продуманная оболочка для CSound , которая существует уже целую вечность

Я использовал тоник с openframeworks, и мне не хватало программирования в swift.

Хотя тонические и открытые рамки являются мощными инструментами,

Я решил лечь в постель с быстрым

1 голос
/ 06 марта 2014

Я один из других авторов Tonic вместе с morgancodes. Я не могу похвалить CoreAudio в высокоуровневой инфраструктуре The Amazing Audio Engine .

Мы оба использовали его вместе с Тоником в ряде проектов. Это решает проблему работы с CoreAudio напрямую, позволяя вам сосредоточиться на реальном контенте и синтезе, а не на уровне аппаратной абстракции.

1 голос
/ 27 февраля 2011

Я только начинаю программировать Audio Unit для iPhone, чтобы создать приложение, похожее на синтезатор. Руководство Apple «Руководство по хостингу аудиоустройств для iOS» выглядит как хороший справочник:

http://developer.apple.com/library/ios/#documentation/MusicAudio/Conceptual/AudioUnitHostingGuide_iOS/AudioUnitHostingFundamentals/AudioUnitHostingFundamentals.html#//apple_ref/doc/uid/TP40009492-CH3-SW11

Руководство содержит ссылки на несколько примеров проектов. Аудио микшер (MixerHost) и aurioTouch:

http://developer.apple.com/library/ios/samplecode/MixerHost/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010210

http://developer.apple.com/library/ios/samplecode/aurioTouch/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007770

0 голосов
/ 18 февраля 2011

В прошлый раз я проверял, что вы не можете использовать пользовательские AU на iOS таким образом, чтобы все установленные приложения могли использовать его (как в MacOS X).

Теоретически вы можете использовать пользовательский AU из своего приложения iOS, загрузив его из пакета приложения и напрямую вызвав функцию рендеринга AU, но затем вы также можете добавить код непосредственно в ваше приложение. Кроме того, я почти уверен, что загрузка и вызов кода, находящегося в динамической библиотеке, будут противоречить политикам AppStore.

Таким образом, вам придется либо выполнять обработку в вашем обратном вызове удаленного ввода-вывода, либо использовать предустановленные Apple AU в AUGraph.

0 голосов
/ 20 января 2010

PD имеет версию , которая работает на iphone, используется RjDj . Если вы согласны использовать чужое приложение, а не писать свое, вы можете сделать немало в сцене RjDj, и есть набор объектов, которые позволяют вам его исправить и протестировать на обычном PD на вашем компьютере. .

Я должен упомянуть: PD - это язык программирования визуальных потоков данных, то есть он полностью готов и может быть использован для разработки графических приложений - но если вы собираетесь делать что-то интересное, я определенно посмотрю на лучшие практики по исправлению ошибок .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...