[BaseLearn]Music21教程一

2,417 阅读6分钟

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战」。

music21是由MIT开发计算音乐学分析Python库,可以处理包括MusicXML,MIDI,abc在内的等多种格式的音乐文件,网络上并没有很多中文教程,希望分享给对计算机音乐抱有同样兴趣的伙伴。

官方文档

建议配合官方图片食用

音符(Notes)

标准音符的概念被包含在noteNote对象之中

直接输入note便可得到该模块的位置

如果你想知道note除了Note还包含什么,可以输入dir(note)

创建音符

让我们使用note.Note创建一个音符

 f = note.Note('F5')

音符的属性

F5是这个音符的音名

通过.name .step.octave可以得到其音名、音级、八度(在第几个八度)信息

 f.name f.step f.octave
 'F' 'F' 5

.step得到不包含变化音及八度信息的音名,这里成为音级严格来说并不准确

当然也可以使用.pitch直接得到其音名

 f.pitch
  <music21.pitch.Pitch F5>

使用.pitch.frequency得到其频率

 f.pitch.frequency
 音高698.456462866008

使用.pitch.pitchClass同样可以得到其音级(距离同一个八度中c的半音数字),使用 .pitch.pitchClassString则可以得到一个String

 f.pitch.pitchClassf.pitch.pitchClassString type(f.pitch.pitchClassString)
 音高5 '5' <class'str'>

在music21中,升降分别使用#-,我们创建一个新的音符

 b_flat = note.Note("B2-")

使用.pitch.accidental可获得其属性(accidental在音乐中表示变音记号),使用.pitch.accident.alter获得其半音变化数量,使用.pitch.accidental.name获得其

 b_flat.pitch.accidental b_flat.pitch.accidental.alter b_flat.pitch.accidental.name
 <accidental flat> -1.0 'flat'

注意,这里是一个浮点数,这意味着music21支持四分音符之类现实中通常不使用的东西

此外,并不是每一个音符都有accidental的,某些音符会返回None

我们可以使用一个判断语句来解决:

 if d.pitch.accidental is not None:
     print(d.pitch.accidental.name)

如果你安装了MusicXML阅读器,使用f.show()可查看其五线谱

修改音符

使用.transpose修改你的音符

 d = b_flat.transpose("M3") #将Bb上调大三度,变为D

这种用法并没有改变音符本身,而是返回一个变量

可以使用inplace=True进行原地操作

休止符

使用note.rest

 a = note.rest() # 记得加括号

最后提醒一个点,不要使用note作为音符的变量

 note = note.Note("C4")

音高(Pitch),时值(Duration)

音高

使用pitch.pitch()创建一个音高对象

 p1 = pitch.Pitch('b-4')

有许多属性和note是一样的

 pl.octave pl.name pl.pitchClass pl.accidental.alter 
 4 'B-' 10 -1.0

.transpose()同样可以使用

.nameWithOctave.midi

 pl.nameWithOctave pl.midi
 'B-4' 70

这些属性大多数都可以修改

 pl.name = "d#"
 pl.octave = 3
 pl.nameWithOctave 
 'D#3'

这时pl代表的音符已经变为了D#3

实际上,每一个Note对象内部,都有一个Pitch对象,我们对note.Note做的一切,都可以用note.Note.pitch对象代替

一些note不支持的属性

 csharp.pitch.spanish # 获得其西班牙名称

可以使用一些其他的方法,来更清晰地打印

 print(csharp.pitch.unicodeName) 
  C♯

获得一些同音的方法

 print( csharp.pitch.getEnharmonic() )
 print( csharp.pitch.getLowerEnharmonic() )
  D-4
  B##3

时值

任何音符的存在都离不开时值Duration

创建一个二分音符

 halfDuration = duration.Duration('half')
 # ‘whole','half','quarter','eighth','16th','32th','64th' 一直到'2048th',虽太小无法在乐谱上无法显示
 # 'breve','longa','maxima' 2,4,8个全音

另一种创建方法是说明他有多少个四分音符

 dottedQuarter = duration.Duration(1.5) 

可以使用.quarterLength得到时值是多少个四分音符

还可以使用Note创建

 c = note.Note("C4", type='whole')

.type可以得到一般类型,如'half','quarter'

.dots可以得到音符有多少个附点

使用.lyric添加歌词(具体看文档吧,不具体介绍了)

 otherNote = note.Note("F6")
 otherNote.lyric = "I'm the Queen of the Night!"

流(Stream)

我们可以通过列表对note等对象进行处理,但是它们对音乐一无所知,因此需要一个类似于列表的对象具有一定“智能”的对象,成为Stream

流有许多子类Score乐谱、Part声部、Measure小节

创建流

Stream中储存的元素必须是music21对象,如果想加入不属于music21的对象,请将其放入ElementWrapper

使用Stream()创建流,.append()方法添加元素,.repeatAppend()方法添加多个相同的音符

 stream1 = stream.Stream()
 stream1.append(note1)
 stream1.append(note2)
 stream1.append(note3)
 ​
 stream2 = stream.Stream()
 n3 = note.Note('D#5') # octave values can be included in creation arguments
 stream2.repeatAppend(n3, 4)

使用.show('text')查看其中的内容及其偏移量(从0.0开始,一般1个偏移量指一个四分音符的长度)

 stream1.show('text')
  {0.0} <music21.note.Note C>
  {2.0} <music21.note.Note F#>
  {3.0} <music21.note.Note B->

流的大部分方法和列表相同,如切片、索引(还可使用.index()访问)、pop().append()len()等,并且流中也可以存放列表

按类分离元素

提供一种过滤流以获取所需元素.getElementByClass()

 for thisNote in stream1.getElementsByClass(["Note", "Rest"]):
     print(thisNote, thisNote.offset)
  <music21.note.Note C> 0.0
  <music21.note.Note F#> 2.0
  <music21.note.Note B-> 3.0

此外也可使用.notes.notesAndRests.pitches等来进行过滤,直接使用会返回所有,如

 stream1.pitches
 [<music21.pitch.Pitch D#5>,
  <music21.pitch.Pitch D#5>,
  <music21.pitch.Pitch D#5>,
  <music21.pitch.Pitch D#5>]
 ​
 for thisNote in stream1.notesAndRests:
     print(thisNote)
  <music21.note.Note C>
  <music21.note.Note F#>
  <music21.note.Note B->

通过偏移量分离元素

getElementsByOffset()

 sOut = stream1.getElementsByOffset(2, 3).stream()
 sOut.show('text')
  {2.0} <music21.note.Note F#>
  {3.0} <music21.note.Note B->   

还有getElementAtOrBefore()(某个偏移量及其之前score = stream.Score() 1),getElementAfterElement()(某个偏移量之后)

更多功能

.analyze('ambitus')获得流中的音域范围

.lowestOffset返回偏移量的最小值

__repr__

.id,可以自己设定,相当于名字,如

 s = stream.Score(id='mainScore')
 p0 = stream.Part(id='part0')
 p1 = stream.Part(id='part1')

.duration储存Duration对象的属性

小节(Measure)

可以使用corpus访问大量的乐谱,使用Parse()从语料库中解析出Score(一种流的子类)

 sBach = corpus.parse('bach/bwv57.8')

它包含一个Metadata对象、一个 StaffGroup对象和四个 Part对象。

可以使用measures()measure()获取多个或一个小节,前者获取整个乐曲所有Part的小节,后者必须对一个Measure对象

然而一个问题是,这与使用getElementsByClass(stream.Measure)并不相同,因为在乐曲中存在小节并不连续的情况

递归方法

.recurse()可以访问流中的每一个元素,若任何子元素也是流,他将访问该流中的每一个元素

他会返回一个生成器,使用循环来访问每一个元素

 recurseScore = s.recurse()
 recurseScore
  <music21.stream.iterator.RecursiveIterator for Score:mainScore @:0>
 for el in s.recurse():
     print(el.offset, el, el.activeSite)
  0.0 <music21.stream.Part part0> <music21.stream.Score mainScore>
  0.0 <music21.stream.Measure 1 offset=0.0> <music21.stream.Part part0>
  0.0 <music21.note.Note C> <music21.stream.Measure 1 offset=0.0>
  4.0 <music21.stream.Measure 2 offset=4.0> <music21.stream.Part part0>
  0.0 <music21.note.Note D> <music21.stream.Measure 2 offset=4.0>
  0.0 <music21.stream.Part part1> <music21.stream.Score mainScore>
  0.0 <music21.stream.Measure 1 offset=0.0> <music21.stream.Part part1>
  0.0 <music21.note.Note E> <music21.stream.Measure 1 offset=0.0>
  4.0 <music21.stream.Measure 2 offset=4.0> <music21.stream.Part part1>
  0.0 <music21.note.Note F> <music21.stream.Measure 2 offset=4.0>                    

大多数过滤方法也可以用于该生成器

扁平化流

.flat可以将嵌套流打平,并赋予新的offset

和弦(Chord)

和弦(Chord)

创建和弦

 cMinor = chord.Chord(["C4","G4","E5-"])  # 通过音名的列表创建
 ​
 d = note.Note('D4')
 fSharp = note.Note('F#4')
 a = note.Note('A5')
 dMajor = chord.Chord([d, fSharp, a]) # 添加已有的音符
 ​
 e7 = chord.Chord("E4 G#4 B4 D5") # 通过带有空格的字符串创建
 ​
 es = chord.Chord("E- G B-") # 如果所有音符的都在一个八度内,则不需要八度信息
 ​

功能

ChordNote对象都是GneralNote对象的子类,因此大部分Note的属性,Chord都能使用

其中,音高由.pitch被替换为.pitches

一些其他功能

 cMinor.isMinorTriad() # 是否是小三和弦
 cMinor.isMajorTriad() # 是否是大三和弦
 cMinor.inversion() # 是否处于转位状态
 cMinor.add() # 添加音符
 cMinor.remove() # 移除音符
 cClosed = cMinor.closedPosition() # 封闭和弦,不会该改变原对象
 semiClosedPosition() # 不太懂
 cn1 = cMinor.commonName # 得到和弦的名字
 fMajor.fullName # 包含step和时值
 fMajor.pitchedCommonName # 加入了pitch信息

和弦和音符一样可以添加到流之中

加载文件

使用corpus.parse()从语料库加载文件

使用converter.parse()从本地磁盘或网络加载文件

如果文件名没有后缀或者后缀有误,可以使用format="FORMAT"

music21会在第一遍读取文件后保存优化版本,若下次读取时检测到没有更改文件,那么速度将会提升2到5倍(大概是通过检查文件修改时间是否变动,可以使用forceSource=True来保证重新加载原文件)

使用converter.Converter().subconvertersList('input')得到其可以读入的所有格式

使用converter.Converter().subconvertersList('output')得到其可以写的所有格式

其可以使用的文件格式有HumdrumMusicXMLMusicXMLMusedataMIDI