Initial import of linmodem-0.2.5.tgz
This commit is contained in:
commit
0457e3a587
|
@ -0,0 +1,33 @@
|
||||||
|
* from version 0.2.4 to 0.2.5
|
||||||
|
=============================
|
||||||
|
- major rewrite, the API becomes stable.
|
||||||
|
- V8/V21/V23 should be usable.
|
||||||
|
- added X11 interface for the simulator.
|
||||||
|
- better line model for the simulator: generic FIR filter.
|
||||||
|
Programmable echo generator (both neat & far end).
|
||||||
|
- V22 tx code (needed for V34 phase 2).
|
||||||
|
- V34: added adaptive equalizer, symbol timing recovery, phase
|
||||||
|
recovery. Tests for fast training on the PP sequence (but not satisfactory).
|
||||||
|
- added AT parser.
|
||||||
|
- added serial protocol.
|
||||||
|
- added working soundcard hw support.
|
||||||
|
|
||||||
|
* from version 0.2.3 to 0.2.4
|
||||||
|
=============================
|
||||||
|
|
||||||
|
- major V90 update, the algebraic part is finished, and the CP packet
|
||||||
|
parsing is done.
|
||||||
|
- added patches from Pavel (serial.c, lmreal.c)
|
||||||
|
|
||||||
|
* from version 0.2.2 to 0.2.3
|
||||||
|
=============================
|
||||||
|
|
||||||
|
- added V90 core, but no usable code yet.
|
||||||
|
- more patches from pavel for ltmodem.
|
||||||
|
|
||||||
|
* from version 0.2.1 to 0.2.2
|
||||||
|
=============================
|
||||||
|
|
||||||
|
- added extended V34 modulation for 31200 & 33600 speeds.
|
||||||
|
- added V34 negociation (not finished)
|
||||||
|
- added Pavel patches for ltmodem
|
|
@ -0,0 +1,340 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) 19yy <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) 19yy name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Library General
|
||||||
|
Public License instead of this License.
|
|
@ -0,0 +1,47 @@
|
||||||
|
# uncomment to use X11 debug interface
|
||||||
|
USE_X11=y
|
||||||
|
|
||||||
|
CFLAGS= -O2 -Wall -g
|
||||||
|
LDFLAGS= -g
|
||||||
|
OBJS= lm.o lmsim.o lmreal.o lmsoundcard.o serial.o atparser.o \
|
||||||
|
dsp.o fsk.o v8.o v21.o v23.o dtmf.o \
|
||||||
|
v34.o v34table.o v22.o v34eq.o \
|
||||||
|
v90.o v90table.o
|
||||||
|
INCLUDES= display.h fsk.h v21.h v34priv.h v90priv.h \
|
||||||
|
dsp.h lm.h v23.h v8.h \
|
||||||
|
dtmf.h lmstates.h v34.h v90.h
|
||||||
|
PROG= lm
|
||||||
|
|
||||||
|
ifdef USE_X11
|
||||||
|
OBJS += display.o
|
||||||
|
LDFLAGS += -L/usr/X11R6/lib -lX11
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: $(PROG)
|
||||||
|
|
||||||
|
$(PROG): $(OBJS)
|
||||||
|
gcc $(LDFLAGS) -o $(PROG) $(OBJS) -lm
|
||||||
|
|
||||||
|
v34gen: v34gen.o dsp.o
|
||||||
|
gcc $(LDFLAGS) -o $@ v34gen.o dsp.o -lm
|
||||||
|
|
||||||
|
v34table.c: v34gen
|
||||||
|
./v34gen > $@
|
||||||
|
|
||||||
|
v90gen: v90gen.o
|
||||||
|
gcc $(LDFLAGS) -o $@ $< -lm
|
||||||
|
|
||||||
|
v90table.c: v90gen
|
||||||
|
./v90gen > $@
|
||||||
|
|
||||||
|
linmodem.dvi: linmodem.tex
|
||||||
|
latex2e linmodem.tex
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.o *~ *.dat core gmon.out *.sw v34table.c v90table.c $(PROG) v34gen v90gen *.aux *.dvi *.log
|
||||||
|
|
||||||
|
tar:
|
||||||
|
( cd .. ; tar zcvf linmodem.tgz linmodem --exclude CVS )
|
||||||
|
|
||||||
|
%.o: %.c $(INCLUDES)
|
||||||
|
gcc $(CFLAGS) -c $*.c
|
|
@ -0,0 +1,99 @@
|
||||||
|
|
||||||
|
Here is the generic Linux Modem. This modem is totally software,
|
||||||
|
it means that all the DSP stuff is done by the main CPU, as in some so
|
||||||
|
called "winmodems". See the main project page at
|
||||||
|
http://www.enst.fr/~bellard/linmodem.html.
|
||||||
|
|
||||||
|
Linmodem is also a research project. It contains all the necessary
|
||||||
|
stuff to test new digital communication algorithms. The line simulator
|
||||||
|
and the X11 interface can be used to simulate a complete communication
|
||||||
|
chain.
|
||||||
|
|
||||||
|
Linmodem is the first modem to integrate a graphical user interface
|
||||||
|
which show the data transmitted graphically (QAM constellation, real
|
||||||
|
time FFT of the received signal, etc...) and which will allow you to
|
||||||
|
monitor the line quality.
|
||||||
|
|
||||||
|
What's done:
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- V34 modulator (sampling rate of 8000 Hz)
|
||||||
|
- V34 demodulator (sampling rate of 8000 Hz), but no echo cancellor.
|
||||||
|
- Algebraic part of V90.
|
||||||
|
- DTMF dialing/receive.
|
||||||
|
- V8 protocol.
|
||||||
|
- V21 modulation & demodulation
|
||||||
|
- V23 modulation & demodulation
|
||||||
|
- sample code to test the protocol.
|
||||||
|
- sample code to test V21, V22, V23, V34 and V90 independently from the modem.
|
||||||
|
- a basic phone line simulator (with echos & typical line
|
||||||
|
amplitude/phase distortion).
|
||||||
|
- an X11 interface (see README.x11)
|
||||||
|
- soundcard interface.
|
||||||
|
- AT command parser & sample tty simulator.
|
||||||
|
- asynchronous protocol.
|
||||||
|
|
||||||
|
See the homepage of the project at
|
||||||
|
http://www.enst.fr/~bellard/linmodem.html to know what are the tasks
|
||||||
|
you could do.
|
||||||
|
|
||||||
|
Read the file README.arch to know the details of the
|
||||||
|
implementation. Next versions will contain the first draft on the
|
||||||
|
algorithms which are implemented.
|
||||||
|
|
||||||
|
Testing:
|
||||||
|
-------
|
||||||
|
|
||||||
|
Yes, you can already hear the modem !
|
||||||
|
|
||||||
|
compile everything, then type:
|
||||||
|
|
||||||
|
lm -sv
|
||||||
|
|
||||||
|
You will see a lot of debug stuff. Then you can press Control C to
|
||||||
|
stop the call. If you play the files 'ans.sw' and 'cal.sw' on your
|
||||||
|
soundcard (16 bit, signed 8kHz), you will hear the DTMF pulses, the V8
|
||||||
|
negociation, and a sample V21 connection. The X11 interface allows you
|
||||||
|
to see the signals exchanged.
|
||||||
|
|
||||||
|
Data pump testing:
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
With the option '-m modulation_name', you can test the V21, V22, V23,
|
||||||
|
V34 and V90 data pumps. You can understand what's going on in this
|
||||||
|
test only if you have a basic knowledge of the modulations.
|
||||||
|
|
||||||
|
The X11 interface can be used to monitor all the main data pump
|
||||||
|
parameters, except for V90 which is not yet completely integrated in
|
||||||
|
the tests (see README.x11).
|
||||||
|
|
||||||
|
Real modems:
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Linmodem won't contain any
|
||||||
|
hardware modem to support modems directly, but it will use kernel
|
||||||
|
drivers which give a unified API to every driver (see README.arch to
|
||||||
|
have an idea of the API)
|
||||||
|
|
||||||
|
Some test code is included in lmreal.c to work with the LTModem stuff
|
||||||
|
available at http://www.close.u-net.com/ltmodem.html. However, it was
|
||||||
|
not tested so don't expect it to work in this version.
|
||||||
|
|
||||||
|
Soundcard testing:
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If you have two PCs connected by soundcards (connect line in -> line
|
||||||
|
out), you may try the soundcard support of linmodem (not tested now,
|
||||||
|
but should work):
|
||||||
|
|
||||||
|
'lm -tv' launches linmodem on your soundcard (device '/dev/dsp'). Then
|
||||||
|
you can type 'ATDTxxx' to compose a number and launch a connection. On
|
||||||
|
the other PC, type 'ATA' to receive the connection. It should work in
|
||||||
|
V23 (or V21 if you change the defaults modulations in lm.c).
|
||||||
|
|
||||||
|
With 'lm -t', linmodem simulates a serial line on '/dev/ttyz0'. You
|
||||||
|
can use minicom or any other terminal emulator to send AT commands.
|
||||||
|
|
||||||
|
Enjoy :-)
|
||||||
|
|
||||||
|
Fabrice Bellard - bellard@email.enst.fr - http://www.enst.fr/~bellard
|
|
@ -0,0 +1,112 @@
|
||||||
|
|
||||||
|
Linmodem architecture
|
||||||
|
|
||||||
|
Copyright (c) 2000 Fabrice Bellard
|
||||||
|
|
||||||
|
|
||||||
|
1) Overview:
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The hardware dependent layer (for example lmreal.c, lmsim.c,
|
||||||
|
lmsoundcard.c) must call the function
|
||||||
|
|
||||||
|
void sm_process(struct sm_state *sm, s16 *output, s16 *input, int nb_samples);
|
||||||
|
|
||||||
|
at least every 10 ms. It must supply 'nb_samples' 16 bit input samples
|
||||||
|
at 8000 Hz. The function sm_process returns the 'output' samples it
|
||||||
|
has computed. These samples can them be sent to the phone line.
|
||||||
|
|
||||||
|
'sm_process' handles the dialing, ring detection and Vxx protocol
|
||||||
|
initization. Then, each protocol can supply a similar function. For
|
||||||
|
example, 'v8.c' supplies the function:
|
||||||
|
|
||||||
|
int V8_process(V8State *sm, s16 *output, s16 *input, int nb_samples);
|
||||||
|
|
||||||
|
It returns the modulation to which the modem must go after the V8
|
||||||
|
phase.
|
||||||
|
|
||||||
|
Note that no threads are used. It implies that each 'xx_process'
|
||||||
|
function must use an internal state machine. The first parameter
|
||||||
|
('V8State' here) is usually the structure in which the state is
|
||||||
|
stored.
|
||||||
|
|
||||||
|
2) Timing:
|
||||||
|
---------
|
||||||
|
|
||||||
|
The protocols must use only 'nb_samples' as a timing, not the real
|
||||||
|
time of the system. It allows to simulate the whole modem without
|
||||||
|
changing anything.
|
||||||
|
|
||||||
|
|
||||||
|
3) Reentrancy:
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Each protocol must be reentrant, so that multiple modems can be
|
||||||
|
instanciated at the same time. It is needed for example to do
|
||||||
|
simulations.
|
||||||
|
|
||||||
|
4) Data handling:
|
||||||
|
----------------
|
||||||
|
|
||||||
|
When a protocol gets a bit from the input samples, it should can a function
|
||||||
|
|
||||||
|
int get_bit(void *opaque);
|
||||||
|
|
||||||
|
provided at its initialization with the opaque parameter 'opaque'. The
|
||||||
|
same should be done for output with :
|
||||||
|
|
||||||
|
void put_bit(void *opaque, int bit);
|
||||||
|
|
||||||
|
'sm_process' instanciate each protocol by giving it one of its
|
||||||
|
standard bit I/O functions. These functions are responsible from
|
||||||
|
handling the higher level protocol (asynchronous, LAPM, V42bis).
|
||||||
|
|
||||||
|
See the file 'serial.c' to see how the data can be handled. V42/V42bis
|
||||||
|
should be added the same way.
|
||||||
|
|
||||||
|
The decoded bytes are put in the FIFO sm->rx_fifo. This fifo is then
|
||||||
|
return to the modem tty. The inverse is done with sm->tx_fifo.
|
||||||
|
|
||||||
|
5) Modem configuration:
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The configuration parameters are defined in 'lm.c'. See 'struct
|
||||||
|
LinModemConfig default_lm_config'.
|
||||||
|
|
||||||
|
6) Linmodem harware interface:
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
See the file lmsoundcard.c which gives an example to interface to a
|
||||||
|
sound card with the Open Sound System API.
|
||||||
|
|
||||||
|
7) Linmodem kernel interface:
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
The hardware interface to an actual modem is not completely defined
|
||||||
|
yet. However, it will be very close to the Open Sound System API:
|
||||||
|
|
||||||
|
- the write() & read() syscalls write & read 16 bit samples from the
|
||||||
|
modem.
|
||||||
|
|
||||||
|
- An ioctl can select the sampling rate (at least 8000 Hz must be
|
||||||
|
available).
|
||||||
|
|
||||||
|
- an ioctl set/reset the offhook relay.
|
||||||
|
|
||||||
|
- an ioctl set/reset the pulse relay.
|
||||||
|
|
||||||
|
- an ioctl gives the state of the ring detection. Each time the ring
|
||||||
|
rings, the event POLLPRI (urgent data) should be set so that the
|
||||||
|
poll() syscall can handle it.
|
||||||
|
|
||||||
|
- an ioctl changes the input & output gains, as the volume for a sound
|
||||||
|
card.
|
||||||
|
|
||||||
|
8) AT command parser:
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
See the file atparse.c: The parser must remain sample. We do not want
|
||||||
|
to support all the AT commands, but only the minimal subset so that
|
||||||
|
linux programs can work : 'chat' for PPP, 'mgetty/vgetty' for
|
||||||
|
voice/data/fax calls, 'sendfax' to send faxes.
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
Linmodem contains an X11 interface which can be used to monitor the
|
||||||
|
modem parameters. It is used for example in v34 testing (lm -m v34). The
|
||||||
|
X11 simulation works only in 16 bit mode. If you are interested by
|
||||||
|
other screen depths, please implement them :-)
|
||||||
|
|
||||||
|
The function keys are used to change the parameters which are dumped
|
||||||
|
on the screen:
|
||||||
|
|
||||||
|
* for V34:
|
||||||
|
|
||||||
|
F1 : dump the brut samples which enter into the modem (at 8000Hz). A
|
||||||
|
hamming windowed FFT is done to output the spectral power.
|
||||||
|
|
||||||
|
F2 : dump the echo cancellor parameters (not implemented yet).
|
||||||
|
|
||||||
|
F3 : dump the samples after frequency normalization & symbol timing
|
||||||
|
recovery. You have 3 samples per baud for V34. A hamming windowed FFT
|
||||||
|
is done to output the spectral power.
|
||||||
|
|
||||||
|
F4 : dump the equalizer filter in both time & frequency domains.
|
||||||
|
|
||||||
|
F5 : dump the QAM points just after the equalizer and phase
|
||||||
|
recovery. This graphic can be used to monitor the overall modem
|
||||||
|
quality.
|
||||||
|
|
||||||
|
|
||||||
|
* for V21/V23 tests
|
||||||
|
|
||||||
|
F1: same as V34
|
||||||
|
|
||||||
|
F3: decision samples
|
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
* Simple AT command parser
|
||||||
|
*
|
||||||
|
* Copyright (c) 2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
|
static void parse_at_line(struct lm_at_state *s, const char *line);
|
||||||
|
static void at_putc(struct lm_at_state *s, int c);
|
||||||
|
static void at_printf(struct lm_at_state *s, char *fmt, ...);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This AT command parser is not intended to be complete nor accurate
|
||||||
|
* but to provide the minimum functionality so that the programs can
|
||||||
|
* make a data/voice/fax connection.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void lm_at_parser_init(struct lm_at_state *s, struct sm_state *sm)
|
||||||
|
{
|
||||||
|
s->sm = sm;
|
||||||
|
s->at_line_ptr = 0;
|
||||||
|
s->at_state = AT_MODE_COMMAND;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lm_at_parser(struct lm_at_state *s)
|
||||||
|
{
|
||||||
|
int state, c;
|
||||||
|
|
||||||
|
switch(s->at_state) {
|
||||||
|
case AT_MODE_COMMAND:
|
||||||
|
for(;;) {
|
||||||
|
/* handle incoming character */
|
||||||
|
c = sm_get_bit(&s->sm->tx_fifo);
|
||||||
|
if (c == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (c == '\n' || c == '\r') {
|
||||||
|
/* line validation */
|
||||||
|
s->at_line[s->at_line_ptr] = '\0';
|
||||||
|
if (s->at_line_ptr != 0) {
|
||||||
|
at_putc(s, '\n');
|
||||||
|
parse_at_line(s, s->at_line);
|
||||||
|
}
|
||||||
|
s->at_line_ptr = 0;
|
||||||
|
} else if (c == '\b') {
|
||||||
|
/* backspace */
|
||||||
|
if (s->at_line_ptr > 0) {
|
||||||
|
s->at_line_ptr--;
|
||||||
|
at_printf(s, "\b \b");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* add new char */
|
||||||
|
if (s->at_line_ptr < (sizeof(s->at_line) - 1)) {
|
||||||
|
s->at_line[s->at_line_ptr++] = c;
|
||||||
|
at_putc(s, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AT_MODE_DIALING:
|
||||||
|
state = lm_get_state(s->sm);
|
||||||
|
if (state == LM_STATE_IDLE) {
|
||||||
|
at_printf(s, "ERROR\n");
|
||||||
|
s->at_state = AT_MODE_COMMAND;
|
||||||
|
} else if (state == LM_STATE_CONNECTED) {
|
||||||
|
at_printf(s, "CONNECT\n");
|
||||||
|
s->at_state = AT_MODE_CONNECTED;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AT_MODE_CONNECTED:
|
||||||
|
if (lm_get_state(s->sm) == LM_STATE_IDLE) {
|
||||||
|
at_printf(s, "OK\n");
|
||||||
|
s->at_state = AT_MODE_COMMAND;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* return TRUE if val is a prefix of str. If it returns TRUE, ptr is
|
||||||
|
set to the next character in 'str' after the prefix */
|
||||||
|
#if 0
|
||||||
|
static int strstart(const char *str, const char *val, const char **ptr)
|
||||||
|
{
|
||||||
|
const char *p, *q;
|
||||||
|
p = str;
|
||||||
|
q = val;
|
||||||
|
while (*q != '\0') {
|
||||||
|
if (*p != *q)
|
||||||
|
return 0;
|
||||||
|
p++;
|
||||||
|
q++;
|
||||||
|
}
|
||||||
|
*ptr = p;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int strcasestart(const char *str, const char *val, const char **ptr)
|
||||||
|
{
|
||||||
|
const char *p, *q;
|
||||||
|
p = str;
|
||||||
|
q = val;
|
||||||
|
while (*q != '\0') {
|
||||||
|
if (toupper(*p) != toupper(*q))
|
||||||
|
return 0;
|
||||||
|
p++;
|
||||||
|
q++;
|
||||||
|
}
|
||||||
|
*ptr = p;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void at_putc(struct lm_at_state *s, int c)
|
||||||
|
{
|
||||||
|
if (c == '\n')
|
||||||
|
sm_put_bit(&s->sm->rx_fifo, '\r');
|
||||||
|
sm_put_bit(&s->sm->rx_fifo, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void at_printf(struct lm_at_state *s, char *fmt, ...)
|
||||||
|
{
|
||||||
|
char buf[256], *p;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
snprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
p = buf;
|
||||||
|
while (*p) {
|
||||||
|
c = *p++;
|
||||||
|
at_putc(s, c);
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_at_line(struct lm_at_state *s, const char *line)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
struct sm_state *sm = s->sm;
|
||||||
|
|
||||||
|
if (strcasestart(line, "ATD", &p)) {
|
||||||
|
int pulse;
|
||||||
|
|
||||||
|
pulse = sm->lm_config->pulse_dial;
|
||||||
|
if (strcasestart(p, "P", &p))
|
||||||
|
pulse = 1;
|
||||||
|
if (strcasestart(p, "T", &p))
|
||||||
|
pulse = 0;
|
||||||
|
lm_start_dial(sm, pulse, p);
|
||||||
|
s->at_state = AT_MODE_DIALING;
|
||||||
|
} else if (strcasestart(line, "ATI", &p)) {
|
||||||
|
at_printf(s, "Linmodem " LM_VERSION "\n");
|
||||||
|
} else if (strcasestart(line, "ATZ", &p)) {
|
||||||
|
at_printf(s, "OK\n");
|
||||||
|
} else if (strcasestart(line, "ATA", &p)) {
|
||||||
|
lm_start_receive(sm);
|
||||||
|
s->at_state = AT_MODE_DIALING;
|
||||||
|
} else if (strcasestart(line, "ATH", &p)) {
|
||||||
|
lm_hangup(sm);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,557 @@
|
||||||
|
/*
|
||||||
|
* X11 interface for linmodem
|
||||||
|
*
|
||||||
|
* Copyright (c) 2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/extensions/XShm.h>
|
||||||
|
#include <X11/keysym.h>
|
||||||
|
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define NB_MODES 5
|
||||||
|
|
||||||
|
enum {
|
||||||
|
DISP_MODE_SAMPLE, /* display the brut samples */
|
||||||
|
DISP_MODE_ECHOCANCEL, /* display the echo cancellor info */
|
||||||
|
DISP_MODE_SAMPLESYNC, /* display the samples after freq normalize
|
||||||
|
& symbol recovery */
|
||||||
|
DISP_MODE_EQUALIZER, /* display the equalizer filter */
|
||||||
|
DISP_MODE_QAM, /* display the qam */
|
||||||
|
} disp_state;
|
||||||
|
|
||||||
|
char *mode_str[NB_MODES] = {
|
||||||
|
"Sample",
|
||||||
|
"Echo Cancel",
|
||||||
|
"Sample Sync",
|
||||||
|
"Equalizer",
|
||||||
|
"QAM",
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define QAM_SIZE 460
|
||||||
|
|
||||||
|
static void set_state(int state);
|
||||||
|
|
||||||
|
/**************************/
|
||||||
|
|
||||||
|
#define RGB(r, g, b) ((((r) >> 3) << 11) | (((g) >> 2) << 5) | ((b) >> 3))
|
||||||
|
|
||||||
|
Display *display;
|
||||||
|
Window window;
|
||||||
|
GC gc;
|
||||||
|
XFontStruct *xfont;
|
||||||
|
unsigned int fg, bg;
|
||||||
|
int minx, miny;
|
||||||
|
|
||||||
|
int font_xsize, font_ysize;
|
||||||
|
|
||||||
|
int lm_display_init(void)
|
||||||
|
{
|
||||||
|
XSizeHints hint;
|
||||||
|
int screen;
|
||||||
|
XVisualInfo vinfo;
|
||||||
|
int size_x = 640;
|
||||||
|
int size_y = 480;
|
||||||
|
XSetWindowAttributes xwa;
|
||||||
|
char *fontname = "fixed";
|
||||||
|
|
||||||
|
display = XOpenDisplay("");
|
||||||
|
if (display == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
screen = DefaultScreen(display);
|
||||||
|
|
||||||
|
bg = RGB(0, 0, 0);
|
||||||
|
fg = RGB(255, 255, 255);
|
||||||
|
|
||||||
|
/* Fill in hint structure */
|
||||||
|
|
||||||
|
hint.x = 0;
|
||||||
|
hint.y = 0;
|
||||||
|
hint.width = size_x;
|
||||||
|
hint.height = size_y;
|
||||||
|
hint.flags = PPosition | PSize;
|
||||||
|
|
||||||
|
/* Make the window */
|
||||||
|
if (!XMatchVisualInfo(display, screen, 16, TrueColor, &vinfo)) {
|
||||||
|
printf("A 16 bit visual is need by this program\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
window = XCreateSimpleWindow(display,
|
||||||
|
DefaultRootWindow(display),
|
||||||
|
hint.x, hint.y,
|
||||||
|
hint.width, hint.height,
|
||||||
|
4, fg, bg);
|
||||||
|
/* Enable backing store */
|
||||||
|
xwa.backing_store = Always;
|
||||||
|
XChangeWindowAttributes(display, window, CWBackingStore, &xwa);
|
||||||
|
|
||||||
|
XSelectInput(display, window, StructureNotifyMask);
|
||||||
|
|
||||||
|
/* Tell other applications about this window */
|
||||||
|
|
||||||
|
XSetStandardProperties(display, window,
|
||||||
|
"linmodem", "linmodem",
|
||||||
|
None, NULL, 0, &hint);
|
||||||
|
|
||||||
|
/* Map window. */
|
||||||
|
|
||||||
|
XMapWindow(display, window);
|
||||||
|
|
||||||
|
/* Wait for map. */
|
||||||
|
while (1) {
|
||||||
|
XEvent xev;
|
||||||
|
XNextEvent(display, &xev);
|
||||||
|
if (xev.type == MapNotify && xev.xmap.event == window)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
XSelectInput(display, window, KeyPressMask
|
||||||
|
| ButtonPressMask);
|
||||||
|
|
||||||
|
gc = XCreateGC(display, window, 0, 0);
|
||||||
|
|
||||||
|
xfont = XLoadQueryFont (display, fontname);
|
||||||
|
if (!xfont) {
|
||||||
|
fprintf(stderr, "Could not load font '%s'\n", fontname);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
font_xsize = xfont->max_bounds.rbearing - xfont->min_bounds.lbearing;
|
||||||
|
font_ysize = xfont->max_bounds.ascent + xfont->max_bounds.descent;
|
||||||
|
|
||||||
|
set_state(DISP_MODE_SAMPLE);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lm_display_close(void)
|
||||||
|
{
|
||||||
|
XCloseDisplay(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printf_at(int x, int y, char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
char buf[1024];
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
|
||||||
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
|
||||||
|
XSetFont(display, gc, xfont->fid);
|
||||||
|
|
||||||
|
XSetForeground(display, gc, bg);
|
||||||
|
XFillRectangle(display, window, gc,
|
||||||
|
x * font_xsize, y * font_ysize,
|
||||||
|
font_xsize * strlen(buf), font_ysize);
|
||||||
|
|
||||||
|
XSetForeground(display, gc, fg);
|
||||||
|
XDrawString(display, window, gc, x * font_xsize, (y + 1) * font_ysize - 1,
|
||||||
|
buf, strlen(buf));
|
||||||
|
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* draw a graph at (x1, y1, x1 + w, y1 + h). The points are computed
|
||||||
|
by calc_func */
|
||||||
|
|
||||||
|
#define DG_AUTOSCALE_YMIN 0x0001
|
||||||
|
#define DG_AUTOSCALE_YMAX 0x0002
|
||||||
|
|
||||||
|
void draw_graph(char *title,
|
||||||
|
int x1, int y1, int w, int h,
|
||||||
|
float xmin, float xmax, float ymin, float ymax,
|
||||||
|
int flags, float (*calc_func)(float))
|
||||||
|
{
|
||||||
|
int i, yy, xx;
|
||||||
|
float x, y;
|
||||||
|
int ly;
|
||||||
|
float tab[1024];
|
||||||
|
|
||||||
|
/* auto scale */
|
||||||
|
for(i=0;i<w;i++) {
|
||||||
|
x = ((float)i / (float)(w-1)) * (xmax - xmin) + xmin;
|
||||||
|
y = calc_func(x);
|
||||||
|
tab[i] = y;
|
||||||
|
if (i == 0) {
|
||||||
|
if (flags & DG_AUTOSCALE_YMIN)
|
||||||
|
ymin = y;
|
||||||
|
if (flags & DG_AUTOSCALE_YMAX)
|
||||||
|
ymax = y;
|
||||||
|
} else {
|
||||||
|
if (flags & DG_AUTOSCALE_YMIN && y < ymin)
|
||||||
|
ymin = y;
|
||||||
|
if (flags & DG_AUTOSCALE_YMAX && y > ymax)
|
||||||
|
ymax = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* draw ! */
|
||||||
|
|
||||||
|
XSetForeground(display, gc, bg);
|
||||||
|
XFillRectangle(display, window, gc, x1, y1, w, h);
|
||||||
|
|
||||||
|
XSetForeground(display, gc, RGB(255, 0, 0));
|
||||||
|
XDrawRectangle(display, window, gc, x1, y1, w, h);
|
||||||
|
|
||||||
|
/* axis */
|
||||||
|
XSetForeground(display, gc, RGB(0, 255, 0));
|
||||||
|
if (xmin <= 0.0 && 0.0 <= xmax) {
|
||||||
|
xx = (int)rint(((0.0 - xmin) / (xmax - xmin)) * (w-1)) + x1;
|
||||||
|
XDrawLine(display, window, gc,
|
||||||
|
xx, y1, xx, y1 + h - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ymin <= 0.0 && 0.0 <= ymax) {
|
||||||
|
yy = y1 + h - 1 - (int)rint(((0.0 - ymin) / (ymax - ymin)) * (h-1));
|
||||||
|
XDrawLine(display, window, gc,
|
||||||
|
x1, yy, x1 + w - 1, yy);
|
||||||
|
}
|
||||||
|
|
||||||
|
XSetForeground(display, gc, RGB(255, 255, 255));
|
||||||
|
ly = -1;
|
||||||
|
for(i=0;i<w;i++) {
|
||||||
|
y = tab[i];
|
||||||
|
if (y >= ymin && y <= ymax) {
|
||||||
|
yy = y1 + h - 1 - (int)rint(((y - ymin) / (ymax - ymin)) * (h-1));
|
||||||
|
if (ly >= 0) {
|
||||||
|
XDrawLine(display, window, gc,
|
||||||
|
i - 1, ly, i, yy);
|
||||||
|
} else {
|
||||||
|
XDrawPoint(display, window, gc,
|
||||||
|
i, yy);
|
||||||
|
}
|
||||||
|
ly = yy;
|
||||||
|
} else {
|
||||||
|
ly = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* title */
|
||||||
|
XSetForeground(display, gc, RGB(0, 255, 0));
|
||||||
|
|
||||||
|
printf_at((x1+font_xsize-1) / font_xsize,
|
||||||
|
(y1+font_ysize-1) / font_ysize,
|
||||||
|
"%s - ymin=%6.3e ymax=%6.3e", title, ymin, ymax);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************/
|
||||||
|
/* utilities */
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************/
|
||||||
|
|
||||||
|
/* si and sq must be betwen -1.0 and 1.0 */
|
||||||
|
|
||||||
|
int nb_samples = 0;
|
||||||
|
|
||||||
|
void lm_dump_qam(float si, float sq)
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
if (disp_state != DISP_MODE_QAM)
|
||||||
|
return;
|
||||||
|
|
||||||
|
x = (int)(si * (QAM_SIZE/2)) + (QAM_SIZE/2);
|
||||||
|
y = (int)(sq * (QAM_SIZE/2)) + (QAM_SIZE/2);
|
||||||
|
if (x < 0 || x >= QAM_SIZE ||
|
||||||
|
y < 0 || y >= QAM_SIZE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
XSetForeground(display, gc, RGB(255, 255, 255));
|
||||||
|
XDrawPoint(display, window, gc, x, y);
|
||||||
|
|
||||||
|
nb_samples++;
|
||||||
|
printf_at(minx, 1, "# samples: %d", nb_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* print samples */
|
||||||
|
#define NB_SAMPLES 512
|
||||||
|
|
||||||
|
float sample_mem[NB_CHANNELS][NB_SAMPLES];
|
||||||
|
int sample_pos[NB_CHANNELS];
|
||||||
|
|
||||||
|
float sample_hamming[NB_SAMPLES];
|
||||||
|
int sample_hamming_init = 0;
|
||||||
|
complex sample_fft[NB_SAMPLES];
|
||||||
|
int sample_channel;
|
||||||
|
|
||||||
|
float calc_sample(float x)
|
||||||
|
{
|
||||||
|
return sample_mem[sample_channel][(int)x];
|
||||||
|
}
|
||||||
|
|
||||||
|
float calc_sample_pow(float x)
|
||||||
|
{
|
||||||
|
complex *p = &sample_fft[(int)x];
|
||||||
|
return p->re * p->re + p->im * p->im;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_samples(int channel)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
sample_channel = channel;
|
||||||
|
|
||||||
|
draw_graph("Sample",
|
||||||
|
0, 0, QAM_SIZE, QAM_SIZE/2,
|
||||||
|
0.0, NB_SAMPLES - 1, 0.0, 0.0,
|
||||||
|
DG_AUTOSCALE_YMAX | DG_AUTOSCALE_YMIN,
|
||||||
|
calc_sample);
|
||||||
|
|
||||||
|
if (!sample_hamming_init) {
|
||||||
|
calc_hamming(sample_hamming, NB_SAMPLES);
|
||||||
|
sample_hamming_init = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=0;i<NB_SAMPLES;i++) {
|
||||||
|
sample_fft[i].re = sample_mem[channel][i] * sample_hamming[i];
|
||||||
|
sample_fft[i].im = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fft_calc(sample_fft, NB_SAMPLES, 0);
|
||||||
|
|
||||||
|
draw_graph("Spectral power",
|
||||||
|
0, QAM_SIZE/2, QAM_SIZE, QAM_SIZE/2,
|
||||||
|
0.0, NB_SAMPLES/2 - 1, 0.0, 0.0,
|
||||||
|
DG_AUTOSCALE_YMAX, calc_sample_pow);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lm_dump_sample(int channel, float val)
|
||||||
|
{
|
||||||
|
|
||||||
|
sample_mem[channel][sample_pos[channel]] = val;
|
||||||
|
if (++sample_pos[channel] == NB_SAMPLES) {
|
||||||
|
sample_pos[channel] = 0;
|
||||||
|
|
||||||
|
if ((disp_state == DISP_MODE_SAMPLE &&
|
||||||
|
channel == CHANNEL_SAMPLE) ||
|
||||||
|
(disp_state == DISP_MODE_SAMPLESYNC &&
|
||||||
|
channel == CHANNEL_SAMPLESYNC)) {
|
||||||
|
|
||||||
|
draw_samples(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
int last_eye_y[2];
|
||||||
|
int last_eye_x[2];
|
||||||
|
|
||||||
|
/* print an eye diagram (each symbol is sampled at integer time
|
||||||
|
values). We print 1 centered period */
|
||||||
|
void lm_dump_eye(int channel, float time, float val)
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
if (disp_state != DISP_MODE_EYE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (val < -1)
|
||||||
|
val = -1;
|
||||||
|
else if (val > 1)
|
||||||
|
val = 1;
|
||||||
|
|
||||||
|
y = (int)(val * (QAM_SIZE/2)) + (QAM_SIZE/2);
|
||||||
|
time += 0.5;
|
||||||
|
if (time >= 1.0)
|
||||||
|
time -= 1.0;
|
||||||
|
x = (int)(time * QAM_SIZE);
|
||||||
|
|
||||||
|
XSetForeground(display, gc, RGB(255, 255, 255));
|
||||||
|
if (x > last_eye_x[channel] && 0) {
|
||||||
|
XDrawLine(display, window, gc,
|
||||||
|
last_eye_x[channel], last_eye_y[channel], x, y);
|
||||||
|
} else {
|
||||||
|
XDrawPoint(display, window, gc, x, y);
|
||||||
|
}
|
||||||
|
last_eye_x[channel] = x;
|
||||||
|
last_eye_y[channel] = y;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* print equalizer */
|
||||||
|
|
||||||
|
#define EQ_FFT_SIZE 144
|
||||||
|
|
||||||
|
static int eq_count = 0;
|
||||||
|
static s32 (*eq_filter)[2];
|
||||||
|
static int eq_norm;
|
||||||
|
static complex eq_fft[EQ_FFT_SIZE];
|
||||||
|
|
||||||
|
float calc_eq_re(float x)
|
||||||
|
{
|
||||||
|
return (float)eq_filter[(int)rint(x)][0] / (float)eq_norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
float calc_eq_im(float x)
|
||||||
|
{
|
||||||
|
return (float)eq_filter[(int)rint(x)][1] / (float)eq_norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
float calc_eq_pow(float x)
|
||||||
|
{
|
||||||
|
complex *p = &eq_fft[(int)x];
|
||||||
|
return p->re * p->re + p->im * p->im;
|
||||||
|
}
|
||||||
|
|
||||||
|
float calc_eq_phase(float x)
|
||||||
|
{
|
||||||
|
complex *p = &eq_fft[(int)x];
|
||||||
|
return atan2(p->im, p->re);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lm_dump_equalizer(s32 eq_filter1[][2], int norm, int size)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (disp_state != DISP_MODE_EQUALIZER)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (++eq_count == 1) {
|
||||||
|
eq_count = 0;
|
||||||
|
eq_filter = eq_filter1;
|
||||||
|
eq_norm = norm;
|
||||||
|
|
||||||
|
draw_graph("Eqz real",
|
||||||
|
0, 0, QAM_SIZE, QAM_SIZE/4,
|
||||||
|
0.0, size - 1, -1.5, 1.5,
|
||||||
|
0,
|
||||||
|
calc_eq_re);
|
||||||
|
|
||||||
|
draw_graph("Eqz imag",
|
||||||
|
0, QAM_SIZE/4, QAM_SIZE, QAM_SIZE/4,
|
||||||
|
0.0, size - 1, -1.5, 1.5,
|
||||||
|
0,
|
||||||
|
calc_eq_im);
|
||||||
|
|
||||||
|
for(i=0;i<EQ_FFT_SIZE;i++) {
|
||||||
|
if (i < size) {
|
||||||
|
eq_fft[i].re = eq_filter[i][0] / (float)norm;
|
||||||
|
eq_fft[i].im = eq_filter[i][1] / (float)norm;
|
||||||
|
} else {
|
||||||
|
eq_fft[i].re = eq_fft[i].im = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EQ_FFT_SIZE == 144) {
|
||||||
|
complex eq_fft1[144];
|
||||||
|
|
||||||
|
slow_fft(eq_fft1, eq_fft, EQ_FFT_SIZE, 0);
|
||||||
|
for(i=0;i<EQ_FFT_SIZE;i++)
|
||||||
|
eq_fft[i] = eq_fft1[i];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fft_calc(eq_fft, EQ_FFT_SIZE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_graph("Eqz spec pow",
|
||||||
|
0, 2*QAM_SIZE/4, QAM_SIZE, QAM_SIZE/4,
|
||||||
|
0.0, EQ_FFT_SIZE - 1, 0.0, 0.0,
|
||||||
|
DG_AUTOSCALE_YMAX, calc_eq_pow);
|
||||||
|
|
||||||
|
draw_graph("Eqz spec phase",
|
||||||
|
0, 3*QAM_SIZE/4, QAM_SIZE, QAM_SIZE/4,
|
||||||
|
0.0, EQ_FFT_SIZE - 1, -M_PI, M_PI,
|
||||||
|
0, calc_eq_phase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* agc */
|
||||||
|
|
||||||
|
void lm_dump_agc(float gain)
|
||||||
|
{
|
||||||
|
printf_at(minx, 2, "AGC: %10.5f", gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lm_dump_linesim_power(float tx_db, float rx_db, float noise_db)
|
||||||
|
{
|
||||||
|
printf_at(minx, 3, "TX: %6.2f dB SNR: %6.2f dB", tx_db, rx_db - noise_db);
|
||||||
|
printf_at(minx, 4, "RX: %6.2f dB N0: %6.2f dB", rx_db, noise_db);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_state(int state)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
minx = ((QAM_SIZE + font_xsize) / font_xsize) + 1;
|
||||||
|
miny = ((QAM_SIZE + font_ysize) / font_ysize);
|
||||||
|
|
||||||
|
disp_state = state;
|
||||||
|
|
||||||
|
XSetForeground(display, gc, bg);
|
||||||
|
XFillRectangle(display, window, gc, 0, 0, 640, 480);
|
||||||
|
|
||||||
|
XSetForeground(display, gc, RGB(255, 0, 0));
|
||||||
|
XDrawRectangle(display, window, gc, 0, 0, QAM_SIZE, QAM_SIZE);
|
||||||
|
|
||||||
|
switch(disp_state) {
|
||||||
|
case DISP_MODE_QAM:
|
||||||
|
XDrawLine(display, window, gc, 0, QAM_SIZE/2, QAM_SIZE-1, QAM_SIZE/2);
|
||||||
|
XDrawLine(display, window, gc, QAM_SIZE/2, 0, QAM_SIZE/2, QAM_SIZE-1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf_at(minx, 0, "Mode: %s", mode_str[disp_state]);
|
||||||
|
|
||||||
|
for(i=0;i<NB_MODES;i++) {
|
||||||
|
printf_at(1 + 15 * i, miny, "F%d:%s", i + 1, mode_str[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int lm_display_poll_event(void)
|
||||||
|
{
|
||||||
|
char buf[80];
|
||||||
|
XEvent xev;
|
||||||
|
KeySym keysym;
|
||||||
|
XComposeStatus status;
|
||||||
|
|
||||||
|
|
||||||
|
if (XPending(display) <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
XNextEvent(display, &xev);
|
||||||
|
switch(xev.type) {
|
||||||
|
case KeyPress:
|
||||||
|
XLookupString((XKeyEvent *) & xev, buf, 80, &keysym, &status);
|
||||||
|
switch(keysym) {
|
||||||
|
case XK_q:
|
||||||
|
return 1;
|
||||||
|
case XK_F1:
|
||||||
|
case XK_F2:
|
||||||
|
case XK_F3:
|
||||||
|
case XK_F4:
|
||||||
|
case XK_F5:
|
||||||
|
case XK_F6:
|
||||||
|
case XK_F7:
|
||||||
|
case XK_F8:
|
||||||
|
{
|
||||||
|
int mode;
|
||||||
|
|
||||||
|
mode = keysym - XK_F1;
|
||||||
|
if (mode < NB_MODES) {
|
||||||
|
set_state(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* display.c */
|
||||||
|
|
||||||
|
int lm_display_init(void);
|
||||||
|
void lm_display_close(void);
|
||||||
|
int lm_display_poll_event(void);
|
||||||
|
|
||||||
|
void lm_dump_qam(float si, float sq);
|
||||||
|
void lm_dump_eye(int channel, float time, float val);
|
||||||
|
|
||||||
|
#define NB_CHANNELS 2
|
||||||
|
enum {
|
||||||
|
CHANNEL_SAMPLE = 0,
|
||||||
|
CHANNEL_SAMPLESYNC,
|
||||||
|
};
|
||||||
|
void lm_dump_sample(int channel, float val);
|
||||||
|
void lm_dump_agc(float gain);
|
||||||
|
void lm_dump_equalizer(s32 eq_filter1[][2], int norm, int size);
|
||||||
|
void lm_dump_echocancel(s32 eq_filter1[][2], int norm, int size);
|
||||||
|
void lm_dump_linesim_power(float tx_db, float rx_db, float noise_db);
|
|
@ -0,0 +1,204 @@
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
s16 cos_tab[COS_TABLE_SIZE];
|
||||||
|
|
||||||
|
void dsp_init(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i=0;i<COS_TABLE_SIZE;i++) {
|
||||||
|
cos_tab[i] = (int) (cos( 2 * M_PI * i / COS_TABLE_SIZE) * COS_BASE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DFT computation with Goertzel algorithm */
|
||||||
|
|
||||||
|
int compute_DFT(s16 *cos_tab, s16 *sin_tab, s16 *x, int k,int n)
|
||||||
|
{
|
||||||
|
int y_re,y_im,i,j,y_2;
|
||||||
|
|
||||||
|
j = 0;
|
||||||
|
#if 0
|
||||||
|
int s0,s1,s2;
|
||||||
|
s0 = s1 = 0;
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
s2 = x[i] + ((cos_tab[j] * s1) >> (COS_BITS-1)) - s0;
|
||||||
|
s0 = s1;
|
||||||
|
s1 = s2;
|
||||||
|
j += k;
|
||||||
|
if (j >= n) j -= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
y_re = s1 - ((cos_tab[k] * s0) >> COS_BITS);
|
||||||
|
/* cannot use cos_tab because n is even */
|
||||||
|
y_im = s1 - ((sin_tab[k] * s0) >> COS_BITS);
|
||||||
|
#else
|
||||||
|
y_re = y_im = 0;
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
y_re += cos_tab[j] * x[i];
|
||||||
|
y_im += sin_tab[j] * x[i];
|
||||||
|
j += k;
|
||||||
|
if (j >= n) j -= n;
|
||||||
|
}
|
||||||
|
y_re = y_re >> COS_BITS;
|
||||||
|
y_im = y_im >> COS_BITS;
|
||||||
|
#endif
|
||||||
|
y_2 = y_re * y_re + y_im * y_im;
|
||||||
|
return y_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* horrible fft code - only needed to debug or fixed table generation */
|
||||||
|
|
||||||
|
#define FFT_MAX_SIZE 2048
|
||||||
|
|
||||||
|
static float norm;
|
||||||
|
static float wfft[FFT_MAX_SIZE];
|
||||||
|
static short tinv[FFT_MAX_SIZE];
|
||||||
|
static int nf = 0, nf2, nf4;
|
||||||
|
|
||||||
|
int fft_init(int n)
|
||||||
|
{
|
||||||
|
float p,k;
|
||||||
|
int i,j,a,c, nk;
|
||||||
|
|
||||||
|
nf=n;
|
||||||
|
nf2=nf/2;
|
||||||
|
nf4=nf/4;
|
||||||
|
nk=0;
|
||||||
|
while (n>>=1) nk++;
|
||||||
|
norm=1/sqrt(nf);
|
||||||
|
|
||||||
|
k=0;
|
||||||
|
p=(2*M_PI)/nf;
|
||||||
|
for(i=0;i<=nf4;i++) {
|
||||||
|
wfft[i]=cos(k);
|
||||||
|
k+=p;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=0;i<nf;i++) {
|
||||||
|
a=i;
|
||||||
|
c=0;
|
||||||
|
for(j=0;j<nk;j++) {
|
||||||
|
c=(c<<1)|(a&1);
|
||||||
|
a>>=1;
|
||||||
|
}
|
||||||
|
tinv[i]= c<=i ? -1 : c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* r = TRUE : reverse FFT */
|
||||||
|
void fft_calc(complex *x,int n, int r)
|
||||||
|
{
|
||||||
|
int i,j,k,l;
|
||||||
|
complex a,b,c;
|
||||||
|
complex *p,*q;
|
||||||
|
|
||||||
|
/* auto init of coefficients */
|
||||||
|
if (n != nf) {
|
||||||
|
fft_init(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
k=nf2;
|
||||||
|
l=1;
|
||||||
|
do {
|
||||||
|
p=x;
|
||||||
|
q=x+k;
|
||||||
|
i=l;
|
||||||
|
do {
|
||||||
|
j=0;
|
||||||
|
do {
|
||||||
|
a=*p;
|
||||||
|
b=*q;
|
||||||
|
p->re=a.re+b.re;
|
||||||
|
p->im=a.im+b.im;
|
||||||
|
b.re=a.re-b.re;
|
||||||
|
b.im=a.im-b.im;
|
||||||
|
if (j==0) {
|
||||||
|
*q=b;
|
||||||
|
} else if (j==nf4) {
|
||||||
|
if (r) {
|
||||||
|
q->re=b.im;
|
||||||
|
q->im=-b.re;
|
||||||
|
} else {
|
||||||
|
q->re=-b.im;
|
||||||
|
q->im=b.re;
|
||||||
|
}
|
||||||
|
q->re=-b.im;
|
||||||
|
q->im=b.re;
|
||||||
|
} else if (j<nf4) {
|
||||||
|
c.re=wfft[j];
|
||||||
|
c.im=wfft[nf4-j];
|
||||||
|
if (r) c.im=-c.im;
|
||||||
|
q->re=b.re*c.re-b.im*c.im;
|
||||||
|
q->im=b.im*c.re+b.re*c.im;
|
||||||
|
} else {
|
||||||
|
c.re=-wfft[nf2-j];
|
||||||
|
c.im=wfft[j-nf4];
|
||||||
|
if (r) c.im=-c.im;
|
||||||
|
q->re=b.re*c.re-b.im*c.im;
|
||||||
|
q->im=b.im*c.re+b.re*c.im;
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
q++;
|
||||||
|
j+=l;
|
||||||
|
} while (j<nf2);
|
||||||
|
p+=k;
|
||||||
|
q+=k;
|
||||||
|
} while (--i);
|
||||||
|
k>>=1;
|
||||||
|
l<<=1;
|
||||||
|
} while (k);
|
||||||
|
|
||||||
|
for(i=0,p=x;i<nf;i++,p++) {
|
||||||
|
p->re*=norm;
|
||||||
|
p->im*=norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=0,p=x;i<nf;i++,p++) if ((j=tinv[i])!=-1) {
|
||||||
|
a=*p;
|
||||||
|
*p=x[j];
|
||||||
|
x[j]=a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hamming window */
|
||||||
|
|
||||||
|
void calc_hamming(float *ham, int NF)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for(i=0;i<NF;i++) {
|
||||||
|
ham[i]=0.54-0.46*cos((2*M_PI*i)/NF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* slow floating point fft, for testing & init */
|
||||||
|
|
||||||
|
void slow_fft(complex *output, complex *input, int n, int r)
|
||||||
|
{
|
||||||
|
complex coef[n], a, b, c;
|
||||||
|
int i,j;
|
||||||
|
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
float a;
|
||||||
|
a = - 2 * M_PI * i / (float) n;
|
||||||
|
if (r)
|
||||||
|
a = -a;
|
||||||
|
coef[i].re = cos(a);
|
||||||
|
coef[i].im = sin(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
a.re = 0;
|
||||||
|
a.im = 0;
|
||||||
|
for(j=0;j<n;j++) {
|
||||||
|
b = input[j];
|
||||||
|
c = coef[(j * i) % n];
|
||||||
|
a.re += b.re*c.re-b.im*c.im;
|
||||||
|
a.im += b.im*c.re+b.re*c.im;
|
||||||
|
}
|
||||||
|
output[i] = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
|
||||||
|
typedef signed char s8;
|
||||||
|
typedef unsigned char u8;
|
||||||
|
typedef short s16;
|
||||||
|
typedef unsigned short u16;
|
||||||
|
typedef int s32;
|
||||||
|
typedef unsigned int u32;
|
||||||
|
typedef long long s64;
|
||||||
|
typedef unsigned long long u64;
|
||||||
|
|
||||||
|
#define PHASE_BITS 16
|
||||||
|
#define PHASE_BASE (1 << PHASE_BITS)
|
||||||
|
|
||||||
|
#define COS_BITS 14
|
||||||
|
#define COS_BASE (1 << COS_BITS)
|
||||||
|
|
||||||
|
#define COS_TABLE_BITS 13
|
||||||
|
#define COS_TABLE_SIZE (1 << COS_TABLE_BITS)
|
||||||
|
|
||||||
|
extern s16 cos_tab[COS_TABLE_SIZE];
|
||||||
|
|
||||||
|
void dsp_init(void);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* unoptimized DSP C functions */
|
||||||
|
/* XXX: optimize them for each architecture */
|
||||||
|
|
||||||
|
static inline int dsp_cos(int phase)
|
||||||
|
{
|
||||||
|
return cos_tab[(phase >> (PHASE_BITS - COS_TABLE_BITS)) & (COS_TABLE_SIZE-1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int dsp_dot_prod(const s16 *tab1, const s16 *tab2,
|
||||||
|
int n, int sum)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
sum += tab1[i] * tab2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int dsp_norm2(s16 *tab, int n, int sum)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
sum += tab[i] * tab[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void dsp_sar_tab(s16 *tab, int n, int shift)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
tab[i] >>= shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int dsp_max_bits(s16 *tab, int n)
|
||||||
|
{
|
||||||
|
int i, max, v, b;
|
||||||
|
max = 0;
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
v = abs(tab[i]);
|
||||||
|
if (v > max)
|
||||||
|
max = v;
|
||||||
|
}
|
||||||
|
b = 0;
|
||||||
|
while (max != 0) {
|
||||||
|
b++;
|
||||||
|
max>>=1;
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int dsp_sqr(int n)
|
||||||
|
{
|
||||||
|
return n*n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int compute_DFT(s16 *cos_tab, s16 *sin_tab, s16 *x, int k,int n);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
float re,im;
|
||||||
|
} complex;
|
||||||
|
|
||||||
|
/* medium speed FFT */
|
||||||
|
void fft_calc(complex *x,int n, int r);
|
||||||
|
|
||||||
|
/* slow FFT for any size */
|
||||||
|
void slow_fft(complex *output, complex *input, int n, int r);
|
||||||
|
|
||||||
|
/* compute the hamming window */
|
||||||
|
void calc_hamming(float *ham, int NF);
|
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
* DTMF demodulator, inspirated from the multimon project of Thomas
|
||||||
|
* Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu). It is different
|
||||||
|
* in the sense that it uses a true block based DFT estimator.
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DTMF frequencies
|
||||||
|
*
|
||||||
|
* 1209 1336 1477 1633
|
||||||
|
* 697 1 2 3 A
|
||||||
|
* 770 4 5 6 B
|
||||||
|
* 852 7 8 9 C
|
||||||
|
* 941 * 0 # D
|
||||||
|
* */
|
||||||
|
|
||||||
|
static const char *dtmf_transl = "123A456B789C*0#D";
|
||||||
|
|
||||||
|
static int dtmf_freq[] = {
|
||||||
|
1209, 1336, 1477, 1633,
|
||||||
|
697, 770, 852, 941,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SAMPLE_RATE 8000
|
||||||
|
|
||||||
|
/* Each DTMF digit is estimed on N samples by estimating the DFT of
|
||||||
|
the signal. It is quite reliable, but the frequency resolution is
|
||||||
|
not accurate enough to meet the very strict ITU requirements. */
|
||||||
|
|
||||||
|
/* DTMF modulation */
|
||||||
|
|
||||||
|
void DTMF_mod_init(DTMF_mod_state *s)
|
||||||
|
{
|
||||||
|
s->t1 = s->t2 = 0;
|
||||||
|
s->omega1 = 0;
|
||||||
|
s->samples_left = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compute parameters for a new digit */
|
||||||
|
static void compute_params(DTMF_mod_state *s)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
int v,digit,f1,f2;
|
||||||
|
|
||||||
|
/* if we were playing a digit, we make a pause */
|
||||||
|
if (s->omega1 != 0)
|
||||||
|
goto nodigit;
|
||||||
|
|
||||||
|
digit = s->get_digit(s->opaque);
|
||||||
|
if (digit == -1)
|
||||||
|
goto nodigit;
|
||||||
|
p = dtmf_transl;
|
||||||
|
while (*p != digit && *p) p++;
|
||||||
|
if (*p) {
|
||||||
|
v = p - dtmf_transl;
|
||||||
|
f1 = dtmf_freq[v & 3];
|
||||||
|
f2 = dtmf_freq[4 + (v >> 2)];
|
||||||
|
s->omega1 = (PHASE_BASE * f1) / SAMPLE_RATE;
|
||||||
|
s->omega2 = (PHASE_BASE * f2) / SAMPLE_RATE;
|
||||||
|
/* amplitude */
|
||||||
|
s->amp = (int) (pow(10, s->dtmf_level / 20.0) * 32768.0);
|
||||||
|
/* number of samples to play */
|
||||||
|
s->samples_left = (s->digit_length_ms * SAMPLE_RATE) / 1000;
|
||||||
|
} else {
|
||||||
|
nodigit:
|
||||||
|
s->omega1 = 0;
|
||||||
|
s->samples_left = (s->digit_pause_ms * SAMPLE_RATE) / 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DTMF_mod(DTMF_mod_state *s, s16 *samples, unsigned int nb)
|
||||||
|
{
|
||||||
|
int len, t1, t2, amp, i;
|
||||||
|
|
||||||
|
while (nb > 0) {
|
||||||
|
if (s->samples_left == 0) {
|
||||||
|
compute_params(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
len = nb;
|
||||||
|
if (len > s->samples_left)
|
||||||
|
len = s->samples_left;
|
||||||
|
|
||||||
|
if (s->omega1 != 0) {
|
||||||
|
t1 = s->t1;
|
||||||
|
t2 = s->t2;
|
||||||
|
amp = s->amp;
|
||||||
|
for(i=0;i<len;i++) {
|
||||||
|
int v;
|
||||||
|
v = ((dsp_cos(t1) + dsp_cos(t2)) * amp) >> COS_BITS;
|
||||||
|
samples[i] = v;
|
||||||
|
t1 += s->omega1;
|
||||||
|
t2 += s->omega2;
|
||||||
|
}
|
||||||
|
s->t1 = t1;
|
||||||
|
s->t2 = t2;
|
||||||
|
} else {
|
||||||
|
memset(samples, 0, nb * 2); /* silence between digits */
|
||||||
|
}
|
||||||
|
|
||||||
|
nb -= len;
|
||||||
|
samples += len;
|
||||||
|
s->samples_left -= len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DTMF demodulation */
|
||||||
|
|
||||||
|
void DTMF_demod_init(DTMF_demod_state *s)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for(i=0;i<DTMF_N;i++) {
|
||||||
|
s->cos_tab[i] = (int) (cos( 2 * M_PI * i / DTMF_N) * COS_BASE);
|
||||||
|
s->sin_tab[i] = (int) (sin( 2 * M_PI * i / DTMF_N) * COS_BASE);
|
||||||
|
}
|
||||||
|
for(i=0;i<8;i++) {
|
||||||
|
float v;
|
||||||
|
v = (float)dtmf_freq[i] / (float)SAMPLE_RATE * (float)DTMF_N;
|
||||||
|
s->dtmf_coefs[i] = (int)rint(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
s->buf_ptr = 0;
|
||||||
|
s->last_digit = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int find_max(int *val,int n)
|
||||||
|
{
|
||||||
|
int i,j,max;
|
||||||
|
|
||||||
|
j = 0;
|
||||||
|
max = val[0];
|
||||||
|
for(i=1;i<n;i++) {
|
||||||
|
if (val[i] > max) {
|
||||||
|
j = i;
|
||||||
|
max = val[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DTMF_demod(DTMF_demod_state *s,
|
||||||
|
const s16 *samples, unsigned int nb)
|
||||||
|
{
|
||||||
|
int i, j, power[8], power0, v1, v2, digit, bits, p1, p2, p0;
|
||||||
|
|
||||||
|
for(j=0;j<nb;j++) {
|
||||||
|
s->buf[s->buf_ptr++] = samples[j];
|
||||||
|
|
||||||
|
if (s->buf_ptr >= DTMF_N) {
|
||||||
|
/* decision based on one block */
|
||||||
|
bits = dsp_max_bits(s->buf, DTMF_N);
|
||||||
|
if (bits < 8) bits = 8;
|
||||||
|
dsp_sar_tab(s->buf, DTMF_N, bits - 8);
|
||||||
|
|
||||||
|
power0 = dsp_norm2(s->buf, DTMF_N, 0);
|
||||||
|
|
||||||
|
for(i=0;i<8;i++) {
|
||||||
|
power[i] = compute_DFT(s->cos_tab, s->sin_tab, s->buf,
|
||||||
|
s->dtmf_coefs[i], DTMF_N);
|
||||||
|
}
|
||||||
|
|
||||||
|
v1 = find_max(power, 4);
|
||||||
|
v2 = find_max(power + 4, 4);
|
||||||
|
p0 = (int)(power0 * 0.3);
|
||||||
|
p1 = (2 * power[v1]) / DTMF_N;
|
||||||
|
p2 = (2 * power[v2 + 4]) / DTMF_N;
|
||||||
|
#if 0
|
||||||
|
printf("%d %d %d %f %f\n",p0, v1, v2, (float) p1 / p0,(float) p2 / p0);
|
||||||
|
#endif
|
||||||
|
if (p1 > p0 && p2 > p0)
|
||||||
|
digit = dtmf_transl[v1 | (v2 << 2)];
|
||||||
|
else
|
||||||
|
digit = 0;
|
||||||
|
|
||||||
|
if (digit != s->last_digit) {
|
||||||
|
if (digit != 0) {
|
||||||
|
s->put_digit(s->opaque, digit);
|
||||||
|
}
|
||||||
|
s->last_digit = digit;
|
||||||
|
}
|
||||||
|
s->buf_ptr = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
/* modulation */
|
||||||
|
typedef struct {
|
||||||
|
/* parameters */
|
||||||
|
int dtmf_level; /* in dB */
|
||||||
|
int digit_length_ms;
|
||||||
|
int digit_pause_ms;
|
||||||
|
void *opaque;
|
||||||
|
int (*get_digit)(void *opaque);
|
||||||
|
|
||||||
|
/* internal state */
|
||||||
|
int omega1,omega2,t1,t2;
|
||||||
|
int samples_left;
|
||||||
|
int amp;
|
||||||
|
} DTMF_mod_state;
|
||||||
|
|
||||||
|
void DTMF_mod_init(DTMF_mod_state *s);
|
||||||
|
void DTMF_mod(DTMF_mod_state *s, s16 *samples, unsigned int nb);
|
||||||
|
|
||||||
|
/* demodulation */
|
||||||
|
#define DTMF_N 205
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* parameters */
|
||||||
|
void *opaque;
|
||||||
|
void (*put_digit)(void *opaque, int digit);
|
||||||
|
|
||||||
|
/* internal state */
|
||||||
|
s16 buf[DTMF_N];
|
||||||
|
int buf_ptr;
|
||||||
|
s16 cos_tab[DTMF_N];
|
||||||
|
s16 sin_tab[DTMF_N];
|
||||||
|
int last_digit;
|
||||||
|
int dtmf_coefs[8]; /* coefficient number of the DFT */
|
||||||
|
} DTMF_demod_state;
|
||||||
|
|
||||||
|
void DTMF_demod_init(DTMF_demod_state *s);
|
||||||
|
void DTMF_demod(DTMF_demod_state *s,
|
||||||
|
const s16 *samples, unsigned int nb);
|
|
@ -0,0 +1,259 @@
|
||||||
|
/*
|
||||||
|
* generic FSK modulator & demodulator
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999,2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*/
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
void FSK_mod_init(FSK_mod_state *s)
|
||||||
|
{
|
||||||
|
int b;
|
||||||
|
|
||||||
|
s->omega[0] = (PHASE_BASE * s->f_lo) / s->sample_rate;
|
||||||
|
s->omega[1] = (PHASE_BASE * s->f_hi) / s->sample_rate;
|
||||||
|
s->baud_incr = (s->baud_rate * 0x10000) / s->sample_rate;
|
||||||
|
s->phase = 0;
|
||||||
|
s->baud_frac = 0;
|
||||||
|
b = 0;
|
||||||
|
s->current_bit = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSK_mod(FSK_mod_state *s, s16 *samples, unsigned int nb)
|
||||||
|
{
|
||||||
|
int phase,baud_frac,b,i;
|
||||||
|
|
||||||
|
phase = s->phase;
|
||||||
|
baud_frac = s->baud_frac;
|
||||||
|
b = s->current_bit;
|
||||||
|
|
||||||
|
for(i=0;i<nb;i++) {
|
||||||
|
baud_frac += s->baud_incr;
|
||||||
|
if (baud_frac >= 0x10000) {
|
||||||
|
baud_frac -= 0x10000;
|
||||||
|
b = s->get_bit(s->opaque);
|
||||||
|
}
|
||||||
|
samples[i] = dsp_cos(phase);
|
||||||
|
phase += s->omega[b];
|
||||||
|
}
|
||||||
|
s->phase = phase;
|
||||||
|
s->baud_frac = baud_frac;
|
||||||
|
s->current_bit = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSK_demod_init(FSK_demod_state *s)
|
||||||
|
{
|
||||||
|
float phase;
|
||||||
|
int i, a;
|
||||||
|
|
||||||
|
s->baud_incr = (s->baud_rate * 0x10000) / s->sample_rate;
|
||||||
|
s->baud_pll = 0;
|
||||||
|
s->baud_pll_adj = s->baud_incr / 4;
|
||||||
|
|
||||||
|
s->filter_size = s->sample_rate / s->baud_rate;
|
||||||
|
|
||||||
|
memset(s->filter_buf, 0, sizeof(s->filter_buf));
|
||||||
|
s->buf_ptr = s->filter_size;
|
||||||
|
s->lastsample = 0;
|
||||||
|
|
||||||
|
/* compute the filters */
|
||||||
|
for(i=0;i<s->filter_size;i++) {
|
||||||
|
phase = 2 * M_PI * s->f_lo * i / (float)s->sample_rate;
|
||||||
|
s->filter_lo_i[i] = (int) (cos(phase) * COS_BASE);
|
||||||
|
s->filter_lo_q[i] = (int) (sin(phase) * COS_BASE);
|
||||||
|
|
||||||
|
phase = 2 * M_PI * s->f_hi * i / (float)s->sample_rate;
|
||||||
|
s->filter_hi_i[i] = (int) (cos(phase) * COS_BASE);
|
||||||
|
s->filter_hi_q[i] = (int) (sin(phase) * COS_BASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
s->shift = -2;
|
||||||
|
a = s->filter_size;
|
||||||
|
while (a != 0) {
|
||||||
|
s->shift++;
|
||||||
|
a /= 2;
|
||||||
|
}
|
||||||
|
printf("shift=%d\n", s->shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSK_demod(FSK_demod_state *s, const s16 *samples, unsigned int nb)
|
||||||
|
{
|
||||||
|
int buf_ptr, corr, newsample, baud_pll, i;
|
||||||
|
int sum;
|
||||||
|
|
||||||
|
baud_pll = s->baud_pll;
|
||||||
|
buf_ptr = s->buf_ptr;
|
||||||
|
|
||||||
|
for(i=0;i<nb;i++) {
|
||||||
|
/* add a new sample in the demodulation filter */
|
||||||
|
s->filter_buf[buf_ptr++] = samples[i] >> s->shift;
|
||||||
|
if (buf_ptr == FSK_FILTER_BUF_SIZE) {
|
||||||
|
memmove(s->filter_buf,
|
||||||
|
s->filter_buf + FSK_FILTER_BUF_SIZE - s->filter_size,
|
||||||
|
s->filter_size * sizeof(s16));
|
||||||
|
buf_ptr = s->filter_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* non coherent FSK demodulation - not optimal, but it seems
|
||||||
|
very difficult to do another way */
|
||||||
|
corr = dsp_dot_prod(s->filter_buf + buf_ptr - s->filter_size,
|
||||||
|
s->filter_hi_i, s->filter_size, 0);
|
||||||
|
corr = corr >> COS_BITS;
|
||||||
|
sum = corr * corr;
|
||||||
|
|
||||||
|
corr = dsp_dot_prod(s->filter_buf + buf_ptr - s->filter_size,
|
||||||
|
s->filter_hi_q, s->filter_size, 0);
|
||||||
|
corr = corr >> COS_BITS;
|
||||||
|
sum += corr * corr;
|
||||||
|
|
||||||
|
corr = dsp_dot_prod(s->filter_buf + buf_ptr - s->filter_size,
|
||||||
|
s->filter_lo_i, s->filter_size, 0);
|
||||||
|
corr = corr >> COS_BITS;
|
||||||
|
sum -= corr * corr;
|
||||||
|
|
||||||
|
corr = dsp_dot_prod(s->filter_buf + buf_ptr - s->filter_size,
|
||||||
|
s->filter_lo_q, s->filter_size, 0);
|
||||||
|
corr = corr >> COS_BITS;
|
||||||
|
sum -= corr * corr;
|
||||||
|
|
||||||
|
lm_dump_sample(CHANNEL_SAMPLESYNC, sum / 32768.0);
|
||||||
|
// printf("sum=%0.3f\n", sum / 65536.0);
|
||||||
|
newsample = sum > 0;
|
||||||
|
|
||||||
|
/* baud PLL synchronisation : when we see a transition of
|
||||||
|
frequency, we tend to modify the baud phase so that it is
|
||||||
|
in the middle of two bits */
|
||||||
|
if (s->lastsample != newsample) {
|
||||||
|
s->lastsample = newsample;
|
||||||
|
// printf("pll=%0.3f (%d)\n", baud_pll / 65536.0, newsample);
|
||||||
|
if (baud_pll < 0x8000)
|
||||||
|
baud_pll += s->baud_pll_adj;
|
||||||
|
else
|
||||||
|
baud_pll -= s->baud_pll_adj;
|
||||||
|
}
|
||||||
|
|
||||||
|
baud_pll += s->baud_incr;
|
||||||
|
|
||||||
|
if (baud_pll >= 0x10000) {
|
||||||
|
baud_pll -= 0x10000;
|
||||||
|
// printf("baud=%f (%d)\n", baud_pll / 65536.0, s->lastsample);
|
||||||
|
s->put_bit(s->opaque, s->lastsample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s->baud_pll = baud_pll;
|
||||||
|
s->buf_ptr = buf_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* test for FSK using V21 or V23 */
|
||||||
|
|
||||||
|
#define NB_SAMPLES 40
|
||||||
|
|
||||||
|
#define MAXDELAY 32
|
||||||
|
|
||||||
|
static int tx_bits[MAXDELAY], tx_ptr = 0, rx_ptr = 0;
|
||||||
|
static int tx_blank = 32;
|
||||||
|
|
||||||
|
/* transmit random bits with a sync header (31 ones, 1 zero) */
|
||||||
|
static int test_get_bit(void *opaque)
|
||||||
|
{
|
||||||
|
int bit;
|
||||||
|
|
||||||
|
if (tx_blank != 0) {
|
||||||
|
/* send 1 at the beginning for synchronization */
|
||||||
|
bit = (tx_blank > 1);
|
||||||
|
tx_blank--;
|
||||||
|
} else {
|
||||||
|
bit = random() % 2;
|
||||||
|
tx_bits[tx_ptr] = bit;
|
||||||
|
if (++tx_ptr == MAXDELAY)
|
||||||
|
tx_ptr = 0;
|
||||||
|
}
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nb_bits = 0, errors = 0, sync_count = 0, got_sync = 0;
|
||||||
|
|
||||||
|
static void test_put_bit(void *opaque, int bit)
|
||||||
|
{
|
||||||
|
int tbit;
|
||||||
|
|
||||||
|
if (!got_sync) {
|
||||||
|
|
||||||
|
if (bit) {
|
||||||
|
sync_count++;
|
||||||
|
} else {
|
||||||
|
if (sync_count > 16)
|
||||||
|
got_sync = 1;
|
||||||
|
sync_count = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tbit = tx_bits[rx_ptr];
|
||||||
|
if (++rx_ptr == MAXDELAY)
|
||||||
|
rx_ptr = 0;
|
||||||
|
if (bit != tbit) {
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
nb_bits++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSK_test(int do_v23)
|
||||||
|
{
|
||||||
|
FSK_mod_state tx;
|
||||||
|
FSK_demod_state rx;
|
||||||
|
int err, calling;
|
||||||
|
struct LineModelState *line_state;
|
||||||
|
s16 buf[NB_SAMPLES];
|
||||||
|
s16 buf1[NB_SAMPLES];
|
||||||
|
s16 buf2[NB_SAMPLES];
|
||||||
|
s16 buf3[NB_SAMPLES];
|
||||||
|
FILE *f1;
|
||||||
|
|
||||||
|
err = lm_display_init();
|
||||||
|
if (err < 0) {
|
||||||
|
fprintf(stderr, "Could not init X display\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
line_state = line_model_init();
|
||||||
|
|
||||||
|
f1 = fopen("cal.sw", "wb");
|
||||||
|
if (f1 == NULL) {
|
||||||
|
perror("cal.sw");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
calling = 0;
|
||||||
|
if (do_v23) {
|
||||||
|
V23_mod_init(&tx, calling, test_get_bit, NULL);
|
||||||
|
V23_demod_init(&rx, 1 - calling, test_put_bit, NULL);
|
||||||
|
} else {
|
||||||
|
V21_mod_init(&tx, calling, test_get_bit, NULL);
|
||||||
|
V21_demod_init(&rx, 1 - calling, test_put_bit, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
nb_bits = 0;
|
||||||
|
errors = 0;
|
||||||
|
for(;;) {
|
||||||
|
if (lm_display_poll_event())
|
||||||
|
break;
|
||||||
|
|
||||||
|
FSK_mod(&tx, buf, NB_SAMPLES);
|
||||||
|
memset(buf3, 0, sizeof(buf3));
|
||||||
|
|
||||||
|
line_model(line_state, buf1, buf, buf2, buf3, NB_SAMPLES);
|
||||||
|
|
||||||
|
fwrite(buf, 1, NB_SAMPLES * 2, f1);
|
||||||
|
|
||||||
|
FSK_demod(&rx, buf1, NB_SAMPLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f1);
|
||||||
|
|
||||||
|
printf("errors=%d nb_bits=%d Pe=%f\n",
|
||||||
|
errors, nb_bits, (float) errors / (float)nb_bits);
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
#ifndef FSK_H
|
||||||
|
#define FSK_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* parameters */
|
||||||
|
int f_lo,f_hi;
|
||||||
|
int sample_rate;
|
||||||
|
int baud_rate;
|
||||||
|
|
||||||
|
/* local variables */
|
||||||
|
int phase, baud_frac, baud_incr;
|
||||||
|
int omega[2];
|
||||||
|
int current_bit;
|
||||||
|
void *opaque;
|
||||||
|
get_bit_func get_bit;
|
||||||
|
} FSK_mod_state;
|
||||||
|
|
||||||
|
/* max = 106 for 75 bauds */
|
||||||
|
#define FSK_FILTER_SIZE 128
|
||||||
|
#define FSK_FILTER_BUF_SIZE 256
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* parameters */
|
||||||
|
int f_lo,f_hi;
|
||||||
|
int sample_rate;
|
||||||
|
int baud_rate;
|
||||||
|
|
||||||
|
/* local variables */
|
||||||
|
int filter_size;
|
||||||
|
s16 filter_lo_i[FSK_FILTER_SIZE];
|
||||||
|
s16 filter_lo_q[FSK_FILTER_SIZE];
|
||||||
|
|
||||||
|
s16 filter_hi_i[FSK_FILTER_SIZE];
|
||||||
|
s16 filter_hi_q[FSK_FILTER_SIZE];
|
||||||
|
|
||||||
|
s16 filter_buf[FSK_FILTER_BUF_SIZE];
|
||||||
|
int buf_ptr;
|
||||||
|
|
||||||
|
int baud_incr;
|
||||||
|
int baud_pll, baud_pll_adj, baud_pll_threshold;
|
||||||
|
int lastsample;
|
||||||
|
int shift;
|
||||||
|
|
||||||
|
void *opaque;
|
||||||
|
put_bit_func put_bit;
|
||||||
|
} FSK_demod_state;
|
||||||
|
|
||||||
|
void FSK_mod_init(FSK_mod_state *s);
|
||||||
|
void FSK_mod(FSK_mod_state *s, s16 *samples, unsigned int nb);
|
||||||
|
void FSK_demod_init(FSK_demod_state *s);
|
||||||
|
void FSK_demod(FSK_demod_state *s, const s16 *samples, unsigned int nb);
|
||||||
|
|
||||||
|
void FSK_test(int do_v23);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,579 @@
|
||||||
|
/*
|
||||||
|
* The Generic Linux Soft Modem
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999,2000 Fabrice Bellard.
|
||||||
|
* Copyright (c) 1999 Pavel Machek
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* This implementation is totally clean room. It was written by
|
||||||
|
* reading the ITU specification and by using basic signal processing
|
||||||
|
* knowledge.
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include "lm.h"
|
||||||
|
#include "v34.h"
|
||||||
|
#include "v90.h"
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
|
int lm_debug = 0;
|
||||||
|
int debug_state = 0;
|
||||||
|
|
||||||
|
/* default parameters */
|
||||||
|
static LinModemConfig default_lm_config =
|
||||||
|
{
|
||||||
|
pulse_dial: 0,
|
||||||
|
dtmf_level: -9,
|
||||||
|
dtmf_digit_length: 150,
|
||||||
|
dtmf_pause_length: 100,
|
||||||
|
available_modulations: V8_MOD_V21 | V8_MOD_V23,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* fifo handling */
|
||||||
|
|
||||||
|
void sm_init_fifo(struct sm_fifo *f, u8 *buf, int size)
|
||||||
|
{
|
||||||
|
f->sptr = buf;
|
||||||
|
f->eptr = buf + size;
|
||||||
|
f->wptr = f->rptr = f->sptr;
|
||||||
|
f->size = 0;
|
||||||
|
f->max_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sm_flush(struct sm_fifo *f)
|
||||||
|
{
|
||||||
|
f->wptr = f->rptr = f->sptr;
|
||||||
|
f->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sm_size(struct sm_fifo *f)
|
||||||
|
{
|
||||||
|
return f->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sm_put_bit(struct sm_fifo *f, int v)
|
||||||
|
{
|
||||||
|
if (f->size < f->max_size) {
|
||||||
|
*f->wptr++ = v;
|
||||||
|
if (f->wptr == f->eptr) f->wptr = f->sptr;
|
||||||
|
f->size++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* put bits, from MSB to LSB */
|
||||||
|
void sm_put_bits(struct sm_fifo *f, int v, int n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for(i=n-1;i>=0;i--) {
|
||||||
|
sm_put_bit(f, (v >> i) & 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int sm_peek_bit(struct sm_fifo *f)
|
||||||
|
{
|
||||||
|
if (f->size > 0)
|
||||||
|
return *f->rptr;
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sm_get_bit(struct sm_fifo *f)
|
||||||
|
{
|
||||||
|
int v;
|
||||||
|
|
||||||
|
if (f->size > 0) {
|
||||||
|
f->size--;
|
||||||
|
v = *f->rptr++;
|
||||||
|
if (f->rptr == f->eptr) f->rptr = f->sptr;
|
||||||
|
return v;
|
||||||
|
} else {
|
||||||
|
/* fifo empty */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* return -1 if not enough bits */
|
||||||
|
int sm_get_bits(struct sm_fifo *f, int n)
|
||||||
|
{
|
||||||
|
int v,i;
|
||||||
|
|
||||||
|
if (f->size < n)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
v = 0;
|
||||||
|
for(i=n-1;i>=0;i--) {
|
||||||
|
v |= (sm_get_bit(f) << i);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* timer handling */
|
||||||
|
|
||||||
|
/* note: the current time handling is horrible because we use a global
|
||||||
|
state which is initialized when entering in sm_process() */
|
||||||
|
static unsigned int sim_time;
|
||||||
|
|
||||||
|
|
||||||
|
/* current time, in samples */
|
||||||
|
int sm_time(void)
|
||||||
|
{
|
||||||
|
return sim_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* delay is in ms */
|
||||||
|
void sm_set_timer(struct sm_timer *t, int delay)
|
||||||
|
{
|
||||||
|
t->timeout = sm_time() + (delay * 8000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* return 1 if timer expired */
|
||||||
|
int sm_check_timer(struct sm_timer *t)
|
||||||
|
{
|
||||||
|
long timeout;
|
||||||
|
|
||||||
|
timeout = sm_time();
|
||||||
|
return (timeout >= t->timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Main modem state machine. It handles the dialing & rings, and then
|
||||||
|
call the corresponding protocol handlers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
char *sm_states_str[] = {
|
||||||
|
#define TAG(s) #s ,
|
||||||
|
#include "lmstates.h"
|
||||||
|
};
|
||||||
|
|
||||||
|
static int dtmf_get_digit(void *opaque)
|
||||||
|
{
|
||||||
|
struct sm_state *sm = opaque;
|
||||||
|
if (sm->dtmf_ptr < sm->dtmf_len) {
|
||||||
|
return sm->call_num[sm->dtmf_ptr++];
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dtmf_put_digit(void *opaque, int digit)
|
||||||
|
{
|
||||||
|
printf("DTMF: got digit '%c'\n", digit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sm_process(struct sm_state *sm, s16 *output, s16 *input, int nb_samples)
|
||||||
|
{
|
||||||
|
/* XXX: time hack */
|
||||||
|
sim_time = sm->time;
|
||||||
|
|
||||||
|
/* modulation */
|
||||||
|
switch(sm->state) {
|
||||||
|
case SM_DTMF_DIAL_WAIT:
|
||||||
|
case SM_DTMF_DIAL_WAIT1:
|
||||||
|
DTMF_mod(&sm->dtmf_tx, output, nb_samples);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
memset(output, 0, nb_samples * sizeof(s16));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* demodulation */
|
||||||
|
switch(sm->state) {
|
||||||
|
case SM_TEST_RING2:
|
||||||
|
DTMF_demod(&sm->dtmf_rx, input, nb_samples);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write state transition */
|
||||||
|
if (lm_debug) {
|
||||||
|
if (sm->state != sm->debug_laststate) {
|
||||||
|
sm->debug_laststate = sm->state;
|
||||||
|
printf("%s: state: %s\n", sm->name, sm_states_str[sm->state]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(sm->state) {
|
||||||
|
|
||||||
|
/* nothing to do (except waiting for a connection) */
|
||||||
|
case SM_IDLE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SM_GO_ONHOOK:
|
||||||
|
{
|
||||||
|
sm->hw->set_offhook(sm->hw_state, 0);
|
||||||
|
sm->state = SM_IDLE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* calling modem */
|
||||||
|
case SM_CALL:
|
||||||
|
{
|
||||||
|
sm->calling = 1;
|
||||||
|
sm->hangup_request = 0;
|
||||||
|
sm->state = SM_DTMF_DIAL;
|
||||||
|
sm->hw->set_offhook(sm->hw_state, 1);
|
||||||
|
sm_set_timer(&sm->dtmf_timer, 2000);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SM_DTMF_DIAL:
|
||||||
|
if (sm->hangup_request) {
|
||||||
|
sm->state = SM_GO_ONHOOK;
|
||||||
|
} else if (sm_check_timer(&sm->dtmf_timer)) {
|
||||||
|
DTMF_mod_state *p = &sm->dtmf_tx;
|
||||||
|
|
||||||
|
sm->dtmf_ptr = 0;
|
||||||
|
sm->dtmf_len = strlen(sm->call_num);
|
||||||
|
|
||||||
|
p->get_digit = dtmf_get_digit;
|
||||||
|
p->opaque = sm;
|
||||||
|
p->dtmf_level = sm->lm_config->dtmf_level;
|
||||||
|
p->digit_length_ms = sm->lm_config->dtmf_digit_length;
|
||||||
|
p->digit_pause_ms = sm->lm_config->dtmf_pause_length;
|
||||||
|
DTMF_mod_init(p);
|
||||||
|
|
||||||
|
sm->state = SM_DTMF_DIAL_WAIT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SM_DTMF_DIAL_WAIT:
|
||||||
|
{
|
||||||
|
if (sm->dtmf_ptr >= sm->dtmf_len) {
|
||||||
|
/* wait some time after dialing */
|
||||||
|
sm_set_timer(&sm->dtmf_timer, 1000);
|
||||||
|
sm->state = SM_DTMF_DIAL_WAIT1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SM_DTMF_DIAL_WAIT1:
|
||||||
|
{
|
||||||
|
if (sm_check_timer(&sm->dtmf_timer)) {
|
||||||
|
/* start of V8 */
|
||||||
|
V8_init(&sm->u.v8_state, 1, sm->lm_config->available_modulations);
|
||||||
|
sm->state = SM_V8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* answer modem */
|
||||||
|
|
||||||
|
/* wait 2 sec until ring detected (for simulation only) */
|
||||||
|
/* we try to recognize the DTMF value for fun :-) */
|
||||||
|
case SM_TEST_RING:
|
||||||
|
{
|
||||||
|
sm->calling = 0;
|
||||||
|
sm->dtmf_rx.opaque = sm;
|
||||||
|
sm->dtmf_rx.put_digit = dtmf_put_digit;
|
||||||
|
DTMF_demod_init(&sm->dtmf_rx);
|
||||||
|
|
||||||
|
sm_set_timer(&sm->ring_timer, 5000);
|
||||||
|
sm->state = SM_TEST_RING2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SM_TEST_RING2:
|
||||||
|
{
|
||||||
|
if (sm_check_timer(&sm->ring_timer)) {
|
||||||
|
sm->state = SM_RECEIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* entry point to receive a connection */
|
||||||
|
|
||||||
|
case SM_RECEIVE:
|
||||||
|
{
|
||||||
|
sm->calling = 0;
|
||||||
|
sm->hangup_request = 0;
|
||||||
|
sm->hw->set_offhook(sm->hw_state, 1);
|
||||||
|
V8_init(&sm->u.v8_state, 0, sm->lm_config->available_modulations);
|
||||||
|
sm->state = SM_V8;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* V8 handling (both calling & receive) */
|
||||||
|
case SM_V8:
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
if (sm->hangup_request) {
|
||||||
|
printf("ddezde\n");
|
||||||
|
sm->state = SM_GO_ONHOOK;
|
||||||
|
} else {
|
||||||
|
ret = V8_process(&sm->u.v8_state, output, input, nb_samples);
|
||||||
|
switch(ret) {
|
||||||
|
case V8_MOD_HANGUP:
|
||||||
|
sm->state = SM_GO_ONHOOK;
|
||||||
|
break;
|
||||||
|
case V8_MOD_V21:
|
||||||
|
V21_init(&sm->u.v21_state, sm->calling,
|
||||||
|
serial_get_bit, serial_put_bit, sm);
|
||||||
|
sm->state = SM_V21;
|
||||||
|
break;
|
||||||
|
case V8_MOD_V23:
|
||||||
|
V23_init(&sm->u.v23_state, sm->calling,
|
||||||
|
serial_get_bit, serial_put_bit, sm);
|
||||||
|
sm->state = SM_V23;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* V21 handling (both calling & receive) */
|
||||||
|
case SM_V21:
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
ret = V21_process(&sm->u.v21_state, output, input, nb_samples);
|
||||||
|
if (ret || sm->hangup_request)
|
||||||
|
sm->state = SM_GO_ONHOOK;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* V23 handling (both calling & receive) */
|
||||||
|
case SM_V23:
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
ret = V23_process(&sm->u.v23_state, output, input, nb_samples);
|
||||||
|
if (ret || sm->hangup_request)
|
||||||
|
sm->state = SM_GO_ONHOOK;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XXX: time hack */
|
||||||
|
sm->time = sim_time + nb_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send a dial request.
|
||||||
|
*/
|
||||||
|
int lm_start_dial(struct sm_state *s, int pulse, const char *number)
|
||||||
|
{
|
||||||
|
if (s->state != SM_IDLE)
|
||||||
|
return -1;
|
||||||
|
s->pulse_dial = pulse;
|
||||||
|
strcpy(s->call_num, number);
|
||||||
|
s->state = SM_CALL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send a receive request (for example to respond to a ring)
|
||||||
|
*/
|
||||||
|
int lm_start_receive(struct sm_state *s)
|
||||||
|
{
|
||||||
|
if (s->state != SM_IDLE)
|
||||||
|
return -1;
|
||||||
|
s->state = SM_RECEIVE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* disconnect the modem
|
||||||
|
*/
|
||||||
|
int lm_hangup(struct sm_state *s)
|
||||||
|
{
|
||||||
|
if (s->state == SM_IDLE)
|
||||||
|
return -1;
|
||||||
|
s->hangup_request = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* return a simplified state of the modem.
|
||||||
|
*/
|
||||||
|
enum lm_get_state_val lm_get_state(struct sm_state *s)
|
||||||
|
{
|
||||||
|
switch(s->state) {
|
||||||
|
case SM_IDLE:
|
||||||
|
return LM_STATE_IDLE;
|
||||||
|
case SM_V21:
|
||||||
|
case SM_V23:
|
||||||
|
return LM_STATE_CONNECTED;
|
||||||
|
default:
|
||||||
|
return LM_STATE_CONNECTING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void lm_init(struct sm_state *sm, struct sm_hw_info *hw, const char *name)
|
||||||
|
{
|
||||||
|
memset(sm, 0, sizeof(*sm));
|
||||||
|
sm->hw = hw;
|
||||||
|
|
||||||
|
/* pretty name */
|
||||||
|
strcpy(sm->name, name);
|
||||||
|
|
||||||
|
/* init fifos */
|
||||||
|
sm_init_fifo(&sm->tx_fifo, sm->tx_fifo_buf, SM_FIFO_SIZE);
|
||||||
|
sm_init_fifo(&sm->rx_fifo, sm->rx_fifo_buf, SM_FIFO_SIZE);
|
||||||
|
|
||||||
|
/* we open the hardware driver */
|
||||||
|
sm->hw_state = malloc(sizeof(struct lm_interface_state));
|
||||||
|
sm->hw_state->sm = sm;
|
||||||
|
sm->hw->open(sm->hw_state);
|
||||||
|
|
||||||
|
sm->debug_laststate = -1;
|
||||||
|
sm->state = SM_IDLE;
|
||||||
|
|
||||||
|
/* config */
|
||||||
|
sm->lm_config = &default_lm_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sigusr1_debug(int dummy)
|
||||||
|
{
|
||||||
|
printf( "<<<Debugging signal came>>>\n" );
|
||||||
|
debug_state = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void help(void)
|
||||||
|
{
|
||||||
|
printf("Linux Generic Software Modem\n"
|
||||||
|
"Copyright (c) 1999, 2000 Fabrice Bellard\n"
|
||||||
|
"\n"
|
||||||
|
"usage: lm [options]"
|
||||||
|
"Test options:\n"
|
||||||
|
"-v : verbose mode (additive)\n"
|
||||||
|
"-s : modem test with the line simulator\n"
|
||||||
|
"-m mod: test the modulation 'mod'. 'mod' can be:\n"
|
||||||
|
" v21, v23, v22, v34, v90\n"
|
||||||
|
"\n"
|
||||||
|
"Sound card support\n"
|
||||||
|
"-t : use sound card as modem\n"
|
||||||
|
"\n"
|
||||||
|
"LTmodem support:\n"
|
||||||
|
"-a : test answer mode with ltmodem\n"
|
||||||
|
"-c command: use 'command' as modem driver\n"
|
||||||
|
"-d number: dial 'number'\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
MODE_NONE,
|
||||||
|
MODE_LINESIM,
|
||||||
|
MODE_V21TEST,
|
||||||
|
MODE_V22TEST,
|
||||||
|
MODE_V23TEST,
|
||||||
|
MODE_V34TEST,
|
||||||
|
MODE_V90TEST,
|
||||||
|
MODE_LTMODEM_CALL,
|
||||||
|
MODE_LTMODEM_ANSWER,
|
||||||
|
MODE_SOUNDCARD,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern char *modem_command, *dial_number;
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int c, mode, calling;
|
||||||
|
|
||||||
|
signal(SIGUSR1, sigusr1_debug);
|
||||||
|
|
||||||
|
mode = MODE_NONE;
|
||||||
|
calling = 0;
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
c = getopt(argc, argv, "hvstrac:d:m:");
|
||||||
|
if (c == -1) break;
|
||||||
|
switch(c) {
|
||||||
|
case 'v':
|
||||||
|
lm_debug++;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
if (!strcasecmp(optarg, "v21"))
|
||||||
|
mode = MODE_V21TEST;
|
||||||
|
else if (!strcasecmp(optarg, "v22"))
|
||||||
|
mode = MODE_V22TEST;
|
||||||
|
else if (!strcasecmp(optarg, "v23"))
|
||||||
|
mode = MODE_V23TEST;
|
||||||
|
else if (!strcasecmp(optarg, "v34"))
|
||||||
|
mode = MODE_V34TEST;
|
||||||
|
else if (!strcasecmp(optarg, "v90"))
|
||||||
|
mode = MODE_V90TEST;
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "incorrect modulation: '%s'\n", optarg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
mode = MODE_LINESIM;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
mode = MODE_SOUNDCARD;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* LTmodem options */
|
||||||
|
case 'c':
|
||||||
|
modem_command = optarg;
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
break;
|
||||||
|
case 'a': /* "Answer" mode */
|
||||||
|
mode = MODE_LTMODEM_ANSWER;
|
||||||
|
break;
|
||||||
|
case 'd': /* Dial given number and exit mode */
|
||||||
|
mode = MODE_LTMODEM_CALL;
|
||||||
|
dial_number = optarg;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
help();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == MODE_NONE) {
|
||||||
|
help();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
srandom(0); /* we want a deterministic test */
|
||||||
|
dsp_init();
|
||||||
|
V34_static_init();
|
||||||
|
|
||||||
|
switch(mode) {
|
||||||
|
case MODE_V21TEST:
|
||||||
|
FSK_test(0);
|
||||||
|
break;
|
||||||
|
case MODE_V22TEST:
|
||||||
|
V22_test();
|
||||||
|
break;
|
||||||
|
case MODE_V23TEST:
|
||||||
|
FSK_test(1);
|
||||||
|
break;
|
||||||
|
case MODE_V34TEST:
|
||||||
|
V34_test();
|
||||||
|
break;
|
||||||
|
case MODE_V90TEST:
|
||||||
|
V90_test();
|
||||||
|
break;
|
||||||
|
case MODE_LINESIM:
|
||||||
|
line_simulate();
|
||||||
|
break;
|
||||||
|
case MODE_LTMODEM_CALL:
|
||||||
|
case MODE_LTMODEM_ANSWER:
|
||||||
|
real_test(mode == MODE_LTMODEM_CALL);
|
||||||
|
system("ltmodem -c");
|
||||||
|
break;
|
||||||
|
case MODE_SOUNDCARD:
|
||||||
|
soundcard_modem();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,213 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "dsp.h"
|
||||||
|
|
||||||
|
#define LM_VERSION "0.2.5"
|
||||||
|
|
||||||
|
/* bit fifo */
|
||||||
|
|
||||||
|
struct sm_fifo {
|
||||||
|
unsigned char *sptr, *wptr, *rptr, *eptr;
|
||||||
|
int size, max_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
void sm_flush(struct sm_fifo *f);
|
||||||
|
int sm_size(struct sm_fifo *f);
|
||||||
|
void sm_put_bit(struct sm_fifo *f, int v);
|
||||||
|
void sm_put_bits(struct sm_fifo *f, int v, int n);
|
||||||
|
int sm_get_bit(struct sm_fifo *f);
|
||||||
|
int sm_get_bits(struct sm_fifo *f, int n);
|
||||||
|
void sm_init_fifo(struct sm_fifo *f, u8 *buf, int size);
|
||||||
|
|
||||||
|
/* bit I/O for data pumps */
|
||||||
|
typedef void (*put_bit_func)(void *opaque, int bit);
|
||||||
|
typedef int (*get_bit_func)(void *opaque);
|
||||||
|
|
||||||
|
/* timer */
|
||||||
|
struct sm_timer {
|
||||||
|
long timeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
int sm_time(void);
|
||||||
|
void sm_set_timer(struct sm_timer *t, int delay);
|
||||||
|
int sm_check_timer(struct sm_timer *t);
|
||||||
|
|
||||||
|
/* debug */
|
||||||
|
extern int lm_debug;
|
||||||
|
extern char *sm_states_str[];
|
||||||
|
|
||||||
|
/* protocol description */
|
||||||
|
|
||||||
|
#include "dtmf.h"
|
||||||
|
#include "fsk.h"
|
||||||
|
#include "v21.h"
|
||||||
|
#include "v22.h"
|
||||||
|
#include "v23.h"
|
||||||
|
#include "v8.h"
|
||||||
|
#include "v34.h"
|
||||||
|
|
||||||
|
/* modem state */
|
||||||
|
#define SM_FIFO_SIZE 4096
|
||||||
|
|
||||||
|
struct sm_state {
|
||||||
|
/* pretty name of the modem (to debug) */
|
||||||
|
char name[16];
|
||||||
|
|
||||||
|
struct sm_hw_info *hw;
|
||||||
|
struct lm_interface_state *hw_state;
|
||||||
|
|
||||||
|
/* bytes to be transmitted */
|
||||||
|
struct sm_fifo tx_fifo;
|
||||||
|
u8 tx_fifo_buf[SM_FIFO_SIZE];
|
||||||
|
|
||||||
|
/* received chars */
|
||||||
|
struct sm_fifo rx_fifo;
|
||||||
|
u8 rx_fifo_buf[SM_FIFO_SIZE];
|
||||||
|
|
||||||
|
/* true if we are the caller */
|
||||||
|
int calling;
|
||||||
|
/* phone number to call */
|
||||||
|
char call_num[64];
|
||||||
|
int pulse_dial; /* TRUE if we must use pulse dialing */
|
||||||
|
|
||||||
|
/* dialing */
|
||||||
|
struct sm_timer dtmf_timer;
|
||||||
|
DTMF_mod_state dtmf_tx;
|
||||||
|
int dtmf_ptr, dtmf_len; /* pointer in call_num */
|
||||||
|
|
||||||
|
/* dtmf receive: for testing (or voice mode in the future) */
|
||||||
|
struct sm_timer ring_timer;
|
||||||
|
DTMF_demod_state dtmf_rx;
|
||||||
|
|
||||||
|
/* modulation state */
|
||||||
|
union {
|
||||||
|
V8State v8_state;
|
||||||
|
V21State v21_state;
|
||||||
|
V23State v23_state;
|
||||||
|
} u;
|
||||||
|
|
||||||
|
/* serial state */
|
||||||
|
int serial_data_bits; /* 5 to 8 */
|
||||||
|
int serial_parity, serial_use_parity;
|
||||||
|
int serial_wordsize;
|
||||||
|
|
||||||
|
unsigned int serial_buf;
|
||||||
|
int serial_cnt;
|
||||||
|
|
||||||
|
unsigned int serial_tx_buf;
|
||||||
|
int serial_tx_cnt;
|
||||||
|
|
||||||
|
/* main modem state */
|
||||||
|
int state;
|
||||||
|
int debug_laststate;
|
||||||
|
int hangup_request;
|
||||||
|
unsigned int time; /* current time (in samples) */
|
||||||
|
|
||||||
|
/* config */
|
||||||
|
struct LinModemConfig *lm_config;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* linmodem configuration registers */
|
||||||
|
typedef struct LinModemConfig {
|
||||||
|
int pulse_dial; /* default dial type */
|
||||||
|
int dtmf_level; /* in dB */
|
||||||
|
int dtmf_digit_length; /* in ms */
|
||||||
|
int dtmf_pause_length; /* in ms */
|
||||||
|
int available_modulations; /* mask of available modulations */
|
||||||
|
} LinModemConfig;
|
||||||
|
|
||||||
|
/* abstract line interface driver */
|
||||||
|
struct lm_interface_state {
|
||||||
|
struct sm_state *sm;
|
||||||
|
int handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* modem hardware interface abstraction */
|
||||||
|
/* XXX: must be slightly modified for kernel mode operation */
|
||||||
|
struct sm_hw_info {
|
||||||
|
int (*open)(struct lm_interface_state *s);
|
||||||
|
void (*close)(struct lm_interface_state *s);
|
||||||
|
|
||||||
|
/* off hook or on hook modem */
|
||||||
|
void (*set_offhook)(struct lm_interface_state *s, int v);
|
||||||
|
|
||||||
|
/* when the ring is enabled, an event E_RING is sent to the
|
||||||
|
process if a ring occured */
|
||||||
|
void (*set_ring)(struct lm_interface_state *s, int v);
|
||||||
|
|
||||||
|
/* the main modem loop is here */
|
||||||
|
void (*main_loop)(struct lm_interface_state *s);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* general state of the modem */
|
||||||
|
|
||||||
|
enum {
|
||||||
|
#define TAG(s) s,
|
||||||
|
#include "lmstates.h"
|
||||||
|
};
|
||||||
|
|
||||||
|
void lm_init(struct sm_state *sm, struct sm_hw_info *hw, const char *name);
|
||||||
|
|
||||||
|
/* main modem process */
|
||||||
|
void sm_process(struct sm_state *sm, s16 *output, s16 *input, int nb_samples);
|
||||||
|
|
||||||
|
int lm_start_dial(struct sm_state *s, int pulse, const char *number);
|
||||||
|
int lm_start_receive(struct sm_state *s);
|
||||||
|
int lm_hangup(struct sm_state *s);
|
||||||
|
|
||||||
|
enum lm_get_state_val {
|
||||||
|
LM_STATE_IDLE,
|
||||||
|
LM_STATE_CONNECTING,
|
||||||
|
LM_STATE_CONNECTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum lm_get_state_val lm_get_state(struct sm_state *s);
|
||||||
|
|
||||||
|
/* lmsim.c */
|
||||||
|
|
||||||
|
void line_simulate(void);
|
||||||
|
|
||||||
|
struct LineModelState;
|
||||||
|
|
||||||
|
struct LineModelState *line_model_init(void);
|
||||||
|
void line_model(struct LineModelState *s,
|
||||||
|
s16 *output1, const s16 *input1,
|
||||||
|
s16 *output2, const s16 *input2,
|
||||||
|
int nb_samples);
|
||||||
|
|
||||||
|
/* lmreal.c */
|
||||||
|
|
||||||
|
void real_test(int calling);
|
||||||
|
|
||||||
|
/* lmsoundcard.c */
|
||||||
|
void soundcard_modem(void);
|
||||||
|
|
||||||
|
/* serial.c */
|
||||||
|
|
||||||
|
void serial_init(struct sm_state *s, int data_bits, int parity);
|
||||||
|
int serial_get_bit(void *opaque);
|
||||||
|
void serial_put_bit(void *opaque, int bit);
|
||||||
|
|
||||||
|
/* atparser.c */
|
||||||
|
|
||||||
|
enum lm_at_state_type {
|
||||||
|
AT_MODE_COMMAND,
|
||||||
|
AT_MODE_DIALING,
|
||||||
|
AT_MODE_CONNECTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct lm_at_state {
|
||||||
|
char at_line[256];
|
||||||
|
int at_line_ptr;
|
||||||
|
int at_state;
|
||||||
|
struct sm_state *sm; /* corresponding modem state */
|
||||||
|
};
|
||||||
|
|
||||||
|
void lm_at_parser_init(struct lm_at_state *s, struct sm_state *sm);
|
||||||
|
void lm_at_parser(struct lm_at_state *s);
|
||||||
|
|
||||||
|
#include "display.h"
|
|
@ -0,0 +1,95 @@
|
||||||
|
/* real modem: suitable for ltmodem */
|
||||||
|
|
||||||
|
/* Copyright 1999 Pavel Machek <pavel@suse.cz>, distribute under GPL v2 */
|
||||||
|
|
||||||
|
#include "lm.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define NB_SAMPLES 0x50
|
||||||
|
|
||||||
|
extern struct sm_hw_info sm_hw_null;
|
||||||
|
|
||||||
|
void readit(int f, void *buf, int len)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
while (len > 0) {
|
||||||
|
if ((res = read(f, buf, len)) <= 0) {
|
||||||
|
printf("Read failed: %m");
|
||||||
|
exit(5);
|
||||||
|
}
|
||||||
|
len -= res;
|
||||||
|
buf += res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *modem_command = "tee /tmp/delme.xmit | ltmodem -u 2> /dev/null | tee /tmp/delme.rec\n",
|
||||||
|
*dial_number = "31415926";
|
||||||
|
|
||||||
|
void real_test(int calling)
|
||||||
|
{
|
||||||
|
int fildesin[2];
|
||||||
|
int fildesout[2];
|
||||||
|
int i;
|
||||||
|
struct sm_state sm, *dce = &sm;
|
||||||
|
s16 in_buf[NB_SAMPLES];
|
||||||
|
s16 out_buf[NB_SAMPLES];
|
||||||
|
|
||||||
|
lm_init(dce, &sm_hw_null, "real");
|
||||||
|
|
||||||
|
strcpy(dce->call_num,dial_number);
|
||||||
|
|
||||||
|
pipe(fildesin);
|
||||||
|
pipe(fildesout);
|
||||||
|
|
||||||
|
printf( "Starting communication with ltmodem\n" );
|
||||||
|
fflush(stdout); fflush(stderr);
|
||||||
|
|
||||||
|
if (!fork()) {
|
||||||
|
close(0);
|
||||||
|
dup(fildesin[0]);
|
||||||
|
close(1);
|
||||||
|
dup(fildesout[1]);
|
||||||
|
close(fildesin[0]);
|
||||||
|
close(fildesin[1]);
|
||||||
|
close(fildesout[0]);
|
||||||
|
close(fildesout[1]);
|
||||||
|
system( modem_command );
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* test call */
|
||||||
|
|
||||||
|
if (calling) {
|
||||||
|
lm_start_dial(dce, 0, dial_number);
|
||||||
|
} else {
|
||||||
|
lm_start_receive(dce);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fildesin[0]);
|
||||||
|
close(fildesout[1]);
|
||||||
|
|
||||||
|
printf( "Kicking modem..." ); fflush(stdout);
|
||||||
|
bzero(out_buf, 2*NB_SAMPLES);
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i=0; i<100; i++) {
|
||||||
|
write(fildesin[1], out_buf, 2*NB_SAMPLES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf( "Modem kicked\n" );
|
||||||
|
|
||||||
|
/* answer_dce->state = SM_TEST_RING; */
|
||||||
|
for(;;) {
|
||||||
|
if (lm_get_state(dce) == LM_STATE_IDLE)
|
||||||
|
break;
|
||||||
|
|
||||||
|
readit(fildesout[0], in_buf, NB_SAMPLES*2);
|
||||||
|
|
||||||
|
sm_process(dce, out_buf, in_buf, NB_SAMPLES);
|
||||||
|
|
||||||
|
if ((i = write(fildesin[1], out_buf, NB_SAMPLES*2)) != NB_SAMPLES*2) {
|
||||||
|
printf( "Error writing -- got short sample (%d,%m)\n",i );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf( "Looks like we are done\n" );
|
||||||
|
}
|
|
@ -0,0 +1,467 @@
|
||||||
|
/*
|
||||||
|
* Implementation of the phone line simulator
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999,2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
#define NB_SAMPLES 40 /* 5 ms */
|
||||||
|
#define SAMPLE_RATE 8000
|
||||||
|
|
||||||
|
#define SAMPLE_REF 0x4000 /* 0 dB reference (sample level) */
|
||||||
|
|
||||||
|
/* 'calling' is used to know in which direction we transmit for echo cancellation */
|
||||||
|
struct sm_hw_info sm_hw_null;
|
||||||
|
|
||||||
|
|
||||||
|
/* transmit from 'A' to 'B' */
|
||||||
|
static void tx_rx(struct sm_state *A, struct sm_state *B, int dump)
|
||||||
|
{
|
||||||
|
/* transmit data from call_dce */
|
||||||
|
if (sm_size(&A->tx_fifo) < 10) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i=0;i<256;i++)
|
||||||
|
sm_put_bit(&A->tx_fifo, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* receive data from answer_dce */
|
||||||
|
for(;;) {
|
||||||
|
int c;
|
||||||
|
c = sm_get_bit(&B->rx_fifo);
|
||||||
|
if (c == -1)
|
||||||
|
break;
|
||||||
|
if (dump) {
|
||||||
|
printf("[%02x]", c);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void line_simulate(void)
|
||||||
|
{
|
||||||
|
struct sm_state sm1, sm2, *call_dce = &sm1, *answer_dce = &sm2;
|
||||||
|
s16 answer_buf[NB_SAMPLES], call_buf[NB_SAMPLES];
|
||||||
|
s16 answer_buf1[NB_SAMPLES], call_buf1[NB_SAMPLES];
|
||||||
|
FILE *f1,*f2;
|
||||||
|
struct LineModelState *line_state;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = lm_display_init();
|
||||||
|
if (err < 0) {
|
||||||
|
fprintf(stderr, "Could not init X display\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
line_state = line_model_init();
|
||||||
|
|
||||||
|
/* init two modems */
|
||||||
|
lm_init(call_dce, &sm_hw_null, "cal");
|
||||||
|
lm_init(answer_dce, &sm_hw_null, "ans");
|
||||||
|
|
||||||
|
/* start calls */
|
||||||
|
lm_start_dial(call_dce, 0, "1234567890");
|
||||||
|
lm_start_receive(answer_dce);
|
||||||
|
answer_dce->state = SM_TEST_RING;
|
||||||
|
|
||||||
|
f1 = fopen("cal.sw", "wb");
|
||||||
|
if (f1 == NULL) {
|
||||||
|
perror("cal.sw");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
f2 = fopen("ans.sw", "wb");
|
||||||
|
if (f1 == NULL) {
|
||||||
|
perror("ans.sw");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(answer_buf, 0, sizeof(answer_buf));
|
||||||
|
|
||||||
|
/* init asynchronous tx & rx */
|
||||||
|
serial_init(call_dce, 8, 'N');
|
||||||
|
serial_init(answer_dce, 8, 'N');
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
lm_display_poll_event();
|
||||||
|
|
||||||
|
/* transmit & receive in both direction & dump call to ans modem */
|
||||||
|
tx_rx(call_dce, answer_dce, 0);
|
||||||
|
tx_rx(answer_dce, call_dce, 1);
|
||||||
|
|
||||||
|
/* exit connection if ONHOOK state */
|
||||||
|
|
||||||
|
if (lm_get_state(call_dce) == LM_STATE_IDLE ||
|
||||||
|
lm_get_state(answer_dce) == LM_STATE_IDLE)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* simulate both modem & output the samples to disk */
|
||||||
|
|
||||||
|
sm_process(call_dce, call_buf, answer_buf1, NB_SAMPLES);
|
||||||
|
fwrite(call_buf, 1, NB_SAMPLES * 2, f1);
|
||||||
|
|
||||||
|
sm_process(answer_dce, answer_buf, call_buf1, NB_SAMPLES);
|
||||||
|
fwrite(answer_buf, 1, NB_SAMPLES * 2, f2);
|
||||||
|
|
||||||
|
/* simulate the phone line */
|
||||||
|
line_model(line_state,
|
||||||
|
call_buf1, call_buf,
|
||||||
|
answer_buf1, answer_buf, NB_SAMPLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f1);
|
||||||
|
fclose(f2);
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
if (lm_display_poll_event())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lm_display_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sim_open(struct lm_interface_state *s)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sim_close(struct lm_interface_state *s)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sim_set_offhook(struct lm_interface_state *s, int v)
|
||||||
|
{
|
||||||
|
if (v) {
|
||||||
|
printf("%s: offhook\n",s->sm->name);
|
||||||
|
} else {
|
||||||
|
printf("%s: onhook\n",s->sm->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sim_set_ring(struct lm_interface_state *s, int v)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sim_main_loop(struct lm_interface_state *s)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sm_hw_info sm_hw_null =
|
||||||
|
{
|
||||||
|
sim_open,
|
||||||
|
sim_close,
|
||||||
|
sim_set_offhook,
|
||||||
|
sim_set_ring,
|
||||||
|
sim_main_loop,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* simple phone line model */
|
||||||
|
|
||||||
|
#define LINE_FILTER_SIZE 129
|
||||||
|
#define A 0.99 /* constant for exponential averaging for power estimations */
|
||||||
|
#define FFT_SIZE 1024
|
||||||
|
|
||||||
|
/* state of a uni directional line */
|
||||||
|
typedef struct {
|
||||||
|
float buf[LINE_FILTER_SIZE]; /* last transmitted samples (ring buffer,
|
||||||
|
used by the line filter) */
|
||||||
|
int buf_ptr; /* pointer of the last transmitted sample in buf */
|
||||||
|
float tx_pow; /* estimated transmit power */
|
||||||
|
float rx_pow; /* estimated receive power */
|
||||||
|
float noise_pow; /* estimated noise power */
|
||||||
|
} UniDirLineState;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct LineModelState {
|
||||||
|
UniDirLineState line1, line2;
|
||||||
|
float fout1, fout2;
|
||||||
|
} LineModelState;
|
||||||
|
|
||||||
|
static float line_filter[LINE_FILTER_SIZE];
|
||||||
|
static float sigma; /* gaussian noise sigma */
|
||||||
|
static int nb_clamped; /* number of overflows */
|
||||||
|
static float modem_hybrid_echo; /* echo level created by the modem hybrid */
|
||||||
|
static float cs_hybrid_echo; /* echo level created by the central site hybrid */
|
||||||
|
|
||||||
|
#define RANDMAX 0x7fffffff
|
||||||
|
|
||||||
|
float random_unif(void)
|
||||||
|
{
|
||||||
|
return (float) random() / RANDMAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
float random_gaussian(void)
|
||||||
|
{
|
||||||
|
float v1, v2, s , m;
|
||||||
|
do {
|
||||||
|
v1 = 2 * random_unif() - 1;
|
||||||
|
v2 = 2 * random_unif() - 1;
|
||||||
|
s = v1 * v1 + v2 * v2;
|
||||||
|
} while (s >= 1);
|
||||||
|
m = sqrt(-2 * log(s)/s);
|
||||||
|
return v1 * m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tabulated medium range telephone line respond (from p 537, Digital
|
||||||
|
Communication, John G. Proakis */
|
||||||
|
|
||||||
|
/* amp 1.0 -> 2.15, freq = 3000 Hz -> 3.2, by 0.2 increments
|
||||||
|
delay = 4 ms -> 2.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
float phone_amp[23] = {
|
||||||
|
0,
|
||||||
|
0.9,
|
||||||
|
1.4,
|
||||||
|
1.8,
|
||||||
|
2.0,
|
||||||
|
2.1,
|
||||||
|
2.3,
|
||||||
|
2.3,
|
||||||
|
2.2,
|
||||||
|
2.1,
|
||||||
|
2.0,
|
||||||
|
1.85,
|
||||||
|
1.75,
|
||||||
|
1.55,
|
||||||
|
1.3,
|
||||||
|
1.1,
|
||||||
|
0.8,
|
||||||
|
0.55,
|
||||||
|
0.25,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.00,
|
||||||
|
};
|
||||||
|
|
||||||
|
float phone_delay[23] = {
|
||||||
|
2.2, /* NA */
|
||||||
|
2.2, /* NA */
|
||||||
|
2.2,
|
||||||
|
0.9,
|
||||||
|
0.5,
|
||||||
|
0.25,
|
||||||
|
0.1,
|
||||||
|
0.05,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.05,
|
||||||
|
0.1,
|
||||||
|
0.2,
|
||||||
|
0.4,
|
||||||
|
0.5,
|
||||||
|
0.9,
|
||||||
|
1.2,
|
||||||
|
2.2,
|
||||||
|
2.2, /* NA */
|
||||||
|
2.2, /* NA */
|
||||||
|
2.2, /* NA */
|
||||||
|
2.2, /* NA */
|
||||||
|
};
|
||||||
|
|
||||||
|
static void build_line_impulse_response(void)
|
||||||
|
{
|
||||||
|
float f, f1, a, amp, phase, delay;
|
||||||
|
int index, i, j;
|
||||||
|
complex tab[FFT_SIZE];
|
||||||
|
FILE *outfile;
|
||||||
|
|
||||||
|
for(i=0;i<FFT_SIZE/2;i++) {
|
||||||
|
f = (float) i / FFT_SIZE;
|
||||||
|
f = f * SAMPLE_RATE;
|
||||||
|
f1 = f / 3000.0 * 3.2 / 0.2;
|
||||||
|
a = f1 - floor(f1);
|
||||||
|
index = (int)floor(f1);
|
||||||
|
|
||||||
|
/* linear interpolation */
|
||||||
|
amp = (1 - a) * phone_amp[index] + a * phone_amp[index+1];
|
||||||
|
amp = amp / 2.15;
|
||||||
|
|
||||||
|
delay = (1 - a) * phone_delay[index] + a * phone_delay[index+1];
|
||||||
|
delay = delay / 2.2 * 4;
|
||||||
|
|
||||||
|
phase = 2 * M_PI * f * delay * 0.001;
|
||||||
|
#if 0
|
||||||
|
printf("index=%d a=%0.3f f=%0.0f amp=%0.2f delay=%0.2f %0.1f\n",
|
||||||
|
index, a, f, amp, delay, phase);
|
||||||
|
#endif
|
||||||
|
tab[i].re = amp * cos(phase);
|
||||||
|
tab[i].im = amp * sin(phase);
|
||||||
|
}
|
||||||
|
tab[FFT_SIZE/2].im = 0;
|
||||||
|
for(i=1;i<FFT_SIZE/2;i++) {
|
||||||
|
tab[FFT_SIZE - i].re = tab[i].re;
|
||||||
|
tab[FFT_SIZE - i].im = - tab[i].im;
|
||||||
|
}
|
||||||
|
|
||||||
|
fft_calc(tab, FFT_SIZE, 1);
|
||||||
|
|
||||||
|
outfile = fopen("a", "w");
|
||||||
|
j = FFT_SIZE - (LINE_FILTER_SIZE - 1)/2;
|
||||||
|
for(i=0;i<LINE_FILTER_SIZE;i++) {
|
||||||
|
line_filter[i] = tab[j].re;
|
||||||
|
fprintf(outfile, "%f\n", tab[j].re);
|
||||||
|
if (++j == FFT_SIZE)
|
||||||
|
j = 0;
|
||||||
|
}
|
||||||
|
fclose(outfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
LineModelState *line_model_init(void)
|
||||||
|
{
|
||||||
|
float N0,SNR, p, echo_level;
|
||||||
|
int i;
|
||||||
|
LineModelState *s;
|
||||||
|
|
||||||
|
s = malloc(sizeof(LineModelState));
|
||||||
|
memset(s, 0, sizeof(LineModelState));
|
||||||
|
|
||||||
|
SNR = 25; /* wanted SNR */
|
||||||
|
N0 = pow(10,-SNR/10.0);
|
||||||
|
sigma=sqrt(N0/2) * (float)SAMPLE_REF;
|
||||||
|
|
||||||
|
/* echos */
|
||||||
|
echo_level = -15; /* in dB */
|
||||||
|
cs_hybrid_echo = pow(10, echo_level/20.0);
|
||||||
|
modem_hybrid_echo = pow(10, echo_level/20.0);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
build_line_impulse_response();
|
||||||
|
#else
|
||||||
|
/* simple filter */
|
||||||
|
line_filter[LINE_FILTER_SIZE/2+1] = 0.3;
|
||||||
|
line_filter[LINE_FILTER_SIZE/2] = 1.0;
|
||||||
|
line_filter[LINE_FILTER_SIZE/2-1] = 0.3;
|
||||||
|
#endif
|
||||||
|
/* normalize the filter to a power of 1.0 */
|
||||||
|
p = 0;
|
||||||
|
for(i=0;i<LINE_FILTER_SIZE;i++) {
|
||||||
|
p += line_filter[i] * line_filter[i];
|
||||||
|
}
|
||||||
|
p = sqrt(p);
|
||||||
|
for(i=0;i<LINE_FILTER_SIZE;i++) line_filter[i] /= p;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
for(i=0;i<LINE_FILTER_SIZE;i++)
|
||||||
|
printf("%5d %0.3f\n", i, line_filter[i]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float compute_db(float a)
|
||||||
|
{
|
||||||
|
return 10.0 * log(a) / log(10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dump_count;
|
||||||
|
|
||||||
|
static float calc_line_filter(UniDirLineState *s,
|
||||||
|
float v, int calling)
|
||||||
|
{
|
||||||
|
float sum, noise;
|
||||||
|
int j, p;
|
||||||
|
|
||||||
|
/* compute tx power */
|
||||||
|
s->tx_pow = (v * v) * (1.0 - A) + A * s->tx_pow;
|
||||||
|
|
||||||
|
/* add the sample in the filter buffer */
|
||||||
|
p = s->buf_ptr;
|
||||||
|
s->buf[p] = v;
|
||||||
|
if (++p == LINE_FILTER_SIZE)
|
||||||
|
p = 0;
|
||||||
|
s->buf_ptr = p;
|
||||||
|
|
||||||
|
/* apply the filter */
|
||||||
|
sum = 0;
|
||||||
|
for(j=0;j<LINE_FILTER_SIZE;j++) {
|
||||||
|
sum += line_filter[j] * s->buf[p];
|
||||||
|
if (++p == LINE_FILTER_SIZE)
|
||||||
|
p = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add noise */
|
||||||
|
noise = random_gaussian() * sigma;
|
||||||
|
sum += noise;
|
||||||
|
|
||||||
|
/* (testing only: noise power) */
|
||||||
|
s->noise_pow = (noise * noise) * (1.0 - A) + A * s->noise_pow;
|
||||||
|
|
||||||
|
/* compute rx_power */
|
||||||
|
s->rx_pow = (sum * sum) * (1.0 - A) + A * s->rx_pow;
|
||||||
|
|
||||||
|
/* dump estimations */
|
||||||
|
if (calling && ++dump_count == 50) {
|
||||||
|
float ref_db;
|
||||||
|
|
||||||
|
dump_count = 0;
|
||||||
|
ref_db = compute_db(SAMPLE_REF * SAMPLE_REF);
|
||||||
|
lm_dump_linesim_power(compute_db(s->tx_pow) - ref_db,
|
||||||
|
compute_db(s->rx_pow) - ref_db,
|
||||||
|
compute_db(s->noise_pow) - ref_db);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clamp(float a)
|
||||||
|
{
|
||||||
|
if (a < -32768) {
|
||||||
|
a = -32768;
|
||||||
|
nb_clamped++;
|
||||||
|
} else if (a > 32767) {
|
||||||
|
a = 32767;
|
||||||
|
nb_clamped++;
|
||||||
|
}
|
||||||
|
return (int)rint(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* modem (cal) modem (ans)
|
||||||
|
*
|
||||||
|
* input1 -> output1 (line1)
|
||||||
|
* output2 <- input2 (line2)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void line_model(LineModelState *s,
|
||||||
|
s16 *output1, const s16 *input1,
|
||||||
|
s16 *output2, const s16 *input2,
|
||||||
|
int nb_samples)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
float in1, in2, out1, out2;
|
||||||
|
float tmp1, tmp2;
|
||||||
|
|
||||||
|
for(i=0;i<nb_samples;i++) {
|
||||||
|
in1 = input1[i];
|
||||||
|
in2 = input2[i];
|
||||||
|
|
||||||
|
/* echo from cal modem central site hybrid */
|
||||||
|
tmp1 = in1 + s->fout2 * cs_hybrid_echo;
|
||||||
|
|
||||||
|
/* echo from ans modem central site hybrid */
|
||||||
|
tmp2 = in2 + s->fout1 * cs_hybrid_echo;
|
||||||
|
|
||||||
|
/* line filters & noise */
|
||||||
|
s->fout1 = calc_line_filter(&s->line1, tmp1, 1);
|
||||||
|
|
||||||
|
s->fout2 = calc_line_filter(&s->line2, tmp2, 0);
|
||||||
|
|
||||||
|
/* echo from ans modem hybrid */
|
||||||
|
out1 = s->fout1 + in2 * modem_hybrid_echo;
|
||||||
|
lm_dump_sample(CHANNEL_SAMPLE, out1 / 32768.0);
|
||||||
|
|
||||||
|
/* echo from cal modem hybrid */
|
||||||
|
out2 = s->fout2 + in1 * modem_hybrid_echo;
|
||||||
|
|
||||||
|
output1[i] = clamp(out1);
|
||||||
|
output2[i] = clamp(out2);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
/* sample interface code to use a linux soundcard */
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <linux/soundcard.h>
|
||||||
|
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
/* OSS buffer size */
|
||||||
|
#define FRAGMENT_BITS 8
|
||||||
|
#define NB_FRAGMENTS (32768 >> FRAGMENT_BITS)
|
||||||
|
|
||||||
|
/* 128 samples : 16 ms, maybe too much ? */
|
||||||
|
#define NB_SAMPLES ((1 << FRAGMENT_BITS)/2)
|
||||||
|
|
||||||
|
/* default pty */
|
||||||
|
#define PTY_NAME "z0"
|
||||||
|
char *pty_name = "/dev/pty" PTY_NAME;
|
||||||
|
char *tty_name = "/dev/tty" PTY_NAME;
|
||||||
|
|
||||||
|
extern struct sm_hw_info sm_hw_soundcard;
|
||||||
|
|
||||||
|
void soundcard_modem(void)
|
||||||
|
{
|
||||||
|
struct sm_state sm1, *dce = &sm1;
|
||||||
|
struct sm_hw_info *hw = &sm_hw_soundcard;
|
||||||
|
s16 in_buf[NB_SAMPLES];
|
||||||
|
s16 out_buf[NB_SAMPLES];
|
||||||
|
u8 buf[1024];
|
||||||
|
struct lm_at_state at_parser;
|
||||||
|
fd_set rfds, wfds;
|
||||||
|
int hw_handle, tty_handle, max_handle, n;
|
||||||
|
int out_buf_flushed, i, len;
|
||||||
|
|
||||||
|
if (!lm_debug) {
|
||||||
|
printf("linmodem tty is '%s'\n", tty_name);
|
||||||
|
tty_handle = open(pty_name, O_RDWR);
|
||||||
|
if (tty_handle < 0) {
|
||||||
|
perror(pty_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("linmodem tty is stdout\n");
|
||||||
|
tty_handle = 0;
|
||||||
|
}
|
||||||
|
fcntl(tty_handle, F_SETFL, O_NONBLOCK);
|
||||||
|
|
||||||
|
lm_init(dce, hw, "sc");
|
||||||
|
lm_at_parser_init(&at_parser, dce);
|
||||||
|
|
||||||
|
|
||||||
|
hw_handle = dce->hw_state->handle;
|
||||||
|
out_buf_flushed = 1;
|
||||||
|
/* XXX: non block mode ? */
|
||||||
|
|
||||||
|
/* test call */
|
||||||
|
printf("linmodem started.\n");
|
||||||
|
for(;;) {
|
||||||
|
/* sound card & tty handling */
|
||||||
|
FD_ZERO(&rfds);
|
||||||
|
FD_SET(hw_handle, &rfds);
|
||||||
|
FD_SET(tty_handle, &rfds);
|
||||||
|
FD_ZERO(&wfds);
|
||||||
|
if (!out_buf_flushed)
|
||||||
|
FD_SET(hw_handle, &wfds);
|
||||||
|
if (sm_size(&dce->rx_fifo) > 0)
|
||||||
|
FD_SET(tty_handle, &wfds);
|
||||||
|
max_handle = tty_handle;
|
||||||
|
if (hw_handle > tty_handle)
|
||||||
|
max_handle = hw_handle;
|
||||||
|
|
||||||
|
n = select(max_handle + 1, &rfds, &wfds, NULL, NULL);
|
||||||
|
if (n > 0) {
|
||||||
|
/* process at commands */
|
||||||
|
lm_at_parser(&at_parser);
|
||||||
|
|
||||||
|
/* read from tty */
|
||||||
|
if (FD_ISSET(tty_handle, &rfds)) {
|
||||||
|
len = read(tty_handle, buf, sizeof(buf));
|
||||||
|
for(i=0;i<len;i++) {
|
||||||
|
sm_put_bit(&dce->tx_fifo, buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* write to tty */
|
||||||
|
if (FD_ISSET(tty_handle, &wfds)) {
|
||||||
|
struct sm_fifo *f = &dce->rx_fifo;
|
||||||
|
int size;
|
||||||
|
size = f->eptr - f->rptr;
|
||||||
|
if (size > f->size)
|
||||||
|
size = f->size;
|
||||||
|
len = write(tty_handle, f->rptr, size);
|
||||||
|
if (len > 0) {
|
||||||
|
f->rptr += len;
|
||||||
|
f->size -= len;
|
||||||
|
if (f->rptr == f->eptr)
|
||||||
|
f->rptr = f->sptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we assume that the modem read & write per block of
|
||||||
|
NB_SAMPLES. It makes no sense the underlying hardware
|
||||||
|
does something else */
|
||||||
|
|
||||||
|
/* read from modem */
|
||||||
|
if (FD_ISSET(hw_handle, &rfds) && out_buf_flushed) {
|
||||||
|
len = read(hw_handle, in_buf, NB_SAMPLES * 2);
|
||||||
|
if (len == NB_SAMPLES * 2) {
|
||||||
|
/* process the modem samples */
|
||||||
|
sm_process(dce, out_buf, in_buf, NB_SAMPLES);
|
||||||
|
out_buf_flushed = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* write to modem */
|
||||||
|
if (FD_ISSET(hw_handle, &wfds)) {
|
||||||
|
len = write(hw_handle, out_buf, NB_SAMPLES * 2);
|
||||||
|
if (len == NB_SAMPLES * 2) {
|
||||||
|
out_buf_flushed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int soundcard_open(struct lm_interface_state *s)
|
||||||
|
{
|
||||||
|
int tmp, err;
|
||||||
|
/* init the sound card to 8000 Hz, Mono, 16 bits */
|
||||||
|
|
||||||
|
s->handle = open("/dev/dsp", O_RDWR);
|
||||||
|
if (s->handle < 0) {
|
||||||
|
perror("/dev/dsp");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set the card to duplex */
|
||||||
|
tmp=0;
|
||||||
|
err=ioctl(s->handle, SNDCTL_DSP_SETDUPLEX, &tmp);
|
||||||
|
if (err < 0) {
|
||||||
|
perror("SNDCTL_DSP_SETDUPLEX");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buffer size */
|
||||||
|
tmp=(NB_FRAGMENTS << 16) | FRAGMENT_BITS;
|
||||||
|
err=ioctl(s->handle, SNDCTL_DSP_SETFRAGMENT, &tmp);
|
||||||
|
if (err < 0) {
|
||||||
|
perror("set fragment");
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp=AFMT_S16_LE;
|
||||||
|
err=ioctl(s->handle,SNDCTL_DSP_SETFMT,&tmp);
|
||||||
|
if (err < 0) goto error;
|
||||||
|
|
||||||
|
/* should be last */
|
||||||
|
tmp = 8000;
|
||||||
|
err=ioctl(s->handle,SNDCTL_DSP_SPEED,&tmp);
|
||||||
|
if (err < 0) goto error;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
error:
|
||||||
|
close(s->handle);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void soundcard_close(struct lm_interface_state *s)
|
||||||
|
{
|
||||||
|
close(s->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void soundcard_set_offhook(struct lm_interface_state *s, int v)
|
||||||
|
{
|
||||||
|
if (v) {
|
||||||
|
printf("%s: offhook\n",s->sm->name);
|
||||||
|
} else {
|
||||||
|
printf("%s: onhook\n",s->sm->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void soundcard_set_ring(struct lm_interface_state *s, int v)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void soundcard_main_loop(struct lm_interface_state *s)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sm_hw_info sm_hw_soundcard =
|
||||||
|
{
|
||||||
|
soundcard_open,
|
||||||
|
soundcard_close,
|
||||||
|
soundcard_set_offhook,
|
||||||
|
soundcard_set_ring,
|
||||||
|
soundcard_main_loop,
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* modem states */
|
||||||
|
|
||||||
|
/* sm_process */
|
||||||
|
TAG(SM_IDLE)
|
||||||
|
TAG(SM_CALL)
|
||||||
|
TAG(SM_GO_ONHOOK)
|
||||||
|
TAG(SM_PREDTMF_WAIT)
|
||||||
|
TAG(SM_DTMF_DIAL)
|
||||||
|
TAG(SM_DTMF_DIAL_WAIT)
|
||||||
|
TAG(SM_DTMF_DIAL_WAIT1)
|
||||||
|
TAG(SM_TEST_RING)
|
||||||
|
TAG(SM_TEST_RING2)
|
||||||
|
TAG(SM_RECEIVE)
|
||||||
|
TAG(SM_V8)
|
||||||
|
TAG(SM_V21)
|
||||||
|
TAG(SM_V23)
|
||||||
|
|
||||||
|
/* V8_process: call */
|
||||||
|
TAG(V8_WAIT_1SECOND)
|
||||||
|
TAG(V8_CI)
|
||||||
|
TAG(V8_CI_SEND)
|
||||||
|
TAG(V8_CI_OFF)
|
||||||
|
TAG(V8_GOT_ANSAM)
|
||||||
|
TAG(V8_CM_SEND)
|
||||||
|
TAG(V8_CJ_SEND)
|
||||||
|
TAG(V8_SIGC)
|
||||||
|
|
||||||
|
/* V8_process: answer */
|
||||||
|
TAG(V8_WAIT)
|
||||||
|
TAG(V8_CM_WAIT)
|
||||||
|
TAG(V8_JM_SEND)
|
||||||
|
TAG(V8_SIGA)
|
||||||
|
|
||||||
|
/* V34 phase 2 */
|
||||||
|
TAG(V34_P2_INFO0_SEND)
|
||||||
|
|
||||||
|
|
||||||
|
#undef TAG
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Serial encoder/decoder.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
/* Init the serial encoder & decoder. 5 <= data_bits <= 8 and parity
|
||||||
|
can be 'E', 'O', or 'N' */
|
||||||
|
void serial_init(struct sm_state *s, int data_bits, int parity)
|
||||||
|
{
|
||||||
|
s->serial_data_bits = data_bits;
|
||||||
|
s->serial_use_parity = (parity == 'E' || parity == 'O');
|
||||||
|
s->serial_parity = (parity == 'O');
|
||||||
|
s->serial_wordsize = data_bits + 2 + s->serial_use_parity;
|
||||||
|
|
||||||
|
/* rx init */
|
||||||
|
s->serial_buf = 0;
|
||||||
|
s->serial_cnt = 0;
|
||||||
|
|
||||||
|
/* tx init */
|
||||||
|
s->serial_tx_buf = 0;
|
||||||
|
s->serial_tx_cnt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* return a bit from the tx fifo with serial encoding */
|
||||||
|
int serial_get_bit(void *opaque)
|
||||||
|
{
|
||||||
|
struct sm_state *s = opaque;
|
||||||
|
int data, j, bit, p;
|
||||||
|
|
||||||
|
if (s->serial_tx_cnt == 0) {
|
||||||
|
data = sm_get_bit(&s->tx_fifo);
|
||||||
|
if (data == -1)
|
||||||
|
return 1;
|
||||||
|
s->serial_tx_cnt = s->serial_wordsize;
|
||||||
|
if (s->serial_use_parity) {
|
||||||
|
p = s->serial_parity;
|
||||||
|
for(j=0;j<s->serial_data_bits;j++) p ^= (data >> j) & 1;
|
||||||
|
s->serial_tx_buf = (data << 2) | (p << 1) | 1;
|
||||||
|
} else {
|
||||||
|
s->serial_tx_buf = (data << 1) | 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s->serial_tx_cnt--;
|
||||||
|
bit = (s->serial_tx_buf >> s->serial_tx_cnt) & 1;
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* decode a serial stream and put it in rx_fifo. Not optimized */
|
||||||
|
void serial_put_bit(void *opaque, int bit)
|
||||||
|
{
|
||||||
|
struct sm_state *s = opaque;
|
||||||
|
int mask, p, j, data;
|
||||||
|
|
||||||
|
s->serial_buf = (s->serial_buf << 1) | bit;
|
||||||
|
if (s->serial_cnt >= (s->serial_wordsize-1)) {
|
||||||
|
mask = 1 | (1 << (s->serial_wordsize-1));
|
||||||
|
|
||||||
|
if ((s->serial_buf & mask) == 0x1) {
|
||||||
|
data = (s->serial_buf & ((1 << s->serial_wordsize) - 1)) >> 1;
|
||||||
|
|
||||||
|
if (s->serial_use_parity) {
|
||||||
|
p = s->serial_parity;
|
||||||
|
for(j=0;j<=s->serial_data_bits;j++) p ^= (data >> j) & 1;
|
||||||
|
if (!p)
|
||||||
|
sm_put_bit(&s->rx_fifo, data >> 1);
|
||||||
|
} else {
|
||||||
|
sm_put_bit(&s->rx_fifo, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
s->serial_cnt = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s->serial_cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Implementation of the V21 modulation/demodulation
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999,2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*/
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
#define SAMPLE_RATE 8000
|
||||||
|
|
||||||
|
void V21_mod_init(FSK_mod_state *s, int calling, get_bit_func get_bit, void *opaque)
|
||||||
|
{
|
||||||
|
if (calling) {
|
||||||
|
/* channel 1 */
|
||||||
|
s->f_lo = 1080 + 100;
|
||||||
|
s->f_hi = 1080 - 100;
|
||||||
|
} else {
|
||||||
|
/* channel 2 */
|
||||||
|
s->f_lo = 1750 + 100;
|
||||||
|
s->f_hi = 1750 - 100;
|
||||||
|
}
|
||||||
|
s->baud_rate = 300;
|
||||||
|
s->sample_rate = SAMPLE_RATE;
|
||||||
|
s->get_bit = get_bit;
|
||||||
|
s->opaque = opaque;
|
||||||
|
|
||||||
|
FSK_mod_init(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void V21_demod_init(FSK_demod_state *s, int calling, put_bit_func put_bit, void *opaque)
|
||||||
|
{
|
||||||
|
if (!calling) {
|
||||||
|
/* channel 1 */
|
||||||
|
s->f_lo = 1080 + 100;
|
||||||
|
s->f_hi = 1080 - 100;
|
||||||
|
} else {
|
||||||
|
/* channel 2 */
|
||||||
|
s->f_lo = 1750 + 100;
|
||||||
|
s->f_hi = 1750 - 100;
|
||||||
|
}
|
||||||
|
s->baud_rate = 300;
|
||||||
|
s->sample_rate = SAMPLE_RATE;
|
||||||
|
s->put_bit = put_bit;
|
||||||
|
s->opaque = opaque;
|
||||||
|
FSK_demod_init(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void V21_init(V21State *s, int calling,
|
||||||
|
get_bit_func get_bit, put_bit_func put_bit, void *opaque)
|
||||||
|
{
|
||||||
|
V21_mod_init(&s->tx, calling, get_bit, opaque);
|
||||||
|
V21_demod_init(&s->rx, calling, put_bit, opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
int V21_process(V21State *s, s16 *output, s16 *input, int nb_samples)
|
||||||
|
{
|
||||||
|
/* XXX: handle disconnect detection by looking at the power */
|
||||||
|
FSK_mod(&s->tx, output, nb_samples);
|
||||||
|
FSK_demod(&s->rx, input, nb_samples);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
void V21_mod_init(FSK_mod_state *s, int calling, get_bit_func get_bit, void *opaque);
|
||||||
|
void V21_demod_init(FSK_demod_state *s, int calling, put_bit_func put_bit, void *opaque);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FSK_mod_state tx;
|
||||||
|
FSK_demod_state rx;
|
||||||
|
} V21State;
|
||||||
|
|
||||||
|
void V21_init(V21State *s, int calling,
|
||||||
|
get_bit_func get_bit, put_bit_func put_bit, void *opaque);
|
||||||
|
int V21_process(V21State *sm, s16 *output, s16 *input, int nb_samples);
|
||||||
|
|
|
@ -0,0 +1,293 @@
|
||||||
|
/*
|
||||||
|
* V22 modulator & demodulator
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999,2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*/
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This code is also used by the V34 phase 2 at 600 bits/s. V22bis
|
||||||
|
* 2400 bps is implemented in the modulation but not in the
|
||||||
|
* demodulation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void V22_mod_init(V22ModState *s)
|
||||||
|
{
|
||||||
|
s->baud_phase = 0;
|
||||||
|
s->baud_num = 3;
|
||||||
|
s->baud_denom = 40;
|
||||||
|
s->tx_filter_wsize = V22_TX_FILTER_SIZE / s->baud_denom;
|
||||||
|
memset(s->tx_buf, 0, sizeof(s->tx_buf));
|
||||||
|
s->tx_outbuf_ptr = 0;
|
||||||
|
s->carrier_phase = 0;
|
||||||
|
s->carrier2_phase = 0;
|
||||||
|
s->Z = 0;
|
||||||
|
|
||||||
|
if (s->calling) {
|
||||||
|
/* call modem DPSK: 600 bps, carrier at 1200 Hz, 0 db */
|
||||||
|
s->carrier_incr = (PHASE_BASE * 1200.0) / V34_SAMPLE_RATE;
|
||||||
|
} else {
|
||||||
|
/* answer modem DPSK: 600 bps, carrier at 2400 Hz, -1 db,
|
||||||
|
guard tone at 1800 Hz, -7db */
|
||||||
|
s->carrier_incr = (PHASE_BASE * 2400.0) / V34_SAMPLE_RATE;
|
||||||
|
s->carrier2_incr = (PHASE_BASE * 1800.0) / V34_SAMPLE_RATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void V22_mod_baseband(V22ModState *s, s16 *x_ptr, s16 *y_ptr)
|
||||||
|
{
|
||||||
|
int x, y, x1, y1, b1, b2;
|
||||||
|
|
||||||
|
/* handle each kind of modulation */
|
||||||
|
switch(s->mod_type) {
|
||||||
|
default:
|
||||||
|
case V34_MOD_600:
|
||||||
|
b1 = s->get_bit(s->opaque);
|
||||||
|
/* rotation by 0 or 180 degrees */
|
||||||
|
s->Z = s->Z ^ (b1 << 1);
|
||||||
|
x1 = 0x2000;
|
||||||
|
y1 = 0x2000;
|
||||||
|
break;
|
||||||
|
case V22_MOD_600:
|
||||||
|
b1 = s->get_bit(s->opaque);
|
||||||
|
/* rotation by 90 or 270 degrees */
|
||||||
|
s->Z = (s->Z + ((b1 << 1) | 1)) & 3;
|
||||||
|
x1 = 0x2000;
|
||||||
|
y1 = 0x2000;
|
||||||
|
break;
|
||||||
|
case V22_MOD_1200:
|
||||||
|
b1 = s->get_bit(s->opaque);
|
||||||
|
b2 = s->get_bit(s->opaque);
|
||||||
|
b2 ^= (1 - b1);
|
||||||
|
s->Z = (s->Z + ((b1 << 1) | b2)) & 3;
|
||||||
|
x1 = 0x2000;
|
||||||
|
y1 = 0x2000;
|
||||||
|
break;
|
||||||
|
case V22_MOD_2400:
|
||||||
|
/* quadrant selection */
|
||||||
|
b1 = s->get_bit(s->opaque);
|
||||||
|
b2 = s->get_bit(s->opaque);
|
||||||
|
b2 ^= (1 - b1);
|
||||||
|
s->Z = (s->Z + ((b1 << 1) | b2)) & 3;
|
||||||
|
/* 4 positions inside the quadrant */
|
||||||
|
b1 = s->get_bit(s->opaque);
|
||||||
|
b2 = s->get_bit(s->opaque);
|
||||||
|
/* XXX: normalize */
|
||||||
|
x1 = 0x1000;
|
||||||
|
if (b2)
|
||||||
|
x1 += 0x2000;
|
||||||
|
y1 = 0x1000;
|
||||||
|
if (b1)
|
||||||
|
y1 += 0x2000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rotate counter clockwise */
|
||||||
|
switch(s->Z) {
|
||||||
|
case 0:
|
||||||
|
x = x1;
|
||||||
|
y = y1;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
x = -y1;
|
||||||
|
y = x1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
x = -x1;
|
||||||
|
y = -y1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case 3:
|
||||||
|
x = y1;
|
||||||
|
y = -x1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*x_ptr = x;
|
||||||
|
*y_ptr = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void V22_mod(V22ModState *s, s16 *samples, unsigned int nb)
|
||||||
|
{
|
||||||
|
int i, j, k, val, si, sq, ph;
|
||||||
|
|
||||||
|
for(i=0;i<nb;i++) {
|
||||||
|
|
||||||
|
/* apply the spectrum shaping filter */
|
||||||
|
ph = s->baud_phase;
|
||||||
|
si = sq = 0;
|
||||||
|
for(j=0;j<s->tx_filter_wsize;j++) {
|
||||||
|
k = (s->tx_outbuf_ptr - j - 1) &
|
||||||
|
(V22_TX_BUF_SIZE - 1);
|
||||||
|
si += s->tx_buf[k][0] * v22_tx_filter[ph];
|
||||||
|
sq += s->tx_buf[k][1] * v22_tx_filter[ph];
|
||||||
|
ph += s->baud_denom;
|
||||||
|
}
|
||||||
|
si = si >> 14;
|
||||||
|
sq = sq >> 14;
|
||||||
|
|
||||||
|
/* get next baseband symbol ? */
|
||||||
|
s->baud_phase += s->baud_num;
|
||||||
|
if (s->baud_phase >= s->baud_denom) {
|
||||||
|
s->baud_phase -= s->baud_denom;
|
||||||
|
V22_mod_baseband(s,
|
||||||
|
&s->tx_buf[s->tx_outbuf_ptr][0],
|
||||||
|
&s->tx_buf[s->tx_outbuf_ptr][1]);
|
||||||
|
|
||||||
|
s->tx_outbuf_ptr = (s->tx_outbuf_ptr + 1) & (V22_TX_BUF_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
val = (si * dsp_cos(s->carrier_phase) -
|
||||||
|
sq * dsp_cos((PHASE_BASE/4) - s->carrier_phase)) >> COS_BITS;
|
||||||
|
s->carrier_phase += s->carrier_incr;
|
||||||
|
if (!s->calling) {
|
||||||
|
/* a 1800 Hz tone is added for answer modem modulation at 6 dB below it */
|
||||||
|
val += (dsp_cos(s->carrier2_phase) >> 1);
|
||||||
|
s->carrier2_phase += s->carrier2_incr;
|
||||||
|
}
|
||||||
|
samples[i] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void V22_demod_init(V22DemodState *s)
|
||||||
|
{
|
||||||
|
s->baud_phase = 0;
|
||||||
|
s->baud_num = 3;
|
||||||
|
s->baud_denom = 40;
|
||||||
|
|
||||||
|
s->carrier_phase = 0;
|
||||||
|
|
||||||
|
if (!s->calling) {
|
||||||
|
/* call modem DPSK: 600 bps, carrier at 1200 Hz, 0 db */
|
||||||
|
s->carrier_incr = (PHASE_BITS * 1200) / 8000;
|
||||||
|
} else {
|
||||||
|
/* answer modem DPSK: 600 bps, carrier at 2400 Hz, -1 db,
|
||||||
|
guard tone at 1800 Hz, -7db */
|
||||||
|
s->carrier_incr = (PHASE_BITS * 2400) / 8000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void V22_demod(V22DemodState *s, s16 *samples, unsigned int nb)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* test for FSK using V21 or V23 */
|
||||||
|
|
||||||
|
#define NB_SAMPLES 40
|
||||||
|
|
||||||
|
#define MAXDELAY 32
|
||||||
|
|
||||||
|
static int tx_bits[MAXDELAY], tx_ptr = 0, rx_ptr = 0;
|
||||||
|
static int tx_blank = 32;
|
||||||
|
|
||||||
|
/* transmit random bits with a sync header (31 ones, 1 zero) */
|
||||||
|
static int test_get_bit(void *opaque)
|
||||||
|
{
|
||||||
|
int bit;
|
||||||
|
|
||||||
|
if (tx_blank != 0) {
|
||||||
|
/* send 1 at the beginning for synchronization */
|
||||||
|
bit = (tx_blank > 1);
|
||||||
|
tx_blank--;
|
||||||
|
} else {
|
||||||
|
bit = random() % 2;
|
||||||
|
tx_bits[tx_ptr] = bit;
|
||||||
|
if (++tx_ptr == MAXDELAY)
|
||||||
|
tx_ptr = 0;
|
||||||
|
}
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nb_bits = 0, errors = 0, sync_count = 0, got_sync = 0;
|
||||||
|
|
||||||
|
static void test_put_bit(void *opaque, int bit)
|
||||||
|
{
|
||||||
|
int tbit;
|
||||||
|
|
||||||
|
if (!got_sync) {
|
||||||
|
|
||||||
|
if (bit) {
|
||||||
|
sync_count++;
|
||||||
|
} else {
|
||||||
|
if (sync_count > 16)
|
||||||
|
got_sync = 1;
|
||||||
|
sync_count = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tbit = tx_bits[rx_ptr];
|
||||||
|
if (++rx_ptr == MAXDELAY)
|
||||||
|
rx_ptr = 0;
|
||||||
|
if (bit != tbit) {
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
nb_bits++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void V22_test(void)
|
||||||
|
{
|
||||||
|
V22ModState tx;
|
||||||
|
V22DemodState rx;
|
||||||
|
int err, calling;
|
||||||
|
struct LineModelState *line_state;
|
||||||
|
s16 buf[NB_SAMPLES];
|
||||||
|
s16 buf1[NB_SAMPLES];
|
||||||
|
s16 buf2[NB_SAMPLES];
|
||||||
|
s16 buf3[NB_SAMPLES];
|
||||||
|
FILE *f1;
|
||||||
|
|
||||||
|
err = lm_display_init();
|
||||||
|
if (err < 0) {
|
||||||
|
fprintf(stderr, "Could not init X display\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
line_state = line_model_init();
|
||||||
|
|
||||||
|
f1 = fopen("cal.sw", "wb");
|
||||||
|
if (f1 == NULL) {
|
||||||
|
perror("cal.sw");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
calling = 0;
|
||||||
|
|
||||||
|
tx.calling = calling;
|
||||||
|
tx.opaque = NULL;
|
||||||
|
tx.get_bit = test_get_bit;
|
||||||
|
tx.mod_type = V34_MOD_600;
|
||||||
|
V22_mod_init(&tx);
|
||||||
|
|
||||||
|
rx.calling = !calling;
|
||||||
|
rx.opaque = NULL;
|
||||||
|
rx.put_bit = test_put_bit;
|
||||||
|
rx.mod_type = tx.mod_type;
|
||||||
|
V22_demod_init(&rx);
|
||||||
|
|
||||||
|
nb_bits = 0;
|
||||||
|
errors = 0;
|
||||||
|
for(;;) {
|
||||||
|
if (lm_display_poll_event())
|
||||||
|
break;
|
||||||
|
|
||||||
|
V22_mod(&tx, buf, NB_SAMPLES);
|
||||||
|
memset(buf3, 0, sizeof(buf3));
|
||||||
|
|
||||||
|
line_model(line_state, buf1, buf, buf2, buf3, NB_SAMPLES);
|
||||||
|
|
||||||
|
fwrite(buf, 1, NB_SAMPLES * 2, f1);
|
||||||
|
|
||||||
|
V22_demod(&rx, buf1, NB_SAMPLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f1);
|
||||||
|
|
||||||
|
printf("errors=%d nb_bits=%d Pe=%f\n",
|
||||||
|
errors, nb_bits, (float) errors / (float)nb_bits);
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
enum ModulationType {
|
||||||
|
V34_MOD_600,
|
||||||
|
V22_MOD_600,
|
||||||
|
V22_MOD_1200,
|
||||||
|
V22_MOD_2400,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 40 phases (sure too much, but we don't optimize right now) */
|
||||||
|
#define V22_TX_FILTER_SIZE (20 * 40)
|
||||||
|
#define V22_TX_BUF_SIZE 64
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* parameters */
|
||||||
|
int calling;
|
||||||
|
enum ModulationType mod_type;
|
||||||
|
void *opaque;
|
||||||
|
get_bit_func get_bit;
|
||||||
|
|
||||||
|
/* state */
|
||||||
|
int baud_phase, baud_num, baud_denom;
|
||||||
|
int carrier_phase, carrier_incr;
|
||||||
|
int carrier2_phase, carrier2_incr;
|
||||||
|
int tx_filter_wsize;
|
||||||
|
s16 tx_buf[V22_TX_BUF_SIZE][2]; /* complex symbols to be sent */
|
||||||
|
int tx_outbuf_ptr; /* index of the next symbol in tx_buf */
|
||||||
|
int Z; /* last value transmitted */
|
||||||
|
} V22ModState;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* parameters */
|
||||||
|
int calling;
|
||||||
|
enum ModulationType mod_type;
|
||||||
|
void *opaque;
|
||||||
|
put_bit_func put_bit;
|
||||||
|
|
||||||
|
int baud_phase, baud_num, baud_denom;
|
||||||
|
int carrier_phase, carrier_incr;
|
||||||
|
} V22DemodState;
|
||||||
|
|
||||||
|
extern s16 v22_tx_filter[V22_TX_FILTER_SIZE];
|
||||||
|
|
||||||
|
void V22_mod_init(V22ModState *s);
|
||||||
|
void V22_mod(V22ModState *s, s16 *samples, unsigned int nb);
|
||||||
|
|
||||||
|
void V22_test(void);
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* generic V23 modulator & demodulator
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "lm.h"
|
||||||
|
#include "fsk.h"
|
||||||
|
|
||||||
|
#define SAMPLE_RATE 8000
|
||||||
|
|
||||||
|
void V23_mod_init(FSK_mod_state *s, int calling, get_bit_func get_bit, void *opaque)
|
||||||
|
{
|
||||||
|
if (calling) {
|
||||||
|
/* 75 bauds */
|
||||||
|
s->f_lo = 390;
|
||||||
|
s->f_hi = 450;
|
||||||
|
s->baud_rate = 75;
|
||||||
|
} else {
|
||||||
|
/* 1200 bauds */
|
||||||
|
s->f_lo = 1300;
|
||||||
|
s->f_hi = 2100;
|
||||||
|
s->baud_rate = 1200;
|
||||||
|
}
|
||||||
|
s->sample_rate = SAMPLE_RATE;
|
||||||
|
s->get_bit = get_bit;
|
||||||
|
s->opaque = opaque;
|
||||||
|
|
||||||
|
FSK_mod_init(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void V23_demod_init(FSK_demod_state *s, int calling, put_bit_func put_bit, void *opaque)
|
||||||
|
{
|
||||||
|
if (!calling) {
|
||||||
|
/* 75 bauds */
|
||||||
|
s->f_lo = 390;
|
||||||
|
s->f_hi = 450;
|
||||||
|
s->baud_rate = 75;
|
||||||
|
} else {
|
||||||
|
/* 1200 bauds */
|
||||||
|
s->f_lo = 1300;
|
||||||
|
s->f_hi = 2100;
|
||||||
|
s->baud_rate = 1200;
|
||||||
|
}
|
||||||
|
s->sample_rate = SAMPLE_RATE;
|
||||||
|
s->put_bit = put_bit;
|
||||||
|
s->opaque = opaque;
|
||||||
|
|
||||||
|
FSK_demod_init(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void V23_init(V23State *s, int calling,
|
||||||
|
get_bit_func get_bit, put_bit_func put_bit, void *opaque)
|
||||||
|
{
|
||||||
|
V23_mod_init(&s->tx, calling, get_bit, opaque);
|
||||||
|
V23_demod_init(&s->rx, calling, put_bit, opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
int V23_process(V23State *s, s16 *output, s16 *input, int nb_samples)
|
||||||
|
{
|
||||||
|
FSK_mod(&s->tx, output, nb_samples);
|
||||||
|
FSK_demod(&s->rx, input, nb_samples);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
void V23_mod_init(FSK_mod_state *s, int calling, get_bit_func get_bit, void *opaque);
|
||||||
|
void V23_demod_init(FSK_demod_state *s, int calling, put_bit_func put_bit, void *opaque);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FSK_mod_state tx;
|
||||||
|
FSK_demod_state rx;
|
||||||
|
} V23State;
|
||||||
|
|
||||||
|
void V23_init(V23State *s, int calling,
|
||||||
|
get_bit_func get_bit, put_bit_func put_bit, void *opaque);
|
||||||
|
int V23_process(V23State *s, s16 *output, s16 *input, int nb_samples);
|
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef V34_H
|
||||||
|
#define V34_H
|
||||||
|
|
||||||
|
#include "v34priv.h"
|
||||||
|
|
||||||
|
/* should be called once to init some V34 static tables */
|
||||||
|
void V34_static_init(void);
|
||||||
|
|
||||||
|
struct V34State;
|
||||||
|
|
||||||
|
void V34_init(struct V34State *s, int calling);
|
||||||
|
int V34_process(struct V34State *s, s16 *output, s16 *input, int nb_samples);
|
||||||
|
|
||||||
|
/* V34 half duplex test with line simulator */
|
||||||
|
void V34_test(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,506 @@
|
||||||
|
/*
|
||||||
|
* Implementation of the V34 equalizer
|
||||||
|
*
|
||||||
|
* Copyright (c) 2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* This implementation is totally clean room. It was written by
|
||||||
|
* reading the V34 specification and by using basic signal processing
|
||||||
|
* knowledge.
|
||||||
|
*/
|
||||||
|
#include "lm.h"
|
||||||
|
#include "v34priv.h"
|
||||||
|
|
||||||
|
//#define DEBUG
|
||||||
|
|
||||||
|
#define SQRT3_2 (int)(0.8660254 * 0x4000)
|
||||||
|
|
||||||
|
#define CMUL(a,b,c) \
|
||||||
|
{\
|
||||||
|
(a).re=((b).re*(c).re-(b).im*(c).im) >> 14;\
|
||||||
|
(a).im=((b).im*(c).re+(b).re*(c).im) >> 14;\
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SCALE(a,b,shift) \
|
||||||
|
{\
|
||||||
|
(a).re=(b).re >> (shift);\
|
||||||
|
(a).im=(b).im >> (shift);\
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FFT23_SIZE (EQ_FRAC * V34_PP_SIZE)
|
||||||
|
#define RENORM 256.0
|
||||||
|
#define FRAC 16384.0
|
||||||
|
|
||||||
|
static icomplex fft144_table[FFT23_SIZE];
|
||||||
|
static s16 fft144_reverse[FFT23_SIZE];
|
||||||
|
|
||||||
|
icomplex tabPP[V34_PP_SIZE]; /* PP is used to generate the PP signal */
|
||||||
|
static icomplex tabPP_fft[V34_PP_SIZE];
|
||||||
|
|
||||||
|
/* Compute an fft on an array whose size n = 2^k.3^l. The algorithm is
|
||||||
|
not the most efficient, but it is simple. Each stage of the fft
|
||||||
|
normalize the result: it is divided by 2 (for fft2) or 4 (for fft3)
|
||||||
|
at each stage. For 144, the renormalization is 2^8 */
|
||||||
|
static void fft23(icomplex *output, icomplex *tab, unsigned int n)
|
||||||
|
{
|
||||||
|
unsigned int s, i, j, k;
|
||||||
|
icomplex *p, *q, *r, *c1_ptr, *c2_ptr;
|
||||||
|
s = n;
|
||||||
|
k = 1;
|
||||||
|
while (s != 1) {
|
||||||
|
if ((s % 3) == 0) {
|
||||||
|
/* we handle first the '3' factors */
|
||||||
|
s /= 3;
|
||||||
|
for(p = tab;p<tab + n;p+=2 * s) {
|
||||||
|
c1_ptr = fft144_table;
|
||||||
|
c2_ptr = fft144_table;
|
||||||
|
q = p + s;
|
||||||
|
r = p + 2*s;
|
||||||
|
for(j=0;j<s;j++) {
|
||||||
|
icomplex a,b,c;
|
||||||
|
int tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
|
||||||
|
int tmp8, tmp9, tmp10, tmp11, tmp12;
|
||||||
|
|
||||||
|
SCALE(a, *p, 2);
|
||||||
|
SCALE(b, *q, 2);
|
||||||
|
SCALE(c, *r, 2);
|
||||||
|
|
||||||
|
/* fft on 3 points */
|
||||||
|
tmp1 = a.re;
|
||||||
|
tmp10 = a.im;
|
||||||
|
|
||||||
|
tmp2 = b.re;
|
||||||
|
tmp3 = c.re;
|
||||||
|
tmp4 = tmp2 + tmp3;
|
||||||
|
tmp9 = (SQRT3_2 * (tmp3 - tmp2)) >> 14;
|
||||||
|
tmp6 = b.im;
|
||||||
|
tmp7 = c.im;
|
||||||
|
tmp8 = (SQRT3_2 * (tmp6 - tmp7)) >> 14;
|
||||||
|
tmp11 = tmp6 + tmp7;
|
||||||
|
|
||||||
|
p->re = (tmp1 + tmp4);
|
||||||
|
tmp5 = tmp1 - (tmp4 >> 1);
|
||||||
|
c.re = (tmp5 - tmp8);
|
||||||
|
b.re = (tmp5 + tmp8);
|
||||||
|
p->im = tmp10 + tmp11;
|
||||||
|
tmp12 = tmp10 - (tmp11 >> 1);
|
||||||
|
b.im = (tmp9 + tmp12);
|
||||||
|
c.im = (tmp12 - tmp9);
|
||||||
|
|
||||||
|
/* post multiplications */
|
||||||
|
CMUL(*q, b, *c1_ptr);
|
||||||
|
CMUL(*r, c, *c2_ptr);
|
||||||
|
|
||||||
|
p++;
|
||||||
|
q++;
|
||||||
|
r++;
|
||||||
|
c1_ptr += k;
|
||||||
|
c2_ptr += 2 * k;
|
||||||
|
if (c2_ptr >= (fft144_table + n))
|
||||||
|
c2_ptr -= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k *= 3;
|
||||||
|
} else {
|
||||||
|
/* '2' factors */
|
||||||
|
s /= 2;
|
||||||
|
for(p=tab;p<tab + n;p += s) {
|
||||||
|
c1_ptr = fft144_table;
|
||||||
|
q = p + s;
|
||||||
|
for(j=0;j<s;j++) {
|
||||||
|
icomplex a, b;
|
||||||
|
|
||||||
|
SCALE(a, *p, 1);
|
||||||
|
SCALE(b, *q, 1);
|
||||||
|
|
||||||
|
/* fft on 2 points */
|
||||||
|
p->re = (a.re + b.re);
|
||||||
|
p->im = (a.im + b.im);
|
||||||
|
b.re = (a.re - b.re);
|
||||||
|
b.im = (a.im - b.im);
|
||||||
|
|
||||||
|
/* post multiplication */
|
||||||
|
CMUL(*q, b, *c1_ptr);
|
||||||
|
p++;
|
||||||
|
q++;
|
||||||
|
c1_ptr += k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* now we reverse the indices : cannot permute in place because
|
||||||
|
fft_reverse is not involutive */
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
output[fft144_reverse[i]] = tab[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static s16 cos12[12] = { 16384, 14188, 8192, 0, -8192, -14188,
|
||||||
|
-16384, -14188, -8192, 0, 8192, 14188 };
|
||||||
|
|
||||||
|
|
||||||
|
static icomplex tabtmp[48];
|
||||||
|
|
||||||
|
/* Init some constants for the equalizer. May be moved to v34gen.c if
|
||||||
|
it is too complicated */
|
||||||
|
void V34eq_init(void)
|
||||||
|
{
|
||||||
|
int i, j, k, n;
|
||||||
|
float a, carrier;
|
||||||
|
complex tab1[V34_PP_SIZE], tab2[V34_PP_SIZE];
|
||||||
|
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
a = - 2 * M_PI * i / (float) FFT23_SIZE;
|
||||||
|
fft144_table[i].re = (int)(cos(a) * 0x4000);
|
||||||
|
fft144_table[i].im = (int)(sin(a) * 0x4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reverse table */
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
int base;
|
||||||
|
j = i;
|
||||||
|
k = 0;
|
||||||
|
n = FFT23_SIZE;
|
||||||
|
while (n != 1) {
|
||||||
|
if ((n % 2) == 0)
|
||||||
|
base = 2;
|
||||||
|
else
|
||||||
|
base = 3;
|
||||||
|
|
||||||
|
k = base * k + (j % base);
|
||||||
|
j /= base;
|
||||||
|
n /= base;
|
||||||
|
}
|
||||||
|
fft144_reverse[i] = k;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* compute the V34 PP sequence */
|
||||||
|
for(k=0;k<12;k++) {
|
||||||
|
for(i=0;i<4;i++) {
|
||||||
|
j = k * i;
|
||||||
|
if ((k % 3) == 1)
|
||||||
|
j += 4;
|
||||||
|
j = j % 12;
|
||||||
|
tabPP[4*k+i].re = cos12[j];
|
||||||
|
tabPP[4*k+i].im = cos12[(15 - j) % 12];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fft of PP sequence */
|
||||||
|
carrier = 0.0;
|
||||||
|
for(i=0;i<V34_PP_SIZE;i++) {
|
||||||
|
icomplex a, b;
|
||||||
|
#if 0
|
||||||
|
b.re = (int)(cos(carrier) * 0x4000);
|
||||||
|
b.im = (int)(sin(carrier) * 0x4000);
|
||||||
|
CMUL(a, tabPP[i], b);
|
||||||
|
#else
|
||||||
|
a = tabPP[i];
|
||||||
|
#endif
|
||||||
|
tabtmp[i] = a;
|
||||||
|
tab1[i].re = a.re / 16384.0 / sqrt(48);
|
||||||
|
tab1[i].im = a.im / 16384.0 / sqrt(48);
|
||||||
|
carrier -= 2 * M_PI * 1920.0 / 3200.0;
|
||||||
|
// carrier -= 2 * M_PI * 1800.0 / 2400.0;
|
||||||
|
}
|
||||||
|
slow_fft(tab2, tab1, V34_PP_SIZE, 0);
|
||||||
|
|
||||||
|
for(i=0;i<V34_PP_SIZE;i++) {
|
||||||
|
tabPP_fft[i].re = (int)(tab2[i].re * 0x4000);
|
||||||
|
tabPP_fft[i].im = (int)(tab2[i].im * 0x4000);
|
||||||
|
#if 0
|
||||||
|
printf("%3d: %7.4f %7.4f\n",
|
||||||
|
i, tab2[i].re, tab2[i].im);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Fast training of the equalizer based on the PP sequence */
|
||||||
|
void V34_fast_equalize1(V34DSPState *s, s16 *input)
|
||||||
|
{
|
||||||
|
int i,k,j, vmax, v, lshift, renorm;
|
||||||
|
icomplex tab[FFT23_SIZE], tab1[FFT23_SIZE];
|
||||||
|
float carrier;
|
||||||
|
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
tab[i].re = input[i];
|
||||||
|
tab[i].im = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* test: modulate a PP sequence */
|
||||||
|
{
|
||||||
|
icomplex a, b;
|
||||||
|
float carrier, carrier_incr;
|
||||||
|
int p;
|
||||||
|
|
||||||
|
p = 0;
|
||||||
|
carrier_incr = (2 * M_PI * 0.25);
|
||||||
|
for(i=0;i<FFT23_SIZE/3;i++) {
|
||||||
|
a = tabPP[i];
|
||||||
|
b = tabPP[(i+1) % (FFT23_SIZE/3)];
|
||||||
|
|
||||||
|
tab[p].re = a.re;
|
||||||
|
tab[p].im = a.im;
|
||||||
|
p++;
|
||||||
|
tab[p].re = 0;
|
||||||
|
tab[p].im = 0;
|
||||||
|
p++;
|
||||||
|
tab[p].re = 0;
|
||||||
|
tab[p].im = 0;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
tab[i].re = 0;
|
||||||
|
tab[i].im = 0;
|
||||||
|
if (i == 2)
|
||||||
|
tab[i].re = FRAC;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("Fast equalizer Input:\n");
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
printf("%3d: %7.4f %7.4f\n",
|
||||||
|
i, tab[i].re / FRAC, tab[i].im / FRAC);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fft23(tab1, tab, FFT23_SIZE);
|
||||||
|
|
||||||
|
/* find best renormalization shift (the fft prefers to have its
|
||||||
|
inputs as close as 2^14 as possible) */
|
||||||
|
|
||||||
|
for(i=0;i<FFT23_SIZE;i++)
|
||||||
|
tab[i] = tab1[i];
|
||||||
|
|
||||||
|
vmax = 0;
|
||||||
|
for(i=0;i<FFT23_SIZE/2;i++) {
|
||||||
|
v = abs(tab[i].re);
|
||||||
|
if (v > vmax)
|
||||||
|
vmax = v;
|
||||||
|
v = abs(tab[i].im);
|
||||||
|
if (v > vmax)
|
||||||
|
vmax = v;
|
||||||
|
}
|
||||||
|
lshift = 0;
|
||||||
|
while (vmax < 0x4000) {
|
||||||
|
vmax <<= 1;
|
||||||
|
lshift++;
|
||||||
|
}
|
||||||
|
#if defined(DEBUG) || 1
|
||||||
|
printf("vmax=%d lshift=%d\n", vmax, lshift);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for(i=0;i<FFT23_SIZE/2;i++) {
|
||||||
|
tab[i].re <<= lshift;
|
||||||
|
tab[i].im <<= lshift;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=0;i<24;i++) {
|
||||||
|
icomplex a, b, c;
|
||||||
|
int norm, d, e;
|
||||||
|
|
||||||
|
c = tabPP_fft[i];
|
||||||
|
b = tab[i];
|
||||||
|
norm = b.re * b.re + b.im * b.im;
|
||||||
|
b = tab[i+ 48];
|
||||||
|
norm += b.re * b.re + b.im * b.im;
|
||||||
|
norm = norm >> 14;
|
||||||
|
if (norm == 0)
|
||||||
|
norm = 1;
|
||||||
|
c.re = (c.re << 14) / norm;
|
||||||
|
c.im = (c.im << 14) / norm;
|
||||||
|
|
||||||
|
b.re = tab[i].re;
|
||||||
|
b.im = - tab[i].im;
|
||||||
|
CMUL(a, c, b);
|
||||||
|
tab1[i] = a;
|
||||||
|
|
||||||
|
b.re = tab[48 + i].re;
|
||||||
|
b.im = - tab[48 + i].im;
|
||||||
|
CMUL(a, c, b);
|
||||||
|
tab1[48 + i] = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=24;i<48;i++) {
|
||||||
|
icomplex a, b, c;
|
||||||
|
int norm;
|
||||||
|
|
||||||
|
c = tabPP_fft[i];
|
||||||
|
b = tab[i];
|
||||||
|
norm = b.re * b.re + b.im * b.im;
|
||||||
|
norm = norm >> 14;
|
||||||
|
if (norm == 0)
|
||||||
|
norm = 1;
|
||||||
|
c.re = (c.re << 14) / norm;
|
||||||
|
c.im = (c.im << 14) / norm;
|
||||||
|
|
||||||
|
b.re = tab[i].re;
|
||||||
|
b.im = - tab[i].im;
|
||||||
|
CMUL(a, c, b);
|
||||||
|
tab1[i] = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=FFT23_SIZE/2;i<FFT23_SIZE;i++) {
|
||||||
|
tab1[i].re = 0;
|
||||||
|
tab1[i].im = 0;
|
||||||
|
}
|
||||||
|
for(i=0;i<FFT23_SIZE;i++)
|
||||||
|
tab[i] = tab1[i];
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("After FFT and division:\n");
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
printf("%3d: %7.4f %7.4f\n",
|
||||||
|
i, tab[i].re / FRAC, tab[i].im / FRAC);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* inverse FFT (we assume the size is a multiple of two) */
|
||||||
|
for(i=1;i<FFT23_SIZE/2;i++) {
|
||||||
|
icomplex a;
|
||||||
|
j = FFT23_SIZE - i;
|
||||||
|
a = tab[i];
|
||||||
|
tab[i] = tab[j];
|
||||||
|
tab[j] = a;
|
||||||
|
}
|
||||||
|
fft23(tab1, tab, FFT23_SIZE);
|
||||||
|
|
||||||
|
/* find the maximum real value & center the equalizer on that value */
|
||||||
|
vmax = 0;
|
||||||
|
j = 0;
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
v = abs(tab1[i].re);
|
||||||
|
if (v > vmax) {
|
||||||
|
vmax = v;
|
||||||
|
j = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* center & renormalize */
|
||||||
|
renorm = (int) (256.0 / FFT23_SIZE * RENORM * RENORM * 4.0 /
|
||||||
|
(1 << (14 - lshift)));
|
||||||
|
|
||||||
|
#if defined(DEBUG)
|
||||||
|
printf("Equalizer:\n");
|
||||||
|
printf("center=%d renorm=%d\n", j, renorm);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
k = FFT23_SIZE/2;
|
||||||
|
while (((j - k) % 3) != 0) {
|
||||||
|
if (++j == FFT23_SIZE)
|
||||||
|
j = 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
k = 0;
|
||||||
|
j = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
// s->eq_filter[k][0] = (tab1[j].re * renorm) << 8;
|
||||||
|
// s->eq_filter[k][1] = (tab1[j].im * renorm) << 8;
|
||||||
|
if (++k == FFT23_SIZE)
|
||||||
|
k = 0;
|
||||||
|
if (++j == FFT23_SIZE)
|
||||||
|
j = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
printf("%3d: %7.4f %7.4f\n",
|
||||||
|
i,
|
||||||
|
(float)s->eq_filter[i][0] / (1 << 30),
|
||||||
|
(float)s->eq_filter[i][1] / (1 << 30));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CMUL
|
||||||
|
|
||||||
|
#define CMUL(a,b,c) \
|
||||||
|
{\
|
||||||
|
(a).re=((b).re*(c).re-(b).im*(c).im);\
|
||||||
|
(a).im=((b).im*(c).re+(b).re*(c).im);\
|
||||||
|
}
|
||||||
|
|
||||||
|
void V34_fast_equalize(V34DSPState *s, s16 *input)
|
||||||
|
{
|
||||||
|
complex tab[144], tab1[144];
|
||||||
|
complex a, b, c;
|
||||||
|
float norm, d;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
tab1[i].re = input[i];
|
||||||
|
tab1[i].im = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
slow_fft(tab, tab1, 144, 0);
|
||||||
|
|
||||||
|
for(i=0;i<24;i++) {
|
||||||
|
c.re = tabPP_fft[i].re / FRAC;
|
||||||
|
c.im = tabPP_fft[i].im / FRAC;
|
||||||
|
|
||||||
|
b = tab[i];
|
||||||
|
norm = b.re * b.re + b.im * b.im;
|
||||||
|
b = tab[i+ 48];
|
||||||
|
norm += b.re * b.re + b.im * b.im;
|
||||||
|
c.re = (c.re) / norm;
|
||||||
|
c.im = (c.im) / norm;
|
||||||
|
|
||||||
|
b.re = tab[i].re;
|
||||||
|
b.im = - tab[i].im;
|
||||||
|
CMUL(a, c, b);
|
||||||
|
tab1[i] = a;
|
||||||
|
|
||||||
|
b.re = tab[48 + i].re;
|
||||||
|
b.im = - tab[48 + i].im;
|
||||||
|
CMUL(a, c, b);
|
||||||
|
tab1[48 + i] = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=24;i<48;i++) {
|
||||||
|
c.re = tabPP_fft[i].re / FRAC;
|
||||||
|
c.im = tabPP_fft[i].im / FRAC;
|
||||||
|
b = tab[i];
|
||||||
|
norm = b.re * b.re + b.im * b.im;
|
||||||
|
c.re = (c.re) / norm;
|
||||||
|
c.im = (c.im) / norm;
|
||||||
|
|
||||||
|
b.re = tab[i].re;
|
||||||
|
b.im = - tab[i].im;
|
||||||
|
CMUL(a, c, b);
|
||||||
|
tab1[i] = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=FFT23_SIZE/2;i<FFT23_SIZE;i++) {
|
||||||
|
tab1[i].re = 0;
|
||||||
|
tab1[i].im = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
printf("%3d: %7.4f %7.4f\n",
|
||||||
|
i, tab[i].re / FRAC, tab[i].im / FRAC);
|
||||||
|
}
|
||||||
|
|
||||||
|
slow_fft(tab, tab1, 144, 1);
|
||||||
|
|
||||||
|
|
||||||
|
for(i=0;i<FFT23_SIZE;i++) {
|
||||||
|
s->eq_filter[i][0] = (int)(tab[i].re * FRAC * FRAC / 12) << 16;
|
||||||
|
s->eq_filter[i][1] = (int)(tab[i].im * FRAC * FRAC / 12) << 16;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,321 @@
|
||||||
|
/*
|
||||||
|
* V34 constant data generator
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999,2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "dsp.h"
|
||||||
|
|
||||||
|
#define V34_SAMPLE_RATE_NUM 10
|
||||||
|
#define V34_SAMPLE_RATE_DEN 3
|
||||||
|
#define V34_SAMPLE_RATE ((2400*V34_SAMPLE_RATE_NUM)/V34_SAMPLE_RATE_DEN)
|
||||||
|
#define V22_TX_FILTER_SIZE (20 * 40)
|
||||||
|
|
||||||
|
#define RC_FILTER_SIZE 40
|
||||||
|
|
||||||
|
void find_data_rot(int *data_ptr, int *rot_ptr, int x0, int y0)
|
||||||
|
{
|
||||||
|
int xx,yy,data,rot,x,y;
|
||||||
|
|
||||||
|
/* find the data & rotation */
|
||||||
|
for(data=0;data<4;data++) {
|
||||||
|
x = -3 + (data & 1) * 4;
|
||||||
|
y = -3 + (data >> 1) * 4;
|
||||||
|
for(rot=0;rot<4;rot++) {
|
||||||
|
if (x == x0 && y == y0) {
|
||||||
|
*data_ptr = data;
|
||||||
|
*rot_ptr = rot;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* rotate by 90 */
|
||||||
|
xx = y;
|
||||||
|
yy = -x;
|
||||||
|
x = xx;
|
||||||
|
y = yy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 0 : rotation by 180, 1 = rotation by 90 */
|
||||||
|
int classify(int x[2][2])
|
||||||
|
{
|
||||||
|
int x0, y0, x1, y1,d0,d1,r0,r1;
|
||||||
|
|
||||||
|
x0 = 2 * x[0][0] - 3;
|
||||||
|
y0 = 2 * x[0][1] - 3;
|
||||||
|
x1 = 2 * x[1][0] - 3;
|
||||||
|
y1 = 2 * x[1][1] - 3;
|
||||||
|
|
||||||
|
find_data_rot(&d0,&r0,x0,y0);
|
||||||
|
find_data_rot(&d1,&r1,x1,y1);
|
||||||
|
if (((r0 - r1) & 1) == 0)
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void gen_table(int nb_trans)
|
||||||
|
{
|
||||||
|
int Y[5],Yt[5], trans, ss[2][3], xx[2][2];
|
||||||
|
int res, i, y0;
|
||||||
|
|
||||||
|
printf("u8 trellis_trans_%d[256][4] = {\n",
|
||||||
|
nb_trans);
|
||||||
|
|
||||||
|
for(y0=0;y0<2;y0++) {
|
||||||
|
|
||||||
|
for(trans=0;trans<nb_trans;trans++) {
|
||||||
|
printf(" /* trans=%d y0=%d */\n", trans, y0);
|
||||||
|
Yt[1] = trans & 1;
|
||||||
|
Yt[2] = (trans >> 1) & 1;
|
||||||
|
Yt[4] = (trans >> 2) & 1;
|
||||||
|
Yt[3] = (trans >> 3) & 1;
|
||||||
|
|
||||||
|
for(xx[0][0] = 0; xx[0][0] < 4; xx[0][0]++)
|
||||||
|
for(xx[0][1] = 0; xx[0][1] < 4; xx[0][1]++)
|
||||||
|
for(xx[1][0] = 0; xx[1][0] < 4; xx[1][0]++)
|
||||||
|
for(xx[1][1] = 0; xx[1][1] < 4; xx[1][1]++) {
|
||||||
|
|
||||||
|
/* (§ 9.6.3.1) find Y vector. We traducted the table into binary expressions */
|
||||||
|
for(i=0;i<2;i++) {
|
||||||
|
int x,y,y0,x0,y1,x1;
|
||||||
|
|
||||||
|
/* XXX: is it right to suppose that we use figure 9 as a periodic mapping ? */
|
||||||
|
x = xx[i][0];
|
||||||
|
y = xx[i][1];
|
||||||
|
|
||||||
|
x0 = x & 1;
|
||||||
|
x1 = ((x & 2) >> 1);
|
||||||
|
y0 = y & 1;
|
||||||
|
y1 = ((y & 2) >> 1);
|
||||||
|
|
||||||
|
ss[i][2] = x1 ^ y1 ^ y0 ^ x0;
|
||||||
|
ss[i][1] = y0;
|
||||||
|
ss[i][0] = y0 ^ x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* table 13 traducted into binary operations */
|
||||||
|
Y[4] = ss[0][2] ^ ss[1][2];
|
||||||
|
Y[3] = ss[0][1];
|
||||||
|
Y[2] = ss[0][0];
|
||||||
|
Y[1] = (ss[0][0] & ~ss[1][0] & 1) ^ ss[0][1] ^ ss[1][1];
|
||||||
|
|
||||||
|
res = 0;
|
||||||
|
switch(nb_trans) {
|
||||||
|
case 4:
|
||||||
|
res = (Yt[1] == Y[1] &&
|
||||||
|
Yt[2] == Y[2]);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
res = (Yt[1] == Y[1] &&
|
||||||
|
Yt[2] == Y[2] &&
|
||||||
|
Yt[4] == Y[4]);
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
res = (Yt[1] == Y[1] &&
|
||||||
|
Yt[2] == Y[2] &&
|
||||||
|
Yt[3] == Y[3] &&
|
||||||
|
Yt[4] == Y[4]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res && classify(xx) == y0) {
|
||||||
|
printf(" { %d, %d, %d, %d },\n",
|
||||||
|
xx[0][0],
|
||||||
|
xx[0][1],
|
||||||
|
xx[1][0],
|
||||||
|
xx[1][1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("};\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 S_tab[6][8] = {
|
||||||
|
/* a, c, d1, e1, d2, e2, J, P */
|
||||||
|
{ 1, 1, 2, 3, 3, 4, 7, 12, }, /* S=2400 */
|
||||||
|
{ 8, 7, 3, 5, 2, 3, 8, 12, }, /* S=2743 */
|
||||||
|
{ 7, 6, 3, 5, 2, 3, 7, 14, }, /* S=2800 */
|
||||||
|
{ 5, 4, 3, 5, 2, 3, 7, 15, }, /* S=3000 */
|
||||||
|
{ 4, 3, 4, 7, 3, 5, 7, 16, }, /* S=3200 */
|
||||||
|
{10, 7, 4, 7, 4, 7, 8, 15, }, /* S=3429 */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* this table depends on the sample rate. We have S=a1/c1 * V34_SAMPLE_RATE */
|
||||||
|
static u8 baud_tab[6][2] = {
|
||||||
|
/* a1, c1 */
|
||||||
|
{ 3, 10 },
|
||||||
|
{ 12, 35 },
|
||||||
|
{ 7, 20 },
|
||||||
|
{ 3, 8 },
|
||||||
|
{ 2, 5 },
|
||||||
|
{ 3, 7 },
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FFT_SIZE 2048
|
||||||
|
|
||||||
|
/* build a square raised cosine nyquist filter of n coefficients
|
||||||
|
centered on f0, with coefficients alpha and beta.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void build_sqr_nyquist_filter(float *filter,
|
||||||
|
float f0, float alpha, float beta, int n)
|
||||||
|
{
|
||||||
|
float f, f1, f2, val, tau, norm;
|
||||||
|
int i,j;
|
||||||
|
complex tab[FFT_SIZE];
|
||||||
|
|
||||||
|
f1 = (1.0 - beta) * alpha;
|
||||||
|
f2 = (1.0 + beta) * alpha;
|
||||||
|
tau = 0.5 / alpha;
|
||||||
|
norm = tau / sqrt(FFT_SIZE);
|
||||||
|
if (f0 != 0)
|
||||||
|
norm *= 0.5;
|
||||||
|
|
||||||
|
for(i=0;i<=FFT_SIZE/2;i++) {
|
||||||
|
f = i / (float)FFT_SIZE;
|
||||||
|
|
||||||
|
/* center on f0 */
|
||||||
|
f = fabs(f - f0);
|
||||||
|
|
||||||
|
if (f <= f1)
|
||||||
|
val = 1;
|
||||||
|
else if (f <= f2) {
|
||||||
|
val = 0.5 * (1.0 + cos((M_PI * tau / beta) * (f - f1)));
|
||||||
|
} else {
|
||||||
|
val = 0;
|
||||||
|
}
|
||||||
|
val = sqrt(val);
|
||||||
|
|
||||||
|
tab[i].re = val * norm;
|
||||||
|
tab[i].im = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=1;i<FFT_SIZE;i++) tab[FFT_SIZE - i] = tab[i];
|
||||||
|
|
||||||
|
fft_calc(tab, FFT_SIZE, 0);
|
||||||
|
|
||||||
|
j = FFT_SIZE - ((n-1)/2);
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
filter[i] = tab[j].re;
|
||||||
|
if (++j == FFT_SIZE)
|
||||||
|
j = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_filter(char *name, float *filter, int n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
printf("s16 %s[%d]=\n{\n",
|
||||||
|
name, n);
|
||||||
|
for(i=0;i<n;i++) {
|
||||||
|
printf("%6d, ", (int)(filter[i] * 0x4000));
|
||||||
|
if ((i % 8) == 7)
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("\n};\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int i, j, n;
|
||||||
|
float filter[FFT_SIZE];
|
||||||
|
char buf[512];
|
||||||
|
|
||||||
|
printf("/* THIS SOURCE CODE IS AUTOMATICALLY GENERATED - DO NOT MODIFY */\n");
|
||||||
|
printf("/*\n"
|
||||||
|
" * V34 tables\n"
|
||||||
|
" * \n"
|
||||||
|
" * Copyright (c) 1999,2000 Fabrice Bellard.\n"
|
||||||
|
" *\n"
|
||||||
|
" * This code is released under the GNU General Public License version\n"
|
||||||
|
" * 2. Please read the file COPYING to know the exact terms of the\n"
|
||||||
|
" * license.\n"
|
||||||
|
" */\n");
|
||||||
|
|
||||||
|
printf("#include \"lm.h\"\n"
|
||||||
|
"#include \"v34priv.h\"\n"
|
||||||
|
"\n");
|
||||||
|
|
||||||
|
/* tables for trellis coded modulation */
|
||||||
|
gen_table(4);
|
||||||
|
gen_table(8);
|
||||||
|
gen_table(16);
|
||||||
|
|
||||||
|
/* rx filters which convert the 8000 Hz flow to (symbol_rate * 3)
|
||||||
|
with a nyquist filter */
|
||||||
|
|
||||||
|
for(j=0;j<6;j++) {
|
||||||
|
float symbol_rate, carrier, alpha, beta, freq;
|
||||||
|
|
||||||
|
symbol_rate = 2400.0 *
|
||||||
|
(float)S_tab[j][0] / (float)S_tab[j][1];
|
||||||
|
|
||||||
|
for(i=0;i<2;i++) {
|
||||||
|
if (i == 1 &&
|
||||||
|
S_tab[j][2] == S_tab[j][4] &&
|
||||||
|
S_tab[j][3] == S_tab[j][5])
|
||||||
|
break;
|
||||||
|
|
||||||
|
carrier = symbol_rate *
|
||||||
|
(float)S_tab[j][2 + 2*i] / (float)S_tab[j][3 + 2*i];
|
||||||
|
|
||||||
|
alpha = symbol_rate / (2.0 * symbol_rate * 3.0 * baud_tab[j][1]);
|
||||||
|
beta = 0.1;
|
||||||
|
freq = carrier / (symbol_rate * 3.0 * baud_tab[j][1]);
|
||||||
|
n = RC_FILTER_SIZE * baud_tab[j][1] + 1;
|
||||||
|
|
||||||
|
printf("/* S=%d carrier=%d alpha=%f beta=%f f0=%f */\n",
|
||||||
|
(int)rint(symbol_rate),
|
||||||
|
(int)rint(carrier),
|
||||||
|
alpha, beta, freq);
|
||||||
|
|
||||||
|
build_sqr_nyquist_filter(filter, freq, alpha, beta, n);
|
||||||
|
|
||||||
|
sprintf(buf, "v34_rx_filter_%d_%d",
|
||||||
|
(int)rint(symbol_rate),
|
||||||
|
(int)rint(carrier));
|
||||||
|
|
||||||
|
write_filter(buf, filter, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tx filters */
|
||||||
|
|
||||||
|
for(j=0;j<6;j++) {
|
||||||
|
float alpha, beta;
|
||||||
|
|
||||||
|
alpha = 1.0 / (2.0 * baud_tab[j][1]);
|
||||||
|
beta = 0.1;
|
||||||
|
n = RC_FILTER_SIZE * baud_tab[j][1] + 1;
|
||||||
|
|
||||||
|
build_sqr_nyquist_filter(filter, 0.0, alpha, beta, n);
|
||||||
|
|
||||||
|
sprintf(buf, "v34_rc_%d_filter", baud_tab[j][1]);
|
||||||
|
write_filter(buf, filter, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* V22 filters */
|
||||||
|
|
||||||
|
/* 600 sym/s for 8000 Hz, beta=0.75 */
|
||||||
|
build_sqr_nyquist_filter(filter, 0.0, 1.0 / (2.0 * 40.0), 0.75,
|
||||||
|
V22_TX_FILTER_SIZE);
|
||||||
|
|
||||||
|
write_filter("v22_tx_filter", filter, V22_TX_FILTER_SIZE);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* Implementation of the V34 phase 2
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999,2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* This implementation is totally clean room. It was written by
|
||||||
|
* reading the V34 specification and by using basic signal processing
|
||||||
|
* knowledge.
|
||||||
|
*/
|
||||||
|
#include "lm.h"
|
||||||
|
#include "v34priv.h"
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
|
#define INFO_SYNC 0x72
|
||||||
|
|
||||||
|
/* send the v34 info0 sequence */
|
||||||
|
void V34_send_info0(V34State *s, int ack)
|
||||||
|
{
|
||||||
|
u8 buf[48], *p, *q;
|
||||||
|
int crc;
|
||||||
|
|
||||||
|
p = buf;
|
||||||
|
put_bits(&p, 4, 0xf); /* fill bits */
|
||||||
|
put_bits(&p, 8, INFO_SYNC); /* sync word */
|
||||||
|
|
||||||
|
put_bits(&p, 1, 1); /* symbol rate 2743 supported ? */
|
||||||
|
put_bits(&p, 1, 1); /* symbol rate 2800 supported ? */
|
||||||
|
put_bits(&p, 1, 1); /* symbol rate 3429 supported ? */
|
||||||
|
put_bits(&p, 1, 1); /* symbol rate 3000, low carrier supported ? */
|
||||||
|
put_bits(&p, 1, 1); /* symbol rate 3000, high carrier supported ? */
|
||||||
|
put_bits(&p, 1, 1); /* symbol rate 3200, low carrier supported ? */
|
||||||
|
put_bits(&p, 1, 1); /* symbol rate 3200, high carrier supported ? */
|
||||||
|
|
||||||
|
put_bits(&p, 1, 1); /* symbol rate 3429 disallowed ? */
|
||||||
|
put_bits(&p, 1, 1); /* can power reduce ? */
|
||||||
|
put_bits(&p, 3, 5); /* difference between emit & receive sym rate */
|
||||||
|
put_bits(&p, 1, 0); /* from CME modem ? */
|
||||||
|
put_bits(&p, 1, 0); /* reserved by ITU */
|
||||||
|
put_bits(&p, 2, 0); /* tx clock source: 0=internal */
|
||||||
|
put_bits(&p, 1, ack); /* ack reception */
|
||||||
|
|
||||||
|
/* crc */
|
||||||
|
crc = calc_crc(buf + 12, p - (buf + 12));
|
||||||
|
put_bits(&p, 16, crc);
|
||||||
|
|
||||||
|
put_bits(&p, 4, 0xf); /* fill bits */
|
||||||
|
|
||||||
|
for(q=buf;q<p;q++) {
|
||||||
|
put_sym(s, buf[i], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* send the v34 info1c sequence */
|
||||||
|
void V34_send_info1c(V34State *s)
|
||||||
|
{
|
||||||
|
u8 buf[109], *p;
|
||||||
|
int crc;
|
||||||
|
|
||||||
|
p = buf;
|
||||||
|
|
||||||
|
put_bits(&p, 4, 0xf); /* fill bits */
|
||||||
|
put_bits(&p, 8, INFO_SYNC); /* sync word */
|
||||||
|
|
||||||
|
put_bits(&p, 3, 1); /* minimum power reduction (XXX) */
|
||||||
|
put_bits(&p, 3, 1); /* additional power reduction (XXX) */
|
||||||
|
put_bits(&p, 7, 0); /* length of MD sequence (35 ms incr) */
|
||||||
|
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
/* for each symbol speed (increasing order) */
|
||||||
|
put_bits(&p, 1, 0); /* use high carrier ? (XXX) */
|
||||||
|
put_bits(&p, 4, 0); /* pre emphasis filter (XXX) */
|
||||||
|
put_bits(&p, 4, 12); /* projected data rate (XXX) */
|
||||||
|
}
|
||||||
|
|
||||||
|
put_bits(&p, 10, 0); /* frequency offset (XXX) */
|
||||||
|
|
||||||
|
/* crc */
|
||||||
|
crc = calc_crc(buf + 12, p - (buf + 12));
|
||||||
|
put_bits(&p, 16, crc);
|
||||||
|
|
||||||
|
put_bits(&p, 4, 0xf); /* fill bits */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* send the v34 info1a sequence */
|
||||||
|
void V34_send_info1a(V34State *s)
|
||||||
|
{
|
||||||
|
u8 buf[70], *p;
|
||||||
|
int crc;
|
||||||
|
|
||||||
|
p = buf;
|
||||||
|
|
||||||
|
put_bits(&p, 4, 0xf); /* fill bits */
|
||||||
|
put_bits(&p, 8, INFO_SYNC); /* sync word */
|
||||||
|
|
||||||
|
put_bits(&p, 3, 1); /* minimum power reduction (XXX) */
|
||||||
|
put_bits(&p, 3, 1); /* additional power reduction (XXX) */
|
||||||
|
put_bits(&p, 7, 0); /* length of MD sequence (35 ms incr) */
|
||||||
|
|
||||||
|
put_bits(&p, 1, 0); /* high carrier used (XXX) */
|
||||||
|
put_bits(&p, 4, 0); /* pre emphasis filter (XXX) */
|
||||||
|
put_bits(&p, 4, 12); /* proj max data rate (XXX) */
|
||||||
|
|
||||||
|
put_bits(&p, 3, 4); /* sym rate ans->cal (XXX : 3200) */
|
||||||
|
put_bits(&p, 3, 4); /* sym rate cal->ans (XXX : 3200) */
|
||||||
|
|
||||||
|
put_bits(&p, 10, 0); /* frequency offset (XXX) */
|
||||||
|
|
||||||
|
/* crc */
|
||||||
|
crc = calc_crc(buf + 12, p - (buf + 12));
|
||||||
|
put_bits(&p, 16, crc);
|
||||||
|
|
||||||
|
put_bits(&p, 4, 0xf); /* fill bits */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void V34_send_L1(V34State *s, int rep)
|
||||||
|
{
|
||||||
|
static char ph[25] = {
|
||||||
|
/* 150 */ 1,-1, 1, 1,
|
||||||
|
/* 750 */ 1, 0, 1, 0,
|
||||||
|
/*1350 */ 1, 1,-1, 0,
|
||||||
|
/*1950 */ 1, 1,-1, 0,
|
||||||
|
/*2550 */ 1,-1, 1,-1,
|
||||||
|
/*3150 */ -1,-1,-1, 1,
|
||||||
|
/*3750 */ 1 };
|
||||||
|
|
||||||
|
for(n=0;n<rep;n++) {
|
||||||
|
for(i=0;i<25;i++) {
|
||||||
|
if (ph[i]) {
|
||||||
|
put_sym(s, i * 150 + 150, ph[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int V34P2_process(V34State *s, s16 *output, s16 *input, int nb_samples)
|
||||||
|
{
|
||||||
|
/* modulation */
|
||||||
|
switch(s->state) {
|
||||||
|
case V34_P2_INFO0_SEND:
|
||||||
|
V34_send_info0(
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* demodulation */
|
||||||
|
switch(s->state) {
|
||||||
|
case V34_P2_INFO0_SEND:
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
#ifndef V34PRIV_H
|
||||||
|
#define V34PRIV_H
|
||||||
|
|
||||||
|
#define MAX_MAPPING_FRAME_SIZE 79
|
||||||
|
#define M_MAX 18
|
||||||
|
|
||||||
|
/* symbol rate */
|
||||||
|
enum {
|
||||||
|
V34_S2400,
|
||||||
|
V34_S2743,
|
||||||
|
V34_S2800,
|
||||||
|
V34_S3000,
|
||||||
|
V34_S3200,
|
||||||
|
V34_S3429,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* constellation parameters */
|
||||||
|
#define L_MAX 1664 /* max number of points in the constellation */
|
||||||
|
#define C_MIN -11
|
||||||
|
#define C_MAX 11
|
||||||
|
#define C_MAX_SIZE ((C_MAX-C_MIN+1)*(C_MAX-C_MIN+1))
|
||||||
|
#define C_RADIUS (2*(C_MAX-C_MIN)+1) /* max coordinate of the constellation */
|
||||||
|
#define SYNC_PATTERN 0x77FA /* (table 12) synchronisation pattern for J=8 */
|
||||||
|
|
||||||
|
#define V34_SAMPLE_RATE_NUM 10
|
||||||
|
#define V34_SAMPLE_RATE_DEN 3
|
||||||
|
#define V34_SAMPLE_RATE ((2400*V34_SAMPLE_RATE_NUM)/V34_SAMPLE_RATE_DEN)
|
||||||
|
|
||||||
|
/* size of the raised root cosine filter (for both rx & tx) */
|
||||||
|
#define RC_FILTER_SIZE 40
|
||||||
|
|
||||||
|
#define TX_BUF_SIZE (2048)
|
||||||
|
|
||||||
|
#define RX_BUF1_SIZE 256
|
||||||
|
#define RX_BUF2_SIZE 256
|
||||||
|
|
||||||
|
/* size of the complex equalizer filter */
|
||||||
|
#define EQ_FRAC 3
|
||||||
|
#define EQ_SIZE (52*EQ_FRAC)
|
||||||
|
|
||||||
|
#define AGC_WINDOW_SIZE 512 /* must be a power of two, in input samples */
|
||||||
|
|
||||||
|
#define TRELLIS_MAX_STATES 64
|
||||||
|
/* 5 times the constraint length */
|
||||||
|
#define TRELLIS_LENGTH (6*5)
|
||||||
|
|
||||||
|
/* 10 fractional bits for nyquist filters */
|
||||||
|
#define NQ_BITS 10
|
||||||
|
#define NQ_BASE (1 << NQ_BITS)
|
||||||
|
|
||||||
|
/* state of the signal processing part of the V34 transmitter */
|
||||||
|
typedef struct V34DSPState {
|
||||||
|
/* V34 parameters */
|
||||||
|
int calling; /* true if we are the caller */
|
||||||
|
int S; /* index for symbol rate */
|
||||||
|
int expanded_shape; /* true if expanded shape used */
|
||||||
|
int R; /* transmit rate (in bits/s, including aux channel) */
|
||||||
|
int conv_nb_states; /* number of states of the convolutional coder */
|
||||||
|
int use_non_linear;
|
||||||
|
int use_high_carrier;
|
||||||
|
s16 h[3][2]; /* precoding coefficients (14 bits fractional part) */
|
||||||
|
|
||||||
|
void *opaque;
|
||||||
|
get_bit_func get_bit;
|
||||||
|
|
||||||
|
put_bit_func put_bit;
|
||||||
|
|
||||||
|
/* do not modify after this */
|
||||||
|
|
||||||
|
int N; /* total number of bits in a data frame */
|
||||||
|
int W; /* number of aux bits in a data frame (0 = no aux channel) */
|
||||||
|
int J; /* number of data frame in a super frame */
|
||||||
|
int P; /* number of mapping frame in a data frame */
|
||||||
|
int b; /* max length of a mapping frame */
|
||||||
|
int r; /* counter to know the length of the mapping frame */
|
||||||
|
int K; /* mapping parameters */
|
||||||
|
int q;
|
||||||
|
int L; /* current number of points of the constellation */
|
||||||
|
int M; /* current number of rings */
|
||||||
|
int Z_1; /* previous Z value (see § 9.5) */
|
||||||
|
|
||||||
|
int mapping_frame; /* number of the mapping frame */
|
||||||
|
int rcnt, acnt; /* fractional counters to know the number of bits
|
||||||
|
in a mapping frame */
|
||||||
|
int half_data_frame_count; /* number of half data frame */
|
||||||
|
int sync_count; /* counter mod 2P for synchronisation */
|
||||||
|
s16 x[3][2]; /* 3 most recent samples for precoding (7 bit fractional part) */
|
||||||
|
int U0;
|
||||||
|
int conv_reg; /* memory of the convolutional coder */
|
||||||
|
int scrambler_reg; /* state of the self synchronizing scrambler */
|
||||||
|
float carrier_freq;
|
||||||
|
float symbol_rate;
|
||||||
|
s8 constellation[C_MAX_SIZE][2];
|
||||||
|
|
||||||
|
/* precomputed bases for the ring computation */
|
||||||
|
int g2_tab[8*(M_MAX - 1) + 1];
|
||||||
|
int g4_tab[8*(M_MAX - 1) + 1];
|
||||||
|
int g8_tab[8*(M_MAX - 1) + 1];
|
||||||
|
int z8_tab[8*(M_MAX - 1) + 1];
|
||||||
|
|
||||||
|
/* for decoding only */
|
||||||
|
u16 constellation_to_code[C_RADIUS+1][C_RADIUS+1];
|
||||||
|
|
||||||
|
/* for encoding only */
|
||||||
|
s16 *tx_filter;
|
||||||
|
s16 tx_buf[TX_BUF_SIZE][2];
|
||||||
|
int tx_buf_ptr, tx_outbuf_ptr, tx_buf_size;
|
||||||
|
int tx_filter_wsize;
|
||||||
|
int baud_num, baud_denom;
|
||||||
|
int baud_incr;
|
||||||
|
int baud_phase;
|
||||||
|
int carrier_phase;
|
||||||
|
int carrier_incr;
|
||||||
|
|
||||||
|
s16 tx_amp; /* amplitude for transmit : each symbol is multiplied
|
||||||
|
by it (1:8:7) */
|
||||||
|
|
||||||
|
int baud3_phase;
|
||||||
|
s16 *rx_filter;
|
||||||
|
int rx_filter_wsize;
|
||||||
|
s16 rx_buf1[RX_BUF1_SIZE];
|
||||||
|
int rx_buf1_ptr;
|
||||||
|
|
||||||
|
/* symbol synchronization */
|
||||||
|
s16 sync_low_mem[2];
|
||||||
|
s16 sync_low_coef[2];
|
||||||
|
s16 sync_high_mem[2];
|
||||||
|
s16 sync_high_coef[2];
|
||||||
|
s16 sync_A, sync_B, sync_C;
|
||||||
|
|
||||||
|
/* equalizer */
|
||||||
|
s32 eq_filter[EQ_SIZE][2];
|
||||||
|
s16 eq_buf[EQ_SIZE];
|
||||||
|
int eq_buf_ptr;
|
||||||
|
int eq_shift;
|
||||||
|
|
||||||
|
/* AGC */
|
||||||
|
float agc_mem;
|
||||||
|
float agc_coef;
|
||||||
|
int agc_power;
|
||||||
|
int agc_gain;
|
||||||
|
|
||||||
|
/* Viterbi decoder */
|
||||||
|
|
||||||
|
/* the previous decoded decision comming to this path. Each
|
||||||
|
decision Y[5] is coded on one byte */
|
||||||
|
|
||||||
|
u8 state_decision[TRELLIS_MAX_STATES][TRELLIS_LENGTH];
|
||||||
|
u8 state_path[TRELLIS_MAX_STATES][TRELLIS_LENGTH];
|
||||||
|
s16 state_memory[TRELLIS_LENGTH][4];
|
||||||
|
u8 u0_memory[TRELLIS_LENGTH];
|
||||||
|
int state_error[TRELLIS_MAX_STATES];
|
||||||
|
int state_error1[TRELLIS_MAX_STATES];
|
||||||
|
int trellis_ptr;
|
||||||
|
|
||||||
|
/* decoder synchronization */
|
||||||
|
int phase_4d; /* index of the current 2d symbol in the 4D symbol
|
||||||
|
(0 or 1) */
|
||||||
|
int phase_mse; /* MSE to find if we are synchronized on a 4D symbol */
|
||||||
|
int phase_mse_cnt;
|
||||||
|
|
||||||
|
s16 yy[2][2]; /* current 4D symbol */
|
||||||
|
s16 rx_mapping_frame[2*4][2];
|
||||||
|
int rx_mapping_frame_count;
|
||||||
|
|
||||||
|
/* rx state */
|
||||||
|
int sym_count;
|
||||||
|
|
||||||
|
/* current V34 protocol state */
|
||||||
|
int state;
|
||||||
|
int is_16states; /* 16 states required in the startup sequences */
|
||||||
|
|
||||||
|
/* interaction with receiver */
|
||||||
|
int J_received;
|
||||||
|
int JP_received;
|
||||||
|
} V34DSPState;
|
||||||
|
|
||||||
|
u8 trellis_trans_4[256][4];
|
||||||
|
u8 trellis_trans_8[256][4];
|
||||||
|
u8 trellis_trans_16[256][4];
|
||||||
|
|
||||||
|
/* V34 states */
|
||||||
|
enum {
|
||||||
|
V34_STARTUP3_S1,
|
||||||
|
V34_STARTUP3_SINV1,
|
||||||
|
V34_STARTUP3_S2,
|
||||||
|
V34_STARTUP3_SINV2,
|
||||||
|
V34_STARTUP3_PP,
|
||||||
|
V34_STARTUP3_TRN,
|
||||||
|
V34_STARTUP3_J,
|
||||||
|
V34_STARTUP3_JP,
|
||||||
|
V34_STARTUP3_WAIT_J,
|
||||||
|
|
||||||
|
V34_STARTUP4_S,
|
||||||
|
V34_STARTUP4_WAIT_JP,
|
||||||
|
V34_STARTUP4_SINV,
|
||||||
|
V34_STARTUP4_TRN,
|
||||||
|
V34_STARTUP4_MP,
|
||||||
|
V34_STARTUP4_MPP,
|
||||||
|
V34_STARTUP4_E,
|
||||||
|
V34_DATA,
|
||||||
|
|
||||||
|
/* receive only */
|
||||||
|
V34_STARTUP3_WAIT_S1,
|
||||||
|
};
|
||||||
|
|
||||||
|
void put_bits(u8 **pp, int n, int bits);
|
||||||
|
int calc_crc(u8 *buf, int size);
|
||||||
|
void v34_send_info0(V34DSPState *s, int ack);
|
||||||
|
|
||||||
|
#define DSPK_TX_FILTER_SIZE 321
|
||||||
|
extern s16 v34_dpsk_tx_filter[DSPK_TX_FILTER_SIZE];
|
||||||
|
|
||||||
|
extern s16 v34_rc_5_filter[];
|
||||||
|
extern s16 v34_rc_7_filter[];
|
||||||
|
extern s16 v34_rc_8_filter[];
|
||||||
|
extern s16 v34_rc_10_filter[];
|
||||||
|
extern s16 v34_rc_20_filter[];
|
||||||
|
extern s16 v34_rc_35_filter[];
|
||||||
|
|
||||||
|
extern s16 v34_rx_filter_2400_1600[];
|
||||||
|
extern s16 v34_rx_filter_2400_1800[];
|
||||||
|
extern s16 v34_rx_filter_2743_1646[];
|
||||||
|
extern s16 v34_rx_filter_2743_1829[];
|
||||||
|
extern s16 v34_rx_filter_2800_1680[];
|
||||||
|
extern s16 v34_rx_filter_2800_1867[];
|
||||||
|
extern s16 v34_rx_filter_3000_1800[];
|
||||||
|
extern s16 v34_rx_filter_3000_2000[];
|
||||||
|
extern s16 v34_rx_filter_3200_1829[];
|
||||||
|
extern s16 v34_rx_filter_3200_1920[];
|
||||||
|
extern s16 v34_rx_filter_3429_1959[];
|
||||||
|
|
||||||
|
/* v34eq.c */
|
||||||
|
typedef struct {
|
||||||
|
s16 re, im;
|
||||||
|
} icomplex;
|
||||||
|
|
||||||
|
#define V34_PP_SIZE 48
|
||||||
|
extern icomplex tabPP[V34_PP_SIZE];
|
||||||
|
|
||||||
|
void V34eq_init(void);
|
||||||
|
void V34_fast_equalize(V34DSPState *s, s16 *input);
|
||||||
|
|
||||||
|
typedef struct V34State {
|
||||||
|
/* V34 parameters test */
|
||||||
|
int calling; /* true if we are the caller */
|
||||||
|
int S; /* index for symbol rate */
|
||||||
|
int expanded_shape; /* true if expanded shape used */
|
||||||
|
int R; /* transmit rate (in bits/s, excluding aux channel) */
|
||||||
|
int conv_nb_states; /* number of states of the convolutional coder */
|
||||||
|
int use_non_linear;
|
||||||
|
int use_high_carrier;
|
||||||
|
int use_aux_channel;
|
||||||
|
s16 h[3][2]; /* precoding coefficients (14 bits fractional part) */
|
||||||
|
|
||||||
|
V34DSPState v34_tx;
|
||||||
|
V34DSPState v34_rx;
|
||||||
|
} V34State;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,559 @@
|
||||||
|
/*
|
||||||
|
* V8 protocol handler
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999,2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* This implementation is totally clean room. It was written by
|
||||||
|
* reading the ITU specification and by using basic signal processing
|
||||||
|
* knowledge.
|
||||||
|
*/
|
||||||
|
#include "lm.h"
|
||||||
|
|
||||||
|
static void V8_mod_init(V8_mod_state *s)
|
||||||
|
{
|
||||||
|
s->sample_rate = V8_SAMPLE_RATE;
|
||||||
|
|
||||||
|
/* ANSam tone: 2100 Hz, amplitude modulated at 15 Hz, with phase
|
||||||
|
reversal every 450 ms */
|
||||||
|
s->phase = 0;
|
||||||
|
s->phase_incr = (int) (PHASE_BASE * 2100.0 / s->sample_rate);
|
||||||
|
s->mod_phase = 0;
|
||||||
|
s->mod_phase_incr = (int) (PHASE_BASE * 15.0 / s->sample_rate);
|
||||||
|
s->phase_reverse_samples = (int) (s->sample_rate * 0.450);
|
||||||
|
s->phase_reverse_left = 0;
|
||||||
|
/* XXX: incorrect power */
|
||||||
|
s->amp = (int) (pow(10, s->tone_level / 20.0) * 32768.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void V8_mod(V8_mod_state *s, s16 *samples, unsigned int nb)
|
||||||
|
{
|
||||||
|
int amp,i;
|
||||||
|
|
||||||
|
for(i=0;i<nb;i++) {
|
||||||
|
/* handle phase reversal every 450 ms */
|
||||||
|
if (s->phase_reverse_left == 0) {
|
||||||
|
s->phase_reverse_left = s->phase_reverse_samples;
|
||||||
|
s->phase += PHASE_BASE / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
amp = (dsp_cos(s->mod_phase) * (int)(0.2 * COS_BASE)) >> COS_BITS;
|
||||||
|
amp += COS_BASE; /* between 0.8 and 1.2 */
|
||||||
|
samples[i] = (amp * dsp_cos(s->phase)) >> COS_BITS;
|
||||||
|
s->mod_phase += s->mod_phase_incr;
|
||||||
|
s->phase += s->phase_incr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recognize the V8 ANSam tone. Some other tones (in particular V21
|
||||||
|
tone) may be added later. We compute the DFT for every interesting
|
||||||
|
frequency and do a threshold with the power. Not the best method,
|
||||||
|
but easy to implement and quite reliable. */
|
||||||
|
|
||||||
|
static void V8_demod_init(V8_demod_state *s)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i=0;i<V8_N;i++) {
|
||||||
|
s->cos_tab[i] = (int) (cos( 2 * M_PI * i / V8_N) * COS_BASE);
|
||||||
|
s->sin_tab[i] = (int) (sin( 2 * M_PI * i / V8_N) * COS_BASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
s->buf_ptr = 0;
|
||||||
|
s->v8_ANSam_detected = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void V8_demod(V8_demod_state *s, const s16 *samples, unsigned int nb)
|
||||||
|
{
|
||||||
|
int i, p0, p1;
|
||||||
|
|
||||||
|
for(i=0;i<nb;i++) {
|
||||||
|
s->buf[s->buf_ptr++] = samples[i];
|
||||||
|
if (s->buf_ptr >= V8_N) {
|
||||||
|
s->buf_ptr = 0;
|
||||||
|
dsp_sar_tab(s->buf, V8_N, 8);
|
||||||
|
p0 = dsp_norm2(s->buf, V8_N, 0);
|
||||||
|
p1 = compute_DFT(s->cos_tab, s->sin_tab, s->buf, DFT_COEF_2100, V8_N);
|
||||||
|
/* XXX: this test is incorrect (not homogenous) */
|
||||||
|
if ((p0 > 1000) && (p1 > (5*p0))) {
|
||||||
|
/* V8 tone recognized */
|
||||||
|
s->v8_ANSam_detected = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* V8 stream decoding */
|
||||||
|
static void ci_decode(V8State *s)
|
||||||
|
{
|
||||||
|
int data = s->rx_data[0];
|
||||||
|
if (data == 0x83) {
|
||||||
|
printf("CI: data call\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CM or JM decoding */
|
||||||
|
static void cm_decode(V8State *s)
|
||||||
|
{
|
||||||
|
u8 *p;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
if (s->got_cm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (s->cm_count > 0) {
|
||||||
|
/* we must receive two identical CM sequences */
|
||||||
|
if (s->cm_count == s->rx_data_ptr &&
|
||||||
|
!memcmp(s->cm_data, s->rx_data, s->rx_data_ptr)) {
|
||||||
|
/* got CM !! */
|
||||||
|
s->got_cm = 1;
|
||||||
|
/* decode it */
|
||||||
|
/* XXX: this decoding is sufficient for modulations, but
|
||||||
|
not exhaustive */
|
||||||
|
s->decoded_modulations = 0;
|
||||||
|
p = s->cm_data;
|
||||||
|
/* zero is used to indicate the end */
|
||||||
|
s->cm_data[s->cm_count] = 0;
|
||||||
|
|
||||||
|
c = *p++;
|
||||||
|
/* call function */
|
||||||
|
if ((c & 0xf8) != 0x80)
|
||||||
|
return;
|
||||||
|
if (c != V8_CALL_FUNC_DATA)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* modulation */
|
||||||
|
c = *p++;
|
||||||
|
if ((c & 0xf8) != V8_MODN0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (c & V8_MODN0_V90)
|
||||||
|
s->decoded_modulations |= V8_MOD_V90;
|
||||||
|
if (c & V8_MODN0_V34)
|
||||||
|
s->decoded_modulations |= V8_MOD_V34;
|
||||||
|
|
||||||
|
c = *p++;
|
||||||
|
if ((c & 0x1c) == V8_EXT) {
|
||||||
|
/* ignored */
|
||||||
|
c = *p++;
|
||||||
|
if ((c & 0x1c) == V8_EXT) {
|
||||||
|
if (c & V8_MODN2_V23)
|
||||||
|
s->decoded_modulations |= V8_MOD_V23;
|
||||||
|
if (c & V8_MODN2_V21)
|
||||||
|
s->decoded_modulations |= V8_MOD_V21;
|
||||||
|
/* skip other extensions */
|
||||||
|
do {
|
||||||
|
c = *p++;
|
||||||
|
} while ((c & 0x1c) == V8_EXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* save the current CM sequence */
|
||||||
|
s->cm_count = s->rx_data_ptr;
|
||||||
|
memcpy(s->cm_data, s->rx_data, s->rx_data_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_bit(void *opaque, int bit)
|
||||||
|
{
|
||||||
|
V8State *s = opaque;
|
||||||
|
int new_state, i;
|
||||||
|
|
||||||
|
/* wait ten ones & synchro */
|
||||||
|
s->bit_sync = ((s->bit_sync << 1) | bit) & ((1 << 20) - 1);
|
||||||
|
if (s->bit_sync == ((V8_TEN_ONES << 10) | V8_CI_SYNC)) {
|
||||||
|
new_state = V8_CI_SYNC;
|
||||||
|
goto data_init;
|
||||||
|
} else if (s->bit_sync == ((V8_TEN_ONES << 10) | V8_CM_SYNC)) {
|
||||||
|
new_state = V8_CM_SYNC;
|
||||||
|
data_init:
|
||||||
|
/* debug */
|
||||||
|
if (s->data_state == V8_CI_SYNC) {
|
||||||
|
printf("CI: ");
|
||||||
|
} else if (s->data_state == V8_CM_SYNC) {
|
||||||
|
if (s->calling)
|
||||||
|
printf("JM: ");
|
||||||
|
else
|
||||||
|
printf("CM: ");
|
||||||
|
}
|
||||||
|
for(i=0;i<s->rx_data_ptr;i++) printf(" %02x", s->rx_data[i]);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
/* decode previous sequence */
|
||||||
|
switch(s->data_state) {
|
||||||
|
case V8_CI_SYNC:
|
||||||
|
ci_decode(s);
|
||||||
|
break;
|
||||||
|
case V8_CM_SYNC:
|
||||||
|
cm_decode(s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
s->data_state = new_state;
|
||||||
|
s->bit_buf = 0;
|
||||||
|
s->bit_cnt = 0;
|
||||||
|
s->rx_data_ptr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parse octets with 1 bit start, 1 bit stop */
|
||||||
|
if (s->data_state) {
|
||||||
|
s->bit_buf = ((s->bit_buf << 1) | bit) & ((1 << 10) - 1);
|
||||||
|
s->bit_cnt++;
|
||||||
|
/* start, stop ? */
|
||||||
|
if ((s->bit_buf & 0x201) == 0x001 && s->bit_cnt >= 10) {
|
||||||
|
int data;
|
||||||
|
/* store the available data */
|
||||||
|
data = (s->bit_buf >> 1) & 0xff;
|
||||||
|
printf("got data: %d %02x\n", s->data_state, data);
|
||||||
|
/* CJ detection */
|
||||||
|
if (data == 0) {
|
||||||
|
if (++s->data_zero_count == 3) {
|
||||||
|
s->got_cj = 1;
|
||||||
|
printf("got CJ\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s->data_zero_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s->rx_data_ptr < (sizeof(s->rx_data)-1)) {
|
||||||
|
s->rx_data[s->rx_data_ptr++] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->bit_cnt = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void v8_decode_init(V8State *s)
|
||||||
|
{
|
||||||
|
V21_demod_init(&s->v21_rx, s->calling, put_bit, s);
|
||||||
|
s->data_state = 0;
|
||||||
|
s->bit_sync = 0;
|
||||||
|
s->cm_count = 0;
|
||||||
|
s->got_cm = 0;
|
||||||
|
|
||||||
|
s->got_cj = 0;
|
||||||
|
s->data_zero_count = 0;
|
||||||
|
s->rx_data_ptr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int get_bit(void *opaque)
|
||||||
|
{
|
||||||
|
V8State *s = opaque;
|
||||||
|
int bit;
|
||||||
|
|
||||||
|
bit = sm_get_bit(&s->tx_fifo);
|
||||||
|
if (bit < 0)
|
||||||
|
bit = 1;
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void v8_put_byte(V8State *s, int data)
|
||||||
|
{
|
||||||
|
/* insert start & stop bits */
|
||||||
|
sm_put_bits(&s->tx_fifo, ((data & 0xff) << 1) | 1, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void V8_init(V8State *sm, int calling, int mod_mask)
|
||||||
|
{
|
||||||
|
sm->debug_laststate = -1;
|
||||||
|
sm->calling = calling;
|
||||||
|
if (sm->calling) {
|
||||||
|
sm->state = V8_WAIT_1SECOND;
|
||||||
|
sm_set_timer(&sm->v8_start_timer, 1000);
|
||||||
|
} else {
|
||||||
|
/* wait 200 ms */
|
||||||
|
sm_set_timer(&sm->v8_connect_timer, 200);
|
||||||
|
sm->state = V8_WAIT;
|
||||||
|
}
|
||||||
|
sm_init_fifo(&sm->rx_fifo, sm->rx_buf, sizeof(sm->rx_buf));
|
||||||
|
sm_init_fifo(&sm->tx_fifo, sm->tx_buf, sizeof(sm->tx_buf));
|
||||||
|
sm->modulation_mask = mod_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* send CM or JM */
|
||||||
|
static void cm_send(V8State *s, int mod_mask)
|
||||||
|
{
|
||||||
|
int val;
|
||||||
|
|
||||||
|
sm_put_bits(&s->tx_fifo, V8_TEN_ONES, 10);
|
||||||
|
sm_put_bits(&s->tx_fifo, V8_CM_SYNC, 10);
|
||||||
|
|
||||||
|
/* data call */
|
||||||
|
v8_put_byte(s, V8_CALL_FUNC_DATA);
|
||||||
|
|
||||||
|
/* supported modulations */
|
||||||
|
val = V8_MODN0;
|
||||||
|
if (mod_mask & V8_MOD_V90)
|
||||||
|
val |= V8_MODN0_V90;
|
||||||
|
if (mod_mask & V8_MOD_V34)
|
||||||
|
val |= V8_MODN0_V34;
|
||||||
|
v8_put_byte(s, val);
|
||||||
|
v8_put_byte(s, V8_EXT);
|
||||||
|
val = V8_EXT;
|
||||||
|
if (mod_mask & V8_MOD_V23)
|
||||||
|
val |= V8_MODN2_V23;
|
||||||
|
if (mod_mask & V8_MOD_V21)
|
||||||
|
val |= V8_MODN2_V21;
|
||||||
|
v8_put_byte(s, val);
|
||||||
|
|
||||||
|
/* for now, no LAPM */
|
||||||
|
//v8_put_byte(s, V8_DATA_LAPM);
|
||||||
|
|
||||||
|
/* We are not on celullar connection. What is that,
|
||||||
|
anyway? GSM? Don't send this - we don't what it is
|
||||||
|
for, anyway. */
|
||||||
|
//v8_put_byte(s, V8_DATA_NOCELULAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* selection the modulation according to V8 priority from the bits in 'mask' */
|
||||||
|
static int select_modulation(int mask)
|
||||||
|
{
|
||||||
|
int val;
|
||||||
|
|
||||||
|
val = V8_MOD_HANGUP;
|
||||||
|
/* use modulations in this order */
|
||||||
|
if (mask & V8_MOD_V21)
|
||||||
|
val = V8_MOD_V21;
|
||||||
|
if (mask & V8_MOD_V23)
|
||||||
|
val = V8_MOD_V23;
|
||||||
|
if (mask & V8_MOD_V34)
|
||||||
|
val = V8_MOD_V34;
|
||||||
|
if (mask & V8_MOD_V90)
|
||||||
|
val = V8_MOD_V90;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* V8 protocol handler */
|
||||||
|
int V8_process(V8State *s, s16 *output, s16 *input, int nb_samples)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
/* modulation part */
|
||||||
|
switch (s->state) {
|
||||||
|
case V8_CI_SEND:
|
||||||
|
case V8_CM_SEND:
|
||||||
|
case V8_JM_SEND:
|
||||||
|
case V8_CJ_SEND:
|
||||||
|
/* modulate with V21 */
|
||||||
|
FSK_mod(&s->v21_tx, output, nb_samples);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_CM_WAIT:
|
||||||
|
/* send ANSam modulation */
|
||||||
|
V8_mod(&s->v8_tx, output, nb_samples);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
/* output nothing */
|
||||||
|
memset(output, 0, nb_samples * sizeof(s16));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* demodulation part */
|
||||||
|
switch (s->state) {
|
||||||
|
case V8_CI:
|
||||||
|
case V8_CI_OFF:
|
||||||
|
case V8_CI_SEND:
|
||||||
|
/* detect ANSam */
|
||||||
|
V8_demod(&s->v8_rx, input, nb_samples);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_CM_WAIT:
|
||||||
|
case V8_CM_SEND:
|
||||||
|
case V8_JM_SEND:
|
||||||
|
/* V21 receive */
|
||||||
|
FSK_demod(&s->v21_rx, input, nb_samples);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* state machine */
|
||||||
|
|
||||||
|
if (lm_debug) {
|
||||||
|
if (s->state != s->debug_laststate) {
|
||||||
|
s->debug_laststate = s->state;
|
||||||
|
printf("%s: V8: state: %s\n",
|
||||||
|
s->calling ? "cal" : "ans", sm_states_str[s->state]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(s->state) {
|
||||||
|
|
||||||
|
case V8_WAIT_1SECOND:
|
||||||
|
{
|
||||||
|
/* wait 1 second before sending the first CI packet */
|
||||||
|
if (sm_check_timer(&s->v8_start_timer)) {
|
||||||
|
s->state = V8_CI;
|
||||||
|
s->v8_ci_count = 0;
|
||||||
|
V8_demod_init(&s->v8_rx); /* init ANSam detection */
|
||||||
|
V21_mod_init(&s->v21_tx, 1, get_bit, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* send the CI packets */
|
||||||
|
case V8_CI:
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* send 4 CI packets (at least 3 must be sent) */
|
||||||
|
for(i=0;i<4;i++) {
|
||||||
|
sm_put_bits(&s->tx_fifo, V8_TEN_ONES, 10);
|
||||||
|
sm_put_bits(&s->tx_fifo, V8_CI_SYNC, 10);
|
||||||
|
v8_put_byte(s, V8_CALL_FUNC_DATA);
|
||||||
|
}
|
||||||
|
s->state = V8_CI_SEND;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_CI_SEND:
|
||||||
|
{
|
||||||
|
if (sm_size(&s->tx_fifo) == 0) {
|
||||||
|
s->state = V8_CI_OFF;
|
||||||
|
sm_set_timer(&s->v8_ci_timer, 500); /* 0.5 s off */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_CI_OFF:
|
||||||
|
{
|
||||||
|
/* check if an ANSam tone is detected */
|
||||||
|
if (s->v8_rx.v8_ANSam_detected) {
|
||||||
|
sm_set_timer(&s->v8_ci_timer, V8_TE);
|
||||||
|
s->state = V8_GOT_ANSAM;
|
||||||
|
} else if (sm_check_timer(&s->v8_ci_timer)) {
|
||||||
|
if (++s->v8_ci_count == V8_MAX_CI_SEQ) {
|
||||||
|
ret = V8_MOD_HANGUP;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s->state = V8_CI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_GOT_ANSAM:
|
||||||
|
{
|
||||||
|
if (sm_check_timer(&s->v8_ci_timer)) {
|
||||||
|
v8_decode_init(s);
|
||||||
|
s->state = V8_CM_SEND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_CM_SEND:
|
||||||
|
{
|
||||||
|
if (s->got_cm) {
|
||||||
|
/* if JM detected, we send CJ & wait for 75 ms before exiting V8 */
|
||||||
|
|
||||||
|
s->selected_mod_mask = s->modulation_mask & s->decoded_modulations;
|
||||||
|
s->selected_modulation = select_modulation(s->selected_mod_mask);
|
||||||
|
|
||||||
|
/* flush tx queue */
|
||||||
|
sm_flush(&s->tx_fifo);
|
||||||
|
v8_put_byte(s, 0);
|
||||||
|
v8_put_byte(s, 0);
|
||||||
|
v8_put_byte(s, 0);
|
||||||
|
/* a few more bytes to fill the time */
|
||||||
|
v8_put_byte(s, 0);
|
||||||
|
v8_put_byte(s, 0);
|
||||||
|
v8_put_byte(s, 0);
|
||||||
|
v8_put_byte(s, 0);
|
||||||
|
v8_put_byte(s, 0);
|
||||||
|
v8_put_byte(s, 0);
|
||||||
|
s->state = V8_CJ_SEND;
|
||||||
|
} else if (sm_size(&s->tx_fifo) == 0) {
|
||||||
|
/* send CM */
|
||||||
|
cm_send(s, s->modulation_mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_CJ_SEND:
|
||||||
|
/* wait until CJ is sent */
|
||||||
|
if (sm_size(&s->tx_fifo) == 0) {
|
||||||
|
sm_set_timer(&s->v8_start_timer, 75);
|
||||||
|
s->state = V8_SIGC;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_SIGC:
|
||||||
|
if (sm_check_timer(&s->v8_start_timer)) {
|
||||||
|
/* it's OK, let's start the wanted modulation */
|
||||||
|
ret = s->selected_modulation;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
/* V8 answer */
|
||||||
|
|
||||||
|
case V8_WAIT:
|
||||||
|
{
|
||||||
|
if (sm_check_timer(&s->v8_connect_timer)) {
|
||||||
|
/* send the ANSam tone */
|
||||||
|
s->v8_tx.tone_level = -3; /* XXX: fix it */
|
||||||
|
V8_mod_init(&s->v8_tx);
|
||||||
|
|
||||||
|
/* prepare V21 to receive CI or CM */
|
||||||
|
v8_decode_init(s);
|
||||||
|
|
||||||
|
/* wait at most 5 seconds */
|
||||||
|
sm_set_timer(&s->v8_connect_timer, 5000);
|
||||||
|
s->state = V8_CM_WAIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_CM_WAIT:
|
||||||
|
{
|
||||||
|
if (sm_check_timer(&s->v8_connect_timer)) {
|
||||||
|
/* timeout */
|
||||||
|
ret = V8_MOD_HANGUP;
|
||||||
|
} else {
|
||||||
|
if (s->got_cm) {
|
||||||
|
/* stop sending ANSam & send JM */
|
||||||
|
V21_mod_init(&s->v21_tx, 0, get_bit, s);
|
||||||
|
/* timeout for JM */
|
||||||
|
sm_set_timer(&s->v8_connect_timer, 5000);
|
||||||
|
s->state = V8_JM_SEND;
|
||||||
|
s->selected_mod_mask = s->modulation_mask & s->decoded_modulations;
|
||||||
|
s->selected_modulation = select_modulation(s->selected_mod_mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_JM_SEND:
|
||||||
|
{
|
||||||
|
if (sm_check_timer(&s->v8_connect_timer)) {
|
||||||
|
/* timeout */
|
||||||
|
ret = V8_MOD_HANGUP;
|
||||||
|
} else if (s->got_cj) {
|
||||||
|
/* stop sending JM & wait 75 ms */
|
||||||
|
sm_set_timer(&s->v8_connect_timer, 75);
|
||||||
|
s->state = V8_SIGA;
|
||||||
|
} else if (sm_size(&s->tx_fifo) == 0) {
|
||||||
|
/* Send JM */
|
||||||
|
cm_send(s, s->selected_mod_mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V8_SIGA:
|
||||||
|
if (sm_check_timer(&s->v8_connect_timer)) {
|
||||||
|
ret = s->selected_modulation;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
|
||||||
|
#define V8_SAMPLE_RATE 8000
|
||||||
|
|
||||||
|
/* V8 tone ANSam tone synthesis */
|
||||||
|
typedef struct {
|
||||||
|
/* parameters */
|
||||||
|
int tone_level;
|
||||||
|
|
||||||
|
/* internal */
|
||||||
|
int sample_rate;
|
||||||
|
int phase, phase_incr;
|
||||||
|
int mod_phase, mod_phase_incr;
|
||||||
|
int phase_reverse_samples;
|
||||||
|
int phase_reverse_left;
|
||||||
|
int amp;
|
||||||
|
} V8_mod_state;
|
||||||
|
|
||||||
|
|
||||||
|
/* ANSam tone detection */
|
||||||
|
/* 25 ms window */
|
||||||
|
#define V8_N 200
|
||||||
|
#define DFT_COEF_2100 ((int)((2100.0 / V8_SAMPLE_RATE * V8_N) + 0.5))
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int buf_ptr;
|
||||||
|
s16 buf[V8_N];
|
||||||
|
s16 cos_tab[V8_N];
|
||||||
|
s16 sin_tab[V8_N];
|
||||||
|
int v8_ANSam_detected; /* true if ANSam detected */
|
||||||
|
} V8_demod_state;
|
||||||
|
|
||||||
|
/* Te period, as in the V8 spec 500 <= Te */
|
||||||
|
#define V8_TE 800
|
||||||
|
|
||||||
|
/* V8 main state */
|
||||||
|
typedef struct {
|
||||||
|
int calling; /* true if we are the calling modem */
|
||||||
|
int state; /* current state of the V8 protocol */
|
||||||
|
int debug_laststate;
|
||||||
|
struct sm_timer v8_start_timer;
|
||||||
|
struct sm_timer v8_ci_timer;
|
||||||
|
struct sm_timer v8_connect_timer;
|
||||||
|
int v8_ci_count;
|
||||||
|
FSK_mod_state v21_tx;
|
||||||
|
FSK_demod_state v21_rx;
|
||||||
|
struct sm_fifo rx_fifo;
|
||||||
|
struct sm_fifo tx_fifo;
|
||||||
|
u8 rx_buf[256];
|
||||||
|
u8 tx_buf[256];
|
||||||
|
V8_mod_state v8_tx;
|
||||||
|
V8_demod_state v8_rx;
|
||||||
|
|
||||||
|
/* available modulations */
|
||||||
|
int modulation_mask;
|
||||||
|
|
||||||
|
/* V8 data parsing */
|
||||||
|
unsigned int bit_buf;
|
||||||
|
unsigned int bit_sync;
|
||||||
|
int bit_cnt;
|
||||||
|
int data_state; /* indicates the type of synchro */
|
||||||
|
u8 rx_data[64];
|
||||||
|
int rx_data_ptr;
|
||||||
|
|
||||||
|
/* CM/JM parsing */
|
||||||
|
u8 cm_data[64];
|
||||||
|
int cm_count;
|
||||||
|
int got_cm;
|
||||||
|
int decoded_modulations; /* modulation mask decoded from CM/JM */
|
||||||
|
|
||||||
|
/* CJ parsing */
|
||||||
|
int got_cj;
|
||||||
|
int data_zero_count;
|
||||||
|
|
||||||
|
int selected_mod_mask; /* selected modulation mask */
|
||||||
|
int selected_modulation; /* see V8_MOD_xxx */
|
||||||
|
} V8State;
|
||||||
|
|
||||||
|
/* V8 protocol definitions */
|
||||||
|
#define V8_MAX_CI_SEQ 10 /* maximum number of CI packets sent */
|
||||||
|
|
||||||
|
#define V8_TEN_ONES 0x3ff
|
||||||
|
#define V8_CI_SYNC 0x001
|
||||||
|
#define V8_CM_SYNC 0x00f
|
||||||
|
|
||||||
|
#define V8_CALL_FUNC_DATA 0x83
|
||||||
|
|
||||||
|
#define V8_MODN0 0xA0
|
||||||
|
#define V8_EXT 0x08
|
||||||
|
|
||||||
|
#define V8_MODN0_V90 0x04 /* FIXME */
|
||||||
|
#define V8_MODN0_V34 0x02
|
||||||
|
#define V8_MODN2_V21 0x01
|
||||||
|
#define V8_MODN2_V23 0x20
|
||||||
|
|
||||||
|
#define V8_DATA_NOCELULAR 0xB0
|
||||||
|
#define V8_DATA_LAPM 0x54
|
||||||
|
|
||||||
|
#define V8_MOD_V90 (1 << 0) /* FIXME */
|
||||||
|
#define V8_MOD_V34 (1 << 1) /* V34 duplex */
|
||||||
|
#define V8_MOD_V23 (1 << 10) /* V23 duplex */
|
||||||
|
#define V8_MOD_V21 (1 << 12) /* V21 duplex */
|
||||||
|
|
||||||
|
#define V8_MOD_HANGUP 0x8000 /* indicate hangup */
|
||||||
|
|
||||||
|
void V8_init(V8State *sm, int calling, int mod_mask);
|
||||||
|
int V8_process(V8State *sm, s16 *output, s16 *input, int nb_samples);
|
|
@ -0,0 +1,798 @@
|
||||||
|
/*
|
||||||
|
* Implementation of the V90 modulation/demodulation
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999,2000 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* This implementation is totally clean room. It was written by
|
||||||
|
* reading the V90 specification and by using basic signal processing
|
||||||
|
* knowledge.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "lm.h"
|
||||||
|
#include "v90priv.h"
|
||||||
|
#include "v34priv.h"
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
|
/* Compute the average power of a given constellation :
|
||||||
|
nothing complicated here, except that each ucode must be counted
|
||||||
|
correctly. We took the algorithm of the spec. It could be very
|
||||||
|
optimized and simplified.
|
||||||
|
*/
|
||||||
|
int compute_power(V90EncodeState *s)
|
||||||
|
{
|
||||||
|
int m,v,i,j;
|
||||||
|
u64 p,n,r[6],k[6],a;
|
||||||
|
|
||||||
|
n = ((u64)1 << s->K) - 1;
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
r[i] = n;
|
||||||
|
k[i] = n % s->M[i];
|
||||||
|
n = n / s->M[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
p = 0;
|
||||||
|
a = 1;
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
m = s->M[i];
|
||||||
|
for(j=0;j<m;j++) {
|
||||||
|
if (j < k[i]) {
|
||||||
|
n = a * (r[i+1] + 1);
|
||||||
|
} else if (j == k[i]) {
|
||||||
|
n = ((u64)1 << s->K) - a * (r[i] - r[i+1]);
|
||||||
|
} else {
|
||||||
|
n = a * r[i+1];
|
||||||
|
}
|
||||||
|
v = s->ucode_to_linear[s->m_to_ucode[i][j]];
|
||||||
|
p += v * v * n;
|
||||||
|
}
|
||||||
|
a = a * m;
|
||||||
|
}
|
||||||
|
return p / ((u64) 6 << s->K);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int sign_op[4] = { 0, 0x55, 0xff, 0xaa };
|
||||||
|
|
||||||
|
/* Select the best method for the current shaping frame
|
||||||
|
We find the the shortest path in a treillis of depth ld
|
||||||
|
*/
|
||||||
|
static void select_best_signs(V90EncodeState *s, int sp_frame_size, int pp)
|
||||||
|
{
|
||||||
|
int ucode_ptr, depth, i;
|
||||||
|
int state, lstate, lstate_min;
|
||||||
|
int t_min, x_min, y_min, v_min, w_min, sg;
|
||||||
|
int x1, y1, v1, w1, x, y, v, w;
|
||||||
|
u8 mem_l[2][TREILLIS_MAX_DEPTH+2],
|
||||||
|
mem_t[2][TREILLIS_MAX_DEPTH+2];
|
||||||
|
s16 mem_x[2][TREILLIS_MAX_DEPTH+2],
|
||||||
|
mem_y[2][TREILLIS_MAX_DEPTH+2],
|
||||||
|
mem_v[2][TREILLIS_MAX_DEPTH+2],
|
||||||
|
mem_w[2][TREILLIS_MAX_DEPTH+2];
|
||||||
|
|
||||||
|
/* save new pp signs */
|
||||||
|
s->pp[s->ucode_ptr] = pp;
|
||||||
|
|
||||||
|
/* Find best path starting at state Q */
|
||||||
|
|
||||||
|
ucode_ptr = (s->ucode_ptr - (sp_frame_size * s->ld)) & (UCODE_BUF_SIZE-1);
|
||||||
|
|
||||||
|
/* fill the treillis memory */
|
||||||
|
mem_t[s->Q][0] = s->t;
|
||||||
|
mem_x[s->Q][0] = s->x;
|
||||||
|
mem_y[s->Q][0] = s->y;
|
||||||
|
mem_v[s->Q][0] = s->v;
|
||||||
|
mem_w[s->Q][0] = 0;
|
||||||
|
|
||||||
|
for(depth = 0; depth <= s->ld; depth++) {
|
||||||
|
|
||||||
|
for(state = 0; state < 2; state++) {
|
||||||
|
|
||||||
|
w_min = 0x7fffffff;
|
||||||
|
lstate_min = t_min = x_min = y_min = v_min = 0;
|
||||||
|
|
||||||
|
/* we select the current state at the beginning of the treillis */
|
||||||
|
if (depth == 0)
|
||||||
|
lstate = s->Q;
|
||||||
|
else
|
||||||
|
lstate = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
/* compute the signs */
|
||||||
|
sg = mem_t[lstate][depth] ^ s->pp[ucode_ptr];
|
||||||
|
/* apply the bit invertion method */
|
||||||
|
sg ^= sign_op[(state << 1) | lstate];
|
||||||
|
|
||||||
|
x1 = mem_x[lstate][depth];
|
||||||
|
y1 = mem_y[lstate][depth];
|
||||||
|
v1 = mem_v[lstate][depth];
|
||||||
|
w1 = mem_w[lstate][depth];
|
||||||
|
|
||||||
|
/* apply the spectral shaping filter to the frame */
|
||||||
|
for(i=0;i<sp_frame_size;i++) {
|
||||||
|
x = s->ucode_to_linear[s->ucode[(ucode_ptr + i) & (UCODE_BUF_SIZE-1)]];
|
||||||
|
if (((sg >> i) & 1) == 0)
|
||||||
|
x = -x;
|
||||||
|
|
||||||
|
y = x - ((s->b1 * x1 + s->a1 * y1) >> 6);
|
||||||
|
v = y - ((s->b2 * y1 + s->a2 * v1) >> 6);
|
||||||
|
w = ((v * v) >> 4) + w1;
|
||||||
|
|
||||||
|
x1 = x;
|
||||||
|
y1 = y;
|
||||||
|
v1 = v;
|
||||||
|
w1 = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* best transition ? */
|
||||||
|
if (w1 < w_min) {
|
||||||
|
t_min = sg;
|
||||||
|
x_min = x1;
|
||||||
|
y_min = y1;
|
||||||
|
v_min = v1;
|
||||||
|
w_min = w1;
|
||||||
|
lstate_min = lstate;
|
||||||
|
}
|
||||||
|
lstate++;
|
||||||
|
} while (lstate < 2 && depth != 0);
|
||||||
|
|
||||||
|
/* selects the best transition */
|
||||||
|
mem_t[state][depth+1] = t_min;
|
||||||
|
mem_x[state][depth+1] = x_min;
|
||||||
|
mem_y[state][depth+1] = y_min;
|
||||||
|
mem_v[state][depth+1] = v_min;
|
||||||
|
mem_w[state][depth+1] = w_min;
|
||||||
|
mem_l[state][depth+1] = lstate_min;
|
||||||
|
}
|
||||||
|
|
||||||
|
ucode_ptr = (ucode_ptr + sp_frame_size) & (UCODE_BUF_SIZE-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* now select the best next state by recursing thru the treillis */
|
||||||
|
if (mem_w[1][s->ld + 1] < mem_w[0][s->ld + 1])
|
||||||
|
state = 1;
|
||||||
|
else
|
||||||
|
state = 0;
|
||||||
|
|
||||||
|
for(depth=s->ld;depth>0;depth--)
|
||||||
|
state = mem_l[state][depth];
|
||||||
|
|
||||||
|
s->Q = state;
|
||||||
|
|
||||||
|
/* update current values */
|
||||||
|
s->t = mem_t[state][1];
|
||||||
|
s->x = mem_x[state][1];
|
||||||
|
s->y = mem_y[state][1];
|
||||||
|
s->v = mem_v[state][1];
|
||||||
|
|
||||||
|
s->ucode_ptr = (s->ucode_ptr + sp_frame_size) & (UCODE_BUF_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* converts (S+K) data bits to 6 a/u law values (note: we output
|
||||||
|
* linear values which may be converted back to u/a law). A delay of
|
||||||
|
* (ld * frame_size) is introduced due to the shaping treillis.
|
||||||
|
*/
|
||||||
|
static void v90_encode_mapping_frame(V90EncodeState *s, s16 *samples, u8 *data)
|
||||||
|
{
|
||||||
|
int k, l, i, j, frame_size, nb_frames, p, signs;
|
||||||
|
u64 v;
|
||||||
|
int pv[3];
|
||||||
|
|
||||||
|
/* modulo mapping to ucodes */
|
||||||
|
v = 0;
|
||||||
|
for(i=0;i<s->K;i++) v |= (data[i+s->S] << i);
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
k = v % s->M[i];
|
||||||
|
v /= s->M[i];
|
||||||
|
/* compute the corresponding ucode */
|
||||||
|
s->ucode[(s->ucode_ptr + i) & (UCODE_BUF_SIZE - 1)] = s->m_to_ucode[i][k];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* computation of the sign of the ucodes */
|
||||||
|
switch(s->S) {
|
||||||
|
case 6:
|
||||||
|
default:
|
||||||
|
/* no redundancy & spectral shaping */
|
||||||
|
l = s->last_sign;
|
||||||
|
signs = 0;
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
l = data[i] ^ l;
|
||||||
|
signs |= (l << i);
|
||||||
|
}
|
||||||
|
s->last_sign = l;
|
||||||
|
s->ucode_ptr = (s->ucode_ptr + 6) & (UCODE_BUF_SIZE - 1);
|
||||||
|
frame_size = 6;
|
||||||
|
goto skip_spectral_shaping;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
/* 1 bit of redundancy */
|
||||||
|
{
|
||||||
|
int pp1, pp3, pp5;
|
||||||
|
|
||||||
|
pp1 = data[0] ^ s->last_sign;
|
||||||
|
pp3 = data[2] ^ pp1;
|
||||||
|
pp5 = data[4] ^ pp3;
|
||||||
|
s->last_sign = pp5;
|
||||||
|
|
||||||
|
pv[0] = (pp1 << 1) | (data[1] << 2) | (pp3 << 3) |
|
||||||
|
(data[3] << 4) | (pp5 << 5);
|
||||||
|
|
||||||
|
frame_size = 6;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
/* 2 bits of redundancy */
|
||||||
|
{
|
||||||
|
int pp1, pp4;
|
||||||
|
|
||||||
|
pp1 = data[0] ^ s->last_sign;
|
||||||
|
pp4 = data[2] ^ pp1;
|
||||||
|
|
||||||
|
s->last_sign = pp4;
|
||||||
|
|
||||||
|
pv[0] = (pp1 << 1) | (data[1] << 2);
|
||||||
|
pv[1] = (pp4 << 1) | (data[3] << 2);
|
||||||
|
|
||||||
|
frame_size = 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
/* 3 bits of redundancy */
|
||||||
|
{
|
||||||
|
int pp1, pp3, pp5;
|
||||||
|
|
||||||
|
pp1 = data[0] ^ s->last_sign;
|
||||||
|
pp3 = data[1] ^ pp1;
|
||||||
|
pp5 = data[2] ^ pp3;
|
||||||
|
|
||||||
|
s->last_sign = pp5;
|
||||||
|
|
||||||
|
pv[0] = (pp1 << 1);
|
||||||
|
pv[1] = (pp3 << 1);
|
||||||
|
pv[2] = (pp5 << 1);
|
||||||
|
|
||||||
|
frame_size = 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* select the best signs for each frame (with a delay of ld) */
|
||||||
|
nb_frames = 6-s->S;
|
||||||
|
signs = 0;
|
||||||
|
for(i=0,j=0; i<nb_frames; i++, j+=frame_size) {
|
||||||
|
select_best_signs(s, frame_size, pv[i]);
|
||||||
|
l = s->t & ((1 << frame_size) - 1);
|
||||||
|
signs |= (l << j);
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_spectral_shaping:
|
||||||
|
|
||||||
|
/* now the signs are computed, we can compute the pcm values from
|
||||||
|
the ucodes. It may be faster to use the convertion already done
|
||||||
|
for spectral shaping */
|
||||||
|
p = (s->ucode_ptr - 6 - (frame_size * s->ld)) & (UCODE_BUF_SIZE - 1);
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
int x;
|
||||||
|
x = s->ucode_to_linear[s->ucode[p]];
|
||||||
|
if (((signs >> i) & 1) == 0)
|
||||||
|
x = -x;
|
||||||
|
|
||||||
|
samples[i] = x;
|
||||||
|
p = (p + 1) & (UCODE_BUF_SIZE - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* converts 6 a/u law samples to (S+K) data bits
|
||||||
|
*/
|
||||||
|
static void v90_decode_mapping_frame(V90DecodeState *s, u8 *data, s16 *samples)
|
||||||
|
{
|
||||||
|
s16 *tab;
|
||||||
|
int d1, d2, i, j, l, val, v, m_min, m_max, m;
|
||||||
|
u8 signs[6];
|
||||||
|
u64 bitbuf;
|
||||||
|
|
||||||
|
/* find signs & ucode */
|
||||||
|
bitbuf = 0;
|
||||||
|
for(j=5;j>=0;j--) {
|
||||||
|
val = samples[j];
|
||||||
|
if (val < 0) {
|
||||||
|
val = - val;
|
||||||
|
signs[j] = 0;
|
||||||
|
} else {
|
||||||
|
signs[j] = 1;
|
||||||
|
}
|
||||||
|
/* We look for the value closest to val with a binary search:
|
||||||
|
see Knuth section 6.2 exercice 1. */
|
||||||
|
tab = &s->m_to_linear[j][0];
|
||||||
|
m_min = 0;
|
||||||
|
m_max = s->M[j] - 1;
|
||||||
|
while (m_min <= m_max) {
|
||||||
|
m = (m_min + m_max) >> 1;
|
||||||
|
v = tab[m];
|
||||||
|
if (v == val)
|
||||||
|
goto found;
|
||||||
|
else if (val > v) {
|
||||||
|
m_max = m - 1;
|
||||||
|
} else {
|
||||||
|
m_min = m + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d1 = tab[m_max] - val;
|
||||||
|
d2 = val - tab[m_min];
|
||||||
|
if (d1 < d2)
|
||||||
|
m = m_max;
|
||||||
|
else
|
||||||
|
m = m_min;
|
||||||
|
found:
|
||||||
|
/* now we update the modulo value */
|
||||||
|
bitbuf = bitbuf * s->M[j] + m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* output the K bits */
|
||||||
|
for(i=0;i<s->K;i++)
|
||||||
|
data[i + s->S] = (bitbuf >> i) & 1;
|
||||||
|
|
||||||
|
switch(s->S) {
|
||||||
|
default:
|
||||||
|
case 6:
|
||||||
|
/* no redundant bit */
|
||||||
|
l = s->last_sign;
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
data[i] = l ^ signs[i];
|
||||||
|
l = signs[i];
|
||||||
|
}
|
||||||
|
s->last_sign = l;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
/* 1 redundant bit */
|
||||||
|
{
|
||||||
|
int pp1, pp3, pp5, t0, pv0, Q1;
|
||||||
|
|
||||||
|
t0 = 0;
|
||||||
|
for(i=0;i<6;i++)
|
||||||
|
t0 |= (signs[i] << i);
|
||||||
|
|
||||||
|
pv0 = s->t ^ t0;
|
||||||
|
Q1 = (pv0 & 1) ^ s->Q;
|
||||||
|
pv0 ^= sign_op[s->Q | (Q1 << 1)] & 0x3f;
|
||||||
|
s->t = t0;
|
||||||
|
s->Q = Q1;
|
||||||
|
|
||||||
|
/* extract the data bits */
|
||||||
|
data[1] = (pv0 >> 2) & 1;
|
||||||
|
data[3] = (pv0 >> 4) & 1;
|
||||||
|
pp1 = ((pv0 >> 1) & 1);
|
||||||
|
pp3 = ((pv0 >> 3) & 1);
|
||||||
|
pp5 = ((pv0 >> 5) & 1);
|
||||||
|
data[0] = pp1 ^ s->last_sign;
|
||||||
|
data[2] = pp1 ^ pp3;
|
||||||
|
data[4] = pp3 ^ pp5;
|
||||||
|
s->last_sign = pp5;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
/* 2 redundant bits */
|
||||||
|
{
|
||||||
|
int pp1, pp4, t0, t1, pv0, pv1, Q1, Q2;
|
||||||
|
|
||||||
|
t0 = signs[0] | (signs[1] << 1) | (signs[2] << 2);
|
||||||
|
t1 = signs[3] | (signs[4] << 1) | (signs[5] << 2);
|
||||||
|
|
||||||
|
pv0 = s->t ^ t0;
|
||||||
|
Q1 = (pv0 & 1) ^ s->Q;
|
||||||
|
pv0 ^= sign_op[s->Q | (Q1 << 1)] & 7;
|
||||||
|
|
||||||
|
pv1 = t0 ^ t1;
|
||||||
|
Q2 = (pv1 & 1) ^ Q1;
|
||||||
|
pv1 ^= sign_op[Q1 | (Q2 << 1)] & 7;
|
||||||
|
|
||||||
|
s->t = t1;
|
||||||
|
s->Q = Q2;
|
||||||
|
|
||||||
|
data[1] = (pv0 >> 2) & 1;
|
||||||
|
data[3] = (pv1 >> 2) & 1;
|
||||||
|
pp1 = (pv0 >> 1) & 1;
|
||||||
|
pp4 = (pv1 >> 1) & 1;
|
||||||
|
data[0] = pp1 ^ s->last_sign;
|
||||||
|
data[2] = pp4 ^ pp1;
|
||||||
|
s->last_sign = pp4;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
/* 3 redundant bits */
|
||||||
|
{
|
||||||
|
int pp1, pp3, pp5, t0, t1, t2, pv0, pv1, pv2, Q1, Q2, Q3;
|
||||||
|
|
||||||
|
t0 = signs[0] | (signs[1] << 1);
|
||||||
|
t1 = signs[2] | (signs[3] << 1);
|
||||||
|
t2 = signs[4] | (signs[5] << 1);
|
||||||
|
|
||||||
|
pv0 = s->t ^ t0;
|
||||||
|
Q1 = (pv0 & 1) ^ s->Q;
|
||||||
|
pv0 ^= sign_op[s->Q | (Q1 << 1)] & 3;
|
||||||
|
|
||||||
|
pv1 = t0 ^ t1;
|
||||||
|
Q2 = (pv1 & 1) ^ Q1;
|
||||||
|
pv1 ^= sign_op[Q1 | (Q2 << 1)] & 3;
|
||||||
|
|
||||||
|
pv2 = t1 ^ t2;
|
||||||
|
Q3 = (pv2 & 1) ^ Q2;
|
||||||
|
pv2 ^= sign_op[Q2 | (Q3 << 1)] & 3;
|
||||||
|
|
||||||
|
s->t = t2;
|
||||||
|
s->Q = Q3;
|
||||||
|
|
||||||
|
pp1 = (pv0 >> 1) & 1;
|
||||||
|
pp3 = (pv1 >> 1) & 1;
|
||||||
|
pp5 = (pv2 >> 1) & 1;
|
||||||
|
|
||||||
|
data[0] = pp1 ^ s->last_sign;
|
||||||
|
data[1] = pp3 ^ pp1;
|
||||||
|
data[2] = pp5 ^ pp3;
|
||||||
|
s->last_sign = pp5;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void compute_constellation(V90EncodeState *s,
|
||||||
|
u8 m_index[6], u8 ucode_used[6][128])
|
||||||
|
{
|
||||||
|
int i,j,k,m;
|
||||||
|
|
||||||
|
/* the ucode are taken from the bigger value down to the smaller value */
|
||||||
|
for(j=0;j<6;j++) {
|
||||||
|
k = m_index[j];
|
||||||
|
m = 0;
|
||||||
|
for(i=127;i>=0;i--) {
|
||||||
|
if (ucode_used[k][i]) {
|
||||||
|
s->m_to_ucode[j][m] = i;
|
||||||
|
m++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s->M[j] = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
for(j=0;j<6;j++) {
|
||||||
|
printf("M[%d]: ", j);
|
||||||
|
for(i=0;i<s->M[j];i++) {
|
||||||
|
printf("%3d ", s->m_to_ucode[j][i]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void v90_encode_init(V90EncodeState *s)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void v90_decode_init(V90DecodeState *s)
|
||||||
|
{
|
||||||
|
int i,j,m;
|
||||||
|
|
||||||
|
/* some test values (You can modify them to test the
|
||||||
|
modulator/demodulator) */
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
for(j=0;j<16;j++)
|
||||||
|
s->ucode_used[i][10 + j * 6] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->K = 4 * 6;
|
||||||
|
s->S = 5;
|
||||||
|
s->alaw = 1;
|
||||||
|
s->ld = 2;
|
||||||
|
s->a1 = (int) (0.1 * 64);
|
||||||
|
s->a2 = (int) (0.2 * 64);
|
||||||
|
s->b1 = (int) (-0.1 * 64);
|
||||||
|
s->b2 = (int) (0.1 * 64);
|
||||||
|
|
||||||
|
|
||||||
|
/* we suppose that all other values are set to zero */
|
||||||
|
|
||||||
|
|
||||||
|
/* compute the decode tables */
|
||||||
|
if (s->alaw)
|
||||||
|
s->ucode_to_linear = v90_alaw_ucode_to_linear;
|
||||||
|
else
|
||||||
|
s->ucode_to_linear = v90_ulaw_ucode_to_linear;
|
||||||
|
|
||||||
|
for(j=0;j<6;j++) {
|
||||||
|
m = 0;
|
||||||
|
for(i=127;i>=0;i--) {
|
||||||
|
if (s->ucode_used[j][i]) {
|
||||||
|
s->m_to_linear[j][m] = s->ucode_to_linear[i];
|
||||||
|
m++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s->M[j] = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 test_buf[1000];
|
||||||
|
|
||||||
|
/* send a CP frame (analog modem) */
|
||||||
|
static void v90_send_CP(V90DecodeState *s, int is_CP, int ack)
|
||||||
|
{
|
||||||
|
u8 *buf, *p;
|
||||||
|
int i, crc, drn;
|
||||||
|
|
||||||
|
buf = test_buf;
|
||||||
|
|
||||||
|
p = buf;
|
||||||
|
put_bits(&p, 17, 0x1ffff); /* frame sync */
|
||||||
|
|
||||||
|
put_bits(&p, 1, 0); /* start bit */
|
||||||
|
put_bits(&p, 1, 0); /* reserved */
|
||||||
|
put_bits(&p, 1, is_CP); /* 0=CPt 1=CP frame */
|
||||||
|
drn = s->K + s->S;
|
||||||
|
if (is_CP)
|
||||||
|
drn -= 20;
|
||||||
|
else
|
||||||
|
drn -= 8;
|
||||||
|
put_bits(&p, 5, drn); /* drn: speed */
|
||||||
|
put_bits(&p, 5, 0); /* reserved */
|
||||||
|
put_bits(&p, 1, 0); /* 1 if silence asked */
|
||||||
|
put_bits(&p, 2, 6 - s->S); /* Sr */
|
||||||
|
put_bits(&p, 1, ack); /* ack */
|
||||||
|
|
||||||
|
put_bits(&p, 1, 0); /* start bit */
|
||||||
|
put_bits(&p, 1, s->alaw); /* u/a law selection */
|
||||||
|
for(i=0;i<13;i++) {
|
||||||
|
put_bits(&p, 1, 1); /* speed (i+2) * 2400 supported (V34 part) */
|
||||||
|
}
|
||||||
|
put_bits(&p, 2, s->ld);
|
||||||
|
|
||||||
|
put_bits(&p, 1, 0); /* start bit */
|
||||||
|
put_bits(&p, 16, 0); /* RMS value for TRN1d */
|
||||||
|
|
||||||
|
put_bits(&p, 1, 0); /* start bit */
|
||||||
|
put_bits(&p, 8, s->a1 & 0xff); /* spectral shaping parameters */
|
||||||
|
put_bits(&p, 8, s->a2 & 0xff); /* spectral shaping parameters */
|
||||||
|
put_bits(&p, 1, 0); /* start bit */
|
||||||
|
put_bits(&p, 8, s->b1 & 0xff); /* spectral shaping parameters */
|
||||||
|
put_bits(&p, 8, s->b2 & 0xff); /* spectral shaping parameters */
|
||||||
|
|
||||||
|
put_bits(&p, 1, 0); /* start bit */
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
if (i == 4)
|
||||||
|
put_bits(&p, 1, 0); /* start bit */
|
||||||
|
put_bits(&p, 4, 0); /* modulation index */
|
||||||
|
}
|
||||||
|
|
||||||
|
put_bits(&p, 1, 0); /* different different tx - D/A constellations ? */
|
||||||
|
put_bits(&p, 7, 0); /* reserved */
|
||||||
|
|
||||||
|
/* transmit the constellation */
|
||||||
|
for(i=0;i<128;i++) {
|
||||||
|
if ((i & 15) == 0)
|
||||||
|
put_bits(&p, 1, 0); /* start bit */
|
||||||
|
put_bits(&p, 1, s->ucode_used[0][i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CRC */
|
||||||
|
put_bits(&p, 1, 0); /* start bit */
|
||||||
|
crc = calc_crc(buf + 17, p - (buf+17));
|
||||||
|
put_bits(&p, 16, crc);
|
||||||
|
|
||||||
|
put_bits(&p, 3, 0); /* fill */
|
||||||
|
printf("CP size= %d\n", p - buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_bit(u8 **pp)
|
||||||
|
{
|
||||||
|
u8 *p;
|
||||||
|
int v;
|
||||||
|
|
||||||
|
p = *pp;
|
||||||
|
v = *p++;
|
||||||
|
*pp = p;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int get_bits(u8 *p, int n)
|
||||||
|
{
|
||||||
|
int i, v;
|
||||||
|
|
||||||
|
v = 0;
|
||||||
|
for(i=n-1;i>=0;i--) {
|
||||||
|
v |= *p++ << i;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parse the CP packet & compute the V90 parameters */
|
||||||
|
static void v90_parse_CP(V90EncodeState *s, u8 *buf)
|
||||||
|
{
|
||||||
|
u8 m_index[6];
|
||||||
|
u8 ucode_used[7][128];
|
||||||
|
int drn, i, j, k, nb_constellations;
|
||||||
|
|
||||||
|
/* now the whole packet is can be read */
|
||||||
|
|
||||||
|
s->S = 6 - get_bits(buf + 31, 2);
|
||||||
|
s->alaw = get_bits(buf + 35, 1);
|
||||||
|
s->ld = get_bits(buf + 49, 2);
|
||||||
|
s->a1 = (s8) get_bits(buf + 69, 8);
|
||||||
|
s->a2 = (s8) get_bits(buf + 77, 8);
|
||||||
|
s->b1 = (s8) get_bits(buf + 86, 8);
|
||||||
|
s->b2 = (s8) get_bits(buf + 94, 8);
|
||||||
|
|
||||||
|
for(i=0;i<4;i++)
|
||||||
|
m_index[i] = get_bits(buf + 103 + i * 4, 4);
|
||||||
|
|
||||||
|
for(i=0;i<2;i++)
|
||||||
|
m_index[4 + i] = get_bits(buf + 120 + i * 4, 4);
|
||||||
|
nb_constellations = 0;
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
if (m_index[i] > nb_constellations) nb_constellations = m_index[i];
|
||||||
|
}
|
||||||
|
nb_constellations++;
|
||||||
|
if (buf[128])
|
||||||
|
nb_constellations++;
|
||||||
|
|
||||||
|
for(i=0;i<nb_constellations;i++) {
|
||||||
|
for(j=0;j<128;j++) {
|
||||||
|
k = i * 128 + j;
|
||||||
|
ucode_used[i][j] = buf[137 + (17 * (k >> 4)) + (k & 15)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compute K */
|
||||||
|
drn = get_bits(buf + 20, 5);
|
||||||
|
if (buf[19])
|
||||||
|
drn += 20;
|
||||||
|
else
|
||||||
|
drn += 8;
|
||||||
|
s->K = drn - s->S;
|
||||||
|
|
||||||
|
if (s->alaw)
|
||||||
|
s->ucode_to_linear = v90_alaw_ucode_to_linear;
|
||||||
|
else
|
||||||
|
s->ucode_to_linear = v90_ulaw_ucode_to_linear;
|
||||||
|
|
||||||
|
printf("V90_received_CP:\n");
|
||||||
|
compute_constellation(s, m_index, ucode_used);
|
||||||
|
|
||||||
|
printf("S=%d K=%d R=%d alaw=%d ld=%d a1=%d a2=%d b1=%d b2=%d\n",
|
||||||
|
s->S, s->K, ((s->S + s->K) * 8000) / 6,
|
||||||
|
s->alaw, s->ld, s->a1, s->a2, s->b1, s->b2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* received & parse the CP packet */
|
||||||
|
static void v90_receive_CP(V90EncodeState *s)
|
||||||
|
{
|
||||||
|
u8 buf[1024], *p, *q;
|
||||||
|
int b, i, frame_index, one_count, frame_count, nb_constellations;
|
||||||
|
int crc1;
|
||||||
|
u8 m_index[6];
|
||||||
|
|
||||||
|
p = test_buf;
|
||||||
|
|
||||||
|
wait_sync:
|
||||||
|
one_count = 0;
|
||||||
|
|
||||||
|
while (get_bit(&p)) {
|
||||||
|
one_count++;
|
||||||
|
}
|
||||||
|
if (one_count != 17)
|
||||||
|
goto wait_sync;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("got CP sync\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
frame_index = 0;
|
||||||
|
frame_count = 8;
|
||||||
|
crc1 = 1;
|
||||||
|
q = buf + 17;
|
||||||
|
while (frame_index < frame_count) {
|
||||||
|
printf("%2d: ", frame_index);
|
||||||
|
*q++ = 0;
|
||||||
|
for(i=0;i<16;i++) {
|
||||||
|
b = get_bit(&p);
|
||||||
|
*q++ = b;
|
||||||
|
printf("%d", b);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
if (frame_index == 6) {
|
||||||
|
/* compute the number of constellation to read */
|
||||||
|
for(i=0;i<4;i++) {
|
||||||
|
m_index[i] = get_bits(buf + 103 + i * 4, 4);
|
||||||
|
}
|
||||||
|
for(i=0;i<2;i++) {
|
||||||
|
m_index[4 + i] = get_bits(buf + 120 + i * 4, 4);
|
||||||
|
}
|
||||||
|
nb_constellations = 0;
|
||||||
|
for(i=0;i<6;i++) {
|
||||||
|
if (m_index[i] > nb_constellations) nb_constellations = m_index[i];
|
||||||
|
}
|
||||||
|
nb_constellations++;
|
||||||
|
if (buf[128])
|
||||||
|
nb_constellations++;
|
||||||
|
frame_count += 8 * nb_constellations;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame_index == (frame_count - 1)) {
|
||||||
|
/* check the crc (it must be zero because we include the crc itself) */
|
||||||
|
crc1 = calc_crc(buf + 17, q - (buf+17));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_bit(&p) != 0) {
|
||||||
|
printf("start bit expected\n");
|
||||||
|
goto wait_sync;
|
||||||
|
}
|
||||||
|
frame_index++;
|
||||||
|
}
|
||||||
|
if (crc1 != 0)
|
||||||
|
goto wait_sync;
|
||||||
|
|
||||||
|
v90_parse_CP(s, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* simple test of V90 algebraic computations */
|
||||||
|
/* Note: if ld != 0 and 3 <= S <= 4, the delay introduced with data[][]
|
||||||
|
is not correct */
|
||||||
|
|
||||||
|
void V90_test(void)
|
||||||
|
{
|
||||||
|
int i,j,n,l;
|
||||||
|
V90EncodeState v90_enc;
|
||||||
|
V90DecodeState v90_dec;
|
||||||
|
u8 data[5][48], data1[48];
|
||||||
|
s16 samples[6];
|
||||||
|
|
||||||
|
/* init modem state */
|
||||||
|
memset(&v90_enc, 0, sizeof(v90_enc));
|
||||||
|
v90_encode_init(&v90_enc);
|
||||||
|
|
||||||
|
memset(&v90_dec, 0, sizeof(v90_dec));
|
||||||
|
v90_decode_init(&v90_dec);
|
||||||
|
|
||||||
|
/* send the CP sequence which contains the modulation parameters */
|
||||||
|
v90_send_CP(&v90_dec, 1, 0);
|
||||||
|
|
||||||
|
/* "receive" it ! */
|
||||||
|
v90_receive_CP(&v90_enc);
|
||||||
|
|
||||||
|
/* number of data bits per mapping frame */
|
||||||
|
n = v90_enc.S + v90_enc.K;
|
||||||
|
|
||||||
|
/* transmit & receive 1000 mapping frames */
|
||||||
|
memset(data, 0, sizeof(data));
|
||||||
|
l = 0;
|
||||||
|
for(i=0;i<1000;i++) {
|
||||||
|
for(j=0;j<n;j++) data[l][j] = random() & 1;
|
||||||
|
|
||||||
|
v90_encode_mapping_frame(&v90_enc, samples, data[l]);
|
||||||
|
|
||||||
|
// for(j=0;j<6;j++) samples[j] += (random() % 32) - 16;
|
||||||
|
|
||||||
|
printf("%4d: ", i);
|
||||||
|
for(j=0;j<6;j++) printf("%6d,", samples[j]);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
v90_decode_mapping_frame(&v90_dec, data1, samples);
|
||||||
|
|
||||||
|
l = (l + 1) % (v90_dec.ld+1);
|
||||||
|
|
||||||
|
for(j=0;j<n;j++) {
|
||||||
|
if (data[l][j] != data1[j]) {
|
||||||
|
printf("error mapping frame=%d bit=%d\n", i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#ifndef V90_H
|
||||||
|
#define V90_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int dummy;
|
||||||
|
} V90_params;
|
||||||
|
|
||||||
|
void V90_test(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* V90 table generator
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999 Fabrice Bellard.
|
||||||
|
*
|
||||||
|
* This code is released under the GNU General Public License version
|
||||||
|
* 2. Please read the file COPYING to know the exact terms of the
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* This implementation is totally clean room. It was written by
|
||||||
|
* reading the V90 specification and by using basic signal processing
|
||||||
|
* knowledge.
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/* from g711.c by SUN microsystems (unrestricted use) */
|
||||||
|
|
||||||
|
#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */
|
||||||
|
#define QUANT_MASK (0xf) /* Quantization field mask. */
|
||||||
|
#define NSEGS (8) /* Number of A-law segments. */
|
||||||
|
#define SEG_SHIFT (4) /* Left shift for segment number. */
|
||||||
|
#define SEG_MASK (0x70) /* Segment field mask. */
|
||||||
|
|
||||||
|
#define BIAS (0x84) /* Bias for linear code. */
|
||||||
|
/*
|
||||||
|
* alaw2linear() - Convert an A-law value to 16-bit linear PCM
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
alaw2linear(a_val)
|
||||||
|
unsigned char a_val;
|
||||||
|
{
|
||||||
|
int t;
|
||||||
|
int seg;
|
||||||
|
|
||||||
|
a_val ^= 0x55;
|
||||||
|
|
||||||
|
t = (a_val & QUANT_MASK) << 4;
|
||||||
|
seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT;
|
||||||
|
switch (seg) {
|
||||||
|
case 0:
|
||||||
|
t += 8;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
t += 0x108;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
t += 0x108;
|
||||||
|
t <<= seg - 1;
|
||||||
|
}
|
||||||
|
return ((a_val & SIGN_BIT) ? t : -t);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ulaw2linear(u_val)
|
||||||
|
unsigned char u_val;
|
||||||
|
{
|
||||||
|
int t;
|
||||||
|
|
||||||
|
/* Complement to obtain normal u-law value. */
|
||||||
|
u_val = ~u_val;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract and bias the quantization bits. Then
|
||||||
|
* shift up by the segment number and subtract out the bias.
|
||||||
|
*/
|
||||||
|
t = ((u_val & QUANT_MASK) << 3) + BIAS;
|
||||||
|
t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
|
||||||
|
|
||||||
|
return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
printf("/* THIS SOURCE CODE IS AUTOMATICALLY GENERATED - DO NOT MODIFY */\n");
|
||||||
|
|
||||||
|
printf("/*\n"
|
||||||
|
" * V90 tables\n"
|
||||||
|
" * \n"
|
||||||
|
" * Copyright (c) 1999 Fabrice Bellard.\n"
|
||||||
|
" *\n"
|
||||||
|
" * This code is released under the GNU General Public License version\n"
|
||||||
|
" * 2. Please read the file COPYING to know the exact terms of the\n"
|
||||||
|
" * license.\n"
|
||||||
|
" */\n");
|
||||||
|
|
||||||
|
printf("#include \"lm.h\"\n"
|
||||||
|
"#include \"v90priv.h\"\n"
|
||||||
|
"\n");
|
||||||
|
|
||||||
|
printf("const s16 v90_ulaw_ucode_to_linear[128]= {\n");
|
||||||
|
for(i=0;i<128;i++) {
|
||||||
|
printf("%5d,", ulaw2linear(i ^ 0xff));
|
||||||
|
if ((i & 7) == 7) printf("\n");
|
||||||
|
}
|
||||||
|
printf("};\n");
|
||||||
|
|
||||||
|
printf("const s16 v90_alaw_ucode_to_linear[128]= {\n");
|
||||||
|
for(i=0;i<128;i++) {
|
||||||
|
printf("%5d,", alaw2linear(i ^ 0xd5));
|
||||||
|
if ((i & 7) == 7) printf("\n");
|
||||||
|
}
|
||||||
|
printf("};\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
#ifndef V90PRIV_H
|
||||||
|
#define V90PRIV_H
|
||||||
|
|
||||||
|
#define V90_SAMPLE_RATE 8000
|
||||||
|
#define TREILLIS_MAX_DEPTH 4
|
||||||
|
/* ring buffer size of the previous ucodes : must be a power of two */
|
||||||
|
#define UCODE_BUF_SIZE (TREILLIS_MAX_DEPTH * 8)
|
||||||
|
|
||||||
|
/* state of the signal processing part of the V90 modem */
|
||||||
|
typedef struct V90EncodeState {
|
||||||
|
struct sm_state *sm;
|
||||||
|
|
||||||
|
int alaw; /* true=alaw, false=ulaw */
|
||||||
|
|
||||||
|
/* frame parameters (a frame = 6 PAM values) */
|
||||||
|
int S; /* sign bits (3 <= S <= 6) */
|
||||||
|
int K; /* number of bits for the ring coder */
|
||||||
|
int a1, a2, b1, b2; /* spectral conformer parameters */
|
||||||
|
|
||||||
|
int M[6]; /* ring size */
|
||||||
|
u8 m_to_ucode[6][128]; /* encoding table */
|
||||||
|
int ld; /* depth for spectral shaping (0 <= ld <= 3) */
|
||||||
|
|
||||||
|
/* sign mapper */
|
||||||
|
int ucode_ptr; /* index of the first pam value of the
|
||||||
|
current mapping frame */
|
||||||
|
u8 ucode[UCODE_BUF_SIZE]; /* current output of the modulator */
|
||||||
|
u8 pp[UCODE_BUF_SIZE]; /* corresponding signs (grouped by frame) */
|
||||||
|
int last_sign; /* sign of the last computed frame */
|
||||||
|
|
||||||
|
/* spectral shaping treillis */
|
||||||
|
u8 t; /* signs of the frame */
|
||||||
|
s16 x; /* last x value: not stricly needed, but simply the treillis computation */
|
||||||
|
s16 y; /* memory for spectral shaping filter */
|
||||||
|
s16 v; /* idem */
|
||||||
|
s16 w; /* idem */
|
||||||
|
int Q; /* current shaping treillis state
|
||||||
|
(with a delay of ld) */
|
||||||
|
const s16 *ucode_to_linear; /* table to retrieve the linear values from ucodes */
|
||||||
|
} V90EncodeState;
|
||||||
|
|
||||||
|
const s16 v90_ulaw_ucode_to_linear[128];
|
||||||
|
const s16 v90_alaw_ucode_to_linear[128];
|
||||||
|
|
||||||
|
typedef struct V90DecodeState {
|
||||||
|
struct sm_state *sm;
|
||||||
|
|
||||||
|
int alaw; /* true=alaw, false=ulaw */
|
||||||
|
|
||||||
|
/* frame parameters (a frame = 6 PAM values) */
|
||||||
|
int S; /* sign bits (3 <= S <= 6) */
|
||||||
|
int K; /* number of bits for the ring coder */
|
||||||
|
|
||||||
|
int M[6]; /* ring size */
|
||||||
|
u8 ucode_used[6][128];
|
||||||
|
s16 m_to_linear[6][128]; /* give the estimated linear value of each received modulo */
|
||||||
|
int ld;
|
||||||
|
s8 a1, a2, b1, b2;
|
||||||
|
int last_sign;
|
||||||
|
int t;
|
||||||
|
int Q;
|
||||||
|
const s16 *ucode_to_linear; /* table to retrieve the linear values from ucodes */
|
||||||
|
} V90DecodeState;
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue