how to properly generate ideal sine test sample? - Page 1
Forum section: electronics / general

author: radiolistener
author_rank: Super Contributor
timestamp: May 31, 2022, 09:51:57 am
content: Basically I need a test vector with ideal sine wave, it should be 14 bit integer format and consists of 16 samples (a single sine period with maximum amplitude). I'm expecting symmetric AES17 sample representation which means that the max negative value is not used (for symmetry) and mean value should be 0.Previously I was used test vector provided by another people, I don't know how it was generated, but it seems that this vector provides better sine performance than I can generate. Here it is:Code:031345792756781917567579231340-3134-5792-7567-8191-7567-5792-3134And it's spectrum:Now I'm trying to generate the same sine with code in the following way:Code:var bits = 14;var halfRange = Math.Pow(2, bits-1);for (var i = 0; i < 16; i++){double sine = Math.Sin(2 * Math.PI * i / 16);sample = round( sine * (halfRange - 1D) );}sine * (halfRange - 1D) result:Code:0.03134.55999450245055791.911644699017567.497250799958191.07567.497250799955791.9116446990113134.55999450245140.0000000000010030750644159092-3134.5599945024496-5791.91164469901-7567.4972507999482-8191.0-7567.4972507999491-5791.911644699012-3134.5599945024555and after round I get this:Code:031355792756781917567579231350-3135-5792-7567-8191-7567-5792-3135And it's spectrum:It seems that my sine has a little worse performance, it has -87.91 dB for 3'rd harmonic, while first sine has -95.83 dB for 3'rd harmonic.Both test vectors almost the same, the only difference is 3135 instead of 3134. But I have no idea how to obtain 3134 value, because 3134.5599945 should be rounded to 3135. And if I replace round with floor (cut-off fraction part), I will get incorrect value for the third value, because 5791.91 will be rounded to 5791, but it should be 5792...So there are two questions:1) Is the first sine really has better performance? (maybe my spectrum calculation is just not so accurate)2) How to calculate it with proper rounding?

author: asmi
author_rank: Super Contributor
timestamp: May 31, 2022, 03:40:40 pm
content: 1. There is no such thing as "the ideal sine wave". It's a purely mathematical construct.2. 14 bits gives a resolution of 1/(2^14) = 6.103515625 * 10^-5, which gives an error of 20 * log (6.103515625 * 10^-5) ~ -84.3 dB. So unless I screwed up the math somewhere (again), you can't really get anything much better than that with this format.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 31, 2022, 05:18:41 pm
content: Indeed.The fact you seem to be getting "better performance" with actual rounding errors (if I get what you said correctly) is marginal. It's probably the result of an additional rounding error from the spectrum analysis itself that happens to give a more favorable figure.As asmi said, you can't get better with 14 bits.

author: asmi
author_rank: Super Contributor
timestamp: May 31, 2022, 05:57:01 pm
content: Quote from: SiliconWizard on May 31, 2022, 05:18:41 pmTechnically you could've gotten two times better result (about -90.3 dB) if ADC would be able to do a proper rounding (round-to-nearest), but as far as I know all of them round down, which is why the max error is 1 LSD, not 0.5 LSD which would be the case for round-to-nearest rounding. But in any case ~-90.3 dB seems to be the absolute best case possible, so not sure how he got better numbers.

author: radiolistener
author_rank: Super Contributor
timestamp: May 31, 2022, 08:54:39 pm
content: Quote from: asmi on May 31, 2022, 03:40:40 pmI understand discrete signal dynamic range limit and quantization noise. But the goal is not to bypass this limitation. The goal is to get sine with minimum possible distortion for specific resolution.Quote from: asmi on May 31, 2022, 03:40:40 pmDynamic range 6.02*14 = 84.28 dB represents dynamic range of worst case for any possible signal. It is limited due to quantization noise at -84.28 dB level. But I'm talking about specific sine, which performance depends on error between aperture and actual sine points, so its actual performance will depends on a bit resolution and relation between sine frequency and sample rate.I need a discrete sine wave which best fits with continuous sine for selected bit resolution. The criterion here is a minimum possible harmonics/distortion level after waveform reconstruction from discrete to continuous representation.Quote from: asmi on May 31, 2022, 05:57:01 pmReconstructed wave dynamic range exceeds theoretical -84 dB noise floor because errors (due to misalignment between discrete points and aperture) is not maximum possible for selected combination of sine parameters, bit resolution and sample rate.For example 14-bit 96 kS/s DC signal (constant sample value) has zero noise, so it hasinfinite dynamic rangewhich is much better than mentioned 84.28 dB for 14 bit. But you cannot get the same result for any kind of signal. While 84 dB limitation means guarantee that 14 bit resolution can represent at least 84 dB dynamic range signal in worst case for any signal. That limitation is for worst case. As mentioned, for DC signal it will be infinite.

author: hamster_nz
author_rank: Super Contributor
timestamp: June 01, 2022, 12:08:33 am
content: Would you mind looking at this set of values?Code:0 3134 5791 7566 8190 7566 5791 3134 0 -3134 -5791 -7566 -8190 -7566 -5791 -3134If they are better than the original I'll share the code that found them...

author: macboy
author_rank: Super Contributor
timestamp: June 01, 2022, 12:25:09 am
content: Try scaling very slightly up or down (fraction of one full scale bit). You can find a point where it will round as you want.Even better, add noise (yes, add) to achieve dithering which can dramatically reduce distortion, at the expense of random noise. If you need substantially less than 0.5 FS bandwidth, then push (shape) the noise up to high frequencies and filter it out. This is how you can reproduce a -105 dB tone from a CD with theoretically only 96 dB of dynamic range.

author: OM222O
author_rank: Frequent Contributor
timestamp: June 01, 2022, 12:41:11 am
content: An easy implementation in python is (you can use an online python interpreter):Code:import numpy as npidx = np.arange(16) # Number of samples per periodsf = idx.max() / (2*np.pi) # scaling factor for the periodarr = idx/sf # create a new array that goes from 0 to 2 pi in N stepsarr = np.sin(arr) # calculate the sin value for each pointinteger_arr = (arr*(2**13)).astype('int32') # Scale the array by 2^13 (the actual sine values go between -1 and 1, giving you an effective range of 2, so you need to scale by max int/2)print(integer_arr)this results in:[ 0 3331 6087 7791 8147 7094 4815 1703 -1703 -4815 -7094 -8147 -7791 -6087 -3331 0]you can easily change the number of samples , add phase offset or change the scaling factor etc.Edit: to change the phase offset just add it to line 5, for example you can do arr = np.sin(arr + (np.pi/2)) to add a pi/2 offset and turn this into a cos table

author: BrianHG
author_rank: Super Contributor
timestamp: June 01, 2022, 02:42:56 am
content: Quote from: OM222O on June 01, 2022, 12:41:11 am2 sequential samples with the value 0 in the 16 phase loop?Wouldn't that generate substantial distortion?

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 01, 2022, 02:48:43 am
content: Yes, it's 16 samples while the period is computed over 15 samples. Looks like a typical off-by-one issue.

author: radiolistener
author_rank: Super Contributor
timestamp: June 01, 2022, 03:08:46 am
content: Quote from: hamster_nz on June 01, 2022, 12:08:33 amwow, it has -105.25 dBc for 3'rd harmonic and -100.25 dBc for 5'th harmonicAlmost all harmonics below -100 dBc, except 7'th which is -99.05 dBc...How to calculate it?

author: OM222O
author_rank: Frequent Contributor
timestamp: June 01, 2022, 03:23:20 am
content: Quote from: BrianHG on June 01, 2022, 02:42:56 amQuote from: SiliconWizard on June 01, 2022, 02:48:43 am0 and 2 pi are the same point effectively. If this is an issue you can set the array to be of lenght N+1 and ignore the last value

author: radiolistener
author_rank: Super Contributor
timestamp: June 01, 2022, 03:28:15 am
content: Quote from: BrianHG on June 01, 2022, 02:42:56 amYes, just -20 dBc for 2'nd harmonic and the carrier is not full scale, just -0.36 dBc:

author: hamster_nz
author_rank: Super Contributor
timestamp: June 01, 2022, 03:47:48 am
content: Quote from: radiolistener on June 01, 2022, 03:08:46 amJust looked one either side of the original samples, for all samples, and picked which is best:Code:///////////////////////////////////////////// sine_samples.c : hamster@snap.net.nz//// A quick hack to see if I can find better// sine sample values for an ADC/////////////////////////////////////////////#include <stdio.h>#include <math.h>static int data[] = {0, 3134, 5792, 7567, 8191, 7567, 5792, 3134,0, -3134, -5792, -7567, -8191, -7567, -5792, -3134};static double evaluate(int data[]) {double tc = 0.0, ts = 0.0, t_err = 0.0;// Single bin DFTfor(int i = 0; i < 16; i++) {ts += data * sin(i*M_PI*2.0/16);tc += data * cos(i*M_PI*2.0/16);}ts /= 16/2;tc /= 16/2;// Remove the fundimental and total up RMS of what is leftfor(int i = 0; i < 16; i++) {double e = data - sin(i*M_PI*2.0/16) * ts - cos(i*M_PI*2.0/16) * tc;t_err += e*e;}return t_err;}int main(void) {double min = 0.0;int limit = pow(3,16);// to pow(3,16) - to try every combinationfor(int i = 0; i < limit; i++) {int d;// Add either +1,0,-1 to every sample, based on iint x = i;for(int j = 0; j < 16; j++) {switch(x%3) {case 1: d = data-1; break;case 0: d = data; break;case 2: d = data+1; break;}x /= 3;}// Range checkif(d > 8191 || d < -8191)continue;// See if this is any better or worsedouble this_noise = evaluate(d);if(i == 0 || this_noise < min) {min = this_noise;printf("%12.9f: ", this_noise);for(int k = 0; k < 16; k++) {printf("%5d ",d);}printf("\n");}}// Print out the original for comparisonprintf("%12.9f: ", evaluate(data));for(int k = 0; k < 16; k++) {printf("%5d ",data);}printf(" << original\n");return 0;}If I lack the skills to do it properly, I just brute-force it!Here's the output:Code:$ ./main1.527446318: 0 3134 5792 7567 8191 7567 5792 3134 0 -3134 -5792 -7567 -8191 -7567 -5792 -31341.516362543: 0 3135 5792 7567 8191 7567 5792 3134 0 -3134 -5792 -7567 -8191 -7567 -5792 -31341.255278769: 0 3135 5792 7567 8191 7567 5792 3134 0 -3135 -5792 -7567 -8191 -7567 -5792 -31341.188563146: 0 3135 5792 7568 8191 7567 5792 3134 0 -3135 -5792 -7568 -8191 -7567 -5792 -31341.121847524: 0 3135 5792 7568 8191 7568 5792 3134 0 -3135 -5792 -7568 -8191 -7568 -5792 -31341.110763749: 0 3135 5792 7568 8191 7568 5792 3135 0 -3135 -5792 -7568 -8191 -7568 -5792 -31341.023786799: 1 3135 5792 7567 8191 7567 5791 3134 -1 -3135 -5792 -7567 -8191 -7567 -5791 -31340.957071177: 1 3135 5792 7568 8191 7567 5791 3134 -1 -3135 -5792 -7568 -8191 -7567 -5791 -31340.955140664: 0 3134 5791 7567 8190 7567 5791 3134 0 -3134 -5791 -7567 -8190 -7567 -5791 -31340.920336789: 0 3134 5791 7566 8190 7566 5791 3134 0 -3134 -5791 -7566 -8190 -7567 -5791 -31340.920336789: 0 3134 5791 7566 8190 7566 5791 3134 0 -3134 -5791 -7567 -8190 -7566 -5791 -31340.920336789: 0 3134 5791 7566 8190 7567 5791 3134 0 -3134 -5791 -7566 -8190 -7566 -5791 -31340.920336789: 0 3134 5791 7567 8190 7566 5791 3134 0 -3134 -5791 -7566 -8190 -7566 -5791 -31340.506366571: 0 3134 5791 7566 8190 7566 5791 3134 0 -3134 -5791 -7566 -8190 -7566 -5791 -31341.527446318: 0 3134 5792 7567 8191 7567 5792 3134 0 -3134 -5792 -7567 -8191 -7567 -5792 -3134 << original

author: hamster_nz
author_rank: Super Contributor
timestamp: June 01, 2022, 03:53:24 am
content: This might be a tiny bit better, but has a tiny phase shift:Code:1 3135 5792 7567 8190 7566 5790 3133 -1 -3135 -5792 -7567 -8190 -7566 -5790 -3133

author: OM222O
author_rank: Frequent Contributor
timestamp: June 01, 2022, 04:32:23 am
content: I'm still not sure what the point of this topic is. There are an infinite number of phase shifts you can try and some may work better than others in specific test cases, but you always have some rounding errors. If you want to minimize quantization error that's pretty easy to do mathematically. If you want to generate a specific frequency and phase shift, that's also pretty easy to do, but like others said, there is no "ideal sine wave". You can re-write the program I gave you in any language and get the same results, but it seems pointless to worry about the harmonics noise as they can be filtered out in any good design. What's the main goal of generating the sine table?The funny thing is that you don't even need a spectrum analyzer to test this, using any FFT analysis tool (numpy supports this) should give you the same results, so you can just simulate the whole thing and optimize it as much as you want, but again, I can't find a good reason for why you should.Edit: I used 17 samples and ignoring the last 0, this results in almost exactly the same result as you posted initially:[ 0 3134 5792 7568 8192 7568 5792 3134 0 -3134 -5792 -7568 -8192 -7568 -5792 -3134 0]

author: Someone
author_rank: Super Contributor
timestamp: June 01, 2022, 04:32:58 am
content: Quote from: radiolistener on June 01, 2022, 03:08:46 amIf you dont mind losing some gain:[0 3107 5741 7501 8119 7501 5741 3107 0 -3107 -5741 -7501 -8119 -7501 -5741 -3107]Many ways to skin this cat!

author: Someone
author_rank: Super Contributor
timestamp: June 01, 2022, 04:35:36 am
content: Quote from: OM222O on June 01, 2022, 04:32:23 amExcept it isnt! The cost function has many local minima, so there is no easy method.

author: hamster_nz
author_rank: Super Contributor
timestamp: June 01, 2022, 06:01:12 am
content: Quote from: OM222O on June 01, 2022, 04:32:23 am... except 8192 and -8192 is out of range.If your DAC's reconstruction filter is significantly filtering the 3rd harmonic of f/16 sine wave it might be a little too aggressive... or you don't care about using the full requency performance of your DAC.

author: OM222O
author_rank: Frequent Contributor
timestamp: June 01, 2022, 06:12:49 am
content: Quote from: hamster_nz on June 01, 2022, 06:01:12 amI forgot to add a range check, but that's pretty easy:Code:integer_arr[np.where(integer_arr> HIGH)] = HIGHinteger_arr[np.where(integer_arr< LOW)] = LOWwhere HIGH and LOW are the limits. This is another weird thing because ADCs don't accept negative values as far as I know, so typically I add 1 to the sine values to shift the range from (-1 to 1) to (0 to 2) and then scale by (2^bits)-1.

author: BrianHG
author_rank: Super Contributor
timestamp: June 01, 2022, 06:32:06 am
content: Quote from: OM222O on June 01, 2022, 06:12:49 amOnce again, such patching distorts the sine wave.Do the all the math properly and you will properly hit your targets.

author: OM222O
author_rank: Frequent Contributor
timestamp: June 01, 2022, 06:55:52 am
content: Quote from: BrianHG on June 01, 2022, 06:32:06 amCode:import numpy as npidx = np.arange(17) # Number of samples per periodsf = idx.max() / (2*np.pi) # scaling factor for the periodarr = idx/sf # create a new array that goes from 0 to 2 pi in N stepsarr = np.sin(arr) # calculate the sin value for each pointinteger_arr = np.round((arr*((2**13)-1))).astype('int32') # Scale the array by (2^13)-1 and then round the values(the actual sine values go between -1 and 1, giving you an effective range of 2, so you need to scale by max int/2)print(integer_arr[:-1])[ 0 3135 5792 7567 8191 7567 5792 3135 0 -3135 -5792 -7567 -8191 -7567 -5792 -3135]fun fact, removing the round produces EXACTLY what he initially posted, so I'm fairly sure that's how the table was made

author: radiolistener
author_rank: Super Contributor
timestamp: June 01, 2022, 07:42:45 am
content: Quote from: OM222O on June 01, 2022, 04:32:23 amThe point of topic is how to generate sine wave for specific resolution (in my case 14 bit, 16 samples per period). To get as low as possible distortion/harmonics. I need it as a test vector. As you can see simple use ofroundorfloorfunction doesn't provide best result.Quote from: OM222O on June 01, 2022, 04:32:23 amQuantization error and phase shift doesn't matter for me. The goal is to get minimum possible sine distortion/harmonics level. I need a test vector for testing DSP block. DSP block works with much higher resolution than 14 bit of ADC source. I want to get maximum possible from 14 bit source.Since I want to get ideal sine for specific conditions - 14-bit and 16 samples per period, ideal sample with sine definitely exists. Because one sample is better than other. So, some sample is the best. And I needs the way to calculate it.Quote from: OM222O on June 01, 2022, 04:32:23 amI tried to use your program withonline python compiler, include fix for sample length. But it produce dirty sine, I have no idea what is the reason for that.Quote from: OM222O on June 01, 2022, 04:32:23 amNo it cannot be filtered, I need a clean test vectors for testing DSP block. DSP block has 64-bit fixed point resolution and I want to see every small artifact which happens in signal processing. Such as spurs/harmonics.Quote from: OM222O on June 01, 2022, 04:32:23 amSpectrum analyzer which I use is FFT based and is a part of software/gateware which I want to test with these test vectorsBy the way, my FFT can have some errors, because I'm using some speed optimizations. I tested it and it seems that errors are below noticeable level, but who knows...

author: radiolistener
author_rank: Super Contributor
timestamp: June 01, 2022, 07:53:46 am
content: Quote from: OM222O on June 01, 2022, 06:55:52 amNo, just tested your code with removed round ononline python compiller, the result is:Code:0 3134 5791 7567 8191 7567 5791 3134 0 -3134 -5791 -7567 -8191 -7567 -5791 -3134But original sample has 5792 instead 5791Here is original sample:Code:0 3134 5792 7567 8191 7567 5792 3134 0 -3134 -5792 -7567 -8191 -7567 -5792 -3134Are you sure that your code produce the same?

author: OM222O
author_rank: Frequent Contributor
timestamp: June 01, 2022, 08:04:51 am
content: you're right, I missed the 1 difference since I was having a quick look, but for all intends and purposes that is HOW to generate a sine table. rounding, phase offset, scaling, etc. is the parameters you can play with if you want to optimize something

author: radiolistener
author_rank: Super Contributor
timestamp: June 01, 2022, 08:11:22 am
content: Quote from: OM222O on June 01, 2022, 08:04:51 amI already played with it a long time, and cannot find the way how version which starts from 0 3134 5792 is calculated. But this version has more clean sine than version calculated with simple round or without round.Original sample has -95.83 dBc for 3'rd harmonic. While round version has -87.91 dBc which is worse for almost 8 dB.And nowhamster_nzprovidedeven more clean sine:Code:0 3134 5791 7566 8190 7566 5791 3134 0 -3134 -5791 -7566 -8190 -7566 -5791 -3134it has third harmonic -105 dBc!The only problem is that it was generated with brute-force method. I'm looking the way to generate it with math expression.Any idea why usual calculation with round produce result which is not the best?

author: hamster_nz
author_rank: Super Contributor
timestamp: June 01, 2022, 08:35:03 am
content: I'll leave the PC looking for better for a while, but this one seems to be quite good!Code:1598 4551 6811 8034 8034 6811 4551 1598 -1598 -4551 -6811 -8034 -8034 -6811 -4551 -1598About 1/20th the RMS error of the original. Be interested what your design makes of it.(note that it is still scaled by 8181, but because of the phase the max value doesn't appear in the numbers...

author: hamster_nz
author_rank: Super Contributor
timestamp: June 01, 2022, 08:53:19 am
content: Quote from: radiolistener on June 01, 2022, 08:11:22 amIt is because of interplay between the rounding alters the level of the fundamental.That and we are truly playing around it the weeds...

author: radiolistener
author_rank: Super Contributor
timestamp: June 01, 2022, 09:13:03 am
content: Quote from: hamster_nz on June 01, 2022, 08:35:03 amYes. All harmonics are bellow -103 dBc!

author: OM222O
author_rank: Frequent Contributor
timestamp: June 01, 2022, 09:21:13 am
content: Quote from: radiolistener on June 01, 2022, 08:11:22 amTo be honest I've always hated math and avoided it like the plague, but there's a much better method than brute force. For each offset (the variable) you can add up the decimal part of each sampling point (cost) and use something like stochastic gradient decent with momentum or some other optimization / approximation algorithm to minimize the cost. there are lots of examples of SGD with python online, so give those a shot. Brute force algorithm is bad because there are an infinite number of phase offsets you can try, but optimization algorithms converge fairly quickly. This minimizes quantization noise, but again, I'm not sure why you would want to do this.

author: hamster_nz
author_rank: Super Contributor
timestamp: June 01, 2022, 10:01:04 am
content: Quote from: OM222O on June 01, 2022, 09:21:13 amTalk is cheap - feel free to find the optimal answer. I know there is at least one better set of values out there for you to find, and I would be very interested in seeing your code.QuoteSometimes just playing with numbers is fun - and it is exploring a bit of the landscape where some of my own problems sometimes live.It might come in useful one day, for exactly the same sort of reasons it is useful for Radiolistener. Then again it might not

author: Someone
author_rank: Super Contributor
timestamp: June 01, 2022, 10:18:11 am
content: Quote from: OM222O on June 01, 2022, 09:21:13 amAs already said:Quote from: Someone on June 01, 2022, 04:35:36 amIf you'd like to propose a solution to this problem that has a convex solution we're all ears. But quantisation noise is still an unsolved optimisation problem, gradient descent fails hard.

author: brucehoult
author_rank: Super Contributor
timestamp: June 01, 2022, 10:43:45 am
content: Quote from: radiolistener on June 01, 2022, 08:11:22 amThat's mathematically impossible. The best you can do is search a state space systematically for good candidates, then do a 2D gradient descent on amplitude and phase from the best candidates to find their local optimums.

author: OM222O
author_rank: Frequent Contributor
timestamp: June 01, 2022, 11:05:19 am
content: Quote from: Someone on June 01, 2022, 10:18:11 amA very quick and dirty plot of the rms error with phase going from 0 to pi in 1000 steps shows a clear repeating pattern:So realistically you only need to consider the first 100 steps or so, after that all of the answers are repeated and the same. I changed it to calculate with 10000 steps. 9358th value (phase offset of 2.9399024052293283 radians) had the lowest RMS error (0.5637751689365491) and the actual values were:[ 1641 -1555 -4514 -6786 -8025 -8042 -6835 -4587 -1641 1555 4514 6786 8025 8042 6835 4587]if you want to get an even better result, you can keep repeating this over a smaller and smaller range but I'll leave it here. The actual noise of the DAC and filter components will have a much higher impact in any real design, so this is all entirely pointless.Edit: code for those who are curiousCode:import numpy as npfrom matplotlib import pyplot as pltdef create_table(gain, phase, points):idx = np.arange(points+1)sf = idx.max() / (2*np.pi)arr = idx[:-1]/sfarr = np.sin(arr + phase)*gaininteger_arr = np.round(arr).astype('int32')return (arr, integer_arr)def calculate_error(arr, integer_arr):diff = integer_arr - arrerr = np.sqrt((diff**2).sum())return errn = 10000errors = []increment = np.pi/nfor i in range(n):arr, integer_arr = create_table((2**13)-1, increment*i, 16)errors.append(calculate_error(arr, integer_arr))errors = np.array(errors)plt.figure(figsize=(16,9))plt.plot(np.arange(len(errors)),errors)plt.show()arr, integer_arr = create_table((2**13)-1, increment*np.argmin(errors), 16)print(np.argmin(errors))print(increment*np.argmin(errors))print(calculate_error(arr, integer_arr))print(integer_arr)If you want to run it online, remove the matplotlib import and the 3 plot lines (plt.figure, plt.plot and plt.show)Edit 2: [ 1641 4587 6835 8042 8025 6786 4514 1555 -1641 -4587 -6835 -8042 -8025 -6786 -4514 -1555] is even better (100k steps over the first 0.4 radians)

author: BrianHG
author_rank: Super Contributor
timestamp: June 01, 2022, 04:46:00 pm
content: Quote from: hamster_nz on June 01, 2022, 10:01:04 amHere is a use: Using this sine table as a reference for multimode digital QAM modulator, every DB you can extract in purity especially with high quantization levels, the sturdier the SNR of your decoded data on the other side.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 01, 2022, 05:11:15 pm
content: Quote from: OM222O on June 01, 2022, 03:23:20 amOf course, but that means you only have 15 effective samples per period, and not 16.

author: rstofer
author_rank: Super Contributor
timestamp: June 01, 2022, 05:32:05 pm
content: Quote from: OM222O on June 01, 2022, 11:05:19 amI was! I am beginning to think there is some merit to Python, specifically in the libraries. numpy and matplotlib are great examples.

author: OM222O
author_rank: Frequent Contributor
timestamp: June 01, 2022, 09:21:44 pm
content: Quote from: rstofer on June 01, 2022, 05:32:05 pmFor data analysis / scientific computing Python is the go-to language. It's extremely flexible and if performance is an issue for large computations, you can create C extensions for it using something like Cython. Numpy is actually written in C because it's used to crunch numbers.I realized there is a small mistake in the code I posted. To calculate the RMS error, I didn't divide by N .The actual line should be:Code:err = np.sqrt((diff**2).sum()/len(arr))but this doesn't actually change the results.

author: radiolistener
author_rank: Super Contributor
timestamp: June 01, 2022, 11:53:46 pm
content: Quote from: OM222O on June 01, 2022, 11:05:19 amthanks, your version has -101.02 dBc for 3'rd harmonic.And version generated with fixed code has -101.02 dBc for 3'rd harmonic.By the way,hamster_nzprovided me (in private message) with version which is even more clean: -109.13 dBc for 3'rd harmonic

author: Someone
author_rank: Super Contributor
timestamp: June 02, 2022, 12:37:10 am
content: Quote from: radiolistener on June 01, 2022, 11:53:46 pmStill not surpassing the earlier example:Quote from: Someone on June 01, 2022, 04:32:58 amIf you have a preference for maximum suppression in the 3rd harmonic it can be pushed at least -148dB (likely further).

author: radiolistener
author_rank: Super Contributor
timestamp: June 02, 2022, 06:13:13 am
content: Quote from: Someone on June 02, 2022, 12:37:10 amHow? It will be useful for filter testing.

author: radiolistener
author_rank: Super Contributor
timestamp: June 02, 2022, 06:52:06 am
content: Quote from: Someone on June 02, 2022, 12:37:10 amwow, I didn't noticed this one. It is not full scale -0.08 dBFS, but its SFDR is 119.56 dBHow did you generated it?PS: I was changed approach to find the harmonic index, previously I was used search local extremum around expected index, but such way is buggy, because it catch noise peaks near harmonic position. Now I just use N*index, where index is the first harmonic index. So, now it displays NaN dB for even harmonics (missing harmonic). The same value for even harmonics it displays for other sine examples provided it this topic. Odd harmonics levels with a new approach is almost the same.

author: Someone
author_rank: Super Contributor
timestamp: June 02, 2022, 07:15:01 am
content: Quote from: radiolistener on June 02, 2022, 06:52:06 amThat one is relatively quick to find with a simple sweep of amplitude selecting the lowest RMS error, optimisation with phase or directly on the points finds the same answer.

author: Berni
author_rank: Super Contributor
timestamp: June 02, 2022, 08:10:35 am
content: If you are using this to test DAC then looping a single sine wave sample is the wrong way to do it.This runs the converter trough the same cycle of codes, so it can give a false idea of the performance as it might hit codes that are particularly good or ones that are particularly bad.The proper way requires generating a sinewave that is not divisible with the sample rate. In that case each wave of the sine wave passes trough different values, this exercises many more unique DAC codes. The spectral analysis can then be done over a window of 1000s of these sine waves to capture the average spectrum.Generating the test data for this is quite a bit harder tho. You either have to hold a really long waveform in memory or calculate the sinewave on the fly. This involves things like CORDIC algorithms. I would generally just cheat here and use the Altera NCO IP Block that implements all of this (Including showing you a FFT of its performance while you are adjusting the design constraints)

author: hamster_nz
author_rank: Super Contributor
timestamp: June 02, 2022, 09:46:27 pm
content: Quote from: Someone on June 02, 2022, 07:15:01 amThat is very impressive! I'm going to file that away,it may be useful as a practical joke to play on somebody.... If I tested my FFT code on that vector and saw those results I would swear that it was broken!

author: hamster_nz
author_rank: Super Contributor
timestamp: June 03, 2022, 04:58:10 am
content: Here's a signal with low harmonics, and amplitude > +/-8191, but all samples within the range of +/- 8191.Code:1236, 4249, 6615, 7974, 8119, 7028, 4867, 1965, -1236, -4249, -6615, -7974, -8119, -7028, -4867, -1965Might make an interesting way to look for odd math overflows in a DSP design.

author: cbutlera
author_rank: Regular Contributor
timestamp: June 06, 2022, 04:22:34 pm
content: Quote from: Berni on June 02, 2022, 08:10:35 amHere is a simple algorithm that will generate a sine-wave with 12.433075... samples per cycle. The amplitude of the sine-wave will vary a little over time, but will never differ by more than 1ppm from its initial value, no matter how long it runs. So more than good enough to test a 14 bit DAC, and exercise almost every code.Code:x = 2079038281;a = 0;for (;;) {x += (a >> 1);a -= (x >> 1);}'x' and 'a' are 32 bit signed integers. The DAC samples are the upper 14 bits of either 'x' or 'a' and have an amplitude of 8191. To avoid a 1/2 LSB DC offset in the output, add 0x20000 to the value in 'x' or 'a' before extracting the upper 14 bits.

author: radiolistener
author_rank: Super Contributor
timestamp: June 07, 2022, 05:38:23 am
content: Quote from: cbutlera on June 06, 2022, 04:22:34 pmthis is a real magicHow it works?

author: BrianHG
author_rank: Super Contributor
timestamp: June 07, 2022, 05:48:47 am
content: Quote from: radiolistener on June 07, 2022, 05:38:23 amI think we covered this in an old MCU thread on how to generate a sine wave without any floating point or multiplication hardware. After LUT and interpolation, we finally reached something similar to this, but with only 16 bit ints.

author: Berni
author_rank: Super Contributor
timestamp: June 07, 2022, 06:38:43 am
content: Quote from: radiolistener on June 07, 2022, 05:38:23 amNice trick there. The results are impressive, Cheap to implement in a FPGA too.By the looks of it this is two integrators looped into each other. This sort of setup oscillates infinitely as the values move from one integrator to the other and back again (much like a ideal LC circuit with 0 damping). Similar to how a NOT gate ring oscillator works except this is in "analog values"

author: brucehoult
author_rank: Super Contributor
timestamp: June 07, 2022, 07:13:48 am
content: Quote from: Berni on June 07, 2022, 06:38:43 amIt's a rotation matrix, sneakily using the already-updated value of x for the -sin(theta) bit. Or two shearing matrix concatenated.

author: radiolistener
author_rank: Super Contributor
timestamp: June 07, 2022, 07:49:00 am
content: I see different initial value doesn't affects frequency, but affects amplitude in a non-obvious way.How to select initial value for that algorithm?Is there any way to change frequency but keep low distortions?

author: cbutlera
author_rank: Regular Contributor
timestamp: June 07, 2022, 07:56:37 am
content: Quote from: radiolistener on June 07, 2022, 05:38:23 amThis is an algorithm that I discovered while playing around with numbers back in the 1980s. I was writing DSP code for voice-band modems at the time, on quite limited hardware, so tricks like this looked interesting.There is nothing special about the initial value for x. It just sets the amplitude, although some choices can result in a little more variation in the amplitude with time than others. There is also nothing special about the right shift. It is just a 32 bit multiply by 0.5.An example of the more general algorithm that can generate any arbitrary frequency with no significant long term amplitude drift is:Code:p = 2 * sin(pi / n)x = 1;a = -p / 2;for (;;) {x += a * p;a -= x * p;}Here x has an amplitude of 1, and n is the number of samples per cycle of the generated sine-wave. The accumulated amplitude error over time is typically less than 1:1012after 1012samples, with all variables as IEEE 80-bit extended precision floating point.The algorithm actually works with any fixed point or floating point number system.Note that x and a are not orthogonal if regarded as simultaneous samples. However, they are orthogonal as time interleaved samples, where the in-phase and quadrature DACs are alternately updated.The key to the amplitude stability of this algorithm is that the frequency is set by a single constant, so the fact that this constant is of finite precision has no impact. The only errors are those due to finite arithmetic, and those errors have no intrinsic bias to either increase or decrease the amplitude. The accumulated amplitude error has the appearance of a random walk, so the area of the annulus containing the orthogonalised equivalent to x and a (let's call these x and y) only increases with the square root of the number of steps. Therefore over time, the probability of the algorithm landing on an x, y pair that it has visited before increases. Once this happens there will obviously be no further increase in the peak to peak variation of the amplitude.Here is the maths (If I haven't fouled up the MathJax).Assuming an amplitude of 1, a starting phase of zero and an angular increment of \$\theta\$, at iteration n we have:$$x_n=sin(n \theta) \\y_n=cos(n \theta - \theta / 2)$$Using the standard expansion of \$cos(A+B)\$, \$y_n\$ can be written as$$y_n=cos(n \theta)cos(\theta/2 ) + sin(n \theta)sin(\theta/2)$$Similarly, \$y_{n+1}\$ can be written as:$$y_{n+1}=cos(n \theta + \theta / 2) \\y_{n+1}=cos(n \theta)cos(\theta/2 ) - sin(n \theta)sin(\theta/2)$$So \$y_{n+1}\$ can therefore be calculated from \$y_n\$, \$x_n\$ and the constant \$2sin(\theta/2) \$:$$y_{n+1}=y_n - 2 sin(\theta/2)x_n$$Using the standard expansion of \$sin(A+B) \$, \$x_n \$ can be written as:$$x_n=sin(n \theta)=sin(n \theta + \theta / 2 - \theta / 2) \\x_n=sin(n \theta + \theta / 2)cos(\theta / 2) - cos(n \theta + \theta / 2)sin(\theta / 2)$$Similarly, \$x_{n+1} \$ can be written as:$$x_{n+1}=sin(n \theta + \theta / 2 + \theta / 2) \\x_{n+1}=sin(n \theta + \theta / 2)cos(\theta / 2) + cos(n \theta + \theta / 2)sin(\theta / 2)$$So \$x_{n+1} \$ can therefore be calculated from \$x_n \$, \$y_{n+1} \$ and the constant \$ 2sin(\theta/2) \$:$$x_{n+1}=x_n + 2sin(\theta/2)y_{n+1}$$

author: cbutlera
author_rank: Regular Contributor
timestamp: June 07, 2022, 08:24:00 am
content: Quote from: radiolistener on June 07, 2022, 07:49:00 amWith reference to the more general algorithm.Code:p = 2 * sin(pi / n)x = 1;a = -p / 2;for (;;) {x += a * p;a -= x * p;}If a is initialised to 0 the initial value for x is the required amplitude divided by cos(pi/n). If a is initialised as above, then the initial value for x is just the amplitude.That set's the overall amplitude, but the precise initialisation value can then be adjusted a little by trial and error, to minimise the amplitude noise.The precise value also changes the cycle time of the algorithm before it repeats the same sequence of a/x pairs. In the case of the first example I posted, with a starting x of 2079038281, the cycle time is a little over 500 million samples.

author: emece67
author_rank: Frequent Contributor
timestamp: June 07, 2022, 09:20:17 am
content: .

author: cbutlera
author_rank: Regular Contributor
timestamp: June 07, 2022, 11:05:42 am
content: Quote from: emece67 on June 07, 2022, 09:20:17 amThanks for finding that mention of the algorithm. Do you know of a reference that investigates its stability?The algorithm itself is almost trivial. The interesting thing that I discovered back in 1980s was its long-term stability. At the time, I assumed that what I had discovered was well known. I'm still sure that I couldn't possibly have been the first to discover its long-term stability, but it would be nice to have a reference that dates from before the mid 1980s, so that I could know for sure.After reading this thread, I tried to search for a reference to it myself, and all that I could find was a possible link to a 1996 paper by John Edwardshttps://www.embedded.com/whats-your-sine-finding-the-right-algorithm-for-digital-frequency-synthesis-on-a-dsp/In the mid to late 1980s, John Edwards was a student on summer placement at the company where I was developing DSP code for voice-band modems. He was assisting me with DSP code and algorithm modelling, so I would almost certainly have discussed what I had found with him.EDIT:Since writing the above, I have just been in touch with John Edwardshttps://www.numerix-dsp.com/. It seems that the solution to the problem of error accumulation with recursive rotation that he proposed, and that was mentioned in the above link, is not the algorithm that I have been talking about.

author: emece67
author_rank: Frequent Contributor
timestamp: June 07, 2022, 01:46:39 pm
content: .

author: emece67
author_rank: Frequent Contributor
timestamp: June 07, 2022, 03:06:49 pm
content: .

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 07, 2022, 05:57:22 pm
content: Quote from: brucehoult on June 07, 2022, 07:13:48 amWhich in its given form, I also consider a particular case of CORDIC.

author: Someone
author_rank: Super Contributor
timestamp: June 07, 2022, 11:18:29 pm
content: Quote from: emece67 on June 07, 2022, 03:06:49 pmLonger sequencescanhave lower noise in any given bin, but its just processing gain:https://ccrma.stanford.edu/~jos/sasp/Processing_Gain.htmlThat is not a guarantee the noise will be lower just by having a longer sequence.The OP was trying to work out how to find the idealized rounding, there is not a direct solution. Rounding to integers usually introduces correlated noise, which does not disappear through processing gain.

author: Someone
author_rank: Super Contributor
timestamp: June 07, 2022, 11:43:52 pm
content: Quote from: SiliconWizard on June 07, 2022, 05:57:22 pmExpanding out the equation I didnt get the classical Toeplitz symmetry of a rotation matrix:cos(a) -sin(a)sin(a) cos(a)but instead, expanding the given code:Quote from: cbutlera on June 07, 2022, 08:24:00 amgives something decidedly not symmetric:1 p-p 1-p^2* sorry, cant be bothered to work out mathjax formatting!

author: radiolistener
author_rank: Super Contributor
timestamp: June 08, 2022, 10:14:35 am
content: despite the fact it has no harmonics, the noise (at noise floor) is very synthetic and contains a lot of a small spurs.Is it possible to add some dithering in order to get "soft" and flat noise floor?

author: Berni
author_rank: Super Contributor
timestamp: June 08, 2022, 11:15:42 am
content: Quote from: radiolistener on June 08, 2022, 10:14:35 am14bit only has has a dynamic range of 86dB so the noise floor being down there in the -125dB range means it is performing many times better than you wanted in the original post.

author: radiolistener
author_rank: Super Contributor
timestamp: June 08, 2022, 11:22:12 am
content: Quote from: Berni on June 08, 2022, 11:15:42 amActually it has -111 dBFS noise floor at FFT size 8192.So, there is process gain about 10*log10(8192/2)=36.12 dB and normalized SNR = 111-36 = 75 dBFS.The picture above shows RMS noise for a small piece of bandwidth = 2.7 kHz, while ADC sample rate is 96000 kHz, so it shows noise level with process gain 10*log10(48000/2.7) = 42.5 dB. And normalized SNR is about 126-42.5 = 83.5 dBFS.But I want to get rid of these spurs, to not confuse it with images from signal processing... its not an issue if the noise will be a little higher to hide these spurs.

author: Berni
author_rank: Super Contributor
timestamp: June 08, 2022, 11:44:17 am
content: You can sum in some random white noise to mask it using a larger flat noise floor.Still this is a good result on actual hardware. Getting any significantly better noise floors out of it likely involves narrow bandpass filters to select just the frequency you want.