From d3e5d59f2aa0dffe41184f1ebba5d15e5ff2d892 Mon Sep 17 00:00:00 2001 From: hill Date: Wed, 23 Sep 2026 11:30:08 +0200 Subject: [PATCH] Mark an octave-repeating scale wider than an octave as not duplicating AbstractOctaveRepeatingScale completes its steps to the next octave above their sum, so steps wider than an octave make a pattern spanning two or more octaves. The network was still marked octave-duplicating, and realizing it moved the reference by single octaves into the middle of that pattern, so the scale started an octave above its tonic: >>> sc = scale.OctaveRepeatingScale('c4', ['P5', 'P5']) >>> sc.pitches [, , , ] Mark the network octave-duplicating only when the completed pattern spans one octave. A pattern of one octave builds exactly as before. --- music21/scale/__init__.py | 11 ++++++----- music21/scale/test_scale_main.py | 7 ++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/music21/scale/__init__.py b/music21/scale/__init__.py index ebb66478e..9dc898f0f 100644 --- a/music21/scale/__init__.py +++ b/music21/scale/__init__.py @@ -6,7 +6,7 @@ # Michael Scott Asato Cuthbert # Jose Cabal-Ugaz # -# Copyright: Copyright © 2009-2011 Michael Scott Asato Cuthbert +# Copyright: Copyright © 2009-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ ''' @@ -1005,10 +1005,6 @@ def __init__(self, mode: t.Any = None, **keywords) -> None: mode = ['P8'] self.buildNetwork(mode=mode) - # by definition, these are forced to be octave duplicating - # though, do to some intervals, duplication may not happen every oct - self.octaveDuplicating = True - def buildNetwork(self, mode: t.Any = None) -> None: ''' Here, mode is the list of intervals. @@ -1022,6 +1018,11 @@ def buildNetwork(self, mode: t.Any = None) -> None: if iComplement is not None: mode.append(iComplement) + # steps wider than an octave complete a pattern spanning several + # octaves, which does not repeat every octave + span = intervalSum.semitones + (iComplement.semitones if iComplement else 0) + self.octaveDuplicating = span == 12 + self.tonicDegree = 1 self._net = intervalNetwork.IntervalNetwork(mode, octaveDuplicating=self.octaveDuplicating) diff --git a/music21/scale/test_scale_main.py b/music21/scale/test_scale_main.py index ded1bd6d9..1ccf36487 100644 --- a/music21/scale/test_scale_main.py +++ b/music21/scale/test_scale_main.py @@ -5,7 +5,7 @@ # Authors: Christopher Ariza # Michael Scott Asato Cuthbert # -# Copyright: Copyright © 2010-2024 Michael Scott Asato Cuthbert +# Copyright: Copyright © 2010-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ from __future__ import annotations @@ -233,6 +233,11 @@ def testBasic(self): ], 'notMatched': [, ]}''', pformat(sc1.match(sc2))) + def testOctaveRepeatingScaleWiderThanAnOctave(self): + sc = scale.OctaveRepeatingScale('c4', ['P5', 'P5']) + self.assertEqual(self.pitchOut(sc.pitches), '[C4, G4, D5, C6]') + self.assertEqual(self.pitchOut(sc.getPitches('c3', 'c6')), '[D3, C4, G4, D5, C6]') + def testCyclicalScales(self): sc = scale.CyclicalScale('c4', ['m2', 'm2'])