🥦Python和C++音调音符规划和算法

关键词

Python | C++ | 音符 | 声音 | 全音阶 | 表征 | 音高 | 音程 | 质量 | 情态 | 音调 | 模态 | 连音 | 节拍 | 节奏 | 算法 | 时间 | 时长 | 算子 | 旋律 | 和弦 | MIDI | 消息 | 约束规划 | 移位变换 | 重塑 | 合成 | 音色 | 傅里叶 | 歌曲 | 数字声音

🏈page指点迷津 | Brief

🎯要点

  1. 🎯音符表征和代码实现:🖊半音位置和索引之间的转换 | 🖊全音阶音调表征 | 🖊全音阶缓存 | 🖊全音阶音高表征。🎯音程表征和代码实现:🖊音程质量 | 🖊计算开始和结束音高。🎯情态和音调和代码实现:🖊创建情态:全音阶调式、五声音阶、八音调态、全音调态、布鲁斯调式 | 🖊构建模态 | 🖊音调表征 | 🖊生成基于音高的音阶。🎯音符时长代码实现:🖊时间线转换算法 | 🖊节拍时间转换算法 | 🖊全音符时间表征 | 🖊位置和时长算子 | 🖊节拍位置 | 🖊拍号和节奏 | 🖊时间转换算法。🎯音符及其连音代码实现:🖊梁和连音定义 | 🖊反转旋律的算法。🎯和弦代码实现:🖊文本规范构建和弦 | 🖊三级和弦 | 🖊次和弦 | 🖊四和弦 。🎯乐器模型代码实现:🖊演奏记号列表。🎯乐谱表征和代码实现:🖊声音中的嵌套音符 | 🖊节奏、节拍和调号序列 | 🖊基于时间的音符搜索方式:红黑树方式、音程树方式 | 🖊事件和观察者模式更新音程树。🎯MIDI 表征和代码实现:🖊MIDI消息定义 | 🖊乐谱和MIDI文件转换算法 | 🖊音符转换为MIDI文件。🎯音调约束规划 | 🎯音调移位变换 | 🎯旋律反转变换和重塑 | 🎯和声转录 | 🎯逆转旋律。

  2. 🎯音乐合成:🖊节拍自定义:🖊使用底鼓、军鼓和踩镲 | 🖊旋律创建:🖊贝多芬《致爱丽丝》混音版 | 🖊和弦创建:🖊观察音色、和声和时间的细微变化 | 🖊音阶、调和旋律构建 | 🖊全音阶和弦 | 🖊傅里叶变换创建音效 | 🖊 基于音符的音效 | 🖊歌曲编辑。

  3. 🎯Python数字声音合成 | 🎯C++数字声音合成

🍇C++多抽头混响音效

混响与延迟类似,都为声音添加了回声。 不过,它的不同之处在于,延迟会添加一个回声(即使该回声会重复出现,每次都会更安静),而混响会在声音中添加多个回声。 基本上,混响使声音听起来像是在教堂、深洞或小浴室中演奏的。 延迟只会让事情听起来有点回声。

该技术非常简单。 您有一个样本缓冲区来保存最后 N 个样本,然后当您处理传入样本时,您添加来自延迟缓冲区的多个样本,每个样本乘以不同的音量(幅度)并将其添加到传入样本中以获得输出样本。 然后,您还将该传出样本放入循环缓冲区中当前索引处的混响缓冲区中。

这是一些关于如何实现它的伪代码:

 // The size of the delay buffer is controlled by the maximum time parameter of all taps
 // make sure the buffer is initialized with silence
 reverbBuffer[reverbSamples] = 0;
   
 // start the circular buffer index at 0
 reverbIndex= 0;
   
 // process each input sample
 for (i = 0; i < numSamples; ++i)
 {
   // calculate the output sample, which is the input sample plus all the taps from the delay buffer
   // TODO: handle wrapping around zero when the tapTime is greater than the current reverbIndex
   outSample[i] = inSample[i];
   for (j = 0; j < numTaps; ++j)
     outSample[i] += reverbBuffer[reverbIndex - taps[j].tapTime] * taps[j].feedbackMultiplier; 
   
   // also store this output sample in the reverb buffer
   reverbBuffer[reverbIndex] = outSample[i];
   
   // advance the circular buffer index
   reverbIndex++;
   if (reverbIndex>= reverbSamples)
     reverbIndex= 0;
 }

在下面的示例代码以及上面的混响处理示例中,以下是所使用的拍子的时间和幅度。幅度以 dB 和幅度的形式给出,因此您可以看到您更喜欢的那个。

 Time (ms) dB Amplitude 79250.0562130230.0707230150.1778340230.0707470170.1412532210.0891662130.2238\begin{array}{lll} \text { Time (ms) } & dB & \text { Amplitude } \\ 79 & -25 & 0.0562 \\ 130 & -23 & 0.0707 \\ 230 & -15 & 0.1778 \\ 340 & -23 & 0.0707 \\ 470 & -17 & 0.1412 \\ 532 & -21 & 0.0891 \\ 662 & -13 & 0.2238 \end{array}

通过更多的努力,您可能会想出一些更好的抽头值来使混响听起来更好。下面的示例代码加载 in.wav,使用上面提到的 Tap 对其进行处理,并将其写出为 out.wav。 与往常一样,波形加载代码对于某些波形格式存在一些问题。

 #define _CRT_SECURE_NO_WARNINGS
  
 #include <array>
 #include <stdio.h>
 #include <string.h>
 #include <stdint.h>
 #include <cmath>
 #include <vector>
  
 #define _USE_MATH_DEFINES
 #include <math.h>
 ​
 template <typename T, typename PHANTOM_TYPE>
 struct SNumeric
 {
 public:
     explicit SNumeric(const T &value) : m_value(value) { }
     SNumeric() : m_value() { }
     inline T& Value() { return m_value; }
     inline const T& Value() const { return m_value; }
  
     typedef SNumeric<T, PHANTOM_TYPE> TType;
     typedef T TInnerType;
  
     // Math Operations
     TType operator+ (const TType &b) const
     {
         return TType(this->Value() + b.Value());
     }
  
     TType operator- (const TType &b) const
     {
         return TType(this->Value() - b.Value());
     }
  
     TType operator* (const TType &b) const
     {
         return TType(this->Value() * b.Value());
     }
  
     TType operator/ (const TType &b) const
     {
         return TType(this->Value() / b.Value());
     }
  
     TType& operator+= (const TType &b)
     {
         Value() += b.Value();
         return *this;
     }
  
     TType& operator-= (const TType &b)
     {
         Value() -= b.Value();
         return *this;
     }
  
     TType& operator*= (const TType &b)
     {
         Value() *= b.Value();
         return *this;
     }
  
     TType& operator/= (const TType &b)
     {
         Value() /= b.Value();
         return *this;
     }
  
     TType& operator++ ()
     {
         Value()++;
         return *this;
     }
  
     TType& operator-- ()
     {
         Value()--;
         return *this;
     }
  
     // Extended Math Operations
     template <typename T>
     T Divide(const TType &b)
     {
         return ((T)this->Value()) / ((T)b.Value());
     }
  
     // Logic Operations
     bool operator< (const TType &b) const {
         return this->Value() < b.Value();
     }
     bool operator<= (const TType &b) const {
         return this->Value() <= b.Value();
     }
     bool operator> (const TType &b) const {
         return this->Value() > b.Value();
     }
     bool operator>= (const TType &b) const {
         return this->Value() >= b.Value();
     }
     bool operator== (const TType &b) const {
         return this->Value() == b.Value();
     }
     bool operator!= (const TType &b) const {
         return this->Value() != b.Value();
     }
  
 private:
     T m_value;
 };
  
  
 typedef uint8_t uint8;
 typedef uint16_t uint16;
 typedef uint32_t uint32;
 typedef int16_t int16;
 typedef int32_t int32;
  
 typedef SNumeric<float, struct S__Frequency>      TFrequency;
 typedef SNumeric<uint32, struct S__TimeMs>        TTimeMs;
 typedef SNumeric<uint32, struct S__Samples>       TSamples;
 typedef SNumeric<float, struct S__FractSamples>   TFractionalSamples;
 typedef SNumeric<float, struct S__Decibels>       TDecibels;
 typedef SNumeric<float, struct S__Amplitude>      TAmplitude;
 typedef SNumeric<float, struct S__Phase>          TPhase;
  
  
 static const float c_pi = (float)M_PI;
 static const float c_twoPi = c_pi * 2.0f;
  
 //=====================================================================================
 // Structs
 //=====================================================================================
  
 struct SSoundSettings
 {
     TSamples        m_sampleRate;
     TTimeMs         m_lengthMs;
     TSamples        m_currentSample;
 };
  
  
 struct SReverbTap
 {
     TSamples    m_timeOffset;
     TAmplitude  m_feedback;
 };
  
 class CMultitapReverb
 {
 public:
     CMultitapReverb(const std::vector<SReverbTap>& taps)
         : m_sampleIndex(0)
     {
 ​
         m_taps = taps;
         TSamples largestTimeOffset(0);
         std::for_each(m_taps.begin(), m_taps.end(),
             [&largestTimeOffset](const SReverbTap& tap)
             {
                 if (tap.m_timeOffset > largestTimeOffset)
                     largestTimeOffset = tap.m_timeOffset;
             }
         );
 ​
         if (largestTimeOffset.Value() == 0)
             return;
  
         m_samples.resize(largestTimeOffset.Value()+1);
         std::fill(m_samples.begin(), m_samples.end(), TAmplitude(0.0f));
     }
  
     TAmplitude ProcessSample(TAmplitude sample)
     {
 ​
         if (m_samples.size() == 0)
             return sample;
 ​
         TAmplitude outSample = sample;
         std::for_each(m_taps.begin(), m_taps.end(),
             [&outSample, this](const SReverbTap& tap)
             {
                 size_t tapSampleIndex;
                 if (tap.m_timeOffset.Value() > m_sampleIndex)
                     tapSampleIndex = m_samples.size() - 1 - (tap.m_timeOffset.Value() - m_sampleIndex);
                 else
                     tapSampleIndex = m_sampleIndex - tap.m_timeOffset.Value();
  
                 outSample += m_samples[tapSampleIndex] * tap.m_feedback;
             }
         );
  
         m_samples[m_sampleIndex] = outSample;
  
         m_sampleIndex++;
         if (m_sampleIndex >= m_samples.size())
             m_sampleIndex = 0;
  
         return outSample;
     }
  
 private:
     std::vector<SReverbTap> m_taps;
     std::vector<TAmplitude> m_samples;
     size_t                  m_sampleIndex;
 };
  
 inline TDecibels AmplitudeToDB(TAmplitude volume)
 {
     return TDecibels(log10(volume.Value()));
 }
  
 inline TAmplitude DBToAmplitude(TDecibels dB)
 {
     return TAmplitude(pow(10.0f, dB.Value() / 20.0f));
 }
  
 TSamples SecondsToSamples(const SSoundSettings &s, float seconds)
 {
     return TSamples((int)(seconds * (float)s.m_sampleRate.Value()));
 }
  
 TSamples MilliSecondsToSamples(const SSoundSettings &s, float milliseconds)
 {
     return SecondsToSamples(s, milliseconds / 1000.0f);
 }
  
 TTimeMs SecondsToMilliseconds(float seconds)
 {
     return TTimeMs((uint32)(seconds * 1000.0f));
 }
  
 TFrequency Frequency(float octave, float note)
 {
 ​
     return TFrequency((float)(440 * pow(2.0, ((double)((octave - 4) * 12 + note)) / 12.0)));
 }
  
 template <typename T>
 T AmplitudeToAudioSample(const TAmplitude& in)
 {
     const T c_min = std::numeric_limits<T>::min();
     const T c_max = std::numeric_limits<T>::max();
     const float c_minFloat = (float)c_min;
     const float c_maxFloat = (float)c_max;
  
     float ret = in.Value() * c_maxFloat;
  
     if (ret < c_minFloat)
         return c_min;
  
     if (ret > c_maxFloat)
         return c_max;
  
     return (T)ret;
 }
  
 TAmplitude GetLerpedAudioSample(const std::vector<TAmplitude>& samples, TFractionalSamples& index)
 {
     uint32 a = (uint32)floor(index.Value());
     uint32 b = a + 1;
     float fract = index.Value() - floor(index.Value());
  
     float ampA = 0.0f;
     if (a >= 0 && a < samples.size())
         ampA = samples[a].Value();
  
     float ampB = 0.0f;
     if (b >= 0 && b < samples.size())
         ampB = samples[b].Value();
  
     return TAmplitude(fract * ampB + (1.0f - fract) * ampA);
 }
  
 void NormalizeSamples(std::vector<TAmplitude>& samples, TAmplitude maxAmplitude)
 {
 ​
     if (samples.size() == 0)
         return;
  
     TAmplitude largestAbsVal = TAmplitude(abs(samples.front().Value()));
     std::for_each(samples.begin() + 1, samples.end(), [&largestAbsVal](const TAmplitude &a)
     {
         TAmplitude absVal = TAmplitude(abs(a.Value()));
         if (absVal > largestAbsVal)
             largestAbsVal = absVal;
     }
     );
  
 ​
     largestAbsVal /= maxAmplitude;
     if (largestAbsVal <= TAmplitude(0.0f))
         return;
  
     std::for_each(samples.begin(), samples.end(), [&largestAbsVal](TAmplitude &a)
     {
         a /= largestAbsVal;
  
         if (a >= TAmplitude(1.0f))
         {
             int ijkl = 0;
         }
     }
     );
 }
  
 void ResampleData(std::vector<TAmplitude>& samples, int srcSampleRate, int destSampleRate)
 {
 ​
     if (srcSampleRate == destSampleRate)
         return;
  
     float fResampleRatio = (float)destSampleRate / (float)srcSampleRate;
  
     int nNewDataNumSamples = (int)((float)samples.size() * fResampleRatio);
  
     std::vector<TAmplitude> newSamples;
     newSamples.resize(nNewDataNumSamples);
  
     for (int nIndex = 0; nIndex < nNewDataNumSamples; ++nIndex)
         newSamples[nIndex] = GetLerpedAudioSample(samples, TFractionalSamples((float)nIndex / fResampleRatio));
  
 ​
     std::swap(samples, newSamples);
 }
  
 void ChangeNumChannels(std::vector<TAmplitude>& samples, int nSrcChannels, int nDestChannels)
 {
 ​
     if (nSrcChannels == nDestChannels ||
         nSrcChannels < 1 || nSrcChannels > 2 ||
         nDestChannels < 1 || nDestChannels > 2)
     {
         return;
     }
  
     if (nDestChannels == 2)
     {
         std::vector<TAmplitude> newSamples;
         newSamples.resize(samples.size() * 2);
         for (size_t index = 0; index < samples.size(); ++index)
         {
             newSamples[index * 2] = samples[index];
             newSamples[index * 2 + 1] = samples[index];
         }
  
         std::swap(samples, newSamples);
     }
 ​
     else
     {
         std::vector<TAmplitude> newSamples;
         newSamples.resize(samples.size() / 2);
         for (size_t index = 0; index < samples.size() / 2; ++index)
             newSamples[index] = samples[index * 2] + samples[index * 2 + 1];
  
         std::swap(samples, newSamples);
     }
 }
  
 float PCMToFloat(unsigned char *pPCMData, int nNumBytes)
 {
     switch (nNumBytes)
     {
     case 1:
     {
         uint8 data = pPCMData[0];
         return (float)data / 255.0f;
     }
     case 2:
     {
         int16 data = pPCMData[1] << 8 | pPCMData[0];
         return ((float)data) / ((float)0x00007fff);
     }
     case 3:
     {
         int32 data = pPCMData[2] << 16 | pPCMData[1] << 8 | pPCMData[0];
         return ((float)data) / ((float)0x007fffff);
     }
     case 4:
     {
         int32 data = pPCMData[3] << 24 | pPCMData[2] << 16 | pPCMData[1] << 8 | pPCMData[0];
         return ((float)data) / ((float)0x7fffffff);
     }
     default:
     {
         return 0.0f;
     }
     }
 }
  
 struct SMinimalWaveFileHeader
 {
 ​
     unsigned char m_szChunkID[4];      //0
     uint32        m_nChunkSize;        //4
     unsigned char m_szFormat[4];       //8
 ​
     unsigned char m_szSubChunk1ID[4];  //12
     uint32        m_nSubChunk1Size;    //16
     uint16        m_nAudioFormat;      //18
     uint16        m_nNumChannels;      //20
     uint32        m_nSampleRate;       //24
     uint32        m_nByteRate;         //28
     uint16        m_nBlockAlign;       //30
     uint16        m_nBitsPerSample;    //32
  
     unsigned char m_szSubChunk2ID[4];  //36
     uint32        m_nSubChunk2Size;    //40
  
 ​
 };
  
 ​
 template <typename T>
 bool WriteWaveFile(const char *fileName, const std::vector<TAmplitude> &samples, const SSoundSettings &sound)
 {
     //open the file if we can
     FILE *file = fopen(fileName, "w+b");
     if (!file)
         return false;
  
     //calculate bits per sample and the data size
     const int32 bitsPerSample = sizeof(T) * 8;
     const int dataSize = samples.size() * sizeof(T);
  
     SMinimalWaveFileHeader waveHeader;
  
     //fill out the main chunk
     memcpy(waveHeader.m_szChunkID, "RIFF", 4);
     waveHeader.m_nChunkSize = dataSize + 36;
     memcpy(waveHeader.m_szFormat, "WAVE", 4);
  
     //fill out sub chunk 1 "fmt "
     memcpy(waveHeader.m_szSubChunk1ID, "fmt ", 4);
     waveHeader.m_nSubChunk1Size = 16;
     waveHeader.m_nAudioFormat = 1;
     waveHeader.m_nNumChannels = 1;
     waveHeader.m_nSampleRate = sound.m_sampleRate.Value();
     waveHeader.m_nByteRate = sound.m_sampleRate.Value() * 1 * bitsPerSample / 8;
     waveHeader.m_nBlockAlign = 1 * bitsPerSample / 8;
     waveHeader.m_nBitsPerSample = bitsPerSample;
  
     //fill out sub chunk 2 "data"
     memcpy(waveHeader.m_szSubChunk2ID, "data", 4);
     waveHeader.m_nSubChunk2Size = dataSize;
  
     //write the header
     fwrite(&waveHeader, sizeof(SMinimalWaveFileHeader), 1, file);
  
     //write the wave data itself, converting it from float to the type specified
     std::vector<T> outSamples;
     outSamples.resize(samples.size());
     for (size_t index = 0; index < samples.size(); ++index)
         outSamples[index] = AmplitudeToAudioSample<T>(samples[index]);
     fwrite(&outSamples[0], dataSize, 1, file);
  
     //close the file and return success
     fclose(file);
     return true;
 }
  
 ​
 bool ReadWaveFile(const char *fileName, std::vector<TAmplitude>& samples, int32 sampleRate)
 {
     //open the file if we can
     FILE *File = fopen(fileName, "rb");
     if (!File)
     {
         return false;
     }
  
     //read the main chunk ID and make sure it's "RIFF"
     char buffer[5];
     buffer[4] = 0;
     if (fread(buffer, 4, 1, File) != 1 || strcmp(buffer, "RIFF"))
     {
         fclose(File);
         return false;
     }
  
     //read the main chunk size
     uint32 nChunkSize;
     if (fread(&nChunkSize, 4, 1, File) != 1)
     {
         fclose(File);
         return false;
     }
  
     //read the format and make sure it's "WAVE"
     if (fread(buffer, 4, 1, File) != 1 || strcmp(buffer, "WAVE"))
     {
         fclose(File);
         return false;
     }
  
     long chunkPosFmt = -1;
     long chunkPosData = -1;
  
     while (chunkPosFmt == -1 || chunkPosData == -1)
     {
         //read a sub chunk id and a chunk size if we can
         if (fread(buffer, 4, 1, File) != 1 || fread(&nChunkSize, 4, 1, File) != 1)
         {
             fclose(File);
             return false;
         }
  
         //if we hit a fmt
         if (!strcmp(buffer, "fmt "))
         {
             chunkPosFmt = ftell(File) - 8;
         }
         //else if we hit a data
         else if (!strcmp(buffer, "data"))
         {
             chunkPosData = ftell(File) - 8;
         }
  
         //skip to the next chunk
         fseek(File, nChunkSize, SEEK_CUR);
     }
  
     //we'll use this handy struct to load in 
     SMinimalWaveFileHeader waveData;
  
     //load the fmt part if we can
     fseek(File, chunkPosFmt, SEEK_SET);
     if (fread(&waveData.m_szSubChunk1ID, 24, 1, File) != 1)
     {
         fclose(File);
         return false;
     }
  
     //load the data part if we can
     fseek(File, chunkPosData, SEEK_SET);
     if (fread(&waveData.m_szSubChunk2ID, 8, 1, File) != 1)
     {
         fclose(File);
         return false;
     }
  
     //verify a couple things about the file data
     if (waveData.m_nAudioFormat != 1 ||       //only pcm data
         waveData.m_nNumChannels < 1 ||        //must have a channel
         waveData.m_nNumChannels > 2 ||        //must not have more than 2
         waveData.m_nBitsPerSample > 32 ||     //32 bits per sample max
         waveData.m_nBitsPerSample % 8 != 0 || //must be a multiple of 8 bites
         waveData.m_nBlockAlign > 8)           //blocks must be 8 bytes or lower
     {
         fclose(File);
         return false;
     }
  
     //figure out how many samples and blocks there are total in the source data
     int nBytesPerBlock = waveData.m_nBlockAlign;
     int nNumBlocks = waveData.m_nSubChunk2Size / nBytesPerBlock;
     int nNumSourceSamples = nNumBlocks * waveData.m_nNumChannels;
  
     //allocate space for the source samples
     samples.resize(nNumSourceSamples);
  
     //maximum size of a block is 8 bytes.  4 bytes per samples, 2 channels
     unsigned char pBlockData[8];
     memset(pBlockData, 0, 8);
  
     //read in the source samples at whatever sample rate / number of channels it might be in
     int nBytesPerSample = nBytesPerBlock / waveData.m_nNumChannels;
     for (int nIndex = 0; nIndex < nNumSourceSamples; nIndex += waveData.m_nNumChannels)
     {
         //read in a block
         if (fread(pBlockData, waveData.m_nBlockAlign, 1, File) != 1)
         {
             fclose(File);
             return false;
         }
  
         //get the first sample
         samples[nIndex].Value() = PCMToFloat(pBlockData, nBytesPerSample);
  
         //get the second sample if there is one
         if (waveData.m_nNumChannels == 2)
         {
             samples[nIndex + 1].Value() = PCMToFloat(&pBlockData[nBytesPerSample], nBytesPerSample);
         }
     }
  
     //re-sample the sample rate up or down as needed
     ResampleData(samples, waveData.m_nSampleRate, sampleRate);
  
     //handle switching from mono to stereo or vice versa
     ChangeNumChannels(samples, waveData.m_nNumChannels, 1);
  
     return true;
 }
 

Last updated