首页 > 解决方案 > 是否有一个简单的振幅表可以用来为乐器生成音符?

问题描述

我想为合成音符生成 1 秒波样本。我通常的蛮力策略有太多组合来解决这个问题。

这可以使用加法方法完成,还是比下面的示例复杂得多。

样本 = Amp[0]*sin( freq ) + Amp[1] sin( freq 2 ) + Amp[2] sin( freq 3 ) 等等...

生成一个音符:我的目标:flute_f_amp_C = { 1.0f, .... }; 如果 Tandy 可以在 1984 年做到这一点,为什么在 2021 年会如此神秘

我的生成代码看起来像这样,但我想输入长笛、钢琴、单簧管等的振幅值......

#define ampstrument000A { 1.0f } // equal blend of all tones
#define ampstrument000B { 1.0f, 0.0f } // fundamental tone only
#define ampstrument000C { 0.0f, 1.0f } // omit fundamental tone

#define ampstrument001 { 1.0f,  0.0f, 0.45f, 0.10f, 0.74f, 0.0f, 0.5f, 0.20f, 0.1f, 0.20f }; // bees
#define ampstrument002 { 1.00f, 0.00f, 0.00f, 0.65f, 0.00f, 0.33f, 0.0f, 0.00f, 0.00f, 0.00f }; // keyboard
#define ampstrument003 { 1.00f, 0.00f, 0.00f, 0.66f, 0.00f, 0.33f, 0.0f, 0.11f, 0.00f, 0.00f }; // ???
#define ampstrument004 { 1.00f, 0.00f, 0.00f, 0.11f, 0.00f, 0.66f, 0.0f, 0.33f, 0.00f, 0.00f }; // ???
#define ampstrument005 { 1.00f, 0.00f, 0.00f, 0.11f, 0.00f, 0.33f, 0.0f, 0.66f, 0.00f, 0.00f }; // ???
#define ampstrument006 { 1.00f, 0.00f, 0.00f, 0.33f, 0.00f, 0.33f, 0.0f, 0.33f, 0.00f, 0.00f }; // ???
#define ampstrument007 { 1.00f, 0.00f, 0.6666f, 0.00f, 0.3333f, 0.00f, 0.1111f, 0.00f, 0.00f, 0.00f }; // piano like
#define ampstrument008 { 1.00f, 0.00f, 0.1111f, 0.00f, 0.3333f, 0.00f, 0.6666f, 0.00f, 0.00f, 0.00f }; // wind like

我选择了 66%、33%、11% 的随机组合,似乎能产生悦耳的声音。最有效的似乎是素数 index-1,除了 index [2-1]。

示例:sample = Amp[1-1] sin( freq 1 ) + Amp[3-1] sin( freq 3 )+ Amp[5-1] sin( freq 5 ) + Amp[7-1] sin( freq 7 )。

#define NOTE_SAMPLE_RATE 44100

typedef struct pcm2chan {
 int16_t left = 0;
 int16_t right = 0;
} pcm2chan;

class xa_notes // public : xa_instrument
{

private:

bool ready = false;

bool IsPlaying = false;

UINT32 nSampleRate = NOTE_SAMPLE_RATE;

pcm2chan data[NOTE_SAMPLE_RATE] = { 0 };

WAVEFORMATEX xa_wfx = { 0};

XAUDIO2_BUFFER xa_buff = { 0 };

IXAudio2SourceVoice *ifc_source_voice = NULL;

IXAudio2 **ext_engine_ptr = NULL; // provided by the instrument object

public:
 
// TBD: these need to be global or static or a part of the instrument class 
const double table_C[10]  = ampstrument007;
const double table_Cs[10] = ampstrument007;
const double table_D[10]  = ampstrument007;
const double table_Ds[10] = ampstrument007;
const double table_E[10]  = ampstrument007;
const double table_F[10]  = ampstrument007;
const double table_Fs[10] = ampstrument007;
const double table_G[10]  = ampstrument007;
const double table_Gs[10] = ampstrument007;
const double table_A[10]  = ampstrument007;
const double table_As[10] = ampstrument007;
const double table_B[10]  = ampstrument007;

// my constructor looks like this

xa_notes(IXAudio2 **ext_Engine, double fund_freq, double *amp_table) 
{

 // validate the xaudio engine pointer
 if (ext_Engine == NULL) { return }

 if ((*ext_Engine) == NULL) { return; }

 ext_engine_ptr = ext_Engine;

 float vol = 0.65f * (float)0x7FFF; // 65 percent volume

float t = 0.0f;

float dt = (1.0f / (float)nSampleRate);

float s_2pi = 2.0f * M_PI;

float sample = 0.0f;

for (int a = 0; a < nSampleRate; ++a)
{
   sample = 0.0f;

   for (int b = 0; b < 10; ++b)
   {
       // TBD: may need to omit fundamental frequencies below 20hz
       
       tone = (fund_freq * (float)(b+1) * s_2pi * t); 
       
       sample += amp_table[b] * sin( tone );
   }

   data[a].left  = (int16_t)roundf(sample * vol);
   data[a].right = (int16_t)roundf(sample * vol);
   
   t += dt;
}

// create the buffer structure that stays with the object
    xa_buff.AudioBytes = (nSampleRate * sizeof(pcm2chan)); 
    xa_buff.pAudioData = (BYTE*)data;

    xa_buff.Flags = XAUDIO2_END_OF_STREAM;
    xa_buff.LoopBegin = 0;
    xa_buff.LoopCount = 0;
    xa_buff.LoopLength = 0;
    xa_buff.pContext = (void*)this; // used by the instruments callbacks
    xa_buff.PlayBegin = 0;
    xa_buff.PlayLength = 0;

// create WAVEFORMATEX structure
    xa_wfx.cbSize = 0; // basic and no extra data attached to xa_wfx

    xa_wfx.nAvgBytesPerSec = (nSampleRate * 2 * 16 / 8); //BYTE_RATE;
    xa_wfx.nBlockAlign = 4; //BLOCK_ALIGN;
    xa_wfx.nChannels = 2; // 2 channels
    xa_wfx.nSamplesPerSec = (DWORD)nSampleRate; // NOTE_SAMPLE_RATE
    xa_wfx.wBitsPerSample = 16; // BITS_PER_SAMPLE = 16;
    xa_wfx.wFormatTag = WAVE_FORMAT_PCM; 

// create source voice, TBD: filters, TBD: effects, etc...

// create a source_voice that can use a filter 
(*ext_engine_ptr)->CreateSourceVoice(
 &ifc_source_voice, 
 &xa_wfx,
 XAUDIO2_VOICE_USEFILTER); // allow filters

if (ifc_source_voice == NULL) { return; }

ready = true;

}

~xa_notes()
{
    Stop(); // ifc_source_voice->Stop()

    SAFE_DESTROY(ifc_source_voice); // ifc_source_voice->DestroyVoice()
    
    ready = false;
}

void Play(float ratio = 1.0f, float dur = 1.0f, UINT32 loop_count = 0, bool fgnd = true)
{
   if (!ready) { return; }

   if (ifc_source_voice == NULL) { return; }

   dur = min(max(dur, 0.1f), 1.0f);
   
   UINT32 samples = min(nSampleRate, (UINT32)round((float)nSampleRate * dur));         
   //ratio = min(max(ratio, XAUDIO2_MIN_RATIO), XAUDIO2_MAX_FREQ_RATIO);
  
   // update the needed members of the buffer, All of the other members stays the same
   xa_buff.PlayLength = samples;
   xa_buff.LoopCount = loop_count;

   ifc_source_voice->SubmitSourceBuffer(&xa_buff);

   Stop();

   ifc_source_voice->SetFrequencyRatio(ratio);

   ifc_source_voice->Start(0);
   
   IsPlaying = true;

   // TBD: use the WaitForSingleEvent method instead of Sleep
   if (fgnd) { Sleep(1000.0f * dur); }
}

void Stop()
{
   if (!ready) { return; }

   if (ifc_source_voice == NULL) { return; }

   if (!IsPlaying) { return; }
   
   ifc_source_voice->Stop();

   IsPlaying = false;
}

} // end of xa_notes class declaration / definition

// The final product would look like this inside of the instrument class

xa_notes *xa_note_C  = new xa_notes(&MainApp->XA_Engine, freq_C, &table_C);
xa_notes *xa_note_Cs = new xa_notes(&MainApp->XA_Engine, freq_Cs, &table_Cs);
xa_notes *xa_note_D  = new xa_notes(&MainApp->XA_Engine, freq_D, &table_D);
xa_notes *xa_note_Ds = new xa_notes(&MainApp->XA_Engine, freq_Ds, &table_Ds);

xa_note_C->Play(); // play C (at the creation frequency) in foreground for 1 sec.
xa_note_Ds->Play(oct_ratio_3, 0.20f, 4, false); // play Ds (3 octects above the creation frequency) in the background for 0.2 seconds * 4 loops

or

xa_instrument_piano->append_note(&xa_note_C, dur);
xa_instrument_piano->append_chord2(&xa_note_E, dur1, &xa_note_G, dur2);
xa_instrument_piano->Play(loop_count=0, fgnd=false);

标签: c++classcomxaudio2

解决方案


推荐阅读