+This is javascript drawing on an HTML5 canvas in real time.
+
+
+
+
+
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..92df917
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,16 @@
+MIT No Attribution
+
+Copyright 2003-2024 PEW's Corner
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this
+software and associated documentation files (the "Software"), to deal in the Software
+without restriction, including without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/LTE/active_RS.png b/LTE/active_RS.png
new file mode 100644
index 0000000..f8513fa
Binary files /dev/null and b/LTE/active_RS.png differ
diff --git a/LTE/dci_decoder.html b/LTE/dci_decoder.html
new file mode 100644
index 0000000..c24deee
--- /dev/null
+++ b/LTE/dci_decoder.html
@@ -0,0 +1,1688 @@
+
+
+
+
+
+
+
+
+LTE DCI Decoder
+
+
+
+
+
+
+
+
+
+
LTE DCI Decoder
+
(Supports all Rel-8 DCI formats)
+
+
WARNING: If this text is still visible when the page is fully loaded, scripts are disabled in your browser and this page will not work properly!
In this document formulas are derived for the Doppler shift in
+various cases.
+
+
+
+
Stationary Transmitter, Moving Receiver
+
+
Assume we have a medium in which waves are transmitted with a
+frequency of f0 and propagate with velocity vw relative
+to the medium. If the wave transmitter is at rest relative to the medium, the
+wavelength everywhere in the medium will be:
+
+
Equation 1:
+
+
+
+
If the receiver is moving with velocity vRx
+relative to the medium and in the direction of the transmitter, then the receiver's
+velocity relative to the waves will be:
+
+
Equation 2:
+
+
+
+
Thus, the received waves will have the apparent frequency:
+
+
Equation 3:
+
+
+
+
By substituting Equation 1 and Equation 2 into Equation 3, we obtain the following expression for the apparent frequency:
+
+
Equation 4:
+
+
+
+
This can be rewritten to express the relative Doppler shift:
+
+
Equation 5:
+
+
+
+
Keep in mind that the velocities vw and vRx
+are relative to the medium.
+
+
+
+
Moving Transmitter, Stationary Receiver
+
+
Now let's assume that the transmitter is moving with
+velocity vTx relative to the medium and towards the receiver, which
+is at rest relative to the medium. The wavelength in front of the transmitter
+will be reduced by the distance it travels during one wave period T0
+(=1/f0), so the wavelength becomes:
+
+
Equation 6:
+
+
+
+
The received waves will therefore have the apparent
+frequency:
+
+
Equation 7:
+
+
+
+
By substituting Equation 6 into Equation 7, we obtain the following expression for the apparent frequency:
+
+
Equation 8:
+
+
+
+
And the relative Doppler shift becomes:
+
+
Equation 9:
+
+
+
+
When vTx is small compared to vw, Equation
+8 and Equation 9 become similar to the stationary transmitter case (Equation 4 and Equation 5) as shown below:
+
+
Equation 10:
+
+
+
+
Equation 11:
+
+
+
+
+
+
Moving Transmitter, Moving Receiver
+
+
If both the transmitter and the receiver are moving relative
+to the medium and towards each other with the velocities vTx and vRx,
+respectively, relative to the medium, then the wavelength λ will be
+described by Equation 6, and the receiver's velocity v relative to the waves will be described by Equation 2. For the receiver the waves will therefore have the apparent frequency:
+
+
Equation 12:
+
+
+
+
and the relative Doppler shift:
+
+
Equation 13:
+
+
+
+
When vRx and vTx are small compared to vw,
+these equations can be simplified to:
+
+
Equation 14:
+
+
+
+
Equation 15:
+
+
+
+
Note that vTx and vRx should have the
+same sign when they are pointed in opposite directions. vw should always
+be positive.
+
+
+
+
Electromagnetic Waves
+
+
For electromagnetic waves it is not necessary to relate the
+motion of the transmitter, receiver, and waves to a medium. Only the velocities
+relative to the receiver are important. If the velocity vTx of the
+transmitter relative to the receiver (and towards it) is much smaller than the velocity
+vw of the waves (which is 299,792,458 m/s in a vacuum), we can reuse Equation 10
+and Equation 11, regardless of whether the receiver is moving or not:
+
+
Equation 16:
+
+
+
+
Equation 17:
+
+
+
+
Remember that in this case vTx is the transmitter's velocity relative to the
+receiver - not relative to the medium. Equation 16 and Equation 17 could of course also
+be obtained by setting vRx=0 in Equation 14 and Equation 15, respectively.
+
+
If the transmitter velocity vTx is not small compared to the electromagnetic
+wave velocity vw, relativistic effects (according to
+Einstein's Theory of Relativity)
+will be non-negligible and need to be taken into account. In this case,
+time dilation
+will reduce the transmitted frequency f0 in Equation 8 by the Lorentz factor γ
+when observed from the receiver's frame of reference (before accounting for the Doppler shift).
+The Lorentz factor is determined by the transmitter's velocity vTx relative to the
+receiver as follows:
+
+
Equation 18:
+
+
+
+
Replacing f0 in Equation 8 with the time dilated value f0/γ,
+we get the combined result of time dilation and Doppler shift:
+
+
Equation 19:
+
+
+
+
Equation 20:
+
+
+
+
(vTx is the transmitter's velocity relative to the receiver and towards the receiver,
+and vw is the electromagnetic wave velocity).
Note: This is Q amplitude / I amplitude, converted to dB.
+
+
Phase mismatch
+
°
+
+
Note: This is Q phase - (I phase - 90°).
+
+
Resulting unwanted signal
+
dB
+
+
Note: This is magnitude of unwanted spectral component (at frequency -f) relative to wanted component (at frequency f), in dB.
+
+
+
+
+
+
+
+
I
+
Q
+
+
+
+
+
+
+
f
+
mag(dB)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iq_mismatch_theory/doc.html b/iq_mismatch_theory/doc.html
new file mode 100644
index 0000000..04ac12f
--- /dev/null
+++ b/iq_mismatch_theory/doc.html
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+I/Q Signal Mismatch Theory
+
+
+
+
+
+
+
+
+
I/Q Signal Mismatch Theory
+
+
+
+
Introduction
+
+
This document describes the impact of amplitude and phase
+mismatch in an I/Q (in-phase/quadrature-phase) signal pair. The result of a mismatch is an
+unwanted spectral component at the negative signal frequency.
+
+
+
+
Theory
+
+
An ideal sinusoidal I/Q signal pair with no amplitude or
+phase mismatch can be represented by the following complex function:
+
+
Equation 1:
+
+
y(t) = A exp(i(ωt + φ)) =
+A cos(ωt + φ) + i A sin(ωt + φ)
+
+
The real part (the first term in the second expression) of
+y(t) is the I signal, and the imaginary part (the second term except the "i")
+is the Q signal. ω, A, and φ are the angular frequency, amplitude and
+phase, respectively.
+
+
If an amplitude and phase mismatch is introduced, the
+function becomes:
+
+
Equation 2:
+
+
y(t) = A cos(ωt + φ) +
+i α A sin(ωt + φ + ε)
+
+
Here, A and φ are the amplitude and phase of the I
+signal, α is the amplitude mismatch (the ratio of Q amplitude to I
+amplitude), and ε is the phase mismatch (the Q phase plus 90 degrees minus the I
+phase). For α=0 dB (α=1) and ε=0 degrees there is no mismatch.
+
+
By rewriting Equation 2 we can split the mismatched I/Q signal into its spectral components:
The first term is the wanted signal (including both I and Q
+parts) - a pure complex sinusoid at the frequency ω. The second term is an
+unwanted signal at the negative frequency -ω. The wanted signal is simply
+the ideal I/Q signal from Equation 1 multiplied by a complex constant, which modifies the amplitude and phase (here, the I and Q parts are modified equally).
+
+
From Equation 3 the ratio of the unwanted signal magnitude to the wanted signal magnitude can be found:
+
+
Equation 4:
+
+
+
+
+
+
Conclusion
+
+
It has been shown that the introduction of an amplitude
+and/or phase mismatch between the I and Q signals causes an unwanted spectral
+component to appear at the mirror frequency. Also, the amplitude and phase of
+the wanted spectral component will change. Equation 3 quantifies these effects.
+Pronounced like the first part of the "ay"-sound in "say", i.e. without the final "y"-sound.
+The "e" in "get" is also a fair approximation.
+The German "ä" and the Danish "æ" are better approximations.
+
+
+
ei/ee
+
+Pronounced as a Japanese "e" (see above), but twice as long in duration.
+Note that in hiragana text this long vowel often occurs when a syllable (e.g. "ke") is
+followed by an "i". In such cases the "i" is misleading and should not be pronounced as an "i" -
+it only serves to prolong the "e" sound.
+
+
+
i
+
+Pronounced like the "ee" in "feet", but short in duration.
+It is NOT pronounced like the "i" in "him".
+Often the "i" is not pronounced in the syllable "shi" when it is followed by another syllable.
+
+
+
o
+
+Pronounced like the first part of the "o"-sound in "old", i.e. without the final "u"-sound.
+The Danish "å" is also a good approximation.
+
+
+
ou/oo
+
+Pronounced as a Japanese "o" (see above), but twice as long in duration.
+Note that in hiragana text this long vowel often occurs when a syllable (e.g. "ko") is
+followed by a "u". In such cases the "u" is misleading and should not be pronounced as a "u" -
+it only serves to prolong the "o" sound.
+
+
+
g
+
+Pronounced like the "g" in "get" when it occurs at the beginning of words.
+Pronounced like the "ng" in "sing" in all other cases.
+Some people always pronounce it like the "g" in "get".
+
+
+
n
+
+Pronounced like "n" at the beginning of syllables,
+or when followed by any of the consonants ch, d, j, n, r, t, z, and sometimes s(h)
+(even when these consonants belong to the next word) -
+note that these consonants are all produced with the tip of the tongue (except s(h)).
+Pronounced like "m" when followed by m, b, or p (these consonants are all produced with the lips).
+Pronounced like the "ng" in "sing" in all other cases.
+
+
+
r
+
+Pronounced almost like the "rd" in "weirdo" (with an American-style "r").
+
+
+
z
+
+Pronounced like "dz" at the beginning of words.
+Pronounced like "z" in all other cases.
+
+
Octave defaults to same as for previous note (or rest) in same sequence (initially 4)
+
Relative duration: c1 c c/2 c c3/8 (whole, whole, half, half, 3/8)
+
Relative duration defaults to same as for previous note in same sequence (initially 1/4)
+
Absolute duration: c2500*1/4 c (2 quarter notes at 2500 ms per whole note)
+
Absolute duration defaults to same as for previous note in same sequence (initially 2000)
+
Rests: r1 r r/2 (whole, whole, and half rests)
+
Tracks: 1: c d e 2: g a h (track 1 plays c d e while track 2 plays g a h in parallel, default track is 0)
+
Tracks: 1: c d 2: g a h 1: e (same as above, except track 1 is defined in 2 parts)
+
Instruments: 1(2): c d (track 1 uses instrument 2, default instrument is 0)
+
Named sequences: hiho: g a 1: c1 1 hiho d (track 1 uses itself and hiho sequence and becomes: c1 c1 g/4 a/4 d1)
+
A reference to a named sequence is replaced by all notes in the sequence at the point of
+ reference, i.e. not including notes added to the sequence later
+
Volume: v0.5 c d v1 c d (notes c and d at half volume, then at full (auto-scaled) volume)
+
Comments: /* Multi-line comment */ c d // Single-line comment
+
+
+
+
diff --git a/page.js b/page.js
new file mode 100644
index 0000000..882cb90
--- /dev/null
+++ b/page.js
@@ -0,0 +1,550 @@
+"use strict";
+
+var genericPageSetup =
+{
+ debug: false,
+
+ script_version: '7',
+ style_version: undefined,
+
+ // A web page can add extra menu buttons to this array before the page load event
+ extra_menu_buttons: [],
+
+ // Page URL with and without the hash/query part
+ this_page_url: undefined,
+ this_page_noarg_url: undefined,
+
+ // Page header related variables
+ header_elem: undefined,
+ header_height: undefined,
+ has_scrolled: false,
+ last_scroll_pos: 0,
+ header_is_hidden: false,
+
+ // Page menu related variables
+ menu_group_elem: undefined,
+ menu_is_open: false,
+ pending_menu_action: undefined,
+ // Named history state values
+ menu_closed_history_state: null,
+ menu_open_history_state: 1,
+ // Menu button DOM elements and visibility states
+ update_pg_addr_menu_button_info:
+ {
+ id: 'update_pg_addr_button',
+ button_elem: undefined,
+ is_visible: false,
+ },
+ reset_pg_addr_menu_button_info:
+ {
+ id: 'reset_pg_addr_button',
+ button_elem: undefined,
+ is_visible: false,
+ },
+
+ // Relative URL path from the current page to the web site root (the location of this script is used as the root);
+ // includes a trailing '/' unless empty (meaning that the current page is in the root)
+ rel_path_to_root: (function pg_rel_path_to_root()
+ {
+ var scripts = document.getElementsByTagName('script');
+ var re = new RegExp('^(.*?[^/])(?:/(?!/)|$)(.*)');
+ var matches = re.exec(scripts[scripts.length - 1].src);
+ var this_script_url_parts = [matches[1]].concat(matches[2].split('/')).slice(0, -1);
+ matches = re.exec(location.href);
+ var this_page_url_parts = [matches[1]].concat(matches[2].split('/')).slice(0, -1);
+ var smallest_nbr_parts = Math.min(this_script_url_parts.length, this_page_url_parts.length);
+ for (var nbr_common_parts = 0;
+ nbr_common_parts < smallest_nbr_parts && this_script_url_parts[nbr_common_parts] == this_page_url_parts[nbr_common_parts];
+ nbr_common_parts++);
+ var nbr_backout_levels = (nbr_common_parts == 0 ? 0 : this_page_url_parts.length - nbr_common_parts);
+ var rel_path =
+ new Array(nbr_backout_levels + 1).join('../') +
+ this_script_url_parts.slice(nbr_common_parts).join('/');
+ if (rel_path.length > 0 && rel_path.slice(-1) != '/')
+ rel_path += '/';
+ return rel_path;
+ })(),
+
+ // Handles the page load event for the purpose of creating a page header and setting up related event handlers
+ init: function pg_init()
+ {
+ this.style_version = getComputedStyle(document.documentElement).getPropertyValue('--style-version').trim() || '?';
+ if (this.debug) console.log('Page loaded (script v' + this.script_version + ', style v' + this.style_version + ')');
+ this.refresh_page_info();
+ // Disable the browser's automatic restoring of the scroll position when navigating the history (sometimes it
+ // doesn't work, and sometimes it gets in the way) - we'll handle this ourselves
+ history.scrollRestoration = 'manual';
+ this.make_header();
+ var that = this;
+ window.addEventListener('scroll', function(){that.scroll_handler()}, false);
+ setInterval(function(){that.time_handler()}, 200);
+ var nav_event_handler = function(e){that.navigation_handler(e)};
+ // Register a handler for the pageshow event (comes after the load event - or without the load event - when
+ // navigating from a different page)
+ window.addEventListener('pageshow', nav_event_handler, false);
+ // Register a handler for the popstate event (comes when navigating from a different history entry for the same
+ // page - and for some older browsers it also comes after the pageshow event when navigating from a different
+ // page)
+ window.addEventListener('popstate', nav_event_handler, false);
+ },
+
+ refresh_page_info: function pg_refresh_page_info()
+ {
+ this.this_page_url = location.href;
+ this.this_page_noarg_url = this.this_page_url.split('#')[0].split('?')[0];
+ this.set_menu_button_visible(this.reset_pg_addr_menu_button_info,
+ this.this_page_url != this.this_page_noarg_url);
+ },
+
+ // Opens the page menu
+ open_menu: function pg_open_menu()
+ {
+ this.menu_is_open = true;
+ this.menu_group_elem.style.display = 'block';
+ if (this.debug) console.log('Adding and going to menu-open state');
+ // Create and go to the menu-open history state
+ history.pushState({state: this.menu_open_history_state, scroll: window.pageYOffset}, '');
+ },
+
+ // Closes the page menu if it is open, and ensures that all state reflects that it is closed
+ close_menu: function pg_close_menu()
+ {
+ this.menu_is_open = false;
+ this.menu_group_elem.style.display = 'none';
+ if (history.state.state == this.menu_open_history_state)
+ {
+ // We're in the menu-open history state, so go back to the menu-closed history state
+ if (this.debug) console.log('Going back');
+ history.back();
+ }
+ },
+
+ update_pg_addr_menu_button: [
+ 'update_pg_addr_button',
+ 'Update page address with setup',
+ 'genericPageSetup.update_page_addr_with_settings()'
+ ],
+
+ reset_pg_addr_menu_button: [
+ 'reset_pg_addr_button',
+ 'Reset page address',
+ 'genericPageSetup.reset_page_addr()'
+ ],
+
+ feedback_menu_button: [
+ 'feedback_button',
+ 'Feedback',
+ 'genericPageSetup.open_feedback_form()',
+ ],
+
+ // Performs the action for the 'Update page address with setup' menu button
+ update_page_addr_with_settings: function pg_update_page_addr_with_settings()
+ {
+ var url_with_settings = this.make_url_from_settings();
+ if (this.debug) console.log('update_pg_addr_with_settings: ' + url_with_settings);
+ history.replaceState(history.state, '', url_with_settings);
+ this.refresh_page_info();
+ },
+
+ // Performs the action for the 'Reset page address' menu button
+ reset_page_addr: function pg_reset_page_addr()
+ {
+ history.replaceState(history.state, '', this.this_page_noarg_url);
+ this.refresh_page_info();
+ },
+
+ // Performs the action for the 'Feedback' menu button
+ open_feedback_form: function pg_open_feedback_form()
+ {
+ var info = this.this_page_url + ' (modified ' + document.lastModified +
+ ', script v' + this.script_version +
+ ', style v' + this.style_version + ')\n';
+ window.open(this.rel_path_to_root + 'feedback.html#' + encodeURIComponent(info), '_blank');
+ },
+
+ // Makes the specified menu button visible or invisible as specified, or applies the previously set visibility if
+ // 'visible' is not true or false
+ set_menu_button_visible: function pg_set_menu_button_visible(button_info, visible)
+ {
+ if (this.debug) console.log('Requested visibility of ' + button_info.id + ': ' + visible);
+ if (visible === true || visible === false)
+ {
+ button_info.is_visible = visible;
+ }
+ if (button_info.button_elem)
+ {
+ if (this.debug) console.log('Setting visibility of ' + button_info.id + ' to ' + button_info.is_visible);
+ button_info.button_elem.style.display = button_info.is_visible ? 'block' : 'none';
+ }
+ },
+
+ // Handles click events on menu item buttons
+ menu_item_click_handler: function pg_menu_item_click_handler(event)
+ {
+ // Prepare the menu item action for execution; the sequence of events is:
+ // 1. The click event triggers this handler which prepares the action.
+ // 2. The same click event then triggers the click handler on the underlying menu_group_elem.
+ // 3. The menu_group_elem click handler closes the menu and goes back to the menu-closed history state.
+ // 4. This triggers the history popstate event which triggers the navigation_handler.
+ // 5. The navigation_handler executes the pending action prepared in step 1.
+ // This sequence ensures that the browser is back in the normal menu-closed state before the action is executed.
+ this.pending_menu_action = event.target.dataset.action;
+ },
+
+ // Handles key events when the menu is open, for the purpose of closing the menu when the Esc key is pressed
+ menu_key_down_handler: function pg_menu_key_down_handler(event)
+ {
+ // This handler is attached to the page body element and receives events in the capturing phase (not the usual
+ // bubbling phase which comes after the capturing phase), so it receives events before anything else on the page
+ if (this.menu_is_open && event.key == 'Escape')
+ {
+ if (this.debug) console.log('Esc pressed');
+ this.close_menu();
+ // Prevent the key event from reaching any other element on the page
+ event.stopPropagation();
+ }
+ },
+
+ // Handles navigation events for the purpose of closing the menu, executing a pending menu action if any, and
+ // generally avoiding the menu-open history state (which should never be entered on user navigation)
+ navigation_handler: function pg_navigation_handler(event)
+ {
+ // This navigation handler is called on every navigation event (pageshow or popstate) triggered by the user or
+ // by this script, but not on history.pushState which does not trigger any events
+ this.refresh_page_info();
+ if (history.state == null)
+ {
+ history.replaceState({state: this.menu_closed_history_state, scroll: window.pageYOffset}, '');
+ }
+ else if (history.state.state == this.menu_closed_history_state)
+ {
+ var that = this;
+ var old_scroll = history.state.scroll;
+ setTimeout(function(){that.scroll_and_hide_header(old_scroll)});
+ }
+ var pending_menu_action = this.pending_menu_action;
+ this.pending_menu_action = undefined;
+ if (this.debug) console.log('nav_hndlr from ' + event.type + ': (' + history.state.state + ', ' + history.state.scroll + ') ' +
+ pending_menu_action + ' ' + this.this_page_url);
+ // Ensure that the menu is closed in all cases where the navigation handler is called (the only case where we
+ // want the menu to stay open is when it is deliberately opened via history.pushState, and that will not trigger
+ // any call to the navigation handler)
+ this.close_menu();
+ if (pending_menu_action)
+ {
+ // There's a pending action from a menu item selected by the user, so execute that action now
+ setTimeout(function(){eval(pending_menu_action)}, 0);
+ }
+ },
+
+ // Handles scroll events for the purpose of showing or hiding the header depending on the scroll direction (most of
+ // this functionality is implemented in the timer_handler to avoid degrading the browser's scroll performance)
+ scroll_handler: function pg_scroll_handler()
+ {
+ this.has_scrolled = true;
+ },
+
+ // Handles regular timer ticks for the purpose of showing/hiding the header if scroll events have occurred since the
+ // last tick, and for storing the scroll position in the current history entry for later restoration when navigating
+ // back to this history entry
+ time_handler: function pg_time_handler()
+ {
+ if (this.has_scrolled)
+ {
+ this.has_scrolled = false;
+ var new_scroll_pos = window.pageYOffset;
+ history.replaceState({state: history.state.state, scroll: new_scroll_pos}, '');
+ if (!this.menu_is_open && !this.header_is_hidden &&
+ new_scroll_pos > this.last_scroll_pos && new_scroll_pos > this.header_height)
+ {
+ // The header is visible, the menu is closed, and the page has scrolled down beyond the height of the
+ // header, so hide the header
+ this.header_elem.className = 'hidden';
+ this.header_is_hidden = true;
+ } else if (this.header_is_hidden && new_scroll_pos <= this.last_scroll_pos)
+ {
+ // The header is hidden and the page has scrolled up (or "scrolled" to the same position, which will
+ // only happen when navigation to a page causes scroll_and_hide_header to "scroll" to the top and the
+ // scroll position is already at the top), so reveal the header
+ this.header_elem.className = '';
+ this.header_is_hidden = false;
+ }
+ this.last_scroll_pos = new_scroll_pos;
+ }
+ },
+
+ // Creates the page header and menu
+ make_header: function pg_make_header()
+ {
+ var body_elem = document.getElementsByTagName('body')[0];
+
+ var footer_elem = body_elem.appendChild(document.createElement('div'));
+ footer_elem.classList.add('footer');
+ footer_elem.innerHTML = 'Terms<\/a>';
+
+ this.header_elem = body_elem.appendChild(document.createElement('header'));
+
+ var hs = '