PageRenderTime 85ms CodeModel.GetById 40ms RepoModel.GetById 1ms app.codeStats 0ms

/music21/variant.py

http://music21.googlecode.com/
Python | 2508 lines | 2410 code | 26 blank | 72 comment | 28 complexity | 9270b73819c2854efb356bd989b626e0 MD5 | raw file
Possible License(s): BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. # -*- coding: utf-8 -*-
  2. #-------------------------------------------------------------------------------
  3. # Name: variant.py
  4. # Purpose: Translate MusicXML and music21 objects
  5. #
  6. # Authors: Christopher Ariza
  7. # Evan Lynch
  8. # Michael Scott Cuthbert
  9. #
  10. # Copyright: Copyright Š 2012 Michael Scott Cuthbert and the music21 Project
  11. # License: LGPL
  12. #-------------------------------------------------------------------------------
  13. '''
  14. Contains :class:`~music21.variant.Variant` and its subclasses, as well as functions for merging
  15. and showing different variant streams. These functions and the variant class should only be
  16. used when variants of a score are the same length and contain the same measure structure at
  17. this time.
  18. '''
  19. import unittest
  20. import copy
  21. import difflib
  22. from music21 import base
  23. from music21 import exceptions21
  24. from music21 import common
  25. from music21 import stream
  26. from music21 import environment
  27. from music21 import note
  28. from music21 import search
  29. _MOD = "variant.py"
  30. environLocal = environment.Environment(_MOD)
  31. #-------Public Merge Functions
  32. def mergeVariants(streamX, streamY, variantName = 'variant', inPlace = False):
  33. '''
  34. Takes two streams objects or their derivatives (score, part, measure, etc.) which should be variant versions of the same stream,
  35. and merges them (determines differences and stores those differences as variant objects in streamX) via the appropriate merge
  36. function for their type. This will not know how to deal with scores meant for mergePartAsOssia(). If this is the intention, use
  37. that function instead.
  38. >>> aScore, vScore= stream.Score(), stream.Score()
  39. >>> ap1 = stream.Part(converter.parse(" a4 b c d e2 f2 g2 f4 g4 ", "4/4").makeMeasures())
  40. >>> vp1 = stream.Part(converter.parse(" a4 b c e e2 f2 g2 f4 a4 ", "4/4").makeMeasures())
  41. >>> ap2 = stream.Part(converter.parse(" a4 g f e f2 e2 d2 g4 f4 ", "4/4").makeMeasures())
  42. >>> vp2 = stream.Part(converter.parse(" a4 g f e f2 g2 f2 g4 d4 ", "4/4").makeMeasures())
  43. >>> aScore.insert(0.0, ap1)
  44. >>> aScore.insert(0.0, ap2)
  45. >>> vScore.insert(0.0, vp1)
  46. >>> vScore.insert(0.0, vp2)
  47. >>> mergedScore = variant.mergeVariants(aScore, vScore, variantName = 'docvariant', inPlace = False)
  48. >>> mergedScore.show('text')
  49. {0.0} <music21.stream.Part ...>
  50. {0.0} <music21.variant.Variant object of length 4.0>
  51. ...
  52. {0.0} <music21.stream.Part ...>
  53. {0.0} <music21.stream.Measure 1 offset=0.0>
  54. ...
  55. {4.0} <music21.variant.Variant object of length 8.0>
  56. {4.0} <music21.stream.Measure 2 offset=4.0>
  57. ...
  58. {4.0} <music21.bar.Barline style=final>
  59. >>> mergedPart = variant.mergeVariants(ap2, vp2, variantName = 'docvariant', inPlace = False)
  60. >>> mergedPart.show('text')
  61. {0.0} <music21.stream.Measure 1 offset=0.0>
  62. ...
  63. {4.0} <music21.variant.Variant object of length 8.0>
  64. {4.0} <music21.stream.Measure 2 offset=4.0>
  65. ...
  66. {4.0} <music21.bar.Barline style=final>
  67. >>> streamX = converter.parse("a4 b c d", "4/4")
  68. >>> streamY = converter.parse("a4 d c b", "4/4")
  69. >>> mergedStream = variant.mergeVariants(streamX, streamY, variantName = 'docvariant', inPlace = False)
  70. >>> mergedStream.show('text')
  71. {0.0} <music21.meter.TimeSignature 4/4>
  72. {0.0} <music21.note.Note A>
  73. {1.0} <music21.variant.Variant object of length 1.0>
  74. {1.0} <music21.note.Note B>
  75. {2.0} <music21.note.Note C>
  76. {3.0} <music21.variant.Variant object of length 1.0>
  77. {3.0} <music21.note.Note D>
  78. >>> streamY = converter.parse("a4 b c d e f g a", "4/4")
  79. >>> variant.mergeVariants(streamX, streamY, variantName = 'docvariant', inPlace = False)
  80. Traceback (most recent call last):
  81. ...
  82. VariantException: Could not determine what merging method to use. Try using a more specific merging function.
  83. '''
  84. classesX = streamX.classes
  85. if "Score" in classesX:
  86. return mergeVariantScores(streamX, streamY, variantName, inPlace = inPlace)
  87. elif "Part" in classesX or len(streamX.getElementsByClass("Measure")) > 0:
  88. return mergeVariantMeasureStreams(streamX, streamY, variantName, inPlace = inPlace)
  89. elif len(streamX.notesAndRests) > 0 and streamX.duration.quarterLength == streamY.duration.quarterLength:
  90. return mergeVariantsEqualDuration([streamX, streamY], [variantName], inPlace = inPlace)
  91. else:
  92. raise VariantException("Could not determine what merging method to use. Try using a more specific merging function.")
  93. def mergeVariantScores(aScore, vScore, variantName = 'variant', inPlace = False):
  94. '''
  95. Takes two scores and merges them with mergeVariantMeasureStreams, part-by-part.
  96. >>> aScore, vScore= stream.Score(), stream.Score()
  97. >>> ap1 = stream.Part(converter.parse(" a4 b c d e2 f2 g2 f4 g4 ", "4/4").makeMeasures())
  98. >>> vp1 = stream.Part(converter.parse(" a4 b c e e2 f2 g2 f4 a4 ", "4/4").makeMeasures())
  99. >>> ap2 = stream.Part(converter.parse(" a4 g f e f2 e2 d2 g4 f4 ", "4/4").makeMeasures())
  100. >>> vp2 = stream.Part(converter.parse(" a4 g f e f2 g2 f2 g4 d4 ", "4/4").makeMeasures())
  101. >>> aScore.insert(0.0, ap1)
  102. >>> aScore.insert(0.0, ap2)
  103. >>> vScore.insert(0.0, vp1)
  104. >>> vScore.insert(0.0, vp2)
  105. >>> mergedScores = variant.mergeVariantScores(aScore, vScore, variantName = 'docvariant', inPlace = False)
  106. >>> mergedScores.show('text')
  107. {0.0} <music21.stream.Part ...>
  108. {0.0} <music21.variant.Variant object of length 4.0>
  109. {0.0} <music21.stream.Measure 1 offset=0.0>
  110. {0.0} <music21.clef.TrebleClef>
  111. {0.0} <music21.meter.TimeSignature 4/4>
  112. {0.0} <music21.note.Note A>
  113. {1.0} <music21.note.Note B>
  114. {2.0} <music21.note.Note C>
  115. {3.0} <music21.note.Note D>
  116. {4.0} <music21.stream.Measure 2 offset=4.0>
  117. {0.0} <music21.note.Note E>
  118. {2.0} <music21.note.Note F>
  119. {8.0} <music21.variant.Variant object of length 4.0>
  120. {8.0} <music21.stream.Measure 3 offset=8.0>
  121. {0.0} <music21.note.Note G>
  122. {2.0} <music21.note.Note F>
  123. {3.0} <music21.note.Note G>
  124. {4.0} <music21.bar.Barline style=final>
  125. {0.0} <music21.stream.Part ...>
  126. {0.0} <music21.stream.Measure 1 offset=0.0>
  127. {0.0} <music21.clef.TrebleClef>
  128. {0.0} <music21.meter.TimeSignature 4/4>
  129. {0.0} <music21.note.Note A>
  130. {1.0} <music21.note.Note G>
  131. {2.0} <music21.note.Note F>
  132. {3.0} <music21.note.Note E>
  133. {4.0} <music21.variant.Variant object of length 8.0>
  134. {4.0} <music21.stream.Measure 2 offset=4.0>
  135. {0.0} <music21.note.Note F>
  136. {2.0} <music21.note.Note E>
  137. {8.0} <music21.stream.Measure 3 offset=8.0>
  138. {0.0} <music21.note.Note D>
  139. {2.0} <music21.note.Note G>
  140. {3.0} <music21.note.Note F>
  141. {4.0} <music21.bar.Barline style=final>
  142. '''
  143. if len(aScore.parts) != len(vScore.parts):
  144. raise VariantException("These scores do not have the same number of parts and cannot be merged.")
  145. if inPlace is True:
  146. returnObj = aScore
  147. else:
  148. returnObj = copy.deepcopy(aScore)
  149. for returnPart, vPart in zip(returnObj.parts, vScore.parts):
  150. mergeVariantMeasureStreams(returnPart, vPart, variantName, inPlace = True)
  151. if inPlace is False:
  152. return returnObj
  153. def mergeVariantMeasureStreams(streamX, streamY, variantName = 'variant', inPlace = False):
  154. '''
  155. Takes two streams of measures and returns a stream (new if inPlace is False) with the second
  156. merged with the first as variants. This function differs from mergeVariantsEqualDuration by
  157. dealing with streams that are of different length. This function matches measures that are
  158. exactly equal and creates variant objects for regions of measures that differ at all. If more
  159. refined variants are sought (with variation within the bar considered and related but different
  160. bars associated with each other), use variant.refineVariant().
  161. In this example, the second bar has been deleted in the second version, a new bar has been inserted between the
  162. original third and fourth bars, and two bars have been added at the end.
  163. >>> data1M1 = [('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter')]
  164. >>> data1M2 = [('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter'),('b', 'quarter')]
  165. >>> data1M3 = [('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')]
  166. >>> data1M4 = [('d', 'quarter'), ('g', 'eighth'), ('g', 'eighth'), ('a', 'quarter'), ('b', 'quarter')]
  167. >>> data2M1 = [('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter')]
  168. >>> data2M2 = [('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')]
  169. >>> data2M3 = [('e', 'quarter'), ('g', 'eighth'), ('g', 'eighth'), ('a', 'quarter'), ('b', 'quarter')]
  170. >>> data2M4 = [('d', 'quarter'), ('g', 'eighth'), ('g', 'eighth'), ('a', 'quarter'), ('b', 'quarter')]
  171. >>> data2M5 = [('f', 'eighth'), ('c', 'quarter'), ('a', 'eighth'), ('a', 'quarter'), ('b', 'quarter')]
  172. >>> data2M6 = [('g', 'quarter'), ('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')]
  173. >>> data1 = [data1M1, data1M2, data1M3, data1M4]
  174. >>> data2 = [data2M1, data2M2, data2M3, data2M4, data2M5, data2M6]
  175. >>> stream1 = stream.Stream()
  176. >>> stream2 = stream.Stream()
  177. >>> mNumber = 1
  178. >>> for d in data1:
  179. ... m = stream.Measure()
  180. ... m.number = mNumber
  181. ... mNumber += 1
  182. ... for pitchName,durType in d:
  183. ... n = note.Note(pitchName)
  184. ... n.duration.type = durType
  185. ... m.append(n)
  186. ... stream1.append(m)
  187. >>> mNumber = 1
  188. >>> for d in data2:
  189. ... m = stream.Measure()
  190. ... m.number = mNumber
  191. ... mNumber += 1
  192. ... for pitchName,durType in d:
  193. ... n = note.Note(pitchName)
  194. ... n.duration.type = durType
  195. ... m.append(n)
  196. ... stream2.append(m)
  197. >>> #_DOCS_SHOW stream1.show()
  198. .. image:: images/variant_measuresStreamMergeStream1.*
  199. :width: 600
  200. >>> #_DOCS_SHOW stream2.show()
  201. .. image:: images/variant_measuresStreamMergeStream2.*
  202. :width: 600
  203. >>> mergedStream = variant.mergeVariantMeasureStreams(stream1, stream2, 'paris', inPlace = False)
  204. >>> mergedStream.show('text')
  205. {0.0} <music21.stream.Measure 1 offset=0.0>
  206. {0.0} <music21.note.Note A>
  207. {1.0} <music21.note.Note B>
  208. {1.5} <music21.note.Note C>
  209. {2.0} <music21.note.Note A>
  210. {3.0} <music21.note.Note A>
  211. {4.0} <music21.variant.Variant object of length 0.0>
  212. {4.0} <music21.stream.Measure 2 offset=4.0>
  213. {0.0} <music21.note.Note B>
  214. {0.5} <music21.note.Note C>
  215. {1.0} <music21.note.Note A>
  216. {2.0} <music21.note.Note A>
  217. {3.0} <music21.note.Note B>
  218. {8.0} <music21.stream.Measure 3 offset=8.0>
  219. {0.0} <music21.note.Note C>
  220. {1.0} <music21.note.Note D>
  221. {2.0} <music21.note.Note E>
  222. {3.0} <music21.note.Note E>
  223. {12.0} <music21.variant.Variant object of length 4.0>
  224. {12.0} <music21.stream.Measure 4 offset=12.0>
  225. {0.0} <music21.note.Note D>
  226. {1.0} <music21.note.Note G>
  227. {1.5} <music21.note.Note G>
  228. {2.0} <music21.note.Note A>
  229. {3.0} <music21.note.Note B>
  230. {16.0} <music21.variant.Variant object of length 8.0>
  231. >>> mergedStream.variants[0].replacementDuration
  232. 4.0
  233. >>> mergedStream.variants[1].replacementDuration
  234. 0.0
  235. >>> parisStream = mergedStream.activateVariants('paris', inPlace = False)
  236. >>> parisStream.show('text')
  237. {0.0} <music21.stream.Measure 1 offset=0.0>
  238. {0.0} <music21.note.Note A>
  239. {1.0} <music21.note.Note B>
  240. {1.5} <music21.note.Note C>
  241. {2.0} <music21.note.Note A>
  242. {3.0} <music21.note.Note A>
  243. {4.0} <music21.variant.Variant object of length 4.0>
  244. {4.0} <music21.stream.Measure 2 offset=4.0>
  245. {0.0} <music21.note.Note C>
  246. {1.0} <music21.note.Note D>
  247. {2.0} <music21.note.Note E>
  248. {3.0} <music21.note.Note E>
  249. {8.0} <music21.variant.Variant object of length 0.0>
  250. {8.0} <music21.stream.Measure 3 offset=8.0>
  251. {0.0} <music21.note.Note E>
  252. {1.0} <music21.note.Note G>
  253. {1.5} <music21.note.Note G>
  254. {2.0} <music21.note.Note A>
  255. {3.0} <music21.note.Note B>
  256. {12.0} <music21.stream.Measure 4 offset=12.0>
  257. {0.0} <music21.note.Note D>
  258. {1.0} <music21.note.Note G>
  259. {1.5} <music21.note.Note G>
  260. {2.0} <music21.note.Note A>
  261. {3.0} <music21.note.Note B>
  262. {16.0} <music21.variant.Variant object of length 0.0>
  263. {16.0} <music21.stream.Measure 5 offset=16.0>
  264. {0.0} <music21.note.Note F>
  265. {0.5} <music21.note.Note C>
  266. {1.5} <music21.note.Note A>
  267. {2.0} <music21.note.Note A>
  268. {3.0} <music21.note.Note B>
  269. {20.0} <music21.stream.Measure 6 offset=20.0>
  270. {0.0} <music21.note.Note G>
  271. {1.0} <music21.note.Note D>
  272. {2.0} <music21.note.Note E>
  273. {3.0} <music21.note.Note E>
  274. >>> parisStream.variants[0].replacementDuration
  275. 0.0
  276. >>> parisStream.variants[1].replacementDuration
  277. 4.0
  278. >>> parisStream.variants[2].replacementDuration
  279. 8.0
  280. '''
  281. if inPlace is True:
  282. returnObj = streamX
  283. else:
  284. returnObj = copy.deepcopy(streamX)
  285. regions = _getRegionsFromStreams(returnObj,streamY)
  286. for regionType, xRegionStartMeasure, xRegionEndMeasure, yRegionStartMeasure, yRegionEndMeasure in regions: # Note that the 'end' measure indices are 1 greater than the 0-indexed number of the measure.
  287. if xRegionStartMeasure >= len(returnObj.getElementsByClass('Measure')):
  288. startOffset = returnObj.duration.quarterLength #This deals with insertion at the end case where returnObj.measure(xRegionStartMeasure+1) does not exist.
  289. else:
  290. startOffset = returnObj.measure(xRegionStartMeasure+1).getOffsetBySite(returnObj)
  291. if regionType is 'equal':
  292. #yRegion = streamY.measures(yRegionStartMeasure+1, yRegionEndMeasure)
  293. continue #Do nothing
  294. elif regionType is 'replace':
  295. xRegion = returnObj.measures(xRegionStartMeasure+1, xRegionEndMeasure)
  296. replacementDuration = xRegion.duration.quarterLength
  297. yRegion = streamY.measures(yRegionStartMeasure+1, yRegionEndMeasure)
  298. elif regionType is 'delete':
  299. xRegion = returnObj.measures(xRegionStartMeasure+1, xRegionEndMeasure)
  300. replacementDuration = xRegion.duration.quarterLength
  301. yRegion = None
  302. elif regionType is 'insert':
  303. yRegion = streamY.measures(yRegionStartMeasure+1, yRegionEndMeasure)
  304. replacementDuration = 0.0
  305. addVariant(returnObj, startOffset, yRegion, variantName = variantName, replacementDuration = replacementDuration)
  306. if inPlace is True:
  307. return
  308. else:
  309. return returnObj
  310. def mergeVariantsEqualDuration(streams, variantNames, inPlace = False):
  311. '''
  312. Pass this function a list of streams (they must be of the same length or a VariantException will be raised).
  313. It will return a stream which merges the differences between the streams into variant objects keeping the
  314. first stream in the list as the default. If inPlace is True, the first stream in the list will be modified,
  315. otherwise a new stream will be returned. Pass a list of names to associate variants with their sources, if this list
  316. does not contain an entry for each non-default variant, naming may not behave properly. Variants that have the
  317. same differences from the default will be saved as separate variant objects (i.e. more than once under different names).
  318. Also, note that a streams with bars of differing lengths will not behave properly.
  319. >>> stream1 = stream.Stream()
  320. >>> stream2paris = stream.Stream()
  321. >>> stream3london = stream.Stream()
  322. >>> data1 = [('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter'),
  323. ... ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter'),
  324. ... ('b', 'quarter'), ('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter')]
  325. >>> data2 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter'),
  326. ... ('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'), ('a', 'quarter'),
  327. ... ('b', 'quarter'), ('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter')]
  328. >>> data3 = [('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter'),
  329. ... ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter'),
  330. ... ('c', 'quarter'), ('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter')]
  331. >>> for pitchName,durType in data1:
  332. ... n = note.Note(pitchName)
  333. ... n.duration.type = durType
  334. ... stream1.append(n)
  335. >>> for pitchName,durType in data2:
  336. ... n = note.Note(pitchName)
  337. ... n.duration.type = durType
  338. ... stream2paris.append(n)
  339. >>> for pitchName,durType in data3:
  340. ... n = note.Note(pitchName)
  341. ... n.duration.type = durType
  342. ... stream3london.append(n)
  343. >>> mergedStreams = variant.mergeVariantsEqualDuration([stream1, stream2paris, stream3london], ['paris', 'london'])
  344. >>> mergedStreams.show('t')
  345. {0.0} <music21.note.Note A>
  346. {1.0} <music21.variant.Variant object of length 1.0>
  347. {1.0} <music21.note.Note B>
  348. {1.5} <music21.note.Note C>
  349. {2.0} <music21.note.Note A>
  350. {3.0} <music21.variant.Variant object of length 1.0>
  351. {3.0} <music21.note.Note A>
  352. {4.0} <music21.note.Note B>
  353. {4.5} <music21.variant.Variant object of length 1.5>
  354. {4.5} <music21.note.Note C>
  355. {5.0} <music21.note.Note A>
  356. {6.0} <music21.note.Note A>
  357. {7.0} <music21.variant.Variant object of length 1.0>
  358. {7.0} <music21.note.Note B>
  359. {8.0} <music21.note.Note C>
  360. {9.0} <music21.variant.Variant object of length 2.0>
  361. {9.0} <music21.note.Note D>
  362. {10.0} <music21.note.Note E>
  363. >>> mergedStreams.activateVariants('london').show('t')
  364. {0.0} <music21.note.Note A>
  365. {1.0} <music21.variant.Variant object of length 1.0>
  366. {1.0} <music21.note.Note B>
  367. {1.5} <music21.note.Note C>
  368. {2.0} <music21.note.Note A>
  369. {3.0} <music21.variant.Variant object of length 1.0>
  370. {3.0} <music21.note.Note A>
  371. {4.0} <music21.note.Note B>
  372. {4.5} <music21.variant.Variant object of length 1.5>
  373. {4.5} <music21.note.Note C>
  374. {5.0} <music21.note.Note A>
  375. {6.0} <music21.note.Note A>
  376. {7.0} <music21.variant.Variant object of length 1.0>
  377. {7.0} <music21.note.Note C>
  378. {8.0} <music21.note.Note C>
  379. {9.0} <music21.variant.Variant object of length 2.0>
  380. {9.0} <music21.note.Note D>
  381. {10.0} <music21.note.Note E>
  382. If the streams contain parts and measures, the merge function will iterate through them and determine
  383. and store variant differences within each measure/part.
  384. >>> stream1 = stream.Stream()
  385. >>> stream2 = stream.Stream()
  386. >>> data1M1 = [('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter')]
  387. >>> data1M2 = [('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter'),('b', 'quarter')]
  388. >>> data1M3 = [('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')]
  389. >>> data2M1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')]
  390. >>> data2M2 = [('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'), ('a', 'quarter'), ('b', 'quarter')]
  391. >>> data2M3 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')]
  392. >>> data1 = [data1M1, data1M2, data1M3]
  393. >>> data2 = [data2M1, data2M2, data2M3]
  394. >>> tempPart = stream.Part()
  395. >>> for d in data1:
  396. ... m = stream.Measure()
  397. ... for pitchName,durType in d:
  398. ... n = note.Note(pitchName)
  399. ... n.duration.type = durType
  400. ... m.append(n)
  401. ... tempPart.append(m)
  402. >>> stream1.append(tempPart)
  403. >>> tempPart = stream.Part()
  404. >>> for d in data2:
  405. ... m = stream.Measure()
  406. ... for pitchName,durType in d:
  407. ... n = note.Note(pitchName)
  408. ... n.duration.type = durType
  409. ... m.append(n)
  410. ... tempPart.append(m)
  411. >>> stream2.append(tempPart)
  412. >>> mergedStreams = variant.mergeVariantsEqualDuration([stream1, stream2], ['paris'])
  413. >>> mergedStreams.show('t')
  414. {0.0} <music21.stream.Part ...>
  415. {0.0} <music21.stream.Measure 0 offset=0.0>
  416. {0.0} <music21.note.Note A>
  417. {1.0} <music21.variant.Variant object of length 1.0>
  418. {1.0} <music21.note.Note B>
  419. {1.5} <music21.note.Note C>
  420. {2.0} <music21.note.Note A>
  421. {3.0} <music21.variant.Variant object of length 1.0>
  422. {3.0} <music21.note.Note A>
  423. {4.0} <music21.stream.Measure 0 offset=4.0>
  424. {0.0} <music21.note.Note B>
  425. {0.5} <music21.variant.Variant object of length 1.5>
  426. {0.5} <music21.note.Note C>
  427. {1.0} <music21.note.Note A>
  428. {2.0} <music21.note.Note A>
  429. {3.0} <music21.note.Note B>
  430. {8.0} <music21.stream.Measure 0 offset=8.0>
  431. {0.0} <music21.note.Note C>
  432. {1.0} <music21.variant.Variant object of length 3.0>
  433. {1.0} <music21.note.Note D>
  434. {2.0} <music21.note.Note E>
  435. {3.0} <music21.note.Note E>
  436. >>> #_DOCS_SHOW mergedStreams.show()
  437. .. image:: images/variant_measuresAndParts.*
  438. :width: 600
  439. >>> for p in mergedStreams.getElementsByClass('Part'):
  440. ... for m in p.getElementsByClass('Measure'):
  441. ... m.activateVariants('paris', inPlace = True)
  442. >>> mergedStreams.show('t')
  443. {0.0} <music21.stream.Part ...>
  444. {0.0} <music21.stream.Measure 0 offset=0.0>
  445. {0.0} <music21.note.Note A>
  446. {1.0} <music21.variant.Variant object of length 1.0>
  447. {1.0} <music21.note.Note B>
  448. {2.0} <music21.note.Note A>
  449. {3.0} <music21.variant.Variant object of length 1.0>
  450. {3.0} <music21.note.Note G>
  451. {4.0} <music21.stream.Measure 0 offset=4.0>
  452. {0.0} <music21.note.Note B>
  453. {0.5} <music21.variant.Variant object of length 1.5>
  454. {0.5} <music21.note.Note C>
  455. {1.5} <music21.note.Note A>
  456. {2.0} <music21.note.Note A>
  457. {3.0} <music21.note.Note B>
  458. {8.0} <music21.stream.Measure 0 offset=8.0>
  459. {0.0} <music21.note.Note C>
  460. {1.0} <music21.variant.Variant object of length 3.0>
  461. {1.0} <music21.note.Note B>
  462. {2.0} <music21.note.Note A>
  463. {3.0} <music21.note.Note A>
  464. >>> #_DOCS_SHOW mergedStreams.show()
  465. .. image:: images/variant_measuresAndParts2.*
  466. :width: 600
  467. If barlines do not match up, an exception will be thrown. Here two streams that are identical
  468. are merged, except one is in 3/4, the other in 4/4. This throws an exception.
  469. >>> streamDifferentMeasures = stream.Stream()
  470. >>> dataDiffM1 = [('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter')]
  471. >>> dataDiffM2 = [ ('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter')]
  472. >>> dataDiffM3 = [('a', 'quarter'), ('b', 'quarter'), ('c', 'quarter')]
  473. >>> dataDiffM4 = [('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')]
  474. >>> dataDiff = [dataDiffM1, dataDiffM2, dataDiffM3, dataDiffM4]
  475. >>> streamDifferentMeasures.insert(0.0, meter.TimeSignature('3/4'))
  476. >>> tempPart = stream.Part()
  477. >>> for d in dataDiff:
  478. ... m = stream.Measure()
  479. ... for pitchName,durType in d:
  480. ... n = note.Note(pitchName)
  481. ... n.duration.type = durType
  482. ... m.append(n)
  483. ... tempPart.append(m)
  484. >>> streamDifferentMeasures.append(tempPart)
  485. >>> mergedStreams = variant.mergeVariantsEqualDuration([stream1, streamDifferentMeasures], ['paris'])
  486. Traceback (most recent call last):
  487. ...
  488. VariantException: _mergeVariants cannot merge streams which are of different lengths
  489. '''
  490. if inPlace is True:
  491. returnObj = streams[0]
  492. else:
  493. returnObj = copy.deepcopy(streams[0])
  494. variantNames.insert(0, None) # Adds a None element at beginning (corresponding to default variant streams[0])
  495. while len(streams) > len(variantNames): # Adds Blank names if too few
  496. variantNames.append(None)
  497. while len(streams) < len(variantNames): # Removes extra names
  498. variantNames.pop
  499. zipped = zip(streams,variantNames)
  500. for s,variantName in zipped[1:]:
  501. if returnObj.highestTime != s.highestTime:
  502. raise VariantException('cannot merge streams of different lengths')
  503. returnObjParts = returnObj.getElementsByClass('Part')
  504. if len(returnObjParts) != 0: # If parts exist, iterate through them.
  505. sParts = s.getElementsByClass('Part')
  506. for i in range(len(returnObjParts)):
  507. returnObjPart = returnObjParts[i]
  508. sPart = sParts[i]
  509. returnObjMeasures = returnObjPart.getElementsByClass('Measure')
  510. if len(returnObjMeasures) != 0: # If measures exist and parts exist, iterate through them both.
  511. for j in range(len(returnObjMeasures)):
  512. returnObjMeasure = returnObjMeasures[j]
  513. sMeasure = sPart.getElementsByClass('Measure')[j]
  514. _mergeVariants(returnObjMeasure,sMeasure,variantName = variantName, inPlace = True)
  515. else: # If parts exist but no measures.
  516. _mergeVariants(returnObjPart,sPart,variantName = variantName, inPlace = True)
  517. else:
  518. returnObjMeasures = returnObj.getElementsByClass('Measure')
  519. if len(returnObjMeasures) != 0: #If no parts, but still measures, iterate through them.
  520. for j in range(len(returnObjMeasures)):
  521. returnObjMeasure = returnObjMeasures[j]
  522. sMeasure = s.getElementsByClass('Measure')[j]
  523. _mergeVariants(returnObjMeasure,sMeasure, variantName = variantName, inPlace = True)
  524. else: # If no parts and no measures.
  525. _mergeVariants(returnObj,s,variantName = variantName, inPlace = True)
  526. return returnObj
  527. def mergePartAsOssia(mainpart, ossiapart, ossiaName, inPlace = False, compareByMeasureNumber = False, recurseInMeasures = False):
  528. '''
  529. Some MusicXML files are generated with full parts that have only a few non-rest measures instead of ossia parts, such as those
  530. created by Sibelius 7. This function
  531. takes two streams (mainpart and ossiapart), the second interpreted as an ossia. It outputs a stream with the ossia part merged into the stream as a
  532. group of variants.
  533. If compareByMeasureNumber is True, then the ossia measures will be paired with the measures in the mainpart that have the
  534. same measure.number. Otherwise, they will be paired by offset. In most cases these should have the same result.
  535. Note that this method has no way of knowing if a variant is supposed to be a different duration than the segment of stream which it replaces
  536. because that information is not contained in the format of score this method is designed to deal with.
  537. >>> mainstream = converter.parse(" A4 B4 C4 D4 E1 F2 E2 E8 F8 F4 G2 G2 G4 F4 F4 F4 F4 F4 G1 ", "4/4")
  538. >>> ossiastream = converter.parse(" r1 r1 r1 E4 E4 F4 G4 r1 F2 F2 r1 ", "4/4")
  539. >>> mainstream.makeMeasures(inPlace = True)
  540. >>> ossiastream.makeMeasures(inPlace = True)
  541. >>> # mainstream.__class__ = stream.Part
  542. >>> mainpart = stream.Part()
  543. >>> for m in mainstream:
  544. ... mainpart.insert(m.offset, m)
  545. >>> ossiapart = stream.Part()
  546. >>> for m in ossiastream:
  547. ... ossiapart.insert(m.offset, m)
  548. >>> s = stream.Stream()
  549. >>> s.insert(0.0, ossiapart)
  550. >>> s.insert(0.0, mainpart)
  551. >>> #_DOCS_SHOW s.show()
  552. >>> mainpartWithOssiaVariantsFT = variant.mergePartAsOssia(mainpart, ossiapart,
  553. ... ossiaName = 'Parisian Variant',
  554. ... inPlace = False,
  555. ... compareByMeasureNumber = False,
  556. ... recurseInMeasures = True)
  557. >>> mainpartWithOssiaVariantsTT = variant.mergePartAsOssia(mainpart, ossiapart,
  558. ... ossiaName = 'Parisian Variant',
  559. ... inPlace = False,
  560. ... compareByMeasureNumber = True,
  561. ... recurseInMeasures = True)
  562. >>> mainpartWithOssiaVariantsFF = variant.mergePartAsOssia(mainpart, ossiapart,
  563. ... ossiaName = 'Parisian Variant',
  564. ... inPlace = False,
  565. ... compareByMeasureNumber = False,
  566. ... recurseInMeasures = False)
  567. >>> mainpartWithOssiaVariantsTF = variant.mergePartAsOssia(mainpart, ossiapart,
  568. ... ossiaName = 'Parisian Variant',
  569. ... inPlace = False,
  570. ... compareByMeasureNumber = True,
  571. ... recurseInMeasures = False)
  572. >>> mainpartWithOssiaVariantsFT.show('text') == mainpartWithOssiaVariantsTT.show('text')
  573. {0.0} <music21.stream.Measure ...
  574. True
  575. >>> mainpartWithOssiaVariantsFF.show('text') == mainpartWithOssiaVariantsFT.show('text')
  576. {0.0} <music21.stream.Measure ...
  577. True
  578. >>> mainpartWithOssiaVariantsFT.show('text')
  579. {0.0} <music21.stream.Measure 1 offset=0.0>
  580. ...
  581. {12.0} <music21.stream.Measure 4 offset=12.0>
  582. {0.0} <music21.variant.Variant object of length 3.0>
  583. {0.0} <music21.note.Note E>
  584. {0.5} <music21.note.Note F>
  585. {1.0} <music21.note.Note F>
  586. {2.0} <music21.note.Note G>
  587. {16.0} <music21.stream.Measure 5 offset=16.0>
  588. ...
  589. {20.0} <music21.stream.Measure 6 offset=20.0>
  590. {0.0} <music21.variant.Variant object of length 4.0>
  591. {0.0} <music21.note.Note F>
  592. {1.0} <music21.note.Note F>
  593. {2.0} <music21.note.Note F>
  594. {3.0} <music21.note.Note F>
  595. ...
  596. >>> mainpartWithOssiaVariantsFF.activateVariants('Parisian Variant').show('text')
  597. {0.0} <music21.stream.Measure 1 offset=0.0>
  598. ...
  599. {12.0} <music21.variant.Variant object of length 4.0>
  600. {12.0} <music21.stream.Measure 4 offset=12.0>
  601. {0.0} <music21.note.Note E>
  602. {1.0} <music21.note.Note E>
  603. {2.0} <music21.note.Note F>
  604. {3.0} <music21.note.Note G>
  605. {16.0} <music21.stream.Measure 5 offset=16.0>
  606. ...
  607. {20.0} <music21.variant.Variant object of length 4.0>
  608. {20.0} <music21.stream.Measure 6 offset=20.0>
  609. {0.0} <music21.note.Note F>
  610. {2.0} <music21.note.Note F>
  611. ...
  612. '''
  613. if inPlace is True:
  614. returnObj = mainpart
  615. else:
  616. returnObj = copy.deepcopy(mainpart)
  617. if compareByMeasureNumber is True:
  618. for ossiaMeasure in ossiapart.getElementsByClass("Measure"):
  619. if len(ossiaMeasure.notes) > 0: #If the measure is not just rests
  620. ossiaNumber = ossiaMeasure.number
  621. returnMeasure = returnObj.measure(ossiaNumber)
  622. if recurseInMeasures is True:
  623. mergeVariantsEqualDuration([returnMeasure, ossiaMeasure], [ossiaName], inPlace = True)
  624. else:
  625. ossiaOffset = returnMeasure.getOffsetBySite(returnObj)
  626. addVariant(returnObj, ossiaOffset, ossiaMeasure, variantName = ossiaName, variantGroups = None, replacementDuration = None)
  627. else:
  628. for ossiaMeasure in ossiapart.getElementsByClass("Measure"):
  629. if len(ossiaMeasure.notes) > 0: #If the measure is not just rests
  630. ossiaOffset = ossiaMeasure.getOffsetBySite(ossiapart)
  631. if recurseInMeasures is True:
  632. returnMeasure = returnObj.getElementsByOffset(ossiaOffset, classList = [stream.Measure])[0]
  633. mergeVariantsEqualDuration([returnMeasure, ossiaMeasure], [ossiaName], inPlace = True)
  634. else:
  635. addVariant(returnObj, ossiaOffset, ossiaMeasure, variantName = ossiaName, variantGroups = None, replacementDuration = None)
  636. if inPlace is True:
  637. return
  638. else:
  639. return returnObj
  640. #------ Public Helper Functions
  641. def addVariant(s, startOffset, sVariant, variantName = None, variantGroups = None, replacementDuration = None):
  642. '''
  643. Takes a stream, the location of the variant to be added to that stream (startOffset), the content of the
  644. variant to be added (sVariant), and the duration of the section of the stream which the variant
  645. replaces (replacementDuration). If replacementDuration is 0, this is an insertion. If sVariant is
  646. None, this is a deletion.
  647. >>> data1M1 = [('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter')]
  648. >>> data1M3 = [('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')]
  649. >>> data1M2 = [('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter'),('b', 'quarter')]
  650. >>> data1 = [data1M1, data1M2, data1M3]
  651. >>> tempPart = stream.Part()
  652. >>> stream1 = stream.Stream()
  653. >>> for d in data1:
  654. ... m = stream.Measure()
  655. ... for pitchName,durType in d:
  656. ... n = note.Note(pitchName)
  657. ... n.duration.type = durType
  658. ... m.append(n)
  659. ... stream1.append(m)
  660. >>> data2M2 = [('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'), ('a', 'quarter'), ('b', 'quarter')]
  661. >>> stream2 = stream.Stream()
  662. >>> m = stream.Measure()
  663. >>> for pitchName,durType in data2M2:
  664. ... n = note.Note(pitchName)
  665. ... n.duration.type = durType
  666. ... m.append(n)
  667. >>> stream2.append(m)
  668. >>> variant.addVariant(stream1, 4.0, stream2, variantName = 'rhythmic switch', replacementDuration = 4.0)
  669. >>> stream1.show('text')
  670. {0.0} <music21.stream.Measure 0 offset=0.0>
  671. {0.0} <music21.note.Note A>
  672. {1.0} <music21.note.Note B>
  673. {1.5} <music21.note.Note C>
  674. {2.0} <music21.note.Note A>
  675. {3.0} <music21.note.Note A>
  676. {4.0} <music21.variant.Variant object of length 4.0>
  677. {4.0} <music21.stream.Measure 0 offset=4.0>
  678. {0.0} <music21.note.Note B>
  679. {0.5} <music21.note.Note C>
  680. {1.0} <music21.note.Note A>
  681. {2.0} <music21.note.Note A>
  682. {3.0} <music21.note.Note B>
  683. {8.0} <music21.stream.Measure 0 offset=8.0>
  684. {0.0} <music21.note.Note C>
  685. {1.0} <music21.note.Note D>
  686. {2.0} <music21.note.Note E>
  687. {3.0} <music21.note.Note E>
  688. >>> stream1 = stream.Stream()
  689. >>> stream1.repeatAppend(note.Note('e'), 6)
  690. >>> variant1 = variant.Variant()
  691. >>> variant1.repeatAppend(note.Note('f'), 3)
  692. >>> startOffset = 3.0
  693. >>> variant.addVariant(stream1, startOffset, variant1, variantName = 'paris', replacementDuration = 3.0)
  694. >>> stream1.show('text')
  695. {0.0} <music21.note.Note E>
  696. {1.0} <music21.note.Note E>
  697. {2.0} <music21.note.Note E>
  698. {3.0} <music21.variant.Variant object of length 0.0>
  699. {3.0} <music21.note.Note E>
  700. {4.0} <music21.note.Note E>
  701. {5.0} <music21.note.Note E>
  702. '''
  703. tempVariant = Variant()
  704. if variantGroups is not None:
  705. tempVariant.groups = variantGroups
  706. if variantName is not None:
  707. tempVariant.groups.append(variantName)
  708. tempVariant.replacementDuration = replacementDuration
  709. if sVariant is None: #deletion
  710. pass
  711. else: #replacement or insertion
  712. if "Measure" in sVariant.classes: #sVariant is a measure put it in a variant and insert.
  713. tempVariant.append(sVariant)
  714. else: #sVariant is not a measure
  715. sVariantMeasures = sVariant.getElementsByClass('Measure')
  716. if sVariantMeasures == []: # If there are no measures, work element-wise
  717. for e in sVariant:
  718. offset = e.getOffsetBySite(sVariant)+startOffset
  719. tempVariant.insert(offset, e)
  720. else: # if there are measures work measure-wise
  721. for m in sVariantMeasures:
  722. tempVariant.append(m)
  723. s.insert(startOffset, tempVariant)
  724. def refineVariant(s, sVariant, inPlace = False):
  725. '''
  726. Given a stream and variant contained in that stream, returns a stream with that variant 'refined.'
  727. It is refined in the sense that, (with the best estimates) measures which have been determined
  728. to be related are merged within the measure. Suppose a four-bar phrase in a piece is a slightly
  729. different five-bar phrase in a variant. In the variant, every F# has been replaced by an F,
  730. and the last bar is repeated. Given this streams, mergeVariantMeasureStreams would return
  731. the first stream with a single variant object containing the entire 5 bars of the variant.
  732. Calling refineVariant on this stream and that variant object would result in a variant object
  733. in the measures for each F#/F pair, and a variant object containing the added bar at the end.
  734. For a more detailed explanation of how similar measures are properly associated with each other
  735. look at the documentation for _getBestListandScore
  736. Note that this code does not work properly yet.
  737. >>> v = variant.Variant()
  738. >>> variantDataM1 = [('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter'),('b', 'quarter')]
  739. >>> variantDataM2 = [('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')]
  740. >>> variantData = [variantDataM1, variantDataM2]
  741. >>> for d in variantData:
  742. ... m = stream.Measure()
  743. ... for pitchName,durType in d:
  744. ... n = note.Note(pitchName)
  745. ... n.duration.type = durType
  746. ... m.append(n)
  747. ... v.append(m)
  748. >>> v.groups = ['paris']
  749. >>> v.replacementDuration = 8.0
  750. >>> s = stream.Stream()
  751. >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')]
  752. >>> streamDataM2 = [('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'), ('a', 'quarter'), ('b', 'quarter')]
  753. >>> streamDataM3 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')]
  754. >>> streamDataM4 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')]
  755. >>> streamData = [streamDataM1, streamDataM2, streamDataM3, streamDataM4]
  756. >>> for d in streamData:
  757. ... m = stream.Measure()
  758. ... for pitchName,durType in d:
  759. ... n = note.Note(pitchName)
  760. ... n.duration.type = durType
  761. ... m.append(n)
  762. ... s.append(m)
  763. >>> s.insert(4.0, v)
  764. >>> variant.refineVariant(s, v, inPlace = True)
  765. >>> s.show('text')
  766. {0.0} <music21.stream.Measure 0 offset=0.0>
  767. {0.0} <music21.note.Note A>
  768. {1.0} <music21.note.Note B>
  769. {2.0} <music21.note.Note A>
  770. {3.0} <music21.note.Note G>
  771. {4.0} <music21.stream.Measure 0 offset=4.0>
  772. {0.0} <music21.note.Note B>
  773. {0.5} <music21.variant.Variant object of length 1.5>
  774. {0.5} <music21.note.Note C>
  775. {1.5} <music21.note.Note A>
  776. {2.0} <music21.note.Note A>
  777. {3.0} <music21.note.Note B>
  778. {8.0} <music21.stream.Measure 0 offset=8.0>
  779. {0.0} <music21.note.Note C>
  780. {1.0} <music21.variant.Variant object of length 3.0>
  781. {1.0} <music21.note.Note B>
  782. {2.0} <music21.note.Note A>
  783. {3.0} <music21.note.Note A>
  784. {12.0} <music21.stream.Measure 0 offset=12.0>
  785. {0.0} <music21.note.Note C>
  786. {1.0} <music21.note.Note B>
  787. {2.0} <music21.note.Note A>
  788. {3.0} <music21.note.Note A>
  789. '''
  790. # stream that will be returned
  791. if not (sVariant in s.variants):
  792. raise VariantException('%s not found in stream %s.' % (sVariant, s))
  793. if inPlace is True:
  794. returnObject = s
  795. variantRegion = sVariant
  796. else:
  797. sVariantIndex = s.variants.index(sVariant)
  798. returnObject = copy.deepcopy(s)
  799. variantRegion = returnObject.variants(sVariantIndex)
  800. # useful parameters from variant and its location
  801. variantGroups = sVariant.groups
  802. replacementDuration = sVariant.replacementDuration
  803. startOffset = sVariant.getOffsetBySite(s)
  804. #endOffset = replacementDuration+startOffset
  805. # region associated with the given variant in the stream
  806. returnRegion = variantRegion.replacedElements(returnObject)
  807. # associating measures in variantRegion to those in returnRegion -> This is done via 0 indexed lists corresponding to measures
  808. returnRegionMeasureList = [i for i in range(len(returnRegion))]
  809. badnessDict = {}
  810. listDict = {}
  811. variantMeasureList, unused_badness = _getBestListandScore(returnRegion, variantRegion, badnessDict, listDict)
  812. # badness is a measure of how different the streams are. The list returned, variantMeasureList, minimizes that quantity.
  813. # mentioned lists are compared via difflib for optimal edit regions (equal, delete, insert, replace)
  814. sm = difflib.SequenceMatcher()
  815. sm.set_seqs(returnRegionMeasureList, variantMeasureList)
  816. regions = sm.get_opcodes()
  817. # each region is processed for variants.
  818. for regionType, returnStart, returnEnd, variantStart, variantEnd in regions:
  819. startOffset = returnRegion[returnStart].getOffsetBySite(returnRegion)
  820. #endOffset = returnRegion[returnEnd-1].getOffsetBySite(returnRegion)+returnRegion[returnEnd-1].duration.quarterLength
  821. if regionType is 'equal':
  822. returnSubRegion = returnRegion.measures(returnStart+1, returnEnd)
  823. variantSubRegion = variantRegion.measures(variantStart+1, variantEnd)
  824. mergeVariantsEqualDuration([returnSubRegion, variantSubRegion], variantGroups, inPlace = True)
  825. continue
  826. elif regionType is 'replace':
  827. returnSubRegion = returnRegion.measures(returnStart+1, returnEnd)
  828. replacementDuration = returnSubRegion.duration.quarterLength
  829. variantSubRegion = variantRegion.measures(variantStart+1, variantEnd)
  830. elif regionType is 'delete':
  831. returnSubRegion = returnRegion.measures(returnStart+1, returnEnd)
  832. replacementDuration = returnSubRegion.duration.quarterLength
  833. variantSubRegion = None
  834. elif regionType is 'insert':
  835. variantSubRegion = variantRegion.measures(variantStart+1, variantEnd)
  836. replacementDuration = 0.0
  837. addVariant(returnRegion, startOffset, variantSubRegion, variantGroups = variantGroups, replacementDuration = replacementDuration)
  838. returnObject.remove(variantRegion) # The original variant object has been replaced by more refined variant objects and so should be deleted.
  839. if inPlace:
  840. return
  841. else:
  842. return returnObject
  843. def _mergeVariantMeasureStreamsCarefully(streamX, streamY, variantName, inPlace = False):
  844. '''
  845. There seem to be some problems with this function and it isn't well tested.
  846. It is not recommended to use it at this time.
  847. '''
  848. # stream that will be returned
  849. if inPlace is True:
  850. returnObject = streamX
  851. variantObject = streamY
  852. else:
  853. returnObject = copy.deepcopy(streamX)
  854. variantObject = copy.deepcopy(streamY)
  855. # associating measures in variantRegion to those in returnRegion -> This is done via 0 indexed lists corresponding to measures
  856. returnObjectMeasureList = [i for i in range(len(returnObject.getElementsByClass("Measure")))]
  857. badnessDict = {}
  858. listDict = {}
  859. variantObjectMeasureList, unused_badness = _getBestListandScore(returnObject.getElementsByClass("Measure"), variantObject.getElementsByClass("Measure"), badnessDict, listDict)
  860. # badness is a measure of how different the streams are. The list returned, variantMeasureList, minimizes that quantity.
  861. # mentioned lists are compared via difflib for optimal edit regions (equal, delete, insert, replace)
  862. sm = difflib.SequenceMatcher()
  863. sm.set_seqs(returnObjectMeasureList, variantObjectMeasureList)
  864. regions = sm.get_opcodes()
  865. # each region is processed for variants.
  866. for regionType, returnStart, returnEnd, variantStart, variantEnd in regions:
  867. startOffset = returnObject.measure(returnStart+1).getOffsetBySite(returnObject)
  868. #endOffset = returnObject.measure(returnEnd-1).getOffsetBySite(returnObject)+returnObject.measure(returnEnd-1).duration.quarterLength
  869. if regionType is 'equal':
  870. returnSubRegion = returnObject.measures(returnStart+1, returnEnd)
  871. variantSubRegion = variantObject.measures(variantStart+1, variantEnd)
  872. mergeVariantMeasureStreams(returnSubRegion, variantSubRegion, [variantName], inPlace = True)
  873. continue
  874. elif regionType is 'replace':
  875. returnSubRegion = returnObject.measures(returnStart+1, returnEnd)
  876. replacementDuration = returnSubRegion.duration.quarterLength
  877. variantSubRegion = variantObject.measures(variantStart+1, variantEnd)
  878. elif regionType is 'delete':
  879. returnSubRegion = returnObject.measures(returnStart+1, returnEnd)
  880. replacementDuration = returnSubRegion.duration.quarterLength
  881. variantSubRegion = None
  882. elif regionType is 'insert':
  883. variantSubRegion = variantObject.measures(variantStart+1, variantEnd)
  884. replacementDuration = 0.0
  885. addVariant(returnObject, startOffset, variantSubRegion, variantGroups = [variantName], replacementDuration = replacementDuration)
  886. if inPlace:
  887. return
  888. else:
  889. return returnObject
  890. def getMeasureHashes(s):
  891. '''
  892. Takes in a stream containing measures and returns a list of hashes, one for each measure. Currently
  893. implemented with search.translateStreamToString()
  894. >>> s = converter.parse("c4 d8. e16 FF4 a'4 b-2", "2/4")
  895. >>> sm = s.makeMeasures()
  896. >>> hashes = variant.getMeasureHashes(sm)
  897. >>> hashes
  898. ['<P>K@<', ')PQP', 'FZ']
  899. '''
  900. hashes = []
  901. if isinstance(s, list):
  902. for m in s:
  903. hashes.append(search.translateStreamToString(m.notesAndRests))
  904. return hashes
  905. else:
  906. for m in s.getElementsByClass('Measure'):
  907. hashes.append(search.translateStreamToString(m.notesAndRests))
  908. return hashes
  909. #----- Private Helper Functions
  910. def _getBestListandScore(streamX, streamY, badnessDict, listDict, isNone = False, streamXindex = -1, streamYindex = -1):
  911. '''
  912. This is a recursive function which makes a map between two related streams of measures.
  913. It is designed for streams of measures that contain few if any measures that are actually
  914. identical and that have a different number of measures (within reason). For example,
  915. if one stream has 10 bars of eighth notes and the second stream has the same ten bars
  916. of eighth notes except with some dotted rhythms mixed in and the fifth bar is repeated.
  917. The first, streamX, is the reference stream. This function returns a list of
  918. integers with length len(streamY) which maps each measure of StreamY to the measure
  919. in streamX it is most likely associated with. For example, if the returned list is
  920. [0, 2, 3, 'addedBar', 4]. This indicates that streamY is most similar to streamX
  921. after the second bar of streamX has been removed and a new bar inserted between
  922. bars 4 and 5. Note that this list has measures 0-indexed. This function generates this map by
  923. minimizing the difference or 'badness' for the sequence of measures on the whole as determined
  924. by the helper function _simscore which compares measures for similarity. 'addedBar' appears
  925. in the list where this function has determined that the bar appearing
  926. in streamY does not have a counterpart in streamX anywhere and is an insertion.
  927. >>> badnessDict = {}
  928. >>> listDict = {}
  929. >>> stream1 = stream.Stream()
  930. >>> stream2 = stream.Stream()
  931. >>> data1M1 = [('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter')]
  932. >>> data1M2 = [('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), ('a', 'quarter'),('b', 'quarter')]
  933. >>> data1M3 = [('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')]
  934. >>> data2M1 = [('a', 'quarter'), ('b', 'quarter'), ('c', 'quarter'), ('g#', 'quarter')]
  935. >>> da

Large files files are truncated, but you can click here to view the full file