#! /usr/bin/env python3 def get_scale_note(scale, step, offset=0): octave, scale_index = divmod(step, len(scale)) result = scale[scale_index] offset_step = -1 if offset < 0 else 1 while offset != 0: while True: octave, result = divmod(result + offset_step + (octave * 12), 12) if result not in scale: break offset = offset - offset_step result += octave * 12 return result import unittest class TestScaleNote(unittest.TestCase): def test_get_scale_note(self): scale = [0, 2, 4, 5, 7, 9, 11] self.assertEqual(get_scale_note(scale, 0), 0) self.assertEqual(get_scale_note(scale, 3), 5) self.assertEqual(get_scale_note(scale, 6), 11) self.assertEqual(get_scale_note(scale, 7), 12) self.assertEqual(get_scale_note(scale, 13), 23) self.assertEqual(get_scale_note(scale, 14), 24) self.assertEqual(get_scale_note(scale, -1), -1) self.assertEqual(get_scale_note(scale, -4), -7) self.assertEqual(get_scale_note(scale, -7), -12) self.assertEqual(get_scale_note(scale, -8), -13) def test_get_scale_note_offset(self): scale = [0, 2, 4, 5, 7, 9, 11] self.assertEqual(get_scale_note(scale, 3, -1), 3) self.assertEqual(get_scale_note(scale, 3, -2), 1) self.assertEqual(get_scale_note(scale, 3, -3), -2) self.assertEqual(get_scale_note(scale, 10, 1), 18) self.assertEqual(get_scale_note(scale, 10, 2), 20) self.assertEqual(get_scale_note(scale, 10, 3), 22) if __name__ == '__main__': unittest.main()