这也是我第一次在这里发布信息,如果我搞砸了,抱歉。我不确定该怎么称呼,但是我上课的项目但是我蛮横地迫使我摆脱了今天的问题,并且想知道是否有人能够以更有效的方式来做我需要的事情。我花了2个小时很好地研究它,很想看看你们如何解决它。
背景:使用Midi库,您可以提取音轨的音符以及它们的播放时间。完成一些工作后,您将得到如下内容:
notes = ['B3', 'C4', 'G2', 'G3', 'B3', 'D4', 'F4', 'G2', 'D4', 'F4']
ticks = [0, 1, 12, 12, 12, 15, 15, 22, 22,]
滴答基本上是midi的时域。它表示该音符首次播放的时间,并且列表ticks
对应于notes
(notes[0]
在时间ticks[0]
播放)。出于我的目的,我一次只能通过4个设备中的1个播放音符。因此,当我重复滴答声时,我需要同时弹奏和弦或多个音符。默认情况下,设备(通道0)播放一个音符,通道1播放两个音符,依此类推...
例如上面的曲目将像这样播放;
问题:给定notes
和ticks
,使用正确的时间和音符为通道0-3创建指令。如果一个和弦的音调超过4个,我便会忽略它们,因为我对它们的处理不多。基本上,我需要数据结构如下所示:
channel0 = ['B3', 'C4', 'G2', 'D4']
channel1 = [ 0, 0, 'G3', 'F4']
channel2 = [ 0, 0, 'B3', 0 ]
channel3 = [ 0, 0, 0, 0 ]
我首先试图用一堆if语句来怪异地强行使用它,但最终想到了这一点。
解决方案:首先,我创建了一个元组列表,以比较每个刻度在音轨中出现的次数,但这毫无意义。我只是把它做成一个数组。
res = []
for i in ticks:
if i not in res:
res.append(i)
counts = [(ticks.count(x)) for x in res]
然后,我列出了频道列表。我将它们向后排序,因为从下到上更容易处理“矩阵”。
ch0, ch1, ch2, ch3 = ([] for i in range(4))
final = [ch3, ch2, ch1, ch0]
最后,这种可耻的事情以某种方式起作用。
countsidx = -1
while breakMe:
countsidx += 1
for finalidx, vchan in enumerate(final):
if int(finalidx) >= counts[countsidx]:
vchan.append(0)
else:
vchan.append(notes[notesidx])
notesidx += 1
if notesidx == len(notes):
return final
breakMe = False
return final
所以我的问题是:我怎么能用更少的代码做到这一点?有没有人可以更轻松地完成与我相同的操作?我喜欢学习最佳做法。我觉得我花了太多时间在这个简单的事情上。
完整代码
import matplotlib.pyplot as plt
import argparse
import sys
import collections
from mido import MidiFile
from midiutil import MIDIFile
import sys
NOTES = ['C', 'CS', 'D', 'DS', 'E', 'F', 'FS', 'G', 'GS', 'A', 'AS', 'B']
OCTAVES = list(range(11))
NOTES_IN_OCTAVE = len(NOTES)
def countShit(ticks,notes):
breakMe = True
res = []
for i in ticks:
if i not in res:
res.append(i)
counts = [(ticks.count(x)) for x in res]
print('Counts (ticks,occurences) == ', counts)
ch0,ch1,ch2,ch3, = ([] for i in range(4))
final =[ch3,ch2,ch1,ch0]
notesidx = 0
countsidx = -1
while breakMe:
countsidx += 1
for finalidx, vchan in enumerate(final):
if int(finalidx) >= counts[countsidx]:
vchan.append(0)
else:
vchan.append(notes[notesidx])
notesidx += 1
if notesidx == len(notes):
return final
breakMe = False
return final
def convertTuple(tup):
str = ''.join(tup)
return str
def number_to_note(number: int) -> tuple:
octave = number // NOTES_IN_OCTAVE
assert octave in OCTAVES, errors['notes']
assert 0 <= number <= 127, errors['notes']
note = NOTES[number % NOTES_IN_OCTAVE]
return str(note),str(octave-2)
song = midi.read_midifile('mario_06.mid')
song.make_ticks_abs()
tracks =[]
trackNotes =[]
trackTime = []
trackTicks=[]
for track in song:
notes = [note for note in track if note.name == 'Note On']
notes2 = [note for note in track if note.name == 'Note Off']
pitch = [note.pitch for note in notes]
tick = [note.tick for note in notes]
trackTime =[b.tick - a.tick for a,b in zip(notes,notes2)]
tracks += [tick, pitch]
trackNotes += pitch
trackTicks += tick
trackNotesFinal =[]
for i in trackNotes:
k = str(convertTuple(number_to_note(i)))
trackNotesFinal.append(k)
trackNotesFinal
channels = countShit(trackTicks,trackNotesFinal)
答案 0 :(得分:0)
这是使用列表推导和zip
和itertools.groupby
的解决方案。我假设ticks
数组总是有序的。
import itertools
from operator import itemgetter
NUM_CHANNELS = 4
chords = [
[ note for _, note in chord ]
for _, chord in itertools.groupby(zip(ticks, notes), key=itemgetter(0))
]
# [['B3'], ['C4'], ['G2', 'G3', 'B3'], ['D4', 'F4'], ['G2', 'D4']]
channels = [
[ chord[i] if i < len(chord) else None for chord in chords ]
for i in range(NUM_CHANNELS)
]
# [['B3', 'C4', 'G2', 'D4', 'G2'],
# [None, None, 'G3', 'F4', 'D4'],
# [None, None, 'B3', None, None],
# [None, None, None, None, None]]
由于没有注释,我使用None
代替了0
,因为它更具有Python风格(而且,打印时使矩阵列排列得很好)。
由于您可能还想知道矩阵中每一列的时间,因此可以通过另一种列表理解来实现:
timings = [ tick for tick, _ in itertools.groupby(ticks) ]
# [0, 1, 12, 15, 22]