这事儿得从一个深夜说起,我正对着屏幕上一坨红色的报错发呆,隔壁工位的小王突然把手机怼到我脸上——一个外国小伙儿举着吉他,背后是365天的视频快剪,从春天拍到冬天,从街头唱到海边,配乐那叫一个上头,我愣是循环了三遍才想起来问:这歌叫啥?

他耸耸肩:“不知道,反正是他自己写的。” 我低头看了看自己写的那个 for 循环,忽然觉得,也许该用点不一样的方式,记录点什么。
一年的数据,其实就是一个循环
先说说那视频给我的第一感觉——像极了我们写 time.Tick 的感觉,一天一帧,一天一个 append,攒够了365个元素,就是一个完整的切片,老外那吉他扫弦,看着随意,但你仔细听,每一段副歌出现的位置,正好是视频里某个特殊的日期:初次约会、第一次吵架、和好那天的晚霞。
这就让我想到了Go语言里的 slice,你要是想在一个 slice 的中间插入点什么,得小心翼翼地挪位置,还要处理底层数组扩容的边界,但如果你只是老老实实地 append 一年,最后遍历一遍,那感觉反而最踏实。
我找到的那个BGM,其实不是现成的曲子,而是一个开源项目里的一段算法生成的旋律,原作者叫 Jacob,用Go写了个小工具,把视频里每一帧的亮度平均值转成音高,再映射到五声音阶上,你听着是首歌,其实它是一整年的光和影。
用range去理解每一天
我当时冒出的第一个念头是:怎么把这段旋律提取出来?视频里的歌有轻微的底噪,像是录音棚外头的车流声,但吉他音轨特别干净,GitHub上有个库叫 go-audio,专门处理这类解码。
代码逻辑挺直白:先读音频文件,拿 FrameBuffer 按块切,每1024帧为一组,算它的快速傅里叶变换(FFT),吉他弦音大概在80Hz到1200Hz之间,人声会往上顶到2000多,所以我就设了个带通滤波器,把频率掐在150Hz和1000Hz之间,然后找每个窗格里的振幅峰值,那就是当下最响的音符。
func extractNote(buffer *audio.FrameBuffer) float64 {
// 计算频谱重心之类的东西
return frequency
}
你看看,这不就跟那外国小伙每天拍一分钟素材一样么?他是按天取景,我是按毫秒取频率,他的365秒成一部电影,我的365组频谱凑成一张表格。
你的BGM,可能是调好的一碗map
如果你自己动手去搜那个“365天求婚视频”的BGM,大概率会翻到一段叫 “Vlog No. 365” 的钢琴曲,但视频里那个外国小伙,人家后来在评论区说了,那是他用GarageBand自己敲的三和弦走向——C大调,G,Am,F,就这么来回循环。
我试着用Go写了个简单的MIDI生成器,模仿那个走向,得先定义音符频率,MIDI标准里,A4是440Hz,然后每个半音是乘以2的1/12次方。
var halfTone = math.Pow(2, 1.0/12.0)
func midiToFreq(note int) float64 {
return 440.0 * math.Pow(halfTone, float64(note-69))
}
你看,就这几行代码,我可以生成一个C大调的分解和弦,做成WAV文件,但真当代码跑起来,听着那干巴巴的正弦波,我忽然觉得,那视频里打动人的从来不在音符本身,而是音符背后的时间戳——第一天他手抖得拿不稳相机,第一百天她出现在镜头里笑了一下。
节奏是goroutine的事
节奏这事儿,在程序里其实就是 time.Sleep 或者调度器,但你要是听那首BGM,会发现它的鼓点特别“不稳”,偶尔慢半拍,偶尔抢半拍,后来看制作花絮才明白——那老外把每一个鼓点都手动拖到了他亲吻她的那一帧前面。
这跟Go里的 goroutine 调度特别像,你无法精确控制哪个协程先跑,只能控制它们谁先发信号,他用 channel 来传情绪,我用 channel 来传采样点数据:
ch := make(chan float64, 365)
go func() {
for _, day := range days {
ch <- brightnessToVolume(day)
}
}()
每发一个值,吉他声就抖一下,正好对应那视频里每一天的天气阴晴,云多的日子音量压低了,晴天拨片弹得特别脆。
那把吉他,其实是把自己写成了日志
我后来把那个算法跑了整整一夜,第二天早上看输出文件,出来的旋律居然有点耳熟——前15秒像《卡农》,中间有段滑音自己冒了出来,结尾停在一个延长音上,像对方叹了口气。
你非要问这BGM叫什么名字?我可以告诉你,那段音频的开头,有老外自己嘀咕的一句“Okay, let's try this again”,这大概是他录废了无数个开头里的一次,可这恰恰是最动人的部分——不完美的东西,才更像真的在爱着。
写代码写到这儿,我顺手把脚本封了个装,给它起了个名叫 songOfDays.go,没做UI,没写注释,注释就一句话:
// 365天,一天一个音符,按时间排序,别怕跑调,她听不出来的。
外面天亮了,小区鸟叫了,那视频还在我电脑上循环着,你看,他用一年拍了支片子,我用一晚上写了首曲子,代码不完美,旋律也有点糙,但最关键的是——defer 这句,我一直没写在函数里,因为它得在求婚成功那天,才执行。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/tiyu/2690.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《当代码遇上求婚,用Go语言写一首给她的曲子》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:这事儿得从一个深夜说起,我正对着屏幕上一坨红色的报错发呆,隔壁工位的小王突然把手机怼到我脸上——一个外国小伙儿举着吉他,背后是365天的...