formula to get nearest number

Pages: 12
Hey everyone,

I'm creating a midi program to alter incoming midi and putting out the altered midi, I do a bunch of stuff with it (one note chords etc) and everything is working fine. Next function I want to build in is a pretty standard function in music programs but i can't wrap my head around it. > Scales.

When a user select a scale from a list and plays a midi note the scale function should return the nearest (down) note to that played note.

I found a list of scales and example here: https://github.com/BespokeSynth/BespokeSynth/blob/main/resource/userdata_original/scales.json

But the program is way too complicated and I can't seem to see how they do it.

Anyone experience with this?
Just pick the greatest element from the list (scale) that is smaller than or equal to the input.

You can do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// return greates number in list that is smaller than or equal input
// assuming that list is ordered (ascending)
int clamp(const int input, const std::vector<int> &list)
{
	int result = 0;
	for (std::vector<int>::const_iterator iter = list.cbegin(); iter != list.cend(); iter++)
	{
		if (*iter <= input)
		{
			result = *iter;
		}
		else
		{
			break;
		}
	}
	return result;
}

int main()
{
	std::vector<int> scale = { 0,2,3,5,7,8,11 };
	std::cout << clamp(6, scale) << std::endl;
}


If it has to work with unordered lists too:
1
2
3
4
5
6
7
8
9
10
11
12
13
// return greates number in list that is smaller than or equal input
int clamp2(const int input, const std::vector<int> &list)
{
	int result = 0;
	for (std::vector<int>::const_iterator iter = list.cbegin(); iter != list.cend(); iter++)
	{
		if ((*iter <= input) && (*iter > result))
		{
			result = *iter;
		}
	}
	return result;
}
Last edited on
Kigar,

Thank you already, there are 12 semitones in an octave and midinotes correspond with these semitones: https://www.inspiredacoustics.com/en/MIDI_note_numbers_and_center_frequencies

I think this needs to be included too
In what way does that change the task?
If I try to run that code I get: Use of class template 'vector' requires template arguments; argument deduction not ..
And what do you think that error means? Do you know what a template argument is? Template arguments are the parts inside the < > on a template instantiation.
So it's saying that it's trying to use a template without specifying the < > part, in this case <int>. Look at lines 2 and 3 of the post. You'd fix that by doing vector<int> instead of just vector.

Edit: And also change std::vector::const_iter (or change it to just 'auto'). Or just use a for-each loop.
Last edited on
yes, i was reading up on it, already changed the template to int but didn't change the auto. The code is running now!
So, when i run it and i input midi notes, let's say note 24, 68, etc the result will be always 11 off course as this is the highest number in the vector.
So i'm thinking I prob need to have a "root note" that has a value from 0 to 11 (or 1 to 12).
Then do some math that when i play a midi note it will start calculating from that root note and give me back the nearest number according to the scale vector, so if i input for example 45 it could give back 43, 44, 46 etc according to that scale.
I believe it's saying 0 is the root note of the scale, so you'd never go below 45 (110 Hz), if 45 is what you want as the root note.
Last edited on
@Ganado no, for example 24 is note C1, 36 is note C2, 48 is note C3, every time between these notes is C,C#,D,D#,E,F,F# so every octave has 12 notes and just repeats. So every incoming midi note responds to a musical note. Now what I'm trying to accomplisch is to transpose every incoming number to the nearest number in one of those scale vectors
If I'm understanding, the goal here is to to be able to take any midi sequence, and convert it from one mode to another? (e.g. major (Ionian) to natural minor scale (Aeolian)). Don't you need to know what mode the untransposed notes are a part of, in order to do this? Or am I misunderstanding the goal?

If I am misunderstanding, it's fine -- I'd say try out the method you're planning, and then explain it more if your idea doesn't work after you try it.
Last edited on
Sorry, I'm not the best explainer :)

So, i'm processing incoming midi from a keyboard. So the incoming midi is not in a mode. It's just notes being played. I now want to "lock in" these notes to a certain scale. This is a very common practice in synthesizers and sequencers so the player can't play any wrong notes or the sequencer can't play wrong notes if other parts of a song or in the same scale key.
Last edited on
Ah, that makes more sense to me now, and sounds like it aligns with the idea you had in your post from earlier.
Ok, I think I worked out the math, so: :)

The note value I get needs to be divided by x times 12, then the rest value of that needs to be inputted in the function above and x times 12 needs to be added again.
Anyone now how to do this in c++?

So if the incoming number would be 77 then we need to subtract 6 times 12 (to get close to zero and get the remainder) = 72. Then 77 - 12 = 5 needs to be put in the scale function above, we get back another number (the one closest too) and to that number we add 72 again :)
Last edited on
The note value I get needs to be divided by x times 12, then the rest value of that needs to be inputted in the function above and x times 12 needs to be added again.


The modulus operator (rest of integer division) is denoted as % in C/C++:
int rest = input % (x * 12);
Last edited on
Ok I think I solved it!!

1
2
3
4
5
6
7
8
9
10

                               if (currentMessage.getNoteNumber() > 12)
                               {
                                   int quotient = currentMessage.getNoteNumber() / 12;
                                   int remainder = currentMessage.getNoteNumber() % 12;
                                   int notejaja = changeNoteToScale(remainder,scaleexample);
                                   notejaja = notejaja + (quotient*12);
                               
                               }
Just as an aside, if you need both, quotient and remainder (modulus), you can use div():
https://www.cplusplus.com/reference/cstdlib/div/

This can be more efficient, because on most CPU architectures the "divide" instructions returns quotient and remainder at once; and there is no separate "modulus" instruction. So the div() function can be implemented with just a single hardware instruction, whereas your above code would effectively require two divisions...

(unless the compiler is smart enough to optimize to code to a single division, of course)
Last edited on
The compilers are smart enough, at least that's the case with Clang and GCC on x86-64.

The call to std::div is actually slower because they're unable to inline it.
Last edited on
Ok guys, I'm back :D Everything is working fine but now i have to implement a "root note function" I'm way too tired to explain it right but maybe someone will get it :)

So, the user can select a scale and with the above function everything is returned as it should.

Now the user can also select a root note: (C,C#,D,D#,E,F,F#,G,G#,A,A#,B) so from 1 to 12.

This needs to be inserted in the function somehow, so the lookup needs to be transposed.

I've been staring at it for hours but i can't find the solution. So if I just add the root note it's not good because it will only shift the notes higher, so the transpose should set the 0 in the for example: { 0,2,3,5,7,8,11 } to the root note integer (1 to 12)

Sorry for my bad explanation, hopefully someone will understand :)

*EDIT: Found it:

auto note = currentMessage.getNoteNumber() - rootNote;
int quotient = note / 12;
int remainder = note % 12;
notejaja = changeNoteToScale(remainder,scaleSelected) + rootNote;
Last edited on
AAAAAAAND I'm Back :D

So know I reversed the code, to get the nearest number up:

It works but if I put in 11, off course it will break, it should return then the nearest (being 11 in this case) I'm also not sure if my logic is correct...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
               for (std::vector<int>::const_reverse_iterator iter=list.rbegin(); iter!=list.rend(); ++iter)
                {
                   //std::vector<int> scaleexample = {0,2,3,5,7,8,10};
                    if ((*iter) >= input)
                    {
                        std::cout << "Input: " << input;
                        result = *iter;
                        std::cout << "result: " << result;
                    }
                    else
                    {
                        break;
                    }
                }
Initialize result to std::vector::back() before the loop. Or what else, if not the last (greatest) value in the vector, you want to get, if the given input is greater than any value in the vector?
Last edited on
Pages: 12