Teletype dev help - Scale quantizer [SOLVED]

I wrote a couple scale quantizer OPs for Teletype based on @discohead’s N.S lookup table. These are my first OPs and my C is rusty so I’m sure I’m missing something, but I can only get one of my two OPs (QT.S) to work correctly. nb: these OPs are just working with semitones; I might convert to volts later, I dunno.

QT.S simply checks the input semitones against all semitone values on the specified row of table_n_s until it finds the highest value less than the input. I feel like my code’s a bit messy (I wanted to wrap all the input values in useful ways) but it’s working great.

QT.SC is similar but the allowed values are selected by giving scale (0-8), degree (0-6) and chord type / voices (1-7). 1 voice gives a single note, 2 is a third, 3 is a triad, 4 is a seventh, etc. My problem is that my wrapping math is not working when a given degree + chord exceeds the length of the scale. This line: dix = (2 * i + degree) % 7; is supposed to pick the table index and wrap gracefully while checking the input semitones against the table_n_s value but when I test on TT it’s not selecting any values which occur after the wrapping starts. I could use a fresh set of eyes!

QT.S input root scale

static void op_QT_S_get(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),
                     exec_state_t *NOTUSED(es), command_state_t *cs) {
	
	int16_t note_in = cs_pop(cs);
    int16_t root = cs_pop(cs);
    int16_t scale = cs_pop(cs) % 9;
    if (scale < 0) scale = 9 + scale;
	
	note_in = note_in - root;	
	int16_t octave = (note_in >= 0) ? (note_in / 12) : ((note_in + 1) / 12 - 1);

	if (note_in >= 0) {
		note_in = note_in % 12;
	}
	else {
		if (note_in % 12 == 0) {
			note_in = 0;
		}
		else {
			note_in = 12 + (note_in % 12);
		}
	}

	int16_t note_out;
	
	for (uint8_t i = 0; i < 7; i++) {
		if (note_in >= table_n_s[scale][i]) note_out = table_n_s[scale][i];
	}
	
	cs_push(cs, 12 * octave + note_out + root);
}

QT.SC input root scale degree voices

static void op_QT_SC_get(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),
                     exec_state_t *NOTUSED(es), command_state_t *cs) {
	
	int16_t note_in = cs_pop(cs);
    int16_t root = cs_pop(cs);
    int16_t scale = cs_pop(cs) % 9;
    if (scale < 0) scale = 9 + scale;
	
    int16_t degree = cs_pop(cs);
	int16_t degree_octave = (degree >= 0) ? (degree / 7) : ((degree + 1) / 7 - 1);
	if (degree >= 0) {
		degree = degree % 7;
	}
	else {
		if (degree % 7 == 0) {
			degree = 0;
		}
		else {
			degree = 7 + (degree % 7);
		}
	}
	
	int16_t voices = cs_pop(cs);
    if (voices < 1) voices = 1;
	if (voices > 7) voices = 7;
	
	note_in = note_in - root;	
	int16_t octave = (note_in >= 0) ? (note_in / 12) : ((note_in + 1) / 12 - 1);

	if (note_in >= 0) {
		note_in = note_in % 12;
	}
	else {
		if (note_in % 12 == 0) {
			note_in = 0;
		}
		else {
			note_in = 12 + (note_in % 12);
		}
	}

	bool quant = false;
	int16_t max_n_s_val = 0;
	int16_t n_s_val;
	int16_t note_out;
	int16_t dix;
	
	for (int16_t i = 0; i < voices; i++) {
		dix = (2 * i + degree) % 7;
		n_s_val = table_n_s[scale][dix];
		if (n_s_val > max_n_s_val) max_n_s_val = n_s_val;
		if (note_in >= n_s_val) {
			note_out = n_s_val;
			quant = true;
		}
	}
	
	if (!quant) note_out = max_n_s_val - 12;
	
	cs_push(cs, 12 * (octave + degree_octave) + note_out + root);
}
1 Like

I think I mostly get the idea and nothing jumps out to me, can you give some input / output examples of what you expect vs what you’re getting?

1 Like

Example: QT.SC 9 0 0 5 3 should return 9 but returns 4. Allowed values are 9, 0, 4.

QT.SC 11 0 0 4 4 returns 5 but it should return 11. Input 12 returns 11. Allowed values 7, 11, 2, 5.

So for QT.SC do you also want the highest note less than or equal to the input? I apologize that my understanding of music theory is all but nonexistent so I don’t entirely follow the calculation here but it seems to me that this condition:

		if (note_in >= n_s_val) {
			note_out = n_s_val;
			quant = true;
		}

will select whichever value less than or equal to note_in gets hit last in the iteration, not necessarily the largest one. The final value of max_n_s_val is equal to the desired result in both the examples you gave but maybe this is not the correct answer in all cases? I also don’t quite understand this adjustment

if (!quant) note_out = max_n_s_val - 12;

since it seems like this would always give a negative value. What’s the desired behavior when note_in % 12 is 0? Find the least note in the chord?

1 Like

Ohhh you definitely hit the nail on the head. It’s supposed to pick the highest note among the eligible notes, not the last eligible note.

If note_in isn’t bigger than any of the eligible notes in this octave, (!quant) is true and it picks the highest note from the lower octave. So that will always be a negative number of semitones when it needs to dip below the current octave.

1 Like

I used QT.BX for the first time today. Am I right the docs have x and i in the wrong order:

Doc says:
QT.BX x i
quantize 1V/OCT signal x to scale
defined by N.BX in scale index i

But it should be
QT.BX i x

yeah the docs are backwards. good catch.

QT.B and QT.BX are broken/bonkers with negative input voltages

n.b 10 1189 // scale = Bb, C, Eb, F, Ab or N=10,12,15,17,20
vn n.b 1 // return 10 OK
vn n.b -1 // return 5 OK
vn qt.bx 0 n 0 // return 0 OK
vn qt.b n 1 // return 0 OK
vn qt.b n 2 // return 3 OK
vn qt.b n 4 // return 3 OK
vn qt.b n 5 // return 5 OK
vn qt.b n 6 // return 5 OK
vn qt.b n 7 // return 8 OK
vn qt.b n 8 //return 8 OK
vn qt.b n 9 //return 10 OK
vn qt.bx 0 n -1 // return 0 OK
vn qt.bx 0 n -2 // return -3 should be -2
vn qt.b n -2 // return -3 should be -2
vn qt.b n -5 // return -5 should be -4

I hope whoever wrote this will look into it, not sure if it’s @desolationjones or someone else…

1 Like

There is a bug with negative voltages because I dumbly mirrored the voltage around 0 :upside_down_face: Fix incoming.

Cool, looking forward to this! Thanks!