D:/work/code_by/CbmrEngine/SongDb.cpp

查看本檔案說明文件.
00001 /*==============================================================================
00002 TSongDb代表的是歌曲的資料庫
00003 ==============================================================================*/
00004 #include <stdlib.h>
00005 #include <string.h>
00006 #include <math.h>
00007 #include "SongDb.h"
00008 #include "ListTools.h"
00009 #include "cbmrdtw.h"
00010 
00011 #define WARP_NUM 17  //歌聲的伸縮次數,改了這個還要改MIN_WARP_RATE
00012 #define MIN_WARP_RATE 70  //表示最小的伸縮倍率,依WARP_NUM調整,單位是%
00013 #define CONSTA 0.9
00014 #define CONSTB 0.5
00015 
00022 /*==============================================================================
00023 功能:將中介格式平移一個值
00024 傳入:mid為中介格式
00025       size為mid的長度
00026       level為要平移的距離
00027 ==============================================================================*/
00028 static void shiftMid(int *mid, int size, int level)
00029 {
00030   int i;
00031 
00032   for (i = 0; i < size; i++)
00033     mid[i] += level;
00034 }
00041 /*==============================================================================
00042 功能:將中介格式平移到平均值為0
00043 傳入:mid為中介格式
00044       size為mid的長度
00045 ==============================================================================*/
00046 static void shiftMidToMeanZero(int *mid, int size)
00047 {
00048   int i, total;
00049 
00050   for (i = 0, total = 0; i < size; i++)
00051     total += mid[i];
00052   shiftMid(mid, size, -total / size);
00053 }
00061 /*==============================================================================
00062 功能:將一個值插入已經排序好的陣列
00063 傳入:array為目標陣列
00064       size為陣列長度
00065       value為要插入的值
00066 ==============================================================================*/
00067 static void insertArray(int *array, int size, int value)
00068 {
00069   int i, j;
00070 
00071   for (i = size - 1; i >= 0; i--)
00072     if (value >= array[i])
00073       break;
00074   if (i < size - 1)
00075   {
00076     for (j = size - 1; j > i + 1; j--)
00077       array[j] = array[j - 1];
00078     array[i + 1] = value;
00079   }
00080 }
00081 
00082 static bool strequal(char *str1, char *str2)
00083 {
00084   char c1, c2;
00085 
00086   while (*str1 != '\0' && *str2 != '\0')
00087   {
00088     c1 = (*str1 >= 'a' && *str1 <= 'z')? *str1 - 32 : *str1;
00089     c2 = (*str2 >= 'a' && *str2 <= 'z')? *str2 - 32 : *str2;
00090     if (c1 != c2)
00091       return false;
00092     if (c1 < 0 && c2 < 0)  //考慮到雙位元的字
00093     {
00094       if (*(++str1) != *(++str2))
00095         return false;
00096     }
00097     ++str1;
00098     ++str2;
00099   }
00100   return *str1 == *str2;
00101 }
00102 
00103 static bool strnequal(char *str1, char *str2, int n)
00104 {
00105   int i;
00106   char c1, c2;
00107 
00108   i = 0;
00109   while (*str1 != '\0' && *str2 != '\0' && i++ < n)
00110   {
00111     c1 = (*str1 >= 'a' && *str1 <= 'z')? *str1 - 32 : *str1;
00112     c2 = (*str2 >= 'a' && *str2 <= 'z')? *str2 - 32 : *str2;
00113     if (c1 != c2)
00114       return false;
00115     if (*str1 < 0 && *str2 < 0)  //考慮到雙位元的字
00116     {
00117       if (*(++str1) != *(++str2))
00118         return false;
00119       ++i;
00120     }
00121     ++str1;
00122     ++str2;
00123   }
00124   return i == n || *str1 == *str2;
00125 }
00126 
00127 int min(int a, int b)
00128 {
00129   return a <= b? a : b;
00130 }
00131 
00132 /*==============================================================================
00133 功能:建構子
00134 ==============================================================================*/
00135 TSongDb::TSongDb()
00136 {
00137   resampleRate = 4;
00138   songNumber = 0;
00139   compareProgress1 = compareProgress2 = 0;
00140   Song = NULL;
00141   topSongIndex = NULL;
00142   SetOption(COMPARE_FROM_ANYWHERE);
00143 }
00144 
00145 /*==============================================================================
00146 功能:解構子
00147 ==============================================================================*/
00148 TSongDb::~TSongDb()
00149 {
00150   int i;
00151 
00152   for (i = 0; i < songNumber; i++)  //將所有歌曲釋放掉
00153     delete Song[i];
00154   delete[] Song;
00155   delete[] topSongIndex;
00156 }
00157 
00158 void TSongDb::SetOption(int from, char *language, int method)
00159 {
00160   compareFrom = from;
00161   //Q:compareLanguage還沒有寫~這裡一開始都是NULL,不會跑進else
00162   if (language == NULL)
00163     compareLanguage[0] = '\0';
00164   else
00165     strcpy(compareLanguage, language);
00166   compareMethod = method;
00167 }
00178 /*==============================================================================
00179 功能:從index檔和db檔讀取資料庫,如果已經讀取過了,則會附加在後面
00180 傳入:IndexStream為index檔
00181       DbStream為db檔
00182 //Q:cbmrmain.cpp呼叫時_expand是設false.
00183 傳出:是否成功
00184 ==============================================================================*/
00185 bool TSongDb::Read(TInputStream *IndexStream, TInputStream *DbStream, bool _expand)
00186 {
00187   TMyList *List;
00188   TDataInputStream *DIStream;
00189   bool success;
00190   char strLine[1000];
00191   TSongItem *SongItem, **SongTemp;
00192   int i;
00193 
00194   List = new TMyList();
00195   DIStream = new TDataInputStream(IndexStream);
00196   success = true;
00197   while (DIStream->ReadLine(strLine, 1000) > 0)  //從index檔讀取一行字串
00198   {
00199     SongItem = new TSongItem();  //加入一首歌
00200     SongItem->SetProperty(strLine);
00201     if (DbStream)
00202       if (!SongItem->Read(DbStream, resampleRate))  //讀取db檔中的音符
00203       {
00204         delete SongItem;
00205         success = false;
00206         break;
00207       }
00208     List->Add(SongItem);  //先放進linking list
00209   }
00210   delete DIStream;
00211 
00212   SongTemp = Song;
00213   Song = new TSongItem*[songNumber + List->count];  //新的歌曲陣列
00214   memcpy(Song, SongTemp, songNumber * sizeof(TSongItem *));  //將舊的複製到新的
00215   for (i = 0; i < List->count; i++)  //從linking list放進陣列
00216     Song[songNumber + i] = (TSongItem *)List->Items(i);
00217   songNumber += List->count;
00218   delete List;
00219   delete[] SongTemp;  //釋放舊的歌曲陣列
00220 
00221   delete[] topSongIndex;
00222   topSongIndex = new int[songNumber];  //用來排序歌曲的索引陣列
00223 
00224   expand = _expand;
00225   if (expand)
00226     for (i = 0; i < songNumber; i++)
00227       Song[i]->NoteToMid(resampleRate, INTEGER_SCALE);
00228 
00229   return success;
00230 }
00238 /*==============================================================================
00239 功能:將所有歌曲資料寫入index檔和db檔
00240 傳入:IndexStream為index檔
00241       DbStream為db檔
00242 傳出:是否成功
00243 ==============================================================================*/
00244 bool TSongDb::Write(TOutputStream *IndexStream, TOutputStream *DbStream)
00245 {
00246   return Write(IndexStream, DbStream, 0, songNumber - 1);
00247 }
00257 /*==============================================================================
00258 功能:將某些歌曲資料寫入index檔和db檔
00259 傳入:IndexStream為index檔
00260       DbStream為db檔
00261       fromIndex代表從哪一首歌開始
00262       toIndex代表到哪一首歌為止
00263 傳出:是否成功
00264 ==============================================================================*/
00265 bool TSongDb::Write(TOutputStream *IndexStream, TOutputStream *DbStream, int fromIndex, int toIndex)
00266 {
00267   int i;
00268 
00269   if (fromIndex < 0)  //檢查索引範圍
00270     fromIndex = 0;
00271   if (toIndex > songNumber - 1)  //檢查索引範圍
00272     toIndex = songNumber - 1;
00273 
00274   for (i = fromIndex; i <= toIndex; i++)  //寫入檔案
00275     Song[i]->Write(IndexStream, DbStream);
00276 
00277   return true;
00278 }
00285 /*==============================================================================
00286 功能:增加一首歌曲,只新增一個指標連結,並不會將歌曲複製一份
00287 傳入:SongItem為新增的歌
00288 傳出:新增歌曲的索引
00289 ==============================================================================*/
00290 int TSongDb::AddSong(TSongItem *SongItem)
00291 {
00292   TSongItem **SongTemp;
00293 
00294   SongTemp = Song;
00295   Song = new TSongItem *[songNumber + 1];  //新的歌曲陣列
00296   memcpy(Song, SongTemp, songNumber * sizeof(TSongItem *));  //將舊的複製到新的
00297   Song[songNumber] = SongItem;  //增加一首歌
00298   delete[] SongTemp;  //釋放舊的歌曲陣列
00299 
00300   delete[] topSongIndex;
00301   topSongIndex = new int[songNumber + 1];
00302 
00303   return songNumber++;
00304 }
00310 /*==============================================================================
00311 功能:刪除一首歌曲
00312 傳入:index為刪除的歌的索引
00313 ==============================================================================*/
00314 void TSongDb::DeleteSong(int index)
00315 {
00316   int i;
00317 
00318   if (index >= 0 && index < songNumber)
00319   {
00320     delete Song[index];
00321     for (i = index; i < songNumber - 1; i++)  //向前遞補
00322       Song[i] = Song[i + 1];
00323     delete[] topSongIndex;
00324     topSongIndex = new int[--songNumber];
00325   }
00326 }
00332 /*==============================================================================
00333 功能:刪除一首歌曲
00334 傳入:DbEntity為刪除的歌
00335 ==============================================================================*/
00336 void TSongDb::DeleteSong(TSongItem *SongItem)
00337 {
00338   int i;
00339 
00340   for (i = 0; i < songNumber; i++)
00341     if (Song[i] == SongItem)
00342     {
00343       DeleteSong(i);
00344       break;
00345     }
00346 }
00353 /*==============================================================================
00354 功能:傳回某一首歌
00355 傳入:index為指定的歌曲索引
00356 傳出:第index首歌
00357 ==============================================================================*/
00358 TSongItem *TSongDb::GetSong(int index)
00359 {
00360   return (index >= 0 && index < songNumber)? Song[index] : NULL;
00361 }
00367 /*==============================================================================
00368 功能:傳回資料庫中的歌曲數目
00369 傳出:資料庫中的歌曲數目
00370 ==============================================================================*/
00371 int TSongDb::GetSongNumber(void)
00372 {
00373   return songNumber;
00374 }
00381 /*==============================================================================
00382 功能:比對所有歌曲
00383 傳入:waveMid為唱進來的中介格式
00384       waveSize為waveMid的長度
00385 ==============================================================================*/
00386 void TSongDb::Compare(int *waveMid, int waveSize)
00387 {
00388   Compare(waveMid, waveSize, 0, songNumber - 1);
00389 }
00402 /*==============================================================================
00403 功能:比對所部份歌曲
00404 傳入:waveMid為唱進來的中介格式
00405       waveSize為waveMid的長度
00406       fromIndex為比對的起始範圍
00407       toIndex為比對的結束範圍
00408 ==============================================================================*/
00409 void TSongDb::Compare(int *waveMid, int waveSize, int fromIndex, int toIndex)
00410 {
00411   int i, j, k;
00412   //Q:waveCopy的大小原本是200,有時會不夠,要注意
00413   int waveCopy[300], *midiCopy;
00414 //  int waveWarp[WARP_NUM][SEGMENT_SIZE];
00415   int waveWarp[WARP_NUM][1280];
00416   int waveWarpSize[WARP_NUM];
00417   int lsDist, lsDistThreshold, lsDistTemp;
00418   float score;
00419   int *segmentIndex, segmentIndexSize;
00420   int maxMidiSize, midiSize;
00421   int dtwDistance_c, dtwDistance_l, dtwDistance_r, minDtwDistance;
00422   int dtwStringLength_c, dtwStringLength_l, dtwStringLength_r, minDtwStringLength;
00423   bool allLanguage, dtwFixLeft;
00424   TSongItem *CurrentSong;
00425   int minNum;
00426   int *minDist;
00427   int *nonzeroMid;
00428   int best, fromSegment, toSegment;
00429   FILE *fTree;
00430   //Q:topRank = 100?????
00431   const int topRank = 100;
00432   const float multiple = 1.4f;//Q:dtw table長寬比是1.4
00433 //Q:fromIndex和toIndex指的是被比對的歌曲index?
00434   if (fromIndex < 0)
00435     fromIndex = 0;
00436   if (toIndex >= songNumber)
00437     toIndex = songNumber - 1;
00438   compareProgress1 = compareProgress2 = 0;  //比對進度為0
00439   if (compareMethod == METHOD_LINEAR_SCALING || compareMethod == METHOD_LSDTW)
00440   {
00441     compareNum1 = toIndex - fromIndex + 1;  //第一階段比對數目
00442     //Q:topRank=100是常數~幹嘛用???
00443     compareNum2 = compareMethod == METHOD_LINEAR_SCALING? 0 : min(compareNum1, topRank);  //第二階段比對數目
00444     //Q:如果只有LS則第二階段比對數目為0
00445   }
00446   else//Q:沒有做LS,只做DTW,所以第一階段比對數目為0
00447   {
00448     compareNum1 = 0;  //第一階段比對數目
00449     compareNum2 = toIndex - fromIndex + 1;  //第二階段比對數目
00450   }
00451   stopCompare = false;
00452   //Q:compareLanguage[0] == '\0'的話allLanguage=1
00453   allLanguage = compareLanguage[0] == '\0';  //是否比對全部語言
00454 
00455   for (i = fromIndex; i <= toIndex; i++)  //對比對範圍內的所有歌曲
00456   {
00457   //Q:對比對範圍內的所有歌曲做初始值
00458   //Q:topSongIndex?????它在建構子是NULL.它在Read時被new如下一行(在這裡被給值???)
00459   //Q:topSongIndex = new int[songNumber];  //用來排序歌曲的索引陣列
00460     topSongIndex[i - fromIndex] = i;  //在範圍內的index,供排序用
00461     Song[i]->bestSegment = 0;  //先假設使用者最可能從頭唱
00462     Song[i]->score = 0.0f;  //先設定分數為0
00463   }
00464   if (waveSize < 3)  //讓waveWarpSize至少1
00465     return;
00466 
00467 
00468   //============================================================================
00469   //第一階段用 linear scaling 的方法 ===========================================
00470   //============================================================================
00471 //Q:將唱進來的聲音做伸縮,從0.7~1.5每次加5共16個result存在array wavewarp[i][j]裡.
00472 //Q:wavewarp[0]就是0.7倍的那條~將16個result去和db轉出來的中介格式mid比對,看哪段最接近
00473 //Q:mid要比對的index(如果從head比就只有一個,any就是每個音符)去和取出伸縮後最接近的片段比對,
00474 //Q:找出分數最近的 
00475   if (compareMethod == METHOD_LINEAR_SCALING || compareMethod == METHOD_LSDTW)
00476   {
00477     //將歌聲的中介格式複製到waveCopy
00478     //Q:waveMid是傳進來的pitch
00479     waveCopy[0] = waveMid[0];   //Q:改成前一個非休止符的音,所以至少waveCopy[0]要先給
00480                                 //Q:這裡是延長前一個音,而非去掉休止符.
00481     for (i = 1; i < waveSize; i++)  //複製時將休止符改成前一個非休止符的音
00482       waveCopy[i] = (waveMid[i] == 0)? waveCopy[i - 1] : waveMid[i];
00483 
00484     for (i = 0; i < WARP_NUM; i++)  //伸縮WARP_NUM次
00485     {//Q:開頭define了WARP_NUM=17,歌聲的伸縮次數,MIN_WARP_RATE=70,表示最小的伸縮倍率%
00486     //Q:j的最小值是70,最大值是150,在70%~150%中伸縮.0.7倍~1.5倍(每次加5所以有17-1=16次)
00487       j = MIN_WARP_RATE + i * 5;  //伸縮倍率
00488       waveWarpSize[i] = (waveSize - 1) * j / 100;  //伸縮後的長度
00489 //      if (waveWarpSize[i] > SEGMENT_SIZE)//Q:因為唱進來八秒,最長不會超過SEGMENT_SIZE
00490 //        waveWarpSize[i] = SEGMENT_SIZE;
00491       for (k = 0; k < waveWarpSize[i]; k++)  //做resample
00492       {
00493         int _m = 100 * k / j;
00494         float _n = 100.0f * k / j - _m;
00495         waveWarp[i][k] = floor((1.0 - _n) * waveCopy[_m] + _n * waveCopy[_m + 1] + 0.5);  //內插值
00496       }
00497       shiftMidToMeanZero(waveWarp[i], waveWarpSize[i]);  //將waveWarp的平均值調為0
00498     }
00499     //Q:開了一個waveWarpSize最大的,最後那個.(就夠了)
00500  midiCopy = new int[waveWarpSize[WARP_NUM - 1]];
00501     //Q:如果只有LS,則compareNum2=0.錯.所以if只有LS要取min(compareNum1, topRank)
00502     //Q:但在前面,compareNum2在LS時=0,在LSDTW時compareNum2=min(compareNum1, topRank),
00503     //Q:所以這裡為什麼不通通設minNum=min(compareNum1, topRank)???
00504     minDist = new int[minNum = (compareMethod == METHOD_LSDTW)? compareNum2 : min(compareNum1, topRank)];  //用來記錄前minNum名linear scaling距離最小者
00505     for (i = 0; i < minNum; i++)  //Q:INFINITE_INT=9999999
00506       minDist[i] = INFINITE_INT;
00507 
00508 
00509 
00510 
00511     for (compareProgress1 = fromIndex; compareProgress1 <= toIndex && !stopCompare; compareProgress1++)  //比對範圍中每一首midi
00512     {//Q:compareProgress1表示每首midi~一首一首跑
00513       CurrentSong = Song[compareProgress1];
00514       if (!allLanguage && strcmp(CurrentSong->Fields->Values("language"), compareLanguage))  //語言不符合
00515 //Q:語言不合的話這一首就不比了.不比就跳過這一首歌,因此這一次的迴圈結束,使用continue.換下一首.
00516         continue;
00517 
00518 
00519 //Q:為了省空間而沒有在Read時就每首歌都NoteToMid.在這裡才NoteToMid,每首歌比完存了該存的以後
00520 //Q:就把空間free出來.
00521       if (!expand){
00522         CurrentSong->NoteToMid(resampleRate, INTEGER_SCALE);  //展開成中介格式        
00523         }
00524         
00525       //設定要比對的片段位置和數目
00526       if (compareFrom == COMPARE_FROM_REFRAIN)  //從複歌比對
00527       {                                              //Q:midRefrainIndex是"複歌起點在mid中的索引"
00528         segmentIndex = CurrentSong->midRefrainIndex; //Q:用midRefrainIndex是因為用中介格式比
00529         segmentIndexSize = CurrentSong->refrainIndexSize + 1;
00530       }
00531       else  //從頭或任意處比對
00532       {
00533         segmentIndex = CurrentSong->noteIndex; //Q:noteIndex是"每個音符起點在mid中的索引"
00534         segmentIndexSize = (compareFrom == COMPARE_FROM_HEAD)? min(1, CurrentSong->noteIndexSize) : CurrentSong->noteIndexSize;
00535       }
00536       if (segmentIndexSize == 0)  //沒有片段可以比對
00537         continue;
00538 
00539 //Q:mid是從note來,所以還沒有去掉休止符.noteIndex才是扣掉休止符的
00540 //Q:COMPARE_FROM_HEAD的size應該在兩者中取一個小的,以免db檔裡的歌比唱拉長後的短,
00541 //Q:會讀到不該讀的地方(取小的是我改的~原本是用warp的size)
00542       nonzeroMid = new int[midiSize = (compareFrom == COMPARE_FROM_HEAD)? min(CurrentSong->midSize,waveWarpSize[WARP_NUM - 1]) : CurrentSong->midSize - segmentIndex[0]];
00543 //Q:segmentIndex是從noteIndex來,所以segmentIndex[0]一定是非休止符
00544 
00545       nonzeroMid[0] = CurrentSong->mid[j = segmentIndex[0]];  //第一個非休止符的音
00546 
00547 //暫存      for (i = 1; (i < midiSize)&&(j+i)<CurrentSong->midSize; i++)  //將休止符改成前一個非休止符的音
00548 
00549      for (i = 1; i < midiSize; i++)  //將休止符改成前一個非休止符的音
00550 {//Q:(j+i)<CurrentSong->midSize的條件是我加的,因為取消warp上限的話,有時候會爆掉,
00551  //Q:拉長後的比mid還長,會讀到超過midSize的地方,會爆掉   
00552                 nonzeroMid[i] = (CurrentSong->mid[j + i] == 0)? nonzeroMid[i - 1] : CurrentSong->mid[j + i];
00553 
00554 }
00555       lsDistThreshold = minDist[minNum - 1];
00556  
00557       for (i = 0; i < segmentIndexSize; i++)  //比對midi的每一片段
00558       {
00559         if (segmentIndex[i] - segmentIndex[0] + waveWarpSize[WARP_NUM - 1] > midiSize)
00560           break;
00561 
00562         //將midi的中介格式複製到midiCopy,減去segmentIndex[0]是因為它可能不是0
00563         //Q:減去segmentIndex[0]是因為nonzeroMid是從segmentIndex[0]開始的~所以只要加上offset即可(吧)
00564         //Q:(offset=segmentIndex[i] - segmentIndex[0])
00565 //        memcpy(midiCopy, nonzeroMid + segmentIndex[i] - segmentIndex[0], waveWarpSize[WARP_NUM - 1] * sizeof(int));
00566   //Q:應該把waveWarpSize[WARP_NUM - 1]改成midiSize~前面有討論過why
00567         memcpy(midiCopy, nonzeroMid + segmentIndex[i] - segmentIndex[0], min(midiSize,waveWarpSize[WARP_NUM - 1]) * sizeof(int));
00568 
00569         for (j = WARP_NUM - 1; j >= 0; j--)  //對每一次伸縮。必須倒數是因為waveWarpSize[j]隨著j變大而變大,而midiCopy在平移到平均值為0時會一直改變
00570         {//Q:倒數是因為才能在每次平移是全部都有移到.如果從前面開始,就只會移前面一小段,
00571          //Q:後面的不會被移到,等到要做第二次時,前面那一小段跟後面就會有落差,因為後面剛剛沒有平移到
00572           shiftMidToMeanZero(midiCopy, waveWarpSize[j]);  //將midiCopy的平均值調為0
00573           //計算waveCopy伸縮後的waveWarp和midiCopy的距離
00574           //Q:下面除掉這裡又乘回來.why???其實就是lsDistTemp=lsDist.
00575           //Q:不過一開始lsDist沒有值,lsDist在下兩行才開始算
00576           lsDistTemp = lsDistThreshold == INFINITE_INT? INFINITE_INT : lsDistThreshold * waveWarpSize[j];
00577 
00578           for (k = 0, lsDist = 0; k < waveWarpSize[j]; k++)  //計算兩向量的距離
00579             if ((lsDist += abs(waveWarp[j][k] - midiCopy[k])) > lsDistTemp)  //距離超過第minNum名則不再算下去
00580               break;  //Q:距離超過第minNum名就爆了,不用管它.
00581                       //Q:在這裡lsDistTemp存著每一個伸縮對mid~做到目前的最小距離(對某個伸縮)
00582                       //Q:超過最小值就不要了~只要找出這麼多伸縮中最接近的一個即可
00583                       //Q:而minDist是用來存所有的~每首midi的每個片段的前minNum名最接近的
00584  
00585           if (lsDist < lsDistTemp)
00586           {
00587             lsDistThreshold = lsDist / waveWarpSize[j]; //Q:除的意思是???算平均每個音差多少?
00588             CurrentSong->bestSegment = i;  //最接近的片段
00589           }
00590 //Q:這邊做完waveWarp就沒用了,但每首midi都要跟waveWarp來比對,waveWarp是唱進來的,
00591 //Q:所以唱進來的wave的伸縮存在waveWarp裡,在和每首midi比對的loop中都會用到.
00592 //Q:用來找出伸縮後和要比對的那首midi中介格式距離最小的
00593 
00594         }  //for伸縮
00595 
00596       }  //for每個片段 //Q:每個片段的loop是用來找出這首midi中要比對的片段中最接近的那一個,存在bestSegment裡
00597       delete[] nonzeroMid;
00598 
00599       if (lsDistThreshold < minDist[minNum - 1])  //算出來的距離比第minNum名還小
00600       {
00601 //Q:minDist是片段的距離~不是指伸縮~和每首歌的score也無關
00602 //Q:一首歌可能有好幾個片段都很接近query,分數是由lsDistThreshold算出來的,
00603 //Q:在算每個片段時lsDistThreshold都可能變動,最後,一首歌只有一個lsDistThreshold
00604         CurrentSong->score = 100.0f - (float)lsDistThreshold / INTEGER_SCALE;
00605         insertArray(minDist, minNum, lsDistThreshold);  //將距離插入前minNum名距離
00606         //Q:insertArray的做法是,從後往前找出它應該排序的位置,比方說是第五名,
00607         //Q:然後將最後一個存成最後一個的前一個,以此類推~第七個存原本的第六名,
00608         //Q:第六個存原本的第五名,再將新的第五名加在array第五項.
00609         //Q:看不太出來minDist可以幹嘛,好像只為了lsDistThreshold < minDist[minNum - 1]比較用
00610       }
00611 
00612       if (!expand)
00613         CurrentSong->FreeMid();
00614 
00615     }  //for每首midi
00616     delete[] minDist;
00617     delete[] midiCopy;
00618 
00619     if (stopCompare)  //使用者中斷比對
00620       return;
00621     SortByScore(compareNum1, minNum);  //將分數在前minNum名的歌曲排序
00622  //Q:第一階段比對了compareNum1首,排序出前minNum名,排序後的索引放在topSongIndex
00623   }  //if使用linear scaling
00624 
00625 
00626   //============================================================================
00627   //第二階段用 dynamic time warping 的方法 =====================================
00628   //============================================================================
00629   if (compareMethod == METHOD_DTW || compareMethod == METHOD_LSDTW || compareMethod == METHOD_NOTEBEGIN_DTW)
00630   {
00631 //Q:waveCopy是唱進來的pitch.這裡是移掉休止符而非延長,因為dtw本身有伸縮的功能.
00632 //Q:又,移掉休止符或延長休止符的前一個音,兩者效果差不多
00633 
00634     for (i = 0, j = 0; i < waveSize; i++)  //複製時移除修止符
00635       if (waveMid[i] != 0)
00636         waveCopy[j++] = waveMid[i];
00637      waveSize = j;
00638 
00639     shiftMidToMeanZero(waveCopy, waveSize);  //將waveCopy的平均值調為0
00640 
00641 //Q:DTW是否固定起點:LSDTW,NOTEBEGIN_DTW,COMPARE_FROM_HEAD???why???
00642  
00643     dtwFixLeft = (compareMethod == METHOD_LSDTW || compareMethod == METHOD_NOTEBEGIN_DTW || compareFrom == COMPARE_FROM_HEAD);  //DTW是否固定起點
00644 //Q:multiple是常數(dtw table長寬比,切出兩條線,只有兩線中間要比),const float multiple = 1.4f;
00645     maxMidiSize = dtwFixLeft? (waveSize * multiple) : INFINITE_INT;  //midi的中介格式最多取多長出來計算dtw距離
00646     minDist = new int[minNum = min(compareNum2, 100)];  //最多只算出前100名的分數
00647     for (i = 0; i < minNum; i++)  //用來記錄前minNum名dtw距離最小的
00648       minDist[i] = INFINITE_INT;
00649 
00650     for (compareProgress2 = 0; compareProgress2 < compareNum2 && !stopCompare; compareProgress2++)  //比對前幾名midi
00651     {
00652       CurrentSong = Song[topSongIndex[compareProgress2]];
00653       CurrentSong->score = 0.0f;
00654       if (!allLanguage && strcmp(CurrentSong->Fields->Values("language"), compareLanguage))  //語言不符合
00655         continue;
00656 
00657       if (!expand)
00658         CurrentSong->NoteToMid(resampleRate, INTEGER_SCALE);  //展開成中介格式
00659 
00660       if (compareFrom == COMPARE_FROM_REFRAIN)  //從複歌比對
00661       {
00662         segmentIndex = CurrentSong->midRefrainIndex;
00663         segmentIndexSize = CurrentSong->refrainIndexSize + 1;
00664       }
00665       else  //從頭或任意處比對
00666       {
00667         segmentIndex = CurrentSong->noteIndex;
00668         segmentIndexSize = compareFrom == COMPARE_FROM_HEAD? min(1, CurrentSong->noteIndexSize) : CurrentSong->noteIndexSize;
00669       }
00670       if (segmentIndexSize == 0)  //沒有片段可以比對
00671         continue;
00672 //Q:移除休止符的動作在下面的for裡(好像可以像LS一樣提出來在這裡先用nonzeroMid存起來)
00673       midiCopy = new int[min(CurrentSong->midSize, maxMidiSize)];
00674 
00675       if (compareMethod == METHOD_DTW || compareMethod == METHOD_LSDTW)  //只針對第besstIndex個片段做dtw
00676         fromSegment = toSegment = CurrentSong->bestSegment;  //如果沒做第一階段則會是0
00677       else  //if (compareMethod == METHOD_NOTEBEGIN_DTW) 則所有片段都做dtw
00678       {
00679         fromSegment = 0;
00680         toSegment = segmentIndexSize - 1;
00681       }
00682 
00683 
00684       for (best = fromSegment, minDtwDistance = INFINITE_INT; best <= toSegment; best++)  //對CurrentSong每個要做dtw的片段
00685       {
00686         //將midi的中介格式複製到midiCopy並移除休止符
00687         //Q:這裡的midiCopy放進for是因為它的起點會改變,所以不能放在迴圈外.(?!)
00688         for (j = segmentIndex[best], midiSize = 0; j < CurrentSong->midSize && midiSize < maxMidiSize; j++)
00689           if (CurrentSong->mid[j] != 0)
00690             midiCopy[midiSize++] = CurrentSong->mid[j];
00691         if (midiSize == 0)  //有問題的片段
00692           continue;
00693 
00694         shiftMidToMeanZero(midiCopy, midiSize);  //將midiCopy的平均值調為0
00695         //計算waveCopy和midiCopy的距離
00696         dtwDistance_c = dtw(waveCopy, waveSize, midiCopy, midiSize, dtwFixLeft, &dtwStringLength_c, minDist[minNum - 1]);
00697         for (j = INTEGER_SCALE; j >= (INTEGER_SCALE >> 1) && j > 0; j >>= 1)  //循環比對四次
00698         {//Q:INTEGER_SCALE=10,循環比對就好像是用binary search一樣,找出最佳解.
00699          //Q:先從中間開始,看左邊和右邊先比較近,ex左邊,取中間和左邊那段,從這段的中間再開始往左往右,
00700          //Q:以此類推.
00701           shiftMid(midiCopy, midiSize, -j);
00702           dtwDistance_l = dtw(waveCopy, waveSize, midiCopy, midiSize, dtwFixLeft, &dtwStringLength_l, minDist[minNum - 1]);
00703           shiftMid(midiCopy, midiSize, j * 2);
00704           dtwDistance_r = dtw(waveCopy, waveSize, midiCopy, midiSize, dtwFixLeft, &dtwStringLength_r, minDist[minNum - 1]);
00705           if (dtwDistance_l < dtwDistance_c && dtwDistance_l < dtwDistance_r)
00706           {
00707             dtwDistance_c = dtwDistance_l;
00708             shiftMid(midiCopy, midiSize, -j * 2);
00709           }
00710           else if (dtwDistance_r < dtwDistance_c && dtwDistance_r < dtwDistance_l)
00711             dtwDistance_c = dtwDistance_r;
00712           else
00713             shiftMid(midiCopy, midiSize, -j);
00714         }
00715 
00716         insertArray(minDist, minNum, dtwDistance_c);  //將距離插入前minNum名距離
00717         if (dtwDistance_c < minDtwDistance)
00718         {
00719           minDtwDistance = dtwDistance_c;  //這首歌比對的片段當中最小的dtw距離
00720           minDtwStringLength = dtwStringLength_c;  //此時的dtw string length
00721         }
00722       }  //for片段
00723 
00724       if (minDtwDistance < INFINITE_INT)  //minDtwDistance是這首歌比對後的最小dtw距離
00725         CurrentSong->score = (CONSTA * pow(CONSTB, (double)minDtwDistance / INTEGER_SCALE / waveSize) + (1.0 - CONSTA) * minDtwStringLength / waveSize) * 100.0;
00726 
00727       if (!expand)
00728         CurrentSong->FreeMid();
00729 
00730       delete[] midiCopy;
00731     }  //for每首midi
00732     delete[] minDist;
00733     if (!stopCompare)
00734       SortByScore(compareNum2, minNum);  //第二階段比對完之後最多只排出前minNum名,`所以超出minNum名的歌其分數皆沒有意義
00735   }  //if使用DTW
00736 
00737 }
00738 
00748 /*==============================================================================
00749 功能:找出某個欄位符合某個值的歌曲
00750 傳入:fieldName為某一欄位的名稱
00751       value為期望值
00752       exactMatch為字串是否要完全符合或部份符合
00753       result為傳出結果,true代表符合,false代表不符合
00754 傳出:符合的歌曲數目
00755 ==============================================================================*/
00756 int TSongDb::Compare(char *fieldName, char *value, bool exactMatch, bool *result)
00757 {
00758   int i, size, count;
00759   char *c;
00760 
00761   count = 0;
00762   if (exactMatch)
00763   {
00764     for (i = 0; i < songNumber; i++)  //對資料庫每首歌曲
00765       if (result[i] = strequal(Song[i]->Fields->Values(fieldName), value))
00766         ++count;
00767   }
00768   else
00769   {
00770     size = strlen(value);
00771     for (i = 0; i < songNumber; i++)  //對資料庫每首歌曲
00772     {
00773       result[i] = false;
00774       c = Song[i]->Fields->Values(fieldName);
00775       while (*c != '\0')
00776       {
00777         if (strnequal(c, value, size))
00778         {
00779           result[i] = true;
00780           ++count;
00781           break;
00782         }
00783         c += *c < 0? 2 : 1;  //考慮到雙位元的字
00784       }
00785     }
00786   }
00787 
00788   return count;
00789 }
00800 /*==============================================================================
00801 功能:找出某個欄位值符合某個長度的歌曲,若值當中有雙位元的字,會當成一個字
00802 傳入:fieldName為某一欄位的名稱
00803       size為期望值
00804       operate為-1, 0 或 1,代表小於、等於或大於
00805       result為傳出結果,true代表符合,false代表不符合
00806 傳出:符合的歌曲數目
00807 ==============================================================================*/
00808 int TSongDb::Compare(char *fieldName, int size, int operate, bool *result)
00809 {
00810   int i, j, count;
00811   char *c;
00812 
00813   count = 0;
00814   for (i = 0; i < songNumber; i++)  //對資料庫每首歌曲
00815   {
00816     for (c = Song[i]->Fields->Values(fieldName), j = 0; *c != 0;)
00817     {
00818       if (*c < 32 || (*c > 47 && *c < 58) || (*c > 64 && *c < 91) || (*c > 96 && *c < 123) || *c < 0)  //判斷是否是英數字或符號
00819         j++;
00820       c += *c < 0? 2 : 1;  //考慮到雙位元的字
00821     }
00822     if (result[i] = (operate == 0 && j == size) || (j - size) * operate > 0)
00823       ++count;
00824   }
00825 
00826   return count;
00827 }
00834 /*==============================================================================
00835 功能:將歌曲依照分數排序,排序後的索引放在topSongIndex陣列中
00836 輸入:sortNum為要排序的歌曲數目
00837       topNum為只排序出前topNum名即可
00838 ==============================================================================*/
00839 void TSongDb::SortByScore(int sortNum, int topNum)
00840 {
00841   int i, j, k, tempd;
00842   float tempf;
00843   float *score;
00844   
00845   score = new float[sortNum];
00846   for (i = 0; i < sortNum; i++)  //將分數複製到score陣列以便排序
00847     score[i] = Song[topSongIndex[i]]->score;  //此時topSongIndex為排序前的歌曲索引
00848   for (i = 0; i < topNum; i++)  //泡沫排序法(採大的在上面),只排出前topNum名分數最高者
00849   {
00850     k = i;
00851     for (j = i + 1; j < sortNum; j++)
00852       if (score[k] < score[j])
00853         k = j;
00854     if (k != i)
00855     {
00856       tempd = topSongIndex[i];
00857       topSongIndex[i] = topSongIndex[k];
00858       topSongIndex[k] = tempd;
00859       tempf = score[i];
00860       score[i] = score[k];
00861       score[k] = tempf;
00862     }
00863   }
00864   delete[] score;
00865 }
00873 /*==============================================================================
00874 功能:傳回某一個名次的歌曲
00875 傳入:rank為名次,範圍從0到比對的歌曲數-1,否則會傳回無法預期的topSongIndex
00876 傳出:第rank名的歌曲
00877 ==============================================================================*/
00878 TSongItem *TSongDb::GetTopSong(int rank)
00879 {
00880   return Song[topSongIndex[rank]];
00881 }
00888 /*==============================================================================
00889 功能:傳回某一個名次的歌曲索引
00890 傳入:rank為名次,範圍從0到比對的歌曲數-1,否則會傳回無法預期的topSongIndex
00891 傳出:第rank名的歌曲索引
00892 ==============================================================================*/
00893 int TSongDb::GetTopSongIndex(int rank)
00894 {
00895   return topSongIndex[rank];
00896 }
00900 /*==============================================================================
00901 功能:停止比對
00902 ==============================================================================*/
00903 void TSongDb::StopCompare(void)
00904 {
00905   stopCompare = true;
00906 }
00912 /*==============================================================================
00913 功能:傳回比對進度,範圍從0到100
00914 傳出:比對進度
00915 ==============================================================================*/
00916 int TSongDb::GetCompareProgress(void)
00917 {
00918   if (compareNum1 == 0 && compareNum2 == 0)
00919     return 0;
00920   if (compareNum1 == 0)  //只用linear scaling
00921     return compareProgress2 * 100 / compareNum2;
00922   else if (compareNum2 == 0)  //只用DTW
00923     return compareProgress1 * 100 / compareNum1;
00924   else  //兩階段式比對
00925     return compareProgress1 * 95 / compareNum1 +  //第一階段的進度佔總進度的95%
00926            compareProgress2 * 5 / compareNum2;  //第二階段的進度佔總進度的5%
00927 }
00928 

產生日期:Tue Jul 11 11:52:19 2006, 專案:cbmr, 產生器:  doxygen 1.4.7