Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JI OP Broken (Just Intonation) #129

Closed
cmcavoy opened this issue Nov 6, 2017 · 11 comments
Closed

JI OP Broken (Just Intonation) #129

cmcavoy opened this issue Nov 6, 2017 · 11 comments
Labels

Comments

@cmcavoy
Copy link
Contributor

cmcavoy commented Nov 6, 2017

Please describe the bug.

JI OP is broken according to post on lines. Tracking it here.

I haven't confirmed this bug.

@cmcavoy cmcavoy added the bug label Nov 6, 2017
@cmcavoy
Copy link
Contributor Author

cmcavoy commented Nov 6, 2017

Confirmed that this is still an issue. I asked for more information from @galapagoose, he was kind enough to send some more background,

I think I had some pseudo-code lying around that should fix it - only issue is it needed a log() lookup which seemed like it would have to be a custom LUT just for this op. I've resisted digging into the TT codebase so I'm not exactly sure how it would best be implemented.

If you're wanting to pursue it / post the info, the requirement specs are:
Input is 2 integers
The output is a number in the range 0 to V 1

Here's some code w/ bits of pseudo-code slotted in.

static void op_JI() {
	uint32_t n = pop();
	uint32_t d = pop();

	// normalize fraction between 1(root) and 2(octave)
	while( n < d ){
		n <<= 1;
	}
	while( n >= (d << 1) ){
		d <<= 1;
	}

	uint32_t ji = ( n << 8 ) / d; // 24bit LUT base

	// split `ji` into LUT index & fractional coefficient for xfade
	uint8_t ix = ji >> 16;
	uint8_t c  = (uint8_t)(( ji >> 8 ) & (uint32_t)0xFF );

	// lookup
	uint32_t a = ji_lut[ix];
	uint32_t b = ji_lut[ix+1]; // needs edge-check

	// xfade (integer math fun...)
	ji = a + c*(b-a)

	push( ji >> 4 ); // shift right to undo x16 range expansion in table
}

And this is the setup details, though this should likely be done offline and stored in flash rather than at startup.

#define LUT_SIZE 256

// create the LUT (pre-compute & save in flash)
uint16_t ji_lut[LUT_SIZE];
for( uint8_t i=0; i<LUT_SIZE; i++ ){
	// calculate as float / double then cast to u32
	ji_lut[i] = (uint32_t)( (5441.318221
		                        * log_f(0.12231220585 * (float)(i+LUT_SIZE) / (float)LUT_SIZE)
		                      + 4965.36721229)
		                    * 16.0 // expand dynamic range *16 (still fits in s16)
	                      );
}

@trentgill
Copy link
Contributor

Passing these 3 tests should prove it:
JI 4 1 -> 0
JI 1 4 -> 0
JI 3 2 -> 958

@cmcavoy cmcavoy changed the title JI Bug JI OP Broken (Just Intonation) Nov 8, 2017
@cmcavoy
Copy link
Contributor Author

cmcavoy commented Nov 12, 2017

I was able to write this in Python pretty easily based on the formula for computing cents from just intonation provided here. The original formula converted JI to cents (range of 1200 per octave), changing that to 1638 (the TT range for a single volt) works fine.

from math import log
def get_ji(n, d):
    ji = log(n/d) * 1638 / log(2)
    return int(ji)

It works for get_ji(3, 2) but changes the behavior of get_ji(4, 1) returning the fundamental pitch plus four octaves. According to the site above is the correct behavior, except the requirements above say The output is a number in the range 0 to V 1. Not sure what the history of that requirement is, or if it's still valid.

I'd like to try to fix this bug (I'm personally interested in playing around with just intonation based on this really great lines thread). I get basic C syntax, and this seems relatively easy - unless we don't have access to the math library and thus no access to log. My guess is a lot of the example code above is in place to get around this limitation.

Before I go down the path of working around not having log I wanted to confirm - can we use math? /cc @burnsauce @samdoshi @scanner-darkly @tehn

@cmcavoy
Copy link
Contributor Author

cmcavoy commented Nov 12, 2017

The problem with the formula from here is that it will return negative values. get_ji(1, 4) returns -3276, which is correct - except we can't use that. So will need to return 0.

@trentgill
Copy link
Contributor

trentgill commented Nov 12, 2017

tldr: i don't believe you can use log() on a microcontroller without an FPU (or it will be super slow).

//

My intention for the JI op was that it used 'normalized' JI ratios. In my readings that seems to be the traditional way to represent pitches – otherwise people start writing silly, unreadable fractions when they want to go up or down octaves. For this op, I anticipated the octave choice would be done separately, though perhaps integrating it as another parameter would make for terser scripts.

Regarding the python code you post, the problem is that it requires floating point math which we don't have on the teletype hardware. I believe you can still use <math.h>, and the log() fn but owing to the nature of logarithms, you will end up truncating the result, unless you can get some kind of floating point result.

My solution above is to create a table of values that are the answer to your get_ji() function, to which we feed the result of the n/d division. Then I propose a linear interpolation between the nearest two points in the table to improve tuning accuracy & decrease LUT size.

This said, the equation you found is probably a better candidate to create the LUT! My solution was working backward from the frequency-to-midi converter in puredata...

@cmcavoy
Copy link
Contributor Author

cmcavoy commented Nov 12, 2017

Oh wow, yeah - I really don't understand the TT hardware! Thanks for the explanation @trentgill. I knew the TT language didn't support floating point numbers, but didn't realize the actual CPU didn't either. Will really need to change my thinking if I want to try tackle bugs!

Also, understood on the normalization to a single octave. That makes sense (cents? #dadjoke).

@trentgill
Copy link
Contributor

Ahoy. Just a little headsup I found an entirely alternate solution that uses prime factorization & addition/subtraction to do this in a cpu & memory optimized way. Sorry for all the blather about lookup tables.

Hoping to have a PR by the weekend.

@cmcavoy
Copy link
Contributor Author

cmcavoy commented Nov 16, 2017

Sounds very interesting @trentgill! Looking forward to learning from the PR. Given the state of 2.2, there might be a bit of juggling, but I'm definitely interested in using the JI op!

As an aside, I asked a couple of related questions in the Modern C thread on lines. It led to some interesting suggestions. There's a log function that's available through libfixmath that doesn't use floating point types. I'm a total C newb, but started messing around with it. I'm actually stuck on compiling some test code - won't get into the details here, I just need to sit down for a while and figure it out. Anyway, if the log solution w/ libfixmath is appealing - there's good info in that lines thread. Also - the idea of using a LUT was definitely 👍 by the folks in that thread, so...still a viable solution!

@cmcavoy
Copy link
Contributor Author

cmcavoy commented Nov 17, 2017

Looking forward to playing around with this this weekend @trentgill thanks! I'm going to close this issue, but I'm interested in the solution you found...do you have a link you can share? I've looked at the code, but would be interested in discussion about why it works if that's easy to find. Thanks!

@cmcavoy cmcavoy closed this as completed Nov 17, 2017
@trentgill
Copy link
Contributor

trentgill commented Dec 3, 2017 via email

@cmcavoy
Copy link
Contributor Author

cmcavoy commented Dec 3, 2017

Wow @trentgill this is super helpful, thanks for the write up! And the fix. Really interested in just intonation, very thankful for this tool to help experimentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants