Closed saidinesh5 closed 3 years ago
@mathiasvr Yup. Already started extending https://github.com/adamonsoon/rtttl-parse to spit out Temp3, Temp4 values. Also I fixed the issues i was having with my React component. I was trying to base it on the Number component which uses:
handleChange: function (component, value){}
and value
was always undefined.
So just went with something from react documentation and everything started working.
handleChange: function(e) {
this.props.onChange(e.target.name, parseInt(e.target.value));
}
Without further ado, the obligatory screenshot:
Now, do you happen to know any quick formula for me to convert a sound frequency value to the Temp3 register value of beep subroutine? Will save me a lot of time with my rtttl-parse functions.
Basically I have 2 problems left to solve now:
1) Parse rtttl notes into Eep_Pgm_Startup_Tune
array
2) Parse Eep_Pgm_Startup_Tune
into rtttl notes for easy editing. The latter seems to be tricky because there is a many to one mapping of rtttl notes -> Eep_Pgm_Startup_Tune
array, because of having to split up values > 255.
@saidinesh5 Nice work, looks good.
Now, do you happen to know any quick formula for me to convert a sound frequency value to the Temp3 register value of beep subroutine? Will save me a lot of time with my rtttl-parse functions.
I can try to see if I can come up with something.
Basically I have 2 problems left to solve now:
- Parse rtttl notes into
Eep_Pgm_Startup_Tune
array- Parse
Eep_Pgm_Startup_Tune
into rtttl notes for easy editing. The latter seems to be tricky because there is a many to one mapping of rtttl notes ->Eep_Pgm_Startup_Tune
array, because of having to split up values > 255.
What do you mean by splitting up values?
Based on some old data I had, I made this very crude approximation:
Temp3 = Math.round(328687 / freq**1.3746)
Maybe it can be used as a starting point.
@mathiasvr wow. That's a surprisingly good approximation. Needs a little tuning but can do that later by brute forcing all possible Temp3 values and map them to the note frequencies. Shouldn't be too difficult, since we are dealing with an 8 bit word. And splitting up values as in, this is the algorithm I am using to convert rtttl to the startup array:
/**
* Parse RTTTL to Bluejay ESC startup tone data
*
* @param {string} rtttl - RTTTL String
* @returns an array of (Number of pulses, Pulse width) tuples
*/
static parseToBluejayStartupTone(rtttl, startupToneLength) {
let parsedData = Rtttl.parse(rtttl);
startupToneLength = startupToneLength || 128;
const MAX_ITEM_VALUE = 2**8;
// Melody is basically an array of [{ duration(in ms): number, frequency (in Hz): number }]
let melody = parsedData.melody;
let result = new Uint8Array(startupToneLength);
function frequencyToBluejayTemp3(freq) {
// TODO: Later on make this more accurate using brute force
return Math.round(328687/freq**1.3746);
}
var currentResultIndex = 0;
var currentMelodyIndex = 0;
while(currentMelodyIndex < melody.length && currentResultIndex < result.length) {
var item = melody[currentMelodyIndex];
if (item.frequency != 0) {
// temp3 is a measure of wavelength of the sound
let temp3 = frequencyToBluejayTemp3(item.frequency);
if (temp3 > 0 && temp3 < MAX_ITEM_VALUE) {
let duration_per_pulse_ms = 1000/item.frequency; //(25 + temp3*200*25/150)/1000;
let pulses_needed = Math.round(item.duration / duration_per_pulse_ms);
while (pulses_needed > 0 && currentResultIndex < result.length) {
result[currentResultIndex++] = pulses_needed%MAX_ITEM_VALUE;
result[currentResultIndex++] = temp3;
pulses_needed = Math.floor(pulses_needed/MAX_ITEM_VALUE);
}
} else {
console.warn("Skipping note of frequency: ", item.frequency)
}
} else {
// Can wait from 1-255ms for each (Temp3, Temp4) tuple
// So split up a single silent note, if we have to
let duration = Math.round(item.duration);
while (duration > 0 && currentResultIndex < result.length) {
result[currentResultIndex++] = duration%MAX_ITEM_VALUE;
result[currentResultIndex++] = 0;
duration = Math.floor(duration/MAX_ITEM_VALUE);
}
}
currentMelodyIndex++;
}
if (currentMelodyIndex < melody.length) {
console.warn("Only " + currentMelodyIndex + " notes out of " + melody.length + " fit in the startup sequence");
}
return result
}
If the duration can't fit in one Temp4 variable, we split a single rtttl note into multiple (Temp4, Temp3) tuples. That means we lose the original Rtttl data during this conversion process. On top of that, if a given 1/4th note duration can be achieved by a tempo of x bpm, we can create the same note duration using a 1/8th note and double the tempo.
So right now my inverse function looks like:
static parseBluejayStartupToneToRtttl(startupTone) {
// First glob together items we had to split up due to the 8 bit limit
// Convert the array of [(Temp4, Temp3)] to an array of [{duration: number, frequency: number}] . This is kind of like the coin change problem now. But have to first find out what the coins are, before we can go down the path of a greedy approach
//return rttf format as a string: 'Name:Defaults:rtttl'
}
@saidinesh5 It's possible to change the range of Temp3/Temp4 if preferable, I already did this once before. We could also encode the duration differently to avoid duplicating notes? Anyway, looks good so far, do you know what is the typical range of frequencies/notes and durations we need to support?
I can also make a more accurate formula by measuring the frequency produced by the beep routine. This should be easier than brute force even though i'm not entirely sure what you had in mind.
Also, I shipped a version of the configurator that uses eeprom revision 202
for something else, my bad.
Oh that's interesting. Originally i was trying to write the decoder completely in assembly but it got annoying very quickly to look up frequencies and calculate note durations in assembly, so thought we can do it nicely in configurator, with proper error checking - that way the ESC can blindly play what configurator gives it.
These are the tempos (in beats per minute) rtttl tones usually use:
[
'25', '28', '31', '35', '40', '45', '50', '56', '63', '70', '80', '90', '100',
'112', '125', '140', '160', '180', '200', '225', '250', '285', '320', '355',
'400', '450', '500', '565', '635', '715', '800', '900'
]
So the shortest note is: 1/32 * (60000/900)
~ 2ms
And the longest note is: 3/2 * (60000/25)
= 3600ms
As for revision 202 - no worries. Can change mine to 203. Have to squash this branch and rename commits to make this branch mergable anyway.
I opened a pull request of the configurator and added some TODOs there in case you want to look into this: https://github.com/mathiasvr/bluejay-configurator/pull/13
I have looked more into rtttl and I think the range of allowed durations should be calculated like this:
Shortest: 60000 / 900 * 4/32
= 8.3ms
Longest: 60000 / 25 * 4
= 9.6s
I might try to update the beep routine to better accommodate musical notes to simplify how we process and store notes/timings.
Right now we represent a startup tune using:
Eep_Pgm_Startup_Tune
- An array of 128 bytes to store the startup melodyBluejay Startup Melody has a structure like:
In general,
We then blindly loop through these pulses and call the wait/beep subroutine.
This should give us 62 musical notes, which I think should be enough for a start up routine.
The big advantage of doing it this way is that the bluejay configurator app does most of the logic of converting music to these tuples, and the whole implementation will be possible in under 200 bytes of firmware space
The main question now is: are 62 notes enough?
Fixes https://github.com/mathiasvr/bluejay/issues/3
Requires: https://github.com/mathiasvr/bluejay-configurator/pull/13