WebSDR

Source code: https://github.com/jh1ood/WebSDR

pi@raspberrypi:~/Znodejs $ uname -a
Linux raspberrypi 4.9.25-v7+ #994 SMP Fri Apr 28 16:56:00 BST 2017 armv7l GNU/Linux
pi@raspberrypi:~/Znodejs $ node --version
v6.10.2
pi@raspberrypi:~/Znodejs $ npm --version
3.10.10
pi@raspberrypi:~/Znodejs $ mkfifo myfifo
pi@raspberrypi:~/Znodejs $ mkfifo myfifo2
pi@raspberrypi:~/Znodejs $ (./sprig_audio /dev/ttyUSB0 hw:1,0 hw:0,0 > myfifo < myfifo2 &) ; node index.js < myfifo > myfifo2

index.js

// file name = index.js
// (c) 2017 JH1OOD/Mike

const bufa = new Buffer('fefe80e017'            , 'hex'); // preamble
const bufz = new Buffer('fd'                    , 'hex'); // postamble
const buf1 = new Buffer('fefe80e0174351fd'      , 'hex'); // CQ
const buf2 = new Buffer('fefe80e01751525a3ffd'  , 'hex'); // QRZ?
const buf3 = new Buffer('fefe80e003fd'          , 'hex'); // read freq
var   buf4 = new Buffer('fefe80e0050000000000fd', 'hex'); // send freq
 
var SerialPort = require('serialport');
var serial     = new SerialPort(
    '/dev/ttyUSB0',
    {baudrate : 19200, parser : SerialPort.parsers.byteDelimiter([ 0xfd ])});
 
var http  = require('http');
var fs    = require('fs');
var index = fs.readFileSync(__dirname + '/index.html');
var app   = http.createServer(function(req, res) {
                res.writeHead(200, {'Content-Type' : 'text/html'});
                res.end(index);
              })
              .listen(3000);
var io    = require('socket.io').listen(app);

var freqHz  = 0;          // VFO-A frequency
var ndata   = 512 + 2048; // waterfall_data[512] + sound_data[2048]
var pos     = 0;
var myarray = new Array();
var mywater = new Array();
var mysound = new Array();
 
// -- water fall --
 
process.stdin.on('readable', function() { // from spinor_audio
  var buf = process.stdin.read();
  if (buf !== null) {
      for(var i=0;i<buf.length;i++) {
        myarray[pos++] = buf[i];
        if (pos == ndata) {
          for(var j=0;j<512;j++) {     // waterfall_data
            mywater[j] = myarray[j];
          }
          for(var j=0;j<2048;j++) {    // sound_data
            var tmp = myarray[j+512];
            if (tmp>128) tmp -=256;
            mysound   [j] = tmp / 128.0; // signed char to -1.0~+1.0
          }
          io.emit('waterfall', mywater);
          io.emit('sound'    , mysound);
          pos = 0;
        }
      }
  }
});
 
// -- serial for IC-7410 --
 
serial.on('open',
          function() { console.error('serial port /dev/ttyUSB0 is opened.'); });
 
serial.on('data', function(data) {
  if (!(data[0] == 0xfe & data[1] == 0xfe)) {
    console.error('-- received serial data format error');
    }
  if (data[2] == 0xe0 & data[3] == 0x80 & data[4] == 0x03) {
    var f10   = data[5] >> 4 & 0x0f;
    var f1    = data[5] & 0x0f;
    var f1k   = data[6] >> 4 & 0x0f;
    var f100  = data[6] & 0x0f;
    var f100k = data[7] >> 4 & 0x0f;
    var f10k  = data[7] & 0x0f;
    var f10m  = data[8] >> 4 & 0x0f;
    var f1m   = data[8] & 0x0f;
    var freq  = f10m.toString() + f1m.toString() + "," + f100k.toString() +
                f10k.toString() + f1k.toString() + "." + f100.toString()  +
                f10.toString()  + f1 + " kHz";
    freqHz = f10m * 10000000 + f1m * 1000000 + f100k * 100000 + f10k * 10000 +
             f1k * 1000 + f100 * 100 + f10 * 10 + f1;
    io.emit('freqmsg', 'VFO A: ' + freq);
  }
});
 
serial.on('error', function(err) { console.error('Error: ', err.message); })
 
// -- set frequency --
 
    function setfreq(f) {
      var f10m  = Math.floor(f / 10000000); f -= f10m  * 10000000;
      var f1m   = Math.floor(f / 1000000);  f -= f1m   * 1000000;
      var f100k = Math.floor(f / 100000);   f -= f100k * 100000;
      var f10k  = Math.floor(f / 10000);    f -= f10k  * 10000;
      var f1k   = Math.floor(f / 1000);     f -= f1k   * 1000;
      var f100  = Math.floor(f / 100);      f -= f100  * 100;
      var f10   = Math.floor(f / 10);       f -= f10   * 10;
      var f1    = Math.floor(f / 1);
 
      var data = new Array(
          [ 0xfe, 0xfe, 0x80, 0xe0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd ]);
      buf4[5] = f10   << 4 | f1;
      buf4[6] = f1k   << 4 | f100;
      buf4[7] = f100k << 4 | f10k;
      buf4[8] = f10m  << 4 | f1m;
      buf4[9] = 0;
      serial.write(buf4);
    }
 
// -- WebSocket --
 
io.on('connection', function(socket) {
 
  socket.on('message1', function() { serial.write(buf1); });
  socket.on('message2', function() { serial.write(buf2); });
 
  socket.on('message3', function() {
    var newfreq = freqHz + 2000;
    setfreq(newfreq);
  });
 
  socket.on('message4', function() {
    var newfreq = freqHz - 2000;
    setfreq(newfreq);
  });
 
  socket.on('message5', function() {
    console.log('0'); // should be stdout to reach sprig_audio
  });
 
  socket.on('message6', function() {
    console.log('1'); // should be stdout to reach sprig_audio
  });
 
  socket.on('message7', function() {
    console.log('2'); // should be stdout to reach sprig_audio
  });
 
  socket.on('message8', function() {
    console.log('3'); // should be stdout to reach sprig_audio
  });
 
  var cwpitch = 650.0;
  socket.on('message77', function(mx) {
    var newfreq = freqHz - (16000/2048 * mx - cwpitch);
    setfreq(newfreq);
  });

  socket.on('your message', function(msg) {
    serial.write(bufa);
    serial.write(msg );
    serial.write(bufz);
    io.emit('your message', msg);
  });
 
});
 
// -- request freq --
 
function sendTime() {
  serial.write(buf3);
}

setInterval(sendTime, 100);

// -- EOF --

index.html

<!doctype html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing : border-box; }
body { font: 13px Helvetica, Arial; }
form { background: # 000; padding: 3px; position: fixed; bottom: 0; width: 100% ; }
form input { border: 0; padding: 10px; width: 80% ; margin-right : .5% ; }
form button { width: 15% ; background: rgb(143, 188, 143); border: none; padding: 10px; }
#messages{list-style-type : none; margin : 0; padding : 0; }
#messages li{padding : 5px 10px; }
#messages li : nth-child(odd){background : #eee; }
#messages {margin-bottom : 40px }
#messages {font-size : 2em }
#btn1 {position : fixed; top : 10px; left :  10px; font-size : 2em; background-color: darkseagreen }
#btn2 {position : fixed; top : 10px; left :  60px; font-size : 2em; background-color: darkseagreen }
#btn3 {position : fixed; top : 10px; left : 150px; font-size : 2em; background-color: cornsilk }
#btn4 {position : fixed; top : 10px; left : 230px; font-size : 2em; background-color: cornsilk }
#btn5 {position : fixed; top : 10px; left : 310px; font-size : 2em; background-color: paleturquoise }
#btn6 {position : fixed; top : 10px; left : 390px; font-size : 2em; background-color: paleturquoise }
#btn7 {position : fixed; top : 10px; left : 470px; font-size : 2em; background-color: thistle }
#btn8 {position : fixed; top : 10px; left : 550px; font-size : 2em; background-color: thistle }
#mycanvas0 {position : fixed; top :  70px; left :  10px; font-size : 2em }
#mycanvas1 {position : fixed; top :  90px; left :  10px; font-size : 2em }
#mycanvas2 {position : fixed; top : 620px; left :  10px; font-size : 2em }
</style>
</head>

<body>
    <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
    <script src='/socket.io/socket.io.js'></script>
    <script src="https://code.createjs.com/createjs-2015.11.26.min.js"></script>

    <button id="btn1"><font size="5">CQ</font></button>
    <button id="btn2"><font size="5">QRZ?</font></button>
    <button id="btn3"><font size="5">VFO+</font></button>
    <button id="btn4"><font size="5">VFO-</font></button>
    <button id="btn5"><font size="5">BFO+</font></button>
    <button id="btn6"><font size="5">BFO-</font></button>
    <button id="btn7"><font size="5">BFO+</font></button>
    <button id="btn8"><font size="5">BFO-</font></button>

    <div id="messages"></div>
    <form action="">
      <input id="m" autocomplete="off"/><button>Send</button>
    </form>
    <canvas id="mycanvas0" width="512" height= "20" style="background:white;"></canvas>
    <canvas id="mycanvas1" width="512" height="512" style="background:white;"></canvas>
    <canvas id="mycanvas2" width="280" height= "60" style="background:cornsilk;"></canvas>

<script>

function colormap(charcode) {  // 0x00~0xff
  var tmp = charcode / 255.0;  // 0.0~1.0
  var val;
  var r, g, b;
  if (tmp < 0.50) {
    r = 0.0;
  } else if (tmp > 0.75) {
    r = 1.0;
  } else {
    r = 4.0 * tmp - 2.0;
  }

  if (tmp < 0.25) {
    g = 4.0 * tmp;
  } else if (tmp > 0.75) {
    g = -4.0 * tmp + 4.0;
  } else {
    g = 1.0;
  }

  if (tmp < 0.25) {
    b = 1.0;
  } else if (tmp > 0.50) {
    b = 0.0;
  } else {
    b = -4.0 * tmp + 2.0;
  }

  rgb[1] = 255.0 * g;
  rgb[0] = 255.0 * r;
  rgb[2] = 255.0 * b;
}

function waterFall(myarray) {
  ctx1.putImageData(imgData1, 0, 1);
  imgData1 = ctx1.getImageData(0, 0, 512, 512);
  for (j = 0; j < 512; j++) {
    colormap(myarray[j]);
    imgData1.data[0 + j * 4] = rgb[0];
    imgData1.data[1 + j * 4] = rgb[1];
    imgData1.data[2 + j * 4] = rgb[2];
    imgData1.data[3 + j * 4] = 255;
  }
}

function showBpf(ctx0, imgData0, bfo) {
  var cwmarker0 = Math.floor( bfo[0]  / (16000 / 2048) );
  var cwmarker1 = Math.floor( bfo[1]  / (16000 / 2048) );
  console.log('bfo = ', bfo);
  
  for (i = 0; i < 20; i++) {
    for (j = 0; j < 512; j++) {
      var r, g, b, a;
      if(j == cwmarker0 || j == cwmarker1) {
        r = 0; g = 0; b = 0;
      } else if ( j >= cwmarker0-20 && j <= cwmarker0+20 && i > 10) {
        r = 135; g = 206; b= 235;
      } else if ( j >= cwmarker1-20 && j <= cwmarker1+20 && i > 10) {
        r = 216; g = 191; b= 216;
      } else {
        r = 255; g = 255; b= 255;
      }
      imgData0.data[0 + j * 4 + i * imgData0.width * 4] =   r;
      imgData0.data[1 + j * 4 + i * imgData0.width * 4] =   g;
      imgData0.data[2 + j * 4 + i * imgData0.width * 4] =   b;
      imgData0.data[3 + j * 4 + i * imgData0.width * 4] = 255;
    }
  }
  ctx0.putImageData(imgData0, 0, 0);
}

function onClick(e) {
  var rect = e.target.getBoundingClientRect();
  var mx   = e.clientX - rect.left;
  var my   = e.clientY - rect.top;
  console.log('mouse clicked: ', mx, my);
  socket.emit('message77', mx);
}

function playAudioStream(ctx, audio_f32) {
    var audio_buf    = ctx.createBuffer(1, audio_f32.length, 16000);
    var audio_src    = ctx.createBufferSource();
    var current_time = ctx.currentTime;

    audio_buf.getChannelData(0).set(audio_f32);

    audio_src.buffer = audio_buf;
    audio_src.connect(ctx.destination);

    if (current_time < scheduled_time) {
        audio_src.start(scheduled_time);
        scheduled_time += audio_buf.duration;
    } else {
        audio_src.start(current_time);
        scheduled_time = current_time + audio_buf.duration;
    }
}

// main

var socket  = io();

var myarray = new Array();
var canvas0, canvas1, canvas2;
var ctx0, ctx1;
var imgData0, imgData1;
var rgb = new Array(3);

canvas0 = document.getElementById('mycanvas0');
canvas1 = document.getElementById('mycanvas1');
ctx0    = canvas0.getContext('2d');
ctx1    = canvas1.getContext('2d');

canvas1.addEventListener('click', onClick, false);

imgData0 = ctx0.createImageData(512,  20);
imgData1 = ctx1.createImageData(512, 512);

var stage = new createjs.Stage("mycanvas2");
var t     = new createjs.Text("IC-7410", "26px serif", "DarkRed");
t.x       = 10;
t.y       = 10;
stage.addChild(t);
stage.update();

var bfo      = [1000.0, 2000.0];
showBpf(ctx0, imgData0, bfo);

for (i = 0; i < 512; i++) {
  for (j = 0; j < 512; j++) {
    imgData1.data[0 + j * 4 + i * imgData1.width * 4] = j % 256;
    imgData1.data[1 + j * 4 + i * imgData1.width * 4] = i % 256;
    imgData1.data[2 + j * 4 + i * imgData1.width * 4] = 128;
    imgData1.data[3 + j * 4 + i * imgData1.width * 4] = 255;
  }
}
ctx1.putImageData(imgData1, 0, 0);

var audioCtx = new AudioContext;
var scheduled_time = 0;

socket.on('your message', function(msg) {
  $('#messages').append($('<div>').text(msg));
  window.scrollTo(0, document.body.scrollHeight);
});

socket.on('waterfall', function(data) {
  waterFall(data);
});

socket.on('sound'    , function(data) {
  playAudioStream(audioCtx, new Float32Array(data));
});

socket.on('freqmsg', function(msg) {
  t.text = msg;
  stage.update();
});

$('#btn1').click(function() { socket.emit('message1'); console.log('message1'); });
$('#btn2').click(function() { socket.emit('message2'); console.log('message2'); });
$('#btn3').click(function() { socket.emit('message3'); console.log('message3'); });
$('#btn4').click(function() { socket.emit('message4'); console.log('message4'); });
$('#btn5').click(function() { socket.emit('message5'); bfo[0]+=100; showBpf(ctx0, imgData0, bfo);});
$('#btn6').click(function() { socket.emit('message6'); bfo[0]-=100; showBpf(ctx0, imgData0, bfo);});
$('#btn7').click(function() { socket.emit('message7'); bfo[1]+=100; showBpf(ctx0, imgData0, bfo);});
$('#btn8').click(function() { socket.emit('message8'); bfo[1]-=100; showBpf(ctx0, imgData0, bfo);});

$('form').submit(function() {
  socket.emit('your message', $('#m').val());
  $('#m').val('');
  return false;
});
</script>
</body>
</html>

sprig_audio.c

// file name = sprig_audio.c
// (c) 2017 JH1OOD/Mike
// % gcc -Wall -std=c99 sprig_audio.c -o sprig_audio -lm -lasound -lfftw3

#define NFFT     2048
#define NBFO        2
#define BAUDRATE B19200
#define M_PI     3.141592653589793

#include "asoundlib.h"
#include <fftw3.h>
#include <alloca.h>
#include <complex.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

static char myrig  [256]  = "/dev/ttyUSB0"; /* IC-7410 control */
static char device [256]  = "hw:1,0";       /* capture  device (IC-7410) */
static char device2[256]  = "hw:0,0";       /* playback device (Raspberry) */

static unsigned int myrate       [2] = { 16000,  16000};    /* in Hz */
static unsigned int mychannels   [2] = {     1,      2};    /* number of channels */
static unsigned int mybuffer_time[2] = {512000, 512000};    /* in uS */
static unsigned int myperiod_time[2] = {128000, 128000};    /* in uS */
static snd_pcm_sframes_t mybuffer_size[2]; // 16kHz*512uS = 8192 frames
static snd_pcm_sframes_t myperiod_size[2]; // 16kHz*128uS = 2048 frames (= NFFT)

static int byte_per_sample = 2; /* 16 bit format */

int lring = 16384;
static signed short *ringbuffer; // 0x4000
static int wpnt =         0;     // 0x0000
static int rpnt = 16384 / 2;     // 0x2000

static double complex ringbuffer2[151];
static double complex ringbuffer3[151];
static int wpnt2 = 0;

static double audio_signal      [NFFT];
static double fft_window        [NFFT];
static int nbyte, nbyte2;
static double bin_size;
static double amax = 13.0, amin = 7.0;

double bfo_freq [NBFO] = { 1000.0, 2000.0};
double bfo_phase[NBFO] = {    0.0,    0.0};
double bfo_delta[NBFO];

double cw_freq  [NBFO] = {  400.0,  700.0};
double cw_phase [NBFO] = {    0.0,    0.0};
double cw_delta [NBFO];

const int nhilbert = 51; 
const double normalization = 1.0028050845;
double hilbert_coeff[51] = { 
                -1.244274239685367e-02 ,        //  0
                +8.812508503481302e-16 ,        //  1
                -9.007745694645722e-03 ,        //  2
                -4.892921491956048e-16 ,        //  3
                -1.224827000343725e-02 ,        //  4
                -2.457355030119336e-16 ,        //  5
                -1.626770260654829e-02 ,        //  5
                -1.076580799233997e-15 ,        //  7
                -2.126259131785366e-02 ,        //  8
                +1.551646837953304e-15 ,        //  9
                -2.754013546165147e-02 ,        // 10
                -8.271548913440781e-16 ,        // 11
                -3.555144114893793e-02 ,        // 12
                +1.538194937222543e-16 ,        // 13
                -4.616069183085086e-02 ,        // 14
                -2.517547480270933e-16 ,        // 15
                -6.087912031697079e-02 ,        // 16
                +1.529522013663762e-16 ,        // 17
                -8.310832605502005e-02 ,        // 18
                -7.153673299041007e-16 ,        // 19
                -1.216347994939241e-01 ,        // 20
                -3.571474290448791e-17 ,        // 21
                -2.087518418318809e-01 ,        // 22
                -3.409056833722323e-16 ,        // 23
                -6.354638176591967e-01 };       // 24

int    ntap = 151;

double filter_coeff2[151] = { 
                -3.395305452627101e-04 ,
                -3.451719723840665e-04 ,
                -3.530612137292319e-04 ,
                -3.631649177352709e-04 ,
                -3.753639928353780e-04 ,
                -3.894505086974910e-04 ,
                -4.051255048204917e-04 ,
                -4.219977489075693e-04 ,
                -4.395834787202738e-04 ,
                -4.573071519606507e-04 ,
                -4.745032192165208e-04 ,
                -4.904189252244857e-04 ,
                -5.042181337480122e-04 ,
                -5.149861613279066e-04 ,
                -5.217355951351015e-04 ,
                -5.234130602372101e-04 ,
                -5.189068918766871e-04 ,
                -5.070556589445432e-04 ,
                -4.866574758121344e-04 ,
                -4.564800311443923e-04 ,
                -4.152712543468728e-04 ,
                -3.617705329774267e-04 ,
                -2.947203878567924e-04 ,
                -2.128785068104263e-04 ,
                -1.150300330287973e-04 ,
                -6.347328586093010e-19 ,
                1.333341981065508e-04 ,
                2.860304138055082e-04 ,
                4.590695456642169e-04 ,
                6.533436439384927e-04 ,
                8.696444356953537e-04 ,
                1.108652380544769e-03 ,
                1.370926364961174e-03 ,
                1.656894139010764e-03 ,
                1.966843594141855e-03 ,
                2.300914974582423e-03 ,
                2.659094107869474e-03 ,
                3.041206732166469e-03 ,
                3.446913989373043e-03 ,
                3.875709143669074e-03 ,
                4.326915575142740e-03 ,
                4.799686087616610e-03 ,
                5.293003558798781e-03 ,
                5.805682949544904e-03 ,
                6.336374677422204e-03 ,
                6.883569348021978e-03 ,
                7.445603825678312e-03 ,
                8.020668613524446e-03 ,
                8.606816501260849e-03 ,
                9.201972427726550e-03 ,
                9.803944494460752e-03 ,
                1.041043605601629e-02 ,
                1.101905880293651e-02 ,
                1.162734674412459e-02 ,
                1.223277098690524e-02 ,
                1.283275520548310e-02 ,
                1.342469168181124e-02 ,
                1.400595779716308e-02 ,
                1.457393284800538e-02 ,
                1.512601505614670e-02 ,
                1.565963864062149e-02 ,
                1.617229081739082e-02 ,
                1.666152859271505e-02 ,
                1.712499521698524e-02 ,
                1.756043616788736e-02 ,
                1.796571453499896e-02 ,
                1.833882568225810e-02 ,
                1.867791107016340e-02 ,
                1.898127112601672e-02 ,
                1.924737705795075e-02 ,
                1.947488151683051e-02 ,
                1.966262801930648e-02 ,
                1.980965905524953e-02 ,
                1.991522281342583e-02 ,
                1.997877847048119e-02 ,
                2.000000000000000e-02 ,
                1.997877847048119e-02 ,
                1.991522281342583e-02 ,
                1.980965905524953e-02 ,
                1.966262801930648e-02 ,
                1.947488151683051e-02 ,
                1.924737705795075e-02 ,
                1.898127112601672e-02 ,
                1.867791107016340e-02 ,
                1.833882568225810e-02 ,
                1.796571453499896e-02 ,
                1.756043616788736e-02 ,
                1.712499521698524e-02 ,
                1.666152859271505e-02 ,
                1.617229081739082e-02 ,
                1.565963864062149e-02 ,
                1.512601505614670e-02 ,
                1.457393284800538e-02 ,
                1.400595779716308e-02 ,
                1.342469168181124e-02 ,
                1.283275520548310e-02 ,
                1.223277098690524e-02 ,
                1.162734674412459e-02 ,
                1.101905880293651e-02 ,
                1.041043605601629e-02 ,
                9.803944494460752e-03 ,
                9.201972427726550e-03 ,
                8.606816501260849e-03 ,
                8.020668613524446e-03 ,
                7.445603825678312e-03 ,
                6.883569348021978e-03 ,
                6.336374677422204e-03 ,
                5.805682949544904e-03 ,
                5.293003558798781e-03 ,
                4.799686087616610e-03 ,
                4.326915575142740e-03 ,
                3.875709143669074e-03 ,
                3.446913989373043e-03 ,
                3.041206732166469e-03 ,
                2.659094107869474e-03 ,
                2.300914974582423e-03 ,
                1.966843594141855e-03 ,
                1.656894139010764e-03 ,
                1.370926364961174e-03 ,
                1.108652380544769e-03 ,
                8.696444356953537e-04 ,
                6.533436439384927e-04 ,
                4.590695456642169e-04 ,
                2.860304138055082e-04 ,
                1.333341981065508e-04 ,
                -6.347328586093010e-19 ,
                -1.150300330287973e-04 ,
                -2.128785068104263e-04 ,
                -2.947203878567924e-04 ,
                -3.617705329774267e-04 ,
                -4.152712543468728e-04 ,
                -4.564800311443923e-04 ,
                -4.866574758121344e-04 ,
                -5.070556589445432e-04 ,
                -5.189068918766871e-04 ,
                -5.234130602372101e-04 ,
                -5.217355951351015e-04 ,
                -5.149861613279066e-04 ,
                -5.042181337480122e-04 ,
                -4.904189252244857e-04 ,
                -4.745032192165208e-04 ,
                -4.573071519606507e-04 ,
                -4.395834787202738e-04 ,
                -4.219977489075693e-04 ,
                -4.051255048204917e-04 ,
                -3.894505086974910e-04 ,
                -3.753639928353780e-04 ,
                -3.631649177352709e-04 ,
                -3.530612137292319e-04 ,
                -3.451719723840665e-04 ,
                -3.395305452627101e-04 
};

int fd = -1;
double *in;
fftw_complex *out;
fftw_plan p;

static int myset_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, int id) {
  snd_pcm_uframes_t size;
  int err, dir;

  fprintf(stderr, "myset_hwparams: device id = %2d \n", id);

  err = snd_pcm_hw_params_any(handle, params);
  if (err < 0) {
    fprintf(stderr, "Broken configuration for playback: no configurations available: %s\n",
        snd_strerror(err));
    return err;
  }

  err = snd_pcm_hw_params_set_rate_resample(handle, params, 0); // 0: no resampling
  if (err < 0) {
    fprintf(stderr, "Resampling setup failed for playback: %s\n", snd_strerror(err));
    return err;
  }

  err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
  if (err < 0) {
    fprintf(stderr, "Access type not available for playback: %s\n", snd_strerror(err));
    return err;
  }

  err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16);
  if (err < 0) {
    fprintf(stderr, "Sample format not available for playback: %s\n", snd_strerror(err));
    return err;
  }

  err = snd_pcm_hw_params_set_channels(handle, params, mychannels[id]);
  if (err < 0) {
    fprintf(stderr, "Channels count (%i) not available for the device: %s\n", mychannels[id],
           snd_strerror(err));
    return err;
  }

  unsigned int rrate;
  rrate = myrate[id];
  err   = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
  if (err < 0) {
    fprintf(stderr, "Rate %iHz not available for playback: %s\n", myrate[id], snd_strerror(err));
    return err;
  }
  if (rrate != myrate[id]) {
    fprintf(stderr, "set_hwparams2: Rate doesn't match (requested %iHz, get %iHz)\n", myrate[id], err);
    return -EINVAL;
  }

  err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &mybuffer_time[id], &dir);
  if (err < 0) {
    fprintf(stderr, "Unable to set buffer time %i for the device: %s\n", mybuffer_time[id], snd_strerror(err));
    return err;
  }

  err = snd_pcm_hw_params_get_buffer_size(params, &size);
  if (err < 0) {
    fprintf(stderr, "Unable to get buffer size for playback: %s\n", snd_strerror(err));
    return err;
  }
  mybuffer_size[id] = size;

  err = snd_pcm_hw_params_set_period_time_near(handle, params, &myperiod_time[id], &dir);
  if (err < 0) {
    fprintf(stderr, "Unable to set period time %i for playback: %s\n", myperiod_time[id], snd_strerror(err));
    return err;
  }

  err = snd_pcm_hw_params_get_period_size(params, &size, &dir);
  if (err < 0) {
    fprintf(stderr, "Unable to get period size for playback: %s\n", snd_strerror(err));
    return err;
  }
  myperiod_size[id] = size;

  err = snd_pcm_hw_params(handle, params);
  if (err < 0) {
    fprintf(stderr, "Unable to set hw params for playback: %s\n", snd_strerror(err));
    return err;
  }

  fprintf(stderr, " obtained rate = %8d  \n", rrate);
  fprintf(stderr, " mybuffer_size = %8ld \n", mybuffer_size[id]);
  fprintf(stderr, " myperiod_size = %8ld \n", myperiod_size[id]);

  return 0;
}

// ---------------------------------------------------------------------
static int myset_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams, int id) {
  int err;
  fprintf(stderr, "myset_swparams: device id = %2d \n", id);

  err = snd_pcm_sw_params_current(handle, swparams);
  if (err < 0) {
    fprintf(stderr, "Unable to determine current swparams for playback: %s\n",
           snd_strerror(err));
    return err;
  }

  err = snd_pcm_sw_params_set_start_threshold(
      handle, swparams, (mybuffer_size[id] / myperiod_size[id]) * myperiod_size[id]);
  if (err < 0) {
    fprintf(stderr, "Unable to set start threshold mode for playback: %s\n",
           snd_strerror(err));
    return err;
  }

  err = snd_pcm_sw_params_set_avail_min(handle, swparams, myperiod_size[id]);
  if (err < 0) {
    fprintf(stderr, "Unable to set avail min for playback: %s\n", snd_strerror(err));
    return err;
  }

  err = snd_pcm_sw_params(handle, swparams);
  if (err < 0) {
    fprintf(stderr, "Unable to set sw params for playback: %s\n", snd_strerror(err));
    return err;
  }
  return 0;
}

// ---------------------------------------------------------------------
static void async_callback2(snd_async_handler_t *ahandler) {
  snd_pcm_t *handle     = snd_async_handler_get_pcm(ahandler);
  signed short *samples = snd_async_handler_get_callback_private(ahandler);
  snd_pcm_sframes_t avail;
  int err;
  static int icount = 0;
  signed char mychar[4096];

  avail = snd_pcm_avail_update(handle);

  while (avail >= myperiod_size[1]) {
    err = snd_pcm_writei(handle, samples, myperiod_size[1]);

    if (err < 0) {
      fprintf(stderr, "Write error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }

    if (err != myperiod_size[1]) {
      fprintf(stderr, "Write error: written %i expected %li\n", err, myperiod_size[1]);
      exit(EXIT_FAILURE);
    }

    if(0) fprintf( stderr, "async_callback2: icount = %8d, avail = %12ld, period_size = %12ld \n",
        icount++, avail, myperiod_size[1]);

    for (int i = 0; i < myperiod_size[1]; i++) { // also NFFT
      audio_signal[i] = ringbuffer[rpnt];

      double val2 = 0.0; // hilbert filter output
      int index = 0;
      for (int j=0;j<nhilbert;j++) {
        index = rpnt - j;
        if (index < 0) {
          index += 16384;
        }
          val2 += ringbuffer[index] * hilbert_coeff[j];
      }

      int idelayed = rpnt - (nhilbert-1)/2;
      if (idelayed < 0) idelayed += 16834;

      ringbuffer2[wpnt2] = (ringbuffer[idelayed] + I * val2)
                         * (cos(bfo_phase[0])    - I * sin(bfo_phase[0]));

      ringbuffer3[wpnt2] = (ringbuffer[idelayed] + I * val2)
                         * (cos(bfo_phase[1])    - I * sin(bfo_phase[1]));

      double complex val3 = 0.0;
      double complex val4 = 0.0;
      int index3 = 0;
      for(int j=0;j<ntap;j++) {
        index3 = wpnt2 - j;
        if(index3 < 0) index3 += ntap;
        val3 += ringbuffer2[index3] * filter_coeff2[j];
        val4 += ringbuffer3[index3] * filter_coeff2[j];
      }
      val3 *= cos(cw_phase [0]) + I * sin(cw_phase [0]);
      val4 *= cos(cw_phase [1]) + I * sin(cw_phase [1]);

      samples[2 * i]     = (signed short) creal(val3);
      samples[2 * i + 1] = (signed short) creal(val4);
      mychar [i]         = ( (signed short) creal(val3 + val4) ) >> 8;

      rpnt++;
      rpnt &= 0x3fff;

      wpnt2++;
      if(wpnt2 == ntap) wpnt2 = 0;

      for(int j=0;j<NBFO;j++) {
        bfo_phase[j] += bfo_delta[j];
        cw_phase [j] += cw_delta [j];
      }
    }

    for (int i = 0; i < NFFT; i++) {
      in[i] = fft_window[i] * audio_signal[i];
    }

    fftw_execute(p);

    for (int i = 0; i < NFFT / 4; i++) {   // NFFT/4 = 2048/4 = 512
      double val;
      val = out[i][0] * out[i][0] + out[i][1] * out[i][1];
      if (val < pow(10.0, amin)) {
        val = 0.0;
      } else if (val > pow(10.0, amax)) {
        val = 1.0;
      } else {
        val = (log10(val) - amin) / (amax - amin);
      }
      unsigned char c = 255 * val;
      if(i<512) fprintf(stdout, "%c", c); // apple
    }
    for (int i= 0;i<2048;i++) {
      fprintf(stdout, "%c", mychar[i]); // apple
    }
    fflush(stdout);
    avail = snd_pcm_avail_update(handle);
  } // end of while
}

// ---------------------------------------------------------------------
static int async_loop2(snd_pcm_t *handle, signed short *samples) {
  snd_async_handler_t *ahandler;
  int err, count;

  err = snd_async_add_pcm_handler(&ahandler, handle, async_callback2, samples);
  if (err < 0) {
    fprintf(stderr, "Unable to register async handler\n");
    exit(EXIT_FAILURE);
  }
  for (count = 0; count < 2; count++) {
    err = snd_pcm_writei(handle, samples, myperiod_size[1]);
    if (err < 0) {
      fprintf(stderr, "Initial write error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }
    if (err != myperiod_size[1]) {
      fprintf(stderr, "Initial write error: written %i expected %li\n", err,
             myperiod_size[1]);
      exit(EXIT_FAILURE);
    }
  }
  if (snd_pcm_state(handle) == SND_PCM_STATE_PREPARED) {
    err = snd_pcm_start(handle);
    if (err < 0) {
      fprintf(stderr, "Start error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }
  }
  return 0;
}

// ---------------------------------------------------------------------
static void async_callback(snd_async_handler_t *ahandler) {
  snd_pcm_t *handle     = snd_async_handler_get_pcm(ahandler);
  signed short *samples = snd_async_handler_get_callback_private(ahandler);
  snd_pcm_sframes_t avail;
  int err;
  static int icount = 0;
  static double phase = 0.0;
  double phase_delta;
  static double amp = 0.0;

  phase_delta  = 2.0 * M_PI * 2000.0 / 16000.0;
  phase_delta *= 1.0 + 0.1 * sin(2.0 * M_PI * icount / 180.0);

  avail = snd_pcm_avail_update(handle);

  while (avail >= myperiod_size[0]) {
    err = snd_pcm_readi(handle, samples, myperiod_size[0]);
    if (err < 0) {
      fprintf(stderr, "Write error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }

    if (err != myperiod_size[0]) {
      fprintf(stderr, "Write error: written %i expected %li\n", err,
              myperiod_size[0]);
      exit(EXIT_FAILURE);
    }

    int up, down;
    if( icount % 4 == 0) {
      up = 1; down = 0;
    } else if (icount % 4 == 3) {
      up = 0; down = 1;
    } else {
      up = 0; down = 0;
    }

    if(0) fprintf( stderr, "async_callback : icount = %8d, avail = %12ld, amp = %10.4f up = %3d, down = %3d \n",
        icount, avail, amp, up, down);

    for (int i = 0; i < NFFT; i++) {
      if(up == 1) {
        if(i < 20) {
         amp = 1.0-cos(M_PI*i/20.0);
        } else {
         amp = 2.0;
        }
      }
      if (down == 1) {
        if(i < 20) {
          amp = 1.0+cos(M_PI*i/20.0);
        } else {
          amp = 0.0;
        }
      }
      ringbuffer[wpnt++] = 1.0 * samples[i] + 0.0 * amp *sin (phase); phase += phase_delta;
      wpnt &= 0x3fff;
    }
    avail = snd_pcm_avail_update(handle);
  }
  icount++;
}

// ---------------------------------------------------------------------
static int async_loop(snd_pcm_t *handle, signed short *samples) {
  snd_async_handler_t *ahandler;
  int err;

  err = snd_async_add_pcm_handler(&ahandler, handle, async_callback, samples);
  if (err < 0) {
    fprintf(stderr, "Unable to register async handler\n");
    exit(EXIT_FAILURE);
  }

  if (snd_pcm_state(handle) == SND_PCM_STATE_PREPARED) {
    err = snd_pcm_start(handle);
    if (err < 0) {
      fprintf(stderr, "Start error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }
  }
  return 0;
}

// ---------------------------------------------------------------------
void serial_init(void) {
  struct termios tio;
  memset(&tio, 0, sizeof(tio));
  tio.c_cflag     = CS8 | CLOCAL | CREAD;
  tio.c_cc[VEOL]  = 0xfd; /* IC-7410 postamble */
  tio.c_lflag     = 0;    /* non canonical mode */
  tio.c_cc[VTIME] = 0;    /* non canonical mode */
  tio.c_cc[VMIN]  = 1;    /* non canonical mode */

  tio.c_iflag = IGNPAR | ICRNL;
  cfsetispeed(&tio, BAUDRATE);
  cfsetospeed(&tio, BAUDRATE);
  tcsetattr(fd, TCSANOW, &tio);
}

// ---------------------------------------------------------------------
int main(int argc, char *argv[]) {
  struct termios oldtio;
  snd_pcm_t *handle;
  snd_pcm_t *handle2;
  snd_pcm_hw_params_t *hwparams;
  snd_pcm_hw_params_t *hwparams2;
  snd_pcm_sw_params_t *swparams;
  snd_pcm_sw_params_t *swparams2;
  signed short *samples;
  signed short *samples2;
  int err;
  int id;  /* 0: capture device, 1: playback device */

  char buf4stdin[1024];
  
  for(int i=0;i<NBFO;i++) {
    bfo_delta[i] = 2.0 * M_PI * bfo_freq[i] / myrate[1];
    cw_delta [i] = 2.0 * M_PI * cw_freq [i] / myrate[1];
  }

  setvbuf(stdout, NULL, _IOFBF, 0);

  if (argc != 4) {
    fprintf(stderr, "Usage %s /dev/ttyUSB0 hw:1,0 hw:0,0 \n", argv[0]);
    fprintf(stderr,
            " try ls -l /dev/ttyUSB*; arecord -l; aplay -l to know "
            "these parameters.\n");
    return -1;
  }

  strcpy(myrig  , argv[1]);
  strcpy(device , argv[2]);
  strcpy(device2, argv[3]);
  fprintf(stderr, "serial        device is [%s] \n", myrig);
  fprintf(stderr, "audio capture device is [%s] \n", device);
  fprintf(stderr, "audio output  device is [%s] \n", device2);

  bin_size = myrate[0] / (double)NFFT;
  fprintf(stderr, "NFFT = %d, bin_size = %f [Hz] \n", NFFT, bin_size);
  for (int i = 0; i < NFFT; i++) {
    fft_window[i] = 0.54 - 0.46 * cos(2.0 * M_PI * i / (double)NFFT);
  }

  in  = malloc(sizeof(double) * NFFT);
  out = (fftw_complex *) fftw_malloc(sizeof(fftw_complex) * (NFFT / 2 + 1));
  p   = fftw_plan_dft_r2c_1d(NFFT, in, out, FFTW_MEASURE);

  snd_pcm_hw_params_alloca(&hwparams);
  snd_pcm_hw_params_alloca(&hwparams2);
  snd_pcm_sw_params_alloca(&swparams);
  snd_pcm_sw_params_alloca(&swparams2);

  if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
    fprintf(stderr, "Audio capture device open error: %s\n", snd_strerror(err));
    return 0;
  }

  if ((err = snd_pcm_open(&handle2, device2, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
    fprintf(stderr, "Audio output device open error: %s\n", snd_strerror(err));
    return 0;
  }

  id = 0; /* capture device */
  if ((err = myset_hwparams(handle, hwparams, id)) < 0) {
    fprintf(stderr, "Setting of hwparams failed: %s\n", snd_strerror(err));
    exit(EXIT_FAILURE);
  }
  if ((err = myset_swparams(handle, swparams, id)) < 0) {
    fprintf(stderr, "Setting of swparams failed: %s\n", snd_strerror(err));
    exit(EXIT_FAILURE);
  }

  id = 1; /* playback device */
  if ((err = myset_hwparams(handle2, hwparams2, id)) < 0) {
    fprintf(stderr, "Setting of hwparams failed: %s\n", snd_strerror(err));
    exit(EXIT_FAILURE);
  }
  if ((err = myset_swparams(handle2, swparams2, id)) < 0) {
    fprintf(stderr, "Setting of swparams failed: %s\n", snd_strerror(err));
    exit(EXIT_FAILURE);
  }

  nbyte  = myperiod_size[0] * mychannels[0] * byte_per_sample;
  nbyte2 = myperiod_size[1] * mychannels[1] * byte_per_sample;

  samples = malloc(nbyte);
  if (samples == NULL) {
    fprintf(stderr, "cannot malloc samples \n");
    exit(EXIT_FAILURE);
  }

  samples2 = malloc(nbyte2);
  if (samples2 == NULL) {
    fprintf(stderr, "cannot malloc samples2 \n");
    exit(EXIT_FAILURE);
  }

  ringbuffer = malloc( sizeof(short) * lring);
  if (ringbuffer == NULL) {
    fprintf(stderr, "cannot malloc ringbuffer \n");
    exit(EXIT_FAILURE);
  }

  for (int i = 0; i < lring; i++) {
    ringbuffer[i] = 8096.0 * sin(i * 2.0 * 3.14 / 32.0);
  }

  for (int i = 0; i < nbyte2 / byte_per_sample; i++) {
    samples2[2 * i + 0] = 8096.0 * sin(i * 2.0 * 3.14 / 64.0);
    samples2[2 * i + 1] = 8096.0 * sin(i * 2.0 * 3.14 / 64.0);
  }

        hilbert_coeff[(nhilbert-1)/2] = 0.0;          // 25
        for (int i=((nhilbert-1)/2)+1;i<nhilbert;i++) {
                hilbert_coeff[i] = - hilbert_coeff[(nhilbert-1)-i]; // 26 <- -24 
        }   

  if ((err = async_loop(handle, samples)) < 0) {
    fprintf(stderr, "async_loop set error: %s\n", snd_strerror(err));
  }

  if ((err = async_loop2(handle2, samples2)) < 0) {
    fprintf(stderr, "async_loop2 set error: %s\n", snd_strerror(err));
  }

  fd = open(myrig, O_RDWR | O_NOCTTY);
  if (fd < 0) {
    fprintf(stderr, "Error: can not open %s \n", myrig);
    return (-1);
  }
  tcgetattr(fd, &oldtio);
  serial_init();

  while (1) {
    fgets(buf4stdin,512,stdin);
    if(buf4stdin[0] == '0') {
        bfo_freq [0] += 100.0;
        bfo_delta[0] = 2.0 * M_PI * bfo_freq [0] / myrate[1];
        fprintf(stderr, "bfo_freq  = %f \n", bfo_freq[0]);
    }
    if(buf4stdin[0] == '1') {
        bfo_freq [0] -= 100.0;
        bfo_delta[0] = 2.0 * M_PI * bfo_freq [0] / myrate[1];
        fprintf(stderr, "bfo_freq  = %f \n", bfo_freq[0]);
    }
    if(buf4stdin[0] == '2') {
        bfo_freq [1] += 100.0;
        bfo_delta[1] = 2.0 * M_PI * bfo_freq [1] / myrate[1];
        fprintf(stderr, "bfo_freq  = %f \n", bfo_freq[1]);
    }
    if(buf4stdin[0] == '3') {
        bfo_freq [1] -= 100.0;
        bfo_delta[1] = 2.0 * M_PI * bfo_freq[1] / myrate[1];
        fprintf(stderr, "bfo_freq = %f \n", bfo_freq[1]);
    }

    sleep(1);
  }

  fftw_destroy_plan(p);
  fftw_free(in);
  fftw_free(out);

  return 0;
}