一,、老張需求:AI作曲營造新年氛圍我有一個搞嵌入式的朋友老張,全名叫張三,。是真的,,他的身份證上就叫張三。據(jù)說,,出生時,,他父母準備了一堆名字。但是兩人各執(zhí)一派,,大打出手,。吵鬧聲引來隔壁李大爺:實在不行叫張三,我叫李四,,也活得挺好,。于是,互不相讓的一對年輕夫妻,,給孩子上了戶口,,起名叫:張三。 老張長大后,,一直不和李大爺說話,。李大爺告訴小張三:當時,如果,,不是我沖進去,,急中生智給你定下名字,你可能就沒命了,。聽到這里,,小張三才稍微得以釋懷,并且給李大爺磕了個頭,,以示感謝,。李大爺說:客氣了,我起名字時,,我父母也一樣,,最后還是你爺爺給起的李四,咱兩家是世交,。 我打斷了老張:快說,,找我么事? 老張說,,其實我一直覺得,,我不是普通的凡人。 “嗯,,你特別的煩人”,。 老張說,是平凡的“凡”,。我今年40歲了,,哎,你知道嗎,?我前天剛過完40歲生日,,買了一個大蛋…… “說事情!” 老張說,,兄弟,,幫幫忙吧,我想搞一個發(fā)明,,需要你人工智能方向的幫助,。 我說,啊,,你又搞發(fā)明,?這次什么想法。 老張說,,現(xiàn)在快過年了,,我想搞一個仿老式留聲機的盒子,安裝到餐廳里面,。 機器采用自助模式,,只要顧客付錢,,盒子就會自動播放一段,由AI生成的原創(chuàng)新年音樂,,給他們送去祝福,,掃碼還可以下載保存。 我問老張,,你有銷售渠道嗎,? 老張說,放心吧,,餐廳我都談好了,。他們提供場地和網(wǎng)絡(luò),我們只承擔電費就行,。 我沉思了一會,,問老張:老張啊,你認識一個“耿”姓做手工的人嗎,? 老張說,,相信我,我絕對不認識他,,他的那些發(fā)明,,不是沒用,是真沒用,。 我點了點頭:那就好,,我支持你! 二,、midi格式:便攜式音樂描述文件想讓AI 學會作曲,,首先要找到一批音樂樣本,讓它學,。 AI 作曲,,遵循“種瓜得瓜,種豆得豆”的原則:你給它訓練什么風格的樣本,,它最終就會生成什么風格的音樂,。
因此,我們需要找一些輕松活潑的音樂,,這適合新年播放,。 音樂文件的格式,我們選擇MIDI 格式,。MIDI 的全稱是:Musical Instrument Digital Interface ,,翻譯成中文就是:樂器數(shù)字接口。 這是一種什么格式?為什么會有這種格式呢,? 話說,,隨著計算機的普及,電子樂器也出現(xiàn)了,。電子樂器的出現(xiàn),,極大地節(jié)省了成本,,帶來了便利,。 基本上有一個電子樂器,世間的樂器就都有了,。 這個按鈕是架子鼓,,那個按鈕是薩克斯。而在此之前,,你想要發(fā)出這類聲音,,真的得敲架子鼓或者吹薩克斯管。 而且還有更為放肆的事情,。你想用架子鼓一秒敲五下,,得有專業(yè)的技能。但是用電子樂器,,一秒敲五十下也毫不費力,,因為程序就給搞定了。 這些新生事物的出現(xiàn),,常常讓老藝術(shù)家們口吐鮮血,。 電子樂器既然可以演奏音樂,那么就有樂譜,。這樂譜還得有標準,,因為它得在所有電子樂器中都起作用。這個“計算機能理解的樂譜”,,就是MIDI 格式,。 下面我們就來解析一下MIDI 文件??纯此慕Y(jié)構(gòu)是怎么樣的,。 我找到一個機器貓(哆啦A夢)的主題曲,采用python 做一下解析: import pretty_midi # 加載樣本文件 pm = pretty_midi.PrettyMIDI("jqm.midi") # 循環(huán)樂器列表 for i, instrument in enumerate(pm.instruments): instrument_name = pretty_midi.program_to_instrument_name(instrument.program) print(i, instrument_name) # 輸出樂器名稱
這個音樂,,相信大家都很熟悉,,就是:哦、哦,、哦,,哆啦A夢和我一起,讓夢想發(fā)光…… 通過pretty_midi 庫加載MIDI 文件,獲取它的樂器列表pm.instruments ,,打印如下: Acoustic Grand Piano(原聲大鋼琴),、Glockenspiel(鋼片琴)、String Ensemble(弦樂合奏) ,、Muted Trumpet(悶音小號),、Trombone(長號)、Electric Bass(電貝斯),、Acoustic Guitar(原聲吉他),、Flute(長笛)、Acoustic Grand Piano(原聲大鋼琴),、Harmonica(口琴),、Vibraphone(電顫琴)、Bagpipe(蘇格蘭風笛),、Marimba(馬林巴琴)……
我們看到,,短短一個片頭曲,就動用了近20 種樂器,。如果不是專門分析它,,我們還真的聽不出來吶。 那么,,每種樂器的音符可以獲取到嗎,?我們來試試: # 承接上個代碼片段,假設(shè)選定了樂器instrument for j, note in enumerate(instrument.notes): # 音高轉(zhuǎn)音符名稱 note_name = pretty_midi.note_number_to_name(note.pitch) info = ("%s:%.2f->%.2f")%(note_name, note.start, note.end)
打印如下: Acoustic Grand Piano F#3:1.99->2.04 F#2:1.98->2.06 E2:1.99->2.07 C2:1.98->2.08 F#3:2.48->2.53 F#2:2.48->2.56 F#3:2.98->3.03 F#2:2.98->3.06 ……
通過獲取instrument 的notes ,,可以讀到此樂器的演奏信息,。包含:pitch 音高,start 開始時間,,end 結(jié)束時間,,velocity 演奏力度。 名稱 | pitch | start | end | velocity |
---|
示例 | 24 | 1.98 | 2.03 | 82 | 解釋 | 音高(C1,、C2) | 開始時間 | 結(jié)束時間 | 力度 | 范圍 | 128個音高 | 單位為秒 | 單位為秒 | 最高100 |
上面的例子中,,F#3:1.99->2.04 表示:音符F#3 ,演奏時機是從1.99 秒到2.04 秒,。 如果把這些數(shù)據(jù)全都展開,,其實挺壯觀的,應該是如下這樣: 其實,,MIDI 文件對于一首樂曲來說,,就像是一個程序的源代碼,也像是一副藥的配方,。MIDI 文件里,,描述了樂器的構(gòu)成,,以及該樂器的演奏數(shù)據(jù)。 這類文件要比WAV ,、MP3 這些波形文件小得多,。一段30 分鐘鋼琴曲的MIDI 文件,大小一般不超過100KB ,。 因此,,讓人工智能去學習MIDI 文件,并且實現(xiàn)自動作曲,,這是一個很好的選擇,。 三、實戰(zhàn):TensorFlow實現(xiàn)AI作曲我在datasets 目錄下,,放了一批節(jié)奏歡快的MIDI 文件,。 這批文件,,除了節(jié)奏歡快適合在新年播放,,還有一個特點:全部是鋼琴曲。也就是說,,如果打印他的樂器的話,,只有一個,那就是:Acoustic Grand Piano (原聲大鋼琴),。 這么做降低了樣本的復雜性,,僅需要對一種樂器進行訓練和預測。同時,,當它有朝一日練成了AI 作曲神功,,你也別妄想它會鑼鼓齊鳴,它仍然只會彈鋼琴,。 多樂器的復雜訓練當然可行,。但是目前在業(yè)內(nèi),還沒有足夠的數(shù)據(jù)集來支撐這件事情,。
開搞之前,,我們必須得先通盤考慮一下。不然,,我們都不知道該把數(shù)據(jù)搞成么個形式,。 AI 作曲,聽起來很高端,。其實跟文本生成,、詩歌生成,沒有什么區(qū)別,。我之前講過很多相關(guān)的例子《NLP實戰(zhàn):基于LSTM自動生成原創(chuàng)宋詞》《NLP實戰(zhàn):基于GRU的自動對春聯(lián)》《NLP實戰(zhàn):基于RNN的自動藏頭詩》,。如果感興趣,,大家可以先預習一下。不看也不要緊,,后面我也會簡單描述,,但深度肯定不如上面的專項介紹。
利用RNN ,,生成莎士比亞文集,,是NLP 領(lǐng)域的HelloWorld 入門程序。那么,,AI 作曲,,只不過是引入了音樂的概念。另外,,在出入?yún)?shù)上,,維度也豐富了一些。但是,,從本質(zhì)上講,,它還是那一套思路。 所有AI 自動生成的模式,,基本上都是給定一批輸入值+ 輸出值,。然后,讓機器去學習,,自己找規(guī)律,。最后,學成之后,,再給輸入,,讓它自己預測出下一個值。 舉個例子,,莎士比亞文集的生成,,樣本如下: First Citizen: Before we proceed any further, hear me speak. All: Speak, speak.
它是如何讓AI 訓練和學習呢?其實,,就是從目前的數(shù)據(jù)不斷觀察,,觀察出現(xiàn)下一個字符的概率。 當前 | 下一個 | 經(jīng)驗值 |
---|
F | i | ☆ | Fi | r | ☆ | Fir | s | ☆ | Firs | t | ☆☆ | …… | …… | …… |
F 后面大概率會出現(xiàn)i ,。如果現(xiàn)在是Fi ,,那么它的后面該出現(xiàn)r 了。這些,,AI 作為經(jīng)驗記了下了,。
這種記錄概率的經(jīng)驗,在少量樣本的情況下,,是無意義的,。 但是,,當這個樣本變成人類語言庫的時候,那么這個概率就是語法規(guī)范,,就是上帝視角,。 舉個例子,當我說:冬天了,,窗外飄起了__,! 你猜,飄起了什么,?是的,,窗外飄起了雪。 當AI 分析過人類歷史上,,出現(xiàn)過的所有語言之后,。當它進行數(shù)據(jù)分析的時候,最終它會計算出:在人類的語言庫里,,冬天出現(xiàn)飄雪花的情況,,要遠遠高于冬天飄落葉的情況。所以,,它肯定也會告訴你那個空該填:雪花,。 這就是AI 自動作詞、作曲,、作畫的本質(zhì)。它的技術(shù)支撐是帶有鏈式的循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN ),,數(shù)據(jù)支撐就是大量成型的作品,。 3.1 準備:構(gòu)建數(shù)據(jù)集首先,讀取這些數(shù)據(jù),,然后把它們加工成輸入input 和輸出output ,。 展開一個MIDI 文件,我們再來看一下原始數(shù)據(jù): Note(start=1.988633, end=2.035121, pitch=54, velocity=82), Note(start=1.983468, end=2.060947, pitch=42, velocity=78), Note(start=2.479335, end=2.525823, pitch=54, velocity=82)……
我們可以把前幾組,,比如前24 組音符數(shù)據(jù)作為輸入,,然后第25 個作為預測值。后面依次遞推,。把這些數(shù)據(jù)交給AI ,,讓它研究去。 訓練完成之后,,我們隨便給24 個音符數(shù)據(jù),,讓它預測第25 個。然后,,再拿著2~25 ,,讓它預測第26 個,,以此循環(huán)往后,連綿不絕,。 這樣可以嗎,? 可以(能訓練)。但存在問題(結(jié)果非所愿),。 在使用循環(huán)神經(jīng)網(wǎng)絡(luò)的時候,,前后之間要帶有通用規(guī)律的聯(lián)系。比如:前面有“冬天”做鋪墊,,后面遇到“飄”時,,可以更準確地推測出來是“飄雪”。 我們看上面的數(shù)據(jù),,假設(shè)我們忽略velocity (力度)這個很專業(yè)的參數(shù),。僅僅看pitch 音高和start 、end 起始時間,。其中,,音高是128 個音符。它是普遍有規(guī)律的,,值是1~128 ,,不會出圈兒。但是這個起始時間卻很隨機,,這里可以是啊1 秒開始,,再往后可能就是100 秒開始。 如果,,我們只預測2 個音符,,結(jié)果200 秒的時間出現(xiàn)的概率高。那么,,第二個音符豈不是到等到3 分鐘后再演奏,。另外,很顯然演奏是有先后順序的,,因此要起止時間遵從隨機的概率分布,,是不靠譜的。 我覺得,,一個音符會演奏多久,,以及前后音符的時間間距,這兩項相對來說是比較穩(wěn)定的,。他們更適合作為訓練參數(shù),。 因此,我們決定把音符預處理成如下格式: Note(duration=0.16, step=0.00, pitch=54), Note(duration=0.56, step=0.31, pitch=53), Note(duration=0.26, step=0.22, pitch=24), ……
duration 表示演奏時長,,這個音符會響多久,,它等于end-start ,。
step 表示步長,本音符距離上一個出現(xiàn)的時間間隔,,它等于start2-start1 ,。
原始數(shù)據(jù)格式[start,end] ,同預處理后的數(shù)據(jù)格式[duration,step] ,,兩者是可以做到相互轉(zhuǎn)化的,。 我們把所有的訓練集文件整理一下: import pretty_midi import tensorflow as tf
midi_inputs = [] # 存放所有的音符 filenames = tf.io.gfile.glob("datasets/*.midi") # 循環(huán)所有midi文件 for f in filenames: pm = pretty_midi.PrettyMIDI(f) # 加載一個文件 instruments = pm.instruments # 獲取樂器 instrument = instruments[0] # 取第一個樂器,此處是原聲大鋼琴 notes = instrument.notes # 獲取樂器的演奏數(shù)據(jù) # 以開始時間start做個排序,。因為默認是依照end排序 sorted_notes = sorted(notes, key=lambda note: note.start) prev_start = sorted_notes[0].start # 循環(huán)各項指標,,取出前后關(guān)聯(lián)項 for note in sorted_notes: step = note.start - prev_start # 此音符與上一個距離 duration = note.end - note.start # 此音符的演奏時長 prev_start = note.start # 此音符開始時間作為最新 # 指標項:[音高(音符),同前者的間隔,,自身演奏的間隔] midi_inputs.append([note.pitch, step, duration])
上面的操作,,是把所有的MIDI 文件,依照預處理的規(guī)則,,全部處理成[pitch, step, duration] 格式,,然后存放到midi_inputs 數(shù)組中。 這只是第一步操作,。后面我們要把這個樸素的格式,,拆分成輸入和輸出的結(jié)對。然后,,轉(zhuǎn)化為TensorFlow 框架需要的數(shù)據(jù)集格式,。 seq_length = 24 # 輸入序列長度 vocab_size = 128 # 分類數(shù)量
# 將序列拆分為輸入和輸出標簽對 def split_labels(sequences): inputs = sequences[:-1] # 去掉最后一項最為輸入 # 將音高除以128,便于 inputs_x = inputs/[vocab_size,1.0,1.0] y = sequences[-1] # 截取最后一項作為輸出 labels = {"pitch":y[0], "step":y[1],"duration":y[2]} return inputs_x, labels
# 搞成tensor,,便于流操作,,比如notes_ds.window notes_ds = tf.data.Dataset.from_tensor_slices(midi_inputs) cut_seq_length = seq_length+1 # 截取的長度,因為要拆分為輸入+輸出,,因此+1 # 每次滑動一個數(shù)據(jù),每次截取cut_seq_length個長度 windows = notes_ds.window(cut_seq_length, shift=1, stride=1,drop_remainder=True) flatten = lambda x: x.batch(cut_seq_length, drop_remainder=True) sequences = windows.flat_map(flatten) # 將25,,拆分為24+1,。24是輸入,1是預測,。進行訓練 seq_ds = sequences.map(split_labels, num_parallel_calls=tf.data.AUTOTUNE) buffer_size = len(midi_inputs) - seq_length # 拆分批次,,緩存等優(yōu)化 train_ds = (seq_ds.shuffle(buffer_size) .batch(64, drop_remainder=True) .cache().prefetch(tf.data.experimental.AUTOTUNE))
我們先分析split_labels 這個方法。它接收一段序列數(shù)組,。然后將其分為兩段,,最后1 項作為后段,其余部分作為前段,。 我們把seq_length 定義為24 ,,從總數(shù)據(jù)midi_inputs 中,,利用notes_ds.window 實現(xiàn)每次取25 個數(shù)據(jù),取完了向后移動1 格,,再繼續(xù)取數(shù)據(jù),。直到湊不齊25 個數(shù)據(jù)(drop_remainder=True 意思是不足25 棄掉)停止。 至此,,我們就有了一大批以25 為單位的數(shù)據(jù)組,。其實,他們是:1~25 ,、2~26 ,、3~27 …… 然后,我們再調(diào)用split_labels ,,將其全部搞成24+1 的輸入輸出對,。此時數(shù)據(jù)就變成了:(1~24,25) 、(2~25,26) ……,。接著,,再調(diào)用batch 方法,把他們搞成每64 組為一個批次,。這一步是框架的要求,。 至此,我們就把準備工作做好了,。后面,,就該交給神經(jīng)網(wǎng)絡(luò)訓練去了。 3.2 訓練:構(gòu)建神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)這一步,,我們將構(gòu)建一個神經(jīng)網(wǎng)絡(luò)模型,。它將不斷地由24 個音符觀察下一個出現(xiàn)的音符。它記錄,,它思考,,它嘗試推斷,它默寫并對照答案,。一旦見得多了,,量變就會引起質(zhì)變,它將從整個音樂庫的角度,,給出作曲的最優(yōu)解,。 好了,上代碼: input_shape = (seq_length, 3) # 輸入形狀 inputs = tf.keras.Input(input_shape) x = tf.keras.layers.LSTM(128)(inputs) # 輸出形狀 outputs = { 'pitch': tf.keras.layers.Dense(128, name='pitch')(x), 'step': tf.keras.layers.Dense(1, name='step')(x), 'duration': tf.keras.layers.Dense(1, name='duration')(x), } model = tf.keras.Model(inputs, outputs)
上面代碼我們定義了輸入和輸出的格式,,然后中間加了個LSTM 層,。 先說輸入。因為我們給的格式是[音高,間隔,,時長] 共3 個關(guān)鍵指標,。而且每24 個音,預測下一個音,。所以input_shape = (24, 3) ,。 再說輸出。我們最終期望AI 可以自動預測音符,,當然要包含音符的要素,,那也就是outputs = {'pitch','step','duration'} 。其中,,step 和duration 是一個數(shù)就行,,也就是Dense(1) 。但是,,pitch 卻不同,,它需要是128 個音符中的一個。因此,,它是Dense(128) ,。 最后說中間層。我們期望有人能將輸入轉(zhuǎn)為輸出,,而且最好還有記憶,。前后之間要能綜合起來,要根據(jù)前面的鋪墊,,后面給出帶有相關(guān)性的預測,。那么,這個長短期記憶網(wǎng)絡(luò)LSTM (Long Short-Term Memory)就是最佳的選擇了,。 最終,,model.summary() 打印結(jié)構(gòu)如下所示: Layer (type) | Output Shape | Param | Connected to |
---|
input (InputLayer) | [(None, 24, 3)] | 0 | [] | lstm (LSTM) | (None, 128) | 67584 | ['input[0][0]'] | duration (Dense) | (None, 1) | 129 | ['lstm[0][0]'] | pitch (Dense) | (None, 128) | 16512 | ['lstm[0][0]'] | step (Dense) | (None, 1) | 129 | ['lstm[0][0]'] | Total params: 84,354 |
|
|
|
后面,配置訓練參數(shù),,開始訓練: checkpoint_path = 'model/model.ckpt' # 模型存放路徑 model.compile( # 配置參數(shù) loss=loss, loss_weights={'pitch': 0.05,'step': 1.0,'duration':1.0}, optimizer=tf.keras.optimizers.Adam(learning_rate=0.01), ) # 模型保存參數(shù) cp_callback = tf.keras.callbacks.ModelCheckpoint( filepath=checkpoint_path ,save_weights_only=True, save_best_only=True) # 啟動訓練,,訓練50個周期 model.fit(train_ds, validation_data=train_ds , epochs=50,callbacks=[cp_callback])
訓練完成之后,會將模型保存在'model/model.ckpt' 目錄下,。而且,,我們設(shè)置了只保存最優(yōu)的一個模型save_best_only=True 。 上面有個需要特別說明的地方,,那就是在model.compile 中,給損失函數(shù)加了一個權(quán)重loss_weights 的配置,。這是因為,,在輸出的三個參數(shù)中,pitch 音高的128 分類跨度較大,,一旦預測有偏差,,就會導致?lián)p失函數(shù)的值很大,。而step 和duration 本身數(shù)值就很小,都是0.0x 秒,,損失函數(shù)的值變化較小,。這種不匹配,會導致后兩個參數(shù)的變化被忽略,,只關(guān)心pitch 的訓練,。因此需要降低pitch 的權(quán)重平衡一下。至于具體的數(shù)值,,是調(diào)試出來的,。 出于講解的需要,上面的代碼僅僅是關(guān)鍵代碼片段,。文末我會把完整的項目地址公布出來,,那個是可以運行的。
好了,,訓練上50 輪,,保存完結(jié)果模型。下面,,就該去做預測了,。 3.3 預測和播放:實現(xiàn)AI作曲現(xiàn)在這個模型,已經(jīng)可以根據(jù)24 個音符去推測出下一個音符了,。我們來試一下,。 # 加載模型 if os.path.exists(checkpoint_path + '.index'): model.load_weights(checkpoint_path) # 從音符庫中隨機拿出24個音符,當然你也可以自己編 sample_notes = random.sample(midi_inputs, 24) num_predictions = 600 # 預測后面600個 # 循環(huán)600次,,每次取最新的24個 for i in range(num_predictions): # 拿出最后24個 n_notes = sample_notes[-seq_length:] # 主要給音高做一個128分類歸一化 notes = [] for input in n_notes: notes.append([input[0]/vocab_size,input[1],input[2]]) # 將24個音符交給模型預測 predictions = model.predict([notes]) # 取出預測結(jié)果 pitch_logits = predictions['pitch'] pitch = tf.random.categorical(pitch_logits, num_samples=1)[0] step = predictions['step'][0] duration = predictions['duration'][0] pitch, step, duration = int(pitch), float(step), float(duration) # 將預測值添加到音符數(shù)組中,,進行下一次循環(huán) sample_notes.append([pitch, step, duration])
其實,關(guān)鍵代碼就一句predictions = model.predict([notes]) ,。根據(jù)24 個音符,,預測出來下一個音符的pitch 、step 和duration ,。其他的,,都是輔助操作。 我們從素材庫里,,隨機生成了24 個音符,。其實,如果你懂聲樂,,你也可以自己編24 個音符,。這樣,起碼能給音樂定個基調(diào)。因為,,后面的預測都是根據(jù)前面特征來的,。當然,也可以不是24 個,,根據(jù)2 個生成1 個也行,。那前提是,訓練的時候也得是2+1 的模式,。但是,,我感覺還是24 個好,感情更深一些,。 從24 個生成1 個后,,變成了25 個。然后再取這25 個中的最后24 個,,繼續(xù)生成下一個,。循環(huán)600 次,最后生成了624 個音符,。打印一下: [[48, 0.001302083333371229, 0.010416666666628771], [65, 0.11979166666674246, 0.08463541666651508] …… [72, 0.03634712100028992, 0.023365378379821777], [41, 0.04531348496675491, 0.011086761951446533]]
但是,,這是預處理后的特征,并非是可以直接演奏的音符,。是否還記得duration = end-start 以及step=start2-start1 ,。我們需要把它們還原成為MIDI 體系下的屬性: # 復原midi數(shù)據(jù) prev_start = 0 midi_notes = [] for m in sample_notes: pitch, step, duration = m start = prev_start + step end = start + duration prev_start = start midi_notes.append([pitch, start, end])
這樣,就把[pitch, step, duration] 轉(zhuǎn)化成了[pitch, start, end] ,。打印midi_notes 如下: [[48, 0.001302083333371229, 0.01171875], [65, 0.12109375000011369, 0.20572916666662877], …… [72, 32.04372873653976, 32.067094114919584], [41, 32.08904222150652, 32.100128983457964]]
我們從數(shù)據(jù)可以看到,,最后播放到了32 秒。也就說我們AI 生成的這段600 多個音符的樂曲,,可以播放32 秒,。 聽一聽效果,那就把它寫入MIDI 文件吧,。 # 寫入midi文件 pm = pretty_midi.PrettyMIDI() instrument = pretty_midi.Instrument( program=pretty_midi.instrument_name_to_program("Acoustic Grand Piano")) for n in midi_notes: note = pretty_midi.Note(velocity=100,pitch=n[0],start=n[1],end=n[2]) instrument.notes.append(note) pm.instruments.append(instrument) pm.write("out.midi")
MIDI 文件有5 個必需的要素,。其中,樂器我們設(shè)置為"Acoustic Grand Piano" 原聲大鋼琴,。velocity 沒有參與訓練,,但也需要,我們設(shè)為固定值100 ,。其他的3 個參數(shù),,都是AI 生成的,依次代入,。最后,,把結(jié)果生成到out.midi 文件中,。
使用Window 自帶的Media Player 就可以直接播放這個文件。你聽不到,,我可以替你聽一聽。 聽完了,,我談下感受吧,。 怎么描述呢?我覺得,,說好聽對不起良心,,反正,不難聽,。 好了,,AI 作曲就到此為止了。 源代碼已上傳到GitHub 地址是:https://github.com/hlwgy/ai_music,。 做完了,,我還得去找老張談談。 四,、合作:你果然還是這樣的老張我騎電動車去找老張,,我告訴他,AI 作曲哥們搞定了,。 老張問我,,你陽了沒有。 我說,,沒有,。 老張告訴我,他表弟陽了,。 我說,,你不用擔心,畢竟你們離得那么遠,。 老張說,,他陽了后,我們的項目也泡湯了,。 我問為什么,。 老張說:我談好的那家飯店,就是表弟開的,。他陽了之后,,現(xiàn)在不承認了。 我的電腦還停留在開機界面,,我強制關(guān)機,。走了,。 我記得,上一次,,我告訴老張,,三個月不要聯(lián)系我《老張讓我用TensorFlow識別語音命令》。 而這一次,,我什么也沒有說,。就走了。 我是ITF男孩,,帶你從IT視角看世界,。
|