Skip to content

Commit 616042f

Browse files
committed
implement simple sonification
1 parent e191b6f commit 616042f

File tree

3 files changed

+63
-8
lines changed

3 files changed

+63
-8
lines changed

mscales/basic.py

+59-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from itertools import combinations
33
from collections.abc import Iterable
44
import matplotlib.pyplot as plt
5+
import pretty_midi as pm
6+
7+
rng = np.random.default_rng()
58

69

710
class PitchClass:
@@ -265,6 +268,57 @@ def plot(self, kind="area", save=False):
265268
if save:
266269
plt.savefig(save)
267270

271+
def play(
272+
self,
273+
mode: str = "cloud",
274+
n_notes: int = 100,
275+
note_duration: float = 0.15,
276+
velocity: int = 100,
277+
instrument_name: str = "Acoustic Grand Piano",
278+
save_as: str = None,
279+
):
280+
281+
if mode == "cloud":
282+
starts = np.arange(n_notes) * note_duration # onsets
283+
ends = starts + note_duration # offsets
284+
285+
pitches = [x for x in rng.choice(np.nonzero(self.to_vector())[0], size=n_notes)]
286+
octaves = rng.choice(np.arange(3, 7), size=n_notes)
287+
midi_pitches = [(p + 12 * o) for p, o in list(zip(pitches, octaves))]
288+
elif mode == "chord":
289+
pitches = self.pcs
290+
octaves = [4] * pitches.shape[0]
291+
midi_pitches = [(p + 12 * o) for p, o in list(zip(pitches, octaves))]
292+
293+
starts = [0] * pitches.shape[0]
294+
ends = [note_duration] * pitches.shape[0]
295+
296+
# Create a PrettyMIDI object
297+
midi = pm.PrettyMIDI()
298+
299+
# Create an Instrument instance for an instrument
300+
assert (
301+
instrument_name in pm.constants.INSTRUMENT_MAP
302+
), f"Instrument must be in {pm.constants.INSTRUMENT_MAP}"
303+
# instrument_code = pm.constants.INSTRUMENT_MAP.index(instrument_name)
304+
305+
program = pm.instrument_name_to_program(instrument_name)
306+
instrument = pm.Instrument(program=program)
307+
308+
for mp, s, e in zip(midi_pitches, starts, ends):
309+
# Create a Note instance for this note, starting at `s` and ending at `e`.
310+
note = pm.Note(pitch=mp, velocity=velocity, start=s, end=e)
311+
# Add it to instrument
312+
instrument.notes.append(note)
313+
314+
# Add the instrument to the PrettyMIDI object
315+
midi.instruments.append(instrument)
316+
317+
if save_as is not None:
318+
midi.write(save_as)
319+
else:
320+
return midi
321+
268322
def info(self):
269323
print("=" * len(repr(self)))
270324
print(repr(self))
@@ -308,12 +362,14 @@ def info(self):
308362
# s = {0,1,2}
309363
s = "147T"
310364
s = "02479"
311-
# s = {6,9,2}
365+
s = {6, 9, 2}
312366
# s = {7, 10, 1, 5}
313367
# s = [0, 1, 6, 7, 5, 2, 4, 3, 10, 9, 11, 8] # 12-tone row
314368

315369
pcset = PitchClassSet(s)
316370
pcset.info()
317371

318-
ax = pcset.plot(kind="area")
319-
plt.show()
372+
# ax = pcset.plot(kind="area")
373+
# plt.show()
374+
375+
pcset.play(save_as="test.mid", mode="chord")

requirements-dev.txt

+1-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,4 @@ ipython
1515
matplotlib
1616
numpydoc
1717
sphinx-copybutton
18-
sphinx-material
19-
20-
# additional libraries used
21-
# pretty_midi
18+
sphinx-material

requirements.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# List required packages in this file, one per line.
2-
numpy
2+
numpy
3+
pretty_midi
4+
matplotlib

0 commit comments

Comments
 (0)