If I have a combination of pitches played as a chord in a MIDI, how do I extract only two pitches from it and get somewhat similar tone?

While extracting pitches in a MIDI file of piano instrument, I get chords with combination of pitches as `D2.D3.D4.F#4.A4` played for 0.2 duration. However, I want to receive only a maximum of 2 pitches to decrease the total number of unique combinations of pitches in my dataset. For e.g. `D2.F#4`. Thus, how do I know which of the two pitches in the combination will give somewhat the same sound as the entire combination does? Any tips I can implement for this problem? I do not want to lose the actual melody of the piece of the music either.

• Why do you need only 2 pitches? If you want a simplified data set, I'd suggest mimicking what musicians do on their lead sheets and have 3 parameters- chord root, chord quality, and melody note Feb 8 at 18:10
• How much music theory do you know? If we said to keep the root and third or third and seventh would that help? Also is the melody note that you want to keep one of the notes in the chord or is that a separate set of midi data? Feb 8 at 18:21
• Buy polyphonic synths, not paraphonic ;)
– Tom
Feb 8 at 20:37
• I am trying to develop an algorithm for getting a maximum of two notes played at a time. I am not that good at music theory. I am currently using music21 library to handle the MIDI datasets. And I have been getting trouble with a lot of unique combination of pitches. How can I address it? Feb 9 at 3:36
• @ROOT31415 why does it need to be reduced to two pitches at a time? Feb 9 at 14:49

Assuming you mean to do this by a computer program...

You could invert the tones so they are a tertian stack then take the root and third.

You could invert to a tertian stack, take the root, and then also take the original highest tone as the "melody" tone to get chord roots plus melody for two parts.

Both methods won't make any overall analysis of the music and will very likely result in poor choices you wouldn't make if you just reduced the music to two parts.

One way to go about this would be to consider an approximate harmonic spectrum for your instrument and use this to estimate the harmonic spectrum for your chord. Then try to find two (or more generally `n`) different notes and velocities that explain the harmonic spectrum best. Keep in mind that the best notes do not necessarily need to be part of the given chord, so in the worst case we need to try `(K n)` combinations where `K` is the number of possible notes. With K=128 this would be 8128 combinations for n=2, 341376 for n=3. So this is computationally a bit hard. This means we most likely need to try to remove notes that are unlikely to be of use here.

Under strong assumptions (harmonic spectrum is independent of pitch and velocity) and ignoring things such as inharmonicity and tuning differences (that is we round each overtone to one chromatic scale tone) and using the first six harmonics (since these can be somewhat mapped to chromatic notes) this code in sageMath here does such a thing:

``````import scipy.optimize
import itertools

part = (0.91, 0.34, 0.09, 0.2, 0.08, 0.11)
steps = (0, 12, 19, 24, 28, 31)

def h(notes):
res = [0] * (128 + steps[-1])
for (i, v) in notes:
for j in range(6):
res[i + steps[j]] += (v * part[j])**2
return list(map(sqrt, res))

def mod(notes):
vars = var(",".join([f"x{k}" for k in range(len(notes))]))
if len(notes) == 1:
vars = [vars]
notes = list(zip(notes, vars))
return h(notes)

def err_fn(notes1, notes2):
h1 = mod(notes1)
h2 = h(notes2)
sqerr = map(lambda x, y: (x - y)**2, h1, h2)
res = sum(sqerr)
return res.full_simplify()

def err_fn2fn(errfn):
vars = errfn.variables()
f = fast_callable(errfn, vars = vars, domain = RR)
return lambda x: f(*x)

def solve_selection_efn(efn):
v = efn.variables()
n = len(v)
efnfn = err_fn2fn(efn)

res = scipy.optimize.minimize(efnfn, [0] * n, bounds = [(0, 127)] * n)
res = [int(round(_)) for _ in res.x]

return dict(zip(v, res))

def solve_selection(notes1, notes2):
efn = err_fn(notes1, notes2)
return solve_selection_efn(efn)

def ip(v1, v2):
return sum(map(prod, zip(v1, v2)))

def proj(v1, v2):
return ip(v1, v2) / sqrt(ip(v1, v1))

def ang(v1, v2):
return ip(v1, v2) / sqrt(ip(v1, v1) * ip(v2, v2))

_STEP_MAP = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]

def step2notename(n):
oct = n // 12 - 1
cstep = n % 12
return f"{_STEP_MAP[cstep]}{oct}"

def solve_n(notes, n, ths = 0.15, nsol = 5):
lowest_step = min((note for (note, _) in notes))
highest_step = max((note for (note, _) in notes))
viable_notes = range(max(0, lowest_step - steps[-1]), min(127, highest_step + steps[-1]) + 1)
ref = h(notes)
norm_ref = sqrt(ip(ref, ref))
viable_notes = list(filter(lambda nt: ang(h([(nt, 60)]), ref) > ths, viable_notes))
print(viable_notes)
print(len(viable_notes))
c = itertools.combinations(viable_notes, n)
exprs = [(cmb, err_fn(cmb, notes)) for cmb in c]
sols = [(solve_selection_efn(errfn), cmb, errfn) for (cmb, errfn) in exprs]
errs = [(sqrt(errfn.subs(sol)) / norm_ref, list(zip(cmb, sol.values())), [step2notename(_) for _ in cmb]) for (sol, cmb, errfn) in sols]
return sorted(errs)[:min(nsol, len(errs))]

# May take some time!
solve_n([(38, 60), (50, 50), (62, 50), (66, 60), (69, 60)], 2)
``````

Here the chord D2,D3,D4,F#4,A4 with velocitites 60,50,50,60,60 would be reduced to D2,F#4 with velocities 83,60. So basically we’d try to get as much of the D3,D4,A4 by playing the root louder.

With three notes we’d go for D2,F#4,A4 with velocities 78,60,61 and with four notes we’d go with D2,D4,F#4,A4 with velocities 70,53,60,60.

With two notes we get a nearly similar match for D2,A4 and slightly worse match for D2,D4 and D2,D3.

With three notes we get noticeably worse matches for D2,D4,F#4 and D2,D3,F#4 and even slightly worse with A4 instead of F#4.

With four notes we get a slightly worse match with D2,D3,F#4,A4 and then a noticeably worse match with D2,F#4,A4,D5. This one is interesting as it gives us a similar sounding combination featuring a note not present in the original selection. All other 4-combinations match worse than the best 3-combination.

Because this is a matter of listening and not mathematics, the best way is to try all 2-note combinations, best via a MIDI-piano-keyboard to understand what's going on, and making a choice:

D2. D3 / D2. D4 / . . . / .F#4 .A4

As suggested, consider inversion too, like:

F#4 -> F#3, hence e.g. F#3..A4 etc.

However, very likely you'll observe by ear:

• the D-octaves are just octaves (i.e. sound quite the same)
• the D-major chord sounds kind of nice `D4.F#4.A4`
• which can be approximated by the power-chord, i.e. omitting the 3rd: `D4. .A4`
• the 1st and the 3rd are not the best choice `D4.F#4`
• even inversion won't do much magic: `D3.F#4`, or `F#3.D4.`, or `D4.F#5`
• and so on.

There are more things you could do with chord variations or chord extensions, which, as stated already in an other answer, depend on your music, i.e. the context where your 5 notes are played.

There are some details about what you're trying to achieve that are missing in the question, but I'm comfortable making two different recommendations.

If you're trying to preserve the overall character of an entire piece of music while only playing two notes at a time, then the best thing to do is simply preserve the highest and lowest notes. The highest note is usually the melody and/or the easiest note for listeners to hear, and the lowest note always plays an important role in the harmonic foundation (although not always the most important role).

One downside to highest and lowest note is that a lot of rhythmic content and inner harmonic movement can be lost. Determining what is the most important note to keep on a moment-to-moment basis would not be easy, because human hears and musical perception are very sophisticated systems.

If you're trying to preserve the character of a single chord, then the best notes to keep are the root and the third, except for many seventh chords keeping the third and the seventh can be a better option.

This method would require an algorithm to determine which of the notes at any given time is the root and which is the third (and potentially which is the seventh). This is not always easy. Also, if you fed this kind of algorithm a whole melody with accompaniment, it will very likely chop up the melody and bass line completely and the piece can end up sounding very different. It would have the same essential harmonic progression, though.