如何用代碼在dubstep電子樂中嵌入可讀數據
來自專欄演算法藝術實驗室
前天我看了一個分享如何在一首Brostep高潮部分的軌道中嵌入可讀數據的帖子。原文中寫的是dubstep,但鑒於這位朋友實在Skrillex的單曲《Right In》中實踐的,我猜他是個美國人,因為只有美國人才會認為Skrillex做的dubstep吧。這不重要,我們還是來看看他是怎麼做的。
《Right In》原版:
Right In-Skrillex - QQ音樂-千萬正版音樂海量無損曲庫新歌熱歌天天暢聽的高品質音樂平台!選擇「dubstep」的原因是,一些不喜歡這種音樂的人開玩笑說它聽起來太「數字化」,就跟給數據機噪音加上鼓和低音差不多,所以這個小哥決定乾脆在「dubstep」的音頻軌道里嵌入可讀的代碼,把這種真正的「數字化」的聲音放進去。
數據機噪音
首先,為了更好地了解歌曲中所有頻段的情況,需要分析這首歌的頻譜,把低頻和高頻分離開。我們需要的數據都隱藏在頻譜當中,這個部分也非常值得細說,下周的退送我們會詳細說說傳奇Aphex Twin的作品頻譜中隱藏的圖像。
libavfilter庫傳送門:
https://www.ffmpeg.org/libavfilter.html
FFmpeg中有一個類庫:libavfilter。該類庫提供了各種視音頻過濾器。
過濾的是《Right In》的build up和drop的部分的頻譜。
Ffmpeg代碼
$ sox orig.wav onlylower.wav sinc 0k-0.1k
演算法藝術實驗室AALab
Ffmpeg代碼
$ sox orig.wav onlyhigher.wav sinc -b 0 0.2k-22k$ cp onlyhigher.wav tmp.wav$ # Do a 2nd round to ensure the lower band is really empty$ sox tmp.wav onlyhigher.wav sinc -b 0 0.2k-22k$ rm tmp.wav
然後我們需要調製信號(將某一載有信息的信號嵌入到另一個信號)。對於非數字信號處理專業的且數學基礎薄弱的人來說這還挺難理解的。
這個人選擇的是ASK調製方式,通過一些動圖他向我們解釋了整個調製過程是怎麼回事。用葉師傅的話說,基本上就是波形生成的結果當參數繼續用在新的波形上,波形中嵌套各種波,一波還未平息一波又來侵襲,信號太平洋。
這個基本代碼很簡單,並在1和0之間切換:
package mainimport ( "encoding/binary" "flag" "log" "os")func main() { ins := flag.String("input", "./in.f64.data", "") outs := flag.String("out", "./out.f64.data", "") flag.Parse() inf, err := os.OpenFile(*ins, os.O_RDONLY, 0644) if err != nil { log.Fatalf("Unable to open output file %s", err.Error()) } outf, err := os.OpenFile(*outs, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { log.Fatalf("Unable to open output file %s", err.Error()) } Symbolrate := 11000 SamplesUntilChange := Symbolrate UpperFlip := false bits := 0 for { var raws float64 err := binary.Read(inf, binary.LittleEndian, &raws) if err != nil { log.Printf("Leaving %s", err.Error()) break } // First, obtain a upper flip normie := (raws + 1) / 2 SamplesUntilChange-- if SamplesUntilChange == 0 { UpperFlip = !UpperFlip SamplesUntilChange = Symbolrate bits++ } if !UpperFlip { normie = normie * -1 } if SamplesUntilChange < 1000 { dest := normie * -1 normie = lerp(dest, normie, float64(SamplesUntilChange)/1000.0) } binary.Write(outf, binary.LittleEndian, normie) } log.Printf("Finished with %d bits / %d bytes", bits, bits/8)}func lerp(a, b, n float64) float64 { return (1-n)*a + n*b}
然後就可以重新組合輸出低音譜文件和較高的頻率,並查看是否可以播放:
$ sox orig.wav onlylower.wav sinc 0k-0.1k$ sox orig.wav onlyhigher.wav sinc 0.1k-22k$ ffmpeg -i onlylower.wav -f f64le -ar 44100 -ac 1 -y in.f64.data$ ./ASK-dubstep -input in.f64.data$ ffmpeg -f f64le -ar 44100 -ac 1 -i out.f64.data -y encoded-bass.wav$ sox -m encoded-bass.wav onlyhigher.wav encoded-bassline.wav
為了編碼我們自己的數據,我們可以使用一個簡單的庫來幫助我們以易於使用的方式讀取數據,然後將它們嵌入在音頻中:
package mainimport ( "encoding/binary" "flag" "log" "os" "strings" "github.com/dgryski/go-bitstream")func main() { ins := flag.String("input", "./in.f64.data", "") outs := flag.String("out", "./out.f64.data", "") encodetarget := flag.String("data", "Hello World!", "") flag.Parse() inf, err := os.OpenFile(*ins, os.O_RDONLY, 0644) if err != nil { log.Fatalf("Unable to open output file %s", err.Error()) } outf, err := os.OpenFile(*outs, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { log.Fatalf("Unable to open output file %s", err.Error()) } sr := strings.NewReader(*encodetarget) bitreader := bitstream.NewReader(sr) Symbolrate := 5500 SamplesUntilChange := Symbolrate UpperFlip := false bits := 0 nextbit := false for { var raws float64 err := binary.Read(inf, binary.LittleEndian, &raws) if err != nil { log.Printf("Leaving %s", err.Error()) break } // First, obtain a upper flip normie := (raws + 1) / 2 SamplesUntilChange-- if SamplesUntilChange == 0 { UpperFlip = nextbit b, _ := bitreader.ReadBit() nextbit = bool(b) SamplesUntilChange = Symbolrate bits++ } if !UpperFlip { normie = normie * -1 } if SamplesUntilChange < 1000 && UpperFlip != nextbit { dest := normie * -1 normie = lerp(dest, normie, float64(SamplesUntilChange)/1000.0) } binary.Write(outf, binary.LittleEndian, normie) } log.Printf("Finished with %d bits / %d bytes", bits, bits/8)}func lerp(a, b, n float64) float64 { return (1-n)*a + n*b}
解碼器:
package mainimport ( "encoding/binary" "flag" "fmt" "log" "os" bitstream "github.com/dgryski/go-bitstream")func main() { ins := flag.String("input", "./in.f64.data", "") symbolrate := flag.Int("srate", 5500, "Symbol rate in samples") flag.Parse() inf, err := os.OpenFile(*ins, os.O_RDONLY, 0644) if err != nil { log.Fatalf("Unable to open output file %s", err.Error()) } bw := bitstream.NewWriter(os.Stdout) bw.WriteBit(bitstream.Bit(false)) SamplesUntilChange := *symbolrate bits := 1 negscore := 0 for { var raws float64 err := binary.Read(inf, binary.LittleEndian, &raws) if err != nil { fmt.Print("
") log.Printf("Leaving %s", err.Error()) break } isNeg := raws < 0 if isNeg { negscore++ } SamplesUntilChange-- if SamplesUntilChange == 0 { rsp := negscore < (*symbolrate / 2) if bits != 1 { bw.WriteBit(bitstream.Bit(rsp)) } negscore = 0 SamplesUntilChange = *symbolrate bits++ } } log.Printf("Finished with %d bits / %d bytes", bits, bits/8)}
然後將它重新組裝成位元組,並輸出到終端。
可能是考慮到版權問題,他用了一個不出名的音樂人(Smooth - Nowhere)的作品錄了一個現場演示視頻。
https://www.zhihu.com/video/984484186647949312最後,這個小哥把需要的軟體和怎麼運行貼到github上了
https://http://github.com/benjojo/dubstep-data
——
演算法藝術實驗室探索數學與編程在設計與藝術中一切之可能用運算和美學讓你變更酷——推薦閱讀:
※如何製作Future bass的Supersaw?
※[Iris Music Channel]Faded-Alan Walker-IrisVoiceTube
※混音指南筆記(一):混音設計的領域與目標
※中國嘻哈文化的崛起,在於你了不了解Hip-Hop?