├── icon-16.png
├── icon-48.png
├── icon-128.png
├── .gitmodules
├── common.css
├── manifest.json
├── audio.html
├── spectrum.html
├── main.js
├── README.md
├── js
├── Detector.js
├── stats.min.js
└── TrackballControls.js
├── Lut.js
├── audio.js
├── spectrum.js
├── rtl2832u.js
├── radiocontroller.js
├── r820t.js
└── rtlcom.js
/icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttrftech/threejs-spectrum/HEAD/icon-16.png
--------------------------------------------------------------------------------
/icon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttrftech/threejs-spectrum/HEAD/icon-48.png
--------------------------------------------------------------------------------
/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttrftech/threejs-spectrum/HEAD/icon-128.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "jsfft"]
2 | path = jsfft
3 | url = https://github.com/dntj/jsfft.git
4 |
--------------------------------------------------------------------------------
/common.css:
--------------------------------------------------------------------------------
1 | canvas {
2 | margin:10px auto;
3 | display:block;
4 | border:1px solid black;
5 | }
6 |
7 | body {
8 | color: #cccccc;
9 | font-family:Monospace;
10 | font-size:13px;
11 | text-align:center;
12 |
13 | background-color: #050505;
14 | margin: 0px;
15 | overflow: hidden;
16 | }
17 |
18 | #info {
19 | position: absolute;
20 | top: 0px; width: 100%;
21 | padding: 5px;
22 | }
23 |
24 | a {
25 | color: #0080ff;
26 | }
27 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Radio Spectrum",
3 | "description": "Display radio spectrum on your browser or ChromeBook using an RTL2832U-based USB digital TV tuner.",
4 | "version": "0.0",
5 | "icons": {
6 | "16": "icon-16.png",
7 | "48": "icon-48.png",
8 | "128": "icon-128.png"
9 | },
10 | "app": {
11 | "background": {
12 | "scripts": ["main.js"]
13 | }
14 | },
15 | "permissions": [
16 | "usb"
17 | ],
18 | "optional_permissions": [
19 | {
20 | "usbDevices": [
21 | {
22 | "vendorId": 3034,
23 | "productId": 10290
24 | },
25 | {
26 | "vendorId": 3034,
27 | "productId": 10296
28 | }
29 | ]
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/audio.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | WebAudio and three.js spectrum flow
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/spectrum.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Radio Spectrum Display
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | // Copyright 2013 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | var usedConnections = [];
16 |
17 | chrome.app.runtime.onLaunched.addListener(function() {
18 | chrome.app.window.create('spectrum.html', {
19 | 'id': 'radioSpectrum',
20 | 'bounds': {
21 | 'width': 800,
22 | 'height': 800
23 | },
24 | 'resizable': true
25 | });
26 | });
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Spectrum Display using three.js
2 | ===============================
3 |
4 | This project contains real time 3D Spectrum display demo using three.js.
5 |
6 | Two options of signal source, audio and radio.
7 |
8 | ## Audio Spectrum Display
9 |
10 | ### Requirement
11 |
12 | - Browser supporting WebGL (tested with Chrome)
13 |
14 | ### Run
15 |
16 | Just open following link [https://ttrftech.github.io/threejs-spectrum/audio.html](https://ttrftech.github.io/threejs-spectrum/audio.html). If permission to access microphone required, click allow button.
17 |
18 | ===
19 |
20 | ## Radio Spectrum Display
21 |
22 | ### Requirement
23 |
24 | - RTL2832U USB Dongle
25 | - Chrome Web Browser
26 |
27 | ### Setup
28 |
29 | $ git clone https://github.com/ttrftech/threejs-spectrum.git
30 | $ cd threejs-spectrum
31 | $ git submodule update --init
32 |
33 | ### Run
34 |
35 | On Chrome Web Browser,
36 |
37 | 1. Open Window>Extensions menu,
38 | 2. Check developer mode,
39 | 3. Click "Load unpacked extension..." button,
40 | 4. Select threejs-spectrum folder on file dialog,
41 | 5. Click "Launch" link in Radio Spectrum extension item.
42 | 6. Extension window will appear, then click Start button.
43 | 7. To change frequency, hit Tab key twice to move focus and enter frequency in MHz.
44 |
45 | ===
46 |
47 | ## Acknowledgment
48 |
49 | - Chrome Radio Receiver Extension [https://github.com/google/radioreceiver](https://github.com/google/radioreceiver)
50 | - three.js [http://threejs.org/](http://threejs.org/) [https://github.com/mrdoob/three.js/](https://github.com/mrdoob/three.js/)
51 | - jsfft [https://github.com/dntj/jsfft](https://github.com/dntj/jsfft)
52 |
--------------------------------------------------------------------------------
/js/Detector.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author alteredq / http://alteredqualia.com/
3 | * @author mr.doob / http://mrdoob.com/
4 | */
5 |
6 | var Detector = {
7 |
8 | canvas: !! window.CanvasRenderingContext2D,
9 | webgl: ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(),
10 | workers: !! window.Worker,
11 | fileapi: window.File && window.FileReader && window.FileList && window.Blob,
12 |
13 | getWebGLErrorMessage: function () {
14 |
15 | var element = document.createElement( 'div' );
16 | element.id = 'webgl-error-message';
17 | element.style.fontFamily = 'monospace';
18 | element.style.fontSize = '13px';
19 | element.style.fontWeight = 'normal';
20 | element.style.textAlign = 'center';
21 | element.style.background = '#fff';
22 | element.style.color = '#000';
23 | element.style.padding = '1.5em';
24 | element.style.width = '400px';
25 | element.style.margin = '5em auto 0';
26 |
27 | if ( ! this.webgl ) {
28 |
29 | element.innerHTML = window.WebGLRenderingContext ? [
30 | 'Your graphics card does not seem to support WebGL.
',
31 | 'Find out how to get it here.'
32 | ].join( '\n' ) : [
33 | 'Your browser does not seem to support WebGL.
',
34 | 'Find out how to get it here.'
35 | ].join( '\n' );
36 |
37 | }
38 |
39 | return element;
40 |
41 | },
42 |
43 | addGetWebGLMessage: function ( parameters ) {
44 |
45 | var parent, id, element;
46 |
47 | parameters = parameters || {};
48 |
49 | parent = parameters.parent !== undefined ? parameters.parent : document.body;
50 | id = parameters.id !== undefined ? parameters.id : 'oldie';
51 |
52 | element = Detector.getWebGLErrorMessage();
53 | element.id = id;
54 |
55 | parent.appendChild( element );
56 |
57 | }
58 |
59 | };
60 |
--------------------------------------------------------------------------------
/js/stats.min.js:
--------------------------------------------------------------------------------
1 | // stats.js - http://github.com/mrdoob/stats.js
2 | var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
3 | i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
4 | k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
5 | "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:11,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
6 | a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};
7 |
--------------------------------------------------------------------------------
/Lut.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author daron1337 / http://daron1337.github.io/
3 | */
4 |
5 | THREE.Lut = function ( colormap, numberofcolors ) {
6 |
7 | this.lut = new Array();
8 | this.map = THREE.ColorMapKeywords[ colormap ];
9 | this.n = numberofcolors;
10 |
11 | var step = 1. / this.n;
12 |
13 | for ( var i = 0; i <= 1; i+=step ) {
14 |
15 | for ( var j = 0; j < this.map.length - 1; j++ ) {
16 |
17 | if ( i >= this.map[ j ][ 0 ] && i < this.map[ j+1 ][ 0 ] ) {
18 |
19 | var min = this.map[ j ][ 0 ];
20 | var max = this.map[ j+1 ][ 0 ];
21 | var color = new THREE.Color( 0xffffff );
22 | var minColor = new THREE.Color( 0xffffff ).setHex( this.map[ j ][ 1 ] );
23 | var maxColor = new THREE.Color( 0xffffff ).setHex( this.map[ j+1 ][ 1 ] );
24 |
25 | color = minColor.lerp( maxColor, ( i - min ) / ( max - min ) );
26 |
27 | this.lut.push(color);
28 |
29 | }
30 |
31 | }
32 |
33 | }
34 |
35 | return this.set( this );
36 |
37 | };
38 |
39 | THREE.Lut.prototype = {
40 |
41 | constructor: THREE.Lut,
42 |
43 | lut: [], map: [], mapname: 'rainbow' , n: 256, minV: 0, maxV: 1,
44 |
45 | set: function ( value ) {
46 |
47 | if ( value instanceof THREE.Lut ) {
48 |
49 | this.copy( value );
50 |
51 | }
52 |
53 | return this;
54 |
55 | },
56 |
57 | setMin: function ( min ) {
58 |
59 | this.minV = min;
60 |
61 | return this;
62 |
63 | },
64 |
65 | setMax: function ( max ) {
66 |
67 | this.maxV = max;
68 |
69 | return this;
70 |
71 | },
72 |
73 | changeNumberOfColors: function ( numberofcolors ) {
74 |
75 | this.n = numberofcolors;
76 |
77 | return new THREE.Lut( this.mapname, this.n );
78 |
79 | },
80 |
81 | changeColorMap: function ( colormap ) {
82 |
83 | this.mapname = colormap;
84 |
85 | return new THREE.Lut( this.mapname, this.n );
86 |
87 | },
88 |
89 | copy: function ( lut ) {
90 |
91 | this.lut = lut.lut;
92 | this.mapname = lut.mapname;
93 | this.map = lut.map;
94 | this.n = lut.n;
95 | this.minV = lut.minV;
96 | this.maxV = lut.maxV;
97 |
98 | return this;
99 |
100 | },
101 |
102 | getColor: function ( alpha ) {
103 |
104 |
105 | if ( alpha <= this.minV ) {
106 |
107 | alpha = this.minV;
108 |
109 | }
110 |
111 | else if ( alpha >= this.maxV ) {
112 |
113 | alpha = this.maxV;
114 |
115 | }
116 |
117 |
118 | alpha = ( alpha - this.minV ) / ( this.maxV - this.minV );
119 |
120 | var colorPosition = Math.round ( alpha * this.n );
121 | colorPosition == this.n ? colorPosition -= 1 : colorPosition;
122 |
123 | return this.lut[ colorPosition ];
124 |
125 | },
126 |
127 | addColorMap: function ( colormapName, arrayOfColors ) {
128 |
129 | THREE.ColorMapKeywords[ colormapName ] = arrayOfColors;
130 |
131 | },
132 |
133 | };
134 |
135 | THREE.ColorMapKeywords = {
136 |
137 | "rainbow": [ [ 0.0, '0x0000FF' ], [ 0.2, '0x00FFFF' ], [ 0.5, '0x00FF00' ], [ 0.8, '0xFFFF00'], [1.0, '0xFF0000' ] ],
138 | "cooltowarm": [ [ 0.0, '0x3C4EC2' ], [ 0.2, '0x9BBCFF' ], [ 0.5, '0xDCDCDC' ], [ 0.8, '0xF6A385'], [1.0, '0xB40426' ] ],
139 | "blackbody" : [ [ 0.0, '0x000000' ], [ 0.2, '0x780000' ], [ 0.5, '0xE63200' ], [ 0.8, '0xFFFF00'], [1.0, '0xFFFFFF' ] ],
140 | "picm" : [ [ 0.0, '0x000000' ], [ 0.10, '0xff00ff' ], [ 0.2, '0x0000ff' ], [ 0.4, '0x00FFFF'], [0.65, '0x00FF00' ], [0.8, '0xFF0000' ], [0.95, '0xFFFF00' ], [1.0, '0xFFFFFF' ]]
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/audio.js:
--------------------------------------------------------------------------------
1 | navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
2 | window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
3 | window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
4 |
5 | if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
6 |
7 | function initialize() {
8 | var container, stats;
9 | var camera, scene, renderer, controls;
10 | var mesh;
11 | var particleSystem;
12 |
13 | var audioElement = document.getElementById("audio");
14 | var container = document.getElementById('container');
15 |
16 | var lut = new THREE.Lut( "picm", 512 );
17 | //var lut = new THREE.Lut( "blackbody", 512 );
18 | //var lut = new THREE.Lut( "cooltowarm", 512 );
19 | //var lut = new THREE.Lut( "rainbow", 512 );
20 | lut.setMax(100);
21 |
22 | function render() {
23 | //var time = Date.now() * 0.001;
24 | //particleSystem.rotation.x = time * 0.25;
25 | //particleSystem.rotation.y = time * 0.5;
26 | renderer.render( scene, camera );
27 | stats.update();
28 | }
29 |
30 | function onWindowResize() {
31 | camera.aspect = window.innerWidth / window.innerHeight;
32 | camera.updateProjectionMatrix();
33 | renderer.setSize( window.innerWidth, window.innerHeight );
34 | controls.handleResize();
35 | render();
36 | }
37 |
38 | function createPlot(frequencyData) {
39 | var z = 500.0;
40 | var l = frequencyData.length;
41 |
42 | var positions = new THREE.Float32Attribute(l, 3);
43 | var colors = new THREE.Float32Attribute(l, 3);
44 | for (var i = 0; i < l; i++) {
45 | var x = i - l/2;
46 | var y = frequencyData[i];
47 | positions.setXYZ(i, x, y, z);
48 | var color = lut.getColor(y);
49 | colors.setXYZ(i, color.r, color.g, color.b);
50 | }
51 | var geometry = new THREE.BufferGeometry();
52 | var material = new THREE.LineBasicMaterial({ vertexColors: true, transparent: true, opacity: 0.7 });
53 | geometry.addAttribute('position', positions);
54 | geometry.addAttribute('color', colors);
55 | geometry.computeBoundingSphere();
56 | return new THREE.Line(geometry, material);
57 | }
58 |
59 | camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 5, 100000 );
60 | camera.position.z = 2000;
61 | camera.position.y = 500;
62 |
63 | controls = new THREE.TrackballControls( camera );
64 | controls.staticMoving = true;
65 | controls.dynamicDampingFactor = 0.3;
66 | controls.keys = [ 65, 83, 68 ];
67 | controls.addEventListener( 'change', render );
68 |
69 | renderer = new THREE.WebGLRenderer( { antialias: false, clearColor: 0x333333, clearAlpha: 1, alpha: false } );
70 | renderer.setSize( window.innerWidth, window.innerHeight );
71 |
72 | container.appendChild( renderer.domElement );
73 |
74 | stats = new Stats();
75 | stats.domElement.style.position = 'absolute';
76 | stats.domElement.style.top = '0px';
77 | container.appendChild( stats.domElement );
78 |
79 | window.addEventListener( 'resize', onWindowResize, false );
80 |
81 | navigator.getUserMedia(
82 | {audio : true},
83 | function(stream) {
84 | var url = URL.createObjectURL(stream);
85 | audioElement.src = url;
86 | var audioContext = new AudioContext();
87 | var mediastreamsource = audioContext.createMediaStreamSource(stream);
88 | var analyser = audioContext.createAnalyser();
89 | var frequencyData = new Uint8Array(analyser.frequencyBinCount);
90 | //var timeDomainData = new Uint8Array(analyser.frequencyBinCount);
91 | mediastreamsource.connect(analyser);
92 |
93 | var animation = function(){
94 | analyser.getByteFrequencyData(frequencyData);
95 | //analyser.getByteTimeDomainData(timeDomainData);
96 |
97 | //scene = new THREE.Scene();
98 | var i;
99 | for (i = 0; i < scene.children.length - 200; i++) {
100 | scene.remove(scene.children[i]);
101 | }
102 | for (; i < scene.children.length; i++) {
103 | scene.children[i].translateZ(-5);
104 | }
105 | particleSystem = createPlot(frequencyData);
106 | scene.add( particleSystem );
107 | render();
108 | controls.update();
109 |
110 | requestAnimationFrame(animation);
111 | };
112 |
113 | scene = new THREE.Scene();
114 | //scene.fog = new THREE.Fog( 0x000000, 3000, 5000);
115 | scene.translateZ(-500);
116 | animation();
117 | },
118 | function(e) {
119 | console.log(e);
120 | }
121 | );
122 | }
123 |
124 | window.addEventListener("load", initialize, false);
125 |
--------------------------------------------------------------------------------
/spectrum.js:
--------------------------------------------------------------------------------
1 |
2 | if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
3 |
4 | function initialize() {
5 | var container, stats;
6 | var camera, scene, renderer, controls;
7 | var mesh;
8 | var particleSystem;
9 |
10 | var audioElement = document.getElementById("audio");
11 | var container = document.getElementById('container');
12 | var radio = new RadioController();
13 |
14 | var lut = new THREE.Lut( "picm", 512 );
15 | //var lut = new THREE.Lut( "blackbody", 512 );
16 | //var lut = new THREE.Lut( "cooltowarm", 512 );
17 | //var lut = new THREE.Lut( "rainbow", 512 );
18 | lut.setMax(100);
19 |
20 | function render() {
21 | //var time = Date.now() * 0.001;
22 | //particleSystem.rotation.x = time * 0.25;
23 | //particleSystem.rotation.y = time * 0.5;
24 | renderer.render( scene, camera );
25 | stats.update();
26 | }
27 |
28 | function onWindowResize() {
29 | camera.aspect = window.innerWidth / window.innerHeight;
30 | camera.updateProjectionMatrix();
31 | renderer.setSize( window.innerWidth, window.innerHeight );
32 | controls.handleResize();
33 | render();
34 | }
35 |
36 | function createParticleSystem(data) {
37 | var z = 500.0;
38 | var pts = [];
39 | var l = data.length;
40 | if (l > 4096)
41 | l = 4096;
42 |
43 |
44 |
45 | var positions = new THREE.Float32Attribute(l, 3);
46 | var colors = new THREE.Float32Attribute(l, 3);
47 | for (var i = 0; i < l/2; i++) {
48 | var x = i - l/2;
49 | var y = data[i + l/2];
50 | positions.setXYZ(i, x, y, z);
51 | var color = lut.getColor(y);
52 | colors.setXYZ(i, color.r, color.g, color.b);
53 | }
54 | for ( ; i < l; i++) {
55 | var x = i - l/2;
56 | var y = data[i - l/2];
57 | positions.setXYZ(i, x, y, z);
58 | var color = lut.getColor(y);
59 | colors.setXYZ(i, color.r, color.g, color.b);
60 | }
61 | var geometry = new THREE.BufferGeometry();
62 | var material = new THREE.LineBasicMaterial({ vertexColors: true, transparent: true, opacity: 0.7 });
63 | geometry.addAttribute('position', positions);
64 | geometry.addAttribute('color', colors);
65 | geometry.computeBoundingSphere();
66 | return new THREE.Line(geometry, material);
67 | }
68 |
69 | function fft(samples) {
70 | var data = new complex_array.ComplexArray(4096);
71 | data.map(function(value, i, n) {
72 | value.real = samples[i * 2 + 1] - 128;
73 | value.imag = samples[i * 2] - 128;
74 | //console.log(samples[i * 2]);
75 | });
76 | data.FFT();
77 | return data.magnitude();
78 | }
79 |
80 | camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 5, 100000 );
81 | camera.position.z = 2000;
82 | camera.position.y = 500;
83 |
84 | controls = new THREE.TrackballControls( camera );
85 | controls.staticMoving = true;
86 | controls.dynamicDampingFactor = 0.3;
87 | controls.keys = [ 65, 83, 68 ];
88 | controls.addEventListener( 'change', render );
89 |
90 | renderer = new THREE.WebGLRenderer( { antialias: false, clearColor: 0x333333, clearAlpha: 1, alpha: false } );
91 | renderer.setSize( window.innerWidth, window.innerHeight );
92 |
93 | container.appendChild( renderer.domElement );
94 |
95 | stats = new Stats();
96 | stats.domElement.style.position = 'absolute';
97 | stats.domElement.style.top = '0px';
98 | container.appendChild( stats.domElement );
99 |
100 | window.addEventListener( 'resize', onWindowResize, false );
101 |
102 | var animation = function() {
103 | samples = radio.getSamples();
104 | if (radio.isPlaying() && samples) {
105 | //console.log(samples.length);
106 | var i;
107 | for (i = 0; i < scene.children.length - 400; i++) {
108 | scene.remove(scene.children[i]);
109 | }
110 | for (; i < scene.children.length; i++) {
111 | scene.children[i].translateZ(-5);
112 | }
113 |
114 | data = fft(new Uint8Array(samples));
115 | //console.log(data);
116 | particleSystem = createParticleSystem(data);
117 | scene.add( particleSystem );
118 | }
119 | render();
120 | controls.update();
121 |
122 | requestAnimationFrame(animation);
123 | };
124 |
125 | scene = new THREE.Scene();
126 | //scene.fog = new THREE.Fog( 0x000000, 3000, 5000);
127 | scene.translateZ(-500);
128 |
129 | function start() {
130 | radio.start(function() {
131 | animation();
132 | });
133 | }
134 | function stop() {
135 | radio.stop();
136 | }
137 | function changeFrequency() {
138 | radio.setFrequency(parseFloat(frequencyField.value) * 1e6);
139 | }
140 | function updateFrequency() {
141 | frequencyField.value = radio.getFrequency() / 1e6;
142 | }
143 |
144 | startButton.addEventListener('click', start);
145 | stopButton.addEventListener('click', stop);
146 | frequencyField.addEventListener('focus', updateFrequency);
147 | frequencyField.addEventListener('change', changeFrequency);
148 | }
149 |
150 | window.addEventListener("load", initialize, false);
151 |
--------------------------------------------------------------------------------
/rtl2832u.js:
--------------------------------------------------------------------------------
1 | // Copyright 2013 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /**
16 | * Operations on the RTL2832U demodulator.
17 | * @param {ConnectionHandle} conn The USB connection handle.
18 | * @param {number} ppm The frequency correction factor, in parts per million.
19 | * @param {number=} opt_gain The optional gain in dB. If unspecified or null, sets auto gain.
20 | * @constructor
21 | */
22 | function RTL2832U(conn, ppm, opt_gain) {
23 |
24 | /**
25 | * Frequency of the oscillator crystal.
26 | */
27 | var XTAL_FREQ = 28800000;
28 |
29 | /**
30 | * Tuner intermediate frequency.
31 | */
32 | var IF_FREQ = 3570000;
33 |
34 | /**
35 | * The number of bytes for each sample.
36 | */
37 | var BYTES_PER_SAMPLE = 2;
38 |
39 | /**
40 | * Communications with the demodulator via USB.
41 | */
42 | var com = new RtlCom(conn);
43 |
44 | /**
45 | * A handler for errors. It's a function that receives the error message.
46 | */
47 | var errorHandler;
48 |
49 | /**
50 | * The tuner used by the dongle.
51 | */
52 | var tuner;
53 |
54 | /**
55 | * Initialize the demodulator.
56 | * @param {Function} kont The continuation for this function.
57 | */
58 | function open(kont) {
59 | com.writeEach([
60 | [CMD.REG, BLOCK.USB, REG.SYSCTL, 0x09, 1],
61 | [CMD.REG, BLOCK.USB, REG.EPA_MAXPKT, 0x0200, 2],
62 | [CMD.REG, BLOCK.USB, REG.EPA_CTL, 0x0210, 2]
63 | ], function() {
64 | com.iface.claim(function() {
65 | com.writeEach([
66 | [CMD.REG, BLOCK.SYS, REG.DEMOD_CTL_1, 0x22, 1],
67 | [CMD.REG, BLOCK.SYS, REG.DEMOD_CTL, 0xe8, 1],
68 | [CMD.DEMODREG, 1, 0x01, 0x14, 1],
69 | [CMD.DEMODREG, 1, 0x01, 0x10, 1],
70 | [CMD.DEMODREG, 1, 0x15, 0x00, 1],
71 | [CMD.DEMODREG, 1, 0x16, 0x0000, 2],
72 | [CMD.DEMODREG, 1, 0x16, 0x00, 1],
73 | [CMD.DEMODREG, 1, 0x17, 0x00, 1],
74 | [CMD.DEMODREG, 1, 0x18, 0x00, 1],
75 | [CMD.DEMODREG, 1, 0x19, 0x00, 1],
76 | [CMD.DEMODREG, 1, 0x1a, 0x00, 1],
77 | [CMD.DEMODREG, 1, 0x1b, 0x00, 1],
78 | [CMD.DEMODREG, 1, 0x1c, 0xca, 1],
79 | [CMD.DEMODREG, 1, 0x1d, 0xdc, 1],
80 | [CMD.DEMODREG, 1, 0x1e, 0xd7, 1],
81 | [CMD.DEMODREG, 1, 0x1f, 0xd8, 1],
82 | [CMD.DEMODREG, 1, 0x20, 0xe0, 1],
83 | [CMD.DEMODREG, 1, 0x21, 0xf2, 1],
84 | [CMD.DEMODREG, 1, 0x22, 0x0e, 1],
85 | [CMD.DEMODREG, 1, 0x23, 0x35, 1],
86 | [CMD.DEMODREG, 1, 0x24, 0x06, 1],
87 | [CMD.DEMODREG, 1, 0x25, 0x50, 1],
88 | [CMD.DEMODREG, 1, 0x26, 0x9c, 1],
89 | [CMD.DEMODREG, 1, 0x27, 0x0d, 1],
90 | [CMD.DEMODREG, 1, 0x28, 0x71, 1],
91 | [CMD.DEMODREG, 1, 0x29, 0x11, 1],
92 | [CMD.DEMODREG, 1, 0x2a, 0x14, 1],
93 | [CMD.DEMODREG, 1, 0x2b, 0x71, 1],
94 | [CMD.DEMODREG, 1, 0x2c, 0x74, 1],
95 | [CMD.DEMODREG, 1, 0x2d, 0x19, 1],
96 | [CMD.DEMODREG, 1, 0x2e, 0x41, 1],
97 | [CMD.DEMODREG, 1, 0x2f, 0xa5, 1],
98 | [CMD.DEMODREG, 0, 0x19, 0x05, 1],
99 | [CMD.DEMODREG, 1, 0x93, 0xf0, 1],
100 | [CMD.DEMODREG, 1, 0x94, 0x0f, 1],
101 | [CMD.DEMODREG, 1, 0x11, 0x00, 1],
102 | [CMD.DEMODREG, 1, 0x04, 0x00, 1],
103 | [CMD.DEMODREG, 0, 0x61, 0x60, 1],
104 | [CMD.DEMODREG, 0, 0x06, 0x80, 1],
105 | [CMD.DEMODREG, 1, 0xb1, 0x1b, 1],
106 | [CMD.DEMODREG, 0, 0x0d, 0x83, 1]
107 | ], function() {
108 |
109 | var xtalFreq = Math.floor(XTAL_FREQ * (1 + ppm / 1000000));
110 | com.i2c.open(function() {
111 | R820T.check(com, function(found) {
112 | if (found) {
113 | tuner = new R820T(com, xtalFreq, throwError);
114 | }
115 | if (!tuner) {
116 | throwError('Sorry, your USB dongle has an unsupported tuner chip. ' +
117 | 'Only the R820T chip is supported.');
118 | return;
119 | }
120 | var multiplier = -1 * Math.floor(IF_FREQ * (1<<22) / xtalFreq);
121 | com.writeEach([
122 | [CMD.DEMODREG, 1, 0xb1, 0x1a, 1],
123 | [CMD.DEMODREG, 0, 0x08, 0x4d, 1],
124 | [CMD.DEMODREG, 1, 0x19, (multiplier >> 16) & 0x3f, 1],
125 | [CMD.DEMODREG, 1, 0x1a, (multiplier >> 8) & 0xff, 1],
126 | [CMD.DEMODREG, 1, 0x1b, multiplier & 0xff, 1],
127 | [CMD.DEMODREG, 1, 0x15, 0x01, 1]
128 | ], function() {
129 | tuner.init(function() {
130 | setGain(opt_gain, function() {
131 | com.i2c.close(kont);
132 | })})})})})})})});
133 | }
134 |
135 | /**
136 | * Sets the requested gain.
137 | * @param {number|null|undefined} gain The gain in dB, or null/undefined
138 | * for automatic gain.
139 | * @param {Function} kont The continuation for this function.
140 | */
141 | function setGain(gain, kont) {
142 | if (gain == null) {
143 | tuner.setAutoGain(kont);
144 | } else {
145 | tuner.setManualGain(gain, kont);
146 | }
147 | }
148 |
149 | /**
150 | * Set the sample rate.
151 | * @param {number} rate The sample rate, in samples/sec.
152 | * @param {Function} kont The continuation for this function. Receives the
153 | * sample rate that was actually set as its first parameter.
154 | */
155 | function setSampleRate(rate, kont) {
156 | var ratio = Math.floor(XTAL_FREQ * (1 << 22) / rate);
157 | ratio &= 0x0ffffffc;
158 | var realRate = Math.floor(XTAL_FREQ * (1 << 22) / ratio);
159 | var ppmOffset = -1 * Math.floor(ppm * (1 << 24) / 1000000);
160 | com.writeEach([
161 | [CMD.DEMODREG, 1, 0x9f, (ratio >> 16) & 0xffff, 2],
162 | [CMD.DEMODREG, 1, 0xa1, ratio & 0xffff, 2],
163 | [CMD.DEMODREG, 1, 0x3e, (ppmOffset >> 8) & 0x3f, 1],
164 | [CMD.DEMODREG, 1, 0x3f, ppmOffset & 0xff, 1]
165 | ], function() {
166 | resetDemodulator(function() {
167 | kont(realRate);
168 | })});
169 | }
170 |
171 | /**
172 | * Resets the demodulator.
173 | * @param {Function} kont The continuation for this function.
174 | */
175 | function resetDemodulator(kont) {
176 | com.writeEach([
177 | [CMD.DEMODREG, 1, 0x01, 0x14, 1],
178 | [CMD.DEMODREG, 1, 0x01, 0x10, 1]
179 | ], kont);
180 | }
181 |
182 | /**
183 | * Tunes the device to the given frequency.
184 | * @param {number} freq The frequency to tune to, in Hertz.
185 | * @param {Function} kont The continuation for this function.
186 | */
187 | function setCenterFrequency(freq, kont) {
188 | com.i2c.open(function() {
189 | tuner.setFrequency(freq + IF_FREQ, function() {
190 | com.i2c.close(kont);
191 | })});
192 | }
193 |
194 | /**
195 | * Resets the sample buffer. Call this before starting to read samples.
196 | * @param {Function} kont The continuation for this function.
197 | */
198 | function resetBuffer(kont) {
199 | com.writeEach([
200 | [CMD.REG, BLOCK.USB, REG.EPA_CTL, 0x0210, 2],
201 | [CMD.REG, BLOCK.USB, REG.EPA_CTL, 0x0000, 2]
202 | ], kont);
203 | }
204 |
205 | /**
206 | * Reads a block of samples off the device.
207 | * @param {number} length The number of samples to read.
208 | * @param {Function} kont The continuation for this function. It will receive
209 | * as its argument an ArrayBuffer containing the read samples, which you
210 | * can interpret as pairs of unsigned 8-bit integers; the first one is
211 | * the sample's I value, and the second one is its Q value.
212 | */
213 | function readSamples(length, kont) {
214 | com.bulk.readBuffer(length * BYTES_PER_SAMPLE, kont);
215 | }
216 |
217 | /**
218 | * Stops the demodulator.
219 | * @param {Function} kont The continuation for this function.
220 | */
221 | function close(kont) {
222 | com.i2c.open(function() {
223 | tuner.close(function() {
224 | com.i2c.close(function() {
225 | com.iface.release(kont);
226 | })})});
227 | }
228 |
229 | /**
230 | * Handles an error.
231 | * @param {string} msg The error message.
232 | */
233 | function throwError(msg) {
234 | if (errorHandler) {
235 | errorHandler(msg);
236 | } else {
237 | throw msg;
238 | }
239 | }
240 |
241 | /**
242 | * Sets a function to call if there's an error communicating with the
243 | * demodulator.
244 | * @param {Function} func The function to call on error.
245 | */
246 | function setOnError(func) {
247 | errorHandler = func;
248 | com.setOnError(throwError);
249 | }
250 |
251 | return {
252 | open: open,
253 | setSampleRate: setSampleRate,
254 | setCenterFrequency: setCenterFrequency,
255 | resetBuffer: resetBuffer,
256 | readSamples: readSamples,
257 | close: close,
258 | setOnError: setOnError
259 | };
260 | }
261 |
262 |
--------------------------------------------------------------------------------
/radiocontroller.js:
--------------------------------------------------------------------------------
1 | // Copyright 2013 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /**
16 | * High-level radio control functions.
17 | * @constructor
18 | */
19 | function RadioController() {
20 |
21 | var TUNERS = [{'vendorId': 0x0bda, 'productId': 0x2832},
22 | {'vendorId': 0x0bda, 'productId': 0x2838}];
23 | //var SAMPLE_RATE = 1024000; // Must be a multiple of 512 * BUFS_PER_SEC
24 | var SAMPLE_RATE = 2048000;
25 | var BUFS_PER_SEC = 100;
26 | var SAMPLES_PER_BUF = Math.floor(SAMPLE_RATE / BUFS_PER_SEC);
27 | var NULL_FUNC = function(){};
28 | var STATE = {
29 | OFF: 0,
30 | STARTING: 1,
31 | PLAYING: 2,
32 | STOPPING: 3,
33 | CHG_FREQ: 4
34 | };
35 | var SUBSTATE = {
36 | USB: 1,
37 | TUNER: 2,
38 | ALL_ON: 3,
39 | TUNING: 4
40 | };
41 |
42 | var state = new State(STATE.OFF);
43 | var requestingBlocks = 0;
44 | var playingBlocks = 0;
45 | var mode = {};
46 | //var frequency = 832500000;
47 | //var frequency = 837500000;
48 | //var frequency = 80400000;
49 | var frequency = 82500000;
50 | var ppm = 0;
51 | var actualPpm = 0;
52 | var offsetCount = -1;
53 | var offsetSum = 0;
54 | var autoGain = true;
55 | var gain = 0;
56 | var errorHandler;
57 | var tuner;
58 | var connection;
59 | var ui;
60 | var samples;
61 |
62 | /**
63 | * Starts playing the radio.
64 | * @param {Function=} opt_callback A function to call when the radio
65 | * starts playing.
66 | */
67 | function start(opt_callback) {
68 | if (state.state == STATE.OFF) {
69 | state = new State(STATE.STARTING, SUBSTATE.USB, opt_callback);
70 | chrome.permissions.request(
71 | {'permissions': [{'usbDevices': TUNERS}]},
72 | function(res) {
73 | if (!res) {
74 | state = new State(STATE.OFF);
75 | throwError('This app has no permission to access the USB ports.');
76 | } else {
77 | processState();
78 | }
79 | });
80 | } else if (state.state == STATE.STOPPING || state.state == STATE.STARTING) {
81 | state = new State(STATE.STARTING, state.substate, opt_callback);
82 | }
83 | }
84 |
85 | /**
86 | * Stops playing the radio.
87 | * @param {Function=} opt_callback A function to call after the radio
88 | * stops playing.
89 | */
90 | function stop(opt_callback) {
91 | if (state.state == STATE.OFF) {
92 | opt_callback && opt_callback();
93 | } else if (state.state == STATE.STARTING || state.state == STATE.STOPPING) {
94 | state = new State(STATE.STOPPING, state.substate, opt_callback);
95 | } else if (state.state != STATE.STOPPING) {
96 | state = new State(STATE.STOPPING, SUBSTATE.ALL_ON, opt_callback);
97 | }
98 | }
99 |
100 | /**
101 | * Tunes to another frequency.
102 | * @param {number} freq The new frequency in Hz.
103 | */
104 | function setFrequency(freq) {
105 | if (state.state == STATE.PLAYING || state.state == STATE.CHG_FREQ) {
106 | state = new State(STATE.CHG_FREQ, null, freq);
107 | } else {
108 | frequency = freq;
109 | ui && ui.update();
110 | }
111 | }
112 |
113 | /**
114 | * Returns the currently tuned frequency.
115 | * @return {number} The current frequency in Hz.
116 | */
117 | function getFrequency() {
118 | return frequency;
119 | }
120 |
121 | /**
122 | * Returns whether the radio is currently playing.
123 | * @param {boolean} Whether the radio is currently playing.
124 | */
125 | function isPlaying() {
126 | return state.state != STATE.OFF && state.state != STATE.STOPPING;
127 | }
128 |
129 | /**
130 | * Returns whether the radio is currently stopping.
131 | * @param {boolean} Whether the radio is currently stopping.
132 | */
133 | function isStopping() {
134 | return state.state == STATE.STOPPING;
135 | }
136 |
137 | /**
138 | * Sets automatic tuner gain.
139 | */
140 | function setAutoGain() {
141 | autoGain = true;
142 | }
143 |
144 | /**
145 | * Sets a particular tuner gain.
146 | * @param {number} gain The tuner gain in dB.
147 | */
148 | function setManualGain(newGain) {
149 | autoGain = false;
150 | if (newGain < 0) {
151 | gain = 0;
152 | } else if (newGain > 47.4) {
153 | gain = 47.4;
154 | } else {
155 | gain = newGain;
156 | }
157 | }
158 |
159 | /**
160 | * Returns whether automatic gain is currently set.
161 | */
162 | function isAutoGain() {
163 | return autoGain;
164 | }
165 |
166 | /**
167 | * Returns the currently-set manual gain in dB.
168 | */
169 | function getManualGain() {
170 | return gain;
171 | }
172 |
173 | /**
174 | * Saves a reference to the current user interface controller.
175 | * @param {Object} iface The controller. Must have an update() method.
176 | */
177 | function setInterface(iface) {
178 | ui = iface;
179 | }
180 |
181 | function getSamples() {
182 | return samples;
183 | }
184 |
185 | /**
186 | * Sets a function to be called when there is an error.
187 | * @param {Function} handler The function to call. Its only parameter
188 | * is the error message.
189 | */
190 | function setOnError(handler) {
191 | errorHandler = handler;
192 | }
193 |
194 | /**
195 | * Handles an error.
196 | * @param {string} msg The error message.
197 | */
198 | function throwError(msg) {
199 | if (errorHandler) {
200 | errorHandler(msg);
201 | } else {
202 | throw msg;
203 | }
204 | }
205 |
206 | /**
207 | * Starts the decoding pipeline.
208 | */
209 | function startPipeline() {
210 | // In this way we read one block while we decode and play another.
211 | if (state.state == STATE.PLAYING) {
212 | processState();
213 | }
214 | processState();
215 | }
216 |
217 | /**
218 | * Performs the appropriate action according to the current state.
219 | */
220 | function processState() {
221 | switch (state.state) {
222 | case STATE.STARTING:
223 | return stateStarting();
224 | case STATE.PLAYING:
225 | return statePlaying();
226 | case STATE.CHG_FREQ:
227 | return stateChangeFrequency();
228 | case STATE.STOPPING:
229 | return stateStopping();
230 | }
231 | }
232 |
233 | /**
234 | * STARTING state. Initializes the tuner and starts the decoding pipeline.
235 | *
236 | * This state has several substates: USB (when it needs to acquire and
237 | * initialize the USB device), TUNER (needs to set the sample rate and
238 | * tuned frequency), and ALL_ON (needs to start the decoding pipeline).
239 | *
240 | * At the last substate it transitions into the PLAYING state.
241 | */
242 | function stateStarting() {
243 | if (state.substate == SUBSTATE.USB) {
244 | state = new State(STATE.STARTING, SUBSTATE.TUNER, state.param);
245 | doFindDevices(0);
246 | } else if (state.substate == SUBSTATE.TUNER) {
247 | state = new State(STATE.STARTING, SUBSTATE.ALL_ON, state.param);
248 | actualPpm = ppm;
249 | tuner = new RTL2832U(connection, actualPpm, autoGain ? null : gain);
250 | tuner.setOnError(throwError);
251 | tuner.open(function() {
252 | tuner.setSampleRate(SAMPLE_RATE, function(rate) {
253 | offsetSum = 0;
254 | offsetCount = -1;
255 | tuner.setCenterFrequency(frequency, function() {
256 | processState();
257 | })})});
258 | } else if (state.substate == SUBSTATE.ALL_ON) {
259 | var cb = state.param;
260 | state = new State(STATE.PLAYING);
261 | tuner.resetBuffer(function() {
262 | cb && cb();
263 | ui && ui.update();
264 | startPipeline();
265 | });
266 | }
267 | }
268 |
269 | /**
270 | * Finds the first matching tuner USB device in the tuner device definition
271 | * list and transitions to the next substate.
272 | * @param {number} index The first element in the list to find.
273 | */
274 | function doFindDevices(index) {
275 | if (index == TUNERS.length) {
276 | state = new State(STATE.OFF);
277 | throwError('USB tuner device not found. The Radio Receiver ' +
278 | 'app needs an RTL2832U-based DVB-T dongle ' +
279 | '(with an R820T tuner chip) to work.');
280 | } else {
281 | chrome.usb.findDevices(TUNERS[index],
282 | function(conns) {
283 | if (conns.length == 0) {
284 | doFindDevices(index + 1);
285 | } else {
286 | connection = conns[0];
287 | processState();
288 | }
289 | });
290 | }
291 | }
292 |
293 | /**
294 | * PLAYING state. Reads a block of samples from the tuner and plays it.
295 | *
296 | * 2 blocks are in flight all at times, so while one block is being
297 | * demodulated and played, the next one is already being sampled.
298 | */
299 | function statePlaying() {
300 | ++requestingBlocks;
301 | tuner.readSamples(SAMPLES_PER_BUF, function(data) {
302 | --requestingBlocks;
303 | if (state.state == STATE.PLAYING) {
304 | //if (playingBlocks <= 2) {
305 | //++playingBlocks;
306 | //decoder.postMessage([0, data, stereoEnabled], [data]);
307 | samples = data;
308 | //}
309 | }
310 | processState();
311 | });
312 | }
313 |
314 | /**
315 | * CHG_FREQ state. Changes tuned frequency.
316 | *
317 | * First it waits until all in-flight blocks have been dealt with. When
318 | * there are no more in-flight blocks it sets the new frequency, resets
319 | * the buffer and transitions into the PLAYING state.
320 | */
321 | function stateChangeFrequency() {
322 | if (requestingBlocks > 0) {
323 | return;
324 | }
325 | frequency = state.param;
326 | ui && ui.update();
327 | offsetSum = 0;
328 | offsetCount = -1;
329 | tuner.setCenterFrequency(frequency, function() {
330 | tuner.resetBuffer(function() {
331 | state = new State(STATE.PLAYING);
332 | startPipeline();
333 | })});
334 | }
335 |
336 | /**
337 | * STOPPING state. Stops playing and shuts the tuner down.
338 | *
339 | * This state has several substates: ALL_ON (when it needs to wait until
340 | * all in-flight blocks have been vacated and close the tuner), TUNER (when
341 | * it has closed the tuner and needs to close the USB device), and USB (when
342 | * it has closed the USB device). After the USB substate it will transition
343 | * to the OFF state.
344 | */
345 | function stateStopping() {
346 | if (state.substate == SUBSTATE.ALL_ON) {
347 | if (requestingBlocks > 0) {
348 | return;
349 | }
350 | state = new State(STATE.STOPPING, SUBSTATE.TUNER, state.param);
351 | ui && ui.update();
352 | tuner.close(function() {
353 | processState();
354 | });
355 | } else if (state.substate == SUBSTATE.TUNER) {
356 | state = new State(STATE.STOPPING, SUBSTATE.USB, state.param);
357 | chrome.usb.closeDevice(connection, function() {
358 | processState();
359 | });
360 | } else if (state.substate == SUBSTATE.USB) {
361 | var cb = state.param;
362 | state = new State(STATE.OFF);
363 | cb && cb();
364 | samples = null;
365 | ui && ui.update();
366 | }
367 | }
368 |
369 | /**
370 | * Constructs a state object.
371 | * @param {number} state The state.
372 | * @param {number=} opt_substate The sub-state.
373 | * @param {*=} opt_param The state's parameter.
374 | */
375 | function State(state, opt_substate, opt_param) {
376 | return {
377 | state: state,
378 | substate: opt_substate,
379 | param: opt_param
380 | };
381 | }
382 |
383 | return {
384 | start: start,
385 | stop: stop,
386 | setFrequency: setFrequency,
387 | getFrequency: getFrequency,
388 | isPlaying: isPlaying,
389 | isStopping: isStopping,
390 | setAutoGain: setAutoGain,
391 | setManualGain: setManualGain,
392 | isAutoGain: isAutoGain,
393 | getManualGain: getManualGain,
394 | setInterface: setInterface,
395 | getSamples: getSamples,
396 | setOnError: setOnError
397 | };
398 | }
399 |
--------------------------------------------------------------------------------
/r820t.js:
--------------------------------------------------------------------------------
1 | // Copyright 2013 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /**
16 | * Operations on the R820T tuner chip.
17 | * @param {RtlCom} com The RTL communications object.
18 | * @param {number} xtalFreq The frequency of the oscillator crystal.
19 | * @param {Function} throwError A function to handle errors.
20 | * @constructor
21 | */
22 | function R820T(com, xtalFreq, throwError) {
23 |
24 | /**
25 | * Initial values for registers 0x05-0x1f.
26 | */
27 | var REGISTERS = [0x83, 0x32, 0x75, 0xc0, 0x40, 0xd6, 0x6c, 0xf5, 0x63, 0x75,
28 | 0x68, 0x6c, 0x83, 0x80, 0x00, 0x0f, 0x00, 0xc0, 0x30, 0x48,
29 | 0xcc, 0x60, 0x00, 0x54, 0xae, 0x4a, 0xc0];
30 |
31 | /**
32 | * Configurations for the multiplexer in different frequency bands.
33 | */
34 | var MUX_CFGS = [
35 | [0, 0x08, 0x02, 0xdf],
36 | [50, 0x08, 0x02, 0xbe],
37 | [55, 0x08, 0x02, 0x8b],
38 | [60, 0x08, 0x02, 0x7b],
39 | [65, 0x08, 0x02, 0x69],
40 | [70, 0x08, 0x02, 0x58],
41 | [75, 0x00, 0x02, 0x44],
42 | [90, 0x00, 0x02, 0x34],
43 | [110, 0x00, 0x02, 0x24],
44 | [140, 0x00, 0x02, 0x14],
45 | [180, 0x00, 0x02, 0x13],
46 | [250, 0x00, 0x02, 0x11],
47 | [280, 0x00, 0x02, 0x00],
48 | [310, 0x00, 0x41, 0x00],
49 | [588, 0x00, 0x40, 0x00]
50 | ];
51 |
52 | /**
53 | * A bit mask to reverse the bits in a byte.
54 | */
55 | var BIT_REVS = [0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
56 | 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf];
57 |
58 | /**
59 | * Whether the PLL in the tuner is locked.
60 | */
61 | var hasPllLock = false;
62 |
63 | /**
64 | * Shadow registers 0x05-0x1f, for setting values using masks.
65 | */
66 | var shadowRegs;
67 |
68 |
69 | /**
70 | * Initializes the tuner.
71 | * @param {Function} kont The continuation for this function.
72 | */
73 | function init(kont) {
74 | initRegisters(REGISTERS, function() {
75 | initElectronics(kont);
76 | });
77 | }
78 |
79 | /**
80 | * Sets the tuner's frequency.
81 | * @param {number} freq The frequency to tune to.
82 | * @param {Function} kont The continuation for this function.
83 | */
84 | function setFrequency(freq, kont) {
85 | setMux(freq, function() {
86 | setPll(freq, kont);
87 | });
88 | }
89 |
90 | /**
91 | * Stops the tuner.
92 | * @param {Function} kont The continuation for this function.
93 | */
94 | function close(kont) {
95 | writeEach([
96 | [0x06, 0xb1, 0xff],
97 | [0x05, 0xb3, 0xff],
98 | [0x07, 0x3a, 0xff],
99 | [0x08, 0x40, 0xff],
100 | [0x09, 0xc0, 0xff],
101 | [0x0a, 0x36, 0xff],
102 | [0x0c, 0x35, 0xff],
103 | [0x0f, 0x68, 0xff],
104 | [0x11, 0x03, 0xff],
105 | [0x17, 0xf4, 0xff],
106 | [0x19, 0x0c, 0xff]
107 | ], kont);
108 | }
109 |
110 | /**
111 | * Initializes all the components of the tuner.
112 | * @param {Function} kont The continuation for this function.
113 | */
114 | function initElectronics(kont) {
115 | writeEach([
116 | [0x0c, 0x00, 0x0f],
117 | [0x13, 49, 0x3f],
118 | [0x1d, 0x00, 0x38]
119 | ], function() {
120 | calibrateFilter(true, function(filterCap) {
121 | writeEach([
122 | [0x0a, 0x10 | filterCap, 0x1f],
123 | [0x0b, 0x6b, 0xef],
124 | [0x07, 0x00, 0x80],
125 | [0x06, 0x10, 0x30],
126 | [0x1e, 0x40, 0x60],
127 | [0x05, 0x00, 0x80],
128 | [0x1f, 0x00, 0x80],
129 | [0x0f, 0x00, 0x80],
130 | [0x19, 0x60, 0x60],
131 | [0x1d, 0xe5, 0xc7],
132 | [0x1c, 0x24, 0xf8],
133 | [0x0d, 0x53, 0xff],
134 | [0x0e, 0x75, 0xff],
135 | [0x05, 0x00, 0x60],
136 | [0x06, 0x00, 0x08],
137 | [0x11, 0x38, 0x08],
138 | [0x17, 0x30, 0x30],
139 | [0x0a, 0x40, 0x60],
140 | [0x1d, 0x00, 0x38],
141 | [0x1c, 0x00, 0x04],
142 | [0x06, 0x00, 0x40],
143 | [0x1a, 0x30, 0x30],
144 | [0x1d, 0x18, 0x38],
145 | [0x1c, 0x24, 0x04],
146 | [0x1e, 0x0d, 0x1f],
147 | [0x1a, 0x20, 0x30]
148 | ], kont);
149 | })});
150 | }
151 |
152 | /**
153 | * Sets the tuner to automatic gain.
154 | * @param {Function} kont The continuation for this function.
155 | */
156 | function setAutoGain(kont) {
157 | writeEach([
158 | [0x05, 0x00, 0x10],
159 | [0x07, 0x10, 0x10],
160 | [0x0c, 0x0b, 0x9f]
161 | ], kont);
162 | }
163 |
164 | /**
165 | * Sets the tuner's manual gain.
166 | * @param {number} gain The tuner's gain, in dB.
167 | * @param {Function} kont The continuation for this function.
168 | */
169 | function setManualGain(gain, kont) {
170 | var step = 0;
171 | if (gain <= 15) {
172 | step = Math.round(((0.00467 * gain - 0.127) * gain + 1.42) * gain - 0.05);
173 | } else if (gain <= 41.5) {
174 | step = Math.round(((0.00703 * gain - 0.0542) * gain + 1.82) * gain - 8.8);
175 | } else {
176 | step = Math.round(((0.116105 * gain - 15.5431) * gain + 693.409) * gain - 10282.3);
177 | }
178 | if (step < 0) {
179 | step = 0;
180 | } else if (step > 28) {
181 | step = 28;
182 | }
183 | var lnaValue = Math.floor((step + 1) / 2);
184 | var mixerValue = Math.floor(step / 2);
185 | writeEach([
186 | [0x05, 0x10, 0x10],
187 | [0x07, 0x00, 0x10],
188 | [0x0c, 0x08, 0x9f],
189 | [0x05, lnaValue, 0x0f],
190 | [0x07, mixerValue, 0x0f]
191 | ], kont);
192 | }
193 |
194 | /**
195 | * Calibrates the filters.
196 | * @param {boolean} firstTry Whether this is the first try to calibrate.
197 | * @param {Function} kont The continuation for this function.
198 | */
199 | function calibrateFilter(firstTry, kont) {
200 | writeEach([
201 | [0x0b, 0x6b, 0x60],
202 | [0x0f, 0x04, 0x04],
203 | [0x10, 0x00, 0x03]
204 | ], function() {
205 | setPll(56000000, function() {
206 | if (!hasPllLock) {
207 | throwError("PLL not locked -- cannot tune to the selected frequency.");
208 | return;
209 | }
210 | writeEach([
211 | [0x0b, 0x10, 0x10],
212 | [0x0b, 0x00, 0x10],
213 | [0x0f, 0x00, 0x04]
214 | ], function() {
215 | readRegBuffer(0x00, 5, function(data) {
216 | var arr = new Uint8Array(data);
217 | var filterCap = arr[4] & 0x0f;
218 | if (filterCap == 0x0f) {
219 | filterCap = 0;
220 | }
221 | if (filterCap != 0 && firstTry) {
222 | calibrateFilter(false, kont);
223 | } else {
224 | kont(filterCap);
225 | }
226 | })})})});
227 | }
228 |
229 | /**
230 | * Sets the multiplexer's frequency.
231 | * @param {number} freq The frequency to set.
232 | * @param {Function} kont The continuation for this function.
233 | */
234 | function setMux(freq, kont) {
235 | var freqMhz = freq / 1000000;
236 | for (var i = 0; i < MUX_CFGS.length - 1; ++i) {
237 | if (freqMhz < MUX_CFGS[i + 1][0]) {
238 | break;
239 | }
240 | }
241 | var cfg = MUX_CFGS[i];
242 | writeEach([
243 | [0x17, cfg[1], 0x08],
244 | [0x1a, cfg[2], 0xc3],
245 | [0x1b, cfg[3], 0xff],
246 | [0x10, 0x00, 0x0b],
247 | [0x08, 0x00, 0x3f],
248 | [0x09, 0x00, 0x3f]
249 | ], kont);
250 | }
251 |
252 | /**
253 | * Sets the PLL's frequency.
254 | * @param {number} freq The frequency to set.
255 | * @param {Function} kont The continuation for this function.
256 | */
257 | function setPll(freq, kont) {
258 | var freqKhz = Math.round(freq / 1000);
259 | var pllRef = Math.floor(xtalFreq);
260 | var pllRefKhz = Math.round(pllRef / 1000);
261 | writeEach([
262 | [0x10, 0x00, 0x10],
263 | [0x1a, 0x00, 0x0c],
264 | [0x12, 0x80, 0xe0]
265 | ], function() {
266 | var divNum = Math.min(6, Math.floor(Math.log(1770000 / freqKhz) / Math.LN2));
267 | var mixDiv = 1 << (divNum + 1);
268 | readRegBuffer(0x00, 5, function(data) {
269 | var arr = new Uint8Array(data);
270 | var vcoFineTune = (arr[4] & 0x30) >> 4;
271 | if (vcoFineTune > 2) {
272 | --divNum;
273 | } else if (vcoFineTune < 2) {
274 | ++divNum;
275 | }
276 | writeRegMask(0x10, divNum << 5, 0xe0, function() {
277 | var vcoFreq = freq * mixDiv;
278 | var nint = Math.floor(vcoFreq / (2 * pllRef));
279 | var vcoFra = Math.floor((vcoFreq - 2 * pllRef * nint) / 1000);
280 | if (nint > 63) {
281 | throwError("No valid PLL values for " + freq + " Hz");
282 | return;
283 | }
284 | var ni = Math.floor((nint - 13) / 4);
285 | var si = nint - 4 * ni - 13;
286 | writeEach([
287 | [0x14, ni + (si << 6), 0xff],
288 | [0x12, vcoFra == 0 ? 0x08 : 0x00, 0x08]
289 | ], function() {
290 | var sdm = Math.min(65535, Math.floor(32768 * vcoFra / pllRefKhz));
291 | writeEach([
292 | [0x16, sdm >> 8, 0xff],
293 | [0x15, sdm & 0xff, 0xff]
294 | ], function() {
295 | getPllLock(true, function() {
296 | writeRegMask(0x1a, 0x08, 0x08, kont);
297 | })})})})})});
298 | }
299 |
300 | /**
301 | * Checks whether the PLL has achieved lock.
302 | * @param {boolean} firstTry Whether this is the first try to achieve lock.
303 | * @param {Function} kont The continuation for this function.
304 | */
305 | function getPllLock(firstTry, kont) {
306 | readRegBuffer(0x00, 3, function(data) {
307 | var arr = new Uint8Array(data);
308 | if (arr[2] & 0x40) {
309 | hasPllLock = true;
310 | return kont();
311 | }
312 | if (firstTry) {
313 | writeRegMask(0x12, 0x60, 0xe0, function() {
314 | return getPllLock(false, kont);
315 | });
316 | } else {
317 | throwError("PLL not locked -- cannot tune to the selected frequency.");
318 | return;
319 | }
320 | });
321 | }
322 |
323 | /**
324 | * Sets the initial values of the 0x05-0x1f registers.
325 | * @param {Array.} regs The values for the registers.
326 | * @param {Function} kont The continuation for this function.
327 | */
328 | function initRegisters(regs, kont) {
329 | shadowRegs = new Uint8Array(regs);
330 | var cmds = [];
331 | for (var i = 0; i < regs.length; ++i) {
332 | cmds.push([CMD.I2CREG, 0x34, i + 5, regs[i]]);
333 | }
334 | com.writeEach(cmds, kont);
335 | }
336 |
337 | /**
338 | * Reads a series of registers into a buffer.
339 | * @param {number} addr The first register's address to read.
340 | * @param {number} length The number of registers to read.
341 | * @param {Function} kont The continuation for this function. It receives
342 | * an ArrayBuffer with the data.
343 | */
344 | function readRegBuffer(addr, length, kont) {
345 | com.i2c.readRegBuffer(0x34, addr, length, function(data) {
346 | var buf = new Uint8Array(data);
347 | for (var i = 0; i < buf.length; ++i) {
348 | var b = buf[i];
349 | buf[i] = (BIT_REVS[b & 0xf] << 4) | BIT_REVS[b >> 4];
350 | }
351 | kont(buf.buffer);
352 | });
353 | }
354 |
355 | /**
356 | * Writes a masked value into a register.
357 | * @param {number} addr The address of the register to write into.
358 | * @param {number} value The value to write.
359 | * @param {number} mask A mask that specifies which bits to write.
360 | * @param {Function} kont The continuation for this function.
361 | */
362 | function writeRegMask(addr, value, mask, kont) {
363 | var rc = shadowRegs[addr - 5];
364 | var val = (rc & ~mask) | (value & mask);
365 | shadowRegs[addr - 5] = val;
366 | com.i2c.writeRegister(0x34, addr, val, kont);
367 | }
368 |
369 | /**
370 | * Perform the write operations given in the array.
371 | * @param {Array.>} array The operations.
372 | * @param {Function} kont The continuation for this function.
373 | */
374 | function writeEach(array, kont) {
375 | var index = 0;
376 | function iterate() {
377 | if (index >= array.length) {
378 | kont();
379 | } else {
380 | var line = array[index++];
381 | writeRegMask(line[0], line[1], line[2], iterate);
382 | }
383 | }
384 | iterate();
385 | }
386 |
387 | return {
388 | init: init,
389 | setFrequency: setFrequency,
390 | setAutoGain: setAutoGain,
391 | setManualGain: setManualGain,
392 | close: close
393 | };
394 | }
395 |
396 | /**
397 | * Checks if the R820T tuner is present.
398 | * @param {RtlCom} com The RTL communications object.
399 | * @param {Function} kont The continuation for this function. It receives
400 | * a boolean that tells whether the tuner is present.
401 | */
402 | R820T.check = function (com, kont) {
403 | com.i2c.readRegister(0x34, 0, function(data) {
404 | kont(data == 0x69);
405 | });
406 | };
407 |
408 |
--------------------------------------------------------------------------------
/js/TrackballControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Eberhard Graether / http://egraether.com/
3 | */
4 |
5 | THREE.TrackballControls = function ( object, domElement ) {
6 |
7 | var _this = this;
8 | var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 };
9 |
10 | this.object = object;
11 | this.domElement = ( domElement !== undefined ) ? domElement : document;
12 |
13 | // API
14 |
15 | this.enabled = true;
16 |
17 | this.screen = { width: 0, height: 0, offsetLeft: 0, offsetTop: 0 };
18 | this.radius = ( this.screen.width + this.screen.height ) / 4;
19 |
20 | this.rotateSpeed = 1.0;
21 | this.zoomSpeed = 1.2;
22 | this.panSpeed = 0.3;
23 |
24 | this.noRotate = false;
25 | this.noZoom = false;
26 | this.noPan = false;
27 |
28 | this.staticMoving = false;
29 | this.dynamicDampingFactor = 0.2;
30 |
31 | this.minDistance = 0;
32 | this.maxDistance = Infinity;
33 |
34 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
35 |
36 | // internals
37 |
38 | this.target = new THREE.Vector3();
39 |
40 | var lastPosition = new THREE.Vector3();
41 |
42 | var _state = STATE.NONE,
43 | _prevState = STATE.NONE,
44 |
45 | _eye = new THREE.Vector3(),
46 |
47 | _rotateStart = new THREE.Vector3(),
48 | _rotateEnd = new THREE.Vector3(),
49 |
50 | _zoomStart = new THREE.Vector2(),
51 | _zoomEnd = new THREE.Vector2(),
52 |
53 | _touchZoomDistanceStart = 0,
54 | _touchZoomDistanceEnd = 0,
55 |
56 | _panStart = new THREE.Vector2(),
57 | _panEnd = new THREE.Vector2();
58 |
59 | // for reset
60 |
61 | this.target0 = this.target.clone();
62 | this.position0 = this.object.position.clone();
63 | this.up0 = this.object.up.clone();
64 |
65 | // events
66 |
67 | var changeEvent = { type: 'change' };
68 |
69 |
70 | // methods
71 |
72 | this.handleResize = function () {
73 |
74 | this.screen.width = window.innerWidth;
75 | this.screen.height = window.innerHeight;
76 |
77 | this.screen.offsetLeft = 0;
78 | this.screen.offsetTop = 0;
79 |
80 | this.radius = ( this.screen.width + this.screen.height ) / 4;
81 |
82 | };
83 |
84 | this.handleEvent = function ( event ) {
85 |
86 | if ( typeof this[ event.type ] == 'function' ) {
87 |
88 | this[ event.type ]( event );
89 |
90 | }
91 |
92 | };
93 |
94 | this.getMouseOnScreen = function ( clientX, clientY ) {
95 |
96 | return new THREE.Vector2(
97 | ( clientX - _this.screen.offsetLeft ) / _this.radius * 0.5,
98 | ( clientY - _this.screen.offsetTop ) / _this.radius * 0.5
99 | );
100 |
101 | };
102 |
103 | this.getMouseProjectionOnBall = function ( clientX, clientY ) {
104 |
105 | var mouseOnBall = new THREE.Vector3(
106 | ( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / _this.radius,
107 | ( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / _this.radius,
108 | 0.0
109 | );
110 |
111 | var length = mouseOnBall.length();
112 |
113 | if ( length > 1.0 ) {
114 |
115 | mouseOnBall.normalize();
116 |
117 | } else {
118 |
119 | mouseOnBall.z = Math.sqrt( 1.0 - length * length );
120 |
121 | }
122 |
123 | _eye.copy( _this.object.position ).sub( _this.target );
124 |
125 | var projection = _this.object.up.clone().setLength( mouseOnBall.y );
126 | projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) );
127 | projection.add( _eye.setLength( mouseOnBall.z ) );
128 |
129 | return projection;
130 |
131 | };
132 |
133 | this.rotateCamera = function () {
134 |
135 | var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
136 |
137 | if ( angle ) {
138 |
139 | var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(),
140 | quaternion = new THREE.Quaternion();
141 |
142 | angle *= _this.rotateSpeed;
143 |
144 | quaternion.setFromAxisAngle( axis, -angle );
145 |
146 | _eye.applyQuaternion( quaternion );
147 | _this.object.up.applyQuaternion( quaternion );
148 |
149 | _rotateEnd.applyQuaternion( quaternion );
150 |
151 | if ( _this.staticMoving ) {
152 |
153 | _rotateStart.copy( _rotateEnd );
154 |
155 | } else {
156 |
157 | quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
158 | _rotateStart.applyQuaternion( quaternion );
159 |
160 | }
161 |
162 | }
163 |
164 | };
165 |
166 | this.zoomCamera = function () {
167 |
168 | if ( _state === STATE.TOUCH_ZOOM ) {
169 |
170 | var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
171 | _touchZoomDistanceStart = _touchZoomDistanceEnd;
172 | _eye.multiplyScalar( factor );
173 |
174 | } else {
175 |
176 | var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
177 |
178 | if ( factor !== 1.0 && factor > 0.0 ) {
179 |
180 | _eye.multiplyScalar( factor );
181 |
182 | if ( _this.staticMoving ) {
183 |
184 | _zoomStart.copy( _zoomEnd );
185 |
186 | } else {
187 |
188 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
189 |
190 | }
191 |
192 | }
193 |
194 | }
195 |
196 | };
197 |
198 | this.panCamera = function () {
199 |
200 | var mouseChange = _panEnd.clone().sub( _panStart );
201 |
202 | if ( mouseChange.lengthSq() ) {
203 |
204 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
205 |
206 | var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x );
207 | pan.add( _this.object.up.clone().setLength( mouseChange.y ) );
208 |
209 | _this.object.position.add( pan );
210 | _this.target.add( pan );
211 |
212 | if ( _this.staticMoving ) {
213 |
214 | _panStart = _panEnd;
215 |
216 | } else {
217 |
218 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
219 |
220 | }
221 |
222 | }
223 |
224 | };
225 |
226 | this.checkDistances = function () {
227 |
228 | if ( !_this.noZoom || !_this.noPan ) {
229 |
230 | if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) {
231 |
232 | _this.object.position.setLength( _this.maxDistance );
233 |
234 | }
235 |
236 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
237 |
238 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
239 |
240 | }
241 |
242 | }
243 |
244 | };
245 |
246 | this.update = function () {
247 |
248 | _eye.subVectors( _this.object.position, _this.target );
249 |
250 | if ( !_this.noRotate ) {
251 |
252 | _this.rotateCamera();
253 |
254 | }
255 |
256 | if ( !_this.noZoom ) {
257 |
258 | _this.zoomCamera();
259 |
260 | }
261 |
262 | if ( !_this.noPan ) {
263 |
264 | _this.panCamera();
265 |
266 | }
267 |
268 | _this.object.position.addVectors( _this.target, _eye );
269 |
270 | _this.checkDistances();
271 |
272 | _this.object.lookAt( _this.target );
273 |
274 | if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {
275 |
276 | _this.dispatchEvent( changeEvent );
277 |
278 | lastPosition.copy( _this.object.position );
279 |
280 | }
281 |
282 | };
283 |
284 | this.reset = function () {
285 |
286 | _state = STATE.NONE;
287 | _prevState = STATE.NONE;
288 |
289 | _this.target.copy( _this.target0 );
290 | _this.object.position.copy( _this.position0 );
291 | _this.object.up.copy( _this.up0 );
292 |
293 | _eye.subVectors( _this.object.position, _this.target );
294 |
295 | _this.object.lookAt( _this.target );
296 |
297 | _this.dispatchEvent( changeEvent );
298 |
299 | lastPosition.copy( _this.object.position );
300 |
301 | };
302 |
303 | // listeners
304 |
305 | function keydown( event ) {
306 |
307 | if ( _this.enabled === false ) return;
308 |
309 | window.removeEventListener( 'keydown', keydown );
310 |
311 | _prevState = _state;
312 |
313 | if ( _state !== STATE.NONE ) {
314 |
315 | return;
316 |
317 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {
318 |
319 | _state = STATE.ROTATE;
320 |
321 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {
322 |
323 | _state = STATE.ZOOM;
324 |
325 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {
326 |
327 | _state = STATE.PAN;
328 |
329 | }
330 |
331 | }
332 |
333 | function keyup( event ) {
334 |
335 | if ( _this.enabled === false ) return;
336 |
337 | _state = _prevState;
338 |
339 | window.addEventListener( 'keydown', keydown, false );
340 |
341 | }
342 |
343 | function mousedown( event ) {
344 |
345 | if ( _this.enabled === false ) return;
346 |
347 | event.preventDefault();
348 | event.stopPropagation();
349 |
350 | if ( _state === STATE.NONE ) {
351 |
352 | _state = event.button;
353 |
354 | }
355 |
356 | if ( _state === STATE.ROTATE && !_this.noRotate ) {
357 |
358 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
359 |
360 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) {
361 |
362 | _zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
363 |
364 | } else if ( _state === STATE.PAN && !_this.noPan ) {
365 |
366 | _panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
367 |
368 | }
369 |
370 | document.addEventListener( 'mousemove', mousemove, false );
371 | document.addEventListener( 'mouseup', mouseup, false );
372 |
373 | }
374 |
375 | function mousemove( event ) {
376 |
377 | if ( _this.enabled === false ) return;
378 |
379 | event.preventDefault();
380 | event.stopPropagation();
381 |
382 | if ( _state === STATE.ROTATE && !_this.noRotate ) {
383 |
384 | _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
385 |
386 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) {
387 |
388 | _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
389 |
390 | } else if ( _state === STATE.PAN && !_this.noPan ) {
391 |
392 | _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
393 |
394 | }
395 |
396 | }
397 |
398 | function mouseup( event ) {
399 |
400 | if ( _this.enabled === false ) return;
401 |
402 | event.preventDefault();
403 | event.stopPropagation();
404 |
405 | _state = STATE.NONE;
406 |
407 | document.removeEventListener( 'mousemove', mousemove );
408 | document.removeEventListener( 'mouseup', mouseup );
409 |
410 | }
411 |
412 | function mousewheel( event ) {
413 |
414 | if ( _this.enabled === false ) return;
415 |
416 | event.preventDefault();
417 | event.stopPropagation();
418 |
419 | var delta = 0;
420 |
421 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
422 |
423 | delta = event.wheelDelta / 40;
424 |
425 | } else if ( event.detail ) { // Firefox
426 |
427 | delta = - event.detail / 3;
428 |
429 | }
430 |
431 | _zoomStart.y += delta * 0.01;
432 |
433 | }
434 |
435 | function touchstart( event ) {
436 |
437 | if ( _this.enabled === false ) return;
438 |
439 | switch ( event.touches.length ) {
440 |
441 | case 1:
442 | _state = STATE.TOUCH_ROTATE;
443 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
444 | break;
445 |
446 | case 2:
447 | _state = STATE.TOUCH_ZOOM;
448 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
449 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
450 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
451 | break;
452 |
453 | case 3:
454 | _state = STATE.TOUCH_PAN;
455 | _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
456 | break;
457 |
458 | default:
459 | _state = STATE.NONE;
460 |
461 | }
462 |
463 | }
464 |
465 | function touchmove( event ) {
466 |
467 | if ( _this.enabled === false ) return;
468 |
469 | event.preventDefault();
470 | event.stopPropagation();
471 |
472 | switch ( event.touches.length ) {
473 |
474 | case 1:
475 | _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
476 | break;
477 |
478 | case 2:
479 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
480 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
481 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
482 | break;
483 |
484 | case 3:
485 | _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
486 | break;
487 |
488 | default:
489 | _state = STATE.NONE;
490 |
491 | }
492 |
493 | }
494 |
495 | function touchend( event ) {
496 |
497 | if ( _this.enabled === false ) return;
498 |
499 | switch ( event.touches.length ) {
500 |
501 | case 1:
502 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
503 | break;
504 |
505 | case 2:
506 | _touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
507 | break;
508 |
509 | case 3:
510 | _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
511 | break;
512 |
513 | }
514 |
515 | _state = STATE.NONE;
516 |
517 | }
518 |
519 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
520 |
521 | this.domElement.addEventListener( 'mousedown', mousedown, false );
522 |
523 | this.domElement.addEventListener( 'mousewheel', mousewheel, false );
524 | this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
525 |
526 | this.domElement.addEventListener( 'touchstart', touchstart, false );
527 | this.domElement.addEventListener( 'touchend', touchend, false );
528 | this.domElement.addEventListener( 'touchmove', touchmove, false );
529 |
530 | window.addEventListener( 'keydown', keydown, false );
531 | window.addEventListener( 'keyup', keyup, false );
532 |
533 | this.handleResize();
534 |
535 | };
536 |
537 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
538 |
--------------------------------------------------------------------------------
/rtlcom.js:
--------------------------------------------------------------------------------
1 | // Copyright 2013 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /**
16 | * Low-level communications with the RTL2832U-based dongle.
17 | * @param {ConnectionHandle} conn The USB connection handle.
18 | * @constructor
19 | */
20 | function RtlCom(conn) {
21 |
22 | /**
23 | * Whether to log all USB transfers.
24 | */
25 | var VERBOSE = false;
26 |
27 | /**
28 | * Set in the control messages' index field for write operations.
29 | */
30 | var WRITE_FLAG = 0x10;
31 |
32 | /**
33 | * Function to call if there was an error in USB transfers.
34 | */
35 | var onError;
36 |
37 | /**
38 | * Writes a buffer into a dongle's register.
39 | * @param {number} block The register's block number.
40 | * @param {number} reg The register number.
41 | * @param {ArrayBuffer} buffer The buffer to write.
42 | * @param {Function} kont The continuation for this function.
43 | */
44 | function writeRegBuffer(block, reg, buffer, kont) {
45 | writeCtrlMsg(reg, block | WRITE_FLAG, buffer, kont);
46 | }
47 |
48 | /**
49 | * Reads a buffer from a dongle's register.
50 | * @param {number} block The register's block number.
51 | * @param {number} reg The register number.
52 | * @param {number} length The length in bytes of the buffer to read.
53 | * @param {Function} kont The continuation for this function.
54 | * It receives the read buffer.
55 | */
56 | function readRegBuffer(block, reg, length, kont) {
57 | readCtrlMsg(reg, block, length, kont);
58 | }
59 |
60 | /**
61 | * Writes a value into a dongle's register.
62 | * @param {number} block The register's block number.
63 | * @param {number} reg The register number.
64 | * @param {number} value The value to write.
65 | * @param {number} length The width in bytes of this value.
66 | * @param {Function} kont The continuation for this function.
67 | */
68 | function writeReg(block, reg, value, length, kont) {
69 | writeCtrlMsg(reg, block | WRITE_FLAG, numberToBuffer(value, length), kont);
70 | }
71 |
72 | /**
73 | * Reads a value from a dongle's register.
74 | * @param {number} block The register's block number.
75 | * @param {number} reg The register number.
76 | * @param {number} length The width in bytes of the value to read.
77 | * @param {Function} kont The continuation for this function.
78 | * It receives the decoded value.
79 | */
80 | function readReg(block, reg, length, kont) {
81 | readCtrlMsg(reg, block, length, function(data) {
82 | kont(bufferToNumber(data));
83 | });
84 | }
85 |
86 | /**
87 | * Writes a masked value into a dongle's register.
88 | * @param {number} block The register's block number.
89 | * @param {number} reg The register number.
90 | * @param {number} value The value to write.
91 | * @param {number} mask The mask for the value to write.
92 | * @param {Function} kont The continuation for this function.
93 | */
94 | function writeRegMask(block, reg, value, mask, kont) {
95 | if (mask == 0xff) {
96 | writeReg(block, reg, value, 1, kont);
97 | } else {
98 | readReg(block, reg, 1, function(old) {
99 | value &= mask;
100 | old &= ~mask;
101 | value |= mask;
102 | writeReg(block, reg, value, 1, kont);
103 | });
104 | }
105 | }
106 |
107 | /**
108 | * Reads a value from a demodulator register.
109 | * @param {number} page The register page number.
110 | * @param {number} addr The register's address.
111 | * @param {Function} kont The continuation for this function.
112 | * It receives the decoded value.
113 | */
114 | function readDemodReg(page, addr, kont) {
115 | readReg(page, (addr << 8) | 0x20, 1, kont);
116 | }
117 |
118 | /**
119 | * Writes a value into a demodulator register.
120 | * @param {number} page The register page number.
121 | * @param {number} addr The register's address.
122 | * @param {number} value The value to write.
123 | * @param {number} len The width in bytes of this value.
124 | * @param {Function} kont The continuation for this function.
125 | */
126 | function writeDemodReg(page, addr, value, len, kont) {
127 | writeRegBuffer(page, (addr << 8) | 0x20, numberToBuffer(value, len, true), function() {
128 | readDemodReg(0x0a, 0x01, kont);
129 | });
130 | }
131 |
132 | /**
133 | * Opens the I2C repeater.
134 | * @param {Function} kont The continuation for this function.
135 | */
136 | function openI2C(kont) {
137 | writeDemodReg(1, 1, 0x18, 1, kont);
138 | }
139 |
140 | /**
141 | * Closes the I2C repeater.
142 | * @param {Function} kont The continuation for this function.
143 | */
144 | function closeI2C(kont) {
145 | writeDemodReg(1, 1, 0x10, 1, kont);
146 | }
147 |
148 | /**
149 | * Reads a value from an I2C register.
150 | * @param {number} addr The device's address.
151 | * @param {number} reg The register number.
152 | * @param {Function} kont The continuation for this function.
153 | */
154 | function readI2CReg(addr, reg, kont) {
155 | writeRegBuffer(BLOCK.I2C, addr, new Uint8Array([reg]).buffer, function() {
156 | readReg(BLOCK.I2C, addr, 1, kont);
157 | });
158 | }
159 |
160 | /**
161 | * Writes a value to an I2C register.
162 | * @param {number} addr The device's address.
163 | * @param {number} reg The register number.
164 | * @param {number} value The value to write.
165 | * @param {number} len The width in bytes of this value.
166 | * @param {Function} kont The continuation for this function.
167 | */
168 | function writeI2CReg(addr, reg, value, kont) {
169 | writeRegBuffer(BLOCK.I2C, addr, new Uint8Array([reg, value]).buffer, kont);
170 | }
171 |
172 | /**
173 | * Reads a buffer from an I2C register.
174 | * @param {number} addr The device's address.
175 | * @param {number} reg The register number.
176 | * @param {number} len The number of bytes to read.
177 | * @param {Function} kont The continuation for this function.
178 | */
179 | function readI2CRegBuffer(addr, reg, len, kont) {
180 | writeRegBuffer(BLOCK.I2C, addr, new Uint8Array([reg]).buffer, function() {
181 | readRegBuffer(BLOCK.I2C, addr, len, kont);
182 | });
183 | }
184 |
185 | /**
186 | * Writes a buffer to an I2C register.
187 | * @param {number} addr The device's address.
188 | * @param {number} reg The register number.
189 | * @param {ArrayBuffer} buffer The buffer to write.
190 | * @param {Function} kont The continuation for this function.
191 | */
192 | function writeI2CRegBuffer(addr, reg, buffer, kont) {
193 | var data = new Uint8Array(buffer.byteLength + 1);
194 | data[0] = reg;
195 | data.set(new Uint8Array(buffer), 1);
196 | writeRegBuffer(BLOCK.I2C, addr, data.buffer, kont);
197 | }
198 |
199 | /**
200 | * Decodes a buffer as a little-endian number.
201 | * @param {ArrayBuffer} buffer The buffer to decode.
202 | * @return {number} The decoded number.
203 | */
204 | function bufferToNumber(buffer) {
205 | var len = buffer.byteLength;
206 | var dv = new DataView(buffer);
207 | if (len == 0) {
208 | return null;
209 | } else if (len == 1) {
210 | return dv.getUint8(0);
211 | } else if (len == 2) {
212 | return dv.getUint16(0, true);
213 | } else if (len == 4) {
214 | return dv.getUint32(0, true);
215 | }
216 | throw 'Cannot parse ' + len + '-byte number';
217 | }
218 |
219 | /**
220 | * Encodes a number into a buffer.
221 | * @param {number} value The number to encode.
222 | * @param {number} len The number of bytes to encode into.
223 | * @param {boolean=} opt_bigEndian Whether to use a big-endian encoding.
224 | */
225 | function numberToBuffer(value, len, opt_bigEndian) {
226 | var buffer = new ArrayBuffer(len);
227 | var dv = new DataView(buffer);
228 | if (len == 1) {
229 | dv.setUint8(0, value);
230 | } else if (len == 2) {
231 | dv.setUint16(0, value, !opt_bigEndian);
232 | } else if (len == 4) {
233 | dv.setUint32(0, value, !opt_bigEndian);
234 | } else {
235 | throw 'Cannot write ' + len + '-byte number';
236 | }
237 | return buffer;
238 | }
239 |
240 | /**
241 | * Sends a USB control message to read from the device.
242 | * @param {number} value The value field of the control message.
243 | * @param {number} index The index field of the control message.
244 | * @param {number} length The number of bytes to read.
245 | * @param {Function} kont The continuation for this function.
246 | */
247 | function readCtrlMsg(value, index, length, kont) {
248 | var ti = {
249 | 'requestType': 'vendor',
250 | 'recipient': 'device',
251 | 'direction': 'in',
252 | 'request': 0,
253 | 'value': value,
254 | 'index': index,
255 | 'length': Math.max(8, length)
256 | };
257 | chrome.usb.controlTransfer(conn, ti, function(event) {
258 | var data = event.data.slice(0, length);
259 | if (VERBOSE) {
260 | console.log('IN value 0x' + value.toString(16) + ' index 0x' +
261 | index.toString(16));
262 | console.log(' read -> ' + dumpBuffer(data));
263 | }
264 | var rc = event.resultCode;
265 | if (rc != 0) {
266 | var msg = 'USB read failed (value 0x' + value.toString(16) +
267 | ' index 0x' + index.toString(16) + '), rc=' + rc +
268 | ', lastErrorMessage="' + chrome.runtime.lastError.message + '"';
269 | if (onError) {
270 | console.error(msg);
271 | return onError(msg);
272 | } else {
273 | throw msg;
274 | }
275 | }
276 | kont(data);
277 | });
278 | }
279 |
280 | /**
281 | * Sends a USB control message to write to the device.
282 | * @param {number} value The value field of the control message.
283 | * @param {number} index The index field of the control message.
284 | * @param {ArrayBuffer} buffer The buffer to write to the device.
285 | * @param {Function} kont The continuation for this function.
286 | */
287 | function writeCtrlMsg(value, index, buffer, kont) {
288 | var ti = {
289 | 'requestType': 'vendor',
290 | 'recipient': 'device',
291 | 'direction': 'out',
292 | 'request': 0,
293 | 'value': value,
294 | 'index': index,
295 | 'data': buffer
296 | };
297 | chrome.usb.controlTransfer(conn, ti, function(event) {
298 | if (VERBOSE) {
299 | console.log('OUT value 0x' + value.toString(16) + ' index 0x' +
300 | index.toString(16) + ' data ' + dumpBuffer(buffer));
301 | }
302 | var rc = event.resultCode;
303 | if (rc != 0) {
304 | var msg = 'USB write failed (value 0x' + value.toString(16) +
305 | ' index 0x' + index.toString(16) + ' data ' + dumpBuffer(buffer) +
306 | '), rc=' + rc + ', lastErrorMessage="' +
307 | chrome.runtime.lastError.message + '"';
308 | if (onError) {
309 | console.error(msg);
310 | return onError(msg);
311 | } else {
312 | throw msg;
313 | }
314 | }
315 | kont();
316 | });
317 | }
318 |
319 | /**
320 | * Does a bulk transfer from the device.
321 | * @param {number} length The number of bytes to read.
322 | * @param {Function} kont The continuation for this function. It receives the
323 | * received buffer.
324 | */
325 | function readBulk(length, kont) {
326 | var ti = {
327 | 'direction': 'in',
328 | 'endpoint': 1,
329 | 'length': length
330 | };
331 | chrome.usb.bulkTransfer(conn, ti, function(event) {
332 | if (VERBOSE) {
333 | console.log('IN BULK requested ' + length + ' received ' + event.data.byteLength);
334 | }
335 | var rc = event.resultCode;
336 | if (rc != 0) {
337 | var msg = 'USB bulk read failed (length 0x' + length.toString(16) +
338 | '), rc=' + rc + ', lastErrorMessage="' +
339 | chrome.runtime.lastError.message + '"';
340 | if (onError) {
341 | console.error(msg);
342 | return onError(msg);
343 | } else {
344 | throw msg;
345 | }
346 | }
347 | kont(event.data);
348 | });
349 | }
350 |
351 | /**
352 | * Claims the USB interface.
353 | * @param {Function} kont The continuation for this function.
354 | */
355 | function claimInterface(kont) {
356 | chrome.usb.claimInterface(conn, 0, kont);
357 | }
358 |
359 | /**
360 | * Releases the USB interface.
361 | * @param {Function} kont The continuation for this function.
362 | */
363 | function releaseInterface(kont) {
364 | chrome.usb.releaseInterface(conn, 0, kont);
365 | }
366 |
367 | /**
368 | * Performs several write operations as specified in an array.
369 | * @param {Array.>} array The operations to perform.
370 | * @param {Function} kont The continuation for this function.
371 | */
372 | function writeEach(array, kont) {
373 | var index = 0;
374 | function iterate() {
375 | if (index >= array.length) {
376 | kont();
377 | } else {
378 | var line = array[index++];
379 | if (line[0] == CMD.REG) {
380 | writeReg(line[1], line[2], line[3], line[4], iterate);
381 | } else if (line[0] == CMD.REGMASK) {
382 | writeRegMask(line[1], line[2], line[3], line[4], iterate);
383 | } else if (line[0] == CMD.DEMODREG) {
384 | writeDemodReg(line[1], line[2], line[3], line[4], iterate);
385 | } else if (line[0] == CMD.I2CREG) {
386 | writeI2CReg(line[1], line[2], line[3], iterate);
387 | } else {
388 | throw 'Unsupported operation [' + line + ']';
389 | }
390 | }
391 | }
392 | iterate();
393 | }
394 |
395 | /**
396 | * Sets a function to call in case of error.
397 | * @param {Function} func The function to call.
398 | */
399 | function setOnError(func) {
400 | onError = func;
401 | }
402 |
403 | /**
404 | * Returns a string representation of a buffer.
405 | * @param {ArrayBuffer} buffer The buffer to display.
406 | * @return {string} The string representation of the buffer.
407 | */
408 | function dumpBuffer(buffer) {
409 | var bytes = [];
410 | var arr = new Uint8Array(buffer);
411 | for (var i = 0; i < arr.length; ++i) {
412 | bytes.push('0x' + arr[i].toString(16));
413 | }
414 | return '[' + bytes + ']';
415 | }
416 |
417 |
418 | return {
419 | writeRegister: writeReg,
420 | readRegister: readReg,
421 | writeRegMask: writeRegMask,
422 | demod: {
423 | readRegister: readDemodReg,
424 | writeRegister: writeDemodReg
425 | },
426 | i2c: {
427 | open: openI2C,
428 | close: closeI2C,
429 | readRegister: readI2CReg,
430 | writeRegister: writeI2CReg,
431 | readRegBuffer: readI2CRegBuffer
432 | },
433 | bulk: {
434 | readBuffer: readBulk
435 | },
436 | iface: {
437 | claim: claimInterface,
438 | release: releaseInterface
439 | },
440 | writeEach: writeEach,
441 | setOnError: setOnError
442 | };
443 | }
444 |
445 | /**
446 | * Commands for writeEach.
447 | */
448 | CMD = {
449 | REG: 1,
450 | REGMASK: 2,
451 | DEMODREG: 3,
452 | I2CREG: 4
453 | };
454 |
455 | /**
456 | * Register blocks.
457 | */
458 | BLOCK = {
459 | DEMOD: 0x000,
460 | USB: 0x100,
461 | SYS: 0x200,
462 | I2C: 0x600
463 | };
464 |
465 | /**
466 | * Device registers.
467 | */
468 | REG = {
469 | SYSCTL: 0x2000,
470 | EPA_CTL: 0x2148,
471 | EPA_MAXPKT: 0x2158,
472 | DEMOD_CTL: 0x3000,
473 | DEMOD_CTL_1: 0x300b
474 | };
475 |
476 |
--------------------------------------------------------------------------------